@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.
- package/README.md +37 -21
- package/dist/ast-nodes.d.ts +14 -4
- package/dist/ast-unions.d.ts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -0
- package/dist/error-registry.js +228 -0
- package/dist/ext/crypto/index.d.ts +3 -3
- package/dist/ext/crypto/index.js +62 -59
- package/dist/ext/exec/index.d.ts +3 -3
- package/dist/ext/exec/index.js +15 -9
- package/dist/ext/fetch/index.d.ts +3 -3
- package/dist/ext/fetch/index.js +17 -12
- package/dist/ext/fetch/request.js +1 -1
- package/dist/ext/fs/index.d.ts +3 -3
- package/dist/ext/fs/index.js +256 -266
- package/dist/ext/fs/sandbox.d.ts +18 -0
- package/dist/ext/fs/sandbox.js +33 -0
- package/dist/ext/kv/index.d.ts +3 -3
- package/dist/ext/kv/index.js +198 -196
- package/dist/ext/kv/store.d.ts +1 -1
- package/dist/ext/kv/store.js +2 -1
- package/dist/ext-parse-bridge.d.ts +10 -0
- package/dist/ext-parse-bridge.js +10 -0
- package/dist/generated/introspection-data.d.ts +1 -1
- package/dist/generated/introspection-data.js +385 -296
- package/dist/generated/version-data.d.ts +1 -1
- package/dist/generated/version-data.js +2 -2
- package/dist/highlight-map.js +1 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -5
- package/dist/lexer/operators.js +1 -0
- package/dist/parser/helpers.js +1 -0
- package/dist/parser/parser-expr.js +44 -5
- package/dist/parser/parser-literals.js +111 -4
- package/dist/parser/parser-shape.js +2 -2
- package/dist/parser/parser-types.js +12 -0
- package/dist/parser/parser-use.js +26 -3
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +2 -0
- package/dist/runtime/core/callable.d.ts +24 -13
- package/dist/runtime/core/callable.js +71 -38
- package/dist/runtime/core/context.d.ts +2 -13
- package/dist/runtime/core/context.js +80 -79
- package/dist/runtime/core/eval/base.d.ts +2 -2
- package/dist/runtime/core/eval/base.js +2 -0
- package/dist/runtime/core/eval/evaluator.d.ts +1 -1
- package/dist/runtime/core/eval/index.d.ts +3 -3
- package/dist/runtime/core/eval/index.js +11 -0
- package/dist/runtime/core/eval/mixins/closures.js +381 -41
- package/dist/runtime/core/eval/mixins/collections.js +81 -6
- package/dist/runtime/core/eval/mixins/control-flow.js +1 -1
- package/dist/runtime/core/eval/mixins/conversion.js +61 -115
- package/dist/runtime/core/eval/mixins/core.js +17 -4
- package/dist/runtime/core/eval/mixins/expressions.js +36 -27
- package/dist/runtime/core/eval/mixins/extraction.js +2 -3
- package/dist/runtime/core/eval/mixins/list-dispatch.js +1 -1
- package/dist/runtime/core/eval/mixins/literals.js +17 -6
- package/dist/runtime/core/eval/mixins/types.js +73 -54
- package/dist/runtime/core/eval/mixins/variables.js +12 -8
- package/dist/runtime/core/execute.d.ts +1 -1
- package/dist/runtime/core/field-descriptor.d.ts +3 -3
- package/dist/runtime/core/field-descriptor.js +2 -1
- package/dist/runtime/core/introspection.d.ts +2 -2
- package/dist/runtime/core/introspection.js +7 -6
- package/dist/runtime/core/resolvers.d.ts +1 -1
- package/dist/runtime/core/signals.d.ts +6 -1
- package/dist/runtime/core/signals.js +9 -0
- package/dist/runtime/core/types/constructors.d.ts +54 -0
- package/dist/runtime/core/types/constructors.js +201 -0
- package/dist/runtime/core/types/guards.d.ts +42 -0
- package/dist/runtime/core/types/guards.js +88 -0
- package/dist/runtime/core/types/index.d.ts +18 -0
- package/dist/runtime/core/types/index.js +19 -0
- package/dist/runtime/core/types/markers.d.ts +12 -0
- package/dist/runtime/core/types/markers.js +7 -0
- package/dist/runtime/core/types/operations.d.ts +98 -0
- package/dist/runtime/core/types/operations.js +804 -0
- package/dist/runtime/core/types/registrations.d.ts +126 -0
- package/dist/runtime/core/types/registrations.js +751 -0
- package/dist/runtime/core/{types.d.ts → types/runtime.d.ts} +22 -10
- package/dist/runtime/core/types/structures.d.ts +146 -0
- package/dist/runtime/core/types/structures.js +12 -0
- package/dist/runtime/core/values.d.ts +29 -209
- package/dist/runtime/core/values.js +56 -968
- package/dist/runtime/ext/builtins.js +88 -68
- package/dist/runtime/ext/extensions.d.ts +31 -125
- package/dist/runtime/ext/extensions.js +2 -94
- package/dist/runtime/ext/test-context.d.ts +28 -0
- package/dist/runtime/ext/test-context.js +155 -0
- package/dist/runtime/index.d.ts +12 -12
- package/dist/runtime/index.js +13 -5
- package/dist/signature-parser.d.ts +2 -2
- package/dist/signature-parser.js +14 -14
- package/dist/token-types.d.ts +1 -0
- package/dist/token-types.js +1 -0
- package/package.json +1 -1
- /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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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 '../../
|
|
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
|
|
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
|
-
*
|
|
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
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
//
|
|
141
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
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(
|
|
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.
|
|
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 <
|
|
447
|
-
const element =
|
|
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(
|
|
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 '../../
|
|
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
|
-
? {
|
|
286
|
-
: {
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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))
|