@mark1russell7/coeval 0.19.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/README.md +1 -1
- package/build/index.d.ts +49 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +552 -34
- package/build/index.js.map +1 -1
- package/package.json +3 -1
- package/src/index.ts +4 -0
- package/src/instances.ts +33 -5
- package/src/reference.ts +142 -12
- package/src/static.ts +8 -2
- package/src/stdlib.ts +367 -23
- package/src/world.ts +59 -0
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
|
|
|
@@ -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
|
*
|
|
@@ -328,9 +324,12 @@ export interface AttributeAnswer {
|
|
|
328
324
|
* This function gives the memo cell of one attribute of one node, for a host that keeps its cells between evaluations
|
|
329
325
|
* (TREE.CELL, D108). The kit uses it at each demand of an attribute in an evaluation.
|
|
330
326
|
*
|
|
331
|
-
* The array `place` is one object for each
|
|
332
|
-
* plan.
|
|
333
|
-
*
|
|
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.
|
|
334
333
|
*
|
|
335
334
|
* The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
|
|
336
335
|
* of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
|
|
@@ -339,7 +338,7 @@ export interface AttributeAnswer {
|
|
|
339
338
|
* The kit marks an attribute in evaluation before it uses the cell. Thus a cycle gives `#CYCLE!` before it reaches the
|
|
340
339
|
* cell again. An exception of the function that `compute` did not throw gives `#CYCLE!` with the chain of the demands.
|
|
341
340
|
*
|
|
342
|
-
* @param place - The keys from the root document to the document of the node.
|
|
341
|
+
* @param place - The key of the node. Its items are the keys from the root document to the document of the node.
|
|
343
342
|
* @param attribute - The name of the attribute.
|
|
344
343
|
* @param compute - The function that visits the rungs of the ladder.
|
|
345
344
|
* @returns The answer.
|
|
@@ -428,14 +427,60 @@ const BUSY: Cell = /* @__PURE__ */ Object.freeze({ busy: true });
|
|
|
428
427
|
const NO_ANSWER: AttributeAnswer = /* @__PURE__ */ Object.freeze({ result: undefined, by: undefined });
|
|
429
428
|
|
|
430
429
|
/**
|
|
431
|
-
* The marks of the attributes in evaluation, for each
|
|
432
|
-
* two evaluations share their
|
|
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
433
|
*/
|
|
434
434
|
const marksByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], Map<string, Cell>>();
|
|
435
435
|
|
|
436
|
-
/** The trees of the cells of a host, for each
|
|
436
|
+
/** The trees of the cells of a host, for each key of a root. Nodes of two evaluations stay in one tree. */
|
|
437
437
|
const buildsByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], TreeBuild>();
|
|
438
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
|
+
|
|
439
484
|
/** This function gives the value of a weak map, or puts a new value in it. */
|
|
440
485
|
function keptAt<T>(map: WeakMap<readonly Key[], T>, place: readonly Key[], make: () => T): T {
|
|
441
486
|
let value = map.get(place);
|
|
@@ -459,6 +504,293 @@ interface Demand {
|
|
|
459
504
|
/** The attributes in evaluation, in the order of their demands. An evaluation does not wait, thus one list serves. */
|
|
460
505
|
const demands: Demand[] = [];
|
|
461
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
|
+
|
|
462
794
|
function attributeText(key: string, type: string, place: readonly Key[]): string {
|
|
463
795
|
return `${key} of ${type} at [${place.join(", ")}]`;
|
|
464
796
|
}
|
|
@@ -528,10 +860,11 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
528
860
|
* fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
|
|
529
861
|
* The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
|
|
530
862
|
*
|
|
531
|
-
* The argument `
|
|
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.
|
|
532
865
|
*/
|
|
533
|
-
function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined, host: AttributeCell | undefined): HostThing {
|
|
534
|
-
const { parts, context, kind: type,
|
|
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;
|
|
535
868
|
// The equations evaluate in the evaluation that made the node, thus their reads go in its list (READS.EQUATION).
|
|
536
869
|
const runtime = (input as InputWithRuntime)[RUNTIME];
|
|
537
870
|
const { tree, of } = contractOf(machine, input.typePath);
|
|
@@ -539,8 +872,10 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined,
|
|
|
539
872
|
// Only a node of the same tree is a parent for the rungs `parent` and `copy`.
|
|
540
873
|
const up = above !== undefined && above.build === build ? above : undefined;
|
|
541
874
|
const cells = new Map<string, Cell>();
|
|
542
|
-
// With the cells of a host, the marks of a
|
|
875
|
+
// With the cells of a host, the marks of a key serve each node of the key (TREE.CELL).
|
|
543
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);
|
|
544
879
|
/** The positions of the items of the list `children`, for the names of the children. */
|
|
545
880
|
let positions: { readonly list: readonly unknown[]; readonly at: Map<unknown, number> } | undefined;
|
|
546
881
|
|
|
@@ -594,8 +929,9 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined,
|
|
|
594
929
|
? undefined
|
|
595
930
|
: (key: string): Result | undefined => {
|
|
596
931
|
const text = attributeText(key, type, place);
|
|
597
|
-
if (marks.has(key)) return
|
|
932
|
+
if (marks.has(key)) return busyAt(state, key);
|
|
598
933
|
if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
|
|
934
|
+
if (circular || openPoint !== undefined) return circularRead(state, key);
|
|
599
935
|
marks.set(key, BUSY);
|
|
600
936
|
demands.push({ cells: marks, key, text });
|
|
601
937
|
let thrown: unknown = NOT_THROWN;
|
|
@@ -625,9 +961,11 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined,
|
|
|
625
961
|
const read = (key: string): Result | undefined => {
|
|
626
962
|
if (through !== undefined) return through(key);
|
|
627
963
|
const cell = cells.get(key);
|
|
628
|
-
if (cell !== undefined) return cell.busy ?
|
|
964
|
+
if (cell !== undefined) return cell.busy ? busyAt(state, key) : cell.result;
|
|
629
965
|
// A node without a contract has only its parts, as before D42. Thus a missing part needs no cell.
|
|
630
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);
|
|
631
969
|
cells.set(key, BUSY);
|
|
632
970
|
demands.push({ cells, key, text: attributeText(key, type, place) });
|
|
633
971
|
let answer: { readonly result: Result; readonly by: AttributeRung } | undefined;
|
|
@@ -649,6 +987,10 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined,
|
|
|
649
987
|
type,
|
|
650
988
|
place,
|
|
651
989
|
tree,
|
|
990
|
+
marks,
|
|
991
|
+
cells,
|
|
992
|
+
host,
|
|
993
|
+
climb,
|
|
652
994
|
read,
|
|
653
995
|
equation(term, names) {
|
|
654
996
|
if (machine === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
@@ -759,11 +1101,13 @@ function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
|
|
|
759
1101
|
const found = input.resolve(SELF_PATH, NO_HOPS);
|
|
760
1102
|
const outer = found.ok ? found.thing : undefined;
|
|
761
1103
|
const above = stateOf(outer);
|
|
762
|
-
|
|
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));
|
|
763
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.
|
|
764
|
-
const build: TreeBuild = host === undefined ? { open: true } : keptAt(buildsByPlace,
|
|
1108
|
+
const build: TreeBuild = host === undefined ? { open: true } : keptAt(buildsByPlace, place, () => ({ open: true }));
|
|
765
1109
|
build.open = true;
|
|
766
|
-
const root = treeThing(input, build, outer, host);
|
|
1110
|
+
const root = treeThing(input, place, build, outer, host);
|
|
767
1111
|
try {
|
|
768
1112
|
return (stateOf(root) as NodeState).json(new Set());
|
|
769
1113
|
} finally {
|
package/src/world.ts
CHANGED
|
@@ -134,11 +134,70 @@ export interface KindFacts {
|
|
|
134
134
|
* Then the term `combine` adds the attribute of each node in the list `children`, one at a time (TREE.FOLD).
|
|
135
135
|
* `combine` sees the names `acc` and `item`.
|
|
136
136
|
* - `default`: the term that gives the value when no rung before it answers and the declaration has no fold.
|
|
137
|
+
* - `circular`: a cycle of this attribute gives its fixed point, not `#CYCLE!` (TREE.CIRCULAR, D113). Only a cycle
|
|
138
|
+
* whose attributes each declare `circular` gives a fixed point.
|
|
137
139
|
*/
|
|
138
140
|
export interface AttributeDeclaration {
|
|
139
141
|
readonly copy?: boolean;
|
|
140
142
|
readonly fold?: { readonly unit: Json; readonly combine: Json };
|
|
141
143
|
readonly default?: Json;
|
|
144
|
+
readonly circular?: CircularDeclaration;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The declaration `circular` of an attribute (TREE.CIRCULAR, D113). The attribute that a cycle reaches first leads it.
|
|
149
|
+
* The leader evaluates again until no value of the cycle changes.
|
|
150
|
+
*
|
|
151
|
+
* - `start`: the value of the attribute before its first round, as JSON data. The kit does not evaluate it.
|
|
152
|
+
* - `limit`: the most rounds of the leader, a whole number of 1 or more. The default is `CIRCULAR_LIMIT`. A cycle that
|
|
153
|
+
* is not stable after the last round gives `#CYCLE!` (TREE.CIRCULAR-LIMIT).
|
|
154
|
+
*/
|
|
155
|
+
export interface CircularDeclaration {
|
|
156
|
+
readonly start: Json;
|
|
157
|
+
readonly limit?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The most rounds of a circular attribute without its own `limit` (TREE.CIRCULAR-LIMIT, D113). */
|
|
161
|
+
export const CIRCULAR_LIMIT = 100;
|
|
162
|
+
|
|
163
|
+
/** A declaration `circular` that the kit can use: its start value and its limit, or the reason of a bad declaration. */
|
|
164
|
+
export type CircularRule = { readonly ok: true; readonly start: Json; readonly limit: number } | { readonly ok: false; readonly reason: string };
|
|
165
|
+
|
|
166
|
+
/** This function tells if a value is JSON data: `null`, a truth value, a finite number, a text, or a container of them. */
|
|
167
|
+
function isJsonData(value: unknown): boolean {
|
|
168
|
+
const stack: unknown[] = [value];
|
|
169
|
+
const seen = new Set<object>();
|
|
170
|
+
while (stack.length > 0) {
|
|
171
|
+
const item = stack.pop();
|
|
172
|
+
if (item === null || typeof item === "string" || typeof item === "boolean") continue;
|
|
173
|
+
if (typeof item === "number") {
|
|
174
|
+
if (!Number.isFinite(item)) return false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!Array.isArray(item) && !isRecord(item)) return false;
|
|
178
|
+
if (seen.has(item)) continue;
|
|
179
|
+
seen.add(item);
|
|
180
|
+
for (const inner of Object.values(item)) stack.push(inner);
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* This function reads the declaration `circular` of an attribute (D113). The tree kit, the reference, `check` and the
|
|
187
|
+
* graph of attribute instances read it with this one function.
|
|
188
|
+
*
|
|
189
|
+
* @param declared - The value of the field `circular`.
|
|
190
|
+
* @returns The start value and the limit, or the reason when the declaration has no correct form.
|
|
191
|
+
*/
|
|
192
|
+
export function circularOf(declared: unknown): CircularRule {
|
|
193
|
+
if (!isRecord(declared) || !Object.hasOwn(declared, "start")) return { ok: false, reason: "The declaration circular needs a record with the field start." };
|
|
194
|
+
const start = field(declared, "start");
|
|
195
|
+
if (!isJsonData(start)) return { ok: false, reason: "The start value of the declaration circular must be JSON data." };
|
|
196
|
+
const limit: unknown = Object.hasOwn(declared, "limit") ? field(declared, "limit") : CIRCULAR_LIMIT;
|
|
197
|
+
if (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1) {
|
|
198
|
+
return { ok: false, reason: "The limit of the declaration circular must be a whole number of 1 or more." };
|
|
199
|
+
}
|
|
200
|
+
return { ok: true, start: start as Json, limit };
|
|
142
201
|
}
|
|
143
202
|
|
|
144
203
|
/**
|