You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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);
    }
}

总结

  1. Listener模式完全可行:只要通过Map(或其他存储方式)维护每个语法节点的计算结果,就能完成表达式求值和变量管理,不需要切换到Visitor。
  2. 核心修正点:去掉对不存在的ctx.v的访问,改用Map存储节点结果。
  3. 额外优化:增加了除零检查、更严谨的类型校验,以及格式化输出逻辑。

内容的提问来源于stack exchange,提问作者Emmanuel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:27:18