计算器引擎
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 注册) |
功能
执行模型
一次表达式求值经历 分词 → 解析成语法树 → 编译成可执行对象 → 在栈机上执行 四个阶段:
- 分词:
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。 - 解析:
InfixParser(src/main/java/openmods/calc/parsing/InfixParser.java:17)实现IAstParser,把 token 流转成IExprNode抽象语法树。同包另有PrefixParser、QuotedParser、PositionalNotationParser三种解析策略。 - 编译:
AstCompiler与PostfixCompiler把语法树转成IExecutable。 - 执行:
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)