@rcrsr/rill 0.16.0 → 0.17.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/ext/crypto/index.d.ts +3 -3
- package/dist/ext/crypto/index.js +61 -58
- package/dist/ext/exec/index.d.ts +3 -3
- package/dist/ext/exec/index.js +14 -8
- package/dist/ext/fetch/index.d.ts +3 -3
- package/dist/ext/fetch/index.js +16 -11
- package/dist/ext/fs/index.d.ts +3 -3
- package/dist/ext/fs/index.js +242 -239
- package/dist/ext/kv/index.d.ts +3 -3
- package/dist/ext/kv/index.js +197 -195
- 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/index.d.ts +15 -4
- package/dist/index.js +14 -5
- package/dist/parser/parser-types.js +12 -0
- package/dist/parser/parser-use.js +7 -1
- package/dist/runtime/core/callable.d.ts +20 -8
- package/dist/runtime/core/callable.js +63 -23
- package/dist/runtime/core/context.d.ts +0 -11
- package/dist/runtime/core/context.js +76 -75
- package/dist/runtime/core/eval/index.d.ts +2 -2
- package/dist/runtime/core/eval/index.js +11 -0
- package/dist/runtime/core/eval/mixins/closures.js +15 -15
- package/dist/runtime/core/eval/mixins/conversion.js +51 -110
- package/dist/runtime/core/eval/mixins/core.js +2 -2
- package/dist/runtime/core/eval/mixins/expressions.js +35 -27
- package/dist/runtime/core/eval/mixins/literals.js +3 -3
- package/dist/runtime/core/eval/mixins/types.js +44 -54
- package/dist/runtime/core/eval/mixins/variables.js +10 -8
- package/dist/runtime/core/field-descriptor.d.ts +3 -3
- package/dist/runtime/core/field-descriptor.js +2 -1
- package/dist/runtime/core/introspection.js +6 -6
- package/dist/runtime/core/markers.d.ts +12 -0
- package/dist/runtime/core/markers.js +7 -0
- package/dist/runtime/core/type-registrations.d.ts +136 -0
- package/dist/runtime/core/type-registrations.js +749 -0
- package/dist/runtime/core/type-structures.d.ts +128 -0
- package/dist/runtime/core/type-structures.js +12 -0
- package/dist/runtime/core/types.d.ts +15 -3
- package/dist/runtime/core/values.d.ts +62 -153
- package/dist/runtime/core/values.js +308 -524
- package/dist/runtime/ext/builtins.js +83 -64
- package/dist/runtime/ext/extensions.d.ts +30 -124
- package/dist/runtime/ext/extensions.js +0 -93
- package/dist/runtime/ext/test-context.d.ts +28 -0
- package/dist/runtime/ext/test-context.js +154 -0
- package/dist/runtime/index.d.ts +22 -8
- package/dist/runtime/index.js +18 -4
- package/dist/signature-parser.d.ts +2 -2
- package/dist/signature-parser.js +14 -14
- package/package.json +1 -1
|
@@ -3,10 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Core value types that flow through Rill programs.
|
|
5
5
|
* Public API for host applications.
|
|
6
|
+
*
|
|
7
|
+
* Dispatch functions (inferType, formatValue, deepEquals, serializeValue,
|
|
8
|
+
* copyValue) re-export from type-registrations.ts protocol implementations.
|
|
6
9
|
*/
|
|
7
10
|
import { RuntimeError } from '../../types.js';
|
|
8
11
|
import { VALID_TYPE_NAMES } from '../../constants.js';
|
|
9
|
-
import {
|
|
12
|
+
import { isCallable, isDict } from './callable.js';
|
|
13
|
+
import { inferType as registryInferType, formatValue as registryFormatValue, deepEquals as registryDeepEquals, serializeValue as registrySerializeValue, copyValue as registryCopyValue, } from './type-registrations.js';
|
|
14
|
+
/**
|
|
15
|
+
* Normalize a TypeStructure that may use the legacy `.type` discriminator.
|
|
16
|
+
* During migration, some code (builtins.ts, ext/) constructs objects with
|
|
17
|
+
* `{ type: 'string' }` instead of `{ kind: 'string' }`. This function
|
|
18
|
+
* converts legacy format to current format on the fly.
|
|
19
|
+
*/
|
|
20
|
+
function normalizeStructure(ts) {
|
|
21
|
+
if (ts.kind !== undefined)
|
|
22
|
+
return ts;
|
|
23
|
+
// Legacy format: { type: 'string' } → { kind: 'string' }
|
|
24
|
+
const legacy = ts;
|
|
25
|
+
if (typeof legacy['type'] === 'string') {
|
|
26
|
+
return { ...legacy, kind: legacy['type'] };
|
|
27
|
+
}
|
|
28
|
+
return ts;
|
|
29
|
+
}
|
|
10
30
|
/** Type guard for RillTuple (spread args) */
|
|
11
31
|
export function isTuple(value) {
|
|
12
32
|
return (typeof value === 'object' &&
|
|
@@ -57,53 +77,24 @@ export function createVector(data, model) {
|
|
|
57
77
|
}
|
|
58
78
|
return { __rill_vector: true, data, model };
|
|
59
79
|
}
|
|
60
|
-
/** Infer the Rill type from a runtime value */
|
|
61
|
-
export
|
|
62
|
-
if (value === null)
|
|
63
|
-
return 'string'; // null treated as empty string
|
|
64
|
-
if (typeof value === 'string')
|
|
65
|
-
return 'string';
|
|
66
|
-
if (typeof value === 'number')
|
|
67
|
-
return 'number';
|
|
68
|
-
if (typeof value === 'boolean')
|
|
69
|
-
return 'bool';
|
|
70
|
-
if (isTuple(value))
|
|
71
|
-
return 'tuple';
|
|
72
|
-
if (isOrdered(value))
|
|
73
|
-
return 'ordered';
|
|
74
|
-
if (isVector(value))
|
|
75
|
-
return 'vector';
|
|
76
|
-
if (Array.isArray(value))
|
|
77
|
-
return 'list';
|
|
78
|
-
if (isTypeValue(value))
|
|
79
|
-
return 'type';
|
|
80
|
-
if (isRillIterator(value))
|
|
81
|
-
return 'iterator';
|
|
82
|
-
if (typeof value === 'object' &&
|
|
83
|
-
'__type' in value &&
|
|
84
|
-
value.__type === 'callable') {
|
|
85
|
-
return 'closure';
|
|
86
|
-
}
|
|
87
|
-
if (typeof value === 'object')
|
|
88
|
-
return 'dict';
|
|
89
|
-
return 'string'; // fallback
|
|
90
|
-
}
|
|
80
|
+
/** Infer the Rill type from a runtime value. Delegates to type-registrations. */
|
|
81
|
+
export const inferType = registryInferType;
|
|
91
82
|
/**
|
|
92
83
|
* Infer the element type for a homogeneous list.
|
|
93
|
-
* Empty arrays return {
|
|
84
|
+
* Empty arrays return { kind: 'any' }.
|
|
94
85
|
* Mixed types throw RILL-R002.
|
|
95
86
|
*/
|
|
96
87
|
export function inferElementType(elements) {
|
|
97
88
|
if (elements.length === 0)
|
|
98
|
-
return {
|
|
89
|
+
return { kind: 'any' };
|
|
99
90
|
const firstElem = elements[0];
|
|
100
|
-
let accType =
|
|
91
|
+
let accType = inferStructure(firstElem);
|
|
101
92
|
for (let i = 1; i < elements.length; i++) {
|
|
102
93
|
const elem = elements[i];
|
|
103
|
-
const elemType =
|
|
94
|
+
const elemType = inferStructure(elem);
|
|
104
95
|
const merged = commonType(accType, elemType);
|
|
105
96
|
if (merged === null) {
|
|
106
|
-
throw new RuntimeError('RILL-R002', `List elements must be the same type: expected ${
|
|
97
|
+
throw new RuntimeError('RILL-R002', `List elements must be the same type: expected ${formatStructure(accType)}, got ${formatStructure(elemType)} at index ${i}`);
|
|
107
98
|
}
|
|
108
99
|
accType = merged;
|
|
109
100
|
}
|
|
@@ -113,7 +104,7 @@ export function inferElementType(elements) {
|
|
|
113
104
|
* Merge uniform value types from two sides of the same compound type.
|
|
114
105
|
* Sub-case A: both carry valueType -> recurse commonType.
|
|
115
106
|
* Sub-case B: both carry structural fields -> extract value types, merge all.
|
|
116
|
-
* Returns the merged
|
|
107
|
+
* Returns the merged TypeStructure on success, undefined when no uniform merge applies.
|
|
117
108
|
*/
|
|
118
109
|
function mergeUniformValueType(aValue, bValue, aFields, bFields) {
|
|
119
110
|
// Sub-case A: both carry valueType
|
|
@@ -143,12 +134,12 @@ function mergeUniformValueType(aValue, bValue, aFields, bFields) {
|
|
|
143
134
|
return undefined;
|
|
144
135
|
}
|
|
145
136
|
/**
|
|
146
|
-
* Return the most specific shared type for two
|
|
137
|
+
* Return the most specific shared type for two TypeStructure values.
|
|
147
138
|
* Returns null when types are incompatible at the top level.
|
|
148
139
|
*
|
|
149
140
|
* Cascade priority:
|
|
150
141
|
* 1. Any-narrowing: if either side is `any`, return the other
|
|
151
|
-
* 2. Structural match: delegate to
|
|
142
|
+
* 2. Structural match: delegate to structureEquals; on true, return a
|
|
152
143
|
* 3. Recursive list: merge inner element types
|
|
153
144
|
* 3b. Uniform valueType: merge dict/tuple/ordered value types
|
|
154
145
|
* 4. Bare type fallback: same compound type but structural mismatch
|
|
@@ -156,103 +147,115 @@ function mergeUniformValueType(aValue, bValue, aFields, bFields) {
|
|
|
156
147
|
*/
|
|
157
148
|
export function commonType(a, b) {
|
|
158
149
|
// 1. Any-narrowing
|
|
159
|
-
if (a.
|
|
150
|
+
if (a.kind === 'any')
|
|
160
151
|
return b;
|
|
161
|
-
if (b.
|
|
152
|
+
if (b.kind === 'any')
|
|
162
153
|
return a;
|
|
163
154
|
// 5. Incompatible top-level types (checked early to short-circuit)
|
|
164
|
-
if (a.
|
|
155
|
+
if (a.kind !== b.kind)
|
|
165
156
|
return null;
|
|
166
157
|
// 2. Structural match
|
|
167
|
-
if (
|
|
158
|
+
if (structureEquals(a, b))
|
|
168
159
|
return a;
|
|
169
160
|
// 3. Recursive list element merging
|
|
170
|
-
if (a.
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
if (a.kind === 'list' && b.kind === 'list') {
|
|
162
|
+
const aList = a;
|
|
163
|
+
const bList = b;
|
|
164
|
+
if (aList.element !== undefined && bList.element !== undefined) {
|
|
165
|
+
const inner = commonType(aList.element, bList.element);
|
|
173
166
|
if (inner !== null)
|
|
174
|
-
return {
|
|
167
|
+
return { kind: 'list', element: inner };
|
|
175
168
|
}
|
|
176
|
-
return {
|
|
169
|
+
return { kind: 'list' };
|
|
177
170
|
}
|
|
178
171
|
// 3b. Uniform valueType merging for dict/tuple/ordered
|
|
179
|
-
if (a.
|
|
180
|
-
const
|
|
172
|
+
if (a.kind === 'dict' && b.kind === 'dict') {
|
|
173
|
+
const aDict = a;
|
|
174
|
+
const bDict = b;
|
|
175
|
+
const merged = mergeUniformValueType(aDict.valueType, bDict.valueType, aDict.fields ? Object.values(aDict.fields) : undefined, bDict.fields ? Object.values(bDict.fields) : undefined);
|
|
181
176
|
if (merged !== undefined)
|
|
182
|
-
return {
|
|
177
|
+
return { kind: 'dict', valueType: merged };
|
|
183
178
|
}
|
|
184
|
-
if (a.
|
|
185
|
-
const
|
|
179
|
+
if (a.kind === 'tuple' && b.kind === 'tuple') {
|
|
180
|
+
const aTuple = a;
|
|
181
|
+
const bTuple = b;
|
|
182
|
+
const merged = mergeUniformValueType(aTuple.valueType, bTuple.valueType, aTuple.elements, bTuple.elements);
|
|
186
183
|
if (merged !== undefined)
|
|
187
|
-
return {
|
|
184
|
+
return { kind: 'tuple', valueType: merged };
|
|
188
185
|
}
|
|
189
|
-
if (a.
|
|
190
|
-
const
|
|
186
|
+
if (a.kind === 'ordered' && b.kind === 'ordered') {
|
|
187
|
+
const aOrd = a;
|
|
188
|
+
const bOrd = b;
|
|
189
|
+
const merged = mergeUniformValueType(aOrd.valueType, bOrd.valueType, aOrd.fields, bOrd.fields);
|
|
191
190
|
if (merged !== undefined)
|
|
192
|
-
return {
|
|
191
|
+
return { kind: 'ordered', valueType: merged };
|
|
193
192
|
}
|
|
194
193
|
// 4. Bare type fallback for compound types.
|
|
195
194
|
// The cast is safe for closure/dict/tuple/ordered (all sub-fields optional).
|
|
196
|
-
// For union, members is required by
|
|
195
|
+
// For union, members is required by TypeStructure but omitted here intentionally:
|
|
197
196
|
// bare union signals structural incompatibility without enumerating members.
|
|
198
|
-
if (a.
|
|
199
|
-
a.
|
|
200
|
-
a.
|
|
201
|
-
a.
|
|
202
|
-
a.
|
|
203
|
-
return {
|
|
197
|
+
if (a.kind === 'closure' ||
|
|
198
|
+
a.kind === 'dict' ||
|
|
199
|
+
a.kind === 'tuple' ||
|
|
200
|
+
a.kind === 'ordered' ||
|
|
201
|
+
a.kind === 'union') {
|
|
202
|
+
return { kind: a.kind };
|
|
204
203
|
}
|
|
205
204
|
return null;
|
|
206
205
|
}
|
|
207
206
|
/** Compare two structural types for equality. */
|
|
208
|
-
export function
|
|
209
|
-
if (a.
|
|
207
|
+
export function structureEquals(a, b) {
|
|
208
|
+
if (a.kind !== b.kind)
|
|
210
209
|
return false;
|
|
211
|
-
// Leaf variants compare by
|
|
212
|
-
if (a.
|
|
213
|
-
a.
|
|
214
|
-
a.
|
|
215
|
-
a.
|
|
216
|
-
a.
|
|
217
|
-
a.
|
|
210
|
+
// Leaf variants compare by kind alone
|
|
211
|
+
if (a.kind === 'number' ||
|
|
212
|
+
a.kind === 'string' ||
|
|
213
|
+
a.kind === 'bool' ||
|
|
214
|
+
a.kind === 'vector' ||
|
|
215
|
+
a.kind === 'type' ||
|
|
216
|
+
a.kind === 'any') {
|
|
218
217
|
return true;
|
|
219
218
|
}
|
|
220
|
-
if (a.
|
|
221
|
-
|
|
219
|
+
if (a.kind === 'list' && b.kind === 'list') {
|
|
220
|
+
const aList = a;
|
|
221
|
+
const bList = b;
|
|
222
|
+
if (aList.element === undefined && bList.element === undefined)
|
|
222
223
|
return true;
|
|
223
|
-
if (
|
|
224
|
+
if (aList.element === undefined || bList.element === undefined)
|
|
224
225
|
return false;
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
if (a.
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
226
|
+
return structureEquals(aList.element, bList.element);
|
|
227
|
+
}
|
|
228
|
+
if (a.kind === 'dict' && b.kind === 'dict') {
|
|
229
|
+
const aDict = a;
|
|
230
|
+
const bDict = b;
|
|
231
|
+
// Uniform valueType comparison
|
|
232
|
+
const aHasValue = aDict.valueType !== undefined;
|
|
233
|
+
const bHasValue = bDict.valueType !== undefined;
|
|
231
234
|
if (aHasValue || bHasValue) {
|
|
232
235
|
if (!aHasValue || !bHasValue)
|
|
233
236
|
return false;
|
|
234
|
-
return
|
|
237
|
+
return structureEquals(aDict.valueType, bDict.valueType);
|
|
235
238
|
}
|
|
236
239
|
// Structural fields comparison
|
|
237
|
-
if (
|
|
240
|
+
if (aDict.fields === undefined && bDict.fields === undefined)
|
|
238
241
|
return true;
|
|
239
|
-
if (
|
|
242
|
+
if (aDict.fields === undefined || bDict.fields === undefined)
|
|
240
243
|
return false;
|
|
241
|
-
const aKeys = Object.keys(
|
|
242
|
-
const bKeys = Object.keys(
|
|
244
|
+
const aKeys = Object.keys(aDict.fields).sort();
|
|
245
|
+
const bKeys = Object.keys(bDict.fields).sort();
|
|
243
246
|
if (aKeys.length !== bKeys.length)
|
|
244
247
|
return false;
|
|
245
248
|
for (let i = 0; i < aKeys.length; i++) {
|
|
246
249
|
const key = aKeys[i];
|
|
247
250
|
if (key !== bKeys[i])
|
|
248
251
|
return false;
|
|
249
|
-
const aField =
|
|
250
|
-
const bField =
|
|
252
|
+
const aField = aDict.fields[key];
|
|
253
|
+
const bField = bDict.fields[key];
|
|
251
254
|
const aHasDefault = aField.defaultValue !== undefined;
|
|
252
255
|
const bHasDefault = bField.defaultValue !== undefined;
|
|
253
256
|
if (aHasDefault !== bHasDefault)
|
|
254
257
|
return false;
|
|
255
|
-
if (!
|
|
258
|
+
if (!structureEquals(aField.type, bField.type))
|
|
256
259
|
return false;
|
|
257
260
|
if (aHasDefault && bHasDefault) {
|
|
258
261
|
if (!deepEquals(aField.defaultValue, bField.defaultValue))
|
|
@@ -261,26 +264,28 @@ export function structuralTypeEquals(a, b) {
|
|
|
261
264
|
}
|
|
262
265
|
return true;
|
|
263
266
|
}
|
|
264
|
-
if (a.
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
267
|
+
if (a.kind === 'tuple' && b.kind === 'tuple') {
|
|
268
|
+
const aTuple = a;
|
|
269
|
+
const bTuple = b;
|
|
270
|
+
// Uniform valueType comparison
|
|
271
|
+
const aHasValue = aTuple.valueType !== undefined;
|
|
272
|
+
const bHasValue = bTuple.valueType !== undefined;
|
|
268
273
|
if (aHasValue || bHasValue) {
|
|
269
274
|
if (!aHasValue || !bHasValue)
|
|
270
275
|
return false;
|
|
271
|
-
return
|
|
276
|
+
return structureEquals(aTuple.valueType, bTuple.valueType);
|
|
272
277
|
}
|
|
273
278
|
// Structural elements comparison
|
|
274
|
-
if (
|
|
279
|
+
if (aTuple.elements === undefined && bTuple.elements === undefined)
|
|
275
280
|
return true;
|
|
276
|
-
if (
|
|
281
|
+
if (aTuple.elements === undefined || bTuple.elements === undefined)
|
|
277
282
|
return false;
|
|
278
|
-
if (
|
|
283
|
+
if (aTuple.elements.length !== bTuple.elements.length)
|
|
279
284
|
return false;
|
|
280
|
-
for (let i = 0; i <
|
|
281
|
-
const aElem =
|
|
282
|
-
const bElem =
|
|
283
|
-
if (!
|
|
285
|
+
for (let i = 0; i < aTuple.elements.length; i++) {
|
|
286
|
+
const aElem = aTuple.elements[i];
|
|
287
|
+
const bElem = bTuple.elements[i];
|
|
288
|
+
if (!structureEquals(aElem.type, bElem.type))
|
|
284
289
|
return false;
|
|
285
290
|
const aDefault = aElem.defaultValue;
|
|
286
291
|
const bDefault = bElem.defaultValue;
|
|
@@ -293,28 +298,30 @@ export function structuralTypeEquals(a, b) {
|
|
|
293
298
|
}
|
|
294
299
|
return true;
|
|
295
300
|
}
|
|
296
|
-
if (a.
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
301
|
+
if (a.kind === 'ordered' && b.kind === 'ordered') {
|
|
302
|
+
const aOrd = a;
|
|
303
|
+
const bOrd = b;
|
|
304
|
+
// Uniform valueType comparison
|
|
305
|
+
const aHasValue = aOrd.valueType !== undefined;
|
|
306
|
+
const bHasValue = bOrd.valueType !== undefined;
|
|
300
307
|
if (aHasValue || bHasValue) {
|
|
301
308
|
if (!aHasValue || !bHasValue)
|
|
302
309
|
return false;
|
|
303
|
-
return
|
|
310
|
+
return structureEquals(aOrd.valueType, bOrd.valueType);
|
|
304
311
|
}
|
|
305
312
|
// Structural fields comparison
|
|
306
|
-
if (
|
|
313
|
+
if (aOrd.fields === undefined && bOrd.fields === undefined)
|
|
307
314
|
return true;
|
|
308
|
-
if (
|
|
315
|
+
if (aOrd.fields === undefined || bOrd.fields === undefined)
|
|
309
316
|
return false;
|
|
310
|
-
if (
|
|
317
|
+
if (aOrd.fields.length !== bOrd.fields.length)
|
|
311
318
|
return false;
|
|
312
|
-
for (let i = 0; i <
|
|
313
|
-
const aField =
|
|
314
|
-
const bField =
|
|
319
|
+
for (let i = 0; i < aOrd.fields.length; i++) {
|
|
320
|
+
const aField = aOrd.fields[i];
|
|
321
|
+
const bField = bOrd.fields[i];
|
|
315
322
|
if (aField.name !== bField.name)
|
|
316
323
|
return false;
|
|
317
|
-
if (!
|
|
324
|
+
if (!structureEquals(aField.type, bField.type))
|
|
318
325
|
return false;
|
|
319
326
|
const aDefault = aField.defaultValue;
|
|
320
327
|
const bDefault = bField.defaultValue;
|
|
@@ -327,31 +334,35 @@ export function structuralTypeEquals(a, b) {
|
|
|
327
334
|
}
|
|
328
335
|
return true;
|
|
329
336
|
}
|
|
330
|
-
if (a.
|
|
331
|
-
|
|
337
|
+
if (a.kind === 'union' && b.kind === 'union') {
|
|
338
|
+
const aUnion = a;
|
|
339
|
+
const bUnion = b;
|
|
340
|
+
if (aUnion.members.length !== bUnion.members.length)
|
|
332
341
|
return false;
|
|
333
|
-
for (let i = 0; i <
|
|
334
|
-
if (!
|
|
342
|
+
for (let i = 0; i < aUnion.members.length; i++) {
|
|
343
|
+
if (!structureEquals(aUnion.members[i], bUnion.members[i]))
|
|
335
344
|
return false;
|
|
336
345
|
}
|
|
337
346
|
return true;
|
|
338
347
|
}
|
|
339
|
-
if (a.
|
|
340
|
-
|
|
348
|
+
if (a.kind === 'closure' && b.kind === 'closure') {
|
|
349
|
+
const aCls = a;
|
|
350
|
+
const bCls = b;
|
|
351
|
+
if (aCls.params === undefined && bCls.params === undefined) {
|
|
341
352
|
// Both absent: compare ret
|
|
342
353
|
}
|
|
343
|
-
else if (
|
|
354
|
+
else if (aCls.params === undefined || bCls.params === undefined) {
|
|
344
355
|
return false;
|
|
345
356
|
}
|
|
346
357
|
else {
|
|
347
|
-
if (
|
|
358
|
+
if (aCls.params.length !== bCls.params.length)
|
|
348
359
|
return false;
|
|
349
|
-
for (let i = 0; i <
|
|
350
|
-
const aParam =
|
|
351
|
-
const bParam =
|
|
360
|
+
for (let i = 0; i < aCls.params.length; i++) {
|
|
361
|
+
const aParam = aCls.params[i];
|
|
362
|
+
const bParam = bCls.params[i];
|
|
352
363
|
if (aParam.name !== bParam.name)
|
|
353
364
|
return false;
|
|
354
|
-
if (!
|
|
365
|
+
if (!structureEquals(aParam.type, bParam.type))
|
|
355
366
|
return false;
|
|
356
367
|
const aDefault = aParam.defaultValue;
|
|
357
368
|
const bDefault = bParam.defaultValue;
|
|
@@ -363,213 +374,213 @@ export function structuralTypeEquals(a, b) {
|
|
|
363
374
|
return false;
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
|
-
if (
|
|
377
|
+
if (aCls.ret === undefined && bCls.ret === undefined)
|
|
367
378
|
return true;
|
|
368
|
-
if (
|
|
379
|
+
if (aCls.ret === undefined || bCls.ret === undefined)
|
|
369
380
|
return false;
|
|
370
|
-
return
|
|
381
|
+
return structureEquals(aCls.ret, bCls.ret);
|
|
371
382
|
}
|
|
372
383
|
return false;
|
|
373
384
|
}
|
|
385
|
+
/** @deprecated Use structureEquals instead. */
|
|
386
|
+
export const structuralTypeEquals = structureEquals;
|
|
374
387
|
/** Infer the structural type descriptor for any Rill value. */
|
|
375
|
-
export function
|
|
388
|
+
export function inferStructure(value) {
|
|
376
389
|
if (value === null || typeof value === 'string') {
|
|
377
|
-
return {
|
|
390
|
+
return { kind: 'string' };
|
|
378
391
|
}
|
|
379
392
|
if (typeof value === 'number') {
|
|
380
|
-
return {
|
|
393
|
+
return { kind: 'number' };
|
|
381
394
|
}
|
|
382
395
|
if (typeof value === 'boolean') {
|
|
383
|
-
return {
|
|
396
|
+
return { kind: 'bool' };
|
|
384
397
|
}
|
|
385
398
|
if (isTypeValue(value)) {
|
|
386
|
-
return {
|
|
399
|
+
return { kind: 'type' };
|
|
387
400
|
}
|
|
388
401
|
if (Array.isArray(value)) {
|
|
389
|
-
return {
|
|
402
|
+
return { kind: 'list', element: inferElementType(value) };
|
|
390
403
|
}
|
|
391
404
|
if (isTuple(value)) {
|
|
392
405
|
return {
|
|
393
|
-
|
|
406
|
+
kind: 'tuple',
|
|
394
407
|
elements: value.entries.map((e) => ({
|
|
395
|
-
type:
|
|
408
|
+
type: inferStructure(e),
|
|
396
409
|
})),
|
|
397
410
|
};
|
|
398
411
|
}
|
|
399
412
|
if (isOrdered(value)) {
|
|
400
413
|
return {
|
|
401
|
-
|
|
414
|
+
kind: 'ordered',
|
|
402
415
|
fields: value.entries.map(([k, v]) => ({
|
|
403
416
|
name: k,
|
|
404
|
-
type:
|
|
417
|
+
type: inferStructure(v),
|
|
405
418
|
})),
|
|
406
419
|
};
|
|
407
420
|
}
|
|
408
421
|
if (isVector(value)) {
|
|
409
|
-
return {
|
|
422
|
+
return { kind: 'vector' };
|
|
410
423
|
}
|
|
411
424
|
if (isCallable(value)) {
|
|
412
|
-
const params = (value.params ?? []).map((p) => paramToFieldDef(p.name, p.type ?? {
|
|
425
|
+
const params = (value.params ?? []).map((p) => paramToFieldDef(p.name, p.type ?? { kind: 'any' }, p.defaultValue));
|
|
413
426
|
const ret = value.returnType.structure;
|
|
414
|
-
return {
|
|
427
|
+
return { kind: 'closure', params, ret };
|
|
415
428
|
}
|
|
416
429
|
if (typeof value === 'object') {
|
|
417
430
|
const dict = value;
|
|
418
431
|
const fields = {};
|
|
419
432
|
for (const [k, v] of Object.entries(dict)) {
|
|
420
|
-
fields[k] = { type:
|
|
433
|
+
fields[k] = { type: inferStructure(v) };
|
|
421
434
|
}
|
|
422
|
-
return {
|
|
435
|
+
return { kind: 'dict', fields };
|
|
423
436
|
}
|
|
424
437
|
throw new RuntimeError('RILL-R004', `Cannot infer structural type for ${formatValue(value)}`);
|
|
425
438
|
}
|
|
439
|
+
/** @deprecated Use inferStructure instead. */
|
|
440
|
+
export const inferStructuralType = inferStructure;
|
|
426
441
|
/**
|
|
427
442
|
* Check if a value matches a structural type descriptor.
|
|
428
443
|
* Used for runtime type checking (`:?` operator).
|
|
429
444
|
*/
|
|
430
|
-
export function
|
|
445
|
+
export function structureMatches(value, type) {
|
|
446
|
+
type = normalizeStructure(type);
|
|
431
447
|
if (typeof value === 'undefined') {
|
|
432
448
|
throw new RuntimeError('RILL-R004', 'Cannot type-check non-value');
|
|
433
449
|
}
|
|
434
|
-
if (type.
|
|
450
|
+
if (type.kind === 'any')
|
|
435
451
|
return true;
|
|
436
452
|
// Leaf primitive variants: match by inferred type name
|
|
437
|
-
if (type.
|
|
438
|
-
type.
|
|
439
|
-
type.
|
|
440
|
-
type.
|
|
441
|
-
type.
|
|
442
|
-
return inferType(value) === type.
|
|
443
|
-
}
|
|
444
|
-
if (type.
|
|
453
|
+
if (type.kind === 'number' ||
|
|
454
|
+
type.kind === 'string' ||
|
|
455
|
+
type.kind === 'bool' ||
|
|
456
|
+
type.kind === 'vector' ||
|
|
457
|
+
type.kind === 'type') {
|
|
458
|
+
return inferType(value) === type.kind;
|
|
459
|
+
}
|
|
460
|
+
if (type.kind === 'list') {
|
|
461
|
+
const t = type;
|
|
445
462
|
if (!Array.isArray(value))
|
|
446
463
|
return false;
|
|
447
|
-
|
|
448
|
-
if (type.element === undefined)
|
|
464
|
+
if (t.element === undefined)
|
|
449
465
|
return true;
|
|
450
|
-
if (
|
|
466
|
+
if (t.element.kind === 'any')
|
|
451
467
|
return true;
|
|
452
|
-
return value.every((elem) =>
|
|
468
|
+
return value.every((elem) => structureMatches(elem, t.element));
|
|
453
469
|
}
|
|
454
|
-
if (type.
|
|
470
|
+
if (type.kind === 'dict') {
|
|
471
|
+
const t = type;
|
|
455
472
|
if (!isDict(value))
|
|
456
473
|
return false;
|
|
457
|
-
|
|
458
|
-
if (type.valueType !== undefined) {
|
|
474
|
+
if (t.valueType !== undefined) {
|
|
459
475
|
const vals = Object.values(value);
|
|
460
|
-
return vals.every((v) =>
|
|
476
|
+
return vals.every((v) => structureMatches(v, t.valueType));
|
|
461
477
|
}
|
|
462
|
-
|
|
463
|
-
if (type.fields === undefined)
|
|
478
|
+
if (t.fields === undefined)
|
|
464
479
|
return true;
|
|
465
|
-
const dictKeys = Object.keys(
|
|
466
|
-
// Empty fields object matches any dict
|
|
480
|
+
const dictKeys = Object.keys(t.fields);
|
|
467
481
|
if (dictKeys.length === 0)
|
|
468
482
|
return true;
|
|
469
483
|
const dict = value;
|
|
470
484
|
for (const key of dictKeys) {
|
|
471
485
|
if (!(key in dict)) {
|
|
472
|
-
const field =
|
|
486
|
+
const field = t.fields[key];
|
|
473
487
|
if (field.defaultValue !== undefined)
|
|
474
488
|
continue;
|
|
475
489
|
return false;
|
|
476
490
|
}
|
|
477
|
-
const field =
|
|
478
|
-
if (!
|
|
491
|
+
const field = t.fields[key];
|
|
492
|
+
if (!structureMatches(dict[key], field.type))
|
|
479
493
|
return false;
|
|
480
494
|
}
|
|
481
495
|
return true;
|
|
482
496
|
}
|
|
483
|
-
if (type.
|
|
497
|
+
if (type.kind === 'tuple') {
|
|
498
|
+
const t = type;
|
|
484
499
|
if (!isTuple(value))
|
|
485
500
|
return false;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
return value.entries.every((v) => structuralTypeMatches(v, type.valueType));
|
|
501
|
+
if (t.valueType !== undefined) {
|
|
502
|
+
return value.entries.every((v) => structureMatches(v, t.valueType));
|
|
489
503
|
}
|
|
490
|
-
|
|
491
|
-
if (type.elements === undefined)
|
|
504
|
+
if (t.elements === undefined)
|
|
492
505
|
return true;
|
|
493
|
-
if (
|
|
506
|
+
if (t.elements.length === 0)
|
|
494
507
|
return value.entries.length === 0;
|
|
495
|
-
|
|
496
|
-
if (value.entries.length > type.elements.length)
|
|
508
|
+
if (value.entries.length > t.elements.length)
|
|
497
509
|
return false;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const field = type.elements[i];
|
|
510
|
+
if (value.entries.length < t.elements.length) {
|
|
511
|
+
for (let i = value.entries.length; i < t.elements.length; i++) {
|
|
512
|
+
const field = t.elements[i];
|
|
502
513
|
if (field.defaultValue === undefined)
|
|
503
514
|
return false;
|
|
504
515
|
}
|
|
505
516
|
}
|
|
506
517
|
for (let i = 0; i < value.entries.length; i++) {
|
|
507
|
-
if (!
|
|
518
|
+
if (!structureMatches(value.entries[i], t.elements[i].type))
|
|
508
519
|
return false;
|
|
509
520
|
}
|
|
510
521
|
return true;
|
|
511
522
|
}
|
|
512
|
-
if (type.
|
|
523
|
+
if (type.kind === 'ordered') {
|
|
524
|
+
const t = type;
|
|
513
525
|
if (!isOrdered(value))
|
|
514
526
|
return false;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
return value.entries.every(([, v]) => structuralTypeMatches(v, type.valueType));
|
|
527
|
+
if (t.valueType !== undefined) {
|
|
528
|
+
return value.entries.every(([, v]) => structureMatches(v, t.valueType));
|
|
518
529
|
}
|
|
519
|
-
|
|
520
|
-
if (type.fields === undefined)
|
|
530
|
+
if (t.fields === undefined)
|
|
521
531
|
return true;
|
|
522
|
-
if (
|
|
532
|
+
if (t.fields.length === 0)
|
|
523
533
|
return value.entries.length === 0;
|
|
524
|
-
|
|
525
|
-
if (value.entries.length > type.fields.length)
|
|
534
|
+
if (value.entries.length > t.fields.length)
|
|
526
535
|
return false;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const field = type.fields[i];
|
|
536
|
+
if (value.entries.length < t.fields.length) {
|
|
537
|
+
for (let i = value.entries.length; i < t.fields.length; i++) {
|
|
538
|
+
const field = t.fields[i];
|
|
531
539
|
if (field.defaultValue === undefined)
|
|
532
540
|
return false;
|
|
533
541
|
}
|
|
534
542
|
}
|
|
535
543
|
for (let i = 0; i < value.entries.length; i++) {
|
|
536
|
-
const field =
|
|
544
|
+
const field = t.fields[i];
|
|
537
545
|
const [actualName, actualValue] = value.entries[i];
|
|
538
546
|
if (actualName !== field.name)
|
|
539
547
|
return false;
|
|
540
|
-
if (!
|
|
548
|
+
if (!structureMatches(actualValue, field.type))
|
|
541
549
|
return false;
|
|
542
550
|
}
|
|
543
551
|
return true;
|
|
544
552
|
}
|
|
545
|
-
if (type.
|
|
553
|
+
if (type.kind === 'closure') {
|
|
554
|
+
const t = type;
|
|
546
555
|
if (!isCallable(value))
|
|
547
556
|
return false;
|
|
548
|
-
|
|
549
|
-
if (type.params === undefined)
|
|
557
|
+
if (t.params === undefined)
|
|
550
558
|
return true;
|
|
551
559
|
const valueParams = value.params ?? [];
|
|
552
|
-
if (valueParams.length !==
|
|
560
|
+
if (valueParams.length !== t.params.length)
|
|
553
561
|
return false;
|
|
554
|
-
for (let i = 0; i <
|
|
555
|
-
const field =
|
|
562
|
+
for (let i = 0; i < t.params.length; i++) {
|
|
563
|
+
const field = t.params[i];
|
|
556
564
|
const param = valueParams[i];
|
|
557
565
|
if (param.name !== field.name)
|
|
558
566
|
return false;
|
|
559
|
-
const paramType = param.type ?? {
|
|
560
|
-
if (!
|
|
567
|
+
const paramType = param.type ?? { kind: 'any' };
|
|
568
|
+
if (!structureEquals(paramType, field.type))
|
|
561
569
|
return false;
|
|
562
570
|
}
|
|
563
571
|
const retType = value.returnType.structure;
|
|
564
|
-
if (
|
|
572
|
+
if (t.ret === undefined)
|
|
565
573
|
return true;
|
|
566
|
-
return
|
|
574
|
+
return structureEquals(retType, t.ret);
|
|
567
575
|
}
|
|
568
|
-
if (type.
|
|
569
|
-
|
|
576
|
+
if (type.kind === 'union') {
|
|
577
|
+
const t = type;
|
|
578
|
+
return t.members.some((member) => structureMatches(value, member));
|
|
570
579
|
}
|
|
571
580
|
return false;
|
|
572
581
|
}
|
|
582
|
+
/** @deprecated Use structureMatches instead. */
|
|
583
|
+
export const structuralTypeMatches = structureMatches;
|
|
573
584
|
/** Build a closure param field definition from name, type, and optional default. */
|
|
574
585
|
export function paramToFieldDef(name, type, defaultValue) {
|
|
575
586
|
const field = { name, type };
|
|
@@ -592,84 +603,92 @@ function formatRillLiteral(value) {
|
|
|
592
603
|
return formatValue(value);
|
|
593
604
|
}
|
|
594
605
|
/** Format a structural type descriptor as a human-readable string. */
|
|
595
|
-
export function
|
|
596
|
-
if (type.
|
|
597
|
-
type.
|
|
598
|
-
type.
|
|
599
|
-
type.
|
|
600
|
-
type.
|
|
601
|
-
type.
|
|
602
|
-
return type.
|
|
603
|
-
}
|
|
604
|
-
if (type.
|
|
605
|
-
|
|
606
|
+
export function formatStructure(type) {
|
|
607
|
+
if (type.kind === 'any' ||
|
|
608
|
+
type.kind === 'number' ||
|
|
609
|
+
type.kind === 'string' ||
|
|
610
|
+
type.kind === 'bool' ||
|
|
611
|
+
type.kind === 'vector' ||
|
|
612
|
+
type.kind === 'type') {
|
|
613
|
+
return type.kind;
|
|
614
|
+
}
|
|
615
|
+
if (type.kind === 'list') {
|
|
616
|
+
const t = type;
|
|
617
|
+
if (t.element === undefined)
|
|
606
618
|
return 'list';
|
|
607
|
-
return `list(${
|
|
619
|
+
return `list(${formatStructure(t.element)})`;
|
|
608
620
|
}
|
|
609
|
-
if (type.
|
|
610
|
-
|
|
611
|
-
|
|
621
|
+
if (type.kind === 'dict') {
|
|
622
|
+
const t = type;
|
|
623
|
+
if (t.valueType !== undefined && t.fields === undefined) {
|
|
624
|
+
return `dict(${formatStructure(t.valueType)})`;
|
|
612
625
|
}
|
|
613
|
-
if (
|
|
626
|
+
if (t.fields === undefined)
|
|
614
627
|
return 'dict';
|
|
615
|
-
const parts = Object.keys(
|
|
628
|
+
const parts = Object.keys(t.fields)
|
|
616
629
|
.sort()
|
|
617
630
|
.map((k) => {
|
|
618
|
-
const field =
|
|
619
|
-
const base = `${k}: ${
|
|
631
|
+
const field = t.fields[k];
|
|
632
|
+
const base = `${k}: ${formatStructure(field.type)}`;
|
|
620
633
|
if (field.defaultValue === undefined)
|
|
621
634
|
return base;
|
|
622
635
|
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
623
636
|
});
|
|
624
637
|
return `dict(${parts.join(', ')})`;
|
|
625
638
|
}
|
|
626
|
-
if (type.
|
|
627
|
-
|
|
628
|
-
|
|
639
|
+
if (type.kind === 'tuple') {
|
|
640
|
+
const t = type;
|
|
641
|
+
if (t.valueType !== undefined && t.elements === undefined) {
|
|
642
|
+
return `tuple(${formatStructure(t.valueType)})`;
|
|
629
643
|
}
|
|
630
|
-
if (
|
|
644
|
+
if (t.elements === undefined)
|
|
631
645
|
return 'tuple';
|
|
632
|
-
const parts =
|
|
633
|
-
const base =
|
|
646
|
+
const parts = t.elements.map((field) => {
|
|
647
|
+
const base = formatStructure(field.type);
|
|
634
648
|
if (field.defaultValue === undefined)
|
|
635
649
|
return base;
|
|
636
650
|
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
637
651
|
});
|
|
638
652
|
return `tuple(${parts.join(', ')})`;
|
|
639
653
|
}
|
|
640
|
-
if (type.
|
|
641
|
-
|
|
642
|
-
|
|
654
|
+
if (type.kind === 'ordered') {
|
|
655
|
+
const t = type;
|
|
656
|
+
if (t.valueType !== undefined && t.fields === undefined) {
|
|
657
|
+
return `ordered(${formatStructure(t.valueType)})`;
|
|
643
658
|
}
|
|
644
|
-
if (
|
|
659
|
+
if (t.fields === undefined)
|
|
645
660
|
return 'ordered';
|
|
646
|
-
const parts =
|
|
647
|
-
const base = `${field.name}: ${
|
|
661
|
+
const parts = t.fields.map((field) => {
|
|
662
|
+
const base = `${field.name}: ${formatStructure(field.type)}`;
|
|
648
663
|
if (field.defaultValue === undefined)
|
|
649
664
|
return base;
|
|
650
665
|
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
651
666
|
});
|
|
652
667
|
return `ordered(${parts.join(', ')})`;
|
|
653
668
|
}
|
|
654
|
-
if (type.
|
|
655
|
-
|
|
669
|
+
if (type.kind === 'closure') {
|
|
670
|
+
const t = type;
|
|
671
|
+
if (t.params === undefined)
|
|
656
672
|
return 'closure';
|
|
657
|
-
const params =
|
|
673
|
+
const params = t.params
|
|
658
674
|
.map((field) => {
|
|
659
|
-
const base = `${field.name}: ${
|
|
675
|
+
const base = `${field.name}: ${formatStructure(field.type)}`;
|
|
660
676
|
if (field.defaultValue === undefined)
|
|
661
677
|
return base;
|
|
662
678
|
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
663
679
|
})
|
|
664
680
|
.join(', ');
|
|
665
|
-
const ret =
|
|
681
|
+
const ret = t.ret !== undefined ? formatStructure(t.ret) : 'any';
|
|
666
682
|
return `|${params}| :${ret}`;
|
|
667
683
|
}
|
|
668
|
-
if (type.
|
|
669
|
-
|
|
684
|
+
if (type.kind === 'union') {
|
|
685
|
+
const t = type;
|
|
686
|
+
return t.members.map(formatStructure).join('|');
|
|
670
687
|
}
|
|
671
688
|
return 'any';
|
|
672
689
|
}
|
|
690
|
+
/** @deprecated Use formatStructure instead. */
|
|
691
|
+
export const formatStructuralType = formatStructure;
|
|
673
692
|
/**
|
|
674
693
|
* Check if a value is of the expected type.
|
|
675
694
|
* Returns true if the value matches the expected type, false otherwise.
|
|
@@ -706,88 +725,12 @@ export function isTruthy(value) {
|
|
|
706
725
|
export function isEmpty(value) {
|
|
707
726
|
return !isTruthy(value);
|
|
708
727
|
}
|
|
709
|
-
/** Format a value for display */
|
|
710
|
-
export
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (typeof value === 'number')
|
|
716
|
-
return String(value);
|
|
717
|
-
if (typeof value === 'boolean')
|
|
718
|
-
return value ? 'true' : 'false';
|
|
719
|
-
if (isCallable(value)) {
|
|
720
|
-
return 'type(closure)';
|
|
721
|
-
}
|
|
722
|
-
if (isTuple(value)) {
|
|
723
|
-
return `tuple[${value.entries.map(formatValue).join(', ')}]`;
|
|
724
|
-
}
|
|
725
|
-
if (isOrdered(value)) {
|
|
726
|
-
const parts = value.entries.map(([k, v]) => `${k}: ${formatValue(v)}`);
|
|
727
|
-
return `ordered[${parts.join(', ')}]`;
|
|
728
|
-
}
|
|
729
|
-
if (isRillIterator(value)) {
|
|
730
|
-
return 'type(iterator)';
|
|
731
|
-
}
|
|
732
|
-
if (Array.isArray(value)) {
|
|
733
|
-
return `list[${value.map(formatValue).join(', ')}]`;
|
|
734
|
-
}
|
|
735
|
-
if (isVector(value)) {
|
|
736
|
-
return `vector(${value.model}, ${value.data.length}d)`;
|
|
737
|
-
}
|
|
738
|
-
if (isTypeValue(value)) {
|
|
739
|
-
return formatStructuralType(value.structure);
|
|
740
|
-
}
|
|
741
|
-
// Plain dict
|
|
742
|
-
if (typeof value === 'object') {
|
|
743
|
-
const dict = value;
|
|
744
|
-
const parts = Object.entries(dict).map(([k, v]) => `${k}: ${formatValue(v)}`);
|
|
745
|
-
return `dict[${parts.join(', ')}]`;
|
|
746
|
-
}
|
|
747
|
-
return String(value);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Convert a RillValue to a JSON-serializable value.
|
|
751
|
-
* @throws {Error} plain Error (not RuntimeError) for non-serializable types
|
|
752
|
-
*/
|
|
753
|
-
export function valueToJSON(value) {
|
|
754
|
-
if (value === null)
|
|
755
|
-
return null;
|
|
756
|
-
if (typeof value === 'string')
|
|
757
|
-
return value;
|
|
758
|
-
if (typeof value === 'number')
|
|
759
|
-
return value;
|
|
760
|
-
if (typeof value === 'boolean')
|
|
761
|
-
return value;
|
|
762
|
-
if (Array.isArray(value)) {
|
|
763
|
-
return value.map(valueToJSON);
|
|
764
|
-
}
|
|
765
|
-
if (isCallable(value)) {
|
|
766
|
-
throw new Error('closures are not JSON-serializable');
|
|
767
|
-
}
|
|
768
|
-
if (isTuple(value)) {
|
|
769
|
-
throw new Error('tuples are not JSON-serializable');
|
|
770
|
-
}
|
|
771
|
-
if (isOrdered(value)) {
|
|
772
|
-
throw new Error('ordered values are not JSON-serializable');
|
|
773
|
-
}
|
|
774
|
-
if (isVector(value)) {
|
|
775
|
-
throw new Error('vectors are not JSON-serializable');
|
|
776
|
-
}
|
|
777
|
-
if (isTypeValue(value)) {
|
|
778
|
-
throw new Error('type values are not JSON-serializable');
|
|
779
|
-
}
|
|
780
|
-
if (isRillIterator(value)) {
|
|
781
|
-
throw new Error('iterators are not JSON-serializable');
|
|
782
|
-
}
|
|
783
|
-
// Plain dict
|
|
784
|
-
const dict = value;
|
|
785
|
-
const result = {};
|
|
786
|
-
for (const [k, v] of Object.entries(dict)) {
|
|
787
|
-
result[k] = valueToJSON(v);
|
|
788
|
-
}
|
|
789
|
-
return result;
|
|
790
|
-
}
|
|
728
|
+
/** Format a value for display. Delegates to type-registrations. */
|
|
729
|
+
export const formatValue = registryFormatValue;
|
|
730
|
+
/** Serialize a Rill value for JSON transport. Delegates to type-registrations. */
|
|
731
|
+
export const serializeValue = registrySerializeValue;
|
|
732
|
+
/** @deprecated Use serializeValue instead. */
|
|
733
|
+
export const valueToJSON = serializeValue;
|
|
791
734
|
/**
|
|
792
735
|
* Convert a RillValue to a NativeResult for host consumption.
|
|
793
736
|
* Non-representable types (closures, vectors, type values, iterators) produce descriptor objects.
|
|
@@ -795,7 +738,7 @@ export function valueToJSON(value) {
|
|
|
795
738
|
*/
|
|
796
739
|
export function toNative(value) {
|
|
797
740
|
const rillTypeName = inferType(value);
|
|
798
|
-
const rillTypeSignature =
|
|
741
|
+
const rillTypeSignature = formatStructure(inferStructure(value));
|
|
799
742
|
const nativeValue = toNativeValue(value);
|
|
800
743
|
return { rillTypeName, rillTypeSignature, value: nativeValue };
|
|
801
744
|
}
|
|
@@ -812,7 +755,7 @@ function toNativeValue(value) {
|
|
|
812
755
|
return value.map(toNativeValue);
|
|
813
756
|
}
|
|
814
757
|
if (isCallable(value)) {
|
|
815
|
-
return { signature:
|
|
758
|
+
return { signature: formatStructure(inferStructure(value)) };
|
|
816
759
|
}
|
|
817
760
|
if (isTuple(value)) {
|
|
818
761
|
return value.entries.map(toNativeValue);
|
|
@@ -830,10 +773,10 @@ function toNativeValue(value) {
|
|
|
830
773
|
if (isTypeValue(value)) {
|
|
831
774
|
return {
|
|
832
775
|
name: value.typeName,
|
|
833
|
-
signature:
|
|
776
|
+
signature: formatStructure(value.structure),
|
|
834
777
|
};
|
|
835
778
|
}
|
|
836
|
-
if (
|
|
779
|
+
if (isIterator(value)) {
|
|
837
780
|
return { done: value.done };
|
|
838
781
|
}
|
|
839
782
|
// Plain dict
|
|
@@ -844,152 +787,8 @@ function toNativeValue(value) {
|
|
|
844
787
|
}
|
|
845
788
|
return result;
|
|
846
789
|
}
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
* - Primitives: value equality
|
|
850
|
-
* - Tuples: length + recursive element equality
|
|
851
|
-
* - Dicts: same keys + recursive value equality (order-independent)
|
|
852
|
-
*/
|
|
853
|
-
export function deepEquals(a, b) {
|
|
854
|
-
// Handle primitives and null
|
|
855
|
-
if (a === b)
|
|
856
|
-
return true;
|
|
857
|
-
if (a === null || b === null)
|
|
858
|
-
return false;
|
|
859
|
-
if (typeof a !== typeof b)
|
|
860
|
-
return false;
|
|
861
|
-
// Primitives (string, number, boolean) - covered by === above
|
|
862
|
-
if (typeof a !== 'object' || typeof b !== 'object')
|
|
863
|
-
return false;
|
|
864
|
-
// Both are non-null objects at this point
|
|
865
|
-
const aObj = a;
|
|
866
|
-
const bObj = b;
|
|
867
|
-
// Check for tuples (positional spread args)
|
|
868
|
-
const aIsTuple = isTuple(a);
|
|
869
|
-
const bIsTuple = isTuple(b);
|
|
870
|
-
if (aIsTuple !== bIsTuple)
|
|
871
|
-
return false;
|
|
872
|
-
if (aIsTuple && bIsTuple) {
|
|
873
|
-
if (a.entries.length !== b.entries.length)
|
|
874
|
-
return false;
|
|
875
|
-
for (let i = 0; i < a.entries.length; i++) {
|
|
876
|
-
const aVal = a.entries[i];
|
|
877
|
-
const bVal = b.entries[i];
|
|
878
|
-
if (aVal === undefined || bVal === undefined) {
|
|
879
|
-
if (aVal !== bVal)
|
|
880
|
-
return false;
|
|
881
|
-
}
|
|
882
|
-
else if (!deepEquals(aVal, bVal)) {
|
|
883
|
-
return false;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return true;
|
|
887
|
-
}
|
|
888
|
-
// Check for ordered (named spread args)
|
|
889
|
-
const aIsOrdered = isOrdered(a);
|
|
890
|
-
const bIsOrdered = isOrdered(b);
|
|
891
|
-
if (aIsOrdered !== bIsOrdered)
|
|
892
|
-
return false;
|
|
893
|
-
if (aIsOrdered && bIsOrdered) {
|
|
894
|
-
if (a.entries.length !== b.entries.length)
|
|
895
|
-
return false;
|
|
896
|
-
for (let i = 0; i < a.entries.length; i++) {
|
|
897
|
-
const aEntry = a.entries[i];
|
|
898
|
-
const bEntry = b.entries[i];
|
|
899
|
-
if (aEntry === undefined || bEntry === undefined)
|
|
900
|
-
return false;
|
|
901
|
-
if (aEntry[0] !== bEntry[0])
|
|
902
|
-
return false;
|
|
903
|
-
if (!deepEquals(aEntry[1], bEntry[1]))
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
return true;
|
|
907
|
-
}
|
|
908
|
-
// Check for vectors
|
|
909
|
-
const aIsVector = isVector(a);
|
|
910
|
-
const bIsVector = isVector(b);
|
|
911
|
-
if (aIsVector !== bIsVector)
|
|
912
|
-
return false;
|
|
913
|
-
if (aIsVector && bIsVector) {
|
|
914
|
-
// Vectors equal when model matches AND all float elements match
|
|
915
|
-
if (a.model !== b.model)
|
|
916
|
-
return false;
|
|
917
|
-
if (a.data.length !== b.data.length)
|
|
918
|
-
return false;
|
|
919
|
-
for (let i = 0; i < a.data.length; i++) {
|
|
920
|
-
const aVal = a.data[i];
|
|
921
|
-
const bVal = b.data[i];
|
|
922
|
-
if (aVal !== bVal)
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
return true;
|
|
926
|
-
}
|
|
927
|
-
// Check for type values (first-class type names)
|
|
928
|
-
const aIsTypeValue = isTypeValue(a);
|
|
929
|
-
const bIsTypeValue = isTypeValue(b);
|
|
930
|
-
if (aIsTypeValue !== bIsTypeValue)
|
|
931
|
-
return false;
|
|
932
|
-
if (aIsTypeValue && bIsTypeValue) {
|
|
933
|
-
return structuralTypeEquals(a.structure, b.structure);
|
|
934
|
-
}
|
|
935
|
-
// Check for arrays (lists)
|
|
936
|
-
const aIsArray = Array.isArray(a);
|
|
937
|
-
const bIsArray = Array.isArray(b);
|
|
938
|
-
if (aIsArray !== bIsArray)
|
|
939
|
-
return false;
|
|
940
|
-
if (aIsArray && bIsArray) {
|
|
941
|
-
if (a.length !== b.length)
|
|
942
|
-
return false;
|
|
943
|
-
for (let i = 0; i < a.length; i++) {
|
|
944
|
-
const aElem = a[i];
|
|
945
|
-
const bElem = b[i];
|
|
946
|
-
if (aElem === undefined || bElem === undefined) {
|
|
947
|
-
if (aElem !== bElem)
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
else if (!deepEquals(aElem, bElem)) {
|
|
951
|
-
return false;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return true;
|
|
955
|
-
}
|
|
956
|
-
// Both are dicts (plain objects) or callables
|
|
957
|
-
// For script callables, use structural equality (params + body AST + captured values)
|
|
958
|
-
// For runtime/application callables, use reference equality
|
|
959
|
-
if ('__type' in aObj || '__type' in bObj) {
|
|
960
|
-
// Both must be callables to be equal
|
|
961
|
-
if (!('__type' in aObj) || !('__type' in bObj))
|
|
962
|
-
return false;
|
|
963
|
-
if (aObj.__type !== 'callable' || bObj.__type !== 'callable')
|
|
964
|
-
return false;
|
|
965
|
-
// Script callables: structural equality
|
|
966
|
-
if (isScriptCallable(a) && isScriptCallable(b)) {
|
|
967
|
-
return callableEquals(a, b, deepEquals);
|
|
968
|
-
}
|
|
969
|
-
// Runtime/application callables: reference equality
|
|
970
|
-
return a === b;
|
|
971
|
-
}
|
|
972
|
-
const aDict = a;
|
|
973
|
-
const bDict = b;
|
|
974
|
-
const aKeys = Object.keys(aDict);
|
|
975
|
-
const bKeys = Object.keys(bDict);
|
|
976
|
-
if (aKeys.length !== bKeys.length)
|
|
977
|
-
return false;
|
|
978
|
-
for (const key of aKeys) {
|
|
979
|
-
if (!(key in bDict))
|
|
980
|
-
return false;
|
|
981
|
-
const aVal = aDict[key];
|
|
982
|
-
const bVal = bDict[key];
|
|
983
|
-
if (aVal === undefined || bVal === undefined) {
|
|
984
|
-
if (aVal !== bVal)
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
else if (!deepEquals(aVal, bVal)) {
|
|
988
|
-
return false;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return true;
|
|
992
|
-
}
|
|
790
|
+
/** Deep structural equality for all Rill values. Delegates to type-registrations. */
|
|
791
|
+
export const deepEquals = registryDeepEquals;
|
|
993
792
|
/** Reserved dict method names that cannot be overridden */
|
|
994
793
|
export const RESERVED_DICT_METHODS = ['keys', 'values', 'entries'];
|
|
995
794
|
/**
|
|
@@ -999,43 +798,51 @@ export const RESERVED_DICT_METHODS = ['keys', 'values', 'entries'];
|
|
|
999
798
|
export const anyTypeValue = Object.freeze({
|
|
1000
799
|
__rill_type: true,
|
|
1001
800
|
typeName: 'any',
|
|
1002
|
-
structure: {
|
|
801
|
+
structure: { kind: 'any' },
|
|
1003
802
|
});
|
|
1004
803
|
/**
|
|
1005
|
-
* Convert a
|
|
1006
|
-
* Uses the
|
|
804
|
+
* Convert a TypeStructure descriptor to a RillTypeValue.
|
|
805
|
+
* Uses the TypeStructure's `kind` field as the `typeName`.
|
|
1007
806
|
* Falls back to 'any' for compound types that lack a direct RillTypeName mapping.
|
|
1008
807
|
*/
|
|
1009
|
-
export function
|
|
808
|
+
export function structureToTypeValue(type) {
|
|
1010
809
|
const validNames = VALID_TYPE_NAMES;
|
|
1011
810
|
return Object.freeze({
|
|
1012
811
|
__rill_type: true,
|
|
1013
|
-
typeName: (validNames.includes(type.
|
|
1014
|
-
? type.
|
|
812
|
+
typeName: (validNames.includes(type.kind)
|
|
813
|
+
? type.kind
|
|
1015
814
|
: 'any'),
|
|
1016
815
|
structure: type,
|
|
1017
816
|
});
|
|
1018
817
|
}
|
|
818
|
+
/** @deprecated Use structureToTypeValue instead. */
|
|
819
|
+
export const rillTypeToTypeValue = structureToTypeValue;
|
|
1019
820
|
/**
|
|
1020
821
|
* Check if a type is a collection (dict, ordered, tuple) with defined
|
|
1021
822
|
* fields or elements. Used to decide if an empty collection can be
|
|
1022
823
|
* synthesized and hydrated.
|
|
1023
824
|
*/
|
|
1024
825
|
export function hasCollectionFields(type) {
|
|
1025
|
-
return ((type.
|
|
1026
|
-
(
|
|
1027
|
-
|
|
826
|
+
return ((type.kind === 'dict' &&
|
|
827
|
+
(!!type.fields ||
|
|
828
|
+
!!type.valueType)) ||
|
|
829
|
+
(type.kind === 'ordered' &&
|
|
830
|
+
(!!type.fields ||
|
|
831
|
+
!!type.valueType)) ||
|
|
832
|
+
(type.kind === 'tuple' &&
|
|
833
|
+
(!!type.elements ||
|
|
834
|
+
!!type.valueType)));
|
|
1028
835
|
}
|
|
1029
836
|
/**
|
|
1030
|
-
* Create an empty collection value matching the given
|
|
837
|
+
* Create an empty collection value matching the given TypeStructure.
|
|
1031
838
|
* Assumes the type is dict, ordered, or tuple.
|
|
1032
839
|
*/
|
|
1033
840
|
export function emptyForType(type) {
|
|
1034
|
-
if (type.
|
|
841
|
+
if (type.kind === 'dict')
|
|
1035
842
|
return {};
|
|
1036
|
-
if (type.
|
|
843
|
+
if (type.kind === 'ordered')
|
|
1037
844
|
return createOrdered([]);
|
|
1038
|
-
if (type.
|
|
845
|
+
if (type.kind === 'tuple')
|
|
1039
846
|
return createTuple([]);
|
|
1040
847
|
return {};
|
|
1041
848
|
}
|
|
@@ -1050,7 +857,7 @@ export function isReservedMethod(name) {
|
|
|
1050
857
|
* - next: callable - function to get next iterator
|
|
1051
858
|
* - value: any (only required when not done) - current element
|
|
1052
859
|
*/
|
|
1053
|
-
export function
|
|
860
|
+
export function isIterator(value) {
|
|
1054
861
|
if (!isDict(value))
|
|
1055
862
|
return false;
|
|
1056
863
|
const dict = value;
|
|
@@ -1063,32 +870,9 @@ export function isRillIterator(value) {
|
|
|
1063
870
|
return false;
|
|
1064
871
|
return true;
|
|
1065
872
|
}
|
|
1066
|
-
/**
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
export function deepCopyRillValue(value) {
|
|
1073
|
-
if (value === null || typeof value !== 'object') {
|
|
1074
|
-
return value;
|
|
1075
|
-
}
|
|
1076
|
-
if (Array.isArray(value)) {
|
|
1077
|
-
return value.map(deepCopyRillValue);
|
|
1078
|
-
}
|
|
1079
|
-
// Plain dict: copy recursively. Special markers (RillTuple, RillOrdered, etc.)
|
|
1080
|
-
// carry __rill_* own properties and are treated as immutable; return as-is.
|
|
1081
|
-
if (!('__rill_tuple' in value) &&
|
|
1082
|
-
!('__rill_ordered' in value) &&
|
|
1083
|
-
!('__rill_vector' in value) &&
|
|
1084
|
-
!('__rill_type' in value) &&
|
|
1085
|
-
!('__type' in value) &&
|
|
1086
|
-
!('__rill_field_descriptor' in value)) {
|
|
1087
|
-
const copy = {};
|
|
1088
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1089
|
-
copy[k] = deepCopyRillValue(v);
|
|
1090
|
-
}
|
|
1091
|
-
return copy;
|
|
1092
|
-
}
|
|
1093
|
-
return value;
|
|
1094
|
-
}
|
|
873
|
+
/** @deprecated Use isIterator instead. */
|
|
874
|
+
export const isRillIterator = isIterator;
|
|
875
|
+
/** Copy a RillValue. Delegates to type-registrations. */
|
|
876
|
+
export const copyValue = registryCopyValue;
|
|
877
|
+
/** @deprecated Use copyValue instead. */
|
|
878
|
+
export const deepCopyRillValue = copyValue;
|