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!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.Haben Sie sich Irony.net angesehen ? Es richtet sich an .Net und funktioniert daher sehr gut, verfügt über geeignete Werkzeuge, geeignete Beispiele und funktioniert einfach. Das einzige Problem ist, dass es immer noch ein bisschen "alpha-ish" ist, so dass sich Dokumentation und Versionen ein wenig zu ändern scheinen, aber wenn Sie sich nur an eine Version halten, können Sie raffinierte Dinge tun.
ps Entschuldigung für die schlechte Antwort, bei der Sie ein Problem mit X stellen und jemand mit Y etwas anderes vorschlägt; ^)
quelle
Meine persönliche Erfahrung ist, dass Sie vor dem Erlernen von ANTLR auf C # /. NET genügend Zeit haben sollten, um ANTLR auf Java zu lernen. Das gibt Ihnen Wissen über alle Bausteine und später können Sie sich auf C # /. NET bewerben.
Ich habe kürzlich ein paar Blog-Beiträge geschrieben,
Es wird davon ausgegangen, dass Sie mit ANTLR unter Java vertraut sind und bereit sind, Ihre Grammatikdatei nach C # /. NET zu migrieren.
quelle
Hier gibt es einen großartigen Artikel darüber, wie man Antlr und C # zusammen verwendet:
http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx
Es handelt sich um einen Artikel des Erstellers von NCalc, einem Bewerter für mathematische Ausdrücke für C # - http://ncalc.codeplex.com
Sie können die Grammatik für NCalc auch hier herunterladen: http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g
Beispiel für die Funktionsweise von NCalc:
Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); e.Parameters["Pi2"] = new Expression("Pi * Pi"); e.Parameters["X"] = 10; e.EvaluateParameter += delegate(string name, ParameterArgs args) { if (name == "Pi") args.Result = 3.14; }; Debug.Assert(117.07 == e.Evaluate());
hoffe es ist hilfreich
quelle