@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,466 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import {
|
|
4
|
+
createInitialState,
|
|
5
|
+
step,
|
|
6
|
+
runUntilPause,
|
|
7
|
+
buildLocalContext,
|
|
8
|
+
buildGlobalContext,
|
|
9
|
+
stepUntilCondition,
|
|
10
|
+
type ContextVariable,
|
|
11
|
+
type ContextEntry,
|
|
12
|
+
} from '../index';
|
|
13
|
+
|
|
14
|
+
// Helper to filter context entries to only variables
|
|
15
|
+
function getVariables(entries: ContextEntry[]): ContextVariable[] {
|
|
16
|
+
return entries.filter((e): e is ContextVariable => e.kind === 'variable');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('Context Building Functions', () => {
|
|
20
|
+
test('buildLocalContext returns empty array for initial state', () => {
|
|
21
|
+
const ast = parse('let x = "hello"');
|
|
22
|
+
const state = createInitialState(ast);
|
|
23
|
+
|
|
24
|
+
const context = buildLocalContext(state);
|
|
25
|
+
expect(context).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('buildLocalContext returns variables from current frame with frame info', () => {
|
|
29
|
+
const ast = parse(`
|
|
30
|
+
let x = "hello"
|
|
31
|
+
const y: json = { key: "value" }
|
|
32
|
+
`);
|
|
33
|
+
let state = createInitialState(ast);
|
|
34
|
+
state = runUntilPause(state);
|
|
35
|
+
|
|
36
|
+
const context = buildLocalContext(state);
|
|
37
|
+
const variables = getVariables(context);
|
|
38
|
+
expect(variables).toHaveLength(2);
|
|
39
|
+
|
|
40
|
+
const xVar = variables.find((v) => v.name === 'x');
|
|
41
|
+
expect(xVar).toBeDefined();
|
|
42
|
+
expect(xVar?.value).toBe('hello');
|
|
43
|
+
expect(xVar?.type).toBe('text'); // Inferred from string value
|
|
44
|
+
expect(xVar?.frameName).toBe('<entry>');
|
|
45
|
+
expect(xVar?.frameDepth).toBe(0);
|
|
46
|
+
|
|
47
|
+
const yVar = variables.find((v) => v.name === 'y');
|
|
48
|
+
expect(yVar).toBeDefined();
|
|
49
|
+
expect(yVar?.value).toEqual({ key: 'value' });
|
|
50
|
+
expect(yVar?.type).toBe('json');
|
|
51
|
+
expect(yVar?.frameName).toBe('<entry>');
|
|
52
|
+
expect(yVar?.frameDepth).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('buildGlobalContext returns empty array for initial state', () => {
|
|
56
|
+
const ast = parse('let x = "hello"');
|
|
57
|
+
const state = createInitialState(ast);
|
|
58
|
+
|
|
59
|
+
const context = buildGlobalContext(state);
|
|
60
|
+
expect(context).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('buildGlobalContext returns variables from all frames with frame info', () => {
|
|
64
|
+
const ast = parse(`
|
|
65
|
+
let outer = "outer value"
|
|
66
|
+
function inner(): text {
|
|
67
|
+
let innerVar = "inner value"
|
|
68
|
+
return innerVar
|
|
69
|
+
}
|
|
70
|
+
let result = inner()
|
|
71
|
+
`);
|
|
72
|
+
let state = createInitialState(ast);
|
|
73
|
+
state = runUntilPause(state);
|
|
74
|
+
|
|
75
|
+
// After completion, only main frame remains
|
|
76
|
+
const context = buildGlobalContext(state);
|
|
77
|
+
const variables = getVariables(context);
|
|
78
|
+
|
|
79
|
+
const outerVar = variables.find((v) => v.name === 'outer');
|
|
80
|
+
expect(outerVar).toBeDefined();
|
|
81
|
+
expect(outerVar?.frameName).toBe('<entry>');
|
|
82
|
+
expect(outerVar?.frameDepth).toBe(0);
|
|
83
|
+
|
|
84
|
+
const resultVar = variables.find((v) => v.name === 'result');
|
|
85
|
+
expect(resultVar).toBeDefined();
|
|
86
|
+
expect(resultVar?.frameName).toBe('<entry>');
|
|
87
|
+
expect(resultVar?.frameDepth).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('context is rebuilt before each instruction in step()', () => {
|
|
91
|
+
const ast = parse(`
|
|
92
|
+
let a = "first"
|
|
93
|
+
let b = "second"
|
|
94
|
+
`);
|
|
95
|
+
let state = createInitialState(ast);
|
|
96
|
+
|
|
97
|
+
// Initial state - context should be empty
|
|
98
|
+
expect(state.localContext).toEqual([]);
|
|
99
|
+
expect(state.globalContext).toEqual([]);
|
|
100
|
+
|
|
101
|
+
// Step through until first variable is declared
|
|
102
|
+
while (state.status === 'running' && !state.callStack[0]?.locals['a']) {
|
|
103
|
+
state = step(state);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// After declaring 'a', step once more and context should reflect it
|
|
107
|
+
state = step(state);
|
|
108
|
+
expect(getVariables(state.localContext).some((v) => v.name === 'a')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('context includes correct type annotations', () => {
|
|
112
|
+
const ast = parse(`
|
|
113
|
+
let textVar = "hello"
|
|
114
|
+
let jsonVar: json = { data: "value" }
|
|
115
|
+
`);
|
|
116
|
+
let state = createInitialState(ast);
|
|
117
|
+
state = runUntilPause(state);
|
|
118
|
+
|
|
119
|
+
const context = buildLocalContext(state);
|
|
120
|
+
const variables = getVariables(context);
|
|
121
|
+
|
|
122
|
+
const textVar = variables.find((v) => v.name === 'textVar');
|
|
123
|
+
expect(textVar?.type).toBe('text'); // Inferred from string value
|
|
124
|
+
|
|
125
|
+
const jsonVar = variables.find((v) => v.name === 'jsonVar');
|
|
126
|
+
expect(jsonVar?.type).toBe('json');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('context updates correctly after variable reassignment', () => {
|
|
130
|
+
const ast = parse(`
|
|
131
|
+
let x = "initial"
|
|
132
|
+
x = "updated"
|
|
133
|
+
`);
|
|
134
|
+
let state = createInitialState(ast);
|
|
135
|
+
state = runUntilPause(state);
|
|
136
|
+
|
|
137
|
+
// With snapshotting, context preserves history: both 'initial' and 'updated' entries
|
|
138
|
+
const context = buildLocalContext(state);
|
|
139
|
+
const xEntries = getVariables(context).filter((v) => v.name === 'x');
|
|
140
|
+
expect(xEntries).toHaveLength(2);
|
|
141
|
+
expect(xEntries[0]?.value).toBe('initial');
|
|
142
|
+
expect(xEntries[1]?.value).toBe('updated');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('context works with nested function calls and shows frame depth', () => {
|
|
146
|
+
const ast = parse(`
|
|
147
|
+
let outer = "outer"
|
|
148
|
+
function getInner(): text {
|
|
149
|
+
let inner = "inner"
|
|
150
|
+
return inner
|
|
151
|
+
}
|
|
152
|
+
let result = getInner()
|
|
153
|
+
`);
|
|
154
|
+
let state = createInitialState(ast);
|
|
155
|
+
|
|
156
|
+
// Run until we're inside the function (have 2 frames)
|
|
157
|
+
while (state.status === 'running' && state.callStack.length < 2) {
|
|
158
|
+
state = step(state);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If we got inside the function, check global context with frame depths
|
|
162
|
+
if (state.callStack.length >= 2) {
|
|
163
|
+
const globalCtx = buildGlobalContext(state);
|
|
164
|
+
const globalVars = getVariables(globalCtx);
|
|
165
|
+
|
|
166
|
+
// Outer should be from <entry> frame (depth 0 = entry)
|
|
167
|
+
const outerVar = globalVars.find((v) => v.name === 'outer');
|
|
168
|
+
expect(outerVar).toBeDefined();
|
|
169
|
+
expect(outerVar?.frameName).toBe('<entry>');
|
|
170
|
+
expect(outerVar?.frameDepth).toBe(0); // Entry frame is always depth 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Complete execution
|
|
174
|
+
state = runUntilPause(state);
|
|
175
|
+
|
|
176
|
+
// After completion, result should be set with correct frame info
|
|
177
|
+
const finalContext = buildLocalContext(state);
|
|
178
|
+
const resultVar = getVariables(finalContext).find((v) => v.name === 'result');
|
|
179
|
+
expect(resultVar?.value).toBe('inner');
|
|
180
|
+
expect(resultVar?.frameName).toBe('<entry>');
|
|
181
|
+
expect(resultVar?.frameDepth).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('localContext and globalContext are stored in state', () => {
|
|
185
|
+
const ast = parse(`
|
|
186
|
+
let x = "value"
|
|
187
|
+
`);
|
|
188
|
+
let state = createInitialState(ast);
|
|
189
|
+
|
|
190
|
+
// Step through execution
|
|
191
|
+
while (state.status === 'running') {
|
|
192
|
+
state = step(state);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// State should have context properties
|
|
196
|
+
expect(Array.isArray(state.localContext)).toBe(true);
|
|
197
|
+
expect(Array.isArray(state.globalContext)).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('context filters out model declarations', () => {
|
|
201
|
+
const ast = parse(`
|
|
202
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
203
|
+
let x = "hello"
|
|
204
|
+
`);
|
|
205
|
+
let state = createInitialState(ast);
|
|
206
|
+
state = runUntilPause(state);
|
|
207
|
+
|
|
208
|
+
const context = buildLocalContext(state);
|
|
209
|
+
const variables = getVariables(context);
|
|
210
|
+
|
|
211
|
+
// Model should be filtered out of context (it's config, not data for AI)
|
|
212
|
+
const modelVar = variables.find((v) => v.name === 'm');
|
|
213
|
+
expect(modelVar).toBeUndefined();
|
|
214
|
+
|
|
215
|
+
// Regular variable should still be in context
|
|
216
|
+
const xVar = variables.find((v) => v.name === 'x');
|
|
217
|
+
expect(xVar?.value).toBe('hello');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('context filters out prompt type variables', () => {
|
|
221
|
+
const ast = parse(`
|
|
222
|
+
let systemPrompt: prompt = "You are a helpful assistant"
|
|
223
|
+
const question: prompt = "What is 2+2?"
|
|
224
|
+
let regularVar = "hello"
|
|
225
|
+
`);
|
|
226
|
+
let state = createInitialState(ast);
|
|
227
|
+
state = runUntilPause(state);
|
|
228
|
+
|
|
229
|
+
const context = buildLocalContext(state);
|
|
230
|
+
const variables = getVariables(context);
|
|
231
|
+
|
|
232
|
+
// Prompt variables should be filtered out (they are instructions, not data)
|
|
233
|
+
const promptVar = variables.find((v) => v.name === 'systemPrompt');
|
|
234
|
+
expect(promptVar).toBeUndefined();
|
|
235
|
+
|
|
236
|
+
const questionVar = variables.find((v) => v.name === 'question');
|
|
237
|
+
expect(questionVar).toBeUndefined();
|
|
238
|
+
|
|
239
|
+
// Regular variable should still be in context
|
|
240
|
+
const regularVarCtx = variables.find((v) => v.name === 'regularVar');
|
|
241
|
+
expect(regularVarCtx?.value).toBe('hello');
|
|
242
|
+
|
|
243
|
+
// Only the regular variable should be in context
|
|
244
|
+
expect(variables).toHaveLength(1);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('full context array verification with nested blocks and function calls', () => {
|
|
248
|
+
const ast = parse(`
|
|
249
|
+
let outer = "outer_value"
|
|
250
|
+
const outerConst: json = { key: "json_value" }
|
|
251
|
+
|
|
252
|
+
function processData(input: text): text {
|
|
253
|
+
let funcLocal = "func_local"
|
|
254
|
+
if true {
|
|
255
|
+
let blockVar = "block_value"
|
|
256
|
+
blockVar = "updated_block"
|
|
257
|
+
}
|
|
258
|
+
return input
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let result = processData("arg_value")
|
|
262
|
+
`);
|
|
263
|
+
|
|
264
|
+
let state = createInitialState(ast);
|
|
265
|
+
|
|
266
|
+
// Helper to normalize context for comparison (sort by name)
|
|
267
|
+
const normalizeContext = (ctx: ContextEntry[]) =>
|
|
268
|
+
[...getVariables(ctx)].sort((a, b) => a.name.localeCompare(b.name));
|
|
269
|
+
|
|
270
|
+
// Step until we're inside the function (2 frames)
|
|
271
|
+
state = stepUntilCondition(state, (s) => s.callStack.length >= 2);
|
|
272
|
+
|
|
273
|
+
// Continue stepping until funcLocal is declared
|
|
274
|
+
while (
|
|
275
|
+
state.status === 'running' &&
|
|
276
|
+
state.callStack.length >= 2 &&
|
|
277
|
+
!state.callStack[1]?.locals['funcLocal']
|
|
278
|
+
) {
|
|
279
|
+
state = step(state);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Take snapshot inside function, after funcLocal declared
|
|
283
|
+
if (state.callStack.length >= 2 && state.callStack[1]?.locals['funcLocal']) {
|
|
284
|
+
state = step(state); // Step once more to rebuild context
|
|
285
|
+
|
|
286
|
+
const localCtx = normalizeContext(buildLocalContext(state));
|
|
287
|
+
const globalCtx = normalizeContext(buildGlobalContext(state));
|
|
288
|
+
|
|
289
|
+
// Local context should only have function frame variables (depth 1 = called from entry)
|
|
290
|
+
// Function parameters now have explicit type annotations
|
|
291
|
+
expect(localCtx).toEqual([
|
|
292
|
+
{ kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
293
|
+
{ kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
// Global context should have <entry> frame (depth 0) + function frame (depth 1)
|
|
297
|
+
expect(globalCtx).toEqual([
|
|
298
|
+
{ kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
299
|
+
{ kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
300
|
+
{ kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
301
|
+
{ kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Continue stepping until we're inside the if block (blockVar declared)
|
|
306
|
+
while (
|
|
307
|
+
state.status === 'running' &&
|
|
308
|
+
state.callStack.length >= 2 &&
|
|
309
|
+
!state.callStack[1]?.locals['blockVar']
|
|
310
|
+
) {
|
|
311
|
+
state = step(state);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Take snapshot inside the nested block
|
|
315
|
+
if (state.callStack.length >= 2 && state.callStack[1]?.locals['blockVar']) {
|
|
316
|
+
state = step(state); // Step once more to rebuild context
|
|
317
|
+
|
|
318
|
+
const localCtxInBlock = normalizeContext(buildLocalContext(state));
|
|
319
|
+
const globalCtxInBlock = normalizeContext(buildGlobalContext(state));
|
|
320
|
+
|
|
321
|
+
// Local context now includes blockVar (blocks share frame with function)
|
|
322
|
+
// Function parameters now have explicit type annotations
|
|
323
|
+
expect(localCtxInBlock).toEqual([
|
|
324
|
+
{ kind: 'variable', name: 'blockVar', value: 'block_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
325
|
+
{ kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
326
|
+
{ kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
// Global context includes everything
|
|
330
|
+
expect(globalCtxInBlock).toEqual([
|
|
331
|
+
{ kind: 'variable', name: 'blockVar', value: 'block_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
332
|
+
{ kind: 'variable', name: 'funcLocal', value: 'func_local', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
333
|
+
{ kind: 'variable', name: 'input', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: 'processData', frameDepth: 1 },
|
|
334
|
+
{ kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
335
|
+
{ kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Continue until blockVar is updated
|
|
340
|
+
while (
|
|
341
|
+
state.status === 'running' &&
|
|
342
|
+
state.callStack.length >= 2 &&
|
|
343
|
+
state.callStack[1]?.locals['blockVar']?.value !== 'updated_block'
|
|
344
|
+
) {
|
|
345
|
+
state = step(state);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Verify updated value in context (check immediately, don't step again
|
|
349
|
+
// since next instruction might be exit_block which removes blockVar)
|
|
350
|
+
if (state.callStack[1]?.locals['blockVar']?.value === 'updated_block') {
|
|
351
|
+
const localCtxUpdated = normalizeContext(buildLocalContext(state));
|
|
352
|
+
|
|
353
|
+
// With snapshotting, blockVar has two entries - find the last one (updated value)
|
|
354
|
+
const blockVarEntries = localCtxUpdated.filter((v) => v.name === 'blockVar');
|
|
355
|
+
const lastBlockVar = blockVarEntries[blockVarEntries.length - 1];
|
|
356
|
+
expect(lastBlockVar?.value).toBe('updated_block');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Run to completion
|
|
360
|
+
state = runUntilPause(state);
|
|
361
|
+
expect(state.status).toBe('completed');
|
|
362
|
+
|
|
363
|
+
// Final context - back to main frame only, block variables gone
|
|
364
|
+
const finalLocalCtx = normalizeContext(buildLocalContext(state));
|
|
365
|
+
const finalGlobalCtx = normalizeContext(buildGlobalContext(state));
|
|
366
|
+
|
|
367
|
+
// Should have outer, outerConst, and result (blockVar and funcLocal are gone)
|
|
368
|
+
// All in <entry> frame at depth 0 since it's the only frame
|
|
369
|
+
expect(finalLocalCtx).toEqual([
|
|
370
|
+
{ kind: 'variable', name: 'outer', value: 'outer_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
371
|
+
{ kind: 'variable', name: 'outerConst', value: { key: 'json_value' }, type: 'json', isConst: true, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
372
|
+
{ kind: 'variable', name: 'result', value: 'arg_value', type: 'text', isConst: false, source: null, frameName: '<entry>', frameDepth: 0 },
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
// Local and global should be same when only one frame
|
|
376
|
+
expect(finalGlobalCtx).toEqual(finalLocalCtx);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('Tool Call Context Entries', () => {
|
|
381
|
+
test('tool-call entries in orderedEntries appear in context', () => {
|
|
382
|
+
const ast = parse('let x = "test"');
|
|
383
|
+
let state = createInitialState(ast);
|
|
384
|
+
state = runUntilPause(state);
|
|
385
|
+
|
|
386
|
+
// Add a tool-call entry directly to test buildLocalContext handles them
|
|
387
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
388
|
+
frame.orderedEntries.push({
|
|
389
|
+
kind: 'tool-call',
|
|
390
|
+
toolName: 'getWeather',
|
|
391
|
+
args: { city: 'Seattle' },
|
|
392
|
+
result: { temp: 55, condition: 'rainy' },
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const context = buildLocalContext(state);
|
|
396
|
+
|
|
397
|
+
// Should have the variable and the tool call
|
|
398
|
+
expect(context).toHaveLength(2);
|
|
399
|
+
|
|
400
|
+
const toolCall = context.find((e) => e.kind === 'tool-call');
|
|
401
|
+
expect(toolCall).toBeDefined();
|
|
402
|
+
expect(toolCall?.kind).toBe('tool-call');
|
|
403
|
+
if (toolCall?.kind === 'tool-call') {
|
|
404
|
+
expect(toolCall.toolName).toBe('getWeather');
|
|
405
|
+
expect(toolCall.args).toEqual({ city: 'Seattle' });
|
|
406
|
+
expect(toolCall.result).toEqual({ temp: 55, condition: 'rainy' });
|
|
407
|
+
expect(toolCall.frameName).toBe('<entry>');
|
|
408
|
+
expect(toolCall.frameDepth).toBe(0);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('tool-call entries with errors appear in context', () => {
|
|
413
|
+
const ast = parse('let x = "test"');
|
|
414
|
+
let state = createInitialState(ast);
|
|
415
|
+
state = runUntilPause(state);
|
|
416
|
+
|
|
417
|
+
// Add a failed tool call
|
|
418
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
419
|
+
frame.orderedEntries.push({
|
|
420
|
+
kind: 'tool-call',
|
|
421
|
+
toolName: 'readFile',
|
|
422
|
+
args: { path: '/nonexistent' },
|
|
423
|
+
error: 'File not found',
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const context = buildLocalContext(state);
|
|
427
|
+
|
|
428
|
+
const toolCall = context.find((e) => e.kind === 'tool-call');
|
|
429
|
+
expect(toolCall).toBeDefined();
|
|
430
|
+
if (toolCall?.kind === 'tool-call') {
|
|
431
|
+
expect(toolCall.toolName).toBe('readFile');
|
|
432
|
+
expect(toolCall.error).toBe('File not found');
|
|
433
|
+
expect(toolCall.result).toBeUndefined();
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('multiple tool calls appear in order', () => {
|
|
438
|
+
const ast = parse('let x = "test"');
|
|
439
|
+
let state = createInitialState(ast);
|
|
440
|
+
state = runUntilPause(state);
|
|
441
|
+
|
|
442
|
+
// Add multiple tool calls
|
|
443
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
444
|
+
frame.orderedEntries.push(
|
|
445
|
+
{
|
|
446
|
+
kind: 'tool-call',
|
|
447
|
+
toolName: 'step1',
|
|
448
|
+
args: {},
|
|
449
|
+
result: 'done1',
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
kind: 'tool-call',
|
|
453
|
+
toolName: 'step2',
|
|
454
|
+
args: {},
|
|
455
|
+
result: 'done2',
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const context = buildLocalContext(state);
|
|
460
|
+
const toolCalls = context.filter((e) => e.kind === 'tool-call');
|
|
461
|
+
|
|
462
|
+
expect(toolCalls).toHaveLength(2);
|
|
463
|
+
expect(toolCalls[0].kind === 'tool-call' && toolCalls[0].toolName).toBe('step1');
|
|
464
|
+
expect(toolCalls[1].kind === 'tool-call' && toolCalls[1].toolName).toBe('step2');
|
|
465
|
+
});
|
|
466
|
+
});
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Tests for auto-imported core functions (print, env)
|
|
2
|
+
// These functions are available everywhere without explicit import
|
|
3
|
+
|
|
4
|
+
import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
|
5
|
+
import { parse } from '../../parser/parse';
|
|
6
|
+
import { Runtime } from '../index';
|
|
7
|
+
import type { AIProvider, AIResponse } from '../types';
|
|
8
|
+
|
|
9
|
+
// Mock provider for tests that don't need AI
|
|
10
|
+
function createMockProvider(): AIProvider {
|
|
11
|
+
return {
|
|
12
|
+
async chat(): Promise<AIResponse> {
|
|
13
|
+
return { content: 'mock response', toolCalls: [] };
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('Core Functions - Auto-imported', () => {
|
|
19
|
+
describe('print()', () => {
|
|
20
|
+
test('print works at top level without import', async () => {
|
|
21
|
+
const ast = parse(`
|
|
22
|
+
let x = print("hello world")
|
|
23
|
+
let y = 42
|
|
24
|
+
`);
|
|
25
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
26
|
+
await runtime.run();
|
|
27
|
+
// print returns undefined, but shouldn't throw
|
|
28
|
+
expect(runtime.getValue('y')).toBe(42);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('print works inside function without import', async () => {
|
|
32
|
+
const ast = parse(`
|
|
33
|
+
function logAndReturn(msg: text): text {
|
|
34
|
+
print(msg)
|
|
35
|
+
return msg
|
|
36
|
+
}
|
|
37
|
+
let result = logAndReturn("test message")
|
|
38
|
+
`);
|
|
39
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
40
|
+
await runtime.run();
|
|
41
|
+
expect(runtime.getValue('result')).toBe('test message');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('print works with different value types', async () => {
|
|
45
|
+
const ast = parse(`
|
|
46
|
+
print("string")
|
|
47
|
+
print(42)
|
|
48
|
+
print(true)
|
|
49
|
+
print([1, 2, 3])
|
|
50
|
+
let done = true
|
|
51
|
+
`);
|
|
52
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
53
|
+
await runtime.run();
|
|
54
|
+
expect(runtime.getValue('done')).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('env()', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
process.env.CORE_TEST_VAR = 'test-value';
|
|
61
|
+
process.env.CORE_TEST_VAR2 = 'second-value';
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
delete process.env.CORE_TEST_VAR;
|
|
66
|
+
delete process.env.CORE_TEST_VAR2;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('env works at top level without import', async () => {
|
|
70
|
+
const ast = parse(`
|
|
71
|
+
let value = env("CORE_TEST_VAR")
|
|
72
|
+
`);
|
|
73
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
74
|
+
await runtime.run();
|
|
75
|
+
expect(runtime.getValue('value')).toBe('test-value');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('env with default value when var not set', async () => {
|
|
79
|
+
const ast = parse(`
|
|
80
|
+
let value = env("NONEXISTENT_VAR_12345", "default-value")
|
|
81
|
+
`);
|
|
82
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
83
|
+
await runtime.run();
|
|
84
|
+
expect(runtime.getValue('value')).toBe('default-value');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('env returns empty string for missing var without default', async () => {
|
|
88
|
+
const ast = parse(`
|
|
89
|
+
let value = env("NONEXISTENT_VAR_12345")
|
|
90
|
+
`);
|
|
91
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
92
|
+
await runtime.run();
|
|
93
|
+
expect(runtime.getValue('value')).toBe('');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('env works inside function without import', async () => {
|
|
97
|
+
const ast = parse(`
|
|
98
|
+
function getEnvValue(name: text): text {
|
|
99
|
+
return env(name)
|
|
100
|
+
}
|
|
101
|
+
let value = getEnvValue("CORE_TEST_VAR")
|
|
102
|
+
`);
|
|
103
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
104
|
+
await runtime.run();
|
|
105
|
+
expect(runtime.getValue('value')).toBe('test-value');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('env works in json object literal', async () => {
|
|
109
|
+
const ast = parse(`
|
|
110
|
+
let config: json = {
|
|
111
|
+
apiKey: env("CORE_TEST_VAR"),
|
|
112
|
+
other: env("CORE_TEST_VAR2")
|
|
113
|
+
}
|
|
114
|
+
`);
|
|
115
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
116
|
+
await runtime.run();
|
|
117
|
+
expect(runtime.getValue('config')).toEqual({
|
|
118
|
+
apiKey: 'test-value',
|
|
119
|
+
other: 'second-value',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('core functions in nested contexts', () => {
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
process.env.NESTED_TEST_VAR = 'nested-value';
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterEach(() => {
|
|
130
|
+
delete process.env.NESTED_TEST_VAR;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('core functions work in chained function calls', async () => {
|
|
134
|
+
const ast = parse(`
|
|
135
|
+
function inner(): text {
|
|
136
|
+
return env("NESTED_TEST_VAR")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function outer(): text {
|
|
140
|
+
print("calling inner")
|
|
141
|
+
return inner()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let result = outer()
|
|
145
|
+
`);
|
|
146
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
147
|
+
await runtime.run();
|
|
148
|
+
expect(runtime.getValue('result')).toBe('nested-value');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('core functions work in loops', async () => {
|
|
152
|
+
const ast = parse(`
|
|
153
|
+
let count = 0
|
|
154
|
+
for i in [1, 2, 3] {
|
|
155
|
+
print(env("NESTED_TEST_VAR"))
|
|
156
|
+
count = count + 1
|
|
157
|
+
}
|
|
158
|
+
`);
|
|
159
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
160
|
+
await runtime.run();
|
|
161
|
+
expect(runtime.getValue('count')).toBe(3);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('core functions work in conditionals', async () => {
|
|
165
|
+
const ast = parse(`
|
|
166
|
+
let value = ""
|
|
167
|
+
if true {
|
|
168
|
+
value = env("NESTED_TEST_VAR")
|
|
169
|
+
print("got value: " + value)
|
|
170
|
+
}
|
|
171
|
+
`);
|
|
172
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
173
|
+
await runtime.run();
|
|
174
|
+
expect(runtime.getValue('value')).toBe('nested-value');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('core functions cannot be imported', () => {
|
|
179
|
+
test('importing env from system fails', async () => {
|
|
180
|
+
const ast = parse(`
|
|
181
|
+
import { env } from "system"
|
|
182
|
+
let x = 1
|
|
183
|
+
`);
|
|
184
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
185
|
+
await expect(runtime.run()).rejects.toThrow("'env' is not exported from 'system'");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('importing print from system fails', async () => {
|
|
189
|
+
const ast = parse(`
|
|
190
|
+
import { print } from "system"
|
|
191
|
+
let x = 1
|
|
192
|
+
`);
|
|
193
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
194
|
+
await expect(runtime.run()).rejects.toThrow("'print' is not exported from 'system'");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('importing from system/core is blocked', async () => {
|
|
198
|
+
const ast = parse(`
|
|
199
|
+
import { env } from "system/core"
|
|
200
|
+
let x = 1
|
|
201
|
+
`);
|
|
202
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
203
|
+
await expect(runtime.run()).rejects.toThrow("'system/core' cannot be imported");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('library functions still require import', () => {
|
|
208
|
+
test('uuid requires import from system', async () => {
|
|
209
|
+
const ast = parse(`
|
|
210
|
+
let id = uuid()
|
|
211
|
+
`);
|
|
212
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
213
|
+
await expect(runtime.run()).rejects.toThrow("'uuid' is not defined");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('uuid works when imported from system', async () => {
|
|
217
|
+
const ast = parse(`
|
|
218
|
+
import { uuid } from "system"
|
|
219
|
+
let id = uuid()
|
|
220
|
+
`);
|
|
221
|
+
const runtime = new Runtime(ast, createMockProvider());
|
|
222
|
+
await runtime.run();
|
|
223
|
+
const id = runtime.getValue('id') as string;
|
|
224
|
+
expect(typeof id).toBe('string');
|
|
225
|
+
expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|