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()