@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.
- package/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- 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
|
+
}
|