@tom2012/cc-web 2026.5.18-a → 2026.5.18-c

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 (186) hide show
  1. package/README.md +1 -1
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +2 -21
  4. package/backend/dist/index.js.map +1 -1
  5. package/backend/dist/routes/global-tracks.d.ts +2 -5
  6. package/backend/dist/routes/global-tracks.d.ts.map +1 -1
  7. package/backend/dist/routes/global-tracks.js +2 -28
  8. package/backend/dist/routes/global-tracks.js.map +1 -1
  9. package/backend/dist/routes/tracks.d.ts +3 -9
  10. package/backend/dist/routes/tracks.d.ts.map +1 -1
  11. package/backend/dist/routes/tracks.js +3 -153
  12. package/backend/dist/routes/tracks.js.map +1 -1
  13. package/backend/dist/tracks/ask-user-bridge.d.ts +1 -6
  14. package/backend/dist/tracks/ask-user-bridge.d.ts.map +1 -1
  15. package/backend/dist/tracks/ask-user-bridge.js +0 -22
  16. package/backend/dist/tracks/ask-user-bridge.js.map +1 -1
  17. package/backend/dist/tracks/index.d.ts +1 -5
  18. package/backend/dist/tracks/index.d.ts.map +1 -1
  19. package/backend/dist/tracks/index.js +1 -10
  20. package/backend/dist/tracks/index.js.map +1 -1
  21. package/backend/package-lock.json +1 -64
  22. package/backend/package.json +1 -5
  23. package/frontend/dist/assets/{ChatOverlay-C-MIdZJm.js → ChatOverlay-i8gy2y-T.js} +1 -1
  24. package/frontend/dist/assets/{GraphPreview-D2qXaMDE.js → GraphPreview-CoOHeDk0.js} +2 -2
  25. package/frontend/dist/assets/{MobilePage-BolIAlON.js → MobilePage-ZYuVSqIu.js} +3 -3
  26. package/frontend/dist/assets/{OfficePreview-Bw5JlSpR.js → OfficePreview-BP_FVyDv.js} +2 -2
  27. package/frontend/dist/assets/{PdfPreview-DA96QUeL.js → PdfPreview-B_Is_82c.js} +1 -1
  28. package/frontend/dist/assets/{ProjectPage-CmbJ2g7j.js → ProjectPage-Bi7P0xvD.js} +5 -5
  29. package/frontend/dist/assets/{SettingsPage-_Cm-AVw4.js → SettingsPage-DlqnuIy1.js} +2 -2
  30. package/frontend/dist/assets/{SkillHubPage-By-ccKQj.js → SkillHubPage-FFefLcmN.js} +3 -3
  31. package/frontend/dist/assets/{chevron-down-DM_Mo17i.js → chevron-down-DqAoFpmS.js} +1 -1
  32. package/frontend/dist/assets/{index-DuGXgwGK.js → index-B5mDtgD1.js} +2 -2
  33. package/frontend/dist/assets/{index-ZXKKVcgm.js → index-Bj-uhUZY.js} +1 -1
  34. package/frontend/dist/assets/{index-B1ToZu3N.js → index-DzYZJMgp.js} +1 -1
  35. package/frontend/dist/assets/index-mxYmpvtf.css +1 -0
  36. package/frontend/dist/assets/{jszip.min-Bm8u3Q9Z.js → jszip.min-DW73pE0J.js} +1 -1
  37. package/frontend/dist/assets/{select-CF-2DxAz.js → select-HgZm25ew.js} +1 -1
  38. package/frontend/dist/assets/{user-mc2CAtZS.js → user-BOTyiGCj.js} +1 -1
  39. package/frontend/dist/index.html +2 -2
  40. package/package.json +1 -1
  41. package/backend/vendor/@tom2012/train-core/dist/ast-cache.d.ts +0 -74
  42. package/backend/vendor/@tom2012/train-core/dist/ast-cache.d.ts.map +0 -1
  43. package/backend/vendor/@tom2012/train-core/dist/ast-cache.js +0 -157
  44. package/backend/vendor/@tom2012/train-core/dist/ast-cache.js.map +0 -1
  45. package/backend/vendor/@tom2012/train-core/dist/ast.d.ts +0 -350
  46. package/backend/vendor/@tom2012/train-core/dist/ast.d.ts.map +0 -1
  47. package/backend/vendor/@tom2012/train-core/dist/ast.js +0 -15
  48. package/backend/vendor/@tom2012/train-core/dist/ast.js.map +0 -1
  49. package/backend/vendor/@tom2012/train-core/dist/builder.d.ts +0 -21
  50. package/backend/vendor/@tom2012/train-core/dist/builder.d.ts.map +0 -1
  51. package/backend/vendor/@tom2012/train-core/dist/builder.js +0 -1254
  52. package/backend/vendor/@tom2012/train-core/dist/builder.js.map +0 -1
  53. package/backend/vendor/@tom2012/train-core/dist/builtins.d.ts +0 -17
  54. package/backend/vendor/@tom2012/train-core/dist/builtins.d.ts.map +0 -1
  55. package/backend/vendor/@tom2012/train-core/dist/builtins.js +0 -500
  56. package/backend/vendor/@tom2012/train-core/dist/builtins.js.map +0 -1
  57. package/backend/vendor/@tom2012/train-core/dist/index.d.ts +0 -54
  58. package/backend/vendor/@tom2012/train-core/dist/index.d.ts.map +0 -1
  59. package/backend/vendor/@tom2012/train-core/dist/index.js +0 -66
  60. package/backend/vendor/@tom2012/train-core/dist/index.js.map +0 -1
  61. package/backend/vendor/@tom2012/train-core/dist/interpreter.d.ts +0 -110
  62. package/backend/vendor/@tom2012/train-core/dist/interpreter.d.ts.map +0 -1
  63. package/backend/vendor/@tom2012/train-core/dist/interpreter.js +0 -943
  64. package/backend/vendor/@tom2012/train-core/dist/interpreter.js.map +0 -1
  65. package/backend/vendor/@tom2012/train-core/dist/lexer.d.ts +0 -88
  66. package/backend/vendor/@tom2012/train-core/dist/lexer.d.ts.map +0 -1
  67. package/backend/vendor/@tom2012/train-core/dist/lexer.js +0 -243
  68. package/backend/vendor/@tom2012/train-core/dist/lexer.js.map +0 -1
  69. package/backend/vendor/@tom2012/train-core/dist/module-loader.d.ts +0 -74
  70. package/backend/vendor/@tom2012/train-core/dist/module-loader.d.ts.map +0 -1
  71. package/backend/vendor/@tom2012/train-core/dist/module-loader.js +0 -149
  72. package/backend/vendor/@tom2012/train-core/dist/module-loader.js.map +0 -1
  73. package/backend/vendor/@tom2012/train-core/dist/parser.d.ts +0 -138
  74. package/backend/vendor/@tom2012/train-core/dist/parser.d.ts.map +0 -1
  75. package/backend/vendor/@tom2012/train-core/dist/parser.js +0 -861
  76. package/backend/vendor/@tom2012/train-core/dist/parser.js.map +0 -1
  77. package/backend/vendor/@tom2012/train-core/dist/prompt-composer.d.ts +0 -49
  78. package/backend/vendor/@tom2012/train-core/dist/prompt-composer.d.ts.map +0 -1
  79. package/backend/vendor/@tom2012/train-core/dist/prompt-composer.js +0 -159
  80. package/backend/vendor/@tom2012/train-core/dist/prompt-composer.js.map +0 -1
  81. package/backend/vendor/@tom2012/train-core/dist/runtime.d.ts +0 -146
  82. package/backend/vendor/@tom2012/train-core/dist/runtime.d.ts.map +0 -1
  83. package/backend/vendor/@tom2012/train-core/dist/runtime.js +0 -156
  84. package/backend/vendor/@tom2012/train-core/dist/runtime.js.map +0 -1
  85. package/backend/vendor/@tom2012/train-core/dist/type-descriptor.d.ts +0 -18
  86. package/backend/vendor/@tom2012/train-core/dist/type-descriptor.d.ts.map +0 -1
  87. package/backend/vendor/@tom2012/train-core/dist/type-descriptor.js +0 -94
  88. package/backend/vendor/@tom2012/train-core/dist/type-descriptor.js.map +0 -1
  89. package/backend/vendor/@tom2012/train-core/dist/validation.d.ts +0 -44
  90. package/backend/vendor/@tom2012/train-core/dist/validation.d.ts.map +0 -1
  91. package/backend/vendor/@tom2012/train-core/dist/validation.js +0 -271
  92. package/backend/vendor/@tom2012/train-core/dist/validation.js.map +0 -1
  93. package/backend/vendor/@tom2012/train-core/package.json +0 -35
  94. package/frontend/dist/assets/TrackEditor-EL1VBAhT.js +0 -14
  95. package/frontend/dist/assets/TrackGraphEditor-BlSkKLI0.js +0 -2
  96. package/frontend/dist/assets/TrackGraphEditor-Fd0xVSp_.css +0 -1
  97. package/frontend/dist/assets/abap-LPLW346S.js +0 -7
  98. package/frontend/dist/assets/apex-Dk-jUCUV.js +0 -7
  99. package/frontend/dist/assets/azcli-DPUMmPlX.js +0 -7
  100. package/frontend/dist/assets/bat-C1Qbg1bV.js +0 -7
  101. package/frontend/dist/assets/bicep-D-e-VSJi.js +0 -7
  102. package/frontend/dist/assets/cameligo-CjUqTgqL.js +0 -7
  103. package/frontend/dist/assets/clojure-BWsu6Kju.js +0 -7
  104. package/frontend/dist/assets/codicon-DCmgc-ay.ttf +0 -0
  105. package/frontend/dist/assets/coffee-DeC36AK3.js +0 -7
  106. package/frontend/dist/assets/cpp-DcZnmBWT.js +0 -7
  107. package/frontend/dist/assets/csharp-DqBXBMf0.js +0 -7
  108. package/frontend/dist/assets/csp-CkO7y8ul.js +0 -7
  109. package/frontend/dist/assets/css-OAcfVtED.js +0 -7
  110. package/frontend/dist/assets/cssMode-CicA3AgL.js +0 -7
  111. package/frontend/dist/assets/cypher-40UGrUjD.js +0 -7
  112. package/frontend/dist/assets/dart-DmixyLor.js +0 -7
  113. package/frontend/dist/assets/dockerfile-CayO4nTA.js +0 -7
  114. package/frontend/dist/assets/ecl-DRrKCuVc.js +0 -7
  115. package/frontend/dist/assets/editor-B5EY1bb8.css +0 -1
  116. package/frontend/dist/assets/editor.main-DKkZVnWw.js +0 -37
  117. package/frontend/dist/assets/elixir-J3qpp9mX.js +0 -7
  118. package/frontend/dist/assets/flow9-DzW2c4Im.js +0 -7
  119. package/frontend/dist/assets/freemarker2-BAiKgR7W.js +0 -7
  120. package/frontend/dist/assets/fsharp-D00tn_6c.js +0 -7
  121. package/frontend/dist/assets/go-NnNmgWK_.js +0 -7
  122. package/frontend/dist/assets/graphql-C9_--VLF.js +0 -7
  123. package/frontend/dist/assets/handlebars-C65m36HJ.js +0 -7
  124. package/frontend/dist/assets/hcl-BhbOULbp.js +0 -7
  125. package/frontend/dist/assets/html-CQKNdpNj.js +0 -7
  126. package/frontend/dist/assets/htmlMode-lb8O19um.js +0 -7
  127. package/frontend/dist/assets/index-BzylQAjm.js +0 -1
  128. package/frontend/dist/assets/index-CI8kSYwX.css +0 -1
  129. package/frontend/dist/assets/ini-zR-_X5iG.js +0 -7
  130. package/frontend/dist/assets/java-CSTjsyoZ.js +0 -7
  131. package/frontend/dist/assets/javascript-FWRasi5r.js +0 -7
  132. package/frontend/dist/assets/jsonMode-D8aGj5qS.js +0 -7
  133. package/frontend/dist/assets/julia-DioMOKVu.js +0 -7
  134. package/frontend/dist/assets/kotlin-Dsb0PKmW.js +0 -7
  135. package/frontend/dist/assets/less-CZYLoAVD.js +0 -7
  136. package/frontend/dist/assets/lexon-d5JUiwNk.js +0 -7
  137. package/frontend/dist/assets/liquid-Bu7MHLsF.js +0 -7
  138. package/frontend/dist/assets/lua-1Al72GG8.js +0 -7
  139. package/frontend/dist/assets/m3-hx1xCPC3.js +0 -7
  140. package/frontend/dist/assets/markdown-DPGrH1xZ.js +0 -7
  141. package/frontend/dist/assets/mdx-C9KjBxd9.js +0 -7
  142. package/frontend/dist/assets/mips-CYbZjkIm.js +0 -7
  143. package/frontend/dist/assets/msdax-DAioKM5A.js +0 -7
  144. package/frontend/dist/assets/mysql-7e9GUebS.js +0 -7
  145. package/frontend/dist/assets/objective-c-BGf8QNjz.js +0 -7
  146. package/frontend/dist/assets/pascal-hEDRz3un.js +0 -7
  147. package/frontend/dist/assets/pascaligo-gqEhN6pG.js +0 -7
  148. package/frontend/dist/assets/perl-CN8Ht9Vk.js +0 -7
  149. package/frontend/dist/assets/pgsql-CRCqHQBx.js +0 -7
  150. package/frontend/dist/assets/php-DTZrlAwe.js +0 -7
  151. package/frontend/dist/assets/pla-CRmh4UcC.js +0 -7
  152. package/frontend/dist/assets/postiats-Car2G7g8.js +0 -7
  153. package/frontend/dist/assets/powerquery-BJKl8V3o.js +0 -7
  154. package/frontend/dist/assets/powershell-Du7a_J7r.js +0 -7
  155. package/frontend/dist/assets/protobuf-ChneWI0H.js +0 -7
  156. package/frontend/dist/assets/pug-BH6LSJaf.js +0 -7
  157. package/frontend/dist/assets/python-DnHhXfg2.js +0 -7
  158. package/frontend/dist/assets/qsharp-CU-Hxpxi.js +0 -7
  159. package/frontend/dist/assets/r-Bw-W15zh.js +0 -7
  160. package/frontend/dist/assets/razor-D4OpMyGk.js +0 -7
  161. package/frontend/dist/assets/redis-bY-X9-ol.js +0 -7
  162. package/frontend/dist/assets/redshift-CMu3ubbS.js +0 -7
  163. package/frontend/dist/assets/restructuredtext-KNkiWGNN.js +0 -7
  164. package/frontend/dist/assets/ruby-dQWMhU86.js +0 -7
  165. package/frontend/dist/assets/rust-BAcHhnEU.js +0 -7
  166. package/frontend/dist/assets/sb-Bidr5w2z.js +0 -7
  167. package/frontend/dist/assets/scala-imTtxsGY.js +0 -7
  168. package/frontend/dist/assets/scheme-BY-WHKiB.js +0 -7
  169. package/frontend/dist/assets/scss-NDoRP52C.js +0 -7
  170. package/frontend/dist/assets/shell-DrIjotnh.js +0 -7
  171. package/frontend/dist/assets/solidity-DTahNycF.js +0 -7
  172. package/frontend/dist/assets/sophia-DtyrjGkZ.js +0 -7
  173. package/frontend/dist/assets/sparql-CFuS5E3z.js +0 -7
  174. package/frontend/dist/assets/sql-BRxcnbRv.js +0 -7
  175. package/frontend/dist/assets/st-Dz4uh7MK.js +0 -7
  176. package/frontend/dist/assets/swift-Digsrw1G.js +0 -11
  177. package/frontend/dist/assets/systemverilog-7TDrkMau.js +0 -7
  178. package/frontend/dist/assets/tcl-BtaFcnDi.js +0 -7
  179. package/frontend/dist/assets/tsMode-RIZqtAps.js +0 -7
  180. package/frontend/dist/assets/twig-Deid_oBs.js +0 -7
  181. package/frontend/dist/assets/typescript-DJeBIxGF.js +0 -7
  182. package/frontend/dist/assets/typespec-zUaxhbG8.js +0 -7
  183. package/frontend/dist/assets/vb-DK0rKJzo.js +0 -7
  184. package/frontend/dist/assets/wgsl-B8fNVlOQ.js +0 -7
  185. package/frontend/dist/assets/xml-DuazQqBz.js +0 -7
  186. package/frontend/dist/assets/yaml-DsG3CKlY.js +0 -7
