@vibe-lang/runtime 0.2.5

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 (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,794 @@
1
+ import type { CstNode, IToken } from 'chevrotain';
2
+ import { vibeParser } from './index';
3
+ import * as AST from '../ast';
4
+ import type { SourceLocation } from '../errors';
5
+ import {
6
+ tokenLocation,
7
+ parseStringLiteral,
8
+ getFirstToken,
9
+ buildBinaryChain,
10
+ buildMixedBinaryChain,
11
+ buildSingleBinary,
12
+ makeStringLiteral,
13
+ makeTemplateLiteral,
14
+ makeNumberLiteral,
15
+ makeBooleanLiteral,
16
+ makeNullLiteral,
17
+ makeIdentifier,
18
+ makeTsBlock,
19
+ makeCallExpression,
20
+ makeIndexExpression,
21
+ makeSliceExpression,
22
+ makeMemberExpression,
23
+ makeVibeExpression,
24
+ makeContextSpecifier,
25
+ } from './visitor/helpers';
26
+
27
+ // Get the base visitor class from the parser
28
+ const BaseVibeVisitor = vibeParser.getBaseCstVisitorConstructor();
29
+
30
+ class VibeAstVisitor extends BaseVibeVisitor {
31
+ constructor() {
32
+ super();
33
+ this.validateVisitor();
34
+ }
35
+
36
+ // ============================================================================
37
+ // Program
38
+ // ============================================================================
39
+
40
+ program(ctx: { statement?: CstNode[] }): AST.Program {
41
+ const statements = ctx.statement?.map((s) => this.visit(s)) ?? [];
42
+ return {
43
+ type: 'Program',
44
+ body: statements,
45
+ location: statements[0]?.location ?? { line: 1, column: 1 },
46
+ };
47
+ }
48
+
49
+ // ============================================================================
50
+ // Statements
51
+ // ============================================================================
52
+
53
+ statement(ctx: Record<string, CstNode[]>): AST.Statement {
54
+ if (ctx.importDeclaration) return this.visit(ctx.importDeclaration);
55
+ if (ctx.exportDeclaration) return this.visit(ctx.exportDeclaration);
56
+ if (ctx.asyncStatement) return this.visit(ctx.asyncStatement);
57
+ if (ctx.letDeclaration) return this.visit(ctx.letDeclaration);
58
+ if (ctx.constDeclaration) return this.visit(ctx.constDeclaration);
59
+ if (ctx.modelDeclaration) return this.visit(ctx.modelDeclaration);
60
+ if (ctx.functionDeclaration) return this.visit(ctx.functionDeclaration);
61
+ if (ctx.toolDeclaration) return this.visit(ctx.toolDeclaration);
62
+ if (ctx.returnStatement) return this.visit(ctx.returnStatement);
63
+ if (ctx.breakStatement) return this.visit(ctx.breakStatement);
64
+ if (ctx.ifStatement) return this.visit(ctx.ifStatement);
65
+ if (ctx.forInStatement) return this.visit(ctx.forInStatement);
66
+ if (ctx.whileStatement) return this.visit(ctx.whileStatement);
67
+ if (ctx.blockStatement) return this.visit(ctx.blockStatement);
68
+ if (ctx.expressionStatement) return this.visit(ctx.expressionStatement);
69
+ throw new Error('Unknown statement type');
70
+ }
71
+
72
+ importDeclaration(ctx: { Import: IToken[]; importSpecifierList: CstNode[]; StringLiteral: IToken[] }): AST.ImportDeclaration {
73
+ const specifiers = this.visit(ctx.importSpecifierList) as AST.ImportSpecifier[];
74
+ const sourcePath = parseStringLiteral(ctx.StringLiteral[0]);
75
+ const sourceType = sourcePath.endsWith('.vibe') ? 'vibe' : 'ts';
76
+
77
+ return {
78
+ type: 'ImportDeclaration',
79
+ specifiers,
80
+ source: sourcePath,
81
+ sourceType,
82
+ location: tokenLocation(ctx.Import[0]),
83
+ };
84
+ }
85
+
86
+ importSpecifierList(ctx: { Identifier: IToken[] }): AST.ImportSpecifier[] {
87
+ return ctx.Identifier.map((token) => ({
88
+ imported: token.image,
89
+ local: token.image, // Same name for now (no "as" support)
90
+ }));
91
+ }
92
+
93
+ exportDeclaration(ctx: { Export: IToken[]; functionDeclaration?: CstNode[]; letDeclaration?: CstNode[]; constDeclaration?: CstNode[]; modelDeclaration?: CstNode[] }): AST.ExportDeclaration {
94
+ const decl = ctx.functionDeclaration ?? ctx.letDeclaration ?? ctx.constDeclaration ?? ctx.modelDeclaration;
95
+ if (!decl) throw new Error('Unknown export declaration type');
96
+ return { type: 'ExportDeclaration', declaration: this.visit(decl), location: tokenLocation(ctx.Export[0]) };
97
+ }
98
+
99
+ asyncStatement(ctx: { Async: IToken[]; asyncLetDeclaration?: CstNode[]; asyncConstDeclaration?: CstNode[]; asyncExpression?: CstNode[] }): AST.Statement {
100
+ if (ctx.asyncLetDeclaration) {
101
+ return this.visit(ctx.asyncLetDeclaration);
102
+ }
103
+ if (ctx.asyncConstDeclaration) {
104
+ return this.visit(ctx.asyncConstDeclaration);
105
+ }
106
+ // Standalone async expression (fire-and-forget)
107
+ const expr = this.visit(ctx.asyncExpression!) as AST.VibeExpression | AST.TsBlock | AST.CallExpression;
108
+ return {
109
+ type: 'AsyncStatement',
110
+ expression: expr,
111
+ location: tokenLocation(ctx.Async[0]),
112
+ };
113
+ }
114
+
115
+ asyncLetDeclaration(ctx: { Let: IToken[]; Private?: IToken[]; Identifier?: IToken[]; typeAnnotation?: CstNode[]; asyncExpression?: CstNode[]; destructuringPattern?: CstNode[] }): AST.LetDeclaration | AST.DestructuringDeclaration {
116
+ // Check for destructuring pattern
117
+ if (ctx.destructuringPattern) {
118
+ const fields = this.visit(ctx.destructuringPattern) as AST.DestructuringField[];
119
+ return {
120
+ type: 'DestructuringDeclaration',
121
+ fields,
122
+ initializer: this.visit(ctx.asyncExpression!),
123
+ isConst: false,
124
+ isAsync: true,
125
+ location: tokenLocation(ctx.Let[0]),
126
+ };
127
+ }
128
+
129
+ // Regular let declaration
130
+ const typeAnnotation = ctx.typeAnnotation ? this.visit(ctx.typeAnnotation) : null;
131
+ const isPrivate = ctx.Private !== undefined;
132
+ const node: AST.LetDeclaration = {
133
+ type: 'LetDeclaration',
134
+ name: ctx.Identifier![0].image,
135
+ typeAnnotation,
136
+ initializer: this.visit(ctx.asyncExpression!),
137
+ isAsync: true,
138
+ location: tokenLocation(ctx.Let[0]),
139
+ };
140
+ if (isPrivate) {
141
+ node.isPrivate = true;
142
+ }
143
+ return node;
144
+ }
145
+
146
+ asyncConstDeclaration(ctx: { Const: IToken[]; Private?: IToken[]; Identifier?: IToken[]; typeAnnotation?: CstNode[]; asyncExpression: CstNode[]; destructuringPattern?: CstNode[] }): AST.ConstDeclaration | AST.DestructuringDeclaration {
147
+ // Check for destructuring pattern
148
+ if (ctx.destructuringPattern) {
149
+ const fields = this.visit(ctx.destructuringPattern) as AST.DestructuringField[];
150
+ return {
151
+ type: 'DestructuringDeclaration',
152
+ fields,
153
+ initializer: this.visit(ctx.asyncExpression),
154
+ isConst: true,
155
+ isAsync: true,
156
+ location: tokenLocation(ctx.Const[0]),
157
+ };
158
+ }
159
+
160
+ // Regular const declaration
161
+ const typeAnnotation = ctx.typeAnnotation ? this.visit(ctx.typeAnnotation) : null;
162
+ const isPrivate = ctx.Private !== undefined;
163
+ const node: AST.ConstDeclaration = {
164
+ type: 'ConstDeclaration',
165
+ name: ctx.Identifier![0].image,
166
+ typeAnnotation,
167
+ initializer: this.visit(ctx.asyncExpression),
168
+ isAsync: true,
169
+ location: tokenLocation(ctx.Const[0]),
170
+ };
171
+ if (isPrivate) {
172
+ node.isPrivate = true;
173
+ }
174
+ return node;
175
+ }
176
+
177
+ asyncExpression(ctx: { Vibe?: IToken[]; Do?: IToken[]; TsBlock?: IToken[]; expression?: CstNode[]; vibeModifiers?: CstNode[]; postfixExpression?: CstNode[] }): AST.Expression {
178
+ if (ctx.Vibe) {
179
+ const prompt = this.visit(ctx.expression![0]);
180
+ const modifiers = this.visit(ctx.vibeModifiers![0]) as { model: AST.Expression | null; context: AST.ContextSpecifier | null };
181
+ return makeVibeExpression(ctx.Vibe[0], prompt, modifiers.model, modifiers.context, 'vibe');
182
+ }
183
+ if (ctx.Do) {
184
+ const prompt = this.visit(ctx.expression![0]);
185
+ const modifiers = this.visit(ctx.vibeModifiers![0]) as { model: AST.Expression | null; context: AST.ContextSpecifier | null };
186
+ return makeVibeExpression(ctx.Do[0], prompt, modifiers.model, modifiers.context, 'do');
187
+ }
188
+ if (ctx.TsBlock) {
189
+ return makeTsBlock(ctx.TsBlock[0]);
190
+ }
191
+ if (ctx.postfixExpression) {
192
+ return this.visit(ctx.postfixExpression);
193
+ }
194
+ throw new Error('Unknown async expression type');
195
+ }
196
+
197
+ letDeclaration(ctx: { Let: IToken[]; Private?: IToken[]; Identifier?: IToken[]; typeAnnotation?: CstNode[]; expression?: CstNode[]; destructuringPattern?: CstNode[] }): AST.LetDeclaration | AST.DestructuringDeclaration {
198
+ // Check for destructuring pattern
199
+ if (ctx.destructuringPattern) {
200
+ const fields = this.visit(ctx.destructuringPattern) as AST.DestructuringField[];
201
+ return {
202
+ type: 'DestructuringDeclaration',
203
+ fields,
204
+ initializer: this.visit(ctx.expression!),
205
+ isConst: false,
206
+ location: tokenLocation(ctx.Let[0]),
207
+ };
208
+ }
209
+
210
+ // Regular let declaration
211
+ const typeAnnotation = ctx.typeAnnotation ? this.visit(ctx.typeAnnotation) : null;
212
+ const isPrivate = ctx.Private !== undefined;
213
+ const node: AST.LetDeclaration = {
214
+ type: 'LetDeclaration',
215
+ name: ctx.Identifier![0].image,
216
+ typeAnnotation,
217
+ initializer: ctx.expression ? this.visit(ctx.expression) : null,
218
+ location: tokenLocation(ctx.Let[0]),
219
+ };
220
+ if (isPrivate) {
221
+ node.isPrivate = true;
222
+ }
223
+ return node;
224
+ }
225
+
226
+ typeAnnotation(ctx: { TextType?: IToken[]; JsonType?: IToken[]; PromptType?: IToken[]; BooleanType?: IToken[]; NumberType?: IToken[]; Model?: IToken[]; LBracket?: IToken[] }): string {
227
+ // Get base type
228
+ const baseType = ctx.TextType ? 'text' : ctx.JsonType ? 'json' : ctx.PromptType ? 'prompt' : ctx.BooleanType ? 'boolean' : ctx.NumberType ? 'number' : 'model';
229
+ // Count array brackets
230
+ const bracketCount = ctx.LBracket?.length ?? 0;
231
+ return baseType + '[]'.repeat(bracketCount);
232
+ }
233
+
234
+ constDeclaration(ctx: { Const: IToken[]; Private?: IToken[]; Identifier?: IToken[]; typeAnnotation?: CstNode[]; expression: CstNode[]; destructuringPattern?: CstNode[] }): AST.ConstDeclaration | AST.DestructuringDeclaration {
235
+ // Check for destructuring pattern
236
+ if (ctx.destructuringPattern) {
237
+ const fields = this.visit(ctx.destructuringPattern) as AST.DestructuringField[];
238
+ return {
239
+ type: 'DestructuringDeclaration',
240
+ fields,
241
+ initializer: this.visit(ctx.expression),
242
+ isConst: true,
243
+ location: tokenLocation(ctx.Const[0]),
244
+ };
245
+ }
246
+
247
+ // Regular const declaration
248
+ const typeAnnotation = ctx.typeAnnotation ? this.visit(ctx.typeAnnotation) : null;
249
+ const isPrivate = ctx.Private !== undefined;
250
+ const node: AST.ConstDeclaration = {
251
+ type: 'ConstDeclaration',
252
+ name: ctx.Identifier![0].image,
253
+ typeAnnotation,
254
+ initializer: this.visit(ctx.expression),
255
+ location: tokenLocation(ctx.Const[0]),
256
+ };
257
+ if (isPrivate) {
258
+ node.isPrivate = true;
259
+ }
260
+ return node;
261
+ }
262
+
263
+ destructuringPattern(ctx: { destructuringField: CstNode[] }): AST.DestructuringField[] {
264
+ return ctx.destructuringField.map((f) => this.visit(f));
265
+ }
266
+
267
+ destructuringField(ctx: { Private?: IToken[]; Identifier: IToken[]; typeAnnotation: CstNode[] }): AST.DestructuringField {
268
+ const isPrivate = ctx.Private !== undefined;
269
+ const field: AST.DestructuringField = {
270
+ name: ctx.Identifier[0].image,
271
+ type: this.visit(ctx.typeAnnotation),
272
+ };
273
+ if (isPrivate) {
274
+ field.isPrivate = true;
275
+ }
276
+ return field;
277
+ }
278
+
279
+ modelDeclaration(ctx: { Model: IToken[]; Identifier: IToken[]; objectLiteral: CstNode[] }): AST.ModelDeclaration {
280
+ const { properties, location } = this.visit(ctx.objectLiteral) as { properties: Map<string, { value: AST.Expression; location: SourceLocation }>; location: SourceLocation };
281
+ return {
282
+ type: 'ModelDeclaration',
283
+ name: ctx.Identifier[0].image,
284
+ config: {
285
+ type: 'ModelConfig',
286
+ modelName: properties.get('name')?.value ?? null,
287
+ apiKey: properties.get('apiKey')?.value ?? null,
288
+ url: properties.get('url')?.value ?? null,
289
+ provider: properties.get('provider')?.value ?? null,
290
+ maxRetriesOnError: properties.get('maxRetriesOnError')?.value ?? null,
291
+ thinkingLevel: properties.get('thinkingLevel')?.value ?? null,
292
+ tools: properties.get('tools')?.value ?? null,
293
+ providedFields: [...properties.keys()],
294
+ location,
295
+ },
296
+ location: tokenLocation(ctx.Model[0]),
297
+ };
298
+ }
299
+
300
+ objectLiteral(ctx: { LBrace: IToken[]; propertyList?: CstNode[] }): { properties: Map<string, { value: AST.Expression; location: SourceLocation }>; location: SourceLocation } {
301
+ const props = ctx.propertyList ? this.visit(ctx.propertyList) as AST.ObjectProperty[] : [];
302
+ const properties = new Map(props.map((p) => [p.key, { value: p.value, location: p.location }]));
303
+ return { properties, location: tokenLocation(ctx.LBrace[0]) };
304
+ }
305
+
306
+ propertyList(ctx: { property: CstNode[] }): AST.ObjectProperty[] {
307
+ return ctx.property.map((p) => this.visit(p));
308
+ }
309
+
310
+ property(ctx: { Identifier: IToken[]; expression: CstNode[] }): AST.ObjectProperty {
311
+ return { type: 'ObjectProperty', key: ctx.Identifier[0].image, value: this.visit(ctx.expression), location: tokenLocation(ctx.Identifier[0]) };
312
+ }
313
+
314
+ functionDeclaration(ctx: { Function: IToken[]; Identifier: IToken[]; parameterList?: CstNode[]; typeAnnotation?: CstNode[]; blockStatement: CstNode[] }): AST.FunctionDeclaration {
315
+ return {
316
+ type: 'FunctionDeclaration', name: ctx.Identifier[0].image,
317
+ params: ctx.parameterList ? this.visit(ctx.parameterList) : [],
318
+ returnType: ctx.typeAnnotation ? this.visit(ctx.typeAnnotation) : null,
319
+ body: this.visit(ctx.blockStatement), location: tokenLocation(ctx.Function[0]),
320
+ };
321
+ }
322
+
323
+ toolDeclaration(ctx: { Tool: IToken[]; Identifier: IToken[]; toolParameterList?: CstNode[]; toolTypeAnnotation?: CstNode[]; toolMetadata?: CstNode[]; blockStatement: CstNode[] }): AST.ToolDeclaration {
324
+ // Build param descriptions map from @param metadata
325
+ const paramDescriptions: Record<string, string> = {};
326
+ const paramDecorators: string[] = []; // Track @param names for validation
327
+ let description: string | undefined;
328
+
329
+ if (ctx.toolMetadata) {
330
+ for (const metadata of ctx.toolMetadata) {
331
+ const result = this.visit(metadata) as { type: 'description'; text: string } | { type: 'param'; name: string; text: string };
332
+ if (result.type === 'description') {
333
+ description = result.text;
334
+ } else {
335
+ paramDescriptions[result.name] = result.text;
336
+ paramDecorators.push(result.name);
337
+ }
338
+ }
339
+ }
340
+
341
+ // Get parameters and attach descriptions
342
+ const params: AST.ToolParameter[] = ctx.toolParameterList
343
+ ? (this.visit(ctx.toolParameterList) as AST.ToolParameter[]).map((p) => ({
344
+ ...p,
345
+ description: paramDescriptions[p.name],
346
+ }))
347
+ : [];
348
+
349
+ const node: AST.ToolDeclaration = {
350
+ type: 'ToolDeclaration',
351
+ name: ctx.Identifier[0].image,
352
+ params,
353
+ returnType: ctx.toolTypeAnnotation ? this.visit(ctx.toolTypeAnnotation) : null,
354
+ description,
355
+ body: this.visit(ctx.blockStatement),
356
+ location: tokenLocation(ctx.Tool[0]),
357
+ };
358
+
359
+ // Only include paramDecorators if there are any (for validation)
360
+ if (paramDecorators.length > 0) {
361
+ node.paramDecorators = paramDecorators;
362
+ }
363
+
364
+ return node;
365
+ }
366
+
367
+ toolTypeAnnotation(ctx: { TextType?: IToken[]; JsonType?: IToken[]; PromptType?: IToken[]; BooleanType?: IToken[]; NumberType?: IToken[]; Identifier?: IToken[]; LBracket?: IToken[] }): string {
368
+ // Get base type - could be built-in or imported type name
369
+ const baseType = ctx.TextType ? 'text'
370
+ : ctx.JsonType ? 'json'
371
+ : ctx.PromptType ? 'prompt'
372
+ : ctx.BooleanType ? 'boolean'
373
+ : ctx.NumberType ? 'number'
374
+ : ctx.Identifier![0].image; // Imported TS type
375
+ // Count array brackets
376
+ const bracketCount = ctx.LBracket?.length ?? 0;
377
+ return baseType + '[]'.repeat(bracketCount);
378
+ }
379
+
380
+ toolMetadata(ctx: { AtDescription?: IToken[]; AtParam?: IToken[]; Identifier?: IToken[]; StringLiteral: IToken[] }): { type: 'description'; text: string } | { type: 'param'; name: string; text: string } {
381
+ if (ctx.AtDescription) {
382
+ return { type: 'description', text: parseStringLiteral(ctx.StringLiteral[0]) };
383
+ }
384
+ // @param name "text"
385
+ return { type: 'param', name: ctx.Identifier![0].image, text: parseStringLiteral(ctx.StringLiteral[0]) };
386
+ }
387
+
388
+ toolParameter(ctx: { Identifier: IToken[]; toolTypeAnnotation: CstNode[] }): AST.ToolParameter {
389
+ return { name: ctx.Identifier[0].image, typeAnnotation: this.visit(ctx.toolTypeAnnotation) };
390
+ }
391
+
392
+ toolParameterList(ctx: { toolParameter: CstNode[] }): AST.ToolParameter[] {
393
+ return ctx.toolParameter.map((p) => this.visit(p));
394
+ }
395
+
396
+ parameter(ctx: { Identifier: IToken[]; typeAnnotation: CstNode[] }): AST.FunctionParameter {
397
+ return { name: ctx.Identifier[0].image, typeAnnotation: this.visit(ctx.typeAnnotation) };
398
+ }
399
+
400
+ parameterList(ctx: { parameter: CstNode[] }): AST.FunctionParameter[] {
401
+ return ctx.parameter.map((p) => this.visit(p));
402
+ }
403
+
404
+ returnStatement(ctx: { Return: IToken[]; expression?: CstNode[] }): AST.ReturnStatement {
405
+ return { type: 'ReturnStatement', value: ctx.expression ? this.visit(ctx.expression) : null, location: tokenLocation(ctx.Return[0]) };
406
+ }
407
+
408
+ breakStatement(ctx: { Break: IToken[] }): AST.BreakStatement {
409
+ return { type: 'BreakStatement', location: tokenLocation(ctx.Break[0]) };
410
+ }
411
+
412
+ ifStatement(ctx: { If: IToken[]; expression: CstNode[]; blockStatement: CstNode[]; ifStatement?: CstNode[] }): AST.IfStatement {
413
+ const alternate = ctx.ifStatement ? this.visit(ctx.ifStatement) : ctx.blockStatement.length > 1 ? this.visit(ctx.blockStatement[1]) : null;
414
+ return { type: 'IfStatement', condition: this.visit(ctx.expression), consequent: this.visit(ctx.blockStatement[0]), alternate, location: tokenLocation(ctx.If[0]) };
415
+ }
416
+
417
+ forInStatement(ctx: { For: IToken[]; Identifier: IToken[]; expression: CstNode[]; blockStatement: CstNode[]; contextMode?: CstNode[] }): AST.ForInStatement {
418
+ return {
419
+ type: 'ForInStatement',
420
+ variable: ctx.Identifier[0].image,
421
+ iterable: this.visit(ctx.expression),
422
+ body: this.visit(ctx.blockStatement),
423
+ location: tokenLocation(ctx.For[0]),
424
+ contextMode: ctx.contextMode ? this.visit(ctx.contextMode) : 'verbose', // Default to verbose
425
+ };
426
+ }
427
+
428
+ whileStatement(ctx: { While: IToken[]; expression: CstNode[]; blockStatement: CstNode[]; contextMode?: CstNode[] }): AST.WhileStatement {
429
+ return {
430
+ type: 'WhileStatement',
431
+ condition: this.visit(ctx.expression),
432
+ body: this.visit(ctx.blockStatement),
433
+ location: tokenLocation(ctx.While[0]),
434
+ contextMode: ctx.contextMode ? this.visit(ctx.contextMode) : 'verbose', // Default to verbose
435
+ };
436
+ }
437
+
438
+ contextMode(ctx: { Forget?: IToken[]; Verbose?: IToken[]; Compress?: IToken[]; StringLiteral?: IToken[]; Identifier?: IToken[] }): AST.ContextMode {
439
+ if (ctx.Forget) return 'forget';
440
+ if (ctx.Verbose) return 'verbose';
441
+ // Compress with optional args: compress, compress(arg1), compress(arg1, arg2)
442
+ // arg1 can be string literal or identifier (prompt or model - resolved at semantic analysis)
443
+ // arg2 is always identifier (model)
444
+ let arg1: AST.CompressArg | null = null;
445
+ let arg2: AST.CompressArg | null = null;
446
+
447
+ if (ctx.StringLiteral) {
448
+ // First arg is string literal
449
+ arg1 = { kind: 'literal', value: parseStringLiteral(ctx.StringLiteral[0]) };
450
+ // Second arg (if present) is identifier
451
+ if (ctx.Identifier?.[0]) {
452
+ arg2 = { kind: 'identifier', name: ctx.Identifier[0].image };
453
+ }
454
+ } else if (ctx.Identifier) {
455
+ // First arg is identifier
456
+ arg1 = { kind: 'identifier', name: ctx.Identifier[0].image };
457
+ // Second arg (if present) is also identifier
458
+ if (ctx.Identifier[1]) {
459
+ arg2 = { kind: 'identifier', name: ctx.Identifier[1].image };
460
+ }
461
+ }
462
+
463
+ return { compress: { arg1, arg2 } };
464
+ }
465
+
466
+ blockStatement(ctx: { LBrace: IToken[]; statement?: CstNode[] }): AST.BlockStatement {
467
+ return { type: 'BlockStatement', body: ctx.statement?.map((s) => this.visit(s)) ?? [], location: tokenLocation(ctx.LBrace[0]) };
468
+ }
469
+
470
+ expressionStatement(ctx: { expression: CstNode[] }): AST.ExpressionStatement {
471
+ const expr = this.visit(ctx.expression);
472
+ return {
473
+ type: 'ExpressionStatement',
474
+ expression: expr,
475
+ location: expr.location,
476
+ };
477
+ }
478
+
479
+ // ============================================================================
480
+ // Expressions
481
+ // ============================================================================
482
+
483
+ expression(ctx: { Vibe?: IToken[]; Do?: IToken[]; assignmentExpression?: CstNode[]; orExpression?: CstNode[]; expression?: CstNode[]; vibeModifiers?: CstNode[] }): AST.Expression {
484
+ if (ctx.Vibe) {
485
+ const prompt = this.visit(ctx.expression![0]);
486
+ const modifiers = this.visit(ctx.vibeModifiers![0]) as { model: AST.Expression | null; context: AST.ContextSpecifier | null };
487
+ return makeVibeExpression(ctx.Vibe[0], prompt, modifiers.model, modifiers.context, 'vibe');
488
+ }
489
+ if (ctx.Do) {
490
+ const prompt = this.visit(ctx.expression![0]);
491
+ const modifiers = this.visit(ctx.vibeModifiers![0]) as { model: AST.Expression | null; context: AST.ContextSpecifier | null };
492
+ return makeVibeExpression(ctx.Do[0], prompt, modifiers.model, modifiers.context, 'do');
493
+ }
494
+ if (ctx.assignmentExpression) return this.visit(ctx.assignmentExpression);
495
+ return this.visit(ctx.orExpression!);
496
+ }
497
+
498
+ // Parse optional model and context modifiers for vibe/do
499
+ vibeModifiers(ctx: { Identifier?: IToken[]; contextSpecifier?: CstNode[] }): { model: AST.Expression | null; context: AST.ContextSpecifier | null } {
500
+ // No modifiers at all
501
+ if (!ctx.Identifier && !ctx.contextSpecifier) {
502
+ return { model: null, context: null };
503
+ }
504
+
505
+ // Context keyword only (default/local), no model
506
+ if (!ctx.Identifier && ctx.contextSpecifier) {
507
+ return { model: null, context: this.visit(ctx.contextSpecifier[0]) };
508
+ }
509
+
510
+ // Model identifier present
511
+ const modelToken = ctx.Identifier![0];
512
+ const model: AST.Identifier = {
513
+ type: 'Identifier',
514
+ name: modelToken.image,
515
+ location: tokenLocation(modelToken),
516
+ };
517
+
518
+ // Model with optional context
519
+ const context = ctx.contextSpecifier ? this.visit(ctx.contextSpecifier[0]) : null;
520
+ return { model, context };
521
+ }
522
+
523
+ // Or: or
524
+ orExpression(ctx: { andExpression: CstNode[]; Or?: IToken[] }): AST.Expression {
525
+ const operands = ctx.andExpression.map((n) => this.visit(n));
526
+ return buildBinaryChain(operands, 'or', ctx.Or?.length ?? 0);
527
+ }
528
+
529
+ // And: and
530
+ andExpression(ctx: { comparisonExpression: CstNode[]; And?: IToken[] }): AST.Expression {
531
+ const operands = ctx.comparisonExpression.map((n) => this.visit(n));
532
+ return buildBinaryChain(operands, 'and', ctx.And?.length ?? 0);
533
+ }
534
+
535
+ // Comparison: == != < > <= >=
536
+ comparisonExpression(ctx: {
537
+ additiveExpression: CstNode[];
538
+ EqualEqual?: IToken[];
539
+ NotEqual?: IToken[];
540
+ LessThan?: IToken[];
541
+ GreaterThan?: IToken[];
542
+ LessEqual?: IToken[];
543
+ GreaterEqual?: IToken[];
544
+ }): AST.Expression {
545
+ const left = this.visit(ctx.additiveExpression[0]);
546
+ const operators = [
547
+ ...(ctx.EqualEqual ?? []),
548
+ ...(ctx.NotEqual ?? []),
549
+ ...(ctx.LessThan ?? []),
550
+ ...(ctx.GreaterThan ?? []),
551
+ ...(ctx.LessEqual ?? []),
552
+ ...(ctx.GreaterEqual ?? []),
553
+ ];
554
+
555
+ if (operators.length === 0) return left;
556
+ return buildSingleBinary(left, this.visit(ctx.additiveExpression[1]), operators[0]);
557
+ }
558
+
559
+ // Additive: + -
560
+ additiveExpression(ctx: {
561
+ multiplicativeExpression: CstNode[];
562
+ Plus?: IToken[];
563
+ Minus?: IToken[];
564
+ }): AST.Expression {
565
+ const operands = ctx.multiplicativeExpression.map((n) => this.visit(n));
566
+ const operators = [...(ctx.Plus ?? []), ...(ctx.Minus ?? [])];
567
+ return buildMixedBinaryChain(operands, operators);
568
+ }
569
+
570
+ // Multiplicative: * / %
571
+ multiplicativeExpression(ctx: {
572
+ unaryExpression: CstNode[];
573
+ Star?: IToken[];
574
+ Slash?: IToken[];
575
+ Percent?: IToken[];
576
+ }): AST.Expression {
577
+ const operands = ctx.unaryExpression.map((n) => this.visit(n));
578
+ const operators = [...(ctx.Star ?? []), ...(ctx.Slash ?? []), ...(ctx.Percent ?? [])];
579
+ return buildMixedBinaryChain(operands, operators);
580
+ }
581
+
582
+ // Unary: not, -
583
+ unaryExpression(ctx: { Not?: IToken[]; Minus?: IToken[]; unaryExpression?: CstNode[]; rangeExpression?: CstNode[] }): AST.Expression {
584
+ if (ctx.Not) {
585
+ const operand = this.visit(ctx.unaryExpression!);
586
+ return {
587
+ type: 'UnaryExpression',
588
+ operator: 'not' as AST.UnaryOperator,
589
+ operand,
590
+ location: tokenLocation(ctx.Not[0]),
591
+ };
592
+ }
593
+
594
+ if (ctx.Minus && ctx.unaryExpression) {
595
+ const operand = this.visit(ctx.unaryExpression);
596
+ return {
597
+ type: 'UnaryExpression',
598
+ operator: '-' as AST.UnaryOperator,
599
+ operand,
600
+ location: tokenLocation(ctx.Minus[0]),
601
+ };
602
+ }
603
+
604
+ return this.visit(ctx.rangeExpression!);
605
+ }
606
+
607
+ rangeExpression(ctx: { postfixExpression: CstNode[]; DotDot?: IToken[] }): AST.Expression {
608
+ const left = this.visit(ctx.postfixExpression[0]);
609
+
610
+ // If no DotDot, just return the postfix expression
611
+ if (!ctx.DotDot) {
612
+ return left;
613
+ }
614
+
615
+ // Range expression: start..end
616
+ const right = this.visit(ctx.postfixExpression[1]);
617
+ return {
618
+ type: 'RangeExpression',
619
+ start: left,
620
+ end: right,
621
+ location: left.location,
622
+ };
623
+ }
624
+
625
+ assignmentExpression(ctx: { Identifier: IToken[]; expression: CstNode[] }): AST.AssignmentExpression {
626
+ return {
627
+ type: 'AssignmentExpression',
628
+ target: {
629
+ type: 'Identifier',
630
+ name: ctx.Identifier[0].image,
631
+ location: tokenLocation(ctx.Identifier[0]),
632
+ },
633
+ value: this.visit(ctx.expression),
634
+ location: tokenLocation(ctx.Identifier[0]),
635
+ };
636
+ }
637
+
638
+ contextSpecifier(ctx: { Default?: IToken[]; Local?: IToken[]; Identifier?: IToken[] }): AST.ContextSpecifier {
639
+ if (ctx.Default) return makeContextSpecifier(ctx.Default[0], 'default');
640
+ if (ctx.Local) return makeContextSpecifier(ctx.Local[0], 'local');
641
+ return makeContextSpecifier(ctx.Identifier![0], 'variable', ctx.Identifier![0].image);
642
+ }
643
+
644
+ // Postfix: function calls, indexing, slicing, member access
645
+ postfixExpression(ctx: {
646
+ primaryExpression: CstNode[];
647
+ LParen?: IToken[];
648
+ argumentList?: CstNode[];
649
+ LBracket?: IToken[];
650
+ indexOrSlice?: CstNode[];
651
+ Dot?: IToken[];
652
+ Identifier?: IToken[];
653
+ }): AST.Expression {
654
+ let expr = this.visit(ctx.primaryExpression);
655
+ const callTokens = ctx.LParen ?? [];
656
+ const bracketTokens = ctx.LBracket ?? [];
657
+ const dotTokens = ctx.Dot ?? [];
658
+ const identifierTokens = ctx.Identifier ?? [];
659
+
660
+ const allOps = [
661
+ ...callTokens.map((t, i) => ({ type: 'call' as const, index: i, offset: t.startOffset })),
662
+ ...bracketTokens.map((t, i) => ({ type: 'bracket' as const, index: i, offset: t.startOffset })),
663
+ ...dotTokens.map((t, i) => ({ type: 'member' as const, index: i, offset: t.startOffset })),
664
+ ].sort((a, b) => a.offset - b.offset);
665
+
666
+ for (const op of allOps) {
667
+ if (op.type === 'call') {
668
+ expr = makeCallExpression(
669
+ expr,
670
+ ctx.argumentList?.[op.index] ? this.visit(ctx.argumentList[op.index]) : []
671
+ );
672
+ } else if (op.type === 'bracket') {
673
+ const result = this.visit(ctx.indexOrSlice![op.index]) as
674
+ | { kind: 'index'; index: AST.Expression }
675
+ | { kind: 'slice'; start: AST.Expression | null; end: AST.Expression | null };
676
+ expr = result.kind === 'index'
677
+ ? makeIndexExpression(expr, result.index)
678
+ : makeSliceExpression(expr, result.start, result.end);
679
+ } else {
680
+ expr = makeMemberExpression(expr, identifierTokens[op.index].image);
681
+ }
682
+ }
683
+ return expr;
684
+ }
685
+
686
+ // Index or slice inside brackets (Python-style colon syntax)
687
+ indexOrSlice(ctx: { Colon?: IToken[]; expression?: CstNode[] }):
688
+ | { kind: 'index'; index: AST.Expression }
689
+ | { kind: 'slice'; start: AST.Expression | null; end: AST.Expression | null } {
690
+ const hasColon = (ctx.Colon?.length ?? 0) > 0;
691
+ const expressions = ctx.expression ?? [];
692
+
693
+ if (!hasColon) {
694
+ // Single index: [expr]
695
+ return { kind: 'index', index: this.visit(expressions[0]) };
696
+ }
697
+
698
+ // Slice: check if colon comes first
699
+ // If expressions.length === 1 and colon exists, it's either [:expr] or [expr:]
700
+ // If expressions.length === 2 and colon exists, it's [expr:expr]
701
+ // If expressions.length === 0 and colon exists, it's [:] (full slice)
702
+
703
+ if (expressions.length === 0) {
704
+ // [:] - full slice (copy entire array)
705
+ return { kind: 'slice', start: null, end: null };
706
+ }
707
+
708
+ if (expressions.length === 1) {
709
+ // Either [:expr] or [expr:]
710
+ // We need to check if colon comes before or after the expression
711
+ const colonOffset = ctx.Colon![0].startOffset;
712
+ // Get the expression's approximate offset from its location
713
+ const exprResult = this.visit(expressions[0]);
714
+ // If the colon is before the expression, it's [:expr]
715
+ // For [:expr]: colon offset < expression offset
716
+ // For [expr:]: expression offset < colon offset
717
+ const firstExprToken = getFirstToken(expressions[0]);
718
+ if (firstExprToken && colonOffset < firstExprToken.startOffset) {
719
+ // [:expr]
720
+ return { kind: 'slice', start: null, end: exprResult };
721
+ } else {
722
+ // [expr:]
723
+ return { kind: 'slice', start: exprResult, end: null };
724
+ }
725
+ }
726
+
727
+ // [expr:expr]
728
+ return {
729
+ kind: 'slice',
730
+ start: this.visit(expressions[0]),
731
+ end: this.visit(expressions[1]),
732
+ };
733
+ }
734
+
735
+ argumentList(ctx: { expression: CstNode[] }): AST.Expression[] {
736
+ return ctx.expression.map((e) => this.visit(e));
737
+ }
738
+
739
+ primaryExpression(ctx: {
740
+ TsBlock?: IToken[];
741
+ StringLiteral?: IToken[];
742
+ TemplateLiteral?: IToken[];
743
+ NumberLiteral?: IToken[];
744
+ True?: IToken[];
745
+ False?: IToken[];
746
+ Null?: IToken[];
747
+ Identifier?: IToken[];
748
+ objectLiteralExpr?: CstNode[];
749
+ arrayLiteral?: CstNode[];
750
+ expression?: CstNode[];
751
+ }): AST.Expression {
752
+ if (ctx.TsBlock) return makeTsBlock(ctx.TsBlock[0]);
753
+ if (ctx.StringLiteral) return makeStringLiteral(ctx.StringLiteral[0]);
754
+ if (ctx.TemplateLiteral) return makeTemplateLiteral(ctx.TemplateLiteral[0]);
755
+ if (ctx.NumberLiteral) return makeNumberLiteral(ctx.NumberLiteral[0]);
756
+ if (ctx.True) return makeBooleanLiteral(ctx.True[0], true);
757
+ if (ctx.False) return makeBooleanLiteral(ctx.False[0], false);
758
+ if (ctx.Null) return makeNullLiteral(ctx.Null[0]);
759
+ if (ctx.objectLiteralExpr) return this.visit(ctx.objectLiteralExpr);
760
+ if (ctx.arrayLiteral) return this.visit(ctx.arrayLiteral);
761
+ if (ctx.Identifier) return makeIdentifier(ctx.Identifier[0]);
762
+ if (ctx.expression) return this.visit(ctx.expression);
763
+ throw new Error('Unknown primary expression');
764
+ }
765
+
766
+ objectLiteralExpr(ctx: { LBrace: IToken[]; propertyList?: CstNode[] }): AST.ObjectLiteral {
767
+ const properties: AST.ObjectProperty[] = ctx.propertyList
768
+ ? this.visit(ctx.propertyList)
769
+ : [];
770
+ return {
771
+ type: 'ObjectLiteral',
772
+ properties,
773
+ location: tokenLocation(ctx.LBrace[0]),
774
+ };
775
+ }
776
+
777
+ arrayLiteral(ctx: { LBracket: IToken[]; elementList?: CstNode[] }): AST.ArrayLiteral {
778
+ const elements: AST.Expression[] = ctx.elementList
779
+ ? this.visit(ctx.elementList)
780
+ : [];
781
+ return {
782
+ type: 'ArrayLiteral',
783
+ elements,
784
+ location: tokenLocation(ctx.LBracket[0]),
785
+ };
786
+ }
787
+
788
+ elementList(ctx: { expression: CstNode[] }): AST.Expression[] {
789
+ return ctx.expression.map((e) => this.visit(e));
790
+ }
791
+ }
792
+
793
+ // Export a singleton visitor instance
794
+ export const vibeAstVisitor = new VibeAstVisitor();