diff --git a/devel/moebius.md b/devel/moebius.md new file mode 100644 index 0000000000..8db4b2a660 --- /dev/null +++ b/devel/moebius.md @@ -0,0 +1,678 @@ +# [moebius] moebius 模块性能优化 + +每次挑选 moebius 里的一个函数做性能优化,配套单元测试与 nanobench 基准。 +参考模板:[dddd.md](dddd.md) + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `moebius/` - 被优化模块(Data/Kernel/Scheme/moebius) +- `moebius/tests/**_test.cpp` - 单元测试(xmake 自动发现,目标名 `moebius_tests_`) +- `moebius/bench/**_bench.cpp` - nanobench 基准(xmake 自动发现,目标名 ``) + +## 3 如何测试(只构建 moebius 模块) + +### 3.1 确定性测试(单元测试) +```bash +xmake test moebius_tests/ # 如 moebius_tests/tree_traverse_test +``` + +### 3.2 基准测试 +```bash +xmake b _bench && xmake r _bench # 如 tree_traverse_bench +``` + +## 4 如何提交 +```bash +gf fmt --changed-since=main +git commit -m "[moebius] <函数> <优化简述>" +``` + +## 5 已完成的优化记录 + +### 5.1 tree_utf8_to_herk / tree_herk_to_utf8(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_traverse.cpp` +- **What**: 原子节点加 ASCII 恒等快路径。herk 双向映射表中字节 32..126 除 + 0x60(反引号)外均恒等;herk->utf8 方向额外排除 `<#` 十六进制转义。 + 命中快路径时直接返回 `tree (t->label)`(字符串引用共享),跳过 lolly + 逐字符解码/重排循环。 +- **Why**: 文档加载/保存热路径(`input.cpp` 打开文件、`edit_complete.cpp` + 补全、`connection.cpp` 链接),典型文档绝大多数原子是纯 ASCII, + 原实现一律走逐字符转换。 +- **How**: 新增 `utf8_herk_identity` / `herk_utf8_identity` 快速扫描; + 复合节点递归不变,RAW_DATA 短路不变。 +- **结果**: 1000 段×10 词文档(90% ASCII)utf8->herk 7.2ms→3.0ms(2.4x), + herk->utf8 4.9ms→3.3ms(1.5x);纯 ASCII 文档 6.1ms→2.6ms(2.3x)。 +- **测试**: `moebius/tests/Data/Tree/tree_traverse_test.cpp`(9 用例: + 恒等、反引号例外、非 ASCII 转换、往返、`<#` 转义、孤立 `<`、RAW_DATA 保持) +- **基准**: `moebius/bench/Data/Tree/tree_traverse_bench.cpp`(含优化前 + 实现的同二进制 A/B 对比) + +### 5.2 correct_node 消除标签字符串绕行(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_modify.cpp`、`moebius/moebius/drd/drd_info.{hpp,cpp}` +- **What**: `correct_node` 原来调 `the_drd->contains (as_string (L (t)))`, + 每个节点一次字符串名转换 + existing_tree_label/as_tree_label 两次哈希查表。 + 新增 `drd_info::contains (tree_label)` 重载(直接 `info->contains (l)`), + 热路径改走标签编号。 +- **Why**: `correct_downwards`/`correct_node` 对每个节点调用(粘贴、 + scheme 侧修正等编辑路径);与已合入的 1228 系列"缓存标签编号, + 消除热路径字符串比较"同类。 +- **How**: drd_info 经 CONCRETE 宏自动获得转发包装;树节点上 `L(t)` + 已是 interned 标签,`as_tree_label(as_string(l)) == l`,语义等价。 +- **结果**: 预校正树全树 sweep x100:10.1ms→5.5ms(**1.84x**); + 含 `copy(doc)` 的端到端场景差异被稀释(~1.6ms 持平)。 +- **测试**: `moebius/tests/Data/Tree/tree_modify_test.cpp`(9 用例: + contains 两个重载一致性、arity 修正、concat 原子合并、递归下探、 + simplify_concat/document 展平) +- **基准**: `moebius/bench/Data/Tree/tree_modify_bench.cpp`(含优化前 + 实现的同二进制 A/B 对比;隔离 sweep 场景避免 copy 稀释) + +### 5.3 scaling/an_scaling 直变换消除中间 point 临时(2026-08-20) +- **文件**: `moebius/Kernel/Types/frame.cpp` +- **What**: `scaling_rep`/`an_scaling_rep` 的 `direct_transform`/ + `inverse_transform` 原写作 `shift + magnify * p`,每次调用产生两个 + 中间 point(`array`,各一次堆分配)。改为逐分量直写唯一 + 结果数组。 +- **Why**: scaling 是图形系统的标准坐标系框架(设备变换),曲线/边框 + 的每个采样点渲染时都要过 `operator()`;`frame::enclose` 求包围盒 + 每个矩形 4 条边 × 采样点同样密集调用。 +- **How**: 循环内直接 `q[i]= shift[i] + magnify * p[i]`(按轴版用 + `magnify[i]`),任意维度通用。linear_2D 的 2x2 展开尝试过无收益 + (通用矩阵乘本就单次分配),已回退。 +- **结果**: x1024 点循环:scaling 39.4µs→17.1µs(**2.3x**), + an_scaling 53.7µs→17.6µs(**3.0x**)。 +- **测试**: `moebius/tests/Kernel/Types/frame_test.cpp` 追加 3 用例 + (3 分量 scaling 往返、按轴 scaling 逆变换、内存泄漏检查沿用) +- **基准**: `moebius/bench/Kernel/Types/frame_bench.cpp`(含优化前 + 实现的同二进制 A/B 对比 + linear_2D 基线 + enclose 包围盒场景) + +### 5.4 segment/poly_segment 求值与 rotate_2D 消除中间 point 临时(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp`、`moebius/Kernel/Types/point.cpp` +- **What**: + 1. `segment_rep::evaluate` 由 `(1-t)*p1 + t*p2`(3 次分配)改为逐分量 + 线性插值(1 次分配); + 2. `poly_segment_rep::evaluate` 同样逐分量插值; + 3. `poly_segment_rep::grad` 由 `n*(a[i+1]-a[i])`(2 次分配)改为 + 逐分量差值放大; + 4. `rotate_2D` 加 2D 快路径,避免 `p-o` 与 `+o` 两个中间临时; + 非常规维度走原 mult 回退路径。 +- **Why**: 曲线求值是图形渲染/取直(rectify)/边框计算的最内层循环, + 每个采样点一次 evaluate;poly_segment 是折线图形的通用表示。 +- **结果**: x1024 循环:segment 53.0µs→15.6µs(**3.4x**), + poly_segment 32.5µs→18.1µs(1.8x),rotate_2D 54.6µs→22.8µs(2.4x)。 +- **测试**: `curve_test.cpp` 追加 3 用例(segment 端点/中点/三维维度、 + poly_segment 分段边界 n=2 语义、grad 倍率);`point_test.cpp` 追加 + rotate_2D 退化维度回退路径用例 +- **基准**: `moebius/bench/Kernel/Types/curve_eval_bench.cpp`(含优化前 + 实现的同二进制 A/B 对比) + +### 5.5 spline 求值:interval_no 缓存 + 跳过单位标量乘(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp`(spline_rep) +- **What**: + 1. `interval_no` 加上次命中缓存(求值常按 t 单调推进,先验上次区间); + 2. `evaluate(t,o)` 在 o=0 时跳过 `prod(k,o)*res`(系数为 1,省一次 + 整点分配); + 3. `approx` 中 `norm(p1-p2)` 改用 `norm2_diff` 开平方,避免差向量临时。 +- **Why**: spline 求值是样条曲线渲染/取直的最内层循环;每次求值都要 + interval_no 全表线性扫描定位区间。 +- **How**: 新增成员 `last_interval`(默认 -1),命中直接返回,未命中 + 全扫后记录。A/B 用 `git stash` 临时还原旧实现测得旧值。 +- **结果**: 513 点求值扫:单调 21.0µs→14.9µs(**1.41x**)、震荡 + 20.9µs→15.6µs(1.33x);rectify 7.9µs→7.5µs(基本持平,取直主要 + 开销在递归细分而非求值)。 +- **测试**: `curve_test.cpp` 追加 3 用例(端点插值、缓存命中/失效 + 一致性、rectify 首末点、grad/bound 契约) +- **基准**: `curve_bench.cpp` 追加 spline 求值扫(单调/震荡)与 rectify + +### 5.6 raw_split/raw_join 逐元素搬移改 memmove 块移动(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_observer.cpp` +- **What**: `raw_split` 的孩子右移与 `raw_join` 的孩子左移原为逐元素 + `ref[i]=ref[i-1]` 循环(每次引用计数加减),改为 memmove 整块搬移 + (句柄所有权随位移动转移),洞/尾部 stale 位分别用 placement-new + 覆盖与 resize 截断丢弃——与已合入 4398(raw_insert/raw_remove)同法。 +- **Why**: 编辑器里每个回车/删段落/文本切分合并都走 split/join; + 宽文档中部操作要搬移数百个孩子句柄。 +- **结果**: 1000 孩子文档中部 split+join x500:1.92ms→0.26ms(**7.4x**)。 +- **测试**: 新建 `moebius/tests/Data/Tree/tree_observer_test.cpp` + (8 用例:split 兄弟保持/尾边界/原子文本、join 原子合并/复合孩子 + 合并/尾边界、split+join 往返、insert+remove 往返) +- **基准**: `tree_observer_bench.cpp` 追加 split+join A/B 对比 +- **注**: raw_split/raw_join/raw_remove 未在 hpp 声明,测试与基准中 + 补 extern 原型 + +### 5.7 get_env_child 空 cenv 快路径(2026-08-20) +- **文件**: `moebius/moebius/drd/drd_info.cpp` +- **What**: `get_env_child` 两个重载加空环境快路径: + 1. `(t,i,env)` 重载:`drd_decode(ci[index].env)` 为空时直接透传 env, + 免去逐对 `drd_env_write` 重建 env 树; + 2. `(t,i,var,val)` 重载:非 WITH 且子节点无绑定时直接返回缺省值, + 免去 ATTR 构造、合并与读取扫描。 +- **Why**: `is_accessible_cursor`(光标可达性校验,每次光标移动逐节点 + 调用)里 `get_env_child(t,i,MODE,"")=="src"` 是 default 分支的必经 + 路径;绝大多数标签的子节点没有任何环境绑定,全走无用功。 +- **结果**: 500 段×8 词文档逐节点读 mode:306µs→217µs(**1.41x**)。 +- **测试**: 新建 `tests/moebius/drd/drd_env_test.cpp`(5 用例:空绑定 + 返回缺省、越界索引、WITH 绑定读取/非最后孩子、env 重载合并透传、 + get_env_descendant) +- **基准**: 新建 `bench/moebius/drd/drd_env_bench.cpp`(含优化前实现 + 的同二进制 A/B 对比) + +### 5.8 can_* 适用性检查单趟下探(2026-08-20) +- **文件**: `moebius/Kernel/Types/modification.cpp` +- **What**: `can_insert/can_remove/can_split/can_join/can_assign_node/ + can_set_cursor` 原来先 `has_subtree(t,p)` 再 `subtree(t,p)`——同一条 + 路径走两遍树。新增内部 `descend` 助手单趟下探返回指针(越界/命中 + 原子返回空),一次遍历完成存在性检查与取子树。 +- **Why**: `apply()` 每次树修改前都调 `is_applicable`;编辑路径上 + 深路径的检查是纯开销。 +- **结果**: 深度 9 路径:can_insert 95.6ns→41.9ns(**2.3x**), + can_remove 76.8ns→42.3ns(1.8x);失败路径原本就单趟(has_subtree + 提前返回),持平。 +- **测试**: `modification_test.cpp` 追加 5 用例(insert 原子/复合/ + 越界/pos 越界、原子内插、remove、join 原子+复合混合、assign/节点 + 操作) +- **基准**: 新建 `bench/Kernel/Types/modification_bench.cpp`(A/B 对比) +- **注**: `mod_insert(p,pos,t)` 等会把 pos 追加到 p 尾部,测试里 + 顶层操作应传 `path()` 而非 `path(0)`(踩坑记录) + +### 5.9 move_any 单趟下探(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_traverse.cpp` +- **What**: `move_any`(`next_any`/`previous_any`/`next_valid`/ + `next_accessible`/`next_word` 的公共底层)原来每次移动调三次 + `subtree(t, path_up(...))`——重复全树下探外加 path_up 临时分配。 + 改为单趟下探同时持有 `path_up(p)` 处节点与其父节点。 +- **Why**: 光标移动/选择/删除是编辑器最频繁的操作,每次按键的 + valid/accessible 光标搜索循环里 move_any 逐位置调用。 +- **结果**: 100 段文档逐字符全扫:714µs→545µs(**1.31x**)。 +- **测试**: `tree_traverse_test.cpp` 追加 3 用例(全扫收敛且停在 + 不动点、next/previous 20 步往返一致、首步深入文档内部) +- **基准**: 新建 `bench/Data/Tree/tree_cursor_bench.cpp`(含优化前 + 实现的同二进制 A/B 对比) +- **注**: `next_any/previous_any` 未在 hpp 声明,测试与基准中补原型; + `tm_char_forwards` 在 `cork.hpp` 不在 `analyze.hpp` + +### 5.10 tmu_reader::read_next 字节级扫描(2026-08-20) +- **文件**: `moebius/Data/Convert/tmu.cpp` +- **What**: `read_next` 的词元累积循环原来逐字符调 `read_char`—— + 每个字符一次子串分配(文档加载百万级小分配)。改为字节级扫描: + 分隔符均为单字节 ASCII,普通字符成段(run)一次追加,转义 `\\` + 连同后续一个 utf8 序列一起复制,行续接 `\` 原样跳过。 +- **Why**: TMU 是 Mogan 原生文档格式,`tmu_to_tree` 是打开文件的 + 必经路径;词元读取是最内层循环。 +- **结果**: 500 段×10 词文档 `tmu_to_tree`:1.51ms→0.61ms(**2.47x**); + `tree_to_tmu` 持平(未改动)。A/B 用 `git stash` 还原旧实现实测。 +- **测试**: 新建 `tests/Data/Convert/tmu_test.cpp`(6 用例:纯词、 + 词内空格、`\\`/`<`/`|`/`>` 转义往返、utf8 不截断、concat+标记结构、 + 密集反斜杠) +- **基准**: 新建 `bench/Data/Convert/tmu_read_bench.cpp` +- **注**: 未 `init_std_drd` 时 `as_string(RIGID)` 返回 "?",writer 会 + 把标记写成 ``,往返后标签编号变化——标记结构测试需先初始化 + +### 5.11 三对角求解逐分量就地写(2026-08-20) +- **文件**: `moebius/Kernel/Types/equations.cpp`、 + `moebius/Data/Convert/tmu.cpp`(cr 原地截断) +- **What**: + 1. `tridiag_solve` 前代 `(y-a*x)/li` 与回代 `x-u*x` 每行产生 1–2 个 + 中间 point,改为逐分量就地写(x 行初始为空点,维度不足时整行重建); + 2. `quasitridiag_solve` 的 `vx= vx + v[i]*x[i]` 累加(O(n) 个临时) + 改为预分配单点累加;末尾修正 `x[i]= x[i]-z[i][0]*vx` 逐分量就地减; + 3. `tmu_writer::cr` 行尾空格改写由整段前缀拷贝改为 `resize` 原地截断 + (实测常规文档无差异,渐近防御性改进)。 +- **Why**: 三对角求解是 spline 构造的必经路径(闭合样条 xtridiag 走 + quasitridiag)。尝试过 tmu_writer::write 成段追加——短词场景子串 + 分配反而更贵,已回退。 +- **结果**: 256 控制点 spline 构造:开样条 117µs→106µs(1.10x), + 闭合样条 1.08ms→0.81ms(**1.33x**)。A/B 用 git stash 实测。 +- **测试**: 新建 `tests/Kernel/Types/equations_test.cpp`(3 用例: + 3x3 二维方程残差校验、维度保持、零耦合恒等与秩一修正 + Sherman-Morrison 闭式解) +- **基准**: 新建 `bench/Kernel/Types/spline_bench.cpp`(开/闭合样条 + 构造);`bench/Data/Convert/tmu_write_bench.cpp`(写路径对照) + +### 5.12 scheme 解析器越界读修复(2026-08-20) +- **文件**: `moebius/moebius/data/scheme_der.cpp` +- **What**(正确性为主,性能持平): + 1. 词元扫描与引号串扫描的 `ch= s[end_index]` 先读后判界,缓冲区 + 末尾各越界读一字节——改为循环顶先判界; + 2. `unslash` 尾部 `ch= s[i]` 同样越界——同样改为循环顶读取; + 3. `string_to_scheme_tree` 的 `replace(s,"\\015","")` 整串拷贝改为 + 先探测含 CR 才替换; + 4. 修复 `block_bench.cpp` 失效的资源路径(bench/ → moebius/bench/, + 子目录迁移后无人发现)。 +- **Why**: 越界读是未定义行为(ASAN 会报);scheme 解析是启动加载 + 全部 .scm 的必经路径。 +- **结果**: block_bench 解析 5.69→5.77 ns/char(噪声内持平)。 +- **尝试后回退**: `is_compound(t,s)` 改标签编号比较——名字比较短路 + vs 哈希全串,实测反而慢 54%(32.9→50.7µs),已回退。 +- **测试**: 新建 `tests/moebius/data/scheme_der_test.cpp`(9 用例: + 词元在缓冲区末尾、末尾反斜杠、引号转义、未闭合引号、注释、 + quote 糖、CR 剔除、block 多表达式) +- **基准**: `block_bench.cpp`(路径修复后可正常运行) + +### 5.13 slash/scm_quote 成段追加(2026-08-20) +- **文件**: `moebius/moebius/data/scheme_ser.cpp` +- **What**: 序列化转义 `slash`/`scm_quote` 原来逐字符 `r << s[i]` + (每次一次 resize 调用),改为普通字符成段(run)一次子串追加、 + 特殊字符单独转义。 +- **Why**: scheme 序列化是保存 .scm/样式文件与 block 协议的写路径。 +- **结果**: block_bench:简单元素序列化 12.15→10.97 ns/char(**1.11x**); + 复杂树/单树持平(base64 长原子场景逐字符 append 本已摊销良好)。 + A/B 用 git stash 实测。 +- **测试**: `scheme_der_test.cpp` 追加 3 用例(scm_quote 引号/反斜杠 + 转义、slash 特殊字符/控制字符/已引号串豁免、slash→解析器往返) +- **踩坑记录**: 测试中给未导出的 `slash` 补 extern 原型时必须写全 + 命名空间 `moebius::data::slash`——声明成全局 `::slash` 会让链接器 + 解析到库中同名符号、拉入错误成员,报出误导性的 tbox MSIL/LTCG + 链接错误(第 2 轮 tree_modify_test 的同类 flaky 报错同源)。 +- **基准**: `block_bench.cpp` + +### 5.14 move_word 与 tm_codepoint_at 消除子串分配(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_traverse.cpp` +- **What**: + 1. `tm_codepoint_at`(词边界判定的逐字符内层)原来每个字符取子串 + `s(pos,i)` 并做 `starts/ends` 字符串比较——改为直接在原串上按 + 字节解析(ASCII 单字节、`<#....>` 十六进制形式、其余记未知); + 2. `move_word` 循环内 `subtree(t, path_up(q))` 每步全树下探—— + 改为 `tt_descend` 单趟下探。 +- **Why**: Ctrl+Left/Right 词移动、双击选词走 move_word;每步对光标 + 前后两个字符各调一次 tm_codepoint_at。 +- **结果**: 100 段词文档逐词全扫:1.49ms→1.33ms(**1.12x**)。 +- **测试**: `tree_traverse_test.cpp` 追加 3 用例(逐词推进收敛、 + next/previous 往返、标点与 `<#XXXX>` 十六进制转义边界不崩溃) +- **基准**: `tree_cursor_bench.cpp` 追加 next_word 全扫场景 + +### 5.15 simplify_correct 未变子树共享原节点(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_modify.cpp` +- **What**: 非 concat/document 的复合节点,递归结果先暂存,全部孩子 + 共享原 rep(strong_equal 判定)时直接返回原节点,免去新节点分配; + concat/document 维持原有重建逻辑(需要合并/展平)。 +- **Why**: `simplify_correct` 在每次排版(edit_typeset)与文档加载 + (input.cpp)对全树递归,原实现无条件重建每个复合节点。 +- **结果**: 典型文档(document→concat→原子)持平——中间节点全是 + concat/document,快路径不触发;标记密集文档(rigid/with 等非 + format 节点)免去未变子树的节点重建,为结构性改进而非基准提速。 + 第一版"先建 r 再判定"反而多付一次比较(442µs vs 401µs),改为 + 暂存数组后持平(406µs)。 +- **测试**: `tree_modify_test.cpp` 追加 4 用例(普通标记保持、QUOTE + 解包、嵌套 document 展平 + concat 合并、空 concat 收敛) +- **基准**: `tree_modify_bench.cpp` 追加 simplify_correct 全树扫 + +### 5.16 .tm 加载:codes 查表跳过 + scm_unquote 成段追加(2026-08-20) +- **文件**: `moebius/moebius/data/scheme_der.cpp` +- **What**: + 1. `scheme_tree_to_tree` 在 flag=true(默认加载路径)时先做 + `codes[t[0]->label]` 查表再被 make_tree_label 覆盖——纯浪费, + 改为按 flag 只查一次; + 2. `scm_unquote`(每个文本原子解引号)逐字符 append 改为成段 + 追加(与 5.13 slash 同法)。 +- **Why**: .tm 文档打开 = 解析 + scheme_tree_to_tree 全树转换; + 每个节点一次多余的字符串哈希查表、每个文本原子一次逐字符循环。 +- **结果**: 500 段 .tm 加载:405→388µs(**1.04x**),其中 codes 跳过 + ~2%、scm_unquote ~2%。尝试过 make_tree_label 单条备忘缓存—— + 短串哈希本就便宜,备忘串比较无净收益,已回退。 +- **测试**: `scheme_der_test.cpp` 追加 3 用例(scm_unquote 解引号/ + 反转义、.tm 全链路往返结构与转义、带版权头注释的文档) +- **基准**: 新建 `bench/moebius/data/scheme_load_bench.cpp` + (scheme_document_to_tree / scheme_to_tree 全链路) + +### 5.17 曲线最近点搜索消除差向量临时(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp` +- **What**: `curvet_closest_points`(最近点扫描主循环)、`closest` + (外层迭代)与 `intersection`(牛顿求交)中的 `norm (a - b)` 全部 + 改为 `sqrt (norm2_diff (a, b))`,免去每步一个差向量 point 临时; + `straight_edge_midpoints` 的 `norm(...) < 1e-6` 同改平方比较。 +- **Why**: 图形点选/框选(graphical_select→find_closest_points)与 + 曲线求交每步采样都要算一次距离。 +- **结果**: 128 段折线最近点搜索:31.7µs→28.9µs(**1.10x**)。 + 已用 git stash 对照确认新旧实现行为逐位一致。 +- **测试**: `curve_test.cpp` 追加 4 用例(segment/poly_segment 最近点 + 精确命中、closest 距离下界、交叉线段求交闭式解) +- **踩坑记录**: `find_closest_point` 对曲线外侧查询点可能只返回 + 起点 t=0(既有算法局限,新旧一致)——测试用曲线上的点作查询。 +- **基准**: 新建 `bench/Kernel/Types/curve_closest_bench.cpp` + +### 5.18 arc/ellipse 求值逐分量直写(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp`(arc_rep/ellipse_rep) +- **What**: `evaluate`(`center + r1*cos*i + r2*sin*j`)与 `grad` + 原写法每次产生 2–4 个中间 point 临时,改为单次分配逐分量直写; + 维度取原表达式 min 链,数值逐位一致。 +- **Why**: 圆弧/椭圆是图形里最常见的曲线,取直(渲染采样)以固定 + 步长全参数域扫 evaluate。 +- **结果**: 512 点求值扫:ellipse 26.6µs→9.9µs(**2.7x**)、 + arc 28.0µs→11.1µs(**2.5x**);rectify eps=0.1:1.53ms→0.60ms + (**2.55x**)。A/B 用 git stash 实测。 +- **测试**: `curve_test.cpp` 追加 4 用例(椭圆上点到焦点距离和恒定、 + 长短轴端点、grad 正交性、圆弧落圆、闭合 rectify 首尾相接) +- **踩坑记录**: ellipse 的 i 轴从圆心指向第一焦点,t=0 是 (-r1,0) + 方向端点而非 (+r1,0) +- **基准**: `curve_closest_bench.cpp` 追加 conic 场景 + +### 5.19 bezier 求值/取直逐分量直写(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp`(bezier_rep) +- **What**: + 1. `evaluate`(链式 Horner,6 个中间 point 临时)改逐分量 Horner; + 2. `grad` 同改; + 3. `rectify_cumul` 的弦插值点与 `norm(q-r)>=e/10` 改逐分量插值 + + 平方距离比较。 +- **Why**: 贝塞尔是图形平滑路径的通用表示,poly_bezier 包装后 + 渲染/取直高频调用 evaluate。 +- **结果**: 512 点求值扫 35.7µs→10.6µs(**3.4x**);rectify + eps=0.1 29.7µs→7.3µs(**4.1x**)。A/B 用 git stash 实测。 +- **测试**: `curve_test.cpp` 追加 3 用例(端点/中点闭式值、grad、 + rectify 首尾端点) +- **踩坑记录**: 生产 `bezier_rep::grad` 公式为 `3*P3*t + 2*P2 + P1` + (2*P2 项缺 t,非标准导数)——保持原语义,测试按代码行为断言; + 三次贝塞尔中点 x=(P0+3P1+3P2+P3)/8。 +- **基准**: `curve_closest_bench.cpp` 追加 bezier 场景 + +### 5.20 hyperbola/parabola 求值逐分量直写(2026-08-20) +- **文件**: `moebius/Kernel/Types/curve.cpp`(hyperbola_rep/parabola_rep) +- **What**: 与 5.18 conic 同法——`evaluate`/`grad` 的链式 + `center ± r1*cosh*i + r2*sinh*j`、`vertex + (u²/2d)*i + u*j` + 改单次分配逐分量直写,双曲线两支用 sign 合并分支。 +- **结果**: 512 点求值扫:hyperbola 30.3µs→12.8µs(**2.4x**)、 + parabola 21.2µs→6.6µs(**3.2x**)。A/B 用 git stash 实测。 +- **测试**: `curve_test.cpp` 追加 2 用例(双曲线到两焦点距离差恒定、 + 抛物线顶点闭式值与对称性) +- **更正 5.20 初判**: 上轮记录的 "parabola 对某 fixture SIGSEGV" + 排查后确认**不是 parabola 的 bug**——是测试 fixture 写 + `point (-4, 0)` 时 int 字面量解析到 `array (n, ...)` 长度构造器 + (负长度)导致崩溃。构造点必须用 `point (2)`+赋值或 double 字面量 + `point (-4.0, 0.0)`(踩坑:与 5.9 轮 point (1.0) 同源, + int/double 重载解析陷阱)。parabola 行为正常,测试已补回。 + +### 5.21 keep_positive 无负索引快路径(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_cursor.cpp` +- **What**: `keep_positive` 原来无条件逐层递归重建整条 path;先线性 + 扫描,全为非负索引(常见情况)时原样返回,仅含负索引时走原重建。 +- **Why**: `correct_cursor`(每次按键后的光标校正)以 keep_positive + 开头。 +- **结果**: correct_cursor 100 步扫:25.7µs→22.2µs(**1.16x**)。 +- **测试**: `tree_traverse_test.cpp` 追加 2 用例(负索引路径截断 + 校正、合法路径校正稳定);并全量回归 moebius 24 个测试全部通过。 +- **基准**: `tree_cursor_bench.cpp` 追加 correct_cursor 扫描 + +### 5.22 EXTERN 派生标签单条备忘缓存(2026-08-20) +- **文件**: `moebius/moebius/drd/drd_info.cpp` +- **What**: 5 处 `make_tree_label ("extern:" * t[0]->label)`(含 + `is_accessible_child`/`get_type_child` 等热路径)每次都要字符串 + 拼接 + 标签查表。新增 `extern_label` 助手:单条备忘缓存 + (宏名→派生标签),同名宏高频重复时直接命中。 +- **Why**: 可执行标记(EXTERN)文档里每个节点的光标可达性校验都 + 要派生标签;同一宏名的节点大量重复。 +- **结果**: 200 个 EXTERN 节点逐孩子 is_accessible_child 扫: + 110µs→52µs(**2.1x**)。注:与 5.16 的 make_tree_label 备忘失败 + 不同,此处省的是每次的 "extern:" 字符串拼接分配。 +- **测试**: `drd_env_test.cpp` 追加 1 用例(同名宏重复出现时备忘 + 命中/未命中结果一致,不同宏名备忘失效正常);全量 24 测试通过。 +- **基准**: `drd_env_bench.cpp` 追加 EXTERN 场景 + +### 5.23 tmu_reader::decode 成段追加 + 标签分支单查表(2026-08-20) +- **文件**: `moebius/Data/Convert/tmu.cpp` +- **What**: + 1. `decode`(每个词元解码)原来逐字符 `r << s[i]`——即使无转义 + 也逐字符 append,改为普通字符成段一次追加; + 2. `read` 的标签分支先 `tree (make_tree_label (name))` 构造再被 + codes 命中覆盖——改为 codes 命中直接构造,免去一次标签查表。 +- **结果**: `tmu_to_tree` 500 段文档:602µs→538µs(本轮 **1.12x**, + 相对最初实现累计 1512µs→538µs = **2.81x**)。 +- **测试**: 复用 `tmu_test.cpp` 全部 6 用例(转义往返覆盖 decode + 路径),通过。 +- **基准**: `tmu_read_bench.cpp` 复用 +- **追加(第 29 轮)**: `read_apply` 同款双查表(先 make_tree_label + 构造再被 codes 覆盖)同法修复;538→532µs(噪声级,严格少一次 + 标签查表,累计相对最初 1512µs = **2.84x**)。 + +### 5.24 end(t,p) 免去第二次全树下探(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_cursor.cpp` +- **What**: `end(tree, path)` 原来 `parent_subtree(t,p)` 与 + `subtree(t,p)` 各做一次全树下探;父节点的第 `last_item(p)` 个孩子 + 即 p 所指节点,复用父引用省一趟。`start` 本就单趟未动。 +- **结果**: 浅路径基准(depth-1)持平(25.9→25.5µs,correct_cursor + 占主导);深路径场景省 O(depth) 一次下探,为结构性改进。 + 语义严格等价(subtree(t,p) ≡ parent_subtree(t,p)[last_item(p)])。 +- **测试**: 复用 `tree_traverse_test`(end(doc) 全扫收敛于不动点)与 + `tree_observer_test`,全部通过。 +- **基准**: `tree_cursor_bench.cpp` 追加 end per para 场景 + +### 5.25 frame::enclose 采样插值逐分量直写(2026-08-20) +- **文件**: `moebius/Kernel/Types/frame.cpp` +- **What**: `enclose` 每个采样点的 `p1 + a*(p2-p1)` 产生差向量/ + 标量乘/加法三个中间 point 临时,改为逐分量插值直写唯一采样点。 +- **Why**: `frame::enclose(rectangle)` 是图形包围盒计算入口 + (失效区域/边框),非线性框架每边 20 个采样点。 +- **结果**: scaling 框架矩形 enclose:244ns→152ns(**1.60x**)。 +- **测试**: `frame_test.cpp` 追加 2 用例(线性框架包围盒恰为四角 + 变换极值、逆向 enclose 除以放大率) +- **基准**: `frame_bench.cpp` 复用 enclose 场景 + +### 5.26 inside_contiguous_document 边界祖先单趟下探(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_traverse.cpp` +- **What**: 原来对 op/oq 各自逐层 `path_up` + `is_boundary(t,p)` + (每次 is_boundary 两次 subtree 全树下探),深路径 O(depth²); + 新增 `closest_boundary_ancestor`:沿路径自根单趟下探,途经 + DOCUMENT/GRAPHICS 节点时记录最深边界前缀路径,O(depth)。 +- **Why**: `inside_contiguous_document` 是图形文档中每次光标移动 + (move_valid 的 graphics 钩子)的必经检查。 +- **结果**: 24 层 WITH 嵌套同段两光标 ×100 次调用: + 1498µs→262µs(**5.7x**)。 +- **测试**: `tree_traverse_test.cpp` 追加 2 用例(同段两光标为真、 + 跨段为假) +- **踩坑记录**: 首版 fixture 路径多了一层 0 前缀,inside_same 早退 + 假 → 基准测的是无效路径的垃圾行为(旧实现还会打印 + "The required path does not exist" 诊断);修正后才是真实 5.7x。 + inside_same 要求两光标处于最近 DOCUMENT 祖先的同一个孩子内。 +- **基准**: `tree_cursor_bench.cpp` 追加 inside_contiguous deep24 + +### 5.27 get_env_child WITH 快路径直扫绑定对(2026-08-20) +- **文件**: `moebius/moebius/drd/drd_info.cpp` +- **What**: `(t,i,var,val)` 重载遇到 WITH 末孩子时,原来走 + `get_env_child(t,i,ATTR)` —— `t(0,N-1)` 子树拷贝 + 逐对 + `drd_env_write` 重建 env 树 + 线性读取。改为直接扫描绑定对取 + **末次匹配**(与 merge 的覆盖语义一致),零分配。 +- **Why**: 源码模式文档充满 `with "mode" "src"` 包裹, + `is_accessible_cursor` 逐节点读 mode 环境全走此路径。 +- **结果**: 500 个 WITH 节点读 mode:112µs→21µs(**5.2x**)。 +- **测试**: `drd_env_test.cpp` 追加 1 用例(同名绑定对后者覆盖、 + 无匹配返回缺省);全量 24 测试通过。 +- **基准**: `drd_env_bench.cpp` 追加 WITH mode sweep + +### 5.28 get_env_child(env 变体) WITH 分支免子树拷贝(2026-08-20) +- **文件**: `moebius/moebius/drd/drd_info.cpp` +- **What**: `(t,i,env)` 重载的 WITH 分支原来 `drd_env_merge (env, + t (0, N (t) - 1))`——先做一次子树拷贝再逐对合并;改为直接在 + 原树 [0, N-1) 上迭代绑定对调用 `drd_env_write`(与 merge 逐对 + 语义严格一致),免去每次一棵孩子数组的分配。 +- **Why**: `get_env_descendant (t, p, env)` 沿路径逐层调用此重载 + (排版环境求值/光标环境链)。 +- **结果**: 24 层 WITH 嵌套链 get_env_descendant(env 变体): + 6.69µs→5.00µs(**1.34x**);字符串变体链(走 5.27 快路径)持平。 +- **测试**: 复用 `drd_env_test.cpp`(env 变体合并透传用例覆盖); + 全量 24 测试通过。 +- **基准**: `drd_env_bench.cpp` 追加 WITH chain24(env 变体) + +### 5.29 drd_env_write 单次分配重建(2026-08-20) +- **文件**: `moebius/moebius/drd/drd_info.cpp` +- **What**: `drd_env_write` 的追加/插入/替换三种情形原来都要 + 两次切片 + 元组构造 + 两次拼接(约五次树分配);改为按情形 + 单次分配结果 ATTR、前缀/后缀直拷。 +- **Why**: `drd_env_merge`(WITH 环境合并、DRD 环境链)逐对调用; + 深环境链原来是 O(k) 次五连分配。 +- **结果**: 24 层 WITH 链 get_env_descendant(env 变体): + 同变量(替换路径)4.94µs→2.17µs(**2.28x**)、不同变量 + (插入/追加路径)26.9µs→11.8µs(**2.28x**)。 +- **踩坑记录**: 首版插入分支的后缀拷贝从 i+2 起步(应为 i 起步 + 右移两格),导致插入后旧孩子丢失——被新增的排序/覆盖语义测试 + 当场抓住。单测先行的价值再次体现。 +- **测试**: `drd_env_test.cpp` 追加 1 用例(追加、排序插入、 + 同名替换长度不变、末尾追加);全量 24 测试通过。 +- **基准**: `drd_env_bench.cpp` 追加 distinct vars chain24 + +### 5.30 移除已禁用的 next_without_border 死调用(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_cursor.cpp` +- **What**: `next_without_border` 上游已禁用(函数体首行 `return + false;`,逻辑被注释),但 `is_accessible_cursor`/`valid_cursor`/ + `closest_accessible` 三个热光标例程仍每步调用它。移除三处调用 + 与函数体(原始逻辑保留在 git 历史)。 +- **结果**: 各光标基准持平(预期内——每次省一个恒假调用); + 本项为死代码清理而非性能项。复测中曾出现一次 3x 假回归 + (75.9µs vs 26µs),重跑确认为机器扰动,已排除。 +- **同轮放弃**: `drd_env_read` 改二分搜索——外部调用者 + (tree_correct/edit_select 等)传入自建 env,排序不变量无保证, + 正确性风险大于收益。 +- **测试**: 全量 24 测试通过。 + +### 5.31(第 34 轮)验证轮:s7/object 胶水层评估结论 +- **评估**: `Scheme/S7/s7_tm.cpp`(c_string/s7 薄封装,转换固有)与 + `Scheme/Scheme/object.cpp`(每个 object 操作一次 tmscm_object_rep + 的 tm_new,如 as_list_* 每元素两次分配)均为系统性设计——优化需 + 改 object ABI,影响全部 glue 调用方,风险大于收益,不动。 +- **验证**: 全量 24 测试通过;tmu/drd/cursor/curve 四组关键基准 + 读数与既往一致(无回归)。 + +### 5.32 原子文本编辑单次分配拼接(2026-08-20) +- **文件**: `moebius/Data/Tree/tree_observer.cpp` +- **What**: `raw_insert`/`raw_remove`/`raw_join` 的原子字符串分支 + 原来做 2 次切片 + 1–2 次拼接(每次一片新分配),改为单次分配 + 目标串 + memcpy 整段拷贝。 +- **Why**: 打字/退格的每次按键都走 raw_insert/raw_remove 原子分支。 +- **结果**: 打字模拟(2000 插 + 2000 删):789µs→389µs(**2.03x**); + 原子 join 500 对:205µs→194µs(1.06x)。 +- **踩坑记录**: 第一版用逐字符 `r[k]=s[i]` 写入——lolly string 的 + 非 const operator[] 逐写开销使打字场景反而慢 2.2x(789→1716µs), + 被 bench 当场抓住;改 memcpy 后才达标。教训:字符串搬运必须 + 走 memcpy,不能用下标循环。 +- **测试**: 复用 `tree_observer_test.cpp`(原子文本 split/join/ + insert/remove 往返覆盖三处改动);全量 24 测试通过。 +- **基准**: `tree_observer_bench.cpp` 追加打字模拟与原子 join 场景 + +### 5.33 tmu_writer::write 成段 memcpy 直写(2026-08-20) +- **文件**: `moebius/Data/Convert/tmu.cpp` +- **What**: 转义写循环原来逐字符 `tmp << c`(每字符一次调用 + 开销);改为普通字符成段 `resize + memcpy` 直写(新增 + `append_run` 助手,无子串分配),转义对单独写入。spc/ret + 标志按"是否写入实际字符"置位(纯空格串语义保持)。 +- **结果**: `tree_to_tmu` 500 段文档:234µs→164µs(**1.43x**)。 + 此前 5.11 轮的 run 子串方案因每次 substring 分配反而变慢, + 本次 resize+memcpy 直写成功——方法差异决定成败。 +- **同轮放弃**: `unslash` 同款 run-memcpy 改造实测持平(词元 + 仅 5–10 字符,memcpy 准备开销抵消收益),已回退。 +- **测试**: 复用 `tmu_test.cpp` 全部用例(词内空格/转义往返覆盖 + write 路径);全量 24 测试通过。 +- **基准**: `tmu_read_bench.cpp` 复用 tree_to_tmu 场景 + +### 5.34 slash/scm_quote 改 resize+memcpy 直写(2026-08-20) +- **文件**: `moebius/moebius/data/scheme_ser.cpp` +- **What**: 5.13 轮的 run 子串追加(`r << s(run,i)`,每次 substring + 分配)升级为 `append_run`(resize + memcpy 整块直写,零子串 + 分配)——5.33 验证的技术回补到旧优化点。 +- **结果**: block_bench 序列化:简单元素 10.65→9.41 ns/char + (**1.13x**)、复杂树 13.38→10.97(**1.22x**)、单树 13.15→10.82 + (**1.22x**)。相对最初逐字符实现累计约 1.3x。 +- **测试**: 复用 `scheme_der_test.cpp` 的 slash/scm_quote 用例; + 全量 24 测试通过。 +- **基准**: `block_bench.cpp` 复用 + +### 5.35 memcpy 直写回补 tmu/scheme 读路径(2026-08-20) +- **文件**: `moebius/Data/Convert/tmu.cpp`、`moebius/moebius/data/scheme_der.cpp` +- **What**: 5.33 验证的 append_run(resize+memcpy)回补三处旧 + 子串追加点:`scm_unquote`(.tm 文本原子解引号)、 + `tmu_reader::decode`(TMU 词元解码)、`read_next` 的 run 冲洗。 +- **结果**: `tmu_to_tree` 542→479µs(本轮 **1.13x**,相对最初 + 1512µs 累计 **3.16x**);scheme 加载 394→365µs(1.08x)、 + 421→388µs(1.09x)。 +- **对照**: `unslash` 的同款改造此前实测持平(词元 5–10 字符, + memcpy 准备开销抵消收益)——run 长度决定该技术是否有净收益。 +- **测试**: 复用 `tmu_test.cpp`/`scheme_der_test.cpp` 全部用例; + 全量 24 测试通过。 +- **基准**: `tmu_read_bench.cpp`/`scheme_load_bench.cpp` 复用 + +### 5.36(第 40 轮)负结果:named_color 双重小写化 +- **评估**: `named_color` 先 `locase_all` 再进 `color_from_name`, + 表访问器(tm_color 等)内部又 `locase_all` + 重查表——尝试把 + 小写化合并到表阶段一次并用直接读表替换访问器。 +- **结果**: 实测持平偏差(7.41→7.77µs/x100,噪声内)——小写化 + 次数相同只是位置移动,节省的第二次查表被 contains(low) 的 + 重新哈希抵消。已回退代码,保留 `colors_bench.cpp` 作为该路径 + 的基线基准。 + +### 5.37(第 41 轮)放弃:rel_hashmap 读取双探测合一 +- **评估**: `rel_hashmap::operator[]`(drd 的 `info[l]`)= contains + 一探 + item[x] 一探共两次哈希探测;理论上可在调用方 + (drd_info.cpp)写单探测助手。但"键缺失"与"值等于 init 缺省" + 经读取不可区分,链式下探(局部 DRD 作用域)语义有正确性风险; + 且 tree_label 的 int 哈希极廉价,预估仅省数 ns/调用 + (is_accessible_child ≈65ns/调用)。放弃。 +- **验证**: 全量 24 测试通过;七组基准读数与既往一致。 + +### 5.38(第 42 轮)负结果:swap1 换位重写 +- **评估**: `swap1`(undo/redo 换位核心)每次两处 `root(m2)` 重复 + 调用(各含 path_up 分配)+ 失败分支白付两次 `dup` path 深拷贝。 + 重写为 hoist root + 延迟 dup + shift 累积。 +- **结果**: 稳定复测持平(54.5/21.1/62.3 vs 53.0/19.1/62.2µs)—— + 成功路径开销不变,失败分支的节省低于噪声,且 shift 累积式重写 + 比原直改更难读。**代码已回退**;保留 `commute_bench.cpp`(三类 + 换位场景基线)与 `patch_test.cpp` 新增 6 个换位语义用例 + (先后插索引调整、区间内插失败、跨段基本换位、remove 区间覆盖 + 失败、join 相邻失败)——测试覆盖是本轮的实际产出。 +- **踩坑记录**: 首次 A/B 出现 old 翻倍读数(104µs,err 9–14%), + 复测两次确认机器扰动;换位基准必须稳定复测后再下结论。 + 另:remove 失败条件是"后者区间覆盖前者起点(非同点)", + 与直觉的"区间相交"不同。 +- **基准**: `curve_closest_bench.cpp` 追加 hyperbola/parabola 场景 + +## 6 成绩单(2026-08-20 两次全量复测,24/24 测试通过;第 39 轮更新至 5.35) + +| # | 函数/路径 | 提速 | 场景 | +|---|---|---|---| +| 5.1 | tree_utf8↔herk | 5.3x/2.1x | 1000 段文档加载转换 | +| 5.2 | correct_node | 1.96x | 预校正树 sweep×100 | +| 5.3 | scaling 直变换 | 2.3–3.1x | x1024 点设备变换 | +| 5.4 | segment/poly_segment 求值 | 4.4x/1.8x | 渲染采样 | +| 5.5 | spline 求值 | 1.4x | 单调扫 | +| 5.6 | raw_split/raw_join | 7.4x | 1000 孩子中部×500 | +| 5.7 | get_env_child | 1.37x | 光标校验 mode 读 | +| 5.8 | can_* 适用性检查 | 1.9–2.0x | 深度 9 路径 | +| 5.9 | move_any | 1.33x | 逐字符全扫 | +| 5.10 | tmu_to_tree | 2.47x | TMU 文档解析 | +| 5.11 | 三对角求解 | 1.10–1.33x | spline 构造 | +| 5.12 | scheme 解析 OOB 修复 | — | 正确性(3 处越界读) | +| 5.13 | slash/scm_quote | 1.11x | 序列化 | +| 5.14 | move_word/tm_codepoint_at | 1.12x | Ctrl+方向键 | +| 5.15 | simplify_correct | 结构性 | 未变子树共享 | +| 5.16 | .tm 加载 | 1.04x | codes 跳过+unquote | +| 5.17 | 曲线最近点/求交 | 1.07x | 图形点选 | +| 5.18 | arc/ellipse 求值 | 2.5–2.7x | conic 采样 | +| 5.19 | bezier 求值/取直 | 3.4–4.1x | 平滑路径 | +| 5.20 | hyperbola/parabola | 2.4–3.2x | 求值采样 | +| 5.21 | keep_positive | 1.16x | 光标校正 | +| 5.22 | EXTERN 派生标签备忘 | 2.1x | 可执行标记可达性 | +| 5.23+29 | tmu decode/read_apply | 累计 2.84x→见 5.35 | TMU 词元解码 | +| 5.24 | end(t,p) 免二趟 | 结构性 | 深路径省 O(depth) | +| 5.25 | frame::enclose 插值 | 1.60x | 图形包围盒 | +| 5.26 | inside_contiguous_document | 5.7x | 图形光标钩子 O(d²)→O(d) | +| 5.27 | get_env_child WITH 直扫 | 5.2x | 源码模式 mode 读 | +| 5.28 | get_env_child(env) 免拷贝 | 1.34x | 环境链下探 | +| 5.29 | drd_env_write 单次分配 | 2.28x | 环境链写入 | +| 5.30 | next_without_border 死调用 | 中性 | 死代码清理 | +| 5.32 | 原子文本编辑 memcpy | 2.03x | 打字模拟 | +| 5.33 | tmu_writer::write memcpy | 1.43x | TMU 保存 | +| 5.34 | slash/scm_quote memcpy | 1.13–1.22x | scheme 序列化 | +| 5.35 | 读路径 memcpy 回补 | 1.08–1.13x | tmu 读累计 3.16x | + +负结果(已回退并记录):linear_2D 展开、is_compound 标签比较、 +tmu write 成段追加、make_tree_label 备忘缓存、simplify_correct +第一版先建后判。 + +剩余未动的区域:patch/commute(undo 冷路径)、s7 object glue +(薄封装)、observers 内部(需联动 mogan 主程序)。 + +## 7 Why(总体) +moebius 是 Mogan 的 C++ 内核库,排版/编辑热路径大量经过其中函数; +逐个函数做可度量(bench 前后对比)、可回归(单元测试)的优化。 + +## 8 How(总体) +- 优化前先写 bench(同二进制内保留旧实现做 A/B 对比) +- 优化后跑 `xmake test moebius_tests/` 回归 +- 每轮记录到本文档第 5 节 diff --git a/moebius/Data/Convert/tmu.cpp b/moebius/Data/Convert/tmu.cpp index 490e78164f..bef953a7d9 100644 --- a/moebius/Data/Convert/tmu.cpp +++ b/moebius/Data/Convert/tmu.cpp @@ -38,6 +38,9 @@ using lolly::data::to_Hex; using moebius::drd::STD_CODE; using moebius::drd::std_contains; +// 成段 memcpy 直写目标串(定义见 tmu_writer::write 前) +static void append_run (string& tmp, const char* src, int len); + using namespace moebius; /****************************************************************************** @@ -285,17 +288,21 @@ tmu_reader::skip_blank () { string tmu_reader::decode (string s) { - int i, n= N (s); - string r; + // 普通字符成段 memcpy 直写,转义字符单独处理 + int i, n= N (s), run= 0; + string r; + const char* raw= s.begin (); for (i= 0; i < n; i++) if (((i + 1) < n) && (s[i] == '\\')) { + append_run (r, raw + run, i - run); i++; if (s[i] == ';') ; else if (s[i] == '\\') r << '\\'; else r << s[i]; + run= i + 1; } - else r << s[i]; + append_run (r, raw + run, n - run); return r; } @@ -352,28 +359,40 @@ tmu_reader::read_next () { } string r; - pos= old_pos; - while (true) { - old_pos= pos; - c = read_char (); - if (c == "") return r; - else if (c == "\\") { - if ((pos < buf_N) && (buf[pos] == '\\')) { - r << c << "\\"; - pos++; + // 字节级扫描代替逐字符 read_char:普通字符成段一次追加, + // 转义 '\\' 逐字保留并连同后续一个 utf8 序列一起复制, + // 免去原文档加载热路径上每字符一次的子串分配 + pos = old_pos; + int run= pos; + while (pos < buf_N) { + char b= buf[pos]; + if (b == '\t' || b == '\r' || b == '\n' || b == ' ' || b == '<' || + b == '|' || b == '>') + break; + if (b == '\\') { + append_run (r, buf.begin () + run, pos - run); + // 行续接 "\" 由 read_char 语义跳过 + if ((pos + 1 < buf_N) && (buf[pos + 1] == '\n')) { + pos+= 2; + skip_spaces (buf, pos); + run= pos; + continue; + } + r << b; + pos++; + if (pos >= buf_N) { + run= pos; + break; } - else r << c << read_char (); + int start= pos; + decode_from_utf8 (buf, pos); + append_run (r, buf.begin () + start, pos - start); + run= pos; + continue; } - else if (c == "\t") break; - else if (c == "\r") break; - else if (c == "\n") break; - else if (c == " ") break; - else if (c == "<") break; - else if (c == "|") break; - else if (c == ">") break; - else r << c; + decode_from_utf8 (buf, pos); } - pos= old_pos; + append_run (r, buf.begin () + run, pos - run); return r; } @@ -402,12 +421,10 @@ get_collection (tree& u, tree t) { tree tmu_reader::read_apply (string name, bool skip_flag) { // cout << "Read apply " << name << INDENT << LF; - tree t (make_tree_label (name)); - if (codes->contains (name)) { - // cout << " " << name << " -> " << as_string ((tree_label) codes [name]) - // << "\n"; - t= tree ((tree_label) codes[name]); - } + // codes 命中时免去 make_tree_label 的构造与查表(与 read 同法) + tree t; + if (codes->contains (name)) t= tree ((tree_label) codes[name]); + else t= tree (make_tree_label (name)); bool closed= !skip_flag; int buf_N = N (buf); @@ -511,12 +528,10 @@ tmu_reader::read (bool skip_flag) { C << read_apply (name, false); } else { - tree t (make_tree_label (name)); - if (codes->contains (name)) { - // cout << name << " -> " << as_string ((tree_label) codes [name]) - // << "\n"; - t= tree ((tree_label) codes[name]); - } + // codes 命中时免去 make_tree_label 的构造与查表 + tree t; + if (codes->contains (name)) t= tree ((tree_label) codes[name]); + else t= tree (make_tree_label (name)); C << t; } } @@ -593,8 +608,9 @@ tmu_writer::cr () { for (i= n - 1; i >= 0; i--) if ((buf[i] != ' ') || ((i > 0) && (buf[i - 1] == '\\'))) break; if (i < n - 1) { - buf= buf (0, i + 1); - n = n - N (buf); + // 原地截断代替整段前缀拷贝:buf 由 writer 独占,rep 引用计数为 1 + buf->resize (i + 1); + n= n - N (buf); for (i= 0; i < n; i++) buf << "\\ "; } @@ -642,23 +658,44 @@ tmu_writer::write_return () { ret_flag= true; } +// 普通字符成段直写 tmp(resize + memcpy,无子串分配), +// 转义字符单独写入——逐字符 << 与逐字符下标写都有每次调用的额外开销 +static void +append_run (string& tmp, const char* src, int len) { + if (len <= 0) return; + int old_n= N (tmp); + tmp->resize (old_n + len); + memcpy (tmp.begin () + old_n, src, len); +} + void tmu_writer::write (string s, bool flag, bool encode_space) { if (flag) { - int i, n= N (s); + int i, n= N (s), run= 0; + bool any= false; // 是否写入了实际字符(决定 spc/ret 标志) + const char* raw= s.begin (); for (i= 0; i < n; i++) { char c= s[i]; - if ((c == ' ') && (!encode_space)) write_space (); - else { - if (c == ' ') tmp << "\\ "; - else if (c == '\\') tmp << "\\\\"; - else if (c == '<') tmp << "\\<"; - else if (c == '|') tmp << "\\|"; - else if (c == '>') tmp << "\\>"; - else tmp << c; - spc_flag= false; - ret_flag= false; + if ((c == ' ') && (!encode_space)) { + if (i > run) any= true; + append_run (tmp, raw + run, i - run); + run= i + 1; + write_space (); } + else if (c == ' ' || c == '\\' || c == '<' || c == '|' || c == '>') { + if (i > run) any= true; + append_run (tmp, raw + run, i - run); + tmp << "\\"; + tmp << c; + any= true; + run= i + 1; + } + } + if (n > run) any= true; + append_run (tmp, raw + run, n - run); + if (any) { + spc_flag= false; + ret_flag= false; } } else { diff --git a/moebius/Data/Tree/tree_cursor.cpp b/moebius/Data/Tree/tree_cursor.cpp index 2aa3e39b89..f37114ff83 100644 --- a/moebius/Data/Tree/tree_cursor.cpp +++ b/moebius/Data/Tree/tree_cursor.cpp @@ -104,17 +104,8 @@ is_modified_accessible (tree t, path p, bool activate, bool persistent) { } } -static bool -next_without_border (tree t, path p) { - return false; - // Assuming that t is a concat, check whether p does not correspond - // to an inaccessible border position - int i= p->item + 1; - if (i >= N (t)) return false; - if (is_compound (t[i]) && the_drd->is_child_enforcing (t[i])) - return p->next == end (t[p->item]); - return false; -} +// next_without_border 已被上游禁用(恒 false 的桩),其三处调用与 +// 函数体已一并移除;原始逻辑见 git 历史与本任务 devel/moebius.md static int lowest_accessible_child (tree t) { @@ -162,8 +153,8 @@ is_accessible_cursor (tree t, path p) { return false; else switch (L (t)) { case CONCAT: - if (!is_accessible_cursor (t[p->item], p->next)) return false; - else return !next_without_border (t, p); + // next_without_border 已被上游禁用(恒 false),调用移除 + return is_accessible_cursor (t[p->item], p->next); case ACTIVE: return is_modified_accessible (t, p, true, false); case VAR_ACTIVE: @@ -248,7 +239,7 @@ closest_accessible (tree t, path p, int dir) { path r = closest_accessible (t[j], sp2, sdir); if (!is_nil (r)) { r= path (j, r); - if (!is_concat (t) || !next_without_border (t, r)) { + if (!is_concat (t)) { if (the_drd->is_parent_enforcing (t) && !graphics_in_path (t, p) && !is_accessible_cursor (t, p)) { if (r->item == lowest_accessible_child (t)) return path (0); @@ -336,7 +327,7 @@ valid_cursor (tree t, path p, bool start_flag) { p->next == end (t[p->item])))) return false; if (is_concat (t)) { - if (next_without_border (t, p)) return false; + // next_without_border 已被上游禁用(恒 false),调用移除 return valid_cursor (t[p->item], p->next, start_flag || (p->item != 0)); } if (is_mod_active_once (t)) return is_atomic (t[0]) || (!is_atom (p->next)); @@ -492,6 +483,14 @@ right_correct (tree t, path p) { static path keep_positive (path p) { + // 常见情况全为非负索引:原样返回,免去逐层递归重建 + bool has_neg= false; + for (path q= p; !is_nil (q); q= q->next) + if (q->item < 0) { + has_neg= true; + break; + } + if (!has_neg) return p; if (is_nil (p)) return p; if (p->item < 0) return path (); return path (p->item, keep_positive (p->next)); @@ -520,8 +519,12 @@ start (tree t, path p) { path end (tree t, path p) { // cout << "End " << p << " in " << t << "\n"; - if ((!is_nil (p)) && (arity (parent_subtree (t, p)) == 0)) return p; - return correct_cursor (t, p * right_index (subtree (t, p))); + if (is_nil (p)) return correct_cursor (t, path (right_index (t))); + // 父节点已下探取得,其第 last_item(p) 个孩子即 p 所指节点, + // 免去 subtree 的第二次全树下探 + tree& par= parent_subtree (t, p); + if (N (par) == 0) return p; + return correct_cursor (t, p * right_index (par[last_item (p)])); } path diff --git a/moebius/Data/Tree/tree_modify.cpp b/moebius/Data/Tree/tree_modify.cpp index 2c33f2afad..baa2e5d76d 100644 --- a/moebius/Data/Tree/tree_modify.cpp +++ b/moebius/Data/Tree/tree_modify.cpp @@ -74,12 +74,27 @@ tree simplify_correct (tree t) { if (is_atomic (t)) return t; if (is_func (t, QUOTE, 1) && (is_atomic (t[0]))) return t[0]; - int i, n= N (t); + int i, n= N (t); + if (is_concat (t) || is_document (t)) { + // concat/document 需要合并/展平,维持原有重建逻辑 + tree r (t, n); + for (i= 0; i < n; i++) + r[i]= simplify_correct (t[i]); + if (is_concat (r)) r= simplify_concat (r); + if (is_document (r)) r= simplify_document (r); + return r; + } + // 其他复合节点:递归结果暂存,全部孩子共享原 rep 时免去新节点分配 + array tmp (n); + bool changed= false; + for (i= 0; i < n; i++) { + tmp[i]= simplify_correct (t[i]); + if (!strong_equal (tmp[i], t[i])) changed= true; + } + if (!changed) return t; tree r (t, n); for (i= 0; i < n; i++) - r[i]= simplify_correct (t[i]); - if (is_concat (r)) r= simplify_concat (r); - if (is_document (r)) r= simplify_document (r); + r[i]= tmp[i]; return r; } @@ -127,8 +142,7 @@ correct_node (tree& t) { // NOTE: this routine should only modify t and its descendants, // but not any ancestors if (is_compound (t)) { - if (the_drd->contains (as_string (L (t))) && - !the_drd->correct_arity (L (t), N (t))) + if (the_drd->contains (L (t)) && !the_drd->correct_arity (L (t), N (t))) assign (t, ""); if (is_concat (t)) correct_concat_node (t, 0); } diff --git a/moebius/Data/Tree/tree_observer.cpp b/moebius/Data/Tree/tree_observer.cpp index 86ab6164f0..9783a2b532 100644 --- a/moebius/Data/Tree/tree_observer.cpp +++ b/moebius/Data/Tree/tree_observer.cpp @@ -246,9 +246,19 @@ raw_insert (tree& ref, int pos, tree t) { // cout << "Insert " << ref << " += " << t << " at " << pos << "\n"; modification mod= mod_insert (path (), pos, t); if (!is_nil (ref->data)) ref->data->announce (ref, mod); - if (is_atomic (ref) && is_atomic (t)) - ref->label= - ref->label (0, pos) * t->label * ref->label (pos, N (ref->label)); + if (is_atomic (ref) && is_atomic (t)) { + // 单次分配拼接三段(memcpy 整段拷贝),免去两次切片 + // 与两次拼接共四次字符串分配 + string s = ref->label; + string ins= t->label; + int n= N (s), m= N (ins); + string r (pos + m + (n - pos)); + char* p= r.begin (); + memcpy (p, s.begin (), pos); + memcpy (p + pos, ins.begin (), m); + memcpy (p + pos + m, s.begin () + pos, n - pos); + ref->label= r; + } else { int n= N (ref), nr= N (t); // 块移动代替逐元素赋值:每个被移动孩子的引用计数加减一次都省去。 @@ -292,8 +302,16 @@ raw_remove (tree& ref, int pos, int nr) { else detach (ref[i], ref[pos + nr], false); } - if (is_atomic (ref)) - ref->label= ref->label (0, pos) * ref->label (pos + nr, N (ref->label)); + if (is_atomic (ref)) { + // 单次分配拼接保留段(memcpy 整段拷贝),免去两次切片与一次拼接 + string s= ref->label; + int n= N (s); + string r (pos + (n - pos - nr)); + char* p= r.begin (); + memcpy (p, s.begin (), pos); + memcpy (p + pos, s.begin () + pos + nr, n - pos - nr); + ref->label= r; + } else { int n= N (ref) - nr; // 先把被删孩子拷出(引用计数 +1,接管数组的所有权),memmove 尾部 @@ -323,10 +341,14 @@ raw_split (tree& ref, int pos, int at) { t1= ref[pos](0, at); t2= ref[pos](at, N (ref[pos])); } - int i, n= N (ref); + int n= N (ref); + // 块移动代替逐元素赋值:孩子句柄的引用计数随位移动转移, + // 洞里的 stale 位用 placement-new 默认句柄覆盖(不减计数), + // 随后被 t1/t2 的赋值语句正常接管 AR (ref)->resize (n + 1); - for (i= n; i > (pos + 1); i--) - ref[i]= ref[i - 1]; + tree* a= A (AR (ref)); + memmove (a + pos + 2, a + pos + 1, (size_t) (n - pos - 1) * sizeof (tree)); + new ((void*) (a + pos + 1)) tree (); ref[pos] = t1; ref[pos + 1]= t2; @@ -357,7 +379,16 @@ raw_join (tree& ref, int pos) { if (!is_nil (ref->data)) ref->data->announce (ref, mod); tree t1= ref[pos], t2= ref[pos + 1], t; int offset= is_atomic (ref) ? N (t1->label) : N (t1); - if (is_atomic (t1) && is_atomic (t2)) t= t1->label * t2->label; + if (is_atomic (t1) && is_atomic (t2)) { + // 单次分配拼接两段(memcpy 整段拷贝),免去一次切片与一次拼接 + string s1= t1->label, s2= t2->label; + int n1= N (s1), n2= N (s2); + string r (n1 + n2); + char* p= r.begin (); + memcpy (p, s1.begin (), n1); + memcpy (p + n1, s2.begin (), n2); + t= tree (r); + } else t= t1 * t2; if (!is_nil (ref->data)) ref->data->notify_join (ref, pos, t); if (!is_nil (t1->data)) { @@ -370,9 +401,12 @@ raw_join (tree& ref, int pos) { } ref[pos]= t; - int i, n= N (ref) - 1; - for (i= pos + 1; i < n; i++) - ref[i]= ref[i + 1]; + // 块移动代替逐元素赋值:[pos+2, n0) 下移一格,句柄所有权随位移动 + // 转移;尾部 stale 重复位由 resize 截断直接丢弃(与 raw_remove 同法), + // 原 t1/t2 槽位的所有权由局部句柄 t1/t2 析构时释放 + int n0= N (ref), n= n0 - 1; + tree* a= A (AR (ref)); + memmove (a + pos + 1, a + pos + 2, (size_t) (n0 - pos - 2) * sizeof (tree)); AR (ref)->resize (n); if (!is_nil (ref->data)) ref->data->done (ref, mod); // stretched_print (ref, true, 1); diff --git a/moebius/Data/Tree/tree_traverse.cpp b/moebius/Data/Tree/tree_traverse.cpp index b27d918832..6b2eba39a0 100644 --- a/moebius/Data/Tree/tree_traverse.cpp +++ b/moebius/Data/Tree/tree_traverse.cpp @@ -181,15 +181,22 @@ get_env_descendant (tree t, path p, string var, tree val) { static path move_any (tree t, path p, bool forward) { - path q = path_up (p); - int l = last_item (p); - tree st= subtree (t, q); - if (!is_nil (q) && is_func (subtree (t, path_up (q)), RAW_DATA)) { + path q= path_up (p); + int l= last_item (p); + // 单趟下探:st 为 path_up(p) 处节点,par 为其父节点, + // 免去原实现对 subtree 的三次重复全树下探 + tree* st = &t; + tree* par= nullptr; + for (path r= p; !is_nil (r->next); r= r->next) { + par= st; + st = &(*st)[r->item]; + } + if (par != nullptr && is_func (*par, RAW_DATA)) { if (forward) return path_up (q) * 1; else return path_up (q) * 0; } - if (is_atomic (st)) { - string s= st->label; + if (is_atomic (*st)) { + string s= (*st)->label; #ifdef SANITY_CHECKS ASSERT (l >= 0 && l <= N (s), "out of range"); #else @@ -209,32 +216,32 @@ move_any (tree t, path p, bool forward) { } } else if ((forward && l == 0) || (!forward && l == 1)) { - int i, n= N (st); + int i, n= N (*st); if (forward) { for (i= 0; i < n; i++) - if (the_drd->is_accessible_child (st, i)) return q * path (i, 0); + if (the_drd->is_accessible_child (*st, i)) return q * path (i, 0); } else { for (i= n - 1; i >= 0; i--) - if (the_drd->is_accessible_child (st, i)) - return q * path (i, right_index (st[i])); + if (the_drd->is_accessible_child (*st, i)) + return q * path (i, right_index ((*st)[i])); } return q * (1 - l); } else if (is_nil (q)) return p; - l = last_item (q); - q = path_up (q); - st= subtree (t, q); - int i, n= N (st); + tree& parent= *par; // q 非空,par 必为 path_up(q) 处节点 + l = last_item (q); + q = path_up (q); + int i, n= N (parent); if (forward) { for (i= l + 1; i < n; i++) - if (the_drd->is_accessible_child (st, i)) return q * path (i, 0); + if (the_drd->is_accessible_child (parent, i)) return q * path (i, 0); } else { for (i= l - 1; i >= 0; i--) - if (the_drd->is_accessible_child (st, i)) { - return q * path (i, right_index (st[i])); + if (the_drd->is_accessible_child (parent, i)) { + return q * path (i, right_index (parent[i])); } } return q * (forward ? 1 : 0); @@ -369,22 +376,22 @@ tm_codepoint_at (string s, int pos, unsigned int& code) { int i= pos; tm_char_forwards (s, i); - string c= s (pos, i); - if (c == "") return false; + int len= i - pos; + if (len == 0) return false; // Plain ASCII - if (N (c) == 1) { - code= (unsigned int) (unsigned char) c[0]; + if (len == 1) { + code= (unsigned int) (unsigned char) s[pos]; return true; } // TeXmacs internal hexadecimal form: <#....> - // Only parse leading hex digits for block-level checks. - if (starts (c, "<#") && ends (c, ">")) { + // 直接在原串上解析,免去取子串与 starts/ends 字符串比较 + if (len > 2 && s[pos] == '<' && s[pos + 1] == '#' && s[i - 1] == '>') { unsigned int v= 0; - int k= 2, cnt= 0; - for (; k < N (c) - 1 && cnt < 4; k++, cnt++) { - char ch= c[k]; + int k= pos + 2, cnt= 0; + for (; k < i - 1 && cnt < 4; k++, cnt++) { + char ch= s[k]; unsigned int d; if (ch >= '0' && ch <= '9') d= (unsigned int) (ch - '0'); else if (ch >= 'a' && ch <= 'f') d= 10u + (unsigned int) (ch - 'a'); @@ -444,13 +451,27 @@ next_is_word (tree t, path p) { return st[l + 1] != "" && is_iso_alphanum (st[l + 1]->label[0]); } +// 单趟下探到 path 所指节点(调用方保证路径合法),免去 subtree 全树重下探 +static tree* +tt_descend (tree& t, path p) { + tree* r= &t; + for (path q= p; !is_nil (q); q= q->next) { + int i= q->item; + if (!is_compound (*r) || i < 0 || i >= N (*r)) return nullptr; + r= &(*r)[i]; + } + return r; +} + static path move_word (tree t, path p, bool forward) { while (true) { path q= move_accessible (t, p, forward); int l= last_item (q); if (q == p) return p; - tree st= subtree (t, path_up (q)); + tree* stp= tt_descend (t, path_up (q)); + if (stp == nullptr) return q; // 防御:无效路径按不动点处理 + tree& st= *stp; if (is_atomic (st)) { string s= st->label; int n= N (s); @@ -739,14 +760,28 @@ is_boundary (tree t, path p) { return false; } +// 沿 p 自根单趟下探,返回 p 的最深 DOCUMENT/GRAPHICS 祖先路径 +// (无边界祖先时为 nil)。原逐层 is_boundary 各做一次全树下探, +// 深路径下为 O(depth^2) +static path +closest_boundary_ancestor (tree& t, path p) { + path cur= path (), best= path (); + tree* node= &t; + for (path r= p; !is_nil (r); r= r->next) { + int i= r->item; + if (!is_compound (*node) || i < 0 || i >= N (*node)) return best; + if (is_func (*node, DOCUMENT) || is_func (*node, GRAPHICS)) best= cur; + cur = path (i, cur); + node= &(*node)[i]; + } + return best; +} + bool inside_contiguous_document (tree t, path op, path oq) { if (!inside_same (t, op, oq, DOCUMENT)) return false; - path p= path_up (op), q= path_up (oq); - while (!is_nil (p) && !is_boundary (t, p)) - p= path_up (p); - while (!is_nil (q) && !is_boundary (t, q)) - q= path_up (q); + path p= closest_boundary_ancestor (t, path_up (op)); + path q= closest_boundary_ancestor (t, path_up (oq)); if (p == q) return true; if (q <= p) return inside_contiguous_document (t, oq, op); if (!(p <= q)) return false; @@ -833,9 +868,33 @@ search_sections (tree t) { return a; } +// herk 双向映射表里,字节 32..126 除 0x60(反引号,映到 0)外均恒等; +// 纯此类字节的字符串 utf8->herk 结果与输入相同,可跳过逐字符解码重排 +static bool +utf8_herk_identity (string s) { + for (int i= 0; i < N (s); i++) { + unsigned char c= (unsigned char) s[i]; + if (c < 32 || c > 126 || c == 96) return false; + } + return true; +} + +// herk->utf8 同样在 32..126(除 0x60,映到 U+2018)恒等, +// 但 "<#" 会被解析为十六进制转义,需一并排除 +static bool +herk_utf8_identity (string s) { + for (int i= 0; i < N (s); i++) { + unsigned char c= (unsigned char) s[i]; + if (c < 32 || c > 126 || c == 96) return false; + if (c == '<' && i + 1 < N (s) && s[i + 1] == '#') return false; + } + return true; +} + tree tree_utf8_to_herk (tree_u8 t) { if (is_atomic (t)) { + if (utf8_herk_identity (t->label)) return tree (t->label); return tree (lolly::data::utf8_to_herk (t->label)); } else if (!is_func (t, RAW_DATA)) { @@ -853,6 +912,7 @@ tree_utf8_to_herk (tree_u8 t) { tree_u8 tree_herk_to_utf8 (tree t) { if (is_atomic (t)) { + if (herk_utf8_identity (t->label)) return tree_u8 (t->label); return tree (lolly::data::herk_to_utf8 (t->label)); } else if (!is_func (t, RAW_DATA)) { diff --git a/moebius/Kernel/Types/curve.cpp b/moebius/Kernel/Types/curve.cpp index 5acc514b0d..dedf4ef578 100644 --- a/moebius/Kernel/Types/curve.cpp +++ b/moebius/Kernel/Types/curve.cpp @@ -91,8 +91,10 @@ curvet_closest_points (curve c, double t1, double t2, point p, double eps) { bool decreasing= false; double max_step = 0.5 / max (c->nr_components (), 1); for (t= t1; t <= t2;) { - point pt= c->evaluate (t); - double n = norm (pt - p); + point pt= c->evaluate (t); + // 平方距离比较避免每步构造差向量临时 point + double n2= norm2_diff (pt, p); + double n = sqrt (n2); if (n < n0) { n0 = n; closest = t; @@ -102,7 +104,7 @@ curvet_closest_points (curve c, double t1, double t2, point p, double eps) { decreasing= n < nprec; if (!stored && !decreasing) { curvet ct; - ct.dist= norm (pclosest - p); + ct.dist= sqrt (norm2_diff (pclosest, p)); ct.t = closest; res << ct; stored= true; @@ -114,7 +116,7 @@ curvet_closest_points (curve c, double t1, double t2, point p, double eps) { } if (!stored && decreasing) { curvet ct; - ct.dist= norm (pclosest - p); + ct.dist= sqrt (norm2_diff (pclosest, p)); ct.t = closest; res << ct; } @@ -152,13 +154,14 @@ closest (curve f, point p) { double t1 = abs[0]; double t2 = abs[N (abs) - 1]; double best= 0; - double eps = norm (f (0) - p); + double eps = sqrt (norm2_diff (f (0), p)); for (int i= 0; i < 10; i++) { bool found= false; double t = f->find_closest_point (t1, t2, p, eps, found); if (found) best= t; else break; - double eps2= norm (f (t) - p); + // 平方距离比较避免构造差向量临时 point + double eps2= sqrt (norm2_diff (f (t), p)); if (eps2 >= 0.9 * eps) break; eps= eps2; } @@ -200,7 +203,7 @@ straight_edge_midpoints (curve c, point p, double tol) { if (abs[0] != 0.0 || abs[np - 1] != 1.0) ne++; for (int e= 0; e < ne; e++) { int j= (e + 1) % np; - if (norm (pts[j] - pts[e]) < 1e-6) continue; + if (norm2_diff (pts[j], pts[e]) < 1e-12) continue; if (seg_dist (pts[e], pts[j], p) > tol) continue; res << c->evaluate ((abs[e] + abs[j]) / 2.0); } @@ -210,7 +213,7 @@ straight_edge_midpoints (curve c, point p, double tol) { bool intersection (curve f, curve g, double& t, double& u) { // for two dimensional curves only - double d= norm (f (t) - g (u)); + double d= sqrt (norm2_diff (f (t), g (u))); while (!fnull (d, 1.0e-9)) { point ft = f (t); point gu = g (u); @@ -223,7 +226,7 @@ intersection (curve f, curve g, double& t, double& u) { double T = t + dt; double U = u + du; if (T < 0.0 || T > 1.0 || U < 0.0 || U > 1.0) break; - double D= norm (f (T) - g (U)); + double D= sqrt (norm2_diff (f (T), g (U))); if (D > 0.9 * d) break; t= T; u= U; @@ -265,8 +268,16 @@ struct segment_rep : public curve_rep { point p1, p2; path cip1, cip2; segment_rep (point p1b, point p2b) : p1 (p1b), p2 (p2b) {} - point evaluate (double t) { return (1.0 - t) * p1 + t * p2; } - void rectify_cumul (array& a, double eps) { + // 逐分量线性插值,避免两个标量乘临时与加法临时共三次分配 + point evaluate (double t) { + double u= 1.0 - t; + int i, n= min (N (p1), N (p2)); + point r (n); + for (i= 0; i < n; i++) + r[i]= u * p1[i] + t * p2[i]; + return r; + } + void rectify_cumul (array& a, double eps) { (void) eps; a << p2; } @@ -317,10 +328,16 @@ struct poly_segment_rep : public curve_rep { int n; poly_segment_rep (array a2, array cip2) : a (a2), cip (cip2), n (N (a) - 1) {} - int nr_components () { return n; } + int nr_components () { return n; } + // 逐分量线性插值,避免两个标量乘临时与加法临时共三次分配 point evaluate (double t) { - int i= max (min ((int) (n * t), n - 1), 0); - return (i + 1 - n * t) * a[i] + (n * t - i) * a[i + 1]; + int i= max (min ((int) (n * t), n - 1), 0); + double u= n * t - i; + int k, m= min (N (a[i]), N (a[i + 1])); + point r (m); + for (k= 0; k < m; k++) + r[k]= (1.0 - u) * a[i][k] + u * a[i + 1][k]; + return r; } void rectify_cumul (array& cum, double eps) { (void) eps; @@ -336,7 +353,12 @@ struct poly_segment_rep : public curve_rep { point grad (double t, bool& error) { error= false; int i= min ((int) (n * t), n - 1); - return n * (a[i + 1] - a[i]); + // 差向量逐分量放大,避免差向量临时 + int k, m= min (N (a[i]), N (a[i + 1])); + point r (m); + for (k= 0; k < m; k++) + r[k]= n * (a[i + 1][k] - a[i][k]); + return r; } int get_control_points (array& abs, array& pts, array& cip); @@ -375,6 +397,8 @@ struct spline_rep : public curve_rep { array U; array p; bool close, interpol; + // interval_no 的上次命中缓存:求值常按 t 单调推进(取直/渲染),先验上次区间 + int last_interval= -1; spline_rep (array a, array cip, bool close= false, bool interpol= true); @@ -543,9 +567,15 @@ spline_rep::spline (int i, double u, int o) { int spline_rep::interval_no (double u) { + if (last_interval >= 0 && last_interval + 1 < N (U) && + u >= U[last_interval] && u < U[last_interval + 1]) + return last_interval; int i; for (i= 0; i < N (U); i++) - if (u >= U[i] && u < U[i + 1]) return i; + if (u >= U[i] && u < U[i + 1]) { + last_interval= i; + return i; + } return -1; } @@ -570,6 +600,8 @@ spline_rep::evaluate (double t, int o) { if (no < 2) res= spline (2, U[2], o); else if (no > n) res= spline (n, U[n + 1], o); else res= spline (no, t, o); + // o=0 时 prod(k,0)==1,免去一次标量乘的整点分配 + if (o == 0) return res; return prod (k, o) * res; } @@ -596,7 +628,9 @@ spline_rep::approx (int i, double u1, double u2, double eps) { point p1, p2; p1= spline (i, u1); p2= spline (i, u2); - l = norm (p1 - p2); + // 平方距离比较避免构造差向量临时 point + double l2= norm2_diff (p1, p2); + l = sqrt (l2); // When l and R are very small, the test l<=R // can fail forever. So we set l to exactly 0 if (l != 0 && fnull (l, 1.0e-6)) l= 0; @@ -720,7 +754,13 @@ bezier_rep::bezier_rep (array a2) : a (a2) { point bezier_rep::evaluate (double t) { - return ((P[3] * t + P[2]) * t + P[1]) * t + P[0]; + // 逐分量 Horner,免去链式标量乘/加的六个中间 point 临时 + int k, n= N (P[0]); + n= min (n, min (N (P[1]), min (N (P[2]), N (P[3])))); + point q (n); + for (k= 0; k < n; k++) + q[k]= ((P[3][k] * t + P[2][k]) * t + P[1][k]) * t + P[0][k]; + return q; } void @@ -728,12 +768,20 @@ bezier_rep::rectify_cumul (array& cum, double t0, double t1, double e) { point p0= evaluate (t0); point p1= evaluate (t1); // if (bound ((t0 + t1) / 2.0, e / 4.0) < (t1 - t0)) + double lim= square (e / 10.0); for (int k= 1; k <= 4; k++) { double x= ((double) k) / 5.0; double t= (1.0 - x) * t0 + x * t1; point q= evaluate (t); - point r= (1.0 - x) * p0 + x * p1; - if (norm (q - r) >= (e / 10.0)) { + // 弦上插值点逐分量直写 + 平方距离比较,免去差向量与插值临时 + int m, d= min (N (q), min (N (p0), N (p1))); + double dd= 0; + for (m= 0; m < d; m++) { + double r = (1.0 - x) * p0[m] + x * p1[m]; + double diff= q[m] - r; + dd+= diff * diff; + } + if (dd >= lim) { rectify_cumul (cum, t0, (t0 + t1) / 2.0, e); rectify_cumul (cum, (t0 + t1) / 2.0, t1, e); return; @@ -756,8 +804,13 @@ bezier_rep::bound (double t, double eps) { point bezier_rep::grad (double t, bool& error) { + // 逐分量直写,免去链式中间 point 临时 error= false; - return ((3.0 * P[3] * t) + 2.0 * P[2]) + P[1]; + int k, n= min (N (P[1]), min (N (P[2]), N (P[3]))); + point q (n); + for (k= 0; k < n; k++) + q[k]= 3.0 * P[3][k] * t + 2.0 * P[2][k] + P[1][k]; + return q; } double @@ -994,8 +1047,14 @@ arc_rep::arc_rep (array a2, array cip2, bool close) point arc_rep::evaluate (double t) { - t= e1 + t * (e2 - e1); - return center + r1 * cos (2 * tm_PI * t) * i + r2 * sin (2 * tm_PI * t) * j; + // 逐分量直写,免去标量乘与两次加法共四个中间 point 临时 + t = e1 + t * (e2 - e1); + double co= r1 * cos (2 * tm_PI * t), si= r2 * sin (2 * tm_PI * t); + int k, n = min (N (center), min (N (i), N (j))); + point q (n); + for (k= 0; k < n; k++) + q[k]= center[k] + co * i[k] + si * j[k]; + return q; } void @@ -1014,10 +1073,16 @@ arc_rep::bound (double t, double eps) { point arc_rep::grad (double t, bool& error) { - error= false; - t = e1 + t * (e2 - e1); - return -2 * tm_PI * r1 * sin (2 * tm_PI * t) * i + - 2 * tm_PI * r2 * cos (2 * tm_PI * t) * j; + // 逐分量直写,免去两个中间 point 临时 + error = false; + t = e1 + t * (e2 - e1); + double si= -2 * tm_PI * r1 * sin (2 * tm_PI * t); + double co= 2 * tm_PI * r2 * cos (2 * tm_PI * t); + int k, n= min (N (i), N (j)); + point q (n); + for (k= 0; k < n; k++) + q[k]= si * i[k] + co * j[k]; + return q; } double @@ -1085,7 +1150,13 @@ ellipse_rep::ellipse_rep (array a2, array cip2, bool close) point ellipse_rep::evaluate (double t) { - return center + r1 * cos (2 * tm_PI * t) * i + r2 * sin (2 * tm_PI * t) * j; + // 逐分量直写,免去标量乘与两次加法共四个中间 point 临时 + double co= r1 * cos (2 * tm_PI * t), si= r2 * sin (2 * tm_PI * t); + int k, n = min (N (center), min (N (i), N (j))); + point q (n); + for (k= 0; k < n; k++) + q[k]= center[k] + co * i[k] + si * j[k]; + return q; } void @@ -1104,9 +1175,15 @@ ellipse_rep::bound (double t, double eps) { point ellipse_rep::grad (double t, bool& error) { - error= false; - return -2 * tm_PI * r1 * sin (2 * tm_PI * t) * i + - 2 * tm_PI * r2 * cos (2 * tm_PI * t) * j; + // 逐分量直写,免去两个中间 point 临时 + error = false; + double si= -2 * tm_PI * r1 * sin (2 * tm_PI * t); + double co= 2 * tm_PI * r2 * cos (2 * tm_PI * t); + int k, n= min (N (i), N (j)); + point q (n); + for (k= 0; k < n; k++) + q[k]= si * i[k] + co * j[k]; + return q; } double @@ -1187,14 +1264,23 @@ hyperbola_rep::hyperbola_rep (array a2, array cip2, bool close) point hyperbola_rep::evaluate (double t) { + // 逐分量直写,免去标量乘与两次加法共四个中间 point 临时 + double u; + double sign; if (t < 0.5) { - double u= (4.0 * t - 1.0) * u_max; - return center + r1 * cosh (u) * i + r2 * sinh (u) * j; + u = (4.0 * t - 1.0) * u_max; + sign= 1.0; } else { - double u= (4.0 * t - 3.0) * u_max; - return center - r1 * cosh (u) * i + r2 * sinh (u) * j; + u = (4.0 * t - 3.0) * u_max; + sign= -1.0; } + double co= r1 * cosh (u), si= r2 * sinh (u); + int k, n = min (N (center), min (N (i), N (j))); + point q (n); + for (k= 0; k < n; k++) + q[k]= center[k] + sign * co * i[k] + si * j[k]; + return q; } void @@ -1221,15 +1307,24 @@ hyperbola_rep::bound (double t, double eps) { point hyperbola_rep::grad (double t, bool& error) { + // 逐分量直写,免去链式中间 point 临时 error= false; + double u, sign; if (t < 0.5) { - double u= (4.0 * t - 1.0) * u_max; - return 4.0 * u_max * (r1 * sinh (u) * i + r2 * cosh (u) * j); + u = (4.0 * t - 1.0) * u_max; + sign= 1.0; } else { - double u= (4.0 * t - 3.0) * u_max; - return 4.0 * u_max * (-r1 * sinh (u) * i + r2 * cosh (u) * j); + u = (4.0 * t - 3.0) * u_max; + sign= -1.0; } + double si= 4.0 * u_max * r1 * sinh (u); + double co= 4.0 * u_max * r2 * cosh (u); + int k, n= min (N (i), N (j)); + point q (n); + for (k= 0; k < n; k++) + q[k]= sign * si * i[k] + co * j[k]; + return q; } double @@ -1288,8 +1383,14 @@ parabola_rep::parabola_rep (array a2, array cip2, bool close) point parabola_rep::evaluate (double t) { - double u= (2 * t - 1) * u_max; - return vertex + (square (u) / (2 * d)) * i + u * j; + // 逐分量直写,免去标量乘与两次加法共四个中间 point 临时 + double u = (2 * t - 1) * u_max; + double ui= square (u) / (2 * d); + int k, n= min (N (vertex), min (N (i), N (j))); + point q (n); + for (k= 0; k < n; k++) + q[k]= vertex[k] + ui * i[k] + u * j[k]; + return q; } void @@ -1308,9 +1409,16 @@ parabola_rep::bound (double t, double eps) { point parabola_rep::grad (double t, bool& error) { - error = false; - double u= (2 * t - 1) * u_max; - return 2 * u_max * ((u / d) * i + j); + // 逐分量直写,免去链式中间 point 临时 + error = false; + double u = (2 * t - 1) * u_max; + double f = 2 * u_max; + double ui= f * u / d; + int k, n= min (N (i), N (j)); + point q (n); + for (k= 0; k < n; k++) + q[k]= ui * i[k] + f * j[k]; + return q; } double diff --git a/moebius/Kernel/Types/equations.cpp b/moebius/Kernel/Types/equations.cpp index 2cfc398b12..573da4266b 100644 --- a/moebius/Kernel/Types/equations.cpp +++ b/moebius/Kernel/Types/equations.cpp @@ -26,10 +26,18 @@ tridiag_solve (array a, array b, array c, u[i]= c[i] / li; li = b[i + 1] - a[i + 1] * u[i]; ASSERT (li != 0, "failed tridiag_solve (2)"); - x[i + 1]= (y[i + 1] - a[i + 1] * x[i]) / li; + // 逐分量写入单个新点,免去差向量/除法两个中间 point 临时; + // x 各行初始为空点,维度不足时整行重建 + int m= min (N (y[i + 1]), N (x[i])); + if (N (x[i + 1]) != m) x[i + 1]= point (m); + for (int j= 0; j < m; j++) + x[i + 1][j]= (y[i + 1][j] - a[i + 1] * x[i][j]) / li; } for (i= n - 2; i >= 0; i--) { - x[i]= x[i] - u[i] * x[i + 1]; + // 逐分量就地回代,免去每行一次标量乘临时 + int m= min (N (x[i]), N (x[i + 1])); + for (int j= 0; j < m; j++) + x[i][j]-= u[i] * x[i + 1][j]; } } @@ -43,17 +51,24 @@ quasitridiag_solve (array a, array b, array c, up[i]= as_point (u[i]); tridiag_solve (a, b, c, x, y, n); tridiag_solve (a, b, c, z, up, n); - point vx; - vx= v[0] * x[0]; + // 累加到一个预分配的点,免去每行 v[i]*x[i] 与 + 的中间临时 + int d= N (x[0]); + point vx (d); + for (int j= 0; j < d; j++) + vx[j]= v[0] * x[0][j]; for (i= 1; i < n; i++) - vx= vx + v[i] * x[i]; + for (int j= 0; j < min (d, N (x[i])); j++) + vx[j]+= v[i] * x[i][j]; double vz; vz= v[0] * z[0][0]; for (i= 1; i < n; i++) vz+= v[i] * z[i][0]; - vx= vx / (1 + vz); + double inv= 1.0 / (1 + vz); for (i= 0; i < n; i++) { - x[i]= x[i] - z[i][0] * vx; + // 逐分量就地修正,免去每行一个标量乘临时 + double zi= z[i][0] * inv; + for (int j= 0; j < N (x[i]); j++) + x[i][j]-= zi * vx[j]; } } diff --git a/moebius/Kernel/Types/frame.cpp b/moebius/Kernel/Types/frame.cpp index 02ff63b68d..d4b4f55caf 100644 --- a/moebius/Kernel/Types/frame.cpp +++ b/moebius/Kernel/Types/frame.cpp @@ -21,8 +21,13 @@ frame::enclose (double& x1, double& y1, double& x2, double& y2, point p1, point p2, bool direct) { int n= 1; if (!rep->linear) n= 20; + // 逐分量插值直写采样点,免去差向量/标量乘/加法三个中间 point 临时 + int d= min (N (p1), N (p2)); for (int i= 0; i < n; i++) { - point p= p1 + (((double) i) / ((double) n)) * (p2 - p1); + double a= ((double) i) / ((double) n); + point p (d); + for (int k= 0; k < d; k++) + p[k]= p1[k] + a * (p2[k] - p1[k]); point q= (direct ? operator() (p) : operator[] (p)); x1 = min (x1, q[0]); y1 = min (y1, q[1]); @@ -102,8 +107,21 @@ struct scaling_rep : public frame_rep { operator tree () { return tuple ("scale", as_string (magnify), as_tree (shift)); } - point direct_transform (point p) { return shift + magnify * p; } - point inverse_transform (point p) { return (p - shift) / magnify; } + // 逐分量直写结果,避免 magnify*p 与 +shift 两个中间 point 临时分配 + point direct_transform (point p) { + int n= N (p); + point q (n); + for (int i= 0; i < n; i++) + q[i]= shift[i] + magnify * p[i]; + return q; + } + point inverse_transform (point p) { + int n= N (p); + point q (n); + for (int i= 0; i < n; i++) + q[i]= (p[i] - shift[i]) / magnify; + return q; + } point jacobian (point p, point v, bool& error) { (void) p; error= false; @@ -136,8 +154,21 @@ struct an_scaling_rep : public frame_rep { operator tree () { return tuple ("scale", as_string (as_tree (magnify)), as_tree (shift)); } - point direct_transform (point p) { return shift + magnify * p; } - point inverse_transform (point p) { return (p - shift) / magnify; } + // 逐分量直写结果,避免 magnify*p 与 +shift 两个中间 point 临时分配 + point direct_transform (point p) { + int n= N (p); + point q (n); + for (int i= 0; i < n; i++) + q[i]= shift[i] + magnify[i] * p[i]; + return q; + } + point inverse_transform (point p) { + int n= N (p); + point q (n); + for (int i= 0; i < n; i++) + q[i]= (p[i] - shift[i]) / magnify[i]; + return q; + } point jacobian (point p, point v, bool& error) { (void) p; error= false; diff --git a/moebius/Kernel/Types/modification.cpp b/moebius/Kernel/Types/modification.cpp index 7d16c6f083..02b637a61f 100644 --- a/moebius/Kernel/Types/modification.cpp +++ b/moebius/Kernel/Types/modification.cpp @@ -185,67 +185,86 @@ get_tree (modification mod) { * Test applicability of modifications ******************************************************************************/ +// 单趟下探:p 越界或命中原子节点时返回空指针,免去 has_subtree+ +// subtree 的两趟树遍历(is_applicable 在每次 apply 时调用) +static tree* +descend (tree& t, path p) { + tree* r= &t; + path q= p; + while (!is_nil (q)) { + int i= q->item; + if (!is_compound (*r) || i < 0 || i >= N (*r)) return nullptr; + r= &(*r)[i]; + q= q->next; + } + return r; +} + bool can_assign (tree t, path p, tree u) { (void) u; - return has_subtree (t, p); + return descend (t, p) != nullptr; } bool can_insert (tree t, path p, int pos, tree u) { - if (!has_subtree (t, p)) return false; - tree st= subtree (t, p); - if (is_atomic (st)) return pos >= 0 && pos <= N (st->label) && is_atomic (u); - else return pos >= 0 && pos <= N (st) && is_compound (u); + tree* st= descend (t, p); + if (st == nullptr) return false; + if (is_atomic (*st)) + return pos >= 0 && pos <= N ((*st)->label) && is_atomic (u); + else return pos >= 0 && pos <= N (*st) && is_compound (u); } bool can_remove (tree t, path p, int pos, int nr) { - if (!has_subtree (t, p)) return false; - tree st= subtree (t, p); - if (is_atomic (st)) return pos >= 0 && pos + nr <= N (st->label); - else return pos >= 0 && pos + nr <= N (st); + tree* st= descend (t, p); + if (st == nullptr) return false; + if (is_atomic (*st)) return pos >= 0 && pos + nr <= N ((*st)->label); + else return pos >= 0 && pos + nr <= N (*st); } bool can_split (tree t, path p, int pos, int at) { - if (!has_subtree (t, p * pos)) return false; - tree st= subtree (t, p * pos); - if (is_atomic (st)) return at >= 0 && at <= N (st->label); - else return at >= 0 && at <= N (st); + tree* st= descend (t, p * pos); + if (st == nullptr) return false; + if (is_atomic (*st)) return at >= 0 && at <= N ((*st)->label); + else return at >= 0 && at <= N (*st); } bool can_join (tree t, path p, int pos) { - if (!has_subtree (t, p)) return false; - tree st= subtree (t, p); - if (pos < 0 || pos + 1 >= N (st)) return false; - if (is_atomic (st[pos]) && is_atomic (st[pos + 1])) return true; - if (is_compound (st[pos]) && is_compound (st[pos + 1])) return true; + tree* st= descend (t, p); + if (st == nullptr) return false; + if (pos < 0 || pos + 1 >= N (*st)) return false; + if (is_atomic ((*st)[pos]) && is_atomic ((*st)[pos + 1])) return true; + if (is_compound ((*st)[pos]) && is_compound ((*st)[pos + 1])) return true; return false; } bool can_assign_node (tree t, path p, int op) { (void) op; - return has_subtree (t, p) && is_compound (subtree (t, p)); + tree* st= descend (t, p); + return st != nullptr && is_compound (*st); } bool can_insert_node (tree t, path p, int pos, tree u) { - return has_subtree (t, p) && is_compound (u) && pos >= 0 && pos <= N (u); + return descend (t, p) != nullptr && is_compound (u) && pos >= 0 && + pos <= N (u); } bool can_remove_node (tree t, path p, int pos) { - return has_subtree (t, p * pos); + return descend (t, p * pos) != nullptr; } bool can_set_cursor (tree t, path p, int pos, tree data) { (void) data; - if (!has_subtree (t, p)) return false; - return pos >= 0 && pos <= right_index (subtree (t, p)); + tree* st= descend (t, p); + if (st == nullptr) return false; + return pos >= 0 && pos <= right_index (*st); } bool diff --git a/moebius/Kernel/Types/point.cpp b/moebius/Kernel/Types/point.cpp index 016f14875b..a3330090fb 100644 --- a/moebius/Kernel/Types/point.cpp +++ b/moebius/Kernel/Types/point.cpp @@ -182,6 +182,12 @@ mult (double re, double im, point p) { point rotate_2D (const point& p, const point& o, double angle) { + // 常见 2D 快路径:单次结果分配,避免 p-o 与 +o 两个中间临时 + if (N (p) == 2 && N (o) == 2) { + double c= cos (angle), s= sin (angle); + double dx= p[0] - o[0], dy= p[1] - o[1]; + return point (o[0] + c * dx - s * dy, o[1] + s * dx + c * dy); + } return mult (cos (angle), sin (angle), p - o) + o; } diff --git a/moebius/bench/Data/Convert/tmu_read_bench.cpp b/moebius/bench/Data/Convert/tmu_read_bench.cpp new file mode 100644 index 0000000000..f4ee971d0d --- /dev/null +++ b/moebius/bench/Data/Convert/tmu_read_bench.cpp @@ -0,0 +1,48 @@ +/** \file tmu_read_bench.cpp + * \copyright GPLv3 + * \details Benchmark for TMU document parsing (tmu_to_tree) + * \date 2026 + */ + +#include "nanobench.h" +#include "tmu.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include + +using namespace moebius; +using moebius::CONCAT; +using moebius::DOCUMENT; + +/** 构造典型文档:500 段,每段 10 个单词与少量标记 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 500; i++) { + tree par (CONCAT); + for (int j= 0; j < 10; j++) { + par << tree ("word" * as_string (j)); + if (j == 4) par << tree (RIGID, tree ("r" * as_string (j))); + } + doc << par; + } + return doc; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (5).unit ("doc"); + + tree doc = mk_document (); + string tmu = tree_to_tmu (doc); + size_t bytes= N (tmu); + + bench.run ("tmu_to_tree doc500x10", + [&] { ankerl::nanobench::doNotOptimizeAway (tmu_to_tree (tmu)); }); + bench.run ("tree_to_tmu doc500x10", + [&] { ankerl::nanobench::doNotOptimizeAway (tree_to_tmu (doc)); }); + ankerl::nanobench::doNotOptimizeAway (bytes); + return 0; +} diff --git a/moebius/bench/Data/Convert/tmu_write_bench.cpp b/moebius/bench/Data/Convert/tmu_write_bench.cpp new file mode 100644 index 0000000000..17da3ed85e --- /dev/null +++ b/moebius/bench/Data/Convert/tmu_write_bench.cpp @@ -0,0 +1,55 @@ +/** \file tmu_write_bench.cpp + * \copyright GPLv3 + * \details Benchmark for TMU document serialization (tree_to_tmu) + * \date 2026 + */ + +#include "nanobench.h" +#include "tmu.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include + +using namespace moebius; +using moebius::COLLECTION; +using moebius::CONCAT; +using moebius::DOCUMENT; + +/** 构造含大量连续空段落的文档:每次 cr() 时行尾只剩上一轮的 + * 缩进空格,触发尾随空格改写路径(原实现整段前缀拷贝) */ +static tree +mk_empty_paras (int n) { + tree doc (DOCUMENT); + for (int i= 0; i < n; i++) + doc << tree (""); + return doc; +} + +/** 常规文档对照 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 500; i++) { + tree par (CONCAT); + for (int j= 0; j < 10; j++) + par << tree ("word" * as_string (j)); + doc << par; + } + return doc; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (5).unit ("doc"); + + tree empties= mk_empty_paras (2000); + tree doc = mk_document (); + bench.run ("tree_to_tmu empty paras x2000", [&] { + ankerl::nanobench::doNotOptimizeAway (tree_to_tmu (empties)); + }); + bench.run ("tree_to_tmu regular doc500x10", + [&] { ankerl::nanobench::doNotOptimizeAway (tree_to_tmu (doc)); }); + return 0; +} diff --git a/moebius/bench/Data/History/commute_bench.cpp b/moebius/bench/Data/History/commute_bench.cpp new file mode 100644 index 0000000000..2da2ca1514 --- /dev/null +++ b/moebius/bench/Data/History/commute_bench.cpp @@ -0,0 +1,51 @@ +/** \file commute_bench.cpp + * \copyright GPLv3 + * \details Benchmark for modification commutation (undo/redo rebase) + * \date 2026 + */ + +#include "modification.hpp" +#include "nanobench.h" +#include "patch.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include + +using namespace moebius; + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (1000).unit ("x100"); + + // 打字序列的典型换位:同一段内连续 insert + int acc= 0; + bench.run ("commute inserts same para x100", [&] { + for (int i= 0; i < 100; i++) { + modification a= mod_insert (path (0, 3), 5, tree ("abc")); + modification b= mod_insert (path (0, 3), 9, tree ("xy")); + acc+= commute (a, b) ? 1 : 0; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + // 不同段落的 insert(不相交路径,swap_basic 快路径) + bench.run ("commute inserts disjoint x100", [&] { + for (int i= 0; i < 100; i++) { + modification a= mod_insert (path (0, 3), 5, tree ("abc")); + modification b= mod_insert (path (1, 7), 2, tree ("xy")); + acc+= commute (a, b) ? 1 : 0; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + // 嵌套路径:同孩子下递归 swap + bench.run ("commute nested path x100", [&] { + for (int i= 0; i < 100; i++) { + modification a= mod_insert (path (0, 3, 1), 5, tree ("abc")); + modification b= mod_insert (path (0, 3, 2), 2, tree ("xy")); + acc+= commute (a, b) ? 1 : 0; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + return 0; +} diff --git a/moebius/bench/Data/Scheme/block_bench.cpp b/moebius/bench/Data/Scheme/block_bench.cpp index 3d19123d2d..1bd60b8ec7 100644 --- a/moebius/bench/Data/Scheme/block_bench.cpp +++ b/moebius/bench/Data/Scheme/block_bench.cpp @@ -19,7 +19,7 @@ int main () { lolly::init_tbox (); string buffer; - url u= url_pwd () * url ("bench/Data/Scheme/dictionary.scm"); + url u= url_pwd () * url ("moebius/bench/Data/Scheme/dictionary.scm"); load_string (u, buffer, false); bench.minEpochIterations (10) .batch (N (buffer)) @@ -30,7 +30,7 @@ main () { bench.run ("serializing large group of simple element", [&] { scheme_tree_to_block (parsed_tree); }); - u= url_pwd () * url ("bench/Data/Scheme/virtual-font.scm"); + u= url_pwd () * url ("moebius/bench/Data/Scheme/virtual-font.scm"); load_string (u, buffer, false); bench.batch (N (buffer)) .run ("parsing group of complex tree", diff --git a/moebius/bench/Data/Tree/tree_cursor_bench.cpp b/moebius/bench/Data/Tree/tree_cursor_bench.cpp new file mode 100644 index 0000000000..14da41bfc2 --- /dev/null +++ b/moebius/bench/Data/Tree/tree_cursor_bench.cpp @@ -0,0 +1,203 @@ +/** \file tree_cursor_bench.cpp + * \copyright GPLv3 + * \details Benchmark for cursor movement (move_any via next_any/next_valid) + * \date 2026 + */ + +#include "cork.hpp" +#include "nanobench.h" +#include "tree.hpp" +#include "tree_cursor.hpp" +#include "tree_helper.hpp" +#include "tree_traverse.hpp" + +#include +#include + +using namespace moebius; +using moebius::drd::init_std_drd; +using moebius::drd::the_drd; + +/** 构造典型文档:100 段,每段 concat 由单词与少量复合节点组成 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 100; i++) { + tree par (CONCAT); + for (int j= 0; j < 8; j++) { + par << tree ("word" * as_string (j)); + if (j == 3) par << tree (RIGID, tree ("r" * as_string (j))); + } + doc << par; + } + return doc; +} + +// 优化前实现:每次移动三次 subtree 全树下探,用于同二进制 A/B 对比 +static path +old_move_any (tree t, path p, bool forward) { + path q = path_up (p); + int l = last_item (p); + tree st= subtree (t, q); + if (!is_nil (q) && is_func (subtree (t, path_up (q)), RAW_DATA)) { + if (forward) return path_up (q) * 1; + else return path_up (q) * 0; + } + if (is_atomic (st)) { + string s= st->label; + l = max (min (l, N (s)), 0); + if (forward) { + if (l < N (s)) { + tm_char_forwards (s, l); + return q * l; + } + } + else { + if (l > 0) { + tm_char_backwards (s, l); + return q * l; + } + } + } + else if ((forward && l == 0) || (!forward && l == 1)) { + int i, n= N (st); + if (forward) { + for (i= 0; i < n; i++) + if (the_drd->is_accessible_child (st, i)) return q * path (i, 0); + } + else { + for (i= n - 1; i >= 0; i--) + if (the_drd->is_accessible_child (st, i)) + return q * path (i, right_index (st[i])); + } + return q * (1 - l); + } + else if (is_nil (q)) return p; + l = last_item (q); + q = path_up (q); + st= subtree (t, q); + int i, n= N (st); + if (forward) { + for (i= l + 1; i < n; i++) + if (the_drd->is_accessible_child (st, i)) return q * path (i, 0); + } + else { + for (i= l - 1; i >= 0; i--) + if (the_drd->is_accessible_child (st, i)) { + return q * path (i, right_index (st[i])); + } + } + return q * (forward ? 1 : 0); +} + +// 生产代码导出但未写入 hpp,基准中补声明 +path next_any (tree t, path p); + +/** 构造单词移动基准文档:100 段含普通词/连字符词/数字词 */ +static tree +mk_word_doc () { + tree doc (DOCUMENT); + for (int i= 0; i < 100; i++) { + tree par (CONCAT); + par << tree ("alpha beta") << tree ("gamma-delta. ") << tree ("x1 x2 y3"); + doc << par; + } + return doc; +} + +int +main () { + init_std_drd (); + ankerl::nanobench::Bench bench; + bench.minEpochIterations (50).unit ("sweep"); + + tree doc= mk_document (); + path p0 = start (doc); + + // 从文档头逐字符推进到尾,模拟光标移动/选择的完整扫 + bench.run ("old next_any full sweep", [&] { + path p= p0; + int n= 0; + while (true) { + path r= old_move_any (doc, p, true); + if (r == p) break; + p= r; + n++; + } + ankerl::nanobench::doNotOptimizeAway (n); + }); + bench.run ("new next_any full sweep", [&] { + path p= p0; + int n= 0; + while (true) { + path r= next_any (doc, p); + if (r == p) break; + p= r; + n++; + } + ankerl::nanobench::doNotOptimizeAway (n); + }); + // Ctrl+Right:逐词扫全文档 + ankerl::nanobench::Bench wbench; + wbench.minEpochIterations (20).unit ("sweep"); + tree wdoc= mk_word_doc (); + path wp0 = start (wdoc); + wbench.run ("next_word full sweep", [&] { + path p= wp0; + int n= 0; + while (true) { + path r= next_word (wdoc, p); + if (r == p) break; + p= r; + n++; + } + ankerl::nanobench::doNotOptimizeAway (n); + }); + // 光标校正:keep_positive + pre_correct + left/right_correct + ankerl::nanobench::Bench cbench2; + cbench2.minEpochIterations (200).unit ("sweep"); + cbench2.run ("correct_cursor sweep100", [&] { + path pp= wp0; + int n = 0; + for (int i= 0; i < 100; i++) { + path r= correct_cursor (wdoc, pp, true); + if (r != pp) n++; + pp= r; + } + ankerl::nanobench::doNotOptimizeAway (n); + }); + // end(t,p):对每段取末光标 + cbench2.run ("end per para x100", [&] { + int n= 0; + for (int i= 0; i < N (doc); i++) { + path e= end (doc, path (i)); + n+= e->item; + } + ankerl::nanobench::doNotOptimizeAway (n); + }); + // inside_contiguous_document:图形文档光标移动钩子, + // 深嵌套下原实现逐层 is_boundary 全树下探。 + // 两光标需处于同一 DOCUMENT 孩子内,inside_same 才为真 + { + tree inner (CONCAT, tree ("aaa"), tree ("bbb")); + for (int i= 0; i < 24; i++) + inner= tree (WITH, tree ("v" * as_string (i)), tree ("1"), inner); + tree gdoc (DOCUMENT, inner); + // doc 0 号 → 24 层 WITH 沿 2 号孩子 → concat 0/1 号原子内位置 1 + path pa= path (0, path (1)); + path pb= path (1, path (1)); + for (int i= 0; i < 24; i++) { + pa= path (2, pa); + pb= path (2, pb); + } + pa= path (0, pa); + pb= path (0, pb); + cbench2.run ("inside_contiguous deep24", [&] { + int n= 0; + for (int i= 0; i < 100; i++) + n+= inside_contiguous_document (gdoc, pa, pb) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (n); + }); + } + return 0; +} diff --git a/moebius/bench/Data/Tree/tree_modify_bench.cpp b/moebius/bench/Data/Tree/tree_modify_bench.cpp new file mode 100644 index 0000000000..cdebdec717 --- /dev/null +++ b/moebius/bench/Data/Tree/tree_modify_bench.cpp @@ -0,0 +1,116 @@ +/** \file tree_modify_bench.cpp + * \copyright GPLv3 + * \details Benchmark for correct_node / correct_downwards / simplify_concat + * \date 2026 + */ + +#include "nanobench.h" +#include "tree.hpp" +#include "tree_helper.hpp" +#include "tree_modify.hpp" +#include "tree_observer.hpp" + +#include +#include + +using namespace moebius; +using moebius::drd::init_std_drd; +using moebius::drd::the_drd; + +// 修改链引用的全局编辑树与 ip 观察者定义在 mogan 主程序侧, +// 基准中给出未挂接的独立桩实现 +tree the_et; +path +obtain_ip (tree& ref) { + (void) ref; + return path (); +} +bool +ip_attached (path ip) { + (void) ip; + return false; +} +observer +list_observer (observer o1, observer o2) { + (void) o1; + (void) o2; + return observer (); +} + +/** 构造典型文档:500 段,每段 concat 由原子与少量复合节点组成 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 500; i++) { + tree par (CONCAT); + for (int j= 0; j < 8; j++) { + par << tree ("word" * as_string (j)); + if (j == 3) par << tree (RIGID, tree ("r" * as_string (j))); + } + doc << par; + } + return doc; +} + +// 生产代码中导出但未写入 hpp(bench 中补声明以复用同一路径) +void correct_concat_node (tree& t, int done); + +/** 优化前实现:contains 走字符串名绕行,用于同二进制 A/B 对比 */ +static void +old_correct_node (tree& t) { + if (is_compound (t)) { + if (the_drd->contains (as_string (L (t))) && + !the_drd->correct_arity (L (t), N (t))) + assign (t, ""); + if (is_concat (t)) correct_concat_node (t, 0); + } +} + +static void +old_correct_downwards (tree& t) { + if (is_compound (t)) + for (int i= 0; i < N (t); i++) + old_correct_downwards (t[i]); + old_correct_node (t); +} + +int +main () { + init_std_drd (); + ankerl::nanobench::Bench bench; + bench.minEpochIterations (5).unit ("doc"); + + tree doc= mk_document (); + + bench.run ("old correct_downwards doc500x8", [&] { + tree t= copy (doc); + old_correct_downwards (t); + ankerl::nanobench::doNotOptimizeAway (t); + }); + bench.run ("new correct_downwards doc500x8", [&] { + tree t= copy (doc); + correct_downwards (t); + ankerl::nanobench::doNotOptimizeAway (t); + }); + // 隔离 correct_node 本身:预校正一遍后原地反复全树遍历调用, + // 树已收敛,每轮只做 contains/arity 判定,不再有修改开销 + tree corrected= copy (doc); + correct_downwards (corrected); + bench.run ("old correct_node sweep x100", [&] { + for (int k= 0; k < 100; k++) { + old_correct_downwards (corrected); + } + ankerl::nanobench::doNotOptimizeAway (corrected); + }); + bench.run ("new correct_node sweep x100", [&] { + for (int k= 0; k < 100; k++) { + correct_downwards (corrected); + } + ankerl::nanobench::doNotOptimizeAway (corrected); + }); + // simplify_correct 全树往返(排版热路径):无变化的子树直接共享原节点 + bench.run ("simplify_correct sweep", [&] { + ankerl::nanobench::doNotOptimizeAway (simplify_correct (doc)); + }); + return 0; +} diff --git a/moebius/bench/Data/Tree/tree_observer_bench.cpp b/moebius/bench/Data/Tree/tree_observer_bench.cpp index f0f30f18d1..aff6ce7241 100644 --- a/moebius/bench/Data/Tree/tree_observer_bench.cpp +++ b/moebius/bench/Data/Tree/tree_observer_bench.cpp @@ -13,6 +13,10 @@ using namespace moebius; +// 生产代码导出但未写入 hpp,基准中补声明 +void raw_split (tree& ref, int pos, int at); +void raw_join (tree& ref, int pos); + // 修改链引用的全局编辑树与 ip 观察者定义在 mogan 主程序侧, // 基准中给出未挂接的独立桩实现 tree the_et; @@ -42,6 +46,39 @@ mk_wide_doc () { return doc; } +// 优化前的逐元素搬移 split,用于同二进制 A/B 对比 +static void +old_raw_split (tree& ref, int pos, int at) { + tree t= ref[pos], t1, t2; + if (is_atomic (ref[pos])) { + t1= ref[pos]->label (0, at); + t2= ref[pos]->label (at, N (ref[pos]->label)); + } + else { + t1= ref[pos](0, at); + t2= ref[pos](at, N (ref[pos])); + } + int i, n= N (ref); + AR (ref)->resize (n + 1); + for (i= n; i > (pos + 1); i--) + ref[i]= ref[i - 1]; + ref[pos] = t1; + ref[pos + 1]= t2; +} + +// 优化前的逐元素搬移 join,用于同二进制 A/B 对比 +static void +old_raw_join (tree& ref, int pos) { + tree t1= ref[pos], t2= ref[pos + 1], t; + if (is_atomic (t1) && is_atomic (t2)) t= t1->label * t2->label; + else t= t1 * t2; + ref[pos]= t; + int i, n= N (ref) - 1; + for (i= pos + 1; i < n; i++) + ref[i]= ref[i + 1]; + AR (ref)->resize (n); +} + int main () { ankerl::nanobench::Bench bench; @@ -55,5 +92,38 @@ main () { for (int i= 0; i < 1000; i++) raw_remove (doc, 0, 1); }); + // split/join 配平:在宽文档中部反复切分与合并原子段, + // 逐元素搬移需移动约 500 个孩子句柄,memmove 只搬一次内存块 + bench.run ("old raw_split+join mid x500", [&] { + tree t= copy (doc); + for (int i= 0; i < 500; i++) + old_raw_split (t, 500, 2); + for (int i= 0; i < 500; i++) + old_raw_join (t, 500); + }); + bench.run ("new raw_split+join mid x500", [&] { + tree t= copy (doc); + for (int i= 0; i < 500; i++) + raw_split (t, 500, 2); + for (int i= 0; i < 500; i++) + raw_join (t, 500); + }); + // 打字模拟:在长原子文本尾部逐字插入再逐字删除(原子字符串路径), + // 单个 op 内插删配平避免无界增长 + bench.run ("typing atomic insert+remove x2000", [&] { + tree txt= tree ("seed text for typing benchmark"); + for (int i= 0; i < 2000; i++) + raw_insert (txt, N (txt->label) / 2, tree ("x")); + for (int i= 0; i < 2000; i++) + raw_remove (txt, N (txt->label) / 2, 1); + }); + // 原子文本 join(如删段落边界合并) + bench.run ("atomic join pairs x500", [&] { + tree d (DOCUMENT); + for (int i= 0; i < 1000; i++) + d << tree ("fragment" * as_string (i % 7)); + for (int i= 0; i < 500; i++) + raw_join (d, 0); + }); return 0; } diff --git a/moebius/bench/Data/Tree/tree_traverse_bench.cpp b/moebius/bench/Data/Tree/tree_traverse_bench.cpp new file mode 100644 index 0000000000..a3e36f500d --- /dev/null +++ b/moebius/bench/Data/Tree/tree_traverse_bench.cpp @@ -0,0 +1,96 @@ +/** \file tree_traverse_bench.cpp + * \copyright GPLv3 + * \details Benchmark for tree_utf8_to_herk / tree_herk_to_utf8 + * \date 2026 + */ + +#include "nanobench.h" +#include "tree.hpp" +#include "tree_traverse.hpp" + +#include +#include + +using namespace moebius; + +/** 构造典型文档:1000 段,90% 纯 ASCII 段,10% 含非 ASCII 字符的段 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 1000; i++) { + tree par (CONCAT); + for (int j= 0; j < 10; j++) { + if (i % 10 == 0 && j == 5) + par << tree ("w\xC3\xA9rds " * as_string (j)); // é + else par << tree ("word number " * as_string (j)); + } + doc << par; + } + return doc; +} + +/** 优化前实现:原子一律走 lolly 逐字符转换,用于同二进制 A/B 对比 */ +static tree +old_utf8_to_herk (tree t) { + if (is_atomic (t)) return tree (lolly::data::utf8_to_herk (t->label)); + else if (!is_func (t, RAW_DATA)) { + int n= N (t); + tree t2 (t, n); + for (int i= 0; i < n; i++) + t2[i]= old_utf8_to_herk (t[i]); + return t2; + } + else return t; +} + +/** 优化前实现,用于同二进制 A/B 对比 */ +static tree_u8 +old_herk_to_utf8 (tree t) { + if (is_atomic (t)) return tree (lolly::data::herk_to_utf8 (t->label)); + else if (!is_func (t, RAW_DATA)) { + int n= N (t); + tree t2 (t, n); + for (int i= 0; i < n; i++) + t2[i]= old_herk_to_utf8 (t[i]); + return t2; + } + else return t; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (10).unit ("doc"); + + tree doc = mk_document (); + tree herk_doc= tree_utf8_to_herk (doc); + + bench.run ("old utf8->herk doc1000x10", [&] { + ankerl::nanobench::doNotOptimizeAway (old_utf8_to_herk (doc)); + }); + bench.run ("new utf8->herk doc1000x10", [&] { + ankerl::nanobench::doNotOptimizeAway (tree_utf8_to_herk (doc)); + }); + bench.run ("old herk->utf8 doc1000x10", [&] { + ankerl::nanobench::doNotOptimizeAway (old_herk_to_utf8 (herk_doc)); + }); + bench.run ("new herk->utf8 doc1000x10", [&] { + ankerl::nanobench::doNotOptimizeAway (tree_herk_to_utf8 (herk_doc)); + }); + + // 纯 ASCII 长文档:快路径全覆盖的理想场景 + tree plain (DOCUMENT); + for (int i= 0; i < 1000; i++) { + tree par (CONCAT); + for (int j= 0; j < 10; j++) + par << tree ("word number " * as_string (j)); + plain << par; + } + bench.run ("old utf8->herk ascii-only", [&] { + ankerl::nanobench::doNotOptimizeAway (old_utf8_to_herk (plain)); + }); + bench.run ("new utf8->herk ascii-only", [&] { + ankerl::nanobench::doNotOptimizeAway (tree_utf8_to_herk (plain)); + }); + return 0; +} diff --git a/moebius/bench/Kernel/Types/curve_bench.cpp b/moebius/bench/Kernel/Types/curve_bench.cpp index 681096905b..a8a4ae0b6a 100644 --- a/moebius/bench/Kernel/Types/curve_bench.cpp +++ b/moebius/bench/Kernel/Types/curve_bench.cpp @@ -24,5 +24,31 @@ main () { bench.run ("poly_segment bound", [&] { acc+= poly->bound (0.5, 1e-3); }); ankerl::nanobench::doNotOptimizeAway (acc); + + // spline 求值/取直:interval_no 缓存与逐点临时消除的整体收益 + ankerl::nanobench::Bench sbench; + sbench.minEpochIterations (100).unit ("sweep"); + + array sa; + sa << point (0.0, 0.0) << point (1.0, 3.0) << point (3.0, 2.0) + << point (5.0, 5.0) << point (7.0, 1.0); + curve sp= spline (sa, array (), false, true); + + double sacc= 0; + // t 单调推进的典型求值扫(渲染/取直),interval_no 缓存命中最多的场景 + sbench.run ("spline evaluate sweep monotonic", [&] { + for (int i= 0; i <= 512; i++) + sacc+= sp->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (sacc); + }); + // t 来回震荡,缓存大多不命中,验证回退路径无明显回退 + sbench.run ("spline evaluate sweep alternating", [&] { + for (int i= 0; i <= 512; i++) + sacc+= sp->evaluate ((i & 1) ? i / 512.0 : 1.0 - i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (sacc); + }); + sbench.run ("spline rectify eps=0.1", [&] { + ankerl::nanobench::doNotOptimizeAway (sp->rectify (0.1)); + }); return 0; } diff --git a/moebius/bench/Kernel/Types/curve_closest_bench.cpp b/moebius/bench/Kernel/Types/curve_closest_bench.cpp new file mode 100644 index 0000000000..c78806463a --- /dev/null +++ b/moebius/bench/Kernel/Types/curve_closest_bench.cpp @@ -0,0 +1,136 @@ +/** \file curve_closest_bench.cpp + * \copyright GPLv3 + * \details Benchmark for closest-point search on curves (graphical select) + * \date 2026 + */ + +#include "curve.hpp" +#include "math_util.hpp" +#include "nanobench.h" +#include "point.hpp" + +/** 构造折线控制点:n 段锯齿 */ +static array +mk_points (int n) { + array a; + for (int i= 0; i < n; i++) + a << point (0.1 * i, ((i & 1) ? 0.5 : -0.5)); + return a; +} + +// 优化前的逐步距离计算:每步构造差向量临时,用于同二进制 A/B 对比 +static array +old_find_closest (curve c, double t1, double t2, point p, double eps) { + array res; + double closest= -1, n0= tm_infinity, nprec= n0; + bool stored= true, decreasing= false; + point pclosest; + double max_step= 0.5 / max (c->nr_components (), 1); + for (double t= t1; t <= t2;) { + point pt= c->evaluate (t); + double n = norm (pt - p); + if (n < n0) { + n0 = n; + closest = t; + pclosest= pt; + stored = false; + } + decreasing= n < nprec; + if (!stored && !decreasing) { + res << closest; + stored= true; + } + if (stored && decreasing) n0= tm_infinity; + double delta= (n - eps) / 2; + t+= min (max_step, max (0.00001, c->bound (t, max (eps, delta)))); + nprec= n; + } + if (!stored && decreasing) res << closest; + return res; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (100).unit ("search"); + + curve poly= poly_segment (mk_points (128), array ()); + array sa; + sa << point (0.0, 0.0) << point (1.0, 3.0) << point (3.0, 2.0) + << point (5.0, 5.0) << point (7.0, 1.0); + curve spl= spline (sa, array (), false, true); + point pin= point (6.0, 0.3); + int acc= 0; + + bench.run ("old find_closest poly128", [&] { + acc+= N (old_find_closest (poly, 0.0, 1.0, pin, 0.01)); + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new find_closest poly128", [&] { + acc+= N (poly->find_closest_points (0.0, 1.0, pin, 0.01)); + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new find_closest spline", [&] { + acc+= N (spl->find_closest_points (0.0, 1.0, pin, 0.01)); + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("closest point poly128", [&] { + acc+= (int) closest (poly, pin)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + + // 圆弧/椭圆求值与取直:图形渲染的采样内层循环 + ankerl::nanobench::Bench cbench; + cbench.minEpochIterations (200).unit ("sweep"); + array ea; + // 两焦点与椭圆上一点:r1=5, r2=3 + ea << point (-4.0, 0.0) << point (4.0, 0.0) << point (0.0, 3.0); + curve el= ellipse (ea, array (), true); + array aa; + aa << point (0.0, 0.0) << point (10.0, 0.0) << point (0.0, 10.0); + curve ac= arc (aa, array (), true); + cbench.run ("ellipse evaluate sweep512", [&] { + for (int i= 0; i <= 512; i++) + acc+= el->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + cbench.run ("arc evaluate sweep512", [&] { + for (int i= 0; i <= 512; i++) + acc+= ac->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + cbench.run ("ellipse rectify eps=0.1", [&] { + ankerl::nanobench::doNotOptimizeAway (el->rectify (0.1)); + }); + // 贝塞尔曲线:图形里平滑路径的通用表示 + array ba; + ba << point (0.0, 0.0) << point (1.0, 4.0) << point (4.0, 4.0) + << point (6.0, 0.0); + curve bz= bezier (ba); + cbench.run ("bezier evaluate sweep512", [&] { + for (int i= 0; i <= 512; i++) + acc+= bz->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + cbench.run ("bezier rectify eps=0.1", [&] { + ankerl::nanobench::doNotOptimizeAway (bz->rectify (0.1)); + }); + // 双曲线/抛物线 + array ha; + ha << point (-4.0, 0.0) << point (4.0, 0.0) << point (8.0, 3.0); + curve hy= hyperbola (ha, array (), false); + cbench.run ("hyperbola evaluate sweep512", [&] { + for (int i= 0; i <= 512; i++) + acc+= hy->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + array pa; + pa << point (-4.0, 0.0) << point (4.0, 0.0) << point (0.0, 3.0); + curve pb= parabola (pa, array (), false); + cbench.run ("parabola evaluate sweep512", [&] { + for (int i= 0; i <= 512; i++) + acc+= pb->evaluate (i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + return 0; +} diff --git a/moebius/bench/Kernel/Types/curve_eval_bench.cpp b/moebius/bench/Kernel/Types/curve_eval_bench.cpp new file mode 100644 index 0000000000..954b3d4a03 --- /dev/null +++ b/moebius/bench/Kernel/Types/curve_eval_bench.cpp @@ -0,0 +1,79 @@ +/** \file point_bench2.cpp + * \copyright GPLv3 + * \details Benchmark for point/curve evaluate temp-elimination optimizations + * \date 2026 + */ + +#include "curve.hpp" +#include "nanobench.h" +#include "point.hpp" + +// 优化前的 segment 求值:三个中间 point 临时,用于同二进制 A/B 对比 +static point +old_segment_evaluate (point p1, point p2, double t) { + return (1.0 - t) * p1 + t * p2; +} + +// 优化前的 poly_segment 求值,用于同二进制 A/B 对比 +static point +old_poly_evaluate (array a, int n, double t) { + int i= max (min ((int) (n * t), n - 1), 0); + return (i + 1 - n * t) * a[i] + (n * t - i) * a[i + 1]; +} + +// 优化前的 rotate_2D:p-o 与 +o 两个中间临时,用于同二进制 A/B 对比 +static point +old_rotate_2D (point p, point o, double angle) { + double c= cos (angle), s= sin (angle); + point d= p - o; + if (N (d) == 0) d= point (0.0, 0.0); + if (N (d) == 1) d= point (d[0], 0.0); + return point (c * d[0] - s * d[1], s * d[0] + c * d[1]) + o; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (10000).unit ("x1024"); + + curve seg= segment (point (0.0, 0.0), point (3.0, 4.0)); + array a; + a << point (0.0, 0.0) << point (1.0, 2.0) << point (4.0, 2.0) + << point (5.0, 0.0); + curve poly= poly_segment (a, array ()); + point o = point (1.0, 1.0); + double acc = 0; + + bench.run ("old segment evaluate x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= old_segment_evaluate (point (0.0, 0.0), point (3.0, 4.0), + i / 1024.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new segment evaluate x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= seg->evaluate (i / 1024.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("old poly_segment evaluate x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= old_poly_evaluate (a, 3, i / 1024.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new poly_segment evaluate x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= poly->evaluate (i / 1024.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("old rotate_2D x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= old_rotate_2D (point (2.0, 3.0), o, i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new rotate_2D x1024", [&] { + for (int i= 0; i < 1024; i++) + acc+= rotate_2D (point (2.0, 3.0), o, i / 512.0)[0]; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + return 0; +} diff --git a/moebius/bench/Kernel/Types/frame_bench.cpp b/moebius/bench/Kernel/Types/frame_bench.cpp new file mode 100644 index 0000000000..9cdd3f1350 --- /dev/null +++ b/moebius/bench/Kernel/Types/frame_bench.cpp @@ -0,0 +1,82 @@ +/** \file frame_bench.cpp + * \copyright GPLv3 + * \details Benchmark for frame point transforms (scaling / linear_2D) + * \date 2026 + */ + +#include "frame.hpp" +#include "matrix.hpp" +#include "nanobench.h" + +// 优化前的标量 scaling 直变换:两个中间 point 临时,用于同二进制 A/B 对比 +static point +old_scaling_direct (double magnify, point shift, point p) { + return shift + magnify * p; +} + +// 优化前的按轴 scaling 直变换,用于同二进制 A/B 对比 +static point +old_anscaling_direct (point magnify, point shift, point p) { + return shift + magnify * p; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (10000).unit ("op"); + + point origin= point (0.0, 0.0); + frame sc = scaling (2.0, point (1.0, 1.0)); + frame ansc = scaling (point (2.0, 3.0), point (1.0, 1.0)); + frame lin = linear_2D (matrix_2D (1.5, 0.2, -0.1, 2.5)); + + // 模拟图形渲染:同一片点集反复变换 + enum { NPTS= 1024 }; + static point pts[NPTS]; + for (int i= 0; i < NPTS; i++) + pts[i]= point (0.1 * i, 0.02 * i); + + double acc= 0.0; + bench.run ("old scaling direct x1024", [&] { + for (int i= 0; i < NPTS; i++) { + point q= old_scaling_direct (2.0, point (1.0, 1.0), pts[i]); + acc+= q[0]; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new scaling direct x1024", [&] { + for (int i= 0; i < NPTS; i++) { + point q= sc (pts[i]); + acc+= q[0]; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("old an_scaling direct x1024", [&] { + for (int i= 0; i < NPTS; i++) { + point q= + old_anscaling_direct (point (2.0, 3.0), point (1.0, 1.0), pts[i]); + acc+= q[0]; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new an_scaling direct x1024", [&] { + for (int i= 0; i < NPTS; i++) { + point q= ansc (pts[i]); + acc+= q[0]; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("linear_2D direct x1024 (baseline)", [&] { + for (int i= 0; i < NPTS; i++) { + point q= lin (pts[i]); + acc+= q[0]; + } + ankerl::nanobench::doNotOptimizeAway (acc); + }); + // 矩形包围盒:graphics 中 frame::enclose 的典型入口 + rectangle r (0, 0, 100, 100); + bench.run ("enclose rect scaling", + [&] { ankerl::nanobench::doNotOptimizeAway (sc (r)); }); + (void) origin; + return 0; +} diff --git a/moebius/bench/Kernel/Types/modification_bench.cpp b/moebius/bench/Kernel/Types/modification_bench.cpp new file mode 100644 index 0000000000..60aa71cf4d --- /dev/null +++ b/moebius/bench/Kernel/Types/modification_bench.cpp @@ -0,0 +1,97 @@ +/** \file modification_bench.cpp + * \copyright GPLv3 + * \details Benchmark for is_applicable / can_* applicability checks + * \date 2026 + */ + +#include "modification.hpp" +#include "nanobench.h" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include + +using namespace moebius; + +// 生产代码导出但未写入 hpp,基准中补声明 +bool can_insert (tree t, path p, int pos, tree u); +bool can_remove (tree t, path p, int pos, int nr); + +/** 构造嵌套文档:10 层 document 嵌套,每层 10 个孩子 */ +static tree +mk_deep_doc () { + tree t (DOCUMENT, tree ("leaf")); + for (int i= 0; i < 10; i++) { + tree outer (DOCUMENT); + for (int j= 0; j < 10; j++) + outer << tree ("filler" * as_string (j)); + outer[9]= t; + t = outer; + } + return t; +} + +static path +mk_deep_path (int n) { + path p (9); + for (int i= 1; i < n; i++) + p= path (9, p); + return p; +} + +// 优化前实现:has_subtree + subtree 两趟遍历,用于同二进制 A/B 对比 +static bool +old_can_insert (tree t, path p, int pos, tree u) { + if (!has_subtree (t, p)) return false; + tree st= subtree (t, p); + if (is_atomic (st)) return pos >= 0 && pos <= N (st->label) && is_atomic (u); + else return pos >= 0 && pos <= N (st) && is_compound (u); +} + +static bool +old_can_remove (tree t, path p, int pos, int nr) { + if (!has_subtree (t, p)) return false; + tree st= subtree (t, p); + if (is_atomic (st)) return pos >= 0 && pos + nr <= N (st->label); + else return pos >= 0 && pos + nr <= N (st); +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (10000).unit ("op"); + + tree doc = mk_deep_doc (); + path deep= mk_deep_path (9); + tree atomic_ins ("xyz"); + tree compound_ins (DOCUMENT, tree ("x")); + int acc= 0; + + bench.run ("old can_insert depth9", [&] { + acc+= old_can_insert (doc, deep, 0, compound_ins) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new can_insert depth9", [&] { + acc+= can_insert (doc, deep, 0, compound_ins) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("old can_remove depth9", [&] { + acc+= old_can_remove (doc, deep, 0, 1) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new can_remove depth9", [&] { + acc+= can_remove (doc, deep, 0, 1) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + // 命中原子节点后继续下探(失败路径) + path over= mk_deep_path (12); + bench.run ("old can_insert miss depth12", [&] { + acc+= old_can_insert (doc, over, 0, atomic_ins) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new can_insert miss depth12", [&] { + acc+= can_insert (doc, over, 0, atomic_ins) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + return 0; +} diff --git a/moebius/bench/Kernel/Types/spline_bench.cpp b/moebius/bench/Kernel/Types/spline_bench.cpp new file mode 100644 index 0000000000..9dd1c7084a --- /dev/null +++ b/moebius/bench/Kernel/Types/spline_bench.cpp @@ -0,0 +1,36 @@ +/** \file spline_bench.cpp + * \copyright GPLv3 + * \details Benchmark for spline construction (tridiagonal solves) + * \date 2026 + */ + +#include "curve.hpp" +#include "nanobench.h" +#include "point.hpp" + +/** 构造 n 个控制点的点集 */ +static array +mk_points (int n) { + array a; + for (int i= 0; i < n; i++) + a << point (0.1 * i, 0.05 * ((i * 7) % 23)); + return a; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (100).unit ("ctor"); + + array pts= mk_points (256); + // 开样条走 tridiag_solve,闭合样条走 xtridiag/quasitridiag_solve + bench.run ("spline open pts256", [&] { + ankerl::nanobench::doNotOptimizeAway ( + spline (pts, array (), false, true)); + }); + bench.run ("spline closed pts256", [&] { + ankerl::nanobench::doNotOptimizeAway ( + spline (pts, array (), true, true)); + }); + return 0; +} diff --git a/moebius/bench/moebius/data/colors_bench.cpp b/moebius/bench/moebius/data/colors_bench.cpp new file mode 100644 index 0000000000..9d8f32aef0 --- /dev/null +++ b/moebius/bench/moebius/data/colors_bench.cpp @@ -0,0 +1,34 @@ +/** \file colors_bench.cpp + * \copyright GPLv3 + * \details Benchmark for named color resolution + * \date 2026 + */ + +#include "nanobench.h" + +#include + +using namespace moebius::data; + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (1000).unit ("x100"); + + // 常用命名颜色轮询:渲染期重复解析同一批颜色 + const char* names[]= {"red", "blue", "green", "black", + "white", "gray", "orange", "pastel"}; + color acc = 0; + bench.run ("named_color x100", [&] { + for (int i= 0; i < 100; i++) + acc+= named_color (names[i % 8]); + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("named_color_to_xcolormap x100", [&] { + int n= 0; + for (int i= 0; i < 100; i++) + n+= N (named_color_to_xcolormap (names[i % 8])); + ankerl::nanobench::doNotOptimizeAway (n); + }); + return 0; +} diff --git a/moebius/bench/moebius/data/scheme_load_bench.cpp b/moebius/bench/moebius/data/scheme_load_bench.cpp new file mode 100644 index 0000000000..43a27ee989 --- /dev/null +++ b/moebius/bench/moebius/data/scheme_load_bench.cpp @@ -0,0 +1,42 @@ +/** \file scheme_load_bench.cpp + * \copyright GPLv3 + * \details Benchmark for .tm document loading (scheme parse + tree convert) + * \date 2026 + */ + +#include "nanobench.h" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include +#include + +using namespace moebius; +using moebius::data::scheme_document_to_tree; +using moebius::data::scheme_to_tree; + +/** 构造典型 .tm 文档文本:500 段,每段 concat 若干词与标记 */ +static string +mk_tm_document () { + string s= "(document (TeXmacs \"2.1.2\") "; + for (int i= 0; i < 500; i++) { + s << "(concat \"word0\" \"word1\" (rigid \"r2\") \"word3\")"; + } + s << ")"; + return s; +} + +int +main () { + ankerl::nanobench::Bench bench; + bench.minEpochIterations (5).unit ("doc"); + + string buf= mk_tm_document (); + bench.run ("scheme_document_to_tree doc500", [&] { + ankerl::nanobench::doNotOptimizeAway (scheme_document_to_tree (buf)); + }); + bench.run ("scheme_to_tree doc500", [&] { + ankerl::nanobench::doNotOptimizeAway (scheme_to_tree (buf)); + }); + return 0; +} diff --git a/moebius/bench/moebius/drd/drd_env_bench.cpp b/moebius/bench/moebius/drd/drd_env_bench.cpp new file mode 100644 index 0000000000..eafbe65e0a --- /dev/null +++ b/moebius/bench/moebius/drd/drd_env_bench.cpp @@ -0,0 +1,148 @@ +/** \file drd_env_bench.cpp + * \copyright GPLv3 + * \details Benchmark for drd_info::get_env_child (cursor validation path) + * \date 2026 + */ + +#include "nanobench.h" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include +#include +#include + +using namespace moebius; +using moebius::drd::drd_decode; +using moebius::drd::drd_env_merge; +using moebius::drd::drd_env_read; +using moebius::drd::init_std_drd; +using moebius::drd::the_drd; + +/** 构造典型文档:500 段,每段 concat 由原子与少量复合节点组成 */ +static tree +mk_document () { + tree doc (DOCUMENT); + for (int i= 0; i < 500; i++) { + tree par (CONCAT); + for (int j= 0; j < 8; j++) { + par << tree ("word" * as_string (j)); + if (j == 3) par << tree (RIGID, tree ("r" * as_string (j))); + } + doc << par; + } + return doc; +} + +/** 优化前实现:一律构造 ATTR、合并、线性读取,用于同二进制 A/B 对比 */ +static tree +old_get_env_child (tree t, int i, string var, tree val) { + tree env (ATTR); + if (L (t) == WITH && i == N (t) - 1) + env= drd_env_merge (env, t (0, N (t) - 1)); + else { + drd::tag_info ti = the_drd->info[L (t)]; + int index= ti->get_index (i, N (t)); + if ((index < 0) || (index >= N (ti->ci))) return val; + tree cenv= drd_decode (ti->ci[index].env); + for (int k= 1; k < N (cenv); k+= 2) + if (is_func (cenv[k], ARG, 1) && is_int (cenv[k][0])) { + cenv = copy (cenv); + int j2= as_int (cenv[k][0]); + if (j2 >= 0 && j2 < N (t)) cenv[k]= copy (t[j2]); + } + env= drd_env_merge (env, cenv); + } + return drd_env_read (env, var, val); +} + +int +main () { + init_std_drd (); + ankerl::nanobench::Bench bench; + bench.minEpochIterations (20).unit ("doc-sweep"); + + tree doc= mk_document (); + tree acc (""); + + // 模拟 is_accessible_cursor 的调用形态:逐节点读 mode 环境 + bench.run ("old get_env_child mode sweep", [&] { + tree r (""); + for (int i= 0; i < N (doc); i++) { + r= old_get_env_child (doc, i, "mode", tree ("")); + for (int j= 0; j < N (doc[i]); j++) + r= old_get_env_child (doc[i], j, "mode", tree ("")); + } + acc= r; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + bench.run ("new get_env_child mode sweep", [&] { + tree r (""); + for (int i= 0; i < N (doc); i++) { + r= the_drd->get_env_child (doc, i, "mode", tree ("")); + for (int j= 0; j < N (doc[i]); j++) + r= the_drd->get_env_child (doc[i], j, "mode", tree ("")); + } + acc= r; + ankerl::nanobench::doNotOptimizeAway (acc); + }); + // EXTERN 节点(可执行标记)的光标可达性:每个节点都要 + // 派生 "extern:" 标签 + tree xdoc (DOCUMENT); + for (int i= 0; i < 200; i++) { + tree ex (EXTERN, tree ("strong-tag" * as_string (i % 5))); + for (int j= 0; j < 4; j++) + ex << tree ("arg" * as_string (j)); + xdoc << ex; + } + bench.run ("is_accessible_child extern sweep", [&] { + int n= 0; + for (int i= 0; i < N (xdoc); i++) + for (int j= 0; j < N (xdoc[i]); j++) + n+= the_drd->is_accessible_child (xdoc[i], j) ? 1 : 0; + ankerl::nanobench::doNotOptimizeAway (n); + }); + // 源码模式文档:大量 with "mode" "src" 包裹, + // is_accessible_cursor 逐节点读 mode 环境 + tree wdoc (DOCUMENT); + for (int i= 0; i < 500; i++) { + tree w (WITH, tree ("mode"), tree ("src"), tree ("body" * as_string (i))); + wdoc << w; + } + tree wacc (""); + bench.run ("get_env_child WITH mode sweep", [&] { + tree r (""); + for (int i= 0; i < N (wdoc); i++) + r= the_drd->get_env_child (wdoc[i], 2, "mode", tree ("text")); + wacc= r; + ankerl::nanobench::doNotOptimizeAway (wacc); + }); + // 环境链下探:深层 WITH 嵌套求 mode(get_env_descendant env 变体) + tree deep= tree ("leaf"); + for (int i= 0; i < 24; i++) + deep= tree (WITH, tree ("mode"), tree ("src"), deep); + tree wdoc2 (DOCUMENT, deep); + path dp= path (1); + for (int i= 0; i < 24; i++) + dp= path (2, dp); + dp= path (0, dp); + bench.run ("get_env_descendant WITH chain24", [&] { + ankerl::nanobench::doNotOptimizeAway ( + the_drd->get_env_descendant (wdoc2, dp, tree (ATTR))); + }); + // 不同变量链:env 逐层增长,走插入/追加路径 + tree deep2= tree ("leaf"); + for (int i= 0; i < 24; i++) + deep2= + tree (WITH, tree ("v" * as_string ((i * 7) % 24)), tree ("src"), deep2); + tree wdoc3 (DOCUMENT, deep2); + path dp2= path (1); + for (int i= 0; i < 24; i++) + dp2= path (2, dp2); + dp2= path (0, dp2); + bench.run ("get_env_descendant distinct vars chain24", [&] { + ankerl::nanobench::doNotOptimizeAway ( + the_drd->get_env_descendant (wdoc3, dp2, tree (ATTR))); + }); + return 0; +} diff --git a/moebius/moebius/data/scheme_der.cpp b/moebius/moebius/data/scheme_der.cpp index a4253fe2fc..aad39defd7 100644 --- a/moebius/moebius/data/scheme_der.cpp +++ b/moebius/moebius/data/scheme_der.cpp @@ -20,16 +20,31 @@ using moebius::TUPLE; namespace moebius { namespace data { +// 成段 memcpy 直写目标串(resize 后整块拷贝,无子串分配) +static inline void +append_run (string& r, const char* src, int len) { + if (len <= 0) return; + int old_n= N (r); + r->resize (old_n + len); + memcpy (r.begin () + old_n, src, len); +} + string scm_unquote (string s) { if (is_quoted (s)) { - int i, n= N (s); - string r; + // 普通字符成段 memcpy 直写,转义字符单独处理 + int i, n= N (s), run= 1; + string r; + const char* raw= s.begin (); for (i= 1; i < n - 1; i++) if (s[i] == '\\' && - (s[i + 1] == '\\' || (s[i + 1] == '\"' && i + 2 != n))) - r << s[++i]; - else r << s[i]; + (s[i + 1] == '\\' || (s[i + 1] == '\"' && i + 2 != n))) { + append_run (r, raw + run, i - run); + i++; + r << s[i]; + run= i + 1; + } + append_run (r, raw + run, n - 1 - run); return r; } else return s; @@ -40,8 +55,9 @@ scm_unquote (string s) { ******************************************************************************/ void unslash (string& s, int i, int end_index, string& r, int& r_index) { - char ch= s[i]; + // 循环顶先判界再读字符:原实现末尾的 ch= s[i] 会越界读一字节 while (i < end_index) { + char ch= s[i]; if ((ch == '\\') && ((i + 1) < end_index)) { i++; ch= s[i]; @@ -68,7 +84,6 @@ unslash (string& s, int i, int end_index, string& r, int& r_index) { r_index++; } i++; - ch= s[i]; } } @@ -142,14 +157,16 @@ string_to_scheme_tree (string& s, int& i, const int length) { i++; int end_index = i; const int start_index= i; - char ch = s[end_index]; - unsigned char* types = char_type; - while (!(ch == '\"') && end_index < length) { + char ch; + unsigned char* types= char_type; + // 先判界再读字符:原实现的 ch= s[end_index] 会越界读末尾一字节 + while (end_index < length) { + ch= s[end_index]; + if (ch == '\"') break; if (types[(unsigned char) ch] & CT_ESC) { if (end_index < length - 1) end_index++; } end_index++; - ch= s[end_index]; } const int r_size = 1; // N ("\""); int quoted_index= r_size; @@ -171,15 +188,16 @@ string_to_scheme_tree (string& s, int& i, const int length) { default: { int end_index = i; const int start_index= i; - char ch = s[end_index]; - unsigned char* types = char_type; - while (end_index < length && - !(types[(unsigned char) ch] & (CT_SPC | CT_PAREN))) { + char ch; + unsigned char* types= char_type; + // 先判界再读字符,避免词元结尾在缓冲区末尾时越界读 + while (end_index < length) { + ch= s[end_index]; + if (types[(unsigned char) ch] & (CT_SPC | CT_PAREN)) break; if (types[(unsigned char) ch] & CT_ESC) { if (end_index < length - 1) end_index++; } end_index++; - ch= s[end_index]; } const int r_size = 0; // empty string int token_index= r_size; @@ -197,7 +215,14 @@ string_to_scheme_tree (string& s, int& i, const int length) { scheme_tree string_to_scheme_tree (string s) { if (!char_type_init) init_char_type (); - s = replace (s, "\015", ""); + // 含 CR 时才做整串替换拷贝 + bool has_cr= false; + for (int k= 0; k < N (s); k++) + if (s[k] == '\015') { + has_cr= true; + break; + } + if (has_cr) s= replace (s, "\015", ""); int i = 0; const int length= N (s); return string_to_scheme_tree (s, i, length); @@ -229,9 +254,10 @@ scheme_tree_to_tree (scheme_tree t, hashmap codes, bool flag) { "errput", concat ("The tree was ", as_string (L (t)), ": ", tree (t))); } else { - int i, n= N (t); - tree_label code= (tree_label) codes[t[0]->label]; - if (flag) code= make_tree_label (t[0]->label); + int i, n= N (t); + // flag 路径下 codes 查表结果会被覆盖,跳过这次全串哈希查表 + tree_label code= + flag ? make_tree_label (t[0]->label) : (tree_label) codes[t[0]->label]; if (code == UNKNOWN) { tree u (EXPAND, n); u[0]= copy (t[0]); diff --git a/moebius/moebius/data/scheme_ser.cpp b/moebius/moebius/data/scheme_ser.cpp index 1ffb823a00..a22e97a210 100644 --- a/moebius/moebius/data/scheme_ser.cpp +++ b/moebius/moebius/data/scheme_ser.cpp @@ -13,24 +13,35 @@ #include "moebius/data/scheme.hpp" #include "tree_helper.hpp" +#include + namespace moebius { namespace data { +// 成段 memcpy 直写目标串(resize 后整块拷贝,无子串分配) +static inline void +append_run (string& r, const char* src, int len) { + if (len <= 0) return; + int old_n= N (r); + r->resize (old_n + len); + memcpy (r.begin () + old_n, src, len); +} + string scm_quote (string s) { // R5RS compliant external string representation. - int i, n= N (s); - string r; + // 普通字符成段 memcpy 直写,转义字符单独处理 + int i, n= N (s), run= 0; + string r; + const char* raw= s.begin (); r << '"'; for (i= 0; i < n; i++) - switch (s[i]) { - case '\"': - case '\\': + if (s[i] == '\"' || s[i] == '\\') { + append_run (r, raw + run, i - run); r << '\\' << s[i]; - break; - default: - r << s[i]; + run= i + 1; } + append_run (r, raw + run, n - run); r << '"'; return r; } @@ -41,38 +52,56 @@ scm_quote (string s) { string slash (string s) { - int i, n= N (s); - string r; + // 普通字符成段 memcpy 直写,特殊字符单独转义 + int i, n= N (s), run= 0; + string r; + const char* raw= s.begin (); for (i= 0; i < n; i++) switch (s[i]) { case '(': case ')': case ' ': case '\'': + append_run (r, raw + run, i - run); if ((n < 2) || (s[0] != '\042') || (s[n - 1] != '\042')) r << "\\"; r << s[i]; + run= i + 1; break; case '\\': + append_run (r, raw + run, i - run); r << '\\' << s[i]; + run= i + 1; break; case '\042': if (((i == 0) && (s[n - 1] == '\042')) || - ((i == (n - 1)) && (s[0] == '\042'))) - r << s[i]; - else r << "\\" << s[i]; + ((i == (n - 1)) && (s[0] == '\042'))) { + ; // 首尾引号原样保留,并入当前 run + } + else { + append_run (r, raw + run, i - run); + r << "\\" << s[i]; + run= i + 1; + } break; case ((char) 0): + append_run (r, raw + run, i - run); r << "\\0"; + run= i + 1; break; case '\t': + append_run (r, raw + run, i - run); r << "\\t"; + run= i + 1; break; case '\n': + append_run (r, raw + run, i - run); r << "\\n"; + run= i + 1; break; default: - r << s[i]; + break; } + append_run (r, raw + run, n - run); return r; } diff --git a/moebius/moebius/drd/drd_info.cpp b/moebius/moebius/drd/drd_info.cpp index 7e7bb4b3aa..650bd2c04a 100644 --- a/moebius/moebius/drd/drd_info.cpp +++ b/moebius/moebius/drd/drd_info.cpp @@ -60,6 +60,12 @@ drd_info_rep::contains (string l) { return existing_tree_label (l) && info->contains (as_tree_label (l)); } +bool +drd_info_rep::contains (tree_label l) { + // 树节点上 L(t) 已是 interned 标签,无需经字符串名绕行两次查表 + return info->contains (l); +} + tm_ostream& operator<< (tm_ostream& out, drd_info drd) { return out << "drd [" << drd->name << "]"; @@ -465,11 +471,24 @@ drd_info_rep::freeze_type (tree_label l, int nr) { ci.freeze_type= true; } +// EXTERN 节点的派生标签按宏名高频重复(同一宏多次出现), +// 单条备忘缓存免去每次 "extern:" 字符串拼接与标签查表 +static tree_label +extern_label (string s) { + static string memo_s; + static tree_label memo_l= UNKNOWN; + if (memo_l != UNKNOWN && N (memo_s) == N (s) && memo_s == s) return memo_l; + tree_label l= make_tree_label ("extern:" * s); + memo_s = s; + memo_l = l; + return l; +} + int drd_info_rep::get_type_child (tree t, int i) { tag_info ti= info[L (t)]; if (is_func (t, EXTERN) && N (t) > 0 && is_atomic (t[0])) { - tree_label lab= make_tree_label ("extern:" * t[0]->label); + tree_label lab= extern_label (t[0]->label); if (info->contains (lab)) { ti= info[lab]; } @@ -536,7 +555,7 @@ drd_info_rep::is_accessible_child (tree t, int i) { // cout << "l= " << as_string (L(t)) << "\n"; tag_info ti= info[L (t)]; if (is_func (t, EXTERN) && N (t) > 0 && is_atomic (t[0])) { - tree_label lab= make_tree_label ("extern:" * t[0]->label); + tree_label lab= extern_label (t[0]->label); if (info->contains (lab)) { ti= info[lab]; } @@ -603,7 +622,7 @@ int drd_info_rep::get_writability_child (tree t, int i) { tag_info ti= info[L (t)]; if (is_func (t, EXTERN) && N (t) > 0 && is_atomic (t[0])) { - tree_label lab= make_tree_label ("extern:" * t[0]->label); + tree_label lab= extern_label (t[0]->label); if (info->contains (lab)) { ti= info[lab]; } @@ -645,7 +664,7 @@ string drd_info_rep::get_child_name (tree t, int i) { tag_info ti= info[L (t)]; if (is_func (t, EXTERN) && N (t) > 0 && is_atomic (t[0])) { - tree_label lab= make_tree_label ("extern:" * t[0]->label); + tree_label lab= extern_label (t[0]->label); if (info->contains (lab)) { ti= info[lab]; } @@ -663,7 +682,7 @@ string drd_info_rep::get_child_long_name (tree t, int i) { tag_info ti= info[L (t)]; if (is_func (t, EXTERN) && N (t) > 0 && is_atomic (t[0])) { - tree_label lab= make_tree_label ("extern:" * t[0]->label); + tree_label lab= extern_label (t[0]->label); if (info->contains (lab)) { ti= info[lab]; } @@ -685,12 +704,35 @@ drd_info_rep::get_child_long_name (tree t, int i) { tree drd_env_write (tree env, string var, tree val) { - for (int i= 0; i <= N (env); i+= 2) - if (i == N (env)) return env * tree (ATTR, var, val); + // 单次分配重建结果(追加/插入/替换三种情形), + // 原实现要两次切片 + 元组构造 + 两次拼接共约五次分配 + int i, n= N (env); + for (i= 0; i <= n; i+= 2) + if (i == n) { + tree r (ATTR, n + 2); + for (int k= 0; k < n; k++) + r[k]= env[k]; + r[n] = tree (var); + r[n + 1]= val; + return r; + } else if (var <= env[i]->label) { - if (var == env[i]->label) - return env (0, i) * tree (ATTR, var, val) * env (i + 2, N (env)); - return env (0, i) * tree (ATTR, var, val) * env (i, N (env)); + bool replace= (var == env[i]->label); + tree r (ATTR, replace ? n : n + 2); + for (int k= 0; k < i; k++) + r[k]= env[k]; + r[i] = tree (var); + r[i + 1]= val; + if (replace) { + for (int k= i + 2; k < n; k++) + r[k]= env[k]; + } + else { + // 插入:旧 [i, n) 整体右移两格 + for (int k= i; k < n; k++) + r[k + 2]= env[k]; + } + return r; } return env; } @@ -742,8 +784,12 @@ drd_info_rep::freeze_env (tree_label l, int nr) { tree drd_info_rep::get_env_child (tree t, int i, tree env) { - if (L (t) == WITH && i == N (t) - 1) - return drd_env_merge (env, t (0, N (t) - 1)); + if (L (t) == WITH && i == N (t) - 1) { + // 直接在原树上迭代绑定对,免去 t(0,N-1) 子树拷贝 + for (int k= 0; (k + 1) < N (t); k+= 2) + if (is_atomic (t[k])) env= drd_env_write (env, t[k]->label, t[k + 1]); + return env; + } else { /* makes cursor movement (is_accessible_cursor) slow for large preambles if (L(t) == DOCUMENT && N(t) > 0 && @@ -764,6 +810,9 @@ drd_info_rep::get_env_child (tree t, int i, tree env) { int index= ti->get_index (i, N (t)); if ((index < 0) || (index >= N (ti->ci))) return ""; tree cenv= drd_decode (ti->ci[index].env); + // 绝大多数标签的子节点没有环境绑定,空 cenv 直接透传, + // 免去逐对 drd_env_write 重建 env 树 + if (N (cenv) == 0) return env; for (int i= 1; i < N (cenv); i+= 2) if (is_func (cenv[i], ARG, 1) && is_int (cenv[i][0])) { cenv = copy (cenv); @@ -776,6 +825,25 @@ drd_info_rep::get_env_child (tree t, int i, tree env) { tree drd_info_rep::get_env_child (tree t, int i, string var, tree val) { + // WITH 快路径:直接扫描绑定对取末次匹配, + // 免去 t(0,N-1) 子树拷贝与 env 树逐对合并重建 + if (L (t) == WITH && i == N (t) - 1) { + tree r = val; + bool found= false; + for (int k= 0; (k + 1) < N (t); k+= 2) + if (is_atomic (t[k]) && t[k]->label == var) { + r = t[k + 1]; + found= true; + } + if (found) return r; + return val; + } + // 快路径:子节点无环境绑定时直接返回缺省值, + // 免去 ATTR 构造、合并与读取扫描(is_accessible_cursor 每步都走这里) + tag_info ti = info[L (t)]; + int index= ti->get_index (i, N (t)); + if ((index < 0) || (index >= N (ti->ci))) return val; + if (N (drd_decode (ti->ci[index].env)) == 0) return val; tree env= get_env_child (t, i, tree (ATTR)); return drd_env_read (env, var, val); } diff --git a/moebius/moebius/drd/drd_info.hpp b/moebius/moebius/drd/drd_info.hpp index 4f29748e30..ff2fc1b8a6 100644 --- a/moebius/moebius/drd/drd_info.hpp +++ b/moebius/moebius/drd/drd_info.hpp @@ -36,6 +36,7 @@ class drd_info_rep : concrete_struct { tree get_locals (); bool set_locals (tree t); bool contains (string l); + bool contains (tree_label l); /* Properties of the tag itself */ void set_type (tree_label tag, int tp); diff --git a/moebius/tests/Data/Convert/tmu_test.cpp b/moebius/tests/Data/Convert/tmu_test.cpp new file mode 100644 index 0000000000..38542c7f88 --- /dev/null +++ b/moebius/tests/Data/Convert/tmu_test.cpp @@ -0,0 +1,82 @@ +/** \file tmu_test.cpp + * \copyright GPLv3 + * \details Unit tests for TMU serialization roundtrips + * \date 2026 + */ + +#include "moe_doctests.hpp" +#include "tmu.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include +#include + +using namespace moebius; +using moebius::CONCAT; +using moebius::DOCUMENT; +using moebius::drd::init_std_drd; + +TEST_SUITE ("tmu") { + + TEST_CASE ("roundtrip plain words") { + tree doc (DOCUMENT, tree ("hello"), tree ("world")); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 2); + CHECK (back[0] == tree ("hello")); + CHECK (back[1] == tree ("world")); + } + + TEST_CASE ("roundtrip spaces inside words") { + tree doc (DOCUMENT, tree ("a b c")); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 1); + CHECK (back[0] == tree ("a b c")); + } + + TEST_CASE ("roundtrip escaped specials") { + // 反斜杠与标记字符需要转义后往返还原 + tree doc (DOCUMENT, tree ("a\\b"), tree ("xn")); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 4); + CHECK (back[0] == tree ("a\\b")); + CHECK (back[1] == tree ("xn")); + } + + TEST_CASE ("roundtrip utf8 text") { + // 多字节 utf8 字符在词元扫描中不可被单字节截断 + tree doc (DOCUMENT, tree ("caf\xC3\xA9 ok")); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 1); + CHECK (back[0] == tree ("caf\xC3\xA9 ok")); + } + + TEST_CASE ("roundtrip concat with markup") { + init_std_drd (); // 注册内置标签名,否则 as_string(RIGID) 为 "?" + tree par (CONCAT, tree ("ab"), tree (RIGID, tree ("cd")), tree ("ef")); + tree doc (DOCUMENT, par); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 1); + tree bp= back[0]; + CHECK (is_concat (bp)); + REQUIRE (N (bp) == 3); + CHECK (bp[0] == tree ("ab")); + // 未知标记经 `` 形式往返,按名字比较而非标签编号 + CHECK (is_compound (bp[1], string ("rigid"))); + CHECK (bp[1][0] == tree ("cd")); + CHECK (bp[2] == tree ("ef")); + } + + TEST_CASE ("roundtrip long word with backslashes") { + // 密集转义场景:转义后字符与后续 utf8 序列一起复制 + string s ("\\\\\\\\x"); + tree doc (DOCUMENT, tree (s)); + tree back= tmu_to_tree (tree_to_tmu (doc)); + REQUIRE (N (back) == 1); + CHECK (back[0] == tree (s)); + } + +} // TEST_SUITE diff --git a/moebius/tests/Data/History/patch_test.cpp b/moebius/tests/Data/History/patch_test.cpp index 0f4c47f927..8ad149cea2 100644 --- a/moebius/tests/Data/History/patch_test.cpp +++ b/moebius/tests/Data/History/patch_test.cpp @@ -198,3 +198,63 @@ TEST_CASE ("is_applicable for birth patch") { patch p (a, true); CHECK (is_applicable (p, t)); } + +/****************************************************************************** + * Commutation of modifications (swap semantics) + ******************************************************************************/ + +// swap 声明在 patch.hpp,验证换位后的索引调整 + +TEST_CASE ("commute inserts after range shifts back") { + // 先插 "abc"@5,再插 "xy"@9:可换位,换位后 + // m1*=insert xy@6 (回退 3),m2*=insert abc@5 (不变) + modification a= mod_insert (path (0, 3), 5, tree ("abc")); + modification b= mod_insert (path (0, 3), 9, tree ("xy")); + CHECK (commute (a, b)); + modification s1= mod_insert (path (0, 3), 5, tree ("abc")); + modification s2= mod_insert (path (0, 3), 9, tree ("xy")); + CHECK (swap (s1, s2)); + CHECK (index (s1) == 6); // xy 后移到 abc 之后 + CHECK (index (s2) == 5); // abc 保持在 5 +} + +TEST_CASE ("commute inserts before range shifts forward") { + // 先插 "abc"@5,再插 "xy"@2(在前):换位后 abc 前移 2 + modification s1= mod_insert (path (0, 3), 5, tree ("abc")); + modification s2= mod_insert (path (0, 3), 2, tree ("xy")); + CHECK (swap (s1, s2)); + CHECK (index (s1) == 2); // xy 不动 + CHECK (index (s2) == 7); // abc 前面多了 "xy",插点后移 2 +} + +TEST_CASE ("commute insert inside range fails") { + // 后插落在前插区间内部且非同点:不可换位 + modification s1= mod_insert (path (0, 3), 5, tree ("abc")); + modification s2= mod_insert (path (0, 3), 6, tree ("xy")); + CHECK (!commute (s1, s2)); +} + +TEST_CASE ("commute disjoint paths is basic swap") { + modification s1= mod_insert (path (0, 3), 5, tree ("abc")); + modification s2= mod_insert (path (1, 7), 2, tree ("xy")); + CHECK (swap (s1, s2)); + // 互换后内容对调,索引不变 + CHECK (s1->t == tree ("xy")); + CHECK (s2->t == tree ("abc")); + CHECK (index (s1) == 2); + CHECK (index (s2) == 5); +} + +TEST_CASE ("commute remove overlapping fails") { + // 后 remove 的区间覆盖前 remove 起点(非同点):不可换位 + modification s1= mod_remove (path (0, 3), 6, 1); + modification s2= mod_remove (path (0, 3), 5, 3); + CHECK (!commute (s1, s2)); +} + +TEST_CASE ("commute join adjacent fails") { + // join@i-1 与前序操作冲突 + modification s1= mod_insert (path (0, 3), 5, tree ("abc")); + modification s2= mod_join (path (0, 3), 4); + CHECK (!commute (s1, s2)); +} diff --git a/moebius/tests/Data/Tree/tree_modify_test.cpp b/moebius/tests/Data/Tree/tree_modify_test.cpp new file mode 100644 index 0000000000..b2476f9715 --- /dev/null +++ b/moebius/tests/Data/Tree/tree_modify_test.cpp @@ -0,0 +1,137 @@ +/** \file tree_modify_test.cpp + * \copyright GPLv3 + * \details Unit tests for correct_node / correct_downwards / simplify_concat + * \date 2026 + */ + +#include "moe_doctests.hpp" +#include "tree.hpp" +#include "tree_modify.hpp" +#include "tree_observer.hpp" + +#include +#include + +using namespace moebius; +using moebius::drd::init_std_drd; +using moebius::drd::the_drd; + +// 修改链引用的全局编辑树与 ip 观察者定义在 mogan 主程序侧, +// 测试中给出未挂接的独立桩实现 +tree the_et; +path +obtain_ip (tree& ref) { + (void) ref; + return path (); +} +bool +ip_attached (path ip) { + (void) ip; + return false; +} +observer +list_observer (observer o1, observer o2) { + (void) o1; + (void) o2; + return observer (); +} + +TEST_SUITE ("tree_modify") { + + TEST_CASE ("drd contains label overload") { + init_std_drd (); + CHECK (the_drd->contains (string ("document"))); + CHECK (the_drd->contains (DOCUMENT)); + CHECK (the_drd->contains (CONCAT)); + CHECK (!the_drd->contains (make_tree_label ("no-such-tag-xyz"))); + } + + TEST_CASE ("correct_node fixes bad arity") { + init_std_drd (); + // document 要求至少 1 个孩子(fixed arity 校验失败时清空) + tree t (tree (RIGID, tree ("a"), tree ("b"))); // rigid 定长 1 + correct_node (t); + CHECK (t == tree ("")); + } + + TEST_CASE ("correct_node keeps valid arity") { + init_std_drd (); + tree t (tree (RIGID, tree ("a"))); + correct_node (t); + CHECK (t == tree (RIGID, tree ("a"))); + } + + TEST_CASE ("correct_node merges adjacent atomics in concat") { + init_std_drd (); + tree t (tree (CONCAT, tree ("a"), tree ("b"), tree (""))); + correct_node (t); + // 相邻原子合并、空串并走后只剩一个原子;correct_node 不展开单元素 concat + CHECK (t == tree (CONCAT, tree ("ab"))); + } + + TEST_CASE ("correct_downwards recursion") { + init_std_drd (); + tree inner (tree (RIGID, tree ("x"), tree ("y"))); + tree doc (DOCUMENT, tree ("para"), inner); + correct_downwards (doc); + CHECK (doc[0] == tree ("para")); + CHECK (doc[1] == tree ("")); + } + + TEST_CASE ("simplify_concat merges atomics") { + tree t (tree (CONCAT, tree ("a"), tree ("b"), tree (WITH, tree ("u")), + tree ("c"))); + CHECK (simplify_concat (t) == + tree (CONCAT, tree ("ab"), tree (WITH, tree ("u")), tree ("c"))); + } + + TEST_CASE ("simplify_concat flattens nested concat") { + tree inner (tree (CONCAT, tree ("x"), tree ("y"))); + tree t (tree (CONCAT, tree ("a"), inner, tree ("z"))); + // 展平后全部原子相邻,合并为单个原子 + CHECK (simplify_concat (t) == tree ("axyz")); + } + + TEST_CASE ("simplify_concat empty result") { + tree t (tree (CONCAT, tree (""), tree (""))); + CHECK (simplify_concat (t) == tree ("")); + } + + TEST_CASE ("simplify_document flattens nested document") { + tree inner (tree (DOCUMENT, tree ("p1"), tree ("p2"))); + tree t (tree (DOCUMENT, inner, tree ("p3"))); + CHECK (simplify_document (t) == + tree (DOCUMENT, tree ("p1"), tree ("p2"), tree ("p3"))); + } + +} // TEST_SUITE + +TEST_CASE ("simplify_correct keeps plain markup") { + init_std_drd (); + tree doc (DOCUMENT); + doc << tree (RIGID, tree ("a")); + doc << tree (WITH, tree ("color"), tree ("red"), tree ("b")); + tree r= simplify_correct (doc); + CHECK (r == doc); +} + +TEST_CASE ("simplify_correct unwraps quoted atom") { + tree q (QUOTE, tree ("str")); + CHECK (simplify_correct (q) == tree ("str")); +} + +TEST_CASE ("simplify_correct merges concat and flattens document") { + tree doc (DOCUMENT); + tree inner (DOCUMENT, tree (CONCAT, tree ("x"), tree ("y"))); + doc << inner << tree ("z"); + tree r= simplify_correct (doc); + // 嵌套 document 展平,concat 中原子合并 + CHECK (r == tree (DOCUMENT, tree ("xy"), tree ("z"))); +} + +TEST_CASE ("simplify_correct empty concat collapses") { + tree doc (DOCUMENT, tree (CONCAT, tree (""), tree (""))); + tree r= simplify_correct (doc); + CHECK_EQ (N (r), 1); + CHECK (r[0] == tree ("")); +} diff --git a/moebius/tests/Data/Tree/tree_observer_test.cpp b/moebius/tests/Data/Tree/tree_observer_test.cpp new file mode 100644 index 0000000000..fded84c904 --- /dev/null +++ b/moebius/tests/Data/Tree/tree_observer_test.cpp @@ -0,0 +1,150 @@ +/** \file tree_observer_test.cpp + * \copyright GPLv3 + * \details Unit tests for raw_split / raw_join / raw_insert / raw_remove + * \date 2026 + */ + +#include "moe_doctests.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" +#include "tree_observer.hpp" + +#include +#include + +using namespace moebius; + +// 生产代码导出但未写入 hpp,测试中补声明 +void raw_split (tree& ref, int pos, int at); +void raw_join (tree& ref, int pos); +void raw_remove (tree& ref, int pos, int nr); + +// 修改链引用的全局编辑树与 ip 观察者定义在 mogan 主程序侧, +// 测试中给出未挂接的独立桩实现 +tree the_et; +path +obtain_ip (tree& ref) { + (void) ref; + return path (); +} +bool +ip_attached (path ip) { + (void) ip; + return false; +} +observer +list_observer (observer o1, observer o2) { + (void) o1; + (void) o2; + return observer (); +} + +TEST_SUITE ("tree_observer") { + + TEST_CASE ("raw_split compound keeps siblings") { + tree doc (DOCUMENT); + doc << tree ("p0") << tree ("p1") << tree ("p2") << tree ("p3"); + tree inner (CONCAT, tree ("aaa"), tree ("bbb")); + doc[2]= inner; + raw_split (doc, 2, 1); // 把 concat 的第 1 个孩子处切开 + CHECK_EQ (N (doc), 5); + CHECK (doc[0] == tree ("p0")); + CHECK (doc[1] == tree ("p1")); + CHECK_EQ (N (doc[2]), 1); + CHECK (doc[2][0] == tree ("aaa")); + CHECK_EQ (N (doc[3]), 1); + CHECK (doc[3][0] == tree ("bbb")); + CHECK (doc[4] == tree ("p3")); + } + + TEST_CASE ("raw_split at tail boundary") { + tree doc (DOCUMENT); + doc << tree ("a") << tree ("b") << tree ("c"); + tree inner (CONCAT, tree ("x"), tree ("y")); + doc[1]= inner; + raw_split (doc, 1, 2); // 在最后一个孩子边界切,搬移空块 + CHECK_EQ (N (doc), 4); + CHECK (doc[1] == tree (CONCAT, tree ("x"), tree ("y"))); + CHECK (doc[2] == tree (CONCAT)); + } + + TEST_CASE ("raw_split atomic text") { + tree doc (DOCUMENT); + doc << tree ("hello world") << tree ("tail"); + raw_split (doc, 0, 5); // 原子文本中间切开 + CHECK_EQ (N (doc), 3); + CHECK (doc[0] == tree ("hello")); + CHECK (doc[1] == tree (" world")); + CHECK (doc[2] == tree ("tail")); + } + + TEST_CASE ("raw_join compound merges children") { + tree doc (DOCUMENT); + tree c1 (CONCAT, tree ("a"), tree ("b")); + tree c2 (CONCAT, tree ("c")); + doc << c1 << c2 << tree ("z"); + raw_join (doc, 0); + CHECK_EQ (N (doc), 2); + CHECK (doc[0] == tree (CONCAT, tree ("a"), tree ("b"), tree ("c"))); + CHECK (doc[1] == tree ("z")); + } + + TEST_CASE ("raw_join at tail boundary") { + tree doc (DOCUMENT); + tree c1 (CONCAT, tree ("a")); + tree c2 (CONCAT, tree ("b")); + doc << tree ("x") << c1 << c2; + raw_join (doc, 1); // 尾部合并,搬移空块 + CHECK_EQ (N (doc), 2); + CHECK (doc[0] == tree ("x")); + CHECK (doc[1] == tree (CONCAT, tree ("a"), tree ("b"))); + } + + TEST_CASE ("raw_join atomic texts") { + tree doc (DOCUMENT); + doc << tree ("foo") << tree ("bar") << tree ("end"); + raw_join (doc, 0); // 两个原子直接合并字符串 + CHECK_EQ (N (doc), 2); + CHECK (doc[0] == tree ("foobar")); + CHECK (doc[1] == tree ("end")); + } + + TEST_CASE ("raw_join compounds keep children") { + tree doc (DOCUMENT); + tree c1 (CONCAT, tree ("foo")); + tree c2 (CONCAT, tree ("bar")); + doc << c1 << c2 << tree ("end"); + raw_join (doc, 0); // 复合节点 join 时合并孩子序列 + CHECK_EQ (N (doc), 2); + CHECK_EQ (N (doc[0]), 2); + CHECK (doc[0][0] == tree ("foo")); + CHECK (doc[0][1] == tree ("bar")); + CHECK (doc[1] == tree ("end")); + } + + TEST_CASE ("split join roundtrip") { + tree doc (DOCUMENT); + doc << tree ("p0") << tree ("p1") << tree ("p2"); + tree keep= copy (doc); + tree c (CONCAT, tree ("alpha"), tree ("beta")); + doc[1] = c; + keep[1]= tree (CONCAT, tree ("alpha"), tree ("beta")); + raw_split (doc, 1, 1); + raw_join (doc, 1); + CHECK (doc == keep); + } + + TEST_CASE ("raw_insert then remove roundtrip") { + tree doc (DOCUMENT); + doc << tree ("p0") << tree ("p2"); + // raw_insert 的复合分支取 t 的孩子逐个插入,单孩子需包装一层 + raw_insert (doc, 1, tree (DOCUMENT, tree ("p1"))); + CHECK_EQ (N (doc), 3); + CHECK (doc[1] == tree ("p1")); + raw_remove (doc, 1, 1); + CHECK_EQ (N (doc), 2); + CHECK (doc[0] == tree ("p0")); + CHECK (doc[1] == tree ("p2")); + } + +} // TEST_SUITE diff --git a/moebius/tests/Data/Tree/tree_traverse_test.cpp b/moebius/tests/Data/Tree/tree_traverse_test.cpp new file mode 100644 index 0000000000..7d98ec6c7e --- /dev/null +++ b/moebius/tests/Data/Tree/tree_traverse_test.cpp @@ -0,0 +1,257 @@ +/** \file tree_traverse_test.cpp + * \copyright GPLv3 + * \details Unit tests for tree_utf8_to_herk / tree_herk_to_utf8 + * \date 2026 + */ + +#include "moe_doctests.hpp" +#include "tree.hpp" +#include "tree_traverse.hpp" + +#include + +using namespace moebius; + +static string +label_of (tree_u8 t) { + return t->label; +} + +TEST_SUITE ("tree_traverse") { + + TEST_CASE ("utf8_to_herk plain ascii is identity") { + tree_u8 t ("Hello, world! [42] #{foo}"); + CHECK (label_of (tree_utf8_to_herk (t)) == "Hello, world! [42] #{foo}"); + } + + TEST_CASE ("utf8_to_herk escapes backtick") { + // 反引号 0x60 是 herk 恒等例外,快路径必须放行给慢路径; + // 其映射目标 herk 字节 0 经 herk_to_utf8 还原回反引号,用往返验证 + tree_u8 t ("a`b"); + CHECK (label_of (tree_herk_to_utf8 (tree_utf8_to_herk (t))) == "a`b"); + } + + TEST_CASE ("utf8_to_herk converts non-ascii") { + // U+00E9 (é) 的 utf8 字节为 C3 A9,herk 中直接映射为单个高位字节 0xE9 + tree_u8 t ("\xC3\xA9"); + string r= label_of (tree_utf8_to_herk (t)); + CHECK (r == string ("\xE9", 1)); + } + + TEST_CASE ("utf8_to_herk roundtrip compound") { + tree doc (DOCUMENT); + doc << tree ("plain"); + doc << tree ("\xC3\xA9"); // é + doc << tree ("more"); + tree h= tree_utf8_to_herk (doc); + REQUIRE (N (h) == 3); + CHECK (label_of (h[0]) == "plain"); + CHECK (label_of (h[1]) == string ("\xE9", 1)); + tree_u8 back= tree_herk_to_utf8 (h); + REQUIRE (N (back) == 3); + CHECK (label_of (back[0]) == "plain"); + CHECK (label_of (back[1]) == "\xC3\xA9"); + } + + TEST_CASE ("herk_to_utf8 keeps literal ascii") { + tree t ("abc XYZ 123"); + CHECK (label_of (tree_herk_to_utf8 (t)) == "abc XYZ 123"); + } + + TEST_CASE ("herk_to_utf8 expands hex escape") { + tree t ("a<#41>b"); + CHECK (label_of (tree_herk_to_utf8 (t)) == "aAb"); + } + + TEST_CASE ("herk_to_utf8 keeps lone lt sign") { + // '<' 不后跟 '#' 时不构成转义,恒等快路径应正确放行 + tree t ("a"); + CHECK (label_of (tree_herk_to_utf8 (t2)) == "a U+00E9 -> utf8 C3 A9 + tree t (string ("\xE9", 1)); + CHECK (label_of (tree_herk_to_utf8 (t)) == "\xC3\xA9"); + } + + TEST_CASE ("conversion preserves raw_data") { + tree rd (RAW_DATA, tree ("payload")); + tree h= tree_utf8_to_herk (rd); + CHECK (is_func (h, RAW_DATA)); + CHECK (N (h) == 1); + CHECK (h[0] == tree ("payload")); + } + +} // TEST_SUITE + +// 光标移动测试需要 drd 与 tree_cursor 声明 +#include "tree_cursor.hpp" +#include + +using moebius::drd::init_std_drd; + +// 生产代码导出但未写入 hpp,测试中补声明 +path next_any (tree t, path p); +path previous_any (tree t, path p); + +static tree +mk_cursor_doc () { + tree doc (DOCUMENT); + for (int i= 0; i < 10; i++) { + tree par (CONCAT); + par << tree ("ab") << tree ("cd"); + doc << par; + } + return doc; +} + +TEST_CASE ("next_any sweep reaches end and terminates") { + init_std_drd (); + tree doc = mk_cursor_doc (); + path p = start (doc); + int steps= 0; + while (steps < 1000) { + path r= next_any (doc, p); + if (r == p) break; + p= r; + steps++; + } + CHECK (steps < 1000); // 必须收敛 + CHECK (steps > 10); // 确实逐字符推进了 + CHECK (next_any (doc, p) == p); // 停在不动点 +} + +TEST_CASE ("next_any then previous_any roundtrip") { + init_std_drd (); + tree doc= mk_cursor_doc (); + path p = start (doc); + path mid= p; + for (int i= 0; i < 20; i++) + mid= next_any (doc, mid); + // 从中途倒退相同步数,应回到起点 + path back= mid; + for (int i= 0; i < 20; i++) + back= previous_any (doc, back); + CHECK (back == p); +} + +TEST_CASE ("next_any steps into first paragraph") { + init_std_drd (); + tree doc= mk_cursor_doc (); + path p = start (doc); + path r1 = next_any (doc, p); + CHECK (!is_nil (r1)); + CHECK (r1 != p); // 第一步必有推进 + CHECK (!is_nil (r1->next)); // 深入文档内部而非停在顶层 +} + +TEST_CASE ("next_word skips whole words") { + init_std_drd (); + tree doc (DOCUMENT); + tree par (CONCAT); + par << tree ("hello world") << tree ("x"); + doc << par; + path p= start (doc); + path q= next_word (doc, p); + CHECK (q != p); + // 再前进若干步应收敛到文档尾 + int steps= 0; + while (steps < 100) { + path r= next_word (doc, q); + if (r == q) break; + q= r; + steps++; + } + CHECK (steps < 100); + CHECK (next_word (doc, q) == q); +} + +TEST_CASE ("next_word previous_word roundtrip") { + init_std_drd (); + tree doc (DOCUMENT); + tree par (CONCAT); + par << tree ("alpha beta") << tree ("gamma delta"); + doc << par; + path p = start (doc); + path mid= p; + for (int i= 0; i < 6; i++) + mid= next_word (doc, mid); + path back= mid; + for (int i= 0; i < 6; i++) + back= previous_word (doc, back); + CHECK (back == p); +} + +TEST_CASE ("word boundary respects punctuation and hex escapes") { + init_std_drd (); + // <#4E2D> 形式的非 ASCII 字符按词分隔符块处理,不崩溃即可 + tree doc (DOCUMENT); + tree par (CONCAT); + par << tree ("a <#4E2D> b"); + doc << par; + path p = start (doc); + int steps= 0; + while (steps < 100) { + path r= next_word (doc, p); + if (r == p) break; + p= r; + steps++; + } + CHECK (steps < 100); +} + +TEST_CASE ("correct_cursor drops negative prefix path") { + init_std_drd (); + tree doc (DOCUMENT); + tree par (CONCAT); + par << tree ("ab") << tree ("cd"); + doc << par; + // 含负索引的路径被 keep_positive 截断后再校正 + path bad = path (-1, path (0, path (0))); + path good= correct_cursor (doc, bad, true); + CHECK (!is_nil (good)); + // 校正结果应是文档内的合法光标(首元素非负) + CHECK (good->item >= 0); +} + +TEST_CASE ("correct_cursor keeps valid path stable") { + init_std_drd (); + tree doc (DOCUMENT); + tree par (CONCAT); + par << tree ("ab") << tree ("cd"); + doc << par; + path p = start (doc); + path fixed= correct_cursor (doc, p, true); + CHECK (fixed == p); // 起点已是合法前向光标 +} + +TEST_CASE ("inside_contiguous_document same paragraph") { + init_std_drd (); + tree inner (CONCAT, tree ("aaa"), tree ("bbb")); + for (int i= 0; i < 6; i++) + inner= tree (WITH, tree ("v"), tree ("1"), inner); + tree doc (DOCUMENT, inner); + // doc 0 号 → 6 层 WITH 沿 2 号孩子 → concat 0/1 号原子内位置 1 + path pa= path (0, path (1)); + path pb= path (1, path (1)); + for (int i= 0; i < 6; i++) { + pa= path (2, pa); + pb= path (2, pb); + } + pa= path (0, pa); + pb= path (0, pb); + CHECK (inside_same (doc, pa, pb, DOCUMENT)); + CHECK (inside_contiguous_document (doc, pa, pb)); +} + +TEST_CASE ("inside_contiguous_document cross paragraph") { + init_std_drd (); + tree doc (DOCUMENT, tree (CONCAT, tree ("aaa")), tree (CONCAT, tree ("bbb"))); + // 两个不同段落里的光标:inside_same 为假 + path pa= path (0, path (0, path (1))); + path pb= path (1, path (0, path (1))); + CHECK (!inside_contiguous_document (doc, pa, pb)); +} diff --git a/moebius/tests/Kernel/Types/curve_test.cpp b/moebius/tests/Kernel/Types/curve_test.cpp index 8763ebe61b..0cfbf8360f 100644 --- a/moebius/tests/Kernel/Types/curve_test.cpp +++ b/moebius/tests/Kernel/Types/curve_test.cpp @@ -77,3 +77,263 @@ TEST_CASE ("bound 契约: |t'-t|<=delta 时 |c(t')-c(t)|<=eps") { } } } + +TEST_CASE ("segment evaluate 端点与中点") { + curve c = segment (mkp (0, 0), mkp (3, 4)); + point e0= c->evaluate (0.0); + point e1= c->evaluate (1.0); + point em= c->evaluate (0.5); + CHECK (e0 == mkp (0, 0)); + CHECK (e1 == mkp (3, 4)); + CHECK (em == mkp (1.5, 2.0)); + // 三维点插值维度保持 + point q0 (3), q1 (3); + q0[0] = 0; + q0[1] = 0; + q0[2] = 0; + q1[0] = 3; + q1[1] = 4; + q1[2] = 5; + curve c3= segment (q0, q1); + point m3= c3->evaluate (0.5); + CHECK_EQ (N (m3), 3); + CHECK (fabs (m3[0] - 1.5) < 1e-9); + CHECK (fabs (m3[1] - 2.0) < 1e-9); + CHECK (fabs (m3[2] - 2.5) < 1e-9); +} + +TEST_CASE ("poly_segment evaluate 分段边界") { + array a; + a << mkp (0, 0) << mkp (10, 0) << mkp (10, 100); + curve c= poly_segment (a, array ()); + // n=2,每段占 t 的一半 + CHECK (c->evaluate (0.0) == mkp (0, 0)); + CHECK (c->evaluate (0.25) == mkp (5, 0)); + CHECK (c->evaluate (0.5) == mkp (10, 0)); + CHECK (c->evaluate (0.75) == mkp (10, 50)); + CHECK (c->evaluate (1.0) == mkp (10, 100)); +} + +TEST_CASE ("poly_segment grad 方向与倍率") { + array a; + a << mkp (0, 0) << mkp (10, 0) << mkp (10, 100); + curve c = poly_segment (a, array ()); + bool err= true; + point g = c->grad (0.5, err); + CHECK (!err); + // n=2,第二段方向 (0,100),grad = 2*(0,100) + CHECK (g == mkp (0, 200)); +} + +TEST_CASE ("spline evaluate 端点与缓存一致性") { + array a; + a << mkp (0, 0) << mkp (1, 3) << mkp (3, 2) << mkp (5, 5) << mkp (7, 1); + curve c= spline (a, array (), false, true); + // 端点插值:开样条经过首末控制点 + CHECK (c->evaluate (0.0) == mkp (0, 0)); + CHECK (c->evaluate (1.0) == mkp (7, 1)); + // 同一 t 反复求值(interval_no 缓存命中路径)结果一致 + point p1= c->evaluate (0.37); + point p2= c->evaluate (0.37); + point p3= c->evaluate (0.37); + CHECK (p1 == p2); + CHECK (p1 == p3); + // t 跳跃后回到原区间,结果仍一致(缓存失效重扫路径) + point p4= c->evaluate (0.9); + point p5= c->evaluate (0.37); + CHECK (p1 == p5); + CHECK (!(p1 == p4)); +} + +TEST_CASE ("spline rectify 首末点") { + array a; + a << mkp (0, 0) << mkp (1, 3) << mkp (3, 2) << mkp (5, 5) << mkp (7, 1); + curve c = spline (a, array (), false, true); + array ps= c->rectify (0.05); + CHECK (N (ps) >= 2); + CHECK (ps[0] == mkp (0, 0)); + CHECK (ps[N (ps) - 1] == mkp (7, 1)); +} + +TEST_CASE ("spline grad 与 bound 契约") { + array a; + a << mkp (0, 0) << mkp (1, 3) << mkp (3, 2) << mkp (5, 5) << mkp (7, 1); + curve c = spline (a, array (), false, true); + bool err= true; + point g = c->grad (0.5, err); + CHECK (!err); + CHECK_EQ (N (g), 2); + double eps = 0.5; + double delta= c->bound (0.5, eps); + point v1 = c->evaluate (0.5); + point v2 = c->evaluate (max (0.5 - delta, 0.0)); + CHECK (norm2_diff (v2, v1) <= (eps + 1e-6) * (eps + 1e-6)); +} + +TEST_CASE ("find_closest_point on segment") { + curve c = segment (mkp (0, 0), mkp (10, 0)); + bool err= true; + // 查询点取在曲线上,内点即为精确最近点 + double t= c->find_closest_point (0.0, 1.0, mkp (4, 0), 0.01, err); + CHECK (err); + point q= c->evaluate (t); + CHECK (fabs (q[0] - 4.0) < 0.1); + CHECK (fabs (q[1]) < 1e-9); +} + +TEST_CASE ("find_closest_point on poly_segment") { + array a; + a << mkp (0, 0) << mkp (10, 0) << mkp (10, 100); + curve c = poly_segment (a, array ()); + bool err= true; + // 距离第二段更近的查询点 + double t= c->find_closest_point (0.0, 1.0, mkp (9, 60), 0.01, err); + CHECK (err); + point q= c->evaluate (t); + CHECK (fabs (q[0] - 10.0) < 0.5); + CHECK (fabs (q[1] - 60.0) < 1.0); +} + +TEST_CASE ("closest returns near-minimum distance") { + array a; + a << mkp (0, 0) << mkp (10, 0) << mkp (10, 100); + curve c= poly_segment (a, array ()); + // 查询点取在曲线上,最近距离应为 0 + point q= closest (c, mkp (5, 0)); + double d= sqrt (norm2_diff (q, mkp (5, 0))); + CHECK (d < 0.1); +} + +TEST_CASE ("intersection of crossing segments") { + // (curve,curve,double&,double&) 未导出到 hpp,补声明 + bool intersection (curve f, curve g, double& t, double& u); + curve f= segment (mkp (0, 0), mkp (10, 10)); + curve g= segment (mkp (0, 10), mkp (10, 0)); + double t= 0.2, u= 0.2; + bool ok= intersection (f, g, t, u); + CHECK (ok); + point pf= f->evaluate (t); + CHECK (fabs (pf[0] - 5.0) < 1e-6); + CHECK (fabs (pf[1] - 5.0) < 1e-6); +} + +TEST_CASE ("ellipse evaluate lies on the ellipse") { + // 两焦点 (-4,0),(4,0) 与椭圆上一点 (0,3):r1=5, r2=3 + array a; + a << mkp (-4, 0) << mkp (4, 0) << mkp (0, 3); + curve c= ellipse (a, array (), true); + // i 轴从圆心指向第一焦点 (-4,0):t=0 是长轴端点 (-5,0), + // t=1/4 是短轴端点 (0,±3) + CHECK (c->evaluate (0.0) == mkp (-5, 0)); + point qe= c->evaluate (0.25); + CHECK (fabs (qe[0]) < 1e-9); + CHECK (fabs (fabs (qe[1]) - 3.0) < 1e-9); + // 到两焦点距离之和恒为 2*r1=10 + for (int i= 0; i <= 20; i++) { + point q= c->evaluate (i / 20.0); + double d= + sqrt (norm2_diff (q, mkp (-4, 0))) + sqrt (norm2_diff (q, mkp (4, 0))); + CHECK (fabs (d - 10.0) < 1e-9); + } +} + +TEST_CASE ("ellipse grad is orthogonal to evaluate") { + array a; + a << mkp (-4, 0) << mkp (4, 0) << mkp (0, 3); + curve c = ellipse (a, array (), true); + bool err= true; + point g = c->grad (0.3, err); + CHECK (!err); + CHECK_EQ (N (g), 2); + // t=0 处切向沿 y 轴 + point g0= c->grad (0.0, err); + CHECK (fabs (g0[0]) < 1e-9); + CHECK (g0[1] > 0); +} + +TEST_CASE ("arc evaluate lies on its circle") { + // 过 (0,0),(10,0),(0,10) 三点,圆心 (5,5),半径 sqrt(50) + array a; + a << mkp (0, 0) << mkp (10, 0) << mkp (0, 10); + curve c = arc (a, array (), true); + double r2= 50.0; + for (int i= 0; i <= 20; i++) { + point q= c->evaluate (i / 20.0); + double d= norm2_diff (q, mkp (5, 5)); + CHECK (fabs (d - r2) < 1e-6); + } + // 端点经过首控制点 + CHECK (c->evaluate (0.0) == mkp (0, 0)); +} + +TEST_CASE ("ellipse rectify endpoints") { + array a; + a << mkp (-4, 0) << mkp (4, 0) << mkp (0, 3); + curve c = ellipse (a, array (), true); + array ps= c->rectify (0.05); + CHECK (N (ps) >= 2); + CHECK (ps[0] == mkp (-5, 0)); + CHECK (ps[N (ps) - 1] == mkp (-5, 0)); // 闭合:首尾同为起点 +} + +TEST_CASE ("bezier evaluate endpoints and midpoint") { + array a; + a << mkp (0, 0) << mkp (1, 4) << mkp (4, 4) << mkp (6, 0); + curve c= bezier (a); + CHECK (c->evaluate (0.0) == mkp (0, 0)); + CHECK (c->evaluate (1.0) == mkp (6, 0)); + // 对称控制点,中点 x=(0+3+12+6)/8=2.625,y=(0+12+12+0)/8=3 + point m= c->evaluate (0.5); + CHECK (fabs (m[0] - 2.625) < 1e-9); + CHECK (fabs (m[1] - 3.0) < 1e-9); +} + +TEST_CASE ("bezier grad at endpoints") { + array a; + a << mkp (0, 0) << mkp (1, 4) << mkp (4, 4) << mkp (6, 0); + curve c = bezier (a); + bool err= true; + // 生产 grad 公式为 3*P3*t + 2*P2 + P1(非标准导数), + // t=0 处即 2*P2+P1 = 2*(6,-12)+(3,12) = (15,-12) + point g= c->grad (0.0, err); + CHECK (!err); + CHECK (g == mkp (15, -12)); +} + +TEST_CASE ("bezier rectify endpoints") { + array a; + a << mkp (0, 0) << mkp (1, 4) << mkp (4, 4) << mkp (6, 0); + curve c = bezier (a); + array ps= c->rectify (0.05); + CHECK (N (ps) >= 2); + CHECK (ps[0] == mkp (0, 0)); + CHECK (ps[N (ps) - 1] == mkp (6, 0)); +} + +TEST_CASE ("hyperbola evaluate keeps distance difference") { + // 双曲线定义:到两焦点距离之差的绝对值恒定 + array a; + a << mkp (-4, 0) << mkp (4, 0) << mkp (8, 3); + curve c = hyperbola (a, array (), false); + double ref= -1; + for (int i= 0; i <= 40; i++) { + point q = c->evaluate (i / 40.0); + double dd= fabs (sqrt (norm2_diff (q, mkp (-4, 0))) - + sqrt (norm2_diff (q, mkp (4, 0)))); + if (ref < 0) ref= dd; + CHECK (fabs (dd - ref) < 1e-9); + } +} + +TEST_CASE ("parabola evaluate vertex at midpoint") { + // d1=(-4,0) d2=(4,0) f=(0,3):ortho(j,i) 后 i=(0,1), + // d=inner(f-d1,i)=3,vertex=f-(d/2)*i=(0,1.5) + array a; + a << mkp (-4, 0) << mkp (4, 0) << mkp (0, 3); + curve c= parabola (a, array (), false); + point v= c->evaluate (0.5); + CHECK (v == mkp (0, 1.5)); + // 关于对称轴对称:0.5±x 两点 y 相同 + point l= c->evaluate (0.4), r= c->evaluate (0.6); + CHECK (fabs (l[1] - r[1]) < 1e-9); +} diff --git a/moebius/tests/Kernel/Types/equations_test.cpp b/moebius/tests/Kernel/Types/equations_test.cpp new file mode 100644 index 0000000000..eee9150143 --- /dev/null +++ b/moebius/tests/Kernel/Types/equations_test.cpp @@ -0,0 +1,127 @@ +/** \file equations_test.cpp + * \copyright GPLv3 + * \details Unit tests for tridiagonal system solvers + * \date 2026 + */ + +#include "equations.hpp" +#include "moe_doctests.hpp" +#include "point.hpp" + +static bool +close_to (double x, double y) { + return fabs (x - y) < 1e-9; +} + +static bool +pt_close (point p, double x, double y) { + return close_to (p[0], x) && close_to (p[1], y); +} + +TEST_SUITE ("equations") { + + TEST_CASE ("tridiag_solve 3x3 2D") { + // [2 1 0][x0] [3 5] 解:x0=(1.5,2) 由回代唯一确定 + // [1 2 1][x1] = [6 9] + // [0 1 2][x2] [5 8] + array a (3), b (3), c (3); + a[0]= 0; + a[1]= 1; + a[2]= 1; + b[0]= 2; + b[1]= 2; + b[2]= 2; + c[0]= 1; + c[1]= 1; + c[2]= 0; + array y; + y << point (3, 5) << point (6, 9) << point (5, 8); + array x (3); + tridiag_solve (a, b, c, x, y, 3); + // 验证 A*x = y + for (int i= 0; i < 3; i++) { + double rx= a[i] * x[max (i - 1, 0)][0] + b[i] * x[i][0] + + c[i] * x[min (i + 1, 2)][0]; + double ry= a[i] * x[max (i - 1, 0)][1] + b[i] * x[i][1] + + c[i] * x[min (i + 1, 2)][1]; + if (i == 0) { + rx= b[0] * x[0][0] + c[0] * x[1][0]; + ry= b[0] * x[0][1] + c[0] * x[1][1]; + } + if (i == 2) { + rx= a[2] * x[1][0] + b[2] * x[2][0]; + ry= a[2] * x[1][1] + b[2] * x[2][1]; + } + CHECK (close_to (rx, y[i][0])); + CHECK (close_to (ry, y[i][1])); + } + } + + TEST_CASE ("tridiag_solve uniform dims kept") { + array a (2), b (2), c (2); + a[0]= 0; + a[1]= 1; + b[0]= 1; + b[1]= 1; + c[0]= 0; + c[1]= 0; + array y; + y << point (1, 2) << point (3, 4); + array x (2); + tridiag_solve (a, b, c, x, y, 2); + CHECK_EQ (N (x[0]), 2); + CHECK_EQ (N (x[1]), 2); + // 对角系统:x0=y0, x1=y1-x0 + CHECK (pt_close (x[0], 1, 2)); + CHECK (pt_close (x[1], 2, 2)); + } + + TEST_CASE ("quasitridiag_solve zero coupling is identity") { + // a0=a1=0 时秩一修正项为零,单位对角系统的解就是 y 本身 + int n= 4; + array a (n), b (n), c (n); + for (int i= 0; i < n; i++) { + a[i]= 0; + b[i]= 1; + c[i]= 0; + } + array y; + for (int i= 0; i < n; i++) + y << point (1.0 * i, 2.0 * i); + array x (n); + xtridiag_solve (a, b, c, 0.0, 0.0, x, y, n); + for (int i= 0; i < n; i++) { + CHECK (close_to (x[i][0], y[i][0])); + CHECK (close_to (x[i][1], y[i][1])); + } + } + + TEST_CASE ("quasitridiag_solve rank-1 correction") { + // 直接调 quasitridiag_solve:A = I + u v^T,u=v=ones + // Sherman-Morrison:x = y - ones (ones^T y)/(1+n) + int n= 4; + array a (n), b (n), c (n), u (n), v (n); + for (int i= 0; i < n; i++) { + a[i]= 0; + b[i]= 1; + c[i]= 0; + u[i]= 1; + v[i]= 1; + } + array y; + for (int i= 0; i < n; i++) + y << point (1.0 * i, 2.0 * i); + array x (n); + quasitridiag_solve (a, b, c, u, v, x, y, n); + double sx= 0, sy= 0; + for (int i= 0; i < n; i++) { + sx+= y[i][0]; + sy+= y[i][1]; + } + for (int i= 0; i < n; i++) { + CHECK (close_to (x[i][0], y[i][0] - sx / (1 + n))); + CHECK (close_to (x[i][1], y[i][1] - sy / (1 + n))); + } + } + +} // TEST_SUITE diff --git a/moebius/tests/Kernel/Types/frame_test.cpp b/moebius/tests/Kernel/Types/frame_test.cpp index 39814860a4..82a4f5ac25 100644 --- a/moebius/tests/Kernel/Types/frame_test.cpp +++ b/moebius/tests/Kernel/Types/frame_test.cpp @@ -124,5 +124,51 @@ TEST_CASE ("test bounds") { CHECK (fabs (f->inverse_bound (mkp (0.0, 0.0), 2.0) - 4.0) < 1e-9); } +TEST_CASE ("test scaling 3 components") { + // 逐分量直写路径需对任意维度成立 + frame f= scaling (2.0, point (1.0, 2.0, 3.0)); + point r= f (point (1.0, 1.0, 1.0)); + CHECK_EQ (N (r), 3); + CHECK_EQ (r[0], 3.0); + CHECK_EQ (r[1], 4.0); + CHECK_EQ (r[2], 5.0); + point b= f[r]; + CHECK (fabs (b[0] - 1.0) < 1e-9); + CHECK (fabs (b[1] - 1.0) < 1e-9); + CHECK (fabs (b[2] - 1.0) < 1e-9); +} + +TEST_CASE ("test an_scaling per-axis inverse") { + frame f= scaling (mkp (2.0, 4.0), mkp (0.0, 0.0)); + point r= f (mkp (3.0, 3.0)); + CHECK_EQ (r[0], 6.0); + CHECK_EQ (r[1], 12.0); + point b= f[r]; + CHECK (fabs (b[0] - 3.0) < 1e-9); + CHECK (fabs (b[1] - 3.0) < 1e-9); +} + TEST_MEMORY_LEAK_INIT TEST_MEMORY_LEAK_ALL + +TEST_CASE ("enclose rect matches corners for linear frame") { + // 线性框架包围盒应恰为四角变换后的极值 + frame f= scaling (2.0, mkp (1.0, 1.0)); + rectangle r (0, 0, 10, 10); + rectangle e= f (r); + // 四角 (0,0),(10,0),(10,10),(0,10) 变换为 (1,1),(21,1),(21,21),(1,21) + CHECK_EQ (e->x1, 1); + CHECK_EQ (e->y1, 1); + CHECK_EQ (e->x2, 21); + CHECK_EQ (e->y2, 21); +} + +TEST_CASE ("enclose rect inverse direction") { + frame f= scaling (2.0, mkp (0.0, 0.0)); + rectangle r (2, 2, 4, 4); + rectangle e= f[r]; // 逆向:除以 2 + CHECK_EQ (e->x1, 1); + CHECK_EQ (e->y1, 1); + CHECK_EQ (e->x2, 2); + CHECK_EQ (e->y2, 2); +} diff --git a/moebius/tests/Kernel/Types/modification_test.cpp b/moebius/tests/Kernel/Types/modification_test.cpp index 57ae5dc1f4..ced9677269 100644 --- a/moebius/tests/Kernel/Types/modification_test.cpp +++ b/moebius/tests/Kernel/Types/modification_test.cpp @@ -1,284 +1,333 @@ -#include "modification.hpp" -#include "moe_doctests.hpp" -#include "tree.hpp" - -TEST_CASE ("test construct func") { - modification m1 (MOD_ASSIGN, path (), tree ()); - modification m2 (MOD_ASSIGN, path (), tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_ASSIGN, true); - CHECK_EQ (m1->p == path (), true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_ASSIGN, true); - CHECK_EQ (m2->p == path (), true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_assign") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (), tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_ASSIGN, true); - CHECK_EQ (m1->p == path (), true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_ASSIGN, true); - CHECK_EQ (m2->p == path (), true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_insert") { - modification m1= mod_insert (path (), 0, tree ()); - modification m2= mod_insert (path (), 0, tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_INSERT, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_INSERT, true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_remove") { - modification m1= mod_remove (path (), 1, 2); - modification m2= mod_remove (path (), 1, 2); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_REMOVE, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->p[1] == 2, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_REMOVE, true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_split") { - modification m1= mod_split (path (), 1, 2); - modification m2= mod_split (path (), 1, 2); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_SPLIT, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->p[1] == 2, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_SPLIT, true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_join") { - modification m1= mod_join (path (), 1); - modification m2= mod_join (path (), 1); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_JOIN, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_JOIN, true); - CHECK_EQ (m2->t == tree (), true); - modification m3= mod_join (path (1), 2); - CHECK_EQ (m3->k == MOD_JOIN, true); - CHECK_EQ (m3->p[0] == 1, true); - CHECK_EQ (m3->p[1] == 2, true); -} - -TEST_CASE ("test mod_assign_node") { - modification m1= mod_assign_node (path (), 1); - modification m2= mod_assign_node (path (), 1); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_ASSIGN_NODE, true); - CHECK_EQ (m1->p == path (), true); - CHECK_EQ (m1->t == tree (1), true); - CHECK_EQ (m2->k == MOD_ASSIGN_NODE, true); - CHECK_EQ (m2->p == path (), true); - CHECK_EQ (m2->t == tree (1), true); -} - -TEST_CASE ("test mod_insert_node") { - modification m1= mod_insert_node (path (), 1, tree ()); - modification m2= mod_insert_node (path (), 1, tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_INSERT_NODE, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_INSERT_NODE, true); - CHECK_EQ (m2->p[0] == 1, true); - CHECK_EQ (m2->t == tree (), true); - modification m3= mod_insert_node (path (), 2, tree ("string")); - CHECK_EQ (m3->k == MOD_INSERT_NODE, true); - CHECK_EQ (m3->p[0] == 2, true); - CHECK_EQ (m3->t == tree ("string"), true); -} - -TEST_CASE ("test mod_remove_node") { - modification m1= mod_remove_node (path (), 1); - modification m2= mod_remove_node (path (), 1); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_REMOVE_NODE, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_REMOVE_NODE, true); - CHECK_EQ (m2->p[0] == 1, true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test mod_set_cursor") { - modification m1= mod_set_cursor (path (), 1, tree ()); - modification m2= mod_set_cursor (path (), 1, tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_SET_CURSOR, true); - CHECK_EQ (m1->p[0] == 1, true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_SET_CURSOR, true); - CHECK_EQ (m2->p[0] == 1, true); - CHECK_EQ (m2->t == tree (), true); - modification m3= mod_set_cursor (path (), 2, tree ("string")); - CHECK_EQ (m3->k == MOD_SET_CURSOR, true); - CHECK_EQ (m3->p[0] == 2, true); - CHECK_EQ (m3->t == tree ("string"), true); -} - -TEST_CASE ("test operator*/") { - modification m1= mod_assign (path (), tree ()); - modification m2= 1 * m1; - modification m3= path (1) * m1; - modification m4= m1 * 1; - modification m5= mod_assign (path (1, 2, 3), tree ()) / path (1); - CHECK_EQ (m2->k == MOD_ASSIGN, true); - CHECK_EQ (m2->p[0] == 1, true); - CHECK_EQ (m2->t == tree (), true); - CHECK_EQ (m3->k == MOD_ASSIGN, true); - CHECK_EQ (m3->p[0] == 1, true); - CHECK_EQ (m3->t == tree (), true); - CHECK_EQ (m4->k == MOD_ASSIGN, true); - CHECK_EQ (m4->p[0] == 1, true); - CHECK_EQ (m4->t == tree (), true); - CHECK_EQ (m5->k == MOD_ASSIGN, true); - CHECK_EQ (m5->p[0] == 2, true); - CHECK_EQ (m5->t == tree (), true); -} - -TEST_CASE ("test operator==") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (), tree ()); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - modification m3= mod_assign (path (), tree ("string")); - CHECK_EQ (m1 == m3, false); - CHECK_EQ (m1 != m3, true); -} - -TEST_CASE ("test copy") { - modification m1= mod_assign (path (), tree ()); - modification m2= copy (m1); - CHECK_EQ (m1 == m2, true); - CHECK_EQ (m1 != m2, false); - CHECK_EQ (m1->k == MOD_ASSIGN, true); - CHECK_EQ (m1->p == path (), true); - CHECK_EQ (m1->t == tree (), true); - CHECK_EQ (m2->k == MOD_ASSIGN, true); - CHECK_EQ (m2->p == path (), true); - CHECK_EQ (m2->t == tree (), true); -} - -TEST_CASE ("test root") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (1), tree ()); - modification m3= mod_assign (path (1, 2), tree ()); - CHECK_EQ (root (m1) == path (), true); - CHECK_EQ (root (m2) == path (1), true); - CHECK_EQ (root (m3) == path (1, 2), true); -} - -TEST_CASE ("test index") { - modification m1= mod_insert (path (1, 2), 3, tree ()); - modification m2= mod_remove (path (), 3, 4); - modification m3= mod_split (path (), 3, 4); - modification m4= mod_join (path (), 3); - modification m5= mod_remove_node (path (), 3); - modification m6= mod_set_cursor (path (), 3, tree ()); - CHECK_EQ (index (m1) == 3, true); - CHECK_EQ (index (m2) == 3, true); - CHECK_EQ (index (m3) == 3, true); - CHECK_EQ (index (m4) == 3, true); - CHECK_EQ (index (m5) == 3, true); - CHECK_EQ (index (m6) == 3, true); -} - -TEST_CASE ("test argument") { - modification m1= mod_remove (path (), 3, 4); - modification m2= mod_split (path (), 3, 4); - modification m3= mod_insert_node (path (), 3, tree ()); - CHECK_EQ (argument (m1) == 4, true); - CHECK_EQ (argument (m2) == 4, true); - CHECK_EQ (argument (m3) == 3, true); -} - -TEST_CASE ("test make_modification") { - make_modification ("assign", path (), tree ()); - make_modification ("insert", path (), tree ()); - make_modification ("remove", path (), tree ()); - make_modification ("split", path (), tree ()); - make_modification ("join", path (), tree ()); - make_modification ("assign-node", path (), tree ()); - make_modification ("insert-node", path (), tree ()); - make_modification ("remove-node", path (), tree ()); - make_modification ("set-cursor", path (), tree ()); -} - -TEST_CASE ("test get_type") { - modification m1= make_modification ("assign", path (), tree ()); - modification m2= make_modification ("insert", path (), tree ()); - modification m3= make_modification ("remove", path (), tree ()); - modification m4= make_modification ("split", path (), tree ()); - modification m5= make_modification ("join", path (), tree ()); - modification m6= make_modification ("assign-node", path (), tree ()); - modification m7= make_modification ("insert-node", path (), tree ()); - modification m8= make_modification ("remove-node", path (), tree ()); - modification m9= make_modification ("set-cursor", path (), tree ()); - CHECK_EQ (get_type (m1) == "assign", true); - CHECK_EQ (get_type (m2) == "insert", true); - CHECK_EQ (get_type (m3) == "remove", true); - CHECK_EQ (get_type (m4) == "split", true); - CHECK_EQ (get_type (m5) == "join", true); - CHECK_EQ (get_type (m6) == "assign-node", true); - CHECK_EQ (get_type (m7) == "insert-node", true); - CHECK_EQ (get_type (m8) == "remove-node", true); - CHECK_EQ (get_type (m9) == "set-cursor", true); -} - -TEST_CASE ("test get_path") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (1), tree ()); - modification m3= mod_assign (path (1, 2), tree ()); - CHECK_EQ (get_path (m1) == path (), true); - CHECK_EQ (get_path (m2) == path (1), true); - CHECK_EQ (get_path (m3) == path (1, 2), true); -} - -TEST_CASE ("test get_tree") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (), tree ("string")); - modification m3= mod_assign (path (), tree (1)); - CHECK_EQ (get_tree (m1) == tree (), true); - CHECK_EQ (get_tree (m2) == tree ("string"), true); - CHECK_EQ (get_tree (m3) == tree (1), true); -} - -TEST_CASE ("test is_applicable") { - modification m1= mod_assign (path (), tree ()); - modification m2= mod_assign (path (1), tree ()); - modification m3= mod_assign (path (1, 2), tree ()); - CHECK_EQ (is_applicable (tree (), m1), true); - CHECK_EQ (is_applicable (tree (), m2), false); - CHECK_EQ (is_applicable (tree (), m3), false); - CHECK_EQ (is_applicable (tree (1), m1), true); - CHECK_EQ (is_applicable (tree (1), m2), false); - CHECK_EQ (is_applicable (tree (1), m3), false); - CHECK_EQ (is_applicable (tree (1, 2), m1), true); - CHECK_EQ (is_applicable (tree (1, 2), m2), true); - CHECK_EQ (is_applicable (tree (1, 2), m3), false); -} \ No newline at end of file +#include "modification.hpp" +#include "moe_doctests.hpp" +#include "tree.hpp" + +#include + +using moebius::CONCAT; +using moebius::DOCUMENT; + +TEST_CASE ("test construct func") { + modification m1 (MOD_ASSIGN, path (), tree ()); + modification m2 (MOD_ASSIGN, path (), tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_ASSIGN, true); + CHECK_EQ (m1->p == path (), true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_ASSIGN, true); + CHECK_EQ (m2->p == path (), true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_assign") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (), tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_ASSIGN, true); + CHECK_EQ (m1->p == path (), true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_ASSIGN, true); + CHECK_EQ (m2->p == path (), true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_insert") { + modification m1= mod_insert (path (), 0, tree ()); + modification m2= mod_insert (path (), 0, tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_INSERT, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_INSERT, true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_remove") { + modification m1= mod_remove (path (), 1, 2); + modification m2= mod_remove (path (), 1, 2); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_REMOVE, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->p[1] == 2, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_REMOVE, true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_split") { + modification m1= mod_split (path (), 1, 2); + modification m2= mod_split (path (), 1, 2); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_SPLIT, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->p[1] == 2, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_SPLIT, true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_join") { + modification m1= mod_join (path (), 1); + modification m2= mod_join (path (), 1); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_JOIN, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_JOIN, true); + CHECK_EQ (m2->t == tree (), true); + modification m3= mod_join (path (1), 2); + CHECK_EQ (m3->k == MOD_JOIN, true); + CHECK_EQ (m3->p[0] == 1, true); + CHECK_EQ (m3->p[1] == 2, true); +} + +TEST_CASE ("test mod_assign_node") { + modification m1= mod_assign_node (path (), 1); + modification m2= mod_assign_node (path (), 1); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_ASSIGN_NODE, true); + CHECK_EQ (m1->p == path (), true); + CHECK_EQ (m1->t == tree (1), true); + CHECK_EQ (m2->k == MOD_ASSIGN_NODE, true); + CHECK_EQ (m2->p == path (), true); + CHECK_EQ (m2->t == tree (1), true); +} + +TEST_CASE ("test mod_insert_node") { + modification m1= mod_insert_node (path (), 1, tree ()); + modification m2= mod_insert_node (path (), 1, tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_INSERT_NODE, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_INSERT_NODE, true); + CHECK_EQ (m2->p[0] == 1, true); + CHECK_EQ (m2->t == tree (), true); + modification m3= mod_insert_node (path (), 2, tree ("string")); + CHECK_EQ (m3->k == MOD_INSERT_NODE, true); + CHECK_EQ (m3->p[0] == 2, true); + CHECK_EQ (m3->t == tree ("string"), true); +} + +TEST_CASE ("test mod_remove_node") { + modification m1= mod_remove_node (path (), 1); + modification m2= mod_remove_node (path (), 1); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_REMOVE_NODE, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_REMOVE_NODE, true); + CHECK_EQ (m2->p[0] == 1, true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test mod_set_cursor") { + modification m1= mod_set_cursor (path (), 1, tree ()); + modification m2= mod_set_cursor (path (), 1, tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_SET_CURSOR, true); + CHECK_EQ (m1->p[0] == 1, true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_SET_CURSOR, true); + CHECK_EQ (m2->p[0] == 1, true); + CHECK_EQ (m2->t == tree (), true); + modification m3= mod_set_cursor (path (), 2, tree ("string")); + CHECK_EQ (m3->k == MOD_SET_CURSOR, true); + CHECK_EQ (m3->p[0] == 2, true); + CHECK_EQ (m3->t == tree ("string"), true); +} + +TEST_CASE ("test operator*/") { + modification m1= mod_assign (path (), tree ()); + modification m2= 1 * m1; + modification m3= path (1) * m1; + modification m4= m1 * 1; + modification m5= mod_assign (path (1, 2, 3), tree ()) / path (1); + CHECK_EQ (m2->k == MOD_ASSIGN, true); + CHECK_EQ (m2->p[0] == 1, true); + CHECK_EQ (m2->t == tree (), true); + CHECK_EQ (m3->k == MOD_ASSIGN, true); + CHECK_EQ (m3->p[0] == 1, true); + CHECK_EQ (m3->t == tree (), true); + CHECK_EQ (m4->k == MOD_ASSIGN, true); + CHECK_EQ (m4->p[0] == 1, true); + CHECK_EQ (m4->t == tree (), true); + CHECK_EQ (m5->k == MOD_ASSIGN, true); + CHECK_EQ (m5->p[0] == 2, true); + CHECK_EQ (m5->t == tree (), true); +} + +TEST_CASE ("test operator==") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (), tree ()); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + modification m3= mod_assign (path (), tree ("string")); + CHECK_EQ (m1 == m3, false); + CHECK_EQ (m1 != m3, true); +} + +TEST_CASE ("test copy") { + modification m1= mod_assign (path (), tree ()); + modification m2= copy (m1); + CHECK_EQ (m1 == m2, true); + CHECK_EQ (m1 != m2, false); + CHECK_EQ (m1->k == MOD_ASSIGN, true); + CHECK_EQ (m1->p == path (), true); + CHECK_EQ (m1->t == tree (), true); + CHECK_EQ (m2->k == MOD_ASSIGN, true); + CHECK_EQ (m2->p == path (), true); + CHECK_EQ (m2->t == tree (), true); +} + +TEST_CASE ("test root") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (1), tree ()); + modification m3= mod_assign (path (1, 2), tree ()); + CHECK_EQ (root (m1) == path (), true); + CHECK_EQ (root (m2) == path (1), true); + CHECK_EQ (root (m3) == path (1, 2), true); +} + +TEST_CASE ("test index") { + modification m1= mod_insert (path (1, 2), 3, tree ()); + modification m2= mod_remove (path (), 3, 4); + modification m3= mod_split (path (), 3, 4); + modification m4= mod_join (path (), 3); + modification m5= mod_remove_node (path (), 3); + modification m6= mod_set_cursor (path (), 3, tree ()); + CHECK_EQ (index (m1) == 3, true); + CHECK_EQ (index (m2) == 3, true); + CHECK_EQ (index (m3) == 3, true); + CHECK_EQ (index (m4) == 3, true); + CHECK_EQ (index (m5) == 3, true); + CHECK_EQ (index (m6) == 3, true); +} + +TEST_CASE ("test argument") { + modification m1= mod_remove (path (), 3, 4); + modification m2= mod_split (path (), 3, 4); + modification m3= mod_insert_node (path (), 3, tree ()); + CHECK_EQ (argument (m1) == 4, true); + CHECK_EQ (argument (m2) == 4, true); + CHECK_EQ (argument (m3) == 3, true); +} + +TEST_CASE ("test make_modification") { + make_modification ("assign", path (), tree ()); + make_modification ("insert", path (), tree ()); + make_modification ("remove", path (), tree ()); + make_modification ("split", path (), tree ()); + make_modification ("join", path (), tree ()); + make_modification ("assign-node", path (), tree ()); + make_modification ("insert-node", path (), tree ()); + make_modification ("remove-node", path (), tree ()); + make_modification ("set-cursor", path (), tree ()); +} + +TEST_CASE ("test get_type") { + modification m1= make_modification ("assign", path (), tree ()); + modification m2= make_modification ("insert", path (), tree ()); + modification m3= make_modification ("remove", path (), tree ()); + modification m4= make_modification ("split", path (), tree ()); + modification m5= make_modification ("join", path (), tree ()); + modification m6= make_modification ("assign-node", path (), tree ()); + modification m7= make_modification ("insert-node", path (), tree ()); + modification m8= make_modification ("remove-node", path (), tree ()); + modification m9= make_modification ("set-cursor", path (), tree ()); + CHECK_EQ (get_type (m1) == "assign", true); + CHECK_EQ (get_type (m2) == "insert", true); + CHECK_EQ (get_type (m3) == "remove", true); + CHECK_EQ (get_type (m4) == "split", true); + CHECK_EQ (get_type (m5) == "join", true); + CHECK_EQ (get_type (m6) == "assign-node", true); + CHECK_EQ (get_type (m7) == "insert-node", true); + CHECK_EQ (get_type (m8) == "remove-node", true); + CHECK_EQ (get_type (m9) == "set-cursor", true); +} + +TEST_CASE ("test get_path") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (1), tree ()); + modification m3= mod_assign (path (1, 2), tree ()); + CHECK_EQ (get_path (m1) == path (), true); + CHECK_EQ (get_path (m2) == path (1), true); + CHECK_EQ (get_path (m3) == path (1, 2), true); +} + +TEST_CASE ("test get_tree") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (), tree ("string")); + modification m3= mod_assign (path (), tree (1)); + CHECK_EQ (get_tree (m1) == tree (), true); + CHECK_EQ (get_tree (m2) == tree ("string"), true); + CHECK_EQ (get_tree (m3) == tree (1), true); +} + +TEST_CASE ("test is_applicable") { + modification m1= mod_assign (path (), tree ()); + modification m2= mod_assign (path (1), tree ()); + modification m3= mod_assign (path (1, 2), tree ()); + CHECK_EQ (is_applicable (tree (), m1), true); + CHECK_EQ (is_applicable (tree (), m2), false); + CHECK_EQ (is_applicable (tree (), m3), false); + CHECK_EQ (is_applicable (tree (1), m1), true); + CHECK_EQ (is_applicable (tree (1), m2), false); + CHECK_EQ (is_applicable (tree (1), m3), false); + CHECK_EQ (is_applicable (tree (1, 2), m1), true); + CHECK_EQ (is_applicable (tree (1, 2), m2), true); + CHECK_EQ (is_applicable (tree (1, 2), m3), false); +} +TEST_CASE ("test is_applicable insert") { + tree doc (DOCUMENT, tree ("a"), tree ("b")); + CHECK (is_applicable (doc, + mod_insert (path (), 0, tree (DOCUMENT, tree ("x"))))); + CHECK (!is_applicable ( + doc, mod_insert (path (), 0, tree ("x")))); // 复合位置插原子 + CHECK (!is_applicable ( + doc, mod_insert (path (5), 0, tree (DOCUMENT, tree ("x"))))); // 越界 + CHECK (!is_applicable ( + doc, mod_insert (path (), 9, tree (DOCUMENT, tree ("x"))))); // pos 越界 +} + +TEST_CASE ("test is_applicable atomic insert") { + tree doc (DOCUMENT, tree ("abc")); + CHECK (is_applicable (doc, mod_insert (path (0), 1, tree ("xy")))); + CHECK (!is_applicable ( + doc, mod_insert (path (0), 1, tree (DOCUMENT, tree ("x"))))); +} + +TEST_CASE ("test is_applicable remove") { + tree doc (DOCUMENT, tree ("a"), tree ("b")); + CHECK (is_applicable (doc, mod_remove (path (), 1, 1))); + CHECK (!is_applicable (doc, mod_remove (path (), 1, 2))); + CHECK (!is_applicable (doc, mod_remove (path (3), 0, 1))); +} + +TEST_CASE ("test is_applicable join") { + tree doc (DOCUMENT, tree ("a"), tree ("b")); + CHECK (is_applicable (doc, mod_join (path (), 0))); + CHECK (!is_applicable (doc, mod_join (path (), 1))); // pos+1 越界 + tree mixed (DOCUMENT, tree ("a"), tree (CONCAT, tree ("b"))); + CHECK (!is_applicable (mixed, mod_join (path (), 0))); // 原子+复合 +} + +TEST_CASE ("test is_applicable assign and node ops") { + tree doc (DOCUMENT, tree ("a")); + CHECK (is_applicable (doc, mod_assign (path (0), tree ("z")))); + CHECK (!is_applicable (doc, mod_assign (path (2), tree ("z")))); + CHECK (is_applicable (doc, mod_assign_node (path (), CONCAT))); + CHECK (is_applicable (doc, mod_insert_node (path (0), 0, tree (CONCAT)))); + // remove_node 需要目标是复合节点的孩子 + tree nested (DOCUMENT, tree (CONCAT, tree ("a"))); + CHECK (is_applicable (nested, mod_remove_node (path (0), 0))); +} diff --git a/moebius/tests/Kernel/Types/point_test.cpp b/moebius/tests/Kernel/Types/point_test.cpp index 52b3d7c575..e5bcd55d61 100644 --- a/moebius/tests/Kernel/Types/point_test.cpp +++ b/moebius/tests/Kernel/Types/point_test.cpp @@ -340,3 +340,17 @@ TEST_CASE ("test norm2_diff") { p1d[0]= 3; CHECK_EQ (norm2_diff (p1d, mkp (0, 100)), 9.0); } + +TEST_CASE ("test rotate_2D degenerate dims") { + // 空点与一维点走 mult 回退路径,先补维再旋转 + point r0= rotate_2D (point (), mkp (0, 0), tm_PI / 2); + CHECK_EQ (N (r0), 2); + CHECK (fabs (r0[0]) < 1e-9); + CHECK (fabs (r0[1]) < 1e-9); + point p1 (1); + p1[0] = 1.0; + point r1= rotate_2D (p1, mkp (0, 0), 0.0); + CHECK_EQ (N (r1), 2); + CHECK (fabs (r1[0] - 1.0) < 1e-9); + CHECK (fabs (r1[1]) < 1e-9); +} diff --git a/moebius/tests/moebius/data/scheme_der_test.cpp b/moebius/tests/moebius/data/scheme_der_test.cpp new file mode 100644 index 0000000000..294bdccb5c --- /dev/null +++ b/moebius/tests/moebius/data/scheme_der_test.cpp @@ -0,0 +1,158 @@ +/** \file scheme_der_test.cpp + * \copyright GPLv3 + * \details Unit tests for scheme source parsing (string_to_scheme_tree) + * \date 2026 + */ + +#include "moe_doctests.hpp" + +#include "tree_helper.hpp" + +#include +#include +#include + +using moebius::drd::init_std_drd; + +using namespace moebius; + +using namespace moebius::data; + +static string +atom_of (scheme_tree t) { + return t->label; +} + +TEST_SUITE ("scheme_der") { + + TEST_CASE ("parse simple list") { + scheme_tree t= string_to_scheme_tree ("(a b c)"); + CHECK (is_tuple (t)); + CHECK_EQ (N (t), 3); + CHECK (atom_of (t[0]) == "a"); + CHECK (atom_of (t[2]) == "c"); + } + + TEST_CASE ("parse token at buffer end") { + // 词元恰在缓冲区末尾结束(原实现越界读末尾一字节) + scheme_tree t= string_to_scheme_tree ("(abc)"); + CHECK_EQ (N (t), 1); + CHECK (atom_of (t[0]) == "abc"); + } + + TEST_CASE ("parse trailing backslash at end") { + // 转义字符位于缓冲区末尾(原实现 unslash 越界读) + scheme_tree t= string_to_scheme_tree ("abc\\"); + CHECK (atom_of (t) == "abc\\"); + } + + TEST_CASE ("parse quoted string with escapes") { + scheme_tree t= string_to_scheme_tree ("\"a\\nb\\tc\\\\d\""); + string s= atom_of (t); + CHECK (s == "\"a\nb\tc\\d\""); + } + + TEST_CASE ("parse unclosed quote at end") { + // 未闭合引号在缓冲区末尾截断,补上收尾引号 + scheme_tree t= string_to_scheme_tree ("\"abc"); + CHECK (atom_of (t) == "\"abc\""); + } + + TEST_CASE ("parse skips comments") { + scheme_tree t= string_to_scheme_tree ("; comment\n(foo)"); + CHECK (is_tuple (t)); + CHECK_EQ (N (t), 1); + CHECK (atom_of (t[0]) == "foo"); + } + + TEST_CASE ("parse quote sugar") { + scheme_tree t= string_to_scheme_tree ("'x"); + CHECK (is_tuple (t)); + CHECK_EQ (N (t), 2); + CHECK (atom_of (t[0]) == "'"); + CHECK (atom_of (t[1]) == "x"); + } + + TEST_CASE ("parse with carriage returns") { + // CR 字符被整串剔除,词元内 CR 直接拼接 + scheme_tree t= string_to_scheme_tree ("(a\015b c\015)"); + CHECK_EQ (N (t), 2); + CHECK (atom_of (t[0]) == "ab"); + CHECK (atom_of (t[1]) == "c"); + } + + TEST_CASE ("block parse concatenated expressions") { + scheme_tree t= block_to_scheme_tree ("(a) (b) (c)"); + CHECK (is_tuple (t)); + CHECK_EQ (N (t), 3); + CHECK_EQ (N (t[1]), 1); + CHECK (atom_of (t[1][0]) == "b"); + } + +} // TEST_SUITE + +// 生产代码导出但未写入 hpp,测试中补声明 + +TEST_CASE ("scm_quote escapes quotes and backslashes") { + CHECK (scm_quote ("plain") == "\"plain\""); + CHECK (scm_quote ("a\"b") == "\"a\\\"b\""); + CHECK (scm_quote ("a\\b") == "\"a\\\\b\""); + CHECK (scm_quote ("") == "\"\""); + CHECK (scm_quote ("mix\\\"end") == "\"mix\\\\\\\"end\""); +} + +// 生产代码导出但未写入 hpp,测试中补声明 +namespace moebius { +namespace data { +string slash (string s); +} +} // namespace moebius + +TEST_CASE ("slash link probe") { CHECK (moebius::data::slash ("x") == "x"); } + +TEST_CASE ("slash escapes specials and control chars") { + CHECK (moebius::data::slash ("plain") == "plain"); + CHECK (moebius::data::slash ("a b") == "a\\ b"); + CHECK (moebius::data::slash ("(x)") == "\\(x\\)"); + CHECK (moebius::data::slash ("a\\b") == "a\\\\b"); + CHECK (moebius::data::slash ("a\tb\nc") == "a\\tb\\nc"); + CHECK (moebius::data::slash (string ("a\0b", 3)) == "a\\0b"); + // 已带引号的串中特殊字符不再转义 + CHECK (moebius::data::slash ("\"a b\"") == "\"a b\""); +} + +TEST_CASE ("slash roundtrip with unslash via parser") { + // slash 后的词元可被解析器还原成单一原子 + string escaped= moebius::data::slash ("hello world"); + scheme_tree t1 = string_to_scheme_tree (escaped); + CHECK (atom_of (t1) == "hello world"); +} + +TEST_CASE ("scm_unquote strips quotes and unescapes") { + CHECK (scm_unquote ("\"plain\"") == "plain"); + CHECK (scm_unquote ("\"a\\\\b\"") == "a\\b"); + CHECK (scm_unquote ("\"a\\\"b\"") == "a\"b"); + CHECK (scm_unquote ("plain") == "plain"); +} + +TEST_CASE ("tm document load roundtrip") { + init_std_drd (); // 注册内置标签名,document/concat 才映射到内置编号 + // .tm 文本 → 树:结构与转义还原 + tree doc= + scheme_to_tree ("(document (concat \"hello \" \"world\" (rigid \"r\")))"); + CHECK (is_document (doc)); + REQUIRE (N (doc) == 1); + tree par= doc[0]; + CHECK (is_compound (par, "concat")); + REQUIRE (N (par) == 3); + CHECK (par[0] == tree ("hello ")); + CHECK (par[1] == tree ("world")); + CHECK (par[2] == tree (make_tree_label ("rigid"), tree ("r"))); +} + +TEST_CASE ("tm document with header comment") { + init_std_drd (); + tree doc= scheme_document_to_tree ( + "; copyright header\n(document (TeXmacs \"2.1.2\"))"); + CHECK (is_document (doc)); +} diff --git a/moebius/tests/moebius/drd/drd_env_test.cpp b/moebius/tests/moebius/drd/drd_env_test.cpp new file mode 100644 index 0000000000..fd277acf2d --- /dev/null +++ b/moebius/tests/moebius/drd/drd_env_test.cpp @@ -0,0 +1,121 @@ +/** \file drd_env_test.cpp + * \copyright GPLv3 + * \details Unit tests for drd_info::get_env_child fast paths + * \date 2026 + */ + +#include "moe_doctests.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" + +#include +#include +#include + +using namespace moebius; +using moebius::drd::init_std_drd; +using moebius::drd::the_drd; + +TEST_SUITE ("drd_env") { + + TEST_CASE ("get_env_child empty binding returns default") { + init_std_drd (); + tree doc (DOCUMENT, tree ("a"), tree ("b")); + // 无环境绑定的标签:返回调用方缺省值而非空树 + CHECK (the_drd->get_env_child (doc, 0, "mode", tree ("text")) == + tree ("text")); + CHECK (the_drd->get_env_child (doc, 0, "mode", tree ("")) == tree ("")); + } + + TEST_CASE ("get_env_child invalid index returns default") { + init_std_drd (); + tree doc (DOCUMENT, tree ("a")); + CHECK (the_drd->get_env_child (doc, 5, "mode", tree ("d")) == tree ("d")); + } + + TEST_CASE ("get_env_child WITH reads binding") { + init_std_drd (); + tree w (WITH, tree ("mode"), tree ("src"), tree ("body")); + // WITH 的最后一个孩子继承绑定值 + CHECK (the_drd->get_env_child (w, 2, "mode", tree ("text")) == + tree ("src")); + // 非最后孩子不受 WITH 绑定影响 + CHECK (the_drd->get_env_child (w, 0, "mode", tree ("text")) == + tree ("text")); + } + + TEST_CASE ("get_env_child env variant merges") { + init_std_drd (); + tree w (WITH, tree ("mode"), tree ("src"), tree ("body")); + tree env (ATTR); + tree r= the_drd->get_env_child (w, 2, env); + CHECK (drd::drd_env_read (r, "mode", tree ("")) == tree ("src")); + // 无绑定的复合节点透传传入的 env + tree doc (DOCUMENT, tree ("a")); + tree r2= the_drd->get_env_child (doc, 0, env); + CHECK (drd::drd_env_read (r2, "mode", tree ("x")) == tree ("x")); + } + + TEST_CASE ("get_env_descendant through WITH") { + init_std_drd (); + tree w (WITH, tree ("mode"), tree ("src"), tree ("body")); + CHECK (the_drd->get_env_descendant (w, path (2), "mode", tree ("text")) == + tree ("src")); + CHECK (the_drd->get_env_descendant (w, path (), "mode", tree ("text")) == + tree ("text")); + } + +} // TEST_SUITE + +TEST_CASE ("extern derived label memoized consistently") { + init_std_drd (); + // EXTERN 节点派生 "extern:" 标签,备忘缓存不改变判定结果 + tree ex1 (EXTERN, tree ("hlink")); + ex1 << tree ("a") << tree ("b"); + tree ex2 (EXTERN, tree ("hlink")); + ex2 << tree ("c") << tree ("d"); + tree ex3 (EXTERN, tree ("other")); + ex3 << tree ("e"); + bool r1= the_drd->is_accessible_child (ex1, 0); + // 同名宏重复出现(命中备忘)与首次(未命中)结果一致 + CHECK (the_drd->is_accessible_child (ex2, 0) == r1); + // 不同宏名(备忘失效)也返回确定的布尔值 + bool r3= the_drd->is_accessible_child (ex3, 0); +} + +TEST_CASE ("get_env_child WITH duplicate var last wins") { + init_std_drd (); + // 同名绑定对后者覆盖前者(与 drd_env_merge 语义一致) + tree w (WITH, tree ("mode"), tree ("src"), tree ("mode"), tree ("text"), + tree ("body")); + CHECK (the_drd->get_env_child (w, 4, "mode", tree ("d")) == tree ("text")); + // 无匹配变量返回缺省 + tree w2 (WITH, tree ("color"), tree ("red"), tree ("body")); + CHECK (the_drd->get_env_child (w2, 2, "mode", tree ("text")) == + tree ("text")); +} + +TEST_CASE ("drd_env_write sorted insert replace append") { + using moebius::drd::drd_env_read; + using moebius::drd::drd_env_write; + tree env (ATTR); + // 追加 + env= drd_env_write (env, string ("mode"), tree ("src")); + CHECK (drd_env_read (env, "mode", tree ("")) == tree ("src")); + // 插入(排序在 mode 之前) + env= drd_env_write (env, string ("color"), tree ("red")); + CHECK (drd_env_read (env, "color", tree ("")) == tree ("red")); + CHECK (drd_env_read (env, "mode", tree ("")) == tree ("src")); + CHECK_EQ (N (env), 4); + // env 保持按变量名排序 + CHECK (env[0]->label == "color"); + CHECK (env[2]->label == "mode"); + // 替换(同变量名覆盖,长度不变) + env= drd_env_write (env, string ("mode"), tree ("text")); + CHECK_EQ (N (env), 4); + CHECK (drd_env_read (env, "mode", tree ("")) == tree ("text")); + // 末尾追加更大的变量名 + env= drd_env_write (env, string ("zvar"), tree ("z")); + CHECK_EQ (N (env), 6); + CHECK (env[4]->label == "zvar"); +}