@calcit/procs 0.12.35 → 0.12.37

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.
Files changed (37) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/README.md +44 -40
  3. package/{rfc → RFCs}/03-05-function-schema-dual-track-rfc.md +11 -12
  4. package/RFCs/05-31-generic-where-bounds-mfs.md +124 -0
  5. package/RFCs/06-01-generic-binding-unification-rfc.md +608 -0
  6. package/{rfc → RFCs}/README.md +1 -0
  7. package/build.rs +74 -1
  8. package/editing-history/2026-0526-0957-match-docs-check-md.md +24 -0
  9. package/editing-history/2026-0601-0003-update-callback-field-specialization.md +19 -0
  10. package/editing-history/2026-0601-0005-release-0.12.36.md +16 -0
  11. package/editing-history/2026-0601-2230-enum-struct-payload-docs.md +4 -0
  12. package/editing-history/2026-0601-2317-data-definition-where-bounds.md +6 -0
  13. package/editing-history/2026-0602-0114-data-definition-where-macro-followup.md +21 -0
  14. package/editing-history/202605210055-tree-show-cr-config-and-edit-cleanup.md +81 -0
  15. package/editing-history/202605312052-macro-schema-roundtrip-and-weak-types.md +6 -0
  16. package/editing-history/202605312059-ci-docs-test-and-fn-helper-tightening.md +3 -0
  17. package/editing-history/202605312105-map-generic-weak-types-followup.md +15 -0
  18. package/lib/calcit.procs.mjs +60 -3
  19. package/lib/js-record.mjs +1 -1
  20. package/lib/package.json +1 -1
  21. package/package.json +1 -1
  22. package/ts-src/calcit.procs.mts +65 -4
  23. package/ts-src/js-record.mts +1 -1
  24. /package/{rfc → RFCs}/02-04-runtime-traits-plan.md +0 -0
  25. /package/{rfc → RFCs}/02-14-project-modernization-roadmap.md +0 -0
  26. /package/{rfc → RFCs}/02-17-register-platform-api-rfc.md +0 -0
  27. /package/{rfc → RFCs}/02-18-language-theory-evolution-plan.md +0 -0
  28. /package/{rfc → RFCs}/02-23-optional-record-macro-plan.md +0 -0
  29. /package/{rfc → RFCs}/03-16-runtime-boundary-refactor-plan.md +0 -0
  30. /package/{rfc → RFCs}/03-18-query-def-tree-show-chunked-display-plan.md +0 -0
  31. /package/{rfc → RFCs}/04-13-call-arg-literal-rewrite-rfc.md +0 -0
  32. /package/{rfc → RFCs}/04-13-type-slot-mechanism-rfc.md +0 -0
  33. /package/{rfc → RFCs}/04-15-match-syntax-rfc.md +0 -0
  34. /package/{rfc → RFCs}/04-15-type-directed-optimization-catalog.md +0 -0
  35. /package/{rfc → RFCs}/04-15-wasm-compilation-feasibility.md +0 -0
  36. /package/{rfc → RFCs}/04-16-wasm-data-structures.md +0 -0
  37. /package/{rfcs → RFCs}/05-12-program-diff-rfc.md +0 -0
