@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.
Files changed (236) hide show
  1. package/README.md +1 -1
  2. package/backend/dist/index.d.ts.map +1 -1
  3. package/backend/dist/index.js +51 -0
  4. package/backend/dist/index.js.map +1 -1
  5. package/backend/dist/routes/flows.d.ts.map +1 -1
  6. package/backend/dist/routes/flows.js +10 -0
  7. package/backend/dist/routes/flows.js.map +1 -1
  8. package/backend/dist/routes/global-tracks.d.ts +11 -0
  9. package/backend/dist/routes/global-tracks.d.ts.map +1 -0
  10. package/backend/dist/routes/global-tracks.js +86 -0
  11. package/backend/dist/routes/global-tracks.js.map +1 -0
  12. package/backend/dist/routes/tracks.d.ts +14 -0
  13. package/backend/dist/routes/tracks.d.ts.map +1 -0
  14. package/backend/dist/routes/tracks.js +184 -0
  15. package/backend/dist/routes/tracks.js.map +1 -0
  16. package/backend/dist/tracks/__tests__/verify-track-t1.d.ts +11 -0
  17. package/backend/dist/tracks/__tests__/verify-track-t1.d.ts.map +1 -0
  18. package/backend/dist/tracks/__tests__/verify-track-t1.js +293 -0
  19. package/backend/dist/tracks/__tests__/verify-track-t1.js.map +1 -0
  20. package/backend/dist/tracks/__tests__/verify-track.d.ts +20 -0
  21. package/backend/dist/tracks/__tests__/verify-track.d.ts.map +1 -0
  22. package/backend/dist/tracks/__tests__/verify-track.js +187 -0
  23. package/backend/dist/tracks/__tests__/verify-track.js.map +1 -0
  24. package/backend/dist/tracks/ask-user-bridge.d.ts +59 -0
  25. package/backend/dist/tracks/ask-user-bridge.d.ts.map +1 -0
  26. package/backend/dist/tracks/ask-user-bridge.js +186 -0
  27. package/backend/dist/tracks/ask-user-bridge.js.map +1 -0
  28. package/backend/dist/tracks/ccweb-train-adapter.d.ts +40 -0
  29. package/backend/dist/tracks/ccweb-train-adapter.d.ts.map +1 -0
  30. package/backend/dist/tracks/ccweb-train-adapter.js +111 -0
  31. package/backend/dist/tracks/ccweb-train-adapter.js.map +1 -0
  32. package/backend/dist/tracks/cross-lock.d.ts +30 -0
  33. package/backend/dist/tracks/cross-lock.d.ts.map +1 -0
  34. package/backend/dist/tracks/cross-lock.js +44 -0
  35. package/backend/dist/tracks/cross-lock.js.map +1 -0
  36. package/backend/dist/tracks/index.d.ts +20 -0
  37. package/backend/dist/tracks/index.d.ts.map +1 -0
  38. package/backend/dist/tracks/index.js +42 -0
  39. package/backend/dist/tracks/index.js.map +1 -0
  40. package/backend/dist/tracks/registry.d.ts +56 -0
  41. package/backend/dist/tracks/registry.d.ts.map +1 -0
  42. package/backend/dist/tracks/registry.js +161 -0
  43. package/backend/dist/tracks/registry.js.map +1 -0
  44. package/backend/dist/tracks/store.d.ts +37 -0
  45. package/backend/dist/tracks/store.d.ts.map +1 -0
  46. package/backend/dist/tracks/store.js +254 -0
  47. package/backend/dist/tracks/store.js.map +1 -0
  48. package/backend/dist/tracks/track-runner.d.ts +62 -0
  49. package/backend/dist/tracks/track-runner.d.ts.map +1 -0
  50. package/backend/dist/tracks/track-runner.js +134 -0
  51. package/backend/dist/tracks/track-runner.js.map +1 -0
  52. package/backend/dist/tracks/train-loader.d.ts +44 -0
  53. package/backend/dist/tracks/train-loader.d.ts.map +1 -0
  54. package/backend/dist/tracks/train-loader.js +32 -0
  55. package/backend/dist/tracks/train-loader.js.map +1 -0
  56. package/backend/dist/tracks/types-train.d.ts +12 -0
  57. package/backend/dist/tracks/types-train.d.ts.map +1 -0
  58. package/backend/dist/tracks/types-train.js +13 -0
  59. package/backend/dist/tracks/types-train.js.map +1 -0
  60. package/backend/dist/tracks/types.d.ts +66 -0
  61. package/backend/dist/tracks/types.d.ts.map +1 -0
  62. package/backend/dist/tracks/types.js +12 -0
  63. package/backend/dist/tracks/types.js.map +1 -0
  64. package/backend/dist/tracks/workflow-data-watcher.d.ts +47 -0
  65. package/backend/dist/tracks/workflow-data-watcher.d.ts.map +1 -0
  66. package/backend/dist/tracks/workflow-data-watcher.js +166 -0
  67. package/backend/dist/tracks/workflow-data-watcher.js.map +1 -0
  68. package/backend/package-lock.json +107 -0
  69. package/backend/package.json +6 -1
  70. package/backend/vendor/@train-lang/adapter-spec/dist/index.d.ts +164 -0
  71. package/backend/vendor/@train-lang/adapter-spec/dist/index.d.ts.map +1 -0
  72. package/backend/vendor/@train-lang/adapter-spec/dist/index.js +13 -0
  73. package/backend/vendor/@train-lang/adapter-spec/dist/index.js.map +1 -0
  74. package/backend/vendor/@train-lang/adapter-spec/package.json +15 -0
  75. package/backend/vendor/@train-lang/core/dist/ast-cache.d.ts +74 -0
  76. package/backend/vendor/@train-lang/core/dist/ast-cache.d.ts.map +1 -0
  77. package/backend/vendor/@train-lang/core/dist/ast-cache.js +157 -0
  78. package/backend/vendor/@train-lang/core/dist/ast-cache.js.map +1 -0
  79. package/backend/vendor/@train-lang/core/dist/ast.d.ts +350 -0
  80. package/backend/vendor/@train-lang/core/dist/ast.d.ts.map +1 -0
  81. package/backend/vendor/@train-lang/core/dist/ast.js +15 -0
  82. package/backend/vendor/@train-lang/core/dist/ast.js.map +1 -0
  83. package/backend/vendor/@train-lang/core/dist/builder.d.ts +21 -0
  84. package/backend/vendor/@train-lang/core/dist/builder.d.ts.map +1 -0
  85. package/backend/vendor/@train-lang/core/dist/builder.js +1221 -0
  86. package/backend/vendor/@train-lang/core/dist/builder.js.map +1 -0
  87. package/backend/vendor/@train-lang/core/dist/builtins.d.ts +17 -0
  88. package/backend/vendor/@train-lang/core/dist/builtins.d.ts.map +1 -0
  89. package/backend/vendor/@train-lang/core/dist/builtins.js +488 -0
  90. package/backend/vendor/@train-lang/core/dist/builtins.js.map +1 -0
  91. package/backend/vendor/@train-lang/core/dist/index.d.ts +54 -0
  92. package/backend/vendor/@train-lang/core/dist/index.d.ts.map +1 -0
  93. package/backend/vendor/@train-lang/core/dist/index.js +66 -0
  94. package/backend/vendor/@train-lang/core/dist/index.js.map +1 -0
  95. package/backend/vendor/@train-lang/core/dist/interpreter.d.ts +110 -0
  96. package/backend/vendor/@train-lang/core/dist/interpreter.d.ts.map +1 -0
  97. package/backend/vendor/@train-lang/core/dist/interpreter.js +894 -0
  98. package/backend/vendor/@train-lang/core/dist/interpreter.js.map +1 -0
  99. package/backend/vendor/@train-lang/core/dist/lexer.d.ts +88 -0
  100. package/backend/vendor/@train-lang/core/dist/lexer.d.ts.map +1 -0
  101. package/backend/vendor/@train-lang/core/dist/lexer.js +243 -0
  102. package/backend/vendor/@train-lang/core/dist/lexer.js.map +1 -0
  103. package/backend/vendor/@train-lang/core/dist/module-loader.d.ts +74 -0
  104. package/backend/vendor/@train-lang/core/dist/module-loader.d.ts.map +1 -0
  105. package/backend/vendor/@train-lang/core/dist/module-loader.js +134 -0
  106. package/backend/vendor/@train-lang/core/dist/module-loader.js.map +1 -0
  107. package/backend/vendor/@train-lang/core/dist/parser.d.ts +135 -0
  108. package/backend/vendor/@train-lang/core/dist/parser.d.ts.map +1 -0
  109. package/backend/vendor/@train-lang/core/dist/parser.js +838 -0
  110. package/backend/vendor/@train-lang/core/dist/parser.js.map +1 -0
  111. package/backend/vendor/@train-lang/core/dist/prompt-composer.d.ts +49 -0
  112. package/backend/vendor/@train-lang/core/dist/prompt-composer.d.ts.map +1 -0
  113. package/backend/vendor/@train-lang/core/dist/prompt-composer.js +159 -0
  114. package/backend/vendor/@train-lang/core/dist/prompt-composer.js.map +1 -0
  115. package/backend/vendor/@train-lang/core/dist/runtime.d.ts +146 -0
  116. package/backend/vendor/@train-lang/core/dist/runtime.d.ts.map +1 -0
  117. package/backend/vendor/@train-lang/core/dist/runtime.js +156 -0
  118. package/backend/vendor/@train-lang/core/dist/runtime.js.map +1 -0
  119. package/backend/vendor/@train-lang/core/dist/type-descriptor.d.ts +18 -0
  120. package/backend/vendor/@train-lang/core/dist/type-descriptor.d.ts.map +1 -0
  121. package/backend/vendor/@train-lang/core/dist/type-descriptor.js +94 -0
  122. package/backend/vendor/@train-lang/core/dist/type-descriptor.js.map +1 -0
  123. package/backend/vendor/@train-lang/core/dist/validation.d.ts +44 -0
  124. package/backend/vendor/@train-lang/core/dist/validation.d.ts.map +1 -0
  125. package/backend/vendor/@train-lang/core/dist/validation.js +271 -0
  126. package/backend/vendor/@train-lang/core/dist/validation.js.map +1 -0
  127. package/backend/vendor/@train-lang/core/package.json +35 -0
  128. package/frontend/dist/assets/{ChatOverlay-DsfZXjjz.js → ChatOverlay-D0AFod2I.js} +1 -1
  129. package/frontend/dist/assets/{GraphPreview-BGZHA8KW.js → GraphPreview-BTnlkocf.js} +2 -2
  130. package/frontend/dist/assets/{MobilePage-B-FiVfUE.js → MobilePage-CnjdRqIt.js} +3 -3
  131. package/frontend/dist/assets/{OfficePreview-CvHPeAlI.js → OfficePreview-nTZKPD9R.js} +2 -2
  132. package/frontend/dist/assets/{PdfPreview-D1OVG7jG.js → PdfPreview-DlB-s5M8.js} +1 -1
  133. package/frontend/dist/assets/{ProjectPage-CyKEenc5.js → ProjectPage-C_3_sZbd.js} +5 -5
  134. package/frontend/dist/assets/{SettingsPage-CIe3HZml.js → SettingsPage-BYSCQKoh.js} +2 -2
  135. package/frontend/dist/assets/{SkillHubPage-CG1-G4Pf.js → SkillHubPage-CUgZXyeE.js} +3 -3
  136. package/frontend/dist/assets/TrackEditor-R2lmPugZ.js +14 -0
  137. package/frontend/dist/assets/abap-LPLW346S.js +7 -0
  138. package/frontend/dist/assets/apex-Dk-jUCUV.js +7 -0
  139. package/frontend/dist/assets/azcli-DPUMmPlX.js +7 -0
  140. package/frontend/dist/assets/bat-C1Qbg1bV.js +7 -0
  141. package/frontend/dist/assets/bicep-D-e-VSJi.js +7 -0
  142. package/frontend/dist/assets/cameligo-CjUqTgqL.js +7 -0
  143. package/frontend/dist/assets/{chevron-down-CS7uu2Ol.js → chevron-down-Bc6Xpnnk.js} +1 -1
  144. package/frontend/dist/assets/clojure-BWsu6Kju.js +7 -0
  145. package/frontend/dist/assets/codicon-DCmgc-ay.ttf +0 -0
  146. package/frontend/dist/assets/coffee-DeC36AK3.js +7 -0
  147. package/frontend/dist/assets/cpp-DcZnmBWT.js +7 -0
  148. package/frontend/dist/assets/csharp-DqBXBMf0.js +7 -0
  149. package/frontend/dist/assets/csp-CkO7y8ul.js +7 -0
  150. package/frontend/dist/assets/css-OAcfVtED.js +7 -0
  151. package/frontend/dist/assets/cssMode-oT-78_Ke.js +7 -0
  152. package/frontend/dist/assets/cypher-40UGrUjD.js +7 -0
  153. package/frontend/dist/assets/dart-DmixyLor.js +7 -0
  154. package/frontend/dist/assets/dockerfile-CayO4nTA.js +7 -0
  155. package/frontend/dist/assets/ecl-DRrKCuVc.js +7 -0
  156. package/frontend/dist/assets/editor-B5EY1bb8.css +1 -0
  157. package/frontend/dist/assets/editor.main-CKHkO4rf.js +37 -0
  158. package/frontend/dist/assets/elixir-J3qpp9mX.js +7 -0
  159. package/frontend/dist/assets/flow9-DzW2c4Im.js +7 -0
  160. package/frontend/dist/assets/freemarker2-gLqtlSdW.js +7 -0
  161. package/frontend/dist/assets/fsharp-D00tn_6c.js +7 -0
  162. package/frontend/dist/assets/go-NnNmgWK_.js +7 -0
  163. package/frontend/dist/assets/graphql-C9_--VLF.js +7 -0
  164. package/frontend/dist/assets/handlebars-CMR-cKzv.js +7 -0
  165. package/frontend/dist/assets/hcl-BhbOULbp.js +7 -0
  166. package/frontend/dist/assets/html-er6LXl7Q.js +7 -0
  167. package/frontend/dist/assets/htmlMode-BY7CWp3G.js +7 -0
  168. package/frontend/dist/assets/index-BmPb0Wl1.css +1 -0
  169. package/frontend/dist/assets/{index-DN91i4kg.js → index-C6JmYBh3.js} +1 -1
  170. package/frontend/dist/assets/{index-BThL9NV3.js → index-C7QaGdFu.js} +2 -2
  171. package/frontend/dist/assets/{index-DCc5jmus.js → index-CuoDBJ4_.js} +1 -1
  172. package/frontend/dist/assets/index-LxjrLxfp.js +1 -0
  173. package/frontend/dist/assets/ini-zR-_X5iG.js +7 -0
  174. package/frontend/dist/assets/java-CSTjsyoZ.js +7 -0
  175. package/frontend/dist/assets/javascript-R0QZ7Rw2.js +7 -0
  176. package/frontend/dist/assets/jsonMode-dh6Js0Qv.js +7 -0
  177. package/frontend/dist/assets/{jszip.min-DHcltCpp.js → jszip.min-D7W3wEX7.js} +1 -1
  178. package/frontend/dist/assets/julia-DioMOKVu.js +7 -0
  179. package/frontend/dist/assets/kotlin-Dsb0PKmW.js +7 -0
  180. package/frontend/dist/assets/less-CZYLoAVD.js +7 -0
  181. package/frontend/dist/assets/lexon-d5JUiwNk.js +7 -0
  182. package/frontend/dist/assets/liquid-ggFR895M.js +7 -0
  183. package/frontend/dist/assets/lua-1Al72GG8.js +7 -0
  184. package/frontend/dist/assets/m3-hx1xCPC3.js +7 -0
  185. package/frontend/dist/assets/markdown-DPGrH1xZ.js +7 -0
  186. package/frontend/dist/assets/mdx-B4WBy0Yh.js +7 -0
  187. package/frontend/dist/assets/mips-CYbZjkIm.js +7 -0
  188. package/frontend/dist/assets/msdax-DAioKM5A.js +7 -0
  189. package/frontend/dist/assets/mysql-7e9GUebS.js +7 -0
  190. package/frontend/dist/assets/objective-c-BGf8QNjz.js +7 -0
  191. package/frontend/dist/assets/pascal-hEDRz3un.js +7 -0
  192. package/frontend/dist/assets/pascaligo-gqEhN6pG.js +7 -0
  193. package/frontend/dist/assets/perl-CN8Ht9Vk.js +7 -0
  194. package/frontend/dist/assets/pgsql-CRCqHQBx.js +7 -0
  195. package/frontend/dist/assets/php-DTZrlAwe.js +7 -0
  196. package/frontend/dist/assets/pla-CRmh4UcC.js +7 -0
  197. package/frontend/dist/assets/postiats-Car2G7g8.js +7 -0
  198. package/frontend/dist/assets/powerquery-BJKl8V3o.js +7 -0
  199. package/frontend/dist/assets/powershell-Du7a_J7r.js +7 -0
  200. package/frontend/dist/assets/protobuf-ChneWI0H.js +7 -0
  201. package/frontend/dist/assets/pug-BH6LSJaf.js +7 -0
  202. package/frontend/dist/assets/python-AIGmVleM.js +7 -0
  203. package/frontend/dist/assets/qsharp-CU-Hxpxi.js +7 -0
  204. package/frontend/dist/assets/r-Bw-W15zh.js +7 -0
  205. package/frontend/dist/assets/razor-BGAZW957.js +7 -0
  206. package/frontend/dist/assets/redis-bY-X9-ol.js +7 -0
  207. package/frontend/dist/assets/redshift-CMu3ubbS.js +7 -0
  208. package/frontend/dist/assets/restructuredtext-KNkiWGNN.js +7 -0
  209. package/frontend/dist/assets/ruby-dQWMhU86.js +7 -0
  210. package/frontend/dist/assets/rust-BAcHhnEU.js +7 -0
  211. package/frontend/dist/assets/sb-Bidr5w2z.js +7 -0
  212. package/frontend/dist/assets/scala-imTtxsGY.js +7 -0
  213. package/frontend/dist/assets/scheme-BY-WHKiB.js +7 -0
  214. package/frontend/dist/assets/scss-NDoRP52C.js +7 -0
  215. package/frontend/dist/assets/{select-C09dwvOR.js → select-B9gOzVx5.js} +1 -1
  216. package/frontend/dist/assets/shell-DrIjotnh.js +7 -0
  217. package/frontend/dist/assets/solidity-DTahNycF.js +7 -0
  218. package/frontend/dist/assets/sophia-DtyrjGkZ.js +7 -0
  219. package/frontend/dist/assets/sparql-CFuS5E3z.js +7 -0
  220. package/frontend/dist/assets/sql-BRxcnbRv.js +7 -0
  221. package/frontend/dist/assets/st-Dz4uh7MK.js +7 -0
  222. package/frontend/dist/assets/swift-Digsrw1G.js +11 -0
  223. package/frontend/dist/assets/systemverilog-7TDrkMau.js +7 -0
  224. package/frontend/dist/assets/tcl-BtaFcnDi.js +7 -0
  225. package/frontend/dist/assets/tsMode-UrEEwBkN.js +7 -0
  226. package/frontend/dist/assets/twig-Deid_oBs.js +7 -0
  227. package/frontend/dist/assets/typescript-BTfMCp5C.js +7 -0
  228. package/frontend/dist/assets/typespec-zUaxhbG8.js +7 -0
  229. package/frontend/dist/assets/{user-Bj8X-WCQ.js → user-BG-Xqf8M.js} +1 -1
  230. package/frontend/dist/assets/vb-DK0rKJzo.js +7 -0
  231. package/frontend/dist/assets/wgsl-B8fNVlOQ.js +7 -0
  232. package/frontend/dist/assets/xml-BCL5DPdB.js +7 -0
  233. package/frontend/dist/assets/yaml-B__nW1c2.js +7 -0
  234. package/frontend/dist/index.html +2 -2
  235. package/package.json +2 -1
  236. package/frontend/dist/assets/index-BkQ6KI1l.css +0 -1
