antlr / stringtemplate4

StringTemplate 4
http://www.stringtemplate.org
Other
953 stars 231 forks source link

Templates in maps loaded via STGroupFile do not use registered ObjectModelAdaptors #223

Open Aethon opened 5 years ago

Aethon commented 5 years ago

Given ST4 4.1 on the JVM and the following code, I would expect that the View would be dumped out identically both times. However, the following is produced:

before template = View(regularName=HIP! HIP!,special_name=HUZZAH!) and after map template = View(regularName=HIP! HIP!,special_name=)

It seems that the templates in the map create a new group based on the file they appear in but this new group does not inherit the model adaptor registrations from the original group. Much confusion ensues.

import org.stringtemplate.v4.*
import org.stringtemplate.v4.misc.*
import java.nio.charset.StandardCharsets

class Adaptor : ObjectModelAdaptor() {
    override fun getProperty(interp: Interpreter, self: ST, o: Any, property: Any?, propertyName: String): Any? =
        if (propertyName == "special_name")
            "HUZZAH!"
        else
            super.getProperty(interp, self, o, property, propertyName)
}

class View(val regularName: String)

fun main(args: Array<String>) {
    val group = STGroupFile(View::class.java.getResource("/strepl/main.stg"), StandardCharsets.UTF_8.name(), '$', '$')
    group.registerModelAdaptor(Object::class.java, Adaptor())
    val st = group.getInstanceOf("main")
    st.add("view", View("HIP! HIP!"))
    println(st.render())
}

/strepl/main:

import "other.stg"

main(view) ::= "$other_main(view)$"

/strepl/other:

delimiters "$", "$"

other_main(view) ::= "before template = $dump(view)$ and after $map.(\"simple\")$"
map ::= [
    "simple": <%map template = $dump(view)$%>
]
dump(view) ::= "View(regularName=$view.regularName$,special_name=$view.special_name$)"
Aethon commented 5 years ago

For anyone that finds this, the workaround I am using is to just store template names in the map and use double indirection to invoke them:

... $(map.(\"simple\"))()$ ...

and

map ::= [
    "simple": "simple_template"
]

simple_template() ::= "map template = $dump(view)$"

It is slightly more verbose, but not so terrible as workarounds go :)

parrt commented 5 years ago

hi. is this Kotlin? Might matter. E.g., what if an object gets wrapped and no longer a java object? Maybe the registration no longer works?

Aethon commented 5 years ago

It is Kotlin. The registrations work, just not after "passing through" a mapped template in a loaded group file.

I will provide a Java repro after the weekend.

Thanks.

parrt commented 5 years ago

ok, yeah, Maybe there's something about parameter handoff or dynamic scoping that is screwing something up here.

Aethon commented 5 years ago

Here is the Java 11 repro (using the same template files above):

package test;

import org.stringtemplate.v4.Interpreter;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.misc.ObjectModelAdaptor;

import java.nio.charset.StandardCharsets;

public class ST4BugReplJava {
    static class Adaptor extends ObjectModelAdaptor {
        @Override
        public Object getProperty(Interpreter interp, ST self, Object o, Object property, String propertyName) {
            if (propertyName.equals("special_name"))
                return "HUZZAH!";
            return super.getProperty(interp, self, o, property, propertyName);
        }
    }

    static class View {
        public String regularName;

        public View(String regularName) {
            this.regularName = regularName;
        }
    }

    static public void main(String[] args) {
        var group = new STGroupFile(View.class.getResource("/strepl/main.stg"), StandardCharsets.UTF_8.name(), '$', '$');
        group.registerModelAdaptor(Object.class, new Adaptor());
        var st = group.getInstanceOf("main");
        st.add("view", new View("HIP! HIP!"));
        System.out.println(st.render());
    }
}