Formula 模块¶
modules/formula 是 KasugaLib 的通用表达式引擎:compute(算术)+ logic(布尔逻辑),带命名空间作用域的变量与函数注册。它是零 Minecraft 依赖的纯 JVM 库——不触碰游戏本体,可供 modelling、core、scripting 等任意模块复用。
引擎从 1.0 Forge 版外部依赖 Mixed-Arithmetic-Logic-Interpreter 迁移而来,是 Blockbench data_points 表达式层(如 "time * 360"、"math.sin(time * 180) * 30"、query.anim_time * 45)以及未来 FSM 条件的基础设施。
前置要求¶
- Java 21(模块 Gradle toolchain 固定为 21)。
- 无需 Minecraft/NeoForge 运行时,单元测试跑在纯 JUnit 5 上。
- 模块通过
settings.gradle(include ':modules:formula')注册;消费方加implementation project(":modules:formula")。
快速开始¶
import lib.kasuga.formula.Code;
import lib.kasuga.formula.compute.data.Namespace;
import lib.kasuga.formula.compute.infrastructure.Formula;
// 新命名空间继承根命名空间的所有内置函数与常量。
Namespace ns = new Namespace(Code.ROOT_NAMESPACE);
Formula f = Code.decodeFormula("sin(rad(time * 180)) * 30", ns);
ns.assign("time", 0.5f); // 变量首次使用自动注册(初值 0)
float result = f.getResult(); // sin(90°) * 30
逻辑表达式同样解码:
var cond = Code.decodeLogical("a > 3 and b <= 10", ns);
ns.assign("a", 5f);
ns.assign("b", 9f);
boolean ok = cond.getResult(); // true
算术公式¶
运算符¶
| 运算符 | 含义 | 优先级 |
|---|---|---|
^ |
幂(Math.pow) |
最高 |
* / % |
乘、除、取模——同级,左结合 | 中 |
+ - |
加、减——同级,左结合 | 最低 |
% 与 * / 同级:10 % 3 * 5 按 (10 % 3) * 5 = 5 计算,而不是 10 % 15。
** 是 ^ 的别名(Python 风格):2 ** 3 == 8。
一元负号全形式支持:-5、-(2 + 3)、2 * -3、3 - -2。括号可任意嵌套。
内置函数¶
由 Code 注册在根命名空间:
| 参数个数 | 函数 |
|---|---|
| 1 | cos sin tan asin acos atan log lg exp round sqrt rad deg floor ceil |
| 2 | pow max min |
| 3 | 默认无——用 namespace.register3Param(...) 注册 |
三角函数按弧度计算;rad/deg 负责角度与弧度互转。参数用逗号分隔:pow(2, 10)、max(3, 7)。
静态常量¶
pi 与 e 注册为只读静态变量(位于 STATIC_VARS,对其 assign 是空操作)。
变量¶
- 名称以小写字母开头:
[a-z][a-z1-9]*,可含_。 - 点号命名做变量作用域分隔:
query.anim_time是 codec 含.的单一变量。 - 表达式中用到的变量自动注册到实例表(初值 0);每次
getResult()都会读namespace.assign("x", v)写入的最新值。
逻辑公式¶
比较运算符¶
>、<、>=、<=、==、!=,以及 <>(!= 的别名)。符号型运算符不要求两侧空格:a>b、a>=5、a+1==6 都能正确解析。
布尔运算符¶
and、or、not(单词型运算符)。因为是单词,必须有两侧空格才能与变量名区分:nota 是变量,not a 是否定。布尔字面量写作 True / False(首字母大写)。数值非零即真(truthy)。
优先级¶
从高到低:==、!=、> <、>= <=、not、and、or。用括号覆盖:not (a > 3)。
限制¶
- 不支持链式比较:
a > b > c抛FormulaSyntaxError(中间结果是真值而非数值)。请写成a > b and b > c。 - 同理,一个表达式内混用多个关系运算符(如
a == b > c)不受支持。
命名空间与注册¶
Namespace 是符号表。子命名空间在构造时拷贝父级的函数、静态变量与实例变量;之后的注册只影响自身。
| API | 行为 |
|---|---|
register(codec, function) |
注册函数实例 |
register1Param/2Param/3Param(codec, computer) |
用 lambda 注册函数 |
register(codec, value) |
注册静态(只读)变量 |
registerInstance(codec, assignable) |
注册运行时变量(表达式用了未知名时自动创建) |
assign(codec, value) |
写运行时变量 |
containsInstance(codec) / getInstance(codec) |
查询实例表 |
clone() |
浅拷贝——map 被复制,parent 链接保留 |
Code 是根命名空间的静态门面:Code.decodeFormula、Code.encodeFormula、Code.decodeLogical、Code.encodeLogical、Code.root()。
错误处理¶
所有异常都继承 RuntimeException:
| 异常 | 含义 |
|---|---|
FormulaSyntaxError |
表达式畸形(括号不配对、运算符序列非法、比较中出现非数值操作数等)。携带可选 message 与 (formula, position) 提示。 |
FormulaParseError |
某个 token 无法解析成任何公式元素;包装底层 FormulaSyntaxError 与出错输入串。 |
FormulaOperationError |
运算符把运算符当成了操作数(如连续两个运算符)。 |
开发与调试¶
在仓库根目录运行:
.\gradlew.bat :modules:formula:test
.\gradlew.bat :modules:formula:compileJava
只跑一个测试类:
.\gradlew.bat :modules:formula:test --tests lib.kasuga.formula.FormulaComputeTest
单元测试覆盖:运算符优先级(含 % 与 * / 同级)、** 幂、一元负号、函数嵌套、变量赋值/重赋值、点号命名、无空格符号运算符、单词运算符空格要求、链式比较报错。
相关源码位置¶
| 区域 | 源码 |
|---|---|
| 静态门面与内置函数 | modules/formula/src/main/java/lib/kasuga/formula/Code.java |
| 符号表 | modules/formula/src/main/java/lib/kasuga/formula/compute/data/Namespace.java |
| 算术行解析/求值 | modules/formula/src/main/java/lib/kasuga/formula/compute/data/Line.java |
| 数字/运算符/变量原子 | modules/formula/src/main/java/lib/kasuga/formula/compute/data/{Numeric,Operational,Variable}.java |
| 函数族 | modules/formula/src/main/java/lib/kasuga/formula/compute/data/functions/ |
| 逻辑行解析/求值 | modules/formula/src/main/java/lib/kasuga/formula/logic/data/LogicalLine.java |
| 逻辑原子与运算符 | modules/formula/src/main/java/lib/kasuga/formula/logic/{data,operations}/ |
| 异常 | modules/formula/src/main/java/lib/kasuga/formula/compute/exceptions/ |
| 单元测试 | modules/formula/src/test/java/lib/kasuga/formula/ |