@elaraai/east 1.0.83 → 1.0.85

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 (252) hide show
  1. package/dist/src/analyze.d.ts.map +1 -1
  2. package/dist/src/analyze.js +48 -0
  3. package/dist/src/analyze.js.map +1 -1
  4. package/dist/src/ast_to_ir.coerce_to.spec.js +4 -4
  5. package/dist/src/ast_to_ir.coerce_to.spec.js.map +1 -1
  6. package/dist/src/ast_to_ir.d.ts +6 -1
  7. package/dist/src/ast_to_ir.d.ts.map +1 -1
  8. package/dist/src/ast_to_ir.js +20 -7
  9. package/dist/src/ast_to_ir.js.map +1 -1
  10. package/dist/src/builtins.d.ts +1 -1
  11. package/dist/src/builtins.d.ts.map +1 -1
  12. package/dist/src/builtins.js +52 -0
  13. package/dist/src/builtins.js.map +1 -1
  14. package/dist/src/codegen/canonical.d.ts +37 -0
  15. package/dist/src/codegen/canonical.d.ts.map +1 -0
  16. package/dist/src/codegen/canonical.js +208 -0
  17. package/dist/src/codegen/canonical.js.map +1 -0
  18. package/dist/src/codegen/codegen.spec.js +132 -201
  19. package/dist/src/codegen/codegen.spec.js.map +1 -1
  20. package/dist/src/codegen/printer.d.ts.map +1 -1
  21. package/dist/src/codegen/printer.js +142 -24
  22. package/dist/src/codegen/printer.js.map +1 -1
  23. package/dist/src/codegen/spellings.d.ts +4 -1
  24. package/dist/src/codegen/spellings.d.ts.map +1 -1
  25. package/dist/src/codegen/spellings.js +5 -1
  26. package/dist/src/codegen/spellings.js.map +1 -1
  27. package/dist/src/compile/builtins/index.d.ts.map +1 -1
  28. package/dist/src/compile/builtins/index.js +2 -0
  29. package/dist/src/compile/builtins/index.js.map +1 -1
  30. package/dist/src/compile/builtins/primitives.d.ts.map +1 -1
  31. package/dist/src/compile/builtins/primitives.js +2 -1
  32. package/dist/src/compile/builtins/primitives.js.map +1 -1
  33. package/dist/src/compile/builtins/query.d.ts +10 -0
  34. package/dist/src/compile/builtins/query.d.ts.map +1 -0
  35. package/dist/src/compile/builtins/query.js +5 -0
  36. package/dist/src/compile/builtins/query.js.map +1 -0
  37. package/dist/src/expr/block.d.ts.map +1 -1
  38. package/dist/src/expr/block.js +2 -1
  39. package/dist/src/expr/block.js.map +1 -1
  40. package/dist/src/expr/index.d.ts +75 -47
  41. package/dist/src/expr/index.d.ts.map +1 -1
  42. package/dist/src/expr/index.js +31 -0
  43. package/dist/src/expr/index.js.map +1 -1
  44. package/dist/src/expr/libs/datetime.d.ts +20 -20
  45. package/dist/src/expr/libs/datetime.d.ts.map +1 -1
  46. package/dist/src/expr/libs/datetime.js +208 -167
  47. package/dist/src/expr/libs/datetime.js.map +1 -1
  48. package/dist/src/expr/libs/float.d.ts +15 -15
  49. package/dist/src/expr/libs/float.d.ts.map +1 -1
  50. package/dist/src/expr/libs/float.js +298 -267
  51. package/dist/src/expr/libs/float.js.map +1 -1
  52. package/dist/src/expr/libs/integer.d.ts +12 -12
  53. package/dist/src/expr/libs/integer.d.ts.map +1 -1
  54. package/dist/src/expr/libs/integer.js +219 -194
  55. package/dist/src/expr/libs/integer.js.map +1 -1
  56. package/dist/src/expr/libs/lazy.d.ts +28 -0
  57. package/dist/src/expr/libs/lazy.d.ts.map +1 -0
  58. package/dist/src/expr/libs/lazy.js +55 -0
  59. package/dist/src/expr/libs/lazy.js.map +1 -0
  60. package/dist/src/expr/libs/lazy.spec.d.ts +6 -0
  61. package/dist/src/expr/libs/lazy.spec.d.ts.map +1 -0
  62. package/dist/src/expr/libs/lazy.spec.js +102 -0
  63. package/dist/src/expr/libs/lazy.spec.js.map +1 -0
  64. package/dist/src/expr/query.d.ts +60 -0
  65. package/dist/src/expr/query.d.ts.map +1 -0
  66. package/dist/src/expr/query.js +84 -0
  67. package/dist/src/expr/query.js.map +1 -0
  68. package/dist/src/index.d.ts +3 -0
  69. package/dist/src/index.d.ts.map +1 -1
  70. package/dist/src/index.js +3 -0
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/internal.d.ts +4 -0
  73. package/dist/src/internal.d.ts.map +1 -1
  74. package/dist/src/internal.js +9 -0
  75. package/dist/src/internal.js.map +1 -1
  76. package/dist/src/location.d.ts +22 -0
  77. package/dist/src/location.d.ts.map +1 -1
  78. package/dist/src/location.js +31 -0
  79. package/dist/src/location.js.map +1 -1
  80. package/dist/src/naming.d.ts +12 -7
  81. package/dist/src/naming.d.ts.map +1 -1
  82. package/dist/src/naming.js +30 -13
  83. package/dist/src/naming.js.map +1 -1
  84. package/dist/src/naming.spec.js +49 -0
  85. package/dist/src/naming.spec.js.map +1 -1
  86. package/dist/src/portable.spec.d.ts +6 -0
  87. package/dist/src/portable.spec.d.ts.map +1 -0
  88. package/dist/src/portable.spec.js +181 -0
  89. package/dist/src/portable.spec.js.map +1 -0
  90. package/dist/src/query/evaluate.d.ts +98 -0
  91. package/dist/src/query/evaluate.d.ts.map +1 -0
  92. package/dist/src/query/evaluate.js +193 -0
  93. package/dist/src/query/evaluate.js.map +1 -0
  94. package/dist/src/query/index.d.ts +17 -0
  95. package/dist/src/query/index.d.ts.map +1 -0
  96. package/dist/src/query/index.js +17 -0
  97. package/dist/src/query/index.js.map +1 -0
  98. package/dist/src/query/jq/catalog.d.ts +174 -0
  99. package/dist/src/query/jq/catalog.d.ts.map +1 -0
  100. package/dist/src/query/jq/catalog.js +1740 -0
  101. package/dist/src/query/jq/catalog.js.map +1 -0
  102. package/dist/src/query/jq/check.d.ts +210 -0
  103. package/dist/src/query/jq/check.d.ts.map +1 -0
  104. package/dist/src/query/jq/check.js +2261 -0
  105. package/dist/src/query/jq/check.js.map +1 -0
  106. package/dist/src/query/jq/complete.d.ts +89 -0
  107. package/dist/src/query/jq/complete.d.ts.map +1 -0
  108. package/dist/src/query/jq/complete.js +320 -0
  109. package/dist/src/query/jq/complete.js.map +1 -0
  110. package/dist/src/query/jq/describe.d.ts +53 -0
  111. package/dist/src/query/jq/describe.d.ts.map +1 -0
  112. package/dist/src/query/jq/describe.js +149 -0
  113. package/dist/src/query/jq/describe.js.map +1 -0
  114. package/dist/src/query/jq/lex.d.ts +118 -0
  115. package/dist/src/query/jq/lex.d.ts.map +1 -0
  116. package/dist/src/query/jq/lex.js +371 -0
  117. package/dist/src/query/jq/lex.js.map +1 -0
  118. package/dist/src/query/jq/literals.d.ts +49 -0
  119. package/dist/src/query/jq/literals.d.ts.map +1 -0
  120. package/dist/src/query/jq/literals.js +78 -0
  121. package/dist/src/query/jq/literals.js.map +1 -0
  122. package/dist/src/query/jq/messages.d.ts +126 -0
  123. package/dist/src/query/jq/messages.d.ts.map +1 -0
  124. package/dist/src/query/jq/messages.js +172 -0
  125. package/dist/src/query/jq/messages.js.map +1 -0
  126. package/dist/src/query/jq/parse.d.ts +70 -0
  127. package/dist/src/query/jq/parse.d.ts.map +1 -0
  128. package/dist/src/query/jq/parse.js +1013 -0
  129. package/dist/src/query/jq/parse.js.map +1 -0
  130. package/dist/src/query/jq/print.d.ts +59 -0
  131. package/dist/src/query/jq/print.d.ts.map +1 -0
  132. package/dist/src/query/jq/print.js +587 -0
  133. package/dist/src/query/jq/print.js.map +1 -0
  134. package/dist/src/query/jq/shapes.d.ts +345 -0
  135. package/dist/src/query/jq/shapes.d.ts.map +1 -0
  136. package/dist/src/query/jq/shapes.js +584 -0
  137. package/dist/src/query/jq/shapes.js.map +1 -0
  138. package/dist/src/query/jq/spans.d.ts +133 -0
  139. package/dist/src/query/jq/spans.d.ts.map +1 -0
  140. package/dist/src/query/jq/spans.js +195 -0
  141. package/dist/src/query/jq/spans.js.map +1 -0
  142. package/dist/src/query/jq/split.d.ts +334 -0
  143. package/dist/src/query/jq/split.d.ts.map +1 -0
  144. package/dist/src/query/jq/split.js +2108 -0
  145. package/dist/src/query/jq/split.js.map +1 -0
  146. package/dist/src/query/jq/strftime.d.ts +20 -0
  147. package/dist/src/query/jq/strftime.d.ts.map +1 -0
  148. package/dist/src/query/jq/strftime.js +65 -0
  149. package/dist/src/query/jq/strftime.js.map +1 -0
  150. package/dist/src/query/jq/summary.d.ts +139 -0
  151. package/dist/src/query/jq/summary.d.ts.map +1 -0
  152. package/dist/src/query/jq/summary.js +222 -0
  153. package/dist/src/query/jq/summary.js.map +1 -0
  154. package/dist/src/query/jq/translate-builtins.d.ts +21 -0
  155. package/dist/src/query/jq/translate-builtins.d.ts.map +1 -0
  156. package/dist/src/query/jq/translate-builtins.js +1873 -0
  157. package/dist/src/query/jq/translate-builtins.js.map +1 -0
  158. package/dist/src/query/jq/translate.d.ts +627 -0
  159. package/dist/src/query/jq/translate.d.ts.map +1 -0
  160. package/dist/src/query/jq/translate.js +3364 -0
  161. package/dist/src/query/jq/translate.js.map +1 -0
  162. package/dist/src/query/types.d.ts +505 -0
  163. package/dist/src/query/types.d.ts.map +1 -0
  164. package/dist/src/query/types.js +231 -0
  165. package/dist/src/query/types.js.map +1 -0
  166. package/dist/src/runner_exec.d.ts +134 -0
  167. package/dist/src/runner_exec.d.ts.map +1 -0
  168. package/dist/src/runner_exec.js +371 -0
  169. package/dist/src/runner_exec.js.map +1 -0
  170. package/dist/src/runner_exec.spec.d.ts +6 -0
  171. package/dist/src/runner_exec.spec.d.ts.map +1 -0
  172. package/dist/src/runner_exec.spec.js +198 -0
  173. package/dist/src/runner_exec.spec.js.map +1 -0
  174. package/dist/src/runner_io.d.ts +370 -0
  175. package/dist/src/runner_io.d.ts.map +1 -0
  176. package/dist/src/runner_io.js +563 -0
  177. package/dist/src/runner_io.js.map +1 -0
  178. package/dist/src/runner_io.spec.d.ts +6 -0
  179. package/dist/src/runner_io.spec.d.ts.map +1 -0
  180. package/dist/src/runner_io.spec.js +224 -0
  181. package/dist/src/runner_io.spec.js.map +1 -0
  182. package/dist/src/runner_merge.d.ts +95 -0
  183. package/dist/src/runner_merge.d.ts.map +1 -0
  184. package/dist/src/runner_merge.js +238 -0
  185. package/dist/src/runner_merge.js.map +1 -0
  186. package/dist/src/runner_protocol.d.ts +60 -6
  187. package/dist/src/runner_protocol.d.ts.map +1 -1
  188. package/dist/src/runner_protocol.js +48 -6
  189. package/dist/src/runner_protocol.js.map +1 -1
  190. package/dist/src/serialization/beast2/index.d.ts +2 -2
  191. package/dist/src/serialization/beast2/index.d.ts.map +1 -1
  192. package/dist/src/serialization/beast2/index.js +1 -1
  193. package/dist/src/serialization/beast2/index.js.map +1 -1
  194. package/dist/src/serialization/beast2/v5/frames.d.ts +10 -6
  195. package/dist/src/serialization/beast2/v5/frames.d.ts.map +1 -1
  196. package/dist/src/serialization/beast2/v5/frames.js +10 -6
  197. package/dist/src/serialization/beast2/v5/frames.js.map +1 -1
  198. package/dist/src/serialization/beast2/v5/frames.spec.d.ts +6 -0
  199. package/dist/src/serialization/beast2/v5/frames.spec.d.ts.map +1 -0
  200. package/dist/src/serialization/beast2/v5/frames.spec.js +42 -0
  201. package/dist/src/serialization/beast2/v5/frames.spec.js.map +1 -0
  202. package/dist/src/serialization/beast2/v5/index.spec.js +108 -0
  203. package/dist/src/serialization/beast2/v5/index.spec.js.map +1 -1
  204. package/dist/src/serialization/beast2/v5/lazy.d.ts +74 -7
  205. package/dist/src/serialization/beast2/v5/lazy.d.ts.map +1 -1
  206. package/dist/src/serialization/beast2/v5/lazy.js +129 -27
  207. package/dist/src/serialization/beast2/v5/lazy.js.map +1 -1
  208. package/dist/src/serialization/beast2/v5/lazy.spec.js +251 -16
  209. package/dist/src/serialization/beast2/v5/lazy.spec.js.map +1 -1
  210. package/dist/src/serialization/beast2/v5/stream.d.ts +139 -23
  211. package/dist/src/serialization/beast2/v5/stream.d.ts.map +1 -1
  212. package/dist/src/serialization/beast2/v5/stream.js +196 -44
  213. package/dist/src/serialization/beast2/v5/stream.js.map +1 -1
  214. package/dist/src/serialization/beast2/v5/type-section.d.ts.map +1 -1
  215. package/dist/src/serialization/beast2/v5/type-section.js +25 -12
  216. package/dist/src/serialization/beast2/v5/type-section.js.map +1 -1
  217. package/dist/src/serialization/beast2/v5/type-section.spec.d.ts +6 -0
  218. package/dist/src/serialization/beast2/v5/type-section.spec.d.ts.map +1 -0
  219. package/dist/src/serialization/beast2/v5/type-section.spec.js +41 -0
  220. package/dist/src/serialization/beast2/v5/type-section.spec.js.map +1 -0
  221. package/dist/src/serialization/beast2/v5/weight.d.ts +36 -0
  222. package/dist/src/serialization/beast2/v5/weight.d.ts.map +1 -0
  223. package/dist/src/serialization/beast2/v5/weight.js +238 -0
  224. package/dist/src/serialization/beast2/v5/weight.js.map +1 -0
  225. package/dist/src/serialization/crc32.d.ts +38 -0
  226. package/dist/src/serialization/crc32.d.ts.map +1 -0
  227. package/dist/src/serialization/crc32.js +70 -0
  228. package/dist/src/serialization/crc32.js.map +1 -0
  229. package/dist/src/serialization/crc32.spec.d.ts +6 -0
  230. package/dist/src/serialization/crc32.spec.d.ts.map +1 -0
  231. package/dist/src/serialization/crc32.spec.js +92 -0
  232. package/dist/src/serialization/crc32.spec.js.map +1 -0
  233. package/dist/src/serialization/east-inferred.spec.js +4 -0
  234. package/dist/src/serialization/east-inferred.spec.js.map +1 -1
  235. package/dist/src/serialization/east.d.ts.map +1 -1
  236. package/dist/src/serialization/east.js +134 -110
  237. package/dist/src/serialization/east.js.map +1 -1
  238. package/dist/src/serialization/east.spec.js +185 -4
  239. package/dist/src/serialization/east.spec.js.map +1 -1
  240. package/dist/src/serialization/east_text.d.ts +25 -0
  241. package/dist/src/serialization/east_text.d.ts.map +1 -0
  242. package/dist/src/serialization/east_text.js +29 -0
  243. package/dist/src/serialization/east_text.js.map +1 -0
  244. package/dist/src/serialization/index.d.ts +1 -1
  245. package/dist/src/serialization/index.d.ts.map +1 -1
  246. package/dist/src/serialization/index.js +1 -1
  247. package/dist/src/serialization/index.js.map +1 -1
  248. package/dist/src/types.d.ts +1 -1
  249. package/dist/src/types.d.ts.map +1 -1
  250. package/dist/src/types.js +7 -6
  251. package/dist/src/types.js.map +1 -1
  252. package/package.json +7 -4
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * IR up to what the cross-runtime contract lets two builders differ on — the
7
+ * erasure `east-c ir normalize` makes, made in TypeScript.
8
+ *
9
+ * @internal
10
+ */
11
+ import { isVariant } from "../containers/variant.js";
12
+ /**
13
+ * The canonical form of an IR value: loc_ids zeroed, variables and labels
14
+ * renamed in the order they are bound and met, recursive type ids renumbered,
15
+ * bigints as strings. Two IR values are the same program when their
16
+ * canonical forms are {@link canonicalEqual}.
17
+ *
18
+ * @param node - an IR value (a node, or any value holding nodes)
19
+ * @returns its canonical form: plain arrays, objects and leaves
20
+ *
21
+ * @remarks
22
+ * Variables are renamed per BINDING, resolved lexically — a parameter, a
23
+ * Let, a loop, match or catch variable each mint a canonical name for the
24
+ * body they scope — not per name: sibling bodies reuse a name (three
25
+ * callbacks each naming their element `x`), and two programs that bind the
26
+ * same variable under different names are the same program. A variable no
27
+ * binding in the value reaches is `free:<name>`.
28
+ *
29
+ * @internal
30
+ */
31
+ export function canonicalIR(node) {
32
+ const labels = new Map();
33
+ const recursive = new Map();
34
+ let variables = 0;
35
+ const rename = (table, name, prefix) => {
36
+ let hit = table.get(name);
37
+ if (hit === undefined) {
38
+ hit = `${prefix}${table.size}`;
39
+ table.set(name, hit);
40
+ }
41
+ return hit;
42
+ };
43
+ /** Binds `variable` (a Variable node) in `scope`: its canonical name, minted in binding order. */
44
+ const bind = (variable, scope) => {
45
+ const name = `v${variables}`;
46
+ variables += 1;
47
+ scope.set(variable.value.name, name);
48
+ return variableNode(variable, name);
49
+ };
50
+ const variableNode = (v, name) => ["Variable", { type: walk(v.value.type, "type"), name, mutable: v.value.mutable, captured: v.value.captured }];
51
+ const lookup = (scopes, name) => {
52
+ for (let i = scopes.length - 1; i >= 0; i--) {
53
+ const hit = scopes[i].get(name);
54
+ if (hit !== undefined)
55
+ return hit;
56
+ }
57
+ return `free:${name}`;
58
+ };
59
+ // the scope chain is threaded through the walk: a body opens a scope, a Let extends the current one
60
+ let chain = [new Map()];
61
+ const inScope = (f) => {
62
+ chain = [...chain, new Map()];
63
+ try {
64
+ return f();
65
+ }
66
+ finally {
67
+ chain = chain.slice(0, -1);
68
+ }
69
+ };
70
+ const walk = (v, context) => {
71
+ if (v === null || v === undefined)
72
+ return null;
73
+ if (typeof v === "bigint")
74
+ return v.toString();
75
+ if (v instanceof Date)
76
+ return v.toISOString();
77
+ if (v instanceof Uint8Array)
78
+ return Array.from(v);
79
+ if (Array.isArray(v))
80
+ return v.map(x => walk(x, context));
81
+ if (isVariant(v)) {
82
+ const tag = v.type;
83
+ const p = v.value;
84
+ if (context === "type") {
85
+ if (tag === "Recursive") {
86
+ const payload = v.value;
87
+ if (payload.type === "ref")
88
+ return ["Recursive", ["ref", rename(recursive, String(payload.value), "r")]];
89
+ return ["Recursive", ["wrapper", { id: rename(recursive, String(payload.value.id), "r"), inner: walk(payload.value.inner, "type") }]];
90
+ }
91
+ return [tag, walk(v.value, context)];
92
+ }
93
+ switch (tag) {
94
+ case "Variable":
95
+ return variableNode(v, lookup(chain, p.name));
96
+ case "Function":
97
+ case "AsyncFunction":
98
+ return inScope(() => [tag, {
99
+ type: walk(p.type, "type"),
100
+ parameters: p.parameters.map(q => bind(q, chain[chain.length - 1])),
101
+ body: walk(p.body, context),
102
+ captures: walk(p.captures, context),
103
+ }]);
104
+ case "Let": {
105
+ const value = walk(p.value, context);
106
+ return [tag, { type: walk(p.type, "type"), value, variable: bind(p.variable, chain[chain.length - 1]) }];
107
+ }
108
+ case "ForArray":
109
+ case "ForDict":
110
+ case "ForSet": {
111
+ const source = tag === "ForArray" ? "array" : tag === "ForDict" ? "dict" : "set";
112
+ const coll = walk(p[source], context);
113
+ return inScope(() => {
114
+ const scope = chain[chain.length - 1];
115
+ const out = { type: walk(p.type, "type"), [source]: coll, label: { name: rename(labels, p.label.name, "L") } };
116
+ if (tag !== "ForSet")
117
+ out["value"] = bind(p.value, scope);
118
+ out["key"] = bind(p.key, scope);
119
+ out["body"] = walk(p.body, context);
120
+ return [tag, out];
121
+ });
122
+ }
123
+ case "Match": {
124
+ const subject = walk(p.variant, context);
125
+ const cases = p.cases.map(c => inScope(() => ({
126
+ case: c.case, variable: bind(c.variable, chain[chain.length - 1]), body: walk(c.body, context),
127
+ })));
128
+ return [tag, { type: walk(p.type, "type"), variant: subject, cases }];
129
+ }
130
+ case "TryCatch": {
131
+ const tryBody = inScope(() => walk(p.try_body, context));
132
+ const caught = inScope(() => {
133
+ const scope = chain[chain.length - 1];
134
+ const message = bind(p.message, scope);
135
+ const stack = bind(p.stack, scope);
136
+ return { message, stack, catch_body: walk(p.catch_body, context) };
137
+ });
138
+ const finallyBody = inScope(() => walk(p.finally_body, context));
139
+ return [tag, { type: walk(p.type, "type"), try_body: tryBody, ...caught, finally_body: finallyBody }];
140
+ }
141
+ case "Block":
142
+ return inScope(() => [tag, { type: walk(p.type, "type"), statements: walk(p.statements, context) }]);
143
+ case "While":
144
+ return [tag, { type: walk(p.type, "type"), predicate: walk(p.predicate, context), label: { name: rename(labels, p.label.name, "L") }, body: inScope(() => walk(p.body, context)) }];
145
+ case "IfElse":
146
+ return [tag, {
147
+ type: walk(p.type, "type"),
148
+ ifs: p.ifs.map(branch => ({ predicate: walk(branch.predicate, context), body: inScope(() => walk(branch.body, context)) })),
149
+ else_body: inScope(() => walk(p.else_body, context)),
150
+ }];
151
+ default:
152
+ return [tag, walk(v.value, context)];
153
+ }
154
+ }
155
+ if (typeof v === "object") {
156
+ const out = {};
157
+ for (const key of Object.keys(v).sort()) {
158
+ if (key === "loc_id") {
159
+ out[key] = "0";
160
+ continue;
161
+ }
162
+ if (key === "label" && typeof v[key] === "object" && v[key] !== null && "name" in v[key]) {
163
+ out[key] = { name: rename(labels, v[key].name, "L") };
164
+ continue;
165
+ }
166
+ out[key] = walk(v[key], key === "type" || key === "type_parameters" ? "type" : context);
167
+ }
168
+ return out;
169
+ }
170
+ return v;
171
+ };
172
+ return walk(node, null);
173
+ }
174
+ /**
175
+ * The first place two canonical forms differ, or `null` when they are the
176
+ * same: a leaf compares by `Object.is`, so -0.0 is not 0.0 and NaN is NaN.
177
+ *
178
+ * @param a - a canonical form ({@link canonicalIR})
179
+ * @param b - another
180
+ * @param path - where `a` and `b` sit, for the report
181
+ * @returns the path of the first difference and the two values there, or `null`
182
+ *
183
+ * @internal
184
+ */
185
+ export function canonicalDifference(a, b, path = "ir") {
186
+ if (Object.is(a, b))
187
+ return null;
188
+ if (Array.isArray(a) && Array.isArray(b)) {
189
+ if (a.length !== b.length)
190
+ return `${path}.length: ${a.length} vs ${b.length}`;
191
+ for (let i = 0; i < a.length; i++) {
192
+ const d = canonicalDifference(a[i], b[i], `${path}[${i}]`);
193
+ if (d !== null)
194
+ return d;
195
+ }
196
+ return null;
197
+ }
198
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
199
+ for (const k of [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()) {
200
+ const d = canonicalDifference(a[k], b[k], `${path}.${k}`);
201
+ if (d !== null)
202
+ return d;
203
+ }
204
+ return null;
205
+ }
206
+ return `${path}: ${String(a)} vs ${String(b)}`;
207
+ }
208
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/codegen/canonical.ts"],"names":[],"mappings":"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"}
@@ -9,11 +9,12 @@
9
9
  * (loc_ids, canonical variable and label names, recursive type ids).
