@rcrsr/rill 0.8.6 → 0.10.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 (191) hide show
  1. package/dist/ast-nodes.d.ts +189 -49
  2. package/dist/ast-nodes.d.ts.map +1 -1
  3. package/dist/ast-unions.d.ts +1 -1
  4. package/dist/ast-unions.d.ts.map +1 -1
  5. package/dist/constants.d.ts +14 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/constants.js +30 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/error-classes.d.ts +3 -1
  10. package/dist/error-classes.d.ts.map +1 -1
  11. package/dist/error-classes.js +11 -5
  12. package/dist/error-classes.js.map +1 -1
  13. package/dist/error-registry.d.ts.map +1 -1
  14. package/dist/error-registry.js +313 -11
  15. package/dist/error-registry.js.map +1 -1
  16. package/dist/ext/crypto/index.d.ts +2 -1
  17. package/dist/ext/crypto/index.d.ts.map +1 -1
  18. package/dist/ext/crypto/index.js +7 -0
  19. package/dist/ext/crypto/index.js.map +1 -1
  20. package/dist/ext/exec/index.d.ts +2 -1
  21. package/dist/ext/exec/index.d.ts.map +1 -1
  22. package/dist/ext/exec/index.js +6 -0
  23. package/dist/ext/exec/index.js.map +1 -1
  24. package/dist/ext/fetch/index.d.ts +2 -1
  25. package/dist/ext/fetch/index.d.ts.map +1 -1
  26. package/dist/ext/fetch/index.js +6 -0
  27. package/dist/ext/fetch/index.js.map +1 -1
  28. package/dist/ext/fs/index.d.ts +2 -1
  29. package/dist/ext/fs/index.d.ts.map +1 -1
  30. package/dist/ext/fs/index.js +3 -0
  31. package/dist/ext/fs/index.js.map +1 -1
  32. package/dist/ext/kv/index.d.ts +2 -1
  33. package/dist/ext/kv/index.d.ts.map +1 -1
  34. package/dist/ext/kv/index.js +5 -1
  35. package/dist/ext/kv/index.js.map +1 -1
  36. package/dist/generated/introspection-data.d.ts +1 -1
  37. package/dist/generated/introspection-data.d.ts.map +1 -1
  38. package/dist/generated/introspection-data.js +194 -185
  39. package/dist/generated/introspection-data.js.map +1 -1
  40. package/dist/generated/version-data.d.ts +1 -1
  41. package/dist/generated/version-data.d.ts.map +1 -1
  42. package/dist/generated/version-data.js +3 -3
  43. package/dist/generated/version-data.js.map +1 -1
  44. package/dist/highlight-map.d.ts.map +1 -1
  45. package/dist/highlight-map.js +8 -2
  46. package/dist/highlight-map.js.map +1 -1
  47. package/dist/index.d.ts +2 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/lexer/operators.d.ts.map +1 -1
  52. package/dist/lexer/operators.js +0 -2
  53. package/dist/lexer/operators.js.map +1 -1
  54. package/dist/lexer/readers.d.ts +18 -1
  55. package/dist/lexer/readers.d.ts.map +1 -1
  56. package/dist/lexer/readers.js +55 -0
  57. package/dist/lexer/readers.js.map +1 -1
  58. package/dist/parser/helpers.d.ts +8 -13
  59. package/dist/parser/helpers.d.ts.map +1 -1
  60. package/dist/parser/helpers.js +42 -35
  61. package/dist/parser/helpers.js.map +1 -1
  62. package/dist/parser/index.d.ts +1 -0
  63. package/dist/parser/index.d.ts.map +1 -1
  64. package/dist/parser/index.js +1 -0
  65. package/dist/parser/index.js.map +1 -1
  66. package/dist/parser/parser-collect.d.ts.map +1 -1
  67. package/dist/parser/parser-collect.js +34 -5
  68. package/dist/parser/parser-collect.js.map +1 -1
  69. package/dist/parser/parser-control.d.ts +1 -1
  70. package/dist/parser/parser-control.d.ts.map +1 -1
  71. package/dist/parser/parser-control.js +11 -2
  72. package/dist/parser/parser-control.js.map +1 -1
  73. package/dist/parser/parser-expr.d.ts +3 -1
  74. package/dist/parser/parser-expr.d.ts.map +1 -1
  75. package/dist/parser/parser-expr.js +377 -100
  76. package/dist/parser/parser-expr.js.map +1 -1
  77. package/dist/parser/parser-extract.d.ts +3 -5
  78. package/dist/parser/parser-extract.d.ts.map +1 -1
  79. package/dist/parser/parser-extract.js +37 -69
  80. package/dist/parser/parser-extract.js.map +1 -1
  81. package/dist/parser/parser-functions.d.ts +2 -2
  82. package/dist/parser/parser-functions.d.ts.map +1 -1
  83. package/dist/parser/parser-functions.js +112 -36
  84. package/dist/parser/parser-functions.js.map +1 -1
  85. package/dist/parser/parser-literals.d.ts +5 -4
  86. package/dist/parser/parser-literals.d.ts.map +1 -1
  87. package/dist/parser/parser-literals.js +316 -47
  88. package/dist/parser/parser-literals.js.map +1 -1
  89. package/dist/parser/parser-script.d.ts.map +1 -1
  90. package/dist/parser/parser-script.js +25 -12
  91. package/dist/parser/parser-script.js.map +1 -1
  92. package/dist/parser/parser-shape.d.ts +13 -0
  93. package/dist/parser/parser-shape.d.ts.map +1 -0
  94. package/dist/parser/parser-shape.js +72 -0
  95. package/dist/parser/parser-shape.js.map +1 -0
  96. package/dist/parser/parser-types.d.ts +31 -0
  97. package/dist/parser/parser-types.d.ts.map +1 -0
  98. package/dist/parser/parser-types.js +78 -0
  99. package/dist/parser/parser-types.js.map +1 -0
  100. package/dist/parser/parser-variables.d.ts.map +1 -1
  101. package/dist/parser/parser-variables.js +10 -1
  102. package/dist/parser/parser-variables.js.map +1 -1
  103. package/dist/runtime/core/callable.d.ts +27 -22
  104. package/dist/runtime/core/callable.d.ts.map +1 -1
  105. package/dist/runtime/core/callable.js +30 -26
  106. package/dist/runtime/core/callable.js.map +1 -1
  107. package/dist/runtime/core/context.d.ts.map +1 -1
  108. package/dist/runtime/core/context.js +8 -8
  109. package/dist/runtime/core/context.js.map +1 -1
  110. package/dist/runtime/core/equals.d.ts.map +1 -1
  111. package/dist/runtime/core/equals.js +179 -30
  112. package/dist/runtime/core/equals.js.map +1 -1
  113. package/dist/runtime/core/eval/base.d.ts +2 -2
  114. package/dist/runtime/core/eval/base.d.ts.map +1 -1
  115. package/dist/runtime/core/eval/evaluator.d.ts.map +1 -1
  116. package/dist/runtime/core/eval/evaluator.js +3 -1
  117. package/dist/runtime/core/eval/evaluator.js.map +1 -1
  118. package/dist/runtime/core/eval/index.d.ts +18 -3
  119. package/dist/runtime/core/eval/index.d.ts.map +1 -1
  120. package/dist/runtime/core/eval/index.js +22 -2
  121. package/dist/runtime/core/eval/index.js.map +1 -1
  122. package/dist/runtime/core/eval/mixins/annotations.d.ts.map +1 -1
  123. package/dist/runtime/core/eval/mixins/annotations.js +14 -8
  124. package/dist/runtime/core/eval/mixins/annotations.js.map +1 -1
  125. package/dist/runtime/core/eval/mixins/closures.d.ts +0 -2
  126. package/dist/runtime/core/eval/mixins/closures.d.ts.map +1 -1
  127. package/dist/runtime/core/eval/mixins/closures.js +341 -105
  128. package/dist/runtime/core/eval/mixins/closures.js.map +1 -1
  129. package/dist/runtime/core/eval/mixins/collections.d.ts.map +1 -1
  130. package/dist/runtime/core/eval/mixins/collections.js +65 -25
  131. package/dist/runtime/core/eval/mixins/collections.js.map +1 -1
  132. package/dist/runtime/core/eval/mixins/control-flow.d.ts.map +1 -1
  133. package/dist/runtime/core/eval/mixins/control-flow.js +21 -17
  134. package/dist/runtime/core/eval/mixins/control-flow.js.map +1 -1
  135. package/dist/runtime/core/eval/mixins/conversion.d.ts +30 -0
  136. package/dist/runtime/core/eval/mixins/conversion.d.ts.map +1 -0
  137. package/dist/runtime/core/eval/mixins/conversion.js +212 -0
  138. package/dist/runtime/core/eval/mixins/conversion.js.map +1 -0
  139. package/dist/runtime/core/eval/mixins/core.d.ts.map +1 -1
  140. package/dist/runtime/core/eval/mixins/core.js +101 -32
  141. package/dist/runtime/core/eval/mixins/core.js.map +1 -1
  142. package/dist/runtime/core/eval/mixins/extraction.d.ts.map +1 -1
  143. package/dist/runtime/core/eval/mixins/extraction.js +136 -30
  144. package/dist/runtime/core/eval/mixins/extraction.js.map +1 -1
  145. package/dist/runtime/core/eval/mixins/list-dispatch.d.ts +17 -0
  146. package/dist/runtime/core/eval/mixins/list-dispatch.d.ts.map +1 -0
  147. package/dist/runtime/core/eval/mixins/list-dispatch.js +97 -0
  148. package/dist/runtime/core/eval/mixins/list-dispatch.js.map +1 -0
  149. package/dist/runtime/core/eval/mixins/literals.d.ts.map +1 -1
  150. package/dist/runtime/core/eval/mixins/literals.js +73 -83
  151. package/dist/runtime/core/eval/mixins/literals.js.map +1 -1
  152. package/dist/runtime/core/eval/mixins/types.d.ts +4 -0
  153. package/dist/runtime/core/eval/mixins/types.d.ts.map +1 -1
  154. package/dist/runtime/core/eval/mixins/types.js +323 -3
  155. package/dist/runtime/core/eval/mixins/types.js.map +1 -1
  156. package/dist/runtime/core/eval/mixins/variables.d.ts.map +1 -1
  157. package/dist/runtime/core/eval/mixins/variables.js +45 -7
  158. package/dist/runtime/core/eval/mixins/variables.js.map +1 -1
  159. package/dist/runtime/core/execute.d.ts.map +1 -1
  160. package/dist/runtime/core/execute.js +3 -16
  161. package/dist/runtime/core/execute.js.map +1 -1
  162. package/dist/runtime/core/field-descriptor.d.ts +29 -0
  163. package/dist/runtime/core/field-descriptor.d.ts.map +1 -0
  164. package/dist/runtime/core/field-descriptor.js +27 -0
  165. package/dist/runtime/core/field-descriptor.js.map +1 -0
  166. package/dist/runtime/core/types.d.ts +15 -6
  167. package/dist/runtime/core/types.d.ts.map +1 -1
  168. package/dist/runtime/core/types.js.map +1 -1
  169. package/dist/runtime/core/values.d.ts +114 -9
  170. package/dist/runtime/core/values.d.ts.map +1 -1
  171. package/dist/runtime/core/values.js +529 -43
  172. package/dist/runtime/core/values.js.map +1 -1
  173. package/dist/runtime/ext/builtins.d.ts.map +1 -1
  174. package/dist/runtime/ext/builtins.js +47 -107
  175. package/dist/runtime/ext/builtins.js.map +1 -1
  176. package/dist/runtime/ext/extensions.d.ts +21 -2
  177. package/dist/runtime/ext/extensions.d.ts.map +1 -1
  178. package/dist/runtime/ext/extensions.js.map +1 -1
  179. package/dist/runtime/index.d.ts +6 -4
  180. package/dist/runtime/index.d.ts.map +1 -1
  181. package/dist/runtime/index.js +7 -2
  182. package/dist/runtime/index.js.map +1 -1
  183. package/dist/token-types.d.ts +7 -2
  184. package/dist/token-types.d.ts.map +1 -1
  185. package/dist/token-types.js +9 -2
  186. package/dist/token-types.js.map +1 -1
  187. package/dist/value-types.d.ts +32 -1
  188. package/dist/value-types.d.ts.map +1 -1
  189. package/dist/value-types.js +1 -1
  190. package/dist/value-types.js.map +1 -1
  191. package/package.json +4 -1
