Disons que vous voulez analyser des expressions simples comprenant les mots suivants:
-
-
de soustraction (également unaire);
-
+
plus;
-
*
la multiplication;
-
/
de la division;
-
(...)
groupement (sous -) expressions;
- entiers et des nombres décimaux.
Une grammaire ANTLR pourrait ressembler à ceci:
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')+)?
;
Maintenant à la création d'un AST, vous ajoutez output=AST;
votre options { ... }
section, et vous mélangez de l'arborescence "opérateurs" dans votre grammaire de la définition de jetons doit être la racine d'un arbre. Il y a deux façons de le faire:
- ajouter
^
et !
après vos jetons. L' ^
provoque le jeton de devenir une racine et l' !
exclut le jeton de l'ast;
- à l'aide de "réécrire les règles":
... -> ^(Root Child Child ...)
.
Prendre la règle d' foo
par exemple:
foo
: TokenA TokenB TokenC TokenD
;
et disons que vous souhaitez TokenB
de devenir la racine et de l' TokenA
et TokenC
devenir ses enfants, et que vous souhaitez exclure TokenD
de l'arbre. Voici comment le faire en utilisant l'option 1:
foo
: TokenA TokenB^ TokenC TokenD!
;
et voici comment le faire en utilisant l'option 2:
foo
: TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
;
Alors, voici la grammaire avec l'arbre d'opérateurs:
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();}
;
J'ai également ajouté un Space
règle ignorer les espaces blancs dans le fichier source et ajouté un peu plus de jetons et les espaces de noms pour l'analyseur lexical et l'analyseur. Notez que l'ordre est important (options { ... }
d'abord, puis tokens { ... }
et enfin l' @... {}
-déclarations d'espace de noms).
C'est tout.
Maintenant générer un analyseur lexical et l'analyseur à partir de votre fichier de grammaire:
java -cp antlr-3.2.jar org.antlr.Outil D'Expression.g
et de mettre la .cs
fichiers dans votre projet en collaboration avec le C# runtime DLL.
Vous pouvez le tester à l'aide de la classe suivante:
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);
}
}
}
qui produit la sortie suivante:
RACINE
*
+
12.5
/
56
UNARY_MIN
7
0.5
ce qui correspond à la suite de l'AST:
(diagramme créé à l'aide de graph.gafol.net)
Notez que ANTLR 3.3 vient de sortir et la CSharp cible est "beta". C'est pourquoi j'ai utilisé ANTLR 3.2 dans mon exemple.
En cas de plutôt simple langues (comme mon exemple ci-dessus), vous pouvez également évaluer le résultat à la volée sans pour autant créer un AST. Vous pouvez le faire que par l'incorporation de la plaine de code C# à l'intérieur de votre fichier de grammaire, et de laisser votre analyseur règles de retourner une valeur spécifique.
Voici un exemple:
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();}
;
qui peut être testé avec la classe:
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());
}
}
}
et produit la sortie suivante:
(12.5 + 56 / -7) * 0.5 = 2.25
MODIFIER
Dans les commentaires, Ralph a écrit:
Astuce pour ceux qui utilisent Visual Studio: vous pouvez mettre quelque chose comme java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"
dans la pré-construire des événements, alors vous pouvez simplement modifier votre grammaire et d'exécuter le projet sans avoir à vous soucier de la reconstruction de l'analyseur lexical/parser.