@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,354 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { createInitialState, runUntilPause } from '../index';
|
|
4
|
+
import { analyze } from '../../semantic';
|
|
5
|
+
|
|
6
|
+
describe('String Interpolation - Regular Strings', () => {
|
|
7
|
+
test('{var} expands to value in regular string', () => {
|
|
8
|
+
const ast = parse(`
|
|
9
|
+
let name = "World"
|
|
10
|
+
let greeting = "Hello {name}!"
|
|
11
|
+
`);
|
|
12
|
+
let state = createInitialState(ast);
|
|
13
|
+
state = runUntilPause(state);
|
|
14
|
+
|
|
15
|
+
expect(state.status).toBe('completed');
|
|
16
|
+
expect(state.callStack[0].locals['greeting'].value).toBe('Hello World!');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('{obj.prop} expands to property value', () => {
|
|
20
|
+
const ast = parse(`
|
|
21
|
+
let user = { name: "Alice", age: 30 }
|
|
22
|
+
let msg = "Name: {user.name}"
|
|
23
|
+
`);
|
|
24
|
+
let state = createInitialState(ast);
|
|
25
|
+
state = runUntilPause(state);
|
|
26
|
+
|
|
27
|
+
expect(state.status).toBe('completed');
|
|
28
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Name: Alice');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('{arr[0]} expands to array element', () => {
|
|
32
|
+
const ast = parse(`
|
|
33
|
+
let items = ["first", "second", "third"]
|
|
34
|
+
let msg = "First item: {items[0]}"
|
|
35
|
+
`);
|
|
36
|
+
let state = createInitialState(ast);
|
|
37
|
+
state = runUntilPause(state);
|
|
38
|
+
|
|
39
|
+
expect(state.status).toBe('completed');
|
|
40
|
+
expect(state.callStack[0].locals['msg'].value).toBe('First item: first');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('{arr[1:3]} expands to array slice', () => {
|
|
44
|
+
const ast = parse(`
|
|
45
|
+
let items = ["a", "b", "c", "d"]
|
|
46
|
+
let msg = "Middle: {items[1:3]}"
|
|
47
|
+
`);
|
|
48
|
+
let state = createInitialState(ast);
|
|
49
|
+
state = runUntilPause(state);
|
|
50
|
+
|
|
51
|
+
expect(state.status).toBe('completed');
|
|
52
|
+
// Array slice should be JSON stringified
|
|
53
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Middle: ["b","c"]');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('multiple interpolations in one string', () => {
|
|
57
|
+
const ast = parse(`
|
|
58
|
+
let first = "John"
|
|
59
|
+
let last = "Doe"
|
|
60
|
+
let full = "{first} {last}"
|
|
61
|
+
`);
|
|
62
|
+
let state = createInitialState(ast);
|
|
63
|
+
state = runUntilPause(state);
|
|
64
|
+
|
|
65
|
+
expect(state.status).toBe('completed');
|
|
66
|
+
expect(state.callStack[0].locals['full'].value).toBe('John Doe');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('private variable can be interpolated in regular strings', () => {
|
|
70
|
+
const ast = parse(`
|
|
71
|
+
let private secret = "hidden"
|
|
72
|
+
let msg = "Secret: {secret}"
|
|
73
|
+
`);
|
|
74
|
+
let state = createInitialState(ast);
|
|
75
|
+
state = runUntilPause(state);
|
|
76
|
+
|
|
77
|
+
expect(state.status).toBe('completed');
|
|
78
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Secret: hidden');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('String Interpolation - Escape Sequences', () => {
|
|
83
|
+
test('\\{var\\} produces literal {var}', () => {
|
|
84
|
+
const ast = parse(String.raw`
|
|
85
|
+
let name = "World"
|
|
86
|
+
let literal = "Use \{name\} for interpolation"
|
|
87
|
+
`);
|
|
88
|
+
let state = createInitialState(ast);
|
|
89
|
+
state = runUntilPause(state);
|
|
90
|
+
|
|
91
|
+
expect(state.status).toBe('completed');
|
|
92
|
+
expect(state.callStack[0].locals['literal'].value).toBe('Use {name} for interpolation');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('\\{ escapes opening brace', () => {
|
|
96
|
+
const ast = parse(String.raw`
|
|
97
|
+
let result = "JSON example: \{ key: value \}"
|
|
98
|
+
`);
|
|
99
|
+
let state = createInitialState(ast);
|
|
100
|
+
state = runUntilPause(state);
|
|
101
|
+
|
|
102
|
+
expect(state.status).toBe('completed');
|
|
103
|
+
expect(state.callStack[0].locals['result'].value).toBe('JSON example: { key: value }');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('\\\\ produces literal backslash', () => {
|
|
107
|
+
const ast = parse(String.raw`
|
|
108
|
+
let path = "C:\\Users\\test"
|
|
109
|
+
`);
|
|
110
|
+
let state = createInitialState(ast);
|
|
111
|
+
state = runUntilPause(state);
|
|
112
|
+
|
|
113
|
+
expect(state.status).toBe('completed');
|
|
114
|
+
expect(state.callStack[0].locals['path'].value).toBe('C:\\Users\\test');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('mixed escapes and interpolations', () => {
|
|
118
|
+
const ast = parse(String.raw`
|
|
119
|
+
let name = "World"
|
|
120
|
+
let mixed = "Hello {name}, use \{braces\} for refs"
|
|
121
|
+
`);
|
|
122
|
+
let state = createInitialState(ast);
|
|
123
|
+
state = runUntilPause(state);
|
|
124
|
+
|
|
125
|
+
expect(state.status).toBe('completed');
|
|
126
|
+
expect(state.callStack[0].locals['mixed'].value).toBe('Hello World, use {braces} for refs');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('String Interpolation - Template Literals (Unified)', () => {
|
|
131
|
+
test('backticks use {var} pattern (not ${var})', () => {
|
|
132
|
+
const ast = parse(`
|
|
133
|
+
let name = "World"
|
|
134
|
+
let greeting = \`Hello {name}!\`
|
|
135
|
+
`);
|
|
136
|
+
let state = createInitialState(ast);
|
|
137
|
+
state = runUntilPause(state);
|
|
138
|
+
|
|
139
|
+
expect(state.status).toBe('completed');
|
|
140
|
+
expect(state.callStack[0].locals['greeting'].value).toBe('Hello World!');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('multiline template with {var} interpolation', () => {
|
|
144
|
+
const ast = parse(`
|
|
145
|
+
let name = "Alice"
|
|
146
|
+
let msg = \`Hello {name},
|
|
147
|
+
Welcome to our app!\`
|
|
148
|
+
`);
|
|
149
|
+
let state = createInitialState(ast);
|
|
150
|
+
state = runUntilPause(state);
|
|
151
|
+
|
|
152
|
+
expect(state.status).toBe('completed');
|
|
153
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Hello Alice,\nWelcome to our app!');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('template literal with property access', () => {
|
|
157
|
+
const ast = parse(`
|
|
158
|
+
let user = { name: "Bob" }
|
|
159
|
+
let msg = \`User: {user.name}\`
|
|
160
|
+
`);
|
|
161
|
+
let state = createInitialState(ast);
|
|
162
|
+
state = runUntilPause(state);
|
|
163
|
+
|
|
164
|
+
expect(state.status).toBe('completed');
|
|
165
|
+
expect(state.callStack[0].locals['msg'].value).toBe('User: Bob');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('String Interpolation - Semantic Validation', () => {
|
|
170
|
+
test('!{var} in regular string produces semantic error', () => {
|
|
171
|
+
const ast = parse(`
|
|
172
|
+
let name = "World"
|
|
173
|
+
let msg = "Hello !{name}"
|
|
174
|
+
`);
|
|
175
|
+
const errors = analyze(ast);
|
|
176
|
+
|
|
177
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
178
|
+
expect(errors[0].message).toContain('Expansion syntax !{name} is only valid in prompt strings');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('undefined variable in interpolation produces error', () => {
|
|
182
|
+
const ast = parse(`
|
|
183
|
+
let msg = "Hello {unknown}!"
|
|
184
|
+
`);
|
|
185
|
+
const errors = analyze(ast);
|
|
186
|
+
|
|
187
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
188
|
+
expect(errors[0].message).toContain("'unknown' is not defined");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('String Interpolation - Prompt-Typed Variables', () => {
|
|
193
|
+
test('prompt variable {var} leaves literal in string', () => {
|
|
194
|
+
const ast = parse(`
|
|
195
|
+
let name = "World"
|
|
196
|
+
let p: prompt = "Greet {name}"
|
|
197
|
+
`);
|
|
198
|
+
let state = createInitialState(ast);
|
|
199
|
+
state = runUntilPause(state);
|
|
200
|
+
|
|
201
|
+
expect(state.status).toBe('completed');
|
|
202
|
+
// In prompt context, {var} is left as-is (reference, not expansion)
|
|
203
|
+
expect(state.callStack[0].locals['p'].value).toBe('Greet {name}');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('prompt variable !{var} expands to value', () => {
|
|
207
|
+
const ast = parse(`
|
|
208
|
+
let name = "World"
|
|
209
|
+
let p: prompt = "Greet !{name}"
|
|
210
|
+
`);
|
|
211
|
+
let state = createInitialState(ast);
|
|
212
|
+
state = runUntilPause(state);
|
|
213
|
+
|
|
214
|
+
expect(state.status).toBe('completed');
|
|
215
|
+
expect(state.callStack[0].locals['p'].value).toBe('Greet World');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('const prompt variable works the same', () => {
|
|
219
|
+
const ast = parse(`
|
|
220
|
+
let target = "user"
|
|
221
|
+
const PROMPT: prompt = "Help the {target}"
|
|
222
|
+
`);
|
|
223
|
+
let state = createInitialState(ast);
|
|
224
|
+
state = runUntilPause(state);
|
|
225
|
+
|
|
226
|
+
expect(state.status).toBe('completed');
|
|
227
|
+
expect(state.callStack[0].locals['PROMPT'].value).toBe('Help the {target}');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('String Interpolation - Complex Access Paths', () => {
|
|
232
|
+
test('{obj.nested.prop} deep property access', () => {
|
|
233
|
+
const ast = parse(`
|
|
234
|
+
let data = { user: { name: "Alice" } }
|
|
235
|
+
let msg = "Name: {data.user.name}"
|
|
236
|
+
`);
|
|
237
|
+
let state = createInitialState(ast);
|
|
238
|
+
state = runUntilPause(state);
|
|
239
|
+
|
|
240
|
+
expect(state.status).toBe('completed');
|
|
241
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Name: Alice');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('{arr[0].prop} array element property access', () => {
|
|
245
|
+
const ast = parse(`
|
|
246
|
+
let users = [{ name: "Alice" }, { name: "Bob" }]
|
|
247
|
+
let msg = "First: {users[0].name}"
|
|
248
|
+
`);
|
|
249
|
+
let state = createInitialState(ast);
|
|
250
|
+
state = runUntilPause(state);
|
|
251
|
+
|
|
252
|
+
expect(state.status).toBe('completed');
|
|
253
|
+
expect(state.callStack[0].locals['msg'].value).toBe('First: Alice');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('{obj.arr[1]} property then index access', () => {
|
|
257
|
+
const ast = parse(`
|
|
258
|
+
let data = { items: ["a", "b", "c"] }
|
|
259
|
+
let msg = "Second: {data.items[1]}"
|
|
260
|
+
`);
|
|
261
|
+
let state = createInitialState(ast);
|
|
262
|
+
state = runUntilPause(state);
|
|
263
|
+
|
|
264
|
+
expect(state.status).toBe('completed');
|
|
265
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Second: b');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('slice with only end index {arr[:2]}', () => {
|
|
269
|
+
const ast = parse(`
|
|
270
|
+
let items = ["a", "b", "c", "d"]
|
|
271
|
+
let msg = "First two: {items[:2]}"
|
|
272
|
+
`);
|
|
273
|
+
let state = createInitialState(ast);
|
|
274
|
+
state = runUntilPause(state);
|
|
275
|
+
|
|
276
|
+
expect(state.status).toBe('completed');
|
|
277
|
+
expect(state.callStack[0].locals['msg'].value).toBe('First two: ["a","b"]');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('slice with only start index {arr[2:]}', () => {
|
|
281
|
+
const ast = parse(`
|
|
282
|
+
let items = ["a", "b", "c", "d"]
|
|
283
|
+
let msg = "From third: {items[2:]}"
|
|
284
|
+
`);
|
|
285
|
+
let state = createInitialState(ast);
|
|
286
|
+
state = runUntilPause(state);
|
|
287
|
+
|
|
288
|
+
expect(state.status).toBe('completed');
|
|
289
|
+
expect(state.callStack[0].locals['msg'].value).toBe('From third: ["c","d"]');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('String Interpolation - Edge Cases', () => {
|
|
294
|
+
test('undefined property returns placeholder', () => {
|
|
295
|
+
const ast = parse(`
|
|
296
|
+
let user = { name: "Alice" }
|
|
297
|
+
let msg = "Age: {user.age}"
|
|
298
|
+
`);
|
|
299
|
+
let state = createInitialState(ast);
|
|
300
|
+
state = runUntilPause(state);
|
|
301
|
+
|
|
302
|
+
expect(state.status).toBe('completed');
|
|
303
|
+
// Undefined property keeps the placeholder
|
|
304
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Age: {user.age}');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('null value interpolates as "null"', () => {
|
|
308
|
+
const ast = parse(`
|
|
309
|
+
let x: text = null
|
|
310
|
+
let msg = "Value: {x}"
|
|
311
|
+
`);
|
|
312
|
+
let state = createInitialState(ast);
|
|
313
|
+
state = runUntilPause(state);
|
|
314
|
+
|
|
315
|
+
expect(state.status).toBe('completed');
|
|
316
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Value: null');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('number interpolation', () => {
|
|
320
|
+
const ast = parse(`
|
|
321
|
+
let count = 42
|
|
322
|
+
let msg = "Count: {count}"
|
|
323
|
+
`);
|
|
324
|
+
let state = createInitialState(ast);
|
|
325
|
+
state = runUntilPause(state);
|
|
326
|
+
|
|
327
|
+
expect(state.status).toBe('completed');
|
|
328
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Count: 42');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('boolean interpolation', () => {
|
|
332
|
+
const ast = parse(`
|
|
333
|
+
let flag = true
|
|
334
|
+
let msg = "Flag: {flag}"
|
|
335
|
+
`);
|
|
336
|
+
let state = createInitialState(ast);
|
|
337
|
+
state = runUntilPause(state);
|
|
338
|
+
|
|
339
|
+
expect(state.status).toBe('completed');
|
|
340
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Flag: true');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('object interpolation (JSON stringify)', () => {
|
|
344
|
+
const ast = parse(`
|
|
345
|
+
let obj = { a: 1, b: 2 }
|
|
346
|
+
let msg = "Object: {obj}"
|
|
347
|
+
`);
|
|
348
|
+
let state = createInitialState(ast);
|
|
349
|
+
state = runUntilPause(state);
|
|
350
|
+
|
|
351
|
+
expect(state.status).toBe('completed');
|
|
352
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Object: {"a":1,"b":2}');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { createInitialState, runUntilPause } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Runtime - Template Literals', () => {
|
|
6
|
+
test('basic template literal without interpolation', () => {
|
|
7
|
+
const ast = parse('let x = `hello world`');
|
|
8
|
+
let state = createInitialState(ast);
|
|
9
|
+
state = runUntilPause(state);
|
|
10
|
+
|
|
11
|
+
expect(state.status).toBe('completed');
|
|
12
|
+
expect(state.callStack[0].locals['x'].value).toBe('hello world');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('template literal with {var} interpolation', () => {
|
|
16
|
+
const ast = parse(`
|
|
17
|
+
let name = "World"
|
|
18
|
+
let greeting = \`Hello {name}!\`
|
|
19
|
+
`);
|
|
20
|
+
let state = createInitialState(ast);
|
|
21
|
+
state = runUntilPause(state);
|
|
22
|
+
|
|
23
|
+
expect(state.status).toBe('completed');
|
|
24
|
+
expect(state.callStack[0].locals['greeting'].value).toBe('Hello World!');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('template literal with multiple interpolations', () => {
|
|
28
|
+
const ast = parse(`
|
|
29
|
+
let first = "John"
|
|
30
|
+
let last = "Doe"
|
|
31
|
+
let full = \`{first} {last}\`
|
|
32
|
+
`);
|
|
33
|
+
let state = createInitialState(ast);
|
|
34
|
+
state = runUntilPause(state);
|
|
35
|
+
|
|
36
|
+
expect(state.status).toBe('completed');
|
|
37
|
+
expect(state.callStack[0].locals['full'].value).toBe('John Doe');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('template literal multiline preserved', () => {
|
|
41
|
+
const ast = parse(`let x = \`line1
|
|
42
|
+
line2
|
|
43
|
+
line3\``);
|
|
44
|
+
let state = createInitialState(ast);
|
|
45
|
+
state = runUntilPause(state);
|
|
46
|
+
|
|
47
|
+
expect(state.status).toBe('completed');
|
|
48
|
+
expect(state.callStack[0].locals['x'].value).toBe('line1\nline2\nline3');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('template literal with multiline and interpolation', () => {
|
|
52
|
+
const ast = parse(`
|
|
53
|
+
let name = "Alice"
|
|
54
|
+
let msg = \`Hello {name},
|
|
55
|
+
Welcome to our app!\`
|
|
56
|
+
`);
|
|
57
|
+
let state = createInitialState(ast);
|
|
58
|
+
state = runUntilPause(state);
|
|
59
|
+
|
|
60
|
+
expect(state.status).toBe('completed');
|
|
61
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Hello Alice,\nWelcome to our app!');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('template literal undefined property keeps placeholder', () => {
|
|
65
|
+
const ast = parse(`
|
|
66
|
+
let user = { name: "Alice" }
|
|
67
|
+
let x = \`Hello {user.unknown}!\`
|
|
68
|
+
`);
|
|
69
|
+
let state = createInitialState(ast);
|
|
70
|
+
state = runUntilPause(state);
|
|
71
|
+
|
|
72
|
+
expect(state.status).toBe('completed');
|
|
73
|
+
// Undefined property keeps the placeholder
|
|
74
|
+
expect(state.callStack[0].locals['x'].value).toBe('Hello {user.unknown}!');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('template literal in function with scope chain', () => {
|
|
78
|
+
const ast = parse(`
|
|
79
|
+
let greeting = "Hello"
|
|
80
|
+
function greet(name: text): text {
|
|
81
|
+
return \`{greeting}, {name}!\`
|
|
82
|
+
}
|
|
83
|
+
let result = greet("World")
|
|
84
|
+
`);
|
|
85
|
+
let state = createInitialState(ast);
|
|
86
|
+
state = runUntilPause(state);
|
|
87
|
+
|
|
88
|
+
expect(state.status).toBe('completed');
|
|
89
|
+
expect(state.callStack[0].locals['result'].value).toBe('Hello, World!');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('template literal shadowing in function', () => {
|
|
93
|
+
const ast = parse(`
|
|
94
|
+
let name = "Global"
|
|
95
|
+
function greet() {
|
|
96
|
+
let name = "Local"
|
|
97
|
+
return \`Hello {name}!\`
|
|
98
|
+
}
|
|
99
|
+
let result = greet()
|
|
100
|
+
`);
|
|
101
|
+
let state = createInitialState(ast);
|
|
102
|
+
state = runUntilPause(state);
|
|
103
|
+
|
|
104
|
+
expect(state.status).toBe('completed');
|
|
105
|
+
expect(state.callStack[0].locals['result'].value).toBe('Hello Local!');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('template literal with object value (JSON stringify)', () => {
|
|
109
|
+
const ast = parse(`
|
|
110
|
+
let data: json = { name: "test" }
|
|
111
|
+
let msg = \`Data: {data}\`
|
|
112
|
+
`);
|
|
113
|
+
let state = createInitialState(ast);
|
|
114
|
+
state = runUntilPause(state);
|
|
115
|
+
|
|
116
|
+
expect(state.status).toBe('completed');
|
|
117
|
+
// Objects get JSON stringified
|
|
118
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Data: {"name":"test"}');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('template literal with boolean value', () => {
|
|
122
|
+
const ast = parse(`
|
|
123
|
+
let flag = true
|
|
124
|
+
let msg = \`Flag is {flag}\`
|
|
125
|
+
`);
|
|
126
|
+
let state = createInitialState(ast);
|
|
127
|
+
state = runUntilPause(state);
|
|
128
|
+
|
|
129
|
+
expect(state.status).toBe('completed');
|
|
130
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Flag is true');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('regular string uses same {var} syntax as template literals', () => {
|
|
134
|
+
const ast = parse(`
|
|
135
|
+
let name = "World"
|
|
136
|
+
let greeting = "Hello {name}!"
|
|
137
|
+
`);
|
|
138
|
+
let state = createInitialState(ast);
|
|
139
|
+
state = runUntilPause(state);
|
|
140
|
+
|
|
141
|
+
expect(state.status).toBe('completed');
|
|
142
|
+
expect(state.callStack[0].locals['greeting'].value).toBe('Hello World!');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('template literal uses unified {var} syntax', () => {
|
|
146
|
+
const ast = parse(`
|
|
147
|
+
let name = "World"
|
|
148
|
+
let greeting = \`Hello {name}!\`
|
|
149
|
+
`);
|
|
150
|
+
let state = createInitialState(ast);
|
|
151
|
+
state = runUntilPause(state);
|
|
152
|
+
|
|
153
|
+
expect(state.status).toBe('completed');
|
|
154
|
+
// {name} IS interpolated in template literals (unified syntax)
|
|
155
|
+
expect(state.callStack[0].locals['greeting'].value).toBe('Hello World!');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('template literal with escaped braces', () => {
|
|
159
|
+
const ast = parse(String.raw`
|
|
160
|
+
let name = "World"
|
|
161
|
+
let msg = ` + '`Use \\{name\\} for interpolation`' + `
|
|
162
|
+
`);
|
|
163
|
+
let state = createInitialState(ast);
|
|
164
|
+
state = runUntilPause(state);
|
|
165
|
+
|
|
166
|
+
expect(state.status).toBe('completed');
|
|
167
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Use {name} for interpolation');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('dollar sign in template literal is literal', () => {
|
|
171
|
+
// Use regular string parsing to include literal $ in Vibe code
|
|
172
|
+
// In Vibe: `Price: ${price}` - the $ is literal, {price} expands
|
|
173
|
+
const ast = parse('let price = 100\nlet msg = `Price: \\${price}`');
|
|
174
|
+
let state = createInitialState(ast);
|
|
175
|
+
state = runUntilPause(state);
|
|
176
|
+
|
|
177
|
+
expect(state.status).toBe('completed');
|
|
178
|
+
// ${price} is NOT valid interpolation syntax - $ is literal, {price} gets expanded
|
|
179
|
+
expect(state.callStack[0].locals['msg'].value).toBe('Price: $100');
|
|
180
|
+
});
|
|
181
|
+
});
|