@calcit/procs 0.11.5 → 0.11.6
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
CHANGED
|
Binary file
|
|
@@ -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。
|
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) {
|