10
10
  *
11
11
  * Three legs: hand-written functions covering every node kind and spelling
12
- * family; every exported `*.examples.ts` example (`/tmp/east-examples-ir`,
13
- * `npm run export:examples`), which also executes and must compute its
14
- * declared `returns`; every compliance-corpus program (`/tmp/east-test-ir`,
15
- * `npm run test:export`). The two exported legs skip when the directories
16
- * are absent — the three-way sweep in east-py runs them under CI's
12
+ * family; every exported `*.examples.ts` example (`EAST_EXAMPLES_IR_DIR`,
13
+ * `make export-examples`), which also executes and must compute its declared
14
+ * `returns`; every compliance-corpus program (`EAST_TEST_IR_DIR`, `make
15
+ * test-export`). The root `paths.mk` sets both to this checkout's `tmp/`. The
16
+ * two exported legs skip when a variable is unset or nothing is exported
17
+ * there — the three-way sweep in east-py runs them under CI's
17
18
  * `EAST_CONFORMANCE_REQUIRED=1`.
18
19
  */
19
20
  import { describe, test, after } from "node:test";
@@ -22,7 +23,8 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync,
22
23
  import { tmpdir } from "node:os";
23
24
  import { dirname, join } from "node:path";
24
25
  import { pathToFileURL } from "node:url";
