@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.
Files changed (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/dist/cli.d.ts +11 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +69 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/demo.d.ts +6 -0
  8. package/dist/demo.d.ts.map +1 -0
  9. package/dist/demo.js +121 -0
  10. package/dist/demo.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lexer/errors.d.ts +9 -0
  16. package/dist/lexer/errors.d.ts.map +1 -0
  17. package/dist/lexer/errors.js +12 -0
  18. package/dist/lexer/errors.js.map +1 -0
  19. package/dist/lexer/helpers.d.ts +14 -0
  20. package/dist/lexer/helpers.d.ts.map +1 -0
  21. package/dist/lexer/helpers.js +30 -0
  22. package/dist/lexer/helpers.js.map +1 -0
  23. package/dist/lexer/index.d.ts +8 -0
  24. package/dist/lexer/index.d.ts.map +1 -0
  25. package/dist/lexer/index.js +8 -0
  26. package/dist/lexer/index.js.map +1 -0
  27. package/dist/lexer/operators.d.ts +11 -0
  28. package/dist/lexer/operators.d.ts.map +1 -0
  29. package/dist/lexer/operators.js +58 -0
  30. package/dist/lexer/operators.js.map +1 -0
  31. package/dist/lexer/readers.d.ts +12 -0
  32. package/dist/lexer/readers.d.ts.map +1 -0
  33. package/dist/lexer/readers.js +144 -0
  34. package/dist/lexer/readers.js.map +1 -0
  35. package/dist/lexer/state.d.ts +18 -0
  36. package/dist/lexer/state.d.ts.map +1 -0
  37. package/dist/lexer/state.js +37 -0
  38. package/dist/lexer/state.js.map +1 -0
  39. package/dist/lexer/tokenizer.d.ts +9 -0
  40. package/dist/lexer/tokenizer.d.ts.map +1 -0
  41. package/dist/lexer/tokenizer.js +100 -0
  42. package/dist/lexer/tokenizer.js.map +1 -0
  43. package/dist/lexer.d.ts +19 -0
  44. package/dist/lexer.d.ts.map +1 -0
  45. package/dist/lexer.js +344 -0
  46. package/dist/lexer.js.map +1 -0
  47. package/dist/parser/arithmetic.d.ts +16 -0
  48. package/dist/parser/arithmetic.d.ts.map +1 -0
  49. package/dist/parser/arithmetic.js +128 -0
  50. package/dist/parser/arithmetic.js.map +1 -0
  51. package/dist/parser/boolean.d.ts +15 -0
  52. package/dist/parser/boolean.d.ts.map +1 -0
  53. package/dist/parser/boolean.js +20 -0
  54. package/dist/parser/boolean.js.map +1 -0
  55. package/dist/parser/control-flow.d.ts +56 -0
  56. package/dist/parser/control-flow.d.ts.map +1 -0
  57. package/dist/parser/control-flow.js +167 -0
  58. package/dist/parser/control-flow.js.map +1 -0
  59. package/dist/parser/expressions.d.ts +23 -0
  60. package/dist/parser/expressions.d.ts.map +1 -0
  61. package/dist/parser/expressions.js +950 -0
  62. package/dist/parser/expressions.js.map +1 -0
  63. package/dist/parser/extraction.d.ts +48 -0
  64. package/dist/parser/extraction.d.ts.map +1 -0
  65. package/dist/parser/extraction.js +279 -0
  66. package/dist/parser/extraction.js.map +1 -0
  67. package/dist/parser/functions.d.ts +20 -0
  68. package/dist/parser/functions.d.ts.map +1 -0
  69. package/dist/parser/functions.js +96 -0
  70. package/dist/parser/functions.js.map +1 -0
  71. package/dist/parser/helpers.d.ts +94 -0
  72. package/dist/parser/helpers.d.ts.map +1 -0
  73. package/dist/parser/helpers.js +225 -0
  74. package/dist/parser/helpers.js.map +1 -0
  75. package/dist/parser/index.d.ts +49 -0
  76. package/dist/parser/index.d.ts.map +1 -0
  77. package/dist/parser/index.js +73 -0
  78. package/dist/parser/index.js.map +1 -0
  79. package/dist/parser/literals.d.ts +37 -0
  80. package/dist/parser/literals.d.ts.map +1 -0
  81. package/dist/parser/literals.js +373 -0
  82. package/dist/parser/literals.js.map +1 -0
  83. package/dist/parser/parser-collect.d.ts +16 -0
  84. package/dist/parser/parser-collect.d.ts.map +1 -0
  85. package/dist/parser/parser-collect.js +125 -0
  86. package/dist/parser/parser-collect.js.map +1 -0
  87. package/dist/parser/parser-control.d.ts +20 -0
  88. package/dist/parser/parser-control.d.ts.map +1 -0
  89. package/dist/parser/parser-control.js +120 -0
  90. package/dist/parser/parser-control.js.map +1 -0
  91. package/dist/parser/parser-expr.d.ts +37 -0
  92. package/dist/parser/parser-expr.d.ts.map +1 -0
  93. package/dist/parser/parser-expr.js +639 -0
  94. package/dist/parser/parser-expr.js.map +1 -0
  95. package/dist/parser/parser-extract.d.ts +17 -0
  96. package/dist/parser/parser-extract.d.ts.map +1 -0
  97. package/dist/parser/parser-extract.js +222 -0
  98. package/dist/parser/parser-extract.js.map +1 -0
  99. package/dist/parser/parser-functions.d.ts +21 -0
  100. package/dist/parser/parser-functions.d.ts.map +1 -0
  101. package/dist/parser/parser-functions.js +155 -0
  102. package/dist/parser/parser-functions.js.map +1 -0
  103. package/dist/parser/parser-literals.d.ts +22 -0
  104. package/dist/parser/parser-literals.d.ts.map +1 -0
  105. package/dist/parser/parser-literals.js +288 -0
  106. package/dist/parser/parser-literals.js.map +1 -0
  107. package/dist/parser/parser-script.d.ts +21 -0
  108. package/dist/parser/parser-script.d.ts.map +1 -0
  109. package/dist/parser/parser-script.js +174 -0
  110. package/dist/parser/parser-script.js.map +1 -0
  111. package/dist/parser/parser-variables.d.ts +20 -0
  112. package/dist/parser/parser-variables.d.ts.map +1 -0
  113. package/dist/parser/parser-variables.js +146 -0
  114. package/dist/parser/parser-variables.js.map +1 -0
  115. package/dist/parser/parser.d.ts +49 -0
  116. package/dist/parser/parser.d.ts.map +1 -0
  117. package/dist/parser/parser.js +54 -0
  118. package/dist/parser/parser.js.map +1 -0
  119. package/dist/parser/script.d.ts +14 -0
  120. package/dist/parser/script.d.ts.map +1 -0
  121. package/dist/parser/script.js +196 -0
  122. package/dist/parser/script.js.map +1 -0
  123. package/dist/parser/state.d.ts +40 -0
  124. package/dist/parser/state.d.ts.map +1 -0
  125. package/dist/parser/state.js +129 -0
  126. package/dist/parser/state.js.map +1 -0
  127. package/dist/parser/variables.d.ts +10 -0
  128. package/dist/parser/variables.d.ts.map +1 -0
  129. package/dist/parser/variables.js +215 -0
  130. package/dist/parser/variables.js.map +1 -0
  131. package/dist/runtime/ast-equals.d.ts +13 -0
  132. package/dist/runtime/ast-equals.d.ts.map +1 -0
  133. package/dist/runtime/ast-equals.js +447 -0
  134. package/dist/runtime/ast-equals.js.map +1 -0
  135. package/dist/runtime/builtins.d.ts +13 -0
  136. package/dist/runtime/builtins.d.ts.map +1 -0
  137. package/dist/runtime/builtins.js +180 -0
  138. package/dist/runtime/builtins.js.map +1 -0
  139. package/dist/runtime/callable.d.ts +88 -0
  140. package/dist/runtime/callable.d.ts.map +1 -0
  141. package/dist/runtime/callable.js +98 -0
  142. package/dist/runtime/callable.js.map +1 -0
  143. package/dist/runtime/context.d.ts +13 -0
  144. package/dist/runtime/context.d.ts.map +1 -0
  145. package/dist/runtime/context.js +73 -0
  146. package/dist/runtime/context.js.map +1 -0
  147. package/dist/runtime/core/callable.d.ts +171 -0
  148. package/dist/runtime/core/callable.d.ts.map +1 -0
  149. package/dist/runtime/core/callable.js +246 -0
  150. package/dist/runtime/core/callable.js.map +1 -0
  151. package/dist/runtime/core/context.d.ts +29 -0
  152. package/dist/runtime/core/context.d.ts.map +1 -0
  153. package/dist/runtime/core/context.js +154 -0
  154. package/dist/runtime/core/context.js.map +1 -0
  155. package/dist/runtime/core/equals.d.ts +9 -0
  156. package/dist/runtime/core/equals.d.ts.map +1 -0
  157. package/dist/runtime/core/equals.js +381 -0
  158. package/dist/runtime/core/equals.js.map +1 -0
  159. package/dist/runtime/core/eval/base.d.ts +65 -0
  160. package/dist/runtime/core/eval/base.d.ts.map +1 -0
  161. package/dist/runtime/core/eval/base.js +112 -0
  162. package/dist/runtime/core/eval/base.js.map +1 -0
  163. package/dist/runtime/core/eval/evaluator.d.ts +47 -0
  164. package/dist/runtime/core/eval/evaluator.d.ts.map +1 -0
  165. package/dist/runtime/core/eval/evaluator.js +73 -0
  166. package/dist/runtime/core/eval/evaluator.js.map +1 -0
  167. package/dist/runtime/core/eval/index.d.ts +57 -0
  168. package/dist/runtime/core/eval/index.d.ts.map +1 -0
  169. package/dist/runtime/core/eval/index.js +95 -0
  170. package/dist/runtime/core/eval/index.js.map +1 -0
  171. package/dist/runtime/core/eval/mixins/annotations.d.ts +19 -0
  172. package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -0
  173. package/dist/runtime/core/eval/mixins/annotations.js +146 -0
  174. package/dist/runtime/core/eval/mixins/annotations.js.map +1 -0
  175. package/dist/runtime/core/eval/mixins/closures.d.ts +49 -0
  176. package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -0
  177. package/dist/runtime/core/eval/mixins/closures.js +479 -0
  178. package/dist/runtime/core/eval/mixins/closures.js.map +1 -0
  179. package/dist/runtime/core/eval/mixins/collections.d.ts +24 -0
  180. package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -0
  181. package/dist/runtime/core/eval/mixins/collections.js +466 -0
  182. package/dist/runtime/core/eval/mixins/collections.js.map +1 -0
  183. package/dist/runtime/core/eval/mixins/control-flow.d.ts +27 -0
  184. package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -0
  185. package/dist/runtime/core/eval/mixins/control-flow.js +369 -0
  186. package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -0
  187. package/dist/runtime/core/eval/mixins/core.d.ts +24 -0
  188. package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -0
  189. package/dist/runtime/core/eval/mixins/core.js +335 -0
  190. package/dist/runtime/core/eval/mixins/core.js.map +1 -0
  191. package/dist/runtime/core/eval/mixins/expressions.d.ts +19 -0
  192. package/dist/runtime/core/eval/mixins/expressions.d.ts.map +1 -0
  193. package/dist/runtime/core/eval/mixins/expressions.js +202 -0
  194. package/dist/runtime/core/eval/mixins/expressions.js.map +1 -0
  195. package/dist/runtime/core/eval/mixins/extraction.d.ts +10 -0
  196. package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -0
  197. package/dist/runtime/core/eval/mixins/extraction.js +250 -0
  198. package/dist/runtime/core/eval/mixins/extraction.js.map +1 -0
  199. package/dist/runtime/core/eval/mixins/literals.d.ts +23 -0
  200. package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -0
  201. package/dist/runtime/core/eval/mixins/literals.js +180 -0
  202. package/dist/runtime/core/eval/mixins/literals.js.map +1 -0
  203. package/dist/runtime/core/eval/mixins/types.d.ts +20 -0
  204. package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -0
  205. package/dist/runtime/core/eval/mixins/types.js +109 -0
  206. package/dist/runtime/core/eval/mixins/types.js.map +1 -0
  207. package/dist/runtime/core/eval/mixins/variables.d.ts +34 -0
  208. package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -0
  209. package/dist/runtime/core/eval/mixins/variables.js +247 -0
  210. package/dist/runtime/core/eval/mixins/variables.js.map +1 -0
  211. package/dist/runtime/core/eval/types.d.ts +41 -0
  212. package/dist/runtime/core/eval/types.d.ts.map +1 -0
  213. package/dist/runtime/core/eval/types.js +10 -0
  214. package/dist/runtime/core/eval/types.js.map +1 -0
  215. package/dist/runtime/core/evaluate.d.ts +42 -0
  216. package/dist/runtime/core/evaluate.d.ts.map +1 -0
  217. package/dist/runtime/core/evaluate.debug.js +1251 -0
  218. package/dist/runtime/core/evaluate.js +1913 -0
  219. package/dist/runtime/core/evaluate.js.map +1 -0
  220. package/dist/runtime/core/execute.d.ts +26 -0
  221. package/dist/runtime/core/execute.d.ts.map +1 -0
  222. package/dist/runtime/core/execute.js +177 -0
  223. package/dist/runtime/core/execute.js.map +1 -0
  224. package/dist/runtime/core/signals.d.ts +19 -0
  225. package/dist/runtime/core/signals.d.ts.map +1 -0
  226. package/dist/runtime/core/signals.js +26 -0
  227. package/dist/runtime/core/signals.js.map +1 -0
  228. package/dist/runtime/core/types.d.ts +177 -0
  229. package/dist/runtime/core/types.d.ts.map +1 -0
  230. package/dist/runtime/core/types.js +50 -0
  231. package/dist/runtime/core/types.js.map +1 -0
  232. package/dist/runtime/core/values.d.ts +66 -0
  233. package/dist/runtime/core/values.d.ts.map +1 -0
  234. package/dist/runtime/core/values.js +240 -0
  235. package/dist/runtime/core/values.js.map +1 -0
  236. package/dist/runtime/evaluate.d.ts +32 -0
  237. package/dist/runtime/evaluate.d.ts.map +1 -0
  238. package/dist/runtime/evaluate.js +1111 -0
  239. package/dist/runtime/evaluate.js.map +1 -0
  240. package/dist/runtime/execute.d.ts +26 -0
  241. package/dist/runtime/execute.d.ts.map +1 -0
  242. package/dist/runtime/execute.js +121 -0
  243. package/dist/runtime/execute.js.map +1 -0
  244. package/dist/runtime/ext/builtins.d.ts +16 -0
  245. package/dist/runtime/ext/builtins.d.ts.map +1 -0
  246. package/dist/runtime/ext/builtins.js +528 -0
  247. package/dist/runtime/ext/builtins.js.map +1 -0
  248. package/dist/runtime/ext/content-parser.d.ts +83 -0
  249. package/dist/runtime/ext/content-parser.d.ts.map +1 -0
  250. package/dist/runtime/ext/content-parser.js +536 -0
  251. package/dist/runtime/ext/content-parser.js.map +1 -0
  252. package/dist/runtime/index.d.ts +28 -0
  253. package/dist/runtime/index.d.ts.map +1 -0
  254. package/dist/runtime/index.js +34 -0
  255. package/dist/runtime/index.js.map +1 -0
  256. package/dist/runtime/signals.d.ts +19 -0
  257. package/dist/runtime/signals.d.ts.map +1 -0
  258. package/dist/runtime/signals.js +26 -0
  259. package/dist/runtime/signals.js.map +1 -0
  260. package/dist/runtime/types.d.ts +169 -0
  261. package/dist/runtime/types.d.ts.map +1 -0
  262. package/dist/runtime/types.js +50 -0
  263. package/dist/runtime/types.js.map +1 -0
  264. package/dist/runtime/values.d.ts +50 -0
  265. package/dist/runtime/values.d.ts.map +1 -0
  266. package/dist/runtime/values.js +209 -0
  267. package/dist/runtime/values.js.map +1 -0
  268. package/dist/runtime.d.ts +254 -0
  269. package/dist/runtime.d.ts.map +1 -0
  270. package/dist/runtime.js +2014 -0
  271. package/dist/runtime.js.map +1 -0
  272. package/dist/types.d.ts +752 -0
  273. package/dist/types.d.ts.map +1 -0
  274. package/dist/types.js +189 -0
  275. package/dist/types.js.map +1 -0
  276. package/docs/00_INDEX.md +65 -0
  277. package/docs/01_guide.md +390 -0
  278. package/docs/02_types.md +399 -0
  279. package/docs/03_variables.md +314 -0
  280. package/docs/04_operators.md +551 -0
  281. package/docs/05_control-flow.md +350 -0
  282. package/docs/06_closures.md +353 -0
  283. package/docs/07_collections.md +686 -0
  284. package/docs/08_iterators.md +330 -0
  285. package/docs/09_strings.md +205 -0
  286. package/docs/10_parsing.md +366 -0
  287. package/docs/11_reference.md +350 -0
  288. package/docs/12_examples.md +771 -0
  289. package/docs/13_modules.md +519 -0
  290. package/docs/14_host-integration.md +826 -0
  291. package/docs/15_grammar.ebnf +693 -0
  292. package/docs/16_conventions.md +696 -0
  293. package/docs/99_llm-reference.txt +300 -0
  294. package/docs/assets/logo.png +0 -0
  295. package/package.json +70 -0
@@ -0,0 +1,1913 @@
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 { isCallable, isDict, isScriptCallable } from './callable.js';
11
+ import { createChildContext, getVariable, hasVariable } from './context.js';
12
+ import { BreakSignal, ReturnSignal } from './signals.js';
13
+ import { checkType, createTupleFromDict, createTupleFromList, deepEquals, formatValue, inferType, isRillIterator, isTuple, isReservedMethod, isTruthy, } from './values.js';
14
+ // ============================================================
15
+ // CONSTANTS
16
+ // ============================================================
17
+ /** Default maximum iterations when no limit annotation is set */
18
+ const DEFAULT_MAX_ITERATIONS = 10000;
19
+ // ============================================================
20
+ // EXPORTED HELPERS (used by execute.ts)
21
+ // ============================================================
22
+ /** Helper to get location from an AST node */
23
+ function getNodeLocation(node) {
24
+ return node?.span.start;
25
+ }
26
+ /**
27
+ * Check if execution has been aborted via AbortSignal.
28
+ * Throws AbortError if signal is aborted.
29
+ */
30
+ export function checkAborted(ctx, node) {
31
+ if (ctx.signal?.aborted) {
32
+ throw new AbortError(getNodeLocation(node));
33
+ }
34
+ }
35
+ /**
36
+ * Check if the current pipe value matches any autoException pattern.
37
+ * Only checks string values. Throws AutoExceptionError on match.
38
+ */
39
+ export function checkAutoExceptions(value, ctx, node) {
40
+ if (typeof value !== 'string' || ctx.autoExceptions.length === 0) {
41
+ return;
42
+ }
43
+ for (const pattern of ctx.autoExceptions) {
44
+ if (pattern.test(value)) {
45
+ throw new AutoExceptionError(pattern.source, value, getNodeLocation(node));
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Handle statement capture: set variable and fire observability event.
51
+ * Returns capture info if a capture occurred.
52
+ */
53
+ export function handleCapture(capture, value, ctx) {
54
+ if (!capture)
55
+ return undefined;
56
+ setVariable(ctx, capture.name, value, capture.typeName, capture.span.start);
57
+ const captureInfo = { name: capture.name, value };
58
+ ctx.observability.onCapture?.(captureInfo);
59
+ return captureInfo;
60
+ }
61
+ // ============================================================
62
+ // TYPE ASSERTION HELPERS
63
+ // ============================================================
64
+ /**
65
+ * Assert that a value is of the expected type.
66
+ * Returns the value unchanged if assertion passes, throws on mismatch.
67
+ * Exported for use by type assertion evaluation.
68
+ */
69
+ export function assertType(value, expected, location) {
70
+ const actual = inferType(value);
71
+ if (actual !== expected) {
72
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Type assertion failed: expected ${expected}, got ${actual}`, location, { expectedType: expected, actualType: actual });
73
+ }
74
+ return value;
75
+ }
76
+ // ============================================================
77
+ // VARIABLE MANAGEMENT
78
+ // ============================================================
79
+ /**
80
+ * Set a variable with type checking.
81
+ * - First assignment locks the type (inferred or explicit)
82
+ * - Subsequent assignments must match the locked type
83
+ * - Explicit type annotation is validated against value type
84
+ * - Cannot shadow outer scope variables (produces error)
85
+ */
86
+ function setVariable(ctx, name, value, explicitType, location) {
87
+ const valueType = inferType(value);
88
+ // Check explicit type annotation matches value
89
+ if (explicitType !== null && explicitType !== valueType) {
90
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Type mismatch: cannot assign ${valueType} to $${name}:${explicitType}`, location, { variableName: name, expectedType: explicitType, actualType: valueType });
91
+ }
92
+ // Check if this is a new variable that would reassign an outer scope variable
93
+ // (error: cannot reassign outer scope variables from child scopes)
94
+ if (!ctx.variables.has(name) && ctx.parent && hasVariable(ctx.parent, name)) {
95
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot reassign outer variable $${name} from child scope`, location, { variableName: name });
96
+ }
97
+ // Check if variable already has a locked type in current scope
98
+ const lockedType = ctx.variableTypes.get(name);
99
+ if (lockedType !== undefined && lockedType !== valueType) {
100
+ 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 });
101
+ }
102
+ // Set the variable and lock its type in current scope
103
+ ctx.variables.set(name, value);
104
+ if (!ctx.variableTypes.has(name)) {
105
+ ctx.variableTypes.set(name, explicitType ?? valueType);
106
+ }
107
+ }
108
+ // ============================================================
109
+ // TIMEOUT WRAPPER
110
+ // ============================================================
111
+ /**
112
+ * Wrap a promise with a timeout. Returns original promise if no timeout configured.
113
+ */
114
+ function withTimeout(promise, timeoutMs, functionName, node) {
115
+ if (timeoutMs === undefined) {
116
+ return promise;
117
+ }
118
+ return Promise.race([
119
+ promise,
120
+ new Promise((_, reject) => {
121
+ setTimeout(() => {
122
+ reject(new TimeoutError(functionName, timeoutMs, getNodeLocation(node)));
123
+ }, timeoutMs);
124
+ }),
125
+ ]);
126
+ }
127
+ // ============================================================
128
+ // EXPRESSION EVALUATION
129
+ // ============================================================
130
+ /**
131
+ * Evaluate argument expressions while preserving the current pipeValue.
132
+ */
133
+ async function evaluateArgs(argExprs, ctx) {
134
+ const savedPipeValue = ctx.pipeValue;
135
+ const args = [];
136
+ for (const arg of argExprs) {
137
+ args.push(await evaluateExpression(arg, ctx));
138
+ }
139
+ ctx.pipeValue = savedPipeValue;
140
+ return args;
141
+ }
142
+ export async function evaluateExpression(expr, ctx) {
143
+ return evaluatePipeChain(expr, ctx);
144
+ }
145
+ async function evaluatePipeChain(chain, ctx) {
146
+ // Save parent's $ - chains don't leak $ modifications to parent scope
147
+ const savedPipeValue = ctx.pipeValue;
148
+ // Evaluate head (can be PostfixExpr, BinaryExpr, or UnaryExpr)
149
+ let value;
150
+ switch (chain.head.type) {
151
+ case 'BinaryExpr':
152
+ value = await evaluateBinaryExpr(chain.head, ctx);
153
+ break;
154
+ case 'UnaryExpr':
155
+ value = await evaluateUnaryExpr(chain.head, ctx);
156
+ break;
157
+ case 'PostfixExpr':
158
+ value = await evaluatePostfixExpr(chain.head, ctx);
159
+ break;
160
+ }
161
+ ctx.pipeValue = value; // OK: local to this chain evaluation
162
+ for (const target of chain.pipes) {
163
+ value = await evaluatePipeTarget(target, value, ctx);
164
+ ctx.pipeValue = value; // OK: flows within chain
165
+ }
166
+ // Handle chain terminator (capture, break, return)
167
+ if (chain.terminator) {
168
+ if (chain.terminator.type === 'Break') {
169
+ // Restore parent's $ before throwing (cleanup)
170
+ ctx.pipeValue = savedPipeValue;
171
+ throw new BreakSignal(value);
172
+ }
173
+ if (chain.terminator.type === 'Return') {
174
+ // Restore parent's $ before throwing (cleanup)
175
+ ctx.pipeValue = savedPipeValue;
176
+ throw new ReturnSignal(value);
177
+ }
178
+ // Capture
179
+ handleCapture(chain.terminator, value, ctx);
180
+ }
181
+ // Restore parent's $ - chain result is returned, but $ doesn't leak
182
+ ctx.pipeValue = savedPipeValue;
183
+ return value;
184
+ }
185
+ async function evaluatePostfixExpr(expr, ctx) {
186
+ let value = await evaluatePrimary(expr.primary, ctx);
187
+ for (const method of expr.methods) {
188
+ value = await evaluateMethod(method, value, ctx);
189
+ }
190
+ return value;
191
+ }
192
+ async function evaluatePrimary(primary, ctx) {
193
+ switch (primary.type) {
194
+ case 'StringLiteral':
195
+ return evaluateString(primary, ctx);
196
+ case 'NumberLiteral':
197
+ return primary.value;
198
+ case 'BoolLiteral':
199
+ return primary.value;
200
+ case 'Tuple':
201
+ return evaluateTuple(primary, ctx);
202
+ case 'Dict':
203
+ return evaluateDict(primary, ctx);
204
+ case 'Closure':
205
+ return await createClosure(primary, ctx);
206
+ case 'Variable':
207
+ return evaluateVariableAsync(primary, ctx);
208
+ case 'HostCall':
209
+ return evaluateHostCall(primary, ctx);
210
+ case 'ClosureCall':
211
+ return evaluateClosureCall(primary, ctx);
212
+ case 'MethodCall':
213
+ if (ctx.pipeValue === null) {
214
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, 'Undefined variable: $', primary.span?.start, { variable: '$' });
215
+ }
216
+ return evaluateMethod(primary, ctx.pipeValue, ctx);
217
+ case 'Conditional':
218
+ return evaluateConditional(primary, ctx);
219
+ case 'WhileLoop':
220
+ return evaluateWhileLoop(primary, ctx);
221
+ case 'DoWhileLoop':
222
+ return evaluateDoWhileLoop(primary, ctx);
223
+ case 'Block':
224
+ return evaluateBlockExpression(primary, ctx);
225
+ case 'GroupedExpr':
226
+ return evaluateGroupedExpr(primary, ctx);
227
+ case 'Spread':
228
+ return evaluateSpread(primary, ctx);
229
+ case 'TypeAssertion':
230
+ return evaluateTypeAssertionPrimary(primary, ctx);
231
+ case 'TypeCheck':
232
+ return evaluateTypeCheckPrimary(primary, ctx);
233
+ default:
234
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown primary type: ${primary.type}`, getNodeLocation(primary));
235
+ }
236
+ }
237
+ /**
238
+ * Evaluate postfix type assertion: expr:type
239
+ * The operand is always present (not null) for postfix form.
240
+ */
241
+ async function evaluateTypeAssertionPrimary(node, ctx) {
242
+ if (!node.operand) {
243
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Postfix type assertion requires operand', node.span.start);
244
+ }
245
+ const value = await evaluatePostfixExpr(node.operand, ctx);
246
+ return evaluateTypeAssertion(node, value, ctx);
247
+ }
248
+ /**
249
+ * Evaluate postfix type check: expr:?type
250
+ * The operand is always present (not null) for postfix form.
251
+ */
252
+ async function evaluateTypeCheckPrimary(node, ctx) {
253
+ if (!node.operand) {
254
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Postfix type check requires operand', node.span.start);
255
+ }
256
+ const value = await evaluatePostfixExpr(node.operand, ctx);
257
+ return evaluateTypeCheck(node, value, ctx);
258
+ }
259
+ async function evaluatePipeTarget(target, input, ctx) {
260
+ ctx.pipeValue = input;
261
+ switch (target.type) {
262
+ case 'Capture':
263
+ return evaluateCapture(target, input, ctx);
264
+ case 'HostCall':
265
+ return evaluateHostCall(target, ctx);
266
+ case 'ClosureCall':
267
+ return evaluateClosureCallWithPipe(target, input, ctx);
268
+ case 'PipeInvoke':
269
+ return evaluatePipeInvoke(target, input, ctx);
270
+ case 'MethodCall':
271
+ return evaluateMethod(target, input, ctx);
272
+ case 'Conditional':
273
+ return evaluateConditional(target, ctx);
274
+ case 'WhileLoop':
275
+ return evaluateWhileLoop(target, ctx);
276
+ case 'DoWhileLoop':
277
+ return evaluateDoWhileLoop(target, ctx);
278
+ case 'Block':
279
+ return evaluateBlockExpression(target, ctx);
280
+ case 'StringLiteral':
281
+ return evaluateString(target, ctx);
282
+ case 'GroupedExpr':
283
+ return evaluateGroupedExpr(target, ctx);
284
+ case 'ClosureChain':
285
+ return evaluateClosureChain(target, input, ctx);
286
+ case 'Destructure':
287
+ return evaluateDestructure(target, input, ctx);
288
+ case 'Slice':
289
+ return evaluateSlice(target, input, ctx);
290
+ case 'Spread':
291
+ return evaluateSpread(target, ctx);
292
+ case 'TypeAssertion':
293
+ return evaluateTypeAssertion(target, input, ctx);
294
+ case 'TypeCheck':
295
+ return evaluateTypeCheck(target, input, ctx);
296
+ case 'EachExpr':
297
+ return evaluateEach(target, input, ctx);
298
+ case 'MapExpr':
299
+ return evaluateMap(target, input, ctx);
300
+ case 'FoldExpr':
301
+ return evaluateFold(target, input, ctx);
302
+ case 'FilterExpr':
303
+ return evaluateFilter(target, input, ctx);
304
+ case 'Variable':
305
+ // $.field is property access on pipe value, not closure invocation
306
+ if (target.isPipeVar && !target.name && target.accessChain.length > 0) {
307
+ return evaluatePipePropertyAccess(target, input, ctx);
308
+ }
309
+ return evaluateVariableInvoke(target, input, ctx);
310
+ case 'PostfixExpr': {
311
+ // Chained methods on pipe value: -> .a.b.c
312
+ let value = input;
313
+ for (const method of target.methods) {
314
+ value = await evaluateMethod(method, value, ctx);
315
+ }
316
+ return value;
317
+ }
318
+ default:
319
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown pipe target type: ${target.type}`, getNodeLocation(target));
320
+ }
321
+ }
322
+ // ============================================================
323
+ // STATEMENT EXECUTION
324
+ // ============================================================
325
+ export async function executeStatement(stmt, ctx) {
326
+ // Handle annotated statements
327
+ if (stmt.type === 'AnnotatedStatement') {
328
+ return executeAnnotatedStatement(stmt, ctx);
329
+ }
330
+ const value = await evaluateExpression(stmt.expression, ctx);
331
+ // Note: Do NOT set ctx.pipeValue = value here.
332
+ // Statements don't propagate $ to siblings. $ flows only via explicit ->.
333
+ checkAutoExceptions(value, ctx, stmt);
334
+ // Terminator handling is now inside PipeChainNode evaluation
335
+ // (evaluatePipeChain handles capture/break/return terminators)
336
+ return value;
337
+ }
338
+ /**
339
+ * Execute an annotated statement.
340
+ * Evaluates annotations, pushes them to the stack, executes the inner statement,
341
+ * and pops the annotations.
342
+ */
343
+ async function executeAnnotatedStatement(stmt, ctx) {
344
+ // Evaluate annotation arguments to build annotation dict
345
+ const newAnnotations = await evaluateAnnotations(stmt.annotations, ctx);
346
+ // Merge with inherited annotations (inner overrides outer)
347
+ const inherited = ctx.annotationStack.at(-1) ?? {};
348
+ const merged = { ...inherited, ...newAnnotations };
349
+ // Push merged annotations, execute inner statement, pop
350
+ ctx.annotationStack.push(merged);
351
+ try {
352
+ return await executeStatement(stmt.statement, ctx);
353
+ }
354
+ finally {
355
+ ctx.annotationStack.pop();
356
+ }
357
+ }
358
+ /**
359
+ * Evaluate annotation arguments to a dict of key-value pairs.
360
+ */
361
+ async function evaluateAnnotations(annotations, ctx) {
362
+ const result = {};
363
+ for (const arg of annotations) {
364
+ if (arg.type === 'NamedArg') {
365
+ result[arg.name] = await evaluateExpression(arg.value, ctx);
366
+ }
367
+ else {
368
+ // SpreadArg: spread tuple/dict keys as annotations
369
+ const spreadValue = await evaluateExpression(arg.expression, ctx);
370
+ if (typeof spreadValue === 'object' &&
371
+ spreadValue !== null &&
372
+ !Array.isArray(spreadValue) &&
373
+ !isCallable(spreadValue)) {
374
+ // Dict: spread all key-value pairs
375
+ Object.assign(result, spreadValue);
376
+ }
377
+ else if (Array.isArray(spreadValue)) {
378
+ // Tuple/list: not valid for annotations (need named keys)
379
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Annotation spread requires dict with named keys, got list', arg.span.start);
380
+ }
381
+ else {
382
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Annotation spread requires dict, got ${typeof spreadValue}`, arg.span.start);
383
+ }
384
+ }
385
+ }
386
+ return result;
387
+ }
388
+ /**
389
+ * Get the current value of an annotation from the annotation stack.
390
+ */
391
+ export function getAnnotation(ctx, key) {
392
+ return ctx.annotationStack.at(-1)?.[key];
393
+ }
394
+ /**
395
+ * Get the iteration limit for loops from the `limit` annotation.
396
+ * Returns the default if not set or if the value is not a positive number.
397
+ */
398
+ function getIterationLimit(ctx) {
399
+ const limit = getAnnotation(ctx, 'limit');
400
+ if (typeof limit === 'number' && limit > 0) {
401
+ return Math.floor(limit);
402
+ }
403
+ return DEFAULT_MAX_ITERATIONS;
404
+ }
405
+ async function evaluateClosureChain(node, input, ctx) {
406
+ const target = await evaluateExpression(node.target, ctx);
407
+ const closures = Array.isArray(target) ? target : [target];
408
+ let accumulated = input;
409
+ for (const closure of closures) {
410
+ accumulated = await invokeAsCallableOrFunction(closure, [accumulated], ctx, node.span.start);
411
+ }
412
+ return accumulated;
413
+ }
414
+ // ============================================================
415
+ // COLLECTION OPERATORS (each, map, fold)
416
+ // ============================================================
417
+ /**
418
+ * Get iterable elements from input value.
419
+ * Returns array of elements to iterate over.
420
+ * For dicts, returns array of { key, value } objects.
421
+ */
422
+ /**
423
+ * Check if a value is a rill iterator (dict with value, done, next fields).
424
+ * Iterators follow the protocol: { value: any, done: bool, next: closure }
425
+ */
426
+ /**
427
+ * Expand a rill iterator into an array of elements.
428
+ * Respects iteration limits to prevent infinite loops.
429
+ */
430
+ async function expandIterator(iterator, ctx, node, limit = 10000) {
431
+ const elements = [];
432
+ let current = iterator;
433
+ let count = 0;
434
+ while (!current['done'] && count < limit) {
435
+ checkAborted(ctx, undefined);
436
+ const val = current['value'];
437
+ if (val !== undefined) {
438
+ elements.push(val);
439
+ }
440
+ count++;
441
+ // Invoke next() to get the next iterator
442
+ const nextClosure = current['next'];
443
+ if (nextClosure === undefined || !isCallable(nextClosure)) {
444
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Iterator .next must be a closure', node.span.start);
445
+ }
446
+ const nextIterator = await invokeCallable(nextClosure, [], ctx, node.span.start);
447
+ if (!isRillIterator(nextIterator)) {
448
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Iterator .next must return an iterator', node.span.start);
449
+ }
450
+ current = nextIterator;
451
+ }
452
+ if (count >= limit && !current['done']) {
453
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Iterator exceeded ${limit} elements (use ^(limit: N) to increase)`, node.span.start);
454
+ }
455
+ return elements;
456
+ }
457
+ async function getIterableElements(input, ctx, node) {
458
+ if (Array.isArray(input)) {
459
+ return input;
460
+ }
461
+ if (typeof input === 'string') {
462
+ return [...input];
463
+ }
464
+ // Check for iterator protocol BEFORE generic dict handling
465
+ if (isRillIterator(input)) {
466
+ return expandIterator(input, ctx, node);
467
+ }
468
+ if (isDict(input)) {
469
+ // Dict iteration: sorted keys, each element is { key, value }
470
+ const keys = Object.keys(input).sort();
471
+ return keys.map((key) => ({
472
+ key,
473
+ value: input[key],
474
+ }));
475
+ }
476
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Collection operators require list, string, dict, or iterator, got ${inferType(input)}`, node.span.start);
477
+ }
478
+ /**
479
+ * Evaluate collection body for a single element.
480
+ * Handles all body forms: closure, block, grouped, variable, postfix, spread.
481
+ */
482
+ async function evaluateIteratorBody(body, element, accumulator, ctx) {
483
+ switch (body.type) {
484
+ case 'Closure': {
485
+ // Inline closure: invoke with element (and accumulator if present in params)
486
+ const closure = await createClosure(body, ctx);
487
+ const args = [element];
488
+ // Accumulator is passed as second arg if closure has 2+ params
489
+ if (accumulator !== null && closure.params.length >= 2) {
490
+ args.push(accumulator);
491
+ }
492
+ // Create context with $@ for closures that use it (e.g., |x| { $x + $@ })
493
+ let invokeCtx = ctx;
494
+ let closureToInvoke = closure;
495
+ if (accumulator !== null) {
496
+ invokeCtx = createChildContext(ctx);
497
+ invokeCtx.variables.set('@', accumulator);
498
+ // Create new closure with updated definingScope to include $@
499
+ closureToInvoke = { ...closure, definingScope: invokeCtx };
500
+ }
501
+ return invokeCallable(closureToInvoke, args, invokeCtx, body.span.start);
502
+ }
503
+ case 'Block': {
504
+ // Block: evaluate with $ = element, $@ = accumulator
505
+ const blockCtx = createChildContext(ctx);
506
+ blockCtx.pipeValue = element;
507
+ if (accumulator !== null) {
508
+ blockCtx.variables.set('@', accumulator);
509
+ }
510
+ return evaluateBlockExpression(body, blockCtx);
511
+ }
512
+ case 'GroupedExpr': {
513
+ // Grouped: evaluate with $ = element, $@ = accumulator (for fold)
514
+ const groupedCtx = createChildContext(ctx);
515
+ groupedCtx.pipeValue = element;
516
+ if (accumulator !== null) {
517
+ groupedCtx.variables.set('@', accumulator);
518
+ }
519
+ return evaluateGroupedExpr(body, groupedCtx);
520
+ }
521
+ case 'Variable': {
522
+ // Bare $ = identity, return element unchanged
523
+ if (body.isPipeVar && !body.name && body.accessChain.length === 0) {
524
+ return element;
525
+ }
526
+ // $[idx] or $.field - evaluate access chain on current element
527
+ if (body.isPipeVar && body.accessChain.length > 0) {
528
+ const bodyCtx = createChildContext(ctx);
529
+ bodyCtx.pipeValue = element;
530
+ return evaluateVariableAsync(body, bodyCtx);
531
+ }
532
+ // Variable closure: get closure and invoke with element
533
+ const varValue = getVariable(ctx, body.name ?? '');
534
+ if (!varValue) {
535
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Undefined variable: $${body.name}`, body.span.start);
536
+ }
537
+ if (!isCallable(varValue)) {
538
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Collection body variable must be callable, got ${inferType(varValue)}`, body.span.start);
539
+ }
540
+ const args = [element];
541
+ if (accumulator !== null &&
542
+ varValue.kind === 'script' &&
543
+ varValue.params.length >= 2) {
544
+ args.push(accumulator);
545
+ }
546
+ return invokeCallable(varValue, args, ctx, body.span.start);
547
+ }
548
+ case 'PostfixExpr': {
549
+ // PostfixExpr: evaluate with $ = element
550
+ const postfixCtx = createChildContext(ctx);
551
+ postfixCtx.pipeValue = element;
552
+ return evaluatePostfixExpr(body, postfixCtx);
553
+ }
554
+ case 'Spread': {
555
+ // Spread: convert element to tuple
556
+ const spreadCtx = createChildContext(ctx);
557
+ spreadCtx.pipeValue = element;
558
+ return evaluateSpread(body, spreadCtx);
559
+ }
560
+ default:
561
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown collection body type: ${body.type}`, body.span.start);
562
+ }
563
+ }
564
+ /**
565
+ * Evaluate each expression: sequential iteration returning list of all results.
566
+ *
567
+ * With accumulator: returns list of running values (scan/prefix-sum pattern)
568
+ * Without accumulator: returns list of body results
569
+ *
570
+ * Supports break for early termination.
571
+ */
572
+ async function evaluateEach(node, input, ctx) {
573
+ const elements = await getIterableElements(input, ctx, node);
574
+ // Empty collection: return []
575
+ if (elements.length === 0) {
576
+ return [];
577
+ }
578
+ // Get initial accumulator value if present
579
+ let accumulator = null;
580
+ if (node.accumulator) {
581
+ accumulator = await evaluateExpression(node.accumulator, ctx);
582
+ }
583
+ else if (node.body.type === 'Closure' && node.body.params.length >= 2) {
584
+ // Inline closure with accumulator: |x, acc = init| body
585
+ const lastParam = node.body.params[node.body.params.length - 1];
586
+ if (lastParam?.defaultValue) {
587
+ accumulator = await evaluatePrimary(lastParam.defaultValue, ctx);
588
+ }
589
+ }
590
+ const results = [];
591
+ try {
592
+ for (const element of elements) {
593
+ checkAborted(ctx, node);
594
+ const result = await evaluateIteratorBody(node.body, element, accumulator, ctx);
595
+ results.push(result);
596
+ // Update accumulator for next iteration (scan pattern)
597
+ if (accumulator !== null) {
598
+ accumulator = result;
599
+ }
600
+ }
601
+ }
602
+ catch (e) {
603
+ if (e instanceof BreakSignal) {
604
+ // Break: return results collected so far
605
+ return results;
606
+ }
607
+ throw e;
608
+ }
609
+ return results;
610
+ }
611
+ /**
612
+ * Evaluate map expression: parallel iteration returning list of all results.
613
+ *
614
+ * Uses Promise.all for concurrent execution.
615
+ * Concurrency limit via ^(limit: N) annotation.
616
+ */
617
+ async function evaluateMap(node, input, ctx) {
618
+ const elements = await getIterableElements(input, ctx, node);
619
+ // Empty collection: return []
620
+ if (elements.length === 0) {
621
+ return [];
622
+ }
623
+ // Check for concurrency limit annotation
624
+ const limitAnnotation = getAnnotation(ctx, 'limit');
625
+ const concurrencyLimit = typeof limitAnnotation === 'number' && limitAnnotation > 0
626
+ ? Math.floor(limitAnnotation)
627
+ : Infinity;
628
+ if (concurrencyLimit === Infinity) {
629
+ // No limit: all in parallel
630
+ const promises = elements.map((element) => evaluateIteratorBody(node.body, element, null, ctx));
631
+ return Promise.all(promises);
632
+ }
633
+ // With limit: process in batches
634
+ const results = [];
635
+ for (let i = 0; i < elements.length; i += concurrencyLimit) {
636
+ checkAborted(ctx, node);
637
+ const batch = elements.slice(i, i + concurrencyLimit);
638
+ const batchPromises = batch.map((element) => evaluateIteratorBody(node.body, element, null, ctx));
639
+ const batchResults = await Promise.all(batchPromises);
640
+ results.push(...batchResults);
641
+ }
642
+ return results;
643
+ }
644
+ /**
645
+ * Evaluate fold expression: sequential reduction returning final result only.
646
+ *
647
+ * Accumulator is required.
648
+ * Empty collection: returns initial accumulator value.
649
+ */
650
+ async function evaluateFold(node, input, ctx) {
651
+ const elements = await getIterableElements(input, ctx, node);
652
+ // Get initial accumulator value
653
+ let accumulator;
654
+ if (node.accumulator) {
655
+ accumulator = await evaluateExpression(node.accumulator, ctx);
656
+ }
657
+ else if (node.body.type === 'Closure' && node.body.params.length >= 2) {
658
+ // Inline closure with accumulator: |x, acc = init| body
659
+ const lastParam = node.body.params[node.body.params.length - 1];
660
+ if (lastParam?.defaultValue) {
661
+ accumulator = await evaluatePrimary(lastParam.defaultValue, ctx);
662
+ }
663
+ else {
664
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Fold requires accumulator: use |x, acc = init| or fold(init) { }', node.span.start);
665
+ }
666
+ }
667
+ else if (node.body.type === 'Variable' && !node.body.isPipeVar) {
668
+ // Variable closure: the closure itself must have an accumulator default
669
+ const varValue = getVariable(ctx, node.body.name ?? '');
670
+ if (!varValue || !isCallable(varValue) || varValue.kind !== 'script') {
671
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Fold variable must be a script closure with accumulator parameter', node.span.start);
672
+ }
673
+ const lastParam = varValue.params[varValue.params.length - 1];
674
+ if (lastParam && lastParam.defaultValue !== null) {
675
+ accumulator = lastParam.defaultValue;
676
+ }
677
+ else {
678
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Fold closure must have accumulator parameter with default value', node.span.start);
679
+ }
680
+ }
681
+ else {
682
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Fold requires accumulator: use |x, acc = init| or fold(init) { }', node.span.start);
683
+ }
684
+ // Empty collection: return initial accumulator
685
+ if (elements.length === 0) {
686
+ return accumulator;
687
+ }
688
+ for (const element of elements) {
689
+ checkAborted(ctx, node);
690
+ accumulator = await evaluateIteratorBody(node.body, element, accumulator, ctx);
691
+ }
692
+ return accumulator;
693
+ }
694
+ /**
695
+ * Evaluate filter expression: parallel filtering, returns elements where predicate is truthy.
696
+ *
697
+ * Executes predicate for all elements concurrently.
698
+ * Preserves original element order.
699
+ * Empty collection: returns [].
700
+ */
701
+ async function evaluateFilter(node, input, ctx) {
702
+ const elements = await getIterableElements(input, ctx, node);
703
+ // Empty collection: return []
704
+ if (elements.length === 0) {
705
+ return [];
706
+ }
707
+ // Evaluate predicate for all elements in parallel
708
+ const predicatePromises = elements.map(async (element) => {
709
+ checkAborted(ctx, node);
710
+ const result = await evaluateIteratorBody(node.body, element, null, ctx);
711
+ // Predicate must return boolean
712
+ if (typeof result !== 'boolean') {
713
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Filter predicate must return boolean, got ${inferType(result)}`, node.span.start);
714
+ }
715
+ return { element, keep: result };
716
+ });
717
+ const results = await Promise.all(predicatePromises);
718
+ // Filter elements where predicate was true
719
+ return results.filter((r) => r.keep).map((r) => r.element);
720
+ }
721
+ async function invokeAsCallableOrFunction(callableOrName, args, ctx, location) {
722
+ if (isCallable(callableOrName)) {
723
+ return invokeCallable(callableOrName, args, ctx, location);
724
+ }
725
+ if (typeof callableOrName === 'string') {
726
+ const fn = ctx.functions.get(callableOrName);
727
+ if (fn) {
728
+ return fn(args, ctx, location);
729
+ }
730
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_FUNCTION, `Unknown function: ${callableOrName}`, location, { functionName: callableOrName });
731
+ }
732
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Expected callable or function name, got ${typeof callableOrName}`, location);
733
+ }
734
+ function evaluateCapture(node, input, ctx) {
735
+ setVariable(ctx, node.name, input, node.typeName, node.span.start);
736
+ ctx.observability.onCapture?.({ name: node.name, value: input });
737
+ return input;
738
+ }
739
+ // ============================================================
740
+ // EXTRACTION OPERATORS
741
+ // ============================================================
742
+ function evaluateDestructure(node, input, ctx) {
743
+ const isList = Array.isArray(input);
744
+ const isDictInput = isDict(input);
745
+ const firstNonSkip = node.elements.find((e) => e.kind !== 'skip');
746
+ const isKeyPattern = firstNonSkip?.kind === 'keyValue';
747
+ if (isKeyPattern) {
748
+ if (!isDictInput) {
749
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Key destructure requires dict, got ${isList ? 'list' : typeof input}`, node.span.start);
750
+ }
751
+ for (const elem of node.elements) {
752
+ if (elem.kind === 'skip')
753
+ continue;
754
+ if (elem.kind === 'nested') {
755
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Nested destructure not supported in dict patterns', elem.span.start);
756
+ }
757
+ if (elem.kind !== 'keyValue' || elem.key === null || elem.name === null) {
758
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Dict destructure requires key: $var patterns', elem.span.start);
759
+ }
760
+ const dictInput = input;
761
+ if (!(elem.key in dictInput)) {
762
+ 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) });
763
+ }
764
+ const dictValue = dictInput[elem.key];
765
+ if (dictValue === undefined) {
766
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Key '${elem.key}' has undefined value`, elem.span.start);
767
+ }
768
+ setVariable(ctx, elem.name, dictValue, elem.typeName, elem.span.start);
769
+ }
770
+ }
771
+ else {
772
+ if (!isList) {
773
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Positional destructure requires list, got ${isDictInput ? 'dict' : typeof input}`, node.span.start);
774
+ }
775
+ const listInput = input;
776
+ if (node.elements.length !== listInput.length) {
777
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Destructure pattern has ${node.elements.length} elements, list has ${listInput.length}`, node.span.start);
778
+ }
779
+ for (let i = 0; i < node.elements.length; i++) {
780
+ const elem = node.elements[i];
781
+ const value = listInput[i];
782
+ if (elem === undefined || value === undefined) {
783
+ continue;
784
+ }
785
+ if (elem.kind === 'skip')
786
+ continue;
787
+ if (elem.kind === 'nested' && elem.nested) {
788
+ evaluateDestructure(elem.nested, value, ctx);
789
+ continue;
790
+ }
791
+ if (elem.name === null) {
792
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Invalid destructure element', elem.span.start);
793
+ }
794
+ setVariable(ctx, elem.name, value, elem.typeName, elem.span.start);
795
+ }
796
+ }
797
+ return input;
798
+ }
799
+ async function evaluateSlice(node, input, ctx) {
800
+ const isList = Array.isArray(input);
801
+ const isString = typeof input === 'string';
802
+ if (!isList && !isString) {
803
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Slice requires list or string, got ${isDict(input) ? 'dict' : typeof input}`, node.span.start);
804
+ }
805
+ const startBound = node.start
806
+ ? await evaluateSliceBound(node.start, ctx)
807
+ : null;
808
+ const stopBound = node.stop ? await evaluateSliceBound(node.stop, ctx) : null;
809
+ const stepBound = node.step ? await evaluateSliceBound(node.step, ctx) : null;
810
+ if (isList) {
811
+ return applySlice(input, input.length, startBound, stopBound, stepBound);
812
+ }
813
+ return applySlice(input, input.length, startBound, stopBound, stepBound);
814
+ }
815
+ async function evaluateSliceBound(bound, ctx) {
816
+ if (bound === null) {
817
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Slice bound is null', undefined);
818
+ }
819
+ switch (bound.type) {
820
+ case 'NumberLiteral':
821
+ return bound.value;
822
+ case 'Variable': {
823
+ const value = evaluateVariable(bound, ctx);
824
+ if (typeof value !== 'number') {
825
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Slice bound must be number, got ${typeof value}`, bound.span.start);
826
+ }
827
+ return value;
828
+ }
829
+ case 'GroupedExpr': {
830
+ const value = await evaluateGroupedExpr(bound, ctx);
831
+ if (typeof value !== 'number') {
832
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Slice bound must be number, got ${typeof value}`, bound.span.start);
833
+ }
834
+ return value;
835
+ }
836
+ }
837
+ }
838
+ function applySlice(input, len, start, stop, step) {
839
+ const actualStep = step ?? 1;
840
+ if (actualStep === 0) {
841
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Slice step cannot be zero', undefined);
842
+ }
843
+ const normalizeIndex = (idx, defaultVal, forStep) => {
844
+ if (idx === null)
845
+ return defaultVal;
846
+ let normalized = idx < 0 ? len + idx : idx;
847
+ if (forStep > 0) {
848
+ normalized = Math.max(0, Math.min(len, normalized));
849
+ }
850
+ else {
851
+ normalized = Math.max(-1, Math.min(len - 1, normalized));
852
+ }
853
+ return normalized;
854
+ };
855
+ const actualStart = normalizeIndex(start, actualStep > 0 ? 0 : len - 1, actualStep);
856
+ const actualStop = normalizeIndex(stop, actualStep > 0 ? len : -1, actualStep);
857
+ const indices = [];
858
+ if (actualStep > 0) {
859
+ for (let i = actualStart; i < actualStop; i += actualStep) {
860
+ indices.push(i);
861
+ }
862
+ }
863
+ else {
864
+ for (let i = actualStart; i > actualStop; i += actualStep) {
865
+ indices.push(i);
866
+ }
867
+ }
868
+ if (Array.isArray(input)) {
869
+ return indices.map((i) => input[i]);
870
+ }
871
+ else {
872
+ return indices.map((i) => input[i]).join('');
873
+ }
874
+ }
875
+ async function evaluateSpread(node, ctx) {
876
+ let value;
877
+ if (node.operand === null) {
878
+ value = ctx.pipeValue;
879
+ }
880
+ else {
881
+ value = await evaluateExpression(node.operand, ctx);
882
+ }
883
+ if (Array.isArray(value)) {
884
+ return createTupleFromList(value);
885
+ }
886
+ if (isDict(value)) {
887
+ return createTupleFromDict(value);
888
+ }
889
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Spread requires list or dict, got ${inferType(value)}`, node.span.start);
890
+ }
891
+ // ============================================================
892
+ // TYPE OPERATIONS
893
+ // ============================================================
894
+ /**
895
+ * Evaluate type assertion: expr:type or :type (shorthand for $:type)
896
+ * Returns value unchanged if type matches, throws on mismatch.
897
+ */
898
+ async function evaluateTypeAssertion(node, input, ctx) {
899
+ // If operand is null, use the input (pipe value)
900
+ // Otherwise, evaluate the operand
901
+ const value = node.operand
902
+ ? await evaluatePostfixExpr(node.operand, ctx)
903
+ : input;
904
+ return assertType(value, node.typeName, node.span.start);
905
+ }
906
+ /**
907
+ * Evaluate type check: expr:?type or :?type (shorthand for $:?type)
908
+ * Returns true if type matches, false otherwise.
909
+ */
910
+ async function evaluateTypeCheck(node, input, ctx) {
911
+ // If operand is null, use the input (pipe value)
912
+ // Otherwise, evaluate the operand
913
+ const value = node.operand
914
+ ? await evaluatePostfixExpr(node.operand, ctx)
915
+ : input;
916
+ return checkType(value, node.typeName);
917
+ }
918
+ // ============================================================
919
+ // LITERAL EVALUATION
920
+ // ============================================================
921
+ async function evaluateString(node, ctx) {
922
+ let result = '';
923
+ // Save pipeValue since interpolation expressions can modify it
924
+ const savedPipeValue = ctx.pipeValue;
925
+ for (const part of node.parts) {
926
+ if (typeof part === 'string') {
927
+ result += part;
928
+ }
929
+ else {
930
+ // InterpolationNode: evaluate the expression
931
+ // Restore pipeValue before each interpolation so they all see the same value
932
+ ctx.pipeValue = savedPipeValue;
933
+ const value = await evaluateExpression(part.expression, ctx);
934
+ result += formatValue(value);
935
+ }
936
+ }
937
+ // Restore pipeValue after string evaluation
938
+ ctx.pipeValue = savedPipeValue;
939
+ return result;
940
+ }
941
+ async function evaluateTuple(node, ctx) {
942
+ const elements = [];
943
+ for (const elem of node.elements) {
944
+ elements.push(await evaluateExpression(elem, ctx));
945
+ }
946
+ return elements;
947
+ }
948
+ function isClosureExpr(expr) {
949
+ if (expr.pipes.length > 0)
950
+ return false;
951
+ if (expr.head.type !== 'PostfixExpr')
952
+ return false;
953
+ if (expr.head.methods.length > 0)
954
+ return false;
955
+ return expr.head.primary.type === 'Closure';
956
+ }
957
+ async function evaluateDict(node, ctx) {
958
+ const result = {};
959
+ for (const entry of node.entries) {
960
+ if (isReservedMethod(entry.key)) {
961
+ 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'] });
962
+ }
963
+ if (isClosureExpr(entry.value)) {
964
+ // Safe cast: isClosureExpr ensures head is PostfixExpr with Closure primary
965
+ const head = entry.value.head;
966
+ const fnLit = head.primary;
967
+ const closure = await createClosure(fnLit, ctx);
968
+ result[entry.key] = closure;
969
+ }
970
+ else {
971
+ result[entry.key] = await evaluateExpression(entry.value, ctx);
972
+ }
973
+ }
974
+ for (const key of Object.keys(result)) {
975
+ const value = result[key];
976
+ if (value !== undefined && isCallable(value)) {
977
+ result[key] = {
978
+ ...value,
979
+ boundDict: result,
980
+ };
981
+ }
982
+ }
983
+ return result;
984
+ }
985
+ async function createClosure(node, ctx) {
986
+ // Store reference to the defining scope for late-bound variable resolution
987
+ const definingScope = ctx;
988
+ const params = [];
989
+ for (const param of node.params) {
990
+ let defaultValue = null;
991
+ if (param.defaultValue) {
992
+ defaultValue = await evaluatePrimary(param.defaultValue, ctx);
993
+ }
994
+ params.push({
995
+ name: param.name,
996
+ typeName: param.typeName,
997
+ defaultValue,
998
+ });
999
+ }
1000
+ const isProperty = params.length === 0;
1001
+ return {
1002
+ __type: 'callable',
1003
+ kind: 'script',
1004
+ params,
1005
+ body: node.body,
1006
+ definingScope,
1007
+ isProperty,
1008
+ };
1009
+ }
1010
+ // ============================================================
1011
+ // VARIABLE EVALUATION
1012
+ // ============================================================
1013
+ function getBaseVariableValue(node, ctx) {
1014
+ if (node.isPipeVar) {
1015
+ if (ctx.pipeValue === null) {
1016
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, 'Undefined variable: $', node.span?.start, { variable: '$' });
1017
+ }
1018
+ return ctx.pipeValue;
1019
+ }
1020
+ if (node.name) {
1021
+ const result = getVariable(ctx, node.name);
1022
+ if (result === undefined) {
1023
+ // Variable doesn't exist - only return null if we'll handle with default/existence check
1024
+ if (node.defaultValue || node.existenceCheck) {
1025
+ return null;
1026
+ }
1027
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Undefined variable: $${node.name}`, node.span?.start, { variable: node.name });
1028
+ }
1029
+ return result;
1030
+ }
1031
+ return null;
1032
+ }
1033
+ function resolveFieldAccess(access, value, ctx) {
1034
+ switch (access.kind) {
1035
+ case 'literal':
1036
+ return access.field;
1037
+ case 'variable': {
1038
+ const varValue = getVariable(ctx, access.variableName);
1039
+ if (typeof varValue === 'string')
1040
+ return varValue;
1041
+ if (typeof varValue === 'number')
1042
+ return varValue;
1043
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Variable field access requires string or number, got ${typeof varValue}`, undefined, {});
1044
+ }
1045
+ case 'alternatives': {
1046
+ // Try each alternative, return first that exists
1047
+ if (typeof value === 'object' &&
1048
+ value !== null &&
1049
+ !Array.isArray(value)) {
1050
+ const dict = value;
1051
+ for (const alt of access.alternatives) {
1052
+ if (alt in dict)
1053
+ return alt;
1054
+ }
1055
+ }
1056
+ return access.alternatives[0] ?? ''; // fallback to first
1057
+ }
1058
+ case 'computed':
1059
+ case 'block':
1060
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Computed/block field access requires async evaluation`, undefined, {});
1061
+ }
1062
+ }
1063
+ function evaluateVariable(node, ctx) {
1064
+ // Note: sync version doesn't support existence checks with computed/block access
1065
+ // Those require async. Simple existence checks are handled here.
1066
+ if (node.existenceCheck) {
1067
+ return evaluateExistenceCheckSync(node, ctx);
1068
+ }
1069
+ let value = getBaseVariableValue(node, ctx);
1070
+ // Use accessChain but skip bracket accesses (require async evaluation)
1071
+ for (const access of node.accessChain) {
1072
+ if (isBracketAccess(access)) {
1073
+ // Bracket accesses require async evaluation - skip in sync context
1074
+ continue;
1075
+ }
1076
+ if (value === null) {
1077
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot access field on null value`, node.span?.start, {});
1078
+ }
1079
+ const field = resolveFieldAccess(access, value, ctx);
1080
+ value = accessField(value, field);
1081
+ }
1082
+ if (value === null) {
1083
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Field access returned null`, node.span?.start, {});
1084
+ }
1085
+ return value;
1086
+ }
1087
+ /**
1088
+ * Sync version of existence check for simple (non-computed/block) access.
1089
+ */
1090
+ function evaluateExistenceCheckSync(node, ctx) {
1091
+ if (!node.existenceCheck)
1092
+ return false;
1093
+ let value = getBaseVariableValue(node, ctx);
1094
+ // Use accessChain but skip bracket accesses (require async evaluation)
1095
+ for (const access of node.accessChain) {
1096
+ if (isBracketAccess(access)) {
1097
+ // Bracket accesses require async evaluation - skip in sync context
1098
+ continue;
1099
+ }
1100
+ if (value === null)
1101
+ return false;
1102
+ const field = resolveFieldAccess(access, value, ctx);
1103
+ value = accessField(value, field);
1104
+ }
1105
+ if (value === null)
1106
+ return false;
1107
+ const finalField = resolveFieldAccess(node.existenceCheck.finalAccess, value, ctx);
1108
+ const finalValue = accessField(value, finalField);
1109
+ if (finalValue === null)
1110
+ return false;
1111
+ if (node.existenceCheck.typeName) {
1112
+ return checkType(finalValue, node.existenceCheck.typeName);
1113
+ }
1114
+ return true;
1115
+ }
1116
+ async function evaluateVariableAsync(node, ctx) {
1117
+ // Handle existence check: .?path returns boolean
1118
+ if (node.existenceCheck) {
1119
+ return evaluateExistenceCheck(node, ctx);
1120
+ }
1121
+ let value = getBaseVariableValue(node, ctx);
1122
+ // Apply unified access chain (maintains order of dot and bracket accesses)
1123
+ for (const access of node.accessChain) {
1124
+ // If value is null/missing, either use default or error
1125
+ if (value === null) {
1126
+ if (node.defaultValue) {
1127
+ return evaluateBody(node.defaultValue, ctx);
1128
+ }
1129
+ if (node.existenceCheck) {
1130
+ return false; // .?field returns false for missing
1131
+ }
1132
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot access field on null value`, node.span?.start, {});
1133
+ }
1134
+ // Check if this is a bracket access
1135
+ if (isBracketAccess(access)) {
1136
+ const indexValue = await evaluatePipeChain(access.expression, ctx);
1137
+ if (typeof indexValue !== 'number') {
1138
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Bracket index must be number, got ${inferType(indexValue)}`, node.span.start, {});
1139
+ }
1140
+ // Handle negative indices (from end)
1141
+ let index = indexValue;
1142
+ if (index < 0) {
1143
+ if (Array.isArray(value)) {
1144
+ index = value.length + index;
1145
+ }
1146
+ else if (typeof value === 'string') {
1147
+ index = value.length + index;
1148
+ }
1149
+ }
1150
+ const prevValue = value;
1151
+ value = accessField(value, index);
1152
+ // If we got null and there's no default, throw specific error
1153
+ if (value === null && !node.defaultValue && !node.existenceCheck) {
1154
+ if (Array.isArray(prevValue)) {
1155
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `List index out of bounds: ${index}`, node.span?.start, {});
1156
+ }
1157
+ }
1158
+ }
1159
+ else {
1160
+ // Field access
1161
+ const field = await resolveFieldAccessAsync(access, value, ctx);
1162
+ const prevValue = value;
1163
+ value = accessField(value, field);
1164
+ if (isCallable(value) && value.isProperty && value.boundDict) {
1165
+ value = await invokeCallable(value, [], ctx, node.span.start);
1166
+ }
1167
+ // If we got null and there's no default, throw specific error
1168
+ if (value === null && !node.defaultValue && !node.existenceCheck) {
1169
+ if (typeof prevValue === 'object' &&
1170
+ prevValue !== null &&
1171
+ !Array.isArray(prevValue) &&
1172
+ !isScriptCallable(prevValue)) {
1173
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Dict has no field '${field}'`, node.span?.start, {});
1174
+ }
1175
+ }
1176
+ }
1177
+ }
1178
+ // Apply default if final value is null, or error if no default
1179
+ if (value === null) {
1180
+ if (node.defaultValue) {
1181
+ return evaluateBody(node.defaultValue, ctx);
1182
+ }
1183
+ if (node.existenceCheck) {
1184
+ return false;
1185
+ }
1186
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Field access returned null`, node.span?.start, {});
1187
+ }
1188
+ return value;
1189
+ }
1190
+ /**
1191
+ * Type guard to check if a PropertyAccess is a BracketAccess
1192
+ */
1193
+ function isBracketAccess(access) {
1194
+ return 'accessKind' in access && access.accessKind === 'bracket';
1195
+ }
1196
+ /**
1197
+ * Evaluate existence check: $data.user.?email or $data.user.?email&string
1198
+ * Returns true if path exists (and optionally matches type), false otherwise.
1199
+ */
1200
+ async function evaluateExistenceCheck(node, ctx) {
1201
+ if (!node.existenceCheck)
1202
+ return false;
1203
+ let value = getBaseVariableValue(node, ctx);
1204
+ // Traverse the path up to (but not including) the final existence check
1205
+ for (const access of node.accessChain) {
1206
+ if (value === null)
1207
+ return false; // Missing intermediate path
1208
+ if (isBracketAccess(access)) {
1209
+ const indexValue = await evaluatePipeChain(access.expression, ctx);
1210
+ if (typeof indexValue !== 'number')
1211
+ return false;
1212
+ let index = indexValue;
1213
+ if (index < 0) {
1214
+ if (Array.isArray(value)) {
1215
+ index = value.length + index;
1216
+ }
1217
+ else if (typeof value === 'string') {
1218
+ index = value.length + index;
1219
+ }
1220
+ }
1221
+ value = accessField(value, index);
1222
+ }
1223
+ else {
1224
+ const field = await resolveFieldAccessAsync(access, value, ctx);
1225
+ value = accessField(value, field);
1226
+ }
1227
+ }
1228
+ // Now check the final element
1229
+ if (value === null)
1230
+ return false;
1231
+ const finalField = await resolveFieldAccessAsync(node.existenceCheck.finalAccess, value, ctx);
1232
+ const finalValue = accessField(value, finalField);
1233
+ // Check if exists
1234
+ if (finalValue === null)
1235
+ return false;
1236
+ // If type check required, verify type matches
1237
+ if (node.existenceCheck.typeName) {
1238
+ return checkType(finalValue, node.existenceCheck.typeName);
1239
+ }
1240
+ return true;
1241
+ }
1242
+ async function resolveFieldAccessAsync(access, value, ctx) {
1243
+ switch (access.kind) {
1244
+ case 'literal':
1245
+ return access.field;
1246
+ case 'variable': {
1247
+ const varValue = getVariable(ctx, access.variableName);
1248
+ if (typeof varValue === 'string')
1249
+ return varValue;
1250
+ if (typeof varValue === 'number')
1251
+ return varValue;
1252
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Variable field access requires string or number, got ${typeof varValue}`, undefined, {});
1253
+ }
1254
+ case 'alternatives': {
1255
+ if (typeof value === 'object' &&
1256
+ value !== null &&
1257
+ !Array.isArray(value)) {
1258
+ const dict = value;
1259
+ for (const alt of access.alternatives) {
1260
+ if (alt in dict)
1261
+ return alt;
1262
+ }
1263
+ }
1264
+ return access.alternatives[0] ?? '';
1265
+ }
1266
+ case 'computed': {
1267
+ const result = await evaluatePipeChain(access.expression, ctx);
1268
+ if (typeof result === 'string')
1269
+ return result;
1270
+ if (typeof result === 'number')
1271
+ return result;
1272
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Computed field access requires string or number result`, undefined, {});
1273
+ }
1274
+ case 'block': {
1275
+ const result = await evaluateBlock(access.block, ctx);
1276
+ if (typeof result === 'string')
1277
+ return result;
1278
+ if (typeof result === 'number')
1279
+ return result;
1280
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Block field access requires string or number result`, undefined, {});
1281
+ }
1282
+ }
1283
+ }
1284
+ function accessField(value, field) {
1285
+ if (value === null)
1286
+ return null;
1287
+ if (typeof field === 'number') {
1288
+ if (Array.isArray(value))
1289
+ return value[field] ?? null;
1290
+ if (typeof value === 'string')
1291
+ return value[field] ?? '';
1292
+ return null;
1293
+ }
1294
+ if (typeof value === 'object' &&
1295
+ !Array.isArray(value) &&
1296
+ !isScriptCallable(value)) {
1297
+ return value[field] ?? null;
1298
+ }
1299
+ return null;
1300
+ }
1301
+ // ============================================================
1302
+ // FUNCTION & METHOD EVALUATION
1303
+ // ============================================================
1304
+ async function evaluateHostCall(node, ctx) {
1305
+ checkAborted(ctx, node);
1306
+ const fn = ctx.functions.get(node.name);
1307
+ if (!fn) {
1308
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_FUNCTION, `Unknown function: ${node.name}`, getNodeLocation(node), { functionName: node.name });
1309
+ }
1310
+ const args = await evaluateArgs(node.args, ctx);
1311
+ if (args.length === 0 && ctx.pipeValue !== null) {
1312
+ args.push(ctx.pipeValue);
1313
+ }
1314
+ ctx.observability.onHostCall?.({ name: node.name, args });
1315
+ const startTime = Date.now();
1316
+ const location = getNodeLocation(node);
1317
+ const result = fn(args, ctx, location);
1318
+ let value;
1319
+ if (result instanceof Promise) {
1320
+ value = await withTimeout(result, ctx.timeout, node.name, node);
1321
+ }
1322
+ else {
1323
+ value = result;
1324
+ }
1325
+ ctx.observability.onFunctionReturn?.({
1326
+ name: node.name,
1327
+ value,
1328
+ durationMs: Date.now() - startTime,
1329
+ });
1330
+ return value;
1331
+ }
1332
+ async function evaluateClosureCall(node, ctx) {
1333
+ return evaluateClosureCallWithPipe(node, ctx.pipeValue, ctx);
1334
+ }
1335
+ async function evaluateClosureCallWithPipe(node, pipeInput, ctx) {
1336
+ // Get the base variable
1337
+ let value = getVariable(ctx, node.name);
1338
+ if (value === undefined || value === null) {
1339
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Unknown variable: $${node.name}`, getNodeLocation(node), { variableName: node.name });
1340
+ }
1341
+ // Traverse accessChain to get the closure (e.g., $math.double)
1342
+ const fullPath = ['$' + node.name, ...node.accessChain].join('.');
1343
+ for (const prop of node.accessChain) {
1344
+ if (value === null) {
1345
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot access property '${prop}' on null in ${fullPath}`, getNodeLocation(node), { property: prop, path: fullPath });
1346
+ }
1347
+ value = accessField(value, prop);
1348
+ }
1349
+ const closure = value;
1350
+ if (!isCallable(closure)) {
1351
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `${fullPath} is not a function (got ${typeof closure})`, getNodeLocation(node), { path: fullPath, actualType: typeof closure });
1352
+ }
1353
+ const args = await evaluateArgs(node.args, ctx);
1354
+ if (isScriptCallable(closure) &&
1355
+ args.length === 0 &&
1356
+ pipeInput !== null &&
1357
+ closure.params.length > 0) {
1358
+ const firstParam = closure.params[0];
1359
+ if (firstParam?.defaultValue === null && !isCallable(pipeInput)) {
1360
+ args.push(pipeInput);
1361
+ }
1362
+ }
1363
+ return invokeCallable(closure, args, ctx, node.span.start);
1364
+ }
1365
+ /**
1366
+ * Evaluate $.field as property access on the pipe value.
1367
+ * This allows -> $.a to access property 'a' of the current pipe value.
1368
+ */
1369
+ async function evaluatePipePropertyAccess(node, pipeInput, ctx) {
1370
+ let value = pipeInput;
1371
+ for (const access of node.accessChain) {
1372
+ if (value === null) {
1373
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot access property on null`, getNodeLocation(node));
1374
+ }
1375
+ if (isBracketAccess(access)) {
1376
+ // Bracket access: [expr]
1377
+ const indexValue = await evaluatePipeChain(access.expression, ctx);
1378
+ if (Array.isArray(value)) {
1379
+ if (typeof indexValue !== 'number') {
1380
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `List index must be number, got ${inferType(indexValue)}`, getNodeLocation(node));
1381
+ }
1382
+ const result = value[indexValue];
1383
+ if (result === undefined) {
1384
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `List index out of bounds: ${indexValue}`, getNodeLocation(node));
1385
+ }
1386
+ value = result;
1387
+ }
1388
+ else if (isDict(value)) {
1389
+ if (typeof indexValue !== 'string') {
1390
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Dict key must be string, got ${inferType(indexValue)}`, getNodeLocation(node));
1391
+ }
1392
+ const result = value[indexValue];
1393
+ if (result === undefined) {
1394
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Undefined dict key: ${indexValue}`, getNodeLocation(node));
1395
+ }
1396
+ value = result;
1397
+ }
1398
+ else {
1399
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot index ${inferType(value)}`, getNodeLocation(node));
1400
+ }
1401
+ }
1402
+ else {
1403
+ // Field access: .field
1404
+ const field = await resolveFieldAccessAsync(access, value, ctx);
1405
+ value = accessField(value, field);
1406
+ if (value === null) {
1407
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Undefined field: ${field}`, getNodeLocation(node));
1408
+ }
1409
+ }
1410
+ }
1411
+ return value;
1412
+ }
1413
+ /**
1414
+ * Evaluate a bare variable as a pipe target: -> $fn
1415
+ * This invokes the closure stored in $fn with the pipe value as argument.
1416
+ * Use :> for captures instead.
1417
+ */
1418
+ async function evaluateVariableInvoke(node, pipeInput, ctx) {
1419
+ // Handle pipe variable ($) - can't invoke $ as a closure
1420
+ if (node.isPipeVar && !node.name) {
1421
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot invoke $ as pipe target. Use :> to capture, or $() to invoke $ as closure`, getNodeLocation(node));
1422
+ }
1423
+ // Check if variable exists
1424
+ const rawValue = node.name ? getVariable(ctx, node.name) : null;
1425
+ if (rawValue === undefined) {
1426
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_VARIABLE, `Unknown variable: $${node.name}`, getNodeLocation(node), { variableName: node.name });
1427
+ }
1428
+ // Get the full variable value (with access chain)
1429
+ const value = evaluateVariable(node, ctx);
1430
+ // Check if it's callable
1431
+ if (!isCallable(value)) {
1432
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot invoke $${node.name}: expected closure, got ${inferType(value)}. Use :> to capture values.`, getNodeLocation(node));
1433
+ }
1434
+ // Invoke with pipe input as first argument
1435
+ return invokeCallable(value, [pipeInput], ctx, node.span.start);
1436
+ }
1437
+ async function invokeCallable(callable, args, ctx, callLocation) {
1438
+ checkAborted(ctx, undefined);
1439
+ if (callable.kind === 'script') {
1440
+ return invokeScriptCallable(callable, args, ctx, callLocation);
1441
+ }
1442
+ else {
1443
+ return invokeFnCallable(callable, args, ctx, callLocation);
1444
+ }
1445
+ }
1446
+ async function invokeFnCallable(callable, args, ctx, callLocation) {
1447
+ const effectiveArgs = callable.boundDict && args.length === 0 ? [callable.boundDict] : args;
1448
+ const result = callable.fn(effectiveArgs, ctx, callLocation);
1449
+ return result instanceof Promise ? await result : result;
1450
+ }
1451
+ // ============================================================
1452
+ // CALLABLE INVOCATION HELPERS
1453
+ // ============================================================
1454
+ function createCallableContext(callable, ctx) {
1455
+ // Create a child context with the defining scope as parent
1456
+ // This enables late-bound variable resolution through the scope chain
1457
+ const callableCtx = {
1458
+ ...ctx,
1459
+ parent: callable.definingScope,
1460
+ variables: new Map(),
1461
+ variableTypes: new Map(),
1462
+ };
1463
+ if (callable.boundDict) {
1464
+ callableCtx.pipeValue = callable.boundDict;
1465
+ }
1466
+ return callableCtx;
1467
+ }
1468
+ function inferTypeFromDefault(defaultValue) {
1469
+ if (defaultValue === null)
1470
+ return null;
1471
+ const t = inferType(defaultValue);
1472
+ return t === 'string' || t === 'number' || t === 'bool' ? t : null;
1473
+ }
1474
+ function validateParamType(param, value, callLocation) {
1475
+ const expectedType = param.typeName ?? inferTypeFromDefault(param.defaultValue);
1476
+ if (expectedType !== null) {
1477
+ const valueType = inferType(value);
1478
+ if (valueType !== expectedType) {
1479
+ 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 });
1480
+ }
1481
+ }
1482
+ }
1483
+ async function invokeScriptCallable(callable, args, ctx, callLocation) {
1484
+ const firstArg = args[0];
1485
+ if (args.length === 1 && firstArg !== undefined && isTuple(firstArg)) {
1486
+ return invokeScriptCallableWithArgs(callable, firstArg, ctx, callLocation);
1487
+ }
1488
+ const callableCtx = createCallableContext(callable, ctx);
1489
+ for (let i = 0; i < callable.params.length; i++) {
1490
+ const param = callable.params[i];
1491
+ let value;
1492
+ if (i < args.length) {
1493
+ value = args[i];
1494
+ }
1495
+ else if (param.defaultValue !== null) {
1496
+ value = param.defaultValue;
1497
+ }
1498
+ else {
1499
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Missing argument for parameter '${param.name}' at position ${i}`, callLocation, { paramName: param.name, position: i });
1500
+ }
1501
+ validateParamType(param, value, callLocation);
1502
+ callableCtx.variables.set(param.name, value);
1503
+ }
1504
+ return evaluateBodyExpression(callable.body, callableCtx);
1505
+ }
1506
+ async function invokeScriptCallableWithArgs(closure, tupleValue, ctx, callLocation) {
1507
+ const closureCtx = createCallableContext(closure, ctx);
1508
+ const hasNumericKeys = [...tupleValue.entries.keys()].some((k) => typeof k === 'number');
1509
+ const hasStringKeys = [...tupleValue.entries.keys()].some((k) => typeof k === 'string');
1510
+ if (hasNumericKeys && hasStringKeys) {
1511
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Tuple cannot mix positional (numeric) and named (string) keys', callLocation);
1512
+ }
1513
+ const boundParams = new Set();
1514
+ if (hasNumericKeys) {
1515
+ for (const [key, value] of tupleValue.entries) {
1516
+ const position = key;
1517
+ const param = closure.params[position];
1518
+ if (param === undefined) {
1519
+ 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 });
1520
+ }
1521
+ validateParamType(param, value, callLocation);
1522
+ closureCtx.variables.set(param.name, value);
1523
+ boundParams.add(param.name);
1524
+ }
1525
+ }
1526
+ else if (hasStringKeys) {
1527
+ const paramNames = new Set(closure.params.map((p) => p.name));
1528
+ for (const [key, value] of tupleValue.entries) {
1529
+ const name = key;
1530
+ if (!paramNames.has(name)) {
1531
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unknown argument '${name}' (valid params: ${[...paramNames].join(', ')})`, callLocation, { argName: name, validParams: [...paramNames] });
1532
+ }
1533
+ const param = closure.params.find((p) => p.name === name);
1534
+ validateParamType(param, value, callLocation);
1535
+ closureCtx.variables.set(name, value);
1536
+ boundParams.add(name);
1537
+ }
1538
+ }
1539
+ for (const param of closure.params) {
1540
+ if (!boundParams.has(param.name)) {
1541
+ if (param.defaultValue !== null) {
1542
+ closureCtx.variables.set(param.name, param.defaultValue);
1543
+ }
1544
+ else {
1545
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Missing argument '${param.name}' (no default value)`, callLocation, { paramName: param.name });
1546
+ }
1547
+ }
1548
+ }
1549
+ return evaluateBodyExpression(closure.body, closureCtx);
1550
+ }
1551
+ async function evaluatePipeInvoke(node, input, ctx) {
1552
+ if (!isScriptCallable(input)) {
1553
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot invoke non-closure value (got ${typeof input})`, getNodeLocation(node));
1554
+ }
1555
+ const args = await evaluateArgs(node.args, ctx);
1556
+ return invokeScriptCallable(input, args, ctx, node.span.start);
1557
+ }
1558
+ async function evaluateMethod(node, receiver, ctx) {
1559
+ checkAborted(ctx, node);
1560
+ // Handle postfix invocation: expr(args) - calls receiver as a closure
1561
+ if (node.type === 'Invoke') {
1562
+ return evaluateInvoke(node, receiver, ctx);
1563
+ }
1564
+ if (isCallable(receiver)) {
1565
+ 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' });
1566
+ }
1567
+ const args = await evaluateArgs(node.args, ctx);
1568
+ if (isDict(receiver)) {
1569
+ const dictValue = receiver[node.name];
1570
+ if (dictValue !== undefined && isCallable(dictValue)) {
1571
+ return invokeCallable(dictValue, args, ctx, getNodeLocation(node));
1572
+ }
1573
+ }
1574
+ const method = ctx.methods.get(node.name);
1575
+ if (!method) {
1576
+ // Fall back to property access on dict (no-arg only)
1577
+ if (isDict(receiver) && args.length === 0 && node.name in receiver) {
1578
+ return receiver[node.name];
1579
+ }
1580
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_UNDEFINED_METHOD, `Unknown method: ${node.name}`, getNodeLocation(node), { methodName: node.name });
1581
+ }
1582
+ const result = method(receiver, args, ctx, getNodeLocation(node));
1583
+ return result instanceof Promise ? await result : result;
1584
+ }
1585
+ /**
1586
+ * Evaluate postfix invocation: expr(args)
1587
+ * Calls the receiver value as a closure with the given arguments.
1588
+ */
1589
+ async function evaluateInvoke(node, receiver, ctx) {
1590
+ if (!isCallable(receiver)) {
1591
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot invoke non-callable value (got ${inferType(receiver)})`, getNodeLocation(node), { actualType: inferType(receiver) });
1592
+ }
1593
+ const args = await evaluateArgs(node.args, ctx);
1594
+ return invokeCallable(receiver, args, ctx, getNodeLocation(node));
1595
+ }
1596
+ // ============================================================
1597
+ // CONTROL FLOW EVALUATION
1598
+ // ============================================================
1599
+ async function evaluateConditional(node, ctx) {
1600
+ // Preserve pipe value before evaluating condition (condition may modify it)
1601
+ const savedPipeValue = ctx.pipeValue;
1602
+ let conditionResult;
1603
+ if (node.condition) {
1604
+ const conditionValue = await evaluateBodyExpression(node.condition, ctx);
1605
+ // Condition must be boolean
1606
+ if (typeof conditionValue !== 'boolean') {
1607
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Conditional expression must be boolean, got ${inferType(conditionValue)}`, node.span.start);
1608
+ }
1609
+ conditionResult = conditionValue;
1610
+ }
1611
+ else {
1612
+ // Piped conditional: $ -> ? then ! else
1613
+ // The pipe value must be boolean
1614
+ if (typeof ctx.pipeValue !== 'boolean') {
1615
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Piped conditional requires boolean, got ${inferType(ctx.pipeValue)}`, node.span.start);
1616
+ }
1617
+ conditionResult = ctx.pipeValue;
1618
+ }
1619
+ // Restore pipe value for then/else branch evaluation
1620
+ ctx.pipeValue = savedPipeValue;
1621
+ if (conditionResult) {
1622
+ // Create child scope for then branch (reads parent, writes local only)
1623
+ const thenCtx = createChildContext(ctx);
1624
+ thenCtx.pipeValue = savedPipeValue;
1625
+ // Use evaluateBody (not evaluateBodyExpression) so ReturnSignal
1626
+ // propagates up to the containing block rather than being caught here
1627
+ return evaluateBody(node.thenBranch, thenCtx);
1628
+ }
1629
+ else if (node.elseBranch) {
1630
+ // Create child scope for else branch (reads parent, writes local only)
1631
+ const elseCtx = createChildContext(ctx);
1632
+ elseCtx.pipeValue = savedPipeValue;
1633
+ if (node.elseBranch.type === 'Conditional') {
1634
+ return evaluateConditional(node.elseBranch, elseCtx);
1635
+ }
1636
+ return evaluateBody(node.elseBranch, elseCtx);
1637
+ }
1638
+ return ctx.pipeValue;
1639
+ }
1640
+ /**
1641
+ * While loop evaluation: cond @ body
1642
+ * Condition must evaluate to boolean. Re-evaluated each iteration.
1643
+ * Each iteration creates a child scope (reads parent, writes local only).
1644
+ * For iteration over collections, use `each` operator instead.
1645
+ */
1646
+ async function evaluateWhileLoop(node, ctx) {
1647
+ // Save original pipe value before evaluating condition
1648
+ const originalPipeValue = ctx.pipeValue;
1649
+ // Evaluate condition
1650
+ const conditionValue = await evaluateExpression(node.condition, ctx);
1651
+ // Restore original pipe value for loop body
1652
+ ctx.pipeValue = originalPipeValue;
1653
+ // Condition must be boolean
1654
+ if (typeof conditionValue !== 'boolean') {
1655
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `While loop condition must be boolean, got ${typeof conditionValue}`, getNodeLocation(node));
1656
+ }
1657
+ let value = ctx.pipeValue;
1658
+ let iterCount = 0;
1659
+ const maxIter = getIterationLimit(ctx);
1660
+ try {
1661
+ let conditionResult = conditionValue;
1662
+ while (conditionResult) {
1663
+ iterCount++;
1664
+ if (iterCount > maxIter) {
1665
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_LIMIT_EXCEEDED, `While loop exceeded ${maxIter} iterations`, getNodeLocation(node), { limit: maxIter, iterations: iterCount });
1666
+ }
1667
+ checkAborted(ctx, node);
1668
+ // Create child scope for this iteration
1669
+ const iterCtx = createChildContext(ctx);
1670
+ iterCtx.pipeValue = value;
1671
+ value = await evaluateBody(node.body, iterCtx);
1672
+ ctx.pipeValue = value;
1673
+ // Re-evaluate condition for next iteration
1674
+ const nextCondition = await evaluateExpression(node.condition, ctx);
1675
+ if (typeof nextCondition !== 'boolean') {
1676
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `While loop condition must be boolean, got ${typeof nextCondition}`, getNodeLocation(node));
1677
+ }
1678
+ conditionResult = nextCondition;
1679
+ // Restore pipeValue after condition evaluation
1680
+ ctx.pipeValue = value;
1681
+ }
1682
+ }
1683
+ catch (e) {
1684
+ if (e instanceof BreakSignal) {
1685
+ return e.value;
1686
+ }
1687
+ throw e;
1688
+ }
1689
+ return value;
1690
+ }
1691
+ async function evaluateDoWhileLoop(node, ctx) {
1692
+ let value = ctx.pipeValue;
1693
+ try {
1694
+ // Do-while: body executes first, then condition is checked
1695
+ // Each iteration creates a child scope (reads parent, writes local only)
1696
+ let shouldContinue = true;
1697
+ while (shouldContinue) {
1698
+ checkAborted(ctx, node);
1699
+ const iterCtx = createChildContext(ctx);
1700
+ iterCtx.pipeValue = value;
1701
+ value = await evaluateBody(node.body, iterCtx);
1702
+ ctx.pipeValue = value;
1703
+ const conditionValue = await evaluateBodyExpression(node.condition, ctx);
1704
+ // Condition must be boolean
1705
+ if (typeof conditionValue !== 'boolean') {
1706
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Do-while condition must be boolean, got ${inferType(conditionValue)}`, getNodeLocation(node));
1707
+ }
1708
+ shouldContinue = conditionValue;
1709
+ }
1710
+ }
1711
+ catch (e) {
1712
+ if (e instanceof BreakSignal) {
1713
+ return e.value;
1714
+ }
1715
+ throw e;
1716
+ }
1717
+ return value;
1718
+ }
1719
+ async function evaluateBlock(node, ctx) {
1720
+ // Create child scope for the block: reads from parent, writes to local only
1721
+ const blockCtx = createChildContext(ctx);
1722
+ // All siblings inherit the SAME $ from parent (captured when block entered)
1723
+ const parentPipeValue = blockCtx.pipeValue;
1724
+ let lastValue = parentPipeValue;
1725
+ for (const stmt of node.statements) {
1726
+ // Each statement gets fresh child context with parent's $
1727
+ // This ensures siblings don't share $ - each sees the block's $
1728
+ const stmtCtx = createChildContext(blockCtx);
1729
+ stmtCtx.pipeValue = parentPipeValue; // Always parent's $, not previous sibling's
1730
+ lastValue = await executeStatement(stmt, stmtCtx);
1731
+ // Variables captured via -> need to be promoted to block scope
1732
+ // so they're visible to later siblings
1733
+ for (const [name, value] of stmtCtx.variables) {
1734
+ if (!blockCtx.variables.has(name)) {
1735
+ blockCtx.variables.set(name, value);
1736
+ const varType = stmtCtx.variableTypes.get(name);
1737
+ if (varType !== undefined) {
1738
+ blockCtx.variableTypes.set(name, varType);
1739
+ }
1740
+ }
1741
+ }
1742
+ }
1743
+ return lastValue; // Last sibling's result is block result
1744
+ }
1745
+ async function evaluateBlockExpression(node, ctx) {
1746
+ try {
1747
+ return await evaluateBlock(node, ctx);
1748
+ }
1749
+ catch (e) {
1750
+ if (e instanceof ReturnSignal) {
1751
+ return e.value;
1752
+ }
1753
+ throw e;
1754
+ }
1755
+ }
1756
+ /**
1757
+ * Evaluate a simple body (Block, GroupedExpr, or PostfixExpr).
1758
+ * Used by conditionals and loops.
1759
+ */
1760
+ async function evaluateBody(node, ctx) {
1761
+ switch (node.type) {
1762
+ case 'Block':
1763
+ return evaluateBlock(node, ctx);
1764
+ case 'GroupedExpr':
1765
+ return evaluateGroupedExpr(node, ctx);
1766
+ case 'PostfixExpr':
1767
+ return evaluatePostfixExpr(node, ctx);
1768
+ case 'PipeChain':
1769
+ return evaluatePipeChain(node, ctx);
1770
+ }
1771
+ }
1772
+ /**
1773
+ * Evaluate a simple body as an expression (catches ReturnSignal).
1774
+ */
1775
+ async function evaluateBodyExpression(node, ctx) {
1776
+ try {
1777
+ return await evaluateBody(node, ctx);
1778
+ }
1779
+ catch (e) {
1780
+ if (e instanceof ReturnSignal) {
1781
+ return e.value;
1782
+ }
1783
+ throw e;
1784
+ }
1785
+ }
1786
+ // ============================================================
1787
+ // EXPRESSION EVALUATION (arithmetic, comparison, logical)
1788
+ // ============================================================
1789
+ async function evaluateBinaryExpr(node, ctx) {
1790
+ const { op } = node;
1791
+ // Logical operators with short-circuit evaluation
1792
+ if (op === '||') {
1793
+ const left = await evaluateExprHead(node.left, ctx);
1794
+ if (isTruthy(left))
1795
+ return true;
1796
+ const right = await evaluateExprHead(node.right, ctx);
1797
+ return isTruthy(right);
1798
+ }
1799
+ if (op === '&&') {
1800
+ const left = await evaluateExprHead(node.left, ctx);
1801
+ if (!isTruthy(left))
1802
+ return false;
1803
+ const right = await evaluateExprHead(node.right, ctx);
1804
+ return isTruthy(right);
1805
+ }
1806
+ // Comparison operators - work on any values, return boolean
1807
+ if (op === '==' ||
1808
+ op === '!=' ||
1809
+ op === '<' ||
1810
+ op === '>' ||
1811
+ op === '<=' ||
1812
+ op === '>=') {
1813
+ const left = await evaluateExprHead(node.left, ctx);
1814
+ const right = await evaluateExprHead(node.right, ctx);
1815
+ return evaluateBinaryComparison(left, right, op, node);
1816
+ }
1817
+ // Arithmetic operators - require numbers
1818
+ const left = await evaluateExprHeadNumber(node.left, ctx);
1819
+ const right = await evaluateExprHeadNumber(node.right, ctx);
1820
+ switch (op) {
1821
+ case '+':
1822
+ return left + right;
1823
+ case '-':
1824
+ return left - right;
1825
+ case '*':
1826
+ return left * right;
1827
+ case '/':
1828
+ if (right === 0) {
1829
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Division by zero', node.span.start);
1830
+ }
1831
+ return left / right;
1832
+ case '%':
1833
+ if (right === 0) {
1834
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, 'Modulo by zero', node.span.start);
1835
+ }
1836
+ return left % right;
1837
+ }
1838
+ }
1839
+ /** Evaluate comparison between two values */
1840
+ function evaluateBinaryComparison(left, right, op, node) {
1841
+ switch (op) {
1842
+ case '==':
1843
+ return deepEquals(left, right);
1844
+ case '!=':
1845
+ return !deepEquals(left, right);
1846
+ case '<':
1847
+ case '>':
1848
+ case '<=':
1849
+ case '>=':
1850
+ // Ordering comparisons require compatible types
1851
+ if (typeof left === 'number' && typeof right === 'number') {
1852
+ return op === '<'
1853
+ ? left < right
1854
+ : op === '>'
1855
+ ? left > right
1856
+ : op === '<='
1857
+ ? left <= right
1858
+ : left >= right;
1859
+ }
1860
+ if (typeof left === 'string' && typeof right === 'string') {
1861
+ return op === '<'
1862
+ ? left < right
1863
+ : op === '>'
1864
+ ? left > right
1865
+ : op === '<='
1866
+ ? left <= right
1867
+ : left >= right;
1868
+ }
1869
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Cannot compare ${inferType(left)} with ${inferType(right)} using ${op}`, node.span.start);
1870
+ }
1871
+ }
1872
+ async function evaluateUnaryExpr(node, ctx) {
1873
+ if (node.op === '!') {
1874
+ const value = await evaluateExprHead(node.operand, ctx);
1875
+ return !isTruthy(value);
1876
+ }
1877
+ // Unary minus
1878
+ const operand = node.operand;
1879
+ if (operand.type === 'UnaryExpr') {
1880
+ const inner = await evaluateUnaryExpr(operand, ctx);
1881
+ if (typeof inner !== 'number') {
1882
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Unary minus requires number, got ${inferType(inner)}`, node.span.start);
1883
+ }
1884
+ return -inner;
1885
+ }
1886
+ const value = await evaluateExprHeadNumber(operand, ctx);
1887
+ return -value;
1888
+ }
1889
+ /** Evaluate expression head, returning any RillValue */
1890
+ async function evaluateExprHead(node, ctx) {
1891
+ switch (node.type) {
1892
+ case 'BinaryExpr':
1893
+ return evaluateBinaryExpr(node, ctx);
1894
+ case 'UnaryExpr':
1895
+ return evaluateUnaryExpr(node, ctx);
1896
+ case 'PostfixExpr':
1897
+ return evaluatePostfixExpr(node, ctx);
1898
+ }
1899
+ }
1900
+ /** Evaluate expression head, requiring a number result */
1901
+ async function evaluateExprHeadNumber(node, ctx) {
1902
+ const value = await evaluateExprHead(node, ctx);
1903
+ if (typeof value !== 'number') {
1904
+ throw new RuntimeError(RILL_ERROR_CODES.RUNTIME_TYPE_ERROR, `Arithmetic requires number, got ${inferType(value)}`, node.span.start);
1905
+ }
1906
+ return value;
1907
+ }
1908
+ async function evaluateGroupedExpr(node, ctx) {
1909
+ // Grouped expressions have their own scope (reads parent, writes local only)
1910
+ const childCtx = createChildContext(ctx);
1911
+ return evaluatePipeChain(node.expression, childCtx);
1912
+ }
1913
+ //# sourceMappingURL=evaluate.js.map