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