@rcrsr/rill 0.1.0
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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -0
- package/dist/demo.d.ts +6 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +121 -0
- package/dist/demo.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/errors.d.ts +9 -0
- package/dist/lexer/errors.d.ts.map +1 -0
- package/dist/lexer/errors.js +12 -0
- package/dist/lexer/errors.js.map +1 -0
- package/dist/lexer/helpers.d.ts +14 -0
- package/dist/lexer/helpers.d.ts.map +1 -0
- package/dist/lexer/helpers.js +30 -0
- package/dist/lexer/helpers.js.map +1 -0
- package/dist/lexer/index.d.ts +8 -0
- package/dist/lexer/index.d.ts.map +1 -0
- package/dist/lexer/index.js +8 -0
- package/dist/lexer/index.js.map +1 -0
- package/dist/lexer/operators.d.ts +11 -0
- package/dist/lexer/operators.d.ts.map +1 -0
- package/dist/lexer/operators.js +58 -0
- package/dist/lexer/operators.js.map +1 -0
- package/dist/lexer/readers.d.ts +12 -0
- package/dist/lexer/readers.d.ts.map +1 -0
- package/dist/lexer/readers.js +144 -0
- package/dist/lexer/readers.js.map +1 -0
- package/dist/lexer/state.d.ts +18 -0
- package/dist/lexer/state.d.ts.map +1 -0
- package/dist/lexer/state.js +37 -0
- package/dist/lexer/state.js.map +1 -0
- package/dist/lexer/tokenizer.d.ts +9 -0
- package/dist/lexer/tokenizer.d.ts.map +1 -0
- package/dist/lexer/tokenizer.js +100 -0
- package/dist/lexer/tokenizer.js.map +1 -0
- package/dist/lexer.d.ts +19 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +344 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser/arithmetic.d.ts +16 -0
- package/dist/parser/arithmetic.d.ts.map +1 -0
- package/dist/parser/arithmetic.js +128 -0
- package/dist/parser/arithmetic.js.map +1 -0
- package/dist/parser/boolean.d.ts +15 -0
- package/dist/parser/boolean.d.ts.map +1 -0
- package/dist/parser/boolean.js +20 -0
- package/dist/parser/boolean.js.map +1 -0
- package/dist/parser/control-flow.d.ts +56 -0
- package/dist/parser/control-flow.d.ts.map +1 -0
- package/dist/parser/control-flow.js +167 -0
- package/dist/parser/control-flow.js.map +1 -0
- package/dist/parser/expressions.d.ts +23 -0
- package/dist/parser/expressions.d.ts.map +1 -0
- package/dist/parser/expressions.js +950 -0
- package/dist/parser/expressions.js.map +1 -0
- package/dist/parser/extraction.d.ts +48 -0
- package/dist/parser/extraction.d.ts.map +1 -0
- package/dist/parser/extraction.js +279 -0
- package/dist/parser/extraction.js.map +1 -0
- package/dist/parser/functions.d.ts +20 -0
- package/dist/parser/functions.d.ts.map +1 -0
- package/dist/parser/functions.js +96 -0
- package/dist/parser/functions.js.map +1 -0
- package/dist/parser/helpers.d.ts +94 -0
- package/dist/parser/helpers.d.ts.map +1 -0
- package/dist/parser/helpers.js +225 -0
- package/dist/parser/helpers.js.map +1 -0
- package/dist/parser/index.d.ts +49 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +73 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/literals.d.ts +37 -0
- package/dist/parser/literals.d.ts.map +1 -0
- package/dist/parser/literals.js +373 -0
- package/dist/parser/literals.js.map +1 -0
- package/dist/parser/parser-collect.d.ts +16 -0
- package/dist/parser/parser-collect.d.ts.map +1 -0
- package/dist/parser/parser-collect.js +125 -0
- package/dist/parser/parser-collect.js.map +1 -0
- package/dist/parser/parser-control.d.ts +20 -0
- package/dist/parser/parser-control.d.ts.map +1 -0
- package/dist/parser/parser-control.js +120 -0
- package/dist/parser/parser-control.js.map +1 -0
- package/dist/parser/parser-expr.d.ts +37 -0
- package/dist/parser/parser-expr.d.ts.map +1 -0
- package/dist/parser/parser-expr.js +639 -0
- package/dist/parser/parser-expr.js.map +1 -0
- package/dist/parser/parser-extract.d.ts +17 -0
- package/dist/parser/parser-extract.d.ts.map +1 -0
- package/dist/parser/parser-extract.js +222 -0
- package/dist/parser/parser-extract.js.map +1 -0
- package/dist/parser/parser-functions.d.ts +21 -0
- package/dist/parser/parser-functions.d.ts.map +1 -0
- package/dist/parser/parser-functions.js +155 -0
- package/dist/parser/parser-functions.js.map +1 -0
- package/dist/parser/parser-literals.d.ts +22 -0
- package/dist/parser/parser-literals.d.ts.map +1 -0
- package/dist/parser/parser-literals.js +288 -0
- package/dist/parser/parser-literals.js.map +1 -0
- package/dist/parser/parser-script.d.ts +21 -0
- package/dist/parser/parser-script.d.ts.map +1 -0
- package/dist/parser/parser-script.js +174 -0
- package/dist/parser/parser-script.js.map +1 -0
- package/dist/parser/parser-variables.d.ts +20 -0
- package/dist/parser/parser-variables.d.ts.map +1 -0
- package/dist/parser/parser-variables.js +146 -0
- package/dist/parser/parser-variables.js.map +1 -0
- package/dist/parser/parser.d.ts +49 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +54 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/script.d.ts +14 -0
- package/dist/parser/script.d.ts.map +1 -0
- package/dist/parser/script.js +196 -0
- package/dist/parser/script.js.map +1 -0
- package/dist/parser/state.d.ts +40 -0
- package/dist/parser/state.d.ts.map +1 -0
- package/dist/parser/state.js +129 -0
- package/dist/parser/state.js.map +1 -0
- package/dist/parser/variables.d.ts +10 -0
- package/dist/parser/variables.d.ts.map +1 -0
- package/dist/parser/variables.js +215 -0
- package/dist/parser/variables.js.map +1 -0
- package/dist/runtime/ast-equals.d.ts +13 -0
- package/dist/runtime/ast-equals.d.ts.map +1 -0
- package/dist/runtime/ast-equals.js +447 -0
- package/dist/runtime/ast-equals.js.map +1 -0
- package/dist/runtime/builtins.d.ts +13 -0
- package/dist/runtime/builtins.d.ts.map +1 -0
- package/dist/runtime/builtins.js +180 -0
- package/dist/runtime/builtins.js.map +1 -0
- package/dist/runtime/callable.d.ts +88 -0
- package/dist/runtime/callable.d.ts.map +1 -0
- package/dist/runtime/callable.js +98 -0
- package/dist/runtime/callable.js.map +1 -0
- package/dist/runtime/context.d.ts +13 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +73 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/core/callable.d.ts +171 -0
- package/dist/runtime/core/callable.d.ts.map +1 -0
- package/dist/runtime/core/callable.js +246 -0
- package/dist/runtime/core/callable.js.map +1 -0
- package/dist/runtime/core/context.d.ts +29 -0
- package/dist/runtime/core/context.d.ts.map +1 -0
- package/dist/runtime/core/context.js +154 -0
- package/dist/runtime/core/context.js.map +1 -0
- package/dist/runtime/core/equals.d.ts +9 -0
- package/dist/runtime/core/equals.d.ts.map +1 -0
- package/dist/runtime/core/equals.js +381 -0
- package/dist/runtime/core/equals.js.map +1 -0
- package/dist/runtime/core/eval/base.d.ts +65 -0
- package/dist/runtime/core/eval/base.d.ts.map +1 -0
- package/dist/runtime/core/eval/base.js +112 -0
- package/dist/runtime/core/eval/base.js.map +1 -0
- package/dist/runtime/core/eval/evaluator.d.ts +47 -0
- package/dist/runtime/core/eval/evaluator.d.ts.map +1 -0
- package/dist/runtime/core/eval/evaluator.js +73 -0
- package/dist/runtime/core/eval/evaluator.js.map +1 -0
- package/dist/runtime/core/eval/index.d.ts +57 -0
- package/dist/runtime/core/eval/index.d.ts.map +1 -0
- package/dist/runtime/core/eval/index.js +95 -0
- package/dist/runtime/core/eval/index.js.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/annotations.js +146 -0
- package/dist/runtime/core/eval/mixins/annotations.js.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts +49 -0
- package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/closures.js +479 -0
- package/dist/runtime/core/eval/mixins/closures.js.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/collections.js +466 -0
- package/dist/runtime/core/eval/mixins/collections.js.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts +27 -0
- package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/control-flow.js +369 -0
- package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -0
- package/dist/runtime/core/eval/mixins/core.d.ts +24 -0
- package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/core.js +335 -0
- package/dist/runtime/core/eval/mixins/core.js.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts +19 -0
- package/dist/runtime/core/eval/mixins/expressions.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/expressions.js +202 -0
- package/dist/runtime/core/eval/mixins/expressions.js.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts +10 -0
- package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/extraction.js +250 -0
- package/dist/runtime/core/eval/mixins/extraction.js.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts +23 -0
- package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/literals.js +180 -0
- package/dist/runtime/core/eval/mixins/literals.js.map +1 -0
- package/dist/runtime/core/eval/mixins/types.d.ts +20 -0
- package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/types.js +109 -0
- package/dist/runtime/core/eval/mixins/types.js.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts +34 -0
- package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -0
- package/dist/runtime/core/eval/mixins/variables.js +247 -0
- package/dist/runtime/core/eval/mixins/variables.js.map +1 -0
- package/dist/runtime/core/eval/types.d.ts +41 -0
- package/dist/runtime/core/eval/types.d.ts.map +1 -0
- package/dist/runtime/core/eval/types.js +10 -0
- package/dist/runtime/core/eval/types.js.map +1 -0
- package/dist/runtime/core/evaluate.d.ts +42 -0
- package/dist/runtime/core/evaluate.d.ts.map +1 -0
- package/dist/runtime/core/evaluate.debug.js +1251 -0
- package/dist/runtime/core/evaluate.js +1913 -0
- package/dist/runtime/core/evaluate.js.map +1 -0
- package/dist/runtime/core/execute.d.ts +26 -0
- package/dist/runtime/core/execute.d.ts.map +1 -0
- package/dist/runtime/core/execute.js +177 -0
- package/dist/runtime/core/execute.js.map +1 -0
- package/dist/runtime/core/signals.d.ts +19 -0
- package/dist/runtime/core/signals.d.ts.map +1 -0
- package/dist/runtime/core/signals.js +26 -0
- package/dist/runtime/core/signals.js.map +1 -0
- package/dist/runtime/core/types.d.ts +177 -0
- package/dist/runtime/core/types.d.ts.map +1 -0
- package/dist/runtime/core/types.js +50 -0
- package/dist/runtime/core/types.js.map +1 -0
- package/dist/runtime/core/values.d.ts +66 -0
- package/dist/runtime/core/values.d.ts.map +1 -0
- package/dist/runtime/core/values.js +240 -0
- package/dist/runtime/core/values.js.map +1 -0
- package/dist/runtime/evaluate.d.ts +32 -0
- package/dist/runtime/evaluate.d.ts.map +1 -0
- package/dist/runtime/evaluate.js +1111 -0
- package/dist/runtime/evaluate.js.map +1 -0
- package/dist/runtime/execute.d.ts +26 -0
- package/dist/runtime/execute.d.ts.map +1 -0
- package/dist/runtime/execute.js +121 -0
- package/dist/runtime/execute.js.map +1 -0
- package/dist/runtime/ext/builtins.d.ts +16 -0
- package/dist/runtime/ext/builtins.d.ts.map +1 -0
- package/dist/runtime/ext/builtins.js +528 -0
- package/dist/runtime/ext/builtins.js.map +1 -0
- package/dist/runtime/ext/content-parser.d.ts +83 -0
- package/dist/runtime/ext/content-parser.d.ts.map +1 -0
- package/dist/runtime/ext/content-parser.js +536 -0
- package/dist/runtime/ext/content-parser.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +34 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/signals.d.ts +19 -0
- package/dist/runtime/signals.d.ts.map +1 -0
- package/dist/runtime/signals.js +26 -0
- package/dist/runtime/signals.js.map +1 -0
- package/dist/runtime/types.d.ts +169 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +50 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/values.d.ts +50 -0
- package/dist/runtime/values.d.ts.map +1 -0
- package/dist/runtime/values.js +209 -0
- package/dist/runtime/values.js.map +1 -0
- package/dist/runtime.d.ts +254 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2014 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +752 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +189 -0
- package/dist/types.js.map +1 -0
- package/docs/00_INDEX.md +65 -0
- package/docs/01_guide.md +390 -0
- package/docs/02_types.md +399 -0
- package/docs/03_variables.md +314 -0
- package/docs/04_operators.md +551 -0
- package/docs/05_control-flow.md +350 -0
- package/docs/06_closures.md +353 -0
- package/docs/07_collections.md +686 -0
- package/docs/08_iterators.md +330 -0
- package/docs/09_strings.md +205 -0
- package/docs/10_parsing.md +366 -0
- package/docs/11_reference.md +350 -0
- package/docs/12_examples.md +771 -0
- package/docs/13_modules.md +519 -0
- package/docs/14_host-integration.md +826 -0
- package/docs/15_grammar.ebnf +693 -0
- package/docs/16_conventions.md +696 -0
- package/docs/99_llm-reference.txt +300 -0
- package/docs/assets/logo.png +0 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Evaluation
|
|
3
|
+
*
|
|
4
|
+
* Internal module for AST evaluation. Not part of public API.
|
|
5
|
+
* All evaluation functions are internal implementation details.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import { AbortError, AutoExceptionError, RILL_ERROR_CODES, RuntimeError, TimeoutError, } from '../types.js';
|
|
10
|
+
import { BUILTIN_METHODS } from './builtins.js';
|
|
11
|
+
import { isCallable, isDict, isScriptCallable } from './callable.js';
|
|
12
|
+
import { BreakSignal, ReturnSignal } from './signals.js';
|
|
13
|
+
import { createArgsFromDict, createArgsFromTuple, deepEquals, formatValue, inferType, isArgs, isReservedMethod, isTruthy, } from './values.js';
|
|
14
|
+
// ============================================================
|
|
15
|
+
// EXPORTED HELPERS (used by execute.ts)
|
|
16
|
+
// ============================================================
|
|
17
|
+
/** Helper to get location from an AST node */
|
|
18
|
+
function getNodeLocation(node) {
|
|
19
|
+
return node?.span.start;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if execution has been aborted via AbortSignal.
|
|
23
|
+
* Throws AbortError if signal is aborted.
|
|
24
|
+
*/
|
|
25
|
+
export function checkAborted(ctx, node) {
|
|
26
|
+
if (ctx.signal?.aborted) {
|
|
27
|
+
throw new AbortError(getNodeLocation(node));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if the current pipe value matches any autoException pattern.
|
|
32
|
+
* Only checks string values. Throws AutoExceptionError on match.
|
|
33
|
+
*/
|
|
34
|
+
export function checkAutoExceptions(value, ctx, node) {
|
|
35
|
+
if (typeof value !== 'string' || ctx.autoExceptions.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const pattern of ctx.autoExceptions) {
|
|
39
|
+
if (pattern.test(value)) {
|
|
40
|
+
throw new AutoExceptionError(pattern.source, value, getNodeLocation(node));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Handle statement capture: set variable and fire observability event.
|
|
46
|
+
* Returns capture info if a capture occurred.
|
|
47
|
+
*/
|
|
48
|
+
export function handleCapture(capture, value, ctx) {
|
|
49
|
+
if (!capture)
|
|
50
|
+
return undefined;
|
|
51
|
+
setVariable(ctx, capture.name, value, capture.typeName, capture.span.start);
|
|
52
|
+
const captureInfo = { name: capture.name, value };
|
|
53
|
+
ctx.observability.onCapture?.(captureInfo);
|
|
54
|
+
return captureInfo;
|
|
55
|
+
}
|
|
56
|
+
// ============================================================
|
|
57
|
+
// VARIABLE MANAGEMENT
|
|
58
|
+
// ============================================================
|
|
59
|
+
/**
|
|
60
|
+
* Set a variable with type checking.
|
|
61
|
+
* - First assignment locks the type (inferred or explicit)
|
|
62
|
+
* - Subsequent assignments must match the locked type
|
|
63
|
+
* - Explicit type annotation is validated against value type
|
|
64
|
+
*/
|
|
65
|
+
function setVariable(ctx, name, value, explicitType, location) {
|
|
66
|
+
const valueType = inferType(value);
|
|
67
|
+
// Check explicit type annotation matches value
|
|
68
|
+
if (explicitType !== null && explicitType !== valueType) {
|
|
69
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Type mismatch: cannot assign ${valueType} to $${name}:${explicitType}`, location, { variableName: name, expectedType: explicitType, actualType: valueType });
|
|
70
|
+
}
|
|
71
|
+
// Check if variable already has a locked type
|
|
72
|
+
const lockedType = ctx.variableTypes.get(name);
|
|
73
|
+
if (lockedType !== undefined && lockedType !== valueType) {
|
|
74
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Type mismatch: cannot assign ${valueType} to $${name} (locked as ${lockedType})`, location, { variableName: name, expectedType: lockedType, actualType: valueType });
|
|
75
|
+
}
|
|
76
|
+
// Set the variable and lock its type
|
|
77
|
+
ctx.variables.set(name, value);
|
|
78
|
+
if (!ctx.variableTypes.has(name)) {
|
|
79
|
+
ctx.variableTypes.set(name, explicitType ?? valueType);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ============================================================
|
|
83
|
+
// TIMEOUT WRAPPER
|
|
84
|
+
// ============================================================
|
|
85
|
+
/**
|
|
86
|
+
* Wrap a promise with a timeout. Returns original promise if no timeout configured.
|
|
87
|
+
*/
|
|
88
|
+
function withTimeout(promise, timeoutMs, functionName, node) {
|
|
89
|
+
if (timeoutMs === undefined) {
|
|
90
|
+
return promise;
|
|
91
|
+
}
|
|
92
|
+
return Promise.race([
|
|
93
|
+
promise,
|
|
94
|
+
new Promise((_, reject) => {
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
reject(new TimeoutError(functionName, timeoutMs, getNodeLocation(node)));
|
|
97
|
+
}, timeoutMs);
|
|
98
|
+
}),
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
// ============================================================
|
|
102
|
+
// EXPRESSION EVALUATION
|
|
103
|
+
// ============================================================
|
|
104
|
+
/**
|
|
105
|
+
* Evaluate argument expressions while preserving the current pipeValue.
|
|
106
|
+
*/
|
|
107
|
+
async function evaluateArgs(argExprs, ctx) {
|
|
108
|
+
const savedPipeValue = ctx.pipeValue;
|
|
109
|
+
const args = [];
|
|
110
|
+
for (const arg of argExprs) {
|
|
111
|
+
args.push(await evaluateExpression(arg, ctx));
|
|
112
|
+
}
|
|
113
|
+
ctx.pipeValue = savedPipeValue;
|
|
114
|
+
return args;
|
|
115
|
+
}
|
|
116
|
+
export async function evaluateExpression(expr, ctx) {
|
|
117
|
+
return evaluatePipeChain(expr, ctx);
|
|
118
|
+
}
|
|
119
|
+
async function evaluatePipeChain(chain, ctx) {
|
|
120
|
+
let value = await evaluatePostfixExpr(chain.head, ctx);
|
|
121
|
+
ctx.pipeValue = value;
|
|
122
|
+
for (const target of chain.pipes) {
|
|
123
|
+
value = await evaluatePipeTarget(target, value, ctx);
|
|
124
|
+
ctx.pipeValue = value;
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
async function evaluatePostfixExpr(expr, ctx) {
|
|
129
|
+
let value = await evaluatePrimary(expr.primary, ctx);
|
|
130
|
+
for (const method of expr.methods) {
|
|
131
|
+
value = await evaluateMethod(method, value, ctx);
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
async function evaluatePrimary(primary, ctx) {
|
|
136
|
+
switch (primary.type) {
|
|
137
|
+
case 'StringLiteral':
|
|
138
|
+
return evaluateString(primary, ctx);
|
|
139
|
+
case 'NumberLiteral':
|
|
140
|
+
return primary.value;
|
|
141
|
+
case 'BoolLiteral':
|
|
142
|
+
return primary.value;
|
|
143
|
+
case 'Tuple':
|
|
144
|
+
return evaluateTuple(primary, ctx);
|
|
145
|
+
case 'Dict':
|
|
146
|
+
return evaluateDict(primary, ctx);
|
|
147
|
+
case 'FunctionLiteral':
|
|
148
|
+
return await createClosure(primary, ctx);
|
|
149
|
+
case 'Variable':
|
|
150
|
+
return evaluateVariableAsync(primary, ctx);
|
|
151
|
+
case 'FunctionCall':
|
|
152
|
+
return evaluateFunctionCall(primary, ctx);
|
|
153
|
+
case 'VariableCall':
|
|
154
|
+
return evaluateVariableCall(primary, ctx);
|
|
155
|
+
case 'MethodCall':
|
|
156
|
+
return evaluateMethod(primary, ctx.pipeValue, ctx);
|
|
157
|
+
case 'Conditional':
|
|
158
|
+
return evaluateConditional(primary, ctx);
|
|
159
|
+
case 'WhileLoop':
|
|
160
|
+
return evaluateWhileLoop(primary, ctx);
|
|
161
|
+
case 'ForLoop':
|
|
162
|
+
return evaluateForLoop(primary, ctx);
|
|
163
|
+
case 'Block':
|
|
164
|
+
return evaluateBlockExpression(primary, ctx);
|
|
165
|
+
case 'Arithmetic':
|
|
166
|
+
return evaluateArithmetic(primary, ctx);
|
|
167
|
+
case 'Spread':
|
|
168
|
+
return evaluateSpread(primary, ctx);
|
|
169
|
+
default:
|
|
170
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown primary type: ${primary.type}`, getNodeLocation(primary));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function evaluatePipeTarget(target, input, ctx) {
|
|
174
|
+
ctx.pipeValue = input;
|
|
175
|
+
switch (target.type) {
|
|
176
|
+
case 'Capture':
|
|
177
|
+
return evaluateCapture(target, input, ctx);
|
|
178
|
+
case 'FunctionCall':
|
|
179
|
+
return evaluateFunctionCall(target, ctx);
|
|
180
|
+
case 'VariableCall':
|
|
181
|
+
return evaluateVariableCallWithPipe(target, input, ctx);
|
|
182
|
+
case 'Invoke':
|
|
183
|
+
return evaluateInvoke(target, input, ctx);
|
|
184
|
+
case 'MethodCall':
|
|
185
|
+
return evaluateMethod(target, input, ctx);
|
|
186
|
+
case 'Conditional':
|
|
187
|
+
return evaluateConditional(target, ctx);
|
|
188
|
+
case 'WhileLoop':
|
|
189
|
+
return evaluateWhileLoop(target, ctx);
|
|
190
|
+
case 'ForLoop':
|
|
191
|
+
return evaluateForLoop(target, ctx);
|
|
192
|
+
case 'Block':
|
|
193
|
+
return evaluateBlockExpression(target, ctx);
|
|
194
|
+
case 'StringLiteral':
|
|
195
|
+
return evaluateString(target, ctx);
|
|
196
|
+
case 'Arithmetic':
|
|
197
|
+
return evaluateArithmetic(target, ctx);
|
|
198
|
+
case 'ParallelSpread':
|
|
199
|
+
return evaluateParallelSpread(target, input, ctx);
|
|
200
|
+
case 'ParallelFilter':
|
|
201
|
+
return evaluateParallelFilter(target, input, ctx);
|
|
202
|
+
case 'SequentialSpread':
|
|
203
|
+
return evaluateSequentialSpread(target, input, ctx);
|
|
204
|
+
case 'Destructure':
|
|
205
|
+
return evaluateDestructure(target, input, ctx);
|
|
206
|
+
case 'Slice':
|
|
207
|
+
return evaluateSlice(target, input, ctx);
|
|
208
|
+
case 'Enumerate':
|
|
209
|
+
return evaluateEnumerate(target, input);
|
|
210
|
+
case 'Spread':
|
|
211
|
+
return evaluateSpread(target, ctx);
|
|
212
|
+
default:
|
|
213
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown pipe target type: ${target.type}`, getNodeLocation(target));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// ============================================================
|
|
217
|
+
// STATEMENT EXECUTION
|
|
218
|
+
// ============================================================
|
|
219
|
+
export async function executeStatement(stmt, ctx) {
|
|
220
|
+
const value = await evaluateExpression(stmt.expression, ctx);
|
|
221
|
+
handleCapture(stmt.capture, value, ctx);
|
|
222
|
+
ctx.pipeValue = value;
|
|
223
|
+
checkAutoExceptions(value, ctx, stmt);
|
|
224
|
+
if (stmt.terminator === 'break') {
|
|
225
|
+
throw new BreakSignal(value);
|
|
226
|
+
}
|
|
227
|
+
if (stmt.terminator === 'return') {
|
|
228
|
+
throw new ReturnSignal(value);
|
|
229
|
+
}
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
// ============================================================
|
|
233
|
+
// SPREAD OPERATIONS
|
|
234
|
+
// ============================================================
|
|
235
|
+
async function evaluateParallelSpread(node, input, ctx) {
|
|
236
|
+
const target = await evaluateExpression(node.target, ctx);
|
|
237
|
+
const inputArray = Array.isArray(input) ? input : null;
|
|
238
|
+
const targetArray = Array.isArray(target) ? target : null;
|
|
239
|
+
if (inputArray && targetArray) {
|
|
240
|
+
if (inputArray.length !== targetArray.length) {
|
|
241
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Parallel zip requires equal lengths: got ${inputArray.length} args and ${targetArray.length} closures`, node.span.start);
|
|
242
|
+
}
|
|
243
|
+
const promises = inputArray.map((arg, i) => {
|
|
244
|
+
const closure = targetArray[i];
|
|
245
|
+
if (closure === undefined) {
|
|
246
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Missing closure at index ${i}`, node.span.start);
|
|
247
|
+
}
|
|
248
|
+
return invokeAsCallableOrFunction(closure, [arg], ctx, node.span.start);
|
|
249
|
+
});
|
|
250
|
+
return Promise.all(promises);
|
|
251
|
+
}
|
|
252
|
+
else if (inputArray && !targetArray) {
|
|
253
|
+
const promises = inputArray.map((arg) => invokeAsCallableOrFunction(target, [arg], ctx, node.span.start));
|
|
254
|
+
return Promise.all(promises);
|
|
255
|
+
}
|
|
256
|
+
else if (!inputArray && targetArray) {
|
|
257
|
+
const promises = targetArray.map((closure) => invokeAsCallableOrFunction(closure, [input], ctx, node.span.start));
|
|
258
|
+
return Promise.all(promises);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const result = await invokeAsCallableOrFunction(target, [input], ctx, node.span.start);
|
|
262
|
+
return [result];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function evaluateParallelFilter(node, input, ctx) {
|
|
266
|
+
if (!Array.isArray(input)) {
|
|
267
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Filter requires tuple, got ${isDict(input) ? 'dict' : typeof input}`, node.span.start);
|
|
268
|
+
}
|
|
269
|
+
const results = [];
|
|
270
|
+
for (const element of input) {
|
|
271
|
+
const savedPipeValue = ctx.pipeValue;
|
|
272
|
+
ctx.pipeValue = element;
|
|
273
|
+
let predicateResult;
|
|
274
|
+
if (node.predicate.type === 'Block') {
|
|
275
|
+
predicateResult = await evaluateBlockExpression(node.predicate, ctx);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const closure = ctx.variables.get(node.predicate.name ?? '');
|
|
279
|
+
if (!closure) {
|
|
280
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Undefined variable: $${node.predicate.name}`, node.predicate.span.start, { variableName: node.predicate.name });
|
|
281
|
+
}
|
|
282
|
+
if (!isCallable(closure)) {
|
|
283
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Filter predicate must be callable, got ${typeof closure}`, node.predicate.span.start);
|
|
284
|
+
}
|
|
285
|
+
predicateResult = await invokeCallable(closure, [element], ctx, node.predicate.span.start);
|
|
286
|
+
}
|
|
287
|
+
if (isTruthy(predicateResult)) {
|
|
288
|
+
results.push(element);
|
|
289
|
+
}
|
|
290
|
+
ctx.pipeValue = savedPipeValue;
|
|
291
|
+
}
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
async function evaluateSequentialSpread(node, input, ctx) {
|
|
295
|
+
const target = await evaluateExpression(node.target, ctx);
|
|
296
|
+
const closures = Array.isArray(target) ? target : [target];
|
|
297
|
+
let accumulated = input;
|
|
298
|
+
for (const closure of closures) {
|
|
299
|
+
accumulated = await invokeAsCallableOrFunction(closure, [accumulated], ctx, node.span.start);
|
|
300
|
+
}
|
|
301
|
+
return accumulated;
|
|
302
|
+
}
|
|
303
|
+
async function invokeAsCallableOrFunction(callableOrName, args, ctx, location) {
|
|
304
|
+
if (isCallable(callableOrName)) {
|
|
305
|
+
return invokeCallable(callableOrName, args, ctx, location);
|
|
306
|
+
}
|
|
307
|
+
if (typeof callableOrName === 'string') {
|
|
308
|
+
const fn = ctx.functions.get(callableOrName);
|
|
309
|
+
if (fn) {
|
|
310
|
+
const result = fn(args, ctx, location);
|
|
311
|
+
return result instanceof Promise ? result : result;
|
|
312
|
+
}
|
|
313
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_FUNCTION, `Unknown function: ${callableOrName}`, location, { functionName: callableOrName });
|
|
314
|
+
}
|
|
315
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Expected callable or function name, got ${typeof callableOrName}`, location);
|
|
316
|
+
}
|
|
317
|
+
function evaluateCapture(node, input, ctx) {
|
|
318
|
+
setVariable(ctx, node.name, input, node.typeName, node.span.start);
|
|
319
|
+
ctx.observability.onCapture?.({ name: node.name, value: input });
|
|
320
|
+
return input;
|
|
321
|
+
}
|
|
322
|
+
// ============================================================
|
|
323
|
+
// EXTRACTION OPERATORS
|
|
324
|
+
// ============================================================
|
|
325
|
+
function evaluateDestructure(node, input, ctx) {
|
|
326
|
+
const isTuple = Array.isArray(input);
|
|
327
|
+
const isDictInput = isDict(input);
|
|
328
|
+
const firstNonSkip = node.elements.find((e) => e.kind !== 'skip');
|
|
329
|
+
const isKeyPattern = firstNonSkip?.kind === 'keyValue';
|
|
330
|
+
if (isKeyPattern) {
|
|
331
|
+
if (!isDictInput) {
|
|
332
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Key destructure requires dict, got ${isTuple ? 'tuple' : typeof input}`, node.span.start);
|
|
333
|
+
}
|
|
334
|
+
for (const elem of node.elements) {
|
|
335
|
+
if (elem.kind === 'skip')
|
|
336
|
+
continue;
|
|
337
|
+
if (elem.kind === 'nested') {
|
|
338
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Nested destructure not supported in dict patterns', elem.span.start);
|
|
339
|
+
}
|
|
340
|
+
if (elem.kind !== 'keyValue' || elem.key === null || elem.name === null) {
|
|
341
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Dict destructure requires key: $var patterns', elem.span.start);
|
|
342
|
+
}
|
|
343
|
+
const dictInput = input;
|
|
344
|
+
if (!(elem.key in dictInput)) {
|
|
345
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Key '${elem.key}' not found in dict`, elem.span.start, { key: elem.key, availableKeys: Object.keys(dictInput) });
|
|
346
|
+
}
|
|
347
|
+
const dictValue = dictInput[elem.key];
|
|
348
|
+
if (dictValue === undefined) {
|
|
349
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Key '${elem.key}' has undefined value`, elem.span.start);
|
|
350
|
+
}
|
|
351
|
+
setVariable(ctx, elem.name, dictValue, elem.typeName, elem.span.start);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
if (!isTuple) {
|
|
356
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Positional destructure requires tuple, got ${isDictInput ? 'dict' : typeof input}`, node.span.start);
|
|
357
|
+
}
|
|
358
|
+
const tupleInput = input;
|
|
359
|
+
if (node.elements.length !== tupleInput.length) {
|
|
360
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Destructure pattern has ${node.elements.length} elements, tuple has ${tupleInput.length}`, node.span.start);
|
|
361
|
+
}
|
|
362
|
+
for (let i = 0; i < node.elements.length; i++) {
|
|
363
|
+
const elem = node.elements[i];
|
|
364
|
+
const value = tupleInput[i];
|
|
365
|
+
if (elem === undefined || value === undefined) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (elem.kind === 'skip')
|
|
369
|
+
continue;
|
|
370
|
+
if (elem.kind === 'nested' && elem.nested) {
|
|
371
|
+
evaluateDestructure(elem.nested, value, ctx);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (elem.name === null) {
|
|
375
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Invalid destructure element', elem.span.start);
|
|
376
|
+
}
|
|
377
|
+
setVariable(ctx, elem.name, value, elem.typeName, elem.span.start);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return input;
|
|
381
|
+
}
|
|
382
|
+
function evaluateSlice(node, input, ctx) {
|
|
383
|
+
const isTuple = Array.isArray(input);
|
|
384
|
+
const isString = typeof input === 'string';
|
|
385
|
+
if (!isTuple && !isString) {
|
|
386
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Slice requires tuple or string, got ${isDict(input) ? 'dict' : typeof input}`, node.span.start);
|
|
387
|
+
}
|
|
388
|
+
const startBound = node.start ? evaluateSliceBound(node.start, ctx) : null;
|
|
389
|
+
const stopBound = node.stop ? evaluateSliceBound(node.stop, ctx) : null;
|
|
390
|
+
const stepBound = node.step ? evaluateSliceBound(node.step, ctx) : null;
|
|
391
|
+
if (isTuple) {
|
|
392
|
+
return applySlice(input, input.length, startBound, stopBound, stepBound);
|
|
393
|
+
}
|
|
394
|
+
return applySlice(input, input.length, startBound, stopBound, stepBound);
|
|
395
|
+
}
|
|
396
|
+
function evaluateSliceBound(bound, ctx) {
|
|
397
|
+
if (bound === null) {
|
|
398
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Slice bound is null', undefined);
|
|
399
|
+
}
|
|
400
|
+
switch (bound.type) {
|
|
401
|
+
case 'NumberLiteral':
|
|
402
|
+
return bound.value;
|
|
403
|
+
case 'Variable': {
|
|
404
|
+
const value = evaluateVariable(bound, ctx);
|
|
405
|
+
if (typeof value !== 'number') {
|
|
406
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Slice bound must be number, got ${typeof value}`, bound.span.start);
|
|
407
|
+
}
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
case 'Arithmetic':
|
|
411
|
+
return evaluateArithmetic(bound, ctx);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function applySlice(input, len, start, stop, step) {
|
|
415
|
+
const actualStep = step ?? 1;
|
|
416
|
+
if (actualStep === 0) {
|
|
417
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Slice step cannot be zero', undefined);
|
|
418
|
+
}
|
|
419
|
+
const normalizeIndex = (idx, defaultVal, forStep) => {
|
|
420
|
+
if (idx === null)
|
|
421
|
+
return defaultVal;
|
|
422
|
+
let normalized = idx < 0 ? len + idx : idx;
|
|
423
|
+
if (forStep > 0) {
|
|
424
|
+
normalized = Math.max(0, Math.min(len, normalized));
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
normalized = Math.max(-1, Math.min(len - 1, normalized));
|
|
428
|
+
}
|
|
429
|
+
return normalized;
|
|
430
|
+
};
|
|
431
|
+
const actualStart = normalizeIndex(start, actualStep > 0 ? 0 : len - 1, actualStep);
|
|
432
|
+
const actualStop = normalizeIndex(stop, actualStep > 0 ? len : -1, actualStep);
|
|
433
|
+
const indices = [];
|
|
434
|
+
if (actualStep > 0) {
|
|
435
|
+
for (let i = actualStart; i < actualStop; i += actualStep) {
|
|
436
|
+
indices.push(i);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
for (let i = actualStart; i > actualStop; i += actualStep) {
|
|
441
|
+
indices.push(i);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (Array.isArray(input)) {
|
|
445
|
+
return indices.map((i) => input[i]);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
return indices.map((i) => input[i]).join('');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function evaluateSpread(node, ctx) {
|
|
452
|
+
let value;
|
|
453
|
+
if (node.operand === null) {
|
|
454
|
+
value = ctx.pipeValue;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
value = await evaluateExpression(node.operand, ctx);
|
|
458
|
+
}
|
|
459
|
+
if (Array.isArray(value)) {
|
|
460
|
+
return createArgsFromTuple(value);
|
|
461
|
+
}
|
|
462
|
+
if (isDict(value)) {
|
|
463
|
+
return createArgsFromDict(value);
|
|
464
|
+
}
|
|
465
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Spread requires tuple or dict, got ${inferType(value)}`, node.span.start);
|
|
466
|
+
}
|
|
467
|
+
function evaluateEnumerate(node, input) {
|
|
468
|
+
if (Array.isArray(input)) {
|
|
469
|
+
return input.map((value, index) => ({
|
|
470
|
+
index,
|
|
471
|
+
value,
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
if (isDict(input)) {
|
|
475
|
+
const keys = Object.keys(input).sort();
|
|
476
|
+
return keys.map((key, index) => ({
|
|
477
|
+
index,
|
|
478
|
+
key,
|
|
479
|
+
value: input[key],
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Enumerate requires tuple or dict, got ${typeof input}`, node.span.start);
|
|
483
|
+
}
|
|
484
|
+
// ============================================================
|
|
485
|
+
// LITERAL EVALUATION
|
|
486
|
+
// ============================================================
|
|
487
|
+
async function evaluateString(node, ctx) {
|
|
488
|
+
let result = '';
|
|
489
|
+
for (const part of node.parts) {
|
|
490
|
+
if (typeof part === 'string') {
|
|
491
|
+
result += part;
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
result += formatValue(ctx.pipeValue);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Handle {$fn(args)} patterns
|
|
498
|
+
const varCallPattern = /\{\s*\$([a-zA-Z_][a-zA-Z0-9_]*)\(\s*([^)]*)\s*\)\s*\}/g;
|
|
499
|
+
const varCallMatches = [...result.matchAll(varCallPattern)];
|
|
500
|
+
for (const match of varCallMatches.reverse()) {
|
|
501
|
+
const fullMatch = match[0];
|
|
502
|
+
const fnName = match[1] ?? '';
|
|
503
|
+
const argsStr = match[2] ?? '';
|
|
504
|
+
const closure = ctx.variables.get(fnName);
|
|
505
|
+
if (closure && isCallable(closure)) {
|
|
506
|
+
const args = parseInterpolationArgs(argsStr, ctx);
|
|
507
|
+
const callResult = await invokeCallable(closure, args, ctx, node.span.start);
|
|
508
|
+
result =
|
|
509
|
+
result.slice(0, match.index) +
|
|
510
|
+
formatValue(callResult) +
|
|
511
|
+
result.slice(match.index + fullMatch.length);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Handle {$} and {$.field} patterns
|
|
515
|
+
result = result.replace(/\{\s*\$(?![a-zA-Z_])([^}]*)\}/g, (_match, field) => {
|
|
516
|
+
let value = ctx.pipeValue;
|
|
517
|
+
const trimmed = field.trim();
|
|
518
|
+
if (trimmed) {
|
|
519
|
+
value = accessField(value, trimmed.slice(1));
|
|
520
|
+
}
|
|
521
|
+
return formatValue(value);
|
|
522
|
+
});
|
|
523
|
+
// Handle {$name} and {$name.field} patterns
|
|
524
|
+
const varPattern = /\{\s*\$([a-zA-Z_][a-zA-Z0-9_]*)([^}]*)\}/g;
|
|
525
|
+
const varMatches = [...result.matchAll(varPattern)];
|
|
526
|
+
for (const match of varMatches.reverse()) {
|
|
527
|
+
const fullMatch = match[0];
|
|
528
|
+
const name = match[1] ?? '';
|
|
529
|
+
const field = match[2] ?? '';
|
|
530
|
+
const idx = match.index;
|
|
531
|
+
if (!ctx.variables.has(name)) {
|
|
532
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Undefined variable: $${name}`, getNodeLocation(node), { variableName: name });
|
|
533
|
+
}
|
|
534
|
+
let value = ctx.variables.get(name) ?? null;
|
|
535
|
+
if (isScriptCallable(value)) {
|
|
536
|
+
const args = value.params.length > 0 ? [ctx.pipeValue] : [];
|
|
537
|
+
value = await invokeScriptCallable(value, args, ctx, node.span.start);
|
|
538
|
+
}
|
|
539
|
+
const trimmed = field.trim();
|
|
540
|
+
if (trimmed) {
|
|
541
|
+
value = accessField(value, trimmed.slice(1));
|
|
542
|
+
}
|
|
543
|
+
result =
|
|
544
|
+
result.slice(0, idx) +
|
|
545
|
+
formatValue(value) +
|
|
546
|
+
result.slice(idx + fullMatch.length);
|
|
547
|
+
}
|
|
548
|
+
// Handle {.method} patterns
|
|
549
|
+
result = result.replace(/\{\s*\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}/g, (_match, methodName) => {
|
|
550
|
+
const method = BUILTIN_METHODS[methodName];
|
|
551
|
+
if (method) {
|
|
552
|
+
const methodResult = method(ctx.pipeValue, [], ctx);
|
|
553
|
+
return formatValue(methodResult);
|
|
554
|
+
}
|
|
555
|
+
return `{.${methodName}}`;
|
|
556
|
+
});
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
function parseInterpolationArgs(argsStr, ctx) {
|
|
560
|
+
const trimmed = argsStr.trim();
|
|
561
|
+
if (!trimmed)
|
|
562
|
+
return [];
|
|
563
|
+
const args = [];
|
|
564
|
+
const parts = trimmed.split(',').map((p) => p.trim());
|
|
565
|
+
for (const part of parts) {
|
|
566
|
+
if (part.startsWith('"') && part.endsWith('"')) {
|
|
567
|
+
args.push(part.slice(1, -1));
|
|
568
|
+
}
|
|
569
|
+
else if (/^-?\d+(\.\d+)?$/.test(part)) {
|
|
570
|
+
args.push(parseFloat(part));
|
|
571
|
+
}
|
|
572
|
+
else if (part === '$') {
|
|
573
|
+
args.push(ctx.pipeValue);
|
|
574
|
+
}
|
|
575
|
+
else if (part.startsWith('$')) {
|
|
576
|
+
const varName = part.slice(1);
|
|
577
|
+
args.push(ctx.variables.get(varName) ?? null);
|
|
578
|
+
}
|
|
579
|
+
else if (part === 'true') {
|
|
580
|
+
args.push(true);
|
|
581
|
+
}
|
|
582
|
+
else if (part === 'false') {
|
|
583
|
+
args.push(false);
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
args.push(part);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return args;
|
|
590
|
+
}
|
|
591
|
+
async function evaluateTuple(node, ctx) {
|
|
592
|
+
const elements = [];
|
|
593
|
+
for (const elem of node.elements) {
|
|
594
|
+
elements.push(await evaluateExpression(elem, ctx));
|
|
595
|
+
}
|
|
596
|
+
return elements;
|
|
597
|
+
}
|
|
598
|
+
function isFunctionLiteralExpr(expr) {
|
|
599
|
+
if (expr.pipes.length > 0)
|
|
600
|
+
return false;
|
|
601
|
+
if (expr.head.methods.length > 0)
|
|
602
|
+
return false;
|
|
603
|
+
return expr.head.primary.type === 'FunctionLiteral';
|
|
604
|
+
}
|
|
605
|
+
async function evaluateDict(node, ctx) {
|
|
606
|
+
const result = {};
|
|
607
|
+
for (const entry of node.entries) {
|
|
608
|
+
if (isReservedMethod(entry.key)) {
|
|
609
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot use reserved method name '${entry.key}' as dict key`, entry.span.start, { key: entry.key, reservedMethods: ['keys', 'values', 'entries'] });
|
|
610
|
+
}
|
|
611
|
+
if (isFunctionLiteralExpr(entry.value)) {
|
|
612
|
+
const fnLit = entry.value.head.primary;
|
|
613
|
+
const closure = await createClosure(fnLit, ctx);
|
|
614
|
+
result[entry.key] = closure;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
result[entry.key] = await evaluateExpression(entry.value, ctx);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
for (const key of Object.keys(result)) {
|
|
621
|
+
const value = result[key];
|
|
622
|
+
if (value !== undefined && isCallable(value)) {
|
|
623
|
+
result[key] = {
|
|
624
|
+
...value,
|
|
625
|
+
boundDict: result,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
631
|
+
async function createClosure(node, ctx) {
|
|
632
|
+
const capturedVars = new Map(ctx.variables);
|
|
633
|
+
const params = [];
|
|
634
|
+
for (const param of node.params) {
|
|
635
|
+
let defaultValue = null;
|
|
636
|
+
if (param.defaultValue) {
|
|
637
|
+
defaultValue = await evaluatePrimary(param.defaultValue, ctx);
|
|
638
|
+
}
|
|
639
|
+
params.push({
|
|
640
|
+
name: param.name,
|
|
641
|
+
typeName: param.typeName,
|
|
642
|
+
defaultValue,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const isProperty = params.length === 0;
|
|
646
|
+
return {
|
|
647
|
+
__type: 'callable',
|
|
648
|
+
kind: 'script',
|
|
649
|
+
params,
|
|
650
|
+
body: node.body,
|
|
651
|
+
capturedVars,
|
|
652
|
+
isProperty,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
// ============================================================
|
|
656
|
+
// VARIABLE EVALUATION
|
|
657
|
+
// ============================================================
|
|
658
|
+
function getBaseVariableValue(node, ctx) {
|
|
659
|
+
if (node.isPipeVar)
|
|
660
|
+
return ctx.pipeValue;
|
|
661
|
+
if (node.name)
|
|
662
|
+
return ctx.variables.get(node.name) ?? null;
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
function evaluateVariable(node, ctx) {
|
|
666
|
+
let value = getBaseVariableValue(node, ctx);
|
|
667
|
+
for (const access of node.fieldAccess) {
|
|
668
|
+
value = accessField(value, access.field);
|
|
669
|
+
}
|
|
670
|
+
return value;
|
|
671
|
+
}
|
|
672
|
+
async function evaluateVariableAsync(node, ctx) {
|
|
673
|
+
let value = getBaseVariableValue(node, ctx);
|
|
674
|
+
for (const access of node.fieldAccess) {
|
|
675
|
+
value = accessField(value, access.field);
|
|
676
|
+
if (isCallable(value) && value.isProperty && value.boundDict) {
|
|
677
|
+
value = await invokeCallable(value, [], ctx, node.span.start);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return value;
|
|
681
|
+
}
|
|
682
|
+
function accessField(value, field) {
|
|
683
|
+
if (value === null)
|
|
684
|
+
return null;
|
|
685
|
+
if (typeof field === 'number') {
|
|
686
|
+
if (Array.isArray(value))
|
|
687
|
+
return value[field] ?? null;
|
|
688
|
+
if (typeof value === 'string')
|
|
689
|
+
return value[field] ?? '';
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
if (typeof value === 'object' && !Array.isArray(value) && !isScriptCallable(value)) {
|
|
693
|
+
return value[field] ?? null;
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
// ============================================================
|
|
698
|
+
// FUNCTION & METHOD EVALUATION
|
|
699
|
+
// ============================================================
|
|
700
|
+
async function evaluateFunctionCall(node, ctx) {
|
|
701
|
+
checkAborted(ctx, node);
|
|
702
|
+
const fn = ctx.functions.get(node.name);
|
|
703
|
+
if (!fn) {
|
|
704
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_FUNCTION, `Unknown function: ${node.name}`, getNodeLocation(node), { functionName: node.name });
|
|
705
|
+
}
|
|
706
|
+
const args = await evaluateArgs(node.args, ctx);
|
|
707
|
+
if (args.length === 0 && ctx.pipeValue !== null) {
|
|
708
|
+
args.push(ctx.pipeValue);
|
|
709
|
+
}
|
|
710
|
+
ctx.observability.onFunctionCall?.({ name: node.name, args });
|
|
711
|
+
const startTime = Date.now();
|
|
712
|
+
const location = getNodeLocation(node);
|
|
713
|
+
const result = fn(args, ctx, location);
|
|
714
|
+
let value;
|
|
715
|
+
if (result instanceof Promise) {
|
|
716
|
+
value = await withTimeout(result, ctx.timeout, node.name, node);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
value = result;
|
|
720
|
+
}
|
|
721
|
+
ctx.observability.onFunctionReturn?.({
|
|
722
|
+
name: node.name,
|
|
723
|
+
value,
|
|
724
|
+
durationMs: Date.now() - startTime,
|
|
725
|
+
});
|
|
726
|
+
return value;
|
|
727
|
+
}
|
|
728
|
+
async function evaluateVariableCall(node, ctx) {
|
|
729
|
+
return evaluateVariableCallWithPipe(node, ctx.pipeValue, ctx);
|
|
730
|
+
}
|
|
731
|
+
async function evaluateVariableCallWithPipe(node, pipeInput, ctx) {
|
|
732
|
+
const closure = ctx.variables.get(node.name);
|
|
733
|
+
if (!closure) {
|
|
734
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Unknown variable: $${node.name}`, getNodeLocation(node), { variableName: node.name });
|
|
735
|
+
}
|
|
736
|
+
if (!isCallable(closure)) {
|
|
737
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Variable $${node.name} is not a function (got ${typeof closure})`, getNodeLocation(node), { variableName: node.name, actualType: typeof closure });
|
|
738
|
+
}
|
|
739
|
+
const args = await evaluateArgs(node.args, ctx);
|
|
740
|
+
if (isScriptCallable(closure) &&
|
|
741
|
+
args.length === 0 &&
|
|
742
|
+
pipeInput !== null &&
|
|
743
|
+
closure.params.length > 0) {
|
|
744
|
+
const firstParam = closure.params[0];
|
|
745
|
+
if (firstParam?.defaultValue === null && !isCallable(pipeInput)) {
|
|
746
|
+
args.push(pipeInput);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return invokeCallable(closure, args, ctx, node.span.start);
|
|
750
|
+
}
|
|
751
|
+
async function invokeCallable(callable, args, ctx, callLocation) {
|
|
752
|
+
checkAborted(ctx, undefined);
|
|
753
|
+
if (callable.kind === 'script') {
|
|
754
|
+
return invokeScriptCallable(callable, args, ctx, callLocation);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
return invokeFnCallable(callable, args, ctx, callLocation);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
async function invokeFnCallable(callable, args, ctx, callLocation) {
|
|
761
|
+
const effectiveArgs = callable.boundDict && args.length === 0 ? [callable.boundDict] : args;
|
|
762
|
+
const result = callable.fn(effectiveArgs, ctx, callLocation);
|
|
763
|
+
return result instanceof Promise ? await result : result;
|
|
764
|
+
}
|
|
765
|
+
// ============================================================
|
|
766
|
+
// CALLABLE INVOCATION HELPERS
|
|
767
|
+
// ============================================================
|
|
768
|
+
function createCallableContext(callable, ctx) {
|
|
769
|
+
const callableCtx = {
|
|
770
|
+
...ctx,
|
|
771
|
+
variables: new Map(callable.capturedVars),
|
|
772
|
+
variableTypes: new Map(ctx.variableTypes),
|
|
773
|
+
};
|
|
774
|
+
if (callable.boundDict) {
|
|
775
|
+
callableCtx.pipeValue = callable.boundDict;
|
|
776
|
+
}
|
|
777
|
+
return callableCtx;
|
|
778
|
+
}
|
|
779
|
+
function inferTypeFromDefault(defaultValue) {
|
|
780
|
+
if (defaultValue === null)
|
|
781
|
+
return null;
|
|
782
|
+
const t = inferType(defaultValue);
|
|
783
|
+
return t === 'string' || t === 'number' || t === 'bool' ? t : null;
|
|
784
|
+
}
|
|
785
|
+
function validateParamType(param, value, callLocation) {
|
|
786
|
+
const expectedType = param.typeName ?? inferTypeFromDefault(param.defaultValue);
|
|
787
|
+
if (expectedType !== null) {
|
|
788
|
+
const valueType = inferType(value);
|
|
789
|
+
if (valueType !== expectedType) {
|
|
790
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Parameter type mismatch: ${param.name} expects ${expectedType}, got ${valueType}`, callLocation, { paramName: param.name, expectedType, actualType: valueType });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function invokeScriptCallable(callable, args, ctx, callLocation) {
|
|
795
|
+
const firstArg = args[0];
|
|
796
|
+
if (args.length === 1 && firstArg !== undefined && isArgs(firstArg)) {
|
|
797
|
+
return invokeScriptCallableWithArgs(callable, firstArg, ctx, callLocation);
|
|
798
|
+
}
|
|
799
|
+
const callableCtx = createCallableContext(callable, ctx);
|
|
800
|
+
for (let i = 0; i < callable.params.length; i++) {
|
|
801
|
+
const param = callable.params[i];
|
|
802
|
+
let value;
|
|
803
|
+
if (i < args.length) {
|
|
804
|
+
value = args[i];
|
|
805
|
+
}
|
|
806
|
+
else if (param.defaultValue !== null) {
|
|
807
|
+
value = param.defaultValue;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Missing argument for parameter '${param.name}' at position ${i}`, callLocation, { paramName: param.name, position: i });
|
|
811
|
+
}
|
|
812
|
+
validateParamType(param, value, callLocation);
|
|
813
|
+
callableCtx.variables.set(param.name, value);
|
|
814
|
+
}
|
|
815
|
+
return evaluateBlockExpression(callable.body, callableCtx);
|
|
816
|
+
}
|
|
817
|
+
async function invokeScriptCallableWithArgs(closure, argsValue, ctx, callLocation) {
|
|
818
|
+
const closureCtx = createCallableContext(closure, ctx);
|
|
819
|
+
const hasNumericKeys = [...argsValue.entries.keys()].some((k) => typeof k === 'number');
|
|
820
|
+
const hasStringKeys = [...argsValue.entries.keys()].some((k) => typeof k === 'string');
|
|
821
|
+
if (hasNumericKeys && hasStringKeys) {
|
|
822
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Args cannot mix positional (numeric) and named (string) keys', callLocation);
|
|
823
|
+
}
|
|
824
|
+
const boundParams = new Set();
|
|
825
|
+
if (hasNumericKeys) {
|
|
826
|
+
for (const [key, value] of argsValue.entries) {
|
|
827
|
+
const position = key;
|
|
828
|
+
const param = closure.params[position];
|
|
829
|
+
if (param === undefined) {
|
|
830
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Extra argument at position ${position} (closure has ${closure.params.length} params)`, callLocation, { position, paramCount: closure.params.length });
|
|
831
|
+
}
|
|
832
|
+
validateParamType(param, value, callLocation);
|
|
833
|
+
closureCtx.variables.set(param.name, value);
|
|
834
|
+
boundParams.add(param.name);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else if (hasStringKeys) {
|
|
838
|
+
const paramNames = new Set(closure.params.map((p) => p.name));
|
|
839
|
+
for (const [key, value] of argsValue.entries) {
|
|
840
|
+
const name = key;
|
|
841
|
+
if (!paramNames.has(name)) {
|
|
842
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown argument '${name}' (valid params: ${[...paramNames].join(', ')})`, callLocation, { argName: name, validParams: [...paramNames] });
|
|
843
|
+
}
|
|
844
|
+
const param = closure.params.find((p) => p.name === name);
|
|
845
|
+
validateParamType(param, value, callLocation);
|
|
846
|
+
closureCtx.variables.set(name, value);
|
|
847
|
+
boundParams.add(name);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
for (const param of closure.params) {
|
|
851
|
+
if (!boundParams.has(param.name)) {
|
|
852
|
+
if (param.defaultValue !== null) {
|
|
853
|
+
closureCtx.variables.set(param.name, param.defaultValue);
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Missing argument '${param.name}' (no default value)`, callLocation, { paramName: param.name });
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return evaluateBlockExpression(closure.body, closureCtx);
|
|
861
|
+
}
|
|
862
|
+
async function evaluateInvoke(node, input, ctx) {
|
|
863
|
+
if (!isScriptCallable(input)) {
|
|
864
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot invoke non-closure value (got ${typeof input})`, getNodeLocation(node));
|
|
865
|
+
}
|
|
866
|
+
const args = await evaluateArgs(node.args, ctx);
|
|
867
|
+
return invokeScriptCallable(input, args, ctx, node.span.start);
|
|
868
|
+
}
|
|
869
|
+
async function evaluateMethod(node, receiver, ctx) {
|
|
870
|
+
checkAborted(ctx, node);
|
|
871
|
+
if (isCallable(receiver)) {
|
|
872
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Method .${node.name} not available on callable (invoke with -> () first)`, getNodeLocation(node), { methodName: node.name, receiverType: 'callable' });
|
|
873
|
+
}
|
|
874
|
+
const args = await evaluateArgs(node.args, ctx);
|
|
875
|
+
if (isDict(receiver)) {
|
|
876
|
+
const dictValue = receiver[node.name];
|
|
877
|
+
if (dictValue !== undefined && isCallable(dictValue)) {
|
|
878
|
+
return invokeCallable(dictValue, args, ctx, getNodeLocation(node));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const method = ctx.methods.get(node.name);
|
|
882
|
+
if (!method) {
|
|
883
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_METHOD, `Unknown method: ${node.name}`, getNodeLocation(node), { methodName: node.name });
|
|
884
|
+
}
|
|
885
|
+
const result = method(receiver, args, ctx, getNodeLocation(node));
|
|
886
|
+
return result instanceof Promise ? await result : result;
|
|
887
|
+
}
|
|
888
|
+
// ============================================================
|
|
889
|
+
// CONTROL FLOW EVALUATION
|
|
890
|
+
// ============================================================
|
|
891
|
+
async function evaluateConditional(node, ctx) {
|
|
892
|
+
let conditionResult;
|
|
893
|
+
if (node.condition) {
|
|
894
|
+
conditionResult = await evaluateBoolExpr(node.condition, ctx);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
conditionResult = isTruthy(ctx.pipeValue);
|
|
898
|
+
}
|
|
899
|
+
if (conditionResult) {
|
|
900
|
+
return evaluateBlock(node.thenBlock, ctx);
|
|
901
|
+
}
|
|
902
|
+
else if (node.elseClause) {
|
|
903
|
+
if (node.elseClause.type === 'Conditional') {
|
|
904
|
+
return evaluateConditional(node.elseClause, ctx);
|
|
905
|
+
}
|
|
906
|
+
return evaluateBlock(node.elseClause, ctx);
|
|
907
|
+
}
|
|
908
|
+
return ctx.pipeValue;
|
|
909
|
+
}
|
|
910
|
+
async function evaluateWhileLoop(node, ctx) {
|
|
911
|
+
const inputValue = ctx.pipeValue;
|
|
912
|
+
let maxIterations = Infinity;
|
|
913
|
+
if (node.maxIterations) {
|
|
914
|
+
const maxVal = await evaluateExpression(node.maxIterations, ctx);
|
|
915
|
+
if (typeof maxVal === 'number') {
|
|
916
|
+
maxIterations = maxVal;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
ctx.pipeValue = inputValue;
|
|
920
|
+
let iterations = 0;
|
|
921
|
+
let value = ctx.pipeValue;
|
|
922
|
+
try {
|
|
923
|
+
while (iterations < maxIterations) {
|
|
924
|
+
checkAborted(ctx, node);
|
|
925
|
+
const conditionResult = await evaluateBoolExpr(node.condition, ctx);
|
|
926
|
+
if (!conditionResult)
|
|
927
|
+
break;
|
|
928
|
+
value = await evaluateBlock(node.body, ctx);
|
|
929
|
+
ctx.pipeValue = value;
|
|
930
|
+
iterations++;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
catch (e) {
|
|
934
|
+
if (e instanceof BreakSignal) {
|
|
935
|
+
return e.value;
|
|
936
|
+
}
|
|
937
|
+
throw e;
|
|
938
|
+
}
|
|
939
|
+
return value;
|
|
940
|
+
}
|
|
941
|
+
async function evaluateForLoop(node, ctx) {
|
|
942
|
+
const input = ctx.pipeValue;
|
|
943
|
+
const results = [];
|
|
944
|
+
try {
|
|
945
|
+
if (Array.isArray(input)) {
|
|
946
|
+
for (const item of input) {
|
|
947
|
+
checkAborted(ctx, node);
|
|
948
|
+
ctx.pipeValue = item;
|
|
949
|
+
results.push(await evaluateBlock(node.body, ctx));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
else if (typeof input === 'string') {
|
|
953
|
+
for (const char of input) {
|
|
954
|
+
checkAborted(ctx, node);
|
|
955
|
+
ctx.pipeValue = char;
|
|
956
|
+
results.push(await evaluateBlock(node.body, ctx));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
checkAborted(ctx, node);
|
|
961
|
+
results.push(await evaluateBlock(node.body, ctx));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
catch (e) {
|
|
965
|
+
if (e instanceof BreakSignal) {
|
|
966
|
+
return e.value;
|
|
967
|
+
}
|
|
968
|
+
throw e;
|
|
969
|
+
}
|
|
970
|
+
return results;
|
|
971
|
+
}
|
|
972
|
+
async function evaluateBlock(node, ctx) {
|
|
973
|
+
let lastValue = ctx.pipeValue;
|
|
974
|
+
for (const stmt of node.statements) {
|
|
975
|
+
lastValue = await executeStatement(stmt, ctx);
|
|
976
|
+
}
|
|
977
|
+
return lastValue;
|
|
978
|
+
}
|
|
979
|
+
async function evaluateBlockExpression(node, ctx) {
|
|
980
|
+
try {
|
|
981
|
+
return await evaluateBlock(node, ctx);
|
|
982
|
+
}
|
|
983
|
+
catch (e) {
|
|
984
|
+
if (e instanceof ReturnSignal) {
|
|
985
|
+
return e.value;
|
|
986
|
+
}
|
|
987
|
+
throw e;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// ============================================================
|
|
991
|
+
// BOOLEAN EXPRESSION EVALUATION
|
|
992
|
+
// ============================================================
|
|
993
|
+
async function evaluateBoolExpr(expr, ctx) {
|
|
994
|
+
if (expr.type === 'Comparison') {
|
|
995
|
+
return evaluateComparison(expr, ctx);
|
|
996
|
+
}
|
|
997
|
+
switch (expr.op) {
|
|
998
|
+
case 'or': {
|
|
999
|
+
for (const operand of expr.operands) {
|
|
1000
|
+
if (await evaluateBoolExpr(operand, ctx))
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
case 'and': {
|
|
1006
|
+
for (const operand of expr.operands) {
|
|
1007
|
+
if (!(await evaluateBoolExpr(operand, ctx)))
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
case 'not': {
|
|
1013
|
+
return !(await evaluateBoolExpr(expr.operand, ctx));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
async function evaluateComparison(node, ctx) {
|
|
1018
|
+
const left = await evaluateSimplePrimary(node.left, ctx);
|
|
1019
|
+
if (!node.op || !node.right) {
|
|
1020
|
+
return isTruthy(left);
|
|
1021
|
+
}
|
|
1022
|
+
const right = await evaluateSimplePrimary(node.right, ctx);
|
|
1023
|
+
switch (node.op) {
|
|
1024
|
+
case '==':
|
|
1025
|
+
return deepEquals(left, right);
|
|
1026
|
+
case '!=':
|
|
1027
|
+
return !deepEquals(left, right);
|
|
1028
|
+
case '<':
|
|
1029
|
+
return left < right;
|
|
1030
|
+
case '>':
|
|
1031
|
+
return left > right;
|
|
1032
|
+
case '<=':
|
|
1033
|
+
return left <= right;
|
|
1034
|
+
case '>=':
|
|
1035
|
+
return left >= right;
|
|
1036
|
+
default:
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async function evaluateSimplePrimary(node, ctx) {
|
|
1041
|
+
switch (node.type) {
|
|
1042
|
+
case 'StringLiteral':
|
|
1043
|
+
return evaluateString(node, ctx);
|
|
1044
|
+
case 'NumberLiteral':
|
|
1045
|
+
return node.value;
|
|
1046
|
+
case 'BoolLiteral':
|
|
1047
|
+
return node.value;
|
|
1048
|
+
case 'Tuple':
|
|
1049
|
+
return evaluateTuple(node, ctx);
|
|
1050
|
+
case 'Dict':
|
|
1051
|
+
return evaluateDict(node, ctx);
|
|
1052
|
+
case 'Variable':
|
|
1053
|
+
return evaluateVariable(node, ctx);
|
|
1054
|
+
case 'FunctionCall':
|
|
1055
|
+
return evaluateFunctionCall(node, ctx);
|
|
1056
|
+
case 'MethodCall':
|
|
1057
|
+
return evaluateMethod(node, ctx.pipeValue, ctx);
|
|
1058
|
+
case 'Block':
|
|
1059
|
+
return evaluateBlockExpression(node, ctx);
|
|
1060
|
+
case 'Arithmetic':
|
|
1061
|
+
return evaluateArithmetic(node, ctx);
|
|
1062
|
+
default:
|
|
1063
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown simple primary type: ${node.type}`, getNodeLocation(node));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
// ============================================================
|
|
1067
|
+
// ARITHMETIC
|
|
1068
|
+
// ============================================================
|
|
1069
|
+
function evaluateArithmetic(node, ctx) {
|
|
1070
|
+
if (node.op === null) {
|
|
1071
|
+
return evaluateArithOperand(node.left, ctx, node);
|
|
1072
|
+
}
|
|
1073
|
+
const left = evaluateArithOperand(node.left, ctx, node);
|
|
1074
|
+
const right = evaluateArithOperand(node.right, ctx, node);
|
|
1075
|
+
switch (node.op) {
|
|
1076
|
+
case '+':
|
|
1077
|
+
return left + right;
|
|
1078
|
+
case '-':
|
|
1079
|
+
return left - right;
|
|
1080
|
+
case '*':
|
|
1081
|
+
return left * right;
|
|
1082
|
+
case '/':
|
|
1083
|
+
if (right === 0) {
|
|
1084
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Division by zero', node.span.start);
|
|
1085
|
+
}
|
|
1086
|
+
return left / right;
|
|
1087
|
+
case '%':
|
|
1088
|
+
if (right === 0) {
|
|
1089
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Modulo by zero', node.span.start);
|
|
1090
|
+
}
|
|
1091
|
+
return left % right;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function evaluateArithOperand(operand, ctx, parent) {
|
|
1095
|
+
switch (operand.type) {
|
|
1096
|
+
case 'NumberLiteral':
|
|
1097
|
+
return operand.value;
|
|
1098
|
+
case 'Variable': {
|
|
1099
|
+
const value = evaluateVariable(operand, ctx);
|
|
1100
|
+
if (typeof value !== 'number') {
|
|
1101
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Arithmetic requires number, got ${inferType(value)}`, operand.span.start);
|
|
1102
|
+
}
|
|
1103
|
+
return value;
|
|
1104
|
+
}
|
|
1105
|
+
case 'Arithmetic':
|
|
1106
|
+
return evaluateArithmetic(operand, ctx);
|
|
1107
|
+
default:
|
|
1108
|
+
throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Invalid arithmetic operand: ${operand.type}`, parent.span.start);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
//# sourceMappingURL=evaluate.js.map
|