Search code examples
javaantlr4stringtemplate-4

Accessing string template rule names from ANTLR base listener


Working on a pretty printer. Based on my understanding of ANTLR and StringTemplate so far, if I want to match all my grammar rules to templates and apply the template each time the grammar rule is invoked, I can create my templates with names matching my grammar rules.

[Side question: Is this how I should approach it? It seems like ANTLR should being doing the work of matching the parsed text to the output templates. My job will be to make sure the parser rules and templates are complete/correct.]

I think ANTLR 3 allowed directly setting templates inside of the ANTLR grammar, but ANTLR 4 seems to have moved away from that.

Based on the above assumptions, it looks like the MyGrammarBaseListener class that ANTLR generates is going to be doing all the work.

I've been able to collect the names of the rules invoked while parsing the text input by converting this example to ANTLR 4. I ended up with this for my enterEveryRule():

@Override public void enterEveryRule(ParserRuleContext ctx) {
    if (builder.length() > 0) {
        builder.append(' ');
    }

    if (ctx.getChildCount() > 0) {
        builder.append('(');
    }

    int ruleIndex = ctx.getRuleIndex();
    String ruleName;
    if (ruleIndex >= 0 && ruleIndex < ruleNames.length) {
        ruleName = ruleNames[ruleIndex];
        System.out.println(ruleName); // this part works as intended
    }
    else {
        ruleName = Integer.toString(ruleIndex);
    }

    builder.append(ruleName);

    // CONFUSION HERE:
    // get template names (looking through the API to figure out how to do this)
    Set<String> templates = (MyTemplates.stg).getTemplateNames()
    // or String[] for return value? Java stuff

    // for each ruleName in ruleNames
    //     if (ruleName == templateName)
    //         run template using rule children as parameters
    //         write pretty-printed version to file
}

The linked example applies the changes to create the text output in exitEveryRule() so I'm not sure where to actually implement my template-matching algorithm. I'll experiment with both enter and exit to see what works best.

My main question is: How do I access the template names in MyTemplates.stg? What do I have to import, etc.?

(I'll probably be back to ask about matching up rule children to template parameters in a different question...)


Solution

  • Following demonstrates a simple way of dynamically accessing and rendering named StringTemplates. Intent is to build varMap values in the listener (or visitor) in its corresponding context, keyed by parameter name, and call the context dependent named template to incrementally render the content of the template.

    public class Render {
    
        private static final String templateDir = "some/path/to/templates";
        private STGroupFile blocksGroup;
        private STGroupFile stmtGroup;
    
        public Render() {
            blocksGroup = new STGroupFile(Strings.concatAsClassPath(templateDir, "Blocks.stg"));
            stmtGroup = new STGroupFile(Strings.concatAsClassPath(templateDir, "Statements.stg"));
        }
    
        public String gen(GenType type, String name) {
            return gen(type, name, null);
        }
    
        /**
         * type is an enum, identifying the group template
         * name is the template name within the group
         * varMap contains the named values to be passed to the template
         */
        public String gen(GenType type, String name, Map<String, Object> varMap) {
            Log.debug(this, name);
            STGroupFile stf = null;
            switch (type) {
                case BLOCK:
                    stf = blocksGroup;
                    break;
                case STMT:
                    stf = stmtGroup;
                    break;
            }
            ST st = stf.getInstanceOf(name);
            if (varMap != null) {
                for (String varName : varMap.keySet()) {
                    try {
                        st.add(varName, varMap.get(varName));
                    } catch (NullPointerException e) {
                        Log.error(this, "Error adding attribute: " + name + ":" + varName + " [" + e.getMessage() + "]");
                    }
                }
            }
            return st.render();
        }
    }