@mark1russell7/coeval 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,1856 @@
1
+ //#region src/doc.ts
2
+ /** The keys that an envelope can have. */
3
+ const ENVELOPE_KEYS = [
4
+ "type",
5
+ "value",
6
+ "name",
7
+ "own"
8
+ ];
9
+ /**
10
+ * This function tells if a value is a JSON record (not an array and not null).
11
+ *
12
+ * @param value - The value to examine.
13
+ * @returns `true` for a record.
14
+ */
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ /**
19
+ * This function reads a field of a record. It reads only the own fields, thus `constructor` is not a field.
20
+ *
21
+ * @param record - The record.
22
+ * @param key - The name of the field.
23
+ * @returns The value of the field, or `undefined` when the record has no such field.
24
+ */
25
+ function field(record, key) {
26
+ return Object.hasOwn(record, key) ? record[key] : void 0;
27
+ }
28
+ /**
29
+ * This function tells if a value is a key of a path: a text, or a whole number from 0.
30
+ *
31
+ * @param value - The value to examine.
32
+ * @returns `true` for a key.
33
+ */
34
+ function isKey(value) {
35
+ return typeof value === "string" || typeof value === "number" && Number.isInteger(value) && value >= 0;
36
+ }
37
+ /**
38
+ * This function tells if a value is a reference to a type. The reference is a text, a list of keys, or a document
39
+ * of type `ref` whose path starts at the root `world`.
40
+ *
41
+ * @param value - The value to examine.
42
+ * @returns `true` for a reference to a type.
43
+ */
44
+ function isTypeRef(value) {
45
+ if (typeof value === "string") return value !== "";
46
+ if (Array.isArray(value)) return value.length > 0 && value.every(isKey);
47
+ if (!isRecord(value)) return false;
48
+ const path = field(value, "value");
49
+ return field(value, "type") === "ref" && Array.isArray(path) && path[0] === "world";
50
+ }
51
+ /**
52
+ * This function gives the path of a reference to a type, from the root of the world.
53
+ *
54
+ * @param type - The reference.
55
+ * @returns The keys from the root of the world, or `undefined` for a computed step that has no fixed key.
56
+ */
57
+ function typePath(type) {
58
+ if (typeof type === "string") return [type];
59
+ if (Array.isArray(type)) return type;
60
+ const steps = type.value.slice(1);
61
+ return steps.every(isKey) ? steps : void 0;
62
+ }
63
+ /**
64
+ * This function gives the text of a type for a reader: its keys, joined with a dot.
65
+ *
66
+ * @param type - The reference to the type.
67
+ * @returns The text, for example `git.push`.
68
+ */
69
+ function typeName(type) {
70
+ return typePath(type)?.join(".") ?? "(computed)";
71
+ }
72
+ /**
73
+ * This function tells if a value has the form of an envelope. That form is a record with a reference to a type
74
+ * and a `value`. It has no other keys than the keys of an envelope. A contract decides where an envelope is
75
+ * expected. This function only checks the form.
76
+ *
77
+ * @param value - The value to examine.
78
+ * @returns `true` when the value has the form of an envelope.
79
+ */
80
+ 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));
88
+ }
89
+ /**
90
+ * This function makes an envelope.
91
+ *
92
+ * @param type - The reference to the kind.
93
+ * @param value - The value.
94
+ * @param header - The optional name and own entries.
95
+ * @returns The envelope.
96
+ */
97
+ function typed(type, value, header = {}) {
98
+ return {
99
+ type,
100
+ value,
101
+ ...header.name === void 0 ? {} : { name: header.name },
102
+ ...header.own === void 0 ? {} : { own: header.own }
103
+ };
104
+ }
105
+ /**
106
+ * This function makes a reference document: a path from a root of the context.
107
+ *
108
+ * @param path - The steps. The first step is a root: `self`, `names`, `world`, `event` or `caps`.
109
+ * @returns The document of type `ref`.
110
+ */
111
+ function ref(...path) {
112
+ return typed("ref", path);
113
+ }
114
+ /**
115
+ * This function gives an envelope as a JSON record, for a store or for a view.
116
+ *
117
+ * @param doc - The envelope.
118
+ * @returns The same envelope as JSON.
119
+ */
120
+ function toJson(doc) {
121
+ return {
122
+ type: doc.type,
123
+ value: doc.value,
124
+ ...doc.name === void 0 ? {} : { name: doc.name },
125
+ ...doc.own === void 0 ? {} : { own: doc.own }
126
+ };
127
+ }
128
+ /**
129
+ * This function reads an envelope from JSON. A value without the form of an envelope gives `undefined`.
130
+ *
131
+ * @param value - The JSON value.
132
+ * @returns The envelope, or `undefined`.
133
+ */
134
+ function fromJson(value) {
135
+ if (!isEnvelope(value)) return void 0;
136
+ const own = field(value, "own");
137
+ const ownEntries = {};
138
+ if (isRecord(own)) {
139
+ for (const [verb, name] of Object.entries(own)) if (typeof name === "string") ownEntries[verb] = name;
140
+ }
141
+ const name = field(value, "name");
142
+ return typed(field(value, "type"), field(value, "value") ?? null, {
143
+ ...typeof name === "string" ? { name } : {},
144
+ ...isRecord(own) ? { own: ownEntries } : {}
145
+ });
146
+ }
147
+ //#endregion
148
+ //#region src/world.ts
149
+ /**
150
+ * This function tells if an entry of the world is a kind.
151
+ *
152
+ * @param entry - The entry.
153
+ * @returns `true` for a kind.
154
+ */
155
+ function isKind(entry) {
156
+ return entry !== void 0 && Object.hasOwn(entry, "verbs");
157
+ }
158
+ /**
159
+ * This function tells if an entry of the world is an alias.
160
+ *
161
+ * @param entry - The entry.
162
+ * @returns `true` for an alias.
163
+ */
164
+ function isAlias(entry) {
165
+ return entry !== void 0 && entry.type === "ref" && Array.isArray(entry.value);
166
+ }
167
+ function entryAt(entries, key) {
168
+ const name = String(key);
169
+ return Object.hasOwn(entries, name) ? entries[name] : void 0;
170
+ }
171
+ /**
172
+ * 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.
174
+ *
175
+ * @param world - The world.
176
+ * @param type - The reference to the type.
177
+ * @returns The kind and the path of the kind after each alias, or the reason of a failure.
178
+ */
179
+ function lookupKind(world, type) {
180
+ const start = typePath(type);
181
+ if (start === void 0) return {
182
+ ok: false,
183
+ reason: "computed",
184
+ path: []
185
+ };
186
+ let path = start;
187
+ let aliases = 0;
188
+ for (;;) {
189
+ let entries = world.kinds;
190
+ let entry;
191
+ let followed = false;
192
+ for (let index = 0; index < path.length; index += 1) {
193
+ const key = path[index];
194
+ entry = entryAt(entries, key);
195
+ if (entry === void 0) return {
196
+ ok: false,
197
+ reason: "unknown",
198
+ path
199
+ };
200
+ if (isAlias(entry)) {
201
+ aliases += 1;
202
+ if (aliases > 32) return {
203
+ ok: false,
204
+ reason: "cycle",
205
+ path
206
+ };
207
+ const target = entry.value.slice(1);
208
+ if (entry.value[0] !== "world" || !target.every((item) => typeof item === "string" || typeof item === "number")) return {
209
+ ok: false,
210
+ reason: "not-a-kind",
211
+ path
212
+ };
213
+ path = [...target, ...path.slice(index + 1)];
214
+ followed = true;
215
+ break;
216
+ }
217
+ if (index < path.length - 1) {
218
+ if (isKind(entry)) return {
219
+ ok: false,
220
+ reason: "not-a-kind",
221
+ path
222
+ };
223
+ entries = entry.kinds;
224
+ }
225
+ }
226
+ if (followed) continue;
227
+ if (!isKind(entry)) return {
228
+ ok: false,
229
+ reason: "not-a-kind",
230
+ path
231
+ };
232
+ return {
233
+ ok: true,
234
+ kind: entry,
235
+ path,
236
+ aliases
237
+ };
238
+ }
239
+ }
240
+ /**
241
+ * This function gives the entries of a world as a JSON record, so that a reference can walk it. A kind is a
242
+ * record with its verbs, an alias is a reference document, and a namespace is a record of its entries.
243
+ *
244
+ * @param entries - The entries.
245
+ * @returns The JSON form.
246
+ */
247
+ function entriesAsJson(entries) {
248
+ const out = {};
249
+ for (const [key, entry] of Object.entries(entries)) if (isAlias(entry)) out[key] = {
250
+ type: "ref",
251
+ value: entry.value
252
+ };
253
+ else if (isKind(entry)) out[key] = JSON.parse(JSON.stringify(entry));
254
+ else out[key] = entriesAsJson(entry.kinds);
255
+ return out;
256
+ }
257
+ /**
258
+ * This function makes the document of a kind, so that a view can draw it as each other document.
259
+ *
260
+ * @param name - The name of the kind.
261
+ * @param kind - The kind.
262
+ * @returns The document of type `kind`.
263
+ */
264
+ 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
+ return {
267
+ type: "kind",
268
+ value: {
269
+ name,
270
+ verbs: { ...kind.verbs },
271
+ parts,
272
+ side: kind.side ?? "derive",
273
+ summary: kind.summary ?? ""
274
+ }
275
+ };
276
+ }
277
+ //#endregion
278
+ //#region src/ladder.ts
279
+ /** The rungs of the ladder, in the order that a question visits them. */
280
+ const RUNGS = [
281
+ "own",
282
+ "kind",
283
+ "default"
284
+ ];
285
+ function entryOf$1(verbs, verb) {
286
+ return verbs !== void 0 && Object.hasOwn(verbs, verb) ? verbs[verb] : void 0;
287
+ }
288
+ /**
289
+ * This function finds the kind that a type names, and follows each alias. It gives `undefined` for an unknown
290
+ * type.
291
+ *
292
+ * @param world - The world.
293
+ * @param type - The reference to the type.
294
+ * @returns The kind, or `undefined`.
295
+ */
296
+ function kindOf(world, type) {
297
+ const found = lookupKind(world, type);
298
+ return found.ok ? found.kind : void 0;
299
+ }
300
+ /**
301
+ * This function finds a kind by a text type. It is the same as `kindOf` for a one-key reference.
302
+ *
303
+ * @param world - The world.
304
+ * @param name - The text of the type.
305
+ * @returns The kind, or `undefined`.
306
+ */
307
+ function kindNamed(world, name) {
308
+ return kindOf(world, name);
309
+ }
310
+ /**
311
+ * This function asks a document a question, and gives the evaluator that answers it.
312
+ *
313
+ * The question visits the rungs in order: the own entry of the envelope, then its kind, then the default of the
314
+ * world. The type comes from the envelope, or else from the type that the position declares. The type is a
315
+ * reference into the world. The ladder does not guess a type from the shape of a value.
316
+ *
317
+ * @param world - The kinds and the defaults.
318
+ * @param doc - The header of the document: an envelope, or `{}` for a bare value.
319
+ * @param verb - The verb of the question, for example `eval` or `view`.
320
+ * @param options - The type that the position declares.
321
+ * @returns The answer, with the record of each rung that the question visited.
322
+ */
323
+ function ask(world, doc, verb, options = {}) {
324
+ const steps = [];
325
+ const own = entryOf$1(doc.own, verb);
326
+ if (own !== void 0) {
327
+ steps.push({
328
+ rung: "own",
329
+ outcome: "answers",
330
+ evaluator: own
331
+ });
332
+ return {
333
+ verb,
334
+ evaluator: own,
335
+ rung: "own",
336
+ steps
337
+ };
338
+ }
339
+ steps.push({
340
+ rung: "own",
341
+ outcome: "no-entry"
342
+ });
343
+ const type = doc.type ?? options.declaredType;
344
+ if (type === void 0) steps.push({
345
+ rung: "kind",
346
+ outcome: "skipped"
347
+ });
348
+ else {
349
+ const source = doc.type === void 0 ? "container" : "document";
350
+ const name = typeName(type);
351
+ const kind = kindOf(world, type);
352
+ if (kind === void 0) {
353
+ steps.push({
354
+ rung: "kind",
355
+ outcome: "unknown-kind",
356
+ kind: name,
357
+ source
358
+ });
359
+ return {
360
+ verb,
361
+ error: "unknown-kind",
362
+ steps
363
+ };
364
+ }
365
+ const entry = entryOf$1(kind.verbs, verb);
366
+ if (entry !== void 0) {
367
+ steps.push({
368
+ rung: "kind",
369
+ outcome: "answers",
370
+ evaluator: entry,
371
+ kind: name,
372
+ source
373
+ });
374
+ return {
375
+ verb,
376
+ evaluator: entry,
377
+ rung: "kind",
378
+ steps
379
+ };
380
+ }
381
+ steps.push({
382
+ rung: "kind",
383
+ outcome: "no-entry",
384
+ kind: name,
385
+ source
386
+ });
387
+ }
388
+ const fallback = entryOf$1(world.defaults, verb);
389
+ if (fallback !== void 0) {
390
+ steps.push({
391
+ rung: "default",
392
+ outcome: "answers",
393
+ evaluator: fallback
394
+ });
395
+ return {
396
+ verb,
397
+ evaluator: fallback,
398
+ rung: "default",
399
+ steps
400
+ };
401
+ }
402
+ steps.push({
403
+ rung: "default",
404
+ outcome: "no-entry"
405
+ });
406
+ return {
407
+ verb,
408
+ error: "no-answer",
409
+ steps
410
+ };
411
+ }
412
+ //#endregion
413
+ //#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");
416
+ /**
417
+ * This function tells if a thing is a host thing.
418
+ *
419
+ * @param thing - The thing.
420
+ * @returns `true` for a host thing.
421
+ */
422
+ function isHostThing(thing) {
423
+ return typeof thing === "object" && thing !== null && !Array.isArray(thing) && thing[THING] === true;
424
+ }
425
+ /** The roots of a context. Each path starts with one of them. */
426
+ const ROOTS = [
427
+ "self",
428
+ "names",
429
+ "world",
430
+ "event",
431
+ "caps"
432
+ ];
433
+ function entryOf(verbs, verb) {
434
+ return verbs !== void 0 && Object.hasOwn(verbs, verb) ? verbs[verb] : void 0;
435
+ }
436
+ function plainStep(thing, key) {
437
+ if (Array.isArray(thing)) {
438
+ const index = typeof key === "number" ? key : /^\d+$/.test(key) ? Number(key) : -1;
439
+ return index >= 0 ? thing[index] : void 0;
440
+ }
441
+ if (isRecord(thing) && typeof key === "string") return field(thing, key);
442
+ }
443
+ /**
444
+ * 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.
447
+ *
448
+ * @param nav - The world and the code of the evaluators.
449
+ * @param thing - The thing that the path reached.
450
+ * @param key - The key of the step.
451
+ * @returns What the step reaches and the rung that answered, or `undefined`.
452
+ */
453
+ function derefStep(nav, thing, key) {
454
+ if (isHostThing(thing)) {
455
+ const reached = thing.deref(key);
456
+ return reached === void 0 ? void 0 : {
457
+ thing: reached,
458
+ by: "own"
459
+ };
460
+ }
461
+ const envelope = fromJson(thing);
462
+ if (envelope !== void 0) {
463
+ const own = entryOf(envelope.own, "deref");
464
+ const ownCode = own === void 0 ? void 0 : nav.derefs?.[own];
465
+ if (ownCode !== void 0) {
466
+ const reached = ownCode(envelope, key);
467
+ return reached === void 0 ? void 0 : {
468
+ thing: reached,
469
+ by: "own"
470
+ };
471
+ }
472
+ const found = lookupKind(nav.world, envelope.type);
473
+ const name = found.ok ? entryOf(found.kind.verbs, "deref") : void 0;
474
+ const code = name === void 0 ? void 0 : nav.derefs?.[name];
475
+ if (code !== void 0) {
476
+ const reached = code(envelope, key);
477
+ return reached === void 0 ? void 0 : {
478
+ thing: reached,
479
+ by: "kind"
480
+ };
481
+ }
482
+ const reached = plainStep(envelope.value, key);
483
+ return reached === void 0 ? void 0 : {
484
+ thing: reached,
485
+ by: "default"
486
+ };
487
+ }
488
+ const reached = plainStep(thing, key);
489
+ return reached === void 0 ? void 0 : {
490
+ thing: reached,
491
+ by: "default"
492
+ };
493
+ }
494
+ /**
495
+ * This function tells if a thing is a reference document: an envelope of the type `ref`.
496
+ *
497
+ * @param thing - The thing.
498
+ * @returns `true` for a reference document.
499
+ */
500
+ 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);
505
+ return path !== void 0 && path.length === 1 && path[0] === "ref";
506
+ }
507
+ /**
508
+ * 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).
510
+ *
511
+ * @param world - The world.
512
+ * @returns The thing.
513
+ */
514
+ function worldThing(world) {
515
+ return entriesAsJson(world.kinds);
516
+ }
517
+ /**
518
+ * This function makes a context. The world comes from the navigator when the call gives none.
519
+ *
520
+ * @param parts - The roots to set.
521
+ * @returns The context.
522
+ */
523
+ function context(parts = {}) {
524
+ return {
525
+ self: parts.self ?? null,
526
+ names: parts.names ?? {},
527
+ world: parts.world ?? null,
528
+ event: parts.event ?? null,
529
+ caps: parts.caps ?? []
530
+ };
531
+ }
532
+ const MAX_COLLAPSE = 64;
533
+ /**
534
+ * This function resolves a path from a context, and records each hop.
535
+ *
536
+ * 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
+ * of the verb `deref` on the thing that the path reached. A step document is evaluated by the walker of its kind,
538
+ * with the thing that the path reached. After each hop, a reference document is followed until the path reaches
539
+ * something else (collapse).
540
+ *
541
+ * @param nav - The world and the code of the evaluators.
542
+ * @param ctx - The context.
543
+ * @param path - The steps.
544
+ * @returns What the path reached and each hop, or an error.
545
+ */
546
+ function resolve(nav, ctx, path) {
547
+ const hops = [];
548
+ let collapses = 0;
549
+ const collapse = (thing) => {
550
+ let here = thing;
551
+ while (isRef(here)) {
552
+ collapses += 1;
553
+ if (collapses > MAX_COLLAPSE) return {
554
+ ok: false,
555
+ code: "#CYCLE!",
556
+ reason: "The references form a cycle.",
557
+ hops
558
+ };
559
+ const inner = walk(here.value);
560
+ if (!inner.ok) return inner;
561
+ here = inner.thing;
562
+ hops.push({
563
+ step: here === null ? null : "collapse",
564
+ thing: here,
565
+ by: "collapse"
566
+ });
567
+ }
568
+ return {
569
+ ok: true,
570
+ thing: here,
571
+ hops
572
+ };
573
+ };
574
+ 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
+ };
582
+ let here = ctx[root];
583
+ hops.push({
584
+ step: root,
585
+ thing: here,
586
+ by: "root"
587
+ });
588
+ const first = collapse(here);
589
+ if (!first.ok) return first;
590
+ here = first.thing;
591
+ for (const step of rest) {
592
+ if (isKey(step)) {
593
+ 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({
601
+ step,
602
+ thing: reached.thing,
603
+ by: reached.by
604
+ });
605
+ here = reached.thing;
606
+ } else {
607
+ const envelope = fromJson(step);
608
+ const found = envelope === void 0 ? void 0 : lookupKind(nav.world, envelope.type);
609
+ const name = found?.ok === true ? entryOf(found.kind.verbs, "step") : void 0;
610
+ 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
+ };
617
+ const result = walker({
618
+ data: envelope.value,
619
+ here,
620
+ context: ctx,
621
+ deref: (thing, key) => derefStep(nav, thing, key)?.thing
622
+ });
623
+ if (!result.ok) return {
624
+ ok: false,
625
+ code: "#REF!",
626
+ reason: result.reason,
627
+ hops
628
+ };
629
+ hops.push({
630
+ step,
631
+ thing: result.thing,
632
+ by: "step"
633
+ });
634
+ here = result.thing;
635
+ }
636
+ const next = collapse(here);
637
+ if (!next.ok) return next;
638
+ here = next.thing;
639
+ }
640
+ return {
641
+ ok: true,
642
+ thing: here,
643
+ hops
644
+ };
645
+ };
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
+ };
652
+ return walk(path);
653
+ }
654
+ /**
655
+ * This function gives the value of a thing: the thing itself for JSON, or the value that a host thing gives.
656
+ *
657
+ * @param thing - The thing.
658
+ * @returns The value.
659
+ */
660
+ function valueOfThing(thing) {
661
+ return isHostThing(thing) ? thing.toJson() : thing;
662
+ }
663
+ //#endregion
664
+ //#region src/classify.ts
665
+ function isStrictSuperset(larger, smaller) {
666
+ return larger.length > smaller.length && smaller.every((name) => larger.includes(name));
667
+ }
668
+ /**
669
+ * This function proposes a kind for a value without a type, from the fields that the value has.
670
+ * A shape applies when the value has each field of the shape. The most specific shapes are the ones that
671
+ * no other applicable shape contains. A host uses a classifier when data comes in, and then tags the data.
672
+ * The ladder does not call a classifier.
673
+ *
674
+ * @param shapes - The shapes to examine.
675
+ * @param value - The value without a type.
676
+ * @returns The proposed kind, or an error with the candidates.
677
+ */
678
+ function classify(shapes, value) {
679
+ const applicable = isRecord(value) ? shapes.filter((shape) => shape.fields.every((name) => field(value, name) !== void 0)) : [];
680
+ const best = applicable.filter((shape) => !applicable.some((other) => isStrictSuperset(other.fields, shape.fields)));
681
+ const candidates = best.map((shape) => shape.name);
682
+ const only = best.length === 1 ? best[0] : void 0;
683
+ if (only !== void 0) return {
684
+ kind: only.kind,
685
+ candidates
686
+ };
687
+ return {
688
+ candidates,
689
+ error: best.length === 0 ? "no-match" : "ambiguous"
690
+ };
691
+ }
692
+ /**
693
+ * This function puts a value in an envelope with a type. The value does not change.
694
+ *
695
+ * @param value - The value.
696
+ * @param kind - The name of the kind.
697
+ * @returns The envelope.
698
+ */
699
+ function tag(value, kind) {
700
+ return typed(kind, value);
701
+ }
702
+ //#endregion
703
+ //#region src/parts.ts
704
+ /**
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.
707
+ *
708
+ * @param spec - The contract of the parts.
709
+ * @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.
711
+ */
712
+ function splitParts(spec, value) {
713
+ const of = (slot, declared) => declared === void 0 ? slot : {
714
+ ...slot,
715
+ of: declared
716
+ };
717
+ if (spec === void 0 || spec.shape === "none") return {
718
+ ok: true,
719
+ data: value,
720
+ parts: []
721
+ };
722
+ if (spec.shape === "list") {
723
+ if (!Array.isArray(value)) return {
724
+ ok: false,
725
+ reason: "The value must be a list."
726
+ };
727
+ return {
728
+ ok: true,
729
+ data: null,
730
+ parts: value.map((item, index) => of({
731
+ key: String(index),
732
+ item
733
+ }, spec.of))
734
+ };
735
+ }
736
+ if (!isRecord(value)) return {
737
+ ok: false,
738
+ reason: "The value must be a record."
739
+ };
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));
750
+ }
751
+ return {
752
+ ok: true,
753
+ data: rest,
754
+ parts
755
+ };
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
+ return {
767
+ ok: true,
768
+ value
769
+ };
770
+ }
771
+ /**
772
+ * This function makes a result with an error value.
773
+ *
774
+ * @param code - The code of the error.
775
+ * @param reason - One sentence that tells the cause.
776
+ * @returns The result.
777
+ */
778
+ function fail(code, reason) {
779
+ return {
780
+ ok: false,
781
+ error: {
782
+ code,
783
+ reason
784
+ }
785
+ };
786
+ }
787
+ /**
788
+ * This function makes a context with names and capabilities. The other roots are empty, and an evaluation sets
789
+ * the world.
790
+ *
791
+ * @param names - The names and their values.
792
+ * @param caps - The effects that the term can cause.
793
+ * @param self - The node of the term, if it has one.
794
+ * @returns The context.
795
+ */
796
+ function makeContext(names = {}, caps = [], self = null) {
797
+ return context({
798
+ names,
799
+ caps,
800
+ self
801
+ });
802
+ }
803
+ /**
804
+ * 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`.
806
+ *
807
+ * @param caps - The capabilities of the context.
808
+ * @param kind - The kind of the effect.
809
+ * @returns `true` when one capability permits it.
810
+ */
811
+ function permits(caps, kind) {
812
+ return caps.some((cap) => cap === kind || kind.startsWith(`${cap}.`));
813
+ }
814
+ function selfLabel(self) {
815
+ if (isHostThing(self)) return self.label;
816
+ return self === null ? "none" : JSON.stringify(self).slice(0, 40);
817
+ }
818
+ const NO_ANSWER = {
819
+ verb: "eval",
820
+ error: "no-answer",
821
+ steps: []
822
+ };
823
+ /** This function drives steps that cannot wait. */
824
+ function drive(steps) {
825
+ const step = steps.next();
826
+ if (step.done === true) return step.value;
827
+ step.value.catch(() => void 0);
828
+ return fail("#CALC!", "A part waited, and its parent cannot wait.");
829
+ }
830
+ /** This function drives steps that can wait. The steps before the first wait happen at once, in the call. */
831
+ async function driveWaiting(steps) {
832
+ let step = steps.next();
833
+ while (step.done !== true) {
834
+ let value;
835
+ try {
836
+ value = await step.value;
837
+ } catch (error) {
838
+ step = steps.throw(error);
839
+ continue;
840
+ }
841
+ step = steps.next(value);
842
+ }
843
+ return step.value;
844
+ }
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,
851
+ doc,
852
+ declared: envelope === void 0,
853
+ 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: []
864
+ };
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;
873
+ };
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)}.`));
882
+ const name = answer.evaluator;
883
+ const runners = s.machine.runners ?? {};
884
+ 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.`));
888
+ 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
902
+ };
903
+ s.effects.push(effect);
904
+ node.effects.push(effect.index);
905
+ return effect;
906
+ };
907
+ const kindText = typeName(doc.type);
908
+ const resolveHere = (steps) => resolve(s.machine, ctx, steps);
909
+ 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);
934
+ }
935
+ };
936
+ try {
937
+ return finish(yield* runner(input));
938
+ } 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);
957
+ }
958
+ };
959
+ try {
960
+ return finish(code(input));
961
+ } 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: []
982
+ };
983
+ }
984
+ function rootContext(machine, context) {
985
+ return context.world === null ? {
986
+ ...context,
987
+ world: worldThing(machine.world)
988
+ } : context;
989
+ }
990
+ function record(s, holder, result) {
991
+ const root = holder.children[0];
992
+ if (root === void 0) throw new Error("The evaluation made no trace.");
993
+ return {
994
+ result,
995
+ trace: root,
996
+ effects: s.effects,
997
+ time: s.tick
998
+ };
999
+ }
1000
+ /**
1001
+ * This function evaluates a document, and records each evaluation in a trace.
1002
+ *
1003
+ * The verb `eval` goes up the ladder for each document. The kind of the document tells where its parts are. The
1004
+ * evaluator gets each part sealed: it can evaluate the part, and it cannot read its value. A failure gives an error
1005
+ * value, and the function does not throw. An effect is recorded in its order, and nothing does it.
1006
+ *
1007
+ * @param machine - The world and the code of the evaluators.
1008
+ * @param doc - The document, as JSON.
1009
+ * @param context - The names and the capabilities at the root.
1010
+ * @param declaredType - The type of a bare root value.
1011
+ * @returns The result, the trace and the effects in their order.
1012
+ */
1013
+ 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)));
1024
+ }
1025
+ /**
1026
+ * This function runs a document: an evaluation in which the host does each effect. A runner waits for its
1027
+ * 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.
1029
+ *
1030
+ * @param machine - The world, the evaluators and the runners.
1031
+ * @param doc - The document, as JSON.
1032
+ * @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.
1034
+ * @returns The result, the trace and the effects in their order, when the run ends.
1035
+ */
1036
+ 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)));
1047
+ }
1048
+ /**
1049
+ * This function finds a node of a trace by its path.
1050
+ *
1051
+ * @param trace - The root of the trace.
1052
+ * @param path - The keys from the root.
1053
+ * @returns The first evaluation at that path, or `undefined` when the document did not evaluate.
1054
+ */
1055
+ function nodeAt(trace, path) {
1056
+ 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;
1060
+ }
1061
+ return node;
1062
+ }
1063
+ /**
1064
+ * This function lists the nodes of a trace in the order of their start.
1065
+ *
1066
+ * @param trace - The root of the trace.
1067
+ * @returns Each node, the root first.
1068
+ */
1069
+ function flatten(trace) {
1070
+ const out = [];
1071
+ const walk = (node) => {
1072
+ out.push(node);
1073
+ node.children.forEach(walk);
1074
+ };
1075
+ walk(trace);
1076
+ return out.sort((a, b) => a.enter - b.enter);
1077
+ }
1078
+ //#endregion
1079
+ //#region src/stdlib.ts
1080
+ /**
1081
+ * The world of the examples: small kinds for numbers, lists, conditions, names, sequences and effects.
1082
+ * Each kind names an evaluator for `eval` and a view for `view`. The site draws each document with its view.
1083
+ */
1084
+ const STD_WORLD = {
1085
+ kinds: {
1086
+ number: {
1087
+ verbs: {
1088
+ eval: "data.number",
1089
+ view: "view.value"
1090
+ },
1091
+ summary: "A number. Its value is the result."
1092
+ },
1093
+ text: {
1094
+ verbs: {
1095
+ eval: "data.text",
1096
+ view: "view.value"
1097
+ },
1098
+ summary: "A text. Its value is the result."
1099
+ },
1100
+ bool: {
1101
+ verbs: {
1102
+ eval: "data.bool",
1103
+ view: "view.value"
1104
+ },
1105
+ summary: "A truth value. Its value is the result."
1106
+ },
1107
+ add: {
1108
+ verbs: {
1109
+ eval: "math.add",
1110
+ view: "view.op"
1111
+ },
1112
+ parts: {
1113
+ shape: "list",
1114
+ of: "number"
1115
+ },
1116
+ summary: "The sum of its parts. A bare number in its list gets the type number."
1117
+ },
1118
+ multiply: {
1119
+ verbs: {
1120
+ eval: "math.multiply",
1121
+ view: "view.op"
1122
+ },
1123
+ parts: {
1124
+ shape: "list",
1125
+ of: "number"
1126
+ },
1127
+ summary: "The product of its parts."
1128
+ },
1129
+ greater: {
1130
+ verbs: {
1131
+ eval: "math.greater",
1132
+ view: "view.op"
1133
+ },
1134
+ parts: {
1135
+ shape: "list",
1136
+ of: "number"
1137
+ },
1138
+ summary: "True when the first part is greater than the second part."
1139
+ },
1140
+ list: {
1141
+ verbs: {
1142
+ eval: "list.all",
1143
+ view: "view.op"
1144
+ },
1145
+ parts: { shape: "list" },
1146
+ summary: "The results of its parts, as a list."
1147
+ },
1148
+ if: {
1149
+ verbs: {
1150
+ eval: "control.if",
1151
+ view: "view.op"
1152
+ },
1153
+ parts: {
1154
+ shape: "record",
1155
+ fields: [
1156
+ "test",
1157
+ "then",
1158
+ "else"
1159
+ ]
1160
+ },
1161
+ summary: "It evaluates the test, then only the branch that the test selects."
1162
+ },
1163
+ ref: {
1164
+ verbs: {
1165
+ eval: "ref.resolve",
1166
+ view: "view.ref"
1167
+ },
1168
+ summary: "A path from a root of the context. Each step goes up the ladder of the verb deref."
1169
+ },
1170
+ nearest: {
1171
+ verbs: {
1172
+ step: "walk.nearest",
1173
+ view: "view.card"
1174
+ },
1175
+ summary: "A step of a path: it follows a key again and again, until it reaches a node of a type."
1176
+ },
1177
+ panel: {
1178
+ verbs: {
1179
+ eval: "tree.node",
1180
+ view: "view.op"
1181
+ },
1182
+ parts: {
1183
+ shape: "record",
1184
+ fields: [
1185
+ "width",
1186
+ "height",
1187
+ "children"
1188
+ ],
1189
+ of: "number"
1190
+ },
1191
+ summary: "A node of a tree. Its parts see it as self, and each node links to its parent."
1192
+ },
1193
+ box: {
1194
+ verbs: {
1195
+ eval: "tree.node",
1196
+ view: "view.op"
1197
+ },
1198
+ parts: {
1199
+ shape: "record",
1200
+ fields: [
1201
+ "width",
1202
+ "height",
1203
+ "children"
1204
+ ],
1205
+ of: "number"
1206
+ },
1207
+ summary: "A node of a tree, as panel. A formula in a box can read the parent of the box."
1208
+ },
1209
+ math: { kinds: {
1210
+ sum: {
1211
+ type: "ref",
1212
+ value: ["world", "add"]
1213
+ },
1214
+ product: {
1215
+ type: "ref",
1216
+ value: ["world", "multiply"]
1217
+ }
1218
+ } },
1219
+ seq: {
1220
+ verbs: {
1221
+ eval: "control.seq",
1222
+ view: "view.seq"
1223
+ },
1224
+ parts: { shape: "list" },
1225
+ summary: "Its steps, in order. The result of a named step is a name for the later steps."
1226
+ },
1227
+ pure: {
1228
+ verbs: {
1229
+ eval: "control.pure",
1230
+ view: "view.op"
1231
+ },
1232
+ parts: { shape: "list" },
1233
+ summary: "It evaluates its part without capabilities, thus an effect in it fails."
1234
+ },
1235
+ log: {
1236
+ verbs: {
1237
+ eval: "effect.log",
1238
+ view: "view.op"
1239
+ },
1240
+ parts: {
1241
+ shape: "list",
1242
+ of: "text"
1243
+ },
1244
+ side: "run",
1245
+ summary: "It writes the result of its part to the log. This is an effect."
1246
+ },
1247
+ write: {
1248
+ verbs: {
1249
+ eval: "effect.write",
1250
+ view: "view.op"
1251
+ },
1252
+ parts: {
1253
+ shape: "record",
1254
+ fields: ["value"]
1255
+ },
1256
+ side: "run",
1257
+ summary: "It writes the result of its part to the store, at its key. This is an effect."
1258
+ }
1259
+ },
1260
+ defaults: { view: "view.card" }
1261
+ };
1262
+ function isNumber(value) {
1263
+ return typeof value === "number" && Number.isFinite(value);
1264
+ }
1265
+ /** This function evaluates each part, collects the errors, and gives the values when no part failed. */
1266
+ function allValues(results) {
1267
+ const errors = results.filter((result) => !result.ok);
1268
+ const first = errors[0];
1269
+ if (first !== void 0 && errors.length === 1) return { error: first };
1270
+ if (errors.length > 1) return { error: fail("#ARGS", `${errors.length} parts failed.`) };
1271
+ return { values: results.map((result) => result.ok ? result.value : null) };
1272
+ }
1273
+ function numbers(values, kind) {
1274
+ const bad = values.findIndex((value) => !isNumber(value));
1275
+ if (bad !== -1) return fail("#VALUE!", `Part ${bad} of ${kind} is not a number.`);
1276
+ return values.filter(isNumber);
1277
+ }
1278
+ function dataOf(type) {
1279
+ return ({ data }) => typeof data === type && (type !== "number" || isNumber(data)) ? ok(data) : fail("#VALUE!", `The value must be a ${type === "string" ? "text" : type}.`);
1280
+ }
1281
+ /** The code of the evaluators of the standard world, by name. */
1282
+ const STD_EVALUATORS = {
1283
+ "data.number": dataOf("number"),
1284
+ "data.text": dataOf("string"),
1285
+ "data.bool": dataOf("boolean"),
1286
+ "math.add": ({ parts }) => {
1287
+ const all = allValues(parts.map((part) => part.evaluate()));
1288
+ if ("error" in all) return all.error;
1289
+ const values = numbers(all.values, "add");
1290
+ return Array.isArray(values) ? ok(values.reduce((sum, value) => sum + value, 0)) : values;
1291
+ },
1292
+ "math.multiply": ({ parts }) => {
1293
+ const all = allValues(parts.map((part) => part.evaluate()));
1294
+ if ("error" in all) return all.error;
1295
+ const values = numbers(all.values, "multiply");
1296
+ return Array.isArray(values) ? ok(values.reduce((product, value) => product * value, 1)) : values;
1297
+ },
1298
+ "math.greater": ({ parts }) => {
1299
+ if (parts.length !== 2) return fail("#VALUE!", "greater needs exactly two parts.");
1300
+ const all = allValues(parts.map((part) => part.evaluate()));
1301
+ if ("error" in all) return all.error;
1302
+ const values = numbers(all.values, "greater");
1303
+ if (!Array.isArray(values)) return values;
1304
+ const [a, b] = values;
1305
+ return ok((a ?? 0) > (b ?? 0));
1306
+ },
1307
+ "ref.resolve": ({ data, resolve }) => {
1308
+ 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);
1314
+ },
1315
+ "tree.node": (input) => treeNode(input.parts, input.context, input.kind)
1316
+ };
1317
+ /**
1318
+ * This function evaluates a node of a tree. The node is a host thing. A step `parent` reaches the node that holds
1319
+ * it. A step with the name of a part evaluates that part on demand, one time. Each part sees the node as self.
1320
+ */
1321
+ function treeNode(parts, context, type) {
1322
+ const memo = /* @__PURE__ */ new Map();
1323
+ const read = (key) => {
1324
+ const known = memo.get(key);
1325
+ if (known !== void 0) return known;
1326
+ const part = parts.find((item) => item.key === key);
1327
+ if (part === void 0) return void 0;
1328
+ const result = part.evaluate({
1329
+ ...context,
1330
+ self: me
1331
+ });
1332
+ memo.set(key, result);
1333
+ return result;
1334
+ };
1335
+ const me = {
1336
+ [THING]: true,
1337
+ type,
1338
+ label: `node ${type}`,
1339
+ deref(key) {
1340
+ if (key === "parent") return context.self;
1341
+ if (key === "type") return type;
1342
+ const result = read(String(key));
1343
+ return result?.ok === true ? result.value : void 0;
1344
+ },
1345
+ toJson() {
1346
+ const out = {};
1347
+ for (const part of parts) {
1348
+ const result = read(part.key);
1349
+ out[part.key] = result?.ok === true ? result.value : null;
1350
+ }
1351
+ return out;
1352
+ }
1353
+ };
1354
+ const out = {};
1355
+ for (const part of parts) {
1356
+ const result = read(part.key);
1357
+ if (result === void 0) continue;
1358
+ if (!result.ok) return result;
1359
+ out[part.key] = result.value;
1360
+ }
1361
+ return ok(out);
1362
+ }
1363
+ /**
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.
1367
+ */
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 {
1376
+ 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
1382
+ };
1383
+ }
1384
+ return {
1385
+ ok: false,
1386
+ reason: "The walk is too long."
1387
+ };
1388
+ };
1389
+ /** The walkers of the standard world, by name. */
1390
+ const STD_WALKERS = { "walk.nearest": nearest };
1391
+ /** This function evaluates parts one after the other, and stops at the first error. */
1392
+ function* inOrder(parts) {
1393
+ const values = [];
1394
+ for (const part of parts) {
1395
+ const result = yield* part.evaluate();
1396
+ if (!result.ok) return { error: result };
1397
+ values.push(result.value);
1398
+ }
1399
+ return { values };
1400
+ }
1401
+ /**
1402
+ * The runners of the standard world, by name: the kinds that can hold effects. Each one evaluates its parts in
1403
+ * order. In a run, an effect ends before the next step starts.
1404
+ */
1405
+ const STD_RUNNERS = {
1406
+ "control.seq": function* ({ parts, context }) {
1407
+ let names = { ...context.names };
1408
+ let last = null;
1409
+ for (const part of parts) {
1410
+ const result = yield* part.evaluate({
1411
+ ...context,
1412
+ names
1413
+ });
1414
+ if (!result.ok) return result;
1415
+ names = {
1416
+ ...names,
1417
+ last: result.value
1418
+ };
1419
+ if (part.name !== void 0) names = {
1420
+ ...names,
1421
+ [part.name]: result.value
1422
+ };
1423
+ last = result.value;
1424
+ }
1425
+ return ok(last);
1426
+ },
1427
+ "control.pure": function* ({ parts, context }) {
1428
+ const only = parts[0];
1429
+ if (only === void 0) return fail("#VALUE!", "pure needs one part.");
1430
+ return yield* only.evaluate({
1431
+ ...context,
1432
+ caps: []
1433
+ });
1434
+ },
1435
+ "control.if": function* ({ parts }) {
1436
+ const test = parts.find((part) => part.key === "test");
1437
+ if (test === void 0) return fail("#VALUE!", "if needs a test.");
1438
+ const chosen = yield* test.evaluate();
1439
+ if (!chosen.ok) return chosen;
1440
+ if (typeof chosen.value !== "boolean") return fail("#VALUE!", "The test of if must give a truth value.");
1441
+ const branch = parts.find((part) => part.key === (chosen.value === true ? "then" : "else"));
1442
+ return branch === void 0 ? ok(null) : yield* branch.evaluate();
1443
+ },
1444
+ "list.all": function* ({ parts }) {
1445
+ const all = yield* inOrder(parts);
1446
+ return "error" in all ? all.error : ok(all.values);
1447
+ },
1448
+ "effect.log": function* ({ parts, effect }) {
1449
+ const all = yield* inOrder(parts);
1450
+ if ("error" in all) return all.error;
1451
+ return yield* effect("log", all.values.length === 1 ? all.values[0] ?? null : all.values);
1452
+ },
1453
+ "effect.write": function* ({ parts, data, effect }) {
1454
+ const key = isRecord(data) ? field(data, "key") : void 0;
1455
+ if (typeof key !== "string") return fail("#VALUE!", "write needs a text key.");
1456
+ const value = parts.find((part) => part.key === "value");
1457
+ if (value === void 0) return fail("#VALUE!", "write needs a value.");
1458
+ const result = yield* value.evaluate();
1459
+ if (!result.ok) return result;
1460
+ return yield* effect("write", {
1461
+ key,
1462
+ value: result.value
1463
+ });
1464
+ }
1465
+ };
1466
+ /** The machine of the examples: the standard world, its evaluators, its runners and its walkers. */
1467
+ const STD_MACHINE = {
1468
+ world: STD_WORLD,
1469
+ evaluators: STD_EVALUATORS,
1470
+ runners: STD_RUNNERS,
1471
+ walkers: STD_WALKERS
1472
+ };
1473
+ /** The capabilities of the examples at the root: the two effects of the standard world. */
1474
+ const STD_CAPS = ["log", "write"];
1475
+ //#endregion
1476
+ //#region src/order.ts
1477
+ /**
1478
+ * This function finds the references that a sequence cannot resolve. A step can read only the steps before it.
1479
+ *
1480
+ * @param steps - The steps, in the written order.
1481
+ * @returns One problem for each bad reference, in the written order.
1482
+ */
1483
+ function checkSteps(steps) {
1484
+ const problems = [];
1485
+ steps.forEach((step, index) => {
1486
+ for (const name of step.reads) {
1487
+ const at = steps.findIndex((other) => other.id === name);
1488
+ if (at === -1) problems.push({
1489
+ step: step.id,
1490
+ reads: name,
1491
+ problem: "unknown-name"
1492
+ });
1493
+ else if (at >= index) problems.push({
1494
+ step: step.id,
1495
+ reads: name,
1496
+ problem: "read-before-defined"
1497
+ });
1498
+ }
1499
+ });
1500
+ return problems;
1501
+ }
1502
+ /**
1503
+ * This function gives the data edges: a step comes after each earlier step that it reads.
1504
+ *
1505
+ * @param steps - The steps, in the written order.
1506
+ * @returns The edges. A bad reference gives no edge.
1507
+ */
1508
+ function dataEdges(steps) {
1509
+ const edges = [];
1510
+ steps.forEach((step, index) => {
1511
+ for (const name of step.reads) {
1512
+ const at = steps.findIndex((other) => other.id === name);
1513
+ if (at !== -1 && at < index) edges.push([name, step.id]);
1514
+ }
1515
+ });
1516
+ return edges;
1517
+ }
1518
+ /**
1519
+ * This function gives the effect edges: each step with an effect comes after the previous step with an effect.
1520
+ * Effects keep their written order. Steps without an effect get no effect edge.
1521
+ *
1522
+ * @param steps - The steps, in the written order.
1523
+ * @returns The edges, one for each pair of consecutive effect steps.
1524
+ */
1525
+ function effectEdges(steps) {
1526
+ const edges = [];
1527
+ let previous;
1528
+ for (const step of steps) {
1529
+ if (!step.effect) continue;
1530
+ if (previous !== void 0) edges.push([previous, step.id]);
1531
+ previous = step.id;
1532
+ }
1533
+ return edges;
1534
+ }
1535
+ /**
1536
+ * This function tells if an order of the steps gives the same meaning as the written order.
1537
+ * The order must contain each step one time, and it must obey the data edges and the effect edges.
1538
+ *
1539
+ * @param steps - The steps, in the written order.
1540
+ * @param order - The names of the steps in the order to examine.
1541
+ * @returns `true` when the order is permitted.
1542
+ */
1543
+ function isAllowedOrder(steps, order) {
1544
+ if (order.length !== steps.length) return false;
1545
+ if (new Set(order).size !== order.length) return false;
1546
+ if (!steps.every((step) => order.includes(step.id))) return false;
1547
+ const position = new Map(order.map((id, index) => [id, index]));
1548
+ return [...dataEdges(steps), ...effectEdges(steps)].every(([before, after]) => (position.get(before) ?? 0) < (position.get(after) ?? 0));
1549
+ }
1550
+ /**
1551
+ * This function puts the steps in layers. Each step goes one layer after the last step that it must follow.
1552
+ * The steps of one layer do not depend on each other, thus they can run at the same time.
1553
+ *
1554
+ * @param steps - The steps, in the written order.
1555
+ * @returns The layers, first to last. Each layer keeps the written order.
1556
+ */
1557
+ function layers(steps) {
1558
+ const edges = [...dataEdges(steps), ...effectEdges(steps)];
1559
+ const depth = /* @__PURE__ */ new Map();
1560
+ for (const step of steps) {
1561
+ const before = edges.filter(([, after]) => after === step.id).map(([first]) => depth.get(first) ?? 0);
1562
+ depth.set(step.id, before.length === 0 ? 0 : Math.max(...before) + 1);
1563
+ }
1564
+ const result = [];
1565
+ for (const step of steps) {
1566
+ const at = depth.get(step.id) ?? 0;
1567
+ while (result.length <= at) result.push([]);
1568
+ result[at]?.push(step.id);
1569
+ }
1570
+ return result;
1571
+ }
1572
+ //#endregion
1573
+ //#region src/context.ts
1574
+ /** The parts of a context. A term can reach outside itself only through these parts. */
1575
+ const PARTS = [
1576
+ "names",
1577
+ "focus",
1578
+ "world",
1579
+ "event",
1580
+ "caps"
1581
+ ];
1582
+ /** The descriptions of the parts, in the order of `PARTS`. */
1583
+ const PART_INFO = [
1584
+ {
1585
+ part: "names",
1586
+ title: "Names",
1587
+ reaches: "The values that a binding gives: a let, a parameter, or the result of an earlier step.",
1588
+ direction: "Back, to the place where the program binds the name."
1589
+ },
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
+ {
1597
+ part: "world",
1598
+ title: "World",
1599
+ reaches: "The stored documents and collections, by an absolute ID or by a query.",
1600
+ direction: "Out, to the store."
1601
+ },
1602
+ {
1603
+ part: "event",
1604
+ title: "Event",
1605
+ reaches: "The trigger of a run: the event and its values.",
1606
+ direction: "From the past, to the run that the event starts."
1607
+ },
1608
+ {
1609
+ part: "caps",
1610
+ title: "Capabilities",
1611
+ reaches: "The effects that a term can cause. A part of the term gets the same set or a smaller set.",
1612
+ direction: "Down, and only smaller."
1613
+ }
1614
+ ];
1615
+ const NONE = {
1616
+ reads: false,
1617
+ gives: "none",
1618
+ note: "It does not use this part."
1619
+ };
1620
+ /** Six constructs that show the range of the context. */
1621
+ const CONSTRUCTS = [
1622
+ {
1623
+ id: "formula",
1624
+ title: "Formula",
1625
+ side: "derive",
1626
+ summary: "A pure formula that gives a value at one position of a structure.",
1627
+ example: {
1628
+ type: "formula",
1629
+ value: {
1630
+ let: { gap: 8 },
1631
+ body: {
1632
+ op: "add",
1633
+ args: [{
1634
+ ref: "width",
1635
+ at: "parent"
1636
+ }, { var: "gap" }]
1637
+ }
1638
+ }
1639
+ },
1640
+ uses: {
1641
+ names: {
1642
+ reads: true,
1643
+ gives: "extends",
1644
+ note: "A let binds names for its body."
1645
+ },
1646
+ focus: {
1647
+ reads: true,
1648
+ gives: "moves",
1649
+ note: "A move reads the parent. An axis moves the focus to each target."
1650
+ },
1651
+ world: NONE,
1652
+ event: NONE,
1653
+ caps: {
1654
+ reads: false,
1655
+ gives: "none",
1656
+ note: "A formula causes no effect, thus its parts get no capability."
1657
+ }
1658
+ }
1659
+ },
1660
+ {
1661
+ id: "sequence",
1662
+ title: "Sequence",
1663
+ side: "run",
1664
+ summary: "A list of steps. A step can read the result of each earlier step by its name.",
1665
+ example: {
1666
+ type: "seq",
1667
+ value: [{
1668
+ type: "store.find",
1669
+ value: { match: { board: "b1" } },
1670
+ name: "cards"
1671
+ }, {
1672
+ type: "store.patch",
1673
+ value: {
1674
+ ids: {
1675
+ type: "ref",
1676
+ value: "cards.ids"
1677
+ },
1678
+ set: { done: true }
1679
+ }
1680
+ }]
1681
+ },
1682
+ uses: {
1683
+ names: {
1684
+ reads: true,
1685
+ gives: "extends",
1686
+ note: "The result of each step is a name for the later steps."
1687
+ },
1688
+ focus: NONE,
1689
+ world: {
1690
+ reads: true,
1691
+ gives: "same",
1692
+ note: "A step reads and writes the store through a procedure."
1693
+ },
1694
+ event: {
1695
+ reads: false,
1696
+ gives: "same",
1697
+ note: "The steps get the event of the run without a change."
1698
+ },
1699
+ caps: {
1700
+ reads: true,
1701
+ gives: "narrows",
1702
+ note: "A nested call gets the same capabilities or fewer."
1703
+ }
1704
+ }
1705
+ },
1706
+ {
1707
+ id: "node",
1708
+ title: "Tree node",
1709
+ side: "derive",
1710
+ summary: "A node of a document tree. Its data and its box go down to its children.",
1711
+ example: {
1712
+ type: "node",
1713
+ value: {
1714
+ data: { title: "Notes" },
1715
+ children: [{
1716
+ type: "text",
1717
+ value: {
1718
+ type: "ref",
1719
+ value: "title"
1720
+ }
1721
+ }]
1722
+ }
1723
+ },
1724
+ uses: {
1725
+ names: {
1726
+ reads: true,
1727
+ gives: "extends",
1728
+ note: "The data of the node becomes a name for its children."
1729
+ },
1730
+ focus: {
1731
+ reads: true,
1732
+ gives: "moves",
1733
+ note: "Each child becomes the focus of its own part."
1734
+ },
1735
+ world: {
1736
+ reads: true,
1737
+ gives: "same",
1738
+ note: "A node can show stored documents."
1739
+ },
1740
+ event: NONE,
1741
+ caps: NONE
1742
+ }
1743
+ },
1744
+ {
1745
+ id: "rule",
1746
+ title: "Layout rule",
1747
+ side: "derive",
1748
+ summary: "A rule that gives the box of a child from the box of its parent.",
1749
+ example: {
1750
+ type: "rule",
1751
+ value: {
1752
+ use: "stack",
1753
+ with: { gap: 8 }
1754
+ }
1755
+ },
1756
+ uses: {
1757
+ names: {
1758
+ reads: true,
1759
+ gives: "none",
1760
+ note: "The rule reads its parameters."
1761
+ },
1762
+ focus: {
1763
+ reads: true,
1764
+ gives: "none",
1765
+ note: "The rule reads the box of the parent and the key of the child."
1766
+ },
1767
+ world: NONE,
1768
+ event: NONE,
1769
+ caps: NONE
1770
+ }
1771
+ },
1772
+ {
1773
+ id: "handler",
1774
+ title: "Event handler",
1775
+ side: "run",
1776
+ summary: "A run that an event starts. It holds a sequence as data until the event comes.",
1777
+ example: {
1778
+ type: "on",
1779
+ value: {
1780
+ event: "click",
1781
+ run: {
1782
+ type: "seq",
1783
+ value: [{
1784
+ type: "card.toggle",
1785
+ value: { id: {
1786
+ type: "ref",
1787
+ value: "event.target"
1788
+ } }
1789
+ }]
1790
+ }
1791
+ }
1792
+ },
1793
+ uses: {
1794
+ names: {
1795
+ reads: true,
1796
+ gives: "same",
1797
+ note: "The handler reads the names of its place."
1798
+ },
1799
+ focus: {
1800
+ reads: true,
1801
+ gives: "same",
1802
+ note: "The handler knows the node that got the event."
1803
+ },
1804
+ world: {
1805
+ reads: true,
1806
+ gives: "same",
1807
+ note: "The run reads and writes the store."
1808
+ },
1809
+ event: {
1810
+ reads: true,
1811
+ gives: "sets",
1812
+ note: "The handler gives the event to its run."
1813
+ },
1814
+ caps: {
1815
+ reads: true,
1816
+ gives: "narrows",
1817
+ note: "The run gets only the capabilities of the handler."
1818
+ }
1819
+ }
1820
+ },
1821
+ {
1822
+ id: "query",
1823
+ title: "Query",
1824
+ side: "derive",
1825
+ summary: "A selection of stored documents. A storage adapter decides where it runs.",
1826
+ example: {
1827
+ type: "query",
1828
+ value: {
1829
+ collection: "cards",
1830
+ match: { board: {
1831
+ type: "ref",
1832
+ value: "board"
1833
+ } }
1834
+ }
1835
+ },
1836
+ uses: {
1837
+ names: {
1838
+ reads: true,
1839
+ gives: "none",
1840
+ note: "The query reads its parameters."
1841
+ },
1842
+ focus: NONE,
1843
+ world: {
1844
+ reads: true,
1845
+ gives: "none",
1846
+ note: "The query reads one collection."
1847
+ },
1848
+ event: NONE,
1849
+ caps: NONE
1850
+ }
1851
+ }
1852
+ ];
1853
+ //#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 };
1855
+
1856
+ //# sourceMappingURL=index.js.map