@tom2012/cc-web 2026.5.18-b → 2026.5.18-e
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 +9 -21
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/_flow-injector.d.ts +4 -0
- package/backend/dist/routes/_flow-injector.d.ts.map +1 -0
- package/backend/dist/routes/_flow-injector.js +17 -0
- package/backend/dist/routes/_flow-injector.js.map +1 -0
- package/backend/dist/routes/global-tracks.d.ts +2 -5
- package/backend/dist/routes/global-tracks.d.ts.map +1 -1
- package/backend/dist/routes/global-tracks.js +2 -28
- package/backend/dist/routes/global-tracks.js.map +1 -1
- package/backend/dist/routes/track-flows.d.ts +12 -0
- package/backend/dist/routes/track-flows.d.ts.map +1 -0
- package/backend/dist/routes/track-flows.js +225 -0
- package/backend/dist/routes/track-flows.js.map +1 -0
- package/backend/dist/routes/tracks.d.ts +3 -9
- package/backend/dist/routes/tracks.d.ts.map +1 -1
- package/backend/dist/routes/tracks.js +3 -153
- package/backend/dist/routes/tracks.js.map +1 -1
- package/backend/dist/terminal-manager-singleton.d.ts +9 -0
- package/backend/dist/terminal-manager-singleton.d.ts.map +1 -0
- package/backend/dist/terminal-manager-singleton.js +6 -0
- package/backend/dist/terminal-manager-singleton.js.map +1 -0
- package/backend/dist/track-flow/__tests__/if-expr-evaluator.test.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/if-expr-evaluator.test.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/if-expr-evaluator.test.js +65 -0
- package/backend/dist/track-flow/__tests__/if-expr-evaluator.test.js.map +1 -0
- package/backend/dist/track-flow/__tests__/if-expr-parser.test.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/if-expr-parser.test.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/if-expr-parser.test.js +76 -0
- package/backend/dist/track-flow/__tests__/if-expr-parser.test.js.map +1 -0
- package/backend/dist/track-flow/__tests__/prompt-translator.test.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/prompt-translator.test.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/prompt-translator.test.js +59 -0
- package/backend/dist/track-flow/__tests__/prompt-translator.test.js.map +1 -0
- package/backend/dist/track-flow/__tests__/run-registry.test.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/run-registry.test.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/run-registry.test.js +54 -0
- package/backend/dist/track-flow/__tests__/run-registry.test.js.map +1 -0
- package/backend/dist/track-flow/__tests__/train-json-sync.test.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/train-json-sync.test.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/train-json-sync.test.js +93 -0
- package/backend/dist/track-flow/__tests__/train-json-sync.test.js.map +1 -0
- package/backend/dist/track-flow/__tests__/verify-flow-v3.d.ts +2 -0
- package/backend/dist/track-flow/__tests__/verify-flow-v3.d.ts.map +1 -0
- package/backend/dist/track-flow/__tests__/verify-flow-v3.js +177 -0
- package/backend/dist/track-flow/__tests__/verify-flow-v3.js.map +1 -0
- package/backend/dist/track-flow/audit-log.d.ts +22 -0
- package/backend/dist/track-flow/audit-log.d.ts.map +1 -0
- package/backend/dist/track-flow/audit-log.js +56 -0
- package/backend/dist/track-flow/audit-log.js.map +1 -0
- package/backend/dist/track-flow/if-expr-evaluator.d.ts +14 -0
- package/backend/dist/track-flow/if-expr-evaluator.d.ts.map +1 -0
- package/backend/dist/track-flow/if-expr-evaluator.js +73 -0
- package/backend/dist/track-flow/if-expr-evaluator.js.map +1 -0
- package/backend/dist/track-flow/if-expr-parser.d.ts +34 -0
- package/backend/dist/track-flow/if-expr-parser.d.ts.map +1 -0
- package/backend/dist/track-flow/if-expr-parser.js +176 -0
- package/backend/dist/track-flow/if-expr-parser.js.map +1 -0
- package/backend/dist/track-flow/index.d.ts +10 -0
- package/backend/dist/track-flow/index.d.ts.map +1 -0
- package/backend/dist/track-flow/index.js +26 -0
- package/backend/dist/track-flow/index.js.map +1 -0
- package/backend/dist/track-flow/llm-dispatcher.d.ts +36 -0
- package/backend/dist/track-flow/llm-dispatcher.d.ts.map +1 -0
- package/backend/dist/track-flow/llm-dispatcher.js +145 -0
- package/backend/dist/track-flow/llm-dispatcher.js.map +1 -0
- package/backend/dist/track-flow/prompt-translator.d.ts +19 -0
- package/backend/dist/track-flow/prompt-translator.d.ts.map +1 -0
- package/backend/dist/track-flow/prompt-translator.js +60 -0
- package/backend/dist/track-flow/prompt-translator.js.map +1 -0
- package/backend/dist/track-flow/run-id.d.ts +2 -0
- package/backend/dist/track-flow/run-id.d.ts.map +1 -0
- package/backend/dist/track-flow/run-id.js +7 -0
- package/backend/dist/track-flow/run-id.js.map +1 -0
- package/backend/dist/track-flow/run-registry.d.ts +77 -0
- package/backend/dist/track-flow/run-registry.d.ts.map +1 -0
- package/backend/dist/track-flow/run-registry.js +148 -0
- package/backend/dist/track-flow/run-registry.js.map +1 -0
- package/backend/dist/track-flow/runtime.d.ts +59 -0
- package/backend/dist/track-flow/runtime.d.ts.map +1 -0
- package/backend/dist/track-flow/runtime.js +204 -0
- package/backend/dist/track-flow/runtime.js.map +1 -0
- package/backend/dist/track-flow/store.d.ts +20 -0
- package/backend/dist/track-flow/store.d.ts.map +1 -0
- package/backend/dist/track-flow/store.js +158 -0
- package/backend/dist/track-flow/store.js.map +1 -0
- package/backend/dist/track-flow/train-json-sync.d.ts +31 -0
- package/backend/dist/track-flow/train-json-sync.d.ts.map +1 -0
- package/backend/dist/track-flow/train-json-sync.js +130 -0
- package/backend/dist/track-flow/train-json-sync.js.map +1 -0
- package/backend/dist/track-flow-ws.d.ts +5 -0
- package/backend/dist/track-flow-ws.d.ts.map +1 -0
- package/backend/dist/track-flow-ws.js +14 -0
- package/backend/dist/track-flow-ws.js.map +1 -0
- package/backend/dist/tracks/ask-user-bridge.d.ts +1 -6
- package/backend/dist/tracks/ask-user-bridge.d.ts.map +1 -1
- package/backend/dist/tracks/ask-user-bridge.js +0 -22
- package/backend/dist/tracks/ask-user-bridge.js.map +1 -1
- package/backend/dist/tracks/index.d.ts +1 -5
- package/backend/dist/tracks/index.d.ts.map +1 -1
- package/backend/dist/tracks/index.js +1 -10
- package/backend/dist/tracks/index.js.map +1 -1
- package/backend/package-lock.json +2066 -124
- package/backend/package.json +6 -5
- package/frontend/dist/assets/{ChatOverlay-DPM_F615.js → ChatOverlay-BsozN13k.js} +1 -1
- package/frontend/dist/assets/{GraphPreview-CJhh33L8.js → GraphPreview-Bagqaatf.js} +2 -2
- package/frontend/dist/assets/{MobilePage-Cx9v8Udp.js → MobilePage-BTKA6Dxj.js} +3 -3
- package/frontend/dist/assets/{OfficePreview-h6zYBZFQ.js → OfficePreview-CMRAt1Qb.js} +2 -2
- package/frontend/dist/assets/{PdfPreview-DbYFye7c.js → PdfPreview-DM7Lq8lA.js} +1 -1
- package/frontend/dist/assets/ProjectPage-3rERYJYF.js +26 -0
- package/frontend/dist/assets/{TrackGraphEditor-Fd0xVSp_.css → ProjectPage-DFCEXprr.css} +32 -1
- package/frontend/dist/assets/{SettingsPage-BkApTPbw.js → SettingsPage-CA9PjfJR.js} +2 -2
- package/frontend/dist/assets/{SkillHubPage-BwfU-hel.js → SkillHubPage-DLt5DGX6.js} +3 -3
- package/frontend/dist/assets/{chevron-down-D4Vj-5wB.js → chevron-down-D7HR2Yyz.js} +1 -1
- package/frontend/dist/assets/{index-CKxFfdqR.js → index-fIUNz576.js} +1 -1
- package/frontend/dist/assets/{index-DUIOrPha.js → index-k0ydgbQO.js} +7 -7
- package/frontend/dist/assets/{index-C-2GBtXy.js → index-tE28G1ot.js} +1 -1
- package/frontend/dist/assets/index-tSU3-nNc.css +1 -0
- package/frontend/dist/assets/{jszip.min-Bn769Ot7.js → jszip.min-BDxK042T.js} +1 -1
- package/frontend/dist/assets/{select-CXSNG3yW.js → select-C6nWxQ7S.js} +2 -2
- package/frontend/dist/assets/{user-Chl3LWFq.js → user-DbW3r9c3.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/backend/vendor/@tom2012/train-core/dist/ast-cache.d.ts +0 -74
- package/backend/vendor/@tom2012/train-core/dist/ast-cache.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/ast-cache.js +0 -157
- package/backend/vendor/@tom2012/train-core/dist/ast-cache.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/ast.d.ts +0 -350
- package/backend/vendor/@tom2012/train-core/dist/ast.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/ast.js +0 -15
- package/backend/vendor/@tom2012/train-core/dist/ast.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/builder.d.ts +0 -21
- package/backend/vendor/@tom2012/train-core/dist/builder.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/builder.js +0 -1254
- package/backend/vendor/@tom2012/train-core/dist/builder.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/builtins.d.ts +0 -17
- package/backend/vendor/@tom2012/train-core/dist/builtins.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/builtins.js +0 -500
- package/backend/vendor/@tom2012/train-core/dist/builtins.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/index.d.ts +0 -54
- package/backend/vendor/@tom2012/train-core/dist/index.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/index.js +0 -66
- package/backend/vendor/@tom2012/train-core/dist/index.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/interpreter.d.ts +0 -110
- package/backend/vendor/@tom2012/train-core/dist/interpreter.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/interpreter.js +0 -943
- package/backend/vendor/@tom2012/train-core/dist/interpreter.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/lexer.d.ts +0 -88
- package/backend/vendor/@tom2012/train-core/dist/lexer.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/lexer.js +0 -243
- package/backend/vendor/@tom2012/train-core/dist/lexer.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/module-loader.d.ts +0 -74
- package/backend/vendor/@tom2012/train-core/dist/module-loader.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/module-loader.js +0 -149
- package/backend/vendor/@tom2012/train-core/dist/module-loader.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/parser.d.ts +0 -138
- package/backend/vendor/@tom2012/train-core/dist/parser.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/parser.js +0 -861
- package/backend/vendor/@tom2012/train-core/dist/parser.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/prompt-composer.d.ts +0 -49
- package/backend/vendor/@tom2012/train-core/dist/prompt-composer.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/prompt-composer.js +0 -159
- package/backend/vendor/@tom2012/train-core/dist/prompt-composer.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/runtime.d.ts +0 -146
- package/backend/vendor/@tom2012/train-core/dist/runtime.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/runtime.js +0 -156
- package/backend/vendor/@tom2012/train-core/dist/runtime.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/type-descriptor.d.ts +0 -18
- package/backend/vendor/@tom2012/train-core/dist/type-descriptor.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/type-descriptor.js +0 -94
- package/backend/vendor/@tom2012/train-core/dist/type-descriptor.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/validation.d.ts +0 -44
- package/backend/vendor/@tom2012/train-core/dist/validation.d.ts.map +0 -1
- package/backend/vendor/@tom2012/train-core/dist/validation.js +0 -271
- package/backend/vendor/@tom2012/train-core/dist/validation.js.map +0 -1
- package/backend/vendor/@tom2012/train-core/package.json +0 -35
- package/frontend/dist/assets/ProjectPage-9CEnUXvW.css +0 -32
- package/frontend/dist/assets/ProjectPage-DaPmvQ-H.js +0 -26
- package/frontend/dist/assets/TrackEditor-BbM0eHTD.js +0 -14
- package/frontend/dist/assets/TrackGraphEditor-DzEqAGFN.js +0 -2
- package/frontend/dist/assets/abap-LPLW346S.js +0 -7
- package/frontend/dist/assets/apex-Dk-jUCUV.js +0 -7
- package/frontend/dist/assets/azcli-DPUMmPlX.js +0 -7
- package/frontend/dist/assets/bat-C1Qbg1bV.js +0 -7
- package/frontend/dist/assets/bicep-D-e-VSJi.js +0 -7
- package/frontend/dist/assets/cameligo-CjUqTgqL.js +0 -7
- package/frontend/dist/assets/clojure-BWsu6Kju.js +0 -7
- package/frontend/dist/assets/codicon-DCmgc-ay.ttf +0 -0
- package/frontend/dist/assets/coffee-DeC36AK3.js +0 -7
- package/frontend/dist/assets/cpp-DcZnmBWT.js +0 -7
- package/frontend/dist/assets/csharp-DqBXBMf0.js +0 -7
- package/frontend/dist/assets/csp-CkO7y8ul.js +0 -7
- package/frontend/dist/assets/css-OAcfVtED.js +0 -7
- package/frontend/dist/assets/cssMode-5whH06Yl.js +0 -7
- package/frontend/dist/assets/cypher-40UGrUjD.js +0 -7
- package/frontend/dist/assets/dart-DmixyLor.js +0 -7
- package/frontend/dist/assets/dockerfile-CayO4nTA.js +0 -7
- package/frontend/dist/assets/ecl-DRrKCuVc.js +0 -7
- package/frontend/dist/assets/editor-B5EY1bb8.css +0 -1
- package/frontend/dist/assets/editor.main-CnjJg6pv.js +0 -37
- package/frontend/dist/assets/elixir-J3qpp9mX.js +0 -7
- package/frontend/dist/assets/flow9-DzW2c4Im.js +0 -7
- package/frontend/dist/assets/freemarker2-CUgILTtX.js +0 -7
- package/frontend/dist/assets/fsharp-D00tn_6c.js +0 -7
- package/frontend/dist/assets/go-NnNmgWK_.js +0 -7
- package/frontend/dist/assets/graphql-C9_--VLF.js +0 -7
- package/frontend/dist/assets/handlebars-C2avKkbK.js +0 -7
- package/frontend/dist/assets/hcl-BhbOULbp.js +0 -7
- package/frontend/dist/assets/html-ZYUiGgvr.js +0 -7
- package/frontend/dist/assets/htmlMode-CeliqUBd.js +0 -7
- package/frontend/dist/assets/index-COjNnvQB.css +0 -1
- package/frontend/dist/assets/index-DjmvYdMM.js +0 -1
- package/frontend/dist/assets/ini-zR-_X5iG.js +0 -7
- package/frontend/dist/assets/java-CSTjsyoZ.js +0 -7
- package/frontend/dist/assets/javascript-0VRCllAK.js +0 -7
- package/frontend/dist/assets/jsonMode-Dwqu43-Y.js +0 -7
- package/frontend/dist/assets/julia-DioMOKVu.js +0 -7
- package/frontend/dist/assets/kotlin-Dsb0PKmW.js +0 -7
- package/frontend/dist/assets/less-CZYLoAVD.js +0 -7
- package/frontend/dist/assets/lexon-d5JUiwNk.js +0 -7
- package/frontend/dist/assets/liquid-E2ejTVvo.js +0 -7
- package/frontend/dist/assets/lua-1Al72GG8.js +0 -7
- package/frontend/dist/assets/m3-hx1xCPC3.js +0 -7
- package/frontend/dist/assets/markdown-DPGrH1xZ.js +0 -7
- package/frontend/dist/assets/mdx-CqJmnu86.js +0 -7
- package/frontend/dist/assets/mips-CYbZjkIm.js +0 -7
- package/frontend/dist/assets/msdax-DAioKM5A.js +0 -7
- package/frontend/dist/assets/mysql-7e9GUebS.js +0 -7
- package/frontend/dist/assets/objective-c-BGf8QNjz.js +0 -7
- package/frontend/dist/assets/pascal-hEDRz3un.js +0 -7
- package/frontend/dist/assets/pascaligo-gqEhN6pG.js +0 -7
- package/frontend/dist/assets/perl-CN8Ht9Vk.js +0 -7
- package/frontend/dist/assets/pgsql-CRCqHQBx.js +0 -7
- package/frontend/dist/assets/php-DTZrlAwe.js +0 -7
- package/frontend/dist/assets/pla-CRmh4UcC.js +0 -7
- package/frontend/dist/assets/postiats-Car2G7g8.js +0 -7
- package/frontend/dist/assets/powerquery-BJKl8V3o.js +0 -7
- package/frontend/dist/assets/powershell-Du7a_J7r.js +0 -7
- package/frontend/dist/assets/protobuf-ChneWI0H.js +0 -7
- package/frontend/dist/assets/pug-BH6LSJaf.js +0 -7
- package/frontend/dist/assets/python-BYNiBUNZ.js +0 -7
- package/frontend/dist/assets/qsharp-CU-Hxpxi.js +0 -7
- package/frontend/dist/assets/r-Bw-W15zh.js +0 -7
- package/frontend/dist/assets/razor-2YR0XVnP.js +0 -7
- package/frontend/dist/assets/redis-bY-X9-ol.js +0 -7
- package/frontend/dist/assets/redshift-CMu3ubbS.js +0 -7
- package/frontend/dist/assets/restructuredtext-KNkiWGNN.js +0 -7
- package/frontend/dist/assets/ruby-dQWMhU86.js +0 -7
- package/frontend/dist/assets/rust-BAcHhnEU.js +0 -7
- package/frontend/dist/assets/sb-Bidr5w2z.js +0 -7
- package/frontend/dist/assets/scala-imTtxsGY.js +0 -7
- package/frontend/dist/assets/scheme-BY-WHKiB.js +0 -7
- package/frontend/dist/assets/scss-NDoRP52C.js +0 -7
- package/frontend/dist/assets/shell-DrIjotnh.js +0 -7
- package/frontend/dist/assets/solidity-DTahNycF.js +0 -7
- package/frontend/dist/assets/sophia-DtyrjGkZ.js +0 -7
- package/frontend/dist/assets/sparql-CFuS5E3z.js +0 -7
- package/frontend/dist/assets/sql-BRxcnbRv.js +0 -7
- package/frontend/dist/assets/st-Dz4uh7MK.js +0 -7
- package/frontend/dist/assets/swift-Digsrw1G.js +0 -11
- package/frontend/dist/assets/systemverilog-7TDrkMau.js +0 -7
- package/frontend/dist/assets/tcl-BtaFcnDi.js +0 -7
- package/frontend/dist/assets/tsMode-DrjOLXjA.js +0 -7
- package/frontend/dist/assets/twig-Deid_oBs.js +0 -7
- package/frontend/dist/assets/typescript--8YtfOFf.js +0 -7
- package/frontend/dist/assets/typespec-zUaxhbG8.js +0 -7
- package/frontend/dist/assets/vb-DK0rKJzo.js +0 -7
- package/frontend/dist/assets/wgsl-B8fNVlOQ.js +0 -7
- package/frontend/dist/assets/xml-CGj_sBRG.js +0 -7
- package/frontend/dist/assets/yaml-of0AgMN8.js +0 -7
|
@@ -1,1254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CST → typed AST visitor.
|
|
3
|
-
*
|
|
4
|
-
* Uses chevrotain's CST visitor pattern. Each `visit*` method
|
|
5
|
-
* corresponds to one grammar rule in `parser.ts` and produces the
|
|
6
|
-
* matching AST node from `ast.ts`.
|
|
7
|
-
*
|
|
8
|
-
* Conventions:
|
|
9
|
-
* - Method name MUST equal the parser rule name (chevrotain dispatch).
|
|
10
|
-
* - `ctx` carries children grouped by rule name (CstNode[]) or token
|
|
11
|
-
* name (IToken[]). Optional rules / tokens may be missing entirely;
|
|
12
|
-
* we read with safe defaults.
|
|
13
|
-
* - All AST nodes carry a `range` derived from the spanning CST/Token.
|
|
14
|
-
*/
|
|
15
|
-
import { trainParser, parseExpression } from './parser.js';
|
|
16
|
-
const BaseVisitor = trainParser.getBaseCstVisitorConstructor();
|
|
17
|
-
// ─── Range helpers ────────────────────────────────────────────────────
|
|
18
|
-
function tokenRange(tok) {
|
|
19
|
-
return {
|
|
20
|
-
startLine: tok.startLine ?? 0,
|
|
21
|
-
startColumn: tok.startColumn ?? 0,
|
|
22
|
-
endLine: tok.endLine ?? 0,
|
|
23
|
-
endColumn: tok.endColumn ?? 0,
|
|
24
|
-
startOffset: tok.startOffset,
|
|
25
|
-
endOffset: tok.endOffset ?? tok.startOffset,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function cstRange(cst) {
|
|
29
|
-
const loc = cst.location;
|
|
30
|
-
if (!loc) {
|
|
31
|
-
return {
|
|
32
|
-
startLine: 0,
|
|
33
|
-
startColumn: 0,
|
|
34
|
-
endLine: 0,
|
|
35
|
-
endColumn: 0,
|
|
36
|
-
startOffset: 0,
|
|
37
|
-
endOffset: 0,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
startLine: loc.startLine ?? 0,
|
|
42
|
-
startColumn: loc.startColumn ?? 0,
|
|
43
|
-
endLine: loc.endLine ?? 0,
|
|
44
|
-
endColumn: loc.endColumn ?? 0,
|
|
45
|
-
startOffset: loc.startOffset,
|
|
46
|
-
endOffset: loc.endOffset ?? loc.startOffset,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// ─── Literal value helpers ────────────────────────────────────────────
|
|
50
|
-
function unquoteString(raw) {
|
|
51
|
-
// raw is the matched StringLit including surrounding quotes
|
|
52
|
-
const inner = raw.slice(1, -1);
|
|
53
|
-
return unescapeStringBody(inner);
|
|
54
|
-
}
|
|
55
|
-
function unescapeStringBody(inner) {
|
|
56
|
-
return inner.replace(/\\(.)/g, (_, ch) => {
|
|
57
|
-
switch (ch) {
|
|
58
|
-
case 'n':
|
|
59
|
-
return '\n';
|
|
60
|
-
case 't':
|
|
61
|
-
return '\t';
|
|
62
|
-
case 'r':
|
|
63
|
-
return '\r';
|
|
64
|
-
case '\\':
|
|
65
|
-
return '\\';
|
|
66
|
-
case '"':
|
|
67
|
-
return '"';
|
|
68
|
-
case "'":
|
|
69
|
-
return "'";
|
|
70
|
-
case '$':
|
|
71
|
-
return '$';
|
|
72
|
-
default:
|
|
73
|
-
return ch;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function splitTemplate(body) {
|
|
78
|
-
// Invariant when output contains any expr: result is strictly
|
|
79
|
-
// chunk, expr, chunk, expr, ..., chunk
|
|
80
|
-
// i.e. first and last segments are always chunks (possibly empty).
|
|
81
|
-
// When no expr appears, output is a single chunk (possibly empty).
|
|
82
|
-
const segments = [];
|
|
83
|
-
let buf = '';
|
|
84
|
-
let bufStart = 0;
|
|
85
|
-
let i = 0;
|
|
86
|
-
const flushChunk = (endPos) => {
|
|
87
|
-
segments.push({
|
|
88
|
-
kind: 'chunk',
|
|
89
|
-
source: buf,
|
|
90
|
-
startInBody: bufStart,
|
|
91
|
-
endInBody: endPos,
|
|
92
|
-
});
|
|
93
|
-
buf = '';
|
|
94
|
-
bufStart = endPos;
|
|
95
|
-
};
|
|
96
|
-
while (i < body.length) {
|
|
97
|
-
if (body[i] === '$' && body[i + 1] === '{') {
|
|
98
|
-
// chunk that precedes this interpolation (may be empty)
|
|
99
|
-
flushChunk(i);
|
|
100
|
-
// find matching `}` (track nested {})
|
|
101
|
-
let depth = 1;
|
|
102
|
-
let j = i + 2;
|
|
103
|
-
while (j < body.length && depth > 0) {
|
|
104
|
-
const ch = body[j];
|
|
105
|
-
if (ch === '{')
|
|
106
|
-
depth++;
|
|
107
|
-
else if (ch === '}') {
|
|
108
|
-
depth--;
|
|
109
|
-
if (depth === 0)
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
j++;
|
|
113
|
-
}
|
|
114
|
-
if (depth !== 0) {
|
|
115
|
-
// Unterminated — recover by treating the rest as one chunk.
|
|
116
|
-
buf += body.slice(i);
|
|
117
|
-
bufStart = i;
|
|
118
|
-
i = body.length;
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
const exprBody = body.slice(i + 2, j);
|
|
122
|
-
segments.push({
|
|
123
|
-
kind: 'expr',
|
|
124
|
-
source: exprBody,
|
|
125
|
-
startInBody: i,
|
|
126
|
-
endInBody: j + 1,
|
|
127
|
-
});
|
|
128
|
-
bufStart = j + 1;
|
|
129
|
-
i = j + 1;
|
|
130
|
-
}
|
|
131
|
-
else if (body[i] === '\\' && i + 1 < body.length) {
|
|
132
|
-
buf += body.slice(i, i + 2);
|
|
133
|
-
i += 2;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
buf += body[i];
|
|
137
|
-
i++;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Final flush: keep the "first and last are chunks" invariant.
|
|
141
|
-
// If we have any expr OR no segments yet, emit a chunk for the tail
|
|
142
|
-
// (possibly empty). If we have only a partially-buffered plain string
|
|
143
|
-
// and no segments, the empty flush still produces the single chunk.
|
|
144
|
-
const hasExpr = segments.some((s) => s.kind === 'expr');
|
|
145
|
-
if (hasExpr || segments.length === 0) {
|
|
146
|
-
flushChunk(body.length);
|
|
147
|
-
}
|
|
148
|
-
else if (buf.length > 0) {
|
|
149
|
-
// pure literal case where buffer was carried past a recovery path
|
|
150
|
-
flushChunk(body.length);
|
|
151
|
-
}
|
|
152
|
-
return segments;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Build either a plain StringLit (no interpolation) or a TemplateString
|
|
156
|
-
* (one or more `${...}`) from the source token. Offsets in returned
|
|
157
|
-
* sub-ranges are relative to the source file (using `tok.startOffset` +
|
|
158
|
-
* 1 to skip the opening quote).
|
|
159
|
-
*/
|
|
160
|
-
function buildStringExpr(tok) {
|
|
161
|
-
const raw = tok.image;
|
|
162
|
-
const body = raw.slice(1, -1); // strip surrounding quotes
|
|
163
|
-
const fullRange = tokenRange(tok);
|
|
164
|
-
const segs = splitTemplate(body);
|
|
165
|
-
if (!segs.some((s) => s.kind === 'expr')) {
|
|
166
|
-
// Pure literal — return StringLit
|
|
167
|
-
const merged = segs.map((s) => s.source).join('');
|
|
168
|
-
return {
|
|
169
|
-
kind: 'StringLit',
|
|
170
|
-
value: unescapeStringBody(merged),
|
|
171
|
-
range: fullRange,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
// Has interpolation — build TemplateString
|
|
175
|
-
const bodyOffset = tok.startOffset + 1; // skip opening quote
|
|
176
|
-
const parts = segs.map((seg) => {
|
|
177
|
-
if (seg.kind === 'chunk') {
|
|
178
|
-
return {
|
|
179
|
-
kind: 'TemplateChunk',
|
|
180
|
-
value: unescapeStringBody(seg.source),
|
|
181
|
-
range: subRange(fullRange, bodyOffset, seg.startInBody, seg.endInBody),
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
// expr — recursively parse it via the parser's exprEntry
|
|
185
|
-
const result = parseExpression(seg.source);
|
|
186
|
-
if (result.lexErrors.length > 0 ||
|
|
187
|
-
result.parseErrors.length > 0 ||
|
|
188
|
-
!result.cst) {
|
|
189
|
-
// emit a fallback: empty string chunk with the broken expr's text.
|
|
190
|
-
// In a future revision we should propagate diagnostics up.
|
|
191
|
-
return {
|
|
192
|
-
kind: 'TemplateChunk',
|
|
193
|
-
value: '${' + seg.source + '}',
|
|
194
|
-
range: subRange(fullRange, bodyOffset, seg.startInBody, seg.endInBody),
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const innerExpr = astBuilder.visit(result.cst);
|
|
198
|
-
return {
|
|
199
|
-
kind: 'TemplateExpr',
|
|
200
|
-
expr: innerExpr,
|
|
201
|
-
range: subRange(fullRange, bodyOffset, seg.startInBody, seg.endInBody),
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
return {
|
|
205
|
-
kind: 'TemplateString',
|
|
206
|
-
parts,
|
|
207
|
-
range: fullRange,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
/** Produce a Range for a sub-region of a single-line-ish source token.
|
|
211
|
-
* Sufficient for now (no per-segment line/column tracking inside templates). */
|
|
212
|
-
function subRange(full, bodyOffset, startInBody, endInBody) {
|
|
213
|
-
return {
|
|
214
|
-
startLine: full.startLine,
|
|
215
|
-
startColumn: full.startColumn,
|
|
216
|
-
endLine: full.endLine,
|
|
217
|
-
endColumn: full.endColumn,
|
|
218
|
-
startOffset: bodyOffset + startInBody,
|
|
219
|
-
endOffset: bodyOffset + endInBody,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function stripAtPrefix(name) {
|
|
223
|
-
return name.startsWith('@') ? name.slice(1) : name;
|
|
224
|
-
}
|
|
225
|
-
// ─── Visitor implementation ───────────────────────────────────────────
|
|
226
|
-
class TrainAstBuilder extends BaseVisitor {
|
|
227
|
-
constructor() {
|
|
228
|
-
super();
|
|
229
|
-
this.validateVisitor();
|
|
230
|
-
}
|
|
231
|
-
// ─── Program ────────────────────────────────────────────────────────
|
|
232
|
-
/** Used by buildStringExpr → parseExpression for ${...} bodies. */
|
|
233
|
-
exprEntry(ctx) {
|
|
234
|
-
return this.visit(ctx.expr[0]);
|
|
235
|
-
}
|
|
236
|
-
program(ctx, _params) {
|
|
237
|
-
const cst = (ctx.$cstNode ?? undefined);
|
|
238
|
-
const items = (ctx.topLevel ?? []).map((c) => this.visit(c));
|
|
239
|
-
return {
|
|
240
|
-
kind: 'Program',
|
|
241
|
-
items,
|
|
242
|
-
range: cst ? cstRange(cst) : emptyRange(items),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
topLevel(ctx) {
|
|
246
|
-
if (ctx.importDecl)
|
|
247
|
-
return this.visit(ctx.importDecl[0]);
|
|
248
|
-
if (ctx.runtimeAnnotation)
|
|
249
|
-
return this.visit(ctx.runtimeAnnotation[0]);
|
|
250
|
-
if (ctx.constDecl)
|
|
251
|
-
return this.visit(ctx.constDecl[0]);
|
|
252
|
-
if (ctx.varDecl)
|
|
253
|
-
return this.visit(ctx.varDecl[0]);
|
|
254
|
-
if (ctx.annotatedDecl)
|
|
255
|
-
return this.visit(ctx.annotatedDecl[0]);
|
|
256
|
-
if (ctx.funcDecl)
|
|
257
|
-
return this.visit(ctx.funcDecl[0]);
|
|
258
|
-
if (ctx.faiDecl)
|
|
259
|
-
return this.visit(ctx.faiDecl[0]);
|
|
260
|
-
if (ctx.exportDecl)
|
|
261
|
-
return this.visit(ctx.exportDecl[0]);
|
|
262
|
-
throw new Error('unreachable: topLevel had no matching alternative');
|
|
263
|
-
}
|
|
264
|
-
// ─── Imports ────────────────────────────────────────────────────────
|
|
265
|
-
importDecl(ctx) {
|
|
266
|
-
const clause = this.visit(ctx.importClause[0]);
|
|
267
|
-
const sourceTok = ctx.StringLit[0];
|
|
268
|
-
const versionTok = ctx.AtName?.[0];
|
|
269
|
-
const importTok = ctx.Import[0];
|
|
270
|
-
const endTok = versionTok ?? sourceTok;
|
|
271
|
-
return {
|
|
272
|
-
kind: 'Import',
|
|
273
|
-
clause,
|
|
274
|
-
source: unquoteString(sourceTok.image),
|
|
275
|
-
version: versionTok ? versionTok.image.slice(1) : null,
|
|
276
|
-
range: spanTokens(importTok, endTok),
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
importClause(ctx) {
|
|
280
|
-
if (ctx.namedImports)
|
|
281
|
-
return this.visit(ctx.namedImports[0]);
|
|
282
|
-
return this.visit(ctx.namespaceImport[0]);
|
|
283
|
-
}
|
|
284
|
-
namedImports(ctx) {
|
|
285
|
-
const specs = (ctx.importSpec ?? []).map((c) => this.visit(c));
|
|
286
|
-
const lcurly = ctx.LCurly[0];
|
|
287
|
-
const rcurly = ctx.RCurly[0];
|
|
288
|
-
return {
|
|
289
|
-
kind: 'NamedImports',
|
|
290
|
-
specs,
|
|
291
|
-
range: spanTokens(lcurly, rcurly),
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
importSpec(ctx) {
|
|
295
|
-
const ids = ctx.Identifier;
|
|
296
|
-
const name = ids[0].image;
|
|
297
|
-
const alias = ids.length > 1 ? ids[1].image : null;
|
|
298
|
-
return {
|
|
299
|
-
kind: 'ImportSpec',
|
|
300
|
-
name,
|
|
301
|
-
alias,
|
|
302
|
-
range: spanTokens(ids[0], ids[ids.length - 1]),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
namespaceImport(ctx) {
|
|
306
|
-
const star = ctx.Star[0];
|
|
307
|
-
const alias = ctx.Identifier[0];
|
|
308
|
-
return {
|
|
309
|
-
kind: 'NamespaceImport',
|
|
310
|
-
alias: alias.image,
|
|
311
|
-
range: spanTokens(star, alias),
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
// ─── Annotations ────────────────────────────────────────────────────
|
|
315
|
-
runtimeAnnotation(ctx) {
|
|
316
|
-
const name = ctx.AtName[0];
|
|
317
|
-
const argsCst = ctx.annoArgList?.[0];
|
|
318
|
-
const args = argsCst ? this.visit(argsCst) : [];
|
|
319
|
-
const rparen = ctx.RParen?.[0];
|
|
320
|
-
const endTok = rparen ?? name;
|
|
321
|
-
return {
|
|
322
|
-
kind: 'RuntimeAnnotation',
|
|
323
|
-
name: stripAtPrefix(name.image),
|
|
324
|
-
args,
|
|
325
|
-
range: spanTokens(name, endTok),
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
declAnnotation(ctx) {
|
|
329
|
-
const name = ctx.AtName[0];
|
|
330
|
-
const argsCst = ctx.annoArgList?.[0];
|
|
331
|
-
const args = argsCst ? this.visit(argsCst) : [];
|
|
332
|
-
const rparen = ctx.RParen?.[0];
|
|
333
|
-
const endTok = rparen ?? name;
|
|
334
|
-
return {
|
|
335
|
-
kind: 'Annotation',
|
|
336
|
-
name: stripAtPrefix(name.image),
|
|
337
|
-
args,
|
|
338
|
-
range: spanTokens(name, endTok),
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
annoArgList(ctx) {
|
|
342
|
-
return (ctx.annoArg ?? []).map((c) => this.visit(c));
|
|
343
|
-
}
|
|
344
|
-
annoArg(ctx) {
|
|
345
|
-
const keyTok = ctx.Identifier?.[0];
|
|
346
|
-
const lits = ctx.literal;
|
|
347
|
-
const literalCst = lits[0];
|
|
348
|
-
const value = this.visit(literalCst);
|
|
349
|
-
const startTok = keyTok ?? findFirstToken(literalCst);
|
|
350
|
-
const endRange = value.range;
|
|
351
|
-
return {
|
|
352
|
-
kind: 'AnnotationArg',
|
|
353
|
-
key: keyTok ? keyTok.image : null,
|
|
354
|
-
value,
|
|
355
|
-
range: {
|
|
356
|
-
startLine: startTok?.startLine ?? endRange.startLine,
|
|
357
|
-
startColumn: startTok?.startColumn ?? endRange.startColumn,
|
|
358
|
-
endLine: endRange.endLine,
|
|
359
|
-
endColumn: endRange.endColumn,
|
|
360
|
-
startOffset: startTok?.startOffset ?? endRange.startOffset,
|
|
361
|
-
endOffset: endRange.endOffset,
|
|
362
|
-
},
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
annotatedDecl(ctx) {
|
|
366
|
-
const annotations = (ctx.declAnnotation ?? []).map((c) => this.visit(c));
|
|
367
|
-
let decl;
|
|
368
|
-
if (ctx.funcDecl)
|
|
369
|
-
decl = this.visit(ctx.funcDecl[0]);
|
|
370
|
-
else
|
|
371
|
-
decl = this.visit(ctx.faiDecl[0]);
|
|
372
|
-
return { ...decl, annotations };
|
|
373
|
-
}
|
|
374
|
-
// ─── Top-level declarations ─────────────────────────────────────────
|
|
375
|
-
constDecl(ctx) {
|
|
376
|
-
const constTok = ctx.Const[0];
|
|
377
|
-
const id = ctx.Identifier[0];
|
|
378
|
-
const type = this.visit(ctx.declTypeAnnot[0]);
|
|
379
|
-
const value = this.visit(ctx.expr[0]);
|
|
380
|
-
return {
|
|
381
|
-
kind: 'ConstDecl',
|
|
382
|
-
name: id.image,
|
|
383
|
-
type,
|
|
384
|
-
value,
|
|
385
|
-
range: spanFromTokenToRange(constTok, value.range),
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
varDecl(ctx) {
|
|
389
|
-
const varTok = ctx.Var[0];
|
|
390
|
-
const id = ctx.Identifier[0];
|
|
391
|
-
const type = this.visit(ctx.declTypeAnnot[0]);
|
|
392
|
-
const init = ctx.expr ? this.visit(ctx.expr[0]) : null;
|
|
393
|
-
const endRange = init?.range ?? type.range;
|
|
394
|
-
return {
|
|
395
|
-
kind: 'VarDecl',
|
|
396
|
-
name: id.image,
|
|
397
|
-
type,
|
|
398
|
-
init,
|
|
399
|
-
range: spanFromTokenToRange(varTok, endRange),
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
funcDecl(ctx) {
|
|
403
|
-
const funcTok = ctx.Func[0];
|
|
404
|
-
const id = ctx.Identifier[0];
|
|
405
|
-
const params = ctx.paramList
|
|
406
|
-
? this.visit(ctx.paramList[0])
|
|
407
|
-
: [];
|
|
408
|
-
const returnType = ctx.typeAnnot
|
|
409
|
-
? this.visit(ctx.typeAnnot[0])
|
|
410
|
-
: null;
|
|
411
|
-
const body = this.visit(ctx.block[0]);
|
|
412
|
-
return {
|
|
413
|
-
kind: 'FuncDecl',
|
|
414
|
-
annotations: [],
|
|
415
|
-
name: id.image,
|
|
416
|
-
params,
|
|
417
|
-
returnType,
|
|
418
|
-
body,
|
|
419
|
-
range: spanFromTokenToRange(funcTok, body.range),
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
faiDecl(ctx) {
|
|
423
|
-
const faiTok = ctx.Fai[0];
|
|
424
|
-
const id = ctx.Identifier[0];
|
|
425
|
-
const params = ctx.faiParamList
|
|
426
|
-
? this.visit(ctx.faiParamList[0])
|
|
427
|
-
: [];
|
|
428
|
-
const outputs = this.visit(ctx.faiOutputList[0]);
|
|
429
|
-
const body = this.visit(ctx.block[0]);
|
|
430
|
-
return {
|
|
431
|
-
kind: 'FaiDecl',
|
|
432
|
-
annotations: [],
|
|
433
|
-
name: id.image,
|
|
434
|
-
params,
|
|
435
|
-
outputs,
|
|
436
|
-
body,
|
|
437
|
-
range: spanFromTokenToRange(faiTok, body.range),
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
exportDecl(ctx) {
|
|
441
|
-
const exportTok = ctx.Export[0];
|
|
442
|
-
let target;
|
|
443
|
-
if (ctx.exportNames)
|
|
444
|
-
target = this.visit(ctx.exportNames[0]);
|
|
445
|
-
else if (ctx.funcDecl)
|
|
446
|
-
target = this.visit(ctx.funcDecl[0]);
|
|
447
|
-
else
|
|
448
|
-
target = this.visit(ctx.faiDecl[0]);
|
|
449
|
-
return {
|
|
450
|
-
kind: 'ExportDecl',
|
|
451
|
-
target,
|
|
452
|
-
range: spanFromTokenToRange(exportTok, target.range),
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
exportNames(ctx) {
|
|
456
|
-
const specs = (ctx.exportSpec ?? []).map((c) => this.visit(c));
|
|
457
|
-
if (specs.length === 0) {
|
|
458
|
-
return {
|
|
459
|
-
kind: 'ExportNames',
|
|
460
|
-
specs: [],
|
|
461
|
-
range: emptyRange([]),
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
const first = specs[0].range;
|
|
465
|
-
const last = specs[specs.length - 1].range;
|
|
466
|
-
return {
|
|
467
|
-
kind: 'ExportNames',
|
|
468
|
-
specs,
|
|
469
|
-
range: spanRanges(first, last),
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
exportSpec(ctx) {
|
|
473
|
-
const ids = ctx.Identifier;
|
|
474
|
-
const name = ids[0].image;
|
|
475
|
-
const alias = ids.length > 1 ? ids[1].image : null;
|
|
476
|
-
return {
|
|
477
|
-
kind: 'ExportSpec',
|
|
478
|
-
name,
|
|
479
|
-
alias,
|
|
480
|
-
range: spanTokens(ids[0], ids[ids.length - 1]),
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
// ─── Parameters / Outputs ───────────────────────────────────────────
|
|
484
|
-
paramList(ctx) {
|
|
485
|
-
return (ctx.param ?? []).map((c) => this.visit(c));
|
|
486
|
-
}
|
|
487
|
-
param(ctx) {
|
|
488
|
-
const id = ctx.Identifier[0];
|
|
489
|
-
const type = ctx.typeAnnot
|
|
490
|
-
? this.visit(ctx.typeAnnot[0])
|
|
491
|
-
: null;
|
|
492
|
-
const endRange = type?.range ?? tokenRange(id);
|
|
493
|
-
return {
|
|
494
|
-
kind: 'Param',
|
|
495
|
-
name: id.image,
|
|
496
|
-
type,
|
|
497
|
-
range: spanFromTokenToRange(id, endRange),
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
faiParamList(ctx) {
|
|
501
|
-
return (ctx.faiParam ?? []).map((c) => this.visit(c));
|
|
502
|
-
}
|
|
503
|
-
faiParam(ctx) {
|
|
504
|
-
const id = ctx.Identifier[0];
|
|
505
|
-
const type = this.visit(ctx.typeAnnot[0]);
|
|
506
|
-
return {
|
|
507
|
-
kind: 'FaiParam',
|
|
508
|
-
name: id.image,
|
|
509
|
-
type,
|
|
510
|
-
range: spanFromTokenToRange(id, type.range),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
faiOutputList(ctx) {
|
|
514
|
-
return ctx.faiOutput.map((c) => this.visit(c));
|
|
515
|
-
}
|
|
516
|
-
faiOutput(ctx) {
|
|
517
|
-
const id = ctx.Identifier[0];
|
|
518
|
-
const type = this.visit(ctx.typeAnnot[0]);
|
|
519
|
-
return {
|
|
520
|
-
kind: 'FaiOutput',
|
|
521
|
-
name: id.image,
|
|
522
|
-
type,
|
|
523
|
-
range: spanFromTokenToRange(id, type.range),
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
// ─── Types ──────────────────────────────────────────────────────────
|
|
527
|
-
typeAnnot(ctx) {
|
|
528
|
-
if (ctx.enumType)
|
|
529
|
-
return this.visit(ctx.enumType[0]);
|
|
530
|
-
if (ctx.arrayType)
|
|
531
|
-
return this.visit(ctx.arrayType[0]);
|
|
532
|
-
if (ctx.objectType)
|
|
533
|
-
return this.visit(ctx.objectType[0]);
|
|
534
|
-
return this.visit(ctx.scalarType[0]);
|
|
535
|
-
}
|
|
536
|
-
// Variant for let/var/const decl types: same AST shape as typeAnnot
|
|
537
|
-
// but scalar/array sub-rules don't allow trailing named constraints
|
|
538
|
-
// (those would silently swallow the next statement). Constraints
|
|
539
|
-
// belong on fai outputs / func params, not local bindings.
|
|
540
|
-
declTypeAnnot(ctx) {
|
|
541
|
-
if (ctx.enumType)
|
|
542
|
-
return this.visit(ctx.enumType[0]);
|
|
543
|
-
if (ctx.declArrayType)
|
|
544
|
-
return this.visit(ctx.declArrayType[0]);
|
|
545
|
-
if (ctx.objectType)
|
|
546
|
-
return this.visit(ctx.objectType[0]);
|
|
547
|
-
return this.visit(ctx.declScalarType[0]);
|
|
548
|
-
}
|
|
549
|
-
declScalarType(ctx) {
|
|
550
|
-
const id = ctx.Identifier[0];
|
|
551
|
-
return {
|
|
552
|
-
kind: 'ScalarType',
|
|
553
|
-
name: id.image,
|
|
554
|
-
constraint: null,
|
|
555
|
-
range: tokenRange(id),
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
declArrayType(ctx) {
|
|
559
|
-
const arrTok = ctx.KwArray[0];
|
|
560
|
-
const element = this.visit(ctx.typeAnnot[0]);
|
|
561
|
-
const rangle = ctx.RAngle[0];
|
|
562
|
-
return {
|
|
563
|
-
kind: 'ArrayType',
|
|
564
|
-
element,
|
|
565
|
-
constraint: null,
|
|
566
|
-
range: spanFromTokenToRange(arrTok, tokenRange(rangle)),
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
scalarType(ctx) {
|
|
570
|
-
const id = ctx.Identifier[0];
|
|
571
|
-
const constraint = ctx.typeConstraint
|
|
572
|
-
? this.visit(ctx.typeConstraint[0])
|
|
573
|
-
: null;
|
|
574
|
-
const endRange = constraint?.range ?? tokenRange(id);
|
|
575
|
-
return {
|
|
576
|
-
kind: 'ScalarType',
|
|
577
|
-
name: id.image,
|
|
578
|
-
constraint,
|
|
579
|
-
range: spanFromTokenToRange(id, endRange),
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
enumType(ctx) {
|
|
583
|
-
const enumTok = ctx.KwEnum[0];
|
|
584
|
-
const variants = ctx.Identifier.map((t) => t.image);
|
|
585
|
-
const lastId = ctx.Identifier.at(-1);
|
|
586
|
-
return {
|
|
587
|
-
kind: 'EnumType',
|
|
588
|
-
variants,
|
|
589
|
-
range: spanTokens(enumTok, lastId),
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
arrayType(ctx) {
|
|
593
|
-
const arrTok = ctx.KwArray[0];
|
|
594
|
-
const element = this.visit(ctx.typeAnnot[0]);
|
|
595
|
-
const rangle = ctx.RAngle[0];
|
|
596
|
-
const constraint = ctx.namedConstraint
|
|
597
|
-
? this.visit(ctx.namedConstraint[0])
|
|
598
|
-
: null;
|
|
599
|
-
const endRange = constraint?.range ?? tokenRange(rangle);
|
|
600
|
-
return {
|
|
601
|
-
kind: 'ArrayType',
|
|
602
|
-
element,
|
|
603
|
-
constraint,
|
|
604
|
-
range: spanFromTokenToRange(arrTok, endRange),
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
objectType(ctx) {
|
|
608
|
-
const objTok = ctx.KwObject[0];
|
|
609
|
-
const rcurly = ctx.RCurly[0];
|
|
610
|
-
const fields = (ctx.objectTypeField ?? []).map((c) => this.visit(c));
|
|
611
|
-
return {
|
|
612
|
-
kind: 'ObjectType',
|
|
613
|
-
fields,
|
|
614
|
-
range: spanTokens(objTok, rcurly),
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
objectTypeField(ctx) {
|
|
618
|
-
const id = ctx.Identifier[0];
|
|
619
|
-
const type = this.visit(ctx.typeAnnot[0]);
|
|
620
|
-
return {
|
|
621
|
-
kind: 'ObjectTypeField',
|
|
622
|
-
name: id.image,
|
|
623
|
-
type,
|
|
624
|
-
range: spanFromTokenToRange(id, type.range),
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
typeConstraint(ctx) {
|
|
628
|
-
if (ctx.rangeConstraint)
|
|
629
|
-
return this.visit(ctx.rangeConstraint[0]);
|
|
630
|
-
return this.visit(ctx.namedConstraint[0]);
|
|
631
|
-
}
|
|
632
|
-
rangeConstraint(ctx) {
|
|
633
|
-
const nums = ctx.numberLit.map((c) => this.visit(c));
|
|
634
|
-
return {
|
|
635
|
-
kind: 'RangeConstraint',
|
|
636
|
-
min: nums[0].value,
|
|
637
|
-
max: nums[1].value,
|
|
638
|
-
range: spanRanges(nums[0].range, nums[1].range),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
namedConstraint(ctx) {
|
|
642
|
-
const key = ctx.Identifier[0];
|
|
643
|
-
let value;
|
|
644
|
-
let endRange;
|
|
645
|
-
if (ctx.numberLit) {
|
|
646
|
-
const n = this.visit(ctx.numberLit[0]);
|
|
647
|
-
value = n.value;
|
|
648
|
-
endRange = n.range;
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
const sTok = ctx.StringLit[0];
|
|
652
|
-
value = unquoteString(sTok.image);
|
|
653
|
-
endRange = tokenRange(sTok);
|
|
654
|
-
}
|
|
655
|
-
return {
|
|
656
|
-
kind: 'NamedConstraint',
|
|
657
|
-
key: key.image,
|
|
658
|
-
value,
|
|
659
|
-
range: spanFromTokenToRange(key, endRange),
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
numberLit(ctx) {
|
|
663
|
-
if (ctx.IntLit) {
|
|
664
|
-
const tok = ctx.IntLit[0];
|
|
665
|
-
return { value: Number.parseInt(tok.image, 10), range: tokenRange(tok) };
|
|
666
|
-
}
|
|
667
|
-
const tok = ctx.FloatLit[0];
|
|
668
|
-
return { value: Number.parseFloat(tok.image), range: tokenRange(tok) };
|
|
669
|
-
}
|
|
670
|
-
// ─── Block / Statements ─────────────────────────────────────────────
|
|
671
|
-
block(ctx) {
|
|
672
|
-
const lcurly = ctx.LCurly[0];
|
|
673
|
-
const rcurly = ctx.RCurly[0];
|
|
674
|
-
const stmts = (ctx.stmt ?? []).map((c) => this.visit(c));
|
|
675
|
-
return { kind: 'Block', stmts, range: spanTokens(lcurly, rcurly) };
|
|
676
|
-
}
|
|
677
|
-
stmt(ctx) {
|
|
678
|
-
if (ctx.letDecl)
|
|
679
|
-
return this.visit(ctx.letDecl[0]);
|
|
680
|
-
if (ctx.ifStmt)
|
|
681
|
-
return this.visit(ctx.ifStmt[0]);
|
|
682
|
-
if (ctx.forStmt)
|
|
683
|
-
return this.visit(ctx.forStmt[0]);
|
|
684
|
-
if (ctx.whileStmt)
|
|
685
|
-
return this.visit(ctx.whileStmt[0]);
|
|
686
|
-
if (ctx.tryStmt)
|
|
687
|
-
return this.visit(ctx.tryStmt[0]);
|
|
688
|
-
if (ctx.breakStmt)
|
|
689
|
-
return this.visit(ctx.breakStmt[0]);
|
|
690
|
-
if (ctx.continueStmt)
|
|
691
|
-
return this.visit(ctx.continueStmt[0]);
|
|
692
|
-
if (ctx.returnStmt)
|
|
693
|
-
return this.visit(ctx.returnStmt[0]);
|
|
694
|
-
if (ctx.assignment)
|
|
695
|
-
return this.visit(ctx.assignment[0]);
|
|
696
|
-
if (ctx.exprStmt)
|
|
697
|
-
return this.visit(ctx.exprStmt[0]);
|
|
698
|
-
throw new Error('unreachable: stmt had no matching alternative');
|
|
699
|
-
}
|
|
700
|
-
letDecl(ctx) {
|
|
701
|
-
const letTok = ctx.Let[0];
|
|
702
|
-
const target = this.visit(ctx.letTarget[0]);
|
|
703
|
-
const type = ctx.declTypeAnnot
|
|
704
|
-
? this.visit(ctx.declTypeAnnot[0])
|
|
705
|
-
: null;
|
|
706
|
-
const init = ctx.expr ? this.visit(ctx.expr[0]) : null;
|
|
707
|
-
const endRange = init?.range ?? type?.range ?? target.range;
|
|
708
|
-
return {
|
|
709
|
-
kind: 'LetDecl',
|
|
710
|
-
target,
|
|
711
|
-
type,
|
|
712
|
-
init,
|
|
713
|
-
range: spanFromTokenToRange(letTok, endRange),
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
letTarget(ctx) {
|
|
717
|
-
if (ctx.Identifier) {
|
|
718
|
-
const id = ctx.Identifier[0];
|
|
719
|
-
return { kind: 'IdentTarget', name: id.image, range: tokenRange(id) };
|
|
720
|
-
}
|
|
721
|
-
if (ctx.objectDestruct)
|
|
722
|
-
return this.visit(ctx.objectDestruct[0]);
|
|
723
|
-
return this.visit(ctx.arrayDestruct[0]);
|
|
724
|
-
}
|
|
725
|
-
objectDestruct(ctx) {
|
|
726
|
-
const lcurly = ctx.LCurly[0];
|
|
727
|
-
const rcurly = ctx.RCurly[0];
|
|
728
|
-
const fields = (ctx.destructField ?? []).map((c) => this.visit(c));
|
|
729
|
-
return {
|
|
730
|
-
kind: 'ObjectDestruct',
|
|
731
|
-
fields,
|
|
732
|
-
range: spanTokens(lcurly, rcurly),
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
destructField(ctx) {
|
|
736
|
-
const ids = ctx.Identifier;
|
|
737
|
-
const source = ids[0].image;
|
|
738
|
-
const local = ids.length > 1 ? ids[1].image : source;
|
|
739
|
-
return {
|
|
740
|
-
kind: 'DestructField',
|
|
741
|
-
source,
|
|
742
|
-
local,
|
|
743
|
-
range: spanTokens(ids[0], ids[ids.length - 1]),
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
arrayDestruct(ctx) {
|
|
747
|
-
const lbracket = ctx.LBracket[0];
|
|
748
|
-
const rbracket = ctx.RBracket[0];
|
|
749
|
-
const names = ctx.Identifier.map((t) => t.image);
|
|
750
|
-
return {
|
|
751
|
-
kind: 'ArrayDestruct',
|
|
752
|
-
names,
|
|
753
|
-
range: spanTokens(lbracket, rbracket),
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
assignment(ctx) {
|
|
757
|
-
const target = this.visit(ctx.lvalue[0]);
|
|
758
|
-
const op = this.visit(ctx.assignOp[0]);
|
|
759
|
-
const value = this.visit(ctx.expr[0]);
|
|
760
|
-
return {
|
|
761
|
-
kind: 'Assignment',
|
|
762
|
-
target,
|
|
763
|
-
op,
|
|
764
|
-
value,
|
|
765
|
-
range: spanRanges(target.range, value.range),
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
lvalue(ctx) {
|
|
769
|
-
const id = ctx.Identifier[0];
|
|
770
|
-
const suffixes = (ctx.lvalueSuffix ?? []).map((c) => this.visit(c));
|
|
771
|
-
const endRange = suffixes.length > 0 ? suffixes[suffixes.length - 1].range : tokenRange(id);
|
|
772
|
-
return {
|
|
773
|
-
kind: 'LValue',
|
|
774
|
-
base: id.image,
|
|
775
|
-
suffixes,
|
|
776
|
-
range: spanFromTokenToRange(id, endRange),
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
lvalueSuffix(ctx) {
|
|
780
|
-
if (ctx.Dot) {
|
|
781
|
-
const dot = ctx.Dot[0];
|
|
782
|
-
const id = ctx.Identifier[0];
|
|
783
|
-
return {
|
|
784
|
-
kind: 'MemberSuffix',
|
|
785
|
-
name: id.image,
|
|
786
|
-
range: spanTokens(dot, id),
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
const lbracket = ctx.LBracket[0];
|
|
790
|
-
const rbracket = ctx.RBracket[0];
|
|
791
|
-
const index = this.visit(ctx.expr[0]);
|
|
792
|
-
return {
|
|
793
|
-
kind: 'IndexSuffix',
|
|
794
|
-
index,
|
|
795
|
-
range: spanTokens(lbracket, rbracket),
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
assignOp(ctx) {
|
|
799
|
-
if (ctx.Equals)
|
|
800
|
-
return '=';
|
|
801
|
-
if (ctx.PlusEq)
|
|
802
|
-
return '+=';
|
|
803
|
-
if (ctx.MinusEq)
|
|
804
|
-
return '-=';
|
|
805
|
-
if (ctx.StarEq)
|
|
806
|
-
return '*=';
|
|
807
|
-
if (ctx.SlashEq)
|
|
808
|
-
return '/=';
|
|
809
|
-
return '%=';
|
|
810
|
-
}
|
|
811
|
-
ifStmt(ctx) {
|
|
812
|
-
const ifTok = ctx.If[0];
|
|
813
|
-
const exprs = ctx.expr;
|
|
814
|
-
const blocks = ctx.block;
|
|
815
|
-
const cond = this.visit(exprs[0]);
|
|
816
|
-
const then = this.visit(blocks[0]);
|
|
817
|
-
// remaining expr/block pairs (each "else if") + optional final else block
|
|
818
|
-
// grammar: ifStmt has: 1 cond expr + 1 then block + N elif-pairs (expr+block) + optional else block
|
|
819
|
-
const elifs = [];
|
|
820
|
-
for (let i = 1; i < exprs.length; i++) {
|
|
821
|
-
const eCond = this.visit(exprs[i]);
|
|
822
|
-
const eBody = this.visit(blocks[i]);
|
|
823
|
-
elifs.push({
|
|
824
|
-
kind: 'ElseIf',
|
|
825
|
-
cond: eCond,
|
|
826
|
-
body: eBody,
|
|
827
|
-
range: spanRanges(eCond.range, eBody.range),
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
// if there is a trailing else, its block is the last one in `blocks`
|
|
831
|
-
let otherwise = null;
|
|
832
|
-
if (blocks.length > exprs.length) {
|
|
833
|
-
otherwise = this.visit(blocks[blocks.length - 1]);
|
|
834
|
-
}
|
|
835
|
-
const endRange = otherwise?.range ?? elifs.at(-1)?.range ?? then.range;
|
|
836
|
-
return {
|
|
837
|
-
kind: 'IfStmt',
|
|
838
|
-
cond,
|
|
839
|
-
then,
|
|
840
|
-
elifs,
|
|
841
|
-
otherwise,
|
|
842
|
-
range: spanFromTokenToRange(ifTok, endRange),
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
forStmt(ctx) {
|
|
846
|
-
const forTok = ctx.For[0];
|
|
847
|
-
const binding = ctx.Identifier[0].image;
|
|
848
|
-
const iterable = this.visit(ctx.expr[0]);
|
|
849
|
-
const body = this.visit(ctx.block[0]);
|
|
850
|
-
return {
|
|
851
|
-
kind: 'ForStmt',
|
|
852
|
-
binding,
|
|
853
|
-
iterable,
|
|
854
|
-
body,
|
|
855
|
-
range: spanFromTokenToRange(forTok, body.range),
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
whileStmt(ctx) {
|
|
859
|
-
const whileTok = ctx.While[0];
|
|
860
|
-
const cond = this.visit(ctx.expr[0]);
|
|
861
|
-
const body = this.visit(ctx.block[0]);
|
|
862
|
-
return {
|
|
863
|
-
kind: 'WhileStmt',
|
|
864
|
-
cond,
|
|
865
|
-
body,
|
|
866
|
-
range: spanFromTokenToRange(whileTok, body.range),
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
tryStmt(ctx) {
|
|
870
|
-
const tryTok = ctx.Try[0];
|
|
871
|
-
const body = this.visit(ctx.block[0]);
|
|
872
|
-
const catches = ctx.catchClause.map((c) => this.visit(c));
|
|
873
|
-
const endRange = catches.at(-1)?.range ?? body.range;
|
|
874
|
-
return {
|
|
875
|
-
kind: 'TryStmt',
|
|
876
|
-
body,
|
|
877
|
-
catches,
|
|
878
|
-
range: spanFromTokenToRange(tryTok, endRange),
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
catchClause(ctx) {
|
|
882
|
-
const catchTok = ctx.Catch[0];
|
|
883
|
-
const ids = ctx.Identifier;
|
|
884
|
-
const errorType = ids[0].image;
|
|
885
|
-
const binding = ids.length > 1 ? ids[1].image : null;
|
|
886
|
-
const body = this.visit(ctx.block[0]);
|
|
887
|
-
return {
|
|
888
|
-
kind: 'CatchClause',
|
|
889
|
-
errorType,
|
|
890
|
-
binding,
|
|
891
|
-
body,
|
|
892
|
-
range: spanFromTokenToRange(catchTok, body.range),
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
breakStmt(ctx) {
|
|
896
|
-
const tok = ctx.Break[0];
|
|
897
|
-
return { kind: 'BreakStmt', range: tokenRange(tok) };
|
|
898
|
-
}
|
|
899
|
-
continueStmt(ctx) {
|
|
900
|
-
const tok = ctx.Continue[0];
|
|
901
|
-
return { kind: 'ContinueStmt', range: tokenRange(tok) };
|
|
902
|
-
}
|
|
903
|
-
returnStmt(ctx) {
|
|
904
|
-
const retTok = ctx.Return[0];
|
|
905
|
-
const value = ctx.expr ? this.visit(ctx.expr[0]) : null;
|
|
906
|
-
return {
|
|
907
|
-
kind: 'ReturnStmt',
|
|
908
|
-
value,
|
|
909
|
-
range: value ? spanFromTokenToRange(retTok, value.range) : tokenRange(retTok),
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
exprStmt(ctx) {
|
|
913
|
-
const expr = this.visit(ctx.expr[0]);
|
|
914
|
-
return { kind: 'ExprStmt', expr, range: expr.range };
|
|
915
|
-
}
|
|
916
|
-
// ─── Expressions ────────────────────────────────────────────────────
|
|
917
|
-
expr(ctx) {
|
|
918
|
-
return this.visit(ctx.ternaryExpr[0]);
|
|
919
|
-
}
|
|
920
|
-
ternaryExpr(ctx) {
|
|
921
|
-
const cond = this.visit(ctx.logicalOrExpr[0]);
|
|
922
|
-
if (!ctx.expr)
|
|
923
|
-
return cond;
|
|
924
|
-
const [thenCst, elseCst] = ctx.expr;
|
|
925
|
-
const thenExpr = this.visit(thenCst);
|
|
926
|
-
const elseExpr = this.visit(elseCst);
|
|
927
|
-
return {
|
|
928
|
-
kind: 'TernaryExpr',
|
|
929
|
-
cond,
|
|
930
|
-
then: thenExpr,
|
|
931
|
-
otherwise: elseExpr,
|
|
932
|
-
range: spanRanges(cond.range, elseExpr.range),
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
logicalOrExpr(ctx) {
|
|
936
|
-
return buildLeftAssocBinary(ctx.logicalAndExpr.map((c) => this.visit(c)), (ctx.OrOr ?? []).map(() => '||'));
|
|
937
|
-
}
|
|
938
|
-
logicalAndExpr(ctx) {
|
|
939
|
-
return buildLeftAssocBinary(ctx.equalityExpr.map((c) => this.visit(c)), (ctx.AndAnd ?? []).map(() => '&&'));
|
|
940
|
-
}
|
|
941
|
-
equalityExpr(ctx) {
|
|
942
|
-
const operands = ctx.comparisonExpr.map((c) => this.visit(c));
|
|
943
|
-
const ops = combineOps(ctx, [['EqEq', '=='], ['NotEq', '!=']]);
|
|
944
|
-
return buildLeftAssocBinary(operands, ops);
|
|
945
|
-
}
|
|
946
|
-
comparisonExpr(ctx) {
|
|
947
|
-
const operands = ctx.additiveExpr.map((c) => this.visit(c));
|
|
948
|
-
const ops = combineOps(ctx, [
|
|
949
|
-
['LAngle', '<'],
|
|
950
|
-
['LtEq', '<='],
|
|
951
|
-
['RAngle', '>'],
|
|
952
|
-
['GtEq', '>='],
|
|
953
|
-
]);
|
|
954
|
-
return buildLeftAssocBinary(operands, ops);
|
|
955
|
-
}
|
|
956
|
-
additiveExpr(ctx) {
|
|
957
|
-
const operands = ctx.multiplicativeExpr.map((c) => this.visit(c));
|
|
958
|
-
const ops = combineOps(ctx, [['Plus', '+'], ['Dash', '-']]);
|
|
959
|
-
return buildLeftAssocBinary(operands, ops);
|
|
960
|
-
}
|
|
961
|
-
multiplicativeExpr(ctx) {
|
|
962
|
-
const operands = ctx.unaryExpr.map((c) => this.visit(c));
|
|
963
|
-
const ops = combineOps(ctx, [
|
|
964
|
-
['Star', '*'],
|
|
965
|
-
['Slash', '/'],
|
|
966
|
-
['Percent', '%'],
|
|
967
|
-
]);
|
|
968
|
-
return buildLeftAssocBinary(operands, ops);
|
|
969
|
-
}
|
|
970
|
-
unaryExpr(ctx) {
|
|
971
|
-
const operand = this.visit(ctx.postfixExpr[0]);
|
|
972
|
-
if (ctx.Dash) {
|
|
973
|
-
const dash = ctx.Dash[0];
|
|
974
|
-
return {
|
|
975
|
-
kind: 'UnaryExpr',
|
|
976
|
-
op: '-',
|
|
977
|
-
operand,
|
|
978
|
-
range: spanFromTokenToRange(dash, operand.range),
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
if (ctx.Bang) {
|
|
982
|
-
const bang = ctx.Bang[0];
|
|
983
|
-
return {
|
|
984
|
-
kind: 'UnaryExpr',
|
|
985
|
-
op: '!',
|
|
986
|
-
operand,
|
|
987
|
-
range: spanFromTokenToRange(bang, operand.range),
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
return operand;
|
|
991
|
-
}
|
|
992
|
-
postfixExpr(ctx) {
|
|
993
|
-
let current = this.visit(ctx.primaryExpr[0]);
|
|
994
|
-
const suffixes = (ctx.postfixSuffix ?? []);
|
|
995
|
-
for (const sufCst of suffixes) {
|
|
996
|
-
const suf = this.visit(sufCst);
|
|
997
|
-
if (suf.tag === 'member') {
|
|
998
|
-
current = {
|
|
999
|
-
kind: 'MemberExpr',
|
|
1000
|
-
object: current,
|
|
1001
|
-
property: suf.name,
|
|
1002
|
-
range: spanRanges(current.range, suf.range),
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
else if (suf.tag === 'index') {
|
|
1006
|
-
current = {
|
|
1007
|
-
kind: 'IndexExpr',
|
|
1008
|
-
object: current,
|
|
1009
|
-
index: suf.index,
|
|
1010
|
-
range: spanRanges(current.range, suf.range),
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
else {
|
|
1014
|
-
current = {
|
|
1015
|
-
kind: 'CallExpr',
|
|
1016
|
-
callee: current,
|
|
1017
|
-
args: suf.args,
|
|
1018
|
-
range: spanRanges(current.range, suf.range),
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
return current;
|
|
1023
|
-
}
|
|
1024
|
-
postfixSuffix(ctx) {
|
|
1025
|
-
if (ctx.Dot) {
|
|
1026
|
-
const dot = ctx.Dot[0];
|
|
1027
|
-
const id = ctx.Identifier[0];
|
|
1028
|
-
return { tag: 'member', name: id.image, range: spanTokens(dot, id) };
|
|
1029
|
-
}
|
|
1030
|
-
if (ctx.LBracket) {
|
|
1031
|
-
const lb = ctx.LBracket[0];
|
|
1032
|
-
const rb = ctx.RBracket[0];
|
|
1033
|
-
const index = this.visit(ctx.expr[0]);
|
|
1034
|
-
return { tag: 'index', index, range: spanTokens(lb, rb) };
|
|
1035
|
-
}
|
|
1036
|
-
const lp = ctx.LParen[0];
|
|
1037
|
-
const rp = ctx.RParen[0];
|
|
1038
|
-
const args = ctx.argList
|
|
1039
|
-
? this.visit(ctx.argList[0])
|
|
1040
|
-
: [];
|
|
1041
|
-
return { tag: 'call', args, range: spanTokens(lp, rp) };
|
|
1042
|
-
}
|
|
1043
|
-
argList(ctx) {
|
|
1044
|
-
return ctx.expr.map((c) => this.visit(c));
|
|
1045
|
-
}
|
|
1046
|
-
primaryExpr(ctx) {
|
|
1047
|
-
if (ctx.literal)
|
|
1048
|
-
return this.visit(ctx.literal[0]);
|
|
1049
|
-
if (ctx.Identifier) {
|
|
1050
|
-
const id = ctx.Identifier[0];
|
|
1051
|
-
return { kind: 'IdentExpr', name: id.image, range: tokenRange(id) };
|
|
1052
|
-
}
|
|
1053
|
-
if (ctx.arrayLit)
|
|
1054
|
-
return this.visit(ctx.arrayLit[0]);
|
|
1055
|
-
if (ctx.objectLit)
|
|
1056
|
-
return this.visit(ctx.objectLit[0]);
|
|
1057
|
-
// parenthesised
|
|
1058
|
-
return this.visit(ctx.expr[0]);
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Returns either a plain Literal or a TemplateString. Callers in
|
|
1062
|
-
* expression position accept Expr; callers expecting a strict Literal
|
|
1063
|
-
* (annotation args, type constraint values) should narrow by `kind`.
|
|
1064
|
-
*/
|
|
1065
|
-
literal(ctx) {
|
|
1066
|
-
if (ctx.IntLit) {
|
|
1067
|
-
const tok = ctx.IntLit[0];
|
|
1068
|
-
return {
|
|
1069
|
-
kind: 'IntLit',
|
|
1070
|
-
value: Number.parseInt(tok.image, 10),
|
|
1071
|
-
range: tokenRange(tok),
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
if (ctx.FloatLit) {
|
|
1075
|
-
const tok = ctx.FloatLit[0];
|
|
1076
|
-
return {
|
|
1077
|
-
kind: 'FloatLit',
|
|
1078
|
-
value: Number.parseFloat(tok.image),
|
|
1079
|
-
range: tokenRange(tok),
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
if (ctx.StringLit) {
|
|
1083
|
-
const tok = ctx.StringLit[0];
|
|
1084
|
-
return buildStringExpr(tok);
|
|
1085
|
-
}
|
|
1086
|
-
if (ctx.True) {
|
|
1087
|
-
const tok = ctx.True[0];
|
|
1088
|
-
return { kind: 'BoolLit', value: true, range: tokenRange(tok) };
|
|
1089
|
-
}
|
|
1090
|
-
if (ctx.False) {
|
|
1091
|
-
const tok = ctx.False[0];
|
|
1092
|
-
return { kind: 'BoolLit', value: false, range: tokenRange(tok) };
|
|
1093
|
-
}
|
|
1094
|
-
const tok = ctx.Null[0];
|
|
1095
|
-
return { kind: 'NullLit', range: tokenRange(tok) };
|
|
1096
|
-
}
|
|
1097
|
-
arrayLit(ctx) {
|
|
1098
|
-
const lb = ctx.LBracket[0];
|
|
1099
|
-
const rb = ctx.RBracket[0];
|
|
1100
|
-
const elements = ctx.expr
|
|
1101
|
-
? ctx.expr.map((c) => this.visit(c))
|
|
1102
|
-
: [];
|
|
1103
|
-
return { kind: 'ArrayLit', elements, range: spanTokens(lb, rb) };
|
|
1104
|
-
}
|
|
1105
|
-
objectLit(ctx) {
|
|
1106
|
-
const lc = ctx.LCurly[0];
|
|
1107
|
-
const rc = ctx.RCurly[0];
|
|
1108
|
-
const fields = (ctx.objectLitField ?? []).map((c) => this.visit(c));
|
|
1109
|
-
return { kind: 'ObjectLit', fields, range: spanTokens(lc, rc) };
|
|
1110
|
-
}
|
|
1111
|
-
objectLitField(ctx) {
|
|
1112
|
-
if (ctx.StringLit) {
|
|
1113
|
-
const keyTok = ctx.StringLit[0];
|
|
1114
|
-
const value = this.visit(ctx.expr[0]);
|
|
1115
|
-
return {
|
|
1116
|
-
kind: 'ObjectLitField',
|
|
1117
|
-
key: unquoteString(keyTok.image),
|
|
1118
|
-
shorthand: false,
|
|
1119
|
-
value,
|
|
1120
|
-
range: spanFromTokenToRange(keyTok, value.range),
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
const idTok = ctx.Identifier[0];
|
|
1124
|
-
if (ctx.expr) {
|
|
1125
|
-
const value = this.visit(ctx.expr[0]);
|
|
1126
|
-
return {
|
|
1127
|
-
kind: 'ObjectLitField',
|
|
1128
|
-
key: idTok.image,
|
|
1129
|
-
shorthand: false,
|
|
1130
|
-
value,
|
|
1131
|
-
range: spanFromTokenToRange(idTok, value.range),
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
// shorthand: { x } means { x: x }
|
|
1135
|
-
return {
|
|
1136
|
-
kind: 'ObjectLitField',
|
|
1137
|
-
key: idTok.image,
|
|
1138
|
-
shorthand: true,
|
|
1139
|
-
value: { kind: 'IdentExpr', name: idTok.image, range: tokenRange(idTok) },
|
|
1140
|
-
range: tokenRange(idTok),
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
// ─── Helpers used by visitor ──────────────────────────────────────────
|
|
1145
|
-
function spanTokens(start, end) {
|
|
1146
|
-
return {
|
|
1147
|
-
startLine: start.startLine ?? 0,
|
|
1148
|
-
startColumn: start.startColumn ?? 0,
|
|
1149
|
-
endLine: end.endLine ?? 0,
|
|
1150
|
-
endColumn: end.endColumn ?? 0,
|
|
1151
|
-
startOffset: start.startOffset,
|
|
1152
|
-
endOffset: end.endOffset ?? end.startOffset,
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
function spanRanges(a, b) {
|
|
1156
|
-
return {
|
|
1157
|
-
startLine: a.startLine,
|
|
1158
|
-
startColumn: a.startColumn,
|
|
1159
|
-
endLine: b.endLine,
|
|
1160
|
-
endColumn: b.endColumn,
|
|
1161
|
-
startOffset: a.startOffset,
|
|
1162
|
-
endOffset: b.endOffset,
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
function spanFromTokenToRange(start, end) {
|
|
1166
|
-
return {
|
|
1167
|
-
startLine: start.startLine ?? 0,
|
|
1168
|
-
startColumn: start.startColumn ?? 0,
|
|
1169
|
-
endLine: end.endLine,
|
|
1170
|
-
endColumn: end.endColumn,
|
|
1171
|
-
startOffset: start.startOffset,
|
|
1172
|
-
endOffset: end.endOffset,
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
function emptyRange(items) {
|
|
1176
|
-
if (items.length === 0) {
|
|
1177
|
-
return {
|
|
1178
|
-
startLine: 0,
|
|
1179
|
-
startColumn: 0,
|
|
1180
|
-
endLine: 0,
|
|
1181
|
-
endColumn: 0,
|
|
1182
|
-
startOffset: 0,
|
|
1183
|
-
endOffset: 0,
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
const first = items[0].range;
|
|
1187
|
-
const last = items[items.length - 1].range;
|
|
1188
|
-
return spanRanges(first, last);
|
|
1189
|
-
}
|
|
1190
|
-
function findFirstToken(node) {
|
|
1191
|
-
// CstNode children: { ruleName: CstNode[] | IToken[], ... }
|
|
1192
|
-
const children = node.children;
|
|
1193
|
-
let earliest;
|
|
1194
|
-
for (const key of Object.keys(children)) {
|
|
1195
|
-
const arr = children[key];
|
|
1196
|
-
for (const child of arr) {
|
|
1197
|
-
let tok;
|
|
1198
|
-
if ('image' in child) {
|
|
1199
|
-
tok = child;
|
|
1200
|
-
}
|
|
1201
|
-
else {
|
|
1202
|
-
tok = findFirstToken(child);
|
|
1203
|
-
}
|
|
1204
|
-
if (tok && (!earliest || tok.startOffset < earliest.startOffset)) {
|
|
1205
|
-
earliest = tok;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
return earliest;
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Build a left-associative binary expression chain from a list of
|
|
1213
|
-
* operands and a parallel list of operators. operators[i] joins
|
|
1214
|
-
* operands[i] and operands[i+1].
|
|
1215
|
-
*/
|
|
1216
|
-
function buildLeftAssocBinary(operands, operators) {
|
|
1217
|
-
let current = operands[0];
|
|
1218
|
-
for (let i = 0; i < operators.length; i++) {
|
|
1219
|
-
const right = operands[i + 1];
|
|
1220
|
-
current = {
|
|
1221
|
-
kind: 'BinaryExpr',
|
|
1222
|
-
op: operators[i],
|
|
1223
|
-
left: current,
|
|
1224
|
-
right,
|
|
1225
|
-
range: spanRanges(current.range, right.range),
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
return current;
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* For rules where multiple alternative token names can appear in MANY,
|
|
1232
|
-
* gather all operator tokens in source order and map them to AST op strings.
|
|
1233
|
-
*/
|
|
1234
|
-
function combineOps(ctx, mapping) {
|
|
1235
|
-
const all = [];
|
|
1236
|
-
for (const [tokName, op] of mapping) {
|
|
1237
|
-
const toks = ctx[tokName] ?? [];
|
|
1238
|
-
for (const t of toks)
|
|
1239
|
-
all.push({ offset: t.startOffset, op });
|
|
1240
|
-
}
|
|
1241
|
-
all.sort((a, b) => a.offset - b.offset);
|
|
1242
|
-
return all.map((x) => x.op);
|
|
1243
|
-
}
|
|
1244
|
-
// ─── Public API ───────────────────────────────────────────────────────
|
|
1245
|
-
const astBuilder = new TrainAstBuilder();
|
|
1246
|
-
/**
|
|
1247
|
-
* Build a typed AST from a parser CST. Returns null if no CST (parse failed).
|
|
1248
|
-
*/
|
|
1249
|
-
export function buildAst(cst) {
|
|
1250
|
-
if (!cst)
|
|
1251
|
-
return null;
|
|
1252
|
-
return astBuilder.visit(cst);
|
|
1253
|
-
}
|
|
1254
|
-
//# sourceMappingURL=builder.js.map
|