tutorial tomassetti mega español java antlr antlr4 lexer

java - tomassetti - Patrón de visitante ANTLR4 en aritmética simple ejemplo



antlr4 tutorial español (2)

Claro, simplemente etiquetarlo de manera diferente. Después de todo, la alternativa ''('' expr '')'' no es un #opExpr :

expr : left=expr op=(''*''|''/'') right=expr #opExpr | left=expr op=(''+''|''-'') right=expr #opExpr | ''('' expr '')'' #parenExpr | atom=INT #atomExpr ;

Y en tu visitante, harías algo como esto:

public class EvalVisitor extends ExpressionsBaseVisitor<Integer> { @Override public Integer visitOpExpr(@NotNull ExpressionsParser.OpExprContext ctx) { int left = visit(ctx.left); int right = visit(ctx.right); String op = ctx.op.getText(); switch (op.charAt(0)) { case ''*'': return left * right; case ''/'': return left / right; case ''+'': return left + right; case ''-'': return left - right; default: throw new IllegalArgumentException("Unknown operator " + op); } } @Override public Integer visitStart(@NotNull ExpressionsParser.StartContext ctx) { return this.visit(ctx.expr()); } @Override public Integer visitAtomExpr(@NotNull ExpressionsParser.AtomExprContext ctx) { return Integer.valueOf(ctx.getText()); } @Override public Integer visitParenExpr(@NotNull ExpressionsParser.ParenExprContext ctx) { return this.visit(ctx.expr()); } public static void main(String[] args) { String expression = "2 * (3 + 4)"; ExpressionsLexer lexer = new ExpressionsLexer(new ANTLRInputStream(expression)); ExpressionsParser parser = new ExpressionsParser(new CommonTokenStream(lexer)); ParseTree tree = parser.start(); Integer answer = new EvalVisitor().visit(tree); System.out.printf("%s = %s/n", expression, answer); } }

Si ejecuta la clase anterior, verá la siguiente salida:

2 * (3 + 4) = 14

Soy un completo novato de ANTLR4, así que perdona mi ignorancia. Me encontré con esta presentación donde se define una gramática aritmética de expresión muy simple. Parece que:

grammar Expressions; start : expr ; expr : left=expr op=(''*''|''/'') right=expr #opExpr | left=expr op=(''+''|''-'') right=expr #opExpr | atom=INT #atomExpr ; INT : (''0''..''9'')+ ; WS : [ /t/r/n]+ -> skip ;

Lo cual es genial porque generará un árbol binario muy simple que se puede recorrer utilizando el patrón de visitante como se explica en las diapositivas, por ejemplo, aquí está la función que visita el expr :

public Integer visitOpExpr(OpExprContext ctx) { int left = visit(ctx.left); int right = visit(ctx.right); String op = ctx.op.getText(); switch (op.charAt(0)) { case ''*'': return left * right; case ''/'': return left / right; case ''+'': return left + right; case ''-'': return left - right; default: throw new IllegalArgumentException("Unkown opeator " + op); } }

Lo siguiente que me gustaría agregar es el soporte para paréntesis. Así que modifiqué el expr siguiente manera:

expr : ''('' expr '')'' #opExpr | left=expr op=(''*''|''/'') right=expr #opExpr | left=expr op=(''+''|''-'') right=expr #opExpr | atom=INT #atomExpr ;

Desafortunadamente, el código anterior falla porque al encontrar paréntesis los tres atributos op , left y right son nulos (falla con NPE).

Creo que podría solucionarlo definiendo un nuevo atributo, por ejemplo, parenthesized=''('' expr '')'' , y luego lidiar con eso en el código del visitante. Sin embargo, me parece excesivo tener un tipo de nodo adicional completo para representar una expresión entre paréntesis. Una solución más simple pero más fea es agregar la siguiente línea de código al comienzo del método visitOpExpr :

if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses!

No me gusta nada de lo anterior porque es muy frágil y depende mucho de la estructura gramatical.

Me pregunto si hay una manera de decirle a ANTLR que simplemente "coma" los paréntesis y trate la expresión como un niño. ¿Esta ahí? ¿Hay una mejor manera de hacer esto?

Nota : Mi objetivo final es extender el ejemplo para incluir expresiones booleanas que puedan contener expresiones aritméticas, por ejemplo, (2+4*3)/10 >= 11 , es decir, una relación (<,>, ==, ~ =, etc.) entre expresiones aritméticas puede definir una expresión booleana atómica. Esto es sencillo y ya tengo la gramática esbozada, pero tengo el mismo problema con el paréntesis, es decir, necesito poder escribir cosas como (también agregaré soporte para variables):

