@rcrsr/rill 0.16.0 → 0.18.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 (97) hide show
  1. package/README.md +37 -21
  2. package/dist/ast-nodes.d.ts +14 -4
  3. package/dist/ast-unions.d.ts +1 -1
  4. package/dist/constants.d.ts +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/error-registry.js +228 -0
  7. package/dist/ext/crypto/index.d.ts +3 -3
  8. package/dist/ext/crypto/index.js +62 -59
  9. package/dist/ext/exec/index.d.ts +3 -3
  10. package/dist/ext/exec/index.js +15 -9
  11. package/dist/ext/fetch/index.d.ts +3 -3
  12. package/dist/ext/fetch/index.js +17 -12
  13. package/dist/ext/fetch/request.js +1 -1
  14. package/dist/ext/fs/index.d.ts +3 -3
  15. package/dist/ext/fs/index.js +256 -266
  16. package/dist/ext/fs/sandbox.d.ts +18 -0
  17. package/dist/ext/fs/sandbox.js +33 -0
  18. package/dist/ext/kv/index.d.ts +3 -3
  19. package/dist/ext/kv/index.js +198 -196
  20. package/dist/ext/kv/store.d.ts +1 -1
  21. package/dist/ext/kv/store.js +2 -1
  22. package/dist/ext-parse-bridge.d.ts +10 -0
  23. package/dist/ext-parse-bridge.js +10 -0
  24. package/dist/generated/introspection-data.d.ts +1 -1
  25. package/dist/generated/introspection-data.js +385 -296
  26. package/dist/generated/version-data.d.ts +1 -1
  27. package/dist/generated/version-data.js +2 -2
  28. package/dist/highlight-map.js +1 -0
  29. package/dist/index.d.ts +1 -4
  30. package/dist/index.js +1 -5
  31. package/dist/lexer/operators.js +1 -0
  32. package/dist/parser/helpers.js +1 -0
  33. package/dist/parser/parser-expr.js +44 -5
  34. package/dist/parser/parser-literals.js +111 -4
  35. package/dist/parser/parser-shape.js +2 -2
  36. package/dist/parser/parser-types.js +12 -0
  37. package/dist/parser/parser-use.js +26 -3
  38. package/dist/parser/parser.d.ts +2 -0
  39. package/dist/parser/parser.js +2 -0
  40. package/dist/runtime/core/callable.d.ts +24 -13
  41. package/dist/runtime/core/callable.js +71 -38
  42. package/dist/runtime/core/context.d.ts +2 -13
  43. package/dist/runtime/core/context.js +80 -79
  44. package/dist/runtime/core/eval/base.d.ts +2 -2
  45. package/dist/runtime/core/eval/base.js +2 -0
  46. package/dist/runtime/core/eval/evaluator.d.ts +1 -1
  47. package/dist/runtime/core/eval/index.d.ts +3 -3
  48. package/dist/runtime/core/eval/index.js +11 -0
  49. package/dist/runtime/core/eval/mixins/closures.js +381 -41
  50. package/dist/runtime/core/eval/mixins/collections.js +81 -6
  51. package/dist/runtime/core/eval/mixins/control-flow.js +1 -1
  52. package/dist/runtime/core/eval/mixins/conversion.js +61 -115
  53. package/dist/runtime/core/eval/mixins/core.js +17 -4
  54. package/dist/runtime/core/eval/mixins/expressions.js +36 -27
  55. package/dist/runtime/core/eval/mixins/extraction.js +2 -3
  56. package/dist/runtime/core/eval/mixins/list-dispatch.js +1 -1
  57. package/dist/runtime/core/eval/mixins/literals.js +17 -6
  58. package/dist/runtime/core/eval/mixins/types.js +73 -54
  59. package/dist/runtime/core/eval/mixins/variables.js +12 -8
  60. package/dist/runtime/core/execute.d.ts +1 -1
  61. package/dist/runtime/core/field-descriptor.d.ts +3 -3
  62. package/dist/runtime/core/field-descriptor.js +2 -1
  63. package/dist/runtime/core/introspection.d.ts +2 -2
  64. package/dist/runtime/core/introspection.js +7 -6
  65. package/dist/runtime/core/resolvers.d.ts +1 -1
  66. package/dist/runtime/core/signals.d.ts +6 -1
  67. package/dist/runtime/core/signals.js +9 -0
  68. package/dist/runtime/core/types/constructors.d.ts +54 -0
  69. package/dist/runtime/core/types/constructors.js +201 -0
  70. package/dist/runtime/core/types/guards.d.ts +42 -0
  71. package/dist/runtime/core/types/guards.js +88 -0
  72. package/dist/runtime/core/types/index.d.ts +18 -0
  73. package/dist/runtime/core/types/index.js +19 -0
  74. package/dist/runtime/core/types/markers.d.ts +12 -0
  75. package/dist/runtime/core/types/markers.js +7 -0
  76. package/dist/runtime/core/types/operations.d.ts +98 -0
  77. package/dist/runtime/core/types/operations.js +804 -0
  78. package/dist/runtime/core/types/registrations.d.ts +126 -0
  79. package/dist/runtime/core/types/registrations.js +751 -0
  80. package/dist/runtime/core/{types.d.ts → types/runtime.d.ts} +22 -10
  81. package/dist/runtime/core/types/structures.d.ts +146 -0
  82. package/dist/runtime/core/types/structures.js +12 -0
  83. package/dist/runtime/core/values.d.ts +29 -209
  84. package/dist/runtime/core/values.js +56 -968
  85. package/dist/runtime/ext/builtins.js +88 -68
  86. package/dist/runtime/ext/extensions.d.ts +31 -125
  87. package/dist/runtime/ext/extensions.js +2 -94
  88. package/dist/runtime/ext/test-context.d.ts +28 -0
  89. package/dist/runtime/ext/test-context.js +155 -0
  90. package/dist/runtime/index.d.ts +12 -12
  91. package/dist/runtime/index.js +13 -5
  92. package/dist/signature-parser.d.ts +2 -2
  93. package/dist/signature-parser.js +14 -14
  94. package/dist/token-types.d.ts +1 -0
  95. package/dist/token-types.js +1 -0
  96. package/package.json +1 -1
  97. /package/dist/runtime/core/{types.js → types/runtime.js} +0 -0
