@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,129 @@
1
+ // Tool execution handlers
2
+
3
+ import * as AST from '../../ast';
4
+ import type { RuntimeState } from '../types';
5
+ import { createVibeValue } from '../types';
6
+ import type { ToolSchema, ToolParameterSchema, VibeToolValue } from '../tools/types';
7
+ import { vibeTypeToJsonSchema } from '../tools/ts-schema';
8
+ import { currentFrame } from '../state';
9
+
10
+ /**
11
+ * Execute a tool declaration - stores the tool as a variable in frame locals.
12
+ */
13
+ export function execToolDeclaration(
14
+ state: RuntimeState,
15
+ decl: AST.ToolDeclaration
16
+ ): RuntimeState {
17
+ // Build tool schema from declaration
18
+ const schema = buildToolSchema(state, decl);
19
+
20
+ // Create executor that wraps the tool body
21
+ const executor = createToolExecutor(state, decl);
22
+
23
+ // Create the tool value (like a model value)
24
+ const toolValue: VibeToolValue = {
25
+ __vibeTool: true,
26
+ name: decl.name,
27
+ schema,
28
+ executor,
29
+ };
30
+
31
+ // Store as a variable in frame locals (like model declarations)
32
+ const frame = currentFrame(state);
33
+ const newLocals = {
34
+ ...frame.locals,
35
+ [decl.name]: createVibeValue(toolValue, { isConst: true, typeAnnotation: null }),
36
+ };
37
+
38
+ return {
39
+ ...state,
40
+ callStack: [
41
+ ...state.callStack.slice(0, -1),
42
+ { ...frame, locals: newLocals },
43
+ ],
44
+ executionLog: [
45
+ ...state.executionLog,
46
+ {
47
+ timestamp: Date.now(),
48
+ instructionType: 'tool_declaration',
49
+ details: { toolName: decl.name },
50
+ },
51
+ ],
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Build a tool schema from the AST declaration.
57
+ * Converts Vibe types to JSON Schema, merging in @param descriptions.
58
+ */
59
+ function buildToolSchema(
60
+ state: RuntimeState,
61
+ decl: AST.ToolDeclaration
62
+ ): ToolSchema {
63
+ // Build map of imported types for resolving TS type references
64
+ const importedTypes = new Map<string, string>();
65
+ for (const [name, info] of Object.entries(state.importedNames)) {
66
+ if (info.sourceType === 'ts') {
67
+ importedTypes.set(name, info.source);
68
+ }
69
+ }
70
+
71
+ const parameters: ToolParameterSchema[] = decl.params.map((param) => ({
72
+ name: param.name,
73
+ type: vibeTypeToJsonSchema(param.typeAnnotation, importedTypes),
74
+ description: param.description,
75
+ required: true, // All tool parameters are required for now
76
+ }));
77
+
78
+ return {
79
+ name: decl.name,
80
+ description: decl.description,
81
+ parameters,
82
+ returns: decl.returnType
83
+ ? vibeTypeToJsonSchema(decl.returnType, importedTypes)
84
+ : undefined,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Create an executor function for a user-defined tool.
90
+ * The executor extracts the TS block from the tool body and runs it.
91
+ */
92
+ function createToolExecutor(
93
+ _state: RuntimeState,
94
+ decl: AST.ToolDeclaration
95
+ ): (args: Record<string, unknown>) => Promise<unknown> {
96
+ // Find the TsBlock in the tool body
97
+ const tsBlock = findTsBlock(decl.body);
98
+
99
+ if (!tsBlock) {
100
+ throw new Error(`Tool '${decl.name}' must have a ts block as its body`);
101
+ }
102
+
103
+ // Return an executor that runs the TS code with the provided args
104
+ return async (args: Record<string, unknown>): Promise<unknown> => {
105
+ // Build parameter values in the order they appear in the ts block params
106
+ const paramValues = tsBlock.params.map((paramName) => args[paramName]);
107
+
108
+ // Create async function from the TS body
109
+ const asyncFn = new Function(
110
+ ...tsBlock.params,
111
+ `return (async () => { ${tsBlock.body} })()`
112
+ );
113
+
114
+ return await asyncFn(...paramValues);
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Find the first TsBlock expression in a block statement.
120
+ */
121
+ function findTsBlock(block: AST.BlockStatement): AST.TsBlock | null {
122
+ for (const stmt of block.body) {
123
+ if (stmt.type === 'ExpressionStatement' && stmt.expression.type === 'TsBlock') {
124
+ return stmt.expression;
125
+ }
126
+ }
127
+ return null;
128
+ }
129
+
@@ -0,0 +1,215 @@
1
+ // TypeScript execution: interpolation, ts blocks, ts eval
2
+
3
+ import * as AST from '../../ast';
4
+ import type { SourceLocation } from '../../errors';
5
+ import type { RuntimeState } from '../types';
6
+ import { resolveValue, isVibeValue } from '../types';
7
+ import { lookupVariable } from './variables';
8
+ import { getImportedValue } from '../modules';
9
+ import { scheduleAsyncOperation, isInAsyncContext } from '../async/scheduling';
10
+
11
+ /**
12
+ * Deep freeze an object to prevent any mutation.
13
+ * Used to enforce const semantics for objects passed to ts blocks.
14
+ */
15
+ function deepFreeze<T>(obj: T): T {
16
+ if (obj === null || typeof obj !== 'object') {
17
+ return obj;
18
+ }
19
+
20
+ // Freeze the object itself
21
+ Object.freeze(obj);
22
+
23
+ // Recursively freeze all properties
24
+ for (const key of Object.keys(obj)) {
25
+ const value = (obj as Record<string, unknown>)[key];
26
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
27
+ deepFreeze(value);
28
+ }
29
+ }
30
+
31
+ return obj;
32
+ }
33
+
34
+ /**
35
+ * String interpolation - {varName} syntax.
36
+ * Resolves AIResultObject to its value for string conversion.
37
+ * If any interpolated variable is a pending async operation, triggers awaiting_async.
38
+ */
39
+ export function execInterpolateString(state: RuntimeState, template: string, location?: SourceLocation): RuntimeState {
40
+ // First pass: check for pending async variables
41
+ const varPattern = /\{(\w+)\}/g;
42
+ const pendingAsyncIds: string[] = [];
43
+ let match;
44
+
45
+ while ((match = varPattern.exec(template)) !== null) {
46
+ const name = match[1];
47
+ const found = lookupVariable(state, name);
48
+ if (found) {
49
+ const variable = found.variable;
50
+ if (isVibeValue(variable) && variable.asyncOperationId) {
51
+ const opId = variable.asyncOperationId;
52
+ const operation = state.asyncOperations.get(opId);
53
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
54
+ pendingAsyncIds.push(opId);
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ // If there are pending async variables, wait for them
61
+ if (pendingAsyncIds.length > 0) {
62
+ return {
63
+ ...state,
64
+ status: 'awaiting_async',
65
+ awaitingAsyncIds: pendingAsyncIds,
66
+ instructionStack: [
67
+ { op: 'interpolate_string', template, location: location ?? { line: 0, column: 0 } },
68
+ ...state.instructionStack,
69
+ ],
70
+ };
71
+ }
72
+
73
+ // All variables ready, do the interpolation
74
+ const result = template.replace(/\{(\w+)\}/g, (_, name) => {
75
+ const found = lookupVariable(state, name);
76
+ if (found) {
77
+ const value = resolveValue(found.variable.value);
78
+ return String(value);
79
+ }
80
+ return `{${name}}`;
81
+ });
82
+
83
+ return { ...state, lastResult: result };
84
+ }
85
+
86
+ /**
87
+ * Template literal interpolation - ${varName} syntax.
88
+ * Resolves AIResultObject to its value for string conversion.
89
+ * If any interpolated variable is a pending async operation, triggers awaiting_async.
90
+ */
91
+ export function execInterpolateTemplate(state: RuntimeState, template: string, location?: SourceLocation): RuntimeState {
92
+ // First pass: check for pending async variables
93
+ const varPattern = /\$\{(\w+)\}/g;
94
+ const pendingAsyncIds: string[] = [];
95
+ let match;
96
+
97
+ while ((match = varPattern.exec(template)) !== null) {
98
+ const name = match[1];
99
+ const found = lookupVariable(state, name);
100
+ if (found) {
101
+ const variable = found.variable;
102
+ if (isVibeValue(variable) && variable.asyncOperationId) {
103
+ const opId = variable.asyncOperationId;
104
+ const operation = state.asyncOperations.get(opId);
105
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
106
+ pendingAsyncIds.push(opId);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ // If there are pending async variables, wait for them
113
+ if (pendingAsyncIds.length > 0) {
114
+ return {
115
+ ...state,
116
+ status: 'awaiting_async',
117
+ awaitingAsyncIds: pendingAsyncIds,
118
+ instructionStack: [
119
+ { op: 'interpolate_template', template, location: location ?? { line: 0, column: 0 } },
120
+ ...state.instructionStack,
121
+ ],
122
+ };
123
+ }
124
+
125
+ // All variables ready, do the interpolation
126
+ const result = template.replace(/\$\{(\w+)\}/g, (_, name) => {
127
+ const found = lookupVariable(state, name);
128
+ if (found) {
129
+ const value = resolveValue(found.variable.value);
130
+ return String(value);
131
+ }
132
+ return `\${${name}}`;
133
+ });
134
+
135
+ return { ...state, lastResult: result };
136
+ }
137
+
138
+ /**
139
+ * TypeScript block - push ts_eval instruction.
140
+ */
141
+ export function execTsBlock(state: RuntimeState, expr: AST.TsBlock): RuntimeState {
142
+ return {
143
+ ...state,
144
+ instructionStack: [
145
+ { op: 'ts_eval', params: expr.params, body: expr.body, location: expr.location },
146
+ ...state.instructionStack,
147
+ ],
148
+ };
149
+ }
150
+
151
+ /**
152
+ * TypeScript eval - pause for async evaluation.
153
+ * When currentAsyncVarName is set (async declaration), schedules the operation
154
+ * for non-blocking execution instead of pausing.
155
+ */
156
+ export function execTsEval(state: RuntimeState, params: string[], body: string, location: SourceLocation): RuntimeState {
157
+ // Look up parameter values from scope or imports
158
+ const paramValues = params.map((name) => {
159
+ // First try regular variables
160
+ const found = lookupVariable(state, name);
161
+ if (found) {
162
+ // Resolve AIResultObject to primitive value for ts blocks
163
+ const value = resolveValue(found.variable.value);
164
+ // Freeze const objects to prevent mutation in ts blocks
165
+ if (found.variable.isConst && value !== null && typeof value === 'object') {
166
+ return deepFreeze(value);
167
+ }
168
+ return value;
169
+ }
170
+ // Then try imported values
171
+ const imported = getImportedValue(state, name);
172
+ if (imported !== undefined) {
173
+ return imported;
174
+ }
175
+ throw new Error(`ReferenceError: '${name}' is not defined`);
176
+ });
177
+
178
+ // Check if we're in async context (variable, destructuring, or fire-and-forget)
179
+ if (isInAsyncContext(state)) {
180
+ // Schedule for non-blocking execution using shared helper
181
+ return scheduleAsyncOperation(
182
+ state,
183
+ {
184
+ type: 'ts',
185
+ tsDetails: {
186
+ params,
187
+ body,
188
+ paramValues,
189
+ location,
190
+ },
191
+ },
192
+ 'async_ts_scheduled'
193
+ );
194
+ }
195
+
196
+ // Normal blocking execution
197
+ return {
198
+ ...state,
199
+ status: 'awaiting_ts',
200
+ pendingTS: {
201
+ params,
202
+ body,
203
+ paramValues,
204
+ location, // Include source location for error reporting
205
+ },
206
+ executionLog: [
207
+ ...state.executionLog,
208
+ {
209
+ timestamp: Date.now(),
210
+ instructionType: 'ts_eval_request',
211
+ details: { params, body: body.slice(0, 100) }, // Truncate body for log
212
+ },
213
+ ],
214
+ };
215
+ }
@@ -0,0 +1,279 @@
1
+ // Variable handling: lookup, declare, assign
2
+
3
+ import type { VibeType } from '../../ast';
4
+ import type { SourceLocation } from '../../errors';
5
+ import type { RuntimeState, VibeValue, StackFrame, ToolCallRecord } from '../types';
6
+ import { createVibeValue, isVibeValue } from '../types';
7
+ import { currentFrame } from '../state';
8
+ import { validateAndCoerce } from '../validation';
9
+ import { getModuleGlobals } from '../modules';
10
+
11
+ /**
12
+ * Look up a variable by walking the scope chain.
13
+ * Returns the variable and its frame index, or null if not found.
14
+ *
15
+ * Module isolation: If we're in an imported function (frame has modulePath),
16
+ * we check that module's globals instead of walking up to the main program.
17
+ */
18
+ export function lookupVariable(state: RuntimeState, name: string): { variable: VibeValue; frameIndex: number } | null {
19
+ let frameIndex: number | null = state.callStack.length - 1;
20
+ let modulePath: string | undefined;
21
+
22
+ while (frameIndex !== null && frameIndex >= 0) {
23
+ const frame: StackFrame = state.callStack[frameIndex];
24
+
25
+ // Check frame locals
26
+ if (frame.locals[name]) {
27
+ return { variable: frame.locals[name], frameIndex };
28
+ }
29
+
30
+ // Track the module path from any frame in the chain
31
+ if (frame.modulePath && !modulePath) {
32
+ modulePath = frame.modulePath;
33
+ }
34
+
35
+ // If we're in a module context and this is the module's root frame,
36
+ // don't walk up to the caller's frames - check module globals instead
37
+ if (modulePath && frame.modulePath === modulePath) {
38
+ // Check module globals before walking to parent (caller's context)
39
+ const moduleGlobals = getModuleGlobals(state, modulePath);
40
+ if (moduleGlobals?.[name]) {
41
+ return { variable: moduleGlobals[name], frameIndex: -1 };
42
+ }
43
+ // Don't continue to caller's frames - module isolation
44
+ return null;
45
+ }
46
+
47
+ frameIndex = frame.parentFrameIndex;
48
+ }
49
+
50
+ // Not in module context - variable not found in main program
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Declare a variable with value from lastResult (or explicit initialValue).
56
+ */
57
+ export function execDeclareVar(
58
+ state: RuntimeState,
59
+ name: string,
60
+ isConst: boolean,
61
+ type: VibeType,
62
+ initialValue?: unknown,
63
+ isPrivate?: boolean,
64
+ location?: SourceLocation
65
+ ): RuntimeState {
66
+ const frame = currentFrame(state);
67
+
68
+ if (frame.locals[name]) {
69
+ throw new Error(`Variable '${name}' is already declared`);
70
+ }
71
+
72
+ const rawValue = initialValue !== undefined ? initialValue : state.lastResult;
73
+
74
+ // If value is already a VibeValue (e.g., from AI response or error), extract its properties
75
+ let innerValue: unknown;
76
+ let toolCalls: ToolCallRecord[] = [];
77
+ let source = initialValue !== undefined ? null : state.lastResultSource;
78
+ let err: VibeValue['err'] = false;
79
+ let errDetails: VibeValue['errDetails'] = null;
80
+ let asyncOperationId: string | undefined;
81
+
82
+ if (isVibeValue(rawValue)) {
83
+ innerValue = rawValue.value;
84
+ toolCalls = rawValue.toolCalls;
85
+ source = rawValue.source ?? source;
86
+ err = rawValue.err; // Preserve error boolean from operations like null arithmetic
87
+ errDetails = rawValue.errDetails; // Preserve error details
88
+ asyncOperationId = rawValue.asyncOperationId; // Preserve async operation ID for pending async
89
+ } else {
90
+ innerValue = rawValue;
91
+ }
92
+
93
+ const { value: validatedValue, inferredType } = validateAndCoerce(innerValue, type, name, location, source);
94
+
95
+ // Use explicit type if provided, otherwise use inferred type
96
+ const finalType = type ?? inferredType;
97
+
98
+ const newLocals = {
99
+ ...frame.locals,
100
+ [name]: createVibeValue(validatedValue, { isConst, typeAnnotation: finalType, source, toolCalls, err, errDetails, isPrivate, asyncOperationId }),
101
+ };
102
+
103
+ // Add variable to ordered entries with snapshotted value for context tracking
104
+ const newOrderedEntries = [
105
+ ...frame.orderedEntries,
106
+ {
107
+ kind: 'variable' as const,
108
+ name,
109
+ value: validatedValue, // Snapshot at assignment time
110
+ type: finalType,
111
+ isConst,
112
+ source,
113
+ ...(isPrivate ? { isPrivate: true } : {}),
114
+ },
115
+ ];
116
+
117
+ const newState: RuntimeState = {
118
+ ...state,
119
+ lastResultSource: null, // Clear after consuming
120
+ callStack: [
121
+ ...state.callStack.slice(0, -1),
122
+ { ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
123
+ ],
124
+ executionLog: [
125
+ ...state.executionLog,
126
+ {
127
+ timestamp: Date.now(),
128
+ instructionType: isConst ? 'const_declaration' : 'let_declaration',
129
+ details: { name, type, isConst },
130
+ result: validatedValue,
131
+ },
132
+ ],
133
+ };
134
+
135
+ return newState;
136
+ }
137
+
138
+ /**
139
+ * Assign a value to an existing variable (from lastResult).
140
+ */
141
+ export function execAssignVar(state: RuntimeState, name: string, location?: SourceLocation): RuntimeState {
142
+ // Walk scope chain to find the variable
143
+ const found = lookupVariable(state, name);
144
+
145
+ if (!found) {
146
+ throw new Error(`ReferenceError: '${name}' is not defined`);
147
+ }
148
+
149
+ const { variable, frameIndex } = found;
150
+
151
+ if (variable.isConst) {
152
+ throw new Error(`TypeError: Cannot assign to constant '${name}'`);
153
+ }
154
+
155
+ // Warn if modifying non-local variable in async isolation
156
+ // (modifications won't persist after the async function returns)
157
+ const currentFrameIndex = state.callStack.length - 1;
158
+ if (state.isInAsyncIsolation && frameIndex !== currentFrameIndex && frameIndex >= 0) {
159
+ console.warn(
160
+ `Warning: Modifying non-local variable '${name}' in async function. ` +
161
+ `This modification will not persist after the function returns.`
162
+ );
163
+ }
164
+
165
+ const rawValue = state.lastResult;
166
+
167
+ // If value is already a VibeValue (e.g., from AI response or error), extract its properties
168
+ let innerValue: unknown;
169
+ let toolCalls: ToolCallRecord[] = [];
170
+ let source = state.lastResultSource;
171
+ let err: VibeValue['err'] = false;
172
+ let errDetails: VibeValue['errDetails'] = null;
173
+
174
+ if (isVibeValue(rawValue)) {
175
+ innerValue = rawValue.value;
176
+ toolCalls = rawValue.toolCalls;
177
+ source = rawValue.source ?? source;
178
+ err = rawValue.err; // Preserve error boolean from operations like null arithmetic
179
+ errDetails = rawValue.errDetails; // Preserve error details
180
+ } else {
181
+ innerValue = rawValue;
182
+ }
183
+
184
+ const { value: validatedValue } = validateAndCoerce(innerValue, variable.typeAnnotation, name, location, source);
185
+
186
+ // Handle module global assignment (frameIndex -1)
187
+ if (frameIndex === -1) {
188
+ // Find which module this belongs to by checking current frame's modulePath
189
+ const currentModulePath = getCurrentModulePath(state);
190
+ if (!currentModulePath) {
191
+ throw new Error(`Internal error: module global assignment without module context`);
192
+ }
193
+
194
+ const module = state.vibeModules[currentModulePath];
195
+ if (!module) {
196
+ throw new Error(`Internal error: module not found: ${currentModulePath}`);
197
+ }
198
+
199
+ const newGlobals = {
200
+ ...module.globals,
201
+ [name]: { ...variable, value: validatedValue, source, toolCalls, err, errDetails },
202
+ };
203
+
204
+ return {
205
+ ...state,
206
+ lastResultSource: null,
207
+ vibeModules: {
208
+ ...state.vibeModules,
209
+ [currentModulePath]: { ...module, globals: newGlobals },
210
+ },
211
+ executionLog: [
212
+ ...state.executionLog,
213
+ {
214
+ timestamp: Date.now(),
215
+ instructionType: 'assignment',
216
+ details: { name, moduleGlobal: true },
217
+ result: validatedValue,
218
+ },
219
+ ],
220
+ };
221
+ }
222
+
223
+ // Regular frame local assignment
224
+ const frame = state.callStack[frameIndex];
225
+ const newLocals = {
226
+ ...frame.locals,
227
+ [name]: { ...variable, value: validatedValue, source, toolCalls, err, errDetails },
228
+ };
229
+
230
+ // Add assignment to ordered entries with snapshotted value for context tracking
231
+ // This captures the history of value changes
232
+ const newOrderedEntries = [
233
+ ...frame.orderedEntries,
234
+ {
235
+ kind: 'variable' as const,
236
+ name,
237
+ value: validatedValue, // Snapshot at assignment time
238
+ type: variable.typeAnnotation,
239
+ isConst: false, // Assignments only happen to non-const variables
240
+ source,
241
+ },
242
+ ];
243
+
244
+ // Update the correct frame in the call stack
245
+ return {
246
+ ...state,
247
+ lastResultSource: null, // Clear after consuming
248
+ callStack: [
249
+ ...state.callStack.slice(0, frameIndex),
250
+ { ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
251
+ ...state.callStack.slice(frameIndex + 1),
252
+ ],
253
+ executionLog: [
254
+ ...state.executionLog,
255
+ {
256
+ timestamp: Date.now(),
257
+ instructionType: 'assignment',
258
+ details: { name },
259
+ result: validatedValue,
260
+ },
261
+ ],
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Get the module path from the current execution context.
267
+ * Walks up the call stack to find a frame with modulePath.
268
+ */
269
+ function getCurrentModulePath(state: RuntimeState): string | undefined {
270
+ let frameIndex: number | null = state.callStack.length - 1;
271
+ while (frameIndex !== null && frameIndex >= 0) {
272
+ const frame: StackFrame = state.callStack[frameIndex];
273
+ if (frame.modulePath) {
274
+ return frame.modulePath;
275
+ }
276
+ frameIndex = frame.parentFrameIndex;
277
+ }
278
+ return undefined;
279
+ }