@mark1russell7/coeval 0.18.0 → 0.20.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/src/stdlib.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Json, type Key, field, isRecord } from "./doc.ts";
1
+ import { type Json, type Key, docEquals, field, isRecord } from "./doc.ts";
2
2
  import {
3
3
  type EvalInput,
4
4
  type Evaluator,
@@ -18,7 +18,7 @@ import {
18
18
  import { Failure } from "./errors.ts";
19
19
  import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
20
20
  import type { World } from "./ladder.ts";
21
- import { type AttributeDeclaration, type TreeContract, equationType, lookupKind } from "./world.ts";
21
+ import { type AttributeDeclaration, type CircularRule, type TreeContract, circularOf, equationType, lookupKind } from "./world.ts";
22
22
  import { STD_DOMAINS } from "./types.ts";
23
23
  import type { SyntaxProfile } from "./syntax.ts";
24
24
 
@@ -236,7 +236,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
236
236
  const found = resolve(data as readonly Json[]);
237
237
  return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
238
238
  },
239
- "tree.node": (input) => treeNode(input),
239
+ "tree.node": (input) => treeNode(input, undefined),
240
240
  };
241
241
 
242
242
  /*
@@ -270,12 +270,8 @@ export interface AttributeTrace {
270
270
  }
271
271
 
272
272
  /** The state of a node behind its brand. */