@@ -21,7 +21,8 @@
21
21
  * @internal
22
22
  */
23
23
  import { RuntimeError } from '../../../../types.js';
24
- import { inferType, isRillIterator, isVector } from '../../values.js';
24
+ import { inferType } from '../../types/registrations.js';
25
+ import { isIterator, isStream, isVector } from '../../types/guards.js';
25
26
  import { createChildContext, getVariable } from '../../context.js';
26
27
  import { BreakSignal } from '../../signals.js';
27
28
  import { isCallable, isDict, marshalArgs } from '../../callable.js';
@@ -35,7 +36,7 @@ const DEFAULT_MAX_ITERATIONS = 10000;
35
36
  * CollectionsMixin implementation.
36
37
  *
37
38
  * Evaluates collection operators: each, map, fold, filter.
38
- * Handles iteration over lists, strings, dicts, and iterators.
39
+ * Handles iteration over lists, strings, dicts, iterators, and streams.
39
40
  *
40
41
  * Depends on:
41
42
  * - EvaluatorBase: ctx, checkAborted(), getNodeLocation()
@@ -55,17 +56,18 @@ const DEFAULT_MAX_ITERATIONS = 10000;
55
56
  * - getIterableElements(input) -> Promise<RillValue[]> (helper)
56
57
  * - evaluateIteratorBody(body, element, accumulator) -> Promise<RillValue> (helper)
57
58
  * - expandIterator(iterator, limit?) -> Promise<RillValue[]> (helper)
59
+ * - expandStream(stream, node, limit?) -> Promise<RillValue[]> (helper)
58
60
  */
