Angenommen, Sie möchten einfache Ausdrücke analysieren, die aus den folgenden Token bestehen:
-
Subtraktion (auch unär);
+
Zusatz;
*
Multiplikation;
/
Teilung;
(...)
Gruppieren von (Unter-) Ausdrücken;
- Ganz- und Dezimalzahlen.
Eine ANTLR-Grammatik könnte folgendermaßen aussehen:
grammar Expression;
options {
language=CSharp2;
}
parse
: exp EOF
;
exp
: addExp
;
addExp
: mulExp (('+' | '-') mulExp)*
;
mulExp
: unaryExp (('*' | '/') unaryExp)*
;
unaryExp
: '-' atom
| atom
;
atom
: Number
| '(' exp ')'
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
Um nun einen richtigen AST zu erstellen, fügen Sie ihn output=AST;
in Ihren options { ... }
Abschnitt ein und mischen einige "Baumoperatoren" in Ihrer Grammatik, um zu definieren, welche Token die Wurzel eines Baums sein sollen. Es gibt zwei Möglichkeiten, dies zu tun:
- füge hinzu
^
und !
nach deinen Token. Das ^
bewirkt , dass das Token eine Wurzel werden und die !
umfasst nicht das Token aus dem ast;
- durch Verwendung von "Regeln umschreiben" :
... -> ^(Root Child Child ...)
.
Nehmen Sie foo
zum Beispiel die Regel :
foo
: TokenA TokenB TokenC TokenD
;
und lassen Sie uns sagen , Sie wollen TokenB
die Wurzel werden und TokenA
und TokenC
seine Kinder zu werden, und Sie ausschließen möchten TokenD
aus dem Baum. So geht's mit Option 1:
foo
: TokenA TokenB^ TokenC TokenD!
;
und so geht's mit Option 2:
foo
: TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
;
Hier ist die Grammatik mit den Baumoperatoren:
grammar Expression;
options {
language=CSharp2;
output=AST;
}
tokens {
ROOT;
UNARY_MIN;
}
@parser::namespace { Demo.Antlr }
@lexer::namespace { Demo.Antlr }
parse
: exp EOF -> ^(ROOT exp)
;
exp
: addExp
;
addExp
: mulExp (('+' | '-')^ mulExp)*
;
mulExp
: unaryExp (('*' | '/')^ unaryExp)*
;
unaryExp
: '-' atom -> ^(UNARY_MIN atom)
| atom
;
atom
: Number
| '(' exp ')' -> exp
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
Space
: (' ' | '\t' | '\r' | '\n'){Skip();}
;
Ich habe auch eine Space
Regel hinzugefügt , um Leerzeichen in der Quelldatei zu ignorieren, und einige zusätzliche Token und Namespaces für den Lexer und den Parser hinzugefügt. Beachten Sie, dass die Reihenfolge wichtig ist ( options { ... }
zuerst, dann tokens { ... }
und schließlich die @... {}
-namespace-Deklarationen).
Das ist es.
Generieren Sie nun einen Lexer und Parser aus Ihrer Grammatikdatei:
java -cp antlr-3.2.jar org.antlr.Tool Expression.g
und fügen Sie die .cs
Dateien in Ihrem Projekt zusammen mit den C # -Runtime-DLLs ein .
Sie können es mit der folgenden Klasse testen:
using System;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using Antlr.StringTemplate;
namespace Demo.Antlr
{
class MainClass
{
public static void Preorder(ITree Tree, int Depth)
{
if(Tree == null)
{
return;
}
for (int i = 0; i < Depth; i++)
{
Console.Write(" ");
}
Console.WriteLine(Tree);
Preorder(Tree.GetChild(0), Depth + 1);
Preorder(Tree.GetChild(1), Depth + 1);
}
public static void Main (string[] args)
{
ANTLRStringStream Input = new ANTLRStringStream("(12.5 + 56 / -7) * 0.5");
ExpressionLexer Lexer = new ExpressionLexer(Input);
CommonTokenStream Tokens = new CommonTokenStream(Lexer);
ExpressionParser Parser = new ExpressionParser(Tokens);
ExpressionParser.parse_return ParseReturn = Parser.parse();
CommonTree Tree = (CommonTree)ParseReturn.Tree;
Preorder(Tree, 0);
}
}
}
welches die folgende Ausgabe erzeugt:
WURZEL
* *
+
12.5
/.
56
UNARY_MIN
7
0,5
was dem folgenden AST entspricht:
(Diagramm erstellt mit graph.gafol.net )
Beachten Sie, dass ANTLR 3.3 gerade veröffentlicht wurde und das CSharp-Ziel "in der Beta" ist. Deshalb habe ich in meinem Beispiel ANTLR 3.2 verwendet.
Bei eher einfachen Sprachen (wie in meinem obigen Beispiel) können Sie das Ergebnis auch im laufenden Betrieb auswerten, ohne einen AST zu erstellen. Sie können dies tun, indem Sie einfachen C # -Code in Ihre Grammatikdatei einbetten und Ihre Parserregeln einen bestimmten Wert zurückgeben lassen.
Hier ist ein Beispiel:
grammar Expression;
options {
language=CSharp2;
}
@parser::namespace { Demo.Antlr }
@lexer::namespace { Demo.Antlr }
parse returns [double value]
: exp EOF {$value = $exp.value;}
;
exp returns [double value]
: addExp {$value = $addExp.value;}
;
addExp returns [double value]
: a=mulExp {$value = $a.value;}
( '+' b=mulExp {$value += $b.value;}
| '-' b=mulExp {$value -= $b.value;}
)*
;
mulExp returns [double value]
: a=unaryExp {$value = $a.value;}
( '*' b=unaryExp {$value *= $b.value;}
| '/' b=unaryExp {$value /= $b.value;}
)*
;
unaryExp returns [double value]
: '-' atom {$value = -1.0 * $atom.value;}
| atom {$value = $atom.value;}
;
atom returns [double value]
: Number {$value = Double.Parse($Number.Text, CultureInfo.InvariantCulture);}
| '(' exp ')' {$value = $exp.value;}
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
Space
: (' ' | '\t' | '\r' | '\n'){Skip();}
;
was mit der Klasse getestet werden kann:
using System;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using Antlr.StringTemplate;
namespace Demo.Antlr
{
class MainClass
{
public static void Main (string[] args)
{
string expression = "(12.5 + 56 / -7) * 0.5";
ANTLRStringStream Input = new ANTLRStringStream(expression);
ExpressionLexer Lexer = new ExpressionLexer(Input);
CommonTokenStream Tokens = new CommonTokenStream(Lexer);
ExpressionParser Parser = new ExpressionParser(Tokens);
Console.WriteLine(expression + " = " + Parser.parse());
}
}
}
und erzeugt die folgende Ausgabe:
(12,5 + 56 / -7) * 0,5 = 2,25
BEARBEITEN
In den Kommentaren schrieb Ralph:
Tipp für Benutzer von Visual Studio: Sie können so etwas wie java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"
in die Pre-Build-Ereignisse einfügen, dann können Sie einfach Ihre Grammatik ändern und das Projekt ausführen, ohne sich um die Neuerstellung des Lexers / Parsers kümmern zu müssen.
parse()
privat undskip()
ist nicht verfügbar, und die C # -Laufzeiten funktionieren nicht damit. Dies sollte mir den Einstieg erleichtern, vielen Dank!