[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"$f99i0vcfx0qrv":14,"$f364gn2dphxjjf":31,"$fvl8z3jvglyli":57,"$f2hf8zv88r59zg":61,"render-antlr4-05-loops":74},{"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},"2101608263357743105","antlr4-05-loops","Antlr4系列⑤：支持循环语句","while循环、break\u002Fcontinue（用异常做控制流跳转）",null,"1946000000000000001",1,4801,13,"2026-09-20T17:44:00","在上一篇文章中我们实现了`if \u002F else if \u002F else`分支语句，本篇文章我们继续完善语法，实现`while`循环语句，并顺带实现循环中常用的`break`和`continue`。\n\n## 一、语法设计\n\n1. while循环\n\n    参考主流语言设计`while (条件) { 语句 }`，条件为真时反复执行代码块，条件为假时结束循环。\n\n2. break \u002F continue\n\n    - `break`：立即跳出当前所在的循环，不再判断条件。\n    - `continue`：跳过本次循环剩余的语句，直接进行下一次条件判断。\n\n## 二、语法定义\n\n根据以上设计原则对之前的语法定义进行完善，完善后的结果如下：\n```\ngrammar RuleSet; \u002F\u002F程序名称和.g4名称一致即可\n\nprog: main+\n   ;\n\nmain: setArg SEMI?\n    | print SEMI?\n    | ifStmt\n    | whileStmt                                             \u002F\u002F 新增\n    | breakStmt                                             \u002F\u002F 新增\n    | continueStmt                                          \u002F\u002F 新增\n    ;\n\n\u002F\u002F 新增：代码块，用花括号包裹0个或多个main，支持if语句内部嵌套if\nblock: '{' main* '}'\n     ;\n\n\u002F\u002F 新增：if \u002F else if \u002F else，else if可以出现0次或多次，else可选\nifStmt: IF '(' cond ')' block\n        (ELSE IF '(' cond ')' block)*\n        (ELSE block)?\n      ;\n\n\u002F\u002F 新增：while循环，条件为真时反复执行block\nwhileStmt: WHILE '(' cond ')' block\n         ;                                                  \u002F\u002F 新增\n\n\u002F\u002F 新增：跳出循环和跳过本次循环剩余部分\nbreakStmt: BREAK SEMI?\n         ;                                                  \u002F\u002F 新增\ncontinueStmt: CONTINUE SEMI?\n            ;                                               \u002F\u002F 新增\n\n\u002F\u002F 条件表达式，支持比较运算、逻辑运算以及布尔字面量\u002F布尔变量\ncond: NOT cond                                              # notCond\n    | cond AND cond                                          # andCond\n    | cond OR cond                                           # orCond\n    | calcu opt=(GT|LT|GE|LE|EQ|NE) calcu                    # compare\n    | '(' cond ')'                                           # condParens\n    | BOOL                                                    # boolLiteral\n    | ID                                                      # condArgValue\n    ;\n\nsetArg :'number'  id = ID '='  calcu                      #numarg\n       |'string'  id = ID '='  STRING                     #chararg\n       |'boolean' id = ID '='  BOOL                       #boolarg\n       ;\ncalcu: calcu opt=(MUL|DIV) calcu                          # mulAndDiv\n     | calcu opt=(ADD|SUB) calcu                          # addAndSub\n     | '(' calcu ')'                                      # parens\n     | NUMBER                                             # number\n     | ID                                                 # argValue\n     ;\nprint: PRINT '(' value = ID ')'                           # printArg\n     | PRINT '(' value = BOOL ')'                         # printBoolean\n     | PRINT '(' value = STRING ')'                       # printString\n     ;\n\nWS : [ \\t\\n\\r]+ -> skip ; \u002F\u002F ->skip表示antlr4在分析语言的文本时，符合这个规则的词法将被无视\nADD : '+' ;\nSUB : '-' ;\nMUL : '*' ;\nDIV : '\u002F' ;\nNUMBER : '-'? [0-9]+('.'([0-9]+)?)? ;        \u002F\u002F 数字正则\nBOOL : 'true' | 'false' ;\nSTRING : '\"' .*? '\"';\nPRINT : 'print';\nSEMI:  ';';\nIF : 'if' ;\nELSE : 'else' ;\nAND : '&&' ;\nOR : '||' ;\nNOT : '!' ;\nGT : '>' ;\nLT : '\u003C' ;\nGE : '>=' ;\nLE : '\u003C=' ;\nEQ : '==' ;\nNE : '!=' ;\n\u002F\u002F以下为本篇新增词法\nWHILE : 'while' ;\nBREAK : 'break' ;\nCONTINUE : 'continue' ;\nfragment ID_LETTER : [a-z] | [A-Z] | '_' ;\nfragment DIGIT : [0-9] ;\nID : ID_LETTER (ID_LETTER | DIGIT)* ;\n```\n为方便和上一篇文章中的语法进行对比，对于本次新增的部分使用注释进行了标注，接下来对本次新增的部分进行解释。\n\n1. 语法部分\n\n    - `whileStmt`复用了上一篇已经定义好的`cond`和`block`，写法上和`ifStmt`非常相似，这也是当初把`block`单独定义成一个复用`main`的语法的意义所在——新增一种语句时不需要重新设计\"语句集合\"，直接拿来用即可。\n\n    - `breakStmt`和`continueStmt`看起来只是简单包一层`BREAK`\u002F`CONTINUE`词法，为什么不直接在`main`里加`BREAK SEMI?`这种更简洁的写法，而是单独定义了两个规则？原因在第②篇文章中提到过：`main`规则本身没有使用`#`定义别名，如果直接在`main`的某个分支上加别名（比如为了后面生成对应的`visitXXX`方法更方便），根据antlr4的规则，`main`下面**所有**分支都必须加别名，这会连带影响到`setArg`、`print`、`ifStmt`等已有分支的代码生成，属于没必要的破坏性改动。单独定义成`breakStmt`、`continueStmt`两个规则，就可以在不影响`main`原有结构的前提下，让antlr4为它们各自生成独立的`visitBreakStmt`、`visitContinueStmt`方法。\n\n## 三、语法实现\n\n接下来使用idea插件将定义好的.g4文件重新生成java文件覆盖之前的文件。在实现`while`之前，先思考一个问题：`break`、`continue`很可能出现在`if`语句内部（比如`while`循环体里套了一个`if`，`break`写在`if`的代码块里），这意味着\"跳出循环\"这个信号要能从很深的嵌套层级里一路传递回最外层的`while`。如果用返回值层层向上传递\"是否需要跳出\"的标记，每一层`visitXXX`方法都要写额外的判断逻辑，非常繁琐。这里采用了一种更简洁的做法——**用异常来实现控制流跳转**：`break`\u002F`continue`被访问到时直接抛出一个自定义异常，Java运行时会自动帮我们把调用栈一路展开，直到被`while`所在的`visitWhileStmt`方法捕获，中间经过多少层`if`、`block`都不需要关心。\n\n定义两个控制流异常：\n\n```java\n\u002F**\n * break语句使用的控制流异常，命中break时抛出，由while循环捕获后跳出循环\n * 只是用来传递流程跳转的信号，不需要收集堆栈信息，重写fillInStackTrace避免每次抛出都填充堆栈带来的开销\n *\u002F\npublic class BreakException extends RuntimeException {\n\n    @Override\n    public synchronized Throwable fillInStackTrace() {\n        return this;\n    }\n}\n```\n\n```java\n\u002F**\n * continue语句使用的控制流异常，命中continue时抛出，由while循环捕获后跳过本次循环剩余部分\n *\u002F\npublic class ContinueException extends RuntimeException {\n\n    @Override\n    public synchronized Throwable fillInStackTrace() {\n        return this;\n    }\n}\n```\n> 关于`fillInStackTrace`：正常创建异常时，JVM会收集当前完整的调用堆栈信息，这一步有一定的性能开销。而这里的两个异常只是用来传递\"跳出循环\"、\"跳过本次循环\"这样的信号，并不需要真正定位异常发生的位置，所以重写`fillInStackTrace`直接返回自身，跳过堆栈收集，避免在循环次数较多时产生不必要的性能损耗。\n\n接着在`MyRuleSetVisitor`中新增本篇涉及到的方法：\n\n```java\n\u002F**\n * while循环，cond为真时反复执行block\n * block内部命中break时会抛出BreakException，在此处捕获后跳出循环\n * block内部命中continue时会抛出ContinueException，在此处捕获后直接进行下一次cond判断\n *\u002F\n@Override\npublic VisitorResult visitWhileStmt(RuleSetParser.WhileStmtContext ctx) {\n    while (visit(ctx.cond()).getBool()) {\n        try {\n            visit(ctx.block());\n        } catch (BreakException e) {\n            break;\n        } catch (ContinueException e) {\n            \u002F\u002F 不需要处理，直接进入下一次while条件判断\n        }\n    }\n    return VisitorResult.nil();\n}\n\n\u002F**\n * break语句，抛出BreakException，由外层的while捕获\n *\u002F\n@Override\npublic VisitorResult visitBreakStmt(RuleSetParser.BreakStmtContext ctx) {\n    throw new BreakException();\n}\n\n\u002F**\n * continue语句，抛出ContinueException，由外层的while捕获\n *\u002F\n@Override\npublic VisitorResult visitContinueStmt(RuleSetParser.ContinueStmtContext ctx) {\n    throw new ContinueException();\n}\n```\n\n## 四、测试代码与执行结果\n\n接下来编写测试代码，用一个1到10的计数循环，验证`while`、`break`、`continue`是否符合预期：\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        String expression =\n                \"number i = 0 \\n\" +\n                        \"while (i \u003C 10) { \\n\" +\n                        \"    number i = i + 1 \\n\" +\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                        \"}\";\n        calcute(expression);\n    }\n    \u002F\u002F calcute方法与上一篇文章保持一致，这里不再重复贴出\n}\n```\n\n执行结果如下：\n\n```\n执行:\nnumber i = 0\nwhile (i \u003C 10) {\n    number 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```\n\n循环从`i = 0`开始，每次先执行`number i = i + 1`让`i`自增，`i`等于5时命中`continue`跳过打印，`i`等于8时命中`break`跳出循环，因此最终打印了`1 2 3 4 6 7`，中间跳过了5，8之后不再继续，符合预期。\n\n> 关于自增写法：细心的读者可能注意到，循环体内让`i`自增用的是`number i = i + 1`，看起来像是\"重新定义\"了一个`number`类型的变量`i`。这是因为目前的语法里还没有设计单独的\"赋值语句\"（类似`i = i + 1`，不带`number`关键字），只能复用变量定义的语法来达到重新赋值的效果——`visitNumarg`的实现是先计算等号右边的`calcu`（此时读到的还是`paramDefine`里`i`的旧值），再把结果写回`paramDefine`，两步操作顺序刚好让这种写法能够模拟出自增的效果。\n\n## 五、遗留的问题\n\n上一篇文章末尾提到，`paramDefine`目前是`MyRuleSetVisitor`里唯一的一个全局共享的`Map`，`if`代码块内定义的变量在外面也能访问。这个问题在本篇的`while`循环里表现得更加明显：循环体内`number i = i + 1`访问和修改的其实一直是外层定义的同一个`i`，而不是一个\"循环体内部的局部变量\"——目前的实现里，\"定义\"和\"赋值\"本质上是同一回事，都只是`paramDefine.put`，没有做任何作用域隔离。下一篇文章我们将专门解决这个问题：引入作用域链，让每个`block`拥有自己的变量表，同时借这个机会把\"定义新变量\"和\"给已有变量赋值\"这两种不同的语义区分开来。\n","\u003Cp>在上一篇文章中我们实现了\u003Ccode>if \u002F else if \u002F else\u003C\u002Fcode>分支语句，本篇文章我们继续完善语法，实现\u003Ccode>while\u003C\u002Fcode>循环语句，并顺带实现循环中常用的\u003Ccode>break\u003C\u002Fcode>和\u003Ccode>continue\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch2>一、语法设计\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>while循环\u003C\u002Fp>\n  \u003Cp>参考主流语言设计\u003Ccode>while (条件) { 语句 }\u003C\u002Fcode>，条件为真时反复执行代码块，条件为假时结束循环。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>break \u002F continue\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>break\u003C\u002Fcode>：立即跳出当前所在的循环，不再判断条件。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>continue\u003C\u002Fcode>：跳过本次循环剩余的语句，直接进行下一次条件判断。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>二、语法定义\u003C\u002Fh2>\n\u003Cp>根据以上设计原则对之前的语法定义进行完善，完善后的结果如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>grammar RuleSet; \u002F\u002F程序名称和.g4名称一致即可\n\nprog: main+\n   ;\n\nmain: setArg SEMI?\n    | print SEMI?\n    | ifStmt\n    | whileStmt                                             \u002F\u002F 新增\n    | breakStmt                                             \u002F\u002F 新增\n    | continueStmt                                          \u002F\u002F 新增\n    ;\n\n\u002F\u002F 新增：代码块，用花括号包裹0个或多个main，支持if语句内部嵌套if\nblock: '{' main* '}'\n     ;\n\n\u002F\u002F 新增：if \u002F else if \u002F else，else if可以出现0次或多次，else可选\nifStmt: IF '(' cond ')' block\n        (ELSE IF '(' cond ')' block)*\n        (ELSE block)?\n      ;\n\n\u002F\u002F 新增：while循环，条件为真时反复执行block\nwhileStmt: WHILE '(' cond ')' block\n         ;                                                  \u002F\u002F 新增\n\n\u002F\u002F 新增：跳出循环和跳过本次循环剩余部分\nbreakStmt: BREAK SEMI?\n         ;                                                  \u002F\u002F 新增\ncontinueStmt: CONTINUE SEMI?\n            ;                                               \u002F\u002F 新增\n\n\u002F\u002F 条件表达式，支持比较运算、逻辑运算以及布尔字面量\u002F布尔变量\ncond: NOT cond                                              # notCond\n    | cond AND cond                                          # andCond\n    | cond OR cond                                           # orCond\n    | calcu opt=(GT|LT|GE|LE|EQ|NE) calcu                    # compare\n    | '(' cond ')'                                           # condParens\n    | BOOL                                                    # boolLiteral\n    | ID                                                      # condArgValue\n    ;\n\nsetArg :'number'  id = ID '='  calcu                      #numarg\n       |'string'  id = ID '='  STRING                     #chararg\n       |'boolean' id = ID '='  BOOL                       #boolarg\n       ;\ncalcu: calcu opt=(MUL|DIV) calcu                          # mulAndDiv\n     | calcu opt=(ADD|SUB) calcu                          # addAndSub\n     | '(' calcu ')'                                      # parens\n     | NUMBER                                             # number\n     | ID                                                 # argValue\n     ;\nprint: PRINT '(' value = ID ')'                           # printArg\n     | PRINT '(' value = BOOL ')'                         # printBoolean\n     | PRINT '(' value = STRING ')'                       # printString\n     ;\n\nWS : [ \\t\\n\\r]+ -&gt; skip ; \u002F\u002F -&gt;skip表示antlr4在分析语言的文本时，符合这个规则的词法将被无视\nADD : '+' ;\nSUB : '-' ;\nMUL : '*' ;\nDIV : '\u002F' ;\nNUMBER : '-'? [0-9]+('.'([0-9]+)?)? ;        \u002F\u002F 数字正则\nBOOL : 'true' | 'false' ;\nSTRING : '\"' .*? '\"';\nPRINT : 'print';\nSEMI:  ';';\nIF : 'if' ;\nELSE : 'else' ;\nAND : '&amp;&amp;' ;\nOR : '||' ;\nNOT : '!' ;\nGT : '&gt;' ;\nLT : '&lt;' ;\nGE : '&gt;=' ;\nLE : '&lt;=' ;\nEQ : '==' ;\nNE : '!=' ;\n\u002F\u002F以下为本篇新增词法\nWHILE : 'while' ;\nBREAK : 'break' ;\nCONTINUE : 'continue' ;\nfragment ID_LETTER : [a-z] | [A-Z] | '_' ;\nfragment DIGIT : [0-9] ;\nID : ID_LETTER (ID_LETTER | DIGIT)* ;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>为方便和上一篇文章中的语法进行对比，对于本次新增的部分使用注释进行了标注，接下来对本次新增的部分进行解释。\u003C\u002Fp>\n\u003Col>\n \u003Cli>\n  \u003Cp>语法部分\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\n    \u003Cp>\u003Ccode>whileStmt\u003C\u002Fcode>复用了上一篇已经定义好的\u003Ccode>cond\u003C\u002Fcode>和\u003Ccode>block\u003C\u002Fcode>，写法上和\u003Ccode>ifStmt\u003C\u002Fcode>非常相似，这也是当初把\u003Ccode>block\u003C\u002Fcode>单独定义成一个复用\u003Ccode>main\u003C\u002Fcode>的语法的意义所在——新增一种语句时不需要重新设计\"语句集合\"，直接拿来用即可。\u003C\u002Fp>\u003C\u002Fli>\n   \u003Cli>\n    \u003Cp>\u003Ccode>breakStmt\u003C\u002Fcode>和\u003Ccode>continueStmt\u003C\u002Fcode>看起来只是简单包一层\u003Ccode>BREAK\u003C\u002Fcode>\u002F\u003Ccode>CONTINUE\u003C\u002Fcode>词法，为什么不直接在\u003Ccode>main\u003C\u002Fcode>里加\u003Ccode>BREAK SEMI?\u003C\u002Fcode>这种更简洁的写法，而是单独定义了两个规则？原因在第②篇文章中提到过：\u003Ccode>main\u003C\u002Fcode>规则本身没有使用\u003Ccode>#\u003C\u002Fcode>定义别名，如果直接在\u003Ccode>main\u003C\u002Fcode>的某个分支上加别名（比如为了后面生成对应的\u003Ccode>visitXXX\u003C\u002Fcode>方法更方便），根据antlr4的规则，\u003Ccode>main\u003C\u002Fcode>下面\u003Cstrong>所有\u003C\u002Fstrong>分支都必须加别名，这会连带影响到\u003Ccode>setArg\u003C\u002Fcode>、\u003Ccode>print\u003C\u002Fcode>、\u003Ccode>ifStmt\u003C\u002Fcode>等已有分支的代码生成，属于没必要的破坏性改动。单独定义成\u003Ccode>breakStmt\u003C\u002Fcode>、\u003Ccode>continueStmt\u003C\u002Fcode>两个规则，就可以在不影响\u003Ccode>main\u003C\u002Fcode>原有结构的前提下，让antlr4为它们各自生成独立的\u003Ccode>visitBreakStmt\u003C\u002Fcode>、\u003Ccode>visitContinueStmt\u003C\u002Fcode>方法。\u003C\u002Fp>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、语法实现\u003C\u002Fh2>\n\u003Cp>接下来使用idea插件将定义好的.g4文件重新生成java文件覆盖之前的文件。在实现\u003Ccode>while\u003C\u002Fcode>之前，先思考一个问题：\u003Ccode>break\u003C\u002Fcode>、\u003Ccode>continue\u003C\u002Fcode>很可能出现在\u003Ccode>if\u003C\u002Fcode>语句内部（比如\u003Ccode>while\u003C\u002Fcode>循环体里套了一个\u003Ccode>if\u003C\u002Fcode>，\u003Ccode>break\u003C\u002Fcode>写在\u003Ccode>if\u003C\u002Fcode>的代码块里），这意味着\"跳出循环\"这个信号要能从很深的嵌套层级里一路传递回最外层的\u003Ccode>while\u003C\u002Fcode>。如果用返回值层层向上传递\"是否需要跳出\"的标记，每一层\u003Ccode>visitXXX\u003C\u002Fcode>方法都要写额外的判断逻辑，非常繁琐。这里采用了一种更简洁的做法——\u003Cstrong>用异常来实现控制流跳转\u003C\u002Fstrong>：\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>被访问到时直接抛出一个自定义异常，Java运行时会自动帮我们把调用栈一路展开，直到被\u003Ccode>while\u003C\u002Fcode>所在的\u003Ccode>visitWhileStmt\u003C\u002Fcode>方法捕获，中间经过多少层\u003Ccode>if\u003C\u002Fcode>、\u003Ccode>block\u003C\u002Fcode>都不需要关心。\u003C\u002Fp>\n\u003Cp>定义两个控制流异常：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * break语句使用的控制流异常，命中break时抛出，由while循环捕获后跳出循环\n * 只是用来传递流程跳转的信号，不需要收集堆栈信息，重写fillInStackTrace避免每次抛出都填充堆栈带来的开销\n *\u002F\npublic class BreakException extends RuntimeException {\n\n    @Override\n    public synchronized Throwable fillInStackTrace() {\n        return this;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * continue语句使用的控制流异常，命中continue时抛出，由while循环捕获后跳过本次循环剩余部分\n *\u002F\npublic class ContinueException extends RuntimeException {\n\n    @Override\n    public synchronized Throwable fillInStackTrace() {\n        return this;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>关于\u003Ccode>fillInStackTrace\u003C\u002Fcode>：正常创建异常时，JVM会收集当前完整的调用堆栈信息，这一步有一定的性能开销。而这里的两个异常只是用来传递\"跳出循环\"、\"跳过本次循环\"这样的信号，并不需要真正定位异常发生的位置，所以重写\u003Ccode>fillInStackTrace\u003C\u002Fcode>直接返回自身，跳过堆栈收集，避免在循环次数较多时产生不必要的性能损耗。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>接着在\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>中新增本篇涉及到的方法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * while循环，cond为真时反复执行block\n * block内部命中break时会抛出BreakException，在此处捕获后跳出循环\n * block内部命中continue时会抛出ContinueException，在此处捕获后直接进行下一次cond判断\n *\u002F\n@Override\npublic VisitorResult visitWhileStmt(RuleSetParser.WhileStmtContext ctx) {\n    while (visit(ctx.cond()).getBool()) {\n        try {\n            visit(ctx.block());\n        } catch (BreakException e) {\n            break;\n        } catch (ContinueException e) {\n            \u002F\u002F 不需要处理，直接进入下一次while条件判断\n        }\n    }\n    return VisitorResult.nil();\n}\n\n\u002F**\n * break语句，抛出BreakException，由外层的while捕获\n *\u002F\n@Override\npublic VisitorResult visitBreakStmt(RuleSetParser.BreakStmtContext ctx) {\n    throw new BreakException();\n}\n\n\u002F**\n * continue语句，抛出ContinueException，由外层的while捕获\n *\u002F\n@Override\npublic VisitorResult visitContinueStmt(RuleSetParser.ContinueStmtContext ctx) {\n    throw new ContinueException();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>四、测试代码与执行结果\u003C\u002Fh2>\n\u003Cp>接下来编写测试代码，用一个1到10的计数循环，验证\u003Ccode>while\u003C\u002Fcode>、\u003Ccode>break\u003C\u002Fcode>、\u003Ccode>continue\u003C\u002Fcode>是否符合预期：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Test {\n    public static void main(String[] args) {\n        String expression =\n                \"number i = 0 \\n\" +\n                        \"while (i &lt; 10) { \\n\" +\n                        \"    number i = i + 1 \\n\" +\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                        \"}\";\n        calcute(expression);\n    }\n    \u002F\u002F calcute方法与上一篇文章保持一致，这里不再重复贴出\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>执行:\nnumber i = 0\nwhile (i &lt; 10) {\n    number 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\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>循环从\u003Ccode>i = 0\u003C\u002Fcode>开始，每次先执行\u003Ccode>number i = i + 1\u003C\u002Fcode>让\u003Ccode>i\u003C\u002Fcode>自增，\u003Ccode>i\u003C\u002Fcode>等于5时命中\u003Ccode>continue\u003C\u002Fcode>跳过打印，\u003Ccode>i\u003C\u002Fcode>等于8时命中\u003Ccode>break\u003C\u002Fcode>跳出循环，因此最终打印了\u003Ccode>1 2 3 4 6 7\u003C\u002Fcode>，中间跳过了5，8之后不再继续，符合预期。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>关于自增写法：细心的读者可能注意到，循环体内让\u003Ccode>i\u003C\u002Fcode>自增用的是\u003Ccode>number i = i + 1\u003C\u002Fcode>，看起来像是\"重新定义\"了一个\u003Ccode>number\u003C\u002Fcode>类型的变量\u003Ccode>i\u003C\u002Fcode>。这是因为目前的语法里还没有设计单独的\"赋值语句\"（类似\u003Ccode>i = i + 1\u003C\u002Fcode>，不带\u003Ccode>number\u003C\u002Fcode>关键字），只能复用变量定义的语法来达到重新赋值的效果——\u003Ccode>visitNumarg\u003C\u002Fcode>的实现是先计算等号右边的\u003Ccode>calcu\u003C\u002Fcode>（此时读到的还是\u003Ccode>paramDefine\u003C\u002Fcode>里\u003Ccode>i\u003C\u002Fcode>的旧值），再把结果写回\u003Ccode>paramDefine\u003C\u002Fcode>，两步操作顺序刚好让这种写法能够模拟出自增的效果。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>五、遗留的问题\u003C\u002Fh2>\n\u003Cp>上一篇文章末尾提到，\u003Ccode>paramDefine\u003C\u002Fcode>目前是\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>里唯一的一个全局共享的\u003Ccode>Map\u003C\u002Fcode>，\u003Ccode>if\u003C\u002Fcode>代码块内定义的变量在外面也能访问。这个问题在本篇的\u003Ccode>while\u003C\u002Fcode>循环里表现得更加明显：循环体内\u003Ccode>number i = i + 1\u003C\u002Fcode>访问和修改的其实一直是外层定义的同一个\u003Ccode>i\u003C\u002Fcode>，而不是一个\"循环体内部的局部变量\"——目前的实现里，\"定义\"和\"赋值\"本质上是同一回事，都只是\u003Ccode>paramDefine.put\u003C\u002Fcode>，没有做任何作用域隔离。下一篇文章我们将专门解决这个问题：引入作用域链，让每个\u003Ccode>block\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},"2101605987801022465","antlr4-04-if-else","Antlr4系列④：支持分支语句","比较运算符、逻辑运算符、if\u002Felse if\u002Felse、代码块block",6668,17,"2026-09-20T17:34:58",{"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},"2101615963315552258","antlr4-06-scope-chain","Antlr4系列⑥：作用域链与变量赋值","Scope作用域链、区分\"定义\"与\"赋值\"、修复变量泄漏",5701,15,"2026-09-20T18:14:34",{"id":50,"slug":51,"title":52,"summary":53,"coverUrl":20,"categoryId":21,"status":22,"topFlag":4,"viewCount":4,"likeCount":4,"wordCount":54,"readingMinutes":55,"publishedAt":56},"2101585710597718018","antlr4-03-variables-and-print","Antlr4系列③：支持变量定义和打印函数","number\u002Fstring\u002Fboolean变量定义、print函数、VisitorResult统一返回类型",8172,21,"2026-09-20T16:14:14",{"code":4,"message":5,"data":58},{"prev":59,"next":60},{"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":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},{"code":4,"message":5,"data":62},[63,68],{"id":21,"name":64,"slug":65,"description":66,"sortOrder":4,"articleCount":67},"技术","tech","编程与技术笔记","11",{"id":69,"name":70,"slug":71,"description":72,"sortOrder":22,"articleCount":73},"1946000000000000002","随笔","essay","生活与思考","0",{"html":75,"toc":76},"\u003Cp>在上一篇文章中我们实现了\u003Ccode>if \u002F else if \u002F else\u003C\u002Fcode>分支语句，本篇文章我们继续完善语法，实现\u003Ccode>while\u003C\u002Fcode>循环语句，并顺带实现循环中常用的\u003Ccode>break\u003C\u002Fcode>和\u003Ccode>continue\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、语法设计\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>while循环\u003C\u002Fp>\n  \u003Cp>参考主流语言设计\u003Ccode>while (条件) { 语句 }\u003C\u002Fcode>，条件为真时反复执行代码块，条件为假时结束循环。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>break \u002F continue\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>break\u003C\u002Fcode>：立即跳出当前所在的循环，不再判断条件。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>continue\u003C\u002Fcode>：跳过本次循环剩余的语句，直接进行下一次条件判断。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-2\">二、语法定义\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>grammar RuleSet; \u002F\u002F程序名称和.g4名称一致即可\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>prog: main+\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>main: setArg SEMI?\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                                             \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | breakStmt                                             \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | continueStmt                                          \u002F\u002F 新增\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 新增：代码块，用花括号包裹0个或多个main，支持if语句内部嵌套if\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>block: '{' main* '}'\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 新增：if \u002F else if \u002F else，else if可以出现0次或多次，else可选\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>ifStmt: IF '(' cond ')' block\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        (ELSE IF '(' cond ')' block)*\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>        (ELSE block)?\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 新增：while循环，条件为真时反复执行block\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>whileStmt: WHILE '(' cond ')' block\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>         ;                                                  \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u002F\u002F 新增：跳出循环和跳过本次循环剩余部分\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>breakStmt: BREAK SEMI?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>         ;                                                  \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>continueStmt: CONTINUE SEMI?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>            ;                                               \u002F\u002F 新增\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u002F\u002F 条件表达式，支持比较运算、逻辑运算以及布尔字面量\u002F布尔变量\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>cond: NOT cond                                              # notCond\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | cond AND cond                                          # andCond\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | cond OR cond                                           # orCond\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | calcu opt=(GT|LT|GE|LE|EQ|NE) calcu                    # compare\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | '(' cond ')'                                           # condParens\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | BOOL                                                    # boolLiteral\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    | ID                                                      # condArgValue\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>setArg :'number'  id = ID '='  calcu                      #numarg\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>       |'string'  id = ID '='  STRING                     #chararg\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>       |'boolean' id = ID '='  BOOL                       #boolarg\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>       ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>calcu: calcu opt=(MUL|DIV) calcu                          # mulAndDiv\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | calcu opt=(ADD|SUB) calcu                          # addAndSub\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | '(' calcu ')'                                      # parens\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | NUMBER                                             # number\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | ID                                                 # argValue\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print: PRINT '(' value = ID ')'                           # printArg\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | PRINT '(' value = BOOL ')'                         # printBoolean\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | PRINT '(' value = STRING ')'                       # printString\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>WS : [ \\t\\n\\r]+ -> skip ; \u002F\u002F ->skip表示antlr4在分析语言的文本时，符合这个规则的词法将被无视\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>ADD : '+' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>SUB : '-' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>MUL : '*' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>DIV : '\u002F' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NUMBER : '-'? [0-9]+('.'([0-9]+)?)? ;        \u002F\u002F 数字正则\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>BOOL : 'true' | 'false' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>STRING : '\"' .*? '\"';\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>PRINT : 'print';\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>SEMI:  ';';\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>IF : 'if' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>ELSE : 'else' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>AND : '&#x26;&#x26;' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>OR : '||' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NOT : '!' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>GT : '>' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>LT : '&#x3C;' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>GE : '>=' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>LE : '&#x3C;=' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>EQ : '==' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>NE : '!=' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u002F\u002F以下为本篇新增词法\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>WHILE : 'while' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>BREAK : 'break' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>CONTINUE : 'continue' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>fragment ID_LETTER : [a-z] | [A-Z] | '_' ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>fragment DIGIT : [0-9] ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>ID : ID_LETTER (ID_LETTER | DIGIT)* ;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>为方便和上一篇文章中的语法进行对比，对于本次新增的部分使用注释进行了标注，接下来对本次新增的部分进行解释。\u003C\u002Fp>\n\u003Col>\n \u003Cli>\n  \u003Cp>语法部分\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\n    \u003Cp>\u003Ccode>whileStmt\u003C\u002Fcode>复用了上一篇已经定义好的\u003Ccode>cond\u003C\u002Fcode>和\u003Ccode>block\u003C\u002Fcode>，写法上和\u003Ccode>ifStmt\u003C\u002Fcode>非常相似，这也是当初把\u003Ccode>block\u003C\u002Fcode>单独定义成一个复用\u003Ccode>main\u003C\u002Fcode>的语法的意义所在——新增一种语句时不需要重新设计\"语句集合\"，直接拿来用即可。\u003C\u002Fp>\u003C\u002Fli>\n   \u003Cli>\n    \u003Cp>\u003Ccode>breakStmt\u003C\u002Fcode>和\u003Ccode>continueStmt\u003C\u002Fcode>看起来只是简单包一层\u003Ccode>BREAK\u003C\u002Fcode>\u002F\u003Ccode>CONTINUE\u003C\u002Fcode>词法，为什么不直接在\u003Ccode>main\u003C\u002Fcode>里加\u003Ccode>BREAK SEMI?\u003C\u002Fcode>这种更简洁的写法，而是单独定义了两个规则？原因在第②篇文章中提到过：\u003Ccode>main\u003C\u002Fcode>规则本身没有使用\u003Ccode>#\u003C\u002Fcode>定义别名，如果直接在\u003Ccode>main\u003C\u002Fcode>的某个分支上加别名（比如为了后面生成对应的\u003Ccode>visitXXX\u003C\u002Fcode>方法更方便），根据antlr4的规则，\u003Ccode>main\u003C\u002Fcode>下面\u003Cstrong>所有\u003C\u002Fstrong>分支都必须加别名，这会连带影响到\u003Ccode>setArg\u003C\u002Fcode>、\u003Ccode>print\u003C\u002Fcode>、\u003Ccode>ifStmt\u003C\u002Fcode>等已有分支的代码生成，属于没必要的破坏性改动。单独定义成\u003Ccode>breakStmt\u003C\u002Fcode>、\u003Ccode>continueStmt\u003C\u002Fcode>两个规则，就可以在不影响\u003Ccode>main\u003C\u002Fcode>原有结构的前提下，让antlr4为它们各自生成独立的\u003Ccode>visitBreakStmt\u003C\u002Fcode>、\u003Ccode>visitContinueStmt\u003C\u002Fcode>方法。\u003C\u002Fp>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、语法实现\u003C\u002Fh2>\n\u003Cp>接下来使用idea插件将定义好的.g4文件重新生成java文件覆盖之前的文件。在实现\u003Ccode>while\u003C\u002Fcode>之前，先思考一个问题：\u003Ccode>break\u003C\u002Fcode>、\u003Ccode>continue\u003C\u002Fcode>很可能出现在\u003Ccode>if\u003C\u002Fcode>语句内部（比如\u003Ccode>while\u003C\u002Fcode>循环体里套了一个\u003Ccode>if\u003C\u002Fcode>，\u003Ccode>break\u003C\u002Fcode>写在\u003Ccode>if\u003C\u002Fcode>的代码块里），这意味着\"跳出循环\"这个信号要能从很深的嵌套层级里一路传递回最外层的\u003Ccode>while\u003C\u002Fcode>。如果用返回值层层向上传递\"是否需要跳出\"的标记，每一层\u003Ccode>visitXXX\u003C\u002Fcode>方法都要写额外的判断逻辑，非常繁琐。这里采用了一种更简洁的做法——\u003Cstrong>用异常来实现控制流跳转\u003C\u002Fstrong>：\u003Ccode>break\u003C\u002Fcode>\u002F\u003Ccode>continue\u003C\u002Fcode>被访问到时直接抛出一个自定义异常，Java运行时会自动帮我们把调用栈一路展开，直到被\u003Ccode>while\u003C\u002Fcode>所在的\u003Ccode>visitWhileStmt\u003C\u002Fcode>方法捕获，中间经过多少层\u003Ccode>if\u003C\u002Fcode>、\u003Ccode>block\u003C\u002Fcode>都不需要关心。\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * break语句使用的控制流异常，命中break时抛出，由while循环捕获后跳出循环\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 只是用来传递流程跳转的信号，不需要收集堆栈信息，重写fillInStackTrace避免每次抛出都填充堆栈带来的开销\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\"> BreakException\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuntimeException\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\">    @\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\"> synchronized\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Throwable \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fillInStackTrace\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\"> this\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\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\"> * continue语句使用的控制流异常，命中continue时抛出，由while循环捕获后跳过本次循环剩余部分\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\"> ContinueException\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuntimeException\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\">    @\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\"> synchronized\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Throwable \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">fillInStackTrace\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\"> this\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\u003Cblockquote>\n \u003Cp>关于\u003Ccode>fillInStackTrace\u003C\u002Fcode>：正常创建异常时，JVM会收集当前完整的调用堆栈信息，这一步有一定的性能开销。而这里的两个异常只是用来传递\"跳出循环\"、\"跳过本次循环\"这样的信号，并不需要真正定位异常发生的位置，所以重写\u003Ccode>fillInStackTrace\u003C\u002Fcode>直接返回自身，跳过堆栈收集，避免在循环次数较多时产生不必要的性能损耗。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>接着在\u003Ccode>MyRuleSetVisitor\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\"> * while循环，cond为真时反复执行block\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * block内部命中break时会抛出BreakException，在此处捕获后跳出循环\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * block内部命中continue时会抛出ContinueException，在此处捕获后直接进行下一次cond判断\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\">visitWhileStmt\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.WhileStmtContext ctx) {\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\"> (\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\">cond\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBool\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\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:#6F42C1;--shiki-dark:#B392F0\">            visit\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:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (BreakException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            break\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\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ContinueException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\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 不需要处理，直接进入下一次while条件判断\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\">    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\"> * break语句，抛出BreakException，由外层的while捕获\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\">visitBreakStmt\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.BreakStmtContext ctx) {\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\"> BreakException\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\"> * continue语句，抛出ContinueException，由外层的while捕获\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\">visitContinueStmt\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.ContinueStmtContext ctx) {\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\"> ContinueException\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\u003Ch2 id=\"toc-4\">四、测试代码与执行结果\u003C\u002Fh2>\n\u003Cp>接下来编写测试代码，用一个1到10的计数循环，验证\u003Ccode>while\u003C\u002Fcode>、\u003Ccode>break\u003C\u002Fcode>、\u003Ccode>continue\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\"> Test\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\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\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\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 expression \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\">                        \"    number 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>\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\">(expression);\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:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F calcute方法与上一篇文章保持一致，这里不再重复贴出\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>执行:\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>    number 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>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>循环从\u003Ccode>i = 0\u003C\u002Fcode>开始，每次先执行\u003Ccode>number i = i + 1\u003C\u002Fcode>让\u003Ccode>i\u003C\u002Fcode>自增，\u003Ccode>i\u003C\u002Fcode>等于5时命中\u003Ccode>continue\u003C\u002Fcode>跳过打印，\u003Ccode>i\u003C\u002Fcode>等于8时命中\u003Ccode>break\u003C\u002Fcode>跳出循环，因此最终打印了\u003Ccode>1 2 3 4 6 7\u003C\u002Fcode>，中间跳过了5，8之后不再继续，符合预期。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>关于自增写法：细心的读者可能注意到，循环体内让\u003Ccode>i\u003C\u002Fcode>自增用的是\u003Ccode>number i = i + 1\u003C\u002Fcode>，看起来像是\"重新定义\"了一个\u003Ccode>number\u003C\u002Fcode>类型的变量\u003Ccode>i\u003C\u002Fcode>。这是因为目前的语法里还没有设计单独的\"赋值语句\"（类似\u003Ccode>i = i + 1\u003C\u002Fcode>，不带\u003Ccode>number\u003C\u002Fcode>关键字），只能复用变量定义的语法来达到重新赋值的效果——\u003Ccode>visitNumarg\u003C\u002Fcode>的实现是先计算等号右边的\u003Ccode>calcu\u003C\u002Fcode>（此时读到的还是\u003Ccode>paramDefine\u003C\u002Fcode>里\u003Ccode>i\u003C\u002Fcode>的旧值），再把结果写回\u003Ccode>paramDefine\u003C\u002Fcode>，两步操作顺序刚好让这种写法能够模拟出自增的效果。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2 id=\"toc-5\">五、遗留的问题\u003C\u002Fh2>\n\u003Cp>上一篇文章末尾提到，\u003Ccode>paramDefine\u003C\u002Fcode>目前是\u003Ccode>MyRuleSetVisitor\u003C\u002Fcode>里唯一的一个全局共享的\u003Ccode>Map\u003C\u002Fcode>，\u003Ccode>if\u003C\u002Fcode>代码块内定义的变量在外面也能访问。这个问题在本篇的\u003Ccode>while\u003C\u002Fcode>循环里表现得更加明显：循环体内\u003Ccode>number i = i + 1\u003C\u002Fcode>访问和修改的其实一直是外层定义的同一个\u003Ccode>i\u003C\u002Fcode>，而不是一个\"循环体内部的局部变量\"——目前的实现里，\"定义\"和\"赋值\"本质上是同一回事，都只是\u003Ccode>paramDefine.put\u003C\u002Fcode>，没有做任何作用域隔离。下一篇文章我们将专门解决这个问题：引入作用域链，让每个\u003Ccode>block\u003C\u002Fcode>拥有自己的变量表，同时借这个机会把\"定义新变量\"和\"给已有变量赋值\"这两种不同的语义区分开来。\u003C\u002Fp>",[77,81,84,87,90],{"id":78,"text":79,"level":80},"toc-1","一、语法设计",2,{"id":82,"text":83,"level":80},"toc-2","二、语法定义",{"id":85,"text":86,"level":80},"toc-3","三、语法实现",{"id":88,"text":89,"level":80},"toc-4","四、测试代码与执行结果",{"id":91,"text":92,"level":80},"toc-5","五、遗留的问题"]