25
- import { East, Expr, variant, ref, some, none, ArrayType, BlobType, DictType, FloatType, FunctionType, IntegerType, NullType, OptionType, RecursiveType, SetType, StringType, StructType, VariantType, VectorType, IRType, EastTypeType, fromJSONFor, equalFor, isVariant, isPlatformDeclaration, toSource, RAW_ONLY, } from "../index.js";
26
+ import { East, Expr, variant, ref, some, none, SortedSet, SortedMap, compareFor, ArrayType, BlobType, BooleanType, DateTimeType, DictType, FloatType, FunctionType, IntegerType, NullType, OptionType, QueryCallType, RecursiveType, SetType, StringType, StructType, VariantType, VectorType, IRType, EastTypeType, fromJSONFor, equalFor, isVariant, isPlatformDeclaration, toSource, RAW_ONLY, } from "../index.js";
27
+ import { canonicalIR as canonical, canonicalDifference as firstDifference } from "./canonical.js";
26
28
  // ── the harness ─────────────────────────────────────────────────────────────
27
29
  const INDEX_URL = new URL("../index.js", import.meta.url).href;
28
30
  const TMP = mkdtempSync(join(tmpdir(), "east-codegen-"));
@@ -38,186 +40,6 @@ async function rebuild(source) {
38
40
  const mod = await import(pathToFileURL(path).href);
39
41
  return mod.main;
40
42
  }
