使用ANTLR Listener实现计算器:能否获取计算结果?
解答:Listener模式完全可以实现计算器,无需切换到Visitor
首先明确:你不需要必须改用Visitor模式,Listener模式完全能够实现计算器的计算逻辑。Listener是基于语法树遍历的事件驱动模型,只要在合适的节点退出方法(exitXXX)中维护好每个节点的计算结果(值和类型),就能完成计算。
你当前代码的核心问题
你直接访问ctx.v这个属性,但ANTLR自动生成的calcParser.ExprContext、calcParser.Expr2Context等上下文类默认并没有v这个成员变量,这会导致编译失败。你需要自己实现一个存储机制,来保存每个语法节点的计算结果(类型和值)。
另外,你的代码中还有一些逻辑错误:比如exitExpr分支中ctx.expr()==null时却访问ctx.expr().v.value,会触发空指针;类型检查的逻辑重复且存在判断错误。
修正后的Listener实现思路
我们可以用一个Map<ParserRuleContext, Variable>来存储每个语法节点对应的计算结果,在每个exitXXX方法中,先处理子节点的结果,再计算当前节点的结果并存入Map,最后在赋值或输出时取出结果。
以下是修正后的关键代码:
1. 在Listener类中添加结果存储Map
public class calcListenerImpl extends calcBaseListener { // 存储每个语法节点的计算结果(类型+值) private Map<ParserRuleContext, Variable> nodeResults = new HashMap<>(); private VariableList varList = VariableList.getList(); // 其他方法... }
2. 修正exitExpr3(处理最基础的表达式单元)
@Override public void exitExpr3(calcParser.Expr3Context ctx) { Variable result = new Variable(); if (ctx.floatNumber != null) { result.type = "float"; result.value = Float.parseFloat(ctx.floatNumber.getText()); } else if (ctx.intNumber != null) { result.type = "int"; result.value = Float.parseFloat(ctx.intNumber.getText()); } else if (ctx.id != null) { Variable existingVar = varList.find(ctx.id.getText()); if (existingVar == null) { System.err.println("Error: Undeclared variable '" + ctx.id.getText() + "'"); System.exit(1); } result.type = existingVar.type; result.value = existingVar.value; } else if (ctx.expr() != null) { // 直接复用子表达式的结果 result = nodeResults.get(ctx.expr()); } nodeResults.put(ctx, result); }
3. 修正exitExpr2(处理乘除运算)
@Override public void exitExpr2(calcParser.Expr2Context ctx) { if (ctx.expr2() == null) { // 只有expr3,直接复用其结果 Variable expr3Result = nodeResults.get(ctx.expr3()); nodeResults.put(ctx, expr3Result); return; } Variable left = nodeResults.get(ctx.expr2()); Variable right = nodeResults.get(ctx.expr3()); Variable result = new Variable(); // 确定结果类型:只要有一个操作数是float,结果就是float result.type = (left.type.equals("float") || right.type.equals("float")) ? "float" : "int"; // 执行运算 if (ctx.mul != null) { result.value = left.value * right.value; } else if (ctx.div != null) { if (right.value == 0) { System.err.println("Error: Division by zero"); System.exit(1); } result.value = left.value / right.value; } nodeResults.put(ctx, result); }
4. 修正exitExpr(处理加减运算)
@Override public void exitExpr(calcParser.ExprContext ctx) { if (ctx.expr() == null) { // 只有expr2,直接复用其结果 Variable expr2Result = nodeResults.get(ctx.expr2()); nodeResults.put(ctx, expr2Result); return; } Variable left = nodeResults.get(ctx.expr()); Variable right = nodeResults.get(ctx.expr2()); Variable result = new Variable(); result.type = (left.type.equals("float") || right.type.equals("float")) ? "float" : "int"; if (ctx.plu != null) { result.value = left.value + right.value; } else if (ctx.min != null) { result.value = left.value - right.value; } nodeResults.put(ctx, result); }
5. 修正exitAssign(处理变量赋值)
@Override public void exitAssign(calcParser.AssignContext ctx) { String varName = ctx.id.getText(); Variable targetVar = varList.find(varName); if (targetVar == null) { System.err.println("Error: Undeclared variable '" + varName + "'"); System.exit(1); } Variable exprResult = nodeResults.get(ctx.expr()); // 类型检查:int变量不能接收float值,反之可以 if (targetVar.type.equals("int") && exprResult.type.equals("float")) { System.err.println("Error: Cannot assign float value to int variable '" + varName + "'"); System.exit(1); } targetVar.value = exprResult.value; }
6. 优化exitWriteFunction(处理输出)
@Override public void exitWriteFunction(calcParser.WriteFunctionContext ctx) { String varName = ctx.id.getText(); Variable var = varList.find(varName); if (var == null) { System.err.println("Error: Undeclared variable '" + varName + "'"); System.exit(1); } // 根据变量类型格式化输出 if (var.type.equals("int")) { System.out.println((int) var.value); } else { System.out.println(var.value); } }
总结
- Listener模式完全可行:只要通过Map(或其他存储方式)维护每个语法节点的计算结果,就能完成表达式求值和变量管理,不需要切换到Visitor。
- 核心修正点:去掉对不存在的
ctx.v的访问,改用Map存储节点结果。 - 额外优化:增加了除零检查、更严谨的类型校验,以及格式化输出逻辑。
内容的提问来源于stack exchange,提问作者Emmanuel
相关产品推荐
相关产品推荐