@@ -0,0 +1,24 @@
1
+ # 2026-05-26 match 文档与 check-md 校验
2
+
3
+ ## 修改内容
4
+
5
+ - 将 `match` 的文档说明统一为同时适用于 enum tuple 和 plain tuple,不再把 `tag-match` 描述成 plain tuple 的唯一入口。
6
+ - 更新 `features/enums.md`、`features/tuples.md`、`features/common-patterns.md`、`features.md`、`quick-reference.md`、`run/agent-advanced.md` 中的推荐表述与示例。
7
+ - 为 `agent-advanced.md` 里无法独立运行的 DOM / 事件示意片段补上 `cirru.no-check`,使 `cr docs check-md` 只校验可执行代码块。
8
+
9
+ ## 验证
10
+
11
+ - `cargo run --bin cr -- eval $'match (:: :point 10 20)\n (:point x y) (+ x y)\n _ 0'`
12
+ - `cargo run --bin cr -- docs check-md docs/features.md`
13
+ - `cargo run --bin cr -- docs check-md docs/features/common-patterns.md`
14
+ - `cargo run --bin cr -- docs check-md docs/features/enums.md`
15
+ - `cargo run --bin cr -- docs check-md docs/features/error-handling.md`
16
+ - `cargo run --bin cr -- docs check-md docs/features/traits.md`
17
+ - `cargo run --bin cr -- docs check-md docs/features/tuples.md`
18
+ - `cargo run --bin cr -- docs check-md docs/quick-reference.md`
19
+ - `cargo run --bin cr -- docs check-md docs/run/agent-advanced.md`
20
+
21
+ ## 知识点
22
+
23
+ - 原生 `match` 可以直接匹配 plain tuple;它不只适用于带 `defenum` 类型信息的 tuple。
24
+ - 文档里的示意片段如果依赖 DOM helper、事件桥接函数或其他未加载上下文,应优先标记为 `cirru.no-check`,否则会被 `check-md` 的 `eval` 阻断。
@@ -0,0 +1,19 @@
1
+ # 2026-06-01 00:03 update callback field specialization
2
+
3
+ ## Summary
4
+
5
+ - specialized `calcit.core/update` call checking so the 3rd callback arg can inherit a known record/struct field type instead of always staying at generic `fn('T) -> 'T`
6
+ - fixed record field inference to resolve `TypeRef`-based struct annotations, not only direct `Record`/`Struct` annotations
7
+ - aligned native `not` proc signature with calcit-core semantics by accepting dynamic input and returning `:bool`
8
+
9
+ ## Why
10
+
11
+ - `respo` used precise type refs like `'respo.app.schema/Task`, but `infer_record_field_type` only looked through direct `Record`/`Struct` annotations, so `update task :done? not` could not see the field type
12
+ - even after specializing `update`, Rust-side proc metadata still described `not` as `fn(:bool?) -> :bool`, which was stricter and inconsistent with the core schema/docs path
13
+
14
+ ## Validation
15
+
16
+ - `cargo test specialize_update_uses_record_field_type_for_callback`
17
+ - `cargo run --manifest-path /Users/chenyong/repo/calcit-lang/calcit/Cargo.toml --bin cr -- js` from `respo`
18
+ - `yarn try-rs`
19
+ - `cargo test`
@@ -0,0 +1,16 @@
1
+ # 2026-06-01 00:05 release 0.12.36
2
+
3
+ ## Summary
4
+
5
+ - bumped crate and npm package version from `0.12.35` to `0.12.36`
6
+ - kept `ts-src/package.json` aligned with the root npm package metadata
7
+ - prepared the workspace for a release lockfile refresh and tag creation
8
+
9
+ ## Why
10
+
11
+ - this release publishes the `update` callback specialization and `not` proc typing fix after the PR checks passed
12
+ - keeping repeated package metadata in sync avoids mismatched npm publish/version outputs during future packaging steps
13
+
14
+ ## Validation
15
+
16
+ - `cargo update --workspace`
@@ -0,0 +1,4 @@
1
+ - Documented that `defenum` payloads can reference named structs and applied generic structs.
2
+ - Added runnable examples in `docs/features/enums.md` for plain struct payloads and generic struct payloads.
3
+ - Clarified that `%::` validates struct payload values and generic arity at runtime when constructing enum instances.
4
+ - Validation: `cargo run --bin cr -- demos/calcit.cirru docs check-md docs/features/enums.md`
@@ -0,0 +1,6 @@
1
+ - added where-bound metadata to struct and enum definitions, preserving it through parsing, hashing, compare, serialization, and enum prototype reconstruction
2
+ - enforced data-definition generic trait bounds at runtime for `%{}`, `%{}?`, and `%::` by inferring concrete type bindings from record fields and enum payloads
3
+ - kept builtin `Show` satisfaction narrow to builtin value families so function-level missing-impl warnings still fire correctly during preprocess
4
+ - covered the new runtime path with focused Rust tests for accepting and rejecting struct/enum payloads under generic where-bounds
5
+ - validated the calcit side with `cargo test where_bounds --lib`, `yarn check-all`, release `cr` build, and downstream respo compilation via `../../calcit-lang/calcit/target/release/cr js`
6
+ - downstream follow-up in respo needed one explicit `assert-type text :string` for the `Op::update` payload under the stricter enum payload check
@@ -0,0 +1,21 @@
1
+ ## Summary
2
+
3
+ - restored high-level `defstruct` / `defenum` where-map syntax end to end after the Rust/runtime work landed
4
+ - aligned JS runtime parsing in `ts-src/calcit.procs.mts` so `defstruct` / `defenum` skip optional generics and where-map entries before parsing fields or variants
5
+ - kept the smoke test in `calcit/test-enum.cirru` on the high-level syntax and reran broad validation
6
+
7
+ ## Knowledge Points
8
+
9
+ - the JS-emitted high-level data-definition forms do not preserve where-map heads as a stable plain symbol; the first item may be a runtime function alias or even an unresolved value shape, so detecting where-map entries by exact head identity is brittle
10
+ - a safer JS-side strategy is structural detection: after an optional generics list, treat a list as a where-map form when its head is not a tag/symbol/string and the remaining items are all 2-item pairs
11
+ - for current JS codegen, generic parameter lists arrive as a simple list of `CalcitSymbol`s, so the runtime parser can ignore that leading entry without needing full type-variable reconstruction
12
+
13
+ ## Validation
14
+
15
+ - `cargo fmt`
16
+ - `cargo test where_bounds --lib`
17
+ - `cargo run --bin cr -- calcit/test-enum.cirru`
18
+ - `yarn compile && yarn try-js`
19
+ - `yarn try-rs`
20
+ - `yarn try-ir`
21
+ - `yarn try-wasm`
@@ -0,0 +1,81 @@
1
+ # 202605210055 — tree show 可选路径 + cr config 顶级命令 + cr edit 清理
2
+
3
+ ## 修改概要
4
+
5
+ ### 1. `cr tree show` — `-p` 参数改为可选,缺失时警告而非报错
6
+
7
+ **动机**: Agent 调用 `cr tree show ns/def` 时若忘记 `-p`,原来直接报错导致 token 浪费。
8
+
9
+ **变更文件**:
10
+
11
+ - `src/cli_args.rs`: `TreeShowCommand.path` 类型从 `String` → `Option<String>`,doc 注释更新
12
+ - `src/bin/cli_handlers/tree.rs`: `handle_show` 中对 `None` 输出黄色 `[Warn]`,默认使用空路径(root)
13
+ - `src/bin/cli_handlers/command_echo.rs`: `push_tree` Show 分支改用 `opt "path"` 宏处理 Option 类型
14
+
15
+ **效果**:
16
+
17
+ ```
18
+ $ cr calcit.cirru tree show "ns/def"
19
+ [Warn] No path (-p) specified; showing from root. Use -p '0' to start from a child node.
20
+ Type: list (4 items)
21
+ ...
22
+ ```
23
+
24
+ ---
25
+
26
+ ### 2. `cr config` — 顶级配置管理命令
27
+
28
+ **动机**: `cr query config`、`cr query modules`、`cr edit config`、`cr edit add-module`、`cr edit rm-module` 分散在不同子命令下,操作配置需要记忆多个入口。新增统一入口 `cr config`。
29
+
30
+ **新命令**:
31
+ | 命令 | 等价旧命令 |
32
+ |---|---|
33
+ | `cr config show` | `cr query config` |
34
+ | `cr config modules` | `cr query modules` |
35
+ | `cr config version [value]` | `cr edit config version ...` |
36
+ | `cr config set <key> <value>` | `cr edit config <key> <value>` |
37
+ | `cr config add-module <path>` | `cr edit add-module <path>` |
38
+ | `cr config rm-module <path>` | `cr edit rm-module <path>` |
39
+
40
+ `cr config version` 可独立使用(无参数显示当前版本,传 `patch/minor/major` 递增,传 semver 直接设置)。
41
+
42
+ **变更文件**:
43
+
44
+ - `src/cli_args.rs`: 添加 `CalcitCommand::Config`、`ConfigCommand`、`ConfigSubcommand`(含 `ConfigVersionCommand`)及各子命令 struct
45
+ - `src/bin/cli_handlers/config.rs`: 新建,实现 `handle_config_command` 及各子命令处理函数(含 `handle_version`)
46
+ - `src/bin/cli_handlers/edit.rs`: `parse_semver_value`、`bump_semver_value` 改为 `pub(crate)`
47
+ - `src/bin/cli_handlers/mod.rs`: 注册 `mod config` 并 re-export `handle_config_command`
48
+ - `src/bin/cli_handlers/command_echo.rs`: 添加 `config_name`、`push_config`,并在 `should_echo_command` / `render_command_echo` 中处理 `Config` 变体
49
+ - `src/bin/cr.rs`: 添加 `CalcitCommand::Config` 分支
50
+
51
+ ---
52
+
53
+ ### 3. `cr edit` 清理 — 移除已迁移至 `cr config` 的子命令
54
+
55
+ **动机**: `cr edit add-module`、`cr edit rm-module`、`cr edit config` 与新增的 `cr config` 重复,保持单一职责。
56
+
57
+ **删除内容**:
58
+
59
+ - `EditSubcommand::AddModule / RmModule / Config` 及对应 struct(`cli_args.rs`)
60
+ - `handle_add_module / handle_rm_module / handle_config` 函数体(`edit.rs`)
61
+ - `command_echo.rs` 中 `edit_name` 和 `push_edit` 两处 match 的三个变体
62
+
63
+ **保留**(被 `config.rs` 和单元测试引用):
64
+
65
+ - `pub(crate) fn parse_semver_value` — semver 字符串解析
66
+ - `pub(crate) fn bump_semver_value` — patch/minor/major 递增
67
+
68
+ **文档更新**:
69
+
70
+ - `docs/run/agent-advanced.md` "模块和配置" 章节、快速参考示例 → 全部改为 `cr config` 命令
71
+ - `docs/CalcitAgent.md` 第 9 节"cr 能力地图" → 新增"配置管理"条目
72
+
73
+ ---
74
+
75
+ ## 知识点
76
+
77
+ - `argh` 中将 `#[argh(option)]` 字段类型改为 `Option<T>` 可使该选项变为可选
78
+ - `pub(crate)` 可在同一 crate 内跨模块共享;被其他模块引用时,不能因删除调用方而一并删除
79
+ - 新增顶级命令时需同步更新 `command_echo.rs` 的三处:`should_echo_command`、`render_command_echo`(名称映射和 token push)
80
+ - 移除 enum 变体时,需同时检查 `command_echo.rs` 的两处 match(`edit_name` 和 `push_edit`)
81
+ - 单元测试也会引用函数,删除前要 grep `tests` 模块确认
@@ -0,0 +1,6 @@
1
+ - refined weak-types reporting in `cr analyze weak-types` to classify schema/code hits with detail paths
2
+ - tightened builtin proc signatures for list/map/set/buf-list helpers toward generic compound types
3
+ - kept `field-match` / `record-match` macro schema inputs precise in `calcit-core` while preserving round-trip behavior
4
+ - fixed macro schema serialization to keep non-dynamic returns like `:record` instead of dropping them during snapshot save/load
5
+ - aligned bare marker tag parsing for `:record` and related special tags with schema normalization behavior
6
+ - updated docs and parser tests to current wrapped schema / guidebook-loading behavior
@@ -0,0 +1,3 @@
1
+ - fixed docs query test to be stable both with and without ~/.config/calcit/docs configured, matching CI runner behavior
2
+ - tightened calcit.core fn helper schemas for &fn:apply / &fn:bind / &fn:map to reduce nested schema-dynamic usage
3
+ - validated with focused docs test, full cargo test, runtime cr calcit/test.cirru, weak-types analyze, and yarn check-all
@@ -0,0 +1,15 @@
1
+ - tightened multiple calcit.core snapshot schemas that safely remove weak composite internals without narrowing runtime behavior: typed path lists for `assoc-in`/`get-in`/`dissoc-in`/`update-in`/`contains-in?`, typed collection helpers like `apply`, `join-str`, `foldr-shortcut`, and explicit output map generics for `map-kv` and `pairs-map`
2
+ - corrected one weak-types false lead by changing `keys` from returning `:list` to `:set`, matching runtime/tests and moving that hit out of the list family
3
+ - extended `cr analyze weak-types` to classify nested schema-dynamic shapes/families, include per-definition summaries, split family/shape counts by `arg`/`return` position, and show per-definition `shape@position` contributors for the remaining hotspots
4
+ - confirmed the current lower-bound weak hits under the existing type system: `&record:to-map` for heterogeneous record values; `&tuple:params`, `&cirru-quote:to-list`, `parse-cirru-list`, `&format-ternary-tree`, `&list:flatten`, `format-cirru`, and `format-cirru-one-liner` for mixed list payloads that cannot be soundly collapsed to `list<'T>`
5
+ - verified the final baseline at schema-dynamic=290 with `cargo test --bin cr analyze_weak_types -- --nocapture`, repeated `cargo run --bin cr -- calcit/test.cirru analyze weak-types --ns calcit.core --only schema-dynamic`, and runtime suite checks via `cargo run --bin cr -- calcit/test.cirru`
6
+ - aligned `parse-float` runtime behavior with the existing optional-number schema across Rust and JS: invalid input now yields `nil`/`null` instead of throwing or leaking `NaN`, and added inference coverage in `calcit/test-types-inference.cirru`
7
+ - aligned JS record missing-field behavior with Rust/eval by making `CalcitRecord.get` return `undefined` on absent fields, which keeps top-level `get` and `get-in` optional semantics consistent across runtimes
8
+ - refined preprocess return inference for `calcit.core/get` and `calcit.core/get-in`, so list/string/map/record accesses produce precise `optional<...>` results for literal paths instead of collapsing to broad `dynamic` in the common safe cases
9
+ - tightened the generic matching path so `Struct` annotations and bare `TypeRef` names bind declared type variables consistently when only one side carries applied type arguments, reducing a gap that previously skipped bindings needed by later generic specialization and `:where` checks
10
+ - validated the follow-up with `cargo run --bin cr -- calcit/test-types-inference.cirru`, `cargo test binds_generic_args_from`, `cargo fmt && cargo test binds_generic_args_from`, and `yarn try-rs`
11
+ - added RFC `RFCs/06-01-generic-binding-unification-rfc.md` to document the next step for Rust-like generic binding unification across `Struct` / `Enum` / `TypeRef`, including concrete examples, rollout stages, and the tradeoff that enum support needs deeper metadata changes than the struct-side fix
12
+ - extended the enum-generic foundation end to end: runtime enum metadata now preserves declared generics, `&enum::new` accepts generic parameter lists, and applied named enum annotations like `(:: 'ResultX :number :string)` validate against payload creation instead of being rejected as "enum cannot be generic"
13
+ - fixed the remaining struct declaration gap in the core macros/runtime boundary: `defstruct Box ([] 'T) (:value 'T)` now lowers to a real generics list for `&struct::new`, and generic field annotations are preserved as type forms instead of being evaluated away before runtime parsing
14
+ - documented the current runnable generic data-structure entry points in `docs/features/records.md` and `docs/features/enums.md`, including a high-level generic `defstruct` example and the current stable generic-enum constructor form via `&enum::new`
15
+ - validated the new docs/examples with direct eval smoke checks for the struct and enum snippets, `cargo run --bin cr -- demos/calcit.cirru docs check-md docs/features/records.md`, `cargo run --bin cr -- demos/calcit.cirru docs check-md docs/features/enums.md`, and `cargo run --bin cr -- calcit/test-generics.cirru`
@@ -103,6 +103,57 @@ const list_items = (item) => {
103
103
  }
