@calcit/procs 0.12.29 → 0.12.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/build.rs +8 -0
- package/editing-history/202604251940-wasm-suite-multi-module-entry.md +53 -0
- package/editing-history/202604252003-wasm-method-includes-string-and-set-refactor.md +71 -0
- package/editing-history/202604252010-wasm-suite-add-fn-lens-edn.md +43 -0
- package/editing-history/202604252051-display-by.md +39 -0
- package/editing-history/202604281728-fix-gensym-stable.md +31 -0
- package/editing-history/202604291400-fix-bind-type-precompile.md +38 -0
- package/lib/calcit.procs.mjs +33 -42
- package/lib/js-buf-list.mjs +44 -0
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/rfc/04-13-call-arg-literal-rewrite-rfc.md +33 -26
- package/rfc/04-16-wasm-data-structures.md +51 -26
- package/ts-src/calcit.procs.mts +35 -42
- package/ts-src/js-buf-list.mts +47 -0
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/build.rs
CHANGED
|
@@ -104,6 +104,14 @@ fn map_key_path_segment(key: &Edn) -> String {
|
|
|
104
104
|
|
|
105
105
|
/// Convert a schema Edn value (either old Quote-wrapped or new direct map) into Edn map form.
|
|
106
106
|
fn parse_schema_from_edn(value: &Edn, owner: &str) -> Result<Edn, String> {
|
|
107
|
+
// Simple scalar schemas (e.g. :dynamic, :nil) are valid as-is — skip Cirru path
|
|
108
|
+
match value {
|
|
109
|
+
Edn::Tag(_) | Edn::Nil => {
|
|
110
|
+
validate_schema_edn_no_legacy_quotes(value, owner)?;
|
|
111
|
+
return Ok(value.clone());
|
|
112
|
+
}
|
|
113
|
+
_ => {}
|
|
114
|
+
}
|
|
107
115
|
// Old format: Edn::Quote wrapping Cirru — convert to direct map Edn
|
|
108
116
|
if let Ok(cirru) = from_edn::<Cirru>(value.clone()) {
|
|
109
117
|
let text = cirru_parser::format(&[cirru], true.into())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 202604251940 — wasm: 新增 test-wasm-suite 多模块入口
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
`test-wasm.cirru` 是手写的小型 WASM 测试用例集,已经通过;但
|
|
6
|
+
`yarn check-all` 还会运行 `calcit/test-X.cirru` 一系列测试模块
|
|
7
|
+
(test-cond / test-math / test-set / ...),目前 WASM target
|
|
8
|
+
还没有专门的入口把这些测试统一跑起来。
|
|
9
|
+
|
|
10
|
+
## 改动
|
|
11
|
+
|
|
12
|
+
1. 新增 `calcit/test-wasm-suite.cirru`:手写的 snapshot 文件,
|
|
13
|
+
通过 `:modules` 引入 `util.cirru` 与若干测试模块,自身定义
|
|
14
|
+
`test-wasm-suite.main/main!` 依次调用各测试模块的 `main!`。
|
|
15
|
+
- 当前包含:`util` + `test-cond` + `test-math` + `test-set` + `test-tuple`
|
|
16
|
+
- `test-tuple/main!` 因 `&tuple:enum` 未实现,被 codegen 标为
|
|
17
|
+
skip,main! 会以 `f64 0.0` 占位返回,对整体不产生副作用。
|
|
18
|
+
2. 新增 `scripts/test-wasm-suite-extended.sh`:编译并运行新入口的
|
|
19
|
+
一键脚本,输出 `[wasm] skipping ...` 的列表,便于后续逐步补齐
|
|
20
|
+
proc 实现。
|
|
21
|
+
|
|
22
|
+
## 已知阻塞(短期不放进新入口)
|
|
23
|
+
|
|
24
|
+
- `test-recursion`:`emit_bump_alloc` 与 closure-as-value 的交互产生
|
|
25
|
+
无效 WAT(`global.set __heap_ptr` 收到 f64),独立编译也会触发;
|
|
26
|
+
与目前 in-progress refactor(emit_set_find_structural / hash_list_or_set)
|
|
27
|
+
联动后表现尤其明显。
|
|
28
|
+
- `test-list`:`local.set f64` 收到 i32(function 35 体内),bytes 模式
|
|
29
|
+
形如 `local.get a (i32) ; local.get b (i32) ; local.set c (f64)`。
|
|
30
|
+
- `test-string`:`includes?` / `.find-index` / `starts-with?` /
|
|
31
|
+
`strip-prefix` / `strip-suffix` 等字符串过程 WASM 未实现;
|
|
32
|
+
`trim` / `blank?` / `parse-float` / `&str:replace` / `format-to-cirru` /
|
|
33
|
+
`&cirru-quote:to-list` / `get-char-code` 同上。
|
|
34
|
+
- `test-fn`:`(let f2 &+)` 把过程作为值放进局部变量,
|
|
35
|
+
当前 WASM 不支持 first-class proc,整个 `main!` 会被 skip。
|
|
36
|
+
- `test-algebra`、`test-map`:旧的 `unreachable` 失败,与本次改动无关。
|
|
37
|
+
|
|
38
|
+
## 用法
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bash scripts/test-wasm-suite-extended.sh
|
|
42
|
+
# 期望最后一行: [test-wasm-suite] PASS
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 后续步骤
|
|
46
|
+
|
|
47
|
+
1. 修 test-recursion 的 closure-as-value / bump_alloc 类型不一致
|
|
48
|
+
(定位 `f64.const 0; local.tee Xf64; global.set __heap_ptr` 来源)。
|
|
49
|
+
2. 修 test-list 的 i32 ↔ f64 binding bug。
|
|
50
|
+
3. 实现 `includes?` / `starts-with?` / `ends-with?` / `strip-prefix` /
|
|
51
|
+
`strip-suffix` / `.find-index` 等 string proc,把 test-string 的
|
|
52
|
+
`test-includes` 子集放进新入口。
|
|
53
|
+
4. 实现 `&tuple:enum` 等 tuple 操作,让 test-tuple/main! 不再被 skip。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 202604252003 — WASM suite extended: .includes? string branch + uncommitted refactor consolidation
|
|
2
|
+
|
|
3
|
+
## Background
|
|
4
|
+
|
|
5
|
+
In the previous session a multi-module WASM suite entry (`calcit/test-wasm-suite.cirru`) was added
|
|
6
|
+
to gradually exercise more of the existing `calcit/test-*.cirru` programs against the WASM codegen.
|
|
7
|
+
At that point a sizable in-progress refactor in `src/codegen/emit_wasm/{heap,hof,lists,runtime,sets}.rs`
|
|
8
|
+
was left uncommitted alongside, and `test-string.cirru` was deferred because folding it into the
|
|
9
|
+
suite triggered an `unreachable` trap.
|
|
10
|
+
|
|
11
|
+
## Findings this round
|
|
12
|
+
|
|
13
|
+
When folding `test-string.main` into `test-wasm-suite.main/main!`, the runtime trap reproduced. By
|
|
14
|
+
calling individual exports (new helper `scripts/test-wasm-call.mjs`) it was clear that:
|
|
15
|
+
|
|
16
|
+
1. `(.includes? "abc" "abc")` returned `0` (false) instead of `1`. Root cause:
|
|
17
|
+
`methods::emit_method_includes` only handled `list/map/set` receivers; for any other type it
|
|
18
|
+
emitted `f64_const(0.0)` as the fallback. Strings therefore never reached
|
|
19
|
+
`__rt_str_find_index`.
|
|
20
|
+
2. After fixing #1, `test-includes` still trapped — the assertions
|
|
21
|
+
`assert= true $ starts-with? :a/b :a/` and `assert= true $ starts-with? :a/b |a/` pass
|
|
22
|
+
tags as arguments. Tags in WASM are encoded as small `f64` values equal to their tag-index id,
|
|
23
|
+
so `__rt_str_starts_with` reads them as bogus heap pointers and silently returns 0. Adding tag→
|
|
24
|
+
string conversion at runtime is a follow-up.
|
|
25
|
+
3. `test-bitwise` traps on `&number:display-by` (radix formatting) which is currently stubbed to
|
|
26
|
+
return `0`.
|
|
27
|
+
|
|
28
|
+
Because #2 and #3 require additional runtime helpers, `test-string.cirru` is left out of the suite
|
|
29
|
+
for this commit. The `.includes?` regression is fixed in isolation; it also benefits any other
|
|
30
|
+
program that calls `.includes?` on a string.
|
|
31
|
+
|
|
32
|
+
## Changes in this commit
|
|
33
|
+
|
|
34
|
+
- `src/codegen/emit_wasm/methods.rs`: extend `emit_method_includes` with a string branch
|
|
35
|
+
(`emit_str_includes_from_local`) that calls `__rt_str_find_index >= 0`.
|
|
36
|
+
- `scripts/test-wasm-call.mjs`: minimal helper to invoke a single named export against the latest
|
|
37
|
+
`js-out/program.wasm`, useful for bisecting which assertion in a `main!` traps.
|
|
38
|
+
|
|
39
|
+
The commit also folds in the previously-uncommitted refactor that landed in
|
|
40
|
+
`src/codegen/emit_wasm/{heap,hof,lists,runtime,sets}.rs`:
|
|
41
|
+
|
|
42
|
+
- `sets.rs`: `emit_set_find_structural` replaces the pointer-only `__rt_set_find_elem` for
|
|
43
|
+
`difference`/`intersection`/set-equality so identical-by-content elements are treated as equal.
|
|
44
|
+
- `runtime.rs`: new `__rt_hash_list_or_set(ptr) -> i32` (XOR-based content hash) plus a couple of
|
|
45
|
+
new helper-function indices.
|
|
46
|
+
- `hof.rs`: `emit_copy_type_tag` so `map`/`filter` preserve the receiver tag (set→set, list→list).
|
|
47
|
+
- `lists.rs`: `range start end step` 3-arg form.
|
|
48
|
+
- `heap.rs`: smarter `emit_hash_proc` that detects heap pointers at runtime and dispatches to the
|
|
49
|
+
list/set hash helper. The dead `emit_hash_expr_i32`/`emit_hash_mix` static helpers are removed.
|
|
50
|
+
- `emit_wasm.rs`: `#[allow(private_interfaces)]` on `emit_equals_core` and
|
|
51
|
+
`emit_equals_core_shallow` (both already returned a `pub(super)`-only opaque local index but are
|
|
52
|
+
used from sibling modules through the `super::*` re-export).
|
|
53
|
+
|
|
54
|
+
## Verification
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bash scripts/cargo-with-sdk.sh clippy --bin cr-wasm -- -D warnings # clean
|
|
58
|
+
bash scripts/cargo-with-sdk.sh build --bin cr-wasm --release # ok
|
|
59
|
+
bash scripts/test-wasm-suite-extended.sh # PASS
|
|
60
|
+
bash scripts/test-wasm-suite.sh # 6/10 (unchanged)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`scripts/test-wasm-suite.sh` skips `test-fn`, `test-string`, `test-tuple`, `test-list` for the
|
|
64
|
+
same reasons documented previously; that is independent of this commit.
|
|
65
|
+
|
|
66
|
+
## Follow-ups
|
|
67
|
+
|
|
68
|
+
1. Tag → string conversion at runtime so string procs accept tags (unblocks `test-string`).
|
|
69
|
+
2. `&number:display-by` radix formatting (unblocks the last 3 assertions in `test-bitwise`).
|
|
70
|
+
3. The other test-string skips (`&str:replace`, `parse-float`, `blank?`, `trim`,
|
|
71
|
+
`get-char-code`, `format-to-cirru`, `&cirru-quote:to-list`).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 202604252010 — WASM suite extended: + test-fn / test-lens / test-edn
|
|
2
|
+
|
|
3
|
+
## Background
|
|
4
|
+
|
|
5
|
+
After landing the `.includes?` string fix, this round adds three more `test-*.cirru` programs to
|
|
6
|
+
`calcit/test-wasm-suite.cirru` to cover more of the WASM runtime surface. Goal: keep growing the
|
|
7
|
+
"runs end-to-end in WASM" set without depending on Host FFI features.
|
|
8
|
+
|
|
9
|
+
## Process
|
|
10
|
+
|
|
11
|
+
For each candidate module:
|
|
12
|
+
|
|
13
|
+
1. Inspect `cr-wasm <module>.cirru` standalone to enumerate `[wasm] skipping ...` lines (these are
|
|
14
|
+
per-def errors that cause that def to be replaced with a `0.0`-returning stub).
|
|
15
|
+
2. Read the module's `main!` body to confirm trapping assertions can survive when the skipped
|
|
16
|
+
helpers return `0`.
|
|
17
|
+
3. Add to the suite, rebuild, and run `bash scripts/test-wasm-suite-extended.sh`.
|
|
18
|
+
|
|
19
|
+
## Result
|
|
20
|
+
|
|
21
|
+
Suite now covers (in addition to previous `test-cond/test-math/test-set/test-tuple`):
|
|
22
|
+
|
|
23
|
+
- `test-fn` — only `&init-builtin-impls!` skipped; suite passes cleanly.
|
|
24
|
+
- `test-lens` — `test-lens.main/test-lens` is skipped (`:::` rejects a quoted form arg) but other
|
|
25
|
+
defs in the module compile; `main!` does not trip the stub.
|
|
26
|
+
- `test-edn` — many sub-tests skipped (`parse-cirru-edn`, `trim`, `&extract-code-into-edn`), but
|
|
27
|
+
the skipped fns return `0` instead of running their assertions, so `main!` completes.
|
|
28
|
+
|
|
29
|
+
Modules tried but reverted:
|
|
30
|
+
|
|
31
|
+
- `test-string` — assertions feed tags into `starts-with?`/`ends-with?`/`includes?`. Tags in WASM
|
|
32
|
+
are encoded as small `f64` ids (their tag-index value), not heap string pointers, so the procs
|
|
33
|
+
silently return `0` and the assertion traps. Needs a tag→string runtime helper.
|
|
34
|
+
- `test-algebra` — `test-map` exercises `%{} AlgebraBox`, `assert-traits`, `.map` on records, and
|
|
35
|
+
`:value` access; record/trait dispatch is not yet implemented in WASM.
|
|
36
|
+
- `test-map` (the standalone, full map suite) — `test-pairs`/`test-keys` etc. compile but trap at
|
|
37
|
+
runtime, likely from map iteration ordering or hash collision behavior. Needs deeper triage.
|
|
38
|
+
|
|
39
|
+
## Verification
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bash scripts/test-wasm-suite-extended.sh # PASS (8 modules including util)
|
|
43
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# `&number:display-by` WASM 实现
|
|
2
|
+
|
|
3
|
+
## 摘要
|
|
4
|
+
|
|
5
|
+
实现了 `&number:display-by` 和 `.display-by` 方法的 WASM codegen,支持二进制、八进制、十六进制格式化,并将 `test-string` 模块加入 WASM 扩展测试套件。
|
|
6
|
+
|
|
7
|
+
## 关键实现点
|
|
8
|
+
|
|
9
|
+
### `build_rt_display_by` (runtime.rs)
|
|
10
|
+
|
|
11
|
+
- 函数签名: `(value: f64, radix: f64) -> f64` (返回字符串堆 ptr)
|
|
12
|
+
- 算法:负数检测 → 计数位数(div 循环)→ 计算前缀长度(radix ∈ {2,8,16} → 2, else 0)→ 分配堆字符串 → 写前缀(`0b`/`0o`/`0x`)→ 从右往左写位数(`'0'..digit` 或 `'a'..digit-10`)
|
|
13
|
+
- locals 索引 2-16,手动分配(非 RuntimeFnBuilder 风格)
|
|
14
|
+
- 负数:先检测,计数时用 abs_i64,最后写 '-'
|
|
15
|
+
|
|
16
|
+
### 注意
|
|
17
|
+
|
|
18
|
+
- 必须先用 `collect_strings_from_expr` 收集 tag 名称才能支持 tag→string(前一次提交已做)
|
|
19
|
+
- local 参数:value=0, radix=1;其余 2..16 为局部变量
|
|
20
|
+
|
|
21
|
+
### 测试
|
|
22
|
+
|
|
23
|
+
- `test-display-by-bin()` = 7 (17 → "0b10001")
|
|
24
|
+
- `test-display-by-hex()` = 4 (17 → "0x11")
|
|
25
|
+
- test-string 模块加入 test-wasm-suite(8个模块),全部 PASS
|
|
26
|
+
|
|
27
|
+
## 修改文件
|
|
28
|
+
|
|
29
|
+
- `src/codegen/emit_wasm/runtime.rs` — 新增 `build_rt_display_by` + 注册 `__rt_display_by`
|
|
30
|
+
- `src/codegen/emit_wasm.rs` — 分拆 `NativeNumberDisplayBy` 从 stub 到 `call_rt("__rt_display_by")`
|
|
31
|
+
- `src/codegen/emit_wasm/methods.rs` — 新增 `.display-by` 方法分发
|
|
32
|
+
- `calcit/test-wasm-suite.cirru` — 加入 `test-string`
|
|
33
|
+
- `calcit/test-wasm.cirru` — 新增 `test-display-by-bin` / `test-display-by-hex`
|
|
34
|
+
- `scripts/test-wasm.mjs` — 新增对应断言
|
|
35
|
+
|
|
36
|
+
## 后续计划
|
|
37
|
+
|
|
38
|
+
- `trim`、`blank?`、`&str:replace`、`parse-float`、`get-char-code`
|
|
39
|
+
- HOF closures-as-values (S1 fn-table)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Fix: Gensym 稳定性问题
|
|
2
|
+
|
|
3
|
+
## 问题描述
|
|
4
|
+
|
|
5
|
+
`cr js` 每次运行会生成不同的 JS 文件,原因是 gensym 计数器不稳定(如 `v__1` 变成 `v__3`)。
|
|
6
|
+
|
|
7
|
+
## 根本原因
|
|
8
|
+
|
|
9
|
+
Calcit 的 gensym 使用 `NS_SYMBOL_DICT: HashMap<Arc<str>, usize>` 记录计数器。由于 Rust 的 `HashMap` 每进程随机化迭代顺序,不同运行时的 def 编译顺序不同,导致 gensym 计数器累积值不一样。
|
|
10
|
+
|
|
11
|
+
有**两条**编译路径,只修了一条是不够的:
|
|
12
|
+
1. `ensure_ns_def_preprocessed` —— 正常编译路径,通过 `with_compiling_def` 重置 gensym 计数器(之前已修)
|
|
13
|
+
2. `compile_source_def_for_snapshot` —— 快照填充路径(JS codegen 时触发),**缺少 `with_compiling_def` 包装**,导致 `CURRENT_COMPILING_DEF` 为 `None`,gensym 退回到 `file_ns`(命名空间级 key),计数器跨 def 累积
|
|
14
|
+
|
|
15
|
+
## 修复方法
|
|
16
|
+
|
|
17
|
+
在 `src/runner/preprocess/mod.rs` 的 `compile_source_def_for_snapshot` 函数中,为 `preprocess_expr` 调用加上 `builtins::meta::with_compiling_def(ns, def, ...)` 包装,与 `ensure_ns_def_preprocessed` 保持一致。
|
|
18
|
+
|
|
19
|
+
## 已修改文件
|
|
20
|
+
|
|
21
|
+
- `src/builtins/syntax.rs`: gensym key 改为 `CURRENT_COMPILING_DEF`(无 `file_ns` 前缀)
|
|
22
|
+
- `src/builtins/meta.rs`: 新增 `CURRENT_COMPILING_DEF` thread_local + `with_compiling_def`(自动重置计数器)
|
|
23
|
+
- `src/runner/preprocess/mod.rs`:
|
|
24
|
+
- `ensure_ns_def_preprocessed`: 包 `with_compiling_def`(第一次修)
|
|
25
|
+
- `compile_source_def_for_snapshot`: 包 `with_compiling_def`(本次修)
|
|
26
|
+
|
|
27
|
+
## 验证
|
|
28
|
+
|
|
29
|
+
- `cr js` 连续 8 次运行,无任何文件变化
|
|
30
|
+
- `cargo test` 67 passed
|
|
31
|
+
- `yarn check-all` 全部通过
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Fix: bind-type 类型槽预编译顺序问题
|
|
2
|
+
|
|
3
|
+
## 问题描述
|
|
4
|
+
|
|
5
|
+
`cr js` 在 tiye-index 项目中,`reel.app.comp.todolist.mjs` 和 `respo.schema.mjs` 仍然不稳定——有时生成 `Op`-aware 版本(带 `import { Op }` 和 `$clt._PCT__$o__$o_(Op, ...)`),有时生成不带 Op 的版本(使用 `$clt._$o__$o_(...)`)。
|
|
6
|
+
|
|
7
|
+
## 根本原因
|
|
8
|
+
|
|
9
|
+
`bind-type :dispatch-op Op` 在 `respo.main/main!` 函数体中,是一个预处理阶段的副作用操作:当 `preprocess_expr` 遇到 `bind-type` 调用时,立即执行 `bind_type_slot("dispatch-op", Op)`,使 `*dispatch-op` 类型槽指向 Op 枚举。
|
|
10
|
+
|
|
11
|
+
问题根源:`reel.app.comp.todolist` 等组件 def 会作为入口函数 `app.main/main!` 的传递依赖被提前编译。由于 HashMap 迭代顺序不确定,某些运行中 `todolist` 在 `respo.main/main!` 之前编译——此时 `*dispatch-op` 类型槽尚未绑定,导致 `d! $ :: :states cursor ...` 中的元组字面量无法被改写为 `%:: Op :states cursor ...`,生成结构上不同的 JS 代码。
|
|
12
|
+
|
|
13
|
+
## 修复方法
|
|
14
|
+
|
|
15
|
+
**双重修复:**
|
|
16
|
+
|
|
17
|
+
### 1. 预处理阶段:`precompile_bind_type_defs` (主要修复)
|
|
18
|
+
|
|
19
|
+
在 `src/lib.rs` 的 `run_program_with_docs` 中,在开始主入口预处理之前,先扫描**所有**程序源代码,找出含有 `bind-type` 调用的 def,并提前编译它们。
|
|
20
|
+
|
|
21
|
+
这确保 `respo.main/main!`(含 `bind-type :dispatch-op Op`)在 `app.main/main!` 的依赖树遍历之前被编译,从而使 `:dispatch-op` 类型槽在所有组件编译前就已绑定。
|
|
22
|
+
|
|
23
|
+
### 2. 快照填充阶段:任务排序 (辅助修复)
|
|
24
|
+
|
|
25
|
+
在 `src/program.rs` 的 `collect_snapshot_fill_tasks` 中,对快照填充任务排序,确保含 `bind-type` 的 def 在快照阶段也优先处理,同时按 (ns, def) 字典序排序保证完全确定性。
|
|
26
|
+
|
|
27
|
+
## 已修改文件
|
|
28
|
+
|
|
29
|
+
- `src/lib.rs`: 在 `run_program_with_docs` 中调用新增的 `precompile_bind_type_defs`
|
|
30
|
+
- `src/runner/preprocess/mod.rs`: 新增 `precompile_bind_type_defs` 公开函数(扫描所有源 def,提前编译含 `bind-type` 的 def)
|
|
31
|
+
- `src/program.rs`:
|
|
32
|
+
- `calcit_contains_bind_type` 改为 `pub fn`(供 preprocess 模块使用)
|
|
33
|
+
- `collect_snapshot_fill_tasks` 中对任务排序(bind-type 优先,其余按字典序)
|
|
34
|
+
|
|
35
|
+
## 验证
|
|
36
|
+
|
|
37
|
+
- `cr js` 在 tiye-index 项目连续 8 次运行,第 1 次(旧输出重新生成)后无任何文件变化
|
|
38
|
+
- `cargo test --lib` 179 tests passed
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -25,6 +25,7 @@ export * from "./custom-formatter.mjs";
|
|
|
25
25
|
export * from "./js-cirru.mjs";
|
|
26
26
|
export * from "./js-arity-helpers.mjs";
|
|
27
27
|
export * from "./js-tag-helpers.mjs";
|
|
28
|
+
export * from "./js-buf-list.mjs";
|
|
28
29
|
export { _$n_compare } from "./js-primes.mjs";
|
|
29
30
|
import { CalcitList, CalcitSliceList, foldl } from "./js-list.mjs";
|
|
30
31
|
import { CalcitMap, CalcitSliceMap } from "./js-map.mjs";
|
|
@@ -299,48 +300,6 @@ export function _$n_record_$o_nth(x, idx) {
|
|
|
299
300
|
throw new Error(`&record:nth index ${i} out of range for record with ${x.values.length} fields`);
|
|
300
301
|
return x.values[i];
|
|
301
302
|
}
|
|
302
|
-
// === BufList — mutable append-only list ===
|
|
303
|
-
// Wrapper around a plain JS Array for O(1) push.
|
|
304
|
-
export class CalcitBufList {
|
|
305
|
-
constructor(buf) {
|
|
306
|
-
this.buf = buf ?? [];
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
export function _$n_buf_list_$o_new() {
|
|
310
|
-
return new CalcitBufList();
|
|
311
|
-
}
|
|
312
|
-
export function _$n_buf_list_$o_push(buf, item) {
|
|
313
|
-
if (!(buf instanceof CalcitBufList))
|
|
314
|
-
throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
315
|
-
buf.buf.push(item);
|
|
316
|
-
return buf;
|
|
317
|
-
}
|
|
318
|
-
export function _$n_buf_list_$o_concat(buf, xs) {
|
|
319
|
-
if (!(buf instanceof CalcitBufList))
|
|
320
|
-
throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
321
|
-
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
322
|
-
const gen = xs.items();
|
|
323
|
-
let next = gen.next();
|
|
324
|
-
while (!next.done) {
|
|
325
|
-
buf.buf.push(next.value);
|
|
326
|
-
next = gen.next();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
331
|
-
}
|
|
332
|
-
return buf;
|
|
333
|
-
}
|
|
334
|
-
export function _$n_buf_list_$o_to_list(buf) {
|
|
335
|
-
if (!(buf instanceof CalcitBufList))
|
|
336
|
-
throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
337
|
-
return new CalcitSliceList([...buf.buf]);
|
|
338
|
-
}
|
|
339
|
-
export function _$n_buf_list_$o_count(buf) {
|
|
340
|
-
if (!(buf instanceof CalcitBufList))
|
|
341
|
-
throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
342
|
-
return buf.buf.length;
|
|
343
|
-
}
|
|
344
303
|
export function _$n_set_$o_count(x) {
|
|
345
304
|
if (x instanceof CalcitSet)
|
|
346
305
|
return x.len();
|
|
@@ -2002,6 +1961,38 @@ export let _$n_map_$o_common_keys = (a, b) => {
|
|
|
2002
1961
|
throw new Error("expected 2 maps");
|
|
2003
1962
|
}
|
|
2004
1963
|
};
|
|
1964
|
+
/** Single-pass diff: returns [drop-keys, new-diff, common-triples] in two traversals instead of 3+ */
|
|
1965
|
+
export let _$n_map_$o_diff_triple = (a, b) => {
|
|
1966
|
+
if ((a instanceof CalcitMap || a instanceof CalcitSliceMap) && (b instanceof CalcitMap || b instanceof CalcitSliceMap)) {
|
|
1967
|
+
let dropKeys = [];
|
|
1968
|
+
let commonTriples = [];
|
|
1969
|
+
// One pass over a: split into drop-keys and common-triples
|
|
1970
|
+
let aKeys = a.keysArray();
|
|
1971
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
1972
|
+
let k = aKeys[i];
|
|
1973
|
+
if (b.contains(k)) {
|
|
1974
|
+
commonTriples.push(new CalcitSliceList([k, a.get(k), b.get(k)]));
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
dropKeys.push(k);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
// One pass over b: collect entries not in a
|
|
1981
|
+
let newDiffPairs = [];
|
|
1982
|
+
let bKeys = b.keysArray();
|
|
1983
|
+
for (let i = 0; i < bKeys.length; i++) {
|
|
1984
|
+
let k = bKeys[i];
|
|
1985
|
+
if (!a.contains(k)) {
|
|
1986
|
+
newDiffPairs.push(k);
|
|
1987
|
+
newDiffPairs.push(b.get(k));
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return new CalcitSliceList([new CalcitSet(dropKeys), new CalcitSliceMap(newDiffPairs), new CalcitSliceList(commonTriples)]);
|
|
1991
|
+
}
|
|
1992
|
+
else {
|
|
1993
|
+
throw new Error("&map:diff-triple expected 2 maps");
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
2005
1996
|
export let bit_shr = (base, step) => {
|
|
2006
1997
|
return base >> step;
|
|
2007
1998
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CalcitList, CalcitSliceList } from "./js-list.mjs";
|
|
2
|
+
// === CalcitBufList — mutable append-only list ===
|
|
3
|
+
// Wrapper around a plain JS Array for O(1) push.
|
|
4
|
+
// Use &buf-list:new / &buf-list:push / &buf-list:to-list in Calcit code.
|
|
5
|
+
export class CalcitBufList {
|
|
6
|
+
constructor(buf) {
|
|
7
|
+
this.buf = buf ?? [];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function _$n_buf_list_$o_new() {
|
|
11
|
+
return new CalcitBufList();
|
|
12
|
+
}
|
|
13
|
+
export function _$n_buf_list_$o_push(buf, item) {
|
|
14
|
+
if (!(buf instanceof CalcitBufList))
|
|
15
|
+
throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
16
|
+
buf.buf.push(item);
|
|
17
|
+
return buf;
|
|
18
|
+
}
|
|
19
|
+
export function _$n_buf_list_$o_concat(buf, xs) {
|
|
20
|
+
if (!(buf instanceof CalcitBufList))
|
|
21
|
+
throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
22
|
+
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
23
|
+
const gen = xs.items();
|
|
24
|
+
let next = gen.next();
|
|
25
|
+
while (!next.done) {
|
|
26
|
+
buf.buf.push(next.value);
|
|
27
|
+
next = gen.next();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
32
|
+
}
|
|
33
|
+
return buf;
|
|
34
|
+
}
|
|
35
|
+
export function _$n_buf_list_$o_to_list(buf) {
|
|
36
|
+
if (!(buf instanceof CalcitBufList))
|
|
37
|
+
throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
38
|
+
return new CalcitSliceList([...buf.buf]);
|
|
39
|
+
}
|
|
40
|
+
export function _$n_buf_list_$o_count(buf) {
|
|
41
|
+
if (!(buf instanceof CalcitBufList))
|
|
42
|
+
throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
43
|
+
return buf.buf.length;
|
|
44
|
+
}
|
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.31",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"url": "git+https://github.com/calcit-lang/calcit.git"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@calcit/ternary-tree": "0.0.
|
|
35
|
+
"@calcit/ternary-tree": "0.0.26",
|
|
36
36
|
"@cirru/parser.ts": "^0.0.9",
|
|
37
37
|
"@cirru/writer.ts": "^0.1.7"
|
|
38
38
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.31",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"url": "git+https://github.com/calcit-lang/calcit.git"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@calcit/ternary-tree": "0.0.
|
|
35
|
+
"@calcit/ternary-tree": "0.0.26",
|
|
36
36
|
"@cirru/parser.ts": "^0.0.9",
|
|
37
37
|
"@cirru/writer.ts": "^0.1.7"
|
|
38
38
|
},
|
|
@@ -36,12 +36,13 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
36
36
|
|
|
37
37
|
预处理器根据函数参数的 schema 类型标注,自动将简写形式改写为完整形式:
|
|
38
38
|
|
|
39
|
-
| 简写
|
|
40
|
-
|
|
39
|
+
| 简写 | 改写为 | 触发条件 |
|
|
40
|
+
| ------------------ | ------------------------- | ------------------------ |
|
|
41
41
|
| `{} (:x 1) (:y 2)` | `%{} Point (:x 1) (:y 2)` | 参数类型为 struct/record |
|
|
42
|
-
| `:: :ok`
|
|
42
|
+
| `:: :ok` | `%:: Result0 :ok` | 参数类型为 enum |
|
|
43
43
|
|
|
44
44
|
改写后的 AST 能正常参与:
|
|
45
|
+
|
|
45
46
|
- 运行时类型验证(field 校验、variant 校验)
|
|
46
47
|
- 预处理阶段类型检查(`check_user_fn_arg_types`)
|
|
47
48
|
- JS codegen(通过 Import 引用而非内联 Struct/Enum 值)
|
|
@@ -75,12 +76,15 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
75
76
|
defstruct Point (:x :number) (:y :number)
|
|
76
77
|
|
|
77
78
|
defn sum-point (p)
|
|
78
|
-
:: :fn $ {} (:return :number)
|
|
79
|
-
:args $ [] 'app.main/Point
|
|
80
79
|
&+ (:x p) (:y p)
|
|
81
80
|
|
|
81
|
+
;; :schema 中标注参数类型
|
|
82
|
+
:: :fn $ {} (:return :number)
|
|
83
|
+
:args $ [] 'app.main/Point
|
|
84
|
+
|
|
82
85
|
;; 简写
|
|
83
86
|
sum-point $ {} (:x 10) (:y 20)
|
|
87
|
+
|
|
84
88
|
;; 预处理改写为
|
|
85
89
|
sum-point $ %{} Point (:x 10) (:y 20)
|
|
86
90
|
```
|
|
@@ -109,10 +113,12 @@ sum-point $ %{} Point (:x 10) (:y 20)
|
|
|
109
113
|
defenum Result0 (:err :string) (:ok)
|
|
110
114
|
|
|
111
115
|
defn takes-result (r)
|
|
112
|
-
:: :fn $ {} (:return :dynamic)
|
|
113
|
-
:args $ [] 'app.main/Result0
|
|
114
116
|
tag-match r ((:ok) :ok) ((:err msg) msg) $ _ :unknown
|
|
115
117
|
|
|
118
|
+
;; :schema 中标注参数类型
|
|
119
|
+
:: :fn $ {} (:return :dynamic)
|
|
120
|
+
:args $ [] 'app.main/Result0
|
|
121
|
+
|
|
116
122
|
;; 简写
|
|
117
123
|
takes-result $ :: :ok
|
|
118
124
|
;; 预处理改写为
|
|
@@ -130,28 +136,28 @@ takes-result $ %:: Result0 :err |error-msg
|
|
|
130
136
|
|
|
131
137
|
在 `CalcitTypeAnnotation` 上新增方法:
|
|
132
138
|
|
|
133
|
-
| 方法
|
|
134
|
-
|
|
139
|
+
| 方法 | 用途 |
|
|
140
|
+
| ------------------------------ | ----------------------------------------- |
|
|
135
141
|
| `resolve_to_struct_with_ref()` | 解析 struct + 可选 (ns, def) 路径(已有) |
|
|
136
|
-
| `resolve_to_enum_with_ref()`
|
|
142
|
+
| `resolve_to_enum_with_ref()` | 解析 enum + 可选 (ns, def) 路径(新增) |
|
|
137
143
|
|
|
138
144
|
两者都处理 `Struct/Record`↔`Enum/Tuple` 直接值、`TypeRef("ns/def")` 程序查找、以及 `Optional(inner)` 解包。
|
|
139
145
|
|
|
140
146
|
底层依赖:
|
|
141
147
|
|
|
142
|
-
| 函数
|
|
143
|
-
|
|
148
|
+
| 函数 | 用途 |
|
|
149
|
+
| -------------------------------------- | -------------------------------------------- |
|
|
144
150
|
| `resolve_struct_from_program(ns, def)` | 从 program registry 查找 struct 定义(已有) |
|
|
145
|
-
| `resolve_enum_from_program(ns, def)`
|
|
151
|
+
| `resolve_enum_from_program(ns, def)` | 从 program registry 查找 enum 定义(新增) |
|
|
146
152
|
|
|
147
153
|
### 4.2 改写函数
|
|
148
154
|
|
|
149
|
-
| 函数
|
|
150
|
-
|
|
151
|
-
| `try_rewrite_map_args_to_records()`
|
|
152
|
-
| `try_rewrite_single_map_to_record()`
|
|
153
|
-
| `try_rewrite_tuple_args_to_enum_tuples()`
|
|
154
|
-
| `try_rewrite_single_tuple_to_enum_tuple()` | 单个参数的 tuple→enum-tuple 改写(新增)
|
|
155
|
+
| 函数 | 用途 |
|
|
156
|
+
| ------------------------------------------ | --------------------------------------------- |
|
|
157
|
+
| `try_rewrite_map_args_to_records()` | 遍历参数列表,对 map 字面量尝试改写(已有) |
|
|
158
|
+
| `try_rewrite_single_map_to_record()` | 单个参数的 map→record 改写(已有) |
|
|
159
|
+
| `try_rewrite_tuple_args_to_enum_tuples()` | 遍历参数列表,对 tuple 字面量尝试改写(新增) |
|
|
160
|
+
| `try_rewrite_single_tuple_to_enum_tuple()` | 单个参数的 tuple→enum-tuple 改写(新增) |
|
|
155
161
|
|
|
156
162
|
### 4.3 集成点
|
|
157
163
|
|
|
@@ -169,18 +175,19 @@ takes-result $ %:: Result0 :err |error-msg
|
|
|
169
175
|
改写时若从 `TypeRef` 解析出 (ns, def) 路径,会构造 `Calcit::Import` 而非内联的 `Calcit::Struct`/`Calcit::Enum`。这是因为 JS codegen 不支持直接 emit `Struct`/`Enum` 字面量——它需要一个变量引用。
|
|
170
176
|
|
|
171
177
|
Import 策略:
|
|
178
|
+
|
|
172
179
|
- 同 namespace → `ImportInfo::SameFile`
|
|
173
180
|
- 跨 namespace → `ImportInfo::NsReferDef`
|
|
174
181
|
|
|
175
182
|
### 修改的文件
|
|
176
183
|
|
|
177
|
-
| 文件
|
|
178
|
-
|
|
179
|
-
| `src/calcit/type_annotation.rs` | `resolve_to_enum_with_ref()`, `resolve_enum_from_program()`
|
|
180
|
-
| `src/runner/preprocess.rs`
|
|
181
|
-
| `docs/features/enums.md`
|
|
182
|
-
| `docs/features/records.md`
|
|
183
|
-
| `calcit/test-enum.cirru`
|
|
184
|
+
| 文件 | 变更 |
|
|
185
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `src/calcit/type_annotation.rs` | `resolve_to_enum_with_ref()`, `resolve_enum_from_program()` |
|
|
187
|
+
| `src/runner/preprocess.rs` | `try_rewrite_tuple_args_to_enum_tuples()`, `try_rewrite_single_tuple_to_enum_tuple()`,集成到 Fn 分支 |
|
|
188
|
+
| `docs/features/enums.md` | 新增 "Automatic Tuple-to-Enum Rewrite" 章节 |
|
|
189
|
+
| `docs/features/records.md` | "Automatic Map-to-Record Rewrite" 章节(已有) |
|
|
190
|
+
| `calcit/test-enum.cirru` | 新增 `test-tuple-to-enum` 测试 |
|
|
184
191
|
|
|
185
192
|
## 5. 测试覆盖
|
|
186
193
|
|
|
@@ -4,10 +4,32 @@
|
|
|
4
4
|
|
|
5
5
|
现有 WASM codegen(`emit_wasm.rs`)采用 all-f64 策略,仅支持纯数值函数。本文档描述如何在保持 all-f64 ABI 兼容的前提下,增量引入 Tag、Record、Tuple/Enum 等结构化数据的支持。
|
|
6
6
|
|
|
7
|
+
## 三层类型架构(核心设计原则)
|
|
8
|
+
|
|
9
|
+
Calcit WASM codegen 按 **Rust `T` vs `dyn Trait`** 的思路分三层处理,详细设计见
|
|
10
|
+
`recollect/WASM-ROADMAP.md §"类型化 WASM 架构:Rust 风格设计模型"`,这里摘要关键决策:
|
|
11
|
+
|
|
12
|
+
| 层级 | Calcit 形态 | WASM 表示 | 阶段 |
|
|
13
|
+
| --------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------- |
|
|
14
|
+
| **静态** | `Number`, `Bool`, `Tag`, `defstruct` Record, Enum Tuple | 直接 f64 / 线性内存 struct,零装箱 | S0(已完成) |
|
|
15
|
+
| **泛型单态化** | `List<Number>`, `Map<Tag, Record>` 等带类型参数的集合;HOF lambda 类型已知 | 编译期展开专用函数;HOF 拦截器内联 | S0 HOF 路径(已完成) |
|
|
16
|
+
| **动态(dyn)** | 标注 `:dynamic` 或无法推断的值;函数作为一等值 | 装箱 tagged pointer(HEAP_MAGIC + type_tag + payload),运行时 dispatch | S1/S2(待做) |
|
|
17
|
+
|
|
18
|
+
**实现规则**:
|
|
19
|
+
|
|
20
|
+
- 函数所有参数/返回值类型静态已知 → 走静态路径,零开销。
|
|
21
|
+
- 类型参数已知的 HOF(`map`/`filter`/`foldl`)→ 编译期单态化,HOF 拦截器内联展开。
|
|
22
|
+
- 类型未知(`:dynamic`)→ 装箱为 tagged pointer,运行时读 type_tag 分支 dispatch。
|
|
23
|
+
- `assert-type` / downcast → 从 dyn 路径收窄回静态路径(读 tag → 失败则 `unreachable` → 成功则拆箱取 payload)。
|
|
24
|
+
- 函数作为值(闭包)→ S1:函数表 `call_indirect`;S3:静态 lift + 捕获环境 struct。
|
|
25
|
+
|
|
26
|
+
**给代码补类型标注就是在给 WASM 加速**——类型覆盖率直接决定静态路径覆盖率。
|
|
27
|
+
实现 trait impl 系统时同样遵循此原则:trait 方法有具体类型时单态化,无类型时走 dyn dispatch。
|
|
28
|
+
|
|
7
29
|
## 设计决策(继承自初始实现)
|
|
8
30
|
|
|
9
31
|
1. **二进制 `.wasm` 输出** — 使用 `wasm-encoder` crate 直接生成标准 WASM 二进制格式。
|
|
10
|
-
2. **All-f64 类型策略** — 所有值统一为 f64,Bool 用 1.0/0.0
|
|
32
|
+
2. **All-f64 类型策略** — 所有值统一为 f64,Bool 用 1.0/0.0 表示(静态层);动态层用 i32 装箱指针(暂以 f64 编码传递)。
|
|
11
33
|
3. **比较运算的 f64 返回** — 使用 `select` 指令将 i32 条件转为 f64 (1.0/0.0)。
|
|
12
34
|
4. **recur 映射到 WASM loop** — 通过 `block_depth` 追踪嵌套深度。
|
|
13
35
|
|
|
@@ -17,22 +39,24 @@
|
|
|
17
39
|
|
|
18
40
|
保持所有函数参数和返回值为 f64,扩展值的语义:
|
|
19
41
|
|
|
20
|
-
| 值类型
|
|
21
|
-
|
|
22
|
-
| Number
|
|
23
|
-
| Bool
|
|
24
|
-
| Nil
|
|
25
|
-
| Tag
|
|
26
|
-
| Record 指针 | f64 编码的 i32 偏移
|
|
27
|
-
| Tuple 指针
|
|
42
|
+
| 值类型 | f64 表示方式 | 说明 |
|
|
43
|
+
| ----------- | -------------------------- | ---------------------------- |
|
|
44
|
+
| Number | 直接 f64 | 不变 |
|
|
45
|
+
| Bool | 1.0 / 0.0 | 不变 |
|
|
46
|
+
| Nil | 0.0 | 不变 |
|
|
47
|
+
| Tag | 正整数 f64 (1.0, 2.0, ...) | 编译时分配,全局唯一 |
|
|
48
|
+
| Record 指针 | f64 编码的 i32 偏移 | 指向线性内存中的 Record 数据 |
|
|
49
|
+
| Tuple 指针 | f64 编码的 i32 偏移 | 指向线性内存中的 Tuple 数据 |
|
|
28
50
|
|
|
29
51
|
指针与数值之间的转换:
|
|
52
|
+
|
|
30
53
|
- 写入: `i32` → `f64.convert_i32_u`
|
|
31
54
|
- 读取: `f64` → `i32.trunc_f64_u`
|
|
32
55
|
|
|
33
56
|
### 歧义规避
|
|
34
57
|
|
|
35
58
|
Tag、指针、数值共用 f64 空间,依赖**编译时类型信息**区分:
|
|
59
|
+
|
|
36
60
|
- WASM 子集要求 Record/Tuple 相关函数必须有类型标注
|
|
37
61
|
- Tag 值与数值范围不重叠(Tag 从高位整数开始分配,或使用特殊编码)
|
|
38
62
|
- 实验阶段不做运行时类型检查,类型错误由 Calcit 预处理保证
|
|
@@ -76,13 +100,13 @@ offset + 8*(n): field_{n-1} (f64, 8 bytes)
|
|
|
76
100
|
|
|
77
101
|
### 操作映射
|
|
78
102
|
|
|
79
|
-
| Calcit 操作
|
|
80
|
-
|
|
81
|
-
| `&%{} struct field1 val1 ...`
|
|
82
|
-
| `&record:get record :field`
|
|
83
|
-
| `&record:nth record idx tag`
|
|
84
|
-
| `&record:assoc record :field val` | 复制整个 Record + 修改指定字段
|
|
85
|
-
| `:field record` (tag-as-fn)
|
|
103
|
+
| Calcit 操作 | WASM 实现 |
|
|
104
|
+
| --------------------------------- | -------------------------------- |
|
|
105
|
+
| `&%{} struct field1 val1 ...` | bump alloc + f64.store 每个字段 |
|
|
106
|
+
| `&record:get record :field` | `f64.load (ptr + field_offset)` |
|
|
107
|
+
| `&record:nth record idx tag` | `f64.load (ptr + (1 + idx) * 8)` |
|
|
108
|
+
| `&record:assoc record :field val` | 复制整个 Record + 修改指定字段 |
|
|
109
|
+
| `:field record` (tag-as-fn) | 等同于 `&record:get` |
|
|
86
110
|
|
|
87
111
|
### 内存分配
|
|
88
112
|
|
|
@@ -117,11 +141,11 @@ offset + 16: payload_1 (f64, 8 bytes)
|
|
|
117
141
|
|
|
118
142
|
### 操作映射
|
|
119
143
|
|
|
120
|
-
| Calcit 操作
|
|
121
|
-
|
|
122
|
-
| `:: tag val1 val2 ...` | bump alloc + store tag + payloads
|
|
123
|
-
| `&tuple:nth tuple idx` | `f64.load (ptr + (1 + idx) * 8)`
|
|
124
|
-
| `tag-match`
|
|
144
|
+
| Calcit 操作 | WASM 实现 |
|
|
145
|
+
| ---------------------- | --------------------------------------------------------------------- |
|
|
146
|
+
| `:: tag val1 val2 ...` | bump alloc + store tag + payloads |
|
|
147
|
+
| `&tuple:nth tuple idx` | `f64.load (ptr + (1 + idx) * 8)` |
|
|
148
|
+
| `tag-match` | load tag → if/else chain(小量 variants)或 `br_table`(多 variants) |
|
|
125
149
|
|
|
126
150
|
### tag-match 编译
|
|
127
151
|
|
|
@@ -150,15 +174,16 @@ List 在 Calcit 中是持久化数据结构(`Vector(Vec)` 或 `TernaryTreeList
|
|
|
150
174
|
|
|
151
175
|
### 方案对比
|
|
152
176
|
|
|
153
|
-
| 方案
|
|
154
|
-
|
|
155
|
-
| Host import (JS 侧处理)
|
|
156
|
-
| 线性内存 UTF-8
|
|
157
|
-
| WASM GC string (proposal) | 低
|
|
177
|
+
| 方案 | 复杂度 | 适用性 |
|
|
178
|
+
| ------------------------- | ------ | --------------------------- |
|
|
179
|
+
| Host import (JS 侧处理) | 低 | 适合 JS 互操作场景 |
|
|
180
|
+
| 线性内存 UTF-8 | 中 | 需要自己维护 string pool |
|
|
181
|
+
| WASM GC string (proposal) | 低 | 需要运行时支持 (V8/Deno ✅) |
|
|
158
182
|
|
|
159
183
|
### 结论
|
|
160
184
|
|
|
161
185
|
本轮不实现 String。推荐路径:
|
|
186
|
+
|
|
162
187
|
1. **近期**: host import 方案(`println` 等通过 host function 代理)
|
|
163
188
|
2. **远期**: WASM GC string 标准化后直接采用
|
|
164
189
|
|
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -43,6 +43,7 @@ export * from "./custom-formatter.mjs";
|
|
|
43
43
|
export * from "./js-cirru.mjs";
|
|
44
44
|
export * from "./js-arity-helpers.mjs";
|
|
45
45
|
export * from "./js-tag-helpers.mjs";
|
|
46
|
+
export * from "./js-buf-list.mjs";
|
|
46
47
|
export { _$n_compare } from "./js-primes.mjs";
|
|
47
48
|
|
|
48
49
|
import { CalcitList, CalcitSliceList, foldl } from "./js-list.mjs";
|
|
@@ -331,49 +332,7 @@ export function _$n_record_$o_nth(x: CalcitValue, idx: CalcitValue): CalcitValue
|
|
|
331
332
|
return x.values[i];
|
|
332
333
|
}
|
|
333
334
|
|
|
334
|
-
// === BufList — mutable append-only list ===
|
|
335
|
-
// Wrapper around a plain JS Array for O(1) push.
|
|
336
|
-
export class CalcitBufList {
|
|
337
|
-
buf: CalcitValue[];
|
|
338
|
-
constructor(buf?: CalcitValue[]) {
|
|
339
|
-
this.buf = buf ?? [];
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function _$n_buf_list_$o_new(): CalcitBufList {
|
|
344
|
-
return new CalcitBufList();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function _$n_buf_list_$o_push(buf: CalcitValue, item: CalcitValue): CalcitBufList {
|
|
348
|
-
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
349
|
-
buf.buf.push(item);
|
|
350
|
-
return buf;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export function _$n_buf_list_$o_concat(buf: CalcitValue, xs: CalcitValue): CalcitBufList {
|
|
354
|
-
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
355
|
-
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
356
|
-
const gen = xs.items();
|
|
357
|
-
let next = gen.next();
|
|
358
|
-
while (!next.done) {
|
|
359
|
-
buf.buf.push(next.value);
|
|
360
|
-
next = gen.next();
|
|
361
|
-
}
|
|
362
|
-
} else {
|
|
363
|
-
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
364
|
-
}
|
|
365
|
-
return buf;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export function _$n_buf_list_$o_to_list(buf: CalcitValue): CalcitSliceList {
|
|
369
|
-
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
370
|
-
return new CalcitSliceList([...buf.buf]);
|
|
371
|
-
}
|
|
372
335
|
|
|
373
|
-
export function _$n_buf_list_$o_count(buf: CalcitValue): number {
|
|
374
|
-
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
375
|
-
return buf.buf.length;
|
|
376
|
-
}
|
|
377
336
|
export function _$n_set_$o_count(x: CalcitValue): number {
|
|
378
337
|
if (x instanceof CalcitSet) return x.len();
|
|
379
338
|
|
|
@@ -2116,6 +2075,40 @@ export let _$n_map_$o_common_keys = (a: CalcitValue, b: CalcitValue): CalcitSet
|
|
|
2116
2075
|
}
|
|
2117
2076
|
};
|
|
2118
2077
|
|
|
2078
|
+
/** Single-pass diff: returns [drop-keys, new-diff, common-triples] in two traversals instead of 3+ */
|
|
2079
|
+
export let _$n_map_$o_diff_triple = (a: CalcitValue, b: CalcitValue): CalcitSliceList => {
|
|
2080
|
+
if ((a instanceof CalcitMap || a instanceof CalcitSliceMap) && (b instanceof CalcitMap || b instanceof CalcitSliceMap)) {
|
|
2081
|
+
let dropKeys: CalcitValue[] = [];
|
|
2082
|
+
let commonTriples: CalcitValue[] = [];
|
|
2083
|
+
|
|
2084
|
+
// One pass over a: split into drop-keys and common-triples
|
|
2085
|
+
let aKeys = a.keysArray();
|
|
2086
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
2087
|
+
let k = aKeys[i];
|
|
2088
|
+
if (b.contains(k)) {
|
|
2089
|
+
commonTriples.push(new CalcitSliceList([k, a.get(k), b.get(k)]));
|
|
2090
|
+
} else {
|
|
2091
|
+
dropKeys.push(k);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// One pass over b: collect entries not in a
|
|
2096
|
+
let newDiffPairs: CalcitValue[] = [];
|
|
2097
|
+
let bKeys = b.keysArray();
|
|
2098
|
+
for (let i = 0; i < bKeys.length; i++) {
|
|
2099
|
+
let k = bKeys[i];
|
|
2100
|
+
if (!a.contains(k)) {
|
|
2101
|
+
newDiffPairs.push(k);
|
|
2102
|
+
newDiffPairs.push(b.get(k));
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
return new CalcitSliceList([new CalcitSet(dropKeys), new CalcitSliceMap(newDiffPairs), new CalcitSliceList(commonTriples)]);
|
|
2107
|
+
} else {
|
|
2108
|
+
throw new Error("&map:diff-triple expected 2 maps");
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
|
|
2119
2112
|
export let bit_shr = (base: number, step: number): number => {
|
|
2120
2113
|
return base >> step;
|
|
2121
2114
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CalcitValue } from "./js-primes.mjs";
|
|
2
|
+
import { CalcitList, CalcitSliceList } from "./js-list.mjs";
|
|
3
|
+
|
|
4
|
+
// === CalcitBufList — mutable append-only list ===
|
|
5
|
+
// Wrapper around a plain JS Array for O(1) push.
|
|
6
|
+
// Use &buf-list:new / &buf-list:push / &buf-list:to-list in Calcit code.
|
|
7
|
+
export class CalcitBufList {
|
|
8
|
+
buf: CalcitValue[];
|
|
9
|
+
constructor(buf?: CalcitValue[]) {
|
|
10
|
+
this.buf = buf ?? [];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function _$n_buf_list_$o_new(): CalcitBufList {
|
|
15
|
+
return new CalcitBufList();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function _$n_buf_list_$o_push(buf: CalcitValue, item: CalcitValue): CalcitBufList {
|
|
19
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
20
|
+
buf.buf.push(item);
|
|
21
|
+
return buf;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function _$n_buf_list_$o_concat(buf: CalcitValue, xs: CalcitValue): CalcitBufList {
|
|
25
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
26
|
+
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
27
|
+
const gen = xs.items();
|
|
28
|
+
let next = gen.next();
|
|
29
|
+
while (!next.done) {
|
|
30
|
+
buf.buf.push(next.value);
|
|
31
|
+
next = gen.next();
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
35
|
+
}
|
|
36
|
+
return buf;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function _$n_buf_list_$o_to_list(buf: CalcitValue): CalcitSliceList {
|
|
40
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
41
|
+
return new CalcitSliceList([...buf.buf]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function _$n_buf_list_$o_count(buf: CalcitValue): number {
|
|
45
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
46
|
+
return buf.buf.length;
|
|
47
|
+
}
|