[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"$f3rymhgiq8sutd":14,"$f3qprsmrrz7qi9":31,"$fi5svhjmjakeg":56,"$f2hf8zv88r59zg":60,"render-antlr4-06-scope-chain":73},{"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},"2101615963315552258","antlr4-06-scope-chain","Antlr4系列⑥：作用域链与变量赋值","Scope作用域链、区分\"定义\"与\"赋值\"、修复变量泄漏",null,"1946000000000000001",1,5701,15,"2026-09-20T18:14:34","在前两篇文章中我们分别实现了`if \u002F else if \u002F else`分支语句和`while`循环语句，并且在这两篇文章的结尾都留了同一个问题：目前所有变量都保存在`MyRuleSetVisitor`里唯一的一个全局`Map`（`paramDefine`）中，代码块内部定义的变量出了代码块依然能访问到，\"定义变量\"和\"给变量重新赋值\"也一直是同一回事。本篇文章我们就来解决这两个问题：引入作用域链，并把\"定义\"和\"赋值\"拆成两种不同的语法。\n\n## 一、问题重现\n\n在动手之前，先分析一下上一篇`while`循环的例子，如果不做任何改动会发生什么。假设我们给`block`引入独立的变量表（这正是本篇要做的事），循环体里继续沿用上一篇`number i = i + 1`的写法：\n\n```\nnumber i = 0\nwhile (i \u003C 10) {\n    number i = i + 1   \u002F\u002F 仍然使用number关键字\n    print(i)\n}\n```\n\n`while`的条件`i \u003C 10`是在`while`所在的外层作用域里判断的，而循环体`{ }`每次进入时都会拿到一个**新的**子作用域。`number i = i + 1`是\"定义\"语句，定义永远只会作用于当前（也就是子）作用域——所以每次循环体都会在子作用域里新建一个`i`，用完之后子作用域被销毁，外层的`i`从始至终都没有被修改过，`while`的条件永远是`0 \u003C 10`，**这会是一个死循环**。\n\n这说明只有作用域是不够的，还必须能够表达\"我要修改的是外层已经存在的这个变量\"，而不是\"在当前作用域新建一个同名变量\"，这正是\"赋值\"和\"定义\"的本质区别。\n\n## 二、语法设计\n\n1. 作用域\n\n    - 每个`block`拥有自己独立的变量表。\n    - `block`内新定义的变量，只在这个`block`内部（含嵌套的`block`）可见，离开`block`后失效。\n    - `block`内部可以访问外层作用域已经定义的变量，也可以定义一个和外层同名的变量来\"遮盖\"外层的变量（术语叫遮蔽\u002Fshadowing），不影响外层变量。\n\n2. 赋值语句\n\n    新增一种不带类型关键字的语句，专门用来修改一个**已经存在**的变量的值：`变量名 = 值`。和`setArg`的区别是：`setArg`（`number`\u002F`string`\u002F`boolean`开头）永远是在当前作用域新建一个变量；新增的赋值语句则是沿着作用域链，找到变量真正定义的那一层作用域并修改它，如果一路找到最外层还是找不到，说明变量根本没有定义过，应该报错，不允许\"顺手\"定义。\n\n## 三、语法定义\n\n根据以上设计新增`assignStmt`语法：\n```\nmain: setArg SEMI?\n    | assignStmt SEMI?                                      \u002F\u002F 新增\n    | print SEMI?\n    | ifStmt\n    | whileStmt\n    | breakStmt\n    | continueStmt\n    ;\n\n\u002F\u002F 新增：给已经定义过的变量重新赋值，与setArg的区别是不需要number\u002Fstring\u002Fboolean类型关键字\nassignStmt: id = ID '=' calcu                              #numAssign\n          | id = ID '=' STRING                              #charAssign\n          | id = ID '=' BOOL                                #boolAssign\n          ;                                                  \u002F\u002F 新增\n```\n`assignStmt`的写法和`setArg`几乎一模一样，只是去掉了开头的类型关键字，同样按等号右边是数值表达式、字符串还是布尔值分成三种规则。这里不需要新增任何词法，也不会和已有语法产生歧义——因为`main`目前的几个分支里，只有`assignStmt`是以裸的`ID`开头的（`setArg`以`number`\u002F`string`\u002F`boolean`开头，`print`以`PRINT`开头，`ifStmt`以`IF`开头，以此类推），antlr4只需要看一个token就能确定接下来要按哪个分支解析。\n\n## 四、作用域的实现\n\n新建一个`Scope`类表示一个作用域：\n\n```java\n\u002F**\n * 作用域，每个block在执行时会创建一个新的Scope，parent指向外层作用域，多个Scope串联起来形成作用域链\n * 定义(define)永远作用于当前作用域；赋值(assign)会沿着作用域链向外查找变量真正所在的作用域并修改\n *\u002F\npublic class Scope {\n\n    private final Scope parent;\n\n    private final Map\u003CString, Object> variables = new HashMap\u003C>();\n\n    public Scope(Scope parent) {\n        this.parent = parent;\n    }\n\n    public Scope getParent() {\n        return parent;\n    }\n\n    \u002F**\n     * 在当前作用域定义一个新变量，如果当前作用域已存在同名变量则覆盖，不影响外层作用域中的同名变量\n     *\u002F\n    public void define(String name, Object value) {\n        variables.put(name, value);\n    }\n\n    \u002F**\n     * 给已经定义过的变量赋值，从当前作用域开始沿着作用域链向外查找变量真正定义的位置并修改\n     *\n     * @return 变量存在并赋值成功返回true，变量未定义返回false\n     *\u002F\n    public boolean assign(String name, Object value) {\n        Scope scope = findDefiningScope(name);\n        if (scope == null) {\n            return false;\n        }\n        scope.variables.put(name, value);\n        return true;\n    }\n\n    \u002F**\n     * 读取变量的值，从当前作用域开始沿着作用域链向外查找\n     *\u002F\n    public Object get(String name) {\n        Scope scope = findDefiningScope(name);\n        return scope == null ? null : scope.variables.get(name);\n    }\n\n    private Scope findDefiningScope(String name) {\n        Scope scope = this;\n        while (scope != null) {\n            if (scope.variables.containsKey(name)) {\n                return scope;\n            }\n            scope = scope.parent;\n        }\n        return null;\n    }\n}\n```\n`Scope`内部用`parent`指向外层作用域，多个`Scope`用`parent`串起来就形成了一条链。`define`只操作当前这一层的`Map`，`get`和`assign`都是从当前层开始，沿着`parent`一路往外找，找到第一个包含该变量名的作用域为止——这就是\"作用域链\"名字的由来。\n\n## 五、语法实现\n\n`MyRuleSetVisitor`不再持有一个全局的`Map`，而是持有一个当前作用域`currentScope`，初始时是没有`parent`的最外层（全局）作用域：\n\n```java\n\u002F**\n * 当前所在的作用域，初始为最外层的全局作用域，进入block时会切换到为该block新建的子作用域，\n * 离开block时切回原来的作用域，多个Scope构成一条作用域链\n *\u002F\nprivate Scope currentScope = new Scope(null);\n```\n\n之前所有操作`paramDefine`的地方，\"定义\"类的操作（`visitNumarg`\u002F`visitChararg`\u002F`visitBoolarg`）改成调用`currentScope.define(...)`，\"读取\"类的操作（`visitArgValue`\u002F`visitPrintArg`\u002F`visitCondArgValue`）改成调用`currentScope.get(...)`，这部分只是简单的替换，不再逐一贴出。\n\n真正的关键改动在`visitBlock`：\n\n```java\n\u002F**\n * 代码块，进入时基于当前作用域新建一个子作用域并切换过去，块内定义的变量只会保存在这个子作用域中；\n * 依次执行块内的每一条main语句；无论正常结束还是因为break\u002Fcontinue异常提前退出，\n * 离开时都要用finally切回原来的作用域，避免作用域链混乱\n *\u002F\n@Override\npublic VisitorResult visitBlock(RuleSetParser.BlockContext ctx) {\n    currentScope = new Scope(currentScope);\n    try {\n        for (RuleSetParser.MainContext mainCtx : ctx.main()) {\n            visit(mainCtx);\n        }\n    } finally {\n        currentScope = currentScope.getParent();\n    }\n    return VisitorResult.nil();\n}\n```\n> 关于`try\u002Ffinally`：上一篇文章中`break`\u002F`continue`是通过抛出异常来实现跳转的，异常会一路穿过`visitBlock`向外抛。如果不用`finally`，一旦block内部抛出了`BreakException`\u002F`ContinueException`，`currentScope = currentScope.getParent()`这一行就会被跳过，作用域永远不会被正确地切回去，后续代码用到的`currentScope`就会是一个已经不该存在的子作用域，引发莫名其妙的bug。`try\u002Ffinally`保证不管block是正常执行完，还是被异常提前中断，都会正确地把作用域切回上一层。\n\n接着实现赋值语句，三种类型的公共逻辑抽成一个私有方法：\n\n```java\n\u002F**\n * 给已定义的数值型变量重新赋值，沿作用域链找到变量真正所在的作用域后修改\n *\u002F\n@Override\npublic VisitorResult visitNumAssign(RuleSetParser.NumAssignContext ctx) {\n    VisitorResult visit = visit(ctx.calcu());\n    assign(ctx.id.getText(), visit.getNumber());\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 给已定义的字符型变量重新赋值\n *\u002F\n@Override\npublic VisitorResult visitCharAssign(RuleSetParser.CharAssignContext ctx) {\n    assign(ctx.id.getText(), ctx.STRING().getText());\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 给已定义的布尔型变量重新赋值\n *\u002F\n@Override\npublic VisitorResult visitBoolAssign(RuleSetParser.BoolAssignContext ctx) {\n    assign(ctx.id.getText(), Boolean.parseBoolean(ctx.BOOL().getText()));\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 赋值的公共逻辑：沿作用域链查找变量真正定义的位置并修改，找不到说明变量未定义，赋值前必须先用\n * number\u002Fstring\u002Fboolean定义\n *\u002F\nprivate void assign(String name, Object value) {\n    if (!currentScope.assign(name, value)) {\n        throw new RuntimeException(\"未定义的变量：\" + name + \"，赋值前需要先用number\u002Fstring\u002Fboolean定义\");\n    }\n}\n```\n\n## 六、测试代码与执行结果\n\n分三个用例验证：循环里用真正的赋值语句实现自增、代码块内的变量不会影响外层同名变量、给未定义的变量赋值会报错。\n\n```java\nString loopExpression =\n        \"number i = 0 \\n\" +\n                \"while (i \u003C 10) { \\n\" +\n                \"    i = i + 1 \\n\" +               \u002F\u002F 不再需要number关键字\n                \"    if (i == 5) { \\n\" +\n                \"        continue \\n\" +\n                \"    } \\n\" +\n                \"    if (i == 8) { \\n\" +\n                \"        break \\n\" +\n                \"    } \\n\" +\n                \"    print(i) \\n\" +\n                \"}\";\ncalcute(loopExpression);\n\nString scopeExpression =\n        \"number a = 1 \\n\" +\n                \"if (a == 1) { \\n\" +\n                \"    number a = 100 \\n\" +           \u002F\u002F if代码块内重新定义a，遮蔽外层的a\n                \"    print(a) \\n\" +\n                \"} \\n\" +\n                \"print(a)\";                          \u002F\u002F 离开代码块后应该还是外层的a\ncalcute(scopeExpression);\n\nString errorExpression = \"b = 1\";                    \u002F\u002F b从未定义过，直接赋值应当报错\ncalcute(errorExpression);\n```\n\n执行结果如下：\n\n```\n执行:\nnumber i = 0\nwhile (i \u003C 10) {\n    i = i + 1\n    if (i == 5) {\n        continue\n    }\n    if (i == 8) {\n        break\n    }\n    print(i)\n}\n\n1.0\n2.0\n3.0\n4.0\n6.0\n7.0\n执行:\nnumber a = 1\nif (a == 1) {\n    number a = 100\n    print(a)\n}\nprint(a)\n\n100.0\n1.0\n执行:\nb = 1\n\nException in thread \"main\" java.lang.RuntimeException: 未定义的变量：b，赋值前需要先用number\u002Fstring\u002Fboolean定义\n\tat cn.irule.MyRuleSetVisitor.assign(MyRuleSetVisitor.java:145)\n\tat cn.irule.MyRuleSetVisitor.visitNumAssign(MyRuleSetVisitor.java:117)\n\t......\n```\n\n第一段验证了改用真正的赋值语句后，循环不再死循环，结果和上一篇文章完全一致；第二段验证了`if`代码块内用`number a = 100`重新定义的`a`只在块内生效，打印出`100.0`，离开代码块后外层的`a`丝毫没有受到影响，打印出`1.0`；第三段验证了对一个从未定义过的变量`b`直接赋值会抛出运行时异常，提示必须先定义，符合预期。\n\n## 七、遗留的问题\n\n至此，我们的小型DSL已经具备了变量、四则运算、字符串\u002F布尔类型、分支、循环、作用域这些编程语言的基本要素，但还缺一样东西——**函数**。目前所有代码都写在一个\"主程序\"里，没有办法把一段逻辑封装起来重复使用。下一篇文章我们将实现自定义函数：函数的定义、参数传递、`return`返回值，并且会引入\"调用栈\"的概念——这也是为后续实现单步调试打基础的关键一步。\n","\u003Cp>在前两篇文章中我们分别实现了\u003Ccode>if \u002F else if \u002F else\u003C\u002Fcode>分支语句和\u003Ccode>while\u003C\u002Fcode>循环语句，并且在这两篇文章的结尾都留了同一个问题：目前所有变量都保存在\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>里唯一的一个全局\u003Ccode>Map\u003C\u002Fcode>（\u003Ccode>paramDefine\u003C\u002Fcode>）中，代码块内部定义的变量出了代码块依然能访问到，\"定义变量\"和\"给变量重新赋值\"也一直是同一回事。本篇文章我们就来解决这两个问题：引入作用域链，并把\"定义\"和\"赋值\"拆成两种不同的语法。\u003C\u002Fp>\n\u003Ch2>一、问题重现\u003C\u002Fh2>\n\u003Cp>在动手之前，先分析一下上一篇\u003Ccode>while\u003C\u002Fcode>循环的例子，如果不做任何改动会发生什么。假设我们给\u003Ccode>block\u003C\u002Fcode>引入独立的变量表（这正是本篇要做的事），循环体里继续沿用上一篇\u003Ccode>number i = i + 1\u003C\u002Fcode>的写法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>number i = 0\nwhile (i &lt; 10) {\n    number i = i + 1   \u002F\u002F 仍然使用number关键字\n    print(i)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>while\u003C\u002Fcode>的条件\u003Ccode>i &lt; 10\u003C\u002Fcode>是在\u003Ccode>while\u003C\u002Fcode>所在的外层作用域里判断的，而循环体\u003Ccode>{ }\u003C\u002Fcode>每次进入时都会拿到一个\u003Cstrong>新的\u003C\u002Fstrong>子作用域。\u003Ccode>number i = i + 1\u003C\u002Fcode>是\"定义\"语句，定义永远只会作用于当前（也就是子）作用域——所以每次循环体都会在子作用域里新建一个\u003Ccode>i\u003C\u002Fcode>，用完之后子作用域被销毁，外层的\u003Ccode>i\u003C\u002Fcode>从始至终都没有被修改过，\u003Ccode>while\u003C\u002Fcode>的条件永远是\u003Ccode>0 &lt; 10\u003C\u002Fcode>，\u003Cstrong>这会是一个死循环\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>这说明只有作用域是不够的，还必须能够表达\"我要修改的是外层已经存在的这个变量\"，而不是\"在当前作用域新建一个同名变量\"，这正是\"赋值\"和\"定义\"的本质区别。\u003C\u002Fp>\n\u003Ch2>二、语法设计\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>作用域\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>每个\u003Ccode>block\u003C\u002Fcode>拥有自己独立的变量表。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>block\u003C\u002Fcode>内新定义的变量，只在这个\u003Ccode>block\u003C\u002Fcode>内部（含嵌套的\u003Ccode>block\u003C\u002Fcode>）可见，离开\u003Ccode>block\u003C\u002Fcode>后失效。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>block\u003C\u002Fcode>内部可以访问外层作用域已经定义的变量，也可以定义一个和外层同名的变量来\"遮盖\"外层的变量（术语叫遮蔽\u002Fshadowing），不影响外层变量。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>赋值语句\u003C\u002Fp>\n  \u003Cp>新增一种不带类型关键字的语句，专门用来修改一个\u003Cstrong>已经存在\u003C\u002Fstrong>的变量的值：\u003Ccode>变量名 = 值\u003C\u002Fcode>。和\u003Ccode>setArg\u003C\u002Fcode>的区别是：\u003Ccode>setArg\u003C\u002Fcode>（\u003Ccode>number\u003C\u002Fcode>\u002F\u003Ccode>string\u003C\u002Fcode>\u002F\u003Ccode>boolean\u003C\u002Fcode>开头）永远是在当前作用域新建一个变量；新增的赋值语句则是沿着作用域链，找到变量真正定义的那一层作用域并修改它，如果一路找到最外层还是找不到，说明变量根本没有定义过，应该报错，不允许\"顺手\"定义。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、语法定义\u003C\u002Fh2>\n\u003Cp>根据以上设计新增\u003Ccode>assignStmt\u003C\u002Fcode>语法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>main: setArg SEMI?\n    | assignStmt SEMI?                                      \u002F\u002F 新增\n    | print SEMI?\n    | ifStmt\n    | whileStmt\n    | breakStmt\n    | continueStmt\n    ;\n\n\u002F\u002F 新增：给已经定义过的变量重新赋值，与setArg的区别是不需要number\u002Fstring\u002Fboolean类型关键字\nassignStmt: id = ID '=' calcu                              #numAssign\n          | id = ID '=' STRING                              #charAssign\n          | id = ID '=' BOOL                                #boolAssign\n          ;                                                  \u002F\u002F 新增\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>assignStmt\u003C\u002Fcode>的写法和\u003Ccode>setArg\u003C\u002Fcode>几乎一模一样，只是去掉了开头的类型关键字，同样按等号右边是数值表达式、字符串还是布尔值分成三种规则。这里不需要新增任何词法，也不会和已有语法产生歧义——因为\u003Ccode>main\u003C\u002Fcode>目前的几个分支里，只有\u003Ccode>assignStmt\u003C\u002Fcode>是以裸的\u003Ccode>ID\u003C\u002Fcode>开头的（\u003Ccode>setArg\u003C\u002Fcode>以\u003Ccode>number\u003C\u002Fcode>\u002F\u003Ccode>string\u003C\u002Fcode>\u002F\u003Ccode>boolean\u003C\u002Fcode>开头，\u003Ccode>print\u003C\u002Fcode>以\u003Ccode>PRINT\u003C\u002Fcode>开头，\u003Ccode>ifStmt\u003C\u002Fcode>以\u003Ccode>IF\u003C\u002Fcode>开头，以此类推），antlr4只需要看一个token就能确定接下来要按哪个分支解析。\u003C\u002Fp>\n\u003Ch2>四、作用域的实现\u003C\u002Fh2>\n\u003Cp>新建一个\u003Ccode>Scope\u003C\u002Fcode>类表示一个作用域：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 作用域，每个block在执行时会创建一个新的Scope，parent指向外层作用域，多个Scope串联起来形成作用域链\n * 定义(define)永远作用于当前作用域；赋值(assign)会沿着作用域链向外查找变量真正所在的作用域并修改\n *\u002F\npublic class Scope {\n\n    private final Scope parent;\n\n    private final Map&lt;String, Object&gt; variables = new HashMap&lt;&gt;();\n\n    public Scope(Scope parent) {\n        this.parent = parent;\n    }\n\n    public Scope getParent() {\n        return parent;\n    }\n\n    \u002F**\n     * 在当前作用域定义一个新变量，如果当前作用域已存在同名变量则覆盖，不影响外层作用域中的同名变量\n     *\u002F\n    public void define(String name, Object value) {\n        variables.put(name, value);\n    }\n\n    \u002F**\n     * 给已经定义过的变量赋值，从当前作用域开始沿着作用域链向外查找变量真正定义的位置并修改\n     *\n     * @return 变量存在并赋值成功返回true，变量未定义返回false\n     *\u002F\n    public boolean assign(String name, Object value) {\n        Scope scope = findDefiningScope(name);\n        if (scope == null) {\n            return false;\n        }\n        scope.variables.put(name, value);\n        return true;\n    }\n\n    \u002F**\n     * 读取变量的值，从当前作用域开始沿着作用域链向外查找\n     *\u002F\n    public Object get(String name) {\n        Scope scope = findDefiningScope(name);\n        return scope == null ? null : scope.variables.get(name);\n    }\n\n    private Scope findDefiningScope(String name) {\n        Scope scope = this;\n        while (scope != null) {\n            if (scope.variables.containsKey(name)) {\n                return scope;\n            }\n            scope = scope.parent;\n        }\n        return null;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>Scope\u003C\u002Fcode>内部用\u003Ccode>parent\u003C\u002Fcode>指向外层作用域，多个\u003Ccode>Scope\u003C\u002Fcode>用\u003Ccode>parent\u003C\u002Fcode>串起来就形成了一条链。\u003Ccode>define\u003C\u002Fcode>只操作当前这一层的\u003Ccode>Map\u003C\u002Fcode>，\u003Ccode>get\u003C\u002Fcode>和\u003Ccode>assign\u003C\u002Fcode>都是从当前层开始，沿着\u003Ccode>parent\u003C\u002Fcode>一路往外找，找到第一个包含该变量名的作用域为止——这就是\"作用域链\"名字的由来。\u003C\u002Fp>\n\u003Ch2>五、语法实现\u003C\u002Fh2>\n\u003Cp>\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>不再持有一个全局的\u003Ccode>Map\u003C\u002Fcode>，而是持有一个当前作用域\u003Ccode>currentScope\u003C\u002Fcode>，初始时是没有\u003Ccode>parent\u003C\u002Fcode>的最外层（全局）作用域：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 当前所在的作用域，初始为最外层的全局作用域，进入block时会切换到为该block新建的子作用域，\n * 离开block时切回原来的作用域，多个Scope构成一条作用域链\n *\u002F\nprivate Scope currentScope = new Scope(null);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>之前所有操作\u003Ccode>paramDefine\u003C\u002Fcode>的地方，\"定义\"类的操作（\u003Ccode>visitNumarg\u003C\u002Fcode>\u002F\u003Ccode>visitChararg\u003C\u002Fcode>\u002F\u003Ccode>visitBoolarg\u003C\u002Fcode>）改成调用\u003Ccode>currentScope.define(...)\u003C\u002Fcode>，\"读取\"类的操作（\u003Ccode>visitArgValue\u003C\u002Fcode>\u002F\u003Ccode>visitPrintArg\u003C\u002Fcode>\u002F\u003Ccode>visitCondArgValue\u003C\u002Fcode>）改成调用\u003Ccode>currentScope.get(...)\u003C\u002Fcode>，这部分只是简单的替换，不再逐一贴出。\u003C\u002Fp>\n\u003Cp>真正的关键改动在\u003Ccode>visitBlock\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 代码块，进入时基于当前作用域新建一个子作用域并切换过去，块内定义的变量只会保存在这个子作用域中；\n * 依次执行块内的每一条main语句；无论正常结束还是因为break\u002Fcontinue异常提前退出，\n * 离开时都要用finally切回原来的作用域，避免作用域链混乱\n *\u002F\n@Override\npublic VisitorResult visitBlock(RuleSetParser.BlockContext ctx) {\n    currentScope = new Scope(currentScope);\n    try {\n        for (RuleSetParser.MainContext mainCtx : ctx.main()) {\n            visit(mainCtx);\n        }\n    } finally {\n        currentScope = currentScope.getParent();\n    }\n    return VisitorResult.nil();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>关于\u003Ccode>try\u002Ffinally\u003C\u002Fcode>：上一篇文章中\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>是通过抛出异常来实现跳转的，异常会一路穿过\u003Ccode>visitBlock\u003C\u002Fcode>向外抛。如果不用\u003Ccode>finally\u003C\u002Fcode>，一旦block内部抛出了\u003Ccode>BreakException\u003C\u002Fcode>\u002F\u003Ccode>ContinueException\u003C\u002Fcode>，\u003Ccode>currentScope = currentScope.getParent()\u003C\u002Fcode>这一行就会被跳过，作用域永远不会被正确地切回去，后续代码用到的\u003Ccode>currentScope\u003C\u002Fcode>就会是一个已经不该存在的子作用域，引发莫名其妙的bug。\u003Ccode>try\u002Ffinally\u003C\u002Fcode>保证不管block是正常执行完，还是被异常提前中断，都会正确地把作用域切回上一层。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>接着实现赋值语句，三种类型的公共逻辑抽成一个私有方法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 给已定义的数值型变量重新赋值，沿作用域链找到变量真正所在的作用域后修改\n *\u002F\n@Override\npublic VisitorResult visitNumAssign(RuleSetParser.NumAssignContext ctx) {\n    VisitorResult visit = visit(ctx.calcu());\n    assign(ctx.id.getText(), visit.getNumber());\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 给已定义的字符型变量重新赋值\n *\u002F\n@Override\npublic VisitorResult visitCharAssign(RuleSetParser.CharAssignContext ctx) {\n    assign(ctx.id.getText(), ctx.STRING().getText());\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 给已定义的布尔型变量重新赋值\n *\u002F\n@Override\npublic VisitorResult visitBoolAssign(RuleSetParser.BoolAssignContext ctx) {\n    assign(ctx.id.getText(), Boolean.parseBoolean(ctx.BOOL().getText()));\n    return VisitorResult.nil();\n}\n\n\u002F**\n * 赋值的公共逻辑：沿作用域链查找变量真正定义的位置并修改，找不到说明变量未定义，赋值前必须先用\n * number\u002Fstring\u002Fboolean定义\n *\u002F\nprivate void assign(String name, Object value) {\n    if (!currentScope.assign(name, value)) {\n        throw new RuntimeException(\"未定义的变量：\" + name + \"，赋值前需要先用number\u002Fstring\u002Fboolean定义\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>六、测试代码与执行结果\u003C\u002Fh2>\n\u003Cp>分三个用例验证：循环里用真正的赋值语句实现自增、代码块内的变量不会影响外层同名变量、给未定义的变量赋值会报错。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">String loopExpression =\n        \"number i = 0 \\n\" +\n                \"while (i &lt; 10) { \\n\" +\n                \"    i = i + 1 \\n\" +               \u002F\u002F 不再需要number关键字\n                \"    if (i == 5) { \\n\" +\n                \"        continue \\n\" +\n                \"    } \\n\" +\n                \"    if (i == 8) { \\n\" +\n                \"        break \\n\" +\n                \"    } \\n\" +\n                \"    print(i) \\n\" +\n                \"}\";\ncalcute(loopExpression);\n\nString scopeExpression =\n        \"number a = 1 \\n\" +\n                \"if (a == 1) { \\n\" +\n                \"    number a = 100 \\n\" +           \u002F\u002F if代码块内重新定义a，遮蔽外层的a\n                \"    print(a) \\n\" +\n                \"} \\n\" +\n                \"print(a)\";                          \u002F\u002F 离开代码块后应该还是外层的a\ncalcute(scopeExpression);\n\nString errorExpression = \"b = 1\";                    \u002F\u002F b从未定义过，直接赋值应当报错\ncalcute(errorExpression);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>执行:\nnumber i = 0\nwhile (i &lt; 10) {\n    i = i + 1\n    if (i == 5) {\n        continue\n    }\n    if (i == 8) {\n        break\n    }\n    print(i)\n}\n\n1.0\n2.0\n3.0\n4.0\n6.0\n7.0\n执行:\nnumber a = 1\nif (a == 1) {\n    number a = 100\n    print(a)\n}\nprint(a)\n\n100.0\n1.0\n执行:\nb = 1\n\nException in thread \"main\" java.lang.RuntimeException: 未定义的变量：b，赋值前需要先用number\u002Fstring\u002Fboolean定义\n\tat cn.irule.MyRuleSetVisitor.assign(MyRuleSetVisitor.java:145)\n\tat cn.irule.MyRuleSetVisitor.visitNumAssign(MyRuleSetVisitor.java:117)\n\t......\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第一段验证了改用真正的赋值语句后，循环不再死循环，结果和上一篇文章完全一致；第二段验证了\u003Ccode>if\u003C\u002Fcode>代码块内用\u003Ccode>number a = 100\u003C\u002Fcode>重新定义的\u003Ccode>a\u003C\u002Fcode>只在块内生效，打印出\u003Ccode>100.0\u003C\u002Fcode>，离开代码块后外层的\u003Ccode>a\u003C\u002Fcode>丝毫没有受到影响，打印出\u003Ccode>1.0\u003C\u002Fcode>；第三段验证了对一个从未定义过的变量\u003Ccode>b\u003C\u002Fcode>直接赋值会抛出运行时异常，提示必须先定义，符合预期。\u003C\u002Fp>\n\u003Ch2>七、遗留的问题\u003C\u002Fh2>\n\u003Cp>至此，我们的小型DSL已经具备了变量、四则运算、字符串\u002F布尔类型、分支、循环、作用域这些编程语言的基本要素，但还缺一样东西——\u003Cstrong>函数\u003C\u002Fstrong>。目前所有代码都写在一个\"主程序\"里，没有办法把一段逻辑封装起来重复使用。下一篇文章我们将实现自定义函数：函数的定义、参数传递、\u003Ccode>return\u003C\u002Fcode>返回值，并且会引入\"调用栈\"的概念——这也是为后续实现单步调试打基础的关键一步。\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},"2101608263357743105","antlr4-05-loops","Antlr4系列⑤：支持循环语句","while循环、break\u002Fcontinue（用异常做控制流跳转）",4801,13,"2026-09-20T17:44:00",{"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},"2101605987801022465","antlr4-04-if-else","Antlr4系列④：支持分支语句","比较运算符、逻辑运算符、if\u002Felse if\u002Felse、代码块block",6668,17,"2026-09-20T17:34:58",{"id":50,"slug":51,"title":52,"summary":53,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":54,"readingMinutes":47,"publishedAt":55},"2101619933819879425","antlr4-07-functions-and-call-stack","Antlr4系列⑦：自定义函数与调用栈","function定义\u002F调用、递归、词法作用域、调用栈防死递归",6508,"2026-09-20T18:33:25",{"code":4,"message":5,"data":57},{"prev":58,"next":59},{"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},{"id":50,"slug":51,"title":52,"summary":53,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":54,"readingMinutes":47,"publishedAt":55},{"code":4,"message":5,"data":61},[62,67],{"id":21,"name":63,"slug":64,"description":65,"sortOrder":4,"articleCount":66},"技术","tech","编程与技术笔记","12",{"id":68,"name":69,"slug":70,"description":71,"sortOrder":22,"articleCount":72},"1946000000000000002","随笔","essay","生活与思考","0",{"html":74,"toc":75},"\u003Cp>在前两篇文章中我们分别实现了\u003Ccode>if \u002F else if \u002F else\u003C\u002Fcode>分支语句和\u003Ccode>while\u003C\u002Fcode>循环语句，并且在这两篇文章的结尾都留了同一个问题：目前所有变量都保存在\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>里唯一的一个全局\u003Ccode>Map\u003C\u002Fcode>（\u003Ccode>paramDefine\u003C\u002Fcode>）中，代码块内部定义的变量出了代码块依然能访问到，\"定义变量\"和\"给变量重新赋值\"也一直是同一回事。本篇文章我们就来解决这两个问题：引入作用域链，并把\"定义\"和\"赋值\"拆成两种不同的语法。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、问题重现\u003C\u002Fh2>\n\u003Cp>在动手之前，先分析一下上一篇\u003Ccode>while\u003C\u002Fcode>循环的例子，如果不做任何改动会发生什么。假设我们给\u003Ccode>block\u003C\u002Fcode>引入独立的变量表（这正是本篇要做的事），循环体里继续沿用上一篇\u003Ccode>number i = i + 1\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>number i = 0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>while (i &#x3C; 10) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    number i = i + 1   \u002F\u002F 仍然使用number关键字\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    print(i)\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>while\u003C\u002Fcode>的条件\u003Ccode>i &lt; 10\u003C\u002Fcode>是在\u003Ccode>while\u003C\u002Fcode>所在的外层作用域里判断的，而循环体\u003Ccode>{ }\u003C\u002Fcode>每次进入时都会拿到一个\u003Cstrong>新的\u003C\u002Fstrong>子作用域。\u003Ccode>number i = i + 1\u003C\u002Fcode>是\"定义\"语句，定义永远只会作用于当前（也就是子）作用域——所以每次循环体都会在子作用域里新建一个\u003Ccode>i\u003C\u002Fcode>，用完之后子作用域被销毁，外层的\u003Ccode>i\u003C\u002Fcode>从始至终都没有被修改过，\u003Ccode>while\u003C\u002Fcode>的条件永远是\u003Ccode>0 &lt; 10\u003C\u002Fcode>，\u003Cstrong>这会是一个死循环\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>这说明只有作用域是不够的，还必须能够表达\"我要修改的是外层已经存在的这个变量\"，而不是\"在当前作用域新建一个同名变量\"，这正是\"赋值\"和\"定义\"的本质区别。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、语法设计\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>作用域\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>每个\u003Ccode>block\u003C\u002Fcode>拥有自己独立的变量表。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>block\u003C\u002Fcode>内新定义的变量，只在这个\u003Ccode>block\u003C\u002Fcode>内部（含嵌套的\u003Ccode>block\u003C\u002Fcode>）可见，离开\u003Ccode>block\u003C\u002Fcode>后失效。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>block\u003C\u002Fcode>内部可以访问外层作用域已经定义的变量，也可以定义一个和外层同名的变量来\"遮盖\"外层的变量（术语叫遮蔽\u002Fshadowing），不影响外层变量。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>赋值语句\u003C\u002Fp>\n  \u003Cp>新增一种不带类型关键字的语句，专门用来修改一个\u003Cstrong>已经存在\u003C\u002Fstrong>的变量的值：\u003Ccode>变量名 = 值\u003C\u002Fcode>。和\u003Ccode>setArg\u003C\u002Fcode>的区别是：\u003Ccode>setArg\u003C\u002Fcode>（\u003Ccode>number\u003C\u002Fcode>\u002F\u003Ccode>string\u003C\u002Fcode>\u002F\u003Ccode>boolean\u003C\u002Fcode>开头）永远是在当前作用域新建一个变量；新增的赋值语句则是沿着作用域链，找到变量真正定义的那一层作用域并修改它，如果一路找到最外层还是找不到，说明变量根本没有定义过，应该报错，不允许\"顺手\"定义。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、语法定义\u003C\u002Fh2>\n\u003Cp>根据以上设计新增\u003Ccode>assignStmt\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>main: setArg SEMI?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | assignStmt SEMI?                                      \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | print SEMI?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | ifStmt\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | whileStmt\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | breakStmt\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | continueStmt\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u002F\u002F 新增：给已经定义过的变量重新赋值，与setArg的区别是不需要number\u002Fstring\u002Fboolean类型关键字\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>assignStmt: id = ID '=' calcu                              #numAssign\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>          | id = ID '=' STRING                              #charAssign\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>          | id = ID '=' BOOL                                #boolAssign\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>          ;                                                  \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>assignStmt\u003C\u002Fcode>的写法和\u003Ccode>setArg\u003C\u002Fcode>几乎一模一样，只是去掉了开头的类型关键字，同样按等号右边是数值表达式、字符串还是布尔值分成三种规则。这里不需要新增任何词法，也不会和已有语法产生歧义——因为\u003Ccode>main\u003C\u002Fcode>目前的几个分支里，只有\u003Ccode>assignStmt\u003C\u002Fcode>是以裸的\u003Ccode>ID\u003C\u002Fcode>开头的（\u003Ccode>setArg\u003C\u002Fcode>以\u003Ccode>number\u003C\u002Fcode>\u002F\u003Ccode>string\u003C\u002Fcode>\u002F\u003Ccode>boolean\u003C\u002Fcode>开头，\u003Ccode>print\u003C\u002Fcode>以\u003Ccode>PRINT\u003C\u002Fcode>开头，\u003Ccode>ifStmt\u003C\u002Fcode>以\u003Ccode>IF\u003C\u002Fcode>开头，以此类推），antlr4只需要看一个token就能确定接下来要按哪个分支解析。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、作用域的实现\u003C\u002Fh2>\n\u003Cp>新建一个\u003Ccode>Scope\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 作用域，每个block在执行时会创建一个新的Scope，parent指向外层作用域，多个Scope串联起来形成作用域链\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 定义(define)永远作用于当前作用域；赋值(assign)会沿着作用域链向外查找变量真正所在的作用域并修改\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:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Scope\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\"> Scope parent;\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\"> Map&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Object\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> variables \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\"> HashMap&#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:#6F42C1;--shiki-dark:#B392F0\"> Scope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Scope \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">parent\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.parent \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> parent;\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:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Scope \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getParent\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\"> parent;\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\">     * 在当前作用域定义一个新变量，如果当前作用域已存在同名变量则覆盖，不影响外层作用域中的同名变量\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:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> define\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Object \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        variables.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, value);\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\">     * 给已经定义过的变量赋值，从当前作用域开始沿着作用域链向外查找变量真正定义的位置并修改\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     *\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     * \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">@return\u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> 变量存在并赋值成功返回true，变量未定义返回false\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:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Object \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Scope scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> findDefiningScope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name);\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\"> (scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\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:#005CC5;--shiki-dark:#79B8FF\"> false\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\">        scope.variables.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, value);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> true\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:#6A737D;--shiki-dark:#6A737D\">    \u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">     * 读取变量的值，从当前作用域开始沿着作用域链向外查找\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:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Object \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Scope scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> findDefiningScope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name);\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\"> scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> ?\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> :\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> scope.variables.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name);\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:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Scope \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">findDefiningScope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Scope scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        while\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\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\"> (scope.variables.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">containsKey\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name)) {\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\"> scope;\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\">            scope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> scope.parent;\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:#005CC5;--shiki-dark:#79B8FF\"> null\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>Scope\u003C\u002Fcode>内部用\u003Ccode>parent\u003C\u002Fcode>指向外层作用域，多个\u003Ccode>Scope\u003C\u002Fcode>用\u003Ccode>parent\u003C\u002Fcode>串起来就形成了一条链。\u003Ccode>define\u003C\u002Fcode>只操作当前这一层的\u003Ccode>Map\u003C\u002Fcode>，\u003Ccode>get\u003C\u002Fcode>和\u003Ccode>assign\u003C\u002Fcode>都是从当前层开始，沿着\u003Ccode>parent\u003C\u002Fcode>一路往外找，找到第一个包含该变量名的作用域为止——这就是\"作用域链\"名字的由来。\u003C\u002Fp>\n\u003Ch2 id=\"toc-5\">五、语法实现\u003C\u002Fh2>\n\u003Cp>\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>不再持有一个全局的\u003Ccode>Map\u003C\u002Fcode>，而是持有一个当前作用域\u003Ccode>currentScope\u003C\u002Fcode>，初始时是没有\u003Ccode>parent\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 当前所在的作用域，初始为最外层的全局作用域，进入block时会切换到为该block新建的子作用域，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 离开block时切回原来的作用域，多个Scope构成一条作用域链\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:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Scope currentScope \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\"> Scope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>之前所有操作\u003Ccode>paramDefine\u003C\u002Fcode>的地方，\"定义\"类的操作（\u003Ccode>visitNumarg\u003C\u002Fcode>\u002F\u003Ccode>visitChararg\u003C\u002Fcode>\u002F\u003Ccode>visitBoolarg\u003C\u002Fcode>）改成调用\u003Ccode>currentScope.define(...)\u003C\u002Fcode>，\"读取\"类的操作（\u003Ccode>visitArgValue\u003C\u002Fcode>\u002F\u003Ccode>visitPrintArg\u003C\u002Fcode>\u002F\u003Ccode>visitCondArgValue\u003C\u002Fcode>）改成调用\u003Ccode>currentScope.get(...)\u003C\u002Fcode>，这部分只是简单的替换，不再逐一贴出。\u003C\u002Fp>\n\u003Cp>真正的关键改动在\u003Ccode>visitBlock\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 代码块，进入时基于当前作用域新建一个子作用域并切换过去，块内定义的变量只会保存在这个子作用域中；\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 依次执行块内的每一条main语句；无论正常结束还是因为break\u002Fcontinue异常提前退出，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 离开时都要用finally切回原来的作用域，避免作用域链混乱\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:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitBlock\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.BlockContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    currentScope \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\"> Scope\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(currentScope);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    try\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\"> (RuleSetParser.MainContext mainCtx \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\">main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">            visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(mainCtx);\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>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">finally\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        currentScope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> currentScope.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getParent\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\u003Cblockquote>\n \u003Cp>关于\u003Ccode>try\u002Ffinally\u003C\u002Fcode>：上一篇文章中\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>是通过抛出异常来实现跳转的，异常会一路穿过\u003Ccode>visitBlock\u003C\u002Fcode>向外抛。如果不用\u003Ccode>finally\u003C\u002Fcode>，一旦block内部抛出了\u003Ccode>BreakException\u003C\u002Fcode>\u002F\u003Ccode>ContinueException\u003C\u002Fcode>，\u003Ccode>currentScope = currentScope.getParent()\u003C\u002Fcode>这一行就会被跳过，作用域永远不会被正确地切回去，后续代码用到的\u003Ccode>currentScope\u003C\u002Fcode>就会是一个已经不该存在的子作用域，引发莫名其妙的bug。\u003Ccode>try\u002Ffinally\u003C\u002Fcode>保证不管block是正常执行完，还是被异常提前中断，都会正确地把作用域切回上一层。\u003C\u002Fp>\n\u003C\u002Fblockquote>\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 给已定义的数值型变量重新赋值，沿作用域链找到变量真正所在的作用域后修改\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:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitNumAssign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.NumAssignContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    VisitorResult visit \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> visit\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.\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:#6F42C1;--shiki-dark:#B392F0\">    assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.id.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(), visit.\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\"> 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>\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\"> * 给已定义的字符型变量重新赋值\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:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitCharAssign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.CharAssignContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.id.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(), ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">STRING\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\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:#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>\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\"> * 给已定义的布尔型变量重新赋值\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:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitBoolAssign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.BoolAssignContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">    assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.id.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\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\">BOOL\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\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:#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>\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\"> * 赋值的公共逻辑：沿作用域链查找变量真正定义的位置并修改，找不到说明变量未定义，赋值前必须先用\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * number\u002Fstring\u002Fboolean定义\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:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String name, Object value) {\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:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">currentScope.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">assign\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, value)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuntimeException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"未定义的变量：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"，赋值前需要先用number\u002Fstring\u002Fboolean定义\"\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\u003Ch2 id=\"toc-6\">六、测试代码与执行结果\u003C\u002Fh2>\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 loopExpression \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"number i = 0 \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"while (i &#x3C; 10) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    i = i + 1 \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">               \u002F\u002F 不再需要number关键字\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    if (i == 5) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"        continue \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    } \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    if (i == 8) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"        break \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    } \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    print(i) \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\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:#6F42C1;--shiki-dark:#B392F0\">calcute\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(loopExpression);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String scopeExpression \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"number a = 1 \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"if (a == 1) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    number a = 100 \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">           \u002F\u002F if代码块内重新定义a，遮蔽外层的a\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"    print(a) \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"} \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\n\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">                \"print(a)\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;                          \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 离开代码块后应该还是外层的a\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calcute\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(scopeExpression);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String errorExpression \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"b = 1\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;                    \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F b从未定义过，直接赋值应当报错\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calcute\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(errorExpression);\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>执行:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number i = 0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>while (i &#x3C; 10) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    i = i + 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    if (i == 5) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        continue\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    if (i == 8) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        break\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    print(i)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>1.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>2.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>3.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>4.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>6.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>7.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>执行:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number a = 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>if (a == 1) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    number a = 100\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    print(a)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print(a)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>100.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>1.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>执行:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>b = 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Exception in thread \"main\" java.lang.RuntimeException: 未定义的变量：b，赋值前需要先用number\u002Fstring\u002Fboolean定义\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\tat cn.irule.MyRuleSetVisitor.assign(MyRuleSetVisitor.java:145)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\tat cn.irule.MyRuleSetVisitor.visitNumAssign(MyRuleSetVisitor.java:117)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\t......\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第一段验证了改用真正的赋值语句后，循环不再死循环，结果和上一篇文章完全一致；第二段验证了\u003Ccode>if\u003C\u002Fcode>代码块内用\u003Ccode>number a = 100\u003C\u002Fcode>重新定义的\u003Ccode>a\u003C\u002Fcode>只在块内生效，打印出\u003Ccode>100.0\u003C\u002Fcode>，离开代码块后外层的\u003Ccode>a\u003C\u002Fcode>丝毫没有受到影响，打印出\u003Ccode>1.0\u003C\u002Fcode>；第三段验证了对一个从未定义过的变量\u003Ccode>b\u003C\u002Fcode>直接赋值会抛出运行时异常，提示必须先定义，符合预期。\u003C\u002Fp>\n\u003Ch2 id=\"toc-7\">七、遗留的问题\u003C\u002Fh2>\n\u003Cp>至此，我们的小型DSL已经具备了变量、四则运算、字符串\u002F布尔类型、分支、循环、作用域这些编程语言的基本要素，但还缺一样东西——\u003Cstrong>函数\u003C\u002Fstrong>。目前所有代码都写在一个\"主程序\"里，没有办法把一段逻辑封装起来重复使用。下一篇文章我们将实现自定义函数：函数的定义、参数传递、\u003Ccode>return\u003C\u002Fcode>返回值，并且会引入\"调用栈\"的概念——这也是为后续实现单步调试打基础的关键一步。\u003C\u002Fp>",[76,80,83,86,89,92,95],{"id":77,"text":78,"level":79},"toc-1","一、问题重现",2,{"id":81,"text":82,"level":79},"toc-2","二、语法设计",{"id":84,"text":85,"level":79},"toc-3","三、语法定义",{"id":87,"text":88,"level":79},"toc-4","四、作用域的实现",{"id":90,"text":91,"level":79},"toc-5","五、语法实现",{"id":93,"text":94,"level":79},"toc-6","六、测试代码与执行结果",{"id":96,"text":97,"level":79},"toc-7","七、遗留的问题"]