@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,305 @@
1
+ // Function call execution
2
+
3
+ import * as AST from '../../ast';
4
+ import type { SourceLocation } from '../../errors';
5
+ import type { RuntimeState, StackFrame } from '../types';
6
+ import { resolveValue, createVibeValue } from '../types';
7
+ import type { VibeToolValue } from '../tools/types';
8
+ import { createFrame } from '../state';
9
+ import { getImportedVibeFunction, getImportedVibeFunctionModulePath } from '../modules';
10
+ import { getCoreFunction } from '../stdlib/core';
11
+ import { validateAndCoerce } from '../validation';
12
+ import { scheduleAsyncOperation, isInAsyncContext } from '../async/scheduling';
13
+
14
+ /**
15
+ * Create a new frame with validated parameters for a Vibe function call.
16
+ * @param modulePath - If set, this frame belongs to an imported module (for scope isolation)
17
+ */
18
+ export function createFunctionFrame(
19
+ funcName: string,
20
+ params: AST.FunctionParameter[],
21
+ args: unknown[],
22
+ modulePath?: string
23
+ ): StackFrame {
24
+ const newFrame = createFrame(funcName, 0);
25
+
26
+ // Set module path for imported functions (enables module scope isolation)
27
+ if (modulePath) {
28
+ newFrame.modulePath = modulePath;
29
+ }
30
+
31
+ for (let i = 0; i < params.length; i++) {
32
+ const param = params[i];
33
+ const argValue = args[i] ?? null;
34
+
35
+ const { value: validatedValue } = validateAndCoerce(
36
+ argValue,
37
+ param.typeAnnotation,
38
+ param.name
39
+ );
40
+
41
+ newFrame.locals[param.name] = createVibeValue(validatedValue, {
42
+ isConst: false,
43
+ typeAnnotation: param.typeAnnotation,
44
+ });
45
+ // Include snapshotted value in ordered entries for context tracking
46
+ newFrame.orderedEntries.push({
47
+ kind: 'variable' as const,
48
+ name: param.name,
49
+ value: validatedValue,
50
+ type: param.typeAnnotation,
51
+ isConst: false, // Parameters are not const
52
+ });
53
+ }
54
+
55
+ return newFrame;
56
+ }
57
+
58
+ /**
59
+ * Execute a Vibe function (local or imported) by pushing its body onto the instruction stack.
60
+ * Note: Functions always forget context on exit (like traditional callstack).
61
+ * @param modulePath - If set, this function belongs to an imported module (for scope isolation)
62
+ */
63
+ function executeVibeFunction(
64
+ state: RuntimeState,
65
+ func: AST.FunctionDeclaration,
66
+ args: unknown[],
67
+ newValueStack: unknown[],
68
+ modulePath?: string
69
+ ): RuntimeState {
70
+ const newFrame = createFunctionFrame(func.name, func.params, args, modulePath);
71
+
72
+ const bodyInstructions = func.body.body.map((s) => ({
73
+ op: 'exec_statement' as const,
74
+ stmt: s,
75
+ location: s.location,
76
+ }));
77
+
78
+ return {
79
+ ...state,
80
+ valueStack: newValueStack,
81
+ callStack: [...state.callStack, newFrame],
82
+ instructionStack: [
83
+ ...bodyInstructions,
84
+ { op: 'pop_frame', location: func.body.location },
85
+ ...state.instructionStack,
86
+ ],
87
+ lastResult: null,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Schedule a Vibe function for async execution.
93
+ * Creates an async operation that will clone state and run the function in isolation.
94
+ */
95
+ function scheduleAsyncVibeFunction(
96
+ state: RuntimeState,
97
+ funcName: string,
98
+ args: unknown[],
99
+ newValueStack: unknown[],
100
+ modulePath?: string
101
+ ): RuntimeState {
102
+ // Use shared scheduling helper, then update valueStack
103
+ const newState = scheduleAsyncOperation(
104
+ state,
105
+ {
106
+ type: 'vibe-function',
107
+ vibeFuncDetails: {
108
+ funcName,
109
+ args,
110
+ modulePath,
111
+ },
112
+ },
113
+ 'async_vibe_function_scheduled'
114
+ );
115
+ return { ...newState, valueStack: newValueStack };
116
+ }
117
+
118
+ /**
119
+ * Execute function call - handles local Vibe, imported Vibe, and imported TS functions.
120
+ * Note: Functions always forget context on exit (like traditional callstack).
121
+ */
122
+ export function execCallFunction(
123
+ state: RuntimeState,
124
+ _funcName: string,
125
+ argCount: number,
126
+ location: SourceLocation
127
+ ): RuntimeState {
128
+ // Get args and callee from value stack, unwrapping VibeValues
129
+ const rawArgs = state.valueStack.slice(-argCount);
130
+ const rawCallee = state.valueStack[state.valueStack.length - argCount - 1];
131
+ const newValueStack = state.valueStack.slice(0, -(argCount + 1));
132
+
133
+ // Unwrap VibeValue for callee and args
134
+ const callee = resolveValue(rawCallee);
135
+ const args = rawArgs.map(arg => resolveValue(arg));
136
+
137
+ // Handle local Vibe function
138
+ if (typeof callee === 'object' && callee !== null && '__vibeFunction' in callee) {
139
+ const funcName = (callee as { __vibeFunction: boolean; name: string }).name;
140
+ const func = state.functions[funcName];
141
+
142
+ if (!func) {
143
+ throw new Error(`ReferenceError: '${funcName}' is not defined`);
144
+ }
145
+
146
+ // Check if we're in async context (variable, destructuring, or fire-and-forget)
147
+ if (isInAsyncContext(state)) {
148
+ return scheduleAsyncVibeFunction(state, funcName, args, newValueStack);
149
+ }
150
+
151
+ return executeVibeFunction(state, func, args, newValueStack);
152
+ }
153
+
154
+ // Handle imported TS function
155
+ if (typeof callee === 'object' && callee !== null && '__vibeImportedTsFunction' in callee) {
156
+ const funcName = (callee as { __vibeImportedTsFunction: boolean; name: string }).name;
157
+ // Resolve AIResultObject values to primitives for TS functions
158
+ const resolvedArgs = args.map(arg => resolveValue(arg));
159
+
160
+ // Check if we're in async context (variable, destructuring, or fire-and-forget)
161
+ if (isInAsyncContext(state)) {
162
+ // Schedule for non-blocking execution using shared helper
163
+ const newState = scheduleAsyncOperation(
164
+ state,
165
+ {
166
+ type: 'ts-function',
167
+ tsFuncDetails: {
168
+ funcName,
169
+ args: resolvedArgs,
170
+ location,
171
+ },
172
+ },
173
+ 'async_ts_function_scheduled'
174
+ );
175
+ return { ...newState, valueStack: newValueStack };
176
+ }
177
+
178
+ // Normal blocking execution
179
+ return {
180
+ ...state,
181
+ valueStack: newValueStack,
182
+ status: 'awaiting_ts',
183
+ pendingImportedTsCall: { funcName, args: resolvedArgs, location },
184
+ executionLog: [
185
+ ...state.executionLog,
186
+ {
187
+ timestamp: Date.now(),
188
+ instructionType: 'imported_ts_call_request',
189
+ details: { funcName, argCount },
190
+ },
191
+ ],
192
+ };
193
+ }
194
+
195
+ // Handle imported Vibe function
196
+ if (typeof callee === 'object' && callee !== null && '__vibeImportedVibeFunction' in callee) {
197
+ const funcName = (callee as { __vibeImportedVibeFunction: boolean; name: string }).name;
198
+ const func = getImportedVibeFunction(state, funcName);
199
+
200
+ if (!func) {
201
+ throw new Error(`ReferenceError: '${funcName}' is not defined`);
202
+ }
203
+
204
+ // Get the module path for scope isolation
205
+ const modulePath = getImportedVibeFunctionModulePath(state, funcName);
206
+
207
+ // Check if we're in async context (variable, destructuring, or fire-and-forget)
208
+ if (isInAsyncContext(state)) {
209
+ return scheduleAsyncVibeFunction(state, funcName, args, newValueStack, modulePath);
210
+ }
211
+
212
+ return executeVibeFunction(state, func, args, newValueStack, modulePath);
213
+ }
214
+
215
+ // Handle tool call - tools cannot be called directly from vibe scripts
216
+ // They can only be used by AI models via the tools array
217
+ if (typeof callee === 'object' && callee !== null && '__vibeTool' in callee) {
218
+ const tool = callee as VibeToolValue;
219
+ throw new Error(
220
+ `TypeError: Cannot call tool '${tool.name}' directly. Tools can only be used by AI models via the tools array in model declarations.`
221
+ );
222
+ }
223
+
224
+ // Handle core function (auto-imported, available everywhere without import)
225
+ if (typeof callee === 'object' && callee !== null && '__vibeCoreFunction' in callee) {
226
+ const funcName = (callee as { __vibeCoreFunction: boolean; name: string }).name;
227
+ const coreFunc = getCoreFunction(funcName);
228
+
229
+ if (!coreFunc) {
230
+ throw new Error(`ReferenceError: Core function '${funcName}' is not defined`);
231
+ }
232
+
233
+ // Core functions are synchronous, execute directly
234
+ const result = coreFunc(...args);
235
+
236
+ return {
237
+ ...state,
238
+ valueStack: newValueStack,
239
+ lastResult: result,
240
+ };
241
+ }
242
+
243
+ // Handle bound method call on object (built-in methods)
244
+ if (typeof callee === 'object' && callee !== null && '__boundMethod' in callee) {
245
+ const { object, method } = callee as { __boundMethod: boolean; object: unknown; method: string };
246
+ const result = executeBuiltinMethod(object, method, args);
247
+
248
+ return {
249
+ ...state,
250
+ valueStack: newValueStack,
251
+ lastResult: result,
252
+ };
253
+ }
254
+
255
+ throw new Error('TypeError: Cannot call non-function');
256
+ }
257
+
258
+ /**
259
+ * Execute a built-in method on an object.
260
+ */
261
+ function executeBuiltinMethod(object: unknown, method: string, args: unknown[]): unknown {
262
+ // Universal toString() method - works on any type
263
+ if (method === 'toString') {
264
+ if (object === null || object === undefined) {
265
+ return '';
266
+ }
267
+ if (typeof object === 'object') {
268
+ return JSON.stringify(object);
269
+ }
270
+ return String(object);
271
+ }
272
+
273
+ // Array methods
274
+ if (Array.isArray(object)) {
275
+ switch (method) {
276
+ case 'len':
277
+ return object.length;
278
+ case 'push':
279
+ if (args.length === 0) {
280
+ throw new Error('push() requires an argument');
281
+ }
282
+ object.push(args[0]);
283
+ return object; // Return the array for chaining
284
+ case 'pop':
285
+ if (object.length === 0) {
286
+ throw new Error('Cannot pop from empty array');
287
+ }
288
+ return object.pop(); // Return the removed element
289
+ default:
290
+ throw new Error(`Unknown array method: ${method}`);
291
+ }
292
+ }
293
+
294
+ // String methods
295
+ if (typeof object === 'string') {
296
+ switch (method) {
297
+ case 'len':
298
+ return object.length;
299
+ default:
300
+ throw new Error(`Unknown string method: ${method}`);
301
+ }
302
+ }
303
+
304
+ throw new Error(`Cannot call method '${method}' on ${typeof object}`);
305
+ }
@@ -0,0 +1,312 @@
1
+ // String interpolation execution for Vibe
2
+ //
3
+ // Two contexts:
4
+ // 1. Regular strings: {var} expands to value
5
+ // 2. Prompt strings (do/vibe): {var} is a reference (left as-is), !{var} expands
6
+
7
+ import type { SourceLocation } from '../../errors';
8
+ import type { RuntimeState, VibeValue } from '../types';
9
+ import { resolveValue, isVibeValue } from '../types';
10
+ import { lookupVariable } from './variables';
11
+ import { ESCAPED_LBRACE, ESCAPED_RBRACE, ESCAPED_BANG_LBRACE, unescapeBraces } from '../../parser/visitor/helpers';
12
+
13
+ // Pattern for interpolation: {var}, {obj.prop}, {arr[0]}, {arr[1:3]}, !{...}
14
+ // Captures: [1] = optional !, [2] = path (var.prop[0] etc.)
15
+ const INTERPOLATION_PATTERN = /(!?)\{(\w+(?:\.\w+|\[\d+\]|\[\d*:\d*\])*)\}/g;
16
+
17
+ // Pattern for simple variable detection (for async checking)
18
+ const SIMPLE_VAR_PATTERN = /(!?)\{(\w+)/g;
19
+
20
+ /**
21
+ * Access path element types
22
+ */
23
+ type AccessElement =
24
+ | { type: 'property'; name: string }
25
+ | { type: 'index'; value: number }
26
+ | { type: 'slice'; start: number | null; end: number | null };
27
+
28
+ /**
29
+ * Parsed interpolation reference
30
+ */
31
+ interface InterpolationRef {
32
+ varName: string;
33
+ accessPath: AccessElement[];
34
+ expand: boolean; // true for !{...}, false for {...}
35
+ fullMatch: string;
36
+ }
37
+
38
+ /**
39
+ * Parse an access path like "obj.prop[0][1:3]" into structured elements
40
+ */
41
+ function parseAccessPath(path: string): { varName: string; accessPath: AccessElement[] } {
42
+ const accessPath: AccessElement[] = [];
43
+
44
+ // Extract variable name (first identifier)
45
+ const varMatch = path.match(/^(\w+)/);
46
+ if (!varMatch) {
47
+ return { varName: path, accessPath: [] };
48
+ }
49
+
50
+ const varName = varMatch[1];
51
+ let remaining = path.slice(varName.length);
52
+
53
+ while (remaining.length > 0) {
54
+ // Property access: .propName
55
+ if (remaining.startsWith('.')) {
56
+ const propMatch = remaining.match(/^\.(\w+)/);
57
+ if (propMatch) {
58
+ accessPath.push({ type: 'property', name: propMatch[1] });
59
+ remaining = remaining.slice(propMatch[0].length);
60
+ continue;
61
+ }
62
+ }
63
+
64
+ // Index or slice access: [n] or [start:end]
65
+ if (remaining.startsWith('[')) {
66
+ // Try slice first: [start:end], [:end], [start:], [:]
67
+ const sliceMatch = remaining.match(/^\[(\d*):(\d*)\]/);
68
+ if (sliceMatch) {
69
+ accessPath.push({
70
+ type: 'slice',
71
+ start: sliceMatch[1] ? parseInt(sliceMatch[1], 10) : null,
72
+ end: sliceMatch[2] ? parseInt(sliceMatch[2], 10) : null,
73
+ });
74
+ remaining = remaining.slice(sliceMatch[0].length);
75
+ continue;
76
+ }
77
+
78
+ // Try index: [n]
79
+ const indexMatch = remaining.match(/^\[(\d+)\]/);
80
+ if (indexMatch) {
81
+ accessPath.push({ type: 'index', value: parseInt(indexMatch[1], 10) });
82
+ remaining = remaining.slice(indexMatch[0].length);
83
+ continue;
84
+ }
85
+ }
86
+
87
+ // Unknown syntax - stop parsing
88
+ break;
89
+ }
90
+
91
+ return { varName, accessPath };
92
+ }
93
+
94
+ /**
95
+ * Resolve a value following an access path
96
+ */
97
+ function resolveAccessPath(value: unknown, accessPath: AccessElement[]): unknown {
98
+ let current = value;
99
+
100
+ for (const element of accessPath) {
101
+ if (current === null || current === undefined) {
102
+ return undefined;
103
+ }
104
+
105
+ switch (element.type) {
106
+ case 'property':
107
+ current = (current as Record<string, unknown>)[element.name];
108
+ break;
109
+
110
+ case 'index':
111
+ current = (current as unknown[])[element.value];
112
+ break;
113
+
114
+ case 'slice': {
115
+ const arr = current as unknown[];
116
+ if (!Array.isArray(arr)) {
117
+ return undefined;
118
+ }
119
+ const start = element.start ?? 0;
120
+ const end = element.end ?? arr.length;
121
+ current = arr.slice(start, end);
122
+ break;
123
+ }
124
+ }
125
+ }
126
+
127
+ return current;
128
+ }
129
+
130
+ /**
131
+ * Parse all interpolation references from a template string
132
+ */
133
+ function parseInterpolations(template: string): InterpolationRef[] {
134
+ const refs: InterpolationRef[] = [];
135
+ INTERPOLATION_PATTERN.lastIndex = 0;
136
+
137
+ let match;
138
+ while ((match = INTERPOLATION_PATTERN.exec(template)) !== null) {
139
+ const [fullMatch, bang, path] = match;
140
+ const { varName, accessPath } = parseAccessPath(path);
141
+
142
+ refs.push({
143
+ varName,
144
+ accessPath,
145
+ expand: bang === '!',
146
+ fullMatch,
147
+ });
148
+ }
149
+
150
+ return refs;
151
+ }
152
+
153
+ /**
154
+ * Check for pending async variables in the template
155
+ */
156
+ function getPendingAsyncIds(state: RuntimeState, template: string): string[] {
157
+ const pendingIds: string[] = [];
158
+ SIMPLE_VAR_PATTERN.lastIndex = 0;
159
+
160
+ let match;
161
+ while ((match = SIMPLE_VAR_PATTERN.exec(template)) !== null) {
162
+ const varName = match[2];
163
+ const found = lookupVariable(state, varName);
164
+
165
+ if (found) {
166
+ const variable = found.variable;
167
+ if (isVibeValue(variable) && variable.asyncOperationId) {
168
+ const opId = variable.asyncOperationId;
169
+ const operation = state.asyncOperations.get(opId);
170
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
171
+ if (!pendingIds.includes(opId)) {
172
+ pendingIds.push(opId);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ return pendingIds;
180
+ }
181
+
182
+ /**
183
+ * Resolve a single interpolation reference to a string value
184
+ */
185
+ function resolveInterpolationToString(state: RuntimeState, ref: InterpolationRef): string {
186
+ const found = lookupVariable(state, ref.varName);
187
+
188
+ if (!found) {
189
+ // Variable not found - leave as placeholder
190
+ return ref.fullMatch;
191
+ }
192
+
193
+ // Get the raw value, resolving VibeValue if needed
194
+ let value = resolveValue(found.variable.value);
195
+
196
+ // Follow access path
197
+ if (ref.accessPath.length > 0) {
198
+ value = resolveAccessPath(value, ref.accessPath);
199
+ }
200
+
201
+ // Convert to string
202
+ if (value === undefined) {
203
+ return ref.fullMatch;
204
+ }
205
+ if (value === null) {
206
+ return 'null';
207
+ }
208
+ if (typeof value === 'object') {
209
+ return JSON.stringify(value);
210
+ }
211
+ return String(value);
212
+ }
213
+
214
+ /**
215
+ * Regular string interpolation - {var} expands to value.
216
+ * Supports: {var}, {obj.prop}, {arr[0]}, {arr[1:3]}
217
+ * !{var} is an ERROR in regular strings (caught by semantic analyzer).
218
+ */
219
+ export function execInterpolateRegularString(
220
+ state: RuntimeState,
221
+ template: string,
222
+ location?: SourceLocation
223
+ ): RuntimeState {
224
+ // Check for pending async variables
225
+ const pendingAsyncIds = getPendingAsyncIds(state, template);
226
+
227
+ if (pendingAsyncIds.length > 0) {
228
+ return {
229
+ ...state,
230
+ status: 'awaiting_async',
231
+ awaitingAsyncIds: pendingAsyncIds,
232
+ instructionStack: [
233
+ { op: 'interpolate_string', template, location: location ?? { line: 0, column: 0 } },
234
+ ...state.instructionStack,
235
+ ],
236
+ };
237
+ }
238
+
239
+ // Perform interpolation - expand all {var} patterns
240
+ const result = template.replace(INTERPOLATION_PATTERN, (fullMatch, bang, path) => {
241
+ const { varName, accessPath } = parseAccessPath(path);
242
+ const ref: InterpolationRef = { varName, accessPath, expand: true, fullMatch };
243
+ return resolveInterpolationToString(state, ref);
244
+ });
245
+
246
+ // Convert escape placeholders back to literal characters
247
+ const finalResult = unescapeBraces(result);
248
+
249
+ return { ...state, lastResult: finalResult };
250
+ }
251
+
252
+ /**
253
+ * Prompt string interpolation - {var} is reference (left as-is), !{var} expands.
254
+ * Supports: {var}, {obj.prop}, {arr[0]}, {arr[1:3]}, !{var}, !{obj.prop}, etc.
255
+ *
256
+ * Even for {var} references, we wait for async variables to resolve because:
257
+ * 1. The context system needs the resolved value to show the AI
258
+ * 2. The variable must be valid and resolved for the prompt to be meaningful
259
+ */
260
+ export function execInterpolatePromptString(
261
+ state: RuntimeState,
262
+ template: string,
263
+ location?: SourceLocation
264
+ ): RuntimeState {
265
+ // Check for pending async variables in ALL interpolations (both {} and !{})
266
+ // For {var} references, we still need the value resolved for context
267
+ const pendingAsyncIds = getPendingAsyncIds(state, template);
268
+
269
+ if (pendingAsyncIds.length > 0) {
270
+ return {
271
+ ...state,
272
+ status: 'awaiting_async',
273
+ awaitingAsyncIds: pendingAsyncIds,
274
+ instructionStack: [
275
+ { op: 'interpolate_prompt_string', template, location: location ?? { line: 0, column: 0 } },
276
+ ...state.instructionStack,
277
+ ],
278
+ };
279
+ }
280
+
281
+ // Perform interpolation
282
+ // - {var} references: leave as-is (AI will see the literal {var})
283
+ // - !{var} expansions: replace with value
284
+ const result = template.replace(INTERPOLATION_PATTERN, (fullMatch, bang, path) => {
285
+ const isExpansion = bang === '!';
286
+
287
+ if (!isExpansion) {
288
+ // {var} reference - leave as-is for AI to see
289
+ return fullMatch;
290
+ }
291
+
292
+ // !{var} expansion - replace with value
293
+ const { varName, accessPath } = parseAccessPath(path);
294
+ const ref: InterpolationRef = { varName, accessPath, expand: true, fullMatch };
295
+ return resolveInterpolationToString(state, ref);
296
+ });
297
+
298
+ // Convert escape placeholders back to literal characters
299
+ const finalResult = unescapeBraces(result);
300
+
301
+ return { ...state, lastResult: finalResult };
302
+ }
303
+
304
+ /**
305
+ * Clear prompt context flag
306
+ */
307
+ export function execClearPromptContext(state: RuntimeState): RuntimeState {
308
+ return {
309
+ ...state,
310
+ inPromptContext: false,
311
+ };
312
+ }