@@ -1,943 +0,0 @@
1
- /**
2
- * train language interpreter — M3.
3
- *
4
- * Compared to M2 this revision:
5
- * - is fully async (every evalExpr / execStmt / callFunc returns Promise),
6
- * so fai calls can suspend on awaited adapter responses without
7
- * blocking the event loop
8
- * - implements real fai execution via an injected LLMAdapter:
9
- * composes a prompt, dispatches to adapter.call, validates outputs,
10
- * re-prompts with feedback on validation failures up to maxAttempts
11
- * - falls back to the M2 "no adapter installed" RuntimeError if no
12
- * adapter is configured
13
- *
14
- * Out of scope (later milestones):
15
- * - Persistent stack-frame serialization (M3+)
16
- * - Subflow / module loading (M5)
17
- * - Cancellation propagation through fai (needs interpreter-level AbortController)
18
- */
19
- import { TrainReturnSignal, TrainBreakSignal, TrainContinueSignal, TrainException, InterpreterBug, newScope, scopeLookup, scopeAssign, isFunctionValue, isBuiltin, } from './runtime.js';
20
- import { defaultBuiltinBindings, formatValue } from './builtins.js';
21
- import { composePrompt } from './prompt-composer.js';
22
- import { composeRetryFeedback, validateOutputs } from './validation.js';
23
- import { applyImport, collectExports, createModuleRegistry, } from './module-loader.js';
24
- import { TrainErrorCode } from './runtime.js';
25
- export class Interpreter {
26
- ctx;
27
- faiCallCounter = 0;
28
- adapter;
29
- maxFaiAttempts;
30
- defaultFaiTimeoutMs;
31
- model;
32
- writeProtocolHint;
33
- hostSignal;
34
- constructor(ctx, cfg = {}) {
35
- this.ctx = ctx;
36
- // ctx is intentionally mutable (private but not readonly) so that
37
- // cross-module calls can swap to the callee's module ctx and
38
- // restore on exit. See callFunc/callFai entry.
39
- this.adapter = cfg.adapter;
40
- this.maxFaiAttempts = cfg.maxFaiAttempts ?? 3;
41
- this.defaultFaiTimeoutMs = cfg.defaultFaiTimeoutMs ?? 600_000;
42
- this.model = cfg.model;
43
- this.writeProtocolHint = cfg.writeProtocolHint;
44
- this.hostSignal = cfg.signal;
45
- }
46
- // ─── Expressions ─────────────────────────────────────────────────────
47
- async evalExpr(expr, scope) {
48
- switch (expr.kind) {
49
- case 'IntLit':
50
- case 'FloatLit':
51
- return expr.value;
52
- case 'StringLit':
53
- return expr.value;
54
- case 'BoolLit':
55
- return expr.value;
56
- case 'NullLit':
57
- return null;
58
- case 'TemplateString':
59
- return this.evalTemplate(expr, scope);
60
- case 'IdentExpr':
61
- return this.evalIdent(expr, scope);
62
- case 'ArrayLit': {
63
- const out = [];
64
- for (const e of expr.elements)
65
- out.push(await this.evalExpr(e, scope));
66
- return out;
67
- }
68
- case 'ObjectLit': {
69
- const obj = {};
70
- for (const f of expr.fields) {
71
- obj[f.key] = await this.evalExpr(f.value, scope);
72
- }
73
- return obj;
74
- }
75
- case 'UnaryExpr':
76
- return this.evalUnary(expr, scope);
77
- case 'BinaryExpr':
78
- return this.evalBinary(expr, scope);
79
- case 'TernaryExpr':
80
- return this.truthy(await this.evalExpr(expr.cond, scope))
81
- ? this.evalExpr(expr.then, scope)
82
- : this.evalExpr(expr.otherwise, scope);
83
- case 'MemberExpr': {
84
- const o = await this.evalExpr(expr.object, scope);
85
- return this.getMember(o, expr.property, expr.range);
86
- }
87
- case 'IndexExpr': {
88
- const o = await this.evalExpr(expr.object, scope);
89
- const k = await this.evalExpr(expr.index, scope);
90
- return this.getIndex(o, k, expr.range);
91
- }
92
- case 'CallExpr':
93
- return this.evalCall(expr, scope);
94
- }
95
- }
96
- evalIdent(expr, scope) {
97
- const localVal = scopeLookup(scope, expr.name);
98
- if (localVal !== undefined)
99
- return localVal;
100
- if (this.ctx.globals.has(expr.name))
101
- return this.ctx.globals.get(expr.name);
102
- if (this.ctx.constants.has(expr.name))
103
- return this.ctx.constants.get(expr.name);
104
- if (this.ctx.functions.has(expr.name))
105
- return this.ctx.functions.get(expr.name);
106
- if (this.ctx.builtins.has(expr.name))
107
- return this.ctx.builtins.get(expr.name);
108
- throw new TrainException('RuntimeError', `Undefined identifier '${expr.name}'`, expr.range);
109
- }
110
- async evalTemplate(expr, scope) {
111
- const parts = [];
112
- for (const p of expr.parts) {
113
- if (p.kind === 'TemplateChunk')
114
- parts.push(p.value);
115
- else
116
- parts.push(formatValue(await this.evalExpr(p.expr, scope)));
117
- }
118
- return parts.join('');
119
- }
120
- async evalUnary(expr, scope) {
121
- const v = await this.evalExpr(expr.operand, scope);
122
- if (expr.op === '-') {
123
- if (typeof v !== 'number')
124
- throw new TrainException('RuntimeError', `unary '-' expected number, got ${typeName(v)}`, expr.range);
125
- return -v;
126
- }
127
- return !this.truthy(v);
128
- }
129
- async evalBinary(expr, scope) {
130
- if (expr.op === '&&') {
131
- const l = await this.evalExpr(expr.left, scope);
132
- if (!this.truthy(l))
133
- return l;
134
- return this.evalExpr(expr.right, scope);
135
- }
136
- if (expr.op === '||') {
137
- const l = await this.evalExpr(expr.left, scope);
138
- if (this.truthy(l))
139
- return l;
140
- return this.evalExpr(expr.right, scope);
141
- }
142
- const l = await this.evalExpr(expr.left, scope);
143
- const r = await this.evalExpr(expr.right, scope);
144
- switch (expr.op) {
145
- case '+':
146
- if (typeof l === 'string' || typeof r === 'string')
147
- return formatValue(l) + formatValue(r);
148
- if (typeof l === 'number' && typeof r === 'number')
149
- return l + r;
150
- throw binTypeErr(expr, l, r);
151
- case '-':
152
- return numBin(expr, l, r, (a, b) => a - b);
153
- case '*':
154
- return numBin(expr, l, r, (a, b) => a * b);
155
- case '/':
156
- if (typeof l === 'number' && typeof r === 'number') {
157
- if (r === 0)
158
- throw new TrainException('RuntimeError', 'division by zero', expr.range);
159
- return l / r;
160
- }
161
- throw binTypeErr(expr, l, r);
162
- case '%':
163
- if (typeof l === 'number' && typeof r === 'number') {
164
- if (r === 0)
165
- throw new TrainException('RuntimeError', 'modulo by zero', expr.range);
166
- return l % r;
167
- }
168
- throw binTypeErr(expr, l, r);
169
- case '==':
170
- return deepEqValue(l, r);
171
- case '!=':
172
- return !deepEqValue(l, r);
173
- case '<':
174
- return cmp(expr, l, r) < 0;
175
- case '<=':
176
- return cmp(expr, l, r) <= 0;
177
- case '>':
178
- return cmp(expr, l, r) > 0;
179
- case '>=':
180
- return cmp(expr, l, r) >= 0;
181
- }
182
- throw new InterpreterBug(`unhandled binary op: ${expr.op}`);
183
- }
184
- getMember(obj, prop, range) {
185
- if (obj === null)
186
- throw new TrainException('RuntimeError', `cannot read property '${prop}' of null`, range);
187
- if (typeof obj === 'object' && !Array.isArray(obj)) {
188
- const o = obj;
189
- if (prop in o)
190
- return o[prop];
191
- // Spec: missing object keys return null (consistent with array
192
- // out-of-bounds and string out-of-bounds, both of which also
193
- // return null). Previously this threw RuntimeError, breaking the
194
- // spec invariant `obj.missing === null` that downstream code
195
- // relies on (e.g. `if (obj.maybe == null) { ... }` patterns).
196
- return null;
197
- }
198
- if (Array.isArray(obj)) {
199
- if (prop === 'length')
200
- return obj.length;
201
- }
202
- if (typeof obj === 'string') {
203
- if (prop === 'length')
204
- return [...obj].length;
205
- }
206
- throw new TrainException('RuntimeError', `cannot read property '${prop}' on ${typeName(obj)}`, range);
207
- }
208
- getIndex(obj, key, range) {
209
- if (Array.isArray(obj)) {
210
- if (typeof key !== 'number')
211
- throw new TrainException('RuntimeError', `array index must be a number, got ${typeName(key)}`, range);
212
- const i = key < 0 ? obj.length + key : key;
213
- if (i < 0 || i >= obj.length)
214
- return null;
215
- return obj[i];
216
- }
217
- if (typeof obj === 'string') {
218
- if (typeof key !== 'number')
219
- throw new TrainException('RuntimeError', `string index must be a number, got ${typeName(key)}`, range);
220
- const codepoints = [...obj];
221
- const i = key < 0 ? codepoints.length + key : key;
222
- if (i < 0 || i >= codepoints.length)
223
- return null;
224
- return codepoints[i];
225
- }
226
- if (obj !== null && typeof obj === 'object') {
227
- if (typeof key !== 'string')
228
- throw new TrainException('RuntimeError', `object key must be a string, got ${typeName(key)}`, range);
229
- const o = obj;
230
- return key in o ? o[key] : null;
231
- }
232
- throw new TrainException('RuntimeError', `cannot index ${typeName(obj)}`, range);
233
- }
234
- async evalCall(expr, scope) {
235
- const callee = await this.evalExpr(expr.callee, scope);
236
- const args = [];
237
- for (const a of expr.args)
238
- args.push(await this.evalExpr(a, scope));
239
- if (isFunctionValue(callee)) {
240
- if (callee.isFai) {
241
- return this.callFai(callee, args, expr.range);
242
- }
243
- return this.callFunc(callee, args, expr.range);
244
- }
245
- if (isBuiltin(callee)) {
246
- const b = callee;
247
- // Builtins may be sync or async (Value | Promise<Value>); awaiting
248
- // a non-Promise is a no-op so this is safe for both cases.
249
- return await b.call(args);
250
- }
251
- throw new TrainException('RuntimeError', `value is not callable: ${typeName(callee)}`, expr.range);
252
- }
253
- async callFunc(fn, args, range) {
254
- if (fn.decl.kind !== 'FuncDecl')
255
- throw new InterpreterBug('callFunc dispatched on non-func decl');
256
- const decl = fn.decl;
257
- if (args.length !== decl.params.length) {
258
- throw new TrainException('RuntimeError', `${fn.name}() expects ${decl.params.length} arg(s), got ${args.length}`, range);
259
- }
260
- const callScope = newScope(fn.definedIn);
261
- decl.params.forEach((p, i) => callScope.bindings.set(p.name, args[i]));
262
- const prevCtx = this.ctx;
263
- if (fn.moduleCtx && fn.moduleCtx !== this.ctx)
264
- this.ctx = fn.moduleCtx;
265
- try {
266
- await this.execBlock(decl.body, callScope);
267
- }
268
- catch (e) {
269
- if (e instanceof TrainReturnSignal)
270
- return e.value ?? null;
271
- throw e;
272
- }
273
- finally {
274
- this.ctx = prevCtx;
275
- }
276
- return null;
277
- }
278
- /**
279
- * Execute a fai call: compose prompt → adapter.call → validate →
280
- * retry-with-feedback loop. Returns the validated outputs as a single
281
- * object (containing one key per declared output).
282
- */
283
- async callFai(fn, args, range) {
284
- if (fn.decl.kind !== 'FaiDecl')
285
- throw new InterpreterBug('callFai dispatched on non-fai decl');
286
- const decl = fn.decl;
287
- if (!this.adapter) {
288
- throw new TrainException('RuntimeError', `fai function '${fn.name}' requires an LLM adapter, none installed`, range);
289
- }
290
- if (args.length !== decl.params.length) {
291
- throw new TrainException('RuntimeError', `fai ${fn.name}() expects ${decl.params.length} arg(s), got ${args.length}`, range);
292
- }
293
- const callId = ++this.faiCallCounter;
294
- const argMap = new Map();
295
- decl.params.forEach((p, i) => argMap.set(p.name, args[i]));
296
- // Compose the prompt once; retry attempts will append feedback.
297
- const base = composePrompt(decl, argMap, {
298
- capabilities: this.adapter.capabilities,
299
- callId,
300
- writeProtocolHint: this.writeProtocolHint,
301
- });
302
- let promptText = base.text;
303
- let lastErrors = null;
304
- for (let attempt = 0; attempt < this.maxFaiAttempts; attempt++) {
305
- if (this.hostSignal?.aborted) {
306
- throw new TrainException('UserCancelError', `fai ${fn.name}: cancelled before attempt ${attempt + 1}`, range);
307
- }
308
- const req = {
309
- callId,
310
- fnName: fn.name,
311
- prompt: promptText,
312
- inputs: base.inputs,
313
- outputs: base.outputs,
314
- options: {
315
- timeoutMs: this.defaultFaiTimeoutMs,
316
- maxAttempts: this.maxFaiAttempts,
317
- attempt,
318
- model: this.model,
319
- signal: this.hostSignal,
320
- },
321
- };
322
- const result = await this.adapter.call(req);
323
- switch (result.kind) {
324
- case 'success': {
325
- const validated = validateOutputs(decl.outputs, result.outputs);
326
- if (validated.ok) {
327
- return validated.outputs;
328
- }
329
- lastErrors = validated;
330
- if (attempt < this.maxFaiAttempts - 1) {
331
- // Append feedback to prompt and retry
332
- promptText =
333
- base.text +
334
- '\n\n' +
335
- composeRetryFeedback(validated.errors, attempt, this.maxFaiAttempts);
336
- }
337
- break;
338
- }
339
- case 'validation-error':
340
- // Adapter performed its own validation and rejected outputs
341
- lastErrors = { ok: false, errors: result.errors };
342
- if (attempt < this.maxFaiAttempts - 1) {
343
- promptText =
344
- base.text +
345
- '\n\n' +
346
- composeRetryFeedback(result.errors, attempt, this.maxFaiAttempts);
347
- }
348
- break;
349
- case 'timeout':
350
- throw new TrainException('TimeoutError', `fai ${fn.name}: adapter timed out (attempt ${attempt + 1})`, range);
351
- case 'cancelled':
352
- throw new TrainException('UserCancelError', `fai ${fn.name}: cancelled`, range);
353
- case 'error':
354
- if (result.recoverable && attempt < this.maxFaiAttempts - 1) {
355
- // try again with original prompt (no schema feedback)
356
- break;
357
- }
358
- throw new TrainException('RuntimeError', `fai ${fn.name}: ${result.message}`, range);
359
- }
360
- }
361
- // All attempts exhausted with validation errors
362
- const errs = (lastErrors && !lastErrors.ok ? lastErrors.errors : []) ?? [];
363
- const summary = errs.map((e) => `${e.outputName}: ${e.message}`).join('; ');
364
- throw new TrainException('ValidationError', `fai ${fn.name}: failed after ${this.maxFaiAttempts} attempt(s) — ${summary || 'no details'}`, range);
365
- }
366
- // ─── Statements / blocks ─────────────────────────────────────────────
367
- async execBlock(block, scope) {
368
- const inner = newScope(scope);
369
- for (const s of block.stmts)
370
- await this.execStmt(s, inner);
371
- }
372
- async execStmt(stmt, scope) {
373
- switch (stmt.kind) {
374
- case 'LetDecl':
375
- return this.execLet(stmt, scope);
376
- case 'Assignment':
377
- return this.execAssign(stmt, scope);
378
- case 'IfStmt':
379
- return this.execIf(stmt, scope);
380
- case 'ForStmt':
381
- return this.execFor(stmt, scope);
382
- case 'WhileStmt':
383
- return this.execWhile(stmt, scope);
384
- case 'TryStmt':
385
- return this.execTry(stmt, scope);
386
- case 'BreakStmt':
387
- throw new TrainBreakSignal();
388
- case 'ContinueStmt':
389
- throw new TrainContinueSignal();
390
- case 'ReturnStmt':
391
- throw new TrainReturnSignal(stmt.value ? await this.evalExpr(stmt.value, scope) : null);
392
- case 'ExprStmt':
393
- await this.evalExpr(stmt.expr, scope);
394
- return;
395
- }
396
- }
397
- async execLet(stmt, scope) {
398
- const v = stmt.init ? await this.evalExpr(stmt.init, scope) : null;
399
- this.bindLetTarget(stmt.target, v, scope, stmt.range);
400
- }
401
- bindLetTarget(target, v, scope, range) {
402
- if (target.kind === 'IdentTarget') {
403
- scope.bindings.set(target.name, v);
404
- return;
405
- }
406
- if (target.kind === 'ObjectDestruct') {
407
- if (v === null ||
408
- typeof v !== 'object' ||
409
- Array.isArray(v) ||
410
- isFunctionValue(v) ||
411
- isBuiltin(v)) {
412
- throw new TrainException('RuntimeError', `cannot object-destructure ${typeName(v)}`, range);
413
- }
414
- const o = v;
415
- for (const f of target.fields) {
416
- scope.bindings.set(f.local, f.source in o ? o[f.source] : null);
417
- }
418
- return;
419
- }
420
- if (!Array.isArray(v)) {
421
- throw new TrainException('RuntimeError', `cannot array-destructure ${typeName(v)}`, range);
422
- }
423
- for (let i = 0; i < target.names.length; i++) {
424
- scope.bindings.set(target.names[i], i < v.length ? v[i] : null);
425
- }
426
- }
427
- async execAssign(stmt, scope) {
428
- const rhs = await this.evalExpr(stmt.value, scope);
429
- const baseName = stmt.target.base;
430
- if (stmt.target.suffixes.length === 0) {
431
- const current = scopeLookup(scope, baseName) ??
432
- (this.ctx.globals.has(baseName)
433
- ? this.ctx.globals.get(baseName)
434
- : undefined);
435
- const newVal = stmt.op === '='
436
- ? rhs
437
- : this.applyCompound(stmt.op, current, rhs, stmt.range);
438
- if (scopeAssign(scope, baseName, newVal))
439
- return;
440
- if (this.ctx.globals.has(baseName)) {
441
- this.ctx.globals.set(baseName, newVal);
442
- return;
443
- }
444
- if (this.ctx.constants.has(baseName))
445
- throw new TrainException('RuntimeError', `cannot reassign const '${baseName}'`, stmt.range);
446
- throw new TrainException('RuntimeError', `assignment to undeclared variable '${baseName}'`, stmt.range);
447
- }
448
- let cursor = scopeLookup(scope, baseName) ??
449
- this.ctx.globals.get(baseName) ??
450
- this.ctx.constants.get(baseName);
451
- if (cursor === undefined) {
452
- throw new TrainException('RuntimeError', `undefined identifier '${baseName}'`, stmt.range);
453
- }
454
- for (let i = 0; i < stmt.target.suffixes.length - 1; i++) {
455
- cursor = await this.followSuffix(cursor, stmt.target.suffixes[i], scope);
456
- }
457
- const lastSuf = stmt.target.suffixes[stmt.target.suffixes.length - 1];
458
- const oldValue = await this.followSuffix(cursor, lastSuf, scope);
459
- const newVal = stmt.op === '='
460
- ? rhs
461
- : this.applyCompound(stmt.op, oldValue, rhs, stmt.range);
462
- await this.setSuffix(cursor, lastSuf, newVal, scope, stmt.range);
463
- }
464
- async followSuffix(obj, suf, scope) {
465
- if (suf.kind === 'MemberSuffix')
466
- return this.getMember(obj, suf.name);
467
- return this.getIndex(obj, await this.evalExpr(suf.index, scope));
468
- }
469
- async setSuffix(obj, suf, val, scope, range) {
470
- if (suf.kind === 'MemberSuffix') {
471
- if (obj === null || typeof obj !== 'object' || Array.isArray(obj))
472
- throw new TrainException('RuntimeError', `cannot set property '${suf.name}' on ${typeName(obj)}`, range);
473
- obj[suf.name] = val;
474
- return;
475
- }
476
- const key = await this.evalExpr(suf.index, scope);
477
- if (Array.isArray(obj)) {
478
- if (typeof key !== 'number')
479
- throw new TrainException('RuntimeError', `array index must be a number, got ${typeName(key)}`, range);
480
- const i = key < 0 ? obj.length + key : key;
481
- obj[i] = val;
482
- return;
483
- }
484
- if (obj !== null && typeof obj === 'object') {
485
- if (typeof key !== 'string')
486
- throw new TrainException('RuntimeError', `object key must be a string`, range);
487
- obj[key] = val;
488
- return;
489
- }
490
- throw new TrainException('RuntimeError', `cannot index-assign ${typeName(obj)}`, range);
491
- }
492
- applyCompound(op, old, rhs, range) {
493
- if (old === undefined)
494
- throw new TrainException('RuntimeError', `compound assignment ${op} on undeclared variable`, range);
495
- if (typeof old !== 'number' || typeof rhs !== 'number') {
496
- if (op === '+=' && (typeof old === 'string' || typeof rhs === 'string')) {
497
- return formatValue(old) + formatValue(rhs);
498
- }
499
- throw new TrainException('RuntimeError', `${op} requires numbers, got ${typeName(old)} and ${typeName(rhs)}`, range);
500
- }
501
- switch (op) {
502
- case '+=':
503
- return old + rhs;
504
- case '-=':
505
- return old - rhs;
506
- case '*=':
507
- return old * rhs;
508
- case '/=':
509
- if (rhs === 0)
510
- throw new TrainException('RuntimeError', 'division by zero', range);
511
- return old / rhs;
512
- case '%=':
513
- if (rhs === 0)
514
- throw new TrainException('RuntimeError', 'modulo by zero', range);
515
- return old % rhs;
516
- case '=':
517
- return rhs;
518
- }
519
- }
520
- async execIf(stmt, scope) {
521
- if (this.truthy(await this.evalExpr(stmt.cond, scope))) {
522
- return this.execBlock(stmt.then, scope);
523
- }
524
- for (const elif of stmt.elifs) {
525
- if (this.truthy(await this.evalExpr(elif.cond, scope))) {
526
- return this.execBlock(elif.body, scope);
527
- }
528
- }
529
- if (stmt.otherwise)
530
- await this.execBlock(stmt.otherwise, scope);
531
- }
532
- async execFor(stmt, scope) {
533
- const iter = await this.evalExpr(stmt.iterable, scope);
534
- const items = this.iterable(iter, stmt.range);
535
- for (const item of items) {
536
- const inner = newScope(scope);
537
- inner.bindings.set(stmt.binding, item);
538
- try {
539
- await this.execBlock(stmt.body, inner);
540
- }
541
- catch (e) {
542
- if (e instanceof TrainBreakSignal)
543
- return;
544
- if (e instanceof TrainContinueSignal)
545
- continue;
546
- throw e;
547
- }
548
- }
549
- }
550
- iterable(v, range) {
551
- if (Array.isArray(v))
552
- return v;
553
- if (typeof v === 'string')
554
- return [...v];
555
- if (v !== null &&
556
- typeof v === 'object' &&
557
- !isFunctionValue(v) &&
558
- !isBuiltin(v)) {
559
- return Object.keys(v);
560
- }
561
- throw new TrainException('RuntimeError', `cannot iterate ${typeName(v)}`, range);
562
- }
563
- async execWhile(stmt, scope) {
564
- while (this.truthy(await this.evalExpr(stmt.cond, scope))) {
565
- try {
566
- await this.execBlock(stmt.body, scope);
567
- }
568
- catch (e) {
569
- if (e instanceof TrainBreakSignal)
570
- return;
571
- if (e instanceof TrainContinueSignal)
572
- continue;
573
- throw e;
574
- }
575
- }
576
- }
577
- async execTry(stmt, scope) {
578
- try {
579
- await this.execBlock(stmt.body, scope);
580
- }
581
- catch (e) {
582
- if (e instanceof TrainException) {
583
- for (const c of stmt.catches) {
584
- // train error types are PascalCase by convention
585
- // (RuntimeError, ValidationError, TimeoutError, ModuleError,
586
- // UserCancelError, ...). When user writes `catch e { ... }`
587
- // with a lowercase identifier, they meant "catch any error
588
- // and bind it as `e`", not "catch the error type literally
589
- // named 'e'" (which would never match anything). Treat the
590
- // lowercase form as catch-all and bind the name as the
591
- // error variable.
592
- const startsLower = c.errorType.length > 0 &&
593
- c.errorType[0] >= 'a' &&
594
- c.errorType[0] <= 'z';
595
- const matches = startsLower || c.errorType === e.errorType;
596
- if (matches) {
597
- const inner = newScope(scope);
598
- const bindingName = c.binding ?? (startsLower ? c.errorType : null);
599
- if (bindingName) {
600
- inner.bindings.set(bindingName, {
601
- type: e.errorType,
602
- message: e.message,
603
- });
604
- }
605
- await this.execBlock(c.body, inner);
606
- return;
607
- }
608
- }
609
- }
610
- throw e;
611
- }
612
- }
613
- // ─── Helpers ─────────────────────────────────────────────────────────
614
- truthy(v) {
615
- if (v === null)
616
- return false;
617
- if (typeof v === 'boolean')
618
- return v;
619
- if (typeof v === 'number')
620
- return v !== 0;
621
- if (typeof v === 'string')
622
- return v.length > 0;
623
- if (Array.isArray(v))
624
- return v.length > 0;
625
- return true;
626
- }
627
- }
628
- export async function runProgram(program, opts = {}) {
629
- const ctx = {
630
- constants: new Map(),
631
- globals: new Map(),
632
- functions: new Map(),
633
- builtins: new Map(),
634
- exports: new Map(),
635
- };
636
- for (const [k, v] of defaultBuiltinBindings()) {
637
- ctx.builtins.set(k, v);
638
- }
639
- if (opts.extraBuiltins) {
640
- for (const [k, v] of opts.extraBuiltins) {
641
- ctx.builtins.set(k, v);
642
- }
643
- }
644
- const interp = new Interpreter(ctx, opts);
645
- const rootScope = newScope(null);
646
- const moduleRegistry = opts.moduleRegistry ?? createModuleRegistry();
647
- const entryFile = opts.entryFile;
648
- const importerStack = opts.__importerStack ?? (entryFile ? [entryFile] : []);
649
- try {
650
- for (const item of program.items) {
651
- registerTopLevelFunctions(item, ctx, rootScope);
652
- }
653
- }
654
- catch (e) {
655
- if (e instanceof TrainException)
656
- return { ok: false, value: null, error: e };
657
- throw e;
658
- }
659
- try {
660
- for (const item of program.items) {
661
- await evalTopLevelItem(item, interp, ctx, rootScope, moduleRegistry, entryFile, importerStack, opts);
662
- }
663
- }
664
- catch (e) {
665
- if (e instanceof TrainException)
666
- return { ok: false, value: null, error: e };
667
- throw e;
668
- }
669
- const entryName = opts.entry ?? 'main';
670
- const internalName = ctx.exports.get(entryName);
671
- if (!internalName) {
672
- return {
673
- ok: false,
674
- value: null,
675
- error: new TrainException('RuntimeError', `no export named '${entryName}' found`),
676
- };
677
- }
678
- const fn = ctx.functions.get(internalName);
679
- if (!fn) {
680
- return {
681
- ok: false,
682
- value: null,
683
- error: new TrainException('RuntimeError', `export '${entryName}' is not a function`),
684
- };
685
- }
686
- try {
687
- if (fn.isFai) {
688
- // Allow fai as entry only if adapter installed
689
- if (!opts.adapter) {
690
- return {
691
- ok: false,
692
- value: null,
693
- error: new TrainException('RuntimeError', `entry function '${entryName}' is a fai; cannot run without an LLM adapter`),
694
- };
695
- }
696
- const value = await interp.callFai(fn, opts.args ?? []);
697
- return { ok: true, value };
698
- }
699
- const value = await interp.callFunc(fn, opts.args ?? []);
700
- return { ok: true, value };
701
- }
702
- catch (e) {
703
- if (e instanceof TrainException)
704
- return { ok: false, value: null, error: e };
705
- throw e;
706
- }
707
- }
708
- /**
709
- * Reject names that already exist as a function, constant, or global in
710
- * this module context. Catches:
711
- * - duplicate `func foo / func foo`
712
- * - `func foo / const foo`
713
- * - duplicate `import { foo } / import { foo }`
714
- * - `import { foo } / func foo` (silent shadow before this fix)
715
- */
716
- function assertFreshSymbol(name, ctx, range, source) {
717
- let prev = null;
718
- if (ctx.functions.has(name))
719
- prev = 'function';
720
- else if (ctx.constants.has(name))
721
- prev = 'const';
722
- else if (ctx.globals.has(name))
723
- prev = 'var';
724
- if (prev !== null) {
725
- throw new TrainException('RuntimeError', `duplicate symbol '${name}' (declared as ${source}, already exists as ${prev})`, range);
726
- }
727
- }
728
- function registerTopLevelFunctions(item, ctx, rootScope) {
729
- if (item.kind === 'FuncDecl' || item.kind === 'FaiDecl') {
730
- assertFreshSymbol(item.name, ctx, item.range, 'function');
731
- ctx.functions.set(item.name, {
732
- __kind: 'function',
733
- name: item.name,
734
- isFai: item.kind === 'FaiDecl',
735
- decl: item,
736
- definedIn: rootScope,
737
- moduleCtx: ctx,
738
- });
739
- return;
740
- }
741
- if (item.kind === 'ExportDecl') {
742
- const tgt = item.target;
743
- if (tgt.kind === 'FuncDecl' || tgt.kind === 'FaiDecl') {
744
- assertFreshSymbol(tgt.name, ctx, tgt.range, 'function');
745
- ctx.functions.set(tgt.name, {
746
- __kind: 'function',
747
- name: tgt.name,
748
- isFai: tgt.kind === 'FaiDecl',
749
- decl: tgt,
750
- definedIn: rootScope,
751
- moduleCtx: ctx,
752
- });
753
- }
754
- }
755
- }
756
- async function evalTopLevelItem(item, interp, ctx, rootScope, moduleRegistry, currentFile, importerStack, opts) {
757
- switch (item.kind) {
758
- case 'Import':
759
- await handleImport(item, ctx, moduleRegistry, currentFile, importerStack, opts);
760
- return;
761
- case 'RuntimeAnnotation':
762
- return;
763
- case 'ConstDecl':
764
- assertFreshSymbol(item.name, ctx, item.range, 'const');
765
- ctx.constants.set(item.name, await interp.evalExpr(item.value, rootScope));
766
- return;
767
- case 'VarDecl':
768
- assertFreshSymbol(item.name, ctx, item.range, 'var');
769
- ctx.globals.set(item.name, item.init ? await interp.evalExpr(item.init, rootScope) : null);
770
- return;
771
- case 'FuncDecl':
772
- case 'FaiDecl':
773
- return;
774
- case 'ExportDecl':
775
- registerExports(item, ctx);
776
- return;
777
- }
778
- }
779
- async function handleImport(imp, importerCtx, registry, currentFile, importerStack, rootOpts) {
780
- if (!currentFile) {
781
- throw new TrainException('ModuleError', `import statement requires entryFile to be set on runProgram (no current module path)`, imp.range, TrainErrorCode.ModuleNotFound);
782
- }
783
- const absPath = registry.resolve(imp.source, currentFile);
784
- // cache hit
785
- if (registry.hasCached(absPath)) {
786
- applyImport(imp, registry.getCached(absPath), importerCtx);
787
- return;
788
- }
789
- // cycle detection
790
- if (registry.isInProgress(absPath)) {
791
- const cycle = [...importerStack, absPath]
792
- .map((p) => p.split('/').pop())
793
- .join(' → ');
794
- throw new TrainException('ModuleError', `circular import: ${cycle}`, imp.range, TrainErrorCode.CircularImport);
795
- }
796
- registry.markInProgress(absPath);
797
- try {
798
- const childSource = await registry.read(absPath);
799
- const { parse } = await import('./parser.js');
800
- const { buildAst } = await import('./builder.js');
801
- const parseResult = parse(childSource);
802
- if (parseResult.lexErrors.length > 0 || parseResult.parseErrors.length > 0) {
803
- throw new TrainException('ModuleError', `module "${imp.source}" has parse errors (${parseResult.lexErrors.length + parseResult.parseErrors.length})`, imp.range, TrainErrorCode.ModuleNotFound);
804
- }
805
- const childAst = buildAst(parseResult.cst);
806
- if (!childAst) {
807
- throw new TrainException('ModuleError', `module "${imp.source}" failed to build AST`, imp.range, TrainErrorCode.ModuleNotFound);
808
- }
809
- // Recurse: run the child module's top-level via runProgram, but in
810
- // "submodule mode" — no entry call, no result; we only want its ctx.
811
- const childResult = await runSubmodule(childAst, {
812
- ...rootOpts,
813
- entryFile: absPath,
814
- moduleRegistry: registry,
815
- __importerStack: [...importerStack, absPath],
816
- });
817
- if (!childResult.ok) {
818
- throw (childResult.error ??
819
- new TrainException('ModuleError', `module "${imp.source}" failed to evaluate`, imp.range, TrainErrorCode.ModuleNotFound));
820
- }
821
- registry.set(absPath, {
822
- absPath,
823
- ctx: childResult.ctx,
824
- exports: collectExports(childResult.ctx),
825
- });
826
- applyImport(imp, registry.getCached(absPath), importerCtx);
827
- }
828
- finally {
829
- registry.unmarkInProgress(absPath);
830
- }
831
- }
832
- /**
833
- * Execute a child module's top-level without calling its entry. Returns
834
- * the populated RuntimeContext for export collection.
835
- */
836
- async function runSubmodule(program, opts) {
837
- const ctx = {
838
- constants: new Map(),
839
- globals: new Map(),
840
- functions: new Map(),
841
- builtins: new Map(),
842
- exports: new Map(),
843
- };
844
- for (const [k, v] of defaultBuiltinBindings()) {
845
- ctx.builtins.set(k, v);
846
- }
847
- if (opts.extraBuiltins) {
848
- for (const [k, v] of opts.extraBuiltins) {
849
- ctx.builtins.set(k, v);
850
- }
851
- }
852
- const interp = new Interpreter(ctx, opts);
853
- const rootScope = newScope(null);
854
- const registry = opts.moduleRegistry;
855
- const importerStack = opts.__importerStack;
856
- for (const item of program.items) {
857
- registerTopLevelFunctions(item, ctx, rootScope);
858
- }
859
- try {
860
- for (const item of program.items) {
861
- await evalTopLevelItem(item, interp, ctx, rootScope, registry, opts.entryFile, importerStack, opts);
862
- }
863
- }
864
- catch (e) {
865
- if (e instanceof TrainException)
866
- return { ok: false, error: e, ctx };
867
- throw e;
868
- }
869
- return { ok: true, ctx };
870
- }
871
- function registerExports(decl, ctx) {
872
- const tgt = decl.target;
873
- if (tgt.kind === 'ExportNames') {
874
- for (const spec of tgt.specs) {
875
- const exported = spec.alias ?? spec.name;
876
- ctx.exports.set(exported, spec.name);
877
- }
878
- return;
879
- }
880
- ctx.exports.set(tgt.name, tgt.name);
881
- }
882
- // ─── Local helpers ────────────────────────────────────────────────────
883
- function typeName(v) {
884
- if (v === null)
885
- return 'null';
886
- if (Array.isArray(v))
887
- return 'array';
888
- if (isFunctionValue(v) || isBuiltin(v))
889
- return 'function';
890
- return typeof v;
891
- }
892
- function numBin(expr, l, r, fn) {
893
- if (typeof l === 'number' && typeof r === 'number')
894
- return fn(l, r);
895
- throw binTypeErr(expr, l, r);
896
- }
897
- function cmp(expr, l, r) {
898
- if (typeof l === 'number' && typeof r === 'number') {
899
- return l < r ? -1 : l > r ? 1 : 0;
900
- }
901
- if (typeof l === 'string' && typeof r === 'string') {
902
- return l < r ? -1 : l > r ? 1 : 0;
903
- }
904
- throw binTypeErr(expr, l, r);
905
- }
906
- function binTypeErr(expr, l, r) {
907
- return new TrainException('RuntimeError', `operator '${expr.op}' undefined for ${typeName(l)} and ${typeName(r)}`, expr.range);
908
- }
909
- function deepEqValue(a, b) {
910
- if (a === b)
911
- return true;
912
- if (a === null || b === null)
913
- return false;
914
- if (typeof a !== typeof b)
915
- return false;
916
- if (Array.isArray(a) && Array.isArray(b)) {
917
- if (a.length !== b.length)
918
- return false;
919
- for (let i = 0; i < a.length; i++)
920
- if (!deepEqValue(a[i], b[i]))
921
- return false;
922
- return true;
923
- }
924
- if (typeof a === 'object' && typeof b === 'object') {
925
- if (Array.isArray(a) || Array.isArray(b))
926
- return false;
927
- const ao = a;
928
- const bo = b;
929
- const ak = Object.keys(ao);
930
- const bk = Object.keys(bo);
931
- if (ak.length !== bk.length)
932
- return false;
933
- for (const k of ak) {
934
- if (!(k in bo))
935
- return false;
936
- if (!deepEqValue(ao[k], bo[k]))
937
- return false;
938
- }
939
- return true;
940
- }
941
- return false;
942
- }
943
- //# sourceMappingURL=interpreter.js.map