@@ -8,7 +8,6 @@
8
8
  * - Invoke operations
9
9
  * - Pipe invocations
10
10
  * - Property access on piped values
11
- * - Closure chains
12
11
  *
13
12
  * Interface requirements (from spec):
14
13
  * - invokeCallable(callable, args, location) -> Promise<RillValue>
@@ -20,7 +19,6 @@
20
19
  * - evaluatePipeInvoke(node, input) -> Promise<RillValue>
21
20
  * - evaluateMethod(node, receiver) -> Promise<RillValue>
22
21
  * - evaluateInvoke(node, receiver) -> Promise<RillValue>
23
- * - evaluateClosureChain(node, input) -> Promise<RillValue>
24
22
  *
25
23
  * Error Handling:
26
24
  * - Undefined functions throw RuntimeError(RUNTIME_UNDEFINED_FUNCTION) [EC-18]
@@ -46,9 +44,9 @@
46
44
  * @internal
47
45
  */
48
46
  import { RuntimeError } from '../../../../types.js';
49
- import { isCallable, isScriptCallable, isApplicationCallable, isDict, validateCallableArgs, } from '../../callable.js';
47
+ import { isCallable, isScriptCallable, isApplicationCallable, isDict, validateCallableArgs, paramsToStructuralType, } from '../../callable.js';
50
48
  import { getVariable, pushCallFrame, popCallFrame } from '../../context.js';
