@mark1russell7/coeval 0.1.0 → 0.2.1
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 +835 -148
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1911 -383
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/context.ts +25 -21
- package/src/deref.ts +178 -58
- 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,96 @@ 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
|
+
if (nav.adopt !== void 0) return void 0;
|
|
779
|
+
let here = ctx[root];
|
|
780
|
+
const plain = nav.plain === true;
|
|
781
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
782
|
+
if (typeof here !== "object" || here === null) return void 0;
|
|
783
|
+
if (!plain && mayBeRef(here)) return void 0;
|
|
784
|
+
if (here[THING] === true) return void 0;
|
|
785
|
+
const step = path[index];
|
|
786
|
+
if (Array.isArray(here)) {
|
|
787
|
+
if (typeof step !== "number" || !Number.isInteger(step) || step < 0 || step >= here.length) return void 0;
|
|
788
|
+
here = here[step];
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (typeof step !== "string" || !Object.hasOwn(here, step)) return void 0;
|
|
792
|
+
if (!plain && isEnvelope(here)) return void 0;
|
|
793
|
+
here = here[step];
|
|
794
|
+
}
|
|
795
|
+
if (!plain && mayBeRef(here)) return void 0;
|
|
796
|
+
return {
|
|
797
|
+
ok: true,
|
|
798
|
+
thing: here,
|
|
799
|
+
hops: NO_HOPS
|
|
800
|
+
};
|
|
801
|
+
}
|
|
533
802
|
/**
|
|
534
803
|
* This function resolves a path from a context, and records each hop.
|
|
535
804
|
*
|
|
536
805
|
* 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
806
|
* of the verb `deref` on the thing that the path reached. A step document is evaluated by the walker of its kind,
|
|
538
807
|
* with the thing that the path reached. After each hop, a reference document is followed until the path reaches
|
|
539
|
-
* something else (collapse).
|
|
808
|
+
* something else (collapse), except in a plain navigation.
|
|
809
|
+
*
|
|
810
|
+
* The codes of a failure:
|
|
811
|
+
*
|
|
812
|
+
* - `#REF!`: the path has no root, or a root that is not open here.
|
|
813
|
+
* - `#NAME?`: the root `names` has no such name.
|
|
814
|
+
* - `#N/A`: a step reaches nothing.
|
|
815
|
+
* - `#STEP?`: a step is not a key and not a step document.
|
|
816
|
+
* - `#CYCLE!`: the references form a cycle.
|
|
540
817
|
*
|
|
541
818
|
* @param nav - The world and the code of the evaluators.
|
|
542
819
|
* @param ctx - The context.
|
|
543
820
|
* @param path - The steps.
|
|
821
|
+
* @param options - `hops: false` does not record the hops.
|
|
544
822
|
* @returns What the path reached and each hop, or an error.
|
|
545
823
|
*/
|
|
546
|
-
function resolve(nav, ctx, path) {
|
|
547
|
-
|
|
824
|
+
function resolve(nav, ctx, path, options = {}) {
|
|
825
|
+
if (options.hops === false) {
|
|
826
|
+
const quick = resolveQuick(nav, ctx, path);
|
|
827
|
+
if (quick !== void 0) return quick;
|
|
828
|
+
}
|
|
829
|
+
const hops = options.hops === false ? void 0 : [];
|
|
830
|
+
const done = hops ?? NO_HOPS;
|
|
831
|
+
const failed = (code, reason) => ({
|
|
832
|
+
ok: false,
|
|
833
|
+
code,
|
|
834
|
+
reason,
|
|
835
|
+
hops: done
|
|
836
|
+
});
|
|
548
837
|
let collapses = 0;
|
|
838
|
+
const adopt = nav.adopt ?? ((value) => value);
|
|
549
839
|
const collapse = (thing) => {
|
|
550
840
|
let here = thing;
|
|
841
|
+
if (nav.plain === true) return {
|
|
842
|
+
ok: true,
|
|
843
|
+
thing: here,
|
|
844
|
+
hops: done
|
|
845
|
+
};
|
|
551
846
|
while (isRef(here)) {
|
|
552
847
|
collapses += 1;
|
|
553
|
-
if (collapses > MAX_COLLAPSE) return
|
|
554
|
-
ok: false,
|
|
555
|
-
code: "#CYCLE!",
|
|
556
|
-
reason: "The references form a cycle.",
|
|
557
|
-
hops
|
|
558
|
-
};
|
|
848
|
+
if (collapses > MAX_COLLAPSE) return failed("#CYCLE!", "The references form a cycle.");
|
|
559
849
|
const inner = walk(here.value);
|
|
560
850
|
if (!inner.ok) return inner;
|
|
561
|
-
here = inner.thing;
|
|
562
|
-
hops
|
|
563
|
-
step:
|
|
851
|
+
here = adopt(inner.thing);
|
|
852
|
+
hops?.push({
|
|
853
|
+
step: "collapse",
|
|
564
854
|
thing: here,
|
|
565
855
|
by: "collapse"
|
|
566
856
|
});
|
|
@@ -568,19 +858,15 @@ function resolve(nav, ctx, path) {
|
|
|
568
858
|
return {
|
|
569
859
|
ok: true,
|
|
570
860
|
thing: here,
|
|
571
|
-
hops
|
|
861
|
+
hops: done
|
|
572
862
|
};
|
|
573
863
|
};
|
|
574
864
|
const walk = (steps) => {
|
|
575
|
-
const
|
|
576
|
-
if (typeof root !== "string" || !ROOTS.includes(root)) return {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
hops
|
|
581
|
-
};
|
|
582
|
-
let here = ctx[root];
|
|
583
|
-
hops.push({
|
|
865
|
+
const root = steps[0];
|
|
866
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) return failed("#REF!", `A path starts with a root: ${ROOTS.join(", ")}.`);
|
|
867
|
+
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(", ")}.`);
|
|
868
|
+
let here = adopt(ctx[root]);
|
|
869
|
+
hops?.push({
|
|
584
870
|
step: root,
|
|
585
871
|
thing: here,
|
|
586
872
|
by: "root"
|
|
@@ -588,50 +874,54 @@ function resolve(nav, ctx, path) {
|
|
|
588
874
|
const first = collapse(here);
|
|
589
875
|
if (!first.ok) return first;
|
|
590
876
|
here = first.thing;
|
|
591
|
-
for (
|
|
877
|
+
for (let index = 1; index < steps.length; index += 1) {
|
|
878
|
+
const step = steps[index];
|
|
879
|
+
if (isHostThing(here) && here.resolve !== void 0 && steps.slice(index).every(isKey)) {
|
|
880
|
+
const keys = steps.slice(index);
|
|
881
|
+
const reached = here.resolve(keys);
|
|
882
|
+
if (reached === void 0) return failed("#N/A", `The path ${keys.join(".")} reaches nothing.`);
|
|
883
|
+
const thing = adopt(reached.thing);
|
|
884
|
+
hops?.push({
|
|
885
|
+
step: keys,
|
|
886
|
+
thing,
|
|
887
|
+
by: "own"
|
|
888
|
+
});
|
|
889
|
+
return collapse(thing);
|
|
890
|
+
}
|
|
592
891
|
if (isKey(step)) {
|
|
593
892
|
const reached = derefStep(nav, here, step);
|
|
594
|
-
if (reached === void 0)
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
hops.push({
|
|
893
|
+
if (reached === void 0) {
|
|
894
|
+
if (root === "names" && index === 1) return failed("#NAME?", `The context has no name ${String(step)}.`);
|
|
895
|
+
return failed("#N/A", `The step ${String(step)} reaches nothing.`);
|
|
896
|
+
}
|
|
897
|
+
here = adopt(reached.thing);
|
|
898
|
+
hops?.push({
|
|
601
899
|
step,
|
|
602
|
-
thing:
|
|
900
|
+
thing: here,
|
|
603
901
|
by: reached.by
|
|
604
902
|
});
|
|
605
|
-
here = reached.thing;
|
|
606
903
|
} else {
|
|
607
|
-
const envelope =
|
|
904
|
+
const envelope = isEnvelope(step) ? step : void 0;
|
|
608
905
|
const found = envelope === void 0 ? void 0 : lookupKind(nav.world, envelope.type);
|
|
609
906
|
const name = found?.ok === true ? entryOf(found.kind.verbs, "step") : void 0;
|
|
610
907
|
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
|
-
};
|
|
908
|
+
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
909
|
const result = walker({
|
|
618
910
|
data: envelope.value,
|
|
619
911
|
here,
|
|
620
912
|
context: ctx,
|
|
621
|
-
deref: (thing, key) =>
|
|
913
|
+
deref: (thing, key) => {
|
|
914
|
+
const reached = derefStep(nav, thing, key);
|
|
915
|
+
return reached === void 0 ? void 0 : adopt(reached.thing);
|
|
916
|
+
}
|
|
622
917
|
});
|
|
623
|
-
if (!result.ok) return
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
};
|
|
629
|
-
hops.push({
|
|
630
|
-
step,
|
|
631
|
-
thing: result.thing,
|
|
918
|
+
if (!result.ok) return failed("#N/A", result.reason);
|
|
919
|
+
here = adopt(result.thing);
|
|
920
|
+
hops?.push({
|
|
921
|
+
step: envelope,
|
|
922
|
+
thing: here,
|
|
632
923
|
by: "step"
|
|
633
924
|
});
|
|
634
|
-
here = result.thing;
|
|
635
925
|
}
|
|
636
926
|
const next = collapse(here);
|
|
637
927
|
if (!next.ok) return next;
|
|
@@ -640,19 +930,14 @@ function resolve(nav, ctx, path) {
|
|
|
640
930
|
return {
|
|
641
931
|
ok: true,
|
|
642
932
|
thing: here,
|
|
643
|
-
hops
|
|
933
|
+
hops: done
|
|
644
934
|
};
|
|
645
935
|
};
|
|
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
|
-
};
|
|
936
|
+
if (path.length === 0) return failed("#REF!", "A path has at least one step: its root.");
|
|
652
937
|
return walk(path);
|
|
653
938
|
}
|
|
654
939
|
/**
|
|
655
|
-
* This function gives the value of a thing: the
|
|
940
|
+
* This function gives the value of a thing: the value that a host thing gives, or else the thing itself.
|
|
656
941
|
*
|
|
657
942
|
* @param thing - The thing.
|
|
658
943
|
* @returns The value.
|
|
@@ -701,89 +986,173 @@ function tag(value, kind) {
|
|
|
701
986
|
}
|
|
702
987
|
//#endregion
|
|
703
988
|
//#region src/parts.ts
|
|
989
|
+
var Bad = class {
|
|
990
|
+
reason;
|
|
991
|
+
at;
|
|
992
|
+
constructor(reason, at) {
|
|
993
|
+
this.reason = reason;
|
|
994
|
+
this.at = at;
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
function setOwn$1(target, key, value) {
|
|
998
|
+
Object.defineProperty(target, key, {
|
|
999
|
+
value,
|
|
1000
|
+
enumerable: true,
|
|
1001
|
+
writable: true,
|
|
1002
|
+
configurable: true
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
function hasSlots(template) {
|
|
1006
|
+
return template.t !== "data";
|
|
1007
|
+
}
|
|
704
1008
|
/**
|
|
705
|
-
* This function splits a value into its
|
|
706
|
-
*
|
|
1009
|
+
* This function splits a value into its parts, by the shape that its kind declares. The evaluator, the view and
|
|
1010
|
+
* the static kit use the same split, thus each one sees the same parts.
|
|
707
1011
|
*
|
|
708
|
-
* @param
|
|
1012
|
+
* @param shape - The shape of the value. The default is data.
|
|
709
1013
|
* @param value - The value of the document.
|
|
710
|
-
* @returns The
|
|
1014
|
+
* @returns The parts and a way to fill their places, or the reason why the value does not have the shape.
|
|
711
1015
|
*/
|
|
712
|
-
function splitParts(
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
1016
|
+
function splitParts(shape, value) {
|
|
1017
|
+
const parts = [];
|
|
1018
|
+
const slot = (item, path, of) => {
|
|
1019
|
+
parts.push({
|
|
1020
|
+
path,
|
|
1021
|
+
key: path.join("."),
|
|
1022
|
+
item,
|
|
1023
|
+
...of === void 0 ? {} : { of }
|
|
1024
|
+
});
|
|
1025
|
+
return {
|
|
1026
|
+
t: "slot",
|
|
1027
|
+
index: parts.length - 1
|
|
1028
|
+
};
|
|
716
1029
|
};
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1030
|
+
const deep = (item, path, of) => {
|
|
1031
|
+
if (isEnvelope(item)) return slot(item, path, of);
|
|
1032
|
+
if (Array.isArray(item)) {
|
|
1033
|
+
const items = item.map((child, index) => deep(child, [...path, index], of));
|
|
1034
|
+
return items.some(hasSlots) ? {
|
|
1035
|
+
t: "list",
|
|
1036
|
+
items
|
|
1037
|
+
} : {
|
|
1038
|
+
t: "data",
|
|
1039
|
+
value: item
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
if (isRecord(item)) {
|
|
1043
|
+
const fields = Object.keys(item).map((key) => [key, deep(item[key], [...path, key], of)]);
|
|
1044
|
+
return fields.some(([, child]) => hasSlots(child)) ? {
|
|
1045
|
+
t: "record",
|
|
1046
|
+
fields
|
|
1047
|
+
} : {
|
|
1048
|
+
t: "data",
|
|
1049
|
+
value: item
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
t: "data",
|
|
1054
|
+
value: item
|
|
1055
|
+
};
|
|
721
1056
|
};
|
|
722
|
-
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
1057
|
+
const walk = (spec, item, path) => {
|
|
1058
|
+
if (spec === void 0 || spec.shape === "none") return {
|
|
1059
|
+
t: "data",
|
|
1060
|
+
value: item
|
|
726
1061
|
};
|
|
1062
|
+
if (spec.shape === "term") return slot(item, path, spec.of);
|
|
1063
|
+
if (spec.shape === "deep") return deep(item, path, spec.of);
|
|
1064
|
+
if (spec.shape === "list") {
|
|
1065
|
+
if (!Array.isArray(item)) throw new Bad("The value must be a list.", path);
|
|
1066
|
+
const each = spec.item ?? {
|
|
1067
|
+
shape: "term",
|
|
1068
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1069
|
+
};
|
|
1070
|
+
return {
|
|
1071
|
+
t: "list",
|
|
1072
|
+
items: item.map((child, index) => walk(each, child, [...path, index]))
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
1076
|
+
if (spec.shape === "map") {
|
|
1077
|
+
const each = spec.item ?? {
|
|
1078
|
+
shape: "term",
|
|
1079
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1080
|
+
};
|
|
1081
|
+
return {
|
|
1082
|
+
t: "record",
|
|
1083
|
+
fields: Object.keys(item).map((key) => [key, walk(each, item[key], [...path, key])])
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
for (const name of spec.required ?? []) if (!Object.hasOwn(item, name)) throw new Bad(`The field ${name} is missing.`, path);
|
|
1087
|
+
const named = Array.isArray(spec.fields) ? Object.fromEntries(spec.fields.map((name) => [name, {
|
|
1088
|
+
shape: "term",
|
|
1089
|
+
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1090
|
+
}])) : spec.fields;
|
|
1091
|
+
const fields = [];
|
|
1092
|
+
for (const key of Object.keys(item)) {
|
|
1093
|
+
const fieldShape = Object.hasOwn(named, key) ? named[key] : spec.rest;
|
|
1094
|
+
fields.push([key, walk(fieldShape, item[key], [...path, key])]);
|
|
1095
|
+
}
|
|
727
1096
|
return {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
parts: value.map((item, index) => of({
|
|
731
|
-
key: String(index),
|
|
732
|
-
item
|
|
733
|
-
}, spec.of))
|
|
1097
|
+
t: "record",
|
|
1098
|
+
fields
|
|
734
1099
|
};
|
|
735
|
-
}
|
|
736
|
-
if (!isRecord(value)) return {
|
|
737
|
-
ok: false,
|
|
738
|
-
reason: "The value must be a record."
|
|
739
1100
|
};
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1101
|
+
let template;
|
|
1102
|
+
try {
|
|
1103
|
+
template = walk(shape, value, []);
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (error instanceof Bad) return {
|
|
1106
|
+
ok: false,
|
|
1107
|
+
reason: error.reason,
|
|
1108
|
+
at: error.at
|
|
1109
|
+
};
|
|
1110
|
+
throw error;
|
|
750
1111
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1112
|
+
const strip = (node) => {
|
|
1113
|
+
if (node.t === "data") return node.value;
|
|
1114
|
+
if (node.t === "slot") return void 0;
|
|
1115
|
+
if (node.t === "list") return node.items.every((child) => child.t === "slot") ? null : node.items.map((child) => strip(child) ?? null);
|
|
1116
|
+
const out = {};
|
|
1117
|
+
for (const [key, child] of node.fields) {
|
|
1118
|
+
const kept = strip(child);
|
|
1119
|
+
if (kept !== void 0) setOwn$1(out, key, kept);
|
|
1120
|
+
}
|
|
1121
|
+
return out;
|
|
1122
|
+
};
|
|
1123
|
+
const fillWith = (node, handles) => {
|
|
1124
|
+
if (node.t === "data") return node.value;
|
|
1125
|
+
if (node.t === "slot") return handles[node.index];
|
|
1126
|
+
if (node.t === "list") return node.items.map((child) => fillWith(child, handles));
|
|
1127
|
+
const out = {};
|
|
1128
|
+
for (const [key, child] of node.fields) setOwn$1(out, key, fillWith(child, handles));
|
|
1129
|
+
return out;
|
|
755
1130
|
};
|
|
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
1131
|
return {
|
|
767
1132
|
ok: true,
|
|
768
|
-
|
|
1133
|
+
parts,
|
|
1134
|
+
data: strip(template) ?? null,
|
|
1135
|
+
fill: (handles) => fillWith(template, handles)
|
|
769
1136
|
};
|
|
770
1137
|
}
|
|
771
1138
|
/**
|
|
772
|
-
* This function
|
|
1139
|
+
* This function tells if the path of a part matches a pattern of a scope rule. The key `"*"` of a pattern matches
|
|
1140
|
+
* each key.
|
|
773
1141
|
*
|
|
774
|
-
* @param
|
|
775
|
-
* @param
|
|
776
|
-
* @returns
|
|
1142
|
+
* @param path - The path of the part.
|
|
1143
|
+
* @param pattern - The pattern.
|
|
1144
|
+
* @returns `true` when the path matches.
|
|
777
1145
|
*/
|
|
778
|
-
function
|
|
779
|
-
return
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
};
|
|
1146
|
+
function pathMatches(path, pattern) {
|
|
1147
|
+
if (path.length !== pattern.length) return false;
|
|
1148
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
1149
|
+
const want = pattern[index];
|
|
1150
|
+
if (want !== "*" && String(want) !== String(path[index])) return false;
|
|
1151
|
+
}
|
|
1152
|
+
return true;
|
|
786
1153
|
}
|
|
1154
|
+
//#endregion
|
|
1155
|
+
//#region src/evaluate.ts
|
|
787
1156
|
/**
|
|
788
1157
|
* This function makes a context with names and capabilities. The other roots are empty, and an evaluation sets
|
|
789
1158
|
* the world.
|
|
@@ -801,194 +1170,557 @@ function makeContext(names = {}, caps = [], self = null) {
|
|
|
801
1170
|
});
|
|
802
1171
|
}
|
|
803
1172
|
/**
|
|
1173
|
+
* This function gives the keys of a capability or of a kind of effect: a text is split at its dots.
|
|
1174
|
+
*
|
|
1175
|
+
* @param kind - The text or the keys.
|
|
1176
|
+
* @returns The keys, as texts.
|
|
1177
|
+
*/
|
|
1178
|
+
function capKeys(kind) {
|
|
1179
|
+
return typeof kind === "string" ? kind.split(".") : kind.map(String);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
804
1182
|
* This function tells if the capabilities permit an effect. A capability permits the kind of its name, and each kind
|
|
805
|
-
*
|
|
1183
|
+
* under it: `store` permits `store.update`. The comparison is key by key, thus `fs.read` does not permit the
|
|
1184
|
+
* kind with the keys `fs` and `read.json`.
|
|
806
1185
|
*
|
|
807
1186
|
* @param caps - The capabilities of the context.
|
|
808
|
-
* @param kind - The kind of the effect.
|
|
1187
|
+
* @param kind - The kind of the effect: a text with dots, or its keys.
|
|
809
1188
|
* @returns `true` when one capability permits it.
|
|
810
1189
|
*/
|
|
811
1190
|
function permits(caps, kind) {
|
|
812
|
-
|
|
1191
|
+
const want = capKeys(kind);
|
|
1192
|
+
return caps.some((cap) => {
|
|
1193
|
+
const have = capKeys(cap);
|
|
1194
|
+
return have.length <= want.length && have.every((key, index) => key === want[index]);
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* This function narrows a context for a part: the part keeps only the capabilities that the context of its parent
|
|
1199
|
+
* permits. Thus an evaluator cannot give a part more effects than it has.
|
|
1200
|
+
*
|
|
1201
|
+
* @param parent - The context of the parent.
|
|
1202
|
+
* @param next - The context that the evaluator gives the part.
|
|
1203
|
+
* @returns The context of the part.
|
|
1204
|
+
*/
|
|
1205
|
+
function narrow(parent, next) {
|
|
1206
|
+
if (next === parent || next.caps === parent.caps) return next;
|
|
1207
|
+
const caps = next.caps.filter((cap) => permits(parent.caps, cap));
|
|
1208
|
+
return caps.length === next.caps.length ? next : {
|
|
1209
|
+
...next,
|
|
1210
|
+
caps
|
|
1211
|
+
};
|
|
813
1212
|
}
|
|
814
1213
|
function selfLabel(self) {
|
|
815
|
-
|
|
816
|
-
|
|
1214
|
+
try {
|
|
1215
|
+
if (isHostThing(self)) return self.label;
|
|
1216
|
+
if (self === null || self === void 0) return "none";
|
|
1217
|
+
return (JSON.stringify(self) ?? describe(self)).slice(0, 40);
|
|
1218
|
+
} catch {
|
|
1219
|
+
return describe(self).slice(0, 40);
|
|
1220
|
+
}
|
|
817
1221
|
}
|
|
818
1222
|
const NO_ANSWER = {
|
|
819
1223
|
verb: "eval",
|
|
820
1224
|
error: "no-answer",
|
|
821
1225
|
steps: []
|
|
822
1226
|
};
|
|
1227
|
+
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
1228
|
+
var Session = class {
|
|
1229
|
+
machine;
|
|
1230
|
+
running;
|
|
1231
|
+
traced;
|
|
1232
|
+
perform;
|
|
1233
|
+
signal;
|
|
1234
|
+
effects;
|
|
1235
|
+
tick = 0;
|
|
1236
|
+
nextId = 0;
|
|
1237
|
+
current;
|
|
1238
|
+
holder;
|
|
1239
|
+
constructor(machine, running, traced, perform, signal, effects) {
|
|
1240
|
+
this.machine = machine;
|
|
1241
|
+
this.running = running;
|
|
1242
|
+
this.traced = traced;
|
|
1243
|
+
this.perform = perform;
|
|
1244
|
+
this.signal = signal;
|
|
1245
|
+
this.effects = effects ?? [];
|
|
1246
|
+
this.holder = traced ? holderNode() : void 0;
|
|
1247
|
+
this.current = this.holder;
|
|
1248
|
+
}
|
|
1249
|
+
resolve(ctx, path, options) {
|
|
1250
|
+
return resolve(this.machine, ctx, path, { hops: options?.hops ?? this.traced });
|
|
1251
|
+
}
|
|
1252
|
+
permitted(ctx, keys) {
|
|
1253
|
+
return this.machine.permits === void 0 ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
|
|
1254
|
+
}
|
|
1255
|
+
record(ctx, kind, payload, place) {
|
|
1256
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
1257
|
+
const text = keys.join(".");
|
|
1258
|
+
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
1259
|
+
const node = this.current;
|
|
1260
|
+
const effect = {
|
|
1261
|
+
index: this.effects.length,
|
|
1262
|
+
kind: text,
|
|
1263
|
+
path: keys,
|
|
1264
|
+
payload,
|
|
1265
|
+
node: node?.id ?? -1,
|
|
1266
|
+
at: this.tick,
|
|
1267
|
+
place,
|
|
1268
|
+
context: ctx
|
|
1269
|
+
};
|
|
1270
|
+
this.effects.push(effect);
|
|
1271
|
+
if (node !== void 0) node.effects.push(effect.index);
|
|
1272
|
+
return effect;
|
|
1273
|
+
}
|
|
1274
|
+
effect(ctx, kind, payload, place) {
|
|
1275
|
+
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
1276
|
+
if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
1277
|
+
const made = this.record(ctx, kind, payload, place);
|
|
1278
|
+
return "ok" in made ? made : ok(payload);
|
|
1279
|
+
}
|
|
1280
|
+
*runEffect(ctx, kind, payload, place) {
|
|
1281
|
+
const made = this.record(ctx, kind, payload, place);
|
|
1282
|
+
if ("ok" in made) return made;
|
|
1283
|
+
const perform = this.perform;
|
|
1284
|
+
if (!this.running || perform === void 0) return ok(payload);
|
|
1285
|
+
const failed = (error) => fail("#CALC!", `The host failed the effect ${made.kind}: ${describe(error)}`, {
|
|
1286
|
+
place,
|
|
1287
|
+
cause: error
|
|
1288
|
+
});
|
|
1289
|
+
let done;
|
|
1290
|
+
try {
|
|
1291
|
+
done = perform(made);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
return failed(error);
|
|
1294
|
+
}
|
|
1295
|
+
if (!(done instanceof Promise)) return ok(done);
|
|
1296
|
+
return yield done.then((value) => ok(value), failed);
|
|
1297
|
+
}
|
|
1298
|
+
*all(list, options = {}) {
|
|
1299
|
+
const results = new Array(list.length);
|
|
1300
|
+
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
1301
|
+
const active = [];
|
|
1302
|
+
let next = 0;
|
|
1303
|
+
const advance = (index, steps, input) => {
|
|
1304
|
+
let step;
|
|
1305
|
+
try {
|
|
1306
|
+
step = input.threw ? steps.throw(input.error) : steps.next(input.value);
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
results[index] = thrown(error, "a part");
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (step.done === true) {
|
|
1312
|
+
results[index] = step.value;
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const item = {
|
|
1316
|
+
index,
|
|
1317
|
+
steps,
|
|
1318
|
+
pending: Promise.resolve()
|
|
1319
|
+
};
|
|
1320
|
+
item.pending = step.value.then((value) => ({
|
|
1321
|
+
item,
|
|
1322
|
+
value,
|
|
1323
|
+
threw: false
|
|
1324
|
+
}), (error) => ({
|
|
1325
|
+
item,
|
|
1326
|
+
error,
|
|
1327
|
+
threw: true
|
|
1328
|
+
}));
|
|
1329
|
+
active.push(item);
|
|
1330
|
+
};
|
|
1331
|
+
const start = () => {
|
|
1332
|
+
while (active.length < limit && next < list.length) {
|
|
1333
|
+
const index = next;
|
|
1334
|
+
next += 1;
|
|
1335
|
+
const begin = list[index];
|
|
1336
|
+
let steps;
|
|
1337
|
+
try {
|
|
1338
|
+
steps = begin();
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
results[index] = thrown(error, "a part");
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
advance(index, steps, { threw: false });
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
start();
|
|
1347
|
+
while (active.length > 0) {
|
|
1348
|
+
const settled = yield Promise.race(active.map((item) => item.pending));
|
|
1349
|
+
active.splice(active.indexOf(settled.item), 1);
|
|
1350
|
+
advance(settled.item.index, settled.item.steps, settled);
|
|
1351
|
+
start();
|
|
1352
|
+
}
|
|
1353
|
+
return results;
|
|
1354
|
+
}
|
|
1355
|
+
enter(meta, ctx) {
|
|
1356
|
+
const node = {
|
|
1357
|
+
id: this.nextId,
|
|
1358
|
+
path: meta.path,
|
|
1359
|
+
doc: meta.doc,
|
|
1360
|
+
declared: meta.declared,
|
|
1361
|
+
answer: meta.answer,
|
|
1362
|
+
side: meta.side,
|
|
1363
|
+
names: Object.keys(ctx.names),
|
|
1364
|
+
scope: ctx.names,
|
|
1365
|
+
caps: ctx.caps,
|
|
1366
|
+
self: selfLabel(ctx.self),
|
|
1367
|
+
enter: this.tick,
|
|
1368
|
+
exit: this.tick,
|
|
1369
|
+
result: fail("#NOEVAL", "The evaluation did not end."),
|
|
1370
|
+
children: [],
|
|
1371
|
+
effects: []
|
|
1372
|
+
};
|
|
1373
|
+
this.nextId += 1;
|
|
1374
|
+
this.tick += 1;
|
|
1375
|
+
(this.current ?? this.holder).children.push(node);
|
|
1376
|
+
return node;
|
|
1377
|
+
}
|
|
1378
|
+
exit(node, result) {
|
|
1379
|
+
node.result = result;
|
|
1380
|
+
node.exit = this.tick;
|
|
1381
|
+
this.tick += 1;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
function holderNode() {
|
|
1385
|
+
return {
|
|
1386
|
+
id: -1,
|
|
1387
|
+
path: [],
|
|
1388
|
+
doc: {
|
|
1389
|
+
type: "?",
|
|
1390
|
+
value: null
|
|
1391
|
+
},
|
|
1392
|
+
declared: false,
|
|
1393
|
+
answer: NO_ANSWER,
|
|
1394
|
+
side: "derive",
|
|
1395
|
+
names: [],
|
|
1396
|
+
scope: {},
|
|
1397
|
+
caps: [],
|
|
1398
|
+
self: "none",
|
|
1399
|
+
enter: 0,
|
|
1400
|
+
exit: 0,
|
|
1401
|
+
result: ok(null),
|
|
1402
|
+
children: [],
|
|
1403
|
+
effects: []
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
823
1406
|
/** This function drives steps that cannot wait. */
|
|
824
1407
|
function drive(steps) {
|
|
825
1408
|
const step = steps.next();
|
|
826
1409
|
if (step.done === true) return step.value;
|
|
827
1410
|
step.value.catch(() => void 0);
|
|
1411
|
+
steps.return(fail("#CALC!", "A part waited, and its parent cannot wait."));
|
|
828
1412
|
return fail("#CALC!", "A part waited, and its parent cannot wait.");
|
|
829
1413
|
}
|
|
1414
|
+
const ABORTED = Symbol("aborted");
|
|
830
1415
|
/** This function drives steps that can wait. The steps before the first wait happen at once, in the call. */
|
|
831
|
-
async function driveWaiting(steps) {
|
|
1416
|
+
async function driveWaiting(steps, signal) {
|
|
1417
|
+
const abort = () => {
|
|
1418
|
+
const reason = fail("#ABORT", `The host stopped the run: ${describe(signal?.reason ?? "no reason")}`, { cause: signal?.reason });
|
|
1419
|
+
try {
|
|
1420
|
+
steps.return(reason);
|
|
1421
|
+
} catch {}
|
|
1422
|
+
return reason;
|
|
1423
|
+
};
|
|
1424
|
+
const aborted = signal === void 0 ? void 0 : new Promise((resolve) => {
|
|
1425
|
+
if (signal.aborted) resolve(ABORTED);
|
|
1426
|
+
else signal.addEventListener("abort", () => resolve(ABORTED), { once: true });
|
|
1427
|
+
});
|
|
1428
|
+
if (signal?.aborted === true) return abort();
|
|
832
1429
|
let step = steps.next();
|
|
833
1430
|
while (step.done !== true) {
|
|
834
1431
|
let value;
|
|
835
1432
|
try {
|
|
836
|
-
value = await step.value;
|
|
1433
|
+
value = aborted === void 0 ? await step.value : await Promise.race([step.value, aborted]);
|
|
837
1434
|
} catch (error) {
|
|
838
1435
|
step = steps.throw(error);
|
|
839
1436
|
continue;
|
|
840
1437
|
}
|
|
1438
|
+
if (value === ABORTED) return abort();
|
|
841
1439
|
step = steps.next(value);
|
|
842
1440
|
}
|
|
843
1441
|
return step.value;
|
|
844
1442
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1443
|
+
function isCompiledEvaluator(evaluator) {
|
|
1444
|
+
return typeof evaluator === "object" && evaluator !== null && typeof evaluator.compile === "function";
|
|
1445
|
+
}
|
|
1446
|
+
function isCompiledRunner(runner) {
|
|
1447
|
+
return typeof runner === "object" && runner !== null && typeof runner.compileRun === "function";
|
|
1448
|
+
}
|
|
1449
|
+
function failing(c, meta, place, code, reason) {
|
|
1450
|
+
const result = fail(code, reason, { place });
|
|
1451
|
+
const plain = () => result;
|
|
1452
|
+
return finish(c, meta, plain, function* () {
|
|
1453
|
+
return result;
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
function finish(c, meta, code, run) {
|
|
1457
|
+
if (!c.traced) return {
|
|
1458
|
+
code,
|
|
1459
|
+
run
|
|
1460
|
+
};
|
|
1461
|
+
const tracedCode = (ctx, rt) => {
|
|
1462
|
+
const s = rt;
|
|
1463
|
+
const node = s.enter(meta, ctx);
|
|
1464
|
+
const before = s.current;
|
|
1465
|
+
s.current = node;
|
|
1466
|
+
let result;
|
|
1467
|
+
try {
|
|
1468
|
+
result = code(ctx, rt);
|
|
1469
|
+
} finally {
|
|
1470
|
+
s.current = before;
|
|
1471
|
+
}
|
|
1472
|
+
s.exit(node, result);
|
|
1473
|
+
return result;
|
|
1474
|
+
};
|
|
1475
|
+
const tracedRun = function* (ctx, rt) {
|
|
1476
|
+
const s = rt;
|
|
1477
|
+
const node = s.enter(meta, ctx);
|
|
1478
|
+
const steps = run(ctx, rt);
|
|
1479
|
+
let input;
|
|
1480
|
+
let threw = false;
|
|
1481
|
+
for (;;) {
|
|
1482
|
+
const before = s.current;
|
|
1483
|
+
s.current = node;
|
|
1484
|
+
let step;
|
|
1485
|
+
try {
|
|
1486
|
+
step = threw ? steps.throw(input) : steps.next(input);
|
|
1487
|
+
} finally {
|
|
1488
|
+
s.current = before;
|
|
1489
|
+
}
|
|
1490
|
+
if (step.done === true) {
|
|
1491
|
+
s.exit(node, step.value);
|
|
1492
|
+
return step.value;
|
|
1493
|
+
}
|
|
1494
|
+
try {
|
|
1495
|
+
input = yield step.value;
|
|
1496
|
+
threw = false;
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
input = error;
|
|
1499
|
+
threw = true;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
return {
|
|
1504
|
+
code: tracedCode,
|
|
1505
|
+
run: tracedRun
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
function compileNode(c, value, place, declared, depth) {
|
|
1509
|
+
const envelope = isEnvelope(value) ? value : void 0;
|
|
1510
|
+
const doc = envelope ?? {
|
|
1511
|
+
type: declared ?? "?",
|
|
1512
|
+
value
|
|
1513
|
+
};
|
|
1514
|
+
const meta0 = {
|
|
1515
|
+
path: place.map(String),
|
|
851
1516
|
doc,
|
|
852
1517
|
declared: envelope === void 0,
|
|
853
1518
|
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: []
|
|
1519
|
+
side: "derive"
|
|
864
1520
|
};
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1521
|
+
if (envelope === void 0 && declared === void 0) return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
1522
|
+
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1523
|
+
const answer = ask(c.machine.world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
1524
|
+
const found = lookupKind(c.machine.world, doc.type);
|
|
1525
|
+
const kind = found.ok ? found.kind : void 0;
|
|
1526
|
+
const meta = {
|
|
1527
|
+
...meta0,
|
|
1528
|
+
answer,
|
|
1529
|
+
side: kind?.side ?? "derive"
|
|
873
1530
|
};
|
|
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)}.`));
|
|
1531
|
+
const kindText = typeName(doc.type);
|
|
1532
|
+
if (answer.error === "unknown-kind") return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
|
|
882
1533
|
const name = answer.evaluator;
|
|
883
|
-
const runners =
|
|
1534
|
+
const runners = c.machine.runners ?? {};
|
|
884
1535
|
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.`));
|
|
1536
|
+
const evaluator = name !== void 0 && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : void 0;
|
|
1537
|
+
if (name === void 0 || runner === void 0 && evaluator === void 0) return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
888
1538
|
const split = splitParts(kind?.parts, doc.value);
|
|
889
|
-
if (!split.ok)
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1539
|
+
if (!split.ok) {
|
|
1540
|
+
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1541
|
+
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
1542
|
+
}
|
|
1543
|
+
const parts = split.parts.map((slot) => {
|
|
1544
|
+
const compiled = compileNode(c, slot.item, [...place, ...slot.path], slot.of, depth + 1);
|
|
1545
|
+
const header = isEnvelope(slot.item) && isRecord(slot.item) && typeof slot.item["name"] === "string" ? slot.item["name"] : void 0;
|
|
1546
|
+
return {
|
|
1547
|
+
key: slot.key,
|
|
1548
|
+
path: slot.path,
|
|
1549
|
+
...header === void 0 ? {} : { name: header },
|
|
1550
|
+
code: compiled.code,
|
|
1551
|
+
run: compiled.run
|
|
902
1552
|
};
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1553
|
+
});
|
|
1554
|
+
const input = {
|
|
1555
|
+
kind: kindText,
|
|
1556
|
+
typePath: found.ok ? found.path : [],
|
|
1557
|
+
doc,
|
|
1558
|
+
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
1559
|
+
data: split.data,
|
|
1560
|
+
parts,
|
|
1561
|
+
place,
|
|
1562
|
+
machine: c.machine
|
|
906
1563
|
};
|
|
907
|
-
const
|
|
908
|
-
const resolveHere = (steps) => resolve(s.machine, ctx, steps);
|
|
1564
|
+
const who = `the evaluator ${name}`;
|
|
909
1565
|
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);
|
|
1566
|
+
const inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
1567
|
+
const run = function* (ctx, rt) {
|
|
1568
|
+
try {
|
|
1569
|
+
return placed(yield* inner(ctx, rt), place);
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
return placed(thrown(error, `the runner ${name}`), place);
|
|
934
1572
|
}
|
|
935
1573
|
};
|
|
1574
|
+
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));
|
|
1575
|
+
return finish(c, meta, code, run);
|
|
1576
|
+
}
|
|
1577
|
+
const inner = isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill);
|
|
1578
|
+
const code = (ctx, rt) => {
|
|
936
1579
|
try {
|
|
937
|
-
return
|
|
1580
|
+
return placed(inner(ctx, rt), place);
|
|
938
1581
|
} 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);
|
|
1582
|
+
return placed(thrown(error, who), place);
|
|
957
1583
|
}
|
|
958
1584
|
};
|
|
1585
|
+
return finish(c, meta, code, function* (ctx, rt) {
|
|
1586
|
+
return code(ctx, rt);
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
function interpret(fn, input, fill) {
|
|
1590
|
+
const { kind, typePath, doc, data, place } = input;
|
|
1591
|
+
const compiled = input.parts;
|
|
1592
|
+
const hasParts = compiled.length > 0;
|
|
1593
|
+
return (ctx, rt) => {
|
|
1594
|
+
const parts = compiled.map((part) => ({
|
|
1595
|
+
key: part.key,
|
|
1596
|
+
path: part.path,
|
|
1597
|
+
...part.name === void 0 ? {} : { name: part.name },
|
|
1598
|
+
evaluate: (override) => part.code(override === void 0 ? ctx : narrow(ctx, override), rt)
|
|
1599
|
+
}));
|
|
1600
|
+
return fn({
|
|
1601
|
+
kind,
|
|
1602
|
+
typePath,
|
|
1603
|
+
doc,
|
|
1604
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
1605
|
+
data,
|
|
1606
|
+
parts,
|
|
1607
|
+
context: ctx,
|
|
1608
|
+
place,
|
|
1609
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1610
|
+
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place)
|
|
1611
|
+
});
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function interpretRun(fn, input, fill) {
|
|
1615
|
+
const { kind, typePath, doc, data, place } = input;
|
|
1616
|
+
const compiled = input.parts;
|
|
1617
|
+
const hasParts = compiled.length > 0;
|
|
1618
|
+
return (ctx, rt) => {
|
|
1619
|
+
const parts = compiled.map((part) => ({
|
|
1620
|
+
key: part.key,
|
|
1621
|
+
path: part.path,
|
|
1622
|
+
...part.name === void 0 ? {} : { name: part.name },
|
|
1623
|
+
evaluate: (override) => part.run(override === void 0 ? ctx : narrow(ctx, override), rt)
|
|
1624
|
+
}));
|
|
1625
|
+
return fn({
|
|
1626
|
+
kind,
|
|
1627
|
+
typePath,
|
|
1628
|
+
doc,
|
|
1629
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
1630
|
+
data,
|
|
1631
|
+
parts,
|
|
1632
|
+
context: ctx,
|
|
1633
|
+
place,
|
|
1634
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1635
|
+
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
1636
|
+
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options)
|
|
1637
|
+
});
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function rootContext(machine, ctx) {
|
|
1641
|
+
return ctx.world === null ? {
|
|
1642
|
+
...ctx,
|
|
1643
|
+
world: worldThing(machine.world)
|
|
1644
|
+
} : ctx;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* This function compiles a document into a plan. The ladder, the kinds and the parts are known after it. A plan
|
|
1648
|
+
* evaluates many times. A document that does not compile gives a plan that fails with the reason, when it evaluates.
|
|
1649
|
+
*
|
|
1650
|
+
* @param machine - The world and the code of the evaluators.
|
|
1651
|
+
* @param doc - The document, as JSON.
|
|
1652
|
+
* @param options - The type of a bare root value, and a trace.
|
|
1653
|
+
* @returns The plan.
|
|
1654
|
+
*/
|
|
1655
|
+
function prepare(machine, doc, options = {}) {
|
|
1656
|
+
const c = {
|
|
1657
|
+
machine,
|
|
1658
|
+
traced: options.trace === true,
|
|
1659
|
+
maxDepth: machine.maxDepth ?? 1e3
|
|
1660
|
+
};
|
|
1661
|
+
let root;
|
|
959
1662
|
try {
|
|
960
|
-
|
|
1663
|
+
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
961
1664
|
} catch (error) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
function
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1665
|
+
const result = thrown(error, "the compilation");
|
|
1666
|
+
root = {
|
|
1667
|
+
code: () => result,
|
|
1668
|
+
run: function* () {
|
|
1669
|
+
return result;
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
const compiled = {
|
|
1674
|
+
key: "",
|
|
1675
|
+
path: [],
|
|
1676
|
+
code: root.code,
|
|
1677
|
+
run: root.run
|
|
1678
|
+
};
|
|
1679
|
+
return {
|
|
1680
|
+
machine,
|
|
1681
|
+
traced: c.traced,
|
|
1682
|
+
root: compiled,
|
|
1683
|
+
evaluate(context = makeContext(), evalOptions = {}) {
|
|
1684
|
+
const s = new Session(machine, false, c.traced, void 0, void 0, evalOptions.effects);
|
|
1685
|
+
return root.code(rootContext(machine, context), s);
|
|
1686
|
+
},
|
|
1687
|
+
run(context = makeContext(), runOptions = {}) {
|
|
1688
|
+
const s = new Session(machine, true, c.traced, runOptions.perform, runOptions.signal, void 0);
|
|
1689
|
+
return driveWaiting(root.run(rootContext(machine, context), s), runOptions.signal);
|
|
1690
|
+
}
|
|
982
1691
|
};
|
|
983
1692
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1693
|
+
/**
|
|
1694
|
+
* This function makes a cache of plans for one machine. A document object compiles one time. Use it only for
|
|
1695
|
+
* documents that do not change after they are made.
|
|
1696
|
+
*
|
|
1697
|
+
* @param machine - The machine.
|
|
1698
|
+
* @param options - The options of each compilation.
|
|
1699
|
+
* @returns A function that gives the plan of a document.
|
|
1700
|
+
*/
|
|
1701
|
+
function planner(machine, options = {}) {
|
|
1702
|
+
const objects = /* @__PURE__ */ new WeakMap();
|
|
1703
|
+
const values = /* @__PURE__ */ new Map();
|
|
1704
|
+
return (doc) => {
|
|
1705
|
+
if (typeof doc === "object" && doc !== null) {
|
|
1706
|
+
let plan = objects.get(doc);
|
|
1707
|
+
if (plan === void 0) {
|
|
1708
|
+
plan = prepare(machine, doc, options);
|
|
1709
|
+
objects.set(doc, plan);
|
|
1710
|
+
}
|
|
1711
|
+
return plan;
|
|
1712
|
+
}
|
|
1713
|
+
let plan = values.get(doc);
|
|
1714
|
+
if (plan === void 0) {
|
|
1715
|
+
plan = prepare(machine, doc, options);
|
|
1716
|
+
if (values.size > 1e3) values.clear();
|
|
1717
|
+
values.set(doc, plan);
|
|
1718
|
+
}
|
|
1719
|
+
return plan;
|
|
1720
|
+
};
|
|
989
1721
|
}
|
|
990
|
-
function record(s,
|
|
991
|
-
const root = holder
|
|
1722
|
+
function record(s, result) {
|
|
1723
|
+
const root = s.holder?.children[0];
|
|
992
1724
|
if (root === void 0) throw new Error("The evaluation made no trace.");
|
|
993
1725
|
return {
|
|
994
1726
|
result,
|
|
@@ -1011,39 +1743,31 @@ function record(s, holder, result) {
|
|
|
1011
1743
|
* @returns The result, the trace and the effects in their order.
|
|
1012
1744
|
*/
|
|
1013
1745
|
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)));
|
|
1746
|
+
const plan = prepare(machine, doc, {
|
|
1747
|
+
trace: true,
|
|
1748
|
+
...declaredType === void 0 ? {} : { declaredType }
|
|
1749
|
+
});
|
|
1750
|
+
const s = new Session(machine, false, true, void 0, void 0, void 0);
|
|
1751
|
+
return record(s, plan.root.code(rootContext(machine, context), s));
|
|
1024
1752
|
}
|
|
1025
1753
|
/**
|
|
1026
1754
|
* This function runs a document: an evaluation in which the host does each effect. A runner waits for its
|
|
1027
1755
|
* 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.
|
|
1756
|
+
* first wait happens at once, in the call. The run records a trace.
|
|
1029
1757
|
*
|
|
1030
1758
|
* @param machine - The world, the evaluators and the runners.
|
|
1031
1759
|
* @param doc - The document, as JSON.
|
|
1032
1760
|
* @param context - The names and the capabilities at the root.
|
|
1033
|
-
* @param options - The host code that does the effects,
|
|
1761
|
+
* @param options - The host code that does the effects, the type of a bare root value, and a signal.
|
|
1034
1762
|
* @returns The result, the trace and the effects in their order, when the run ends.
|
|
1035
1763
|
*/
|
|
1036
1764
|
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)));
|
|
1765
|
+
const plan = prepare(machine, doc, {
|
|
1766
|
+
trace: true,
|
|
1767
|
+
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType }
|
|
1768
|
+
});
|
|
1769
|
+
const s = new Session(machine, true, true, options.perform, options.signal, void 0);
|
|
1770
|
+
return record(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
1047
1771
|
}
|
|
1048
1772
|
/**
|
|
1049
1773
|
* This function finds a node of a trace by its path.
|
|
@@ -1053,10 +1777,16 @@ async function run(machine, doc, context = makeContext(), options = {}) {
|
|
|
1053
1777
|
* @returns The first evaluation at that path, or `undefined` when the document did not evaluate.
|
|
1054
1778
|
*/
|
|
1055
1779
|
function nodeAt(trace, path) {
|
|
1780
|
+
const want = path.map(String);
|
|
1056
1781
|
let node = trace;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1782
|
+
let at = 0;
|
|
1783
|
+
while (node !== void 0 && at < want.length) {
|
|
1784
|
+
const here = node;
|
|
1785
|
+
node = here.children.find((child) => {
|
|
1786
|
+
const tail = child.path.slice(here.path.length);
|
|
1787
|
+
return tail.length > 0 && tail.every((key, index) => key === want[at + index]);
|
|
1788
|
+
});
|
|
1789
|
+
if (node !== void 0) at += node.path.length - here.path.length;
|
|
1060
1790
|
}
|
|
1061
1791
|
return node;
|
|
1062
1792
|
}
|
|
@@ -1068,13 +1798,24 @@ function nodeAt(trace, path) {
|
|
|
1068
1798
|
*/
|
|
1069
1799
|
function flatten(trace) {
|
|
1070
1800
|
const out = [];
|
|
1071
|
-
const
|
|
1801
|
+
const stack = [trace];
|
|
1802
|
+
while (stack.length > 0) {
|
|
1803
|
+
const node = stack.pop();
|
|
1072
1804
|
out.push(node);
|
|
1073
|
-
node.children.
|
|
1074
|
-
}
|
|
1075
|
-
walk(trace);
|
|
1805
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) stack.push(node.children[index]);
|
|
1806
|
+
}
|
|
1076
1807
|
return out.sort((a, b) => a.enter - b.enter);
|
|
1077
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* This function lists the errors of a trace: each node whose result is an error and whose parts did not fail
|
|
1811
|
+
* the same way. These nodes are where each error started.
|
|
1812
|
+
*
|
|
1813
|
+
* @param trace - The root of the trace.
|
|
1814
|
+
* @returns The errors, in the order of their start.
|
|
1815
|
+
*/
|
|
1816
|
+
function failures(trace) {
|
|
1817
|
+
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);
|
|
1818
|
+
}
|
|
1078
1819
|
//#endregion
|
|
1079
1820
|
//#region src/stdlib.ts
|
|
1080
1821
|
/**
|
|
@@ -1222,6 +1963,15 @@ const STD_WORLD = {
|
|
|
1222
1963
|
view: "view.seq"
|
|
1223
1964
|
},
|
|
1224
1965
|
parts: { shape: "list" },
|
|
1966
|
+
scope: [{
|
|
1967
|
+
names: { headers: true },
|
|
1968
|
+
in: [["*"]],
|
|
1969
|
+
sequential: true
|
|
1970
|
+
}, {
|
|
1971
|
+
names: { fixed: ["last"] },
|
|
1972
|
+
in: [["*"]],
|
|
1973
|
+
sequential: true
|
|
1974
|
+
}],
|
|
1225
1975
|
summary: "Its steps, in order. The result of a named step is a name for the later steps."
|
|
1226
1976
|
},
|
|
1227
1977
|
pure: {
|
|
@@ -1306,11 +2056,8 @@ const STD_EVALUATORS = {
|
|
|
1306
2056
|
},
|
|
1307
2057
|
"ref.resolve": ({ data, resolve }) => {
|
|
1308
2058
|
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);
|
|
2059
|
+
const found = resolve(data);
|
|
2060
|
+
return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
|
|
1314
2061
|
},
|
|
1315
2062
|
"tree.node": (input) => treeNode(input.parts, input.context, input.kind)
|
|
1316
2063
|
};
|
|
@@ -1360,34 +2107,44 @@ function treeNode(parts, context, type) {
|
|
|
1360
2107
|
}
|
|
1361
2108
|
return ok(out);
|
|
1362
2109
|
}
|
|
2110
|
+
function hostType(node, wanted) {
|
|
2111
|
+
return isHostThing(node) && node.type === wanted;
|
|
2112
|
+
}
|
|
1363
2113
|
/**
|
|
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.
|
|
2114
|
+
* This function makes a walker `nearest`. The walker follows a key (the default is `parent`) until the node that it
|
|
2115
|
+
* reaches has the wanted type. With `self: true`, the node where the walk starts is the first candidate. A node
|
|
2116
|
+
* without that key ends the walk with an error.
|
|
2117
|
+
*
|
|
2118
|
+
* @param options - The test of a type and the most hops.
|
|
2119
|
+
* @returns The walker.
|
|
1367
2120
|
*/
|
|
1368
|
-
|
|
1369
|
-
const
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
2121
|
+
function nearestWalker(options = {}) {
|
|
2122
|
+
const matches = options.matches ?? hostType;
|
|
2123
|
+
const maxHops = options.maxHops ?? 256;
|
|
2124
|
+
return ({ data, here, deref }) => {
|
|
2125
|
+
const follow = isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent";
|
|
2126
|
+
const wanted = isRecord(data) ? field(data, "type") : void 0;
|
|
2127
|
+
const fromSelf = isRecord(data) && field(data, "self") === true;
|
|
2128
|
+
let at = here;
|
|
2129
|
+
for (let hops = 0; hops < maxHops; hops += 1) {
|
|
2130
|
+
if (hops > 0 || !fromSelf) at = at === void 0 || at === null ? void 0 : deref(at, follow);
|
|
2131
|
+
if (at === void 0 || at === null) return {
|
|
2132
|
+
ok: false,
|
|
2133
|
+
reason: `No node of the type ${String(wanted)} is on the way.`
|
|
2134
|
+
};
|
|
2135
|
+
if (matches(at, wanted)) return {
|
|
2136
|
+
ok: true,
|
|
2137
|
+
thing: at
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
return {
|
|
1376
2141
|
ok: false,
|
|
1377
|
-
reason:
|
|
1378
|
-
};
|
|
1379
|
-
if ((isHostThing(at) ? at.type : void 0) === wanted) return {
|
|
1380
|
-
ok: true,
|
|
1381
|
-
thing: at
|
|
2142
|
+
reason: "The walk is too long."
|
|
1382
2143
|
};
|
|
1383
|
-
}
|
|
1384
|
-
return {
|
|
1385
|
-
ok: false,
|
|
1386
|
-
reason: "The walk is too long."
|
|
1387
2144
|
};
|
|
1388
|
-
}
|
|
2145
|
+
}
|
|
1389
2146
|
/** The walkers of the standard world, by name. */
|
|
1390
|
-
const STD_WALKERS = { "walk.nearest":
|
|
2147
|
+
const STD_WALKERS = { "walk.nearest": nearestWalker() };
|
|
1391
2148
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1392
2149
|
function* inOrder(parts) {
|
|
1393
2150
|
const values = [];
|
|
@@ -1573,26 +2330,26 @@ function layers(steps) {
|
|
|
1573
2330
|
//#region src/context.ts
|
|
1574
2331
|
/** The parts of a context. A term can reach outside itself only through these parts. */
|
|
1575
2332
|
const PARTS = [
|
|
2333
|
+
"self",
|
|
1576
2334
|
"names",
|
|
1577
|
-
"focus",
|
|
1578
2335
|
"world",
|
|
1579
2336
|
"event",
|
|
1580
2337
|
"caps"
|
|
1581
2338
|
];
|
|
1582
2339
|
/** The descriptions of the parts, in the order of `PARTS`. */
|
|
1583
2340
|
const PART_INFO = [
|
|
2341
|
+
{
|
|
2342
|
+
part: "self",
|
|
2343
|
+
title: "Self",
|
|
2344
|
+
reaches: "A position in a structure: the node itself, its parent, its children or its siblings.",
|
|
2345
|
+
direction: "Any direction that a move permits: up, down or to the side."
|
|
2346
|
+
},
|
|
1584
2347
|
{
|
|
1585
2348
|
part: "names",
|
|
1586
2349
|
title: "Names",
|
|
1587
2350
|
reaches: "The values that a binding gives: a let, a parameter, or the result of an earlier step.",
|
|
1588
2351
|
direction: "Back, to the place where the program binds the name."
|
|
1589
2352
|
},
|
|
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
2353
|
{
|
|
1597
2354
|
part: "world",
|
|
1598
2355
|
title: "World",
|
|
@@ -1625,15 +2382,22 @@ const CONSTRUCTS = [
|
|
|
1625
2382
|
side: "derive",
|
|
1626
2383
|
summary: "A pure formula that gives a value at one position of a structure.",
|
|
1627
2384
|
example: {
|
|
1628
|
-
type: "
|
|
2385
|
+
type: "let",
|
|
1629
2386
|
value: {
|
|
1630
|
-
|
|
2387
|
+
bind: { gap: 8 },
|
|
1631
2388
|
body: {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
2389
|
+
type: ["ops", "add"],
|
|
2390
|
+
value: [{
|
|
2391
|
+
type: "ref",
|
|
2392
|
+
value: [
|
|
2393
|
+
"self",
|
|
2394
|
+
"parent",
|
|
2395
|
+
"width"
|
|
2396
|
+
]
|
|
2397
|
+
}, {
|
|
2398
|
+
type: "ref",
|
|
2399
|
+
value: ["names", "gap"]
|
|
2400
|
+
}]
|
|
1637
2401
|
}
|
|
1638
2402
|
}
|
|
1639
2403
|
},
|
|
@@ -1643,10 +2407,10 @@ const CONSTRUCTS = [
|
|
|
1643
2407
|
gives: "extends",
|
|
1644
2408
|
note: "A let binds names for its body."
|
|
1645
2409
|
},
|
|
1646
|
-
|
|
2410
|
+
self: {
|
|
1647
2411
|
reads: true,
|
|
1648
2412
|
gives: "moves",
|
|
1649
|
-
note: "A move reads the parent. An axis moves
|
|
2413
|
+
note: "A move reads the parent. An axis moves self to each target."
|
|
1650
2414
|
},
|
|
1651
2415
|
world: NONE,
|
|
1652
2416
|
event: NONE,
|
|
@@ -1665,15 +2429,19 @@ const CONSTRUCTS = [
|
|
|
1665
2429
|
example: {
|
|
1666
2430
|
type: "seq",
|
|
1667
2431
|
value: [{
|
|
1668
|
-
type: "store
|
|
2432
|
+
type: ["store", "find"],
|
|
1669
2433
|
value: { match: { board: "b1" } },
|
|
1670
2434
|
name: "cards"
|
|
1671
2435
|
}, {
|
|
1672
|
-
type: "store
|
|
2436
|
+
type: ["store", "patch"],
|
|
1673
2437
|
value: {
|
|
1674
2438
|
ids: {
|
|
1675
2439
|
type: "ref",
|
|
1676
|
-
value:
|
|
2440
|
+
value: [
|
|
2441
|
+
"names",
|
|
2442
|
+
"cards",
|
|
2443
|
+
"ids"
|
|
2444
|
+
]
|
|
1677
2445
|
},
|
|
1678
2446
|
set: { done: true }
|
|
1679
2447
|
}
|
|
@@ -1685,7 +2453,7 @@ const CONSTRUCTS = [
|
|
|
1685
2453
|
gives: "extends",
|
|
1686
2454
|
note: "The result of each step is a name for the later steps."
|
|
1687
2455
|
},
|
|
1688
|
-
|
|
2456
|
+
self: NONE,
|
|
1689
2457
|
world: {
|
|
1690
2458
|
reads: true,
|
|
1691
2459
|
gives: "same",
|
|
@@ -1716,7 +2484,7 @@ const CONSTRUCTS = [
|
|
|
1716
2484
|
type: "text",
|
|
1717
2485
|
value: {
|
|
1718
2486
|
type: "ref",
|
|
1719
|
-
value: "title"
|
|
2487
|
+
value: ["names", "title"]
|
|
1720
2488
|
}
|
|
1721
2489
|
}]
|
|
1722
2490
|
}
|
|
@@ -1727,10 +2495,10 @@ const CONSTRUCTS = [
|
|
|
1727
2495
|
gives: "extends",
|
|
1728
2496
|
note: "The data of the node becomes a name for its children."
|
|
1729
2497
|
},
|
|
1730
|
-
|
|
2498
|
+
self: {
|
|
1731
2499
|
reads: true,
|
|
1732
2500
|
gives: "moves",
|
|
1733
|
-
note: "Each child becomes
|
|
2501
|
+
note: "Each child becomes self for its own part."
|
|
1734
2502
|
},
|
|
1735
2503
|
world: {
|
|
1736
2504
|
reads: true,
|
|
@@ -1747,11 +2515,8 @@ const CONSTRUCTS = [
|
|
|
1747
2515
|
side: "derive",
|
|
1748
2516
|
summary: "A rule that gives the box of a child from the box of its parent.",
|
|
1749
2517
|
example: {
|
|
1750
|
-
type: "
|
|
1751
|
-
value: {
|
|
1752
|
-
use: "stack",
|
|
1753
|
-
with: { gap: 8 }
|
|
1754
|
-
}
|
|
2518
|
+
type: ["layouts", "stack"],
|
|
2519
|
+
value: { gap: 8 }
|
|
1755
2520
|
},
|
|
1756
2521
|
uses: {
|
|
1757
2522
|
names: {
|
|
@@ -1759,7 +2524,7 @@ const CONSTRUCTS = [
|
|
|
1759
2524
|
gives: "none",
|
|
1760
2525
|
note: "The rule reads its parameters."
|
|
1761
2526
|
},
|
|
1762
|
-
|
|
2527
|
+
self: {
|
|
1763
2528
|
reads: true,
|
|
1764
2529
|
gives: "none",
|
|
1765
2530
|
note: "The rule reads the box of the parent and the key of the child."
|
|
@@ -1781,10 +2546,10 @@ const CONSTRUCTS = [
|
|
|
1781
2546
|
run: {
|
|
1782
2547
|
type: "seq",
|
|
1783
2548
|
value: [{
|
|
1784
|
-
type: "card
|
|
2549
|
+
type: ["card", "toggle"],
|
|
1785
2550
|
value: { id: {
|
|
1786
2551
|
type: "ref",
|
|
1787
|
-
value: "event
|
|
2552
|
+
value: ["event", "target"]
|
|
1788
2553
|
} }
|
|
1789
2554
|
}]
|
|
1790
2555
|
}
|
|
@@ -1796,7 +2561,7 @@ const CONSTRUCTS = [
|
|
|
1796
2561
|
gives: "same",
|
|
1797
2562
|
note: "The handler reads the names of its place."
|
|
1798
2563
|
},
|
|
1799
|
-
|
|
2564
|
+
self: {
|
|
1800
2565
|
reads: true,
|
|
1801
2566
|
gives: "same",
|
|
1802
2567
|
note: "The handler knows the node that got the event."
|
|
@@ -1829,7 +2594,7 @@ const CONSTRUCTS = [
|
|
|
1829
2594
|
collection: "cards",
|
|
1830
2595
|
match: { board: {
|
|
1831
2596
|
type: "ref",
|
|
1832
|
-
value: "board"
|
|
2597
|
+
value: ["names", "board"]
|
|
1833
2598
|
} }
|
|
1834
2599
|
}
|
|
1835
2600
|
},
|
|
@@ -1839,7 +2604,7 @@ const CONSTRUCTS = [
|
|
|
1839
2604
|
gives: "none",
|
|
1840
2605
|
note: "The query reads its parameters."
|
|
1841
2606
|
},
|
|
1842
|
-
|
|
2607
|
+
self: NONE,
|
|
1843
2608
|
world: {
|
|
1844
2609
|
reads: true,
|
|
1845
2610
|
gives: "none",
|
|
@@ -1851,6 +2616,769 @@ const CONSTRUCTS = [
|
|
|
1851
2616
|
}
|
|
1852
2617
|
];
|
|
1853
2618
|
//#endregion
|
|
1854
|
-
|
|
2619
|
+
//#region src/static.ts
|
|
2620
|
+
function valueAt(value, path) {
|
|
2621
|
+
let here = value;
|
|
2622
|
+
for (const key of path) if (Array.isArray(here)) here = here[Number(key)];
|
|
2623
|
+
else if (isRecord(here) && Object.hasOwn(here, String(key))) here = here[String(key)];
|
|
2624
|
+
else return void 0;
|
|
2625
|
+
return here;
|
|
2626
|
+
}
|
|
2627
|
+
function headerName(item) {
|
|
2628
|
+
return isEnvelope(item) && typeof item.name === "string" ? item.name : void 0;
|
|
2629
|
+
}
|
|
2630
|
+
function namesOf(source, value, covered) {
|
|
2631
|
+
if ("fixed" in source) return source.fixed;
|
|
2632
|
+
if ("keysAt" in source) {
|
|
2633
|
+
const at = valueAt(value, source.keysAt);
|
|
2634
|
+
return isRecord(at) ? Object.keys(at) : [];
|
|
2635
|
+
}
|
|
2636
|
+
if ("listAt" in source) {
|
|
2637
|
+
const at = valueAt(value, source.listAt);
|
|
2638
|
+
return Array.isArray(at) ? at.filter((item) => typeof item === "string") : [];
|
|
2639
|
+
}
|
|
2640
|
+
if ("textAt" in source) {
|
|
2641
|
+
const at = valueAt(value, source.textAt);
|
|
2642
|
+
if (typeof at === "string") return [at];
|
|
2643
|
+
return source.otherwise === void 0 ? [] : [source.otherwise];
|
|
2644
|
+
}
|
|
2645
|
+
return covered.map((slot) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2646
|
+
}
|
|
2647
|
+
/** The names that the scope rules of a kind give each part, and the names that only later parts see. */
|
|
2648
|
+
function scopesOf(rules, value, slots) {
|
|
2649
|
+
const sees = slots.map(() => []);
|
|
2650
|
+
const later = slots.map(() => []);
|
|
2651
|
+
for (const rule of rules ?? []) {
|
|
2652
|
+
const covered = slots.map((slot, index) => ({
|
|
2653
|
+
slot,
|
|
2654
|
+
index
|
|
2655
|
+
})).filter(({ slot }) => rule.in.some((pattern) => pathMatches(slot.path, pattern)));
|
|
2656
|
+
if (rule.sequential !== true) {
|
|
2657
|
+
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
2658
|
+
for (const { index } of covered) sees[index]?.push(...names);
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
if ("headers" in rule.names) {
|
|
2662
|
+
covered.forEach(({ index }, position) => {
|
|
2663
|
+
const before = covered.slice(0, position).map(({ slot }) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2664
|
+
const after = covered.slice(position).map(({ slot }) => headerName(slot.item)).filter((name) => name !== void 0);
|
|
2665
|
+
sees[index]?.push(...before);
|
|
2666
|
+
later[index]?.push(...after);
|
|
2667
|
+
});
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
2671
|
+
covered.forEach(({ index }, position) => {
|
|
2672
|
+
if (position > 0) sees[index]?.push(...names);
|
|
2673
|
+
else later[index]?.push(...names);
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
return {
|
|
2677
|
+
sees,
|
|
2678
|
+
later
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
/**
|
|
2682
|
+
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
2683
|
+
* 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
|
|
2684
|
+
* depth, thus a deep document cannot overflow it.
|
|
2685
|
+
*
|
|
2686
|
+
* @param world - The world of the kinds.
|
|
2687
|
+
* @param doc - The document, as JSON.
|
|
2688
|
+
* @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
|
|
2689
|
+
* @param options - The type of a bare root value, the names of the host, and a function for the problems.
|
|
2690
|
+
*/
|
|
2691
|
+
function walk(world, doc, visit, options = {}) {
|
|
2692
|
+
const stack = [{
|
|
2693
|
+
value: doc,
|
|
2694
|
+
place: [],
|
|
2695
|
+
at: [],
|
|
2696
|
+
declared: options.declaredType,
|
|
2697
|
+
depth: 0,
|
|
2698
|
+
names: new Set(options.names ?? []),
|
|
2699
|
+
later: /* @__PURE__ */ new Set()
|
|
2700
|
+
}];
|
|
2701
|
+
while (stack.length > 0) {
|
|
2702
|
+
const item = stack.pop();
|
|
2703
|
+
const envelope = isEnvelope(item.value) ? item.value : void 0;
|
|
2704
|
+
if (envelope === void 0 && item.declared === void 0) {
|
|
2705
|
+
options.problem?.({
|
|
2706
|
+
place: item.place,
|
|
2707
|
+
at: item.at,
|
|
2708
|
+
code: "#PART?",
|
|
2709
|
+
reason: "The value has no envelope, and its position declares no type."
|
|
2710
|
+
});
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
const doc = envelope ?? {
|
|
2714
|
+
type: item.declared ?? "?",
|
|
2715
|
+
value: item.value
|
|
2716
|
+
};
|
|
2717
|
+
const found = lookupKind(world, doc.type);
|
|
2718
|
+
const kind = found.ok ? found.kind : void 0;
|
|
2719
|
+
if (visit({
|
|
2720
|
+
place: item.place,
|
|
2721
|
+
at: item.at,
|
|
2722
|
+
doc,
|
|
2723
|
+
declared: envelope === void 0,
|
|
2724
|
+
...kind === void 0 ? {} : {
|
|
2725
|
+
kind,
|
|
2726
|
+
typePath: found.ok ? found.path : []
|
|
2727
|
+
},
|
|
2728
|
+
depth: item.depth,
|
|
2729
|
+
names: item.names,
|
|
2730
|
+
later: item.later
|
|
2731
|
+
}) === "skip" || kind === void 0) continue;
|
|
2732
|
+
const split = splitParts(kind.parts, doc.value);
|
|
2733
|
+
if (!split.ok) {
|
|
2734
|
+
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
2735
|
+
options.problem?.({
|
|
2736
|
+
place: item.place,
|
|
2737
|
+
at: item.at,
|
|
2738
|
+
code: "#VALUE!",
|
|
2739
|
+
reason: `${split.reason}${at} The kind is ${typeName(doc.type)}.`
|
|
2740
|
+
});
|
|
2741
|
+
continue;
|
|
2742
|
+
}
|
|
2743
|
+
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
2744
|
+
const base = envelope === void 0 ? item.at : [...item.at, "value"];
|
|
2745
|
+
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
2746
|
+
const slot = split.parts[index];
|
|
2747
|
+
const extra = sees[index] ?? [];
|
|
2748
|
+
const hidden = later[index] ?? [];
|
|
2749
|
+
stack.push({
|
|
2750
|
+
value: slot.item,
|
|
2751
|
+
place: [...item.place, ...slot.path],
|
|
2752
|
+
at: [...base, ...slot.path],
|
|
2753
|
+
declared: slot.of,
|
|
2754
|
+
depth: item.depth + 1,
|
|
2755
|
+
names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
|
|
2756
|
+
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden])
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
|
|
2763
|
+
* reference to a name that a kind around it binds is not free. The others are what the document reads from
|
|
2764
|
+
* outside: its dependencies.
|
|
2765
|
+
*
|
|
2766
|
+
* @param world - The world of the kinds.
|
|
2767
|
+
* @param doc - The document, as JSON.
|
|
2768
|
+
* @param options - The type of a bare root value. The names of the host do not change the result.
|
|
2769
|
+
* @returns The references, in the order of the document.
|
|
2770
|
+
*/
|
|
2771
|
+
function refsOf(world, doc, options = {}) {
|
|
2772
|
+
const out = [];
|
|
2773
|
+
const { names: _host, ...rest } = options;
|
|
2774
|
+
walk(world, doc, (v) => {
|
|
2775
|
+
if (!isRef(v.doc)) return void 0;
|
|
2776
|
+
const path = v.doc.value;
|
|
2777
|
+
const root = typeof path[0] === "string" ? path[0] : "";
|
|
2778
|
+
const first = path[1];
|
|
2779
|
+
const bound = root === "names" && isKey(first) && v.names.has(String(first));
|
|
2780
|
+
out.push({
|
|
2781
|
+
place: v.place,
|
|
2782
|
+
at: v.at,
|
|
2783
|
+
path,
|
|
2784
|
+
root,
|
|
2785
|
+
free: !bound
|
|
2786
|
+
});
|
|
2787
|
+
return "skip";
|
|
2788
|
+
}, rest);
|
|
2789
|
+
return out;
|
|
2790
|
+
}
|
|
2791
|
+
function setAt(value, path, next) {
|
|
2792
|
+
if (path.length === 0) return next;
|
|
2793
|
+
const [key, ...rest] = path;
|
|
2794
|
+
if (Array.isArray(value)) {
|
|
2795
|
+
const copy = [...value];
|
|
2796
|
+
copy[Number(key)] = setAt(value[Number(key)], rest, next);
|
|
2797
|
+
return copy;
|
|
2798
|
+
}
|
|
2799
|
+
const record = isRecord(value) ? value : {};
|
|
2800
|
+
const copy = {};
|
|
2801
|
+
for (const name of Object.keys(record)) Object.defineProperty(copy, name, {
|
|
2802
|
+
value: record[name],
|
|
2803
|
+
enumerable: true,
|
|
2804
|
+
writable: true,
|
|
2805
|
+
configurable: true
|
|
2806
|
+
});
|
|
2807
|
+
Object.defineProperty(copy, String(key), {
|
|
2808
|
+
value: setAt(record[String(key)], rest, next),
|
|
2809
|
+
enumerable: true,
|
|
2810
|
+
writable: true,
|
|
2811
|
+
configurable: true
|
|
2812
|
+
});
|
|
2813
|
+
return copy;
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* This function changes the free references of a document. The document is not changed: the function gives a new
|
|
2817
|
+
* document that shares each part without a change.
|
|
2818
|
+
*
|
|
2819
|
+
* @param world - The world of the kinds.
|
|
2820
|
+
* @param doc - The document, as JSON.
|
|
2821
|
+
* @param change - The function that gives the new document of a reference, or `undefined` to keep it.
|
|
2822
|
+
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
2823
|
+
* @returns The new document.
|
|
2824
|
+
*/
|
|
2825
|
+
function mapRefs(world, doc, change, options = {}) {
|
|
2826
|
+
let out = doc;
|
|
2827
|
+
for (const use of refsOf(world, doc, options)) {
|
|
2828
|
+
if (!use.free) continue;
|
|
2829
|
+
const next = change(use);
|
|
2830
|
+
if (next !== void 0) out = setAt(out, use.at, next);
|
|
2831
|
+
}
|
|
2832
|
+
return out;
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
|
|
2836
|
+
* that an evaluation finds when it reaches them:
|
|
2837
|
+
*
|
|
2838
|
+
* - `#KIND?`: a kind that the world does not have.
|
|
2839
|
+
* - `#NOEVAL`: no evaluator.
|
|
2840
|
+
* - `#VALUE!`: a value without the shape of its kind.
|
|
2841
|
+
* - `#PART?`: a value without a type.
|
|
2842
|
+
* - `#DEPTH!`: too much nesting.
|
|
2843
|
+
* - `#REF!` and `#STEP?`: a bad path.
|
|
2844
|
+
* - `#NAME?`: a name that nothing binds.
|
|
2845
|
+
* - `#CAP!`: an effect that the capabilities do not permit.
|
|
2846
|
+
*
|
|
2847
|
+
* @param machine - The world and the code of the evaluators.
|
|
2848
|
+
* @param doc - The document, as JSON.
|
|
2849
|
+
* @param options - The type of a bare root value, the names of the host and the capabilities.
|
|
2850
|
+
* @returns The problems, in the order of the document.
|
|
2851
|
+
*/
|
|
2852
|
+
function check(machine, doc, options = {}) {
|
|
2853
|
+
const issues = [];
|
|
2854
|
+
const maxDepth = machine.maxDepth ?? 1e3;
|
|
2855
|
+
const anyName = options.names === "any";
|
|
2856
|
+
const roots = machine.roots ?? ROOTS;
|
|
2857
|
+
const has = (table, name) => table !== void 0 && Object.hasOwn(table, name);
|
|
2858
|
+
walk(machine.world, doc, (v) => {
|
|
2859
|
+
if (v.depth > maxDepth) {
|
|
2860
|
+
issues.push({
|
|
2861
|
+
code: "#DEPTH!",
|
|
2862
|
+
place: v.place,
|
|
2863
|
+
reason: `The nesting is deeper than ${maxDepth} documents.`
|
|
2864
|
+
});
|
|
2865
|
+
return "skip";
|
|
2866
|
+
}
|
|
2867
|
+
const header = v.declared ? {} : v.doc;
|
|
2868
|
+
const answer = ask(machine.world, header, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
|
|
2869
|
+
if (answer.error === "unknown-kind") {
|
|
2870
|
+
issues.push({
|
|
2871
|
+
code: "#KIND?",
|
|
2872
|
+
place: v.place,
|
|
2873
|
+
reason: `The world has no kind ${typeName(v.doc.type)}.`
|
|
2874
|
+
});
|
|
2875
|
+
return "skip";
|
|
2876
|
+
}
|
|
2877
|
+
const name = answer.evaluator;
|
|
2878
|
+
if (name === void 0 || !has(machine.evaluators, name) && !has(machine.runners, name)) issues.push({
|
|
2879
|
+
code: "#NOEVAL",
|
|
2880
|
+
place: v.place,
|
|
2881
|
+
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`
|
|
2882
|
+
});
|
|
2883
|
+
if (options.caps !== void 0 && v.kind?.effects !== void 0) {
|
|
2884
|
+
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
2885
|
+
for (const kind of kinds) if (!permits(options.caps, kind)) issues.push({
|
|
2886
|
+
code: "#CAP!",
|
|
2887
|
+
place: v.place,
|
|
2888
|
+
reason: `The capabilities do not permit the effect ${kind.join(".")}.`
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
if (!isRef(v.doc)) return void 0;
|
|
2892
|
+
const path = v.doc.value;
|
|
2893
|
+
const root = path[0];
|
|
2894
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) {
|
|
2895
|
+
issues.push({
|
|
2896
|
+
code: "#REF!",
|
|
2897
|
+
place: v.place,
|
|
2898
|
+
reason: `A path starts with a root: ${ROOTS.join(", ")}.`
|
|
2899
|
+
});
|
|
2900
|
+
return "skip";
|
|
2901
|
+
}
|
|
2902
|
+
if (!roots.includes(root)) issues.push({
|
|
2903
|
+
code: "#REF!",
|
|
2904
|
+
place: v.place,
|
|
2905
|
+
reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}.`
|
|
2906
|
+
});
|
|
2907
|
+
const first = path[1];
|
|
2908
|
+
if (root === "names" && !anyName && isKey(first) && !v.names.has(String(first))) {
|
|
2909
|
+
const later = v.later.has(String(first));
|
|
2910
|
+
issues.push({
|
|
2911
|
+
code: "#NAME?",
|
|
2912
|
+
place: v.place,
|
|
2913
|
+
reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}.`
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
path.slice(1).forEach((step) => {
|
|
2917
|
+
if (isKey(step)) return;
|
|
2918
|
+
const found = isEnvelope(step) ? lookupKind(machine.world, step.type) : void 0;
|
|
2919
|
+
const walker = found?.ok === true && Object.hasOwn(found.kind.verbs, "step") ? found.kind.verbs["step"] : void 0;
|
|
2920
|
+
if (walker === void 0 || !has(machine.walkers, walker)) issues.push({
|
|
2921
|
+
code: "#STEP?",
|
|
2922
|
+
place: v.place,
|
|
2923
|
+
reason: "A step is a key, or a document of a kind with the verb step."
|
|
2924
|
+
});
|
|
2925
|
+
});
|
|
2926
|
+
return "skip";
|
|
2927
|
+
}, {
|
|
2928
|
+
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
2929
|
+
...Array.isArray(options.names) ? { names: options.names } : {},
|
|
2930
|
+
problem: (problem) => issues.push({
|
|
2931
|
+
code: problem.code,
|
|
2932
|
+
place: problem.place,
|
|
2933
|
+
reason: problem.reason
|
|
2934
|
+
})
|
|
2935
|
+
});
|
|
2936
|
+
return issues;
|
|
2937
|
+
}
|
|
2938
|
+
//#endregion
|
|
2939
|
+
//#region src/formula.ts
|
|
2940
|
+
/** The value of a name whose binding failed. A reference to the name gives the error. */
|
|
2941
|
+
var Held = class {
|
|
2942
|
+
error;
|
|
2943
|
+
constructor(error) {
|
|
2944
|
+
this.error = error;
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
const CLOSURE = Symbol.for("coeval.closure");
|
|
2948
|
+
/**
|
|
2949
|
+
* This function tells if a value is a function that the construct `fn` made.
|
|
2950
|
+
*
|
|
2951
|
+
* @param value - The value.
|
|
2952
|
+
* @returns `true` for a closure.
|
|
2953
|
+
*/
|
|
2954
|
+
function isClosure(value) {
|
|
2955
|
+
return typeof value === "function" && value[CLOSURE] !== void 0;
|
|
2956
|
+
}
|
|
2957
|
+
/** The standard extensions: the values that JSON cannot write. */
|
|
2958
|
+
const STD_EXT = {
|
|
2959
|
+
undefined: () => ok(void 0),
|
|
2960
|
+
number: (data) => {
|
|
2961
|
+
if (data === "NaN") return ok(NaN);
|
|
2962
|
+
if (data === "Infinity") return ok(Number.POSITIVE_INFINITY);
|
|
2963
|
+
if (data === "-Infinity") return ok(Number.NEGATIVE_INFINITY);
|
|
2964
|
+
if (data === "-0") return ok(-0);
|
|
2965
|
+
return fail("#VALUE!", "The value of ext.number is NaN, Infinity, -Infinity or -0.");
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
/** The truth of a value in the rules of JavaScript: `0`, `""`, `null`, `undefined` and `NaN` are false. */
|
|
2969
|
+
function jsTruthy(value) {
|
|
2970
|
+
return Boolean(value);
|
|
2971
|
+
}
|
|
2972
|
+
function strictTruthy(value) {
|
|
2973
|
+
return typeof value === "boolean" ? value : void 0;
|
|
2974
|
+
}
|
|
2975
|
+
function setOwn(target, key, value) {
|
|
2976
|
+
Object.defineProperty(target, key, {
|
|
2977
|
+
value,
|
|
2978
|
+
enumerable: true,
|
|
2979
|
+
writable: true,
|
|
2980
|
+
configurable: true
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
function compiled(compile) {
|
|
2984
|
+
return { compile };
|
|
2985
|
+
}
|
|
2986
|
+
function partsOf(value) {
|
|
2987
|
+
return isRecord(value) ? value : {};
|
|
2988
|
+
}
|
|
2989
|
+
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
2990
|
+
/**
|
|
2991
|
+
* This function makes the kinds of the formula kit, for a world. The kinds are the shared constructs of D13 and
|
|
2992
|
+
* the namespaces `ops` and `ext`. A bare value in a position of a term is a literal.
|
|
2993
|
+
*
|
|
2994
|
+
* @returns The entries of the world, by their key.
|
|
2995
|
+
*/
|
|
2996
|
+
function formulaKinds() {
|
|
2997
|
+
const term = {
|
|
2998
|
+
shape: "term",
|
|
2999
|
+
of: "lit"
|
|
3000
|
+
};
|
|
3001
|
+
const terms = {
|
|
3002
|
+
shape: "list",
|
|
3003
|
+
of: "lit"
|
|
3004
|
+
};
|
|
3005
|
+
return {
|
|
3006
|
+
lit: {
|
|
3007
|
+
verbs: { eval: "formula.lit" },
|
|
3008
|
+
summary: LIT_SUMMARY
|
|
3009
|
+
},
|
|
3010
|
+
ref: {
|
|
3011
|
+
verbs: { eval: "formula.ref" },
|
|
3012
|
+
summary: "A path from a root of the context. Its result is what the path reaches."
|
|
3013
|
+
},
|
|
3014
|
+
record: {
|
|
3015
|
+
verbs: { eval: "formula.record" },
|
|
3016
|
+
parts: {
|
|
3017
|
+
shape: "map",
|
|
3018
|
+
of: "lit"
|
|
3019
|
+
},
|
|
3020
|
+
summary: "A record: each field is a term."
|
|
3021
|
+
},
|
|
3022
|
+
list: {
|
|
3023
|
+
verbs: { eval: "formula.list" },
|
|
3024
|
+
parts: terms,
|
|
3025
|
+
summary: "A list: each item is a term."
|
|
3026
|
+
},
|
|
3027
|
+
let: {
|
|
3028
|
+
verbs: { eval: "formula.let" },
|
|
3029
|
+
parts: {
|
|
3030
|
+
shape: "record",
|
|
3031
|
+
fields: {
|
|
3032
|
+
bind: {
|
|
3033
|
+
shape: "map",
|
|
3034
|
+
of: "lit"
|
|
3035
|
+
},
|
|
3036
|
+
body: term
|
|
3037
|
+
},
|
|
3038
|
+
required: ["bind", "body"]
|
|
3039
|
+
},
|
|
3040
|
+
scope: [{
|
|
3041
|
+
names: { keysAt: ["bind"] },
|
|
3042
|
+
in: [["body"]]
|
|
3043
|
+
}],
|
|
3044
|
+
summary: "Names for the body. Each binding sees the names around the let, not the other bindings."
|
|
3045
|
+
},
|
|
3046
|
+
fn: {
|
|
3047
|
+
verbs: { eval: "formula.fn" },
|
|
3048
|
+
parts: {
|
|
3049
|
+
shape: "record",
|
|
3050
|
+
fields: { body: term },
|
|
3051
|
+
required: ["params", "body"]
|
|
3052
|
+
},
|
|
3053
|
+
scope: [{
|
|
3054
|
+
names: { listAt: ["params"] },
|
|
3055
|
+
in: [["body"]]
|
|
3056
|
+
}],
|
|
3057
|
+
summary: "A function: its parameters are names for its body."
|
|
3058
|
+
},
|
|
3059
|
+
call: {
|
|
3060
|
+
verbs: { eval: "formula.call" },
|
|
3061
|
+
parts: {
|
|
3062
|
+
shape: "record",
|
|
3063
|
+
fields: {
|
|
3064
|
+
fn: term,
|
|
3065
|
+
args: terms
|
|
3066
|
+
},
|
|
3067
|
+
required: ["fn"]
|
|
3068
|
+
},
|
|
3069
|
+
summary: "It calls a function with the values of its arguments."
|
|
3070
|
+
},
|
|
3071
|
+
if: {
|
|
3072
|
+
verbs: { eval: "formula.if" },
|
|
3073
|
+
parts: {
|
|
3074
|
+
shape: "record",
|
|
3075
|
+
fields: {
|
|
3076
|
+
test: term,
|
|
3077
|
+
then: term,
|
|
3078
|
+
else: term
|
|
3079
|
+
},
|
|
3080
|
+
required: ["test", "then"]
|
|
3081
|
+
},
|
|
3082
|
+
summary: "It evaluates the test, then only the branch that the test selects."
|
|
3083
|
+
},
|
|
3084
|
+
and: {
|
|
3085
|
+
verbs: { eval: "formula.and" },
|
|
3086
|
+
parts: terms,
|
|
3087
|
+
summary: "True when each term is true. It stops at the first false term."
|
|
3088
|
+
},
|
|
3089
|
+
or: {
|
|
3090
|
+
verbs: { eval: "formula.or" },
|
|
3091
|
+
parts: terms,
|
|
3092
|
+
summary: "True when one term is true. It stops at the first true term."
|
|
3093
|
+
},
|
|
3094
|
+
ops: {
|
|
3095
|
+
kinds: {},
|
|
3096
|
+
fallback: {
|
|
3097
|
+
verbs: { eval: "formula.op" },
|
|
3098
|
+
parts: terms,
|
|
3099
|
+
summary: "An operation of the language, applied to the values of its terms."
|
|
3100
|
+
}
|
|
3101
|
+
},
|
|
3102
|
+
ext: {
|
|
3103
|
+
kinds: {},
|
|
3104
|
+
fallback: {
|
|
3105
|
+
verbs: { eval: "formula.ext" },
|
|
3106
|
+
summary: "An extension of the language. Its value is data."
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* This function makes the evaluators of the formula kit for one language. Each evaluator compiles its document
|
|
3113
|
+
* one time into code.
|
|
3114
|
+
*
|
|
3115
|
+
* @param profile - The operations and the rules of the language.
|
|
3116
|
+
* @returns The evaluators, by their name.
|
|
3117
|
+
*/
|
|
3118
|
+
function formulaEvaluators(profile = {}) {
|
|
3119
|
+
const truthy = profile.truthy ?? strictTruthy;
|
|
3120
|
+
const otherwise = ok(profile.otherwise ?? null);
|
|
3121
|
+
const logicValue = profile.logic === "value";
|
|
3122
|
+
const omit = profile.omit;
|
|
3123
|
+
const missing = profile.missing;
|
|
3124
|
+
const check = profile.check;
|
|
3125
|
+
const lookup = (name) => {
|
|
3126
|
+
const ops = profile.ops;
|
|
3127
|
+
if (ops === void 0) return void 0;
|
|
3128
|
+
if (typeof ops === "function") return ops(name);
|
|
3129
|
+
return Object.hasOwn(ops, name) ? ops[name] : void 0;
|
|
3130
|
+
};
|
|
3131
|
+
const notTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
3132
|
+
return {
|
|
3133
|
+
"formula.lit": compiled(({ doc }) => {
|
|
3134
|
+
const result = ok(doc.value);
|
|
3135
|
+
return () => result;
|
|
3136
|
+
}),
|
|
3137
|
+
"formula.ref": compiled(({ doc }) => {
|
|
3138
|
+
const path = doc.value;
|
|
3139
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
3140
|
+
const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
|
|
3141
|
+
return () => bad;
|
|
3142
|
+
}
|
|
3143
|
+
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
3144
|
+
return (ctx, rt) => {
|
|
3145
|
+
if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
|
|
3146
|
+
const bound = ctx.names[head];
|
|
3147
|
+
if (bound instanceof Held) return {
|
|
3148
|
+
ok: false,
|
|
3149
|
+
error: bound.error
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
const found = rt.resolve(ctx, path);
|
|
3153
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
3154
|
+
const value = valueOfThing(found.thing);
|
|
3155
|
+
if (value instanceof Held) return {
|
|
3156
|
+
ok: false,
|
|
3157
|
+
error: value.error
|
|
3158
|
+
};
|
|
3159
|
+
if (missing !== void 0 && missing(value)) return fail("#N/A", "The path reaches no value.");
|
|
3160
|
+
return ok(value);
|
|
3161
|
+
};
|
|
3162
|
+
}),
|
|
3163
|
+
"formula.record": compiled(({ value }) => {
|
|
3164
|
+
const fields = Object.entries(partsOf(value));
|
|
3165
|
+
return (ctx, rt) => {
|
|
3166
|
+
const out = {};
|
|
3167
|
+
for (const [key, part] of fields) {
|
|
3168
|
+
const result = part.code(ctx, rt);
|
|
3169
|
+
if (!result.ok) {
|
|
3170
|
+
if (omit !== void 0 && omit(result.error)) continue;
|
|
3171
|
+
return result;
|
|
3172
|
+
}
|
|
3173
|
+
setOwn(out, key, result.value);
|
|
3174
|
+
}
|
|
3175
|
+
return ok(out);
|
|
3176
|
+
};
|
|
3177
|
+
}),
|
|
3178
|
+
"formula.list": compiled(({ parts }) => (ctx, rt) => {
|
|
3179
|
+
const out = new Array(parts.length);
|
|
3180
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3181
|
+
const result = parts[index].code(ctx, rt);
|
|
3182
|
+
if (!result.ok) return result;
|
|
3183
|
+
out[index] = result.value;
|
|
3184
|
+
}
|
|
3185
|
+
return ok(out);
|
|
3186
|
+
}),
|
|
3187
|
+
"formula.let": compiled(({ value }) => {
|
|
3188
|
+
const record = partsOf(value);
|
|
3189
|
+
const bindings = Object.entries(partsOf(record["bind"]));
|
|
3190
|
+
const body = record["body"];
|
|
3191
|
+
return (ctx, rt) => {
|
|
3192
|
+
const names = { ...ctx.names };
|
|
3193
|
+
for (const [name, part] of bindings) {
|
|
3194
|
+
const result = part.code(ctx, rt);
|
|
3195
|
+
setOwn(names, name, result.ok ? result.value : new Held(result.error));
|
|
3196
|
+
}
|
|
3197
|
+
return body.code({
|
|
3198
|
+
...ctx,
|
|
3199
|
+
names
|
|
3200
|
+
}, rt);
|
|
3201
|
+
};
|
|
3202
|
+
}),
|
|
3203
|
+
"formula.fn": compiled(({ value, data }) => {
|
|
3204
|
+
const params = isRecord(data) ? data["params"] : void 0;
|
|
3205
|
+
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
|
|
3206
|
+
const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
3207
|
+
return () => bad;
|
|
3208
|
+
}
|
|
3209
|
+
const names = params;
|
|
3210
|
+
const body = partsOf(value)["body"];
|
|
3211
|
+
return (ctx, rt) => {
|
|
3212
|
+
const closure = (...args) => {
|
|
3213
|
+
const scope = { ...ctx.names };
|
|
3214
|
+
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
3215
|
+
const result = body.code({
|
|
3216
|
+
...ctx,
|
|
3217
|
+
names: scope
|
|
3218
|
+
}, rt);
|
|
3219
|
+
if (!result.ok) throw new Failure(result.error);
|
|
3220
|
+
return result.value;
|
|
3221
|
+
};
|
|
3222
|
+
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
3223
|
+
return ok(closure);
|
|
3224
|
+
};
|
|
3225
|
+
}),
|
|
3226
|
+
"formula.call": compiled(({ value }) => {
|
|
3227
|
+
const record = partsOf(value);
|
|
3228
|
+
const fnPart = record["fn"];
|
|
3229
|
+
const args = Array.isArray(record["args"]) ? record["args"] : [];
|
|
3230
|
+
return (ctx, rt) => {
|
|
3231
|
+
const fn = fnPart.code(ctx, rt);
|
|
3232
|
+
if (!fn.ok) return fn;
|
|
3233
|
+
if (typeof fn.value !== "function") return fail("#VALUE!", "The fn of call must give a function.");
|
|
3234
|
+
const values = new Array(args.length);
|
|
3235
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3236
|
+
const result = args[index].code(ctx, rt);
|
|
3237
|
+
if (!result.ok) return result;
|
|
3238
|
+
values[index] = result.value;
|
|
3239
|
+
}
|
|
3240
|
+
try {
|
|
3241
|
+
return ok(fn.value(...values));
|
|
3242
|
+
} catch (error) {
|
|
3243
|
+
return thrown(error, "the function of call");
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
}),
|
|
3247
|
+
"formula.if": compiled(({ value }) => {
|
|
3248
|
+
const record = partsOf(value);
|
|
3249
|
+
const test = record["test"];
|
|
3250
|
+
const then = record["then"];
|
|
3251
|
+
const other = record["else"];
|
|
3252
|
+
return (ctx, rt) => {
|
|
3253
|
+
const chosen = test.code(ctx, rt);
|
|
3254
|
+
if (!chosen.ok) return chosen;
|
|
3255
|
+
const truth = truthy(chosen.value);
|
|
3256
|
+
if (truth === void 0) return notTruth("test of if");
|
|
3257
|
+
if (truth) return then.code(ctx, rt);
|
|
3258
|
+
return other === void 0 ? otherwise : other.code(ctx, rt);
|
|
3259
|
+
};
|
|
3260
|
+
}),
|
|
3261
|
+
"formula.and": compiled(({ parts }) => (ctx, rt) => {
|
|
3262
|
+
let last = ok(true);
|
|
3263
|
+
for (const part of parts) {
|
|
3264
|
+
last = part.code(ctx, rt);
|
|
3265
|
+
if (!last.ok) return last;
|
|
3266
|
+
const truth = truthy(last.value);
|
|
3267
|
+
if (truth === void 0) return notTruth("term of and");
|
|
3268
|
+
if (!truth) return logicValue ? last : ok(false);
|
|
3269
|
+
}
|
|
3270
|
+
return logicValue ? last : ok(true);
|
|
3271
|
+
}),
|
|
3272
|
+
"formula.or": compiled(({ parts }) => (ctx, rt) => {
|
|
3273
|
+
let last = ok(false);
|
|
3274
|
+
for (const part of parts) {
|
|
3275
|
+
last = part.code(ctx, rt);
|
|
3276
|
+
if (!last.ok) return last;
|
|
3277
|
+
const truth = truthy(last.value);
|
|
3278
|
+
if (truth === void 0) return notTruth("term of or");
|
|
3279
|
+
if (truth) return logicValue ? last : ok(true);
|
|
3280
|
+
}
|
|
3281
|
+
return logicValue ? last : ok(false);
|
|
3282
|
+
}),
|
|
3283
|
+
"formula.op": compiled(({ typePath, parts }) => {
|
|
3284
|
+
const name = typePath.slice(1).join(".");
|
|
3285
|
+
const lazy = profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name) ? profile.lazy[name] : void 0;
|
|
3286
|
+
if (lazy !== void 0) return (ctx, rt) => {
|
|
3287
|
+
try {
|
|
3288
|
+
return lazy(parts.map((part) => () => part.code(ctx, rt)));
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
return thrown(error, `the operation ${name}`);
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
const fn = lookup(name);
|
|
3294
|
+
if (fn === void 0) {
|
|
3295
|
+
const unknown = fail("#NAME?", `The language has no operation ${name}.`);
|
|
3296
|
+
return () => unknown;
|
|
3297
|
+
}
|
|
3298
|
+
const done = (out) => check === void 0 ? ok(out) : check(out, name) ?? ok(out);
|
|
3299
|
+
const failed = (error) => thrown(error, `the operation ${name}`);
|
|
3300
|
+
if (parts.length === 1) {
|
|
3301
|
+
const [a] = parts;
|
|
3302
|
+
return (ctx, rt) => {
|
|
3303
|
+
const x = a.code(ctx, rt);
|
|
3304
|
+
if (!x.ok) return x;
|
|
3305
|
+
try {
|
|
3306
|
+
return done(fn(x.value));
|
|
3307
|
+
} catch (error) {
|
|
3308
|
+
return failed(error);
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
if (parts.length === 2) {
|
|
3313
|
+
const [a, b] = parts;
|
|
3314
|
+
return (ctx, rt) => {
|
|
3315
|
+
const x = a.code(ctx, rt);
|
|
3316
|
+
if (!x.ok) return x;
|
|
3317
|
+
const y = b.code(ctx, rt);
|
|
3318
|
+
if (!y.ok) return y;
|
|
3319
|
+
try {
|
|
3320
|
+
return done(fn(x.value, y.value));
|
|
3321
|
+
} catch (error) {
|
|
3322
|
+
return failed(error);
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
return (ctx, rt) => {
|
|
3327
|
+
const values = new Array(parts.length);
|
|
3328
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3329
|
+
const result = parts[index].code(ctx, rt);
|
|
3330
|
+
if (!result.ok) return result;
|
|
3331
|
+
values[index] = result.value;
|
|
3332
|
+
}
|
|
3333
|
+
try {
|
|
3334
|
+
return done(fn(...values));
|
|
3335
|
+
} catch (error) {
|
|
3336
|
+
return failed(error);
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
}),
|
|
3340
|
+
"formula.ext": compiled(({ typePath, doc }) => {
|
|
3341
|
+
const name = typePath.slice(1).join(".");
|
|
3342
|
+
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);
|
|
3343
|
+
if (handler === void 0) {
|
|
3344
|
+
const unknown = fail("#NAME?", `The language has no extension ${name}.`);
|
|
3345
|
+
return () => unknown;
|
|
3346
|
+
}
|
|
3347
|
+
return () => {
|
|
3348
|
+
try {
|
|
3349
|
+
return handler(doc.value);
|
|
3350
|
+
} catch (error) {
|
|
3351
|
+
return thrown(error, `the extension ${name}`);
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
})
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
3359
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
3360
|
+
*
|
|
3361
|
+
* @param profile - The operations and the rules of the language.
|
|
3362
|
+
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
3363
|
+
* @returns The machine.
|
|
3364
|
+
*/
|
|
3365
|
+
function formulaMachine(profile = {}, more = {}) {
|
|
3366
|
+
return {
|
|
3367
|
+
...more,
|
|
3368
|
+
world: {
|
|
3369
|
+
kinds: {
|
|
3370
|
+
...formulaKinds(),
|
|
3371
|
+
...more.world?.kinds
|
|
3372
|
+
},
|
|
3373
|
+
defaults: { ...more.world?.defaults }
|
|
3374
|
+
},
|
|
3375
|
+
evaluators: {
|
|
3376
|
+
...formulaEvaluators(profile),
|
|
3377
|
+
...more.evaluators
|
|
3378
|
+
}
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
//#endregion
|
|
3382
|
+
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
3383
|
|
|
1856
3384
|
//# sourceMappingURL=index.js.map
|