Java: Compilerbau mit ANTLR 3 und StringTemplate (2)
Im ersten Schritt konnte das Programm nur aus einen Befehl bestehen. Im zweiten soll dieser nun beliebig oft im Programm stehen dürfen.
helloworld; helloworld; helloworld;
HelloWorldStatementList.g
Hierzu werden die Befehle in einer Statementliste gesammelt und dann an das Template
übergeben. Zu Deklaration von Variablen gibt es in ANTLR den scope{}-Block,
dessen Inhalt direkt in den generierten Parser kopiert wird. In diesem Fall ist der
scope{}-Block lokal zur program-Regel definiert, so dass innerhalb der ANTLR-Grammatik mit $program::statements auf die Liste zugegriffen wird.
Zur Initialisierung dient der @init{}-Block in dem mit
$program::statements = new ArrayList(); die Liste initialisiert wird.
Die Kommentare aus diesen Blöcke werden in den Parsercode kopiert und stehen in Fall von
ANTLR 3.1.3 in Zeile 60 und 79 von HelloWorldStatementListParser.java.
Die Syntax der Sprache ändert sich nur minimal. program : statement*
erhält zusätzlichen den Stern '*'. Damit kann 'helloworld' nun null oder beliebig oft im Programmcode
stehen. Und ans program()-Template wird die Liste der gesammelten Statements
übergeben.
Das Sammeln der Befehle erfolgt in der statement-Regel durch das Anhängen
des aktuellen Befehls per $program::statements.add($helloworld.st);
an die Liste der Statements.
grammar HelloWorldStatementList;
options {output=template;}
program
scope {
/* Statement list */
List statements;
}
@init {
/* Init statement list */
$program::statements = new ArrayList();
}
: statement* EOF -> program(statements={$program::statements})
;
statement
: helloworld ';' {$program::statements.add($helloworld.st);}
;
helloworld
: 'helloworld' -> helloWorld()
;
WS : (' ' | '\t' | '\r' | '\n')+ {$channel=HIDDEN;}
;
Java.stg
Das program()-Template muss nun nicht nur einen Befehl, sondern alle Befehele ausgeben,
die in der Statementliste stehen. Darum und um das Einrücken kümmert sich StringTemplate
automatisch, so dass man in den Templates keine Schleife programmieren braucht, sondern nur den
Namen plus das gewünschte Trennzeichen angibt: <statements; separator="\n">.
group Java;
program(statements) ::= <<
public class HelloWorld {
public static void main(String[] args) {
<statements; separator="\n">
}
}
>>
helloWorld() ::= <<
System.out.println("Hello World!");
>>
C.stg
group C;
program(statements) ::= <<
#include \<stdio.h\>
int main(int argc, char *argv[]) {
<statements; separator="\n">
return 0;
}
>>
helloWorld() ::= <<
printf("Hello World!\\n");
>>
PHP.stg
group PHP; program(statements) ::= << <statements; separator="\n"> >> helloWorld() ::= << echo "Hello World!\\n"; >>
Main
Beim Hauptprogramm werden nur die Namen der Lexer- und Parser-Klasse angepasst.
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);
HelloWorldStatementListLexer lexer = new HelloWorldStatementListLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
HelloWorldStatementListParser parser = new HelloWorldStatementListParser(tokens);
parser.setTemplateLib(templates);
RuleReturnScope r = parser.program();
System.out.println(r.getTemplate().toString());
} catch (Exception e) {
System.out.println(e);
}
}
}
Compilieren und ausführen
Anschließend wie im ersten Teil beschrieben compilieren und ausführen.
java org.antlr.Tool HelloWorldStatementList.g
javac Main.java HelloWorldStatementListLexer.java HelloWorldStatementListParser.java
> java Main Java.stg input
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
}
}
> java Main PHP.stg input
echo "Hello World!\n";
echo "Hello World!\n";
echo "Hello World!\n";