273
- interface NodeState {
273
+ interface NodeState extends LoopNode {
274
274
  readonly build: TreeBuild;
275
- readonly type: string;
276
- readonly place: readonly Key[];
277
- /** The tree contract of the kind of the node (D42). */
278
- readonly tree: TreeContract | undefined;
279
275
  /**
280
276
  * This function gives an attribute through its ladder (TREE.LADDER).
281
277
  *
@@ -316,6 +312,60 @@ interface NodeState {
316
312
  json(path: Set<object>): Result;
317
313
  }
318
314
 
315
+ /** The answer of the ladder for one attribute of one node (TREE.CELL, D108). */
316
+ export interface AttributeAnswer {
317
+ /** The result of the attribute. It is `undefined` when no rung answered. */
318
+ readonly result: Result | undefined;
319
+ /** The rung that answered. It is `undefined` when no rung answered. */
320
+ readonly by: AttributeRung | undefined;
321
+ }
322
+
323
+ /**
324
+ * This function gives the memo cell of one attribute of one node, for a host that keeps its cells between evaluations
325
+ * (TREE.CELL, D108). The kit uses it at each demand of an attribute in an evaluation.
326
+ *
327
+ * The array `place` is one object for each node of a plan in its scope. It stays the same across the evaluations of
328
+ * that plan. The scope of a node is the node around it, or the demand that made its tree (TREE.CELL-DOCUMENT,
329
+ * D115). Thus a cached plan, for example the plan of an equation, gives a new array for the nodes of each document.
330
+ *
331
+ * A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
332
+ * A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
333
+ *
334
+ * The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
335
+ * of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
336
+ * a cell that computes again uses the last one.
337
+ *
338
+ * The kit marks an attribute in evaluation before it uses the cell. Thus a cycle gives `#CYCLE!` before it reaches the
339
+ * cell again. An exception of the function that `compute` did not throw gives `#CYCLE!` with the chain of the demands.
340
+ *
341
+ * @param place - The key of the node. Its items are the keys from the root document to the document of the node.
342
+ * @param attribute - The name of the attribute.
343
+ * @param compute - The function that visits the rungs of the ladder.
344
+ * @returns The answer.
345
+ */
346
+ export type AttributeCell = (place: readonly Key[], attribute: string, compute: () => AttributeAnswer) => AttributeAnswer;
347
+
348
+ /** The options of the tree kit (D108). */
349
+ export interface TreeOptions {
350
+ /**
351
+ * The memo cells of the host (TREE.CELL). The default is a plain memo for each node and attribute, for one
352
+ * evaluation (TREE.MEMO).
353
+ */
354
+ readonly cell?: AttributeCell;
355
+ }
356
+
357
+ /**
358
+ * This function gives the code of the kind of a node of the decorated tree, with the options of the host. A host puts
359
+ * the code at the name `tree.node` of its machine. Without a cell, the code is the standard code.
360
+ *
361
+ * @param options - The options, for example the memo cells of the host.
362
+ * @returns The code.
363
+ */
364
+ export function treeNodeEvaluator(options: TreeOptions = {}): Evaluator {
365
+ const host = options.cell;
366
+ return host === undefined ? (STD_EVALUATORS["tree.node"] as Evaluator) : (input: EvalInput) => treeNode(input, host);
367
+ }
368
+
319
369
  /**
320
370
  * The memo cell of one attribute: in evaluation, or done with its result and the rung that answered. A done cell
321
371
  * without a result tells that no rung answered.
@@ -373,6 +423,77 @@ function contractOf(machine: Machine | undefined, typePath: readonly Key[]): Nod
373
423
 
374
424
  const BUSY: Cell = /* @__PURE__ */ Object.freeze({ busy: true });
375
425
 
426
+ /** The answer of the ladder when no rung answers. */
427
+ const NO_ANSWER: AttributeAnswer = /* @__PURE__ */ Object.freeze({ result: undefined, by: undefined });
428
+
429
+ /**
430
+ * The marks of the attributes in evaluation, for each key of a node, with the cells of a host (TREE.CELL). Nodes of
431
+ * two evaluations share their key and their kept cells, thus they share their marks. Nodes of two documents have
432
+ * different keys, thus a nested evaluation gets its own marks (TREE.CELL-NESTED, D115).
433
+ */
434
+ const marksByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], Map<string, Cell>>();
435
+
436
+ /** The trees of the cells of a host, for each key of a root. Nodes of two evaluations stay in one tree. */
437
+ const buildsByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], TreeBuild>();
438
+
439
+ /** The keys of the nodes with the cells of a host, for each scope and each place of a plan (D115). */
440
+ const keysByScope = /* @__PURE__ */ new WeakMap<object, WeakMap<readonly Key[], readonly Key[]>>();
441
+
442
+ /** The scopes of the roots that a demand made, for each marks of a node and each name of an attribute (D115). */
443
+ const demandScopes = /* @__PURE__ */ new WeakMap<Map<string, Cell>, Map<string, object>>();
444
+
445
+ /**
446
+ * This function gives the key of a node with the cells of a host (TREE.CELL-DOCUMENT, D115). The key of the node
447
+ * around the node is its scope. A root without such a node has the scope of the open demand, or no scope.
448
+ * Without a scope, the key is the place of the plan. Thus a cached plan gives a new key for the nodes of each
449
+ * document, and each evaluation of a document gives the same keys.
450
+ *
451
+ * @param place - The place of the document of the node in its plan.
452
+ * @param above - The state of the node around the node, or `undefined`.
453
+ * @returns The key: an array with the keys of `place`.
454
+ */
455
+ function cellPlace(place: readonly Key[], above: NodeState | undefined): readonly Key[] {
456
+ let scope: object | undefined = above?.place;
457
+ if (scope === undefined) {
458
+ const demand = demands[demands.length - 1];
459
+ if (demand === undefined) return place;
460
+ let byName = demandScopes.get(demand.cells);
461
+ if (byName === undefined) {
462
+ byName = new Map();
463
+ demandScopes.set(demand.cells, byName);
464
+ }
465
+ scope = byName.get(demand.key);
466
+ if (scope === undefined) {
467
+ scope = {};
468
+ byName.set(demand.key, scope);
469
+ }
470
+ }
471
+ let keys = keysByScope.get(scope);
472
+ if (keys === undefined) {
473
+ keys = new WeakMap();
474
+ keysByScope.set(scope, keys);
475
+ }
476
+ let key = keys.get(place);
477
+ if (key === undefined) {
478
+ key = Object.freeze([...place]);
479
+ keys.set(place, key);
480
+ }
481
+ return key;
482
+ }
483
+
484
+ /** This function gives the value of a weak map, or puts a new value in it. */
485
+ function keptAt<T>(map: WeakMap<readonly Key[], T>, place: readonly Key[], make: () => T): T {
486
+ let value = map.get(place);
487
+ if (value === undefined) {
488
+ value = make();
489
+ map.set(place, value);
490
+ }
491
+ return value;
492
+ }
493
+
494
+ /** A value that no exception has. */
495
+ const NOT_THROWN: unique symbol = /* @__PURE__ */ Symbol("coeval.tree.not-thrown") as never;
496
+
376
497
  /** One attribute in evaluation. The text names it in the reason of a cycle. */
