@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,534 @@
1
+ /**
2
+ * Semantic Analyzer Visitors
3
+ *
4
+ * Statement and expression visitor functions for the semantic analyzer.
5
+ */
6
+ import * as AST from '../ast';
7
+ import type { SourceLocation } from '../errors';
8
+ import type { AnalyzerContext, AnalyzerState } from './analyzer-context';
9
+ import { isValidType } from './types';
10
+ import { isCoreFunction } from '../runtime/stdlib/core';
11
+ import { extractFunctionSignature } from './ts-signatures';
12
+ import { resolve, dirname } from 'path';
13
+ import {
14
+ validateModelConfig,
15
+ validateToolDeclaration,
16
+ validateTypeAnnotation,
17
+ validateLiteralType,
18
+ validateConditionType,
19
+ validateAsyncExpression,
20
+ validateContextMode,
21
+ validateTsBlock,
22
+ checkToolCall,
23
+ checkCallArguments,
24
+ checkPromptType,
25
+ checkModelType,
26
+ checkContextVariable,
27
+ validateStringInterpolation,
28
+ getExpressionType,
29
+ } from './analyzer-validators';
30
+
31
+ /**
32
+ * Visitor interface for recursive visiting.
33
+ */
34
+ export interface AnalyzerVisitor {
35
+ visitStatement(node: AST.Statement): void;
36
+ visitExpression(node: AST.Expression): void;
37
+ }
38
+
39
+ /**
40
+ * Creates statement and expression visitors for the analyzer.
41
+ */
42
+ export function createVisitors(
43
+ ctx: AnalyzerContext,
44
+ state: AnalyzerState
45
+ ): AnalyzerVisitor {
46
+ // Helper to get expression type with context
47
+ const getExprType = (expr: AST.Expression) => getExpressionType(ctx, expr);
48
+
49
+ // Helper for validateLiteralType with context
50
+ const validateLitType = (expr: AST.Expression, type: string, location: SourceLocation) => {
51
+ validateLiteralType(ctx, expr, type, location, getExprType);
52
+ };
53
+
54
+ // Validate that all elements in an array literal have consistent types
55
+ function validateArrayLiteralTypes(node: AST.ArrayLiteral): void {
56
+ if (node.elements.length < 2) {
57
+ return; // Need at least 2 elements to have a mismatch
58
+ }
59
+
60
+ const firstType = getExprType(node.elements[0]);
61
+ if (!firstType) {
62
+ return; // Can't determine type of first element
63
+ }
64
+
65
+ for (let i = 1; i < node.elements.length; i++) {
66
+ const elemType = getExprType(node.elements[i]);
67
+ if (!elemType) {
68
+ continue; // Skip elements with unknown types
69
+ }
70
+ if (elemType !== firstType) {
71
+ ctx.error(
72
+ `Mixed array types: element ${i} is ${elemType} but expected ${firstType}`,
73
+ node.elements[i].location ?? node.location
74
+ );
75
+ return; // Report first mismatch only
76
+ }
77
+ }
78
+ }
79
+
80
+ // Check if an expression is definitely an array (by type or AST structure)
81
+ function isArrayExpression(expr: AST.Expression, exprType: string | null): boolean {
82
+ if (exprType?.endsWith('[]')) {
83
+ return true;
84
+ }
85
+ // Array literals and slices are always arrays
86
+ if (expr.type === 'ArrayLiteral' || expr.type === 'SliceExpression') {
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ // Validate array concatenation types (guard clause style)
93
+ function validateArrayConcatenation(node: AST.BinaryExpression): void {
94
+ if (node.operator !== '+') {
95
+ return;
96
+ }
97
+
98
+ const leftType = getExprType(node.left);
99
+ const rightType = getExprType(node.right);
100
+ const leftIsArray = isArrayExpression(node.left, leftType);
101
+ const rightIsArray = isArrayExpression(node.right, rightType);
102
+
103
+ // Not an array operation - nothing to check
104
+ if (!leftIsArray && !rightIsArray) {
105
+ return;
106
+ }
107
+
108
+ // One is array, one is not - error
109
+ if (!leftIsArray || !rightIsArray) {
110
+ ctx.error('Cannot concatenate array with non-array using +', node.location);
111
+ return;
112
+ }
113
+
114
+ // Both arrays but different types - error
115
+ if (leftType && rightType && leftType !== rightType) {
116
+ ctx.error(
117
+ `Cannot concatenate ${leftType} with ${rightType}: array types must match`,
118
+ node.location
119
+ );
120
+ }
121
+ }
122
+
123
+ function visitStatement(node: AST.Statement): void {
124
+ switch (node.type) {
125
+ case 'ImportDeclaration':
126
+ visitImportDeclaration(node);
127
+ break;
128
+
129
+ case 'ExportDeclaration':
130
+ if (node.declaration.type === 'LetDeclaration') {
131
+ ctx.error(
132
+ `Cannot export mutable variable '${node.declaration.name}'. Only constants can be exported.`,
133
+ node.location
134
+ );
135
+ }
136
+ visitStatement(node.declaration);
137
+ break;
138
+
139
+ case 'LetDeclaration':
140
+ visitVariableDeclaration(node, 'variable');
141
+ break;
142
+
143
+ case 'ConstDeclaration':
144
+ visitVariableDeclaration(node, 'constant');
145
+ break;
146
+
147
+ case 'DestructuringDeclaration':
148
+ visitDestructuringDeclaration(node);
149
+ break;
150
+
151
+ case 'ModelDeclaration':
152
+ ctx.declare(node.name, 'model', node.location);
153
+ validateModelConfig(ctx, node, visitExpression);
154
+ break;
155
+
156
+ case 'FunctionDeclaration':
157
+ if (!state.atTopLevel) {
158
+ ctx.error('Functions can only be declared at global scope', node.location);
159
+ }
160
+ ctx.declare(node.name, 'function', node.location, {
161
+ paramCount: node.params.length,
162
+ paramTypes: node.params.map(p => p.typeAnnotation),
163
+ returnType: node.returnType,
164
+ });
165
+ visitFunction(node);
166
+ break;
167
+
168
+ case 'ReturnStatement':
169
+ if (!state.inFunction) {
170
+ ctx.error('return outside of function', node.location);
171
+ }
172
+ if (node.value) visitExpression(node.value);
173
+ break;
174
+
175
+ case 'BreakStatement':
176
+ if (state.loopDepth === 0) {
177
+ ctx.error('break outside of loop', node.location);
178
+ }
179
+ break;
180
+
181
+ case 'IfStatement':
182
+ visitExpression(node.condition);
183
+ validateConditionType(ctx, node.condition, 'if', getExprType);
184
+ visitStatement(node.consequent);
185
+ if (node.alternate) visitStatement(node.alternate);
186
+ break;
187
+
188
+ case 'ForInStatement':
189
+ visitExpression(node.iterable);
190
+ ctx.symbols.enterScope();
191
+ ctx.declare(node.variable, 'variable', node.location, { typeAnnotation: null });
192
+ state.loopDepth++;
193
+ visitStatement(node.body);
194
+ state.loopDepth--;
195
+ ctx.symbols.exitScope();
196
+ if (node.contextMode) validateContextMode(ctx, node.contextMode, node.location);
197
+ break;
198
+
199
+ case 'WhileStatement':
200
+ visitExpression(node.condition);
201
+ validateConditionType(ctx, node.condition, 'while', getExprType);
202
+ ctx.symbols.enterScope();
203
+ state.loopDepth++;
204
+ visitStatement(node.body);
205
+ state.loopDepth--;
206
+ ctx.symbols.exitScope();
207
+ if (node.contextMode) validateContextMode(ctx, node.contextMode, node.location);
208
+ break;
209
+
210
+ case 'BlockStatement': {
211
+ const wasAtTopLevel = state.atTopLevel;
212
+ state.atTopLevel = false;
213
+ ctx.symbols.enterScope();
214
+ for (const stmt of node.body) {
215
+ visitStatement(stmt);
216
+ }
217
+ ctx.symbols.exitScope();
218
+ state.atTopLevel = wasAtTopLevel;
219
+ break;
220
+ }
221
+
222
+ case 'ExpressionStatement':
223
+ visitExpression(node.expression);
224
+ break;
225
+
226
+ case 'ToolDeclaration':
227
+ if (!state.atTopLevel) {
228
+ ctx.error('Tools can only be declared at global scope', node.location);
229
+ }
230
+ ctx.declare(node.name, 'tool', node.location, {
231
+ paramCount: node.params.length,
232
+ paramTypes: node.params.map(p => p.typeAnnotation),
233
+ returnType: node.returnType,
234
+ });
235
+ validateToolDeclaration(ctx, node);
236
+ break;
237
+
238
+ case 'AsyncStatement':
239
+ validateAsyncExpression(ctx, node.expression, node.location);
240
+ visitExpression(node.expression);
241
+ break;
242
+ }
243
+ }
244
+
245
+ function visitExpression(node: AST.Expression): void {
246
+ switch (node.type) {
247
+ case 'Identifier':
248
+ if (!ctx.symbols.lookup(node.name) && !isCoreFunction(node.name)) {
249
+ ctx.error(`'${node.name}' is not defined`, node.location);
250
+ }
251
+ break;
252
+
253
+ case 'StringLiteral':
254
+ validateStringInterpolation(ctx, node.value, false, node.location);
255
+ break;
256
+
257
+ case 'TemplateLiteral':
258
+ validateStringInterpolation(ctx, node.value, false, node.location);
259
+ break;
260
+
261
+ case 'BooleanLiteral':
262
+ case 'NumberLiteral':
263
+ case 'NullLiteral':
264
+ break;
265
+
266
+ case 'ObjectLiteral':
267
+ node.properties.forEach((prop) => visitExpression(prop.value));
268
+ break;
269
+
270
+ case 'ArrayLiteral':
271
+ node.elements.forEach((element) => visitExpression(element));
272
+ validateArrayLiteralTypes(node);
273
+ break;
274
+
275
+ case 'AssignmentExpression':
276
+ visitAssignmentExpression(node);
277
+ break;
278
+
279
+ case 'VibeExpression':
280
+ visitVibePrompt(node.prompt);
281
+ checkPromptType(ctx, node.prompt);
282
+ if (node.model) checkModelType(ctx, node.model, visitExpression);
283
+ if (node.context) checkContextVariable(ctx, node.context);
284
+ break;
285
+
286
+ case 'CallExpression':
287
+ visitExpression(node.callee);
288
+ node.arguments.forEach((arg) => visitExpression(arg));
289
+ checkCallArguments(ctx, node, getExprType, validateLitType);
290
+ checkToolCall(ctx, node);
291
+ break;
292
+
293
+ case 'TsBlock':
294
+ validateTsBlock(ctx, node);
295
+ break;
296
+
297
+ case 'RangeExpression':
298
+ visitExpression(node.start);
299
+ visitExpression(node.end);
300
+ if (node.start.type === 'NumberLiteral' && node.end.type === 'NumberLiteral') {
301
+ if (node.start.value > node.end.value) {
302
+ ctx.error(`Range start (${node.start.value}) must be <= end (${node.end.value})`, node.location);
303
+ }
304
+ }
305
+ break;
306
+
307
+ case 'BinaryExpression':
308
+ visitExpression(node.left);
309
+ visitExpression(node.right);
310
+ validateArrayConcatenation(node);
311
+ break;
312
+
313
+ case 'UnaryExpression':
314
+ visitExpression(node.operand);
315
+ break;
316
+
317
+ case 'IndexExpression':
318
+ visitExpression(node.object);
319
+ visitExpression(node.index);
320
+ break;
321
+
322
+ case 'SliceExpression':
323
+ visitExpression(node.object);
324
+ if (node.start) visitExpression(node.start);
325
+ if (node.end) visitExpression(node.end);
326
+ break;
327
+
328
+ case 'MemberExpression':
329
+ visitExpression(node.object);
330
+ break;
331
+ }
332
+ }
333
+
334
+ function visitVibePrompt(node: AST.Expression): void {
335
+ if (node.type === 'StringLiteral') {
336
+ validateStringInterpolation(ctx, node.value, true, node.location);
337
+ return;
338
+ }
339
+ if (node.type === 'TemplateLiteral') {
340
+ validateStringInterpolation(ctx, node.value, true, node.location);
341
+ return;
342
+ }
343
+ visitExpression(node);
344
+ }
345
+
346
+ function visitVariableDeclaration(
347
+ node: AST.LetDeclaration | AST.ConstDeclaration,
348
+ kind: 'variable' | 'constant'
349
+ ): void {
350
+ const isNullInitializer = node.initializer?.type === 'NullLiteral';
351
+
352
+ if (isNullInitializer) {
353
+ if (kind === 'constant') {
354
+ ctx.error(`Cannot initialize const with null - const values cannot be reassigned`, node.location);
355
+ } else if (!node.typeAnnotation) {
356
+ ctx.error(`Cannot infer type from null - provide a type annotation: let ${node.name}: <type> = null`, node.location);
357
+ }
358
+ }
359
+
360
+ // Empty array requires explicit type annotation
361
+ const isEmptyArray = node.initializer?.type === 'ArrayLiteral' && node.initializer.elements.length === 0;
362
+ if (isEmptyArray && !node.typeAnnotation) {
363
+ ctx.error(
364
+ `Cannot infer type from empty array - provide a type annotation: let ${node.name}: <type>[] = []`,
365
+ node.location
366
+ );
367
+ }
368
+
369
+ if (node.isAsync && node.initializer) {
370
+ validateAsyncExpression(ctx, node.initializer, node.location);
371
+ }
372
+
373
+ if (node.initializer?.type === 'VibeExpression' && !node.typeAnnotation) {
374
+ ctx.error(
375
+ `Type cannot be inferred from AI call, must assign to explicitly typed variable: ${kind === 'constant' ? 'const' : 'let'} ${node.name}: <type> = ...`,
376
+ node.location
377
+ );
378
+ }
379
+
380
+ if (node.typeAnnotation === 'model' && kind === 'variable') {
381
+ ctx.error(`Variables with type 'model' must be declared with 'const', not 'let'`, node.location);
382
+ }
383
+
384
+ let effectiveType = node.typeAnnotation;
385
+ if (!effectiveType && node.initializer) {
386
+ effectiveType = getExprType(node.initializer);
387
+ }
388
+
389
+ ctx.declare(node.name, kind, node.location, { typeAnnotation: effectiveType });
390
+ if (node.typeAnnotation) {
391
+ validateTypeAnnotation(ctx, node.typeAnnotation, node.location);
392
+ }
393
+ if (node.initializer) {
394
+ if (node.initializer.type === 'CallExpression' && node.initializer.callee.type === 'Identifier') {
395
+ const funcSymbol = ctx.symbols.lookup(node.initializer.callee.name);
396
+ if (funcSymbol?.kind === 'function' && !funcSymbol.returnType) {
397
+ ctx.error(
398
+ `Cannot assign result of '${node.initializer.callee.name}()' to a variable - function has no return type`,
399
+ node.location
400
+ );
401
+ }
402
+ }
403
+ const isPromptType = node.typeAnnotation === 'prompt';
404
+ if (isPromptType && (node.initializer.type === 'StringLiteral' || node.initializer.type === 'TemplateLiteral')) {
405
+ validateStringInterpolation(ctx, node.initializer.value, true, node.initializer.location);
406
+ } else {
407
+ visitExpression(node.initializer);
408
+ }
409
+ if (node.typeAnnotation) {
410
+ validateLitType(node.initializer, node.typeAnnotation, node.location);
411
+ }
412
+ }
413
+ }
414
+
415
+ function visitDestructuringDeclaration(node: AST.DestructuringDeclaration): void {
416
+ for (const field of node.fields) {
417
+ if (!isValidType(field.type)) {
418
+ ctx.error(`Invalid type '${field.type}' for field '${field.name}'`, node.location);
419
+ }
420
+ }
421
+
422
+ if (node.initializer.type !== 'VibeExpression') {
423
+ ctx.error('Destructuring assignment requires a do or vibe expression', node.location);
424
+ }
425
+
426
+ if (node.isAsync) {
427
+ validateAsyncExpression(ctx, node.initializer, node.location);
428
+ }
429
+
430
+ const seenNames = new Set<string>();
431
+ const uniqueFields: typeof node.fields = [];
432
+ for (const field of node.fields) {
433
+ if (seenNames.has(field.name)) {
434
+ ctx.error(`Duplicate field '${field.name}' in destructuring pattern`, node.location);
435
+ } else {
436
+ seenNames.add(field.name);
437
+ uniqueFields.push(field);
438
+ }
439
+ }
440
+
441
+ visitExpression(node.initializer);
442
+
443
+ const declarationKind = node.isConst ? 'constant' : 'variable';
444
+ for (const field of uniqueFields) {
445
+ ctx.declare(field.name, declarationKind, node.location, { typeAnnotation: field.type });
446
+ }
447
+ }
448
+
449
+ function visitAssignmentExpression(node: AST.AssignmentExpression): void {
450
+ const name = node.target.name;
451
+ const symbol = ctx.symbols.lookup(name);
452
+
453
+ if (!symbol) {
454
+ ctx.error(`'${name}' is not defined`, node.target.location);
455
+ } else if (symbol.kind === 'constant') {
456
+ ctx.error(`Cannot reassign constant '${name}'`, node.location);
457
+ } else if (symbol.kind === 'function') {
458
+ ctx.error(`Cannot reassign function '${name}'`, node.location);
459
+ } else if (symbol.kind === 'model') {
460
+ ctx.error(`Cannot reassign model '${name}'`, node.location);
461
+ } else if (symbol.kind === 'import') {
462
+ ctx.error(`Cannot reassign imported '${name}'`, node.location);
463
+ }
464
+
465
+ visitExpression(node.value);
466
+ }
467
+
468
+ function visitImportDeclaration(node: AST.ImportDeclaration): void {
469
+ if (!state.atTopLevel) {
470
+ ctx.error('Imports can only be at global scope', node.location);
471
+ return;
472
+ }
473
+
474
+ const isToolImport = node.source === 'system/tools';
475
+
476
+ if (node.sourceType === 'ts' && ctx.basePath) {
477
+ const sourcePath = resolve(dirname(ctx.basePath), node.source);
478
+ for (const spec of node.specifiers) {
479
+ try {
480
+ const sig = extractFunctionSignature(sourcePath, spec.imported);
481
+ if (sig) {
482
+ ctx.tsImportSignatures.set(spec.local, sig);
483
+ }
484
+ } catch {
485
+ // Skip if can't extract signature
486
+ }
487
+ }
488
+ }
489
+
490
+ for (const spec of node.specifiers) {
491
+ const existing = ctx.symbols.lookup(spec.local);
492
+ if (existing) {
493
+ if (existing.kind === 'import' || existing.kind === 'tool') {
494
+ ctx.error(
495
+ `'${spec.local}' is already imported from another module`,
496
+ node.location
497
+ );
498
+ } else {
499
+ ctx.error(
500
+ `Import '${spec.local}' conflicts with existing ${existing.kind}`,
501
+ node.location
502
+ );
503
+ }
504
+ } else {
505
+ // Tool bundles (allTools, readonlyTools, safeTools) are imports, individual tools are 'tool' kind
506
+ const toolBundles = ['allTools', 'readonlyTools', 'safeTools'];
507
+ const importKind = isToolImport && !toolBundles.includes(spec.local) ? 'tool' : 'import';
508
+ ctx.declare(spec.local, importKind, node.location);
509
+ }
510
+ }
511
+ }
512
+
513
+ function visitFunction(node: AST.FunctionDeclaration): void {
514
+ const wasInFunction = state.inFunction;
515
+ state.inFunction = true;
516
+ ctx.symbols.enterScope();
517
+
518
+ for (const param of node.params) {
519
+ validateTypeAnnotation(ctx, param.typeAnnotation, node.location);
520
+ ctx.declare(param.name, 'parameter', node.location, { typeAnnotation: param.typeAnnotation });
521
+ }
522
+
523
+ if (node.returnType) {
524
+ validateTypeAnnotation(ctx, node.returnType, node.location);
525
+ }
526
+
527
+ visitStatement(node.body);
528
+
529
+ ctx.symbols.exitScope();
530
+ state.inFunction = wasInFunction;
531
+ }
532
+
533
+ return { visitStatement, visitExpression };
534
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Semantic Analyzer
3
+ *
4
+ * Main entry point for semantic analysis of Vibe programs.
5
+ * Orchestrates the analysis using modular validators and visitors.
6
+ */
7
+ import * as AST from '../ast';
8
+ import { SemanticError, type SourceLocation } from '../errors';
9
+ import { SymbolTable, type SymbolKind } from './symbol-table';
10
+ import type { TsFunctionSignature } from './ts-signatures';
11
+ import type { AnalyzerContext, AnalyzerState } from './analyzer-context';
12
+ import { createVisitors } from './analyzer-visitors';
13
+
14
+ export class SemanticAnalyzer {
15
+ private symbols = new SymbolTable();
16
+ private errors: SemanticError[] = [];
17
+ private source?: string;
18
+ private basePath?: string;
19
+ private tsImportSignatures: Map<string, TsFunctionSignature> = new Map();
20
+
21
+ /**
22
+ * Analyze a Vibe program and return any semantic errors.
23
+ */
24
+ analyze(program: AST.Program, source?: string, basePath?: string): SemanticError[] {
25
+ this.errors = [];
26
+ this.source = source;
27
+ this.basePath = basePath;
28
+ this.tsImportSignatures.clear();
29
+ this.symbols.enterScope();
30
+
31
+ // Create context and state for visitors
32
+ const ctx = this.createContext();
33
+ const state: AnalyzerState = {
34
+ inFunction: false,
35
+ atTopLevel: true,
36
+ loopDepth: 0,
37
+ };
38
+
39
+ // Create visitors with context
40
+ const visitors = createVisitors(ctx, state);
41
+
42
+ // Visit all statements
43
+ for (const stmt of program.body) {
44
+ visitors.visitStatement(stmt);
45
+ }
46
+
47
+ this.symbols.exitScope();
48
+ return this.errors;
49
+ }
50
+
51
+ /**
52
+ * Create the analyzer context for validators and visitors.
53
+ */
54
+ private createContext(): AnalyzerContext {
55
+ return {
56
+ symbols: this.symbols,
57
+ tsImportSignatures: this.tsImportSignatures,
58
+ basePath: this.basePath,
59
+ source: this.source,
60
+ inFunction: false,
61
+ atTopLevel: true,
62
+ loopDepth: 0,
63
+ error: (message: string, location: SourceLocation) => {
64
+ this.errors.push(new SemanticError(message, location, this.source));
65
+ },
66
+ declare: (
67
+ name: string,
68
+ kind: SymbolKind,
69
+ location: SourceLocation,
70
+ options?: {
71
+ paramCount?: number;
72
+ typeAnnotation?: string | null;
73
+ paramTypes?: string[];
74
+ returnType?: string | null;
75
+ }
76
+ ) => {
77
+ if (!this.symbols.declare({ name, kind, location, ...options })) {
78
+ this.errors.push(new SemanticError(`'${name}' is already declared`, location, this.source));
79
+ }
80
+ },
81
+ };
82
+ }
83
+ }
@@ -0,0 +1,11 @@
1
+ import type * as AST from '../ast';
2
+ import type { SemanticError } from '../errors';
3
+ import { SemanticAnalyzer } from './analyzer';
4
+
5
+ export { SymbolTable, type Symbol, type SymbolKind } from './symbol-table';
6
+ export { SemanticAnalyzer } from './analyzer';
7
+
8
+ export function analyze(program: AST.Program, source?: string, basePath?: string): SemanticError[] {
9
+ const analyzer = new SemanticAnalyzer();
10
+ return analyzer.analyze(program, source, basePath);
11
+ }
@@ -0,0 +1,58 @@
1
+ import type { SourceLocation } from '../errors';
2
+
3
+ export type SymbolKind = 'variable' | 'constant' | 'model' | 'function' | 'tool' | 'parameter' | 'import';
4
+
5
+ export interface Symbol {
6
+ name: string;
7
+ kind: SymbolKind;
8
+ location: SourceLocation;
9
+ paramCount?: number;
10
+ paramTypes?: string[]; // Parameter types for functions
11
+ returnType?: string | null; // Return type for functions
12
+ typeAnnotation?: string | null;
13
+ }
14
+
15
+ interface Scope {
16
+ parent: Scope | null;
17
+ symbols: Map<string, Symbol>;
18
+ }
19
+
20
+ export class SymbolTable {
21
+ private currentScope: Scope | null = null;
22
+
23
+ enterScope(): void {
24
+ this.currentScope = {
25
+ parent: this.currentScope,
26
+ symbols: new Map(),
27
+ };
28
+ }
29
+
30
+ exitScope(): void {
31
+ if (this.currentScope) {
32
+ this.currentScope = this.currentScope.parent;
33
+ }
34
+ }
35
+
36
+ declare(symbol: Symbol): boolean {
37
+ if (!this.currentScope) return false;
38
+ if (this.currentScope.symbols.has(symbol.name)) {
39
+ return false;
40
+ }
41
+ this.currentScope.symbols.set(symbol.name, symbol);
42
+ return true;
43
+ }
44
+
45
+ lookup(name: string): Symbol | undefined {
46
+ let scope = this.currentScope;
47
+ while (scope) {
48
+ const symbol = scope.symbols.get(name);
49
+ if (symbol) return symbol;
50
+ scope = scope.parent;
51
+ }
52
+ return undefined;
53
+ }
54
+
55
+ lookupLocal(name: string): Symbol | undefined {
56
+ return this.currentScope?.symbols.get(name);
57
+ }
58
+ }