[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"$f2e0za9a1itjcy":14,"$f3ghwiazhi7cc6":31,"$f2nmi71y1vi98l":56,"$f2hf8zv88r59zg":65,"render-antlr4-13-listener-vs-visitor":78},{"code":4,"message":5,"data":6},0,"ok",{"author_role":7,"icp":8,"skill_one_title":7,"about_hero_image":7,"skill_two_title":7,"author_status":7,"skill_one_desc":7,"site_description":9,"site_tagline":7,"author_years":10,"author_city":11,"home_hero_image":7,"tagline_line_two":7,"site_name":12,"closing_quote":7,"author_bio_two":7,"tagline_line_one":7,"weekly_thought":7,"author_tagline":7,"site_url":13,"site_footer":7,"author_bio":7,"skill_two_desc":7,"toolkit":7,"closing_quote_date":7,"og_default_image":7},"","陕ICP备2021009682号-1","花开空白 - 个人博客","10","西安","花开空白","https:\u002F\u002Firule.cn",{"code":4,"message":5,"data":15},{"id":16,"slug":17,"title":18,"summary":19,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":23,"readingMinutes":24,"publishedAt":25,"contentMd":26,"contentHtml":27,"tags":28,"metaTitle":20,"metaDescription":20,"keywords":20,"canonicalUrl":20,"robots":30,"ogImage":20,"faq":20,"aiSummary":20},"2101652901984649217","antlr4-13-listener-vs-visitor","Antlr4系列⑬：Listener模式对比","用Listener重新实现部分能力、实测对比if\u002Felse下Listener与Visitor的行为差异",null,"1946000000000000001",1,5439,14,"2026-09-20T20:41:23","本系列从第一篇开始，一直用的都是antlr4提供的两种业务逻辑实现方式之一——**访问器（Visitor）**。第①篇文章末尾其实提到过，antlr4还提供了另一种方式：**监听器（Listener）**。这篇文章我们就用Listener重新实现一部分语言能力，通过实际跑起来的例子，直观地看看这两种模式的核心区别，以及为什么这个系列从头到尾都选择了Visitor。\n\n## 一、Listener和Visitor的核心区别\n\n- **Visitor**：由你自己的代码调用`visit(ctx)`来决定\"要不要访问某个子节点、访问哪个、什么时候访问、访问完之后返回什么\"。整个遍历过程的控制权在你手里。\n- **Listener**：由antlr4提供的`ParseTreeWalker`来遍历整棵语法树，规则是固定的——**从根节点开始，按照子节点在树上的顺序，无条件地把每一个节点都走一遍**，进入节点时调用`enterXxx`，离开节点时调用`exitXxx`。这两个方法都是`void`，没有返回值，Listener本身也没有任何办法告诉`ParseTreeWalker`\"这个子树不用走了\"。\n\n这个区别听起来有点抽象，我们直接上代码看会更清楚。\n\n## 二、Listener能做什么：用栈模拟\"返回值\"\n\n`enterXxx`\u002F`exitXxx`没有返回值，但我们又需要把子节点算出来的中间结果传递给父节点（比如两个数相加，得先知道这两个数分别是多少）。经典的做法是**用一个栈来模拟返回值的传递**：`ParseTreeWalker`对语法树做的是后序遍历，也就是说一个节点的所有子节点一定会先于这个节点被`exit`。等父节点的`exitXxx`被调用时，它的子节点早就已经把各自的计算结果压进栈里了，直接按顺序弹出来用即可。\n\n```java\npublic class MyRuleSetListener extends RuleSetBaseListener {\n\n    private final Deque\u003CVisitorResult> stack = new ArrayDeque\u003C>();\n\n    public VisitorResult getResult() {\n        return stack.peek();\n    }\n\n    @Override\n    public void exitNumber(RuleSetParser.NumberContext ctx) {\n        stack.push(VisitorResult.value(Double.parseDouble(ctx.getText())));\n    }\n\n    @Override\n    public void exitStringValue(RuleSetParser.StringValueContext ctx) {\n        String text = ctx.getText();\n        stack.push(VisitorResult.value(text.substring(1, text.length() - 1)));\n    }\n\n    @Override\n    public void exitBoolValue(RuleSetParser.BoolValueContext ctx) {\n        stack.push(VisitorResult.value(Boolean.parseBoolean(ctx.getText())));\n    }\n\n    @Override\n    public void exitMulAndDiv(RuleSetParser.MulAndDivContext ctx) {\n        \u002F\u002F 右操作数在左操作数之后被visit，所以先出栈的是右操作数\n        double right = stack.pop().getNumber();\n        double left = stack.pop().getNumber();\n        if (ctx.opt.getType() == RuleSetLexer.MUL) {\n            stack.push(VisitorResult.value(left * right));\n        } else {\n            stack.push(VisitorResult.value(left \u002F right));\n        }\n    }\n\n    @Override\n    public void exitAddAndSub(RuleSetParser.AddAndSubContext ctx) {\n        VisitorResult right = stack.pop();\n        VisitorResult left = stack.pop();\n        if (ctx.opt.getType() == RuleSetLexer.SUB) {\n            stack.push(VisitorResult.value(left.getNumber() - right.getNumber()));\n            return;\n        }\n        if (left.isVarchar() || right.isVarchar()) {\n            stack.push(VisitorResult.value(left.toString() + right.toString()));\n        } else {\n            stack.push(VisitorResult.value(left.getNumber() + right.getNumber()));\n        }\n    }\n\n    \u002F**\n     * print在Listener模式里能做到的极限就是这样：把栈顶（也就是括号里那个calcu的结果）打印出来。\n     * 至于要不要打印、打印哪个分支——Listener完全说了不算，ParseTreeWalker会无条件地把语法树上\n     * 每一个print都走一遍，不管它是不是应该在这次执行中被跳过\n     *\u002F\n    @Override\n    public void exitPrintValue(RuleSetParser.PrintValueContext ctx) {\n        System.out.println(stack.pop());\n    }\n}\n```\n注意这里`括号`（`parens`）不需要任何处理——括号内的`calcu`在这之前已经`exit`过了，结果已经在栈顶，括号本身对结果没有任何影响，什么都不用做。这份代码没有继承任何东西和Scope相关的逻辑，因为一旦涉及到变量读写、函数调用这些需要维护状态的能力，Listener能做的事和Visitor其实也差不太多（无非是把状态维护逻辑搬到Listener类的字段里），真正的差异不在\"能不能维护状态\"，而在下面这一节。\n\n拿两个纯计算的表达式验证一下：\n\n```java\nString numExpression = \"(3 + 4) * 2 - 5\";\nSystem.out.println(\"Listener计算结果: \" + walkCalcu(numExpression));\n\nString strExpression = \"\\\"Hello, \\\" + \\\"Listener\\\"\";\nSystem.out.println(\"Listener计算结果: \" + walkCalcu(strExpression));\n```\n其中`walkCalcu`直接用`parser.calcu()`单独解析一段`calcu`规则（这个技巧在第⑨篇文章设置条件断点时用过），然后用`ParseTreeWalker.DEFAULT.walk(listener, calcuTree)`触发遍历：\n\n```java\nprivate static VisitorResult walkCalcu(String expression) {\n    RuleSetLexer lexer = new RuleSetLexer(CharStreams.fromString(expression));\n    RuleSetParser parser = new RuleSetParser(new CommonTokenStream(lexer));\n    RuleSetParser.CalcuContext calcuTree = parser.calcu();\n    MyRuleSetListener listener = new MyRuleSetListener();\n    ParseTreeWalker.DEFAULT.walk(listener, calcuTree);\n    return listener.getResult();\n}\n```\n\n执行结果：\n```\n表达式: (3 + 4) * 2 - 5\nListener计算结果: 9.0\n\n表达式: \"Hello, \" + \"Listener\"\nListener计算结果: Hello, Listener\n```\n纯计算、没有分支的场景下，Listener和Visitor算出来的结果完全一样。\n\n## 三、Listener做不到什么：无法\"跳过\"一个分支\n\n现在来看真正的核心差异。写一段最简单的`if\u002Felse`：\n\n```\nif (false) {\n    print(\"A - if分支，条件为false，不应该被打印\")\n} else {\n    print(\"B - else分支，应该被打印\")\n}\n```\n按正常的语义，`false`让条件不成立，应该只执行`else`分支，只打印`B`。分别用Listener和Visitor跑同一段代码：\n\n```java\nRuleSetParser.ProgContext prog = parser.prog();\n\nSystem.out.println(\"[Listener方式的执行结果]\");\nMyRuleSetListener listener = new MyRuleSetListener();\nParseTreeWalker.DEFAULT.walk(listener, prog);\n\nSystem.out.println(\"[Visitor方式的执行结果]\");\nMyRuleSetVisitor visitor = new MyRuleSetVisitor();\nvisitor.visit(prog);\n```\n\n执行结果如下：\n```\n[Listener方式的执行结果]\nA - if分支，条件为false，不应该被打印\nB - else分支，应该被打印\n\n[Visitor方式的执行结果]\nB - else分支，应该被打印\n```\nListener把`A`和`B`**都**打印出来了！原因就在开头说的那句话：`ParseTreeWalker`不管条件是`true`还是`false`，都会无条件地把语法树上的每一个节点走一遍，包括`if`分支的`block`和`else`分支的`block`，我们的`MyRuleSetListener`压根没有、也没办法拿到\"条件求值结果\"来决定该不该执行`exitPrintValue`里的打印逻辑——`cond`节点即使被`enter`\u002F`exit`了，Listener也只能被动地\"看着\"它过去，不能拿这个结果去控制接下来该不该继续走某个子树。\n\n反观我们从第①篇用到现在的Visitor实现，`visitIfStmt`里的逻辑是这样的（这是第④篇文章实现的代码）：\n```java\n@Override\npublic VisitorResult visitIfStmt(RuleSetParser.IfStmtContext ctx) {\n    List\u003CRuleSetParser.CondContext> conds = ctx.cond();\n    List\u003CRuleSetParser.BlockContext> blocks = ctx.block();\n    for (int i = 0; i \u003C conds.size(); i++) {\n        if (visit(conds.get(i)).getBool()) {   \u002F\u002F 关键：条件不满足，压根不会去visit对应的block\n            return visit(blocks.get(i));\n        }\n    }\n    if (blocks.size() > conds.size()) {\n        return visit(blocks.get(blocks.size() - 1));\n    }\n    return VisitorResult.nil();\n}\n```\n`visit(conds.get(i)).getBool()`先求出条件的真假，只有为真时才会调用`visit(blocks.get(i))`——**根本不满足条件的那个`block`，Visitor连进都不会进去**，这才是`if\u002Felse`应该有的语义。`while`循环、`break`\u002F`continue`、函数调用\u002F`return`、短路的`&&`\u002F`||`，本系列实现过的所有控制流能力，无一例外都依赖这种\"由代码自己决定要不要访问某个子节点\"的能力，而这恰恰是Listener从设计上就不具备的。\n\n## 四、什么时候该用Listener\n\n看到这里可能会觉得Listener像是个\"残缺\"的方案，但它并不是为了实现解释器而设计的，它更适合的场景是：**需要把语法树完整地过一遍、单纯地收集信息或者做一些互不依赖的检查，不需要根据某个节点的值决定要不要处理其他节点**。典型的例子：\n- 静态检查\u002F语法风格检查（linter）：比如扫描代码里有没有定义了却从来没用过的变量，需要把每一个变量的定义和使用都看一遍，和\"运行到哪一行\"完全无关。\n- 代码格式化、语法高亮：同样需要遍历到每一个token\u002F节点，按节点类型输出对应的格式，不涉及\"选择性执行\"。\n- 生成符号表、做简单的重命名\u002F代码转换：收集信息或者做等价的文本替换，本质上也是要把每个相关节点都处理一遍。\n\n另外，多个不相关的Listener还可以用同一个`ParseTreeWalker`合并在一次遍历里跑（antlr4支持给`walk`传入组合多个Listener的场景），如果你需要同时做几件互不相关的静态分析，用Listener可以避免多次遍历同一棵树，这是Visitor不太容易做到的一个优势——但这和\"实现一门带控制流的语言\"是两个完全不同的需求。\n\n一句话总结：**需要\"选择性地\"\"带着状态和结果\"去驱动树的遍历（比如解释执行一门语言）用Visitor；只需要\"完整地\"遍历一遍树、单纯做观察或收集用Listener**。这也是本系列从第一篇到现在，实现语言解释器的核心逻辑都构建在`MyRuleSetVisitor`之上、而不是Listener之上的根本原因。\n","\u003Cp>本系列从第一篇开始，一直用的都是antlr4提供的两种业务逻辑实现方式之一——\u003Cstrong>访问器（Visitor）\u003C\u002Fstrong>。第①篇文章末尾其实提到过，antlr4还提供了另一种方式：\u003Cstrong>监听器（Listener）\u003C\u002Fstrong>。这篇文章我们就用Listener重新实现一部分语言能力，通过实际跑起来的例子，直观地看看这两种模式的核心区别，以及为什么这个系列从头到尾都选择了Visitor。\u003C\u002Fp>\n\u003Ch2>一、Listener和Visitor的核心区别\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>Visitor\u003C\u002Fstrong>：由你自己的代码调用\u003Ccode>visit(ctx)\u003C\u002Fcode>来决定\"要不要访问某个子节点、访问哪个、什么时候访问、访问完之后返回什么\"。整个遍历过程的控制权在你手里。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Listener\u003C\u002Fstrong>：由antlr4提供的\u003Ccode>ParseTreeWalker\u003C\u002Fcode>来遍历整棵语法树，规则是固定的——\u003Cstrong>从根节点开始，按照子节点在树上的顺序，无条件地把每一个节点都走一遍\u003C\u002Fstrong>，进入节点时调用\u003Ccode>enterXxx\u003C\u002Fcode>，离开节点时调用\u003Ccode>exitXxx\u003C\u002Fcode>。这两个方法都是\u003Ccode>void\u003C\u002Fcode>，没有返回值，Listener本身也没有任何办法告诉\u003Ccode>ParseTreeWalker\u003C\u002Fcode>\"这个子树不用走了\"。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这个区别听起来有点抽象，我们直接上代码看会更清楚。\u003C\u002Fp>\n\u003Ch2>二、Listener能做什么：用栈模拟\"返回值\"\u003C\u002Fh2>\n\u003Cp>\u003Ccode>enterXxx\u003C\u002Fcode>\u002F\u003Ccode>exitXxx\u003C\u002Fcode>没有返回值，但我们又需要把子节点算出来的中间结果传递给父节点（比如两个数相加，得先知道这两个数分别是多少）。经典的做法是\u003Cstrong>用一个栈来模拟返回值的传递\u003C\u002Fstrong>：\u003Ccode>ParseTreeWalker\u003C\u002Fcode>对语法树做的是后序遍历，也就是说一个节点的所有子节点一定会先于这个节点被\u003Ccode>exit\u003C\u002Fcode>。等父节点的\u003Ccode>exitXxx\u003C\u002Fcode>被调用时，它的子节点早就已经把各自的计算结果压进栈里了，直接按顺序弹出来用即可。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class MyRuleSetListener extends RuleSetBaseListener {\n\n    private final Deque&lt;VisitorResult&gt; stack = new ArrayDeque&lt;&gt;();\n\n    public VisitorResult getResult() {\n        return stack.peek();\n    }\n\n    @Override\n    public void exitNumber(RuleSetParser.NumberContext ctx) {\n        stack.push(VisitorResult.value(Double.parseDouble(ctx.getText())));\n    }\n\n    @Override\n    public void exitStringValue(RuleSetParser.StringValueContext ctx) {\n        String text = ctx.getText();\n        stack.push(VisitorResult.value(text.substring(1, text.length() - 1)));\n    }\n\n    @Override\n    public void exitBoolValue(RuleSetParser.BoolValueContext ctx) {\n        stack.push(VisitorResult.value(Boolean.parseBoolean(ctx.getText())));\n    }\n\n    @Override\n    public void exitMulAndDiv(RuleSetParser.MulAndDivContext ctx) {\n        \u002F\u002F 右操作数在左操作数之后被visit，所以先出栈的是右操作数\n        double right = stack.pop().getNumber();\n        double left = stack.pop().getNumber();\n        if (ctx.opt.getType() == RuleSetLexer.MUL) {\n            stack.push(VisitorResult.value(left * right));\n        } else {\n            stack.push(VisitorResult.value(left \u002F right));\n        }\n    }\n\n    @Override\n    public void exitAddAndSub(RuleSetParser.AddAndSubContext ctx) {\n        VisitorResult right = stack.pop();\n        VisitorResult left = stack.pop();\n        if (ctx.opt.getType() == RuleSetLexer.SUB) {\n            stack.push(VisitorResult.value(left.getNumber() - right.getNumber()));\n            return;\n        }\n        if (left.isVarchar() || right.isVarchar()) {\n            stack.push(VisitorResult.value(left.toString() + right.toString()));\n        } else {\n            stack.push(VisitorResult.value(left.getNumber() + right.getNumber()));\n        }\n    }\n\n    \u002F**\n     * print在Listener模式里能做到的极限就是这样：把栈顶（也就是括号里那个calcu的结果）打印出来。\n     * 至于要不要打印、打印哪个分支——Listener完全说了不算，ParseTreeWalker会无条件地把语法树上\n     * 每一个print都走一遍，不管它是不是应该在这次执行中被跳过\n     *\u002F\n    @Override\n    public void exitPrintValue(RuleSetParser.PrintValueContext ctx) {\n        System.out.println(stack.pop());\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意这里\u003Ccode>括号\u003C\u002Fcode>（\u003Ccode>parens\u003C\u002Fcode>）不需要任何处理——括号内的\u003Ccode>calcu\u003C\u002Fcode>在这之前已经\u003Ccode>exit\u003C\u002Fcode>过了，结果已经在栈顶，括号本身对结果没有任何影响，什么都不用做。这份代码没有继承任何东西和Scope相关的逻辑，因为一旦涉及到变量读写、函数调用这些需要维护状态的能力，Listener能做的事和Visitor其实也差不太多（无非是把状态维护逻辑搬到Listener类的字段里），真正的差异不在\"能不能维护状态\"，而在下面这一节。\u003C\u002Fp>\n\u003Cp>拿两个纯计算的表达式验证一下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">String numExpression = \"(3 + 4) * 2 - 5\";\nSystem.out.println(\"Listener计算结果: \" + walkCalcu(numExpression));\n\nString strExpression = \"\\\"Hello, \\\" + \\\"Listener\\\"\";\nSystem.out.println(\"Listener计算结果: \" + walkCalcu(strExpression));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其中\u003Ccode>walkCalcu\u003C\u002Fcode>直接用\u003Ccode>parser.calcu()\u003C\u002Fcode>单独解析一段\u003Ccode>calcu\u003C\u002Fcode>规则（这个技巧在第⑨篇文章设置条件断点时用过），然后用\u003Ccode>ParseTreeWalker.DEFAULT.walk(listener, calcuTree)\u003C\u002Fcode>触发遍历：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static VisitorResult walkCalcu(String expression) {\n    RuleSetLexer lexer = new RuleSetLexer(CharStreams.fromString(expression));\n    RuleSetParser parser = new RuleSetParser(new CommonTokenStream(lexer));\n    RuleSetParser.CalcuContext calcuTree = parser.calcu();\n    MyRuleSetListener listener = new MyRuleSetListener();\n    ParseTreeWalker.DEFAULT.walk(listener, calcuTree);\n    return listener.getResult();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>表达式: (3 + 4) * 2 - 5\nListener计算结果: 9.0\n\n表达式: \"Hello, \" + \"Listener\"\nListener计算结果: Hello, Listener\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>纯计算、没有分支的场景下，Listener和Visitor算出来的结果完全一样。\u003C\u002Fp>\n\u003Ch2>三、Listener做不到什么：无法\"跳过\"一个分支\u003C\u002Fh2>\n\u003Cp>现在来看真正的核心差异。写一段最简单的\u003Ccode>if\u002Felse\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>if (false) {\n    print(\"A - if分支，条件为false，不应该被打印\")\n} else {\n    print(\"B - else分支，应该被打印\")\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>按正常的语义，\u003Ccode>false\u003C\u002Fcode>让条件不成立，应该只执行\u003Ccode>else\u003C\u002Fcode>分支，只打印\u003Ccode>B\u003C\u002Fcode>。分别用Listener和Visitor跑同一段代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">RuleSetParser.ProgContext prog = parser.prog();\n\nSystem.out.println(\"[Listener方式的执行结果]\");\nMyRuleSetListener listener = new MyRuleSetListener();\nParseTreeWalker.DEFAULT.walk(listener, prog);\n\nSystem.out.println(\"[Visitor方式的执行结果]\");\nMyRuleSetVisitor visitor = new MyRuleSetVisitor();\nvisitor.visit(prog);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>[Listener方式的执行结果]\nA - if分支，条件为false，不应该被打印\nB - else分支，应该被打印\n\n[Visitor方式的执行结果]\nB - else分支，应该被打印\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Listener把\u003Ccode>A\u003C\u002Fcode>和\u003Ccode>B\u003C\u002Fcode>\u003Cstrong>都\u003C\u002Fstrong>打印出来了！原因就在开头说的那句话：\u003Ccode>ParseTreeWalker\u003C\u002Fcode>不管条件是\u003Ccode>true\u003C\u002Fcode>还是\u003Ccode>false\u003C\u002Fcode>，都会无条件地把语法树上的每一个节点走一遍，包括\u003Ccode>if\u003C\u002Fcode>分支的\u003Ccode>block\u003C\u002Fcode>和\u003Ccode>else\u003C\u002Fcode>分支的\u003Ccode>block\u003C\u002Fcode>，我们的\u003Ccode>MyRuleSetListener\u003C\u002Fcode>压根没有、也没办法拿到\"条件求值结果\"来决定该不该执行\u003Ccode>exitPrintValue\u003C\u002Fcode>里的打印逻辑——\u003Ccode>cond\u003C\u002Fcode>节点即使被\u003Ccode>enter\u003C\u002Fcode>\u002F\u003Ccode>exit\u003C\u002Fcode>了，Listener也只能被动地\"看着\"它过去，不能拿这个结果去控制接下来该不该继续走某个子树。\u003C\u002Fp>\n\u003Cp>反观我们从第①篇用到现在的Visitor实现，\u003Ccode>visitIfStmt\u003C\u002Fcode>里的逻辑是这样的（这是第④篇文章实现的代码）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">@Override\npublic VisitorResult visitIfStmt(RuleSetParser.IfStmtContext ctx) {\n    List&lt;RuleSetParser.CondContext&gt; conds = ctx.cond();\n    List&lt;RuleSetParser.BlockContext&gt; blocks = ctx.block();\n    for (int i = 0; i &lt; conds.size(); i++) {\n        if (visit(conds.get(i)).getBool()) {   \u002F\u002F 关键：条件不满足，压根不会去visit对应的block\n            return visit(blocks.get(i));\n        }\n    }\n    if (blocks.size() &gt; conds.size()) {\n        return visit(blocks.get(blocks.size() - 1));\n    }\n    return VisitorResult.nil();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>visit(conds.get(i)).getBool()\u003C\u002Fcode>先求出条件的真假，只有为真时才会调用\u003Ccode>visit(blocks.get(i))\u003C\u002Fcode>——\u003Cstrong>根本不满足条件的那个\u003Ccode>block\u003C\u002Fcode>，Visitor连进都不会进去\u003C\u002Fstrong>，这才是\u003Ccode>if\u002Felse\u003C\u002Fcode>应该有的语义。\u003Ccode>while\u003C\u002Fcode>循环、\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>、函数调用\u002F\u003Ccode>return\u003C\u002Fcode>、短路的\u003Ccode>&amp;&amp;\u003C\u002Fcode>\u002F\u003Ccode>||\u003C\u002Fcode>，本系列实现过的所有控制流能力，无一例外都依赖这种\"由代码自己决定要不要访问某个子节点\"的能力，而这恰恰是Listener从设计上就不具备的。\u003C\u002Fp>\n\u003Ch2>四、什么时候该用Listener\u003C\u002Fh2>\n\u003Cp>看到这里可能会觉得Listener像是个\"残缺\"的方案，但它并不是为了实现解释器而设计的，它更适合的场景是：\u003Cstrong>需要把语法树完整地过一遍、单纯地收集信息或者做一些互不依赖的检查，不需要根据某个节点的值决定要不要处理其他节点\u003C\u002Fstrong>。典型的例子：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>静态检查\u002F语法风格检查（linter）：比如扫描代码里有没有定义了却从来没用过的变量，需要把每一个变量的定义和使用都看一遍，和\"运行到哪一行\"完全无关。\u003C\u002Fli>\n \u003Cli>代码格式化、语法高亮：同样需要遍历到每一个token\u002F节点，按节点类型输出对应的格式，不涉及\"选择性执行\"。\u003C\u002Fli>\n \u003Cli>生成符号表、做简单的重命名\u002F代码转换：收集信息或者做等价的文本替换，本质上也是要把每个相关节点都处理一遍。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>另外，多个不相关的Listener还可以用同一个\u003Ccode>ParseTreeWalker\u003C\u002Fcode>合并在一次遍历里跑（antlr4支持给\u003Ccode>walk\u003C\u002Fcode>传入组合多个Listener的场景），如果你需要同时做几件互不相关的静态分析，用Listener可以避免多次遍历同一棵树，这是Visitor不太容易做到的一个优势——但这和\"实现一门带控制流的语言\"是两个完全不同的需求。\u003C\u002Fp>\n\u003Cp>一句话总结：\u003Cstrong>需要\"选择性地\"\"带着状态和结果\"去驱动树的遍历（比如解释执行一门语言）用Visitor；只需要\"完整地\"遍历一遍树、单纯做观察或收集用Listener\u003C\u002Fstrong>。这也是本系列从第一篇到现在，实现语言解释器的核心逻辑都构建在\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>之上、而不是Listener之上的根本原因。\u003C\u002Fp>",[29],"Antlr4","index,follow",{"code":4,"message":5,"data":32},[33,41,49],{"id":34,"slug":35,"title":36,"summary":37,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":38,"readingMinutes":39,"publishedAt":40},"2101584400070656001","antlr4-02-syntax-check-and-priority","Antlr4系列②：语法检查和多规则的优先级","BaseErrorListener做词法\u002F语法校验、calcu递归支持连续混合运算、用opt=别名解决优先级问题",5762,15,"2026-09-20T16:09:01",{"id":42,"slug":43,"title":44,"summary":45,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":46,"readingMinutes":47,"publishedAt":48},"2101608263357743105","antlr4-05-loops","Antlr4系列⑤：支持循环语句","while循环、break\u002Fcontinue（用异常做控制流跳转）",4801,13,"2026-09-20T17:44:00",{"id":50,"slug":51,"title":52,"summary":53,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":54,"readingMinutes":39,"publishedAt":55},"2101615963315552258","antlr4-06-scope-chain","Antlr4系列⑥：作用域链与变量赋值","Scope作用域链、区分\"定义\"与\"赋值\"、修复变量泄漏",5701,"2026-09-20T18:14:34",{"code":4,"message":5,"data":57},{"prev":58,"next":20},{"id":59,"slug":60,"title":61,"summary":62,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":63,"readingMinutes":39,"publishedAt":64},"2101647962931642369","antlr4-12-string-concat-expressions","Antlr4系列⑫：字符串拼接与通用表达式","calcu升级为通用表达式、字符串拼接、setArg\u002FassignStmt\u002Fprint合并简化",5692,"2026-09-20T20:21:45",{"code":4,"message":5,"data":66},[67,72],{"id":21,"name":68,"slug":69,"description":70,"sortOrder":4,"articleCount":71},"技术","tech","编程与技术笔记","13",{"id":73,"name":74,"slug":75,"description":76,"sortOrder":22,"articleCount":77},"1946000000000000002","随笔","essay","生活与思考","0",{"html":79,"toc":80},"\u003Cp>本系列从第一篇开始，一直用的都是antlr4提供的两种业务逻辑实现方式之一——\u003Cstrong>访问器（Visitor）\u003C\u002Fstrong>。第①篇文章末尾其实提到过，antlr4还提供了另一种方式：\u003Cstrong>监听器（Listener）\u003C\u002Fstrong>。这篇文章我们就用Listener重新实现一部分语言能力，通过实际跑起来的例子，直观地看看这两种模式的核心区别，以及为什么这个系列从头到尾都选择了Visitor。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、Listener和Visitor的核心区别\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>Visitor\u003C\u002Fstrong>：由你自己的代码调用\u003Ccode>visit(ctx)\u003C\u002Fcode>来决定\"要不要访问某个子节点、访问哪个、什么时候访问、访问完之后返回什么\"。整个遍历过程的控制权在你手里。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Listener\u003C\u002Fstrong>：由antlr4提供的\u003Ccode>ParseTreeWalker\u003C\u002Fcode>来遍历整棵语法树，规则是固定的——\u003Cstrong>从根节点开始，按照子节点在树上的顺序，无条件地把每一个节点都走一遍\u003C\u002Fstrong>，进入节点时调用\u003Ccode>enterXxx\u003C\u002Fcode>，离开节点时调用\u003Ccode>exitXxx\u003C\u002Fcode>。这两个方法都是\u003Ccode>void\u003C\u002Fcode>，没有返回值，Listener本身也没有任何办法告诉\u003Ccode>ParseTreeWalker\u003C\u002Fcode>\"这个子树不用走了\"。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这个区别听起来有点抽象，我们直接上代码看会更清楚。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、Listener能做什么：用栈模拟\"返回值\"\u003C\u002Fh2>\n\u003Cp>\u003Ccode>enterXxx\u003C\u002Fcode>\u002F\u003Ccode>exitXxx\u003C\u002Fcode>没有返回值，但我们又需要把子节点算出来的中间结果传递给父节点（比如两个数相加，得先知道这两个数分别是多少）。经典的做法是\u003Cstrong>用一个栈来模拟返回值的传递\u003C\u002Fstrong>：\u003Ccode>ParseTreeWalker\u003C\u002Fcode>对语法树做的是后序遍历，也就是说一个节点的所有子节点一定会先于这个节点被\u003Ccode>exit\u003C\u002Fcode>。等父节点的\u003Ccode>exitXxx\u003C\u002Fcode>被调用时，它的子节点早就已经把各自的计算结果压进栈里了，直接按顺序弹出来用即可。\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MyRuleSetListener\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuleSetBaseListener\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Deque&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">VisitorResult\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> stack \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayDeque&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getResult\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">peek\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.NumberContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Double.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">parseDouble\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitStringValue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.StringValueContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        String text \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(text.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">substring\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, text.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">length\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitBoolValue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.BoolValueContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Boolean.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">parseBoolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitMulAndDiv\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.MulAndDivContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 右操作数在左操作数之后被visit，所以先出栈的是右操作数\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        double\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        double\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> left \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ctx.opt.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> RuleSetLexer.MUL) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(left \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(left \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitAddAndSub\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.AddAndSubContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        VisitorResult right \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        VisitorResult left \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ctx.opt.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> RuleSetLexer.SUB) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(left.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (left.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">isVarchar\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">||\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">isVarchar\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(left.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(left.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> right.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     * print在Listener模式里能做到的极限就是这样：把栈顶（也就是括号里那个calcu的结果）打印出来。\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     * 至于要不要打印、打印哪个分支——Listener完全说了不算，ParseTreeWalker会无条件地把语法树上\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     * 每一个print都走一遍，不管它是不是应该在这次执行中被跳过\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     *\u002F\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> exitPrintValue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.PrintValueContext \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(stack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意这里\u003Ccode>括号\u003C\u002Fcode>（\u003Ccode>parens\u003C\u002Fcode>）不需要任何处理——括号内的\u003Ccode>calcu\u003C\u002Fcode>在这之前已经\u003Ccode>exit\u003C\u002Fcode>过了，结果已经在栈顶，括号本身对结果没有任何影响，什么都不用做。这份代码没有继承任何东西和Scope相关的逻辑，因为一旦涉及到变量读写、函数调用这些需要维护状态的能力，Listener能做的事和Visitor其实也差不太多（无非是把状态维护逻辑搬到Listener类的字段里），真正的差异不在\"能不能维护状态\"，而在下面这一节。\u003C\u002Fp>\n\u003Cp>拿两个纯计算的表达式验证一下：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String numExpression \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"(3 + 4) * 2 - 5\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Listener计算结果: \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> walkCalcu\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(numExpression));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String strExpression \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\\"\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">Hello, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\\"\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> + \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\\"\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">Listener\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\\"\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Listener计算结果: \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> walkCalcu\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(strExpression));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其中\u003Ccode>walkCalcu\u003C\u002Fcode>直接用\u003Ccode>parser.calcu()\u003C\u002Fcode>单独解析一段\u003Ccode>calcu\u003C\u002Fcode>规则（这个技巧在第⑨篇文章设置条件断点时用过），然后用\u003Ccode>ParseTreeWalker.DEFAULT.walk(listener, calcuTree)\u003C\u002Fcode>触发遍历：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">walkCalcu\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String expression) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    RuleSetLexer lexer \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuleSetLexer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(CharStreams.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fromString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(expression));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    RuleSetParser parser \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuleSetParser\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CommonTokenStream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(lexer));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    RuleSetParser.CalcuContext calcuTree \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> parser.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calcu\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    MyRuleSetListener listener \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MyRuleSetListener\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    ParseTreeWalker.DEFAULT.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">walk\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(listener, calcuTree);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> listener.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getResult\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>表达式: (3 + 4) * 2 - 5\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Listener计算结果: 9.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>表达式: \"Hello, \" + \"Listener\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Listener计算结果: Hello, Listener\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>纯计算、没有分支的场景下，Listener和Visitor算出来的结果完全一样。\u003C\u002Fp>\n\u003Ch2 id=\"toc-3\">三、Listener做不到什么：无法\"跳过\"一个分支\u003C\u002Fh2>\n\u003Cp>现在来看真正的核心差异。写一段最简单的\u003Ccode>if\u002Felse\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>if (false) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    print(\"A - if分支，条件为false，不应该被打印\")\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>} else {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    print(\"B - else分支，应该被打印\")\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>按正常的语义，\u003Ccode>false\u003C\u002Fcode>让条件不成立，应该只执行\u003Ccode>else\u003C\u002Fcode>分支，只打印\u003Ccode>B\u003C\u002Fcode>。分别用Listener和Visitor跑同一段代码：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">RuleSetParser.ProgContext prog \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> parser.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">prog\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[Listener方式的执行结果]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">MyRuleSetListener listener \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MyRuleSetListener\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">ParseTreeWalker.DEFAULT.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">walk\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(listener, prog);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[Visitor方式的执行结果]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">MyRuleSetVisitor visitor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MyRuleSetVisitor\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">visitor.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(prog);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果如下：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>[Listener方式的执行结果]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>A - if分支，条件为false，不应该被打印\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>B - else分支，应该被打印\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[Visitor方式的执行结果]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>B - else分支，应该被打印\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Listener把\u003Ccode>A\u003C\u002Fcode>和\u003Ccode>B\u003C\u002Fcode>\u003Cstrong>都\u003C\u002Fstrong>打印出来了！原因就在开头说的那句话：\u003Ccode>ParseTreeWalker\u003C\u002Fcode>不管条件是\u003Ccode>true\u003C\u002Fcode>还是\u003Ccode>false\u003C\u002Fcode>，都会无条件地把语法树上的每一个节点走一遍，包括\u003Ccode>if\u003C\u002Fcode>分支的\u003Ccode>block\u003C\u002Fcode>和\u003Ccode>else\u003C\u002Fcode>分支的\u003Ccode>block\u003C\u002Fcode>，我们的\u003Ccode>MyRuleSetListener\u003C\u002Fcode>压根没有、也没办法拿到\"条件求值结果\"来决定该不该执行\u003Ccode>exitPrintValue\u003C\u002Fcode>里的打印逻辑——\u003Ccode>cond\u003C\u002Fcode>节点即使被\u003Ccode>enter\u003C\u002Fcode>\u002F\u003Ccode>exit\u003C\u002Fcode>了，Listener也只能被动地\"看着\"它过去，不能拿这个结果去控制接下来该不该继续走某个子树。\u003C\u002Fp>\n\u003Cp>反观我们从第①篇用到现在的Visitor实现，\u003Ccode>visitIfStmt\u003C\u002Fcode>里的逻辑是这样的（这是第④篇文章实现的代码）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitIfStmt\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.IfStmtContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">RuleSetParser\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">CondContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> conds \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">cond\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">RuleSetParser\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">BlockContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> blocks \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">block\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> conds.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(); i\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(conds.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBool\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 关键：条件不满足，压根不会去visit对应的block\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(blocks.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (blocks.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> conds.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(blocks.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(blocks.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">nil\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>visit(conds.get(i)).getBool()\u003C\u002Fcode>先求出条件的真假，只有为真时才会调用\u003Ccode>visit(blocks.get(i))\u003C\u002Fcode>——\u003Cstrong>根本不满足条件的那个\u003Ccode>block\u003C\u002Fcode>，Visitor连进都不会进去\u003C\u002Fstrong>，这才是\u003Ccode>if\u002Felse\u003C\u002Fcode>应该有的语义。\u003Ccode>while\u003C\u002Fcode>循环、\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>、函数调用\u002F\u003Ccode>return\u003C\u002Fcode>、短路的\u003Ccode>&amp;&amp;\u003C\u002Fcode>\u002F\u003Ccode>||\u003C\u002Fcode>，本系列实现过的所有控制流能力，无一例外都依赖这种\"由代码自己决定要不要访问某个子节点\"的能力，而这恰恰是Listener从设计上就不具备的。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、什么时候该用Listener\u003C\u002Fh2>\n\u003Cp>看到这里可能会觉得Listener像是个\"残缺\"的方案，但它并不是为了实现解释器而设计的，它更适合的场景是：\u003Cstrong>需要把语法树完整地过一遍、单纯地收集信息或者做一些互不依赖的检查，不需要根据某个节点的值决定要不要处理其他节点\u003C\u002Fstrong>。典型的例子：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>静态检查\u002F语法风格检查（linter）：比如扫描代码里有没有定义了却从来没用过的变量，需要把每一个变量的定义和使用都看一遍，和\"运行到哪一行\"完全无关。\u003C\u002Fli>\n \u003Cli>代码格式化、语法高亮：同样需要遍历到每一个token\u002F节点，按节点类型输出对应的格式，不涉及\"选择性执行\"。\u003C\u002Fli>\n \u003Cli>生成符号表、做简单的重命名\u002F代码转换：收集信息或者做等价的文本替换，本质上也是要把每个相关节点都处理一遍。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>另外，多个不相关的Listener还可以用同一个\u003Ccode>ParseTreeWalker\u003C\u002Fcode>合并在一次遍历里跑（antlr4支持给\u003Ccode>walk\u003C\u002Fcode>传入组合多个Listener的场景），如果你需要同时做几件互不相关的静态分析，用Listener可以避免多次遍历同一棵树，这是Visitor不太容易做到的一个优势——但这和\"实现一门带控制流的语言\"是两个完全不同的需求。\u003C\u002Fp>\n\u003Cp>一句话总结：\u003Cstrong>需要\"选择性地\"\"带着状态和结果\"去驱动树的遍历（比如解释执行一门语言）用Visitor；只需要\"完整地\"遍历一遍树、单纯做观察或收集用Listener\u003C\u002Fstrong>。这也是本系列从第一篇到现在，实现语言解释器的核心逻辑都构建在\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>之上、而不是Listener之上的根本原因。\u003C\u002Fp>",[81,85,88,91],{"id":82,"text":83,"level":84},"toc-1","一、Listener和Visitor的核心区别",2,{"id":86,"text":87,"level":84},"toc-2","二、Listener能做什么：用栈模拟\"返回值\"",{"id":89,"text":90,"level":84},"toc-3","三、Listener做不到什么：无法\"跳过\"一个分支",{"id":92,"text":93,"level":84},"toc-4","四、什么时候该用Listener"]