c# - example - antlr python
Usando ANTLR 3.3? (4)
Estoy tratando de comenzar con ANTLR y C #, pero me resulta extraordinariamente difícil debido a la falta de documentación / tutoriales. He encontrado un par de tutoriales poco entusiastas para versiones anteriores, pero parece que se han producido algunos cambios importantes en la API desde entonces.
¿Alguien puede darme un ejemplo simple de cómo crear una gramática y usarla en un programa corto?
Finalmente logré que mi compilación de gramática se convierta en un analizador y un analizador, y puedo compilarlos y ejecutarlos en Visual Studio (después de tener que recompilar el origen ANTLR porque los binarios C # parecen estar desactualizados también). sin mencionar que la fuente no se compila sin algunas correcciones), pero aún no tengo idea de qué hacer con mis clases de analizador / lexer. Supuestamente puede producir un AST con alguna entrada ... y entonces debería ser capaz de hacer algo elegante con eso.
¿Has mirado a Irony.net ? Está dirigido a .Net y, por lo tanto, funciona realmente bien, cuenta con herramientas adecuadas, ejemplos adecuados y simplemente funciona. El único problema es que todavía es un poco "alfa-ish", por lo que la documentación y las versiones parecen cambiar un poco, pero si te quedas con una versión, puedes hacer cosas ingeniosas.
ps pésalo por la mala respuesta donde preguntas un problema sobre X y alguien sugiere algo diferente usando Y; ^)
Digamos que quieres analizar expresiones simples que constan de los siguientes tokens:
-
-
resta (también unario); -
+
adición; -
*
multiplicación; -
/
división; -
(...)
agrupar (sub) expresiones; - números enteros y decimales.
Una gramática ANTLR podría verse así:
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'')+)?
;
Ahora para crear un AST apropiado, agregue output=AST;
en su sección de options { ... }
, y usted mezcla algunos "operadores de árbol" en su gramática que define qué tokens debería ser la raíz de un árbol. Hay dos maneras de hacer esto:
- agregar
^
y!
después de tus tokens El^
hace que el token se convierta en una raíz y el!
excluye el token del ast; - mediante el uso de "reglas de reescritura":
... -> ^(Root Child Child ...)
.
Tome la regla foo
por ejemplo:
foo
: TokenA TokenB TokenC TokenD
;
y digamos que quiere que TokenB
convierta en la raíz y TokenA
y TokenC
para que se convierta en sus hijos, y desea excluir TokenD
del árbol. A continuación, le mostramos cómo hacerlo utilizando la opción 1:
foo
: TokenA TokenB^ TokenC TokenD!
;
y aquí está cómo hacerlo usando la opción 2:
foo
: TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
;
Entonces, aquí está la gramática con los operadores de árbol en ella:
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();}
;
También agregué una regla de Space
para ignorar cualquier espacio en blanco en el archivo fuente y agregué algunos tokens adicionales y espacios de nombres para el lector y el analizador. Tenga en cuenta que el orden es importante ( options { ... }
primero, luego tokens { ... }
y finalmente las declaraciones @... {}
-namespace).
Eso es.
Ahora genere un lexer y un analizador de su archivo de gramática:
java -cp antlr-3.2.jar org.antlr.Tool Expression.g
y coloque los archivos .cs
en su proyecto junto con las DLL de tiempo de ejecución de C # .
Puedes probarlo usando la siguiente clase:
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);
}
}
}
que produce el siguiente resultado:
ROOT * + 12.5 / 56 UNARY_MIN 7 0.5
que corresponde a la siguiente AST:
(diagrama creado usando graph.gafol.net )
Tenga en cuenta que ANTLR 3.3 acaba de ser lanzado y que el objetivo de CSharp está "en beta". Es por eso que utilicé ANTLR 3.2 en mi ejemplo.
En el caso de idiomas bastante simples (como mi ejemplo anterior), también podría evaluar el resultado sobre la marcha sin crear un AST. Puedes hacerlo insertando código simple C # dentro de tu archivo de gramática y permitiendo que tus reglas de analizador devuelvan un valor específico.
Aquí hay un ejemplo:
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();}
;
que se puede probar con la clase:
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());
}
}
}
y produce el siguiente resultado:
(12.5 + 56 / -7) * 0.5 = 2.25
EDITAR
En los comentarios, Ralph escribió:
Sugerencia para aquellos que usan Visual Studio: puedes poner algo como
java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"
en los eventos de preconstrucción, entonces puedes simplemente modifique su gramática y ejecute el proyecto sin tener que preocuparse por la reconstrucción del lexer / analizador.
Hay un excelente artículo sobre cómo usar antlr y C # juntos aquí:
http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx
es un artículo de "cómo se hizo" del creador de NCalc, que es un evaluador de expresiones matemáticas para C # - http://ncalc.codeplex.com
También puede descargar la gramática de NCalc aquí: http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g
ejemplo de cómo funciona 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());
espero que sea útil
Mi experiencia personal es que antes de aprender ANTLR en C # /. NET, debe dedicar suficiente tiempo para aprender ANTLR en Java. Eso le da conocimiento sobre todos los componentes básicos y luego puede aplicar en C # /. NET.
Recientemente escribí algunas publicaciones en el blog,
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-i/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-ii/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-iii/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-iv/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-v/
La suposición es que usted está familiarizado con ANTLR en Java y está listo para migrar su archivo de gramática a C # /. NET.