104
104
  throw new Error(`Expected list entry, got: ${item}`);
105
105
  };
106
+ const isQuotedTypeVar = (item) => {
107
+ if (item instanceof CalcitSymbol) {
108
+ return true;
109
+ }
110
+ if (item instanceof CalcitList || item instanceof CalcitSliceList) {
111
+ const items = Array.from(item.items());
112
+ return items.length === 2 && items[0] instanceof CalcitSymbol && items[0].value === "quote" && items[1] instanceof CalcitSymbol;
113
+ }
114
+ return false;
115
+ };
116
+ const isGenericsEntry = (entry) => {
117
+ if (!(entry instanceof CalcitList || entry instanceof CalcitSliceList)) {
118
+ return false;
119
+ }
120
+ const items = Array.from(entry.items());
121
+ return items.length > 0 && items.every(isQuotedTypeVar);
122
+ };
123
+ const isWhereMapEntry = (entry) => {
124
+ // where-bound can be emitted as a CalcitMap (from _$n__$M_) or as a list-of-pairs
125
+ if (entry instanceof CalcitMap || entry instanceof CalcitSliceMap) {
126
+ return true;
127
+ }
128
+ if (!(entry instanceof CalcitList || entry instanceof CalcitSliceList)) {
129
+ return false;
130
+ }
131
+ const items = Array.from(entry.items());
132
+ if (items.length < 2) {
133
+ return false;
134
+ }
135
+ const head = items[0];
136
+ if (head instanceof CalcitTag || head instanceof CalcitSymbol || typeof head === "string") {
137
+ return false;
138
+ }
139
+ return items.slice(1).every((item) => {
140
+ if (!(item instanceof CalcitList || item instanceof CalcitSliceList)) {
141
+ return false;
142
+ }
143
+ const pair = Array.from(item.items());
144
+ return pair.length === 2;
145
+ });
146
+ };
147
+ const trimDataDefinitionEntries = (entries) => {
148
+ let start = 0;
149
+ if (entries[start] != null && isGenericsEntry(entries[start])) {
150
+ start += 1;
151
+ }
152
+ if (entries[start] != null && isWhereMapEntry(entries[start])) {
153
+ start += 1;
154
+ }
155
+ return entries.slice(start);
156
+ };
106
157
  export let _$n_trait_$o__$o_new = function (name, methods) {
107
158
  if (arguments.length !== 2)
108
159
  throw new Error("&trait::new expected 2 arguments");
@@ -172,7 +223,8 @@ export let _$n_assert_traits = function (value, traitDef) {
172
223
  export let defstruct = (name, ...entries) => {
173
224
  const structName = castTag(name);
174
225
  const fields = [];
175
- for (let entry of entries) {
226
+ const fieldEntries = trimDataDefinitionEntries(entries);
227
+ for (let entry of fieldEntries) {
176
228
  const items = list_items(entry);
177
229
  if (items.length !== 2) {
178
230
  throw new Error(`defstruct expects (field type) pairs, got: ${toString(entry, true)}`);
@@ -194,7 +246,8 @@ export let defstruct = (name, ...entries) => {
194
246
  export let defenum = (name, ...variants) => {
195
247
  const enumName = castTag(name);
196
248
  const entries = [];
197
- for (let variant of variants) {
249
+ const variantEntries = trimDataDefinitionEntries(variants);
250
+ for (let variant of variantEntries) {
198
251
  const items = list_items(variant);
199
252
  if (items.length === 0) {
200
253
  throw new Error("defenum expects variant tag and payload types, got empty list");
@@ -1183,7 +1236,11 @@ export let _$n_str_$o_find_index = (x, y) => {
1183
1236
  return x.indexOf(y);
1184
1237
  };
1185
1238
  export let parse_float = (x) => {
1186
- return parseFloat(x);
1239
+ const value = parseFloat(x);
1240
+ if (Number.isNaN(value)) {
1241
+ return null;
1242
+ }
1243
+ return value;
1187
1244
  };
1188
1245
  export let trim = (x, c) => {
1189
1246
  if (c != null) {
package/lib/js-record.mjs CHANGED
@@ -26,7 +26,7 @@ export class CalcitRecord {
26
26
  return this.values[idx];
27
27
  }
28
28
  else {
29
- throw new Error(`Cannot find :${field} among (${this.fields.join(",")})`);
29
+ return undefined;
30
30
  }
31
31
  }
32
32
  getOrNil(k) {
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.35",
3
+ "version": "0.12.37",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.35",
3
+ "version": "0.12.37",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
@@ -126,6 +126,61 @@ const list_items = (item: CalcitValue): CalcitValue[] => {
126
126
  throw new Error(`Expected list entry, got: ${item}`);
127
127
  };
128
128
 
129
+ const isQuotedTypeVar = (item: CalcitValue): boolean => {
130
+ if (item instanceof CalcitSymbol) {
131
+ return true;
132
+ }
133
+ if (item instanceof CalcitList || item instanceof CalcitSliceList) {
134
+ const items = Array.from(item.items());
135
+ return items.length === 2 && items[0] instanceof CalcitSymbol && items[0].value === "quote" && items[1] instanceof CalcitSymbol;
136
+ }
137
+ return false;
138
+ };
139
+
140
+ const isGenericsEntry = (entry: CalcitValue): boolean => {
141
+ if (!(entry instanceof CalcitList || entry instanceof CalcitSliceList)) {
142
+ return false;
143
+ }
144
+ const items = Array.from(entry.items());
145
+ return items.length > 0 && items.every(isQuotedTypeVar);
146
+ };
147
+
148
+ const isWhereMapEntry = (entry: CalcitValue): boolean => {
149
+ // where-bound can be emitted as a CalcitMap (from _$n__$M_) or as a list-of-pairs
150
+ if (entry instanceof CalcitMap || entry instanceof CalcitSliceMap) {
151
+ return true;
152
+ }
153
+ if (!(entry instanceof CalcitList || entry instanceof CalcitSliceList)) {
154
+ return false;
155
+ }
156
+ const items = Array.from(entry.items());
157
+ if (items.length < 2) {
158
+ return false;
159
+ }
160
+ const head = items[0];
161
+ if (head instanceof CalcitTag || head instanceof CalcitSymbol || typeof head === "string") {
162
+ return false;
163
+ }
164
+ return items.slice(1).every((item) => {
165
+ if (!(item instanceof CalcitList || item instanceof CalcitSliceList)) {
166
+ return false;
167
+ }
168
+ const pair = Array.from(item.items());
169
+ return pair.length === 2;
170
+ });
171
+ };
172
+
173
+ const trimDataDefinitionEntries = (entries: CalcitValue[]): CalcitValue[] => {
174
+ let start = 0;
175
+ if (entries[start] != null && isGenericsEntry(entries[start])) {
176
+ start += 1;
177
+ }
178
+ if (entries[start] != null && isWhereMapEntry(entries[start])) {
179
+ start += 1;
180
+ }
181
+ return entries.slice(start);
182
+ };
183
+
129
184
  export let _$n_trait_$o__$o_new = function (name: CalcitValue, methods: CalcitValue): CalcitTrait {
130
185
  if (arguments.length !== 2) throw new Error("&trait::new expected 2 arguments");
131
186
  const items = list_items(methods);
@@ -195,8 +250,9 @@ export let _$n_assert_traits = function (value: CalcitValue, traitDef: CalcitVal
195
250
  export let defstruct = (name: CalcitValue, ...entries: CalcitValue[]): CalcitStruct => {
196
251
  const structName = castTag(name);
197
252
  const fields: Array<{ tag: CalcitTag; type: CalcitValue }> = [];
253
+ const fieldEntries = trimDataDefinitionEntries(entries);
198
254
 
199
- for (let entry of entries) {
255
+ for (let entry of fieldEntries) {
200
256
  const items = list_items(entry);
201
257
  if (items.length !== 2) {
202
258
  throw new Error(`defstruct expects (field type) pairs, got: ${toString(entry, true)}`);
@@ -221,8 +277,9 @@ export let defstruct = (name: CalcitValue, ...entries: CalcitValue[]): CalcitStr
221
277
  export let defenum = (name: CalcitValue, ...variants: CalcitValue[]): CalcitEnum => {
222
278
  const enumName = castTag(name);
223
279
  const entries: Array<{ tag: CalcitTag; payload: CalcitSliceList }> = [];
280
+ const variantEntries = trimDataDefinitionEntries(variants);
224
281
 
225
- for (let variant of variants) {
282
+ for (let variant of variantEntries) {
226
283
  const items = list_items(variant);
227
284
  if (items.length === 0) {
228
285
  throw new Error("defenum expects variant tag and payload types, got empty list");
@@ -1283,8 +1340,12 @@ export let _$n_str_$o_find_index = (x: string, y: string): number => {
1283
1340
  return x.indexOf(y);
1284
1341
  };
1285
1342
 
1286
- export let parse_float = (x: string): number => {
1287
- return parseFloat(x);
1343
+ export let parse_float = (x: string): number | null => {
1344
+ const value = parseFloat(x);
1345
+ if (Number.isNaN(value)) {
1346
+ return null;
1347
+ }
1348
+ return value;
1288
1349
  };
1289
1350
  export let trim = (x: string, c: string): string => {
1290
1351
  if (c != null) {
@@ -34,7 +34,7 @@ export class CalcitRecord {
34
34
  if (idx >= 0) {
35
35
  return this.values[idx];
36
36
  } else {
37
- throw new Error(`Cannot find :${field} among (${this.fields.join(",")})`);
37
+ return undefined;
38
38
  }
39
39
  }
40
40
  getOrNil(k: CalcitValue) {
File without changes
File without changes
File without changes
File without changes
File without changes