@calcit/procs 0.11.4 → 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) {
|
|
@@ -199,24 +227,37 @@ export let _$n_record_$o_get_name = (x) => {
|
|
|
199
227
|
throw new Error("Expected a record");
|
|
200
228
|
}
|
|
201
229
|
};
|
|
230
|
+
export let _$n_record_$o_struct = (x) => {
|
|
231
|
+
if (x instanceof CalcitRecord) {
|
|
232
|
+
return x.structRef ?? null;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
throw new Error("Expected a record");
|
|
236
|
+
}
|
|
237
|
+
};
|
|
202
238
|
export let _$n_record_$o_from_map = (proto, data) => {
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
246
|
if (data instanceof CalcitRecord) {
|
|
206
|
-
if (fieldsEqual(
|
|
207
|
-
return new CalcitRecord(
|
|
247
|
+
if (fieldsEqual(recordProto.fields, data.fields)) {
|
|
248
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, data.values, recordProto.structRef);
|
|
208
249
|
}
|
|
209
250
|
else {
|
|
210
251
|
let values = [];
|
|
211
|
-
for (let i = 0; i <
|
|
212
|
-
let field =
|
|
252
|
+
for (let i = 0; i < recordProto.fields.length; i++) {
|
|
253
|
+
let field = recordProto.fields[i];
|
|
213
254
|
let idx = findInFields(data.fields, field);
|
|
214
255
|
if (idx < 0) {
|
|
215
256
|
throw new Error(`Cannot find field ${field} among ${data.fields}`);
|
|
216
257
|
}
|
|
217
258
|
values.push(data.values[idx]);
|
|
218
259
|
}
|
|
219
|
-
return new CalcitRecord(
|
|
260
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
else if (data instanceof CalcitMap || data instanceof CalcitSliceMap) {
|
|
@@ -230,8 +271,8 @@ export let _$n_record_$o_from_map = (proto, data) => {
|
|
|
230
271
|
// mutable sort
|
|
231
272
|
pairs_buffer.sort((pair1, pair2) => pair1[0].cmp(pair2[0]));
|
|
232
273
|
let values = [];
|
|
233
|
-
outerLoop: for (let i = 0; i <
|
|
234
|
-
let field =
|
|
274
|
+
outerLoop: for (let i = 0; i < recordProto.fields.length; i++) {
|
|
275
|
+
let field = recordProto.fields[i];
|
|
235
276
|
for (let idx = 0; idx < pairs_buffer.length; idx++) {
|
|
236
277
|
let pair = pairs_buffer[idx];
|
|
237
278
|
if (pair[0] === field) {
|
|
@@ -241,7 +282,7 @@ export let _$n_record_$o_from_map = (proto, data) => {
|
|
|
241
282
|
}
|
|
242
283
|
throw new Error(`Cannot find field ${field} among ${pairs_buffer}`);
|
|
243
284
|
}
|
|
244
|
-
return new CalcitRecord(
|
|
285
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
245
286
|
}
|
|
246
287
|
else {
|
|
247
288
|
throw new Error("Expected record or data for making a record");
|
|
@@ -260,16 +301,25 @@ export let _$n_record_$o_to_map = (x) => {
|
|
|
260
301
|
}
|
|
261
302
|
};
|
|
262
303
|
export let _$n_record_$o_matches_$q_ = (x, y) => {
|
|
263
|
-
|
|
264
|
-
|
|
304
|
+
let targetStruct;
|
|
305
|
+
if (y instanceof CalcitRecord) {
|
|
306
|
+
targetStruct = y.structRef;
|
|
265
307
|
}
|
|
266
|
-
if (
|
|
267
|
-
|
|
308
|
+
else if (y instanceof CalcitStruct) {
|
|
309
|
+
targetStruct = y;
|
|
268
310
|
}
|
|
269
|
-
|
|
270
|
-
|
|
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");
|
|
271
322
|
}
|
|
272
|
-
return fieldsEqual(x.fields, y.fields);
|
|
273
323
|
};
|
|
274
324
|
export function _$n_record_$o_extend_as(obj, new_name, new_key, new_value) {
|
|
275
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,23 +237,36 @@ export let _$n_record_$o_get_name = (x: CalcitRecord): CalcitTag => {
|
|
|
206
237
|
}
|
|
207
238
|
};
|
|
208
239
|
|
|
240
|
+
export let _$n_record_$o_struct = (x: CalcitValue): CalcitValue => {
|
|
241
|
+
if (x instanceof CalcitRecord) {
|
|
242
|
+
return x.structRef ?? null;
|
|
243
|
+
} else {
|
|
244
|
+
throw new Error("Expected a record");
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
209
248
|
export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): CalcitValue => {
|
|
210
|
-
|
|
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
|
+
}
|
|
211
255
|
|
|
212
256
|
if (data instanceof CalcitRecord) {
|
|
213
|
-
if (fieldsEqual(
|
|
214
|
-
return new CalcitRecord(
|
|
257
|
+
if (fieldsEqual(recordProto.fields, data.fields)) {
|
|
258
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, data.values, recordProto.structRef);
|
|
215
259
|
} else {
|
|
216
260
|
let values: Array<CalcitValue> = [];
|
|
217
|
-
for (let i = 0; i <
|
|
218
|
-
let field =
|
|
261
|
+
for (let i = 0; i < recordProto.fields.length; i++) {
|
|
262
|
+
let field = recordProto.fields[i];
|
|
219
263
|
let idx = findInFields(data.fields, field);
|
|
220
264
|
if (idx < 0) {
|
|
221
265
|
throw new Error(`Cannot find field ${field} among ${data.fields}`);
|
|
222
266
|
}
|
|
223
267
|
values.push(data.values[idx]);
|
|
224
268
|
}
|
|
225
|
-
return new CalcitRecord(
|
|
269
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
226
270
|
}
|
|
227
271
|
} else if (data instanceof CalcitMap || data instanceof CalcitSliceMap) {
|
|
228
272
|
let pairs_buffer: Array<[CalcitTag, CalcitValue]> = [];
|
|
@@ -236,8 +280,8 @@ export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): Cal
|
|
|
236
280
|
pairs_buffer.sort((pair1, pair2) => pair1[0].cmp(pair2[0]));
|
|
237
281
|
|
|
238
282
|
let values: Array<CalcitValue> = [];
|
|
239
|
-
outerLoop: for (let i = 0; i <
|
|
240
|
-
let field =
|
|
283
|
+
outerLoop: for (let i = 0; i < recordProto.fields.length; i++) {
|
|
284
|
+
let field = recordProto.fields[i];
|
|
241
285
|
for (let idx = 0; idx < pairs_buffer.length; idx++) {
|
|
242
286
|
let pair = pairs_buffer[idx];
|
|
243
287
|
if (pair[0] === field) {
|
|
@@ -247,7 +291,7 @@ export let _$n_record_$o_from_map = (proto: CalcitValue, data: CalcitValue): Cal
|
|
|
247
291
|
}
|
|
248
292
|
throw new Error(`Cannot find field ${field} among ${pairs_buffer}`);
|
|
249
293
|
}
|
|
250
|
-
return new CalcitRecord(
|
|
294
|
+
return new CalcitRecord(recordProto.name, recordProto.fields, values, recordProto.structRef);
|
|
251
295
|
} else {
|
|
252
296
|
throw new Error("Expected record or data for making a record");
|
|
253
297
|
}
|
|
@@ -266,17 +310,23 @@ export let _$n_record_$o_to_map = (x: CalcitValue): CalcitValue => {
|
|
|
266
310
|
};
|
|
267
311
|
|
|
268
312
|
export let _$n_record_$o_matches_$q_ = (x: CalcitValue, y: CalcitValue): boolean => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
|
|
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");
|
|
274
320
|
}
|
|
275
321
|
|
|
276
|
-
if (x
|
|
277
|
-
|
|
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");
|
|
278
329
|
}
|
|
279
|
-
return fieldsEqual(x.fields, y.fields);
|
|
280
330
|
};
|
|
281
331
|
|
|
282
332
|
export function _$n_record_$o_extend_as(obj: CalcitValue, new_name: CalcitValue, new_key: CalcitValue, new_value: CalcitValue) {
|