计算器引擎

OpenModsLib 自带一套完整的数学表达式引擎(包 openmods.calc,共 39 个根类 + 56 个 parsing 类 + 83 个 types 类 + 13 个 command 类)。它不对玩家暴露方块或物品,而是作为库 API 供其它 mod 嵌入自己的命令系统;唯一直接面向玩家的入口是客户端的 =* 系列指令(见计算器指令)。

引擎本体位于 openmodslib/calc/,类型后端与多类型值系统见类型后端与多类型值系统。

基本信息

属性 值
根包 openmods.calc
核心类型 Calculator<E, M> — E 为值类型,M 为表达式模式
核心文件数 213 个 .java(calc 39 + types 83 + parsing 56 + command 13 + types/* 32)
解析模式 3 种(PREFIX / INFIX / POSTFIX)
玩家入口 客户端指令 =config =eval(别名 =) =fun =let =execute
默认键位 未绑定(KeyBinding 声明在 CalcKey,由 ClientRegistry 注册)

功能

执行模型

一次表达式求值经历 分词 → 解析成语法树 → 编译成可执行对象 → 在栈机上执行 四个阶段:

  1. 分词:Tokenizer 把字符串切成 Token,每个 Token 携带一个 TokenType(src/main/java/openmods/calc/parsing/TokenType.java:15-32),数字类 token 有 5 种(DEC_NUMBER、HEX_NUMBER、OCT_NUMBER、BIN_NUMBER、QUOTED_NUMBER),另有 STRING、SYMBOL、SYMBOL_WITH_ARGS、OPERATOR、LEFT_BRACKET、RIGHT_BRACKET、SEPARATOR、MODIFIER。
  2. 解析:InfixParser(src/main/java/openmods/calc/parsing/InfixParser.java:17)实现 IAstParser,把 token 流转成 IExprNode 抽象语法树。同包另有 PrefixParser、QuotedParser、PositionalNotationParser 三种解析策略。
  3. 编译:AstCompiler 与 PostfixCompiler 把语法树转成 IExecutable。
  4. 执行:Environment 持有一张全局符号表,执行体在 Frame 上运行 —— Frame = SymbolMap + Stack。

Calculator(src/main/java/openmods/calc/Calculator.java:7)把三件东西绑在一起对外暴露:environment(全局符号表)、compilers(编译器)、printer(值打印器,.java:9,11,13)。它提供 7 个便捷方法(.java:33-59):compileAndExecute、compileExecuteAndPop、compileExecuteAndPrint、compileAndSetGlobalSymbol、compileAndDefineGlobalFunction、getProperties、getProperty/setProperty。

运算符字典

所有算符集中注册在 OperatorDictionary<E>(src/main/java/openmods/calc/OperatorDictionary.java:11)中,分三张表:

  • 二元运算符 binaryOperators,经 registerBinaryOperator 注册(.java:42),返回的包装器可调 setDefault() 设定默认运算符(.java:28)
  • 一元运算符 unaryOperators,经 registerUnaryOperator 注册(.java:50)
  • defaultOperator 单一默认算符,经 registerDefaultOperator 注册(.java:57)

allOperators()(.java:66)返回二者的并集,用于指令补全。重复注册会直接 Preconditions.checkState 失败而非静默覆盖(.java:46,54,59)。

BinaryOperator<E>(src/main/java/openmods/calc/BinaryOperator.java:5)带 precedence(优先级,.java:29)与 associativity(结合性,.java:31),默认为左结合 Associativity.LEFT(.java:7)。它有两个子类形态:Direct(直接对栈值运算,.java:43)与 Scoped(可访问符号表,.java:66)。

三种表达式模式

ExprType(src/main/java/openmods/calc/ExprType.java:3)枚举解析模式,字段 hasSingleResult 决定求值结果是单值还是留在栈上:

模式 声明行 hasSingleResult
PREFIX ExprType.java:5 true
INFIX ExprType.java:6 true
POSTFIX ExprType.java:7 false

INFIX 是指令的默认模式(src/main/java/openmods/calc/command/CalcState.java:269)。当 hasSingleResult 为 false 时 =eval 不打印返回值,只回报栈大小(src/main/java/openmods/calc/command/CommandCalcFactory.java 中 eval 分支)。

栈操作内建函数

GenericFunctions.createStackManipulationFunctions(src/main/java/openmods/calc/GenericFunctions.java:54)在所有类型后端上无条件注入 3 个全局符号(由 SimpleCalculatorFactory.create 调用,src/main/java/openmods/calc/SimpleCalculatorFactory.java:25):

符号 参数 行为 行
swap 2,2 交换栈顶两个值 GenericFunctions.java:56
pop 变参 丢弃 N 个栈值(默认 1) GenericFunctions.java:70
dup 变参 复制栈值(入参 N,出参默认 2N) GenericFunctions.java:84

swap 是 FixedCallable(固定 2 参 2 返回),pop 显式拒绝非 0 返回值(.java:75-76),dup 按 i % in 循环复用(.java:105)。

累加器函数族

GenericFunctions.AccumulatorFunction<E>(src/main/java/openmods/calc/GenericFunctions.java:23)是变参聚合函数的基类,被 min/max/sum/avg 等复用。源码注释明确警告其正确性前提:

// WARNING: this assumes 'accumulate' operation is associative!

(GenericFunctions.java:21)。0 参调用返回 null 值,1 参直接返回该值,否则从栈上依次弹出并按 后进先出 顺序聚合(.java:32-45),最后经 process 钩子收尾(.java:47)。

数值

数值名 值 来源
calc 包下 .java 文件数 213 find src/main/java/openmods/calc -name '*.java' | wc -l
其中 types 子包 83 同上按子目录统计
parsing 子包 56 同上
command 子包 13 同上
根包类 39 同上
表达式模式数 3 ExprType.java:5-7
无条件注入的栈函数 3(swap/pop/dup) GenericFunctions.java:56,70,84

交互

触发 行为
客户端输入 = 开头的表达式 由 CalcKey 打开聊天框并预填 = (src/main/java/openmods/calc/command/CalcKey.java)
=eval <表达式> 编译并执行;单值模式打印结果,栈模式回报栈大小
=let <名字> <表达式> 把表达式结果绑定为全局符号(CommandCalcFactory let 分支)
=fun <名字> <参数个数> <函数体> 定义全局函数(CommandCalcFactory fun 分支)
=config mode <PREFIX|INFIX|POSTFIX> 切换解析模式(CommandCalcFactory config.mode 分支)

引擎本身无方块、无物品、无 GUI;全部玩家可见交互都经由计算器指令进入。

错误处理

  • 解析期异常族:InvalidTokenException、UnfinishedExpressionException、UnmatchedBracketsException、NonExpressionException(均在 src/main/java/openmods/calc/parsing/)
  • 运行期:ExecutionErrorException(src/main/java/openmods/calc/ExecutionErrorException.java)、StackValidationException、StackUnderflowException
  • 指令层统一包装:CommandCalc.processCommand 捕获 NestedCommandException(红色聊天输出)与其它 Exception(合并整条 cause 链后抛 CommandException,键 openmodslib.command.calc_error)

相关条目