@calcit/procs 0.11.5 → 0.11.7
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/README.md +3 -3
- package/editing-history/2026-0223-0834-migrate-to-defstruct.md +91 -0
- package/editing-history/2026-0223-2321-ns-imports-code-fixes.md +42 -0
- package/editing-history/2026-0225-0002-language-behavior-eval-mode.md +93 -0
- package/editing-history/2026-0225-0111-guidebook-no-check-block-upgrades.md +52 -0
- package/editing-history/2026-0225-0113-runtime-type-validation-and-macro-quoting.md +41 -0
- package/editing-history/2026-0225-1131-check-md-eval-improvements.md +13 -0
- package/editing-history/2026-0225-1234-watch-mode-default-once.md +6 -0
- package/editing-history/2026-0226-1324-check-md-inprocess-cache-and-path-labels.md +7 -0
- package/editing-history/2026-0227-1640-unify-markdown-readers.md +16 -0
- package/editing-history/2026-0227-1958-tree-rewrite-command-and-reference-model.md +18 -0
- package/editing-history/2026-0227-2200-split-def-command.md +29 -0
- package/editing-history/2026-0227-2212-tree-raise-wrap.md +33 -0
- package/editing-history/2026-0227-2223-calcit-agent-docs-optimize.md +41 -0
- package/editing-history/2026-0301-0212-query-search-entry-and-ffi-warning.md +53 -0
- package/lib/js-record.mjs +59 -17
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/ts-src/js-record.mts +61 -19
- /package/editing-history/{2026-0215-diagnostics-consolidated.md → 2026-0215-0000-diagnostics-consolidated.md} +0 -0
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -50,11 +50,11 @@ cr eval 'thread-first 100 range (map $ \ * % %)'
|
|
|
50
50
|
Run with a [compact.cirru](https://github.com/calcit-lang/lilac/blob/main/compact.cirru):
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
cr compact.cirru
|
|
53
|
+
cr compact.cirru # run once (default)
|
|
54
54
|
|
|
55
|
-
cr
|
|
55
|
+
cr # by default, it picks `compact.cirru`
|
|
56
56
|
|
|
57
|
-
cr # watch mode
|
|
57
|
+
cr -w # watch mode (explicit flag required)
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
By default Calcit reads `:init-fn` and `:reload-fn` inside `compact.cirru` configs. You may also specify functions,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# 2026-0223-0834 — 迁移到 defstruct 方案并实现 `%{}?` 可选字段 Record 宏
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
将 Calcit Record 系统从旧的 `new-record`/`defrecord`/`defrecord!` 方案全面迁移到基于 `defstruct` 的新方案,同时新增 `%{}?` / `&%{}?` 支持可选字段初始化与更新。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 知识点一:`%{}?` 与 `&%{}?` 的语义设计
|
|
10
|
+
|
|
11
|
+
`struct` 初始化为 record 时,部分字段在语义上是可选的,对应 TypeScript 中的 `{ a?: number; b?: number }`。原有的 `%{}` / `&%{}` 要求所有字段都必须显式传入。
|
|
12
|
+
|
|
13
|
+
### `%{}?`(macro)
|
|
14
|
+
|
|
15
|
+
- 初始化 record 时允许省略字段,省略的字段自动填 `nil`。
|
|
16
|
+
- 用法:`%{}? MyRecord (:x 1)`
|
|
17
|
+
|
|
18
|
+
### `&%{}?`(proc)
|
|
19
|
+
|
|
20
|
+
`call_record_partial` 的语义:
|
|
21
|
+
|
|
22
|
+
- **proto 是 Struct**:以全 `nil` 为基础 `values`,用传入的 k-v 覆盖对应位置。
|
|
23
|
+
- 传入未知字段 → 报错;传入重复字段 → 报错;`(args_size - 1) % 2 != 0` → 报错。
|
|
24
|
+
|
|
25
|
+
关键实现细节:`CalcitStruct.fields` 元素类型是 `EdnTag`,比较时用 `f.ref_str()` 而非 `f.as_ref()`(后者无 `AsRef` impl)。
|
|
26
|
+
|
|
27
|
+
### `%{}?` 宏定义
|
|
28
|
+
|
|
29
|
+
```cirru
|
|
30
|
+
defmacro %{}? (R & xs)
|
|
31
|
+
if
|
|
32
|
+
not $ and (list? xs) (every? xs list?)
|
|
33
|
+
raise $ str-spaced "|%{}? expects field entries in list, got:" xs
|
|
34
|
+
&let
|
|
35
|
+
args $ &list:concat & xs
|
|
36
|
+
quasiquote $ &%{}? ~R ~@args
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 知识点二:Rust proc 注册流程
|
|
42
|
+
|
|
43
|
+
新增一个内置 proc 需要同时修改四处:
|
|
44
|
+
|
|
45
|
+
| 文件 | 修改内容 |
|
|
46
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
47
|
+
| `src/calcit/proc_name.rs` | 添加枚举变体 `NativeRecordPartial` + `ProcTypeSignature` |
|
|
48
|
+
| `src/builtins/records.rs` | 实现 `call_record_partial` 函数 |
|
|
49
|
+
| `src/builtins.rs` | 在 `match proc` 中添加分发分支 |
|
|
50
|
+
| `src/runner/preprocess.rs` | 将新 proc 加入"跳过 arity 检查"的 `matches!` 列表 |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 知识点三:`%{}` 严格要求 Struct proto
|
|
55
|
+
|
|
56
|
+
- `call_record`(`%{}`):若 proto 为 `Calcit::Record`,直接返回错误提示改用 `defstruct`。
|
|
57
|
+
- `defstruct` 产生 `Calcit::Struct`(含 `field_types`),并作为 `%{}` / `%{}?` 的唯一原型来源。
|
|
58
|
+
|
|
59
|
+
### `&record:get-name` / `&record:struct` 的参数约束
|
|
60
|
+
|
|
61
|
+
- 两者统一要求传入 `record`,避免 `struct` / `record` 混用语义。
|
|
62
|
+
|
|
63
|
+
### `&record:matches?` 的参数约束
|
|
64
|
+
|
|
65
|
+
- 第一参数要求 `record`。
|
|
66
|
+
- 第二参数接受 `record` 或 `struct`(用于 `record-match` 的模式匹配场景)。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 修改文件
|
|
71
|
+
|
|
72
|
+
| 文件 | 变更内容 |
|
|
73
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `src/calcit/proc_name.rs` | 新增 `NativeRecordPartial` 枚举变体与类型签名;移除 `NewRecord` 变体 |
|
|
75
|
+
| `src/builtins/records.rs` | 新增 `call_record_partial`(struct-only);`matches` 调整为 `(record, record/struct)`;移除 `new_record` 函数 |
|
|
76
|
+
| `src/builtins.rs` | 分发 `NativeRecordPartial`;移除 `NewRecord` 分发 |
|
|
77
|
+
| `src/runner/preprocess.rs` | arity 检查豁免新增 `NativeRecordPartial` |
|
|
78
|
+
| `src/cirru/calcit-core.cirru` | 新增 `%{}?` 宏定义与 `&%{}?` 文档条目;`defrecord`/`defrecord!` 改为 raise error;删除 `new-record` 定义 |
|
|
79
|
+
| `calcit/test-record.cirru` | Cat/BirdShape/Person/City/A/B/C/Demo 全部改为 `defstruct`;删除所有 `new-record` let 绑定;修复各测试函数体 |
|
|
80
|
+
| `docs/CalcitAgent.md` | 类型标注示例中 `new-record` 改为 `defstruct` |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 验证
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cargo run --bin cr -- calcit/test-record.cirru -1
|
|
88
|
+
cargo clippy -- -D warnings
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
全部通过,无 warning。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 2026-0223 命名空间 imports 操作 Bug 修复
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
通过在 `demos/compact.cirru` 上逐一测试 `cr edit` 的 namespace 相关子命令,发现三处导致 Agent 频繁用错的根本原因。
|
|
6
|
+
|
|
7
|
+
## 发现的 Bug
|
|
8
|
+
|
|
9
|
+
### Bug 1:`imports -e 'rule'` 产生平坦结构,与 `extract_require_rules` 不兼容
|
|
10
|
+
|
|
11
|
+
- **现象**:`imports -e 'respo.core :refer $ sym'` 成功,但之后执行 `add-import` 会丢失已有 imports
|
|
12
|
+
- **根因**:`handle_imports` 手工拼接 `ns_code_items`(平坦 list),产生结构:
|
|
13
|
+
```
|
|
14
|
+
["ns", "my.ns", ":require", "respo.core", ":refer", "$", "sym"]
|
|
15
|
+
```
|
|
16
|
+
而 `extract_require_rules`/`build_ns_code` 期望嵌套结构:
|
|
17
|
+
```
|
|
18
|
+
["ns", "my.ns", [":require", ["respo.core", ":refer", "$", "sym"]]]
|
|
19
|
+
```
|
|
20
|
+
导致后续任何依赖 `extract_require_rules` 的操作(`add-import`、`rm-import`)全部解析不到已有规则。
|
|
21
|
+
- **修复**:重写 `handle_imports` 的 rules 解析逻辑,统一调用 `build_ns_code` 生成嵌套结构。同时自动区分输入是单条规则(flat array of strings)还是多条规则(array of arrays)。
|
|
22
|
+
|
|
23
|
+
### Bug 2:`imports -e ':require ...'` 静默产生 `:require :require` 重复
|
|
24
|
+
|
|
25
|
+
- **现象**:写 `:require respo.core :refer $ sym` 不报错,但生成 `ns my.ns :require :require respo.core ...`
|
|
26
|
+
- **修复**:当 Cirru 解析后发现数组第一元素为 `:require` 字符串,立即返回错误消息,引导用户不包含 `:require` 前缀。
|
|
27
|
+
|
|
28
|
+
### Bug 3:`add-ns -e 'ns WRONG_NAME ...'` 名称不一致静默通过
|
|
29
|
+
|
|
30
|
+
- **现象**:`cr edit add-ns my.ns -e 'ns wrong.ns ...'` 成功,file-key 是 `my.ns`,但 ns 声明内写的是 `wrong.ns`,导致 `query ns my.ns` 看到内部名称错误
|
|
31
|
+
- **修复**:当输入解析为 `ns` 表达式时,校验第二个元素是否与位置参数一致,不一致则 Error。
|
|
32
|
+
|
|
33
|
+
## 修改文件
|
|
34
|
+
|
|
35
|
+
- `src/bin/cli_handlers/edit.rs`:`handle_imports`、`handle_add_ns` 函数
|
|
36
|
+
|
|
37
|
+
## 知识点
|
|
38
|
+
|
|
39
|
+
- `imports` 命令的 `-e` 输入格式:**不含 `:require` 前缀**,直接是规则体(`src-ns :refer $ sym`)。单条规则传平坦字符串,多条规则用 `-f` 文件(每行一条)或 `-j` JSON 数组(元素为数组)。
|
|
40
|
+
- `add-import` 和 `imports` 的格式一致,都是 `src-ns :refer $ sym`(无 `:require` 前缀)。
|
|
41
|
+
- `add-ns -e` 中若传完整 `ns` 表达式,内部名称必须与位置参数完全匹配。
|
|
42
|
+
- 最佳实践:优先使用 `add-import`(带保护和覆盖控制),`imports` 只在需要全量重置时使用。
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 2026-0225-0002 eval 模式下的语言行为要点
|
|
2
|
+
|
|
3
|
+
通过对 guidebook 文档代码块进行 eval 验证,发现以下 Calcit 运行时与预处理器的关键行为。
|
|
4
|
+
|
|
5
|
+
## `list-match` 仅适用于 List,不接受 Tuple
|
|
6
|
+
|
|
7
|
+
`list-match` 的展开宏内部调用 `&list:slice`,该函数在 `src/builtins/lists.rs` 中要求输入为 `:list` 类型。
|
|
8
|
+
若传入 `::` 创建的 tuple,预处理阶段报 `Proc &list:slice arg 1 expects type :list, but got :tuple`。
|
|
9
|
+
|
|
10
|
+
正确用法只针对 list,且分支模式是 **`(head tail)`**,`tail` 是剩余元素组成的 list(不是按位置展开多个变量):
|
|
11
|
+
|
|
12
|
+
```cirru
|
|
13
|
+
list-match ([] :point 10 20)
|
|
14
|
+
() |Empty
|
|
15
|
+
(h tl) ([] h tl)
|
|
16
|
+
; => ([] :point ([] 10 20))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## `.-field` 属性访问是 JS codegen 专用
|
|
20
|
+
|
|
21
|
+
`.-x p` 这类属性访问在 `src/codegen/emit_js.rs` 中实现,Rust 解释器(`src/runner/`)没有对应路径。
|
|
22
|
+
eval 模式下报:`method kind access (.-x) is only available in JS codegen, not supported in Rust runtime`.
|
|
23
|
+
|
|
24
|
+
替代方式:对 struct record 字段用 `(:field-name record)` 或 `get record :field-name`。
|
|
25
|
+
|
|
26
|
+
## 数值/字符串字面量在 fn 体内裸行的解析问题
|
|
27
|
+
|
|
28
|
+
`fn`/`defn` 体内,一个字面量(数值 `3.14159` 或字符串 `|demo`)若单独占一行,Cirru 解析器会把该行当作以字面量为 head 的调用列表,从而报 `unknown head 3.14159` 或 `unknown head |demo`。
|
|
29
|
+
|
|
30
|
+
修复方式:加 `, ` 前缀强制作为数据表达式(expression terminator 后接值):
|
|
31
|
+
|
|
32
|
+
```cirru
|
|
33
|
+
defn get-pi () :number
|
|
34
|
+
, 3.14159
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## `tag-match` 要求 defenum variant 的 payload 数量严格匹配
|
|
38
|
+
|
|
39
|
+
`:ok` 无 payload 类型时,`%:: E :ok 42` 传 1 个值报:`enum variant ok expects 0 payload(s), but received: 1`。
|
|
40
|
+
需在 `defenum` 定义中声明对应类型:`defenum Result (:ok :number) (:err :string)`。
|
|
41
|
+
|
|
42
|
+
该检查在 `src/runner/` 中 `%::` 调用路径执行(运行时,非预处理)。
|
|
43
|
+
|
|
44
|
+
## 变量名遮蔽 calcit.core 时触发 warning 并中止 eval
|
|
45
|
+
|
|
46
|
+
预处理器对以下 core 名字的遮蔽一律报 warning,而 eval 模式将任何 warning 视为错误:
|
|
47
|
+
|
|
48
|
+
- `first` → 遮蔽 `calcit.core/first`
|
|
49
|
+
- `rest` → 遮蔽 `calcit.core/rest`
|
|
50
|
+
- 宏参数名 `cond` → 遮蔽 `calcit.core/cond`(在 `defmacro when-not (cond & body)` 中出现)
|
|
51
|
+
|
|
52
|
+
命名建议:用 `n`, `tl`, `h`, `item` 等无冲突名。
|
|
53
|
+
|
|
54
|
+
## `let (x 1)` 单括号写法是无效语法
|
|
55
|
+
|
|
56
|
+
Cirru `let` 绑定在 `src/runner/` 中要求双层 list `((name value) ...)` 形式。
|
|
57
|
+
`let (x 1)` 单括号会得到 `expects pairs in list for let, got: ([] 'x 1)` 错误。
|
|
58
|
+
|
|
59
|
+
有效形式:
|
|
60
|
+
|
|
61
|
+
```cirru
|
|
62
|
+
; 多行缩进
|
|
63
|
+
let
|
|
64
|
+
x 1
|
|
65
|
+
x
|
|
66
|
+
|
|
67
|
+
; 单行(需双括号)
|
|
68
|
+
let ((x 1)) x
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## deftrait/defimpl/defenum/defstruct 在 let 绑定中可正常执行
|
|
72
|
+
|
|
73
|
+
所有类型定义表达式都是普通可求值的形式(返回定义值),可放入 let 绑定:
|
|
74
|
+
|
|
75
|
+
```cirru
|
|
76
|
+
let
|
|
77
|
+
MyFoo $ deftrait MyFoo
|
|
78
|
+
:foo $ :: :fn ('T) ('T) :string
|
|
79
|
+
MyFooImpl $ defimpl MyFooImpl MyFoo
|
|
80
|
+
:foo $ fn (p) (str-spaced |foo (:name p))
|
|
81
|
+
Person0 $ defstruct Person (:name :string)
|
|
82
|
+
Person $ impl-traits Person0 MyFooImpl
|
|
83
|
+
p $ %{} Person (:name |Alice)
|
|
84
|
+
.foo p
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
这使得在 eval/check-md snippet 中也能完整测试 trait dispatch,无需顶层 `ns/def` 定义。
|
|
88
|
+
`def` 是顶层定义专用,eval 模式下不可用,需替换为 `let (Name $ ...)` 形式。
|
|
89
|
+
|
|
90
|
+
## 多行类型注解 tuple 在运行时报错
|
|
91
|
+
|
|
92
|
+
`:: :fn ([] :number) :string` 的多行缩进形式中,最后 `:string` 独占一行,被运行时当作 field access 调用,导致错误。
|
|
93
|
+
紧凑单行形式 `(:: :fn ([] :number) :string)` 可运行。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Guidebook No-Check Block Upgrades (2026-02-25)
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
对 Calcit guidebook (`/Users/chenyong/repo/calcit-lang/guidebook`) 中的代码块进行系统性升级,
|
|
6
|
+
将 `cirru.no-check` 标记替换为可验证的 `cirru` 或 `cirru.no-run` 块。
|
|
7
|
+
|
|
8
|
+
## 修改范围
|
|
9
|
+
|
|
10
|
+
### 1. `docs/features/static-analysis.md`
|
|
11
|
+
|
|
12
|
+
**双重求值 Bug 修复**:
|
|
13
|
+
|
|
14
|
+
- 原始代码:`assert= a ([] "b" 2 "a" 2)` 使用了导致误报的冗余赋值写法。
|
|
15
|
+
- 修复为直接 `assert= (get xs :xs) ys` 模式,规避了 `$ (expr)` 双重求值陷阱。
|
|
16
|
+
- 将 4 个 `cirru.no-check` 块正确转换为可运行/可类型检查的 `cirru` 块。
|
|
17
|
+
|
|
18
|
+
**核心知识点**:在 `let` 绑定中 `x $ (f a)` 会将 `(f a)` 的结果再作为操作符调用,
|
|
19
|
+
必须改为 `x (f a)` 或省略 `$` 的形式。
|
|
20
|
+
|
|
21
|
+
### 2. `docs/features/tuples.md`
|
|
22
|
+
|
|
23
|
+
**伪代码替换**:
|
|
24
|
+
|
|
25
|
+
- 将 5 个含 `&tuple:with-class` 调用的 `cirru.no-check` 块全部移除或替换。
|
|
26
|
+
- `&tuple:with-class` 已从语言中删除,不再作为公共 API 存在。
|
|
27
|
+
- 涉及内部机制的 3 个伪代码块改为 `text` 或 `code` 块并加注释。
|
|
28
|
+
- 保留可运行的 `cirru` 示例块(使用 `defrecord` / `defenum` 实际语法)。
|
|
29
|
+
|
|
30
|
+
### 3. `docs/features/common-patterns.md`
|
|
31
|
+
|
|
32
|
+
- 将 3 个 `cirru.no-check` 块改为 `cirru.no-run`(语法正确但不运行的说明性代码)。
|
|
33
|
+
- 涉及 `println` 副作用型示例以及占位符 `...` 形式的模板代码。
|
|
34
|
+
|
|
35
|
+
### 4. 其他文档
|
|
36
|
+
|
|
37
|
+
- `docs/features/sets.md`, `docs/data/persistent-data.md`, `docs/features/hashmap.md` 等多处:
|
|
38
|
+
- 将说明性 `cirru.no-check` 改为 `cirru.no-run`(语法正确、无副作用但不执行)。
|
|
39
|
+
- 将可验证的示例改为 `cirru`(加入 `assert=` 或 `assert` 验证)。
|
|
40
|
+
|
|
41
|
+
## 测试结果
|
|
42
|
+
|
|
43
|
+
全量测试通过:`yarn check-all` → 188/188 ✅
|
|
44
|
+
|
|
45
|
+
## 相关知识点
|
|
46
|
+
|
|
47
|
+
- **`cirru.no-check` vs `cirru.no-run` vs `cirru`**:
|
|
48
|
+
- `cirru`:完全运行并类型检查,推断语法语义。
|
|
49
|
+
- `cirru.no-run`:语法和类型检查,但不执行(适合副作用或模板代码)。
|
|
50
|
+
- `cirru.no-check`:完全跳过检查(仅在代码不可验证时使用,如调用不存在的 API)。
|
|
51
|
+
- **`check-md` 子命令**:`cr docs check-md <file>` 用于单文件验证,加快 debug 循环。
|
|
52
|
+
- **双重求值陷阱**:`let` 中 `x $ (f a)` 等价于 `x ((f a))`,结果被再次调用,触发 "cannot be used as operator" 错误。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Runtime Type Validation & Macro Quoting Fix
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Added runtime type validation for struct record and enum tuple creation. Fixed `defstruct`/`defenum` macros to support complex type annotations (e.g., `(:optional :string)`) by conditionally quoting list-type forms in macro expansion.
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
### 1. Runtime Type Validation (`src/calcit/type_annotation.rs`)
|
|
10
|
+
|
|
11
|
+
- Added `value_matches_type_annotation(value: &Calcit, expected: &CalcitTypeAnnotation) -> bool`
|
|
12
|
+
- Checks if a runtime value matches a type annotation
|
|
13
|
+
- Handles all annotation variants: Bool, Number, String, Tag, List, Map, Set, Ref, Buffer, Tuple, Record, Struct, Enum, Trait, Optional (allows nil), Dynamic (always true), TypeVar (always true), etc.
|
|
14
|
+
- Added `brief_type_of_value(value: &Calcit) -> &'static str` for error messages
|
|
15
|
+
|
|
16
|
+
### 2. Type Checking at Record/Enum Creation
|
|
17
|
+
|
|
18
|
+
- `src/builtins/records.rs`: Validates field values against struct `field_types` in:
|
|
19
|
+
- `call_record_with_prototype` (`%{}`)
|
|
20
|
+
- `call_record_partial` (`&%{}?`)
|
|
21
|
+
- `record_with` (`&record:with`)
|
|
22
|
+
- `record_from_map` (`&record:from-map`)
|
|
23
|
+
- `src/builtins/meta.rs`: Validates enum payload values in `new_enum_tuple_no_class` (`%::`)
|
|
24
|
+
|
|
25
|
+
### 3. Macro Quoting Fix (`src/cirru/calcit-core.cirru`)
|
|
26
|
+
|
|
27
|
+
**Problem**: `defstruct` and `defenum` are macros that expand to `&struct::new`/`&enum::new` (procs). Proc arguments are fully evaluated at runtime. Complex type annotations like `(:optional :string)` were evaluated as function calls (`(:optional :string)` → `(get :string :optional)` → error).
|
|
28
|
+
|
|
29
|
+
**Fix**: Conditionally quote type annotations in the macro expansion:
|
|
30
|
+
|
|
31
|
+
- List-type forms (e.g., `(:optional :string)`) are wrapped in `(quote ...)` to prevent evaluation
|
|
32
|
+
- Tag/symbol forms (e.g., `:string`, `Status`) are left unquoted for normal evaluation/resolution
|
|
33
|
+
|
|
34
|
+
### 4. Test Updates (`calcit/test-record.cirru`)
|
|
35
|
+
|
|
36
|
+
- `Lagopus0`: `:name :string` → `:name (:optional :string)`
|
|
37
|
+
- `Person`: field types changed to `(:optional :type)` to support nil initialization
|
|
38
|
+
|
|
39
|
+
## Key Insight
|
|
40
|
+
|
|
41
|
+
`defstruct`/`defenum` are macros → expand to proc calls (`&struct::new`/`&enum::new`) → args are evaluated before the handler receives them. Nested list type annotations must be quoted to survive evaluation, but symbol type references (e.g., enum/struct names) must NOT be quoted so they resolve correctly. The fix uses `(list? type-form)` to discriminate.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# check-md and eval improvements
|
|
2
|
+
|
|
3
|
+
- Added `docs check-md --dep` (repeatable) and forwarded deps to internal eval/check-only.
|
|
4
|
+
- Added ns-first snippet handling in eval: merge `ns ...` tail nodes into `ns app.main`.
|
|
5
|
+
- Added check-md hint when `cirru` blocks are fewer than `cirru.no-check` blocks.
|
|
6
|
+
|
|
7
|
+
## 2026-02-26 follow-up
|
|
8
|
+
|
|
9
|
+
- Refactored `docs check-md` to run `cirru` / `cirru.no-run` / `cirru.no-check` in-process instead of spawning `cr` subprocesses for each block.
|
|
10
|
+
- Added shared dependency/core loading cache in `check-md` so modules and core snapshot load once per markdown file, then reuse per block.
|
|
11
|
+
- Kept warning/error details visible in failed block output while preserving pass/fail summary behavior.
|
|
12
|
+
- Simplified path display in logs and header output for default modules: replaced absolute module root with `<mods>/...` placeholder.
|
|
13
|
+
- Shortened displayed `entry` path in `check-md` header by preferring current-directory relative paths when available.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# watch mode default-once update
|
|
2
|
+
|
|
3
|
+
- Unified run mode behavior for `cr`, `cr js`, and `cr ir`: default to once.
|
|
4
|
+
- Added explicit `-w/--watch` switches for top-level direct run and `ir` subcommand.
|
|
5
|
+
- Kept `-1/--once` for backward compatibility.
|
|
6
|
+
- Updated watch-mode related docs in `Agents.md` and `docs/CalcitAgent.md`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# check-md in-process cache and path labels
|
|
2
|
+
|
|
3
|
+
- Replaced process-per-block `docs check-md` execution with in-process Rust checks for `cirru`, `cirru.no-run`, and `cirru.no-check`.
|
|
4
|
+
- Added shared cache for deps/core loading in `check-md`, reducing repeated module/core startup per markdown block.
|
|
5
|
+
- Kept warning/error details visible in block-level failure output.
|
|
6
|
+
- Replaced default module absolute path displays with `<mods>/...` in `loading:` logs and `check-md` deps preview.
|
|
7
|
+
- Normalized `check-md` entry path display to prefer current-directory relative path.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 2026-02-27 16:40
|
|
2
|
+
|
|
3
|
+
## 知识点
|
|
4
|
+
|
|
5
|
+
- 将 `docs read` / `docs agents` / `libs readme` 的 Markdown 读取行为统一到共享模块。
|
|
6
|
+
- 共享能力包括:标题提取、章节匹配、`--full`、`--with-lines`、`--no-subheadings`、提示输出与无匹配错误处理。
|
|
7
|
+
- `docs agents` 增加本地缓存刷新路径与长度展示,并复用统一渲染逻辑。
|
|
8
|
+
- `libs readme` 对齐结构化读取参数,支持本地与远程 README 的一致体验。
|
|
9
|
+
|
|
10
|
+
## 改动概要
|
|
11
|
+
|
|
12
|
+
- 新增 `src/bin/cli_handlers/markdown_read.rs`,沉淀可复用的 markdown section 渲染流程。
|
|
13
|
+
- `src/bin/cli_handlers/docs.rs` 切换为共享渲染入口,并精简参数传递结构。
|
|
14
|
+
- `src/bin/cli_handlers/libs.rs` 切换为共享渲染入口,收敛本地/远程 header 打印样板。
|
|
15
|
+
- `src/bin/cli_handlers/mod.rs` 注册新模块。
|
|
16
|
+
- `src/cli_args.rs` 对齐 `libs readme` 参数与 `docs` 系列的一致性。
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 2026-02-27 19:58
|
|
2
|
+
|
|
3
|
+
## 知识点
|
|
4
|
+
|
|
5
|
+
- `tree structural` 改名为动词化的 `tree rewrite`,更贴近“结构重写”语义,也更容易让 AI 模型理解用途。
|
|
6
|
+
- `rewrite` 明确为**引用驱动**命令:必须提供至少一个 `--with name=path`,否则应提示改用 `tree replace`。
|
|
7
|
+
- 结构引用从旧的 `--refer-*` 模型统一为 `--with` 映射模型(如 `--with self=.`, `--with rhs=2`),减少参数心智负担。
|
|
8
|
+
|
|
9
|
+
## 改动概要
|
|
10
|
+
|
|
11
|
+
- CLI 路由与文案:`TreeSubcommand::Structural` 改为 `TreeSubcommand::Rewrite`,子命令名改为 `rewrite`。
|
|
12
|
+
- `tree` handler:将 `handle_structural` 更名为 `handle_rewrite`,并更新输出提示与错误信息。
|
|
13
|
+
- 结构引用处理:在 `tree` 中统一使用 `parse_with_references` + `process_node_with_references`。
|
|
14
|
+
- 文档更新:`docs/CalcitAgent.md` 与 `Agents.md` 全部改用 `tree rewrite` 与 `--with` 示例。
|
|
15
|
+
- 真实命令验证(基于 `demos/compact.cirru`):
|
|
16
|
+
- `tree replace` 可执行普通替换;
|
|
17
|
+
- `tree rewrite` 无 `--with` 会按预期报错;
|
|
18
|
+
- `tree rewrite` 携带 `--with` 可按预期执行。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 202602272200 — 新增 `edit split-def` 命令
|
|
2
|
+
|
|
3
|
+
## 改动概要
|
|
4
|
+
|
|
5
|
+
新增 `cr edit split-def <ns/def> -p <path> -n <new-name>` 命令,用于将某定义内指定路径的子表达式提取为同命名空间内的一个新定义,原位置替换为新定义的名字。
|
|
6
|
+
|
|
7
|
+
## 修改文件
|
|
8
|
+
|
|
9
|
+
- `src/cli_args.rs`:新增 `EditSplitDefCommand` struct(参数:`target`, `-p/--path`, `-n/--name`);在 `EditSubcommand` 枚举中新增 `SplitDef(EditSplitDefCommand)` 变体。
|
|
10
|
+
- `src/bin/cli_handlers/edit.rs`:import 增加 `EditSplitDefCommand`;dispatch 增加 `EditSubcommand::SplitDef`;实现 `handle_split_def` 函数。
|
|
11
|
+
- `docs/CalcitAgent.md`:
|
|
12
|
+
1. 修复 `tree unwrap` 示例(移除已废弃的 `-i 1` 参数,更新描述为 splice 所有子节点语义)。
|
|
13
|
+
2. 在"结构化变更示例"中增加 `split-def` 的使用示例。
|
|
14
|
+
3. 在"定义操作"列表中增加 `cr edit split-def` 条目。
|
|
15
|
+
4. 新增"🔧 实战重构场景"章节,列举提取子表达式、rename、mv-def、mv/cp、unwrap/rewrite、批量批量重命名等常见重构操作的完整命令序列。
|
|
16
|
+
|
|
17
|
+
## 实现逻辑(`handle_split_def`)
|
|
18
|
+
|
|
19
|
+
1. 用 `navigate_to_path` 读取指定路径的子节点(`extracted`)。
|
|
20
|
+
2. 验证新名称在当前 ns 中不存在(不允许覆盖)。
|
|
21
|
+
3. 用 `apply_operation_at_path(..., "replace", Some(&leaf_new_name))` 将原定义的该路径替换为引用叶子节点。
|
|
22
|
+
4. 用 `CodeEntry::from_code(extracted)` 创建新定义,插入到同 ns 的 `defs` 中。
|
|
23
|
+
5. 保存 snapshot。
|
|
24
|
+
|
|
25
|
+
## 知识点
|
|
26
|
+
|
|
27
|
+
- `split-def` 只操作 AST,不会自动添加 import。如果新定义需要被其他 ns 引用,需手动 `cr edit add-import`。
|
|
28
|
+
- 路径索引规则与 `cr tree` 系列一致(逗号分隔,0-based)。
|
|
29
|
+
- 提取后如需给新定义包装成 `defn` 函数形式,用 `cr tree replace <ns/new-name> -p '' -e 'defn new-name (args...) ...'`。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 202602272212 — 新增 `tree raise` 和 `tree wrap` 命令
|
|
2
|
+
|
|
3
|
+
## 改动概要
|
|
4
|
+
|
|
5
|
+
补齐两个 Lisp 社区(Paredit)常用的结构化编辑操作,完善 `cr tree` 命令集。
|
|
6
|
+
|
|
7
|
+
## 修改文件
|
|
8
|
+
|
|
9
|
+
- `src/cli_args.rs`:新增 `TreeRaiseCommand`、`TreeWrapCommand` 结构体;在 `TreeSubcommand` 枚举中新增 `Raise`、`Wrap` 变体。
|
|
10
|
+
- `src/bin/cli_handlers/tree.rs`:import 增加两个命令结构体;dispatch 增加两个分支;新增 `handle_raise` 和 `handle_wrap` 函数。
|
|
11
|
+
- `docs/CalcitAgent.md`:更新主要操作列表、结构化变更示例、实战重构场景。
|
|
12
|
+
|
|
13
|
+
## 命令语义
|
|
14
|
+
|
|
15
|
+
### `cr tree raise <ns/def> -p <child-path>`
|
|
16
|
+
|
|
17
|
+
等价 Paredit `raise-sexp`。将指定子节点**整体替换掉其父节点**。
|
|
18
|
+
|
|
19
|
+
- `path` 必须至少一个元素(才有父节点)
|
|
20
|
+
- `parent_path = path[..n-1]`,用 `apply_operation_at_path(..., "replace", child)` 实现
|
|
21
|
+
- 典型用途:去掉 `if` 只保留某分支、去掉 `let` 只保留最终返回值表达式
|
|
22
|
+
|
|
23
|
+
### `cr tree wrap <ns/def> -p <path> -e '<template>'`
|
|
24
|
+
|
|
25
|
+
等价 `cr tree rewrite ... -w 'self=.'`,但更简洁。模板中 `self` 自动绑定为原节点。
|
|
26
|
+
|
|
27
|
+
- 适合"加一层调用"的常见模式:`wrap -e 'println self'`、`wrap -e 'let ((x self)) x'`
|
|
28
|
+
- 当需要引用原节点的**子节点**(不只是整体)时,仍需用 `rewrite --with`
|
|
29
|
+
|
|
30
|
+
## 知识点
|
|
31
|
+
|
|
32
|
+
- 三个互相对应的操作:`wrap`(包裹)→ `unwrap`(所有子节点展开)→ `raise`(单子节点替换父)
|
|
33
|
+
- `wrap` 与 `rewrite` 的关系:`wrap` = `rewrite` 固定了 `self=.` 的语法糖,降低常用操作的命令复杂度
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 202602272223 CalcitAgent.md 优化:精简冷僻内容,补充实战示例
|
|
2
|
+
|
|
3
|
+
## 修改概要
|
|
4
|
+
|
|
5
|
+
对 `docs/CalcitAgent.md` 进行了三项优化,使文档对 Agent 更加实用:
|
|
6
|
+
|
|
7
|
+
### 1. 精简 "LLM 辅助:动态方法提示" 一节
|
|
8
|
+
|
|
9
|
+
**知识点**:低频场景的文档应指向 `cr docs`,而不是在主文档中展开细节。
|
|
10
|
+
|
|
11
|
+
- 将 4 个内置函数的冗长说明(每个都有 3-4 行 `用法/用途/说明`)压缩为 4 行简要说明
|
|
12
|
+
- 末尾加 `cr docs read traits.md` 和 `cr docs search 'trait-call'` 的指引入口
|
|
13
|
+
- 减少约 300 字,信噪比更高
|
|
14
|
+
|
|
15
|
+
### 2. 扩充 "常见错误排查" 一节
|
|
16
|
+
|
|
17
|
+
**知识点**:错误排查文档需要配有"如何读懂错误"的示例,而不只是一张表格。
|
|
18
|
+
|
|
19
|
+
变更:
|
|
20
|
+
- 新增 **快速诊断流程** 子节:明确排查步骤(`cr query error` → `--check-only` → `cr eval` 隔离验证),并附带 `cr query error` 输出示例(含 unknown symbol 拼写错)
|
|
21
|
+
- 扩展错误信息对照表:从 4 条扩展到 11 条,覆盖 `unknown symbol`、`let` 语法、`cannot be used as operator`、`foldl` 参数顺序、`imports` `:require` 前缀格式等高频错误,每条均有解决命令
|
|
22
|
+
- 新增 **调试常用命令** 子节:汇总 `cr query error`、`cr eval`、`cr query find`、`cr cirru parse` 等常用排查命令
|
|
23
|
+
- 新增 `.calcit-error.cirru` 备份文件的说明(比 `cr query error` 更完整)
|
|
24
|
+
|
|
25
|
+
### 3. 新增 "🔄 完整功能开发示例" 一节
|
|
26
|
+
|
|
27
|
+
**知识点**:Agent 最常见的任务是"添加新函数",需要端到端的完整流程示例。
|
|
28
|
+
|
|
29
|
+
内容:
|
|
30
|
+
- 步骤 1: `cr query ns` / `cr query defs` / `cr query peek` 确认现有代码
|
|
31
|
+
- 步骤 2: `cr eval` 快速验证写法(含 `--dep` 加载模块的示例)
|
|
32
|
+
- 步骤 3: `cr edit def` 添加新定义
|
|
33
|
+
- 步骤 4: `cr edit add-import` 添加 import,`cr tree replace` 在调用方使用新定义
|
|
34
|
+
- 步骤 5: `cr edit inc --changed` 触发热更新 + `cr query error` 验证 + `cr --check-only` 整体检查
|
|
35
|
+
- 末尾附"常见失误快速修复"(忘记 import、拼写错误、参数顺序错误)
|
|
36
|
+
|
|
37
|
+
## 关联规则
|
|
38
|
+
|
|
39
|
+
- 冷僻/参考类内容应精简,提供 `cr docs search / read` 入口
|
|
40
|
+
- 高频场景应提供完整示例(搜索→修改→验证→热更新)
|
|
41
|
+
- 错误排查文档必须包含错误内容预览,而不只是表格
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 2026-0301-0212 query search entry + ffi warning 简化
|
|
2
|
+
|
|
3
|
+
## 修改概要
|
|
4
|
+
|
|
5
|
+
本次包含两类改动:
|
|
6
|
+
|
|
7
|
+
1. `cr query search` / `cr query search-expr` 支持 `--entry <name>`
|
|
8
|
+
2. 简化 FFI 回调接口实验性告警(去除低价值 warn)
|
|
9
|
+
|
|
10
|
+
## 知识点
|
|
11
|
+
|
|
12
|
+
### 1) search 默认依赖加载来源
|
|
13
|
+
|
|
14
|
+
`query` 之前只从 `configs.modules` 加载模块。
|
|
15
|
+
对于把依赖配置在 `entries.<name>.modules` 的项目,`search`/`search-expr` 可能漏搜。
|
|
16
|
+
|
|
17
|
+
### 2) entry 级依赖叠加策略
|
|
18
|
+
|
|
19
|
+
新增 `load_snapshot_with_entry(input_path, entry)`:
|
|
20
|
+
|
|
21
|
+
- 默认加载 `configs.modules`
|
|
22
|
+
- 指定 `--entry` 时,再叠加 `entries.<entry>.modules`
|
|
23
|
+
- 对模块路径去重(保持顺序)
|
|
24
|
+
- entry 不存在时给出可用 entries 列表
|
|
25
|
+
|
|
26
|
+
### 3) FFI 回调接口稳定性
|
|
27
|
+
|
|
28
|
+
`&call-dylib-edn-fn` 与 `&blocking-dylib-edn-fn` 原先标记为 `Experimental`,触发
|
|
29
|
+
`registered proc ... is marked as experimental` 告警。
|
|
30
|
+
|
|
31
|
+
当前 FFI 功能已稳定、该警告噪声较大,因此将这两个接口标记为 `Public`,保留参数校验与平台校验逻辑不变。
|
|
32
|
+
|
|
33
|
+
## 变更文件
|
|
34
|
+
|
|
35
|
+
- `src/cli_args.rs`
|
|
36
|
+
- 为 `QuerySearchCommand`、`QuerySearchExprCommand` 增加 `--entry` 选项
|
|
37
|
+
|
|
38
|
+
- `src/bin/cli_handlers/query.rs`
|
|
39
|
+
- search / search-expr 参数透传 `entry`
|
|
40
|
+
- 新增 `load_snapshot_with_entry`
|
|
41
|
+
- search 输出中显示 `Entry: <name>`
|
|
42
|
+
|
|
43
|
+
- `src/bin/injection/mod.rs`
|
|
44
|
+
- `&call-dylib-edn-fn` descriptor: `Experimental -> Public`
|
|
45
|
+
- `&blocking-dylib-edn-fn` descriptor: `Experimental -> Public`
|
|
46
|
+
- 注释中移除 experimental 字样
|
|
47
|
+
|
|
48
|
+
- `docs/CalcitAgent.md`
|
|
49
|
+
- 为 `query search` / `search-expr` 增加 `--entry` 说明
|
|
50
|
+
|
|
51
|
+
## 验证
|
|
52
|
+
|
|
53
|
+
- `cargo build --release --bin cr` 通过
|
package/lib/js-record.mjs
CHANGED
|
@@ -169,6 +169,34 @@ export let _$n__PCT__$M_ = (proto, ...xs) => {
|
|
|
169
169
|
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
+
export let _$n__PCT__$M__$q_ = (proto, ...xs) => {
|
|
173
|
+
let recordProto;
|
|
174
|
+
let values;
|
|
175
|
+
if (proto instanceof CalcitStruct) {
|
|
176
|
+
recordProto = new CalcitRecord(proto.name, proto.fields, new Array(proto.fields.length).fill(null), proto);
|
|
177
|
+
values = recordProto.values.slice();
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
throw new Error("Expected prototype to be a struct");
|
|
181
|
+
}
|
|
182
|
+
if (xs.length % 2 !== 0) {
|
|
183
|
+
throw new Error("Expected even number of key/value");
|
|
184
|
+
}
|
|
185
|
+
let touched = new Set();
|
|
186
|
+
for (let i = 0; i < xs.length; i += 2) {
|
|
187
|
+
let k = castTag(xs[i]);
|
|
188
|
+
let idx = findInFields(recordProto.fields, k);
|
|
189
|
+
if (idx < 0) {
|
|
190
|
+
throw new Error(`Cannot find field ${k} among ${recordProto.fields}`);
|
|
191
|
+
}
|
|
192
|
+
if (touched.has(idx)) {
|
|
193
|
+
throw new Error(`record field already has value, probably duplicated key: ${k}`);
|
|
194
|
+
}
|
|
195
|
+
touched.add(idx);
|
|
196
|
+
values[idx] = xs[i + 1];
|
|
197
|
+
}
|
|
198
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
199
|
+
};
|
|
172
200
|
/// update record with new values
|
|
173
201
|
export let _$n_record_$o_with = (proto, ...xs) => {
|
|
174
202
|
if (proto instanceof CalcitRecord) {
|
|
@@ -208,23 +236,28 @@ export let _$n_record_$o_struct = (x) => {
|
|
|
208
236
|
}
|
|
209
237
|
};
|
|
210
238
|
export let _$n_record_$o_from_map = (proto, data) => {
|
|
211
|
-
|
|
212
|
-
|
|
239
|
+
let recordProto;
|
|
240
|
+
if (proto instanceof CalcitStruct) {
|
|
241
|
+
recordProto = new CalcitRecord(proto.name, proto.fields, new Array(proto.fields.length).fill(null), proto);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
throw new Error("Expected prototype to be struct");
|
|
245
|
+
}
|
|
213
246
|
if (data instanceof CalcitRecord) {
|
|
214
|
-
if (fieldsEqual(
|
|
215
|
-
return new CalcitRecord(
|
|
247
|
+
if (fieldsEqual(recordProto.fields, data.fields)) {
|
|
248
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, data.values, recordProto.structRef);
|
|
216
249
|
}
|
|
217
250
|
else {
|
|
218
251
|
let values = [];
|
|
219
|
-
for (let i = 0; i <
|
|
220
|
-
let field =
|
|
252
|
+
for (let i = 0; i < recordProto.fields.length; i++) {
|
|
253
|
+
let field = recordProto.fields[i];
|
|
221
254
|
let idx = findInFields(data.fields, field);
|
|
222
255
|
if (idx < 0) {
|
|
223
256
|
throw new Error(`Cannot find field ${field} among ${data.fields}`);
|
|
224
257
|
}
|
|
225
258
|
values.push(data.values[idx]);
|
|
226
259
|
}
|
|
227
|
-
return new CalcitRecord(
|
|
260
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
228
261
|
}
|
|
229
262
|
}
|
|
230
263
|
else if (data instanceof CalcitMap || data instanceof CalcitSliceMap) {
|
|
@@ -238,8 +271,8 @@ export let _$n_record_$o_from_map = (proto, data) => {
|
|
|
238
271
|
// mutable sort
|
|
239
272
|
pairs_buffer.sort((pair1, pair2) => pair1[0].cmp(pair2[0]));
|
|
240
273
|
let values = [];
|
|
241
|
-
outerLoop: for (let i = 0; i <
|
|
242
|
-
let field =
|
|
274
|
+
outerLoop: for (let i = 0; i < recordProto.fields.length; i++) {
|
|
275
|
+
let field = recordProto.fields[i];
|
|
243
276
|
for (let idx = 0; idx < pairs_buffer.length; idx++) {
|
|
244
277
|
let pair = pairs_buffer[idx];
|
|
245
278
|
if (pair[0] === field) {
|
|
@@ -249,7 +282,7 @@ export let _$n_record_$o_from_map = (proto, data) => {
|
|
|
249
282
|
}
|
|
250
283
|
throw new Error(`Cannot find field ${field} among ${pairs_buffer}`);
|
|
251
284
|
}
|
|
252
|
-
return new CalcitRecord(
|
|
285
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
253
286
|
}
|
|
254
287
|
else {
|
|
255
288
|
throw new Error("Expected record or data for making a record");
|
|
@@ -268,16 +301,25 @@ export let _$n_record_$o_to_map = (x) => {
|
|
|
268
301
|
}
|
|
269
302
|
};
|
|
270
303
|
export let _$n_record_$o_matches_$q_ = (x, y) => {
|
|
271
|
-
|
|
272
|
-
|
|
304
|
+
let targetStruct;
|
|
305
|
+
if (y instanceof CalcitRecord) {
|
|
306
|
+
targetStruct = y.structRef;
|
|
273
307
|
}
|
|
274
|
-
if (
|
|
275
|
-
|
|
308
|
+
else if (y instanceof CalcitStruct) {
|
|
309
|
+
targetStruct = y;
|
|
276
310
|
}
|
|
277
|
-
|
|
278
|
-
|
|
311
|
+
else {
|
|
312
|
+
throw new Error("Expected second argument to be record or struct");
|
|
313
|
+
}
|
|
314
|
+
if (x instanceof CalcitRecord) {
|
|
315
|
+
if (x.name !== targetStruct.name) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return fieldsEqual(x.fields, targetStruct.fields);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
throw new Error("Expected first argument to be record");
|
|
279
322
|
}
|
|
280
|
-
return fieldsEqual(x.fields, y.fields);
|
|
281
323
|
};
|
|
282
324
|
export function _$n_record_$o_extend_as(obj, new_name, new_key, new_value) {
|
|
283
325
|
if (arguments.length !== 4)
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
package/ts-src/js-record.mts
CHANGED
|
@@ -176,6 +176,37 @@ export let _$n__PCT__$M_ = (proto: CalcitValue, ...xs: Array<CalcitValue>): Calc
|
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
178
|
|
|
179
|
+
export let _$n__PCT__$M__$q_ = (proto: CalcitValue, ...xs: Array<CalcitValue>): CalcitValue => {
|
|
180
|
+
let recordProto: CalcitRecord;
|
|
181
|
+
let values: Array<CalcitValue>;
|
|
182
|
+
if (proto instanceof CalcitStruct) {
|
|
183
|
+
recordProto = new CalcitRecord(proto.name, proto.fields, new Array(proto.fields.length).fill(null), proto);
|
|
184
|
+
values = recordProto.values.slice();
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error("Expected prototype to be a struct");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (xs.length % 2 !== 0) {
|
|
190
|
+
throw new Error("Expected even number of key/value");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let touched = new Set<number>();
|
|
194
|
+
for (let i = 0; i < xs.length; i += 2) {
|
|
195
|
+
let k = castTag(xs[i]);
|
|
196
|
+
let idx = findInFields(recordProto.fields, k);
|
|
197
|
+
if (idx < 0) {
|
|
198
|
+
throw new Error(`Cannot find field ${k} among ${recordProto.fields}`);
|
|
199
|
+
}
|
|
200
|
+
if (touched.has(idx)) {
|
|
201
|
+
throw new Error(`record field already has value, probably duplicated key: ${k}`);
|
|
202
|
+
}
|
|
203
|
+
touched.add(idx);
|
|
204
|
+
values[idx] = xs[i + 1];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
208
|
+
};
|
|
209
|
+
|
|
179
210
|
/// update record with new values
|
|
180
211
|
export let _$n_record_$o_with = (proto: CalcitValue, ...xs: Array<CalcitValue>): CalcitValue => {
|
|
181
212
|
if (proto instanceof CalcitRecord) {
|
|
@@ -198,7 +229,7 @@ export let _$n_record_$o_with = (proto: CalcitValue, ...xs: Array<CalcitValue>):
|
|
|
198
229
|
}
|
|
199
230
|
};
|
|
200
231
|
|
|
201
|
-
export let _$n_record_$o_get_name = (x:
|
|
232
|
+
export let _$n_record_$o_get_name = (x: CalcitValue): CalcitTag => {
|
|
202
233
|
if (x instanceof CalcitRecord) {
|
|
203
234
|
return x.name;
|
|
204
235
|
} else {
|
|
@@ -206,7 +237,7 @@ export let _$n_record_$o_get_name = (x: CalcitRecord): CalcitTag => {
|
|
|
206
237
|
}
|
|
207
238
|
};
|
|
208
239
|
|
|
209
|
-
export let _$n_record_$o_struct = (x:
|
|
240
|
+
export let _$n_record_$o_struct = (x: CalcitValue): CalcitValue => {
|
|
210
241
|
if (x instanceof CalcitRecord) {
|
|
211
242
|
return x.structRef ?? null;
|
|
212
243
|
} else {
|
|
@@ -215,22 +246,27 @@ export let _$n_record_$o_struct = (x: CalcitRecord): CalcitValue => {
|
|
|
215
246
|
};
|
|
216
247
|
|
|
217
248
|
export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): CalcitValue => {
|
|
218
|
-
|
|
249
|
+
let recordProto: CalcitRecord;
|
|
250
|
+
if (proto instanceof CalcitStruct) {
|
|
251
|
+
recordProto = new CalcitRecord(proto.name, proto.fields, new Array(proto.fields.length).fill(null), proto);
|
|
252
|
+
} else {
|
|
253
|
+
throw new Error("Expected prototype to be struct");
|
|
254
|
+
}
|
|
219
255
|
|
|
220
256
|
if (data instanceof CalcitRecord) {
|
|
221
|
-
if (fieldsEqual(
|
|
222
|
-
return new CalcitRecord(
|
|
257
|
+
if (fieldsEqual(recordProto.fields, data.fields)) {
|
|
258
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, data.values, recordProto.structRef);
|
|
223
259
|
} else {
|
|
224
260
|
let values: Array<CalcitValue> = [];
|
|
225
|
-
for (let i = 0; i <
|
|
226
|
-
let field =
|
|
261
|
+
for (let i = 0; i < recordProto.fields.length; i++) {
|
|
262
|
+
let field = recordProto.fields[i];
|
|
227
263
|
let idx = findInFields(data.fields, field);
|
|
228
264
|
if (idx < 0) {
|
|
229
265
|
throw new Error(`Cannot find field ${field} among ${data.fields}`);
|
|
230
266
|
}
|
|
231
267
|
values.push(data.values[idx]);
|
|
232
268
|
}
|
|
233
|
-
return new CalcitRecord(
|
|
269
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
234
270
|
}
|
|
235
271
|
} else if (data instanceof CalcitMap || data instanceof CalcitSliceMap) {
|
|
236
272
|
let pairs_buffer: Array<[CalcitTag, CalcitValue]> = [];
|
|
@@ -244,8 +280,8 @@ export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): Cal
|
|
|
244
280
|
pairs_buffer.sort((pair1, pair2) => pair1[0].cmp(pair2[0]));
|
|
245
281
|
|
|
246
282
|
let values: Array<CalcitValue> = [];
|
|
247
|
-
outerLoop: for (let i = 0; i <
|
|
248
|
-
let field =
|
|
283
|
+
outerLoop: for (let i = 0; i < recordProto.fields.length; i++) {
|
|
284
|
+
let field = recordProto.fields[i];
|
|
249
285
|
for (let idx = 0; idx < pairs_buffer.length; idx++) {
|
|
250
286
|
let pair = pairs_buffer[idx];
|
|
251
287
|
if (pair[0] === field) {
|
|
@@ -255,7 +291,7 @@ export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): Cal
|
|
|
255
291
|
}
|
|
256
292
|
throw new Error(`Cannot find field ${field} among ${pairs_buffer}`);
|
|
257
293
|
}
|
|
258
|
-
return new CalcitRecord(
|
|
294
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
259
295
|
} else {
|
|
260
296
|
throw new Error("Expected record or data for making a record");
|
|
261
297
|
}
|
|
@@ -274,17 +310,23 @@ export let _$n_record_$o_to_map = (x: CalcitValue): CalcitValue => {
|
|
|
274
310
|
};
|
|
275
311
|
|
|
276
312
|
export let _$n_record_$o_matches_$q_ = (x: CalcitValue, y: CalcitValue): boolean => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
313
|
+
let targetStruct: CalcitStruct;
|
|
314
|
+
if (y instanceof CalcitRecord) {
|
|
315
|
+
targetStruct = y.structRef;
|
|
316
|
+
} else if (y instanceof CalcitStruct) {
|
|
317
|
+
targetStruct = y;
|
|
318
|
+
} else {
|
|
319
|
+
throw new Error("Expected second argument to be record or struct");
|
|
282
320
|
}
|
|
283
321
|
|
|
284
|
-
if (x
|
|
285
|
-
|
|
322
|
+
if (x instanceof CalcitRecord) {
|
|
323
|
+
if (x.name !== targetStruct.name) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
return fieldsEqual(x.fields, targetStruct.fields);
|
|
327
|
+
} else {
|
|
328
|
+
throw new Error("Expected first argument to be record");
|
|
286
329
|
}
|
|
287
|
-
return fieldsEqual(x.fields, y.fields);
|
|
288
330
|
};
|
|
289
331
|
|
|
290
332
|
export function _$n_record_$o_extend_as(obj: CalcitValue, new_name: CalcitValue, new_key: CalcitValue, new_value: CalcitValue) {
|
|
File without changes
|