51
- import { inferType, isTuple } from '../../values.js';
49
+ import { inferType, isTypeValue, isTuple, isOrdered, createOrdered, inferStructuralType, structuralTypeMatches, formatStructuralType, } from '../../values.js';
52
50
  /**
53
51
  * ClosuresMixin implementation.
54
52
  *
@@ -70,14 +68,13 @@ import { inferType, isTuple } from '../../values.js';
70
68
  * - evaluatePipeInvoke(node, input) -> Promise<RillValue>
71
69
  * - evaluateMethod(node, receiver) -> Promise<RillValue>
72
70
  * - evaluateInvoke(node, receiver) -> Promise<RillValue>
73
- * - evaluateClosureChain(node, input) -> Promise<RillValue>
74
71
  * - evaluateArgs(argExprs) -> Promise<RillValue[]> (helper)
75
72
  * - invokeFnCallable(callable, args, location) -> Promise<RillValue> (helper)
76
73
  * - invokeScriptCallable(callable, args, location) -> Promise<RillValue> (helper)
77
- * - invokeScriptCallableWithArgs(callable, tuple, location) -> Promise<RillValue> (helper)
78
74
  * - createCallableContext(callable) -> RuntimeContext (helper)
79
75
  * - validateParamType(param, value, location) -> void (helper)
80
76
  * - inferTypeFromDefault(defaultValue) -> RillTypeName | null (helper)
77
+ * - bindArgsToParams(argNodes, callable, callLocation) -> Promise<BoundArgs> (helper)
81
78
  */
