@mark1russell7/coeval 0.1.0 → 0.2.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 +41 -0
- package/build/index.d.ts +832 -148
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1898 -376
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/context.ts +25 -21
- package/src/deref.ts +165 -54
- package/src/doc.ts +86 -17
- package/src/errors.ts +141 -0
- package/src/evaluate.ts +797 -281
- package/src/formula.ts +409 -0
- package/src/index.ts +72 -7
- package/src/parts.ts +140 -27
- package/src/static.ts +364 -0
- package/src/stdlib.ts +60 -33
- package/src/world.ts +96 -22
package/build/index.js
CHANGED
|
@@ -7,13 +7,16 @@ const ENVELOPE_KEYS = [
|
|
|
7
7
|
"own"
|
|
8
8
|
];
|
|
9
9
|
/**
|
|
10
|
-
* This function tells if a value is a
|
|
10
|
+
* This function tells if a value is a plain record: an object with the prototype of a plain object, or with no
|
|
11
|
+
* prototype. A list, `null`, a date, a map and an instance of a class are not plain records.
|
|
11
12
|
*
|
|
12
13
|
* @param value - The value to examine.
|
|
13
|
-
* @returns `true` for a record.
|
|
14
|
+
* @returns `true` for a plain record.
|
|
14
15
|
*/
|
|
15
16
|
function isRecord(value) {
|
|
16
|
-
|
|
17
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
18
|
+
const proto = Object.getPrototypeOf(value);
|
|
19
|
+
return proto === Object.prototype || proto === null;
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
22
|
* This function reads a field of a record. It reads only the own fields, thus `constructor` is not a field.
|
|
@@ -69,6 +72,13 @@ function typePath(type) {
|
|
|
69
72
|
function typeName(type) {
|
|
70
73
|
return typePath(type)?.join(".") ?? "(computed)";
|
|
71
74
|
}
|
|
75
|
+
function headerFits(value) {
|
|
76
|
+
if (!isTypeRef(field(value, "type")) || !Object.hasOwn(value, "value")) return false;
|
|
77
|
+
const name = field(value, "name");
|
|
78
|
+
if (name !== void 0 && typeof name !== "string") return false;
|
|
79
|
+
const own = field(value, "own");
|
|
80
|
+
return own === void 0 || isRecord(own);
|
|
81
|
+
}
|
|
72
82
|
/**
|
|
73
83
|
* This function tells if a value has the form of an envelope. That form is a record with a reference to a type
|
|
74
84
|
* and a `value`. It has no other keys than the keys of an envelope. A contract decides where an envelope is
|
|
@@ -78,13 +88,32 @@ function typeName(type) {
|
|
|
78
88
|
* @returns `true` when the value has the form of an envelope.
|
|
79
89
|
*/
|
|
80
90
|
function isEnvelope(value) {
|
|
81
|
-
if (!isRecord(value)) return false;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
if (!isRecord(value) || !headerFits(value)) return false;
|
|
92
|
+
for (const key in value) if (Object.hasOwn(value, key) && !ENVELOPE_KEYS.includes(key)) return false;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* This function reads a stored document: an envelope that also has the keys of a store, for example `{ _id, type,
|
|
97
|
+
* value }`. Only the keys in the list can be beside the keys of the envelope.
|
|
98
|
+
*
|
|
99
|
+
* @param value - The value to examine.
|
|
100
|
+
* @param header - The keys of the store that the document can have.
|
|
101
|
+
* @returns The envelope and the keys of the store, or `undefined` when the value has another form.
|
|
102
|
+
*/
|
|
103
|
+
function stored(value, header) {
|
|
104
|
+
if (!isRecord(value) || !headerFits(value)) return void 0;
|
|
105
|
+
const keys = {};
|
|
106
|
+
const own = {};
|
|
107
|
+
for (const key in value) {
|
|
108
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
109
|
+
if (ENVELOPE_KEYS.includes(key)) own[key] = value[key];
|
|
110
|
+
else if (header.includes(key)) keys[key] = value[key];
|
|
111
|
+
else return void 0;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
envelope: fromJson(own),
|
|
115
|
+
header: keys
|
|
116
|
+
};
|
|
88
117
|
}
|
|
89
118
|
/**
|
|
90
119
|
* This function makes an envelope.
|
|
@@ -144,6 +173,149 @@ function fromJson(value) {
|
|
|
144
173
|
...isRecord(own) ? { own: ownEntries } : {}
|
|
145
174
|
});
|
|
146
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* This function tells if two JSON values are equal. Equal values have the same texts, numbers and truth values,
|
|
178
|
+
* the same items in the same order, and the same fields. The order of the fields does not matter. It does not use the stack for the
|
|
179
|
+
* depth, thus a deep document cannot overflow it.
|
|
180
|
+
*
|
|
181
|
+
* @param a - The first value.
|
|
182
|
+
* @param b - The second value.
|
|
183
|
+
* @returns `true` when the values are equal.
|
|
184
|
+
*/
|
|
185
|
+
function docEquals(a, b) {
|
|
186
|
+
const stack = [[a, b]];
|
|
187
|
+
while (stack.length > 0) {
|
|
188
|
+
const [x, y] = stack.pop();
|
|
189
|
+
if (Object.is(x, y)) continue;
|
|
190
|
+
if (typeof x !== "object" || typeof y !== "object" || x === null || y === null) return false;
|
|
191
|
+
if (Array.isArray(x) !== Array.isArray(y)) return false;
|
|
192
|
+
if (Array.isArray(x)) {
|
|
193
|
+
const ys = y;
|
|
194
|
+
if (x.length !== ys.length) return false;
|
|
195
|
+
for (let index = 0; index < x.length; index += 1) stack.push([x[index], ys[index]]);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const xr = x;
|
|
199
|
+
const yr = y;
|
|
200
|
+
const keys = Object.keys(xr);
|
|
201
|
+
if (keys.length !== Object.keys(yr).length) return false;
|
|
202
|
+
for (const key of keys) {
|
|
203
|
+
if (!Object.hasOwn(yr, key)) return false;
|
|
204
|
+
stack.push([xr[key], yr[key]]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/errors.ts
|
|
211
|
+
/**
|
|
212
|
+
* This function makes a result with a value.
|
|
213
|
+
*
|
|
214
|
+
* @param value - The value.
|
|
215
|
+
* @returns The result.
|
|
216
|
+
*/
|
|
217
|
+
function ok(value) {
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
value
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* This function makes a result with an error value.
|
|
225
|
+
*
|
|
226
|
+
* @param code - The code of the error.
|
|
227
|
+
* @param reason - One sentence that tells the cause.
|
|
228
|
+
* @param more - The place and the cause of the error, when they are known.
|
|
229
|
+
* @returns The result.
|
|
230
|
+
*/
|
|
231
|
+
function fail(code, reason, more = {}) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
error: {
|
|
235
|
+
code,
|
|
236
|
+
reason,
|
|
237
|
+
...more.place === void 0 ? {} : { place: more.place },
|
|
238
|
+
...more.cause === void 0 ? {} : { cause: more.cause }
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* This function gives a failed result its place, when it has none. A result with a value, or an error with a
|
|
244
|
+
* place, stays the same object.
|
|
245
|
+
*
|
|
246
|
+
* @param result - The result.
|
|
247
|
+
* @param place - The keys from the root document to the document that failed.
|
|
248
|
+
* @returns The result with a place.
|
|
249
|
+
*/
|
|
250
|
+
function placed(result, place) {
|
|
251
|
+
if (result.ok || result.error.place !== void 0) return result;
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
error: {
|
|
255
|
+
...result.error,
|
|
256
|
+
place
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* An exception that carries an error value through code of the host. A closure of a formula throws it when the
|
|
262
|
+
* host calls the closure and the closure fails. The kernel catches it and gives its error value again.
|
|
263
|
+
*/
|
|
264
|
+
var Failure = class extends Error {
|
|
265
|
+
error;
|
|
266
|
+
/**
|
|
267
|
+
* This constructor makes the exception.
|
|
268
|
+
*
|
|
269
|
+
* @param error - The error value that the exception carries.
|
|
270
|
+
*/
|
|
271
|
+
constructor(error) {
|
|
272
|
+
super(`${error.code} ${error.reason}`);
|
|
273
|
+
this.name = "Failure";
|
|
274
|
+
this.error = error;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* This function tells if an exception is the overflow of the stack of the host.
|
|
279
|
+
*
|
|
280
|
+
* @param error - The exception.
|
|
281
|
+
* @returns `true` for a stack overflow.
|
|
282
|
+
*/
|
|
283
|
+
function isStackOverflow(error) {
|
|
284
|
+
return error instanceof RangeError && /call stack/i.test(error.message);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* This function changes an exception into an error value. A `Failure` gives its error value. A stack overflow
|
|
288
|
+
* gives `#DEPTH!`. Each other exception gives `#CALC!`, with the exception as the cause.
|
|
289
|
+
*
|
|
290
|
+
* @param error - The exception.
|
|
291
|
+
* @param who - A short text that names the code that threw, for example `the evaluator math.add`.
|
|
292
|
+
* @returns The failed result.
|
|
293
|
+
*/
|
|
294
|
+
function thrown(error, who) {
|
|
295
|
+
if (error instanceof Failure) return {
|
|
296
|
+
ok: false,
|
|
297
|
+
error: error.error
|
|
298
|
+
};
|
|
299
|
+
if (isStackOverflow(error)) return fail("#DEPTH!", `The nesting of ${who} is too deep for the stack of the host.`, { cause: error });
|
|
300
|
+
return fail("#CALC!", `${upper(who)} threw: ${describe(error)}`, { cause: error });
|
|
301
|
+
}
|
|
302
|
+
function upper(text) {
|
|
303
|
+
return text.length === 0 ? text : `${text[0]?.toUpperCase() ?? ""}${text.slice(1)}`;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* This function gives a short text for an exception or any value. It does not throw.
|
|
307
|
+
*
|
|
308
|
+
* @param value - The value.
|
|
309
|
+
* @returns The text.
|
|
310
|
+
*/
|
|
311
|
+
function describe(value) {
|
|
312
|
+
try {
|
|
313
|
+
if (value instanceof Error) return `${value.name}: ${value.message}`;
|
|
314
|
+
return String(value);
|
|
315
|
+
} catch {
|
|
316
|
+
return "(a value without a text)";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
147
319
|
//#endregion
|
|
148
320
|
//#region src/world.ts
|
|
149
321
|
/**
|
|
@@ -164,13 +336,23 @@ function isKind(entry) {
|
|
|
164
336
|
function isAlias(entry) {
|
|
165
337
|
return entry !== void 0 && entry.type === "ref" && Array.isArray(entry.value);
|
|
166
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* This function tells if an entry of the world is a namespace.
|
|
341
|
+
*
|
|
342
|
+
* @param entry - The entry.
|
|
343
|
+
* @returns `true` for a namespace.
|
|
344
|
+
*/
|
|
345
|
+
function isNamespace(entry) {
|
|
346
|
+
return entry !== void 0 && !isKind(entry) && !isAlias(entry) && Object.hasOwn(entry, "kinds");
|
|
347
|
+
}
|
|
167
348
|
function entryAt(entries, key) {
|
|
168
349
|
const name = String(key);
|
|
169
350
|
return Object.hasOwn(entries, name) ? entries[name] : void 0;
|
|
170
351
|
}
|
|
171
352
|
/**
|
|
172
353
|
* This function finds the kind that a type names. It walks the path from the root of the world, one key at a
|
|
173
|
-
* time. An alias on the way is followed until a kind answers (collapse). A cycle of aliases stops the lookup.
|
|
354
|
+
* time. An alias on the way is followed until a kind answers (collapse). A cycle of aliases stops the lookup. In a
|
|
355
|
+
* namespace with a fallback, a key that the namespace does not have gets the fallback kind.
|
|
174
356
|
*
|
|
175
357
|
* @param world - The world.
|
|
176
358
|
* @param type - The reference to the type.
|
|
@@ -187,16 +369,25 @@ function lookupKind(world, type) {
|
|
|
187
369
|
let aliases = 0;
|
|
188
370
|
for (;;) {
|
|
189
371
|
let entries = world.kinds;
|
|
372
|
+
let fallback;
|
|
190
373
|
let entry;
|
|
191
374
|
let followed = false;
|
|
192
375
|
for (let index = 0; index < path.length; index += 1) {
|
|
193
376
|
const key = path[index];
|
|
194
377
|
entry = entryAt(entries, key);
|
|
195
|
-
if (entry === void 0)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
378
|
+
if (entry === void 0) {
|
|
379
|
+
if (fallback !== void 0) return {
|
|
380
|
+
ok: true,
|
|
381
|
+
kind: fallback,
|
|
382
|
+
path,
|
|
383
|
+
aliases
|
|
384
|
+
};
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
reason: "unknown",
|
|
388
|
+
path
|
|
389
|
+
};
|
|
390
|
+
}
|
|
200
391
|
if (isAlias(entry)) {
|
|
201
392
|
aliases += 1;
|
|
202
393
|
if (aliases > 32) return {
|
|
@@ -215,12 +406,13 @@ function lookupKind(world, type) {
|
|
|
215
406
|
break;
|
|
216
407
|
}
|
|
217
408
|
if (index < path.length - 1) {
|
|
218
|
-
if (
|
|
409
|
+
if (!isNamespace(entry)) return {
|
|
219
410
|
ok: false,
|
|
220
411
|
reason: "not-a-kind",
|
|
221
412
|
path
|
|
222
413
|
};
|
|
223
414
|
entries = entry.kinds;
|
|
415
|
+
fallback = entry.fallback;
|
|
224
416
|
}
|
|
225
417
|
}
|
|
226
418
|
if (followed) continue;
|
|
@@ -254,6 +446,14 @@ function entriesAsJson(entries) {
|
|
|
254
446
|
else out[key] = entriesAsJson(entry.kinds);
|
|
255
447
|
return out;
|
|
256
448
|
}
|
|
449
|
+
function describeShape(shape) {
|
|
450
|
+
if (shape === void 0 || shape.shape === "none") return "none";
|
|
451
|
+
if (shape.shape === "term") return `a part${shape.of === void 0 ? "" : ` of ${shape.of}`}`;
|
|
452
|
+
if (shape.shape === "deep") return "each document in the value";
|
|
453
|
+
if (shape.shape === "list") return `a list${shape.of === void 0 ? "" : ` of ${shape.of}`}`;
|
|
454
|
+
if (shape.shape === "map") return `a record of parts${shape.of === void 0 ? "" : ` of ${shape.of}`}`;
|
|
455
|
+
return `the fields ${(Array.isArray(shape.fields) ? shape.fields : Object.keys(shape.fields)).join(", ")}`;
|
|
456
|
+
}
|
|
257
457
|
/**
|
|
258
458
|
* This function makes the document of a kind, so that a view can draw it as each other document.
|
|
259
459
|
*
|
|
@@ -262,18 +462,26 @@ function entriesAsJson(entries) {
|
|
|
262
462
|
* @returns The document of type `kind`.
|
|
263
463
|
*/
|
|
264
464
|
function kindDocument(name, kind) {
|
|
265
|
-
const parts = kind.parts === void 0 || kind.parts.shape === "none" ? "none" : kind.parts.shape === "list" ? `a list${kind.parts.of === void 0 ? "" : ` of ${kind.parts.of}`}` : `the fields ${kind.parts.fields.join(", ")}`;
|
|
266
465
|
return {
|
|
267
466
|
type: "kind",
|
|
268
467
|
value: {
|
|
269
468
|
name,
|
|
270
469
|
verbs: { ...kind.verbs },
|
|
271
|
-
parts,
|
|
470
|
+
parts: describeShape(kind.parts),
|
|
272
471
|
side: kind.side ?? "derive",
|
|
273
472
|
summary: kind.summary ?? ""
|
|
274
473
|
}
|
|
275
474
|
};
|
|
276
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* This function tells if a JSON value is the record of a kind.
|
|
478
|
+
*
|
|
479
|
+
* @param value - The value.
|
|
480
|
+
* @returns `true` when the value has verbs.
|
|
481
|
+
*/
|
|
482
|
+
function looksLikeKind(value) {
|
|
483
|
+
return isRecord(value) && isRecord(field(value, "verbs"));
|
|
484
|
+
}
|
|
277
485
|
//#endregion
|
|
278
486
|
//#region src/ladder.ts
|
|
279
487
|
/** The rungs of the ladder, in the order that a question visits them. */
|
|
@@ -411,8 +619,11 @@ function ask(world, doc, verb, options = {}) {
|
|
|
411
619
|
}
|
|
412
620
|
//#endregion
|
|
413
621
|
//#region src/deref.ts
|
|
414
|
-
/**
|
|
415
|
-
|
|
622
|
+
/**
|
|
623
|
+
* The brand of a host thing: an object of the host that answers the steps of a path itself. The brand is a
|
|
624
|
+
* global symbol, thus two copies of the kernel in one program know the things of each other.
|
|
625
|
+
*/
|
|
626
|
+
const THING = Symbol.for("coeval.thing");
|
|
416
627
|
/**
|
|
417
628
|
* This function tells if a thing is a host thing.
|
|
418
629
|
*
|
|
@@ -420,7 +631,7 @@ const THING = Symbol("coeval.thing");
|
|
|
420
631
|
* @returns `true` for a host thing.
|
|
421
632
|
*/
|
|
422
633
|
function isHostThing(thing) {
|
|
423
|
-
return typeof thing === "object" && thing !== null &&
|
|
634
|
+
return typeof thing === "object" && thing !== null && thing[THING] === true;
|
|
424
635
|
}
|
|
425
636
|
/** The roots of a context. Each path starts with one of them. */
|
|
426
637
|
const ROOTS = [
|
|
@@ -433,22 +644,36 @@ const ROOTS = [
|
|
|
433
644
|
function entryOf(verbs, verb) {
|
|
434
645
|
return verbs !== void 0 && Object.hasOwn(verbs, verb) ? verbs[verb] : void 0;
|
|
435
646
|
}
|
|
647
|
+
/** The own fields of a value: the items and the length of a list, and the own properties of an object. */
|
|
436
648
|
function plainStep(thing, key) {
|
|
437
649
|
if (Array.isArray(thing)) {
|
|
650
|
+
if (key === "length") return {
|
|
651
|
+
found: true,
|
|
652
|
+
value: thing.length
|
|
653
|
+
};
|
|
438
654
|
const index = typeof key === "number" ? key : /^\d+$/.test(key) ? Number(key) : -1;
|
|
439
|
-
return index >= 0
|
|
655
|
+
return index >= 0 && index < thing.length ? {
|
|
656
|
+
found: true,
|
|
657
|
+
value: thing[index]
|
|
658
|
+
} : void 0;
|
|
659
|
+
}
|
|
660
|
+
if (typeof thing === "object" && thing !== null) {
|
|
661
|
+
const name = String(key);
|
|
662
|
+
return Object.hasOwn(thing, name) ? {
|
|
663
|
+
found: true,
|
|
664
|
+
value: thing[name]
|
|
665
|
+
} : void 0;
|
|
440
666
|
}
|
|
441
|
-
if (isRecord(thing) && typeof key === "string") return field(thing, key);
|
|
442
667
|
}
|
|
443
668
|
/**
|
|
444
669
|
* This function answers one step of a path on a thing, through the ladder of the verb `deref`. A host thing
|
|
445
|
-
* answers for itself (own). A document asks its own entry, then its kind. The default goes into the value:
|
|
446
|
-
* of
|
|
670
|
+
* answers for itself (own). A document asks its own entry, then its kind. The default goes into the value: an own
|
|
671
|
+
* field of an object, or an item of a list.
|
|
447
672
|
*
|
|
448
673
|
* @param nav - The world and the code of the evaluators.
|
|
449
674
|
* @param thing - The thing that the path reached.
|
|
450
675
|
* @param key - The key of the step.
|
|
451
|
-
* @returns What the step reaches and the rung that answered, or `undefined
|
|
676
|
+
* @returns What the step reaches and the rung that answered, or `undefined` when it reaches nothing.
|
|
452
677
|
*/
|
|
453
678
|
function derefStep(nav, thing, key) {
|
|
454
679
|
if (isHostThing(thing)) {
|
|
@@ -458,9 +683,10 @@ function derefStep(nav, thing, key) {
|
|
|
458
683
|
by: "own"
|
|
459
684
|
};
|
|
460
685
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const
|
|
686
|
+
if (nav.plain !== true && isEnvelope(thing)) {
|
|
687
|
+
const envelope = thing;
|
|
688
|
+
const ownEntries = field(envelope, "own");
|
|
689
|
+
const own = isRecord(ownEntries) ? entryOf(ownEntries, "deref") : void 0;
|
|
464
690
|
const ownCode = own === void 0 ? void 0 : nav.derefs?.[own];
|
|
465
691
|
if (ownCode !== void 0) {
|
|
466
692
|
const reached = ownCode(envelope, key);
|
|
@@ -481,38 +707,43 @@ function derefStep(nav, thing, key) {
|
|
|
481
707
|
}
|
|
482
708
|
const reached = plainStep(envelope.value, key);
|
|
483
709
|
return reached === void 0 ? void 0 : {
|
|
484
|
-
thing: reached,
|
|
710
|
+
thing: reached.value,
|
|
485
711
|
by: "default"
|
|
486
712
|
};
|
|
487
713
|
}
|
|
488
714
|
const reached = plainStep(thing, key);
|
|
489
715
|
return reached === void 0 ? void 0 : {
|
|
490
|
-
thing: reached,
|
|
716
|
+
thing: reached.value,
|
|
491
717
|
by: "default"
|
|
492
718
|
};
|
|
493
719
|
}
|
|
494
720
|
/**
|
|
495
|
-
* This function tells if a thing is a reference document: an envelope of the type `ref
|
|
721
|
+
* This function tells if a thing is a reference document: an envelope of the type `ref` with a list for its path.
|
|
496
722
|
*
|
|
497
723
|
* @param thing - The thing.
|
|
498
724
|
* @returns `true` for a reference document.
|
|
499
725
|
*/
|
|
500
726
|
function isRef(thing) {
|
|
501
|
-
if (thing
|
|
502
|
-
const
|
|
503
|
-
if (envelope === void 0 || !Array.isArray(envelope.value)) return false;
|
|
504
|
-
const path = typePath(envelope.type);
|
|
727
|
+
if (!isEnvelope(thing) || !Array.isArray(thing.value)) return false;
|
|
728
|
+
const path = typePath(thing.type);
|
|
505
729
|
return path !== void 0 && path.length === 1 && path[0] === "ref";
|
|
506
730
|
}
|
|
731
|
+
const worlds = /* @__PURE__ */ new WeakMap();
|
|
507
732
|
/**
|
|
508
733
|
* This function makes the thing of the root `world`: the entries of the world as JSON. An alias is a reference
|
|
509
|
-
* document in it, thus a walk through the world follows each alias (collapse).
|
|
734
|
+
* document in it, thus a walk through the world follows each alias (collapse). The thing of a world is made one
|
|
735
|
+
* time.
|
|
510
736
|
*
|
|
511
737
|
* @param world - The world.
|
|
512
738
|
* @returns The thing.
|
|
513
739
|
*/
|
|
514
740
|
function worldThing(world) {
|
|
515
|
-
|
|
741
|
+
let thing = worlds.get(world);
|
|
742
|
+
if (thing === void 0) {
|
|
743
|
+
thing = entriesAsJson(world.kinds);
|
|
744
|
+
worlds.set(world, thing);
|
|
745
|
+
}
|
|
746
|
+
return thing;
|
|
516
747
|
}
|
|
517
748
|
/**
|
|
518
749
|
* This function makes a context. The world comes from the navigator when the call gives none.
|
|
@@ -530,37 +761,94 @@ function context(parts = {}) {
|
|
|
530
761
|
};
|
|
531
762
|
}
|
|
532
763
|
const MAX_COLLAPSE = 64;
|
|
764
|
+
const NO_HOPS = Object.freeze([]);
|
|
765
|
+
/** A value that can be a reference document: a record with the type `ref`. The full check comes after. */
|
|
766
|
+
function mayBeRef(thing) {
|
|
767
|
+
return typeof thing === "object" && thing !== null && thing.type === "ref";
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* This function resolves the common path without a record of hops: a root, then keys, through plain values. It
|
|
771
|
+
* gives `undefined` when the path needs the full algorithm. Examples are a host thing, an envelope, a reference to
|
|
772
|
+
* follow, a step document and a failure (for its exact reason).
|
|
773
|
+
*/
|
|
774
|
+
function resolveQuick(nav, ctx, path) {
|
|
775
|
+
const root = path[0];
|
|
776
|
+
if (root !== "names" && root !== "self" && root !== "event") return void 0;
|
|
777
|
+
if (nav.roots !== void 0 && !nav.roots.includes(root)) return void 0;
|
|
778
|
+
let here = ctx[root];
|
|
779
|
+
const plain = nav.plain === true;
|
|
780
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
781
|
+
if (typeof here !== "object" || here === null) return void 0;
|
|
782
|
+
if (!plain && mayBeRef(here)) return void 0;
|
|
783
|
+
if (here[THING] === true) return void 0;
|
|
784
|
+
const step = path[index];
|
|
785
|
+
if (Array.isArray(here)) {
|
|
786
|
+
if (typeof step !== "number" || !Number.isInteger(step) || step < 0 || step >= here.length) return void 0;
|
|
787
|
+
here = here[step];
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (typeof step !== "string" || !Object.hasOwn(here, step)) return void 0;
|
|
791
|
+
if (!plain && isEnvelope(here)) return void 0;
|
|
792
|
+
here = here[step];
|
|
793
|
+
}
|
|
794
|
+
if (!plain && mayBeRef(here)) return void 0;
|
|
795
|
+
return {
|
|
796
|
+
ok: true,
|
|
797
|
+
thing: here,
|
|
798
|
+
hops: NO_HOPS
|
|
799
|
+
};
|
|
800
|
+
}
|
|
533
801
|
/**
|
|
534
802
|
* This function resolves a path from a context, and records each hop.
|
|
535
803
|
*
|
|
536
804
|
* The first step is a root of the context. Each later step is a key or a step document. A key goes up the ladder
|
|
537
805
|
* of the verb `deref` on the thing that the path reached. A step document is evaluated by the walker of its kind,
|
|
538
806
|
* with the thing that the path reached. After each hop, a reference document is followed until the path reaches
|
|
539
|
-
* something else (collapse).
|
|
807
|
+
* something else (collapse), except in a plain navigation.
|
|
808
|
+
*
|
|
809
|
+
* The codes of a failure:
|
|
810
|
+
*
|
|
811
|
+
* - `#REF!`: the path has no root, or a root that is not open here.
|
|
812
|
+
* - `#NAME?`: the root `names` has no such name.
|
|
813
|
+
* - `#N/A`: a step reaches nothing.
|
|
814
|
+
* - `#STEP?`: a step is not a key and not a step document.
|
|
815
|
+
* - `#CYCLE!`: the references form a cycle.
|
|
540
816
|
*
|
|
541
817
|
* @param nav - The world and the code of the evaluators.
|
|
542
818
|
* @param ctx - The context.
|
|
543
819
|
* @param path - The steps.
|
|
820
|
+
* @param options - `hops: false` does not record the hops.
|
|
544
821
|
* @returns What the path reached and each hop, or an error.
|
|
545
822
|
*/
|
|
546
|
-
function resolve(nav, ctx, path) {
|
|
547
|
-
|
|
823
|
+
function resolve(nav, ctx, path, options = {}) {
|
|
824
|
+
if (options.hops === false) {
|
|
825
|
+
const quick = resolveQuick(nav, ctx, path);
|
|
826
|
+
if (quick !== void 0) return quick;
|
|
827
|
+
}
|
|
828
|
+
const hops = options.hops === false ? void 0 : [];
|
|
829
|
+
const done = hops ?? NO_HOPS;
|
|
830
|
+
const failed = (code, reason) => ({
|
|
831
|
+
ok: false,
|
|
832
|
+
code,
|
|
833
|
+
reason,
|
|
834
|
+
hops: done
|
|
835
|
+
});
|
|
548
836
|
let collapses = 0;
|
|
549
837
|
const collapse = (thing) => {
|
|
550
838
|
let here = thing;
|
|
839
|
+
if (nav.plain === true) return {
|
|
840
|
+
ok: true,
|
|
841
|
+
thing: here,
|
|
842
|
+
hops: done
|
|
843
|
+
};
|
|
551
844
|
while (isRef(here)) {
|
|
552
845
|
collapses += 1;
|
|
553
|
-
if (collapses > MAX_COLLAPSE) return
|
|
554
|
-
ok: false,
|
|
555
|
-
code: "#CYCLE!",
|
|
556
|
-
reason: "The references form a cycle.",
|
|
557
|
-
hops
|
|
558
|
-
};
|
|
846
|
+
if (collapses > MAX_COLLAPSE) return failed("#CYCLE!", "The references form a cycle.");
|
|
559
847
|
const inner = walk(here.value);
|
|
560
848
|
if (!inner.ok) return inner;
|
|
561
849
|
here = inner.thing;
|
|
562
|
-
hops
|
|
563
|
-
step:
|
|
850
|
+
hops?.push({
|
|
851
|
+
step: "collapse",
|
|
564
852
|
thing: here,
|
|
565
853
|
by: "collapse"
|
|
566
854
|
});
|
|
@@ -568,19 +856,15 @@ function resolve(nav, ctx, path) {
|
|
|
568
856
|
return {
|
|
569
857
|
ok: true,
|
|
570
858
|
thing: here,
|
|
571
|
-
hops
|
|
859
|
+
hops: done
|
|
572
860
|
};
|
|
573
861
|
};
|
|
574
862
|
const walk = (steps) => {
|
|
575
|
-
const
|
|
576
|
-
if (typeof root !== "string" || !ROOTS.includes(root)) return {
|
|
577
|
-
|
|
578
|
-
code: "#REF!",
|
|
579
|
-
reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
|
|
580
|
-
hops
|
|
581
|
-
};
|
|
863
|
+
const root = steps[0];
|
|
864
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) return failed("#REF!", `A path starts with a root: ${ROOTS.join(", ")}.`);
|
|
865
|
+
if (nav.roots !== void 0 && !nav.roots.includes(root)) return failed("#REF!", `The root ${root} is not open here. The open roots are ${nav.roots.join(", ")}.`);
|
|
582
866
|
let here = ctx[root];
|
|
583
|
-
hops
|
|
867
|
+
hops?.push({
|
|
584
868
|
step: root,
|
|
585
869
|
thing: here,
|
|
586
870
|
by: "root"
|
|
@@ -588,46 +872,46 @@ function resolve(nav, ctx, path) {
|
|
|
588
872
|
const first = collapse(here);
|
|
589
873
|
if (!first.ok) return first;
|
|
590
874
|
here = first.thing;
|
|
591
|
-
for (
|
|
875
|
+
for (let index = 1; index < steps.length; index += 1) {
|
|
876
|
+
const step = steps[index];
|
|
877
|
+
if (isHostThing(here) && here.resolve !== void 0 && steps.slice(index).every(isKey)) {
|
|
878
|
+
const keys = steps.slice(index);
|
|
879
|
+
const reached = here.resolve(keys);
|
|
880
|
+
if (reached === void 0) return failed("#N/A", `The path ${keys.join(".")} reaches nothing.`);
|
|
881
|
+
hops?.push({
|
|
882
|
+
step: keys,
|
|
883
|
+
thing: reached.thing,
|
|
884
|
+
by: "own"
|
|
885
|
+
});
|
|
886
|
+
return collapse(reached.thing);
|
|
887
|
+
}
|
|
592
888
|
if (isKey(step)) {
|
|
593
889
|
const reached = derefStep(nav, here, step);
|
|
594
|
-
if (reached === void 0)
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
};
|
|
600
|
-
hops.push({
|
|
890
|
+
if (reached === void 0) {
|
|
891
|
+
if (root === "names" && index === 1) return failed("#NAME?", `The context has no name ${String(step)}.`);
|
|
892
|
+
return failed("#N/A", `The step ${String(step)} reaches nothing.`);
|
|
893
|
+
}
|
|
894
|
+
hops?.push({
|
|
601
895
|
step,
|
|
602
896
|
thing: reached.thing,
|
|
603
897
|
by: reached.by
|
|
604
898
|
});
|
|
605
899
|
here = reached.thing;
|
|
606
900
|
} else {
|
|
607
|
-
const envelope =
|
|
901
|
+
const envelope = isEnvelope(step) ? step : void 0;
|
|
608
902
|
const found = envelope === void 0 ? void 0 : lookupKind(nav.world, envelope.type);
|
|
609
903
|
const name = found?.ok === true ? entryOf(found.kind.verbs, "step") : void 0;
|
|
610
904
|
const walker = name === void 0 ? void 0 : nav.walkers?.[name];
|
|
611
|
-
if (envelope === void 0 || walker === void 0) return
|
|
612
|
-
ok: false,
|
|
613
|
-
code: "#STEP?",
|
|
614
|
-
reason: "A step is a key, or a document of a kind with the verb step.",
|
|
615
|
-
hops
|
|
616
|
-
};
|
|
905
|
+
if (envelope === void 0 || walker === void 0) return failed("#STEP?", "A step is a key, or a document of a kind with the verb step.");
|
|
617
906
|
const result = walker({
|
|
618
907
|
data: envelope.value,
|
|
619
908
|
here,
|
|
620
909
|
context: ctx,
|
|
621
910
|
deref: (thing, key) => derefStep(nav, thing, key)?.thing
|
|
622
911
|
});
|
|
623
|
-
if (!result.ok) return
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
reason: result.reason,
|
|
627
|
-
hops
|
|
628
|
-
};
|
|
629
|
-
hops.push({
|
|
630
|
-
step,
|
|
912
|
+
if (!result.ok) return failed("#N/A", result.reason);
|
|
913
|
+
hops?.push({
|
|
914
|
+
step: envelope,
|
|
631
915
|
thing: result.thing,
|
|
632
916
|
by: "step"
|
|
633
917
|
});
|
|
@@ -640,19 +924,14 @@ function resolve(nav, ctx, path) {
|
|
|
640
924
|
return {
|
|
641
925
|
ok: true,
|
|
642
926
|
thing: here,
|
|
643
|
-
hops
|
|
927
|
+
hops: done
|
|
644
928
|
};
|
|
645
929
|
};
|
|
646
|
-
if (path.length === 0) return
|
|
647
|
-
ok: false,
|
|
648
|
-
code: "#REF!",
|
|
649
|
-
reason: "A path has at least one step: its root.",
|
|
650
|
-
hops
|
|
651
|
-
};
|
|
930
|
+
if (path.length === 0) return failed("#REF!", "A path has at least one step: its root.");
|
|
652
931
|
return walk(path);
|
|
653
932
|
}
|
|
654
933
|
/**
|
|
655
|
-
* This function gives the value of a thing: the
|
|
934
|
+
* This function gives the value of a thing: the value that a host thing gives, or else the thing itself.
|
|
656
935
|
*
|
|
657
936
|
* @param thing - The thing.
|
|
658
937
|
* @returns The value.
|
|
@@ -701,89 +980,173 @@ function tag(value, kind) {
|
|
|
701
980
|
}
|
|
702
981
|
//#endregion
|
|
703
982
|
//#region src/parts.ts
|
|
983
|
+
var Bad = class {
|
|
984
|
+
reason;
|
|
985
|
+
at;
|
|
986
|
+
constructor(reason, at) {
|
|
987
|
+
this.reason = reason;
|
|
988
|
+
this.at = at;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
function setOwn$1(target, key, value) {
|
|
992
|
+
Object.defineProperty(target, key, {
|
|
993
|
+
value,
|
|
994
|
+
enumerable: true,
|
|
995
|
+
writable: true,
|
|
996
|
+
configurable: true
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
function hasSlots(template) {
|
|
1000
|
+
return template.t !== "data";
|
|
1001
|
+
}
|
|
704
1002
|
/**
|
|
705
|
-
* This function splits a value into its
|
|
706
|
-
*
|
|
1003
|
+
* This function splits a value into its parts, by the shape that its kind declares. The evaluator, the view and
|
|
1004
|
+
* the static kit use the same split, thus each one sees the same parts.
|
|
707
1005
|
*
|
|
708
|
-
* @param
|
|
1006
|
+
* @param shape - The shape of the value. The default is data.
|
|
709
1007
|
* @param value - The value of the document.
|
|
710
|
-
* @returns The
|
|
1008
|
+
* @returns The parts and a way to fill their places, or the reason why the value does not have the shape.
|
|
711
1009
|
*/
|
|
712
|
-
function splitParts(
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
1010
|
+
function splitParts(shape, value) {
|
|
1011
|
+
const parts = [];
|
|
1012
|
+
const slot = (item, path, of) => {
|
|
1013
|
+
parts.push({
|
|
1014
|
+
path,
|
|
1015
|
+
key: path.join("."),
|
|
1016
|
+
item,
|
|
1017
|
+
...of === void 0 ? {} : { of }
|
|
1018
|
+
});
|
|
1019
|
+
return {
|
|
1020
|
+
t: "slot",
|
|
1021
|
+
index: parts.length - 1
|
|
1022
|
+
};
|
|
716
1023
|
};
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1024
|
+
const deep = (item, path, of) => {
|
|
1025
|
+
if (isEnvelope(item)) return slot(item, path, of);
|
|
1026
|
+
if (Array.isArray(item)) {
|
|
1027
|
+
const items = item.map((child, index) => deep(child, [...path, index], of));
|
|
1028
|
+
return items.some(hasSlots) ? {
|
|
1029
|
+
t: "list",
|
|
1030
|
+
items
|
|
1031
|
+
} : {
|
|
1032
|
+
t: "data",
|
|
1033
|
+
value: item
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
if (isRecord(item)) {
|
|
1037
|
+
const fields = Object.keys(item).map((key) => [key, deep(item[key], [...path, key], of)]);
|
|
1038
|
+
return fields.some(([, child]) => hasSlots(child)) ? {
|
|
1039
|
+
t: "record",
|
|
1040
|
+
fields
|
|
1041
|
+
} : {
|
|
1042
|
+
t: "data",
|
|
1043
|
+
value: item
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
t: "data",
|
|
1048
|
+
value: item
|
|
1049
|
+
};
|
|
721
1050
|
};
|
|
722
|
-
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
1051
|
+
const walk = (spec, item, path) => {
|
|
1052
|
+
if (spec === void 0 || spec.shape === "none") return {
|
|
1053
|
+
t: "data",
|
|
1054
|
+
value: item
|
|
726
1055
|
};
|
|
1056
|
+
if (spec.shape === "term") return slot(item, path, spec.of);
|
|
1057
|
+
if (spec.shape === "deep") return deep(item, path, spec.of);
|
|
1058
|
+
if (spec.shape === "list") {
|
|
1059
|
+
if (!Array.isArray(item)) throw new Bad("The value must be a list.", path);
|
|
1060
|
+
const each = spec.item ?? {
|
|
1061
|
+
shape: "term",
|
|
1062
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1063
|
+
};
|
|
1064
|
+
return {
|
|
1065
|
+
t: "list",
|
|
1066
|
+
items: item.map((child, index) => walk(each, child, [...path, index]))
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
1070
|
+
if (spec.shape === "map") {
|
|
1071
|
+
const each = spec.item ?? {
|
|
1072
|
+
shape: "term",
|
|
1073
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1074
|
+
};
|
|
1075
|
+
return {
|
|
1076
|
+
t: "record",
|
|
1077
|
+
fields: Object.keys(item).map((key) => [key, walk(each, item[key], [...path, key])])
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
for (const name of spec.required ?? []) if (!Object.hasOwn(item, name)) throw new Bad(`The field ${name} is missing.`, path);
|
|
1081
|
+
const named = Array.isArray(spec.fields) ? Object.fromEntries(spec.fields.map((name) => [name, {
|
|
1082
|
+
shape: "term",
|
|
1083
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1084
|
+
}])) : spec.fields;
|
|
1085
|
+
const fields = [];
|
|
1086
|
+
for (const key of Object.keys(item)) {
|
|
1087
|
+
const fieldShape = Object.hasOwn(named, key) ? named[key] : spec.rest;
|
|
1088
|
+
fields.push([key, walk(fieldShape, item[key], [...path, key])]);
|
|
1089
|
+
}
|
|
727
1090
|
return {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
parts: value.map((item, index) => of({
|
|
731
|
-
key: String(index),
|
|
732
|
-
item
|
|
733
|
-
}, spec.of))
|
|
1091
|
+
t: "record",
|
|
1092
|
+
fields
|
|
734
1093
|
};
|
|
735
|
-
}
|
|
736
|
-
if (!isRecord(value)) return {
|
|
737
|
-
ok: false,
|
|
738
|
-
reason: "The value must be a record."
|
|
739
1094
|
};
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1095
|
+
let template;
|
|
1096
|
+
try {
|
|
1097
|
+
template = walk(shape, value, []);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
if (error instanceof Bad) return {
|
|
1100
|
+
ok: false,
|
|
1101
|
+
reason: error.reason,
|
|
1102
|
+
at: error.at
|
|
1103
|
+
};
|
|
1104
|
+
throw error;
|
|
750
1105
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1106
|
+
const strip = (node) => {
|
|
1107
|
+
if (node.t === "data") return node.value;
|
|
1108
|
+
if (node.t === "slot") return void 0;
|
|
1109
|
+
if (node.t === "list") return node.items.every((child) => child.t === "slot") ? null : node.items.map((child) => strip(child) ?? null);
|
|
1110
|
+
const out = {};
|
|
1111
|
+
for (const [key, child] of node.fields) {
|
|
1112
|
+
const kept = strip(child);
|
|
1113
|
+
if (kept !== void 0) setOwn$1(out, key, kept);
|
|
1114
|
+
}
|
|
1115
|
+
return out;
|
|
1116
|
+
};
|
|
1117
|
+
const fillWith = (node, handles) => {
|
|
1118
|
+
if (node.t === "data") return node.value;
|
|
1119
|
+
if (node.t === "slot") return handles[node.index];
|
|
1120
|
+
if (node.t === "list") return node.items.map((child) => fillWith(child, handles));
|
|
1121
|
+
const out = {};
|
|
1122
|
+
for (const [key, child] of node.fields) setOwn$1(out, key, fillWith(child, handles));
|
|
1123
|
+
return out;
|
|
755
1124
|
};
|
|
756
|
-
}
|
|
757
|
-
//#endregion
|
|
758
|
-
//#region src/evaluate.ts
|
|
759
|
-
/**
|
|
760
|
-
* This function makes a result with a value.
|
|
761
|
-
*
|
|
762
|
-
* @param value - The value.
|
|
763
|
-
* @returns The result.
|
|
764
|
-
*/
|
|
765
|
-
function ok(value) {
|
|
766
1125
|
return {
|
|
767
1126
|
ok: true,
|
|
768
|
-
|
|
1127
|
+
parts,
|
|
1128
|
+
data: strip(template) ?? null,
|
|
1129
|
+
fill: (handles) => fillWith(template, handles)
|
|
769
1130
|
};
|
|
770
1131
|
}
|
|
771
1132
|
/**
|
|
772
|
-
* This function
|
|
1133
|
+
* This function tells if the path of a part matches a pattern of a scope rule. The key `"*"` of a pattern matches
|
|
1134
|
+
* each key.
|
|
773
1135
|
*
|
|
774
|
-
* @param
|
|
775
|
-
* @param
|
|
776
|
-
* @returns
|
|
1136
|
+
* @param path - The path of the part.
|
|
1137
|
+
* @param pattern - The pattern.
|
|
1138
|
+
* @returns `true` when the path matches.
|
|
777
1139
|
*/
|
|
778
|
-
function
|
|
779
|
-
return
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
};
|
|
1140
|
+
function pathMatches(path, pattern) {
|
|
1141
|
+
if (path.length !== pattern.length) return false;
|
|
1142
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
1143
|
+
const want = pattern[index];
|
|
1144
|
+
if (want !== "*" && String(want) !== String(path[index])) return false;
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
786
1147
|
}
|
|
1148
|
+
//#endregion
|
|
1149
|
+
//#region src/evaluate.ts
|
|
787
1150
|
/**
|
|
788
1151
|
* This function makes a context with names and capabilities. The other roots are empty, and an evaluation sets
|
|
789
1152
|
* the world.
|
|
@@ -801,194 +1164,557 @@ function makeContext(names = {}, caps = [], self = null) {
|
|
|
801
1164
|
});
|
|
802
1165
|
}
|
|
803
1166
|
/**
|
|
1167
|
+
* This function gives the keys of a capability or of a kind of effect: a text is split at its dots.
|
|
1168
|
+
*
|
|
1169
|
+
* @param kind - The text or the keys.
|
|
1170
|
+
* @returns The keys, as texts.
|
|
1171
|
+
*/
|
|
1172
|
+
function capKeys(kind) {
|
|
1173
|
+
return typeof kind === "string" ? kind.split(".") : kind.map(String);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
804
1176
|
* This function tells if the capabilities permit an effect. A capability permits the kind of its name, and each kind
|
|
805
|
-
*
|
|
1177
|
+
* under it: `store` permits `store.update`. The comparison is key by key, thus `fs.read` does not permit the
|
|
1178
|
+
* kind with the keys `fs` and `read.json`.
|
|
806
1179
|
*
|
|
807
1180
|
* @param caps - The capabilities of the context.
|
|
808
|
-
* @param kind - The kind of the effect.
|
|
1181
|
+
* @param kind - The kind of the effect: a text with dots, or its keys.
|
|
809
1182
|
* @returns `true` when one capability permits it.
|
|
810
1183
|
*/
|
|
811
1184
|
function permits(caps, kind) {
|
|
812
|
-
|
|
1185
|
+
const want = capKeys(kind);
|
|
1186
|
+
return caps.some((cap) => {
|
|
1187
|
+
const have = capKeys(cap);
|
|
1188
|
+
return have.length <= want.length && have.every((key, index) => key === want[index]);
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* This function narrows a context for a part: the part keeps only the capabilities that the context of its parent
|
|
1193
|
+
* permits. Thus an evaluator cannot give a part more effects than it has.
|
|
1194
|
+
*
|
|
1195
|
+
* @param parent - The context of the parent.
|
|
1196
|
+
* @param next - The context that the evaluator gives the part.
|
|
1197
|
+
* @returns The context of the part.
|
|
1198
|
+
*/
|
|
1199
|
+
function narrow(parent, next) {
|
|
1200
|
+
if (next === parent || next.caps === parent.caps) return next;
|
|
1201
|
+
const caps = next.caps.filter((cap) => permits(parent.caps, cap));
|
|
1202
|
+
return caps.length === next.caps.length ? next : {
|
|
1203
|
+
...next,
|
|
1204
|
+
caps
|
|
1205
|
+
};
|
|
813
1206
|
}
|
|
814
1207
|
function selfLabel(self) {
|
|
815
|
-
|
|
816
|
-
|
|
1208
|
+
try {
|
|
1209
|
+
if (isHostThing(self)) return self.label;
|
|
1210
|
+
if (self === null || self === void 0) return "none";
|
|
1211
|
+
return (JSON.stringify(self) ?? describe(self)).slice(0, 40);
|
|
1212
|
+
} catch {
|
|
1213
|
+
return describe(self).slice(0, 40);
|
|
1214
|
+
}
|
|
817
1215
|
}
|
|
818
1216
|
const NO_ANSWER = {
|
|
819
1217
|
verb: "eval",
|
|
820
1218
|
error: "no-answer",
|
|
821
1219
|
steps: []
|
|
822
1220
|
};
|
|
1221
|
+
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
1222
|
+
var Session = class {
|
|
1223
|
+
machine;
|
|
1224
|
+
running;
|
|
1225
|
+
traced;
|
|
1226
|
+
perform;
|
|
1227
|
+
signal;
|
|
1228
|
+
effects;
|
|
1229
|
+
tick = 0;
|
|
1230
|
+
nextId = 0;
|
|
1231
|
+
current;
|
|
1232
|
+
holder;
|
|
1233
|
+
constructor(machine, running, traced, perform, signal, effects) {
|
|
1234
|
+
this.machine = machine;
|
|
1235
|
+
this.running = running;
|
|
1236
|
+
this.traced = traced;
|
|
1237
|
+
this.perform = perform;
|
|
1238
|
+
this.signal = signal;
|
|
1239
|
+
this.effects = effects ?? [];
|
|
1240
|
+
this.holder = traced ? holderNode() : void 0;
|
|
1241
|
+
this.current = this.holder;
|
|
1242
|
+
}
|
|
1243
|
+
resolve(ctx, path, options) {
|
|
1244
|
+
return resolve(this.machine, ctx, path, { hops: options?.hops ?? this.traced });
|
|
1245
|
+
}
|
|
1246
|
+
permitted(ctx, keys) {
|
|
1247
|
+
return this.machine.permits === void 0 ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
|
|
1248
|
+
}
|
|
1249
|
+
record(ctx, kind, payload, place) {
|
|
1250
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
1251
|
+
const text = keys.join(".");
|
|
1252
|
+
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
1253
|
+
const node = this.current;
|
|
1254
|
+
const effect = {
|
|
1255
|
+
index: this.effects.length,
|
|
1256
|
+
kind: text,
|
|
1257
|
+
path: keys,
|
|
1258
|
+
payload,
|
|
1259
|
+
node: node?.id ?? -1,
|
|
1260
|
+
at: this.tick,
|
|
1261
|
+
place,
|
|
1262
|
+
context: ctx
|
|
1263
|
+
};
|
|
1264
|
+
this.effects.push(effect);
|
|
1265
|
+
if (node !== void 0) node.effects.push(effect.index);
|
|
1266
|
+
return effect;
|
|
1267
|
+
}
|
|
1268
|
+
effect(ctx, kind, payload, place) {
|
|
1269
|
+
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
1270
|
+
if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
1271
|
+
const made = this.record(ctx, kind, payload, place);
|
|
1272
|
+
return "ok" in made ? made : ok(payload);
|
|
1273
|
+
}
|
|
1274
|
+
*runEffect(ctx, kind, payload, place) {
|
|
1275
|
+
const made = this.record(ctx, kind, payload, place);
|
|
1276
|
+
if ("ok" in made) return made;
|
|
1277
|
+
const perform = this.perform;
|
|
1278
|
+
if (!this.running || perform === void 0) return ok(payload);
|
|
1279
|
+
const failed = (error) => fail("#CALC!", `The host failed the effect ${made.kind}: ${describe(error)}`, {
|
|
1280
|
+
place,
|
|
1281
|
+
cause: error
|
|
1282
|
+
});
|
|
1283
|
+
let done;
|
|
1284
|
+
try {
|
|
1285
|
+
done = perform(made);
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
return failed(error);
|
|
1288
|
+
}
|
|
1289
|
+
if (!(done instanceof Promise)) return ok(done);
|
|
1290
|
+
return yield done.then((value) => ok(value), failed);
|
|
1291
|
+
}
|
|
1292
|
+
*all(list, options = {}) {
|
|
1293
|
+
const results = new Array(list.length);
|
|
1294
|
+
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
1295
|
+
const active = [];
|
|
1296
|
+
let next = 0;
|
|
1297
|
+
const advance = (index, steps, input) => {
|
|
1298
|
+
let step;
|
|
1299
|
+
try {
|
|
1300
|
+
step = input.threw ? steps.throw(input.error) : steps.next(input.value);
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
results[index] = thrown(error, "a part");
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (step.done === true) {
|
|
1306
|
+
results[index] = step.value;
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const item = {
|
|
1310
|
+
index,
|
|
1311
|
+
steps,
|
|
1312
|
+
pending: Promise.resolve()
|
|
1313
|
+
};
|
|
1314
|
+
item.pending = step.value.then((value) => ({
|
|
1315
|
+
item,
|
|
1316
|
+
value,
|
|
1317
|
+
threw: false
|
|
1318
|
+
}), (error) => ({
|
|
1319
|
+
item,
|
|
1320
|
+
error,
|
|
1321
|
+
threw: true
|
|
1322
|
+
}));
|
|
1323
|
+
active.push(item);
|
|
1324
|
+
};
|
|
1325
|
+
const start = () => {
|
|
1326
|
+
while (active.length < limit && next < list.length) {
|
|
1327
|
+
const index = next;
|
|
1328
|
+
next += 1;
|
|
1329
|
+
const begin = list[index];
|
|
1330
|
+
let steps;
|
|
1331
|
+
try {
|
|
1332
|
+
steps = begin();
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
results[index] = thrown(error, "a part");
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
advance(index, steps, { threw: false });
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
start();
|
|
1341
|
+
while (active.length > 0) {
|
|
1342
|
+
const settled = yield Promise.race(active.map((item) => item.pending));
|
|
1343
|
+
active.splice(active.indexOf(settled.item), 1);
|
|
1344
|
+
advance(settled.item.index, settled.item.steps, settled);
|
|
1345
|
+
start();
|
|
1346
|
+
}
|
|
1347
|
+
return results;
|
|
1348
|
+
}
|
|
1349
|
+
enter(meta, ctx) {
|
|
1350
|
+
const node = {
|
|
1351
|
+
id: this.nextId,
|
|
1352
|
+
path: meta.path,
|
|
1353
|
+
doc: meta.doc,
|
|
1354
|
+
declared: meta.declared,
|
|
1355
|
+
answer: meta.answer,
|
|
1356
|
+
side: meta.side,
|
|
1357
|
+
names: Object.keys(ctx.names),
|
|
1358
|
+
scope: ctx.names,
|
|
1359
|
+
caps: ctx.caps,
|
|
1360
|
+
self: selfLabel(ctx.self),
|
|
1361
|
+
enter: this.tick,
|
|
1362
|
+
exit: this.tick,
|
|
1363
|
+
result: fail("#NOEVAL", "The evaluation did not end."),
|
|
1364
|
+
children: [],
|
|
1365
|
+
effects: []
|
|
1366
|
+
};
|
|
1367
|
+
this.nextId += 1;
|
|
1368
|
+
this.tick += 1;
|
|
1369
|
+
(this.current ?? this.holder).children.push(node);
|
|
1370
|
+
return node;
|
|
1371
|
+
}
|
|
1372
|
+
exit(node, result) {
|
|
1373
|
+
node.result = result;
|
|
1374
|
+
node.exit = this.tick;
|
|
1375
|
+
this.tick += 1;
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
function holderNode() {
|
|
1379
|
+
return {
|
|
1380
|
+
id: -1,
|
|
1381
|
+
path: [],
|
|
1382
|
+
doc: {
|
|
1383
|
+
type: "?",
|
|
1384
|
+
value: null
|
|
1385
|
+
},
|
|
1386
|
+
declared: false,
|
|
1387
|
+
answer: NO_ANSWER,
|
|
1388
|
+
side: "derive",
|
|
1389
|
+
names: [],
|
|
1390
|
+
scope: {},
|
|
1391
|
+
caps: [],
|
|
1392
|
+
self: "none",
|
|
1393
|
+
enter: 0,
|
|
1394
|
+
exit: 0,
|
|
1395
|
+
result: ok(null),
|
|
1396
|
+
children: [],
|
|
1397
|
+
effects: []
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
823
1400
|
/** This function drives steps that cannot wait. */
|
|
824
1401
|
function drive(steps) {
|
|
825
1402
|
const step = steps.next();
|
|
826
1403
|
if (step.done === true) return step.value;
|
|
827
1404
|
step.value.catch(() => void 0);
|
|
1405
|
+
steps.return(fail("#CALC!", "A part waited, and its parent cannot wait."));
|
|
828
1406
|
return fail("#CALC!", "A part waited, and its parent cannot wait.");
|
|
829
1407
|
}
|
|
1408
|
+
const ABORTED = Symbol("aborted");
|
|
830
1409
|
/** This function drives steps that can wait. The steps before the first wait happen at once, in the call. */
|
|
831
|
-
async function driveWaiting(steps) {
|
|
1410
|
+
async function driveWaiting(steps, signal) {
|
|
1411
|
+
const abort = () => {
|
|
1412
|
+
const reason = fail("#ABORT", `The host stopped the run: ${describe(signal?.reason ?? "no reason")}`, { cause: signal?.reason });
|
|
1413
|
+
try {
|
|
1414
|
+
steps.return(reason);
|
|
1415
|
+
} catch {}
|
|
1416
|
+
return reason;
|
|
1417
|
+
};
|
|
1418
|
+
const aborted = signal === void 0 ? void 0 : new Promise((resolve) => {
|
|
1419
|
+
if (signal.aborted) resolve(ABORTED);
|
|
1420
|
+
else signal.addEventListener("abort", () => resolve(ABORTED), { once: true });
|
|
1421
|
+
});
|
|
1422
|
+
if (signal?.aborted === true) return abort();
|
|
832
1423
|
let step = steps.next();
|
|
833
1424
|
while (step.done !== true) {
|
|
834
1425
|
let value;
|
|
835
1426
|
try {
|
|
836
|
-
value = await step.value;
|
|
1427
|
+
value = aborted === void 0 ? await step.value : await Promise.race([step.value, aborted]);
|
|
837
1428
|
} catch (error) {
|
|
838
1429
|
step = steps.throw(error);
|
|
839
1430
|
continue;
|
|
840
1431
|
}
|
|
1432
|
+
if (value === ABORTED) return abort();
|
|
841
1433
|
step = steps.next(value);
|
|
842
1434
|
}
|
|
843
1435
|
return step.value;
|
|
844
1436
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1437
|
+
function isCompiledEvaluator(evaluator) {
|
|
1438
|
+
return typeof evaluator === "object" && evaluator !== null && typeof evaluator.compile === "function";
|
|
1439
|
+
}
|
|
1440
|
+
function isCompiledRunner(runner) {
|
|
1441
|
+
return typeof runner === "object" && runner !== null && typeof runner.compileRun === "function";
|
|
1442
|
+
}
|
|
1443
|
+
function failing(c, meta, place, code, reason) {
|
|
1444
|
+
const result = fail(code, reason, { place });
|
|
1445
|
+
const plain = () => result;
|
|
1446
|
+
return finish(c, meta, plain, function* () {
|
|
1447
|
+
return result;
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
function finish(c, meta, code, run) {
|
|
1451
|
+
if (!c.traced) return {
|
|
1452
|
+
code,
|
|
1453
|
+
run
|
|
1454
|
+
};
|
|
1455
|
+
const tracedCode = (ctx, rt) => {
|
|
1456
|
+
const s = rt;
|
|
1457
|
+
const node = s.enter(meta, ctx);
|
|
1458
|
+
const before = s.current;
|
|
1459
|
+
s.current = node;
|
|
1460
|
+
let result;
|
|
1461
|
+
try {
|
|
1462
|
+
result = code(ctx, rt);
|
|
1463
|
+
} finally {
|
|
1464
|
+
s.current = before;
|
|
1465
|
+
}
|
|
1466
|
+
s.exit(node, result);
|
|
1467
|
+
return result;
|
|
1468
|
+
};
|
|
1469
|
+
const tracedRun = function* (ctx, rt) {
|
|
1470
|
+
const s = rt;
|
|
1471
|
+
const node = s.enter(meta, ctx);
|
|
1472
|
+
const steps = run(ctx, rt);
|
|
1473
|
+
let input;
|
|
1474
|
+
let threw = false;
|
|
1475
|
+
for (;;) {
|
|
1476
|
+
const before = s.current;
|
|
1477
|
+
s.current = node;
|
|
1478
|
+
let step;
|
|
1479
|
+
try {
|
|
1480
|
+
step = threw ? steps.throw(input) : steps.next(input);
|
|
1481
|
+
} finally {
|
|
1482
|
+
s.current = before;
|
|
1483
|
+
}
|
|
1484
|
+
if (step.done === true) {
|
|
1485
|
+
s.exit(node, step.value);
|
|
1486
|
+
return step.value;
|
|
1487
|
+
}
|
|
1488
|
+
try {
|
|
1489
|
+
input = yield step.value;
|
|
1490
|
+
threw = false;
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
input = error;
|
|
1493
|
+
threw = true;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
return {
|
|
1498
|
+
code: tracedCode,
|
|
1499
|
+
run: tracedRun
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
function compileNode(c, value, place, declared, depth) {
|
|
1503
|
+
const envelope = isEnvelope(value) ? value : void 0;
|
|
1504
|
+
const doc = envelope ?? {
|
|
1505
|
+
type: declared ?? "?",
|
|
1506
|
+
value
|
|
1507
|
+
};
|
|
1508
|
+
const meta0 = {
|
|
1509
|
+
path: place.map(String),
|
|
851
1510
|
doc,
|
|
852
1511
|
declared: envelope === void 0,
|
|
853
1512
|
answer: NO_ANSWER,
|
|
854
|
-
side: "derive"
|
|
855
|
-
names: Object.keys(ctx.names),
|
|
856
|
-
scope: ctx.names,
|
|
857
|
-
caps: ctx.caps,
|
|
858
|
-
self: selfLabel(ctx.self),
|
|
859
|
-
enter: s.tick,
|
|
860
|
-
exit: s.tick,
|
|
861
|
-
result: fail("#NOEVAL", "The evaluation did not end."),
|
|
862
|
-
children: [],
|
|
863
|
-
effects: []
|
|
1513
|
+
side: "derive"
|
|
864
1514
|
};
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1515
|
+
if (envelope === void 0 && declared === void 0) return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
1516
|
+
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1517
|
+
const answer = ask(c.machine.world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
1518
|
+
const found = lookupKind(c.machine.world, doc.type);
|
|
1519
|
+
const kind = found.ok ? found.kind : void 0;
|
|
1520
|
+
const meta = {
|
|
1521
|
+
...meta0,
|
|
1522
|
+
answer,
|
|
1523
|
+
side: kind?.side ?? "derive"
|
|
873
1524
|
};
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (path.length > maxDepth) return finish(fail("#DEPTH!", `The nesting is deeper than ${maxDepth} documents.`));
|
|
877
|
-
const answer = ask(s.machine.world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
878
|
-
node.answer = answer;
|
|
879
|
-
const kind = kindOf(s.machine.world, doc.type);
|
|
880
|
-
node.side = kind?.side ?? "derive";
|
|
881
|
-
if (answer.error === "unknown-kind") return finish(fail("#KIND?", `The world has no kind ${typeName(doc.type)}.`));
|
|
1525
|
+
const kindText = typeName(doc.type);
|
|
1526
|
+
if (answer.error === "unknown-kind") return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
|
|
882
1527
|
const name = answer.evaluator;
|
|
883
|
-
const runners =
|
|
1528
|
+
const runners = c.machine.runners ?? {};
|
|
884
1529
|
const runner = name !== void 0 && Object.hasOwn(runners, name) ? runners[name] : void 0;
|
|
885
|
-
const
|
|
886
|
-
if (name === void 0 || runner === void 0 &&
|
|
887
|
-
if (runner !== void 0 && s.running && !canWait) return finish(fail("#CALC!", `${typeName(doc.type)} waits for effects, and its parent cannot wait. Give the parent a runner.`));
|
|
1530
|
+
const evaluator = name !== void 0 && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : void 0;
|
|
1531
|
+
if (name === void 0 || runner === void 0 && evaluator === void 0) return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
888
1532
|
const split = splitParts(kind?.parts, doc.value);
|
|
889
|
-
if (!split.ok)
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1533
|
+
if (!split.ok) {
|
|
1534
|
+
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1535
|
+
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
1536
|
+
}
|
|
1537
|
+
const parts = split.parts.map((slot) => {
|
|
1538
|
+
const compiled = compileNode(c, slot.item, [...place, ...slot.path], slot.of, depth + 1);
|
|
1539
|
+
const header = isEnvelope(slot.item) && isRecord(slot.item) && typeof slot.item["name"] === "string" ? slot.item["name"] : void 0;
|
|
1540
|
+
return {
|
|
1541
|
+
key: slot.key,
|
|
1542
|
+
path: slot.path,
|
|
1543
|
+
...header === void 0 ? {} : { name: header },
|
|
1544
|
+
code: compiled.code,
|
|
1545
|
+
run: compiled.run
|
|
902
1546
|
};
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1547
|
+
});
|
|
1548
|
+
const input = {
|
|
1549
|
+
kind: kindText,
|
|
1550
|
+
typePath: found.ok ? found.path : [],
|
|
1551
|
+
doc,
|
|
1552
|
+
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
1553
|
+
data: split.data,
|
|
1554
|
+
parts,
|
|
1555
|
+
place,
|
|
1556
|
+
machine: c.machine
|
|
906
1557
|
};
|
|
907
|
-
const
|
|
908
|
-
const resolveHere = (steps) => resolve(s.machine, ctx, steps);
|
|
1558
|
+
const who = `the evaluator ${name}`;
|
|
909
1559
|
if (runner !== void 0) {
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
kind: kindText,
|
|
917
|
-
data: split.data,
|
|
918
|
-
parts,
|
|
919
|
-
context: ctx,
|
|
920
|
-
resolve: resolveHere,
|
|
921
|
-
effect: function* (effectKind, payload) {
|
|
922
|
-
const made = record(effectKind, payload);
|
|
923
|
-
if ("ok" in made) return made;
|
|
924
|
-
if (!s.running || s.perform === void 0) return ok(payload);
|
|
925
|
-
const failed = (error) => fail("#CALC!", `The host failed the effect ${effectKind}: ${String(error)}`);
|
|
926
|
-
let done;
|
|
927
|
-
try {
|
|
928
|
-
done = s.perform(made);
|
|
929
|
-
} catch (error) {
|
|
930
|
-
return failed(error);
|
|
931
|
-
}
|
|
932
|
-
if (!(done instanceof Promise)) return ok(done ?? null);
|
|
933
|
-
return yield done.then((result) => ok(result ?? null), failed);
|
|
1560
|
+
const inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
1561
|
+
const run = function* (ctx, rt) {
|
|
1562
|
+
try {
|
|
1563
|
+
return placed(yield* inner(ctx, rt), place);
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
return placed(thrown(error, `the runner ${name}`), place);
|
|
934
1566
|
}
|
|
935
1567
|
};
|
|
1568
|
+
const code = (ctx, rt) => rt.running ? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place }) : drive(run(ctx, rt));
|
|
1569
|
+
return finish(c, meta, code, run);
|
|
1570
|
+
}
|
|
1571
|
+
const inner = isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill);
|
|
1572
|
+
const code = (ctx, rt) => {
|
|
936
1573
|
try {
|
|
937
|
-
return
|
|
1574
|
+
return placed(inner(ctx, rt), place);
|
|
938
1575
|
} catch (error) {
|
|
939
|
-
return
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
const parts = slots.map(({ slot, header }) => ({
|
|
943
|
-
key: slot.key,
|
|
944
|
-
...header?.name === void 0 ? {} : { name: header.name },
|
|
945
|
-
evaluate: (override) => drive(visit(s, slot.item, [...path, slot.key], slot.of, override ?? ctx, node, false))
|
|
946
|
-
}));
|
|
947
|
-
const input = {
|
|
948
|
-
kind: kindText,
|
|
949
|
-
data: split.data,
|
|
950
|
-
parts,
|
|
951
|
-
context: ctx,
|
|
952
|
-
resolve: resolveHere,
|
|
953
|
-
effect: (effectKind, payload) => {
|
|
954
|
-
if (s.running) return fail("#CALC!", `The effect ${effectKind} needs a runner: in a run, the host does each effect.`);
|
|
955
|
-
const made = record(effectKind, payload);
|
|
956
|
-
return "ok" in made ? made : ok(payload);
|
|
1576
|
+
return placed(thrown(error, who), place);
|
|
957
1577
|
}
|
|
958
1578
|
};
|
|
1579
|
+
return finish(c, meta, code, function* (ctx, rt) {
|
|
1580
|
+
return code(ctx, rt);
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
function interpret(fn, input, fill) {
|
|
1584
|
+
const { kind, typePath, doc, data, place } = input;
|
|
1585
|
+
const compiled = input.parts;
|
|
1586
|
+
const hasParts = compiled.length > 0;
|
|
1587
|
+
return (ctx, rt) => {
|
|
1588
|
+
const parts = compiled.map((part) => ({
|
|
1589
|
+
key: part.key,
|
|
1590
|
+
path: part.path,
|
|
1591
|
+
...part.name === void 0 ? {} : { name: part.name },
|
|
1592
|
+
evaluate: (override) => part.code(override === void 0 ? ctx : narrow(ctx, override), rt)
|
|
1593
|
+
}));
|
|
1594
|
+
return fn({
|
|
1595
|
+
kind,
|
|
1596
|
+
typePath,
|
|
1597
|
+
doc,
|
|
1598
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
1599
|
+
data,
|
|
1600
|
+
parts,
|
|
1601
|
+
context: ctx,
|
|
1602
|
+
place,
|
|
1603
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1604
|
+
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place)
|
|
1605
|
+
});
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
function interpretRun(fn, input, fill) {
|
|
1609
|
+
const { kind, typePath, doc, data, place } = input;
|
|
1610
|
+
const compiled = input.parts;
|
|
1611
|
+
const hasParts = compiled.length > 0;
|
|
1612
|
+
return (ctx, rt) => {
|
|
1613
|
+
const parts = compiled.map((part) => ({
|
|
1614
|
+
key: part.key,
|
|
1615
|
+
path: part.path,
|
|
1616
|
+
...part.name === void 0 ? {} : { name: part.name },
|
|
1617
|
+
evaluate: (override) => part.run(override === void 0 ? ctx : narrow(ctx, override), rt)
|
|
1618
|
+
}));
|
|
1619
|
+
return fn({
|
|
1620
|
+
kind,
|
|
1621
|
+
typePath,
|
|
1622
|
+
doc,
|
|
1623
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
1624
|
+
data,
|
|
1625
|
+
parts,
|
|
1626
|
+
context: ctx,
|
|
1627
|
+
place,
|
|
1628
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1629
|
+
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
1630
|
+
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options)
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function rootContext(machine, ctx) {
|
|
1635
|
+
return ctx.world === null ? {
|
|
1636
|
+
...ctx,
|
|
1637
|
+
world: worldThing(machine.world)
|
|
1638
|
+
} : ctx;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* This function compiles a document into a plan. The ladder, the kinds and the parts are known after it. A plan
|
|
1642
|
+
* evaluates many times. A document that does not compile gives a plan that fails with the reason, when it evaluates.
|
|
1643
|
+
*
|
|
1644
|
+
* @param machine - The world and the code of the evaluators.
|
|
1645
|
+
* @param doc - The document, as JSON.
|
|
1646
|
+
* @param options - The type of a bare root value, and a trace.
|
|
1647
|
+
* @returns The plan.
|
|
1648
|
+
*/
|
|
1649
|
+
function prepare(machine, doc, options = {}) {
|
|
1650
|
+
const c = {
|
|
1651
|
+
machine,
|
|
1652
|
+
traced: options.trace === true,
|
|
1653
|
+
maxDepth: machine.maxDepth ?? 1e3
|
|
1654
|
+
};
|
|
1655
|
+
let root;
|
|
959
1656
|
try {
|
|
960
|
-
|
|
1657
|
+
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
961
1658
|
} catch (error) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
function
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1659
|
+
const result = thrown(error, "the compilation");
|
|
1660
|
+
root = {
|
|
1661
|
+
code: () => result,
|
|
1662
|
+
run: function* () {
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
const compiled = {
|
|
1668
|
+
key: "",
|
|
1669
|
+
path: [],
|
|
1670
|
+
code: root.code,
|
|
1671
|
+
run: root.run
|
|
1672
|
+
};
|
|
1673
|
+
return {
|
|
1674
|
+
machine,
|
|
1675
|
+
traced: c.traced,
|
|
1676
|
+
root: compiled,
|
|
1677
|
+
evaluate(context = makeContext(), evalOptions = {}) {
|
|
1678
|
+
const s = new Session(machine, false, c.traced, void 0, void 0, evalOptions.effects);
|
|
1679
|
+
return root.code(rootContext(machine, context), s);
|
|
1680
|
+
},
|
|
1681
|
+
run(context = makeContext(), runOptions = {}) {
|
|
1682
|
+
const s = new Session(machine, true, c.traced, runOptions.perform, runOptions.signal, void 0);
|
|
1683
|
+
return driveWaiting(root.run(rootContext(machine, context), s), runOptions.signal);
|
|
1684
|
+
}
|
|
982
1685
|
};
|
|
983
1686
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1687
|
+
/**
|
|
1688
|
+
* This function makes a cache of plans for one machine. A document object compiles one time. Use it only for
|
|
1689
|
+
* documents that do not change after they are made.
|
|
1690
|
+
*
|
|
1691
|
+
* @param machine - The machine.
|
|
1692
|
+
* @param options - The options of each compilation.
|
|
1693
|
+
* @returns A function that gives the plan of a document.
|
|
1694
|
+
*/
|
|
1695
|
+
function planner(machine, options = {}) {
|
|
1696
|
+
const objects = /* @__PURE__ */ new WeakMap();
|
|
1697
|
+
const values = /* @__PURE__ */ new Map();
|
|
1698
|
+
return (doc) => {
|
|
1699
|
+
if (typeof doc === "object" && doc !== null) {
|
|
1700
|
+
let plan = objects.get(doc);
|
|
1701
|
+
if (plan === void 0) {
|
|
1702
|
+
plan = prepare(machine, doc, options);
|
|
1703
|
+
objects.set(doc, plan);
|
|
1704
|
+
}
|
|
1705
|
+
return plan;
|
|
1706
|
+
}
|
|
1707
|
+
let plan = values.get(doc);
|
|
1708
|
+
if (plan === void 0) {
|
|
1709
|
+
plan = prepare(machine, doc, options);
|
|
1710
|
+
if (values.size > 1e3) values.clear();
|
|
1711
|
+
values.set(doc, plan);
|
|
1712
|
+
}
|
|
1713
|
+
return plan;
|
|
1714
|
+
};
|
|
989
1715
|
}
|
|
990
|
-
function record(s,
|
|
991
|
-
const root = holder
|
|
1716
|
+
function record(s, result) {
|
|
1717
|
+
const root = s.holder?.children[0];
|
|
992
1718
|
if (root === void 0) throw new Error("The evaluation made no trace.");
|
|
993
1719
|
return {
|
|
994
1720
|
result,
|
|
@@ -1011,39 +1737,31 @@ function record(s, holder, result) {
|
|
|
1011
1737
|
* @returns The result, the trace and the effects in their order.
|
|
1012
1738
|
*/
|
|
1013
1739
|
function evaluate(machine, doc, context = makeContext(), declaredType) {
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
nextId: 0
|
|
1021
|
-
};
|
|
1022
|
-
const holder = holderNode();
|
|
1023
|
-
return record(s, holder, drive(visit(s, doc, [], declaredType, rootContext(machine, context), holder, false)));
|
|
1740
|
+
const plan = prepare(machine, doc, {
|
|
1741
|
+
trace: true,
|
|
1742
|
+
...declaredType === void 0 ? {} : { declaredType }
|
|
1743
|
+
});
|
|
1744
|
+
const s = new Session(machine, false, true, void 0, void 0, void 0);
|
|
1745
|
+
return record(s, plan.root.code(rootContext(machine, context), s));
|
|
1024
1746
|
}
|
|
1025
1747
|
/**
|
|
1026
1748
|
* This function runs a document: an evaluation in which the host does each effect. A runner waits for its
|
|
1027
1749
|
* parts and for its effects, thus a step can read the result of the step before. The part of the run before the
|
|
1028
|
-
* first wait happens at once, in the call.
|
|
1750
|
+
* first wait happens at once, in the call. The run records a trace.
|
|
1029
1751
|
*
|
|
1030
1752
|
* @param machine - The world, the evaluators and the runners.
|
|
1031
1753
|
* @param doc - The document, as JSON.
|
|
1032
1754
|
* @param context - The names and the capabilities at the root.
|
|
1033
|
-
* @param options - The host code that does the effects,
|
|
1755
|
+
* @param options - The host code that does the effects, the type of a bare root value, and a signal.
|
|
1034
1756
|
* @returns The result, the trace and the effects in their order, when the run ends.
|
|
1035
1757
|
*/
|
|
1036
1758
|
async function run(machine, doc, context = makeContext(), options = {}) {
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
nextId: 0
|
|
1044
|
-
};
|
|
1045
|
-
const holder = holderNode();
|
|
1046
|
-
return record(s, holder, await driveWaiting(visit(s, doc, [], options.declaredType, rootContext(machine, context), holder, true)));
|
|
1759
|
+
const plan = prepare(machine, doc, {
|
|
1760
|
+
trace: true,
|
|
1761
|
+
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType }
|
|
1762
|
+
});
|
|
1763
|
+
const s = new Session(machine, true, true, options.perform, options.signal, void 0);
|
|
1764
|
+
return record(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
1047
1765
|
}
|
|
1048
1766
|
/**
|
|
1049
1767
|
* This function finds a node of a trace by its path.
|
|
@@ -1053,10 +1771,16 @@ async function run(machine, doc, context = makeContext(), options = {}) {
|
|
|
1053
1771
|
* @returns The first evaluation at that path, or `undefined` when the document did not evaluate.
|
|
1054
1772
|
*/
|
|
1055
1773
|
function nodeAt(trace, path) {
|
|
1774
|
+
const want = path.map(String);
|
|
1056
1775
|
let node = trace;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1776
|
+
let at = 0;
|
|
1777
|
+
while (node !== void 0 && at < want.length) {
|
|
1778
|
+
const here = node;
|
|
1779
|
+
node = here.children.find((child) => {
|
|
1780
|
+
const tail = child.path.slice(here.path.length);
|
|
1781
|
+
return tail.length > 0 && tail.every((key, index) => key === want[at + index]);
|
|
1782
|
+
});
|
|
1783
|
+
if (node !== void 0) at += node.path.length - here.path.length;
|
|
1060
1784
|
}
|
|
1061
1785
|
return node;
|
|
1062
1786
|
}
|
|
@@ -1068,13 +1792,24 @@ function nodeAt(trace, path) {
|
|
|
1068
1792
|
*/
|
|
1069
1793
|
function flatten(trace) {
|
|
1070
1794
|
const out = [];
|
|
1071
|
-
const
|
|
1795
|
+
const stack = [trace];
|
|
1796
|
+
while (stack.length > 0) {
|
|
1797
|
+
const node = stack.pop();
|
|
1072
1798
|
out.push(node);
|
|
1073
|
-
node.children.
|
|
1074
|
-
}
|
|
1075
|
-
walk(trace);
|
|
1799
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) stack.push(node.children[index]);
|
|
1800
|
+
}
|
|
1076
1801
|
return out.sort((a, b) => a.enter - b.enter);
|
|
1077
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* This function lists the errors of a trace: each node whose result is an error and whose parts did not fail
|
|
1805
|
+
* the same way. These nodes are where each error started.
|
|
1806
|
+
*
|
|
1807
|
+
* @param trace - The root of the trace.
|
|
1808
|
+
* @returns The errors, in the order of their start.
|
|
1809
|
+
*/
|
|
1810
|
+
function failures(trace) {
|
|
1811
|
+
return flatten(trace).filter((node) => !node.result.ok && !node.children.some((child) => !child.result.ok && child.result.error === node.result.error)).map((node) => node.result.error);
|
|
1812
|
+
}
|
|
1078
1813
|
//#endregion
|
|
1079
1814
|
//#region src/stdlib.ts
|
|
1080
1815
|
/**
|
|
@@ -1222,6 +1957,15 @@ const STD_WORLD = {
|
|
|
1222
1957
|
view: "view.seq"
|
|
1223
1958
|
},
|
|
1224
1959
|
parts: { shape: "list" },
|
|
1960
|
+
scope: [{
|
|
1961
|
+
names: { headers: true },
|
|
1962
|
+
in: [["*"]],
|
|
1963
|
+
sequential: true
|
|
1964
|
+
}, {
|
|
1965
|
+
names: { fixed: ["last"] },
|
|
1966
|
+
in: [["*"]],
|
|
1967
|
+
sequential: true
|
|
1968
|
+
}],
|
|
1225
1969
|
summary: "Its steps, in order. The result of a named step is a name for the later steps."
|
|
1226
1970
|
},
|
|
1227
1971
|
pure: {
|
|
@@ -1306,11 +2050,8 @@ const STD_EVALUATORS = {
|
|
|
1306
2050
|
},
|
|
1307
2051
|
"ref.resolve": ({ data, resolve }) => {
|
|
1308
2052
|
if (!Array.isArray(data) || data.length === 0) return fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
if (found.ok) return ok(valueOfThing(found.thing));
|
|
1312
|
-
if (path[0] === "names" && found.hops.length === 1) return fail("#NAME?", `The context has no name ${String(path[1])}.`);
|
|
1313
|
-
return fail(found.code, found.reason);
|
|
2053
|
+
const found = resolve(data);
|
|
2054
|
+
return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
|
|
1314
2055
|
},
|
|
1315
2056
|
"tree.node": (input) => treeNode(input.parts, input.context, input.kind)
|
|
1316
2057
|
};
|
|
@@ -1360,34 +2101,44 @@ function treeNode(parts, context, type) {
|
|
|
1360
2101
|
}
|
|
1361
2102
|
return ok(out);
|
|
1362
2103
|
}
|
|
2104
|
+
function hostType(node, wanted) {
|
|
2105
|
+
return isHostThing(node) && node.type === wanted;
|
|
2106
|
+
}
|
|
1363
2107
|
/**
|
|
1364
|
-
*
|
|
1365
|
-
* wanted type. With `self: true`, the node where the walk starts is the first candidate. A node
|
|
1366
|
-
* ends the walk with an error.
|
|
2108
|
+
* This function makes a walker `nearest`. The walker follows a key (the default is `parent`) until the node that it
|
|
2109
|
+
* reaches has the wanted type. With `self: true`, the node where the walk starts is the first candidate. A node
|
|
2110
|
+
* without that key ends the walk with an error.
|
|
2111
|
+
*
|
|
2112
|
+
* @param options - The test of a type and the most hops.
|
|
2113
|
+
* @returns The walker.
|
|
1367
2114
|
*/
|
|
1368
|
-
|
|
1369
|
-
const
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
2115
|
+
function nearestWalker(options = {}) {
|
|
2116
|
+
const matches = options.matches ?? hostType;
|
|
2117
|
+
const maxHops = options.maxHops ?? 256;
|
|
2118
|
+
return ({ data, here, deref }) => {
|
|
2119
|
+
const follow = isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent";
|
|
2120
|
+
const wanted = isRecord(data) ? field(data, "type") : void 0;
|
|
2121
|
+
const fromSelf = isRecord(data) && field(data, "self") === true;
|
|
2122
|
+
let at = here;
|
|
2123
|
+
for (let hops = 0; hops < maxHops; hops += 1) {
|
|
2124
|
+
if (hops > 0 || !fromSelf) at = at === void 0 || at === null ? void 0 : deref(at, follow);
|
|
2125
|
+
if (at === void 0 || at === null) return {
|
|
2126
|
+
ok: false,
|
|
2127
|
+
reason: `No node of the type ${String(wanted)} is on the way.`
|
|
2128
|
+
};
|
|
2129
|
+
if (matches(at, wanted)) return {
|
|
2130
|
+
ok: true,
|
|
2131
|
+
thing: at
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
return {
|
|
1376
2135
|
ok: false,
|
|
1377
|
-
reason:
|
|
1378
|
-
};
|
|
1379
|
-
if ((isHostThing(at) ? at.type : void 0) === wanted) return {
|
|
1380
|
-
ok: true,
|
|
1381
|
-
thing: at
|
|
2136
|
+
reason: "The walk is too long."
|
|
1382
2137
|
};
|
|
1383
|
-
}
|
|
1384
|
-
return {
|
|
1385
|
-
ok: false,
|
|
1386
|
-
reason: "The walk is too long."
|
|
1387
2138
|
};
|
|
1388
|
-
}
|
|
2139
|
+
}
|
|
1389
2140
|
/** The walkers of the standard world, by name. */
|
|
1390
|
-
const STD_WALKERS = { "walk.nearest":
|
|
2141
|
+
const STD_WALKERS = { "walk.nearest": nearestWalker() };
|
|
1391
2142
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1392
2143
|
function* inOrder(parts) {
|
|
1393
2144
|
const values = [];
|
|
@@ -1573,26 +2324,26 @@ function layers(steps) {
|
|
|
1573
2324
|
//#region src/context.ts
|
|
1574
2325
|
/** The parts of a context. A term can reach outside itself only through these parts. */
|
|
1575
2326
|
const PARTS = [
|
|
2327
|
+
"self",
|
|
1576
2328
|
"names",
|
|
1577
|
-
"focus",
|
|
1578
2329
|
"world",
|
|
1579
2330
|
"event",
|
|
1580
2331
|
"caps"
|
|
1581
2332
|
];
|
|
1582
2333
|
/** The descriptions of the parts, in the order of `PARTS`. */
|
|
1583
2334
|
const PART_INFO = [
|
|
2335
|
+
{
|
|
2336
|
+
part: "self",
|
|
2337
|
+
title: "Self",
|
|
2338
|
+
reaches: "A position in a structure: the node itself, its parent, its children or its siblings.",
|
|
2339
|
+
direction: "Any direction that a move permits: up, down or to the side."
|
|
2340
|
+
},
|
|
1584
2341
|
{
|
|
1585
2342
|
part: "names",
|
|
1586
2343
|
title: "Names",
|
|
1587
2344
|
reaches: "The values that a binding gives: a let, a parameter, or the result of an earlier step.",
|
|
1588
2345
|
direction: "Back, to the place where the program binds the name."
|
|
1589
2346
|
},
|
|
1590
|
-
{
|
|
1591
|
-
part: "focus",
|
|
1592
|
-
title: "Focus",
|
|
1593
|
-
reaches: "A position in a structure: the node itself, its parent, its children or its siblings.",
|
|
1594
|
-
direction: "Any direction that a move permits: up, down or to the side."
|
|
1595
|
-
},
|
|
1596
2347
|
{
|
|
1597
2348
|
part: "world",
|
|
1598
2349
|
title: "World",
|
|
@@ -1625,15 +2376,22 @@ const CONSTRUCTS = [
|
|
|
1625
2376
|
side: "derive",
|
|
1626
2377
|
summary: "A pure formula that gives a value at one position of a structure.",
|
|
1627
2378
|
example: {
|
|
1628
|
-
type: "
|
|
2379
|
+
type: "let",
|
|
1629
2380
|
value: {
|
|
1630
|
-
|
|
2381
|
+
bind: { gap: 8 },
|
|
1631
2382
|
body: {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
2383
|
+
type: ["ops", "add"],
|
|
2384
|
+
value: [{
|
|
2385
|
+
type: "ref",
|
|
2386
|
+
value: [
|
|
2387
|
+
"self",
|
|
2388
|
+
"parent",
|
|
2389
|
+
"width"
|
|
2390
|
+
]
|
|
2391
|
+
}, {
|
|
2392
|
+
type: "ref",
|
|
2393
|
+
value: ["names", "gap"]
|
|
2394
|
+
}]
|
|
1637
2395
|
}
|
|
1638
2396
|
}
|
|
1639
2397
|
},
|
|
@@ -1643,10 +2401,10 @@ const CONSTRUCTS = [
|
|
|
1643
2401
|
gives: "extends",
|
|
1644
2402
|
note: "A let binds names for its body."
|
|
1645
2403
|
},
|
|
1646
|
-
|
|
2404
|
+
self: {
|
|
1647
2405
|
reads: true,
|
|
1648
2406
|
gives: "moves",
|
|
1649
|
-
note: "A move reads the parent. An axis moves
|
|
2407
|
+
note: "A move reads the parent. An axis moves self to each target."
|
|
1650
2408
|
},
|
|
1651
2409
|
world: NONE,
|
|
1652
2410
|
event: NONE,
|
|
@@ -1665,15 +2423,19 @@ const CONSTRUCTS = [
|
|
|
1665
2423
|
example: {
|
|
1666
2424
|
type: "seq",
|
|
1667
2425
|
value: [{
|
|
1668
|
-
type: "store
|
|
2426
|
+
type: ["store", "find"],
|
|
1669
2427
|
value: { match: { board: "b1" } },
|
|
1670
2428
|
name: "cards"
|
|
1671
2429
|
}, {
|
|
1672
|
-
type: "store
|
|
2430
|
+
type: ["store", "patch"],
|
|
1673
2431
|
value: {
|
|
1674
2432
|
ids: {
|
|
1675
2433
|
type: "ref",
|
|
1676
|
-
value:
|
|
2434
|
+
value: [
|
|
2435
|
+
"names",
|
|
2436
|
+
"cards",
|
|
2437
|
+
"ids"
|
|
2438
|
+
]
|
|
1677
2439
|
},
|
|
1678
2440
|
set: { done: true }
|
|
1679
2441
|
}
|
|
@@ -1685,7 +2447,7 @@ const CONSTRUCTS = [
|
|
|
1685
2447
|
gives: "extends",
|
|
1686
2448
|
note: "The result of each step is a name for the later steps."
|
|
1687
2449
|
},
|
|
1688
|
-
|
|
2450
|
+
self: NONE,
|
|
1689
2451
|
world: {
|
|
1690
2452
|
reads: true,
|
|
1691
2453
|
gives: "same",
|
|
@@ -1716,7 +2478,7 @@ const CONSTRUCTS = [
|
|
|
1716
2478
|
type: "text",
|
|
1717
2479
|
value: {
|
|
1718
2480
|
type: "ref",
|
|
1719
|
-
value: "title"
|
|
2481
|
+
value: ["names", "title"]
|
|
1720
2482
|
}
|
|
1721
2483
|
}]
|
|
1722
2484
|
}
|
|
@@ -1727,10 +2489,10 @@ const CONSTRUCTS = [
|
|
|
1727
2489
|
gives: "extends",
|
|
1728
2490
|
note: "The data of the node becomes a name for its children."
|
|
1729
2491
|
},
|
|
1730
|
-
|
|
2492
|
+
self: {
|
|
1731
2493
|
reads: true,
|
|
1732
2494
|
gives: "moves",
|
|
1733
|
-
note: "Each child becomes
|
|
2495
|
+
note: "Each child becomes self for its own part."
|
|
1734
2496
|
},
|
|
1735
2497
|
world: {
|
|
1736
2498
|
reads: true,
|
|
@@ -1747,11 +2509,8 @@ const CONSTRUCTS = [
|
|
|
1747
2509
|
side: "derive",
|
|
1748
2510
|
summary: "A rule that gives the box of a child from the box of its parent.",
|
|
1749
2511
|
example: {
|
|
1750
|
-
type: "
|
|
1751
|
-
value: {
|
|
1752
|
-
use: "stack",
|
|
1753
|
-
with: { gap: 8 }
|
|
1754
|
-
}
|
|
2512
|
+
type: ["layouts", "stack"],
|
|
2513
|
+
value: { gap: 8 }
|
|
1755
2514
|
},
|
|
1756
2515
|
uses: {
|
|
1757
2516
|
names: {
|
|
@@ -1759,7 +2518,7 @@ const CONSTRUCTS = [
|
|
|
1759
2518
|
gives: "none",
|
|
1760
2519
|
note: "The rule reads its parameters."
|
|
1761
2520
|
},
|
|
1762
|
-
|
|
2521
|
+
self: {
|
|
1763
2522
|
reads: true,
|
|
1764
2523
|
gives: "none",
|
|
1765
2524
|
note: "The rule reads the box of the parent and the key of the child."
|
|
@@ -1781,10 +2540,10 @@ const CONSTRUCTS = [
|
|
|
1781
2540
|
run: {
|
|
1782
2541
|
type: "seq",
|
|
1783
2542
|
value: [{
|
|
1784
|
-
type: "card
|
|
2543
|
+
type: ["card", "toggle"],
|
|
1785
2544
|
value: { id: {
|
|
1786
2545
|
type: "ref",
|
|
1787
|
-
value: "event
|
|
2546
|
+
value: ["event", "target"]
|
|
1788
2547
|
} }
|
|
1789
2548
|
}]
|
|
1790
2549
|
}
|
|
@@ -1796,7 +2555,7 @@ const CONSTRUCTS = [
|
|
|
1796
2555
|
gives: "same",
|
|
1797
2556
|
note: "The handler reads the names of its place."
|
|
1798
2557
|
},
|
|
1799
|
-
|
|
2558
|
+
self: {
|
|
1800
2559
|
reads: true,
|
|
1801
2560
|
gives: "same",
|
|
1802
2561
|
note: "The handler knows the node that got the event."
|
|
@@ -1829,7 +2588,7 @@ const CONSTRUCTS = [
|
|
|
1829
2588
|
collection: "cards",
|
|
1830
2589
|
match: { board: {
|
|
1831
2590
|
type: "ref",
|
|
1832
|
-
value: "board"
|
|
2591
|
+
value: ["names", "board"]
|
|
1833
2592
|
} }
|
|
1834
2593
|
}
|
|
1835
2594
|
},
|
|
@@ -1839,7 +2598,7 @@ const CONSTRUCTS = [
|
|
|
1839
2598
|
gives: "none",
|
|
1840
2599
|
note: "The query reads its parameters."
|
|
1841
2600
|
},
|
|
1842
|
-
|
|
2601
|
+
self: NONE,
|
|
1843
2602
|
world: {
|
|
1844
2603
|
reads: true,
|
|
1845
2604
|
gives: "none",
|
|
@@ -1851,6 +2610,769 @@ const CONSTRUCTS = [
|
|
|
1851
2610
|
}
|
|
1852
2611
|
];
|
|
1853
2612
|
//#endregion
|
|
1854
|
-
|
|
2613
|
+
//#region src/static.ts
|
|
2614
|
+
function valueAt(value, path) {
|
|
2615
|
+
let here = value;
|
|
2616
|
+
for (const key of path) if (Array.isArray(here)) here = here[Number(key)];
|
|
2617
|
+
else if (isRecord(here) && Object.hasOwn(here, String(key))) here = here[String(key)];
|
|
2618
|
+
else return void 0;
|
|
2619
|
+
return here;
|
|
2620
|
+
}
|
|
2621
|
+
function headerName(item) {
|
|
2622
|
+
return isEnvelope(item) && typeof item.name === "string" ? item.name : void 0;
|
|
2623
|
+
}
|
|
2624
|
+
function namesOf(source, value, covered) {
|
|
2625
|
+
if ("fixed" in source) return source.fixed;
|
|
2626
|
+
if ("keysAt" in source) {
|
|
2627
|
+
const at = valueAt(value, source.keysAt);
|
|
2628
|
+
return isRecord(at) ? Object.keys(at) : [];
|
|
2629
|
+
}
|
|
2630
|
+
if ("listAt" in source) {
|
|
2631
|
+
const at = valueAt(value, source.listAt);
|
|
2632
|
+
return Array.isArray(at) ? at.filter((item) => typeof item === "string") : [];
|
|
2633
|
+
}
|
|
2634
|
+
if ("textAt" in source) {
|
|
2635
|
+
const at = valueAt(value, source.textAt);
|
|
2636
|
+
if (typeof at === "string") return [at];
|
|
2637
|
+
return source.otherwise === void 0 ? [] : [source.otherwise];
|
|
2638
|
+
}
|
|
2639
|
+
return covered.map((slot) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2640
|
+
}
|
|
2641
|
+
/** The names that the scope rules of a kind give each part, and the names that only later parts see. */
|
|
2642
|
+
function scopesOf(rules, value, slots) {
|
|
2643
|
+
const sees = slots.map(() => []);
|
|
2644
|
+
const later = slots.map(() => []);
|
|
2645
|
+
for (const rule of rules ?? []) {
|
|
2646
|
+
const covered = slots.map((slot, index) => ({
|
|
2647
|
+
slot,
|
|
2648
|
+
index
|
|
2649
|
+
})).filter(({ slot }) => rule.in.some((pattern) => pathMatches(slot.path, pattern)));
|
|
2650
|
+
if (rule.sequential !== true) {
|
|
2651
|
+
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
2652
|
+
for (const { index } of covered) sees[index]?.push(...names);
|
|
2653
|
+
continue;
|
|
2654
|
+
}
|
|
2655
|
+
if ("headers" in rule.names) {
|
|
2656
|
+
covered.forEach(({ index }, position) => {
|
|
2657
|
+
const before = covered.slice(0, position).map(({ slot }) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2658
|
+
const after = covered.slice(position).map(({ slot }) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2659
|
+
sees[index]?.push(...before);
|
|
2660
|
+
later[index]?.push(...after);
|
|
2661
|
+
});
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
2665
|
+
covered.forEach(({ index }, position) => {
|
|
2666
|
+
if (position > 0) sees[index]?.push(...names);
|
|
2667
|
+
else later[index]?.push(...names);
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
return {
|
|
2671
|
+
sees,
|
|
2672
|
+
later
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
2677
|
+
* kinds. A part of a kind that the world does not have is not visited. The walk uses no stack of the host for the
|
|
2678
|
+
* depth, thus a deep document cannot overflow it.
|
|
2679
|
+
*
|
|
2680
|
+
* @param world - The world of the kinds.
|
|
2681
|
+
* @param doc - The document, as JSON.
|
|
2682
|
+
* @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
|
|
2683
|
+
* @param options - The type of a bare root value, the names of the host, and a function for the problems.
|
|
2684
|
+
*/
|
|
2685
|
+
function walk(world, doc, visit, options = {}) {
|
|
2686
|
+
const stack = [{
|
|
2687
|
+
value: doc,
|
|
2688
|
+
place: [],
|
|
2689
|
+
at: [],
|
|
2690
|
+
declared: options.declaredType,
|
|
2691
|
+
depth: 0,
|
|
2692
|
+
names: new Set(options.names ?? []),
|
|
2693
|
+
later: /* @__PURE__ */ new Set()
|
|
2694
|
+
}];
|
|
2695
|
+
while (stack.length > 0) {
|
|
2696
|
+
const item = stack.pop();
|
|
2697
|
+
const envelope = isEnvelope(item.value) ? item.value : void 0;
|
|
2698
|
+
if (envelope === void 0 && item.declared === void 0) {
|
|
2699
|
+
options.problem?.({
|
|
2700
|
+
place: item.place,
|
|
2701
|
+
at: item.at,
|
|
2702
|
+
code: "#PART?",
|
|
2703
|
+
reason: "The value has no envelope, and its position declares no type."
|
|
2704
|
+
});
|
|
2705
|
+
continue;
|
|
2706
|
+
}
|
|
2707
|
+
const doc = envelope ?? {
|
|
2708
|
+
type: item.declared ?? "?",
|
|
2709
|
+
value: item.value
|
|
2710
|
+
};
|
|
2711
|
+
const found = lookupKind(world, doc.type);
|
|
2712
|
+
const kind = found.ok ? found.kind : void 0;
|
|
2713
|
+
if (visit({
|
|
2714
|
+
place: item.place,
|
|
2715
|
+
at: item.at,
|
|
2716
|
+
doc,
|
|
2717
|
+
declared: envelope === void 0,
|
|
2718
|
+
...kind === void 0 ? {} : {
|
|
2719
|
+
kind,
|
|
2720
|
+
typePath: found.ok ? found.path : []
|
|
2721
|
+
},
|
|
2722
|
+
depth: item.depth,
|
|
2723
|
+
names: item.names,
|
|
2724
|
+
later: item.later
|
|
2725
|
+
}) === "skip" || kind === void 0) continue;
|
|
2726
|
+
const split = splitParts(kind.parts, doc.value);
|
|
2727
|
+
if (!split.ok) {
|
|
2728
|
+
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
2729
|
+
options.problem?.({
|
|
2730
|
+
place: item.place,
|
|
2731
|
+
at: item.at,
|
|
2732
|
+
code: "#VALUE!",
|
|
2733
|
+
reason: `${split.reason}${at} The kind is ${typeName(doc.type)}.`
|
|
2734
|
+
});
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
2738
|
+
const base = envelope === void 0 ? item.at : [...item.at, "value"];
|
|
2739
|
+
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
2740
|
+
const slot = split.parts[index];
|
|
2741
|
+
const extra = sees[index] ?? [];
|
|
2742
|
+
const hidden = later[index] ?? [];
|
|
2743
|
+
stack.push({
|
|
2744
|
+
value: slot.item,
|
|
2745
|
+
place: [...item.place, ...slot.path],
|
|
2746
|
+
at: [...base, ...slot.path],
|
|
2747
|
+
declared: slot.of,
|
|
2748
|
+
depth: item.depth + 1,
|
|
2749
|
+
names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
|
|
2750
|
+
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden])
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
/**
|
|
2756
|
+
* This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
|
|
2757
|
+
* reference to a name that a kind around it binds is not free. The others are what the document reads from
|
|
2758
|
+
* outside: its dependencies.
|
|
2759
|
+
*
|
|
2760
|
+
* @param world - The world of the kinds.
|
|
2761
|
+
* @param doc - The document, as JSON.
|
|
2762
|
+
* @param options - The type of a bare root value. The names of the host do not change the result.
|
|
2763
|
+
* @returns The references, in the order of the document.
|
|
2764
|
+
*/
|
|
2765
|
+
function refsOf(world, doc, options = {}) {
|
|
2766
|
+
const out = [];
|
|
2767
|
+
const { names: _host, ...rest } = options;
|
|
2768
|
+
walk(world, doc, (v) => {
|
|
2769
|
+
if (!isRef(v.doc)) return void 0;
|
|
2770
|
+
const path = v.doc.value;
|
|
2771
|
+
const root = typeof path[0] === "string" ? path[0] : "";
|
|
2772
|
+
const first = path[1];
|
|
2773
|
+
const bound = root === "names" && isKey(first) && v.names.has(String(first));
|
|
2774
|
+
out.push({
|
|
2775
|
+
place: v.place,
|
|
2776
|
+
at: v.at,
|
|
2777
|
+
path,
|
|
2778
|
+
root,
|
|
2779
|
+
free: !bound
|
|
2780
|
+
});
|
|
2781
|
+
return "skip";
|
|
2782
|
+
}, rest);
|
|
2783
|
+
return out;
|
|
2784
|
+
}
|
|
2785
|
+
function setAt(value, path, next) {
|
|
2786
|
+
if (path.length === 0) return next;
|
|
2787
|
+
const [key, ...rest] = path;
|
|
2788
|
+
if (Array.isArray(value)) {
|
|
2789
|
+
const copy = [...value];
|
|
2790
|
+
copy[Number(key)] = setAt(value[Number(key)], rest, next);
|
|
2791
|
+
return copy;
|
|
2792
|
+
}
|
|
2793
|
+
const record = isRecord(value) ? value : {};
|
|
2794
|
+
const copy = {};
|
|
2795
|
+
for (const name of Object.keys(record)) Object.defineProperty(copy, name, {
|
|
2796
|
+
value: record[name],
|
|
2797
|
+
enumerable: true,
|
|
2798
|
+
writable: true,
|
|
2799
|
+
configurable: true
|
|
2800
|
+
});
|
|
2801
|
+
Object.defineProperty(copy, String(key), {
|
|
2802
|
+
value: setAt(record[String(key)], rest, next),
|
|
2803
|
+
enumerable: true,
|
|
2804
|
+
writable: true,
|
|
2805
|
+
configurable: true
|
|
2806
|
+
});
|
|
2807
|
+
return copy;
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* This function changes the free references of a document. The document is not changed: the function gives a new
|
|
2811
|
+
* document that shares each part without a change.
|
|
2812
|
+
*
|
|
2813
|
+
* @param world - The world of the kinds.
|
|
2814
|
+
* @param doc - The document, as JSON.
|
|
2815
|
+
* @param change - The function that gives the new document of a reference, or `undefined` to keep it.
|
|
2816
|
+
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
2817
|
+
* @returns The new document.
|
|
2818
|
+
*/
|
|
2819
|
+
function mapRefs(world, doc, change, options = {}) {
|
|
2820
|
+
let out = doc;
|
|
2821
|
+
for (const use of refsOf(world, doc, options)) {
|
|
2822
|
+
if (!use.free) continue;
|
|
2823
|
+
const next = change(use);
|
|
2824
|
+
if (next !== void 0) out = setAt(out, use.at, next);
|
|
2825
|
+
}
|
|
2826
|
+
return out;
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
|
|
2830
|
+
* that an evaluation finds when it reaches them:
|
|
2831
|
+
*
|
|
2832
|
+
* - `#KIND?`: a kind that the world does not have.
|
|
2833
|
+
* - `#NOEVAL`: no evaluator.
|
|
2834
|
+
* - `#VALUE!`: a value without the shape of its kind.
|
|
2835
|
+
* - `#PART?`: a value without a type.
|
|
2836
|
+
* - `#DEPTH!`: too much nesting.
|
|
2837
|
+
* - `#REF!` and `#STEP?`: a bad path.
|
|
2838
|
+
* - `#NAME?`: a name that nothing binds.
|
|
2839
|
+
* - `#CAP!`: an effect that the capabilities do not permit.
|
|
2840
|
+
*
|
|
2841
|
+
* @param machine - The world and the code of the evaluators.
|
|
2842
|
+
* @param doc - The document, as JSON.
|
|
2843
|
+
* @param options - The type of a bare root value, the names of the host and the capabilities.
|
|
2844
|
+
* @returns The problems, in the order of the document.
|
|
2845
|
+
*/
|
|
2846
|
+
function check(machine, doc, options = {}) {
|
|
2847
|
+
const issues = [];
|
|
2848
|
+
const maxDepth = machine.maxDepth ?? 1e3;
|
|
2849
|
+
const anyName = options.names === "any";
|
|
2850
|
+
const roots = machine.roots ?? ROOTS;
|
|
2851
|
+
const has = (table, name) => table !== void 0 && Object.hasOwn(table, name);
|
|
2852
|
+
walk(machine.world, doc, (v) => {
|
|
2853
|
+
if (v.depth > maxDepth) {
|
|
2854
|
+
issues.push({
|
|
2855
|
+
code: "#DEPTH!",
|
|
2856
|
+
place: v.place,
|
|
2857
|
+
reason: `The nesting is deeper than ${maxDepth} documents.`
|
|
2858
|
+
});
|
|
2859
|
+
return "skip";
|
|
2860
|
+
}
|
|
2861
|
+
const header = v.declared ? {} : v.doc;
|
|
2862
|
+
const answer = ask(machine.world, header, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
|
|
2863
|
+
if (answer.error === "unknown-kind") {
|
|
2864
|
+
issues.push({
|
|
2865
|
+
code: "#KIND?",
|
|
2866
|
+
place: v.place,
|
|
2867
|
+
reason: `The world has no kind ${typeName(v.doc.type)}.`
|
|
2868
|
+
});
|
|
2869
|
+
return "skip";
|
|
2870
|
+
}
|
|
2871
|
+
const name = answer.evaluator;
|
|
2872
|
+
if (name === void 0 || !has(machine.evaluators, name) && !has(machine.runners, name)) issues.push({
|
|
2873
|
+
code: "#NOEVAL",
|
|
2874
|
+
place: v.place,
|
|
2875
|
+
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`
|
|
2876
|
+
});
|
|
2877
|
+
if (options.caps !== void 0 && v.kind?.effects !== void 0) {
|
|
2878
|
+
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
2879
|
+
for (const kind of kinds) if (!permits(options.caps, kind)) issues.push({
|
|
2880
|
+
code: "#CAP!",
|
|
2881
|
+
place: v.place,
|
|
2882
|
+
reason: `The capabilities do not permit the effect ${kind.join(".")}.`
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
if (!isRef(v.doc)) return void 0;
|
|
2886
|
+
const path = v.doc.value;
|
|
2887
|
+
const root = path[0];
|
|
2888
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) {
|
|
2889
|
+
issues.push({
|
|
2890
|
+
code: "#REF!",
|
|
2891
|
+
place: v.place,
|
|
2892
|
+
reason: `A path starts with a root: ${ROOTS.join(", ")}.`
|
|
2893
|
+
});
|
|
2894
|
+
return "skip";
|
|
2895
|
+
}
|
|
2896
|
+
if (!roots.includes(root)) issues.push({
|
|
2897
|
+
code: "#REF!",
|
|
2898
|
+
place: v.place,
|
|
2899
|
+
reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}.`
|
|
2900
|
+
});
|
|
2901
|
+
const first = path[1];
|
|
2902
|
+
if (root === "names" && !anyName && isKey(first) && !v.names.has(String(first))) {
|
|
2903
|
+
const later = v.later.has(String(first));
|
|
2904
|
+
issues.push({
|
|
2905
|
+
code: "#NAME?",
|
|
2906
|
+
place: v.place,
|
|
2907
|
+
reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}.`
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
path.slice(1).forEach((step) => {
|
|
2911
|
+
if (isKey(step)) return;
|
|
2912
|
+
const found = isEnvelope(step) ? lookupKind(machine.world, step.type) : void 0;
|
|
2913
|
+
const walker = found?.ok === true && Object.hasOwn(found.kind.verbs, "step") ? found.kind.verbs["step"] : void 0;
|
|
2914
|
+
if (walker === void 0 || !has(machine.walkers, walker)) issues.push({
|
|
2915
|
+
code: "#STEP?",
|
|
2916
|
+
place: v.place,
|
|
2917
|
+
reason: "A step is a key, or a document of a kind with the verb step."
|
|
2918
|
+
});
|
|
2919
|
+
});
|
|
2920
|
+
return "skip";
|
|
2921
|
+
}, {
|
|
2922
|
+
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
2923
|
+
...Array.isArray(options.names) ? { names: options.names } : {},
|
|
2924
|
+
problem: (problem) => issues.push({
|
|
2925
|
+
code: problem.code,
|
|
2926
|
+
place: problem.place,
|
|
2927
|
+
reason: problem.reason
|
|
2928
|
+
})
|
|
2929
|
+
});
|
|
2930
|
+
return issues;
|
|
2931
|
+
}
|
|
2932
|
+
//#endregion
|
|
2933
|
+
//#region src/formula.ts
|
|
2934
|
+
/** The value of a name whose binding failed. A reference to the name gives the error. */
|
|
2935
|
+
var Held = class {
|
|
2936
|
+
error;
|
|
2937
|
+
constructor(error) {
|
|
2938
|
+
this.error = error;
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
const CLOSURE = Symbol.for("coeval.closure");
|
|
2942
|
+
/**
|
|
2943
|
+
* This function tells if a value is a function that the construct `fn` made.
|
|
2944
|
+
*
|
|
2945
|
+
* @param value - The value.
|
|
2946
|
+
* @returns `true` for a closure.
|
|
2947
|
+
*/
|
|
2948
|
+
function isClosure(value) {
|
|
2949
|
+
return typeof value === "function" && value[CLOSURE] !== void 0;
|
|
2950
|
+
}
|
|
2951
|
+
/** The standard extensions: the values that JSON cannot write. */
|
|
2952
|
+
const STD_EXT = {
|
|
2953
|
+
undefined: () => ok(void 0),
|
|
2954
|
+
number: (data) => {
|
|
2955
|
+
if (data === "NaN") return ok(NaN);
|
|
2956
|
+
if (data === "Infinity") return ok(Number.POSITIVE_INFINITY);
|
|
2957
|
+
if (data === "-Infinity") return ok(Number.NEGATIVE_INFINITY);
|
|
2958
|
+
if (data === "-0") return ok(-0);
|
|
2959
|
+
return fail("#VALUE!", "The value of ext.number is NaN, Infinity, -Infinity or -0.");
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2962
|
+
/** The truth of a value in the rules of JavaScript: `0`, `""`, `null`, `undefined` and `NaN` are false. */
|
|
2963
|
+
function jsTruthy(value) {
|
|
2964
|
+
return Boolean(value);
|
|
2965
|
+
}
|
|
2966
|
+
function strictTruthy(value) {
|
|
2967
|
+
return typeof value === "boolean" ? value : void 0;
|
|
2968
|
+
}
|
|
2969
|
+
function setOwn(target, key, value) {
|
|
2970
|
+
Object.defineProperty(target, key, {
|
|
2971
|
+
value,
|
|
2972
|
+
enumerable: true,
|
|
2973
|
+
writable: true,
|
|
2974
|
+
configurable: true
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
function compiled(compile) {
|
|
2978
|
+
return { compile };
|
|
2979
|
+
}
|
|
2980
|
+
function partsOf(value) {
|
|
2981
|
+
return isRecord(value) ? value : {};
|
|
2982
|
+
}
|
|
2983
|
+
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
2984
|
+
/**
|
|
2985
|
+
* This function makes the kinds of the formula kit, for a world. The kinds are the shared constructs of D13 and
|
|
2986
|
+
* the namespaces `ops` and `ext`. A bare value in a position of a term is a literal.
|
|
2987
|
+
*
|
|
2988
|
+
* @returns The entries of the world, by their key.
|
|
2989
|
+
*/
|
|
2990
|
+
function formulaKinds() {
|
|
2991
|
+
const term = {
|
|
2992
|
+
shape: "term",
|
|
2993
|
+
of: "lit"
|
|
2994
|
+
};
|
|
2995
|
+
const terms = {
|
|
2996
|
+
shape: "list",
|
|
2997
|
+
of: "lit"
|
|
2998
|
+
};
|
|
2999
|
+
return {
|
|
3000
|
+
lit: {
|
|
3001
|
+
verbs: { eval: "formula.lit" },
|
|
3002
|
+
summary: LIT_SUMMARY
|
|
3003
|
+
},
|
|
3004
|
+
ref: {
|
|
3005
|
+
verbs: { eval: "formula.ref" },
|
|
3006
|
+
summary: "A path from a root of the context. Its result is what the path reaches."
|
|
3007
|
+
},
|
|
3008
|
+
record: {
|
|
3009
|
+
verbs: { eval: "formula.record" },
|
|
3010
|
+
parts: {
|
|
3011
|
+
shape: "map",
|
|
3012
|
+
of: "lit"
|
|
3013
|
+
},
|
|
3014
|
+
summary: "A record: each field is a term."
|
|
3015
|
+
},
|
|
3016
|
+
list: {
|
|
3017
|
+
verbs: { eval: "formula.list" },
|
|
3018
|
+
parts: terms,
|
|
3019
|
+
summary: "A list: each item is a term."
|
|
3020
|
+
},
|
|
3021
|
+
let: {
|
|
3022
|
+
verbs: { eval: "formula.let" },
|
|
3023
|
+
parts: {
|
|
3024
|
+
shape: "record",
|
|
3025
|
+
fields: {
|
|
3026
|
+
bind: {
|
|
3027
|
+
shape: "map",
|
|
3028
|
+
of: "lit"
|
|
3029
|
+
},
|
|
3030
|
+
body: term
|
|
3031
|
+
},
|
|
3032
|
+
required: ["bind", "body"]
|
|
3033
|
+
},
|
|
3034
|
+
scope: [{
|
|
3035
|
+
names: { keysAt: ["bind"] },
|
|
3036
|
+
in: [["body"]]
|
|
3037
|
+
}],
|
|
3038
|
+
summary: "Names for the body. Each binding sees the names around the let, not the other bindings."
|
|
3039
|
+
},
|
|
3040
|
+
fn: {
|
|
3041
|
+
verbs: { eval: "formula.fn" },
|
|
3042
|
+
parts: {
|
|
3043
|
+
shape: "record",
|
|
3044
|
+
fields: { body: term },
|
|
3045
|
+
required: ["params", "body"]
|
|
3046
|
+
},
|
|
3047
|
+
scope: [{
|
|
3048
|
+
names: { listAt: ["params"] },
|
|
3049
|
+
in: [["body"]]
|
|
3050
|
+
}],
|
|
3051
|
+
summary: "A function: its parameters are names for its body."
|
|
3052
|
+
},
|
|
3053
|
+
call: {
|
|
3054
|
+
verbs: { eval: "formula.call" },
|
|
3055
|
+
parts: {
|
|
3056
|
+
shape: "record",
|
|
3057
|
+
fields: {
|
|
3058
|
+
fn: term,
|
|
3059
|
+
args: terms
|
|
3060
|
+
},
|
|
3061
|
+
required: ["fn"]
|
|
3062
|
+
},
|
|
3063
|
+
summary: "It calls a function with the values of its arguments."
|
|
3064
|
+
},
|
|
3065
|
+
if: {
|
|
3066
|
+
verbs: { eval: "formula.if" },
|
|
3067
|
+
parts: {
|
|
3068
|
+
shape: "record",
|
|
3069
|
+
fields: {
|
|
3070
|
+
test: term,
|
|
3071
|
+
then: term,
|
|
3072
|
+
else: term
|
|
3073
|
+
},
|
|
3074
|
+
required: ["test", "then"]
|
|
3075
|
+
},
|
|
3076
|
+
summary: "It evaluates the test, then only the branch that the test selects."
|
|
3077
|
+
},
|
|
3078
|
+
and: {
|
|
3079
|
+
verbs: { eval: "formula.and" },
|
|
3080
|
+
parts: terms,
|
|
3081
|
+
summary: "True when each term is true. It stops at the first false term."
|
|
3082
|
+
},
|
|
3083
|
+
or: {
|
|
3084
|
+
verbs: { eval: "formula.or" },
|
|
3085
|
+
parts: terms,
|
|
3086
|
+
summary: "True when one term is true. It stops at the first true term."
|
|
3087
|
+
},
|
|
3088
|
+
ops: {
|
|
3089
|
+
kinds: {},
|
|
3090
|
+
fallback: {
|
|
3091
|
+
verbs: { eval: "formula.op" },
|
|
3092
|
+
parts: terms,
|
|
3093
|
+
summary: "An operation of the language, applied to the values of its terms."
|
|
3094
|
+
}
|
|
3095
|
+
},
|
|
3096
|
+
ext: {
|
|
3097
|
+
kinds: {},
|
|
3098
|
+
fallback: {
|
|
3099
|
+
verbs: { eval: "formula.ext" },
|
|
3100
|
+
summary: "An extension of the language. Its value is data."
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* This function makes the evaluators of the formula kit for one language. Each evaluator compiles its document
|
|
3107
|
+
* one time into code.
|
|
3108
|
+
*
|
|
3109
|
+
* @param profile - The operations and the rules of the language.
|
|
3110
|
+
* @returns The evaluators, by their name.
|
|
3111
|
+
*/
|
|
3112
|
+
function formulaEvaluators(profile = {}) {
|
|
3113
|
+
const truthy = profile.truthy ?? strictTruthy;
|
|
3114
|
+
const otherwise = ok(profile.otherwise ?? null);
|
|
3115
|
+
const logicValue = profile.logic === "value";
|
|
3116
|
+
const omit = profile.omit;
|
|
3117
|
+
const missing = profile.missing;
|
|
3118
|
+
const check = profile.check;
|
|
3119
|
+
const lookup = (name) => {
|
|
3120
|
+
const ops = profile.ops;
|
|
3121
|
+
if (ops === void 0) return void 0;
|
|
3122
|
+
if (typeof ops === "function") return ops(name);
|
|
3123
|
+
return Object.hasOwn(ops, name) ? ops[name] : void 0;
|
|
3124
|
+
};
|
|
3125
|
+
const notTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
3126
|
+
return {
|
|
3127
|
+
"formula.lit": compiled(({ doc }) => {
|
|
3128
|
+
const result = ok(doc.value);
|
|
3129
|
+
return () => result;
|
|
3130
|
+
}),
|
|
3131
|
+
"formula.ref": compiled(({ doc }) => {
|
|
3132
|
+
const path = doc.value;
|
|
3133
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
3134
|
+
const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
|
|
3135
|
+
return () => bad;
|
|
3136
|
+
}
|
|
3137
|
+
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
3138
|
+
return (ctx, rt) => {
|
|
3139
|
+
if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
|
|
3140
|
+
const bound = ctx.names[head];
|
|
3141
|
+
if (bound instanceof Held) return {
|
|
3142
|
+
ok: false,
|
|
3143
|
+
error: bound.error
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
const found = rt.resolve(ctx, path);
|
|
3147
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
3148
|
+
const value = valueOfThing(found.thing);
|
|
3149
|
+
if (value instanceof Held) return {
|
|
3150
|
+
ok: false,
|
|
3151
|
+
error: value.error
|
|
3152
|
+
};
|
|
3153
|
+
if (missing !== void 0 && missing(value)) return fail("#N/A", "The path reaches no value.");
|
|
3154
|
+
return ok(value);
|
|
3155
|
+
};
|
|
3156
|
+
}),
|
|
3157
|
+
"formula.record": compiled(({ value }) => {
|
|
3158
|
+
const fields = Object.entries(partsOf(value));
|
|
3159
|
+
return (ctx, rt) => {
|
|
3160
|
+
const out = {};
|
|
3161
|
+
for (const [key, part] of fields) {
|
|
3162
|
+
const result = part.code(ctx, rt);
|
|
3163
|
+
if (!result.ok) {
|
|
3164
|
+
if (omit !== void 0 && omit(result.error)) continue;
|
|
3165
|
+
return result;
|
|
3166
|
+
}
|
|
3167
|
+
setOwn(out, key, result.value);
|
|
3168
|
+
}
|
|
3169
|
+
return ok(out);
|
|
3170
|
+
};
|
|
3171
|
+
}),
|
|
3172
|
+
"formula.list": compiled(({ parts }) => (ctx, rt) => {
|
|
3173
|
+
const out = new Array(parts.length);
|
|
3174
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3175
|
+
const result = parts[index].code(ctx, rt);
|
|
3176
|
+
if (!result.ok) return result;
|
|
3177
|
+
out[index] = result.value;
|
|
3178
|
+
}
|
|
3179
|
+
return ok(out);
|
|
3180
|
+
}),
|
|
3181
|
+
"formula.let": compiled(({ value }) => {
|
|
3182
|
+
const record = partsOf(value);
|
|
3183
|
+
const bindings = Object.entries(partsOf(record["bind"]));
|
|
3184
|
+
const body = record["body"];
|
|
3185
|
+
return (ctx, rt) => {
|
|
3186
|
+
const names = { ...ctx.names };
|
|
3187
|
+
for (const [name, part] of bindings) {
|
|
3188
|
+
const result = part.code(ctx, rt);
|
|
3189
|
+
setOwn(names, name, result.ok ? result.value : new Held(result.error));
|
|
3190
|
+
}
|
|
3191
|
+
return body.code({
|
|
3192
|
+
...ctx,
|
|
3193
|
+
names
|
|
3194
|
+
}, rt);
|
|
3195
|
+
};
|
|
3196
|
+
}),
|
|
3197
|
+
"formula.fn": compiled(({ value, data }) => {
|
|
3198
|
+
const params = isRecord(data) ? data["params"] : void 0;
|
|
3199
|
+
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
|
|
3200
|
+
const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
3201
|
+
return () => bad;
|
|
3202
|
+
}
|
|
3203
|
+
const names = params;
|
|
3204
|
+
const body = partsOf(value)["body"];
|
|
3205
|
+
return (ctx, rt) => {
|
|
3206
|
+
const closure = (...args) => {
|
|
3207
|
+
const scope = { ...ctx.names };
|
|
3208
|
+
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
3209
|
+
const result = body.code({
|
|
3210
|
+
...ctx,
|
|
3211
|
+
names: scope
|
|
3212
|
+
}, rt);
|
|
3213
|
+
if (!result.ok) throw new Failure(result.error);
|
|
3214
|
+
return result.value;
|
|
3215
|
+
};
|
|
3216
|
+
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
3217
|
+
return ok(closure);
|
|
3218
|
+
};
|
|
3219
|
+
}),
|
|
3220
|
+
"formula.call": compiled(({ value }) => {
|
|
3221
|
+
const record = partsOf(value);
|
|
3222
|
+
const fnPart = record["fn"];
|
|
3223
|
+
const args = Array.isArray(record["args"]) ? record["args"] : [];
|
|
3224
|
+
return (ctx, rt) => {
|
|
3225
|
+
const fn = fnPart.code(ctx, rt);
|
|
3226
|
+
if (!fn.ok) return fn;
|
|
3227
|
+
if (typeof fn.value !== "function") return fail("#VALUE!", "The fn of call must give a function.");
|
|
3228
|
+
const values = new Array(args.length);
|
|
3229
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3230
|
+
const result = args[index].code(ctx, rt);
|
|
3231
|
+
if (!result.ok) return result;
|
|
3232
|
+
values[index] = result.value;
|
|
3233
|
+
}
|
|
3234
|
+
try {
|
|
3235
|
+
return ok(fn.value(...values));
|
|
3236
|
+
} catch (error) {
|
|
3237
|
+
return thrown(error, "the function of call");
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
}),
|
|
3241
|
+
"formula.if": compiled(({ value }) => {
|
|
3242
|
+
const record = partsOf(value);
|
|
3243
|
+
const test = record["test"];
|
|
3244
|
+
const then = record["then"];
|
|
3245
|
+
const other = record["else"];
|
|
3246
|
+
return (ctx, rt) => {
|
|
3247
|
+
const chosen = test.code(ctx, rt);
|
|
3248
|
+
if (!chosen.ok) return chosen;
|
|
3249
|
+
const truth = truthy(chosen.value);
|
|
3250
|
+
if (truth === void 0) return notTruth("test of if");
|
|
3251
|
+
if (truth) return then.code(ctx, rt);
|
|
3252
|
+
return other === void 0 ? otherwise : other.code(ctx, rt);
|
|
3253
|
+
};
|
|
3254
|
+
}),
|
|
3255
|
+
"formula.and": compiled(({ parts }) => (ctx, rt) => {
|
|
3256
|
+
let last = ok(true);
|
|
3257
|
+
for (const part of parts) {
|
|
3258
|
+
last = part.code(ctx, rt);
|
|
3259
|
+
if (!last.ok) return last;
|
|
3260
|
+
const truth = truthy(last.value);
|
|
3261
|
+
if (truth === void 0) return notTruth("term of and");
|
|
3262
|
+
if (!truth) return logicValue ? last : ok(false);
|
|
3263
|
+
}
|
|
3264
|
+
return logicValue ? last : ok(true);
|
|
3265
|
+
}),
|
|
3266
|
+
"formula.or": compiled(({ parts }) => (ctx, rt) => {
|
|
3267
|
+
let last = ok(false);
|
|
3268
|
+
for (const part of parts) {
|
|
3269
|
+
last = part.code(ctx, rt);
|
|
3270
|
+
if (!last.ok) return last;
|
|
3271
|
+
const truth = truthy(last.value);
|
|
3272
|
+
if (truth === void 0) return notTruth("term of or");
|
|
3273
|
+
if (truth) return logicValue ? last : ok(true);
|
|
3274
|
+
}
|
|
3275
|
+
return logicValue ? last : ok(false);
|
|
3276
|
+
}),
|
|
3277
|
+
"formula.op": compiled(({ typePath, parts }) => {
|
|
3278
|
+
const name = typePath.slice(1).join(".");
|
|
3279
|
+
const lazy = profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name) ? profile.lazy[name] : void 0;
|
|
3280
|
+
if (lazy !== void 0) return (ctx, rt) => {
|
|
3281
|
+
try {
|
|
3282
|
+
return lazy(parts.map((part) => () => part.code(ctx, rt)));
|
|
3283
|
+
} catch (error) {
|
|
3284
|
+
return thrown(error, `the operation ${name}`);
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
const fn = lookup(name);
|
|
3288
|
+
if (fn === void 0) {
|
|
3289
|
+
const unknown = fail("#NAME?", `The language has no operation ${name}.`);
|
|
3290
|
+
return () => unknown;
|
|
3291
|
+
}
|
|
3292
|
+
const done = (out) => check === void 0 ? ok(out) : check(out, name) ?? ok(out);
|
|
3293
|
+
const failed = (error) => thrown(error, `the operation ${name}`);
|
|
3294
|
+
if (parts.length === 1) {
|
|
3295
|
+
const [a] = parts;
|
|
3296
|
+
return (ctx, rt) => {
|
|
3297
|
+
const x = a.code(ctx, rt);
|
|
3298
|
+
if (!x.ok) return x;
|
|
3299
|
+
try {
|
|
3300
|
+
return done(fn(x.value));
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
return failed(error);
|
|
3303
|
+
}
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
if (parts.length === 2) {
|
|
3307
|
+
const [a, b] = parts;
|
|
3308
|
+
return (ctx, rt) => {
|
|
3309
|
+
const x = a.code(ctx, rt);
|
|
3310
|
+
if (!x.ok) return x;
|
|
3311
|
+
const y = b.code(ctx, rt);
|
|
3312
|
+
if (!y.ok) return y;
|
|
3313
|
+
try {
|
|
3314
|
+
return done(fn(x.value, y.value));
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
return failed(error);
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
return (ctx, rt) => {
|
|
3321
|
+
const values = new Array(parts.length);
|
|
3322
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3323
|
+
const result = parts[index].code(ctx, rt);
|
|
3324
|
+
if (!result.ok) return result;
|
|
3325
|
+
values[index] = result.value;
|
|
3326
|
+
}
|
|
3327
|
+
try {
|
|
3328
|
+
return done(fn(...values));
|
|
3329
|
+
} catch (error) {
|
|
3330
|
+
return failed(error);
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
3333
|
+
}),
|
|
3334
|
+
"formula.ext": compiled(({ typePath, doc }) => {
|
|
3335
|
+
const name = typePath.slice(1).join(".");
|
|
3336
|
+
const handler = (profile.ext !== void 0 && Object.hasOwn(profile.ext, name) ? profile.ext[name] : void 0) ?? (Object.hasOwn(STD_EXT, name) ? STD_EXT[name] : void 0);
|
|
3337
|
+
if (handler === void 0) {
|
|
3338
|
+
const unknown = fail("#NAME?", `The language has no extension ${name}.`);
|
|
3339
|
+
return () => unknown;
|
|
3340
|
+
}
|
|
3341
|
+
return () => {
|
|
3342
|
+
try {
|
|
3343
|
+
return handler(doc.value);
|
|
3344
|
+
} catch (error) {
|
|
3345
|
+
return thrown(error, `the extension ${name}`);
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
})
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
3353
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
3354
|
+
*
|
|
3355
|
+
* @param profile - The operations and the rules of the language.
|
|
3356
|
+
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
3357
|
+
* @returns The machine.
|
|
3358
|
+
*/
|
|
3359
|
+
function formulaMachine(profile = {}, more = {}) {
|
|
3360
|
+
return {
|
|
3361
|
+
...more,
|
|
3362
|
+
world: {
|
|
3363
|
+
kinds: {
|
|
3364
|
+
...formulaKinds(),
|
|
3365
|
+
...more.world?.kinds
|
|
3366
|
+
},
|
|
3367
|
+
defaults: { ...more.world?.defaults }
|
|
3368
|
+
},
|
|
3369
|
+
evaluators: {
|
|
3370
|
+
...formulaEvaluators(profile),
|
|
3371
|
+
...more.evaluators
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
//#endregion
|
|
3376
|
+
export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaEvaluators, formulaKinds, formulaMachine, fromJson, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, pathMatches, permits, placed, planner, prepare, ref, refsOf, resolve, run, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, valueOfThing, walk, worldThing };
|
|
1855
3377
|
|
|
1856
3378
|
//# sourceMappingURL=index.js.map
|