Search code examples
antlrgrammarstringtemplatecode-translation

String Template: make all variable declaration global


I am trying to implement a translator using ANTLR+StringTemplate. I have a starting language that is java like and multiple destination language.

I used the example: http://www.antlr.org/wiki/display/ST/Language+Translation+Using+ANTLR+and+StringTemplate

One of my destination language needs all variables to be declared globally. I wrote a grammar that recognizes variables, but i cannot find e way in my template for making a local variable to be declared globally.

Of course if I would have just one translation I would be able to do it, but I have multiple translation and some of them have local and global variables. I'd like to make it in the specific template file.

For example it would be great if I could define some sort of variables inside the template for keeping a list of all variable declarations and use it at the end when i define the global scope... but i don't know if this is possibile.


Solution

  • The parser will have to track the variables before passing them to a template. This doesn't mean that you need one parser for a global-based target and another for the other targets, it just means that you need to define some empty templates in the targets.

    Here is a very simple example of how this can be done. I don't propose that your case is this ideal, but I hope it gives you enough to work with.

    Assume that your source grammar, the Java-like one, accepts code like this:

    class Foobar { 
        var a;
        var b;
        var myMethod(var x, var y) { 
           var c;
           var d;
        }
    }
    

    Class Foobar contains member fields a and b, and member method myMethod contains locals c and d. For argument's sake, assume that you want a, b, c, and d to be treated as global variables for a global target, and like normal variables otherwise.

    Here is a grammar that accepts the input defined above, prepped for template output:

    grammar JavaLikeToTemplate;
    
    options { 
        output = template;
    }
    
    @members { 
        private java.util.ArrayList<String> globals = new java.util.ArrayList<String>();
    
    }
    
    compilationUnit : class_def EOF 
                        -> compilationUnit(classDef={$class_def.st}, globals={globals});
    class_def       : CLASS ID LCUR class_body RCUR
                        -> class(name={$ID.text}, body={$class_body.st});
    class_body      : (t+=class_element)+
                        -> append(parts={$t});
    class_element   : class_field
                        -> {$class_field.st}
                    | class_method
                        -> {$class_method.st};
    class_field     : VAR ID SEMI {globals.add($ID.text);}
                        -> classField(name={$ID.text});
    class_method    : VAR ID LPAR paramlist? RPAR LCUR method_body RCUR
                        -> classMethod(name={$ID.text}, params={$paramlist.st}, body={$method_body.st});
    method_body     : (t+=method_element)+
                        -> append(parts={$t});
    method_element  : method_field
                        -> {$method_field.st};
    method_field    : VAR ID SEMI {globals.add($ID.text);}
                        -> methodField(name={$ID.text});
    paramlist       : VAR t+=ID (COMMA VAR t+=ID)*
                        -> paramList(params={$t});
    
    CLASS   : 'class';
    VAR     : 'var';
    ID      : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;
    INT     : ('0'..'9')+;
    COMMA   : ',';
    SEMI    : ';';
    LCUR    : '{';
    RCUR    : '}';
    LPAR    : '(';
    RPAR    : ')';
    EQ      : '=';
    WS      : (' '|'\t'|'\f'|'\r'|'\n'){skip();};
    

    Note that parser member globals tracks the names of variables that a globals-only target is concerned about, but that templates pertaining to fields/variables are still called. This ensures that the grammar is target-neutral.

    Here is a template that produces Java code. Note that compilationUnit ignores input globals because Java doesn't use them.

    group JavaLikeToJava;
    
    compilationUnit(globals, classDef) ::=
    <<
    <classDef>
    >>
    
    class(name, body) ::= 
    <<
    public class <name> { 
        <body>
    }
    >>
    
    classField(name) ::=
    <<
    private Object <name>;
    >>
    
    classMethod(name, params, body) ::=
    <<
    public Object <name>(<params>) {
        <body> 
    }
    >>
    
    methodField(name) ::=
    <<
        Object <name>;
    >>
    
    paramList(params) ::=
    <<
        <params:{p|Object <p.text>}; separator=", ">
    >>
    
    append(parts) ::=
    <<
     <parts;separator="\n">
    >>
    

    Here is a template for a globals target. Note that many of the class templates are empty, but that compilationUnit processes input globals.

    group JavaLikeToGlobal;
    
    globals(names) ::=
    <<
        <names:global()>
    >>
    
    global(name) ::=
    <<
    global <name>
    >>
    
    compilationUnit(globals, classDef) ::=
    <<
    <globals:globals();separator="\n">
    <classDef>
    >>
    
    class(name, body) ::= 
    <<
    <body>
    >>
    
    classField(name) ::=
    <<>>
    
    classMethod(name, params, body) ::=
    <<
    <name>(<params>):
        <body>
    end
    >>
    
    methodField(name) ::=
    <<
    >>
    
    paramList(params) ::=
    <<
        <params:{p| <p.text>}; separator=", ">
    >>
    
    append(parts) ::=
    <<
     <parts;separator="\n">
    >>
    

    Here is the launcher class I'll use to test the grammar and templates.

    public class JavaLikeToTemplateTest {
    
        public static void main(String[] args) throws Exception {
    
            final String code = "class Foobar {\n var Foobar_a;\n var Foobar_b;\n var doSomething() {\n  var doSomething_a;\n  var doSomething_b;\n }\n}"; 
    
            process(code, "JavaLikeToJava.stg");
            process(code, "JavaLikeToGlobal.stg");
    
        }
    
        private static void process(final String code, String templateResourceName)
                throws IOException, RecognitionException, Exception {
            CharStream input = new ANTLRStringStream(code);
            JavaLikeToTemplateLexer lexer = new JavaLikeToTemplateLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
    
            JavaLikeToTemplateParser parser = new JavaLikeToTemplateParser(tokens);
    
            InputStream stream = JavaLikeToTemplateTest.class.getResourceAsStream(templateResourceName);
            Reader reader = new InputStreamReader(stream);
            parser.setTemplateLib(new StringTemplateGroup(reader));
            reader.close();
            stream.close();
    
            JavaLikeToTemplateParser.compilationUnit_return result = parser.compilationUnit();
    
            if (parser.getNumberOfSyntaxErrors() > 0){
                throw new Exception("Syntax Errors encountered!");
            }
    
            System.out.printf("Result with %s:%n%n", templateResourceName);
            System.out.println(result.toString());
        }
    }
    

    Here is the input hard-coded in the test class:

    class Foobar {
     var Foobar_a;
     var Foobar_b;
     var doSomething() {
      var doSomething_a;
      var doSomething_b;
     }
    }
    

    And here is the output produced by the code, using both templates:

    Result with JavaLikeToJava.stg:
    
    public class Foobar { 
         private Object Foobar_a;
         private Object Foobar_b;
         public Object doSomething() {
                Object doSomething_a;
                Object doSomething_b; 
         }
    }
    
    Result with JavaLikeToGlobal.stg:
    
        global Foobar_a
        global Foobar_b
        global doSomething_a
        global doSomething_b
    
    
     doSomething():
    
    
     end
    

    The key is to track globals in the parser regardless of the target language and pass them along with non-global information to the language's template regardless. The target language's template file either processes the globals or it ignores them. A template receive enough information to define both types of languages (whether it uses it all or not), so there's no need to create a new parser.