Java: Compilerbau mit ANTLR 3 und StringTemplate (1)
ANTLR ist ein Parsergenerator von Terence Parr mit dem sich auf unterschiedliche Weise Compiler erstellen lassen. Eine davon ist die Nutzung von Templates - wie beim Model View Controller werden dabei Modell und Ausgabe voneinander getrennt, so dass nur durch das Hinzufügen eines neuen Templates ein generierter Compiler Programmcode für eine weitere Zielsprache erzeugen kann.
Als einfaches Beispiel wird aus HelloWorld Java-, C- und PHP-Quelltext generiert. Die HelloWorldsprache unterstützt dabei nur den Befehl
helloworld;
Grammatik
Bei ANTLR kann der Lexer und die Grammatik in einer Datei definiert werden. In diesem Fall in HelloWorld.g
die Grammatik für eine Sprache, die nur den einen Befehl helloworld;
kennt plus die Lexerregel für das überlesen von Whitespace.
Mit options
wird festgelegt was ANTLR generieren soll. In diesem Fall einen Parser, der StringTemplate
zur Ausgabe nutzt.
program()
und helloWorld()
sind die extern definierten Templates der einzelnen Zielsprachen. EOF
ist in ANTLR eingebaut und steht für das Dateiende. Die Whitespaces werden überlesen, in dem sie in den Kanal 'hidden' geleitet werden.
grammar HelloWorld; options {output=template;} program : statement EOF -> program(statement={$statement.st}) ; statement : helloworld ';' -> {$helloworld.st;} ; helloworld : 'helloworld' -> helloWorld() ; WS : (' ' | '\t' | '\r' | '\n')+ {$channel=HIDDEN;} ;
Templates
Die Templates für die Programmiersprachen stehen in einzelnen Dateien mit der Endung '.stg'.
Java.stg
group Java; program(statement) ::= << public class HelloWorld { public static void main(String[] args) { <statement> } } >> helloWorld() ::= << System.out.println("Hello World!"); >>
C.stg
group C; program(statement) ::= << #include \<stdio.h\> int main(int argc, char *argv[]) { <statement> return 0; } >> helloWorld() ::= << printf("Hello World!\\n"); >>
PHP.stg
group PHP; program(statement) ::= << <statement> >> helloWorld() ::= << echo "Hello World!\\n"; >>
Main
Fehlt noch das Hauptprogramm zum Laden von Template und Eingabe sowie der Initialisierung und Ausführung von Lexer und Parser.
import java.io.*; import org.antlr.runtime.*; import org.antlr.stringtemplate.*; import org.antlr.stringtemplate.language.*; public class Main { public static void main(String[] args) { StringTemplateGroup templates; String templateFilename = ""; String programFilename = ""; if(args.length < 2) { System.out.println("Main [template] [program]"); System.exit(0); } else { templateFilename = args[0]; programFilename = args[1]; } try { templates = new StringTemplateGroup(new FileReader(templateFilename), AngleBracketTemplateLexer.class); CharStream input = new ANTLRFileStream(programFilename); HelloWorldLexer lexer = new HelloWorldLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); HelloWorldParser parser = new HelloWorldParser(tokens); parser.setTemplateLib(templates); RuleReturnScope r = parser.program(); System.out.println(r.getTemplate().toString()); } catch (Exception e) { System.out.println(e); } } }
Compilieren
Zum Compilieren ANTLR-Runtime.jar und StringTemplate.jar in den $CLASSPATH einhängen. Zum Beispiel über Complete ANTLR 3.1.3 jar.
export CLASSPATH=$CLASSPATH:antlr-3.1.3.jar
Und die Grammatik sowie die generierten Dateien compilieren.
java org.antlr.Tool HelloWorld.g javac Main.java HelloWorldLexer.java HelloWorldParser.java
Ausführen
Zum Ausführen des erstellten Compilers Main
mit dem gewünschten Template plus Input starten.
> java Main Java.stg input public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } > java Main C.stg input #include <stdio.h> int main(int argc, char *argv[]) { printf("Hello World!\n"); return 0; }