377
498
  interface Demand {
378
499
  readonly cells: Map<string, Cell>;
@@ -383,6 +504,293 @@ interface Demand {
383
504
  /** The attributes in evaluation, in the order of their demands. An evaluation does not wait, thus one list serves. */
384
505
  const demands: Demand[] = [];
385
506
 
507
+ /** One value of an attribute in an open fixed point (D113). */
508
+ interface LoopValue {
509
+ readonly result: Result | undefined;
510
+ readonly by: AttributeRung | undefined;
511
+ /** The round of an approximation. The round `0` marks a final answer of a node with the cells of a host. */
512
+ readonly round: number;
513
+ /** The plain memo of the node. A stable fixed point writes its final value there. A node with host cells has none. */
514
+ readonly memo: Map<string, Cell> | undefined;
515
+ }
516
+
517
+ /**
518
+ * A fixed point in evaluation (TREE.CIRCULAR, D113). The circular attribute that a demand reaches first leads it. Each
519
+ * circular attribute that a demand reaches during its rounds joins it, thus only one fixed point is open at a time.
520
+ */
521
+ interface FixedPoint {
522
+ /** The index of the demand of the leader in `demands`. */
523
+ readonly depth: number;
524
+ round: number;
525
+ /** `true` when a circular attribute got a new value in this round. */
526
+ changed: boolean;
527
+ /** The values of the fixed point, by the marks of a node and by the name of the attribute. */
528
+ readonly values: Map<Map<string, Cell>, Map<string, LoopValue>>;
529
+ /** The demands of circular attributes, from the leader up. */
530
+ readonly circular: Set<Demand>;
531
+ /** The demands above the leader that read an approximation. Their results are not final. */
532
+ readonly tentative: Set<Demand>;
533
+ }
534
+
535
+ /** The open fixed point, or `undefined`. */
536
+ let openPoint: FixedPoint | undefined;
537
+
538
+ /**
539
+ * What the code of a fixed point needs of a node (D113). The state of each node holds it, thus the functions of a
540
+ * fixed point are module functions, and a node makes no closure for them.
541
+ */
542
+ interface LoopNode {
543
+ readonly type: string;
544
+ readonly place: readonly Key[];
545
+ /** The tree contract of the kind of the node (D42). */
546
+ readonly tree: TreeContract | undefined;
547
+ /** The marks of the attributes in evaluation. Without the cells of a host, they are the plain memo. */
548
+ readonly marks: Map<string, Cell>;
549
+ /** The plain memo of the node. With the cells of a host, it keeps the last answer only for `has` and `trace`. */
550
+ readonly cells: Map<string, Cell>;
551
+ readonly host: AttributeCell | undefined;
552
+ /** This function visits the rungs of the ladder of an attribute in their order, until one answers (TREE.LADDER). */
553
+ readonly climb: (key: string) => AttributeAnswer | undefined;
554
+ }
555
+
556
+ /** For each table `tree.attributes`, `true` when an attribute of it declares `circular`. */
557
+ const circularTables = /* @__PURE__ */ new WeakMap<object, boolean>();
558
+
559
+ /** For each declaration `circular` that is an object, its rule. */
560
+ const circularRules = /* @__PURE__ */ new WeakMap<object, CircularRule>();
561
+
562
+ /** This function tells if a tree contract declares a circular attribute. The answer for a table is computed one time. */
563
+ function hasCircular(tree: TreeContract | undefined): boolean {
564
+ const table = tree?.attributes;
565
+ if (table === undefined) return false;
566
+ let known = circularTables.get(table);
567
+ if (known === undefined) {
568
+ known = Object.keys(table).some((key) => {
569
+ const declared: unknown = entryOf(table, key);
570
+ return typeof declared === "object" && declared !== null && (declared as AttributeDeclaration).circular !== undefined;
571
+ });
572
+ circularTables.set(table, known);
573
+ }
574
+ return known;
575
+ }
576
+
577
+ /** This function gives the declaration `circular` of an attribute of a tree contract (D113), or `undefined`. */
578
+ function circularRule(tree: TreeContract | undefined, key: string): CircularRule | undefined {
579
+ const declared: unknown = entryOf(tree?.attributes, key)?.circular;
580
+ if (declared === undefined) return undefined;
581
+ if (typeof declared !== "object" || declared === null) return circularOf(declared);
582
+ let rule = circularRules.get(declared);
583
+ if (rule === undefined) {
584
+ rule = circularOf(declared);
585
+ circularRules.set(declared, rule);
586
+ }
587
+ return rule;
588
+ }
589
+
590
+ /** This function gives the values of a fixed point for the marks of a node. */
591
+ function loopValues(loop: FixedPoint, marks: Map<string, Cell>): Map<string, LoopValue> {
592
+ let values = loop.values.get(marks);
593
+ if (values === undefined) {
594
+ values = new Map();
595
+ loop.values.set(marks, values);
596
+ }
597
+ return values;
598
+ }
599
+
600
+ /** This function marks each demand above the leader: each one read an approximation, thus its result is not final. */
601
+ function markTentative(loop: FixedPoint): void {
602
+ for (let index = loop.depth + 1; index < demands.length; index += 1) loop.tentative.add(demands[index] as Demand);
603
+ }
604
+
605
+ /** This function tells if two approximations are the same: two values by DOC.EQUALS, two errors by code and reason. */
606
+ function sameApproximation(a: Result | undefined, b: Result | undefined): boolean {
607
+ if (a === undefined || b === undefined) return a === b;
608
+ if (a.ok && b.ok) return docEquals(a.value, b.value);
609
+ if (!a.ok && !b.ok) return a.error.code === b.error.code && a.error.reason === b.error.reason;
610
+ return false;
611
+ }
612
+
613
+ /** This function writes the final value of each circular attribute of the last round in the plain memo of its node. */
614
+ function settle(loop: FixedPoint): void {
615
+ for (const values of loop.values.values()) {
616
+ for (const [key, value] of values) {
617
+ if (value.memo !== undefined && value.round === loop.round) value.memo.set(key, { busy: false, result: value.result, by: value.by });
618
+ }
619
+ }
620
+ }
621
+
622
+ /** The text of the fixed point of a circular attribute that does not become stable (TREE.CIRCULAR-LIMIT). */
623
+ function limitReason(text: string, limit: number): string {
624
+ return `The cycle of the circular attribute ${text} does not become stable within its limit of ${limit} ${limit === 1 ? "round" : "rounds"}.`;
625
+ }
626
+
627
+ /**
628
+ * This function answers a demand that reached an attribute in evaluation in an open fixed point (TREE.CYCLE). A chain
629
+ * of circular attributes gives the current approximation (TREE.CIRCULAR). Each other chain gives `#CYCLE!`.
630
+ */
631
+ function reachedIn(loop: FixedPoint, n: LoopNode, key: string): Result | undefined {
632
+ const first = demands.findIndex((demand) => demand.cells === n.marks && demand.key === key);
633
+ let circular = first >= loop.depth;
634
+ for (let index = first; circular && index < demands.length; index += 1) circular = loop.circular.has(demands[index] as Demand);
635
+ if (!circular) return cycleAt(n.marks, key, attributeText(key, n.type, n.place));
636
+ const rule = circularRule(n.tree, key);
637
+ const kept = loop.values.get(n.marks)?.get(key);
638
+ markTentative(loop);
639
+ return kept !== undefined ? kept.result : ok(rule?.ok === true ? rule.start : null);
640
+ }
641
+
642
+ /**
643
+ * This function evaluates the leader of a fixed point (TREE.CIRCULAR, D113). Its demand is the last one. Each round
644
+ * visits the ladder again, and the rounds end when no circular attribute gets a new value. An error ends the rounds
645
+ * at once. After the last round of the limit, the leader gives `#CYCLE!` at the place of its node.
646
+ */
647
+ function roundsOf(n: LoopNode, key: string, rule: CircularRule & { readonly ok: true }): AttributeAnswer {
648
+ const leader = demands[demands.length - 1] as Demand;
649
+ const loop: FixedPoint = { depth: demands.length - 1, round: 0, changed: false, values: new Map(), circular: new Set([leader]), tentative: new Set() };
650
+ openPoint = loop;
651
+ try {
652
+ let before: Result | undefined = ok(rule.start);
653
+ for (;;) {
654
+ loop.round += 1;
655
+ loop.changed = false;
656
+ const answer = n.climb(key) ?? NO_ANSWER;
657
+ // An error is not a value of the fixed point. The other attributes of the cycle evaluate again at their next demand.
658
+ if (answer.result !== undefined && !answer.result.ok) return answer;
659
+ if (!sameApproximation(before, answer.result)) loop.changed = true;
660
+ before = answer.result;
661
+ loopValues(loop, n.marks).set(key, { result: answer.result, by: answer.by, round: loop.round, memo: undefined });
662
+ if (!loop.changed) {
663
+ settle(loop);
664
+ return answer;
665
+ }
666
+ if (loop.round >= rule.limit) return { result: fail("#CYCLE!", limitReason(attributeText(key, n.type, n.place), rule.limit), { place: n.place }), by: answer.by };
667
+ }
668
+ } finally {
669
+ openPoint = undefined;
670
+ }
671
+ }
672
+
673
+ /**
674
+ * This function evaluates a circular attribute in the open fixed point of another leader, one time in each round
675
+ * (D113). A later demand in the same round gets the value of the round. Each value is an approximation.
676
+ */
677
+ function roundIn(loop: FixedPoint, n: LoopNode, key: string, rule: CircularRule & { readonly ok: true }, kept: LoopValue | undefined): Result | undefined {
678
+ let value = kept;
679
+ if (value === undefined || value.round !== loop.round) {
680
+ const demand: Demand = { cells: n.marks, key, text: attributeText(key, n.type, n.place) };
681
+ n.marks.set(key, BUSY);
682
+ demands.push(demand);
683
+ loop.circular.add(demand);
684
+ let answer: AttributeAnswer;
685
+ try {
686
+ answer = n.climb(key) ?? NO_ANSWER;
687
+ } finally {
688
+ demands.pop();
689
+ n.marks.delete(key);
690
+ }
691
+ if (!sameApproximation(value === undefined ? ok(rule.start) : value.result, answer.result)) loop.changed = true;
692
+ value = { result: answer.result, by: answer.by, round: loop.round, memo: n.host === undefined ? n.cells : undefined };
693
+ loopValues(loop, n.marks).set(key, value);
694
+ }
695
+ markTentative(loop);
696
+ return value.result;
697
+ }
698
+
699
+ /**
700
+ * This function evaluates an attribute without the cell of the host: with the plain memo, or in an open fixed point
701
+ * (D113). A result that read an approximation is not final, thus no memo keeps it.
702
+ */
703
+ function localIn(n: LoopNode, key: string, rule: (CircularRule & { readonly ok: true }) | undefined): Result | undefined {
704
+ const demand: Demand = { cells: n.marks, key, text: attributeText(key, n.type, n.place) };
705
+ const loop = openPoint;
706
+ n.marks.set(key, BUSY);
707
+ demands.push(demand);
708
+ let answer: AttributeAnswer;
709
+ try {
710
+ answer = rule === undefined ? (n.climb(key) ?? NO_ANSWER) : roundsOf(n, key, rule);
711
+ } catch (error) {
712
+ // An exception of the host, for example an overflow of the stack, leaves the attribute without a result.
713
+ n.marks.delete(key);
714
+ throw error;
715
+ } finally {
716
+ demands.pop();
717
+ }
718
+ if (loop !== undefined && loop.tentative.has(demand)) {
719
+ n.marks.delete(key);
720
+ return answer.result;
721
+ }
722
+ if (n.host !== undefined) {
723
+ // The fixed point keeps a final answer of a node with host cells until it ends (D113).
724
+ n.marks.delete(key);
725
+ if (loop !== undefined) loopValues(loop, n.marks).set(key, { result: answer.result, by: answer.by, round: 0, memo: undefined });
726
+ }
727
+ n.cells.set(key, { busy: false, result: answer.result, by: answer.by });
728
+ return answer.result;
729
+ }
730
+
731
+ /**
732
+ * This function answers a demand that reached an attribute in evaluation (TREE.CYCLE). Outside a fixed point, it gives
733
+ * `#CYCLE!` with the chain. In an open fixed point, `reachedIn` answers (TREE.CIRCULAR).
734
+ */
735
+ function busyAt(n: LoopNode, key: string): Result | undefined {
736
+ const loop = openPoint;
737
+ return loop === undefined ? cycleAt(n.marks, key, attributeText(key, n.type, n.place)) : reachedIn(loop, n, key);
738
+ }
739
+
740
+ /**
741
+ * This function evaluates the leader of a fixed point through the cell of the host (TREE.CELL, D113). The cell of the
742
+ * leader gets the final value of the fixed point, from a computation that contains each round.
743
+ */
744
+ function leaderOnHost(n: LoopNode, key: string, rule: CircularRule & { readonly ok: true }, host: AttributeCell): Result | undefined {
745
+ const text = attributeText(key, n.type, n.place);
746
+ n.marks.set(key, BUSY);
747
+ demands.push({ cells: n.marks, key, text });
748
+ let thrown: unknown = NOT_THROWN;
749
+ let answer: AttributeAnswer;
750
+ try {
751
+ answer = host(n.place, key, () => {
752
+ try {
753
+ return roundsOf(n, key, rule);
754
+ } catch (error) {
755
+ thrown = error;
756
+ throw error;
757
+ }
758
+ });
759
+ } catch (error) {
760
+ // An exception of the kit or of the stack goes on, as without a host. Another exception is a cycle of the host.
761
+ if (error === thrown) throw error;
762
+ const chain = demands.map((demand) => demand.text).join(" -> ");
763
+ answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
764
+ } finally {
765
+ demands.pop();
766
+ n.marks.delete(key);
767
+ }
768
+ n.cells.set(key, { busy: false, result: answer.result, by: answer.by });
769
+ return answer.result;
770
+ }
771
+
772
+ /**
773
+ * This function gives an attribute of a node with circular attributes, or an attribute in an open fixed point (D113).
774
+ * The memo and the marks of the node do not hold the attribute. An open fixed point does not use the cells of the host.
775
+ */
776
+ function circularRead(n: LoopNode, key: string): Result | undefined {
777
+ const rule = circularRule(n.tree, key);
778
+ if (rule !== undefined && !rule.ok) {
779
+ const result = fail("#VALUE!", `The attribute ${attributeText(key, n.type, n.place)} has a bad declaration circular. ${rule.reason}`);
780
+ n.cells.set(key, { busy: false, result, by: undefined });
781
+ return result;
782
+ }
783
+ const loop = openPoint;
784
+ if (loop !== undefined) {
785
+ const kept = loop.values.get(n.marks)?.get(key);
786
+ if (rule !== undefined) return roundIn(loop, n, key, rule, kept);
787
+ if (kept !== undefined) return kept.result;
788
+ return localIn(n, key, undefined);
789
+ }
790
+ if (rule === undefined || n.host === undefined) return localIn(n, key, rule);
791
+ return leaderOnHost(n, key, rule, n.host);
792
+ }
793
+
386
794
  function attributeText(key: string, type: string, place: readonly Key[]): string {
387
795
  return `${key} of ${type} at [${place.join(", ")}]`;
388
796
  }
@@ -451,9 +859,12 @@ function plainOf(value: unknown, path: Set<object>): Result {
451
859
  * the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
452
860
  * fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
453
861
  * The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
862
+ *
863
+ * The argument `place` is the key of the node. With the cells of a host, `cellPlace` gives it. Else it is the place of
864
+ * the plan. The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
454
865
  */
455
- function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
456
- const { parts, context, kind: type, place, machine } = input;
866
+ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, outer: Thing | undefined, host: AttributeCell | undefined): HostThing {
867
+ const { parts, context, kind: type, machine } = input;
457
868
  // The equations evaluate in the evaluation that made the node, thus their reads go in its list (READS.EQUATION).
458
869
  const runtime = (input as InputWithRuntime)[RUNTIME];
459
870
  const { tree, of } = contractOf(machine, input.typePath);
@@ -461,6 +872,10 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
461
872
  // Only a node of the same tree is a parent for the rungs `parent` and `copy`.
462
873
  const up = above !== undefined && above.build === build ? above : undefined;
463
874
  const cells = new Map<string, Cell>();
875
+ // With the cells of a host, the marks of a key serve each node of the key (TREE.CELL).
876
+ const marks = host === undefined ? cells : keptAt(marksByPlace, place, () => new Map<string, Cell>());
877
+ // Only a node whose kind declares a circular attribute takes the path of D113 outside a fixed point.
878
+ const circular = hasCircular(tree);
464
879
  /** The positions of the items of the list `children`, for the names of the children. */
465
880
  let positions: { readonly list: readonly unknown[]; readonly at: Map<unknown, number> } | undefined;
466
881
 
@@ -505,11 +920,52 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
505
920
  return undefined;
506
921
  };
507
922
 
923
+ /**
924
+ * This function gives an attribute through the cell of the host (TREE.CELL). The cell is the memo. The node keeps
925
+ * the last answer only for `has` and `trace`.
926
+ */
927
+ const through =
928
+ host === undefined
929
+ ? undefined
930
+ : (key: string): Result | undefined => {
931
+ const text = attributeText(key, type, place);
932
+ if (marks.has(key)) return busyAt(state, key);
933
+ if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
934
+ if (circular || openPoint !== undefined) return circularRead(state, key);
935
+ marks.set(key, BUSY);
936
+ demands.push({ cells: marks, key, text });
937
+ let thrown: unknown = NOT_THROWN;
938
+ let answer: AttributeAnswer;
939
+ try {
940
+ answer = host(place, key, () => {
941
+ try {
942
+ return climb(key) ?? NO_ANSWER;
943
+ } catch (error) {
944
+ thrown = error;
945
+ throw error;
946
+ }
947
+ });
948
+ } catch (error) {
949
+ // An exception of the kit or of the stack goes on, as without a host. Another exception is a cycle of the host.
950
+ if (error === thrown) throw error;
951
+ const chain = demands.map((demand) => demand.text).join(" -> ");
952
+ answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
953
+ } finally {
954
+ demands.pop();
955
+ marks.delete(key);
956
+ }
957
+ cells.set(key, { busy: false, result: answer.result, by: answer.by });
958
+ return answer.result;
959
+ };
960
+
508
961
  const read = (key: string): Result | undefined => {
962
+ if (through !== undefined) return through(key);
509
963
  const cell = cells.get(key);
510
- if (cell !== undefined) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
964
+ if (cell !== undefined) return cell.busy ? busyAt(state, key) : cell.result;
511
965
  // A node without a contract has only its parts, as before D42. Thus a missing part needs no cell.
512
966
  if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
967
+ // A circular attribute and each demand in an open fixed point take the code of D113 (TREE.CIRCULAR).
968
+ if (circular || openPoint !== undefined) return circularRead(state, key);
513
969
  cells.set(key, BUSY);
514
970
  demands.push({ cells, key, text: attributeText(key, type, place) });
515
971
  let answer: { readonly result: Result; readonly by: AttributeRung } | undefined;
@@ -531,6 +987,10 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
531
987
  type,
532
988
  place,
533
989
  tree,
990
+ marks,
991
+ cells,
992
+ host,
993
+ climb,
534
994
  read,
535
995
  equation(term, names) {
536
996
  if (machine === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
@@ -635,14 +1095,19 @@ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
635
1095
  * This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
636
1096
  * thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
637
1097
  * node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
1098
+ * The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of each node.
638
1099
  */
639
- function treeNode(input: EvalInput): Result {
1100
+ function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
640
1101
  const found = input.resolve(SELF_PATH, NO_HOPS);
641
1102
  const outer = found.ok ? found.thing : undefined;
642
1103
  const above = stateOf(outer);
643
- if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer));
644
- const build: TreeBuild = { open: true };
645
- const root = treeThing(input, build, outer);
1104
+ // With the cells of a host, the key of a node holds its scope (D115). Without them, the place of the plan serves.
1105
+ const place = host === undefined ? input.place : cellPlace(input.place, above);
1106
+ if (above !== undefined && above.build.open) return ok(treeThing(input, place, above.build, outer, host));
1107
+ // With the cells of a host, a kept value can hold a node of an earlier evaluation. Thus that node stays in the tree.
1108
+ const build: TreeBuild = host === undefined ? { open: true } : keptAt(buildsByPlace, place, () => ({ open: true }));
1109
+ build.open = true;
1110
+ const root = treeThing(input, place, build, outer, host);
646
1111
  try {
647
1112
  return (stateOf(root) as NodeState).json(new Set());
648
1113
  } finally {
package/src/types.ts CHANGED
@@ -205,8 +205,12 @@ function formOf(type: unknown, domains: Readonly<Record<string, Domain>> | undef
205
205
  return { t: "record", fields: fields as Readonly<Record<string, ValueType>>, optional: new Set(names), closed };
206
206
  }
207
207
 
208
- /** This function gives each base type that holds the members of a base type: the type itself and each parent, through each level. */
209
- function lineOf(name: string, domains: Readonly<Record<string, Domain>> | undefined): ReadonlySet<string> {
208
+ /**
209
+ * This function gives each base type that holds the members of a base type: the type itself and each parent, through
210
+ * each level. A cycle of parents ends at a name that the line has, and a parent that is not a domain ends its branch.
211
+ * The function is internal: `encodeValue` uses it too (D109).
212
+ */
213
+ export function lineOf(name: string, domains: Readonly<Record<string, Domain>> | undefined): ReadonlySet<string> {
210
214
  const seen = new Set<string>([name]);
211
215
  const queue = [name];
212
216
  while (queue.length > 0) {