Java: Compilerbau mit ANTLR 3 und StringTemplate (4)
Mit 'print' integer; wird der Compiler nun um die Ausgabe von
Ganzzahlen per print erweitert.
print123.prg
print 123; helloworld;
Lexer: INTEGER
Erster Schritt ist die Erweiterung des Lexers, um die Erkennung von Ganzzahlen. Dies geschieht mit
INTEGER : ('0'..'9')+ ;
('0'..'9')+ bedeutet, dass die Ziffern 0, 1, 2, ... 9 ein, oder
beliebig oft hintereinanstehen dürfen und dem Tokennamen INTEGER zugeordnet werden.
Lexersymbole wie INTEGER müssen in ANTLR mit einen Großbuchstaben beginnen,
die Namen der Regeln wie statement mit einen Kleinbuchstaben. Es ist in
ANTLR üblich die Lexersymbole durchgehen groß zu schreiben und für die Regeln die
lowerCamelCase-Schreibweise zu benutzen.
Grammatik: 'print' integer;
In der Grammatik wird $INTEGER.text in das
const-StringTemplate (Konstante)
umgesetzt, das an den print-Befehl weitergereicht wird.
Dieser setzt sich aus den direkt angebenen 'print'-Token und dem Ergebnis
der integer-Regel zusammen.
print
: 'print' integer -> print(value={$integer.st})
;
integer
: INTEGER -> const(value={$INTEGER.text})
;
Grammar.g
grammar Grammar;
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);}
| print ';' {$program::statements.add($print.st);}
;
helloworld
: 'helloworld' -> helloWorld()
;
print
: 'print' integer -> print(value={$integer.st})
;
integer
: INTEGER -> const(value={$INTEGER.text})
;
// Lexer
INTEGER : ('0'..'9')+ ;
WS : (' ' | '\t' | '\r' | '\n')+ {$channel=HIDDEN;} ;
Templates: const() und print()
Die Templates werden um const() für die Ganzzahl und print()
zur Generierung des Quelltextes für die Zahlenausgabe erweitert.
Java.stg
group Java;
program(statements) ::= <<
public class HelloWorld {
public static void main(String[] args) {
<statements; separator="\n">
}
}
>>
helloWorld() ::= "System.out.println(\"Hello World!\");"
const(value) ::= "<value>"
print(value) ::= "System.out.println(<value>);"
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");
>>
const(value) ::= "<value>"
print(value) ::= <<
printf("%d\\n", <value>);
>>
PHP.stg
group PHP; program(statements) ::= << <statements; separator="\n"> >> helloWorld() ::= << echo "Hello World!\\n"; >> const(value) ::= "<value>" print(value) ::= << echo <value>, "\n"; >>
Main
Das Hauptprogramm bleibt unverändert.
Main.java
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);
GrammarLexer lexer = new GrammarLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
GrammarParser parser = new GrammarParser(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
Und wird wie in Hello World! beschrieben compiliert und ausgeführt.
> java Main Java.stg print123.prg
public class HelloWorld {
public static void main(String[] args) {
System.out.println(123);
System.out.println("Hello World!");
}
}
> java Main PHP.stg print123.prg
echo 123, "\n";
echo "Hello World!\n";
Bekannte Fehler: Zu große Ganzzahlen möglich
Was in diesem Beispiel fehlt ist die Erkennung von zu großen Zahlen. Da INTEGER
aus beliebig vielen Ziffern bestehen darf, kann die gängige, maximale Größe von 4 294 967 295
für Ganzzahlen schnell überschritten werden, ohne dass dies der Compiler erkennt:
print 9876543210. Hierfür muss später noch eine Fehlererkennung eingebaut werden.