59
61
  function createCollectionsMixin(Base) {
60
62
  return class CollectionsEvaluator extends Base {
61
63
  /**
62
- * Get elements from an iterable value (list, string, dict, or iterator).
64
+ * Get elements from an iterable value (list, string, dict, iterator, or stream).
63
65
  * Throws RuntimeError if value is not iterable.
64
66
  */
65
67
  async getIterableElements(input, node) {
66
68
  // Vector guard [EC-32]
67
69
  if (isVector(input)) {
68
- throw new RuntimeError('RILL-R003', 'Collection operators require list, string, dict, or iterator, got vector', node.span.start);
70
+ throw new RuntimeError('RILL-R003', 'Collection operators require list, string, dict, iterator, or stream, got vector', node.span.start);
69
71
  }
70
72
  if (Array.isArray(input)) {
71
73
  return input;
@@ -73,8 +75,12 @@ function createCollectionsMixin(Base) {
73
75
  if (typeof input === 'string') {
74
76
  return [...input];
75
77
  }
78
+ // Check for stream BEFORE iterator (streams satisfy iterator shape)
79
+ if (isStream(input)) {
80
+ return this.expandStream(input, node);
81
+ }
76
82
  // Check for iterator protocol BEFORE generic dict handling
77
- if (isRillIterator(input)) {
83
+ if (isIterator(input)) {
78
84
  return this.expandIterator(input, node);
79
85
  }
80
86
  if (isDict(input)) {
@@ -85,7 +91,7 @@ function createCollectionsMixin(Base) {
85
91
  value: input[key],
86
92
  }));
87
93
  }
88
- throw new RuntimeError('RILL-R002', `Collection operators require list, string, dict, or iterator, got ${inferType(input)}`, node.span.start);
94
+ throw new RuntimeError('RILL-R002', `Collection operators require list, string, dict, iterator, or stream, got ${inferType(input)}`, node.span.start);
89
95
  }
90
96
  /**
91
97
  * Expand an iterator to a list of values.
@@ -119,6 +125,75 @@ function createCollectionsMixin(Base) {
119
125
  }
120
126
  return elements;
121
127
  }
128
+ /**
129
+ * Expand a stream to a list of chunk values.
130
+ * Consumes async chunks by repeatedly calling the stream's next callable.
131
+ * Respects iteration limits to prevent unbounded expansion.
132
+ *
133
+ * On BreakSignal, calls the stream's dispose callable (if present)
134
+ * before re-throwing (NFR-STREAM-2).
135
+ */
136
+ async expandStream(stream, node, limit = DEFAULT_MAX_ITERATIONS) {
137
+ const elements = [];
138
+ let current = stream;
139
+ let count = 0;
140
+ let expectedType;
141
+ try {
142
+ while (!current.done && count < limit) {
143
+ this.checkAborted();
144
+ const val = current['value'];
145
+ if (val !== undefined) {
146
+ const actualType = inferType(val);
147
+ if (expectedType === undefined) {
148
+ expectedType = actualType;
149
+ }
150
+ else if (actualType !== expectedType) {
151
+ throw new RuntimeError('RILL-R004', `Stream chunk type mismatch: expected ${expectedType}, got ${actualType}`, node.span.start);
152
+ }
153
+ elements.push(val);
154
+ }
155
+ count++;
156
+ // Invoke next() to advance the stream
157
+ const nextClosure = current['next'];
158
+ if (nextClosure === undefined || !isCallable(nextClosure)) {
159
+ throw new RuntimeError('RILL-R002', 'Stream .next must be a closure', node.span.start);
160
+ }
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ const nextStep = await this.invokeCallable(nextClosure, [], this.ctx, node.span.start);
163
+ if (typeof nextStep !== 'object' || nextStep === null) {
164
+ throw new RuntimeError('RILL-R002', 'Stream .next must return a stream step', node.span.start);
165
+ }
166
+ current = nextStep;
167
+ }
168
+ }
169
+ catch (e) {
170
+ if (e instanceof BreakSignal) {
171
+ // Dispose stream resources before re-throwing (IR-14)
172
+ // Access __rill_stream_dispose on the original stream object
173
+ // (step objects don't carry dispose; it lives on the root stream)
174
+ const disposeFn = stream['__rill_stream_dispose'];
175
+ if (typeof disposeFn === 'function') {
176
+ try {
177
+ disposeFn();
178
+ }
179
+ catch (disposeErr) {
180
+ // Propagate dispose errors as RILL-R002 (EC-15)
181
+ if (disposeErr instanceof RuntimeError)
182
+ throw disposeErr;
183
+ throw new RuntimeError('RILL-R002', disposeErr instanceof Error
184
+ ? disposeErr.message
185
+ : String(disposeErr), node.span.start);
186
+ }
187
+ }
188
+ throw e;
189
+ }
190
+ throw e;
191
+ }
192
+ if (count >= limit) {
193
+ throw new RuntimeError('RILL-R010', `Stream expansion exceeded ${limit} iterations`, node.span.start, { limit, iterations: count });
194
+ }
195
+ return elements;
196
+ }
122
197
  /**
123
198
  * Evaluate collection body for a single element.
124
199
  * Handles all body forms: closure, block, grouped, variable, postfix, spread.
@@ -24,7 +24,7 @@
24
24
  * @internal
25
25
  */
26
26
  import { RuntimeError } from '../../../../types.js';
27
- import { inferType } from '../../values.js';
27
+ import { inferType } from '../../types/registrations.js';
28
28
  import { createChildContext } from '../../context.js';
29
29
  import { BreakSignal, ReturnSignal } from '../../signals.js';
30
30
  /**
@@ -27,8 +27,12 @@
27
27
  * @internal
28
28
  */
29
29
  import { RuntimeError } from '../../../../types.js';
30
- import { inferType, isTuple, isOrdered, isTypeValue, createOrdered, createTuple, formatValue, deepCopyRillValue, hasCollectionFields, emptyForType, } from '../../values.js';
30
+ import { inferType } from '../../types/registrations.js';
31
+ import { isTuple, isOrdered, isTypeValue } from '../../types/guards.js';
32
+ import { createOrdered, createTuple, copyValue, emptyForType, } from '../../types/constructors.js';
33
+ import { hasCollectionFields } from '../../values.js';
31
34
  import { isDict } from '../../callable.js';
35
+ import { BUILT_IN_TYPES } from '../../types/registrations.js';
32
36
  import { getVariable } from '../../context.js';
33
37
  /**
34
38
  * ConversionMixin implementation.
@@ -106,7 +110,15 @@ function createConversionMixin(Base) {
106
110
  }
107
111
  /**
108
112
  * Apply conversion from source value to target type name.
109
- * Implements the compatibility matrix from IR-9.
113
+ * Dispatches to protocol.convertTo on the source type's registration.
114
+ *
115
+ * IR-6: Replaces the hardcoded conversion matrix with protocol dispatch.
116
+ *
117
+ * Special cases preserved:
118
+ * - Same type = no-op (short-circuit)
119
+ * - dict -> :>ordered without structural sig raises RILL-R037 (EC-11)
120
+ * - String-to-number parse failure raises RILL-R038 (EC-12)
121
+ * - Missing convertTo target raises RILL-R036 (EC-10)
110
122
  */
111
123
  applyConversion(input, targetType, node) {
112
124
  const sourceType = inferType(input);
@@ -114,105 +126,36 @@ function createConversionMixin(Base) {
114
126
  if (sourceType === targetType) {
115
127
  return input;
116
128
  }
117
- // Apply compatibility matrix
118
- switch (targetType) {
119
- case 'list':
120
- return this.convertToList(input, sourceType, node);
121
- case 'dict':
122
- return this.convertToDict(input, sourceType, node);
123
- case 'tuple':
124
- return this.convertToTuple(input, sourceType, node);
125
- case 'ordered':
126
- // dict -> :>ordered without structural sig is always a runtime error (EC-11)
127
- if (sourceType === 'dict') {
128
- throw new RuntimeError('RILL-R037', 'dict to ordered conversion requires structural type signature', this.getNodeLocation(node));
129
- }
130
- return this.convertToOrdered(input, sourceType, node);
131
- case 'number':
132
- return this.convertToNumber(input, sourceType, node);
133
- case 'string':
134
- return this.convertToString(input, sourceType, node);
135
- case 'bool':
136
- return this.convertToBoolean(input, sourceType, node);
137
- default:
138
- this.throwIncompatible(sourceType, targetType, node);
129
+ // IR-11: :>stream is not supported — stream type cannot be a conversion target
130
+ if (targetType === 'stream') {
131
+ throw new RuntimeError('RILL-R003', 'Type conversion not supported for stream type', this.getNodeLocation(node));
139
132
  }
140
- // TypeScript exhaustiveness
141
- return input;
142
- }
143
- /** Convert to list type. Valid source: tuple. */
144
- convertToList(input, sourceType, node) {
145
- if (isTuple(input)) {
146
- return input.entries;
133
+ // dict -> :>ordered without structural sig is always RILL-R037 (EC-11)
134
+ if (sourceType === 'dict' && targetType === 'ordered') {
135
+ throw new RuntimeError('RILL-R037', 'dict to ordered conversion requires structural type signature', this.getNodeLocation(node));
147
136
  }
148
- this.throwIncompatible(sourceType, 'list', node);
149
- return input;
150
- }
151
- /** Convert to dict type. Valid source: ordered. */
152
- convertToDict(input, sourceType, node) {
153
- if (isOrdered(input)) {
154
- const result = {};
155
- for (const [key, value] of input.entries) {
156
- result[key] = value;
157
- }
158
- return result;
137
+ // Find source type registration and dispatch via protocol.convertTo
138
+ const reg = BUILT_IN_TYPES.find((r) => r.name === sourceType);
139
+ const converter = reg?.protocol.convertTo?.[targetType];
140
+ if (!converter) {
141
+ throw new RuntimeError('RILL-R036', `cannot convert ${sourceType} to ${targetType}`, this.getNodeLocation(node), { source: sourceType, target: targetType });
159
142
  }
160
- this.throwIncompatible(sourceType, 'dict', node);
161
- return input;
162
- }
163
- /** Convert to tuple type. Valid source: list. */
164
- convertToTuple(input, sourceType, node) {
165
- if (Array.isArray(input) && !isTuple(input) && !isOrdered(input)) {
166
- return createTuple(input);
143
+ try {
144
+ return converter(input);
167
145
  }
168
- this.throwIncompatible(sourceType, 'tuple', node);
169
- return input;
170
- }
171
- /** Convert to ordered type. Valid source: dict (with sig, handled separately). */
172
- convertToOrdered(input, sourceType, node) {
173
- // Only dict -> ordered is valid, but it requires a sig (checked by caller)
174
- this.throwIncompatible(sourceType, 'ordered', node);
175
- return input;
176
- }
177
- /** Convert to number type. Valid source: string (parseable) or bool. */
178
- convertToNumber(input, sourceType, node) {
179
- if (sourceType === 'string') {
180
- const str = input;
181
- const parsed = Number(str);
182
- if (isNaN(parsed) || str.trim() === '') {
183
- throw new RuntimeError('RILL-R038', `cannot convert string "${str}" to number`, this.getNodeLocation(node), { value: str });
146
+ catch (err) {
147
+ // Protocol converters throw RuntimeError (RILL-R064/R065/R066);
148
+ // wrap with evaluator-level error codes for user-facing messages.
149
+ // String-to-number parse failures use RILL-R038 (EC-12)
150
+ // Preserve the protocol's detailed message (includes unparseable value).
151
+ if (sourceType === 'string' && targetType === 'number') {
152
+ const message = err instanceof Error ? err.message : String(err);
153
+ throw new RuntimeError('RILL-R038', message, this.getNodeLocation(node), { value: input });
184
154
  }
185
- return parsed;
155
+ // All other conversion failures use RILL-R036 (EC-10)
156
+ // Use consistent "cannot convert X to Y" format.
157
+ throw new RuntimeError('RILL-R036', `cannot convert ${sourceType} to ${targetType}`, this.getNodeLocation(node), { source: sourceType, target: targetType });
186
158
  }
187
- if (sourceType === 'bool') {
188
- return input ? 1 : 0;
189
- }
190
- this.throwIncompatible(sourceType, 'number', node);
191
- return input;
192
- }
193
- /** Convert to bool type. Valid source: number (0 or 1) or string ("true" or "false"). */
194
- convertToBoolean(input, sourceType, node) {
195
- if (sourceType === 'number') {
196
- const n = input;
197
- if (n === 0)
198
- return false;
199
- if (n === 1)
200
- return true;
201
- this.throwIncompatible(sourceType, 'bool', node);
202
- }
203
- if (sourceType === 'string') {
204
- const s = input;
205
- if (s === 'true')
206
- return true;
207
- if (s === 'false')
208
- return false;
209
- this.throwIncompatible(sourceType, 'bool', node);
210
- }
211
- this.throwIncompatible(sourceType, 'bool', node);
212
- }
213
- /** Convert to string type. Valid source: any type via formatValue semantics. */
214
- convertToString(input, _sourceType, _node) {
215
- return formatValue(input);
216
159
  }
217
160
  /**
218
161
  * Convert dict -> :>ordered(field: type = default, ...) using structural signature.
@@ -238,7 +181,7 @@ function createConversionMixin(Base) {
238
181
  // Evaluate the full type constructor to get resolved fields with defaults.
239
182
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
183
  const typeValue = await this.evaluateTypeConstructor(sigNode);
241
- const resolvedFields = typeValue.structure.type === 'ordered' && typeValue.structure.fields
184
+ const resolvedFields = typeValue.structure.kind === 'ordered' && typeValue.structure.fields
242
185
  ? typeValue.structure.fields
243
186
  : [];
244
187
  const entries = [];
@@ -252,7 +195,7 @@ function createConversionMixin(Base) {
252
195
  else if (field.defaultValue !== undefined) {
253
196
  entries.push([
254
197
  fieldName,
255
- this.hydrateNested(deepCopyRillValue(field.defaultValue), field.type, node),
198
+ this.hydrateNested(copyValue(field.defaultValue), field.type, node),
256
199
  ]);
257
200
  }
258
201
  else if (hasCollectionFields(field.type)) {
@@ -292,7 +235,7 @@ function createConversionMixin(Base) {
292
235
  // Evaluate the full type constructor to get resolved fields with defaults.
293
236
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
237
  const typeValue = await this.evaluateTypeConstructor(sigNode);
295
- const resolvedFields = typeValue.structure.type === 'dict' && typeValue.structure.fields
238
+ const resolvedFields = typeValue.structure.kind === 'dict' && typeValue.structure.fields
296
239
  ? typeValue.structure.fields
297
240
  : {};
298
241
  const result = {};
@@ -314,7 +257,7 @@ function createConversionMixin(Base) {
314
257
  // Field missing from input: use default if available, else error
315
258
  if (resolvedField !== undefined &&
316
259
  resolvedField.defaultValue !== undefined) {
317
- result[fieldName] = this.hydrateNested(deepCopyRillValue(resolvedField.defaultValue), resolvedField.type, node);
260
+ result[fieldName] = this.hydrateNested(copyValue(resolvedField.defaultValue), resolvedField.type, node);
318
261
  }
319
262
  else if (resolvedField !== undefined &&
320
263
  hasCollectionFields(resolvedField.type)) {
@@ -345,7 +288,7 @@ function createConversionMixin(Base) {
345
288
  // Evaluate the full type constructor to get resolved elements with defaults.
346
289
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
290
  const typeValue = await this.evaluateTypeConstructor(sigNode);
348
- const resolvedElements = typeValue.structure.type === 'tuple' && typeValue.structure.elements
291
+ const resolvedElements = typeValue.structure.kind === 'tuple' && typeValue.structure.elements
349
292
  ? typeValue.structure.elements
350
293
  : [];
351
294
  const inputEntries = isTupleInput
@@ -360,7 +303,7 @@ function createConversionMixin(Base) {
360
303
  }
361
304
  else if (element.defaultValue !== undefined) {
362
305
  // Missing trailing element with default: deep copy and hydrate
363
- result.push(this.hydrateNested(deepCopyRillValue(element.defaultValue), element.type, node));
306
+ result.push(this.hydrateNested(copyValue(element.defaultValue), element.type, node));
364
307
  }
365
308
  else if (hasCollectionFields(element.type)) {
366
309
  // Missing element with collection type: seed empty and hydrate
@@ -379,17 +322,20 @@ function createConversionMixin(Base) {
379
322
  * Returns the value unchanged if the type has no fields or the value type does not match.
380
323
  */
381
324
  hydrateNested(value, fieldType, node) {
382
- if (fieldType.type === 'dict' && fieldType.fields && isDict(value)) {
325
+ if (fieldType.kind === 'dict' &&
326
+ fieldType.fields &&
327
+ isDict(value)) {
328
+ const ft = fieldType;
383
329
  const dictValue = value;
384
330
  const result = {};
385
- for (const [fieldName, resolvedField] of Object.entries(fieldType.fields)) {
331
+ for (const [fieldName, resolvedField] of Object.entries(ft.fields)) {
386
332
  if (fieldName in dictValue) {
387
333
  const fieldValue = this.hydrateNested(dictValue[fieldName], resolvedField.type, node);
388
334
  result[fieldName] = fieldValue;
389
335
  }
390
336
  else {
391
337
  if (resolvedField.defaultValue !== undefined) {
392
- result[fieldName] = this.hydrateNested(deepCopyRillValue(resolvedField.defaultValue), resolvedField.type, node);
338
+ result[fieldName] = this.hydrateNested(copyValue(resolvedField.defaultValue), resolvedField.type, node);
393
339
  }
394
340
  else if (hasCollectionFields(resolvedField.type)) {
395
341
  result[fieldName] = this.hydrateNested(emptyForType(resolvedField.type), resolvedField.type, node);
@@ -401,7 +347,9 @@ function createConversionMixin(Base) {
401
347
  }
402
348
  return result;
403
349
  }
404
- else if (fieldType.type === 'ordered' && fieldType.fields) {
350
+ else if (fieldType.kind === 'ordered' &&
351
+ fieldType.fields) {
352
+ const ft = fieldType;
405
353
  // Only hydrate if the runtime value is an ordered or dict; return unchanged otherwise.
406
354
  if (!isOrdered(value) && !isDict(value)) {
407
355
  return value;
@@ -412,7 +360,7 @@ function createConversionMixin(Base) {
412
360
  ? value.entries.map(([k, v]) => [k, v])
413
361
  : Object.entries(value));
414
362
  const resultEntries = [];
415
- for (const field of fieldType.fields) {
363
+ for (const field of ft.fields) {
416
364
  const name = field.name;
417
365
  if (lookup.has(name)) {
418
366
  const fieldValue = this.hydrateNested(lookup.get(name), field.type, node);
@@ -421,7 +369,7 @@ function createConversionMixin(Base) {
421
369
  else if (field.defaultValue !== undefined) {
422
370
  resultEntries.push([
423
371
  name,
424
- this.hydrateNested(deepCopyRillValue(field.defaultValue), field.type, node),
372
+ this.hydrateNested(copyValue(field.defaultValue), field.type, node),
425
373
  ]);
426
374
  }
427
375
  else if (hasCollectionFields(field.type)) {
@@ -436,21 +384,23 @@ function createConversionMixin(Base) {
436
384
  }
437
385
  return createOrdered(resultEntries);
438
386
  }
439
- else if (fieldType.type === 'tuple' && fieldType.elements) {
387
+ else if (fieldType.kind === 'tuple' &&
388
+ fieldType.elements) {
389
+ const ft = fieldType;
440
390
  // Only hydrate if the runtime value is a tuple; return unchanged otherwise.
441
391
  if (!isTuple(value)) {
442
392
  return value;
443
393
  }
444
394
  const inputEntries = value.entries;
445
395
  const resultEntries = [];
446
- for (let i = 0; i < fieldType.elements.length; i++) {
447
- const element = fieldType.elements[i];
396
+ for (let i = 0; i < ft.elements.length; i++) {
397
+ const element = ft.elements[i];
448
398
  if (i < inputEntries.length) {
449
399
  const elementValue = this.hydrateNested(inputEntries[i], element.type, node);
450
400
  resultEntries.push(elementValue);
451
401
  }
452
402
  else if (element.defaultValue !== undefined) {
453
- resultEntries.push(this.hydrateNested(deepCopyRillValue(element.defaultValue), element.type, node));
403
+ resultEntries.push(this.hydrateNested(copyValue(element.defaultValue), element.type, node));
454
404
  }
455
405
  else if (hasCollectionFields(element.type)) {
456
406
  resultEntries.push(this.hydrateNested(emptyForType(element.type), element.type, node));
@@ -463,10 +413,6 @@ function createConversionMixin(Base) {
463
413
  }
464
414
  return value;
465
415
  }
466
- /** Throw EC-10 incompatible conversion error. */
467
- throwIncompatible(source, target, node) {
468
- throw new RuntimeError('RILL-R036', `cannot convert ${source} to ${target}`, this.getNodeLocation(node), { source, target });
469
- }
470
416
  };
471
417
  }
472
418
  /**
@@ -21,7 +21,7 @@
21
21
  * @internal
22
22
  */
23
23
  import { RuntimeError } from '../../../../types.js';
24
- import { isTuple } from '../../values.js';
24
+ import { isTuple } from '../../types/guards.js';
25
25
  import { isCallable, isDict, isScriptCallable } from '../../callable.js';
26
26
  import { BreakSignal, ReturnSignal } from '../../signals.js';
27
27
  /**
@@ -102,7 +102,7 @@ function createCoreMixin(Base) {
102
102
  value = await this.evaluatePipeTarget(target, value);
103
103
  this.ctx.pipeValue = value; // OK: flows within chain
104
104
  }
105
- // Handle chain terminator (capture, break, return)
105
+ // Handle chain terminator (capture, break, return, yield)
106
106
  if (chain.terminator) {
107
107
  if (chain.terminator.type === 'Break') {
108
108
  // Restore parent's $ before throwing (cleanup)
@@ -114,6 +114,19 @@ function createCoreMixin(Base) {
114
114
  this.ctx.pipeValue = savedPipeValue;
115
115
  throw new ReturnSignal(value);
116
116
  }
117
+ if (chain.terminator.type === 'Yield') {
118
+ // Restore parent's $ before throwing (cleanup)
119
+ this.ctx.pipeValue = savedPipeValue;
120
+ // Delegate to evaluateYield for chunk type validation + YieldSignal.
121
+ // When inside a stream closure body, evaluateYield pushes to the
122
+ // channel and blocks until the consumer pulls (returns Promise<void>).
123
+ // When outside, it throws YieldSignal synchronously.
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ await this.evaluateYield(value, chain.terminator.span.start);
126
+ // After yield resumes (stream channel case), restore pipe value
127
+ // and return the yielded value as the chain result
128
+ return value;
129
+ }
117
130
  // Capture
118
131
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
132
  await this.handleCapture(chain.terminator, value);
@@ -282,8 +295,8 @@ function createCoreMixin(Base) {
282
295
  primary.typeName === 'ordered' ||
283
296
  primary.typeName === 'vector' ||
284
297
  primary.typeName === 'type'
285
- ? { type: primary.typeName }
286
- : { type: 'any' },
298
+ ? { kind: primary.typeName }
299
+ : { kind: 'any' },
287
300
  });
288
301
  case 'TypeConstructor':
289
302
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -18,9 +18,18 @@
18
18
  * @internal
19
19
  */
20
20
  import { RuntimeError } from '../../../../types.js';
21
- import { inferType, isTruthy, deepEquals } from '../../values.js';
21
+ import { inferType } from '../../types/registrations.js';
22
+ import { isTruthy } from '../../values.js';
23
+ import { BUILT_IN_TYPES } from '../../types/registrations.js';
22
24
  import { createChildContext } from '../../context.js';
23
25
  import { isCallable } from '../../callable.js';
26
+ /**
27
+ * Find the type registration for a value by type name.
28
+ * Returns undefined when no registration matches.
29
+ */
30
+ function findRegistration(typeName) {
31
+ return BUILT_IN_TYPES.find((r) => r.name === typeName);
32
+ }
24
33
  /**
25
34
  * ExpressionsMixin implementation.
26
35
  *
@@ -150,39 +159,39 @@ function createExpressionsMixin(Base) {
150
159
  }
151
160
  }
152
161
  /**
153
- * Evaluate comparison between two values.
154
- * Equality works on all types, ordering requires compatible types.
162
+ * Evaluate comparison between two values via protocol dispatch.
163
+ *
164
+ * - == / != dispatch to protocol.eq; absent eq raises RILL-R002.
165
+ * - Ordering ops dispatch to protocol.compare; absent compare raises RILL-R002.
166
+ *
167
+ * IR-5: Breaking change: bool ordering (e.g. true > false) raises RILL-R002
168
+ * because the bool registration has no protocol.compare.
155
169
  */
156
170
  evaluateBinaryComparison(left, right, op, node) {
171
+ const typeName = inferType(left);
172
+ const reg = findRegistration(typeName);
173
+ if (op === '==' || op === '!=') {
174
+ if (!reg || !reg.protocol.eq) {
175
+ throw new RuntimeError('RILL-R002', `Cannot compare ${typeName} using ${op}`, node.span.start);
176
+ }
177
+ const eqResult = reg.protocol.eq(left, right);
178
+ return op === '==' ? eqResult : !eqResult;
179
+ }
180
+ // Ordering ops: <, >, <=, >=
181
+ const rightTypeName = inferType(right);
182
+ if (!reg || !reg.protocol.compare || typeName !== rightTypeName) {
183
+ throw new RuntimeError('RILL-R002', `Cannot compare ${typeName} with ${rightTypeName} using ${op}`, node.span.start);
184
+ }
185
+ const cmp = reg.protocol.compare(left, right);
157
186
  switch (op) {
158
- case '==':
159
- return deepEquals(left, right);
160
- case '!=':
161
- return !deepEquals(left, right);
162
187
  case '<':
188
+ return cmp < 0;
163
189
  case '>':
190
+ return cmp > 0;
164
191
  case '<=':
192
+ return cmp <= 0;
165
193
  case '>=':
166
- // Ordering comparisons require compatible types
167
- if (typeof left === 'number' && typeof right === 'number') {
168
- return op === '<'
169
- ? left < right
170
- : op === '>'
171
- ? left > right
172
- : op === '<='
173
- ? left <= right
174
- : left >= right;
175
- }
176
- if (typeof left === 'string' && typeof right === 'string') {
177
- return op === '<'
178
- ? left < right
179
- : op === '>'
180
- ? left > right
181
- : op === '<='
182
- ? left <= right
183
- : left >= right;
184
- }
185
- throw new RuntimeError('RILL-R002', `Cannot compare ${inferType(left)} with ${inferType(right)} using ${op}`, node.span.start);
194
+ return cmp >= 0;
186
195
  }
187
196
  }
188
197
  /**
@@ -7,7 +7,8 @@
7
7
  * @internal
8
8
  */
9
9
  import { RuntimeError } from '../../../../types.js';
10
- import { createOrdered, createTuple, inferElementType } from '../../values.js';
10
+ import { createOrdered, createTuple } from '../../types/constructors.js';
11
+ import { inferElementType } from '../../types/operations.js';
11
12
  import { isDict } from '../../callable.js';
12
13
  import { getVariable } from '../../context.js';
13
14
  /**
@@ -77,7 +78,6 @@ function createExtractionMixin(Base) {
77
78
  }
78
79
  // Note: setVariable and resolveTypeRef will be available from VariablesMixin
79
80
  // and TypesMixin which are applied before ExtractionMixin in the composition order
80
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
81
  const dictResolved = elem.typeRef !== null
82
82
  ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
83
  await this.resolveTypeRef(elem.typeRef, (name) => getVariable(this.ctx, name))
@@ -112,7 +112,6 @@ function createExtractionMixin(Base) {
112
112
  }
113
113
  // Note: setVariable and resolveTypeRef will be available from VariablesMixin
114
114
  // and TypesMixin which are applied before ExtractionMixin in the composition order
115
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
115
  const listResolved = elem.typeRef !== null
117
116
  ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
117
  await this.resolveTypeRef(elem.typeRef, (name) => getVariable(this.ctx, name))
@@ -14,7 +14,7 @@
14
14
  * @internal
15
15
  */
16
16
  import { RuntimeError } from '../../../../types.js';
17
- import { inferElementType } from '../../values.js';
17
+ import { inferElementType } from '../../types/operations.js';
18
18
  /**
19
19
  * ListDispatchMixin implementation.
20
20
  *