82
79
  function createClosuresMixin(Base) {
83
80
  return class ClosuresEvaluator extends Base {
@@ -89,8 +86,9 @@ function createClosuresMixin(Base) {
89
86
  const savedPipeValue = this.ctx.pipeValue;
90
87
  const args = [];
91
88
  for (const arg of argExprs) {
89
+ const expr = arg.type === 'SpreadArg' ? arg.expression : arg;
92
90
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
- args.push(await this.evaluateExpression(arg));
91
+ args.push(await this.evaluateExpression(expr));
94
92
  }
95
93
  this.ctx.pipeValue = savedPipeValue;
96
94
  return args;
@@ -188,7 +186,29 @@ function createClosuresMixin(Base) {
188
186
  * Throws RuntimeError on type mismatch.
189
187
  */
190
188
  validateParamType(param, value, callLocation) {
189
+ // IR-4: Structural dispatch — use typeStructure when sub-fields present
190
+ if (param.typeStructure !== undefined) {
191
+ const ts = param.typeStructure;
192
+ const hasSubFields = 'element' in ts ||
193
+ 'fields' in ts ||
194
+ 'elements' in ts ||
195
+ 'params' in ts ||
196
+ 'ret' in ts;
197
+ if (hasSubFields) {
198
+ if (!structuralTypeMatches(value, ts)) {
199
+ throw new RuntimeError('RILL-R001', `Parameter type mismatch: ${param.name} expects ${formatStructuralType(ts)}, got ${formatStructuralType(inferStructuralType(value))}`, callLocation, {
200
+ paramName: param.name,
201
+ expectedType: formatStructuralType(ts),
202
+ actualType: formatStructuralType(inferStructuralType(value)),
203
+ });
204
+ }
205
+ return;
206
+ }
207
+ }
208
+ // Backward-compatible leaf type check
191
209
  const expectedType = param.typeName ?? this.inferTypeFromDefault(param.defaultValue);
210
+ if (expectedType === 'any')
211
+ return;
192
212
  if (expectedType !== null) {
193
213
  const valueType = inferType(value);
194
214
  if (valueType !== expectedType) {
@@ -201,10 +221,6 @@ function createClosuresMixin(Base) {
201
221
  * Handles parameter binding, default values, and type checking.
202
222
  */
203
223
  async invokeScriptCallable(callable, args, callLocation) {
204
- const firstArg = args[0];
205
- if (args.length === 1 && firstArg !== undefined && isTuple(firstArg)) {
206
- return this.invokeScriptCallableWithArgs(callable, firstArg, callLocation);
207
- }
208
224
  const callableCtx = this.createCallableContext(callable);
209
225
  // Validate excess arguments (EC-8)
210
226
  if (args.length > callable.params.length) {
@@ -229,78 +245,24 @@ function createClosuresMixin(Base) {
229
245
  callableCtx.pipeValue = value;
230
246
  }
231
247
  }
248
+ // EC-1: Reject empty block bodies before execution (AC-17)
249
+ if (callable.body.type === 'Block' &&
250
+ callable.body.statements.length === 0) {
251
+ throw new RuntimeError('RILL-R043', 'Closure body produced no value', callLocation, { context: 'Closure body' });
252
+ }
232
253
  // Switch context to callable context
233
254
  const savedCtx = this.ctx;
234
255
  this.ctx = callableCtx;
235
256
  try {
236
257
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
- return await this.evaluateBodyExpression(callable.body);
238
- }
239
- finally {
240
- this.ctx = savedCtx;
241
- }
242
- }
243
- /**
244
- * Invoke script callable with tuple arguments (named or positional).
245
- * Handles *[...] and *[name: val] argument unpacking.
246
- */
247
- async invokeScriptCallableWithArgs(closure, tupleValue, callLocation) {
248
- const closureCtx = this.createCallableContext(closure);
249
- const hasNumericKeys = [...tupleValue.entries.keys()].some((k) => typeof k === 'number');
250
- const hasStringKeys = [...tupleValue.entries.keys()].some((k) => typeof k === 'string');
251
- if (hasNumericKeys && hasStringKeys) {
252
- throw new RuntimeError('RILL-R001', 'Tuple cannot mix positional (numeric) and named (string) keys', callLocation);
253
- }
254
- const boundParams = new Set();
255
- if (hasNumericKeys) {
256
- for (const [key, value] of tupleValue.entries) {
257
- const position = key;
258
- const param = closure.params[position];
259
- if (param === undefined) {
260
- throw new RuntimeError('RILL-R001', `Extra argument at position ${position} (closure has ${closure.params.length} params)`, callLocation, { position, paramCount: closure.params.length });
261
- }
262
- this.validateParamType(param, value, callLocation);
263
- closureCtx.variables.set(param.name, value);
264
- boundParams.add(param.name);
258
+ const result = await this.evaluateBodyExpression(callable.body);
259
+ // IR-4: Assert return value against declared returnShape (AC-14, AC-15, AC-16)
260
+ if (callable.returnShape !== undefined) {
261
+ // EC-4: Type assertion — value must match the declared scalar type
262
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
263
+ this.assertType(result, callable.returnShape.structure, callLocation);
265
264
  }
266
- }
267
- else if (hasStringKeys) {
268
- const paramNames = new Set(closure.params.map((p) => p.name));
269
- for (const [key, value] of tupleValue.entries) {
270
- const name = key;
271
- if (!paramNames.has(name)) {
272
- throw new RuntimeError('RILL-R001', `Unknown argument '${name}' (valid params: ${[...paramNames].join(', ')})`, callLocation, { argName: name, validParams: [...paramNames] });
273
- }
274
- const param = closure.params.find((p) => p.name === name);
275
- this.validateParamType(param, value, callLocation);
276
- closureCtx.variables.set(name, value);
277
- // Block-closures have param named '$': sync with pipeValue for bare $ references
278
- if (name === '$') {
279
- closureCtx.pipeValue = value;
280
- }
281
- boundParams.add(name);
282
- }
283
- }
284
- for (const param of closure.params) {
285
- if (!boundParams.has(param.name)) {
286
- if (param.defaultValue !== null) {
287
- closureCtx.variables.set(param.name, param.defaultValue);
288
- // Block-closures have param named '$': sync with pipeValue for bare $ references
289
- if (param.name === '$') {
290
- closureCtx.pipeValue = param.defaultValue;
291
- }
292
- }
293
- else {
294
- throw new RuntimeError('RILL-R001', `Missing argument '${param.name}' (no default value)`, callLocation, { paramName: param.name });
295
- }
296
- }
297
- }
298
- // Switch context to callable context
299
- const savedCtx = this.ctx;
300
- this.ctx = closureCtx;
301
- try {
302
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
- return await this.evaluateBodyExpression(closure.body);
265
+ return result;
304
266
  }
305
267
  finally {
306
268
  this.ctx = savedCtx;
@@ -316,6 +278,32 @@ function createClosuresMixin(Base) {
316
278
  if (!fn) {
317
279
  throw new RuntimeError('RILL-R006', `Unknown function: ${node.name}`, this.getNodeLocation(node), { functionName: node.name });
318
280
  }
281
+ // EC-10/EC-11: spread-aware path for host calls
282
+ const hasSpread = node.args.some((a) => a.type === 'SpreadArg');
283
+ if (hasSpread) {
284
+ if (typeof fn === 'function') {
285
+ // EC-10: raw built-in (RuntimeCallable) — spread not supported
286
+ throw new RuntimeError('RILL-R001', `Spread not supported for built-in function '${node.name}'`, this.getNodeLocation(node), { functionName: node.name });
287
+ }
288
+ // EC-11: ApplicationCallable — bindArgsToParams handles no-params guard
289
+ const boundArgs = await this.bindArgsToParams(node.args, fn, node.span.start);
290
+ const orderedArgs = fn.params.map((p) => boundArgs.params.get(p.name));
291
+ // Observability: onHostCall before execution
292
+ this.ctx.observability.onHostCall?.({
293
+ name: node.name,
294
+ args: orderedArgs,
295
+ });
296
+ const startTime = performance.now();
297
+ const wrappedPromise = this.withTimeout(this.invokeCallable(fn, orderedArgs, node.span.start, node.name), this.ctx.timeout, node.name, node);
298
+ const result = await wrappedPromise;
299
+ const durationMs = performance.now() - startTime;
300
+ this.ctx.observability.onFunctionReturn?.({
301
+ name: node.name,
302
+ value: result,
303
+ durationMs,
304
+ });
305
+ return result;
306
+ }
319
307
  const args = await this.evaluateArgs(node.args);
320
308
  // Add pipe value to empty args list UNLESS function has typed params with length 0
321
309
  // (typed functions with params: [] explicitly declare zero parameters)
@@ -359,6 +347,50 @@ function createClosuresMixin(Base) {
359
347
  });
360
348
  return result;
361
349
  }
350
+ /**
351
+ * Evaluate host function reference: ns::name (no parens, namespaced).
352
+ *
353
+ * When pipeValue is null (value-capture context): returns the
354
+ * ApplicationCallable directly without invoking [IR-4].
355
+ *
356
+ * When pipeValue is set (pipe/branch context): invokes the callable
357
+ * with the pipe value as the implicit argument, consistent with how
358
+ * bare HostRef behaves as a pipe-stage expression [IR-4].
359
+ *
360
+ * Throws RILL-R006 when the function name is not registered [EC-4].
361
+ */
362
+ async evaluateHostRef(node) {
363
+ this.checkAborted(node);
364
+ const fn = this.ctx.functions.get(node.name);
365
+ if (!fn) {
366
+ throw new RuntimeError('RILL-R006', `Function "${node.name}" not found`, this.getNodeLocation(node), { functionName: node.name });
367
+ }
368
+ // Build ApplicationCallable wrapper for raw CallableFn; pass through
369
+ // ApplicationCallable objects directly.
370
+ let appCallable;
371
+ if (typeof fn === 'function') {
372
+ appCallable = {
373
+ __type: 'callable',
374
+ kind: 'application',
375
+ fn,
376
+ params: undefined,
377
+ isProperty: false,
378
+ };
379
+ }
380
+ else {
381
+ appCallable = fn;
382
+ }
383
+ // Value-capture context: no pipe value → return callable without invoking [IR-4]
384
+ if (this.ctx.pipeValue === null) {
385
+ return appCallable;
386
+ }
387
+ // Pipe/branch context: pipe value present → invoke with it as implicit argument
388
+ const fnHasTypedZeroParams = appCallable.params !== undefined && appCallable.params.length === 0;
389
+ const args = fnHasTypedZeroParams
390
+ ? []
391
+ : [this.ctx.pipeValue];
392
+ return this.invokeCallable(appCallable, args, this.getNodeLocation(node), node.name);
393
+ }
362
394
  /**
363
395
  * Evaluate closure call: $fn(args)
364
396
  * Delegates to evaluateClosureCallWithPipe using current pipe value.
@@ -396,6 +428,15 @@ function createClosuresMixin(Base) {
396
428
  throw new RuntimeError('RILL-R002', `'${fullPath}' is not callable`, this.getNodeLocation(node), { path: fullPath, actualType: inferType(value) });
397
429
  }
398
430
  const closure = value;
431
+ // Spread-aware path: when args contain a SpreadArgNode use bindArgsToParams
432
+ if (node.args.some((a) => a.type === 'SpreadArg')) {
433
+ if (!isScriptCallable(closure) && !isApplicationCallable(closure)) {
434
+ throw new RuntimeError('RILL-R001', `Spread not supported for built-in callable at '${fullPath}'`, this.getNodeLocation(node));
435
+ }
436
+ const boundArgs = await this.bindArgsToParams(node.args, closure, node.span.start);
437
+ const orderedArgs = closure.params.map((p) => boundArgs.params.get(p.name));
438
+ return this.invokeCallable(closure, orderedArgs, node.span.start, fullPath);
439
+ }
399
440
  const args = await this.evaluateArgs(node.args);
400
441
  // If no explicit args and has pipe input, add pipe value as first arg
401
442
  // UNLESS closure has zero parameters (explicit zero-param signature)
@@ -464,6 +505,12 @@ function createClosuresMixin(Base) {
464
505
  if (!isScriptCallable(input)) {
465
506
  throw new RuntimeError('RILL-R002', `Cannot invoke non-closure value (got ${typeof input})`, this.getNodeLocation(node));
466
507
  }
508
+ // Spread-aware path: when args contain a SpreadArgNode use bindArgsToParams
509
+ if (node.args.some((a) => a.type === 'SpreadArg')) {
510
+ const boundArgs = await this.bindArgsToParams(node.args, input, node.span.start);
511
+ const orderedArgs = input.params.map((p) => boundArgs.params.get(p.name));
512
+ return this.invokeScriptCallable(input, orderedArgs, node.span.start);
513
+ }
467
514
  const args = await this.evaluateArgs(node.args);
468
515
  return this.invokeScriptCallable(input, args, node.span.start);
469
516
  }
@@ -480,6 +527,16 @@ function createClosuresMixin(Base) {
480
527
  if (isCallable(receiver)) {
481
528
  throw new RuntimeError('RILL-R003', `Method .${node.name} not available on callable (invoke with -> $() first)`, this.getNodeLocation(node), { methodName: node.name, receiverType: 'callable' });
482
529
  }
530
+ // IR-3: .name on type values returns the typeName string (method path)
531
+ // IR-4: .signature on type values returns formatStructuralType(structure)
532
+ if (isTypeValue(receiver)) {
533
+ if (node.name === 'name') {
534
+ return receiver.typeName;
535
+ }
536
+ if (node.name === 'signature') {
537
+ return formatStructuralType(receiver.structure);
538
+ }
539
+ }
483
540
  const args = await this.evaluateArgs(node.args);
484
541
  if (isDict(receiver)) {
485
542
  const dictValue = receiver[node.name];
@@ -493,6 +550,10 @@ function createClosuresMixin(Base) {
493
550
  if (isDict(receiver) && args.length === 0 && node.name in receiver) {
494
551
  return receiver[node.name];
495
552
  }
553
+ // EC-5: Unknown dot property on type value raises RILL-R009
554
+ if (isTypeValue(receiver)) {
555
+ throw new RuntimeError('RILL-R009', `Property '${node.name}' not found on type value (available: name, signature)`, this.getNodeLocation(node), { property: node.name, type: 'type value' });
556
+ }
496
557
  throw new RuntimeError('RILL-R007', `Unknown method: ${node.name}`, this.getNodeLocation(node), { methodName: node.name });
497
558
  }
498
559
  const result = method(receiver, args, this.ctx, this.getNodeLocation(node));
@@ -506,35 +567,17 @@ function createClosuresMixin(Base) {
506
567
  if (!isCallable(receiver)) {
507
568
  throw new RuntimeError('RILL-R002', `Cannot invoke non-callable value (got ${inferType(receiver)})`, this.getNodeLocation(node), { actualType: inferType(receiver) });
508
569
  }
509
- const args = await this.evaluateArgs(node.args);
510
- return this.invokeCallable(receiver, args, this.getNodeLocation(node));
511
- }
512
- /**
513
- * Evaluate closure chain: >>expr
514
- * Chains multiple closures for composition.
515
- */
516
- async evaluateClosureChain(node, input) {
517
- // Evaluate the target expression to get the closure(s)
518
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
519
- const target = await this.evaluateExpression(node.target);
520
- if (Array.isArray(target)) {
521
- // List of closures: chain them left-to-right
522
- let result = input;
523
- for (const closure of target) {
524
- if (!isCallable(closure)) {
525
- throw new RuntimeError('RILL-R002', `Closure chain element must be callable, got ${inferType(closure)}`, this.getNodeLocation(node));
526
- }
527
- result = await this.invokeCallable(closure, [result], this.getNodeLocation(node));
570
+ // Spread-aware path: when args contain a SpreadArgNode use bindArgsToParams
571
+ if (node.args.some((a) => a.type === 'SpreadArg')) {
572
+ if (!isScriptCallable(receiver) && !isApplicationCallable(receiver)) {
573
+ throw new RuntimeError('RILL-R001', `Spread not supported for built-in callable`, this.getNodeLocation(node));
528
574
  }
529
- return result;
530
- }
531
- else if (isCallable(target)) {
532
- // Single closure: invoke with input
533
- return this.invokeCallable(target, [input], this.getNodeLocation(node));
534
- }
535
- else {
536
- throw new RuntimeError('RILL-R002', `Closure chain requires callable or list of callables, got ${inferType(target)}`, this.getNodeLocation(node));
575
+ const boundArgs = await this.bindArgsToParams(node.args, receiver, node.span.start);
576
+ const orderedArgs = receiver.params.map((p) => boundArgs.params.get(p.name));
577
+ return this.invokeCallable(receiver, orderedArgs, node.span.start);
537
578
  }
579
+ const args = await this.evaluateArgs(node.args);
580
+ return this.invokeCallable(receiver, args, this.getNodeLocation(node));
538
581
  }
539
582
  /**
540
583
  * Evaluate annotation reflection access: .^key
@@ -545,9 +588,72 @@ function createClosuresMixin(Base) {
545
588
  * Throws RUNTIME_UNDEFINED_ANNOTATION for missing annotations.
546
589
  */
547
590
  async evaluateAnnotationAccess(value, key, location) {
591
+ // IR-2: .^type returns a RillTypeValue for any rill value
592
+ if (key === 'type') {
593
+ const typeValue = Object.freeze({
594
+ __rill_type: true,
595
+ typeName: inferType(value),
596
+ structure: inferStructuralType(value),
597
+ });
598
+ return typeValue;
599
+ }
600
+ // IR-5: .^name on type values raises RILL-R008 (type values are not annotation containers)
601
+ if (isTypeValue(value) && key === 'name') {
602
+ throw new RuntimeError('RILL-R008', `Annotation access not supported on type values`, location, { annotationKey: key });
603
+ }
604
+ // IR-2/IR-5: .^input returns the input shape for callable values
605
+ // Params are converted from internal tuples to RillOrdered so the
606
+ // value survives rill's homogeneous-list constraint.
607
+ if (key === 'input') {
608
+ if (isScriptCallable(value)) {
609
+ const shape = value.inputShape;
610
+ if (shape.type === 'closure') {
611
+ return {
612
+ type: shape.type,
613
+ params: createOrdered(shape.params),
614
+ ret: value.returnShape !== undefined
615
+ ? value.returnShape.structure
616
+ : shape.ret,
617
+ };
618
+ }
619
+ return shape;
620
+ }
621
+ if (isApplicationCallable(value)) {
622
+ if (value.params === undefined) {
623
+ // IR-5: untyped host function — no shape available
624
+ return false;
625
+ }
626
+ const shape = paramsToStructuralType(value.params);
627
+ if (shape.type === 'closure') {
628
+ return {
629
+ type: shape.type,
630
+ params: createOrdered(shape.params),
631
+ ret: shape.ret,
632
+ };
633
+ }
634
+ return shape;
635
+ }
636
+ // Non-callable: fall through to existing RILL-R003 guard below
637
+ }
638
+ // IR-3: .^output returns the declared output contract for callable values
639
+ if (key === 'output') {
640
+ if (isScriptCallable(value)) {
641
+ if (value.returnShape !== undefined) {
642
+ return value.returnShape;
643
+ }
644
+ // No :type-target declared — return type value `any` (AC-17, AC-18, AC-19)
645
+ const anyTypeValue = Object.freeze({
646
+ __rill_type: true,
647
+ typeName: 'any',
648
+ structure: { type: 'any' },
649
+ });
650
+ return anyTypeValue;
651
+ }
652
+ // Non-callable: fall through to existing RILL-R003 guard below
653
+ }
548
654
  // Only ScriptCallable supports annotation reflection
549
655
  if (!isScriptCallable(value)) {
550
- throw new RuntimeError('RILL-R003', `Cannot access annotation on ${inferType(value)}`, location, { actualType: inferType(value) });
656
+ throw new RuntimeError('RILL-R003', `annotation not found: ^${key}`, location, { actualType: inferType(value) });
551
657
  }
552
658
  // Access annotation from ScriptCallable
553
659
  const annotationValue = value.annotations[key];
@@ -557,6 +663,136 @@ function createClosuresMixin(Base) {
557
663
  }
558
664
  return annotationValue;
559
665
  }
666
+ /**
667
+ * Bind argument nodes to callable parameters when a SpreadArgNode is present.
668
+ *
669
+ * Evaluates positional args LTR, evaluates the spread expression, dispatches
670
+ * by value type (Tuple, Ordered, or Dict), validates bindings, and returns
671
+ * a Map of param name → value.
672
+ *
673
+ * EC-3: bare ... with null pipe value → RuntimeError
674
+ * EC-4: spread value is not tuple/dict/ordered → RuntimeError
675
+ * EC-5: dict spread key matches no parameter → RuntimeError
676
+ * EC-6: ordered spread key at position N mismatches param at position N → RuntimeError
677
+ * EC-7: duplicate binding (positional + spread) → RuntimeError
678
+ * EC-8: missing required parameter after all args processed → RuntimeError
679
+ * EC-9: extra tuple values exceed param count → RuntimeError
680
+ * EC-11: ApplicationCallable with no params metadata → RuntimeError
681
+ */
682
+ async bindArgsToParams(argNodes, callable, callLocation) {
683
+ // EC-11: ApplicationCallable must have params metadata for spread to work
684
+ if (callable.kind === 'application' && callable.params === undefined) {
685
+ const name = callable.fn.name !== '' ? callable.fn.name : '<anonymous>';
686
+ throw new RuntimeError('RILL-R001', `Spread not supported for host function '${name}': parameter metadata required`, callLocation);
687
+ }
688
+ const params = callable.params;
689
+ const bound = new Map();
690
+ // Positional index: next unbound parameter position
691
+ let positionalIndex = 0;
692
+ // Save pipe value so evaluating args does not mutate it permanently
693
+ const savedPipeValue = this.ctx.pipeValue;
694
+ try {
695
+ for (const argNode of argNodes) {
696
+ if (argNode.type !== 'SpreadArg') {
697
+ // Positional argument
698
+ const param = params[positionalIndex];
699
+ if (param === undefined) {
700
+ // Extra positional arg beyond param count — EC-9 reports after spread
701
+ // but for pure positional excess, error here with the positional count
702
+ throw new RuntimeError('RILL-R001', `Extra positional argument at position ${positionalIndex} (function has ${params.length} parameters)`, callLocation);
703
+ }
704
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
705
+ const value = await this.evaluateExpression(argNode);
706
+ bound.set(param.name, value);
707
+ positionalIndex++;
708
+ }
709
+ else {
710
+ // SpreadArg: evaluate the expression
711
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
712
+ const spreadValue = await this.evaluateExpression(argNode.expression);
713
+ // EC-3: bare ... with no pipe value evaluates to null
714
+ if (spreadValue === null) {
715
+ throw new RuntimeError('RILL-R001', 'Spread requires an active pipe value ($)', callLocation);
716
+ }
717
+ // Dispatch by type: isOrdered BEFORE isDict per spec (IC-3 algorithm step 2)
718
+ if (isTuple(spreadValue)) {
719
+ // Tuple: fill remaining params positionally LTR (EC-9)
720
+ const tupleEntries = spreadValue.entries;
721
+ const remaining = params.length - positionalIndex;
722
+ if (tupleEntries.length > remaining) {
723
+ throw new RuntimeError('RILL-R001', `Spread tuple has ${tupleEntries.length} values but only ${remaining} parameter(s) remain`, callLocation);
724
+ }
725
+ for (let i = 0; i < tupleEntries.length; i++) {
726
+ const param = params[positionalIndex + i];
727
+ // EC-7: duplicate binding
728
+ if (bound.has(param.name)) {
729
+ throw new RuntimeError('RILL-R001', `Duplicate binding for parameter '${param.name}': already bound positionally`, callLocation);
730
+ }
731
+ bound.set(param.name, tupleEntries[i]);
732
+ }
733
+ positionalIndex += tupleEntries.length;
734
+ }
735
+ else if (isOrdered(spreadValue)) {
736
+ // Ordered: match key by name AND position
737
+ // Key at position N within ordered value must match param at (spreadStart + N)
738
+ const orderedEntries = spreadValue.entries;
739
+ for (let i = 0; i < orderedEntries.length; i++) {
740
+ const [key, value] = orderedEntries[i];
741
+ const expectedParam = params[positionalIndex + i];
742
+ // EC-6: key-order mismatch
743
+ if (expectedParam === undefined || expectedParam.name !== key) {
744
+ const expectedName = expectedParam?.name ?? '<none>';
745
+ throw new RuntimeError('RILL-R001', `Ordered spread key '${key}' at position ${i} does not match expected parameter '${expectedName}' at position ${positionalIndex + i}`, callLocation);
746
+ }
747
+ // EC-7: duplicate binding
748
+ if (bound.has(key)) {
749
+ throw new RuntimeError('RILL-R001', `Duplicate binding for parameter '${key}': already bound positionally`, callLocation);
750
+ }
751
+ bound.set(key, value);
752
+ }
753
+ positionalIndex += orderedEntries.length;
754
+ }
755
+ else if (isDict(spreadValue)) {
756
+ // Dict: match each key to param by name (order irrelevant)
757
+ const dictValue = spreadValue;
758
+ const paramNames = new Set(params.map((p) => p.name));
759
+ for (const [key, value] of Object.entries(dictValue)) {
760
+ // EC-5: key matches no parameter
761
+ if (!paramNames.has(key)) {
762
+ const validParams = params.map((p) => p.name).join(', ');
763
+ throw new RuntimeError('RILL-R001', `Dict spread key '${key}' does not match any parameter. Valid parameters: ${validParams}`, callLocation);
764
+ }
765
+ // EC-7: duplicate binding
766
+ if (bound.has(key)) {
767
+ throw new RuntimeError('RILL-R001', `Duplicate binding for parameter '${key}': already bound positionally`, callLocation);
768
+ }
769
+ bound.set(key, value);
770
+ }
771
+ }
772
+ else {
773
+ // EC-4: spread value is not tuple/dict/ordered
774
+ const actualType = inferType(spreadValue);
775
+ throw new RuntimeError('RILL-R001', `Spread requires a tuple, dict, or ordered value, got ${actualType}`, callLocation);
776
+ }
777
+ }
778
+ }
779
+ }
780
+ finally {
781
+ this.ctx.pipeValue = savedPipeValue;
782
+ }
783
+ // EC-8: check for missing required parameters
784
+ for (const param of params) {
785
+ if (!bound.has(param.name)) {
786
+ if (param.defaultValue !== null) {
787
+ bound.set(param.name, param.defaultValue);
788
+ }
789
+ else {
790
+ throw new RuntimeError('RILL-R001', `Missing required parameter '${param.name}'`, callLocation);
791
+ }
792
+ }
793
+ }
794
+ return { params: bound };
795
+ }
560
796
  /**
561
797
  * Evaluate .params property access on closures.
562
798
  * Builds dict from closure parameter metadata.