41
- /**
42
- * The normalized shape of an IR value: loc_ids zeroed, variables and labels
43
- * renamed in first-occurrence order, recursive type ids renumbered, bigints
44
- * as strings — what `east-c ir normalize` erases, erased here in TypeScript.
45
- *
46
- * Variables are renamed per BINDING, resolved lexically — a parameter, a
47
- * Let, a loop or match or catch variable each mint a canonical name for the
48
- * body they scope — not per name: since #639 sibling bodies reuse a name
49
- * (three callbacks each naming their element `x`), and two programs that
50
- * bind the same variable under different names (a printed module drops an
51
- * empty `.catch`, whose default variables the rebuild names afresh) are the
52
- * same program.
53
- */
54
- function canonical(node) {
55
- const labels = new Map();
56
- const recursive = new Map();
57
- let variables = 0;
58
- const rename = (table, name, prefix) => {
59
- let hit = table.get(name);
60
- if (hit === undefined) {
61
- hit = `${prefix}${table.size}`;
62
- table.set(name, hit);
63
- }
64
- return hit;
65
- };
66
- /** Binds `variable` (a Variable node) in `scope`: its canonical name, minted in binding order. */
67
- const bind = (variable, scope) => {
68
- const name = `v${variables}`;
69
- variables += 1;
70
- scope.set(variable.value.name, name);
71
- return variableNode(variable, name);
72
- };
73
- const variableNode = (v, name) => ["Variable", { type: walk(v.value.type, "type", new Map()), name, mutable: v.value.mutable, captured: v.value.captured }];
74
- const lookup = (scopes, name) => {
75
- for (let i = scopes.length - 1; i >= 0; i--) {
76
- const hit = scopes[i].get(name);
77
- if (hit !== undefined)
78
- return hit;
79
- }
80
- return `free:${name}`;
81
- };
82
- // the scope chain is threaded through the walk: a body opens a scope, a Let extends the current one
83
- let chain = [new Map()];
84
- const inScope = (f) => {
85
- chain = [...chain, new Map()];
86
- try {
87
- return f();
88
- }
89
- finally {
90
- chain = chain.slice(0, -1);
91
- }
92
- };
93
- const walk = (v, context, _scope) => {
94
- if (v === null || v === undefined)
95
- return null;
96
- if (typeof v === "bigint")
97
- return v.toString();
98
- if (v instanceof Date)
99
- return v.toISOString();
100
- if (v instanceof Uint8Array)
101
- return Array.from(v);
102
- if (Array.isArray(v))
103
- return v.map(x => walk(x, context));
104
- if (isVariant(v)) {
105
- const tag = v.type;
106
- const p = v.value;
107
- if (context === "type") {
108
- if (tag === "Recursive") {
109
- const payload = v.value;
110
- if (payload.type === "ref")
111
- return ["Recursive", ["ref", rename(recursive, String(payload.value), "r")]];
112
- return ["Recursive", ["wrapper", { id: rename(recursive, String(payload.value.id), "r"), inner: walk(payload.value.inner, "type") }]];
113
- }
114
- return [tag, walk(v.value, context)];
115
- }
116
- switch (tag) {
117
- case "Variable":
118
- return variableNode(v, lookup(chain, p.name));
119
- case "Function":
120
- case "AsyncFunction":
121
- return inScope(() => [tag, {
122
- type: walk(p.type, "type"),
123
- parameters: p.parameters.map(q => bind(q, chain[chain.length - 1])),
124
- body: walk(p.body, context),
125
- captures: walk(p.captures, context),
126
- }]);
127
- case "Let": {
128
- const value = walk(p.value, context);
129
- return [tag, { type: walk(p.type, "type"), value, variable: bind(p.variable, chain[chain.length - 1]) }];
130
- }
131
- case "ForArray":
132
- case "ForDict":
133
- case "ForSet": {
134
- const source = tag === "ForArray" ? "array" : tag === "ForDict" ? "dict" : "set";
135
- const coll = walk(p[source], context);
136
- return inScope(() => {
137
- const scope = chain[chain.length - 1];
138
- const out = { type: walk(p.type, "type"), [source]: coll, label: { name: rename(labels, p.label.name, "L") } };
139
- if (tag !== "ForSet")
140
- out["value"] = bind(p.value, scope);
141
- out["key"] = bind(p.key, scope);
142
- out["body"] = walk(p.body, context);
143
- return [tag, out];
144
- });
145
- }
146
- case "Match": {
147
- const variant = walk(p.variant, context);
148
- const cases = p.cases.map(c => inScope(() => ({
149
- case: c.case, variable: bind(c.variable, chain[chain.length - 1]), body: walk(c.body, context),
150
- })));
151
- return [tag, { type: walk(p.type, "type"), variant, cases }];
152
- }
153
- case "TryCatch": {
154
- const tryBody = inScope(() => walk(p.try_body, context));
155
- const caught = inScope(() => {
156
- const scope = chain[chain.length - 1];
157
- const message = bind(p.message, scope);
158
- const stack = bind(p.stack, scope);
159
- return { message, stack, catch_body: walk(p.catch_body, context) };
160
- });
161
- const finallyBody = inScope(() => walk(p.finally_body, context));
162
- return [tag, { type: walk(p.type, "type"), try_body: tryBody, ...caught, finally_body: finallyBody }];
163
- }
164
- case "Block":
165
- return inScope(() => [tag, { type: walk(p.type, "type"), statements: walk(p.statements, context) }]);
166
- case "While":
167
- return [tag, { type: walk(p.type, "type"), predicate: walk(p.predicate, context), label: { name: rename(labels, p.label.name, "L") }, body: inScope(() => walk(p.body, context)) }];
168
- case "IfElse":
169
- return [tag, {
170
- type: walk(p.type, "type"),
171
- ifs: p.ifs.map(branch => ({ predicate: walk(branch.predicate, context), body: inScope(() => walk(branch.body, context)) })),
172
- else_body: inScope(() => walk(p.else_body, context)),
173
- }];
174
- default:
175
- return [tag, walk(v.value, context)];
176
- }
177
- }
178
- if (typeof v === "object") {
179
- const out = {};
180
- for (const key of Object.keys(v).sort()) {
181
- if (key === "loc_id") {
182
- out[key] = "0";
183
- continue;
184
- }
185
- if (key === "label" && typeof v[key] === "object" && "name" in v[key]) {
186
- out[key] = { name: rename(labels, v[key].name, "L") };
187
- continue;
188
- }
189
- out[key] = walk(v[key], key === "type" || key === "type_parameters" ? "type" : context);
190
- }
191
- return out;
192
- }
193
- return v;
194
- };
195
- return walk(node, null);
196
- }
197
- /** The first path at which two canonical forms differ, or `null`. */
198
- function firstDifference(a, b, path = "ir") {
199
- if (Object.is(a, b))
200
- return null;
201
- if (Array.isArray(a) && Array.isArray(b)) {
202
- if (a.length !== b.length)
203
- return `${path}.length: ${a.length} vs ${b.length}`;
204
- for (let i = 0; i < a.length; i++) {
205
- const d = firstDifference(a[i], b[i], `${path}[${i}]`);
206
- if (d !== null)
207
- return d;
208
- }
209
- return null;
210
- }
211
- if (a !== null && b !== null && typeof a === "object" && typeof b === "object") {
212
- for (const k of [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()) {
213
- const d = firstDifference(a[k], b[k], `${path}.${k}`);
214
- if (d !== null)
215
- return d;
216
- }
217
- return null;
218
- }
219
- return `${path}: ${String(a)} vs ${String(b)}`;
220
- }
221
43
  /** A host value for a message: bigints, Maps and Sets made printable. */
222
44
  function show(v) {
223
45
  return JSON.stringify(v, (_k, x) => typeof x === "bigint" ? `${x}n` : x instanceof Map ? [...x] : x instanceof Set ? [...x] : x);
@@ -595,6 +417,36 @@ describe("codegen: toSource round trips the builder surface", () => {
595
417
  const main = await roundTrip(fn, "regex+csv");
596
418
  assert.equal(main.toIR().compile([])("aab foo", [{ a: 1n }]), fn.toIR().compile([])("aab foo", [{ a: 1n }]));
597
419
  });
420
+ test("a regex JavaScript would re-spell prints raw, and rebuilds as it was", async () => {
421
+ // `new RegExp("").source` is `(?:)`, a `/` reads back `\/`, and flags come back in JavaScript's order
422
+ const fn = East.function([StringType], StringType, ($, s) => {
423
+ const empty = $.const(East.builtin("RegexContains", [], [s, "", ""], BooleanType));
424
+ const slash = $.const(East.builtin("RegexIndexOf", [], [s, "a/b", ""], IntegerType));
425
+ const order = $.const(East.builtin("RegexContains", [], [s, "x", "mi"], BooleanType));
426
+ return East.str `${empty} ${slash} ${order}`;
427
+ });
428
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
429
+ assert.doesNotMatch(source, /new RegExp/, source);
430
+ const main = await roundTrip(fn, "regex re-spelled");
431
+ assert.equal(main.toIR().compile([])("xa/b"), fn.toIR().compile([])("xa/b"));
432
+ });
433
+ test("an option's none over a computed null, and a Float key of -0.0, print as they are and rebuild", async () => {
434
+ const Opt = OptionType(IntegerType);
435
+ const fn = East.function([Opt], IntegerType, ($, o) => {
436
+ // `none` spells the null literal only: a payload computed some other way stays
437
+ const same = $.const(o.match({ none: ($, n) => East.value(variant("none", n), Opt), some: ($, x) => East.value(some(x), Opt) }));
438
+ // JavaScript's Set and Map fold -0 into 0; East keeps -0.0 apart from 0.0
439
+ const zeros = $.const(new SortedSet([-0, 0, 1.5], compareFor(FloatType)), SetType(FloatType));
440
+ const keyed = $.const(new SortedMap([[-0, 1n], [0, 2n]], compareFor(FloatType)), DictType(FloatType, IntegerType));
441
+ return zeros.size().add(keyed.size()).add(same.match({ none: ($) => 0n, some: ($, x) => x }));
442
+ });
443
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
444
+ assert.match(source, /variant\("none", n\)/, source);
445
+ assert.match(source, /new SortedSet\(\[-0, 0, 1\.5\], compareFor\(FloatType\)\)/, source);
446
+ assert.match(source, /new SortedMap\(\[\[-0, 1n\], \[0, 2n\]\], compareFor\(FloatType\)\)/, source);
447
+ const main = await roundTrip(fn, "none payload and -0.0 keys");
448
+ assert.equal(main.toIR().compile([])(some(4n)), 9n); // three floats, two keys, and the 4
449
+ });
598
450
  test("another builder's fresh names print as one v_N sequence, and the printed module prints to itself", async () => {
599
451
  const fn = East.function([ArrayType(IntegerType)], IntegerType, ($, xs) => xs.map(($, x) => x.multiply(2n)).reduce(($, acc, x) => acc.add(x), 0n));
600
452
  // python spells a slot the body did not name `__nN` (TypeScript: `_N`),
@@ -611,6 +463,24 @@ describe("codegen: toSource round trips the builder surface", () => {
611
463
  assert.equal(toSource(main.toIR().ir, { importFrom: INDEX_URL, width: Infinity }), wide, "print → build → print is the identity");
612
464
  assert.equal(toSource(main.toIR().ir, { importFrom: INDEX_URL }), toSource(ir, { importFrom: INDEX_URL }), "… at the default width too");
613
465
  });
466
+ test("a variable named as a name the module imports prints renamed, and rebuilds", async () => {
467
+ const fn = East.function([OptionType(IntegerType)], IntegerType, ($, o) => {
468
+ const fallback = $.const(East.value(none, OptionType(IntegerType)));
469
+ const n = $.const(o.match({ none: ($) => 0n, some: ($, x) => x }));
470
+ return n.add(fallback.match({ none: ($) => 1n, some: ($, x) => x }));
471
+ });
472
+ // the bindings named as the module's own imports (a translated query names a variable `none`)
473
+ const imported = (v) => Array.isArray(v) ? v.map(imported)
474
+ : isVariant(v) ? variant(v.type, imported(v.value))
475
+ : v !== null && typeof v === "object" && !(v instanceof Date) && !(v instanceof Uint8Array)
476
+ ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, k === "name" && typeof x === "string" ? (x === "fallback" ? "none" : x === "n" ? "IntegerType" : x) : imported(x)]))
477
+ : v;
478
+ const ir = imported(fn.toIR().ir);
479
+ const source = toSource(ir, { importFrom: INDEX_URL, width: Infinity });
480
+ assert.doesNotMatch(source, /const none = |const IntegerType = /, source);
481
+ const main = await roundTrip(ir, "import-named variables");
482
+ assert.equal(main.toIR().compile([])(some(4n)), 5n);
483
+ });
614
484
  test("layout: a wide literal, its type and an argument list break one entry per line, as prettier lays them out", async () => {
615
485
  const Ops = StructType({ add: FunctionType([IntegerType, IntegerType], IntegerType), multiply: FunctionType([IntegerType, IntegerType], IntegerType) });
616
486
  const fn = East.function([], IntegerType, ($) => {
@@ -689,6 +559,46 @@ describe("codegen: toSource round trips the builder surface", () => {
689
559
  const main = await roundTrip(fn, "layout hug+chain");
690
560
  assert.equal(main.toIR().compile([])([1n, 2n, 3n], "n"), fn.toIR().compile([])([1n, 2n, 3n], "n"));
691
561
  });
562
+ test("a query prints as the East.jq it was, with named inputs as an object, and rebuilds (#927, #1041)", async () => {
563
+ const Order = StructType({ id: IntegerType, total: FloatType });
564
+ const fn = East.function([ArrayType(Order)], ArrayType(IntegerType), ($, orders) => East.jq(orders, "[.[] | select(.total > 1000) | .id]", ArrayType(IntegerType)));
565
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
566
+ assert.match(source, /\(\$, orders\) => East\.jq\(orders, "\[\.\[\] \| select\(\.total > 1000\) \| \.id\]", ArrayType\(IntegerType\)\)/, source);
567
+ assert.doesNotMatch(source, /east_jq|Query/, source);
568
+ const main = await roundTrip(fn, "jq one input");
569
+ assert.deepEqual(main.toIR().compile([])([{ id: 1n, total: 250.0 }, { id: 2n, total: 1200.0 }]), [2n]);
570
+ // Every named input is an argument of the call, one the query does not read too.
571
+ const named = East.function([ArrayType(Order), IntegerType, StringType], FloatType, ($, orders, min, note) => {
572
+ const total = $.const(East.jq({ orders, min, note }, ".min as $m | [.orders[] | select(.id >= $m) | .total] | add", FloatType));
573
+ return total.add(1.0);
574
+ });
575
+ const printed = toSource(named, { importFrom: INDEX_URL, width: Infinity });
576
+ assert.match(printed, /const total = \$\.const\(East\.jq\(\{ orders: orders, min: min, note: note \}, "[^"]*", FloatType\)\);/, printed);
577
+ await roundTrip(named, "jq named inputs");
578
+ });
579
+ test("a query's program prints as written: an ISO date compared with a DateTime stays the text it was (#1041)", async () => {
580
+ const Order = StructType({ id: IntegerType, at: DateTimeType });
581
+ const fn = East.function([ArrayType(Order)], ArrayType(IntegerType), ($, orders) => East.jq(orders, "[.[] | select(.at >= \"2026-01-01\") | .id]", ArrayType(IntegerType)));
582
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
583
+ assert.match(source, /East\.jq\(orders, "\[\.\[\] \| select\(\.at >= \\"2026-01-01\\"\) \| \.id\]", ArrayType\(IntegerType\)\)/, source);
584
+ await roundTrip(fn, "jq as written");
585
+ });
586
+ test("a query inside a callback prints as East.jq there (#927)", async () => {
587
+ const fn = East.function([ArrayType(ArrayType(IntegerType))], ArrayType(IntegerType), ($, rows) => rows.map(($, row) => East.jq(row, "map(. * 2) | add", IntegerType)));
588
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
589
+ assert.match(source, /rows\.map\(\(\$, row, _\d+\) => East\.jq\(row, "map\(\. \* 2\) \| add", IntegerType\)\)/, source);
590
+ const main = await roundTrip(fn, "jq in a callback");
591
+ assert.deepEqual(main.toIR().compile([])([[1n, 2n], [3n]]), [6n, 6n]);
592
+ });
593
+ test("a Query call whose query is not a constant prints raw, and rebuilds (#1041)", async () => {
594
+ const Numbers = ArrayType(IntegerType);
595
+ const F = FunctionType([Numbers], Numbers);
596
+ const fn = East.function([QueryCallType, Numbers], Numbers, ($, query, xs) => East.builtin("Query", [F], [query, East.function([Numbers], Numbers, ($, ys) => ys)], F)(xs));
597
+ const source = toSource(fn, { importFrom: INDEX_URL, width: Infinity });
598
+ assert.doesNotMatch(source, /East\.jq\(/, source);
599
+ assert.match(source, /East\.builtin\("Query"/, source);
600
+ await roundTrip(fn, "Query call of no constant");
601
+ });
692
602
  test("a raw builtin prints through East.builtin and rebuilds", async () => {
693
603
  const fn = East.function([ArrayType(IntegerType)], ArrayType(IntegerType), ($, xs) => {
694
604
  return xs.getKeys([0n, 1n]);
@@ -699,9 +609,20 @@ describe("codegen: toSource round trips the builder surface", () => {
699
609
  });
700
610
  });
701
611
  // ── the exported corpora ────────────────────────────────────────────────────
702
- const EXAMPLES_DIR = process.env["EAST_EXAMPLES_IR_DIR"] ?? "/tmp/east-examples-ir";
703
- const CORPUS_DIR = process.env["EAST_TEST_IR_DIR"] ?? "/tmp/east-test-ir";
704
612
  const REQUIRED = process.env["EAST_CONFORMANCE_REQUIRED"] === "1";
613
+ /**
614
+ * Where an exported corpus is, from the variable the root `paths.mk` sets:
615
+ * its directory, or why the leg has nothing to read — the variable unset
616
+ * (the spec was not run through make) or nothing exported there.
617
+ */
618
+ function corpusDir(variable, what) {
619
+ const dir = process.env[variable];
620
+ if (dir === undefined)
621
+ return { dir: null, skip: `${variable} is unset: run it through make (make -C libs/east test)` };
622
+ if (!existsSync(dir))
623
+ return { dir: null, skip: `no ${what} in ${dir}` };
624
+ return { dir, skip: false };
625
+ }
705
626
  const decodeIR = fromJSONFor(IRType);
706
627
  const decodeType = fromJSONFor(EastTypeType);
707
628
  function hasPlatform(node) {
@@ -716,14 +637,13 @@ function hasPlatform(node) {
716
637
  return Object.keys(node).some(k => k !== "type" && k !== "type_parameters" && hasPlatform(node[k]));
717
638
  }
718
639
  describe("codegen: every exported example round trips and runs", () => {
719
- const present = existsSync(EXAMPLES_DIR);
720
- if (REQUIRED && !present)
721
- throw new Error(`EAST_CONFORMANCE_REQUIRED=1 but no exported examples in ${EXAMPLES_DIR}`);
722
- const files = present
723
- ? readdirSync(EXAMPLES_DIR).flatMap(suite => readdirSync(join(EXAMPLES_DIR, suite)).filter(f => f.endsWith(".json")).map(f => join(EXAMPLES_DIR, suite, f)))
724
- : [];
640
+ const examples = corpusDir("EAST_EXAMPLES_IR_DIR", "exported examples");
641
+ if (REQUIRED && examples.skip)
642
+ throw new Error(`EAST_CONFORMANCE_REQUIRED=1 but ${examples.skip}`);
643
+ const root = examples.dir;
644
+ const files = root === null ? [] : readdirSync(root).flatMap(suite => readdirSync(join(root, suite)).filter(f => f.endsWith(".json")).map(f => join(root, suite, f)));
725
645
  const rawSeen = new Set();
726
- test(`round trip (${files.length} examples)`, { skip: !present && `no exported examples in ${EXAMPLES_DIR}` }, async () => {
646
+ test(`round trip (${files.length} examples)`, { skip: examples.skip }, async () => {
727
647
  const labelOf = (file) => `${file.split("/").at(-2)}/${file.split("/").at(-1).slice(0, -5)}`;
728
648
  await sweep(files, labelOf, async (file) => {
729
649
  const record = JSON.parse(readFileSync(file, "utf-8"));
@@ -748,14 +668,25 @@ describe("codegen: every exported example round trips and runs", () => {
748
668
  });
749
669
  });
750
670
  describe("codegen: every compliance-corpus program round trips", () => {
751
- const present = existsSync(CORPUS_DIR);
752
- if (REQUIRED && !present)
753
- throw new Error(`EAST_CONFORMANCE_REQUIRED=1 but no exported IR corpus in ${CORPUS_DIR}`);
754
- const files = present ? readdirSync(CORPUS_DIR).filter(f => f.endsWith(".json")).sort() : [];
755
- test(`round trip (${files.length} programs)`, { skip: !present && `no exported IR corpus in ${CORPUS_DIR}` }, async () => {
671
+ const corpus = corpusDir("EAST_TEST_IR_DIR", "exported IR corpus");
672
+ if (REQUIRED && corpus.skip)
673
+ throw new Error(`EAST_CONFORMANCE_REQUIRED=1 but ${corpus.skip}`);
674
+ const root = corpus.dir;
675
+ // the top level, and every `query-*` suite directory beside it (#924, #987), as every runner reads them:
676
+ // their queries print as the East.jq that built them (#927)
677
+ const suites = root === null
678
+ ? []
679
+ : readdirSync(root, { withFileTypes: true }).filter(d => d.isDirectory() && d.name.startsWith("query-")).map(d => d.name).sort();
680
+ const files = root === null
681
+ ? []
682
+ : [
683
+ ...readdirSync(root).filter(f => f.endsWith(".json")).sort(),
684
+ ...suites.flatMap(dir => readdirSync(join(root, dir)).filter(f => f.endsWith(".json")).sort().map(f => join(dir, f))),
685
+ ];
686
+ test(`round trip (${files.length} programs)`, { skip: corpus.skip }, async () => {
756
687
  const labelOf = (file) => `corpus/${file.slice(0, -5)}`;
757
688
  await sweep(files, labelOf, async (file) => {
758
- const record = JSON.parse(readFileSync(join(CORPUS_DIR, file), "utf-8"));
689
+ const record = JSON.parse(readFileSync(join(root, file), "utf-8"));
759
690
  await roundTrip(decodeIR(record.ir), labelOf(file));
760
691
  });
761
692
  });