@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,88 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { parse } from '../../parser/parse';
5
+ import { createInitialState } from '../state';
6
+ import { runWithMockAI } from './helpers';
7
+
8
+ // Get the package root directory (relative to this test file)
9
+ // src/runtime/test -> runtime -> src -> runtime (package root)
10
+ const packageRoot = join(import.meta.dir, '..', '..', '..');
11
+
12
+ // Helper to load and run a vibe script file
13
+ function runVibeScript(
14
+ filename: string,
15
+ aiMockResponses: string | Record<string, string>
16
+ ) {
17
+ const scriptPath = join(packageRoot, 'tests', 'fixtures', filename);
18
+ const source = readFileSync(scriptPath, 'utf-8');
19
+ const ast = parse(source);
20
+ const state = createInitialState(ast);
21
+ return runWithMockAI(state, aiMockResponses);
22
+ }
23
+
24
+ describe('End-to-End Vibe Scripts', () => {
25
+ test('simple-greeting.vibe - basic AI call with interpolation', () => {
26
+ // With unified interpolation, {name} in prompt strings is left as a reference
27
+ // The AI sees the literal {name} and gets the value through context
28
+ const finalState = runVibeScript('simple-greeting.vibe', {
29
+ 'Generate a friendly greeting for {name}': 'Hello Alice! Welcome!',
30
+ });
31
+
32
+ expect(finalState.status).toBe('completed');
33
+ // AI results are AIResultObject - access .value for primitive
34
+ expect(finalState.lastResult.value).toBe('Hello Alice! Welcome!');
35
+ });
36
+
37
+ test('function-call.vibe - functions with multiple AI calls', () => {
38
+ // First prompt: aiPrompt is a regular string variable, {topic} expands to value
39
+ // Second prompt: directly in vibe expression, {content} is left as reference
40
+ const finalState = runVibeScript('function-call.vibe', {
41
+ 'Write a short story about a brave knight':
42
+ 'Once upon a time, a brave knight saved the kingdom.',
43
+ 'Summarize this: {content}': 'Knight saves kingdom.',
44
+ });
45
+
46
+ expect(finalState.status).toBe('completed');
47
+ // AI results are AIResultObject - access .value for primitive
48
+ expect(finalState.lastResult.value).toBe('Knight saves kingdom.');
49
+ });
50
+
51
+ test('conditional-logic.vibe - if statement with AI call', () => {
52
+ const finalState = runVibeScript('conditional-logic.vibe', {
53
+ 'Generate a premium greeting': 'Welcome, valued premium member!',
54
+ });
55
+
56
+ expect(finalState.status).toBe('completed');
57
+ // VibeValue: access .value for the primitive
58
+ expect(finalState.lastResult.value).toBe('Welcome, valued premium member!');
59
+ });
60
+
61
+ test('template-literals.vibe - template literal interpolation with AI', () => {
62
+ // {fullName} is left as reference in prompt (vibe expression)
63
+ const finalState = runVibeScript('template-literals.vibe', {
64
+ 'Generate a welcome message for {fullName}':
65
+ 'Welcome to our platform, John Doe!',
66
+ });
67
+
68
+ expect(finalState.status).toBe('completed');
69
+ // AI results are AIResultObject - access .value for primitive
70
+ expect(finalState.lastResult.value).toBe('Welcome to our platform, John Doe!');
71
+ });
72
+
73
+ test('multiple-ai-calls.vibe - sequential AI calls with data flow', () => {
74
+ // {topic}, {overview}, {details} are left as references in prompts
75
+ const finalState = runVibeScript('multiple-ai-calls.vibe', {
76
+ 'Give a one-sentence overview of {topic}':
77
+ 'Machine learning is AI that learns from data.',
78
+ 'Expand on this: {overview}':
79
+ 'Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.',
80
+ 'Summarize in 5 words: {details}':
81
+ 'AI learns from data automatically',
82
+ });
83
+
84
+ expect(finalState.status).toBe('completed');
85
+ // AI results are AIResultObject - access .value for primitive
86
+ expect(finalState.lastResult.value).toBe('AI learns from data automatically');
87
+ });
88
+ });
@@ -0,0 +1,80 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { resolve, dirname } from 'path';
3
+ import { parse } from '../../../parser/parse';
4
+ import { Runtime, type AIProvider } from '../../index';
5
+ import { loadImports } from '../../modules';
6
+ import { createInitialState } from '../../state';
7
+ import { runUntilPause } from '../../step';
8
+
9
+ const testDir = dirname(import.meta.path);
10
+
11
+ // Mock AI provider for testing
12
+ const mockProvider: AIProvider = {
13
+ execute: async () => ({ value: '' }),
14
+ generateCode: async () => ({ value: '' }),
15
+ askUser: async () => '',
16
+ };
17
+
18
+ describe('Runtime Error Locations with File Paths', () => {
19
+ test('error in main file shows correct file and line', async () => {
20
+ const filePath = resolve(testDir, 'main-error.vibe');
21
+ const source = await Bun.file(filePath).text();
22
+ const ast = parse(source, { file: filePath });
23
+
24
+ const runtime = new Runtime(ast, mockProvider, { basePath: filePath });
25
+
26
+ try {
27
+ await runtime.run();
28
+ expect.unreachable('Should have thrown');
29
+ } catch (e) {
30
+ expect(e).toBeInstanceOf(Error);
31
+ const err = e as Error & { location?: { line: number; column: number; file?: string } };
32
+ expect(err.location).toBeDefined();
33
+ expect(err.location?.line).toBe(3); // let bad: boolean = "not a boolean"
34
+ expect(err.location?.file).toBe(filePath);
35
+ }
36
+ });
37
+
38
+ test('error in imported file shows correct file and line', async () => {
39
+ const mainPath = resolve(testDir, 'main-import-error.vibe');
40
+ const source = await Bun.file(mainPath).text();
41
+ const ast = parse(source, { file: mainPath });
42
+
43
+ // Load imports
44
+ let state = createInitialState(ast);
45
+ state = await loadImports(state, mainPath);
46
+
47
+ // Run until error
48
+ state = runUntilPause(state);
49
+
50
+ expect(state.status).toBe('error');
51
+ expect(state.error).toContain('expected number');
52
+
53
+ // Check the error object has the correct location
54
+ const err = state.errorObject as Error & { location?: { line: number; column: number; file?: string } };
55
+ expect(err).toBeDefined();
56
+ expect(err.location).toBeDefined();
57
+ expect(err.location?.line).toBe(3); // let invalid: number = "not a number"
58
+ expect(err.location?.file).toBe('./utils/helper.vibe');
59
+ });
60
+
61
+ test('error format() includes file path', async () => {
62
+ const filePath = resolve(testDir, 'main-error.vibe');
63
+ const source = await Bun.file(filePath).text();
64
+ const ast = parse(source, { file: filePath });
65
+
66
+ const runtime = new Runtime(ast, mockProvider, { basePath: filePath });
67
+
68
+ try {
69
+ await runtime.run();
70
+ expect.unreachable('Should have thrown');
71
+ } catch (e) {
72
+ const err = e as Error & { format?: () => string };
73
+ expect(err.format).toBeDefined();
74
+ const formatted = err.format!();
75
+ // Should include file path and line number
76
+ expect(formatted).toContain(filePath);
77
+ expect(formatted).toContain(':3:');
78
+ }
79
+ });
80
+ });
@@ -0,0 +1,4 @@
1
+ let first = "ok"
2
+ let second = "also ok"
3
+ let bad: boolean = "not a boolean"
4
+ let fourth = "never reached"
@@ -0,0 +1,3 @@
1
+ import { processData } from "./utils/helper.vibe"
2
+
3
+ let result = processData()
@@ -0,0 +1,5 @@
1
+ export function processData(): text {
2
+ let valid = "some text"
3
+ let invalid: number = "not a number"
4
+ return valid
5
+ }
@@ -0,0 +1,312 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { createInitialState, runUntilPause } from '../index';
4
+
5
+ describe('Runtime For-In Loop', () => {
6
+ // ============================================================================
7
+ // Basic array iteration
8
+ // ============================================================================
9
+
10
+ test('for-in iterates over string array', () => {
11
+ const ast = parse(`
12
+ let items = ["a", "b", "c"]
13
+ let result = ""
14
+ for item in items {
15
+ result = result
16
+ }
17
+ `);
18
+ let state = createInitialState(ast);
19
+ state = runUntilPause(state);
20
+
21
+ expect(state.status).toBe('completed');
22
+ });
23
+
24
+ test('for-in iterates over number array', () => {
25
+ const ast = parse(`
26
+ let sum: number = 0
27
+ for n in [1, 2, 3] {
28
+ sum = ts(sum, n) { return sum + n }
29
+ }
30
+ `);
31
+ let state = createInitialState(ast);
32
+ state = runUntilPause(state);
33
+
34
+ expect(state.status).toBe('awaiting_ts');
35
+ });
36
+
37
+ // ============================================================================
38
+ // Range iteration with .. operator
39
+ // ============================================================================
40
+
41
+ test('range operator 2..5 creates [2,3,4,5]', () => {
42
+ const ast = parse(`
43
+ let range = 2..5
44
+ `);
45
+ let state = createInitialState(ast);
46
+ state = runUntilPause(state);
47
+
48
+ expect(state.status).toBe('completed');
49
+ const frame = state.callStack[0];
50
+ expect(frame.locals['range'].value).toEqual([2, 3, 4, 5]);
51
+ });
52
+
53
+ test('range operator with variables', () => {
54
+ const ast = parse(`
55
+ let start: number = 1
56
+ let end: number = 3
57
+ let range = start..end
58
+ `);
59
+ let state = createInitialState(ast);
60
+ state = runUntilPause(state);
61
+
62
+ expect(state.status).toBe('completed');
63
+ const frame = state.callStack[0];
64
+ expect(frame.locals['range'].value).toEqual([1, 2, 3]);
65
+ });
66
+
67
+ test('range operator with negative to positive', () => {
68
+ const ast = parse(`
69
+ let range = -3..4
70
+ `);
71
+ let state = createInitialState(ast);
72
+ state = runUntilPause(state);
73
+
74
+ expect(state.status).toBe('completed');
75
+ const frame = state.callStack[0];
76
+ expect(frame.locals['range'].value).toEqual([-3, -2, -1, 0, 1, 2, 3, 4]);
77
+ });
78
+
79
+ test('range operator with negative to negative', () => {
80
+ const ast = parse(`
81
+ let range = -5..-2
82
+ `);
83
+ let state = createInitialState(ast);
84
+ state = runUntilPause(state);
85
+
86
+ expect(state.status).toBe('completed');
87
+ const frame = state.callStack[0];
88
+ expect(frame.locals['range'].value).toEqual([-5, -4, -3, -2]);
89
+ });
90
+
91
+ test('range operator with variable bounds (start > end at runtime) produces empty array', () => {
92
+ // When bounds are variables, we can't check at compile time
93
+ // so runtime produces empty array for start > end
94
+ const ast = parse(`
95
+ let start: number = 5
96
+ let end: number = 2
97
+ let range = start..end
98
+ `);
99
+ let state = createInitialState(ast);
100
+ state = runUntilPause(state);
101
+
102
+ expect(state.status).toBe('completed');
103
+ const frame = state.callStack[0];
104
+ expect(frame.locals['range'].value).toEqual([]);
105
+ });
106
+
107
+ test('range operator same start and end produces single element', () => {
108
+ const ast = parse(`
109
+ let range = 3..3
110
+ `);
111
+ let state = createInitialState(ast);
112
+ state = runUntilPause(state);
113
+
114
+ expect(state.status).toBe('completed');
115
+ const frame = state.callStack[0];
116
+ expect(frame.locals['range'].value).toEqual([3]);
117
+ });
118
+
119
+ test('for-in with range operator', () => {
120
+ const ast = parse(`
121
+ let visited = false
122
+ for i in 1..3 {
123
+ visited = true
124
+ }
125
+ `);
126
+ let state = createInitialState(ast);
127
+ state = runUntilPause(state);
128
+
129
+ expect(state.status).toBe('completed');
130
+ const frame = state.callStack[0];
131
+ expect(frame.locals['visited'].value).toBe(true);
132
+ });
133
+
134
+ // ============================================================================
135
+ // Range iteration with single number (for i in N)
136
+ // ============================================================================
137
+
138
+ test('for-in with number creates inclusive range (1 to N)', () => {
139
+ const ast = parse(`
140
+ let count: number = 0
141
+ for i in 3 {
142
+ count = ts(count) { return count + 1 }
143
+ }
144
+ `);
145
+ let state = createInitialState(ast);
146
+ state = runUntilPause(state);
147
+
148
+ // First iteration pauses for ts eval
149
+ expect(state.status).toBe('awaiting_ts');
150
+ });
151
+
152
+ test('for-in with zero iterations', () => {
153
+ const ast = parse(`
154
+ let executed = false
155
+ for i in 0 {
156
+ executed = true
157
+ }
158
+ `);
159
+ let state = createInitialState(ast);
160
+ state = runUntilPause(state);
161
+
162
+ expect(state.status).toBe('completed');
163
+ expect(state.callStack[0].locals['executed'].value).toBe(false);
164
+ });
165
+
166
+ // ============================================================================
167
+ // Loop variable scoping
168
+ // ============================================================================
169
+
170
+ test('loop variable is accessible inside loop', () => {
171
+ const ast = parse(`
172
+ let captured = ""
173
+ for item in ["test"] {
174
+ captured = item
175
+ }
176
+ `);
177
+ let state = createInitialState(ast);
178
+ state = runUntilPause(state);
179
+
180
+ expect(state.status).toBe('completed');
181
+ expect(state.callStack[0].locals['captured'].value).toBe('test');
182
+ });
183
+
184
+ test('loop variable is cleaned up after loop', () => {
185
+ const ast = parse(`
186
+ for item in ["a", "b"] {
187
+ let temp = item
188
+ }
189
+ let outside = "done"
190
+ `);
191
+ let state = createInitialState(ast);
192
+ state = runUntilPause(state);
193
+
194
+ expect(state.status).toBe('completed');
195
+ // 'item' and 'temp' should not be visible
196
+ expect(state.callStack[0].locals['item']).toBeUndefined();
197
+ expect(state.callStack[0].locals['temp']).toBeUndefined();
198
+ expect(state.callStack[0].locals['outside'].value).toBe('done');
199
+ });
200
+
201
+ // ============================================================================
202
+ // Empty array
203
+ // ============================================================================
204
+
205
+ test('for-in with empty array does nothing', () => {
206
+ const ast = parse(`
207
+ let executed = false
208
+ for item in [] {
209
+ executed = true
210
+ }
211
+ `);
212
+ let state = createInitialState(ast);
213
+ state = runUntilPause(state);
214
+
215
+ expect(state.status).toBe('completed');
216
+ expect(state.callStack[0].locals['executed'].value).toBe(false);
217
+ });
218
+
219
+ // ============================================================================
220
+ // Nested loops
221
+ // ============================================================================
222
+
223
+ test('nested for-in loops', () => {
224
+ const ast = parse(`
225
+ let count: number = 0
226
+ for i in 2 {
227
+ for j in 2 {
228
+ count = ts(count) { return count + 1 }
229
+ }
230
+ }
231
+ `);
232
+ let state = createInitialState(ast);
233
+ state = runUntilPause(state);
234
+
235
+ // First iteration of inner loop pauses for ts eval
236
+ expect(state.status).toBe('awaiting_ts');
237
+ });
238
+ });
239
+
240
+ describe('Runtime For-In Error Cases', () => {
241
+ test('for-in with non-integer range throws error', () => {
242
+ const ast = parse(`
243
+ for i in 3.5 {
244
+ let x = i
245
+ }
246
+ `);
247
+ let state = createInitialState(ast);
248
+ state = runUntilPause(state);
249
+
250
+ expect(state.status).toBe('error');
251
+ expect(state.error).toContain('integer');
252
+ });
253
+
254
+ test('for-in with negative range throws error', () => {
255
+ const ast = parse(`
256
+ for i in -3 {
257
+ let x = i
258
+ }
259
+ `);
260
+ let state = createInitialState(ast);
261
+ state = runUntilPause(state);
262
+
263
+ expect(state.status).toBe('error');
264
+ expect(state.error).toContain('non-negative');
265
+ });
266
+
267
+ test('for-in with non-integer range bounds throws error', () => {
268
+ const ast = parse(`
269
+ for i in 1.5..5 {
270
+ let x = i
271
+ }
272
+ `);
273
+ let state = createInitialState(ast);
274
+ state = runUntilPause(state);
275
+
276
+ expect(state.status).toBe('error');
277
+ expect(state.error).toContain('integer');
278
+ });
279
+
280
+ test('[x, y] is treated as a plain array, not a range', () => {
281
+ // Explicit array literal [2, 5] should iterate as a 2-element array,
282
+ // NOT be interpreted as a range 2..5 = [2,3,4,5]
283
+ // The .. operator now handles ranges explicitly
284
+ const ast = parse(`
285
+ let visited = false
286
+ for i in [2, 5] {
287
+ visited = true
288
+ }
289
+ `);
290
+ let state = createInitialState(ast);
291
+ state = runUntilPause(state);
292
+
293
+ expect(state.status).toBe('completed');
294
+ const frame = state.callStack[0];
295
+ expect(frame.locals['visited'].value).toBe(true);
296
+ });
297
+
298
+ test('for-in with string throws error', () => {
299
+ const ast = parse(`
300
+ let x = "not an array"
301
+ for i in x {
302
+ let y = i
303
+ }
304
+ `);
305
+ let state = createInitialState(ast);
306
+ state = runUntilPause(state);
307
+
308
+ expect(state.status).toBe('error');
309
+ expect(state.error).toContain('Cannot iterate');
310
+ expect(state.error).toContain('string');
311
+ });
312
+ });
@@ -0,0 +1,69 @@
1
+ import type { RuntimeState } from '../types';
2
+ import { resumeWithAIResponse, resumeWithUserInput } from '../state';
3
+ import { runUntilPause } from '../step';
4
+
5
+ // Create a mock AI runner that responds with predefined responses
6
+ export function createMockAIRunner(responses: Record<string, string> | string) {
7
+ return function mockAI(state: RuntimeState): RuntimeState {
8
+ if (state.status !== 'awaiting_ai') return state;
9
+
10
+ const response =
11
+ typeof responses === 'string'
12
+ ? responses
13
+ : responses[state.pendingAI?.prompt ?? ''] ?? 'mock response';
14
+
15
+ return resumeWithAIResponse(state, response);
16
+ };
17
+ }
18
+
19
+ // Create a mock user input runner
20
+ export function createMockUserRunner(responses: Record<string, string> | string) {
21
+ return function mockUser(state: RuntimeState): RuntimeState {
22
+ if (state.status !== 'awaiting_user') return state;
23
+
24
+ const response =
25
+ typeof responses === 'string'
26
+ ? responses
27
+ : responses[state.pendingAI?.prompt ?? ''] ?? 'mock input';
28
+
29
+ return resumeWithUserInput(state, response);
30
+ };
31
+ }
32
+
33
+ // Run a program with mock AI responses until completion or error
34
+ export function runWithMockAI(
35
+ state: RuntimeState,
36
+ mockResponse: string | Record<string, string>
37
+ ): RuntimeState {
38
+ const mockAI = createMockAIRunner(mockResponse);
39
+ let current = runUntilPause(state);
40
+
41
+ while (current.status === 'awaiting_ai') {
42
+ current = mockAI(current);
43
+ current = runUntilPause(current);
44
+ }
45
+
46
+ return current;
47
+ }
48
+
49
+ // Run a program with both mock AI and user responses
50
+ export function runWithMocks(
51
+ state: RuntimeState,
52
+ aiResponses: string | Record<string, string>,
53
+ userResponses: string | Record<string, string>
54
+ ): RuntimeState {
55
+ const mockAI = createMockAIRunner(aiResponses);
56
+ const mockUser = createMockUserRunner(userResponses);
57
+ let current = runUntilPause(state);
58
+
59
+ while (current.status === 'awaiting_ai' || current.status === 'awaiting_user') {
60
+ if (current.status === 'awaiting_ai') {
61
+ current = mockAI(current);
62
+ } else {
63
+ current = mockUser(current);
64
+ }
65
+ current = runUntilPause(current);
66
+ }
67
+
68
+ return current;
69
+ }