@mark1russell7/coeval 0.1.0 → 0.2.0

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