[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"$f3o2cnoz3wcat8":14,"$f3hl5nbhjbht5s":31,"$f3564tfsly7lir":57,"$f2hf8zv88r59zg":61,"render-antlr4-11-builtin-functions":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},"2101642321643094018","antlr4-11-builtin-functions","Antlr4系列⑪：内置函数注册机制","BuiltinFunction注册表、len\u002Fsum\u002Fabs\u002Fmax、放宽变量类型限制",null,"1946000000000000001",1,5528,14,"2026-09-20T19:59:19","上一篇文章给语言加上了数组，为了实现`len(arr)`，我们专门在.g4里加了一条`LEN '(' ID ')'`语法。本来这篇文章打算继续讲字符串拼接，但动手之后发现：要让字符串拼接的结果能像数值一样参与后续计算（而不仅仅是打印出来看看），需要把`calcu`从\"只处理数值\"升级成\"能处理任意类型的通用表达式\"，这个改动比预想的要大，值得单独用一篇文章讲清楚。所以这篇文章我们先做一件更基础也更有必要的事——把`len`这种\"每加一个新能力就要改一次.g4\"的做法，换成一套通用的**内置函数注册机制**，字符串相关的内容放到下一篇。\n\n## 一、问题：len为什么要单独开一条语法\n\n回头看上一篇的写法：\n```\n| LEN '(' ID ')'                                      # arrayLen\n```\n这条语法有两个别扭的地方：\n1. `len`长得和普通函数调用一模一样（`名字(参数)`），却因为要拿到数组\"本身\"而不是数组的某个数值，被迫单独设计了一套语法。\n2. 以后每多一个类似的能力（比如求数组之和`sum`、绝对值`abs`），都要重复这个套路——加词法、加语法分支、加对应的`visitXXX`方法，而这些能力本质上并没有引入任何新的语法结构，只是\"内置的、不需要用户自己定义的函数\"而已。\n\n真正的问题出在`visitFuncCall`里：调用函数时，参数是这样求值的：\n```java\nargValues.add(visit(argExpr).getNumber());  \u002F\u002F 旧代码\n```\n不管函数是用户自己写的还是将来想内置的，参数一律被强制转成`Double`。这就是为什么`len(arr)`没法直接复用`funcCall`语法——`arr`作为参数传进去的一瞬间就被要求必须是数值，数组这个类型直接被拦在了门外。而它的根源还要往前追一步：`visitArgValue`在读取变量的时候就已经做了限制：\n\n```java\n\u002F\u002F 旧代码\nObject value = currentScope.get(ctx.getText());\nif (value instanceof Double) {\n    return VisitorResult.value(value);\n}\nthrow new RuntimeException(\"未定义的参数：\" + ctx.getText() + \" 或参数\" + ctx.getText() + \"定义为非数值型\");\n```\n读取一个变量这个动作本身，其实不应该关心它到底是什么类型，这个限制放在这里属于\"操之过急\"。\n\n## 二、解决思路\n\n1. 放宽`visitArgValue`：读取变量只管把值原样包装成`VisitorResult`返回，类型检查交给真正使用这个值的地方去做（比如四则运算里调用`getNumber()`，不是数值型自然会在那里报错）。\n2. `visitFuncCall`里参数求值也不再提前转成`Double`，改成保留完整的`VisitorResult`列表。\n3. 新增一个内置函数注册表：`Map\u003CString, BuiltinFunction>`，`BuiltinFunction`是一个函数式接口，接收`List\u003CVisitorResult>`参数、返回一个`VisitorResult`，内部想怎么处理参数、返回什么类型完全自己决定。\n4. `visitFuncCall`调用时优先查内置函数表，查不到再按老办法查用户自定义函数（`funcDefine`）。\n\n这样`len`就不再需要任何专属语法，直接复用已有的`ID '(' 参数列表 ')'`（也就是`funcCall`）语法即可，`.g4`反而可以做减法——把上一篇专门为`len`加的`LEN`词法和`arrayLen`语法整个删掉。\n\n## 三、语法定义\n\n`.g4`这次是做减法：\n```\ncalcu: calcu opt=(MUL|DIV) calcu                          # mulAndDiv\n     | calcu opt=(ADD|SUB) calcu                          # addAndSub\n     | '(' calcu ')'                                      # parens\n     | NUMBER                                             # number\n     | ID '(' (calcu (',' calcu)*)? ')'                   # funcCall  \u002F\u002F 修改：user函数和内置函数统一走这里\n     | ID '[' calcu ']'                                    # arrayGet\n     | ID                                                 # argValue\n     ;\n```\n删掉了`LEN '(' ID ')' # arrayLen`这一行，以及词法里的`LEN : 'len' ;`——`len`不再是语言的一个语法关键字，只是内置函数表里的一个普通条目。\n\n## 四、语法实现\n\n先定义内置函数的接口：\n```java\n\u002F**\n * 内置函数的统一接口，和用户用function定义的函数不同，内置函数直接用Java代码实现，\n * 参数以VisitorResult的原始形态传入，具体是数值、字符串还是数组由函数自己按需取用，\n * 不像调用用户函数那样强制把每个参数都转成Double\n *\u002F\n@FunctionalInterface\npublic interface BuiltinFunction {\n    VisitorResult call(List\u003CVisitorResult> args);\n}\n```\n\n注册几个内置函数，作为示例：\n```java\n\u002F**\n * 注册内置函数。相比每新增一个能力就往.g4里加一条专门的语法规则（比如上一篇文章里len曾经\n * 单独占用的LEN词法和arrayLen语法），注册成内置函数不需要改语法，只要在这里加一行，\n * 新的能力就能通过已有的\"函数调用\"语法直接使用\n *\u002F\nprivate void registerBuiltins() {\n    builtins.put(\"len\", args -> VisitorResult.value((double) args.get(0).getArray().size()));\n    builtins.put(\"sum\", args -> {\n        double total = 0;\n        for (Double item : args.get(0).getArray()) {\n            total += item;\n        }\n        return VisitorResult.value(total);\n    });\n    builtins.put(\"abs\", args -> VisitorResult.value(Math.abs(args.get(0).getNumber())));\n    builtins.put(\"max\", args -> VisitorResult.value(Math.max(args.get(0).getNumber(), args.get(1).getNumber())));\n}\n```\n`len`和`sum`从参数里取出数组（`getArray()`），`abs`和`max`从参数里取出数值（`getNumber()`）——是数组还是数值完全由内置函数自己按需解读，`visitFuncCall`本身不再对参数类型做任何假设：\n\n```java\n@Override\npublic VisitorResult visitFuncCall(RuleSetParser.FuncCallContext ctx) {\n    String funcName = ctx.ID().getText();\n    List\u003CVisitorResult> argValues = new ArrayList\u003C>();\n    for (RuleSetParser.CalcuContext argExpr : ctx.calcu()) {\n        argValues.add(visit(argExpr));\n    }\n\n    BuiltinFunction builtin = builtins.get(funcName);\n    if (builtin != null) {\n        return builtin.call(argValues);\n    }\n\n    RuleSetParser.FuncDeclContext funcDecl = funcDefine.get(funcName);\n    if (funcDecl == null) {\n        throw new RuntimeException(\"未定义的函数：\" + funcName);\n    }\n    List\u003CString> paramNames = funcDecl.paramList() == null\n            ? Collections.emptyList()\n            : paramNames(funcDecl.paramList());\n    if (argValues.size() != paramNames.size()) {\n        throw new RuntimeException(\"函数\" + funcName + \"需要\" + paramNames.size() + \"个参数，实际传入\" + argValues.size() + \"个\");\n    }\n    if (callStack.size() >= MAX_CALL_DEPTH) {\n        throw new RuntimeException(\"函数调用层级超过\" + MAX_CALL_DEPTH + \"层，可能存在死递归：\" + callStack);\n    }\n\n    Scope callerScope = currentScope;\n    Scope funcScope = new Scope(globalScope);\n    for (int i = 0; i \u003C paramNames.size(); i++) {\n        funcScope.define(paramNames.get(i), argValues.get(i).getNumber());  \u002F\u002F 用户函数的参数仍然只支持数值\n    }\n\n    callStack.push(funcName);\n    currentScope = funcScope;\n    try {\n        visit(funcDecl.block());\n        return VisitorResult.nil();\n    } catch (ReturnException e) {\n        return e.getValue();\n    } finally {\n        currentScope = callerScope;\n        callStack.pop();\n    }\n}\n```\n> 关于内置函数不占用调用栈：内置函数是直接执行的Java代码，不涉及`Scope`切换，也不会递归地重新走一遍`visitFuncCall`，所以既不需要`callStack.push`，也不会有栈溢出的风险，这也是它和用户自定义函数的本质区别——一个是解释器\"解释执行\"的脚本代码，一个是解释器自身\"原生实现\"的能力。\n\n最后放宽`visitArgValue`：\n```java\n@Override\npublic VisitorResult visitArgValue(RuleSetParser.ArgValueContext ctx) {\n    \u002F\u002F 这里不再要求value必须是Double：读取变量本身不应该关心它到底是什么类型，\n    \u002F\u002F 类型是否匹配交给真正使用这个值的地方去检查（比如四则运算会调用getNumber()，\n    \u002F\u002F 不是数值型就会在那里报错），这样像len(arr)这样把数组整体作为参数传递才有可能实现\n    Object value = currentScope.get(ctx.getText());\n    if (value == null) {\n        throw new RuntimeException(\"未定义的参数：\" + ctx.getText());\n    }\n    return VisitorResult.value(value);\n}\n```\n\n## 五、测试代码与执行结果\n\n```java\nString expression =\n        \"array arr = [3, -7, 2, 9] \\n\" +\n                \"number arrLen = len(arr) \\n\" +\n                \"print(arrLen) \\n\" +\n                \"number arrSum = sum(arr) \\n\" +\n                \"print(arrSum) \\n\" +\n                \"number absVal = abs(-7) \\n\" +\n                \"print(absVal) \\n\" +\n                \"number maxVal = max(3, 9) \\n\" +\n                \"print(maxVal) \\n\" +\n                \"number bad = len(5)\";     \u002F\u002F 故意传一个非数组的参数给len\ncalcute(expression);\n```\n\n执行结果如下：\n\n```\n执行:\narray arr = [3, -7, 2, 9]\nnumber arrLen = len(arr)\nprint(arrLen)\nnumber arrSum = sum(arr)\nprint(arrSum)\nnumber absVal = abs(-7)\nprint(absVal)\nnumber maxVal = max(3, 9)\nprint(maxVal)\nnumber bad = len(5)\n\n4.0\n7.0\n7.0\n9.0\nException in thread \"main\" java.lang.RuntimeException: 非Array类型\n\tat cn.irule.VisitorResult.getArray(VisitorResult.java:60)\n\tat cn.irule.MyRuleSetVisitor.lambda$registerBuiltins$0(MyRuleSetVisitor.java:64)\n\t......\n```\n`len(arr)`得到`4.0`，`sum(arr)`算出`3 - 7 + 2 + 9 = 7.0`，`abs(-7)`是`7.0`，`max(3, 9)`是`9.0`，全部正确；最后故意给`len`传了一个数值而不是数组，在`getArray()`里正确地抛出了\"非Array类型\"，说明类型检查被推迟到了真正使用参数的地方，符合预期。另外把上一篇文章数组那个例子（`arr[i]`读写下标、`while (i \u003C len(arr))`遍历）重新跑了一遍，结果和上一篇完全一致，说明这次重构没有破坏原有行为。\n\n## 六、遗留的问题\n\n内置函数注册表解决的是\"新增一种不需要用户写代码就能用的能力\"该怎么落地的问题，但目前这套机制只在`calcu`（也就是数值上下文）里能被调用到，返回值如果不是数值（比如以后想加一个内置的`str(number)`把数字转成字符串），调用方拿到手之后除了打印基本什么都做不了——因为`string`类型的变量目前还只能通过字符串字面量来定义或赋值，没办法接收一个函数调用的结果。这正是本来想在这篇讲的字符串拼接会遇到的同一个根本问题：要让字符串（乃至将来任何类型）都能像数值一样自由地参与表达式计算、被变量接收，`calcu`需要从\"数值表达式\"升级为真正通用的\"表达式\"。下一篇文章我们就来动这个手术。\n","\u003Cp>上一篇文章给语言加上了数组，为了实现\u003Ccode>len(arr)\u003C\u002Fcode>，我们专门在.g4里加了一条\u003Ccode>LEN '(' ID ')'\u003C\u002Fcode>语法。本来这篇文章打算继续讲字符串拼接，但动手之后发现：要让字符串拼接的结果能像数值一样参与后续计算（而不仅仅是打印出来看看），需要把\u003Ccode>calcu\u003C\u002Fcode>从\"只处理数值\"升级成\"能处理任意类型的通用表达式\"，这个改动比预想的要大，值得单独用一篇文章讲清楚。所以这篇文章我们先做一件更基础也更有必要的事——把\u003Ccode>len\u003C\u002Fcode>这种\"每加一个新能力就要改一次.g4\"的做法，换成一套通用的\u003Cstrong>内置函数注册机制\u003C\u002Fstrong>，字符串相关的内容放到下一篇。\u003C\u002Fp>\n\u003Ch2>一、问题：len为什么要单独开一条语法\u003C\u002Fh2>\n\u003Cp>回头看上一篇的写法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>| LEN '(' ID ')'                                      # arrayLen\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这条语法有两个别扭的地方：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Ccode>len\u003C\u002Fcode>长得和普通函数调用一模一样（\u003Ccode>名字(参数)\u003C\u002Fcode>），却因为要拿到数组\"本身\"而不是数组的某个数值，被迫单独设计了一套语法。\u003C\u002Fli>\n \u003Cli>以后每多一个类似的能力（比如求数组之和\u003Ccode>sum\u003C\u002Fcode>、绝对值\u003Ccode>abs\u003C\u002Fcode>），都要重复这个套路——加词法、加语法分支、加对应的\u003Ccode>visitXXX\u003C\u002Fcode>方法，而这些能力本质上并没有引入任何新的语法结构，只是\"内置的、不需要用户自己定义的函数\"而已。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>真正的问题出在\u003Ccode>visitFuncCall\u003C\u002Fcode>里：调用函数时，参数是这样求值的：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">argValues.add(visit(argExpr).getNumber());  \u002F\u002F 旧代码\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>不管函数是用户自己写的还是将来想内置的，参数一律被强制转成\u003Ccode>Double\u003C\u002Fcode>。这就是为什么\u003Ccode>len(arr)\u003C\u002Fcode>没法直接复用\u003Ccode>funcCall\u003C\u002Fcode>语法——\u003Ccode>arr\u003C\u002Fcode>作为参数传进去的一瞬间就被要求必须是数值，数组这个类型直接被拦在了门外。而它的根源还要往前追一步：\u003Ccode>visitArgValue\u003C\u002Fcode>在读取变量的时候就已经做了限制：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 旧代码\nObject value = currentScope.get(ctx.getText());\nif (value instanceof Double) {\n    return VisitorResult.value(value);\n}\nthrow new RuntimeException(\"未定义的参数：\" + ctx.getText() + \" 或参数\" + ctx.getText() + \"定义为非数值型\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>读取一个变量这个动作本身，其实不应该关心它到底是什么类型，这个限制放在这里属于\"操之过急\"。\u003C\u002Fp>\n\u003Ch2>二、解决思路\u003C\u002Fh2>\n\u003Col>\n \u003Cli>放宽\u003Ccode>visitArgValue\u003C\u002Fcode>：读取变量只管把值原样包装成\u003Ccode>VisitorResult\u003C\u002Fcode>返回，类型检查交给真正使用这个值的地方去做（比如四则运算里调用\u003Ccode>getNumber()\u003C\u002Fcode>，不是数值型自然会在那里报错）。\u003C\u002Fli>\n \u003Cli>\u003Ccode>visitFuncCall\u003C\u002Fcode>里参数求值也不再提前转成\u003Ccode>Double\u003C\u002Fcode>，改成保留完整的\u003Ccode>VisitorResult\u003C\u002Fcode>列表。\u003C\u002Fli>\n \u003Cli>新增一个内置函数注册表：\u003Ccode>Map&lt;String, BuiltinFunction&gt;\u003C\u002Fcode>，\u003Ccode>BuiltinFunction\u003C\u002Fcode>是一个函数式接口，接收\u003Ccode>List&lt;VisitorResult&gt;\u003C\u002Fcode>参数、返回一个\u003Ccode>VisitorResult\u003C\u002Fcode>，内部想怎么处理参数、返回什么类型完全自己决定。\u003C\u002Fli>\n \u003Cli>\u003Ccode>visitFuncCall\u003C\u002Fcode>调用时优先查内置函数表，查不到再按老办法查用户自定义函数（\u003Ccode>funcDefine\u003C\u002Fcode>）。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这样\u003Ccode>len\u003C\u002Fcode>就不再需要任何专属语法，直接复用已有的\u003Ccode>ID '(' 参数列表 ')'\u003C\u002Fcode>（也就是\u003Ccode>funcCall\u003C\u002Fcode>）语法即可，\u003Ccode>.g4\u003C\u002Fcode>反而可以做减法——把上一篇专门为\u003Ccode>len\u003C\u002Fcode>加的\u003Ccode>LEN\u003C\u002Fcode>词法和\u003Ccode>arrayLen\u003C\u002Fcode>语法整个删掉。\u003C\u002Fp>\n\u003Ch2>三、语法定义\u003C\u002Fh2>\n\u003Cp>\u003Ccode>.g4\u003C\u002Fcode>这次是做减法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>calcu: calcu opt=(MUL|DIV) calcu                          # mulAndDiv\n     | calcu opt=(ADD|SUB) calcu                          # addAndSub\n     | '(' calcu ')'                                      # parens\n     | NUMBER                                             # number\n     | ID '(' (calcu (',' calcu)*)? ')'                   # funcCall  \u002F\u002F 修改：user函数和内置函数统一走这里\n     | ID '[' calcu ']'                                    # arrayGet\n     | ID                                                 # argValue\n     ;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>删掉了\u003Ccode>LEN '(' ID ')' # arrayLen\u003C\u002Fcode>这一行，以及词法里的\u003Ccode>LEN : 'len' ;\u003C\u002Fcode>——\u003Ccode>len\u003C\u002Fcode>不再是语言的一个语法关键字，只是内置函数表里的一个普通条目。\u003C\u002Fp>\n\u003Ch2>四、语法实现\u003C\u002Fh2>\n\u003Cp>先定义内置函数的接口：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 内置函数的统一接口，和用户用function定义的函数不同，内置函数直接用Java代码实现，\n * 参数以VisitorResult的原始形态传入，具体是数值、字符串还是数组由函数自己按需取用，\n * 不像调用用户函数那样强制把每个参数都转成Double\n *\u002F\n@FunctionalInterface\npublic interface BuiltinFunction {\n    VisitorResult call(List&lt;VisitorResult&gt; args);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注册几个内置函数，作为示例：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F**\n * 注册内置函数。相比每新增一个能力就往.g4里加一条专门的语法规则（比如上一篇文章里len曾经\n * 单独占用的LEN词法和arrayLen语法），注册成内置函数不需要改语法，只要在这里加一行，\n * 新的能力就能通过已有的\"函数调用\"语法直接使用\n *\u002F\nprivate void registerBuiltins() {\n    builtins.put(\"len\", args -&gt; VisitorResult.value((double) args.get(0).getArray().size()));\n    builtins.put(\"sum\", args -&gt; {\n        double total = 0;\n        for (Double item : args.get(0).getArray()) {\n            total += item;\n        }\n        return VisitorResult.value(total);\n    });\n    builtins.put(\"abs\", args -&gt; VisitorResult.value(Math.abs(args.get(0).getNumber())));\n    builtins.put(\"max\", args -&gt; VisitorResult.value(Math.max(args.get(0).getNumber(), args.get(1).getNumber())));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>len\u003C\u002Fcode>和\u003Ccode>sum\u003C\u002Fcode>从参数里取出数组（\u003Ccode>getArray()\u003C\u002Fcode>），\u003Ccode>abs\u003C\u002Fcode>和\u003Ccode>max\u003C\u002Fcode>从参数里取出数值（\u003Ccode>getNumber()\u003C\u002Fcode>）——是数组还是数值完全由内置函数自己按需解读，\u003Ccode>visitFuncCall\u003C\u002Fcode>本身不再对参数类型做任何假设：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">@Override\npublic VisitorResult visitFuncCall(RuleSetParser.FuncCallContext ctx) {\n    String funcName = ctx.ID().getText();\n    List&lt;VisitorResult&gt; argValues = new ArrayList&lt;&gt;();\n    for (RuleSetParser.CalcuContext argExpr : ctx.calcu()) {\n        argValues.add(visit(argExpr));\n    }\n\n    BuiltinFunction builtin = builtins.get(funcName);\n    if (builtin != null) {\n        return builtin.call(argValues);\n    }\n\n    RuleSetParser.FuncDeclContext funcDecl = funcDefine.get(funcName);\n    if (funcDecl == null) {\n        throw new RuntimeException(\"未定义的函数：\" + funcName);\n    }\n    List&lt;String&gt; paramNames = funcDecl.paramList() == null\n            ? Collections.emptyList()\n            : paramNames(funcDecl.paramList());\n    if (argValues.size() != paramNames.size()) {\n        throw new RuntimeException(\"函数\" + funcName + \"需要\" + paramNames.size() + \"个参数，实际传入\" + argValues.size() + \"个\");\n    }\n    if (callStack.size() &gt;= MAX_CALL_DEPTH) {\n        throw new RuntimeException(\"函数调用层级超过\" + MAX_CALL_DEPTH + \"层，可能存在死递归：\" + callStack);\n    }\n\n    Scope callerScope = currentScope;\n    Scope funcScope = new Scope(globalScope);\n    for (int i = 0; i &lt; paramNames.size(); i++) {\n        funcScope.define(paramNames.get(i), argValues.get(i).getNumber());  \u002F\u002F 用户函数的参数仍然只支持数值\n    }\n\n    callStack.push(funcName);\n    currentScope = funcScope;\n    try {\n        visit(funcDecl.block());\n        return VisitorResult.nil();\n    } catch (ReturnException e) {\n        return e.getValue();\n    } finally {\n        currentScope = callerScope;\n        callStack.pop();\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>关于内置函数不占用调用栈：内置函数是直接执行的Java代码，不涉及\u003Ccode>Scope\u003C\u002Fcode>切换，也不会递归地重新走一遍\u003Ccode>visitFuncCall\u003C\u002Fcode>，所以既不需要\u003Ccode>callStack.push\u003C\u002Fcode>，也不会有栈溢出的风险，这也是它和用户自定义函数的本质区别——一个是解释器\"解释执行\"的脚本代码，一个是解释器自身\"原生实现\"的能力。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>最后放宽\u003Ccode>visitArgValue\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">@Override\npublic VisitorResult visitArgValue(RuleSetParser.ArgValueContext ctx) {\n    \u002F\u002F 这里不再要求value必须是Double：读取变量本身不应该关心它到底是什么类型，\n    \u002F\u002F 类型是否匹配交给真正使用这个值的地方去检查（比如四则运算会调用getNumber()，\n    \u002F\u002F 不是数值型就会在那里报错），这样像len(arr)这样把数组整体作为参数传递才有可能实现\n    Object value = currentScope.get(ctx.getText());\n    if (value == null) {\n        throw new RuntimeException(\"未定义的参数：\" + ctx.getText());\n    }\n    return VisitorResult.value(value);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>五、测试代码与执行结果\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-java\">String expression =\n        \"array arr = [3, -7, 2, 9] \\n\" +\n                \"number arrLen = len(arr) \\n\" +\n                \"print(arrLen) \\n\" +\n                \"number arrSum = sum(arr) \\n\" +\n                \"print(arrSum) \\n\" +\n                \"number absVal = abs(-7) \\n\" +\n                \"print(absVal) \\n\" +\n                \"number maxVal = max(3, 9) \\n\" +\n                \"print(maxVal) \\n\" +\n                \"number bad = len(5)\";     \u002F\u002F 故意传一个非数组的参数给len\ncalcute(expression);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行结果如下：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>执行:\narray arr = [3, -7, 2, 9]\nnumber arrLen = len(arr)\nprint(arrLen)\nnumber arrSum = sum(arr)\nprint(arrSum)\nnumber absVal = abs(-7)\nprint(absVal)\nnumber maxVal = max(3, 9)\nprint(maxVal)\nnumber bad = len(5)\n\n4.0\n7.0\n7.0\n9.0\nException in thread \"main\" java.lang.RuntimeException: 非Array类型\n\tat cn.irule.VisitorResult.getArray(VisitorResult.java:60)\n\tat cn.irule.MyRuleSetVisitor.lambda$registerBuiltins$0(MyRuleSetVisitor.java:64)\n\t......\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>len(arr)\u003C\u002Fcode>得到\u003Ccode>4.0\u003C\u002Fcode>，\u003Ccode>sum(arr)\u003C\u002Fcode>算出\u003Ccode>3 - 7 + 2 + 9 = 7.0\u003C\u002Fcode>，\u003Ccode>abs(-7)\u003C\u002Fcode>是\u003Ccode>7.0\u003C\u002Fcode>，\u003Ccode>max(3, 9)\u003C\u002Fcode>是\u003Ccode>9.0\u003C\u002Fcode>，全部正确；最后故意给\u003Ccode>len\u003C\u002Fcode>传了一个数值而不是数组，在\u003Ccode>getArray()\u003C\u002Fcode>里正确地抛出了\"非Array类型\"，说明类型检查被推迟到了真正使用参数的地方，符合预期。另外把上一篇文章数组那个例子（\u003Ccode>arr[i]\u003C\u002Fcode>读写下标、\u003Ccode>while (i &lt; len(arr))\u003C\u002Fcode>遍历）重新跑了一遍，结果和上一篇完全一致，说明这次重构没有破坏原有行为。\u003C\u002Fp>\n\u003Ch2>六、遗留的问题\u003C\u002Fh2>\n\u003Cp>内置函数注册表解决的是\"新增一种不需要用户写代码就能用的能力\"该怎么落地的问题，但目前这套机制只在\u003Ccode>calcu\u003C\u002Fcode>（也就是数值上下文）里能被调用到，返回值如果不是数值（比如以后想加一个内置的\u003Ccode>str(number)\u003C\u002Fcode>把数字转成字符串），调用方拿到手之后除了打印基本什么都做不了——因为\u003Ccode>string\u003C\u002Fcode>类型的变量目前还只能通过字符串字面量来定义或赋值，没办法接收一个函数调用的结果。这正是本来想在这篇讲的字符串拼接会遇到的同一个根本问题：要让字符串（乃至将来任何类型）都能像数值一样自由地参与表达式计算、被变量接收，\u003Ccode>calcu\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},"2101635146136862722","antlr4-10-arrays","Antlr4系列⑩：支持数组","array类型、字面量、下标读写、len",5036,13,"2026-09-20T19:30:48",{"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},"2101647962931642369","antlr4-12-string-concat-expressions","Antlr4系列⑫：字符串拼接与通用表达式","calcu升级为通用表达式、字符串拼接、setArg\u002FassignStmt\u002Fprint合并简化",5692,15,"2026-09-20T20:21:45",{"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},"2101619933819879425","antlr4-07-functions-and-call-stack","Antlr4系列⑦：自定义函数与调用栈","function定义\u002F调用、递归、词法作用域、调用栈防死递归",6508,17,"2026-09-20T18:33:25",{"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","编程与技术笔记","13",{"id":69,"name":70,"slug":71,"description":72,"sortOrder":22,"articleCount":73},"1946000000000000002","随笔","essay","生活与思考","0",{"html":75,"toc":76},"\u003Cp>上一篇文章给语言加上了数组，为了实现\u003Ccode>len(arr)\u003C\u002Fcode>，我们专门在.g4里加了一条\u003Ccode>LEN '(' ID ')'\u003C\u002Fcode>语法。本来这篇文章打算继续讲字符串拼接，但动手之后发现：要让字符串拼接的结果能像数值一样参与后续计算（而不仅仅是打印出来看看），需要把\u003Ccode>calcu\u003C\u002Fcode>从\"只处理数值\"升级成\"能处理任意类型的通用表达式\"，这个改动比预想的要大，值得单独用一篇文章讲清楚。所以这篇文章我们先做一件更基础也更有必要的事——把\u003Ccode>len\u003C\u002Fcode>这种\"每加一个新能力就要改一次.g4\"的做法，换成一套通用的\u003Cstrong>内置函数注册机制\u003C\u002Fstrong>，字符串相关的内容放到下一篇。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、问题：len为什么要单独开一条语法\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>| LEN '(' ID ')'                                      # arrayLen\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>\u003Ccode>len\u003C\u002Fcode>长得和普通函数调用一模一样（\u003Ccode>名字(参数)\u003C\u002Fcode>），却因为要拿到数组\"本身\"而不是数组的某个数值，被迫单独设计了一套语法。\u003C\u002Fli>\n \u003Cli>以后每多一个类似的能力（比如求数组之和\u003Ccode>sum\u003C\u002Fcode>、绝对值\u003Ccode>abs\u003C\u002Fcode>），都要重复这个套路——加词法、加语法分支、加对应的\u003Ccode>visitXXX\u003C\u002Fcode>方法，而这些能力本质上并没有引入任何新的语法结构，只是\"内置的、不需要用户自己定义的函数\"而已。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>真正的问题出在\u003Ccode>visitFuncCall\u003C\u002Fcode>里：调用函数时，参数是这样求值的：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">argValues.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\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\">(argExpr).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 旧代码\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>不管函数是用户自己写的还是将来想内置的，参数一律被强制转成\u003Ccode>Double\u003C\u002Fcode>。这就是为什么\u003Ccode>len(arr)\u003C\u002Fcode>没法直接复用\u003Ccode>funcCall\u003C\u002Fcode>语法——\u003Ccode>arr\u003C\u002Fcode>作为参数传进去的一瞬间就被要求必须是数值，数组这个类型直接被拦在了门外。而它的根源还要往前追一步：\u003Ccode>visitArgValue\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\u002F 旧代码\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Object value \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\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (value \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">instanceof\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Double) {\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\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(value);\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\">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\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\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\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"定义为非数值型\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>读取一个变量这个动作本身，其实不应该关心它到底是什么类型，这个限制放在这里属于\"操之过急\"。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、解决思路\u003C\u002Fh2>\n\u003Col>\n \u003Cli>放宽\u003Ccode>visitArgValue\u003C\u002Fcode>：读取变量只管把值原样包装成\u003Ccode>VisitorResult\u003C\u002Fcode>返回，类型检查交给真正使用这个值的地方去做（比如四则运算里调用\u003Ccode>getNumber()\u003C\u002Fcode>，不是数值型自然会在那里报错）。\u003C\u002Fli>\n \u003Cli>\u003Ccode>visitFuncCall\u003C\u002Fcode>里参数求值也不再提前转成\u003Ccode>Double\u003C\u002Fcode>，改成保留完整的\u003Ccode>VisitorResult\u003C\u002Fcode>列表。\u003C\u002Fli>\n \u003Cli>新增一个内置函数注册表：\u003Ccode>Map&lt;String, BuiltinFunction&gt;\u003C\u002Fcode>，\u003Ccode>BuiltinFunction\u003C\u002Fcode>是一个函数式接口，接收\u003Ccode>List&lt;VisitorResult&gt;\u003C\u002Fcode>参数、返回一个\u003Ccode>VisitorResult\u003C\u002Fcode>，内部想怎么处理参数、返回什么类型完全自己决定。\u003C\u002Fli>\n \u003Cli>\u003Ccode>visitFuncCall\u003C\u002Fcode>调用时优先查内置函数表，查不到再按老办法查用户自定义函数（\u003Ccode>funcDefine\u003C\u002Fcode>）。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这样\u003Ccode>len\u003C\u002Fcode>就不再需要任何专属语法，直接复用已有的\u003Ccode>ID '(' 参数列表 ')'\u003C\u002Fcode>（也就是\u003Ccode>funcCall\u003C\u002Fcode>）语法即可，\u003Ccode>.g4\u003C\u002Fcode>反而可以做减法——把上一篇专门为\u003Ccode>len\u003C\u002Fcode>加的\u003Ccode>LEN\u003C\u002Fcode>词法和\u003Ccode>arrayLen\u003C\u002Fcode>语法整个删掉。\u003C\u002Fp>\n\u003Ch2 id=\"toc-3\">三、语法定义\u003C\u002Fh2>\n\u003Cp>\u003Ccode>.g4\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>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 '(' (calcu (',' calcu)*)? ')'                   # funcCall  \u002F\u002F 修改：user函数和内置函数统一走这里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>     | ID '[' calcu ']'                                    # arrayGet\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>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>删掉了\u003Ccode>LEN '(' ID ')' # arrayLen\u003C\u002Fcode>这一行，以及词法里的\u003Ccode>LEN : 'len' ;\u003C\u002Fcode>——\u003Ccode>len\u003C\u002Fcode>不再是语言的一个语法关键字，只是内置函数表里的一个普通条目。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、语法实现\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:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 内置函数的统一接口，和用户用function定义的函数不同，内置函数直接用Java代码实现，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 参数以VisitorResult的原始形态传入，具体是数值、字符串还是数组由函数自己按需取用，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 不像调用用户函数那样强制把每个参数都转成Double\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\">FunctionalInterface\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\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BuiltinFunction\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">call\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">VisitorResult\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\">}\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 style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F**\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 注册内置函数。相比每新增一个能力就往.g4里加一条专门的语法规则（比如上一篇文章里len曾经\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\"> * 单独占用的LEN词法和arrayLen语法），注册成内置函数不需要改语法，只要在这里加一行，\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\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> registerBuiltins\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    builtins.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"len\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, args \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">((\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) args.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getArray\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    builtins.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"sum\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, args \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        double\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> total \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\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\"> (Double item \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> args.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getArray\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            total \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> item;\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\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(total);\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\">    builtins.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"abs\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, args \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Math.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">abs\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(args.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    builtins.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"max\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, args \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Math.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">max\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(args.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(), args.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">())));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>len\u003C\u002Fcode>和\u003Ccode>sum\u003C\u002Fcode>从参数里取出数组（\u003Ccode>getArray()\u003C\u002Fcode>），\u003Ccode>abs\u003C\u002Fcode>和\u003Ccode>max\u003C\u002Fcode>从参数里取出数值（\u003Ccode>getNumber()\u003C\u002Fcode>）——是数组还是数值完全由内置函数自己按需解读，\u003Ccode>visitFuncCall\u003C\u002Fcode>本身不再对参数类型做任何假设：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitFuncCall\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.FuncCallContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    String funcName \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\">ID\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:#24292E;--shiki-dark:#E1E4E8\">    List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">VisitorResult\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> argValues \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\"> ArrayList&#x3C;>();\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.CalcuContext argExpr \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\">calcu\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        argValues.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\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\">(argExpr));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    BuiltinFunction builtin \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builtins.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(funcName);\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\"> (builtin \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:#24292E;--shiki-dark:#E1E4E8\"> builtin.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">call\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(argValues);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    RuleSetParser.FuncDeclContext funcDecl \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> funcDefine.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(funcName);\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\"> (funcDecl \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\">        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\"> funcName);\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\">    List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> paramNames \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> funcDecl.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paramList\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            ?\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Collections.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">emptyList\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            :\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> paramNames\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(funcDecl.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paramList\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\"> (argValues.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> paramNames.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        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\"> funcName \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\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\"> paramNames.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"个参数，实际传入\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> argValues.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"个\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\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\"> (callStack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> MAX_CALL_DEPTH) {\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\"> MAX_CALL_DEPTH \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\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\"> callStack);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Scope callerScope \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> currentScope;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Scope funcScope \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\">(globalScope);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> paramNames.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(); i\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        funcScope.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">define\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(paramNames.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i), argValues.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getNumber\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 用户函数的参数仍然只支持数值\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    callStack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">push\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(funcName);\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\"> funcScope;\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\">(funcDecl.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">block\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        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>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ReturnException \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\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> e.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getValue\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\">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\"> callerScope;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        callStack.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pop\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>关于内置函数不占用调用栈：内置函数是直接执行的Java代码，不涉及\u003Ccode>Scope\u003C\u002Fcode>切换，也不会递归地重新走一遍\u003Ccode>visitFuncCall\u003C\u002Fcode>，所以既不需要\u003Ccode>callStack.push\u003C\u002Fcode>，也不会有栈溢出的风险，这也是它和用户自定义函数的本质区别——一个是解释器\"解释执行\"的脚本代码，一个是解释器自身\"原生实现\"的能力。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>最后放宽\u003Ccode>visitArgValue\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> VisitorResult \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">visitArgValue\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(RuleSetParser.ArgValueContext ctx) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 这里不再要求value必须是Double：读取变量本身不应该关心它到底是什么类型，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 类型是否匹配交给真正使用这个值的地方去检查（比如四则运算会调用getNumber()，\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 不是数值型就会在那里报错），这样像len(arr)这样把数组整体作为参数传递才有可能实现\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Object value \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\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (value \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\">        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\"> ctx.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getText\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\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\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(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>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"toc-5\">五、测试代码与执行结果\u003C\u002Fh2>\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 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\">        \"array arr = [3, -7, 2, 9] \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 arrLen = len(arr) \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(arrLen) \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 arrSum = sum(arr) \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(arrSum) \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 absVal = abs(-7) \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(absVal) \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 maxVal = max(3, 9) \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(maxVal) \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 bad = len(5)\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;     \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 故意传一个非数组的参数给len\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\">\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>array arr = [3, -7, 2, 9]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number arrLen = len(arr)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print(arrLen)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number arrSum = sum(arr)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print(arrSum)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number absVal = abs(-7)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print(absVal)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number maxVal = max(3, 9)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>print(maxVal)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>number bad = len(5)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>4.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>7.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>7.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>9.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>Exception in thread \"main\" java.lang.RuntimeException: 非Array类型\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\tat cn.irule.VisitorResult.getArray(VisitorResult.java:60)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\tat cn.irule.MyRuleSetVisitor.lambda$registerBuiltins$0(MyRuleSetVisitor.java:64)\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>len(arr)\u003C\u002Fcode>得到\u003Ccode>4.0\u003C\u002Fcode>，\u003Ccode>sum(arr)\u003C\u002Fcode>算出\u003Ccode>3 - 7 + 2 + 9 = 7.0\u003C\u002Fcode>，\u003Ccode>abs(-7)\u003C\u002Fcode>是\u003Ccode>7.0\u003C\u002Fcode>，\u003Ccode>max(3, 9)\u003C\u002Fcode>是\u003Ccode>9.0\u003C\u002Fcode>，全部正确；最后故意给\u003Ccode>len\u003C\u002Fcode>传了一个数值而不是数组，在\u003Ccode>getArray()\u003C\u002Fcode>里正确地抛出了\"非Array类型\"，说明类型检查被推迟到了真正使用参数的地方，符合预期。另外把上一篇文章数组那个例子（\u003Ccode>arr[i]\u003C\u002Fcode>读写下标、\u003Ccode>while (i &lt; len(arr))\u003C\u002Fcode>遍历）重新跑了一遍，结果和上一篇完全一致，说明这次重构没有破坏原有行为。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">六、遗留的问题\u003C\u002Fh2>\n\u003Cp>内置函数注册表解决的是\"新增一种不需要用户写代码就能用的能力\"该怎么落地的问题，但目前这套机制只在\u003Ccode>calcu\u003C\u002Fcode>（也就是数值上下文）里能被调用到，返回值如果不是数值（比如以后想加一个内置的\u003Ccode>str(number)\u003C\u002Fcode>把数字转成字符串），调用方拿到手之后除了打印基本什么都做不了——因为\u003Ccode>string\u003C\u002Fcode>类型的变量目前还只能通过字符串字面量来定义或赋值，没办法接收一个函数调用的结果。这正是本来想在这篇讲的字符串拼接会遇到的同一个根本问题：要让字符串（乃至将来任何类型）都能像数值一样自由地参与表达式计算、被变量接收，\u003Ccode>calcu\u003C\u002Fcode>需要从\"数值表达式\"升级为真正通用的\"表达式\"。下一篇文章我们就来动这个手术。\u003C\u002Fp>",[77,81,84,87,90,93],{"id":78,"text":79,"level":80},"toc-1","一、问题：len为什么要单独开一条语法",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","五、测试代码与执行结果",{"id":94,"text":95,"level":80},"toc-6","六、遗留的问题"]