@@ -0,0 +1,1221 @@
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.typeAnnot[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.typeAnnot[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
+ scalarType(ctx) {
537
+ const id = ctx.Identifier[0];
538
+ const constraint = ctx.typeConstraint
539
+ ? this.visit(ctx.typeConstraint[0])
540
+ : null;
541
+ const endRange = constraint?.range ?? tokenRange(id);
542
+ return {
543
+ kind: 'ScalarType',
544
+ name: id.image,
545
+ constraint,
546
+ range: spanFromTokenToRange(id, endRange),
547
+ };
548
+ }
549
+ enumType(ctx) {
550
+ const enumTok = ctx.KwEnum[0];
551
+ const variants = ctx.Identifier.map((t) => t.image);
552
+ const lastId = ctx.Identifier.at(-1);
553
+ return {
554
+ kind: 'EnumType',
555
+ variants,
556
+ range: spanTokens(enumTok, lastId),
557
+ };
558
+ }
559
+ arrayType(ctx) {
560
+ const arrTok = ctx.KwArray[0];
561
+ const element = this.visit(ctx.typeAnnot[0]);
562
+ const rangle = ctx.RAngle[0];
563
+ const constraint = ctx.namedConstraint
564
+ ? this.visit(ctx.namedConstraint[0])
565
+ : null;
566
+ const endRange = constraint?.range ?? tokenRange(rangle);
567
+ return {
568
+ kind: 'ArrayType',
569
+ element,
570
+ constraint,
571
+ range: spanFromTokenToRange(arrTok, endRange),
572
+ };
573
+ }
574
+ objectType(ctx) {
575
+ const objTok = ctx.KwObject[0];
576
+ const rcurly = ctx.RCurly[0];
577
+ const fields = (ctx.objectTypeField ?? []).map((c) => this.visit(c));
578
+ return {
579
+ kind: 'ObjectType',
580
+ fields,
581
+ range: spanTokens(objTok, rcurly),
582
+ };
583
+ }
584
+ objectTypeField(ctx) {
585
+ const id = ctx.Identifier[0];
586
+ const type = this.visit(ctx.typeAnnot[0]);
587
+ return {
588
+ kind: 'ObjectTypeField',
589
+ name: id.image,
590
+ type,
591
+ range: spanFromTokenToRange(id, type.range),
592
+ };
593
+ }
594
+ typeConstraint(ctx) {
595
+ if (ctx.rangeConstraint)
596
+ return this.visit(ctx.rangeConstraint[0]);
597
+ return this.visit(ctx.namedConstraint[0]);
598
+ }
599
+ rangeConstraint(ctx) {
600
+ const nums = ctx.numberLit.map((c) => this.visit(c));
601
+ return {
602
+ kind: 'RangeConstraint',
603
+ min: nums[0].value,
604
+ max: nums[1].value,
605
+ range: spanRanges(nums[0].range, nums[1].range),
606
+ };
607
+ }
608
+ namedConstraint(ctx) {
609
+ const key = ctx.Identifier[0];
610
+ let value;
611
+ let endRange;
612
+ if (ctx.numberLit) {
613
+ const n = this.visit(ctx.numberLit[0]);
614
+ value = n.value;
615
+ endRange = n.range;
616
+ }
617
+ else {
618
+ const sTok = ctx.StringLit[0];
619
+ value = unquoteString(sTok.image);
620
+ endRange = tokenRange(sTok);
621
+ }
622
+ return {
623
+ kind: 'NamedConstraint',
624
+ key: key.image,
625
+ value,
626
+ range: spanFromTokenToRange(key, endRange),
627
+ };
628
+ }
629
+ numberLit(ctx) {
630
+ if (ctx.IntLit) {
631
+ const tok = ctx.IntLit[0];
632
+ return { value: Number.parseInt(tok.image, 10), range: tokenRange(tok) };
633
+ }
634
+ const tok = ctx.FloatLit[0];
635
+ return { value: Number.parseFloat(tok.image), range: tokenRange(tok) };
636
+ }
637
+ // ─── Block / Statements ─────────────────────────────────────────────
638
+ block(ctx) {
639
+ const lcurly = ctx.LCurly[0];
640
+ const rcurly = ctx.RCurly[0];
641
+ const stmts = (ctx.stmt ?? []).map((c) => this.visit(c));
642
+ return { kind: 'Block', stmts, range: spanTokens(lcurly, rcurly) };
643
+ }
644
+ stmt(ctx) {
645
+ if (ctx.letDecl)
646
+ return this.visit(ctx.letDecl[0]);
647
+ if (ctx.ifStmt)
648
+ return this.visit(ctx.ifStmt[0]);
649
+ if (ctx.forStmt)
650
+ return this.visit(ctx.forStmt[0]);
651
+ if (ctx.whileStmt)
652
+ return this.visit(ctx.whileStmt[0]);
653
+ if (ctx.tryStmt)
654
+ return this.visit(ctx.tryStmt[0]);
655
+ if (ctx.breakStmt)
656
+ return this.visit(ctx.breakStmt[0]);
657
+ if (ctx.continueStmt)
658
+ return this.visit(ctx.continueStmt[0]);
659
+ if (ctx.returnStmt)
660
+ return this.visit(ctx.returnStmt[0]);
661
+ if (ctx.assignment)
662
+ return this.visit(ctx.assignment[0]);
663
+ if (ctx.exprStmt)
664
+ return this.visit(ctx.exprStmt[0]);
665
+ throw new Error('unreachable: stmt had no matching alternative');
666
+ }
667
+ letDecl(ctx) {
668
+ const letTok = ctx.Let[0];
669
+ const target = this.visit(ctx.letTarget[0]);
670
+ const type = ctx.typeAnnot
671
+ ? this.visit(ctx.typeAnnot[0])
672
+ : null;
673
+ const init = ctx.expr ? this.visit(ctx.expr[0]) : null;
674
+ const endRange = init?.range ?? type?.range ?? target.range;
675
+ return {
676
+ kind: 'LetDecl',
677
+ target,
678
+ type,
679
+ init,
680
+ range: spanFromTokenToRange(letTok, endRange),
681
+ };
682
+ }
683
+ letTarget(ctx) {
684
+ if (ctx.Identifier) {
685
+ const id = ctx.Identifier[0];
686
+ return { kind: 'IdentTarget', name: id.image, range: tokenRange(id) };
687
+ }
688
+ if (ctx.objectDestruct)
689
+ return this.visit(ctx.objectDestruct[0]);
690
+ return this.visit(ctx.arrayDestruct[0]);
691
+ }
692
+ objectDestruct(ctx) {
693
+ const lcurly = ctx.LCurly[0];
694
+ const rcurly = ctx.RCurly[0];
695
+ const fields = (ctx.destructField ?? []).map((c) => this.visit(c));
696
+ return {
697
+ kind: 'ObjectDestruct',
698
+ fields,
699
+ range: spanTokens(lcurly, rcurly),
700
+ };
701
+ }
702
+ destructField(ctx) {
703
+ const ids = ctx.Identifier;
704
+ const source = ids[0].image;
705
+ const local = ids.length > 1 ? ids[1].image : source;
706
+ return {
707
+ kind: 'DestructField',
708
+ source,
709
+ local,
710
+ range: spanTokens(ids[0], ids[ids.length - 1]),
711
+ };
712
+ }
713
+ arrayDestruct(ctx) {
714
+ const lbracket = ctx.LBracket[0];
715
+ const rbracket = ctx.RBracket[0];
716
+ const names = ctx.Identifier.map((t) => t.image);
717
+ return {
718
+ kind: 'ArrayDestruct',
719
+ names,
720
+ range: spanTokens(lbracket, rbracket),
721
+ };
722
+ }
723
+ assignment(ctx) {
724
+ const target = this.visit(ctx.lvalue[0]);
725
+ const op = this.visit(ctx.assignOp[0]);
726
+ const value = this.visit(ctx.expr[0]);
727
+ return {
728
+ kind: 'Assignment',
729
+ target,
730
+ op,
731
+ value,
732
+ range: spanRanges(target.range, value.range),
733
+ };
734
+ }
735
+ lvalue(ctx) {
736
+ const id = ctx.Identifier[0];
737
+ const suffixes = (ctx.lvalueSuffix ?? []).map((c) => this.visit(c));
738
+ const endRange = suffixes.length > 0 ? suffixes[suffixes.length - 1].range : tokenRange(id);
739
+ return {
740
+ kind: 'LValue',
741
+ base: id.image,
742
+ suffixes,
743
+ range: spanFromTokenToRange(id, endRange),
744
+ };
745
+ }
746
+ lvalueSuffix(ctx) {
747
+ if (ctx.Dot) {
748
+ const dot = ctx.Dot[0];
749
+ const id = ctx.Identifier[0];
750
+ return {
751
+ kind: 'MemberSuffix',
752
+ name: id.image,
753
+ range: spanTokens(dot, id),
754
+ };
755
+ }
756
+ const lbracket = ctx.LBracket[0];
757
+ const rbracket = ctx.RBracket[0];
758
+ const index = this.visit(ctx.expr[0]);
759
+ return {
760
+ kind: 'IndexSuffix',
761
+ index,
762
+ range: spanTokens(lbracket, rbracket),
763
+ };
764
+ }
765
+ assignOp(ctx) {
766
+ if (ctx.Equals)
767
+ return '=';
768
+ if (ctx.PlusEq)
769
+ return '+=';
770
+ if (ctx.MinusEq)
771
+ return '-=';
772
+ if (ctx.StarEq)
773
+ return '*=';
774
+ if (ctx.SlashEq)
775
+ return '/=';
776
+ return '%=';
777
+ }
778
+ ifStmt(ctx) {
779
+ const ifTok = ctx.If[0];
780
+ const exprs = ctx.expr;
781
+ const blocks = ctx.block;
782
+ const cond = this.visit(exprs[0]);
783
+ const then = this.visit(blocks[0]);
784
+ // remaining expr/block pairs (each "else if") + optional final else block
785
+ // grammar: ifStmt has: 1 cond expr + 1 then block + N elif-pairs (expr+block) + optional else block
786
+ const elifs = [];
787
+ for (let i = 1; i < exprs.length; i++) {
788
+ const eCond = this.visit(exprs[i]);
789
+ const eBody = this.visit(blocks[i]);
790
+ elifs.push({
791
+ kind: 'ElseIf',
792
+ cond: eCond,
793
+ body: eBody,
794
+ range: spanRanges(eCond.range, eBody.range),
795
+ });
796
+ }
797
+ // if there is a trailing else, its block is the last one in `blocks`
798
+ let otherwise = null;
799
+ if (blocks.length > exprs.length) {
800
+ otherwise = this.visit(blocks[blocks.length - 1]);
801
+ }
802
+ const endRange = otherwise?.range ?? elifs.at(-1)?.range ?? then.range;
803
+ return {
804
+ kind: 'IfStmt',
805
+ cond,
806
+ then,
807
+ elifs,
808
+ otherwise,
809
+ range: spanFromTokenToRange(ifTok, endRange),
810
+ };
811
+ }
812
+ forStmt(ctx) {
813
+ const forTok = ctx.For[0];
814
+ const binding = ctx.Identifier[0].image;
815
+ const iterable = this.visit(ctx.expr[0]);
816
+ const body = this.visit(ctx.block[0]);
817
+ return {
818
+ kind: 'ForStmt',
819
+ binding,
820
+ iterable,
821
+ body,
822
+ range: spanFromTokenToRange(forTok, body.range),
823
+ };
824
+ }
825
+ whileStmt(ctx) {
826
+ const whileTok = ctx.While[0];
827
+ const cond = this.visit(ctx.expr[0]);
828
+ const body = this.visit(ctx.block[0]);
829
+ return {
830
+ kind: 'WhileStmt',
831
+ cond,
832
+ body,
833
+ range: spanFromTokenToRange(whileTok, body.range),
834
+ };
835
+ }
836
+ tryStmt(ctx) {
837
+ const tryTok = ctx.Try[0];
838
+ const body = this.visit(ctx.block[0]);
839
+ const catches = ctx.catchClause.map((c) => this.visit(c));
840
+ const endRange = catches.at(-1)?.range ?? body.range;
841
+ return {
842
+ kind: 'TryStmt',
843
+ body,
844
+ catches,
845
+ range: spanFromTokenToRange(tryTok, endRange),
846
+ };
847
+ }
848
+ catchClause(ctx) {
849
+ const catchTok = ctx.Catch[0];
850
+ const ids = ctx.Identifier;
851
+ const errorType = ids[0].image;
852
+ const binding = ids.length > 1 ? ids[1].image : null;
853
+ const body = this.visit(ctx.block[0]);
854
+ return {
855
+ kind: 'CatchClause',
856
+ errorType,
857
+ binding,
858
+ body,
859
+ range: spanFromTokenToRange(catchTok, body.range),
860
+ };
861
+ }
862
+ breakStmt(ctx) {
863
+ const tok = ctx.Break[0];
864
+ return { kind: 'BreakStmt', range: tokenRange(tok) };
865
+ }
866
+ continueStmt(ctx) {
867
+ const tok = ctx.Continue[0];
868
+ return { kind: 'ContinueStmt', range: tokenRange(tok) };
869
+ }
870
+ returnStmt(ctx) {
871
+ const retTok = ctx.Return[0];
872
+ const value = ctx.expr ? this.visit(ctx.expr[0]) : null;
873
+ return {
874
+ kind: 'ReturnStmt',
875
+ value,
876
+ range: value ? spanFromTokenToRange(retTok, value.range) : tokenRange(retTok),
877
+ };
878
+ }
879
+ exprStmt(ctx) {
880
+ const expr = this.visit(ctx.expr[0]);
881
+ return { kind: 'ExprStmt', expr, range: expr.range };
882
+ }
883
+ // ─── Expressions ────────────────────────────────────────────────────
884
+ expr(ctx) {
885
+ return this.visit(ctx.ternaryExpr[0]);
886
+ }
887
+ ternaryExpr(ctx) {
888
+ const cond = this.visit(ctx.logicalOrExpr[0]);
889
+ if (!ctx.expr)
890
+ return cond;
891
+ const [thenCst, elseCst] = ctx.expr;
892
+ const thenExpr = this.visit(thenCst);
893
+ const elseExpr = this.visit(elseCst);
894
+ return {
895
+ kind: 'TernaryExpr',
896
+ cond,
897
+ then: thenExpr,
898
+ otherwise: elseExpr,
899
+ range: spanRanges(cond.range, elseExpr.range),
900
+ };
901
+ }
902
+ logicalOrExpr(ctx) {
903
+ return buildLeftAssocBinary(ctx.logicalAndExpr.map((c) => this.visit(c)), (ctx.OrOr ?? []).map(() => '||'));
904
+ }
905
+ logicalAndExpr(ctx) {
906
+ return buildLeftAssocBinary(ctx.equalityExpr.map((c) => this.visit(c)), (ctx.AndAnd ?? []).map(() => '&&'));
907
+ }
908
+ equalityExpr(ctx) {
909
+ const operands = ctx.comparisonExpr.map((c) => this.visit(c));
910
+ const ops = combineOps(ctx, [['EqEq', '=='], ['NotEq', '!=']]);
911
+ return buildLeftAssocBinary(operands, ops);
912
+ }
913
+ comparisonExpr(ctx) {
914
+ const operands = ctx.additiveExpr.map((c) => this.visit(c));
915
+ const ops = combineOps(ctx, [
916
+ ['LAngle', '<'],
917
+ ['LtEq', '<='],
918
+ ['RAngle', '>'],
919
+ ['GtEq', '>='],
920
+ ]);
921
+ return buildLeftAssocBinary(operands, ops);
922
+ }
923
+ additiveExpr(ctx) {
924
+ const operands = ctx.multiplicativeExpr.map((c) => this.visit(c));
925
+ const ops = combineOps(ctx, [['Plus', '+'], ['Dash', '-']]);
926
+ return buildLeftAssocBinary(operands, ops);
927
+ }
928
+ multiplicativeExpr(ctx) {
929
+ const operands = ctx.unaryExpr.map((c) => this.visit(c));
930
+ const ops = combineOps(ctx, [
931
+ ['Star', '*'],
932
+ ['Slash', '/'],
933
+ ['Percent', '%'],
934
+ ]);
935
+ return buildLeftAssocBinary(operands, ops);
936
+ }
937
+ unaryExpr(ctx) {
938
+ const operand = this.visit(ctx.postfixExpr[0]);
939
+ if (ctx.Dash) {
940
+ const dash = ctx.Dash[0];
941
+ return {
942
+ kind: 'UnaryExpr',
943
+ op: '-',
944
+ operand,
945
+ range: spanFromTokenToRange(dash, operand.range),
946
+ };
947
+ }
948
+ if (ctx.Bang) {
949
+ const bang = ctx.Bang[0];
950
+ return {
951
+ kind: 'UnaryExpr',
952
+ op: '!',
953
+ operand,
954
+ range: spanFromTokenToRange(bang, operand.range),
955
+ };
956
+ }
957
+ return operand;
958
+ }
959
+ postfixExpr(ctx) {
960
+ let current = this.visit(ctx.primaryExpr[0]);
961
+ const suffixes = (ctx.postfixSuffix ?? []);
962
+ for (const sufCst of suffixes) {
963
+ const suf = this.visit(sufCst);
964
+ if (suf.tag === 'member') {
965
+ current = {
966
+ kind: 'MemberExpr',
967
+ object: current,
968
+ property: suf.name,
969
+ range: spanRanges(current.range, suf.range),
970
+ };
971
+ }
972
+ else if (suf.tag === 'index') {
973
+ current = {
974
+ kind: 'IndexExpr',
975
+ object: current,
976
+ index: suf.index,
977
+ range: spanRanges(current.range, suf.range),
978
+ };
979
+ }
980
+ else {
981
+ current = {
982
+ kind: 'CallExpr',
983
+ callee: current,
984
+ args: suf.args,
985
+ range: spanRanges(current.range, suf.range),
986
+ };
987
+ }
988
+ }
989
+ return current;
990
+ }
991
+ postfixSuffix(ctx) {
992
+ if (ctx.Dot) {
993
+ const dot = ctx.Dot[0];
994
+ const id = ctx.Identifier[0];
995
+ return { tag: 'member', name: id.image, range: spanTokens(dot, id) };
996
+ }
997
+ if (ctx.LBracket) {
998
+ const lb = ctx.LBracket[0];
999
+ const rb = ctx.RBracket[0];
1000
+ const index = this.visit(ctx.expr[0]);
1001
+ return { tag: 'index', index, range: spanTokens(lb, rb) };
1002
+ }
1003
+ const lp = ctx.LParen[0];
1004
+ const rp = ctx.RParen[0];
1005
+ const args = ctx.argList
1006
+ ? this.visit(ctx.argList[0])
1007
+ : [];
1008
+ return { tag: 'call', args, range: spanTokens(lp, rp) };
1009
+ }
1010
+ argList(ctx) {
1011
+ return ctx.expr.map((c) => this.visit(c));
1012
+ }
1013
+ primaryExpr(ctx) {
1014
+ if (ctx.literal)
1015
+ return this.visit(ctx.literal[0]);
1016
+ if (ctx.Identifier) {
1017
+ const id = ctx.Identifier[0];
1018
+ return { kind: 'IdentExpr', name: id.image, range: tokenRange(id) };
1019
+ }
1020
+ if (ctx.arrayLit)
1021
+ return this.visit(ctx.arrayLit[0]);
1022
+ if (ctx.objectLit)
1023
+ return this.visit(ctx.objectLit[0]);
1024
+ // parenthesised
1025
+ return this.visit(ctx.expr[0]);
1026
+ }
1027
+ /**
1028
+ * Returns either a plain Literal or a TemplateString. Callers in
1029
+ * expression position accept Expr; callers expecting a strict Literal
1030
+ * (annotation args, type constraint values) should narrow by `kind`.
1031
+ */
1032
+ literal(ctx) {
1033
+ if (ctx.IntLit) {
1034
+ const tok = ctx.IntLit[0];
1035
+ return {
1036
+ kind: 'IntLit',
1037
+ value: Number.parseInt(tok.image, 10),
1038
+ range: tokenRange(tok),
1039
+ };
1040
+ }
1041
+ if (ctx.FloatLit) {
1042
+ const tok = ctx.FloatLit[0];
1043
+ return {
1044
+ kind: 'FloatLit',
1045
+ value: Number.parseFloat(tok.image),
1046
+ range: tokenRange(tok),
1047
+ };
1048
+ }
1049
+ if (ctx.StringLit) {
1050
+ const tok = ctx.StringLit[0];
1051
+ return buildStringExpr(tok);
1052
+ }
1053
+ if (ctx.True) {
1054
+ const tok = ctx.True[0];
1055
+ return { kind: 'BoolLit', value: true, range: tokenRange(tok) };
1056
+ }
1057
+ if (ctx.False) {
1058
+ const tok = ctx.False[0];
1059
+ return { kind: 'BoolLit', value: false, range: tokenRange(tok) };
1060
+ }
1061
+ const tok = ctx.Null[0];
1062
+ return { kind: 'NullLit', range: tokenRange(tok) };
1063
+ }
1064
+ arrayLit(ctx) {
1065
+ const lb = ctx.LBracket[0];
1066
+ const rb = ctx.RBracket[0];
1067
+ const elements = ctx.expr
1068
+ ? ctx.expr.map((c) => this.visit(c))
1069
+ : [];
1070
+ return { kind: 'ArrayLit', elements, range: spanTokens(lb, rb) };
1071
+ }
1072
+ objectLit(ctx) {
1073
+ const lc = ctx.LCurly[0];
1074
+ const rc = ctx.RCurly[0];
1075
+ const fields = (ctx.objectLitField ?? []).map((c) => this.visit(c));
1076
+ return { kind: 'ObjectLit', fields, range: spanTokens(lc, rc) };
1077
+ }
1078
+ objectLitField(ctx) {
1079
+ if (ctx.StringLit) {
1080
+ const keyTok = ctx.StringLit[0];
1081
+ const value = this.visit(ctx.expr[0]);
1082
+ return {
1083
+ kind: 'ObjectLitField',
1084
+ key: unquoteString(keyTok.image),
1085
+ shorthand: false,
1086
+ value,
1087
+ range: spanFromTokenToRange(keyTok, value.range),
1088
+ };
1089
+ }
1090
+ const idTok = ctx.Identifier[0];
1091
+ if (ctx.expr) {
1092
+ const value = this.visit(ctx.expr[0]);
1093
+ return {
1094
+ kind: 'ObjectLitField',
1095
+ key: idTok.image,
1096
+ shorthand: false,
1097
+ value,
1098
+ range: spanFromTokenToRange(idTok, value.range),
1099
+ };
1100
+ }
1101
+ // shorthand: { x } means { x: x }
1102
+ return {
1103
+ kind: 'ObjectLitField',
1104
+ key: idTok.image,
1105
+ shorthand: true,
1106
+ value: { kind: 'IdentExpr', name: idTok.image, range: tokenRange(idTok) },
1107
+ range: tokenRange(idTok),
1108
+ };
1109
+ }
1110
+ }
1111
+ // ─── Helpers used by visitor ──────────────────────────────────────────
1112
+ function spanTokens(start, end) {
1113
+ return {
1114
+ startLine: start.startLine ?? 0,
1115
+ startColumn: start.startColumn ?? 0,
1116
+ endLine: end.endLine ?? 0,
1117
+ endColumn: end.endColumn ?? 0,
1118
+ startOffset: start.startOffset,
1119
+ endOffset: end.endOffset ?? end.startOffset,
1120
+ };
1121
+ }
1122
+ function spanRanges(a, b) {
1123
+ return {
1124
+ startLine: a.startLine,
1125
+ startColumn: a.startColumn,
1126
+ endLine: b.endLine,
1127
+ endColumn: b.endColumn,
1128
+ startOffset: a.startOffset,
1129
+ endOffset: b.endOffset,
1130
+ };
1131
+ }
1132
+ function spanFromTokenToRange(start, end) {
1133
+ return {
1134
+ startLine: start.startLine ?? 0,
1135
+ startColumn: start.startColumn ?? 0,
1136
+ endLine: end.endLine,
1137
+ endColumn: end.endColumn,
1138
+ startOffset: start.startOffset,
1139
+ endOffset: end.endOffset,
1140
+ };
1141
+ }
1142
+ function emptyRange(items) {
1143
+ if (items.length === 0) {
1144
+ return {
1145
+ startLine: 0,
1146
+ startColumn: 0,
1147
+ endLine: 0,
1148
+ endColumn: 0,
1149
+ startOffset: 0,
1150
+ endOffset: 0,
1151
+ };
1152
+ }
1153
+ const first = items[0].range;
1154
+ const last = items[items.length - 1].range;
1155
+ return spanRanges(first, last);
1156
+ }
1157
+ function findFirstToken(node) {
1158
+ // CstNode children: { ruleName: CstNode[] | IToken[], ... }
1159
+ const children = node.children;
1160
+ let earliest;
1161
+ for (const key of Object.keys(children)) {
1162
+ const arr = children[key];
1163
+ for (const child of arr) {
1164
+ let tok;
1165
+ if ('image' in child) {
1166
+ tok = child;
1167
+ }
1168
+ else {
1169
+ tok = findFirstToken(child);
1170
+ }
1171
+ if (tok && (!earliest || tok.startOffset < earliest.startOffset)) {
1172
+ earliest = tok;
1173
+ }
1174
+ }
1175
+ }
1176
+ return earliest;
1177
+ }
1178
+ /**
1179
+ * Build a left-associative binary expression chain from a list of
1180
+ * operands and a parallel list of operators. operators[i] joins
1181
+ * operands[i] and operands[i+1].
1182
+ */
1183
+ function buildLeftAssocBinary(operands, operators) {
1184
+ let current = operands[0];
1185
+ for (let i = 0; i < operators.length; i++) {
1186
+ const right = operands[i + 1];
1187
+ current = {
1188
+ kind: 'BinaryExpr',
1189
+ op: operators[i],
1190
+ left: current,
1191
+ right,
1192
+ range: spanRanges(current.range, right.range),
1193
+ };
1194
+ }
1195
+ return current;
1196
+ }
1197
+ /**
1198
+ * For rules where multiple alternative token names can appear in MANY,
1199
+ * gather all operator tokens in source order and map them to AST op strings.
1200
+ */
1201
+ function combineOps(ctx, mapping) {
1202
+ const all = [];
1203
+ for (const [tokName, op] of mapping) {
1204
+ const toks = ctx[tokName] ?? [];
1205
+ for (const t of toks)
1206
+ all.push({ offset: t.startOffset, op });
1207
+ }
1208
+ all.sort((a, b) => a.offset - b.offset);
1209
+ return all.map((x) => x.op);
1210
+ }
1211
+ // ─── Public API ───────────────────────────────────────────────────────
1212
+ const astBuilder = new TrainAstBuilder();
1213
+ /**
1214
+ * Build a typed AST from a parser CST. Returns null if no CST (parse failed).
1215
+ */
1216
+ export function buildAst(cst) {
1217
+ if (!cst)
1218
+ return null;
1219
+ return astBuilder.visit(cst);
1220
+ }
1221
+ //# sourceMappingURL=builder.js.map