@calcit/procs 0.10.7 → 0.10.8
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/drafts/assert-types-plan.md +1 -1
- package/drafts/assert-types.md +5 -5
- package/drafts/last-session.md +199 -0
- package/lib/calcit.procs.mjs +79 -1
- package/lib/js-tuple.mjs +5 -4
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/ts-src/calcit.procs.mts +89 -1
- package/ts-src/js-tuple.mts +6 -4
|
@@ -60,7 +60,7 @@ scope_types.insert(Arc::from("user"), Arc::new(test_record));
|
|
|
60
60
|
| 阶段 2:语法解析 | Cirru→Calcit 过程识别 `assert-type`、`hint-fn` 并保留 AST 注解 | `cr --check-only` 示例 |
|
|
61
61
|
| 阶段 3:类型传播 | `preprocess_*` 传递 `ScopeTypes` 并在 `Local` 中带上 `type_info` | demo 函数 + `&inspect-type` helper |
|
|
62
62
|
| 阶段 4:Record 静态校验 | 校验 `Record` 字段、提供错误定位、覆盖 `&record:get`/`.-field` | 合法/非法调用示例 + `cr query error` 输出 |
|
|
63
|
-
| 阶段 5:运行时与内置函数 | `&inspect-type` 原语、内置函数签名、`
|
|
63
|
+
| 阶段 5:运行时与内置函数 | `&inspect-type` 原语、内置函数签名、`dynamic` 回退策略 | 运行时示例 + 文档/测试 |
|
|
64
64
|
|
|
65
65
|
> 阶段 1~3 已交付;表格保留原路线,方便追踪阶段 4/5 的补完情况。
|
|
66
66
|
|
package/drafts/assert-types.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- 在宏展开后的 IR (Intermediate Representation) 中,为 `Local` 变量关联类型信息。
|
|
8
8
|
- 支持 `assert-type` 和 `hint-fn` 语法进行函数参数、返回值类型标记。
|
|
9
9
|
- 自动利用 `Record` 信息进行方法调用(Method Call)的静态验证。
|
|
10
|
-
- 允许 `
|
|
10
|
+
- 允许 `dynamic` 类型,并提供运行时查看手段。
|
|
11
11
|
- 为内置函数补充类型提示,保持向后兼容。
|
|
12
12
|
|
|
13
13
|
## 2. 技术方案建议
|
|
@@ -123,8 +123,8 @@ defn add-numbers (a b)
|
|
|
123
123
|
|
|
124
124
|
#### 2.0.6 未标注类型的处理
|
|
125
125
|
|
|
126
|
-
- 默认所有未标注的变量 `type_info` 为 `None`,对应 `
|
|
127
|
-
- `
|
|
126
|
+
- 默认所有未标注的变量 `type_info` 为 `None`,对应 `dynamic` 类型
|
|
127
|
+
- `dynamic` 类型不触发静态类型检查
|
|
128
128
|
- 保持向后兼容:老代码无需修改即可运行
|
|
129
129
|
|
|
130
130
|
#### 2.0.7 类型系统定位与范围
|
|
@@ -388,7 +388,7 @@ pub struct FnInfo {
|
|
|
388
388
|
|
|
389
389
|
- `return_type`: 通过 `hint-fn $ return-type :type` 声明
|
|
390
390
|
- `arg_types`: 通过 `assert-type arg :type` 在函数体内收集
|
|
391
|
-
- 默认值为 `None`,表示 `
|
|
391
|
+
- 默认值为 `None`,表示 `dynamic` 类型
|
|
392
392
|
|
|
393
393
|
### 2.2 预处理器增强 (`preprocess.rs`)
|
|
394
394
|
|
|
@@ -430,7 +430,7 @@ pub struct FnInfo {
|
|
|
430
430
|
- 逻辑运算:`not` - `bool -> bool`
|
|
431
431
|
- 类型检查:`type-of` - `any -> tag`
|
|
432
432
|
- 可以逐步扩展更多 Proc 的类型信息
|
|
433
|
-
- **兼容性**:老代码保持 `type_info` 为 `None` (即 `
|
|
433
|
+
- **兼容性**:老代码保持 `type_info` 为 `None` (即 `dynamic`),不进行强校验。
|
|
434
434
|
|
|
435
435
|
### 2.4 运行时支持
|
|
436
436
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Enum Tuple Runtime Validation - Session Summary
|
|
2
|
+
|
|
3
|
+
## 已完成的工作
|
|
4
|
+
|
|
5
|
+
### 1. Rust 侧实现
|
|
6
|
+
|
|
7
|
+
- ✅ **新增函数** (`src/builtins/meta.rs`):
|
|
8
|
+
- `new_enum_tuple()`: `%%::` 操作符,创建带 enum 元数据的 tuple 并验证
|
|
9
|
+
- `tuple_enum()`: 获取 tuple 的 enum 原型
|
|
10
|
+
- `tuple_enum_has_variant()`: 检查 enum 是否有指定 variant
|
|
11
|
+
- `tuple_enum_variant_arity()`: 获取 variant 的参数个数
|
|
12
|
+
- ✅ **验证逻辑**: 在创建时检查 tag 是否存在于 enum,arity 是否匹配
|
|
13
|
+
- ✅ **单元测试**: 23 个 Rust 单元测试全部通过
|
|
14
|
+
|
|
15
|
+
### 2. TypeScript 侧实现
|
|
16
|
+
|
|
17
|
+
- ✅ **CalcitTuple 类增强** (`ts-src/js-tuple.mts`):
|
|
18
|
+
- 添加 `enum` 属性存储 enum 原型
|
|
19
|
+
- 更新构造函数和 `assoc` 方法
|
|
20
|
+
- ✅ **新增导出函数** (`ts-src/calcit.procs.mts`):
|
|
21
|
+
- `_$n_tuple_$o_enum()`: 获取 enum 原型
|
|
22
|
+
- `_$n_tuple_$o_enum_has_variant()`: 检查 variant 存在性
|
|
23
|
+
- `_$n_tuple_$o_enum_variant_arity()`: 获取 variant arity
|
|
24
|
+
- `_PCT__PCT__$o__$o_()`: `%%::` 操作符实现
|
|
25
|
+
- ✅ **代码优化**: 使用 CalcitRecord 的 `contains()` 和 `getOrNil()` 方法
|
|
26
|
+
|
|
27
|
+
### 3. 核心库更新
|
|
28
|
+
|
|
29
|
+
- ✅ **函数定义** (`src/cirru/calcit-core.cirru`):
|
|
30
|
+
- 添加 `&tuple:enum`, `&tuple:enum-has-variant?`, `&tuple:enum-variant-arity` 标记为 `&runtime-inplementation`
|
|
31
|
+
|
|
32
|
+
### 4. 测试状态
|
|
33
|
+
|
|
34
|
+
- ✅ 完整测试套件(`yarn check-all`)全部通过
|
|
35
|
+
- ✅ Rust 解释器和 JavaScript 编译模式都正常工作
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 发现的核心问题 ⚠️
|
|
40
|
+
|
|
41
|
+
### 问题:EDN 反序列化丢失 enum 元数据
|
|
42
|
+
|
|
43
|
+
**现象:**
|
|
44
|
+
从 `compact.cirru` 加载的代码中,tuple 的 enum 元数据丢失。
|
|
45
|
+
|
|
46
|
+
**根本原因:**
|
|
47
|
+
在 `src/data/edn.rs:132`,EDN 反序列化时硬编码设置:
|
|
48
|
+
|
|
49
|
+
```rust
|
|
50
|
+
Edn::Tuple(EdnTupleView { tag, extra }) => Calcit::Tuple(CalcitTuple {
|
|
51
|
+
tag: Arc::new(edn_to_calcit(tag, options)),
|
|
52
|
+
extra: extra.iter().map(|x| edn_to_calcit(x, options)).collect(),
|
|
53
|
+
class: None,
|
|
54
|
+
sum_type: None, // ← 问题所在!
|
|
55
|
+
}),
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**影响范围:**
|
|
59
|
+
|
|
60
|
+
- ✅ **直接调用有效**: REPL、`cr eval` 中使用 `%%::` 会正常验证
|
|
61
|
+
- ❌ **文件加载失效**: 从 snapshot 加载的代码,tuple 的 `sum_type` 为 `None`
|
|
62
|
+
- ❌ **tag-match 验证无效**: 因为无法获取 enum 元数据,所以 tag-match 中的验证被跳过
|
|
63
|
+
|
|
64
|
+
**验证流程对比:**
|
|
65
|
+
|
|
66
|
+
| 场景 | enum 元数据 | 验证效果 |
|
|
67
|
+
| ---------------------------- | ----------- | ----------------- |
|
|
68
|
+
| `cr eval "(%%:: C E :ok 1)"` | ✅ 保留 | ✅ 创建时验证有效 |
|
|
69
|
+
| 从 compact.cirru 加载 | ❌ 丢失 | ❌ 无法验证 |
|
|
70
|
+
|
|
71
|
+
**证据:**
|
|
72
|
+
运行测试时 tuple 显示 `(%%:: :ok 42 (:class ResultClass) (:enum Result))`,说明序列化时保存了 enum 信息(见 `src/codegen/gen_ir.rs:380`),但反序列化时没有恢复。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 未来改进计划
|
|
77
|
+
|
|
78
|
+
### 短期方案(已实现)
|
|
79
|
+
|
|
80
|
+
- ✅ 依赖创建时验证(`%%::` 调用时)
|
|
81
|
+
- ✅ 移除了 tag-match 中无效的验证代码
|
|
82
|
+
- ✅ 确保现有功能正常工作
|
|
83
|
+
|
|
84
|
+
### 中期方案(待实现)
|
|
85
|
+
|
|
86
|
+
**修复 EDN 反序列化,恢复 enum 元数据**
|
|
87
|
+
|
|
88
|
+
需要修改的文件:
|
|
89
|
+
|
|
90
|
+
1. `src/data/edn.rs` - 反序列化逻辑
|
|
91
|
+
|
|
92
|
+
- 解析 tuple 的 `:enum` 标签
|
|
93
|
+
- 在 program/snapshot 中查找对应的 enum 定义
|
|
94
|
+
- 恢复 `sum_type` 字段
|
|
95
|
+
|
|
96
|
+
2. 可能的实现思路:
|
|
97
|
+
|
|
98
|
+
```rust
|
|
99
|
+
// 伪代码
|
|
100
|
+
Edn::Tuple(EdnTupleView { tag, extra }) => {
|
|
101
|
+
let mut tuple = CalcitTuple {
|
|
102
|
+
tag: Arc::new(edn_to_calcit(tag, options)),
|
|
103
|
+
extra: extra.iter().map(|x| edn_to_calcit(x, options)).collect(),
|
|
104
|
+
class: None,
|
|
105
|
+
sum_type: None,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 检查是否有 :enum 标签
|
|
109
|
+
if let Some(enum_name) = get_enum_tag(&extra) {
|
|
110
|
+
// 从 program 中查找 enum 定义
|
|
111
|
+
if let Some(enum_def) = program.find_enum(enum_name) {
|
|
112
|
+
tuple.sum_type = Some(Arc::new(enum_def));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Calcit::Tuple(tuple)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. 挑战:
|
|
121
|
+
- 需要访问 program/snapshot 上下文
|
|
122
|
+
- EDN 解析器目前是无状态的
|
|
123
|
+
- 可能需要重构 `edn_to_calcit` 函数签名
|
|
124
|
+
|
|
125
|
+
### 长期方案(可选)
|
|
126
|
+
|
|
127
|
+
**静态类型检查**
|
|
128
|
+
|
|
129
|
+
- 在编译/preprocessing 阶段检查 enum 使用
|
|
130
|
+
- 无需依赖运行时元数据
|
|
131
|
+
- 需要类型推导系统支持
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 当前状态
|
|
136
|
+
|
|
137
|
+
### 可用功能
|
|
138
|
+
|
|
139
|
+
- ✅ `%%:: Class Enum :tag payload...` 创建 enum tuple 并验证
|
|
140
|
+
- ✅ `&tuple:enum tuple` 获取 enum 原型(仅运行时创建的有效)
|
|
141
|
+
- ✅ `&tuple:enum-has-variant? enum tag` 检查 variant
|
|
142
|
+
- ✅ `&tuple:enum-variant-arity enum tag` 获取 arity
|
|
143
|
+
|
|
144
|
+
### 已知限制
|
|
145
|
+
|
|
146
|
+
- ⚠️ 从文件加载的代码无法进行 enum 验证
|
|
147
|
+
- ⚠️ tag-match 中无法检查 enum 信息
|
|
148
|
+
- ⚠️ 需要用户确保使用 `%%::` 创建 enum tuple
|
|
149
|
+
|
|
150
|
+
### 测试文件
|
|
151
|
+
|
|
152
|
+
- `calcit/test-sum-types.cirru` - 基本 enum 功能测试
|
|
153
|
+
- `calcit/test-enum-validation.cirru` - 创建时验证测试
|
|
154
|
+
- `calcit/test-tag-match-validation.cirru` - tag-match 验证测试(当前受限)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 相关文件
|
|
159
|
+
|
|
160
|
+
### 核心实现
|
|
161
|
+
|
|
162
|
+
- `src/builtins/meta.rs` - Rust 侧函数实现
|
|
163
|
+
- `src/calcit/proc_name.rs` - Proc 枚举定义
|
|
164
|
+
- `src/builtins.rs` - 内置函数映射
|
|
165
|
+
- `src/cirru/calcit-core.cirru` - Calcit 核心库定义
|
|
166
|
+
- `ts-src/js-tuple.mts` - TypeScript Tuple 类
|
|
167
|
+
- `ts-src/calcit.procs.mts` - TypeScript 导出函数
|
|
168
|
+
|
|
169
|
+
### 需要修改的文件
|
|
170
|
+
|
|
171
|
+
- `src/data/edn.rs` - EDN 反序列化(待修复)
|
|
172
|
+
- `src/codegen/gen_ir.rs` - IR 生成(已正确序列化 enum)
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 后续步骤
|
|
177
|
+
|
|
178
|
+
1. **确认需求优先级**:
|
|
179
|
+
|
|
180
|
+
- 是否需要立即修复 EDN 反序列化?
|
|
181
|
+
- 或者当前的创建时验证已经足够?
|
|
182
|
+
|
|
183
|
+
2. **如果修复 EDN**:
|
|
184
|
+
|
|
185
|
+
- 研究 `edn_to_calcit` 函数调用链
|
|
186
|
+
- 设计上下文传递机制
|
|
187
|
+
- 实现 enum 定义查找
|
|
188
|
+
- 添加测试验证
|
|
189
|
+
|
|
190
|
+
3. **文档更新**:
|
|
191
|
+
- 说明 enum tuple 的使用方式
|
|
192
|
+
- 注明当前限制
|
|
193
|
+
- 提供最佳实践指南
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
**最后更新**: 2026-01-15
|
|
198
|
+
**当前分支**: enum-validation
|
|
199
|
+
**测试状态**: ✅ 全部通过 (yarn check-all)
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -269,7 +269,65 @@ export let _$n_tuple_$o_with_class = function (x, y) {
|
|
|
269
269
|
throw new Error("&tuple:with-class expects a tuple");
|
|
270
270
|
if (!(y instanceof CalcitRecord))
|
|
271
271
|
throw new Error("&tuple:with-class expects second argument in record");
|
|
272
|
-
return new CalcitTuple(x.tag, x.extra, y);
|
|
272
|
+
return new CalcitTuple(x.tag, x.extra, y, x.enumPrototype);
|
|
273
|
+
};
|
|
274
|
+
export let _$n_tuple_$o_enum = function (x) {
|
|
275
|
+
if (arguments.length !== 1)
|
|
276
|
+
throw new Error("&tuple:enum takes 1 argument");
|
|
277
|
+
if (!(x instanceof CalcitTuple))
|
|
278
|
+
throw new Error("&tuple:enum expects a tuple");
|
|
279
|
+
return x.enumPrototype ?? null;
|
|
280
|
+
};
|
|
281
|
+
const assert_enum_tag_args = (procName, enumPrototype, variantTag) => {
|
|
282
|
+
if (!(enumPrototype instanceof CalcitRecord)) {
|
|
283
|
+
throw new Error(`${procName} expects enum prototype as first argument`);
|
|
284
|
+
}
|
|
285
|
+
if (!(variantTag instanceof CalcitTag)) {
|
|
286
|
+
throw new Error(`${procName} expects tag as second argument`);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
export let _$n_tuple_$o_enum_has_variant = function (enumPrototype, variantTag) {
|
|
290
|
+
if (arguments.length !== 2)
|
|
291
|
+
throw new Error("&tuple:enum-has-variant? takes 2 arguments");
|
|
292
|
+
assert_enum_tag_args("&tuple:enum-has-variant?", enumPrototype, variantTag);
|
|
293
|
+
return enumPrototype.contains(variantTag);
|
|
294
|
+
};
|
|
295
|
+
export let _$n_tuple_$o_enum_variant_arity = function (enumPrototype, variantTag) {
|
|
296
|
+
if (arguments.length !== 2)
|
|
297
|
+
throw new Error("&tuple:enum-variant-arity takes 2 arguments");
|
|
298
|
+
assert_enum_tag_args("&tuple:enum-variant-arity", enumPrototype, variantTag);
|
|
299
|
+
const variant = enumPrototype.getOrNil(variantTag);
|
|
300
|
+
if (variant === undefined) {
|
|
301
|
+
throw new Error(`Variant ${variantTag.value} not found in enum ${enumPrototype.name.value}`);
|
|
302
|
+
}
|
|
303
|
+
if (variant instanceof CalcitSliceList) {
|
|
304
|
+
return variant.len();
|
|
305
|
+
}
|
|
306
|
+
throw new Error("Expected variant to be a list");
|
|
307
|
+
};
|
|
308
|
+
export let _$n_tuple_$o_validate_enum = function (tuple, tag) {
|
|
309
|
+
if (arguments.length !== 2)
|
|
310
|
+
throw new Error("&tuple:validate-enum takes 2 arguments");
|
|
311
|
+
if (!(tuple instanceof CalcitTuple))
|
|
312
|
+
throw new Error("&tuple:validate-enum expects a tuple as first argument");
|
|
313
|
+
if (tuple.enumPrototype == null) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
assert_enum_tag_args("&tuple:validate-enum", tuple.enumPrototype, tag);
|
|
317
|
+
const tagValue = tag;
|
|
318
|
+
const variant = tuple.enumPrototype.getOrNil(tagValue);
|
|
319
|
+
if (variant === undefined) {
|
|
320
|
+
throw new Error(`enum does not have variant ${tagValue.value} for ${tuple}`);
|
|
321
|
+
}
|
|
322
|
+
if (variant instanceof CalcitSliceList) {
|
|
323
|
+
const expected = variant.len();
|
|
324
|
+
const actual = tuple.extra.length;
|
|
325
|
+
if (expected !== actual) {
|
|
326
|
+
throw new Error(`enum variant expects ${expected} payload(s), got ${actual} for ${tuple}`);
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
throw new Error("Expected variant to be a list");
|
|
273
331
|
};
|
|
274
332
|
export let _$n_record_$o_get = function (xs, k) {
|
|
275
333
|
if (arguments.length !== 2) {
|
|
@@ -1204,6 +1262,26 @@ export let _$o__$o_ = (tagName, ...extra) => {
|
|
|
1204
1262
|
export let _PCT__$o__$o_ = (klass, tag, ...extra) => {
|
|
1205
1263
|
return new CalcitTuple(tag, extra, klass);
|
|
1206
1264
|
};
|
|
1265
|
+
export let _PCT__PCT__$o__$o_ = (klass, enumPrototype, tag, ...extra) => {
|
|
1266
|
+
// Runtime validation: check if tag exists in enum and arity matches
|
|
1267
|
+
assert_enum_tag_args("%%::", enumPrototype, tag);
|
|
1268
|
+
const tagValue = tag;
|
|
1269
|
+
const variantDefinition = enumPrototype.getOrNil(tagValue);
|
|
1270
|
+
if (variantDefinition === undefined) {
|
|
1271
|
+
throw new Error(`Enum ${enumPrototype.name.value} does not have variant ${tagValue.value}`);
|
|
1272
|
+
}
|
|
1273
|
+
if (variantDefinition instanceof CalcitSliceList) {
|
|
1274
|
+
const expectedArity = variantDefinition.len();
|
|
1275
|
+
const actualArity = extra.length;
|
|
1276
|
+
if (expectedArity !== actualArity) {
|
|
1277
|
+
throw new Error(`Variant ${tagValue.value} expects ${expectedArity} payload(s), but got ${actualArity}`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
throw new Error(`Expected variant definition to be a list, got ${variantDefinition}`);
|
|
1282
|
+
}
|
|
1283
|
+
return new CalcitTuple(tag, extra, klass, enumPrototype);
|
|
1284
|
+
};
|
|
1207
1285
|
// mutable place for core to register
|
|
1208
1286
|
let calcit_builtin_classes = {
|
|
1209
1287
|
number: null,
|
package/lib/js-tuple.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { _$n__$e_, toString } from "./calcit-data.mjs";
|
|
2
2
|
import { CalcitRecord } from "./js-record.mjs";
|
|
3
3
|
export class CalcitTuple {
|
|
4
|
-
constructor(tagName, extra, klass) {
|
|
4
|
+
constructor(tagName, extra, klass, enumPrototype = null) {
|
|
5
5
|
this.tag = tagName;
|
|
6
6
|
this.extra = extra;
|
|
7
7
|
this.klass = klass;
|
|
8
|
+
this.enumPrototype = enumPrototype;
|
|
8
9
|
this.cachedHash = null;
|
|
9
10
|
}
|
|
10
11
|
get(n) {
|
|
@@ -15,17 +16,17 @@ export class CalcitTuple {
|
|
|
15
16
|
return this.extra[n - 1];
|
|
16
17
|
}
|
|
17
18
|
else {
|
|
18
|
-
throw new Error(
|
|
19
|
+
throw new Error(`Tuple only have ${this.extra.length + 1} elements`);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
assoc(n, v) {
|
|
22
23
|
if (n === 0) {
|
|
23
|
-
return new CalcitTuple(v, this.extra, this.klass);
|
|
24
|
+
return new CalcitTuple(v, this.extra, this.klass, this.enumPrototype);
|
|
24
25
|
}
|
|
25
26
|
else if (n - 1 < this.extra.length) {
|
|
26
27
|
let next_extra = this.extra.slice();
|
|
27
28
|
next_extra[n - 1] = v;
|
|
28
|
-
return new CalcitTuple(this.tag, next_extra, this.klass);
|
|
29
|
+
return new CalcitTuple(this.tag, next_extra, this.klass, this.enumPrototype);
|
|
29
30
|
}
|
|
30
31
|
else {
|
|
31
32
|
throw new Error(`Tuple only have ${this.extra.length} elements`);
|
package/lib/package.json
CHANGED
package/package.json
CHANGED
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -300,7 +300,70 @@ export let _$n_tuple_$o_with_class = function (x: CalcitTuple, y: CalcitRecord)
|
|
|
300
300
|
if (arguments.length !== 2) throw new Error("&tuple:with-class takes 2 arguments");
|
|
301
301
|
if (!(x instanceof CalcitTuple)) throw new Error("&tuple:with-class expects a tuple");
|
|
302
302
|
if (!(y instanceof CalcitRecord)) throw new Error("&tuple:with-class expects second argument in record");
|
|
303
|
-
return new CalcitTuple(x.tag, x.extra, y);
|
|
303
|
+
return new CalcitTuple(x.tag, x.extra, y, x.enumPrototype);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export let _$n_tuple_$o_enum = function (x: CalcitTuple) {
|
|
307
|
+
if (arguments.length !== 1) throw new Error("&tuple:enum takes 1 argument");
|
|
308
|
+
if (!(x instanceof CalcitTuple)) throw new Error("&tuple:enum expects a tuple");
|
|
309
|
+
return x.enumPrototype ?? null;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const assert_enum_tag_args = (procName: string, enumPrototype: CalcitRecord, variantTag: CalcitTag) => {
|
|
313
|
+
if (!(enumPrototype instanceof CalcitRecord)) {
|
|
314
|
+
throw new Error(`${procName} expects enum prototype as first argument`);
|
|
315
|
+
}
|
|
316
|
+
if (!(variantTag instanceof CalcitTag)) {
|
|
317
|
+
throw new Error(`${procName} expects tag as second argument`);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export let _$n_tuple_$o_enum_has_variant = function (enumPrototype: CalcitRecord, variantTag: CalcitTag) {
|
|
322
|
+
if (arguments.length !== 2) throw new Error("&tuple:enum-has-variant? takes 2 arguments");
|
|
323
|
+
assert_enum_tag_args("&tuple:enum-has-variant?", enumPrototype, variantTag);
|
|
324
|
+
return enumPrototype.contains(variantTag);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
export let _$n_tuple_$o_enum_variant_arity = function (enumPrototype: CalcitRecord, variantTag: CalcitTag) {
|
|
328
|
+
if (arguments.length !== 2) throw new Error("&tuple:enum-variant-arity takes 2 arguments");
|
|
329
|
+
assert_enum_tag_args("&tuple:enum-variant-arity", enumPrototype, variantTag);
|
|
330
|
+
|
|
331
|
+
const variant = enumPrototype.getOrNil(variantTag);
|
|
332
|
+
if (variant === undefined) {
|
|
333
|
+
throw new Error(`Variant ${variantTag.value} not found in enum ${enumPrototype.name.value}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (variant instanceof CalcitSliceList) {
|
|
337
|
+
return variant.len();
|
|
338
|
+
}
|
|
339
|
+
throw new Error("Expected variant to be a list");
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export let _$n_tuple_$o_validate_enum = function (tuple: CalcitValue, tag: CalcitValue): CalcitValue {
|
|
343
|
+
if (arguments.length !== 2) throw new Error("&tuple:validate-enum takes 2 arguments");
|
|
344
|
+
if (!(tuple instanceof CalcitTuple)) throw new Error("&tuple:validate-enum expects a tuple as first argument");
|
|
345
|
+
if (tuple.enumPrototype == null) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
assert_enum_tag_args("&tuple:validate-enum", tuple.enumPrototype, tag as CalcitTag);
|
|
350
|
+
|
|
351
|
+
const tagValue = tag as CalcitTag;
|
|
352
|
+
const variant = tuple.enumPrototype.getOrNil(tagValue);
|
|
353
|
+
if (variant === undefined) {
|
|
354
|
+
throw new Error(`enum does not have variant ${tagValue.value} for ${tuple}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (variant instanceof CalcitSliceList) {
|
|
358
|
+
const expected = variant.len();
|
|
359
|
+
const actual = tuple.extra.length;
|
|
360
|
+
if (expected !== actual) {
|
|
361
|
+
throw new Error(`enum variant expects ${expected} payload(s), got ${actual} for ${tuple}`);
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
throw new Error("Expected variant to be a list");
|
|
304
367
|
};
|
|
305
368
|
|
|
306
369
|
export let _$n_record_$o_get = function (xs: CalcitValue, k: CalcitTag) {
|
|
@@ -1291,6 +1354,31 @@ export let _PCT__$o__$o_ = (klass: CalcitRecord, tag: CalcitValue, ...extra: Cal
|
|
|
1291
1354
|
return new CalcitTuple(tag, extra, klass);
|
|
1292
1355
|
};
|
|
1293
1356
|
|
|
1357
|
+
export let _PCT__PCT__$o__$o_ = (klass: CalcitRecord, enumPrototype: CalcitRecord, tag: CalcitValue, ...extra: CalcitValue[]): CalcitTuple => {
|
|
1358
|
+
// Runtime validation: check if tag exists in enum and arity matches
|
|
1359
|
+
assert_enum_tag_args("%%::", enumPrototype, tag as CalcitTag);
|
|
1360
|
+
const tagValue = tag as CalcitTag;
|
|
1361
|
+
|
|
1362
|
+
const variantDefinition = enumPrototype.getOrNil(tagValue);
|
|
1363
|
+
if (variantDefinition === undefined) {
|
|
1364
|
+
throw new Error(`Enum ${enumPrototype.name.value} does not have variant ${tagValue.value}`);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
if (variantDefinition instanceof CalcitSliceList) {
|
|
1368
|
+
const expectedArity = variantDefinition.len();
|
|
1369
|
+
const actualArity = extra.length;
|
|
1370
|
+
if (expectedArity !== actualArity) {
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
`Variant ${tagValue.value} expects ${expectedArity} payload(s), but got ${actualArity}`
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
} else {
|
|
1376
|
+
throw new Error(`Expected variant definition to be a list, got ${variantDefinition}`);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
return new CalcitTuple(tag, extra, klass, enumPrototype);
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1294
1382
|
// mutable place for core to register
|
|
1295
1383
|
let calcit_builtin_classes = {
|
|
1296
1384
|
number: null as CalcitRecord,
|
package/ts-src/js-tuple.mts
CHANGED
|
@@ -8,11 +8,13 @@ export class CalcitTuple {
|
|
|
8
8
|
tag: CalcitValue;
|
|
9
9
|
extra: CalcitValue[];
|
|
10
10
|
klass: CalcitRecord;
|
|
11
|
+
enumPrototype: CalcitRecord;
|
|
11
12
|
cachedHash: Hash;
|
|
12
|
-
constructor(tagName: CalcitValue, extra: CalcitValue[], klass: CalcitRecord) {
|
|
13
|
+
constructor(tagName: CalcitValue, extra: CalcitValue[], klass: CalcitRecord, enumPrototype: CalcitRecord = null) {
|
|
13
14
|
this.tag = tagName;
|
|
14
15
|
this.extra = extra;
|
|
15
16
|
this.klass = klass;
|
|
17
|
+
this.enumPrototype = enumPrototype;
|
|
16
18
|
this.cachedHash = null;
|
|
17
19
|
}
|
|
18
20
|
get(n: number) {
|
|
@@ -21,16 +23,16 @@ export class CalcitTuple {
|
|
|
21
23
|
} else if (n - 1 < this.extra.length) {
|
|
22
24
|
return this.extra[n - 1];
|
|
23
25
|
} else {
|
|
24
|
-
throw new Error(
|
|
26
|
+
throw new Error(`Tuple only have ${this.extra.length + 1} elements`);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
assoc(n: number, v: CalcitValue) {
|
|
28
30
|
if (n === 0) {
|
|
29
|
-
return new CalcitTuple(v, this.extra, this.klass);
|
|
31
|
+
return new CalcitTuple(v, this.extra, this.klass, this.enumPrototype);
|
|
30
32
|
} else if (n - 1 < this.extra.length) {
|
|
31
33
|
let next_extra = this.extra.slice();
|
|
32
34
|
next_extra[n - 1] = v;
|
|
33
|
-
return new CalcitTuple(this.tag, next_extra, this.klass);
|
|
35
|
+
return new CalcitTuple(this.tag, next_extra, this.klass, this.enumPrototype);
|
|
34
36
|
} else {
|
|
35
37
|
throw new Error(`Tuple only have ${this.extra.length} elements`);
|
|
36
38
|
}
|