@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/README.md +4 -4
- package/build/index.d.ts +307 -20
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3362 -1537
- package/build/index.js.map +1 -1
- package/package.json +3 -1
- package/src/codec.ts +30 -10
- package/src/durable.ts +17 -5
- package/src/facts.ts +55 -19
- package/src/formula.ts +45 -1
- package/src/index.ts +20 -2
- package/src/instances.ts +665 -0
- package/src/reads.ts +186 -2
- package/src/reference.ts +142 -12
- package/src/static.ts +363 -50
- package/src/stdlib.ts +480 -15
- package/src/types.ts +6 -2
- package/src/typing.ts +143 -3
- package/src/world.ts +108 -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
|
|
|
@@ -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,
|
|
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 ?
|
|
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
|
-
|
|
644
|
-
const
|
|
645
|
-
|
|
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
|
-
/**
|
|
209
|
-
function
|
|
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) {
|