((2+4*x)/10 >= 11) | ( x>1 & x<3 )

EDITAR : Se corrigió la precedencia de la expresión entre paréntesis, los paréntesis siempre tienen mayor precedencia.


He trasladado a Python Visitor e incluso a Python Listener

Oyente de Python

from antlr4 import * from arithmeticLexer import arithmeticLexer from arithmeticListener import arithmeticListener from arithmeticParser import arithmeticParser import sys ## grammar arithmetic; ## ## start : expr ; ## ## expr : left=expr op=(''*''|''/'') right=expr #opExpr ## | left=expr op=(''+''|''-'') right=expr #opExpr ## | ''('' expr '')'' #parenExpr ## | atom=INT #atomExpr ## ; ## ## INT : (''0''..''9'')+ ; ## ## WS : [ /t/r/n]+ -> skip ; import codecs import sys def dump(obj): for attr in dir(obj): print("obj.%s = %r" % (attr, getattr(obj, attr))) def is_number(s): try: float(s) return True except ValueError: return False class arithmeticPrintListener(arithmeticListener): def __init__(self): self.stack = [] # Exit a parse tree produced by arithmeticParser#opExpr. def exitOpExpr(self, ctx:arithmeticParser.OpExprContext): print(''exitOpExpr INP'',ctx.op.text,ctx.left.getText(),ctx.right.getText()) op = ctx.op.text opchar1=op[0] right= self.stack.pop() left= self.stack.pop() if opchar1 == ''*'': val = left * right elif opchar1 == ''/'': val = left / right elif opchar1 == ''+'': val = left + right elif opchar1 == ''-'': val = left - right else: raise ValueError("Unknown operator " + op) print("exitOpExpr OUT",opchar1,left,right,val) self.stack.append(val) # Exit a parse tree produced by arithmeticParser#atomExpr. def exitAtomExpr(self, ctx:arithmeticParser.AtomExprContext): val=int(ctx.getText()) print(''exitAtomExpr'',val) self.stack.append(val) def main(): #lexer = arithmeticLexer(StdinStream()) expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))" lexer = arithmeticLexer(InputStream(expression)) stream = CommonTokenStream(lexer) parser = arithmeticParser(stream) tree = parser.start() printer = arithmeticPrintListener() walker = ParseTreeWalker() walker.walk(printer, tree) if __name__ == ''__main__'': main()

Visitante de Python

from antlr4 import * from arithmeticLexer import arithmeticLexer from arithmeticVisitor import arithmeticVisitor from arithmeticParser import arithmeticParser import sys from pprint import pprint ## grammar arithmetic; ## ## start : expr ; ## ## expr : left=expr op=(''*''|''/'') right=expr #opExpr ## | left=expr op=(''+''|''-'') right=expr #opExpr ## | ''('' expr '')'' #parenExpr ## | atom=INT #atomExpr ## ; ## ## INT : (''0''..''9'')+ ; ## ## WS : [ /t/r/n]+ -> skip ; import codecs import sys class EvalVisitor(arithmeticVisitor): def visitOpExpr(self, ctx): #print("visitOpExpr",ctx.getText()) left = self.visit(ctx.left) right = self.visit(ctx.right) op = ctx.op.text; # for attr in dir(ctx.op): ########### BEST # print("ctx.op.%s = %r" % (attr, getattr(ctx.op, attr))) #print("visitOpExpr",dir(ctx.op),left,right) opchar1=op[0] if opchar1 == ''*'': val = left * right elif opchar1 == ''/'': val = left / right elif opchar1 == ''+'': val = left + right elif opchar1 == ''-'': val = left - right else: raise ValueError("Unknown operator " + op) print("visitOpExpr",opchar1,left,right,val) return val def visitStart(self, ctx): print("visitStart",ctx.getText()) return self.visit(ctx.expr()) def visitAtomExpr(self, ctx): print("visitAtomExpr",int(ctx.getText())) return int(ctx.getText()) def visitParenExpr(self, ctx): print("visitParenExpr",ctx.getText()) return self.visit(ctx.expr()) def main(): #lexer = arithmeticLexer(StdinStream()) expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))" lexer = arithmeticLexer(InputStream(expression)) stream = CommonTokenStream(lexer) parser = arithmeticParser(stream) tree = parser.start() answer = EvalVisitor().visit(tree) print(answer) if __name__ == ''__main__'': main()