@tom2012/cc-web 2026.5.14-a → 2026.5.15-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +51 -0
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/flows.d.ts.map +1 -1
- package/backend/dist/routes/flows.js +10 -0
- package/backend/dist/routes/flows.js.map +1 -1
- package/backend/dist/routes/global-tracks.d.ts +11 -0
- package/backend/dist/routes/global-tracks.d.ts.map +1 -0
- package/backend/dist/routes/global-tracks.js +86 -0
- package/backend/dist/routes/global-tracks.js.map +1 -0
- package/backend/dist/routes/tracks.d.ts +14 -0
- package/backend/dist/routes/tracks.d.ts.map +1 -0
- package/backend/dist/routes/tracks.js +184 -0
- package/backend/dist/routes/tracks.js.map +1 -0
- package/backend/dist/tracks/__tests__/verify-track-t1.d.ts +11 -0
- package/backend/dist/tracks/__tests__/verify-track-t1.d.ts.map +1 -0
- package/backend/dist/tracks/__tests__/verify-track-t1.js +293 -0
- package/backend/dist/tracks/__tests__/verify-track-t1.js.map +1 -0
- package/backend/dist/tracks/__tests__/verify-track.d.ts +20 -0
- package/backend/dist/tracks/__tests__/verify-track.d.ts.map +1 -0
- package/backend/dist/tracks/__tests__/verify-track.js +187 -0
- package/backend/dist/tracks/__tests__/verify-track.js.map +1 -0
- package/backend/dist/tracks/ask-user-bridge.d.ts +59 -0
- package/backend/dist/tracks/ask-user-bridge.d.ts.map +1 -0
- package/backend/dist/tracks/ask-user-bridge.js +186 -0
- package/backend/dist/tracks/ask-user-bridge.js.map +1 -0
- package/backend/dist/tracks/ccweb-train-adapter.d.ts +40 -0
- package/backend/dist/tracks/ccweb-train-adapter.d.ts.map +1 -0
- package/backend/dist/tracks/ccweb-train-adapter.js +111 -0
- package/backend/dist/tracks/ccweb-train-adapter.js.map +1 -0
- package/backend/dist/tracks/cross-lock.d.ts +30 -0
- package/backend/dist/tracks/cross-lock.d.ts.map +1 -0
- package/backend/dist/tracks/cross-lock.js +44 -0
- package/backend/dist/tracks/cross-lock.js.map +1 -0
- package/backend/dist/tracks/index.d.ts +20 -0
- package/backend/dist/tracks/index.d.ts.map +1 -0
- package/backend/dist/tracks/index.js +42 -0
- package/backend/dist/tracks/index.js.map +1 -0
- package/backend/dist/tracks/registry.d.ts +56 -0
- package/backend/dist/tracks/registry.d.ts.map +1 -0
- package/backend/dist/tracks/registry.js +161 -0
- package/backend/dist/tracks/registry.js.map +1 -0
- package/backend/dist/tracks/store.d.ts +37 -0
- package/backend/dist/tracks/store.d.ts.map +1 -0
- package/backend/dist/tracks/store.js +254 -0
- package/backend/dist/tracks/store.js.map +1 -0
- package/backend/dist/tracks/track-runner.d.ts +62 -0
- package/backend/dist/tracks/track-runner.d.ts.map +1 -0
- package/backend/dist/tracks/track-runner.js +134 -0
- package/backend/dist/tracks/track-runner.js.map +1 -0
- package/backend/dist/tracks/train-loader.d.ts +44 -0
- package/backend/dist/tracks/train-loader.d.ts.map +1 -0
- package/backend/dist/tracks/train-loader.js +32 -0
- package/backend/dist/tracks/train-loader.js.map +1 -0
- package/backend/dist/tracks/types-train.d.ts +12 -0
- package/backend/dist/tracks/types-train.d.ts.map +1 -0
- package/backend/dist/tracks/types-train.js +13 -0
- package/backend/dist/tracks/types-train.js.map +1 -0
- package/backend/dist/tracks/types.d.ts +66 -0
- package/backend/dist/tracks/types.d.ts.map +1 -0
- package/backend/dist/tracks/types.js +12 -0
- package/backend/dist/tracks/types.js.map +1 -0
- package/backend/dist/tracks/workflow-data-watcher.d.ts +47 -0
- package/backend/dist/tracks/workflow-data-watcher.d.ts.map +1 -0
- package/backend/dist/tracks/workflow-data-watcher.js +166 -0
- package/backend/dist/tracks/workflow-data-watcher.js.map +1 -0
- package/backend/package-lock.json +107 -0
- package/backend/package.json +6 -1
- package/backend/vendor/@train-lang/adapter-spec/dist/index.d.ts +164 -0
- package/backend/vendor/@train-lang/adapter-spec/dist/index.d.ts.map +1 -0
- package/backend/vendor/@train-lang/adapter-spec/dist/index.js +13 -0
- package/backend/vendor/@train-lang/adapter-spec/dist/index.js.map +1 -0
- package/backend/vendor/@train-lang/adapter-spec/package.json +15 -0
- package/backend/vendor/@train-lang/core/dist/ast-cache.d.ts +74 -0
- package/backend/vendor/@train-lang/core/dist/ast-cache.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/ast-cache.js +157 -0
- package/backend/vendor/@train-lang/core/dist/ast-cache.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/ast.d.ts +350 -0
- package/backend/vendor/@train-lang/core/dist/ast.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/ast.js +15 -0
- package/backend/vendor/@train-lang/core/dist/ast.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/builder.d.ts +21 -0
- package/backend/vendor/@train-lang/core/dist/builder.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/builder.js +1221 -0
- package/backend/vendor/@train-lang/core/dist/builder.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/builtins.d.ts +17 -0
- package/backend/vendor/@train-lang/core/dist/builtins.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/builtins.js +488 -0
- package/backend/vendor/@train-lang/core/dist/builtins.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/index.d.ts +54 -0
- package/backend/vendor/@train-lang/core/dist/index.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/index.js +66 -0
- package/backend/vendor/@train-lang/core/dist/index.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/interpreter.d.ts +110 -0
- package/backend/vendor/@train-lang/core/dist/interpreter.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/interpreter.js +894 -0
- package/backend/vendor/@train-lang/core/dist/interpreter.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/lexer.d.ts +88 -0
- package/backend/vendor/@train-lang/core/dist/lexer.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/lexer.js +243 -0
- package/backend/vendor/@train-lang/core/dist/lexer.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/module-loader.d.ts +74 -0
- package/backend/vendor/@train-lang/core/dist/module-loader.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/module-loader.js +134 -0
- package/backend/vendor/@train-lang/core/dist/module-loader.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/parser.d.ts +135 -0
- package/backend/vendor/@train-lang/core/dist/parser.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/parser.js +838 -0
- package/backend/vendor/@train-lang/core/dist/parser.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/prompt-composer.d.ts +49 -0
- package/backend/vendor/@train-lang/core/dist/prompt-composer.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/prompt-composer.js +159 -0
- package/backend/vendor/@train-lang/core/dist/prompt-composer.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/runtime.d.ts +146 -0
- package/backend/vendor/@train-lang/core/dist/runtime.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/runtime.js +156 -0
- package/backend/vendor/@train-lang/core/dist/runtime.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/type-descriptor.d.ts +18 -0
- package/backend/vendor/@train-lang/core/dist/type-descriptor.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/type-descriptor.js +94 -0
- package/backend/vendor/@train-lang/core/dist/type-descriptor.js.map +1 -0
- package/backend/vendor/@train-lang/core/dist/validation.d.ts +44 -0
- package/backend/vendor/@train-lang/core/dist/validation.d.ts.map +1 -0
- package/backend/vendor/@train-lang/core/dist/validation.js +271 -0
- package/backend/vendor/@train-lang/core/dist/validation.js.map +1 -0
- package/backend/vendor/@train-lang/core/package.json +35 -0
- package/frontend/dist/assets/{ChatOverlay-DsfZXjjz.js → ChatOverlay-D0AFod2I.js} +1 -1
- package/frontend/dist/assets/{GraphPreview-BGZHA8KW.js → GraphPreview-BTnlkocf.js} +2 -2
- package/frontend/dist/assets/{MobilePage-B-FiVfUE.js → MobilePage-CnjdRqIt.js} +3 -3
- package/frontend/dist/assets/{OfficePreview-CvHPeAlI.js → OfficePreview-nTZKPD9R.js} +2 -2
- package/frontend/dist/assets/{PdfPreview-D1OVG7jG.js → PdfPreview-DlB-s5M8.js} +1 -1
- package/frontend/dist/assets/{ProjectPage-CyKEenc5.js → ProjectPage-C_3_sZbd.js} +5 -5
- package/frontend/dist/assets/{SettingsPage-CIe3HZml.js → SettingsPage-BYSCQKoh.js} +2 -2
- package/frontend/dist/assets/{SkillHubPage-CG1-G4Pf.js → SkillHubPage-CUgZXyeE.js} +3 -3
- package/frontend/dist/assets/TrackEditor-R2lmPugZ.js +14 -0
- package/frontend/dist/assets/abap-LPLW346S.js +7 -0
- package/frontend/dist/assets/apex-Dk-jUCUV.js +7 -0
- package/frontend/dist/assets/azcli-DPUMmPlX.js +7 -0
- package/frontend/dist/assets/bat-C1Qbg1bV.js +7 -0
- package/frontend/dist/assets/bicep-D-e-VSJi.js +7 -0
- package/frontend/dist/assets/cameligo-CjUqTgqL.js +7 -0
- package/frontend/dist/assets/{chevron-down-CS7uu2Ol.js → chevron-down-Bc6Xpnnk.js} +1 -1
- package/frontend/dist/assets/clojure-BWsu6Kju.js +7 -0
- package/frontend/dist/assets/codicon-DCmgc-ay.ttf +0 -0
- package/frontend/dist/assets/coffee-DeC36AK3.js +7 -0
- package/frontend/dist/assets/cpp-DcZnmBWT.js +7 -0
- package/frontend/dist/assets/csharp-DqBXBMf0.js +7 -0
- package/frontend/dist/assets/csp-CkO7y8ul.js +7 -0
- package/frontend/dist/assets/css-OAcfVtED.js +7 -0
- package/frontend/dist/assets/cssMode-oT-78_Ke.js +7 -0
- package/frontend/dist/assets/cypher-40UGrUjD.js +7 -0
- package/frontend/dist/assets/dart-DmixyLor.js +7 -0
- package/frontend/dist/assets/dockerfile-CayO4nTA.js +7 -0
- package/frontend/dist/assets/ecl-DRrKCuVc.js +7 -0
- package/frontend/dist/assets/editor-B5EY1bb8.css +1 -0
- package/frontend/dist/assets/editor.main-CKHkO4rf.js +37 -0
- package/frontend/dist/assets/elixir-J3qpp9mX.js +7 -0
- package/frontend/dist/assets/flow9-DzW2c4Im.js +7 -0
- package/frontend/dist/assets/freemarker2-gLqtlSdW.js +7 -0
- package/frontend/dist/assets/fsharp-D00tn_6c.js +7 -0
- package/frontend/dist/assets/go-NnNmgWK_.js +7 -0
- package/frontend/dist/assets/graphql-C9_--VLF.js +7 -0
- package/frontend/dist/assets/handlebars-CMR-cKzv.js +7 -0
- package/frontend/dist/assets/hcl-BhbOULbp.js +7 -0
- package/frontend/dist/assets/html-er6LXl7Q.js +7 -0
- package/frontend/dist/assets/htmlMode-BY7CWp3G.js +7 -0
- package/frontend/dist/assets/index-BmPb0Wl1.css +1 -0
- package/frontend/dist/assets/{index-DN91i4kg.js → index-C6JmYBh3.js} +1 -1
- package/frontend/dist/assets/{index-BThL9NV3.js → index-C7QaGdFu.js} +2 -2
- package/frontend/dist/assets/{index-DCc5jmus.js → index-CuoDBJ4_.js} +1 -1
- package/frontend/dist/assets/index-LxjrLxfp.js +1 -0
- package/frontend/dist/assets/ini-zR-_X5iG.js +7 -0
- package/frontend/dist/assets/java-CSTjsyoZ.js +7 -0
- package/frontend/dist/assets/javascript-R0QZ7Rw2.js +7 -0
- package/frontend/dist/assets/jsonMode-dh6Js0Qv.js +7 -0
- package/frontend/dist/assets/{jszip.min-DHcltCpp.js → jszip.min-D7W3wEX7.js} +1 -1
- package/frontend/dist/assets/julia-DioMOKVu.js +7 -0
- package/frontend/dist/assets/kotlin-Dsb0PKmW.js +7 -0
- package/frontend/dist/assets/less-CZYLoAVD.js +7 -0
- package/frontend/dist/assets/lexon-d5JUiwNk.js +7 -0
- package/frontend/dist/assets/liquid-ggFR895M.js +7 -0
- package/frontend/dist/assets/lua-1Al72GG8.js +7 -0
- package/frontend/dist/assets/m3-hx1xCPC3.js +7 -0
- package/frontend/dist/assets/markdown-DPGrH1xZ.js +7 -0
- package/frontend/dist/assets/mdx-B4WBy0Yh.js +7 -0
- package/frontend/dist/assets/mips-CYbZjkIm.js +7 -0
- package/frontend/dist/assets/msdax-DAioKM5A.js +7 -0
- package/frontend/dist/assets/mysql-7e9GUebS.js +7 -0
- package/frontend/dist/assets/objective-c-BGf8QNjz.js +7 -0
- package/frontend/dist/assets/pascal-hEDRz3un.js +7 -0
- package/frontend/dist/assets/pascaligo-gqEhN6pG.js +7 -0
- package/frontend/dist/assets/perl-CN8Ht9Vk.js +7 -0
- package/frontend/dist/assets/pgsql-CRCqHQBx.js +7 -0
- package/frontend/dist/assets/php-DTZrlAwe.js +7 -0
- package/frontend/dist/assets/pla-CRmh4UcC.js +7 -0
- package/frontend/dist/assets/postiats-Car2G7g8.js +7 -0
- package/frontend/dist/assets/powerquery-BJKl8V3o.js +7 -0
- package/frontend/dist/assets/powershell-Du7a_J7r.js +7 -0
- package/frontend/dist/assets/protobuf-ChneWI0H.js +7 -0
- package/frontend/dist/assets/pug-BH6LSJaf.js +7 -0
- package/frontend/dist/assets/python-AIGmVleM.js +7 -0
- package/frontend/dist/assets/qsharp-CU-Hxpxi.js +7 -0
- package/frontend/dist/assets/r-Bw-W15zh.js +7 -0
- package/frontend/dist/assets/razor-BGAZW957.js +7 -0
- package/frontend/dist/assets/redis-bY-X9-ol.js +7 -0
- package/frontend/dist/assets/redshift-CMu3ubbS.js +7 -0
- package/frontend/dist/assets/restructuredtext-KNkiWGNN.js +7 -0
- package/frontend/dist/assets/ruby-dQWMhU86.js +7 -0
- package/frontend/dist/assets/rust-BAcHhnEU.js +7 -0
- package/frontend/dist/assets/sb-Bidr5w2z.js +7 -0
- package/frontend/dist/assets/scala-imTtxsGY.js +7 -0
- package/frontend/dist/assets/scheme-BY-WHKiB.js +7 -0
- package/frontend/dist/assets/scss-NDoRP52C.js +7 -0
- package/frontend/dist/assets/{select-C09dwvOR.js → select-B9gOzVx5.js} +1 -1
- package/frontend/dist/assets/shell-DrIjotnh.js +7 -0
- package/frontend/dist/assets/solidity-DTahNycF.js +7 -0
- package/frontend/dist/assets/sophia-DtyrjGkZ.js +7 -0
- package/frontend/dist/assets/sparql-CFuS5E3z.js +7 -0
- package/frontend/dist/assets/sql-BRxcnbRv.js +7 -0
- package/frontend/dist/assets/st-Dz4uh7MK.js +7 -0
- package/frontend/dist/assets/swift-Digsrw1G.js +11 -0
- package/frontend/dist/assets/systemverilog-7TDrkMau.js +7 -0
- package/frontend/dist/assets/tcl-BtaFcnDi.js +7 -0
- package/frontend/dist/assets/tsMode-UrEEwBkN.js +7 -0
- package/frontend/dist/assets/twig-Deid_oBs.js +7 -0
- package/frontend/dist/assets/typescript-BTfMCp5C.js +7 -0
- package/frontend/dist/assets/typespec-zUaxhbG8.js +7 -0
- package/frontend/dist/assets/{user-Bj8X-WCQ.js → user-BG-Xqf8M.js} +1 -1
- package/frontend/dist/assets/vb-DK0rKJzo.js +7 -0
- package/frontend/dist/assets/wgsl-B8fNVlOQ.js +7 -0
- package/frontend/dist/assets/xml-BCL5DPdB.js +7 -0
- package/frontend/dist/assets/yaml-B__nW1c2.js +7 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +2 -1
- package/frontend/dist/assets/index-BkQ6KI1l.css +0 -1
|
@@ -0,0 +1,894 @@
|
|
|
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
|
+
throw new TrainException('RuntimeError', `unknown property '${prop}'`, range);
|
|
192
|
+
}
|
|
193
|
+
if (Array.isArray(obj)) {
|
|
194
|
+
if (prop === 'length')
|
|
195
|
+
return obj.length;
|
|
196
|
+
}
|
|
197
|
+
if (typeof obj === 'string') {
|
|
198
|
+
if (prop === 'length')
|
|
199
|
+
return [...obj].length;
|
|
200
|
+
}
|
|
201
|
+
throw new TrainException('RuntimeError', `cannot read property '${prop}' on ${typeName(obj)}`, range);
|
|
202
|
+
}
|
|
203
|
+
getIndex(obj, key, range) {
|
|
204
|
+
if (Array.isArray(obj)) {
|
|
205
|
+
if (typeof key !== 'number')
|
|
206
|
+
throw new TrainException('RuntimeError', `array index must be a number, got ${typeName(key)}`, range);
|
|
207
|
+
const i = key < 0 ? obj.length + key : key;
|
|
208
|
+
if (i < 0 || i >= obj.length)
|
|
209
|
+
return null;
|
|
210
|
+
return obj[i];
|
|
211
|
+
}
|
|
212
|
+
if (typeof obj === 'string') {
|
|
213
|
+
if (typeof key !== 'number')
|
|
214
|
+
throw new TrainException('RuntimeError', `string index must be a number, got ${typeName(key)}`, range);
|
|
215
|
+
const codepoints = [...obj];
|
|
216
|
+
const i = key < 0 ? codepoints.length + key : key;
|
|
217
|
+
if (i < 0 || i >= codepoints.length)
|
|
218
|
+
return null;
|
|
219
|
+
return codepoints[i];
|
|
220
|
+
}
|
|
221
|
+
if (obj !== null && typeof obj === 'object') {
|
|
222
|
+
if (typeof key !== 'string')
|
|
223
|
+
throw new TrainException('RuntimeError', `object key must be a string, got ${typeName(key)}`, range);
|
|
224
|
+
const o = obj;
|
|
225
|
+
return key in o ? o[key] : null;
|
|
226
|
+
}
|
|
227
|
+
throw new TrainException('RuntimeError', `cannot index ${typeName(obj)}`, range);
|
|
228
|
+
}
|
|
229
|
+
async evalCall(expr, scope) {
|
|
230
|
+
const callee = await this.evalExpr(expr.callee, scope);
|
|
231
|
+
const args = [];
|
|
232
|
+
for (const a of expr.args)
|
|
233
|
+
args.push(await this.evalExpr(a, scope));
|
|
234
|
+
if (isFunctionValue(callee)) {
|
|
235
|
+
if (callee.isFai) {
|
|
236
|
+
return this.callFai(callee, args, expr.range);
|
|
237
|
+
}
|
|
238
|
+
return this.callFunc(callee, args, expr.range);
|
|
239
|
+
}
|
|
240
|
+
if (isBuiltin(callee)) {
|
|
241
|
+
const b = callee;
|
|
242
|
+
// Builtins may be sync or async (Value | Promise<Value>); awaiting
|
|
243
|
+
// a non-Promise is a no-op so this is safe for both cases.
|
|
244
|
+
return await b.call(args);
|
|
245
|
+
}
|
|
246
|
+
throw new TrainException('RuntimeError', `value is not callable: ${typeName(callee)}`, expr.range);
|
|
247
|
+
}
|
|
248
|
+
async callFunc(fn, args, range) {
|
|
249
|
+
if (fn.decl.kind !== 'FuncDecl')
|
|
250
|
+
throw new InterpreterBug('callFunc dispatched on non-func decl');
|
|
251
|
+
const decl = fn.decl;
|
|
252
|
+
if (args.length !== decl.params.length) {
|
|
253
|
+
throw new TrainException('RuntimeError', `${fn.name}() expects ${decl.params.length} arg(s), got ${args.length}`, range);
|
|
254
|
+
}
|
|
255
|
+
const callScope = newScope(fn.definedIn);
|
|
256
|
+
decl.params.forEach((p, i) => callScope.bindings.set(p.name, args[i]));
|
|
257
|
+
const prevCtx = this.ctx;
|
|
258
|
+
if (fn.moduleCtx && fn.moduleCtx !== this.ctx)
|
|
259
|
+
this.ctx = fn.moduleCtx;
|
|
260
|
+
try {
|
|
261
|
+
await this.execBlock(decl.body, callScope);
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
if (e instanceof TrainReturnSignal)
|
|
265
|
+
return e.value ?? null;
|
|
266
|
+
throw e;
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
this.ctx = prevCtx;
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Execute a fai call: compose prompt → adapter.call → validate →
|
|
275
|
+
* retry-with-feedback loop. Returns the validated outputs as a single
|
|
276
|
+
* object (containing one key per declared output).
|
|
277
|
+
*/
|
|
278
|
+
async callFai(fn, args, range) {
|
|
279
|
+
if (fn.decl.kind !== 'FaiDecl')
|
|
280
|
+
throw new InterpreterBug('callFai dispatched on non-fai decl');
|
|
281
|
+
const decl = fn.decl;
|
|
282
|
+
if (!this.adapter) {
|
|
283
|
+
throw new TrainException('RuntimeError', `fai function '${fn.name}' requires an LLM adapter, none installed`, range);
|
|
284
|
+
}
|
|
285
|
+
if (args.length !== decl.params.length) {
|
|
286
|
+
throw new TrainException('RuntimeError', `fai ${fn.name}() expects ${decl.params.length} arg(s), got ${args.length}`, range);
|
|
287
|
+
}
|
|
288
|
+
const callId = ++this.faiCallCounter;
|
|
289
|
+
const argMap = new Map();
|
|
290
|
+
decl.params.forEach((p, i) => argMap.set(p.name, args[i]));
|
|
291
|
+
// Compose the prompt once; retry attempts will append feedback.
|
|
292
|
+
const base = composePrompt(decl, argMap, {
|
|
293
|
+
capabilities: this.adapter.capabilities,
|
|
294
|
+
callId,
|
|
295
|
+
writeProtocolHint: this.writeProtocolHint,
|
|
296
|
+
});
|
|
297
|
+
let promptText = base.text;
|
|
298
|
+
let lastErrors = null;
|
|
299
|
+
for (let attempt = 0; attempt < this.maxFaiAttempts; attempt++) {
|
|
300
|
+
if (this.hostSignal?.aborted) {
|
|
301
|
+
throw new TrainException('UserCancelError', `fai ${fn.name}: cancelled before attempt ${attempt + 1}`, range);
|
|
302
|
+
}
|
|
303
|
+
const req = {
|
|
304
|
+
callId,
|
|
305
|
+
fnName: fn.name,
|
|
306
|
+
prompt: promptText,
|
|
307
|
+
inputs: base.inputs,
|
|
308
|
+
outputs: base.outputs,
|
|
309
|
+
options: {
|
|
310
|
+
timeoutMs: this.defaultFaiTimeoutMs,
|
|
311
|
+
maxAttempts: this.maxFaiAttempts,
|
|
312
|
+
attempt,
|
|
313
|
+
model: this.model,
|
|
314
|
+
signal: this.hostSignal,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
const result = await this.adapter.call(req);
|
|
318
|
+
switch (result.kind) {
|
|
319
|
+
case 'success': {
|
|
320
|
+
const validated = validateOutputs(decl.outputs, result.outputs);
|
|
321
|
+
if (validated.ok) {
|
|
322
|
+
return validated.outputs;
|
|
323
|
+
}
|
|
324
|
+
lastErrors = validated;
|
|
325
|
+
if (attempt < this.maxFaiAttempts - 1) {
|
|
326
|
+
// Append feedback to prompt and retry
|
|
327
|
+
promptText =
|
|
328
|
+
base.text +
|
|
329
|
+
'\n\n' +
|
|
330
|
+
composeRetryFeedback(validated.errors, attempt, this.maxFaiAttempts);
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'validation-error':
|
|
335
|
+
// Adapter performed its own validation and rejected outputs
|
|
336
|
+
lastErrors = { ok: false, errors: result.errors };
|
|
337
|
+
if (attempt < this.maxFaiAttempts - 1) {
|
|
338
|
+
promptText =
|
|
339
|
+
base.text +
|
|
340
|
+
'\n\n' +
|
|
341
|
+
composeRetryFeedback(result.errors, attempt, this.maxFaiAttempts);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
case 'timeout':
|
|
345
|
+
throw new TrainException('TimeoutError', `fai ${fn.name}: adapter timed out (attempt ${attempt + 1})`, range);
|
|
346
|
+
case 'cancelled':
|
|
347
|
+
throw new TrainException('UserCancelError', `fai ${fn.name}: cancelled`, range);
|
|
348
|
+
case 'error':
|
|
349
|
+
if (result.recoverable && attempt < this.maxFaiAttempts - 1) {
|
|
350
|
+
// try again with original prompt (no schema feedback)
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
throw new TrainException('RuntimeError', `fai ${fn.name}: ${result.message}`, range);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// All attempts exhausted with validation errors
|
|
357
|
+
const errs = (lastErrors && !lastErrors.ok ? lastErrors.errors : []) ?? [];
|
|
358
|
+
const summary = errs.map((e) => `${e.outputName}: ${e.message}`).join('; ');
|
|
359
|
+
throw new TrainException('ValidationError', `fai ${fn.name}: failed after ${this.maxFaiAttempts} attempt(s) — ${summary || 'no details'}`, range);
|
|
360
|
+
}
|
|
361
|
+
// ─── Statements / blocks ─────────────────────────────────────────────
|
|
362
|
+
async execBlock(block, scope) {
|
|
363
|
+
const inner = newScope(scope);
|
|
364
|
+
for (const s of block.stmts)
|
|
365
|
+
await this.execStmt(s, inner);
|
|
366
|
+
}
|
|
367
|
+
async execStmt(stmt, scope) {
|
|
368
|
+
switch (stmt.kind) {
|
|
369
|
+
case 'LetDecl':
|
|
370
|
+
return this.execLet(stmt, scope);
|
|
371
|
+
case 'Assignment':
|
|
372
|
+
return this.execAssign(stmt, scope);
|
|
373
|
+
case 'IfStmt':
|
|
374
|
+
return this.execIf(stmt, scope);
|
|
375
|
+
case 'ForStmt':
|
|
376
|
+
return this.execFor(stmt, scope);
|
|
377
|
+
case 'WhileStmt':
|
|
378
|
+
return this.execWhile(stmt, scope);
|
|
379
|
+
case 'TryStmt':
|
|
380
|
+
return this.execTry(stmt, scope);
|
|
381
|
+
case 'BreakStmt':
|
|
382
|
+
throw new TrainBreakSignal();
|
|
383
|
+
case 'ContinueStmt':
|
|
384
|
+
throw new TrainContinueSignal();
|
|
385
|
+
case 'ReturnStmt':
|
|
386
|
+
throw new TrainReturnSignal(stmt.value ? await this.evalExpr(stmt.value, scope) : null);
|
|
387
|
+
case 'ExprStmt':
|
|
388
|
+
await this.evalExpr(stmt.expr, scope);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async execLet(stmt, scope) {
|
|
393
|
+
const v = stmt.init ? await this.evalExpr(stmt.init, scope) : null;
|
|
394
|
+
this.bindLetTarget(stmt.target, v, scope, stmt.range);
|
|
395
|
+
}
|
|
396
|
+
bindLetTarget(target, v, scope, range) {
|
|
397
|
+
if (target.kind === 'IdentTarget') {
|
|
398
|
+
scope.bindings.set(target.name, v);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (target.kind === 'ObjectDestruct') {
|
|
402
|
+
if (v === null ||
|
|
403
|
+
typeof v !== 'object' ||
|
|
404
|
+
Array.isArray(v) ||
|
|
405
|
+
isFunctionValue(v) ||
|
|
406
|
+
isBuiltin(v)) {
|
|
407
|
+
throw new TrainException('RuntimeError', `cannot object-destructure ${typeName(v)}`, range);
|
|
408
|
+
}
|
|
409
|
+
const o = v;
|
|
410
|
+
for (const f of target.fields) {
|
|
411
|
+
scope.bindings.set(f.local, f.source in o ? o[f.source] : null);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (!Array.isArray(v)) {
|
|
416
|
+
throw new TrainException('RuntimeError', `cannot array-destructure ${typeName(v)}`, range);
|
|
417
|
+
}
|
|
418
|
+
for (let i = 0; i < target.names.length; i++) {
|
|
419
|
+
scope.bindings.set(target.names[i], i < v.length ? v[i] : null);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async execAssign(stmt, scope) {
|
|
423
|
+
const rhs = await this.evalExpr(stmt.value, scope);
|
|
424
|
+
const baseName = stmt.target.base;
|
|
425
|
+
if (stmt.target.suffixes.length === 0) {
|
|
426
|
+
const current = scopeLookup(scope, baseName) ??
|
|
427
|
+
(this.ctx.globals.has(baseName)
|
|
428
|
+
? this.ctx.globals.get(baseName)
|
|
429
|
+
: undefined);
|
|
430
|
+
const newVal = stmt.op === '='
|
|
431
|
+
? rhs
|
|
432
|
+
: this.applyCompound(stmt.op, current, rhs, stmt.range);
|
|
433
|
+
if (scopeAssign(scope, baseName, newVal))
|
|
434
|
+
return;
|
|
435
|
+
if (this.ctx.globals.has(baseName)) {
|
|
436
|
+
this.ctx.globals.set(baseName, newVal);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (this.ctx.constants.has(baseName))
|
|
440
|
+
throw new TrainException('RuntimeError', `cannot reassign const '${baseName}'`, stmt.range);
|
|
441
|
+
throw new TrainException('RuntimeError', `assignment to undeclared variable '${baseName}'`, stmt.range);
|
|
442
|
+
}
|
|
443
|
+
let cursor = scopeLookup(scope, baseName) ??
|
|
444
|
+
this.ctx.globals.get(baseName) ??
|
|
445
|
+
this.ctx.constants.get(baseName);
|
|
446
|
+
if (cursor === undefined) {
|
|
447
|
+
throw new TrainException('RuntimeError', `undefined identifier '${baseName}'`, stmt.range);
|
|
448
|
+
}
|
|
449
|
+
for (let i = 0; i < stmt.target.suffixes.length - 1; i++) {
|
|
450
|
+
cursor = await this.followSuffix(cursor, stmt.target.suffixes[i], scope);
|
|
451
|
+
}
|
|
452
|
+
const lastSuf = stmt.target.suffixes[stmt.target.suffixes.length - 1];
|
|
453
|
+
const oldValue = await this.followSuffix(cursor, lastSuf, scope);
|
|
454
|
+
const newVal = stmt.op === '='
|
|
455
|
+
? rhs
|
|
456
|
+
: this.applyCompound(stmt.op, oldValue, rhs, stmt.range);
|
|
457
|
+
await this.setSuffix(cursor, lastSuf, newVal, scope, stmt.range);
|
|
458
|
+
}
|
|
459
|
+
async followSuffix(obj, suf, scope) {
|
|
460
|
+
if (suf.kind === 'MemberSuffix')
|
|
461
|
+
return this.getMember(obj, suf.name);
|
|
462
|
+
return this.getIndex(obj, await this.evalExpr(suf.index, scope));
|
|
463
|
+
}
|
|
464
|
+
async setSuffix(obj, suf, val, scope, range) {
|
|
465
|
+
if (suf.kind === 'MemberSuffix') {
|
|
466
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj))
|
|
467
|
+
throw new TrainException('RuntimeError', `cannot set property '${suf.name}' on ${typeName(obj)}`, range);
|
|
468
|
+
obj[suf.name] = val;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const key = await this.evalExpr(suf.index, scope);
|
|
472
|
+
if (Array.isArray(obj)) {
|
|
473
|
+
if (typeof key !== 'number')
|
|
474
|
+
throw new TrainException('RuntimeError', `array index must be a number, got ${typeName(key)}`, range);
|
|
475
|
+
const i = key < 0 ? obj.length + key : key;
|
|
476
|
+
obj[i] = val;
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (obj !== null && typeof obj === 'object') {
|
|
480
|
+
if (typeof key !== 'string')
|
|
481
|
+
throw new TrainException('RuntimeError', `object key must be a string`, range);
|
|
482
|
+
obj[key] = val;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
throw new TrainException('RuntimeError', `cannot index-assign ${typeName(obj)}`, range);
|
|
486
|
+
}
|
|
487
|
+
applyCompound(op, old, rhs, range) {
|
|
488
|
+
if (old === undefined)
|
|
489
|
+
throw new TrainException('RuntimeError', `compound assignment ${op} on undeclared variable`, range);
|
|
490
|
+
if (typeof old !== 'number' || typeof rhs !== 'number') {
|
|
491
|
+
if (op === '+=' && (typeof old === 'string' || typeof rhs === 'string')) {
|
|
492
|
+
return formatValue(old) + formatValue(rhs);
|
|
493
|
+
}
|
|
494
|
+
throw new TrainException('RuntimeError', `${op} requires numbers, got ${typeName(old)} and ${typeName(rhs)}`, range);
|
|
495
|
+
}
|
|
496
|
+
switch (op) {
|
|
497
|
+
case '+=':
|
|
498
|
+
return old + rhs;
|
|
499
|
+
case '-=':
|
|
500
|
+
return old - rhs;
|
|
501
|
+
case '*=':
|
|
502
|
+
return old * rhs;
|
|
503
|
+
case '/=':
|
|
504
|
+
if (rhs === 0)
|
|
505
|
+
throw new TrainException('RuntimeError', 'division by zero', range);
|
|
506
|
+
return old / rhs;
|
|
507
|
+
case '%=':
|
|
508
|
+
if (rhs === 0)
|
|
509
|
+
throw new TrainException('RuntimeError', 'modulo by zero', range);
|
|
510
|
+
return old % rhs;
|
|
511
|
+
case '=':
|
|
512
|
+
return rhs;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async execIf(stmt, scope) {
|
|
516
|
+
if (this.truthy(await this.evalExpr(stmt.cond, scope))) {
|
|
517
|
+
return this.execBlock(stmt.then, scope);
|
|
518
|
+
}
|
|
519
|
+
for (const elif of stmt.elifs) {
|
|
520
|
+
if (this.truthy(await this.evalExpr(elif.cond, scope))) {
|
|
521
|
+
return this.execBlock(elif.body, scope);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (stmt.otherwise)
|
|
525
|
+
await this.execBlock(stmt.otherwise, scope);
|
|
526
|
+
}
|
|
527
|
+
async execFor(stmt, scope) {
|
|
528
|
+
const iter = await this.evalExpr(stmt.iterable, scope);
|
|
529
|
+
const items = this.iterable(iter, stmt.range);
|
|
530
|
+
for (const item of items) {
|
|
531
|
+
const inner = newScope(scope);
|
|
532
|
+
inner.bindings.set(stmt.binding, item);
|
|
533
|
+
try {
|
|
534
|
+
await this.execBlock(stmt.body, inner);
|
|
535
|
+
}
|
|
536
|
+
catch (e) {
|
|
537
|
+
if (e instanceof TrainBreakSignal)
|
|
538
|
+
return;
|
|
539
|
+
if (e instanceof TrainContinueSignal)
|
|
540
|
+
continue;
|
|
541
|
+
throw e;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
iterable(v, range) {
|
|
546
|
+
if (Array.isArray(v))
|
|
547
|
+
return v;
|
|
548
|
+
if (typeof v === 'string')
|
|
549
|
+
return [...v];
|
|
550
|
+
if (v !== null &&
|
|
551
|
+
typeof v === 'object' &&
|
|
552
|
+
!isFunctionValue(v) &&
|
|
553
|
+
!isBuiltin(v)) {
|
|
554
|
+
return Object.keys(v);
|
|
555
|
+
}
|
|
556
|
+
throw new TrainException('RuntimeError', `cannot iterate ${typeName(v)}`, range);
|
|
557
|
+
}
|
|
558
|
+
async execWhile(stmt, scope) {
|
|
559
|
+
while (this.truthy(await this.evalExpr(stmt.cond, scope))) {
|
|
560
|
+
try {
|
|
561
|
+
await this.execBlock(stmt.body, scope);
|
|
562
|
+
}
|
|
563
|
+
catch (e) {
|
|
564
|
+
if (e instanceof TrainBreakSignal)
|
|
565
|
+
return;
|
|
566
|
+
if (e instanceof TrainContinueSignal)
|
|
567
|
+
continue;
|
|
568
|
+
throw e;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async execTry(stmt, scope) {
|
|
573
|
+
try {
|
|
574
|
+
await this.execBlock(stmt.body, scope);
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
if (e instanceof TrainException) {
|
|
578
|
+
for (const c of stmt.catches) {
|
|
579
|
+
if (c.errorType === e.errorType) {
|
|
580
|
+
const inner = newScope(scope);
|
|
581
|
+
if (c.binding) {
|
|
582
|
+
inner.bindings.set(c.binding, {
|
|
583
|
+
type: e.errorType,
|
|
584
|
+
message: e.message,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
await this.execBlock(c.body, inner);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
throw e;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
596
|
+
truthy(v) {
|
|
597
|
+
if (v === null)
|
|
598
|
+
return false;
|
|
599
|
+
if (typeof v === 'boolean')
|
|
600
|
+
return v;
|
|
601
|
+
if (typeof v === 'number')
|
|
602
|
+
return v !== 0;
|
|
603
|
+
if (typeof v === 'string')
|
|
604
|
+
return v.length > 0;
|
|
605
|
+
if (Array.isArray(v))
|
|
606
|
+
return v.length > 0;
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
export async function runProgram(program, opts = {}) {
|
|
611
|
+
const ctx = {
|
|
612
|
+
constants: new Map(),
|
|
613
|
+
globals: new Map(),
|
|
614
|
+
functions: new Map(),
|
|
615
|
+
builtins: new Map(),
|
|
616
|
+
exports: new Map(),
|
|
617
|
+
};
|
|
618
|
+
for (const [k, v] of defaultBuiltinBindings()) {
|
|
619
|
+
ctx.builtins.set(k, v);
|
|
620
|
+
}
|
|
621
|
+
if (opts.extraBuiltins) {
|
|
622
|
+
for (const [k, v] of opts.extraBuiltins) {
|
|
623
|
+
ctx.builtins.set(k, v);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const interp = new Interpreter(ctx, opts);
|
|
627
|
+
const rootScope = newScope(null);
|
|
628
|
+
const moduleRegistry = opts.moduleRegistry ?? createModuleRegistry();
|
|
629
|
+
const entryFile = opts.entryFile;
|
|
630
|
+
const importerStack = opts.__importerStack ?? (entryFile ? [entryFile] : []);
|
|
631
|
+
for (const item of program.items) {
|
|
632
|
+
registerTopLevelFunctions(item, ctx, rootScope);
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
for (const item of program.items) {
|
|
636
|
+
await evalTopLevelItem(item, interp, ctx, rootScope, moduleRegistry, entryFile, importerStack, opts);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
if (e instanceof TrainException)
|
|
641
|
+
return { ok: false, value: null, error: e };
|
|
642
|
+
throw e;
|
|
643
|
+
}
|
|
644
|
+
const entryName = opts.entry ?? 'main';
|
|
645
|
+
const internalName = ctx.exports.get(entryName);
|
|
646
|
+
if (!internalName) {
|
|
647
|
+
return {
|
|
648
|
+
ok: false,
|
|
649
|
+
value: null,
|
|
650
|
+
error: new TrainException('RuntimeError', `no export named '${entryName}' found`),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const fn = ctx.functions.get(internalName);
|
|
654
|
+
if (!fn) {
|
|
655
|
+
return {
|
|
656
|
+
ok: false,
|
|
657
|
+
value: null,
|
|
658
|
+
error: new TrainException('RuntimeError', `export '${entryName}' is not a function`),
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
if (fn.isFai) {
|
|
663
|
+
// Allow fai as entry only if adapter installed
|
|
664
|
+
if (!opts.adapter) {
|
|
665
|
+
return {
|
|
666
|
+
ok: false,
|
|
667
|
+
value: null,
|
|
668
|
+
error: new TrainException('RuntimeError', `entry function '${entryName}' is a fai; cannot run without an LLM adapter`),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const value = await interp.callFai(fn, opts.args ?? []);
|
|
672
|
+
return { ok: true, value };
|
|
673
|
+
}
|
|
674
|
+
const value = await interp.callFunc(fn, opts.args ?? []);
|
|
675
|
+
return { ok: true, value };
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
if (e instanceof TrainException)
|
|
679
|
+
return { ok: false, value: null, error: e };
|
|
680
|
+
throw e;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function registerTopLevelFunctions(item, ctx, rootScope) {
|
|
684
|
+
if (item.kind === 'FuncDecl' || item.kind === 'FaiDecl') {
|
|
685
|
+
ctx.functions.set(item.name, {
|
|
686
|
+
__kind: 'function',
|
|
687
|
+
name: item.name,
|
|
688
|
+
isFai: item.kind === 'FaiDecl',
|
|
689
|
+
decl: item,
|
|
690
|
+
definedIn: rootScope,
|
|
691
|
+
moduleCtx: ctx,
|
|
692
|
+
});
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (item.kind === 'ExportDecl') {
|
|
696
|
+
const tgt = item.target;
|
|
697
|
+
if (tgt.kind === 'FuncDecl' || tgt.kind === 'FaiDecl') {
|
|
698
|
+
ctx.functions.set(tgt.name, {
|
|
699
|
+
__kind: 'function',
|
|
700
|
+
name: tgt.name,
|
|
701
|
+
isFai: tgt.kind === 'FaiDecl',
|
|
702
|
+
decl: tgt,
|
|
703
|
+
definedIn: rootScope,
|
|
704
|
+
moduleCtx: ctx,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async function evalTopLevelItem(item, interp, ctx, rootScope, moduleRegistry, currentFile, importerStack, opts) {
|
|
710
|
+
switch (item.kind) {
|
|
711
|
+
case 'Import':
|
|
712
|
+
await handleImport(item, ctx, moduleRegistry, currentFile, importerStack, opts);
|
|
713
|
+
return;
|
|
714
|
+
case 'RuntimeAnnotation':
|
|
715
|
+
return;
|
|
716
|
+
case 'ConstDecl':
|
|
717
|
+
ctx.constants.set(item.name, await interp.evalExpr(item.value, rootScope));
|
|
718
|
+
return;
|
|
719
|
+
case 'VarDecl':
|
|
720
|
+
ctx.globals.set(item.name, item.init ? await interp.evalExpr(item.init, rootScope) : null);
|
|
721
|
+
return;
|
|
722
|
+
case 'FuncDecl':
|
|
723
|
+
case 'FaiDecl':
|
|
724
|
+
return;
|
|
725
|
+
case 'ExportDecl':
|
|
726
|
+
registerExports(item, ctx);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function handleImport(imp, importerCtx, registry, currentFile, importerStack, rootOpts) {
|
|
731
|
+
if (!currentFile) {
|
|
732
|
+
throw new TrainException('ModuleError', `import statement requires entryFile to be set on runProgram (no current module path)`, imp.range, TrainErrorCode.ModuleNotFound);
|
|
733
|
+
}
|
|
734
|
+
const absPath = registry.resolve(imp.source, currentFile);
|
|
735
|
+
// cache hit
|
|
736
|
+
if (registry.hasCached(absPath)) {
|
|
737
|
+
applyImport(imp, registry.getCached(absPath), importerCtx);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
// cycle detection
|
|
741
|
+
if (registry.isInProgress(absPath)) {
|
|
742
|
+
const cycle = [...importerStack, absPath]
|
|
743
|
+
.map((p) => p.split('/').pop())
|
|
744
|
+
.join(' → ');
|
|
745
|
+
throw new TrainException('ModuleError', `circular import: ${cycle}`, imp.range, TrainErrorCode.CircularImport);
|
|
746
|
+
}
|
|
747
|
+
registry.markInProgress(absPath);
|
|
748
|
+
try {
|
|
749
|
+
const childSource = await registry.read(absPath);
|
|
750
|
+
const { parse } = await import('./parser.js');
|
|
751
|
+
const { buildAst } = await import('./builder.js');
|
|
752
|
+
const parseResult = parse(childSource);
|
|
753
|
+
if (parseResult.lexErrors.length > 0 || parseResult.parseErrors.length > 0) {
|
|
754
|
+
throw new TrainException('ModuleError', `module "${imp.source}" has parse errors (${parseResult.lexErrors.length + parseResult.parseErrors.length})`, imp.range, TrainErrorCode.ModuleNotFound);
|
|
755
|
+
}
|
|
756
|
+
const childAst = buildAst(parseResult.cst);
|
|
757
|
+
if (!childAst) {
|
|
758
|
+
throw new TrainException('ModuleError', `module "${imp.source}" failed to build AST`, imp.range, TrainErrorCode.ModuleNotFound);
|
|
759
|
+
}
|
|
760
|
+
// Recurse: run the child module's top-level via runProgram, but in
|
|
761
|
+
// "submodule mode" — no entry call, no result; we only want its ctx.
|
|
762
|
+
const childResult = await runSubmodule(childAst, {
|
|
763
|
+
...rootOpts,
|
|
764
|
+
entryFile: absPath,
|
|
765
|
+
moduleRegistry: registry,
|
|
766
|
+
__importerStack: [...importerStack, absPath],
|
|
767
|
+
});
|
|
768
|
+
if (!childResult.ok) {
|
|
769
|
+
throw (childResult.error ??
|
|
770
|
+
new TrainException('ModuleError', `module "${imp.source}" failed to evaluate`, imp.range, TrainErrorCode.ModuleNotFound));
|
|
771
|
+
}
|
|
772
|
+
registry.set(absPath, {
|
|
773
|
+
absPath,
|
|
774
|
+
ctx: childResult.ctx,
|
|
775
|
+
exports: collectExports(childResult.ctx),
|
|
776
|
+
});
|
|
777
|
+
applyImport(imp, registry.getCached(absPath), importerCtx);
|
|
778
|
+
}
|
|
779
|
+
finally {
|
|
780
|
+
registry.unmarkInProgress(absPath);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Execute a child module's top-level without calling its entry. Returns
|
|
785
|
+
* the populated RuntimeContext for export collection.
|
|
786
|
+
*/
|
|
787
|
+
async function runSubmodule(program, opts) {
|
|
788
|
+
const ctx = {
|
|
789
|
+
constants: new Map(),
|
|
790
|
+
globals: new Map(),
|
|
791
|
+
functions: new Map(),
|
|
792
|
+
builtins: new Map(),
|
|
793
|
+
exports: new Map(),
|
|
794
|
+
};
|
|
795
|
+
for (const [k, v] of defaultBuiltinBindings()) {
|
|
796
|
+
ctx.builtins.set(k, v);
|
|
797
|
+
}
|
|
798
|
+
if (opts.extraBuiltins) {
|
|
799
|
+
for (const [k, v] of opts.extraBuiltins) {
|
|
800
|
+
ctx.builtins.set(k, v);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const interp = new Interpreter(ctx, opts);
|
|
804
|
+
const rootScope = newScope(null);
|
|
805
|
+
const registry = opts.moduleRegistry;
|
|
806
|
+
const importerStack = opts.__importerStack;
|
|
807
|
+
for (const item of program.items) {
|
|
808
|
+
registerTopLevelFunctions(item, ctx, rootScope);
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
for (const item of program.items) {
|
|
812
|
+
await evalTopLevelItem(item, interp, ctx, rootScope, registry, opts.entryFile, importerStack, opts);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch (e) {
|
|
816
|
+
if (e instanceof TrainException)
|
|
817
|
+
return { ok: false, error: e, ctx };
|
|
818
|
+
throw e;
|
|
819
|
+
}
|
|
820
|
+
return { ok: true, ctx };
|
|
821
|
+
}
|
|
822
|
+
function registerExports(decl, ctx) {
|
|
823
|
+
const tgt = decl.target;
|
|
824
|
+
if (tgt.kind === 'ExportNames') {
|
|
825
|
+
for (const spec of tgt.specs) {
|
|
826
|
+
const exported = spec.alias ?? spec.name;
|
|
827
|
+
ctx.exports.set(exported, spec.name);
|
|
828
|
+
}
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
ctx.exports.set(tgt.name, tgt.name);
|
|
832
|
+
}
|
|
833
|
+
// ─── Local helpers ────────────────────────────────────────────────────
|
|
834
|
+
function typeName(v) {
|
|
835
|
+
if (v === null)
|
|
836
|
+
return 'null';
|
|
837
|
+
if (Array.isArray(v))
|
|
838
|
+
return 'array';
|
|
839
|
+
if (isFunctionValue(v) || isBuiltin(v))
|
|
840
|
+
return 'function';
|
|
841
|
+
return typeof v;
|
|
842
|
+
}
|
|
843
|
+
function numBin(expr, l, r, fn) {
|
|
844
|
+
if (typeof l === 'number' && typeof r === 'number')
|
|
845
|
+
return fn(l, r);
|
|
846
|
+
throw binTypeErr(expr, l, r);
|
|
847
|
+
}
|
|
848
|
+
function cmp(expr, l, r) {
|
|
849
|
+
if (typeof l === 'number' && typeof r === 'number') {
|
|
850
|
+
return l < r ? -1 : l > r ? 1 : 0;
|
|
851
|
+
}
|
|
852
|
+
if (typeof l === 'string' && typeof r === 'string') {
|
|
853
|
+
return l < r ? -1 : l > r ? 1 : 0;
|
|
854
|
+
}
|
|
855
|
+
throw binTypeErr(expr, l, r);
|
|
856
|
+
}
|
|
857
|
+
function binTypeErr(expr, l, r) {
|
|
858
|
+
return new TrainException('RuntimeError', `operator '${expr.op}' undefined for ${typeName(l)} and ${typeName(r)}`, expr.range);
|
|
859
|
+
}
|
|
860
|
+
function deepEqValue(a, b) {
|
|
861
|
+
if (a === b)
|
|
862
|
+
return true;
|
|
863
|
+
if (a === null || b === null)
|
|
864
|
+
return false;
|
|
865
|
+
if (typeof a !== typeof b)
|
|
866
|
+
return false;
|
|
867
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
868
|
+
if (a.length !== b.length)
|
|
869
|
+
return false;
|
|
870
|
+
for (let i = 0; i < a.length; i++)
|
|
871
|
+
if (!deepEqValue(a[i], b[i]))
|
|
872
|
+
return false;
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
876
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
877
|
+
return false;
|
|
878
|
+
const ao = a;
|
|
879
|
+
const bo = b;
|
|
880
|
+
const ak = Object.keys(ao);
|
|
881
|
+
const bk = Object.keys(bo);
|
|
882
|
+
if (ak.length !== bk.length)
|
|
883
|
+
return false;
|
|
884
|
+
for (const k of ak) {
|
|
885
|
+
if (!(k in bo))
|
|
886
|
+
return false;
|
|
887
|
+
if (!deepEqValue(ao[k], bo[k]))
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
//# sourceMappingURL=interpreter.js.map
|