@mark1russell7/coeval 0.21.0 → 0.23.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 +23 -5
- package/build/index.d.ts +420 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5669 -3655
- package/build/index.js.map +1 -1
- package/package.json +4 -4
- package/src/codec-laws.ts +152 -0
- package/src/codec.ts +2 -2
- package/src/conform.ts +662 -70
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +55 -21
- package/src/formula.ts +82 -22
- package/src/index.ts +16 -0
- package/src/instances.ts +61 -6
- package/src/meaning.ts +43 -10
- package/src/parts.ts +7 -4
- package/src/providers.ts +4 -1
- package/src/reference.ts +134 -9
- package/src/schedule.ts +286 -45
- package/src/static.ts +116 -23
- package/src/stdlib.ts +229 -6
- package/src/syntax.ts +430 -48
- package/src/types.ts +2 -1
- package/src/world.ts +106 -1
package/src/static.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type Kind, type LazyRule, type World, type WorldEntry, circularOf, collectOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
1
|
+
import { type Envelope, type Json, type Key, field, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
+
import { type Kind, type LazyRule, type World, type WorldEntry, atArgsOf, atRuleOf, circularOf, collectOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
3
3
|
import { answerOf, ask } from "./ladder.ts";
|
|
4
4
|
import { type PartSlot, partsOf, rulesCover, scopesOf } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
|
|
@@ -8,6 +8,7 @@ import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
|
|
|
8
8
|
import type { ValueType } from "./types.ts";
|
|
9
9
|
import { type RootTypes, type TypeNode, type Typed, machineTypeIssues, typePass } from "./typing.ts";
|
|
10
10
|
import { cycleIssues } from "./instances.ts";
|
|
11
|
+
import { STD_WALKERS } from "./stdlib.ts";
|
|
11
12
|
|
|
12
13
|
/** One document that a walk visits. */
|
|
13
14
|
export interface DocVisit {
|
|
@@ -102,6 +103,17 @@ function keysOf(trail: Trail | undefined): readonly Key[] {
|
|
|
102
103
|
|
|
103
104
|
/** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
|
|
104
105
|
class Visit implements DocVisit {
|
|
106
|
+
readonly doc: Envelope;
|
|
107
|
+
readonly declared: boolean;
|
|
108
|
+
readonly depth: number;
|
|
109
|
+
readonly names: ReadonlySet<string>;
|
|
110
|
+
readonly later: ReadonlySet<string>;
|
|
111
|
+
readonly slot: readonly Key[];
|
|
112
|
+
readonly parent: DocVisit | undefined;
|
|
113
|
+
readonly kind: Kind | undefined;
|
|
114
|
+
readonly typePath: readonly Key[] | undefined;
|
|
115
|
+
readonly address: true | undefined;
|
|
116
|
+
readonly caps: readonly Cap[] | undefined;
|
|
105
117
|
readonly #place: Trail | undefined;
|
|
106
118
|
readonly #at: Trail | undefined;
|
|
107
119
|
readonly #bound: readonly string[];
|
|
@@ -110,20 +122,31 @@ class Visit implements DocVisit {
|
|
|
110
122
|
constructor(
|
|
111
123
|
place: Trail | undefined,
|
|
112
124
|
at: Trail | undefined,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
doc: Envelope,
|
|
126
|
+
declared: boolean,
|
|
127
|
+
depth: number,
|
|
128
|
+
names: ReadonlySet<string>,
|
|
129
|
+
later: ReadonlySet<string>,
|
|
130
|
+
slot: readonly Key[],
|
|
131
|
+
parent: DocVisit | undefined,
|
|
132
|
+
kind: Kind | undefined,
|
|
133
|
+
typePath: readonly Key[] | undefined,
|
|
134
|
+
address: true | undefined,
|
|
135
|
+
caps: readonly Cap[] | undefined,
|
|
124
136
|
bound: readonly string[],
|
|
125
137
|
expect: ValueType | undefined,
|
|
126
138
|
) {
|
|
139
|
+
this.doc = doc;
|
|
140
|
+
this.declared = declared;
|
|
141
|
+
this.depth = depth;
|
|
142
|
+
this.names = names;
|
|
143
|
+
this.later = later;
|
|
144
|
+
this.slot = slot;
|
|
145
|
+
this.parent = parent;
|
|
146
|
+
this.kind = kind;
|
|
147
|
+
this.typePath = typePath;
|
|
148
|
+
this.address = address;
|
|
149
|
+
this.caps = caps;
|
|
127
150
|
this.#place = place;
|
|
128
151
|
this.#at = at;
|
|
129
152
|
this.#bound = bound;
|
|
@@ -690,6 +713,8 @@ export function equationsOf(kind: Kind): readonly Equation[] {
|
|
|
690
713
|
out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
|
|
691
714
|
}
|
|
692
715
|
if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [], attribute: name, down: false });
|
|
716
|
+
// Only the equation is known before the run. The document that it gives is not known (TREE.HIGHER-CHECK, D126).
|
|
717
|
+
if (declared.higher !== undefined) out.push({ label: `tree.attributes.${name}.higher`, term: declared.higher, names: [], attribute: name, down: false });
|
|
693
718
|
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
694
719
|
if (collect?.ok === true && !collect.names) {
|
|
695
720
|
out.push({ label: `tree.attributes.${name}.collect.unit`, term: collect.unit, names: [], attribute: name, down: false });
|
|
@@ -698,6 +723,11 @@ export function equationsOf(kind: Kind): readonly Equation[] {
|
|
|
698
723
|
}
|
|
699
724
|
// A contribution is not the attribute of its node, thus an own part of its name does not hide it (D122).
|
|
700
725
|
for (const [name, term] of Object.entries(tree.contributes ?? {})) out.push({ label: `tree.contributes.${name}`, term, names: [], attribute: name, down: false, contributes: true });
|
|
726
|
+
// An equation of an attribute with parameters sees each parameter as a name (TREE.AT, D124).
|
|
727
|
+
for (const [name, declared] of Object.entries(tree.at ?? {})) {
|
|
728
|
+
const rule = atRuleOf(declared);
|
|
729
|
+
if (rule.ok) out.push({ label: `tree.at.${name}`, term: rule.equation, names: rule.params, attribute: name, down: false, at: true });
|
|
730
|
+
}
|
|
701
731
|
return out;
|
|
702
732
|
}
|
|
703
733
|
|
|
@@ -713,6 +743,8 @@ export interface Equation {
|
|
|
713
743
|
readonly down: boolean;
|
|
714
744
|
/** `true` for an equation of `contributes`: it gives a contribution to a collection (TREE.COLLECT). */
|
|
715
745
|
readonly contributes?: true;
|
|
746
|
+
/** `true` for an equation of `at`: it gives an attribute with parameters (TREE.AT). */
|
|
747
|
+
readonly at?: true;
|
|
716
748
|
}
|
|
717
749
|
|
|
718
750
|
/**
|
|
@@ -877,15 +909,16 @@ function hasEntry(table: Readonly<Record<string, unknown>> | undefined, name: st
|
|
|
877
909
|
/**
|
|
878
910
|
* This function tells if a rung of the ladder can answer an attribute of a node (TREE.LADDER). An unknown parent can
|
|
879
911
|
* be a node of each kind of the world, thus each name of `down` in the world can answer. A copy can always answer,
|
|
880
|
-
* because `check` does not know a node without a parent.
|
|
912
|
+
* because `check` does not know a node without a parent. A kind with `forwards` can answer each other name, because the
|
|
913
|
+
* forwarded node is not known before the run (TREE.FORWARD-CHECK).
|
|
881
914
|
*/
|
|
882
915
|
export function answered(world: World, node: SeenNode, name: string): boolean {
|
|
883
916
|
if (node.own?.has(name) === true) return true;
|
|
884
917
|
const tree = node.kind.tree;
|
|
885
|
-
if (hasEntry(tree?.equations, name)) return true;
|
|
918
|
+
if (hasEntry(tree?.equations, name) || (tree?.forwards !== undefined && tree.forwards !== name)) return true;
|
|
886
919
|
if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
|
|
887
920
|
const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
|
|
888
|
-
return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined);
|
|
921
|
+
return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined || declared.higher !== undefined);
|
|
889
922
|
}
|
|
890
923
|
|
|
891
924
|
/**
|
|
@@ -900,11 +933,12 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
900
933
|
const needs: Need[] = [];
|
|
901
934
|
state.needs.set(kind, needs);
|
|
902
935
|
for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
|
|
936
|
+
if (typeof kind.tree?.forwards === "string" && !Object.hasOwn(STRUCTURAL, kind.tree.forwards)) needs.push({ name: kind.tree.forwards, why: "the kind forwards to it", hidden: undefined });
|
|
903
937
|
const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
|
|
904
|
-
for (const { label, term, names, attribute, down, contributes } of equationsOf(kind)) {
|
|
938
|
+
for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
|
|
905
939
|
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
906
940
|
const read = (name: string): void => {
|
|
907
|
-
needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true ? undefined : attribute });
|
|
941
|
+
needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true || at === true ? undefined : attribute });
|
|
908
942
|
};
|
|
909
943
|
for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
|
|
910
944
|
out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
|
|
@@ -917,12 +951,13 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
917
951
|
* This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
|
|
918
952
|
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
|
|
919
953
|
* declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK). A declaration
|
|
920
|
-
* `collect` without its form gives `#VALUE!` too (TREE.COLLECT).
|
|
954
|
+
* `collect` without its form gives `#VALUE!` too (TREE.COLLECT). So does an entry of `at` without its form (TREE.AT-CHECK).
|
|
955
|
+
* A field `forwards` that is not a text gives `#VALUE!`, and a structural name gives `#NAME?` (TREE.FORWARD-CHECK).
|
|
921
956
|
*/
|
|
922
957
|
function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
923
958
|
const out: Issue[] = [];
|
|
924
959
|
const tree = kind.tree;
|
|
925
|
-
for (const table of ["equations", "down", "attributes", "contributes"] as const) {
|
|
960
|
+
for (const table of ["equations", "down", "attributes", "contributes", "at"] as const) {
|
|
926
961
|
for (const name of Object.keys(tree?.[table] ?? {})) {
|
|
927
962
|
const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
|
|
928
963
|
if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
|
|
@@ -935,6 +970,15 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
|
935
970
|
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
936
971
|
if (collect !== undefined && !collect.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.collect. ${collect.reason}` });
|
|
937
972
|
}
|
|
973
|
+
for (const [name, declared] of Object.entries(tree?.at ?? {})) {
|
|
974
|
+
const rule = atRuleOf(declared);
|
|
975
|
+
if (!rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad entry tree.at.${name}. ${rule.reason}` });
|
|
976
|
+
}
|
|
977
|
+
const forwards: unknown = tree?.forwards;
|
|
978
|
+
if (forwards !== undefined && typeof forwards !== "string") out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad field tree.forwards. It must be a text.` });
|
|
979
|
+
if (typeof forwards === "string" && Object.hasOwn(STRUCTURAL, forwards)) {
|
|
980
|
+
out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.forwards: ${String(STRUCTURAL[forwards])}.` });
|
|
981
|
+
}
|
|
938
982
|
if (kind.side === "run") {
|
|
939
983
|
out.push({ code: "#CALC!", place: [], reason: `The tree kind ${kindText} is on the run side: a node evaluates its attributes on demand, thus in no fixed order.` });
|
|
940
984
|
}
|
|
@@ -966,9 +1010,14 @@ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVi
|
|
|
966
1010
|
* This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
|
|
967
1011
|
* nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
|
|
968
1012
|
* function gives the index of the step and the reason. In an equation, a read of the node of the equation goes to the
|
|
969
|
-
* needs of its kind.
|
|
1013
|
+
* needs of its kind. A step `tree.at` with literal arguments gets the checks of TREE.AT-CHECK.
|
|
970
1014
|
*/
|
|
971
|
-
function readIssue(
|
|
1015
|
+
function readIssue(
|
|
1016
|
+
machine: Machine,
|
|
1017
|
+
self: SeenNode | undefined,
|
|
1018
|
+
path: readonly Json[],
|
|
1019
|
+
rootSelf: RootSelf | undefined,
|
|
1020
|
+
): { readonly index: number; readonly reason: string; readonly code?: ErrorCode } | undefined {
|
|
972
1021
|
let node = self;
|
|
973
1022
|
let index = 1;
|
|
974
1023
|
while (node !== undefined && index < path.length && path[index] === "parent") {
|
|
@@ -976,6 +1025,10 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
976
1025
|
index += 1;
|
|
977
1026
|
}
|
|
978
1027
|
const step = path[index];
|
|
1028
|
+
if (node !== undefined && isAtStep(machine, step)) {
|
|
1029
|
+
const at = atStepOf(node.kind, node.text, step.value);
|
|
1030
|
+
return at.kind === "issue" ? { index, reason: at.reason, code: at.code } : undefined;
|
|
1031
|
+
}
|
|
979
1032
|
if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
|
|
980
1033
|
const name = String(step);
|
|
981
1034
|
if (node.own === undefined) {
|
|
@@ -985,6 +1038,46 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
985
1038
|
return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
|
|
986
1039
|
}
|
|
987
1040
|
|
|
1041
|
+
/** This function tells if a step of a path is a step `tree.at` of the standard walker (TREE.AT, D124). */
|
|
1042
|
+
export function isAtStep(machine: Machine, step: unknown): step is Envelope {
|
|
1043
|
+
return isEnvelope(step) && walkerOf(machine, step) === STD_WALKERS["walk.tree.at"];
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* A step `tree.at` of a known kind before the run (TREE.AT-CHECK, D124). An exact step names an instance. The run of a
|
|
1048
|
+
* step with an issue fails at the step. Only the run knows a step with a document in its data, or a bad entry.
|
|
1049
|
+
*/
|
|
1050
|
+
export type AtStep =
|
|
1051
|
+
| { readonly kind: "exact"; readonly attr: string; readonly key: string; readonly equation: Json; readonly params: readonly string[]; readonly names: Readonly<Record<string, Json>> }
|
|
1052
|
+
| { readonly kind: "issue"; readonly code: ErrorCode; readonly reason: string }
|
|
1053
|
+
| { readonly kind: "unknown" };
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* This function examines the data of a step `tree.at` on a node of a known kind (TREE.AT-CHECK, D124). `check` and the
|
|
1057
|
+
* graph of attribute instances read the step with this one function.
|
|
1058
|
+
*/
|
|
1059
|
+
export function atStepOf(kind: Kind, kindText: string, data: unknown): AtStep {
|
|
1060
|
+
if (holdsDocument(data)) return { kind: "unknown" };
|
|
1061
|
+
const attr = isRecord(data) ? field(data, "attr") : undefined;
|
|
1062
|
+
if (typeof attr !== "string") return { kind: "issue", code: "#VALUE!", reason: "The step tree.at needs a text attr." };
|
|
1063
|
+
const table = kind.tree?.at;
|
|
1064
|
+
const declared = table !== undefined && Object.hasOwn(table, attr) ? table[attr] : undefined;
|
|
1065
|
+
if (declared === undefined) return { kind: "issue", code: "#N/A", reason: `No rung gives the attribute ${attr} with parameters of the kind ${kindText}.` };
|
|
1066
|
+
const rule = atRuleOf(declared);
|
|
1067
|
+
// The issue of a bad entry is an issue of the kind (contractIssues).
|
|
1068
|
+
if (!rule.ok) return { kind: "unknown" };
|
|
1069
|
+
const given = atArgsOf(rule.params, isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {});
|
|
1070
|
+
if (!given.ok) return { kind: "issue", code: "#VALUE!", reason: `The step tree.at of ${attr} of the kind ${kindText} has bad arguments. ${given.reason}` };
|
|
1071
|
+
return { kind: "exact", attr, key: given.key, equation: rule.equation, params: rule.params, names: given.names };
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/** This function tells if a value holds a document. A document in the data of a step can be a part, thus only the run knows its value. */
|
|
1075
|
+
function holdsDocument(value: unknown): boolean {
|
|
1076
|
+
if (isEnvelope(value)) return true;
|
|
1077
|
+
if (Array.isArray(value)) return value.some(holdsDocument);
|
|
1078
|
+
return isRecord(value) && Object.values(value).some(holdsDocument);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
988
1081
|
/** The self of the root of a walk for the checks of attributes: the node of an equation, and the function for its reads. */
|
|
989
1082
|
interface RootSelf {
|
|
990
1083
|
readonly node: SeenNode;
|
|
@@ -1149,7 +1242,7 @@ function checkAll(
|
|
|
1149
1242
|
// Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
|
|
1150
1243
|
if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
|
|
1151
1244
|
const issue = readIssue(machine, self, path, rootSelf);
|
|
1152
|
-
if (issue !== undefined) push({ code: "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
|
|
1245
|
+
if (issue !== undefined) push({ code: issue.code ?? "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
|
|
1153
1246
|
}
|
|
1154
1247
|
path.forEach((step, index) => {
|
|
1155
1248
|
if (index === 0 || isKey(step)) return;
|
package/src/stdlib.ts
CHANGED
|
@@ -18,7 +18,19 @@ 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 {
|
|
21
|
+
import {
|
|
22
|
+
type AttributeDeclaration,
|
|
23
|
+
type CircularRule,
|
|
24
|
+
type TreeContract,
|
|
25
|
+
GENERATION_LIMIT,
|
|
26
|
+
atArgsOf,
|
|
27
|
+
atRuleOf,
|
|
28
|
+
circularOf,
|
|
29
|
+
collectOf,
|
|
30
|
+
documentKey,
|
|
31
|
+
equationType,
|
|
32
|
+
lookupKind,
|
|
33
|
+
} from "./world.ts";
|
|
22
34
|
import { STD_DOMAINS } from "./types.ts";
|
|
23
35
|
import type { SyntaxProfile } from "./syntax.ts";
|
|
24
36
|
|
|
@@ -74,6 +86,16 @@ export const STD_WORLD: World = {
|
|
|
74
86
|
pure: true,
|
|
75
87
|
summary: "A step of a path: it goes up to the nearest registry of names, and it gives the node with the name (TREE.NAMED).",
|
|
76
88
|
},
|
|
89
|
+
at: {
|
|
90
|
+
verbs: { step: "walk.tree.at", view: "view.card" },
|
|
91
|
+
pure: true,
|
|
92
|
+
summary: "A step of a path: it reads an attribute with parameters of the node before it, with the arguments of the step (TREE.AT).",
|
|
93
|
+
},
|
|
94
|
+
quote: {
|
|
95
|
+
verbs: { eval: "data.quote", view: "view.value" },
|
|
96
|
+
pure: true,
|
|
97
|
+
summary: "A document as data. Its value is the result, and nothing in it evaluates. A higher-order attribute uses it (TREE.HIGHER).",
|
|
98
|
+
},
|
|
77
99
|
},
|
|
78
100
|
},
|
|
79
101
|
panel: {
|
|
@@ -177,6 +199,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
177
199
|
{ name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
|
|
178
200
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
179
201
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
202
|
+
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
203
|
+
{ name: "tree.quote", kind: "tree.quote", args: "literal" },
|
|
180
204
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
181
205
|
{ name: "add", kind: "add" },
|
|
182
206
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -218,6 +242,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
|
|
|
218
242
|
"data.number": /* @__PURE__ */ dataOf("number"),
|
|
219
243
|
"data.text": /* @__PURE__ */ dataOf("string"),
|
|
220
244
|
"data.bool": /* @__PURE__ */ dataOf("boolean"),
|
|
245
|
+
"data.quote": ({ data }) => ok(data ?? null),
|
|
221
246
|
"math.add": ({ parts }) => {
|
|
222
247
|
const all = allValues(parts.map((part) => part.evaluate()));
|
|
223
248
|
if ("error" in all) return all.error;
|
|
@@ -265,10 +290,10 @@ interface TreeBuild {
|
|
|
265
290
|
}
|
|
266
291
|
|
|
267
292
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
268
|
-
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default";
|
|
293
|
+
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default" | "forward";
|
|
269
294
|
|
|
270
295
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
271
|
-
export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default"] as const);
|
|
296
|
+
export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default", "forward"] as const);
|
|
272
297
|
|
|
273
298
|
/** The record of the demand of one attribute of a node (TREE.RUNGS). */
|
|
274
299
|
export interface AttributeTrace {
|
|
@@ -343,6 +368,10 @@ export interface AttributeAnswer {
|
|
|
343
368
|
* A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
|
|
344
369
|
* A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
|
|
345
370
|
*
|
|
371
|
+
* An attribute with parameters gets one cell for each key of its arguments (TREE.AT-CELL, D124). Its `place` is a
|
|
372
|
+
* second array with the same keys, one for each key of a node. Its `attribute` is the JSON text of the name and the
|
|
373
|
+
* arguments, for example `["height",{"width":200}]`.
|
|
374
|
+
*
|
|
346
375
|
* The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
|
|
347
376
|
* of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
|
|
348
377
|
* a cell that computes again uses the last one.
|
|
@@ -916,6 +945,180 @@ function collected(root: HostThing, key: string, declared: unknown): Result {
|
|
|
916
945
|
return acc;
|
|
917
946
|
}
|
|
918
947
|
|
|
948
|
+
/** A function from the name of an attribute and the arguments to the result, or `undefined` when no rung answers (D124). */
|
|
949
|
+
export type AtReader = (attr: string, args: unknown) => Result | undefined;
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* The host things that answer the step `tree.at` with their own function (D124). A node of the reference is in it, and
|
|
953
|
+
* its fields do not change. The walker reads the state of a node of the kit.
|
|
954
|
+
*/
|
|
955
|
+
export const AT_READERS: WeakMap<object, AtReader> = /* @__PURE__ */ new WeakMap();
|
|
956
|
+
|
|
957
|
+
/** The memo of the instances of the attributes with parameters of each node, without the cells of a host (TREE.AT). */
|
|
958
|
+
const atMemos = /* @__PURE__ */ new WeakMap<NodeState, Map<string, Cell>>();
|
|
959
|
+
|
|
960
|
+
/** The second key of each key of a node, for the cells of the attributes with parameters (TREE.AT-CELL). */
|
|
961
|
+
const atPlaces = /* @__PURE__ */ new WeakMap<readonly Key[], readonly Key[]>();
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* This function gives an instance of an attribute with parameters of a node (TREE.AT, D124). The equation in
|
|
965
|
+
* `tree.at` of the kind of the node evaluates with the arguments as names. The memo keeps one result for each key of
|
|
966
|
+
* the arguments, and a demand of an instance in evaluation gives `#CYCLE!`. The function is outside the code of
|
|
967
|
+
* `read`, thus a node without such attributes does no new work.
|
|
968
|
+
*/
|
|
969
|
+
function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
|
|
970
|
+
const declared: unknown = entryOf(n.tree?.at, attr);
|
|
971
|
+
if (declared === undefined) return undefined;
|
|
972
|
+
const rule = atRuleOf(declared);
|
|
973
|
+
if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(attr, n.type, n.place)} has a bad entry tree.at. ${rule.reason}`);
|
|
974
|
+
const given = atArgsOf(rule.params, args);
|
|
975
|
+
if (!given.ok) return fail("#VALUE!", `The step tree.at of ${attributeText(attr, n.type, n.place)} has bad arguments. ${given.reason}`);
|
|
976
|
+
// An open fixed point does not use the cells of the host (D113).
|
|
977
|
+
const host = openPoint === undefined ? n.host : undefined;
|
|
978
|
+
const place = n.host === undefined ? n.place : keptAt(atPlaces, n.place, () => Object.freeze([...n.place]));
|
|
979
|
+
let marks: Map<string, Cell> | undefined = n.host === undefined ? atMemos.get(n) : keptAt(marksByPlace, place, () => new Map<string, Cell>());
|
|
980
|
+
if (marks === undefined) {
|
|
981
|
+
marks = new Map();
|
|
982
|
+
atMemos.set(n, marks);
|
|
983
|
+
}
|
|
984
|
+
const key = `[${JSON.stringify(attr)},${given.key}]`;
|
|
985
|
+
const text = attributeText(`${attr} with ${given.key}`, n.type, n.place);
|
|
986
|
+
const cell = marks.get(key);
|
|
987
|
+
if (cell !== undefined) return cell.busy ? cycleAt(marks, key, text) : cell.result;
|
|
988
|
+
const demand: Demand = { cells: marks, key, text };
|
|
989
|
+
const loop = openPoint;
|
|
990
|
+
const compute = (): AttributeAnswer => ({ result: n.equation(rule.equation, given.names), by: "kind" });
|
|
991
|
+
marks.set(key, BUSY);
|
|
992
|
+
demands.push(demand);
|
|
993
|
+
let thrown: unknown = NOT_THROWN;
|
|
994
|
+
let answer: AttributeAnswer;
|
|
995
|
+
try {
|
|
996
|
+
answer =
|
|
997
|
+
host === undefined
|
|
998
|
+
? compute()
|
|
999
|
+
: host(place, key, () => {
|
|
1000
|
+
try {
|
|
1001
|
+
return compute();
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
thrown = error;
|
|
1004
|
+
throw error;
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
marks.delete(key);
|
|
1009
|
+
// An exception of the kit or of the stack goes on. Another exception of the cell is a cycle of the host (D108).
|
|
1010
|
+
if (host === undefined || error === thrown) throw error;
|
|
1011
|
+
const chain = demands.map((item) => item.text).join(" -> ");
|
|
1012
|
+
answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
|
|
1013
|
+
} finally {
|
|
1014
|
+
demands.pop();
|
|
1015
|
+
}
|
|
1016
|
+
// A result that read an approximation is not final. With the cells of a host, the cell is the memo.
|
|
1017
|
+
if (n.host !== undefined || loop?.tentative.has(demand) === true) marks.delete(key);
|
|
1018
|
+
else marks.set(key, { busy: false, result: answer.result, by: answer.by });
|
|
1019
|
+
return answer.result;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/** The most documents and the most characters of the keys in the table of generated documents. */
|
|
1023
|
+
const INTERNED_DOCUMENTS = 1000;
|
|
1024
|
+
const INTERNED_CHARACTERS = 16_000_000;
|
|
1025
|
+
|
|
1026
|
+
/** The first document of each structural key (TREE.HIGHER-PLAN, D126). */
|
|
1027
|
+
const internedDocuments = /* @__PURE__ */ new Map<string, Json>();
|
|
1028
|
+
|
|
1029
|
+
/** The kept document of each document object. An object gets its key one time, as PLAN.CACHE says. */
|
|
1030
|
+
const internedObjects = /* @__PURE__ */ new WeakMap<object, Json>();
|
|
1031
|
+
|
|
1032
|
+
/** The characters of the keys of `internedDocuments`. */
|
|
1033
|
+
let internedCharacters = 0;
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* This function gives the one object of a document (TREE.HIGHER-PLAN, D126). `equationPlan` caches by object, thus a
|
|
1037
|
+
* new object with a known key compiles nothing. The table has a limit, and a full table starts again. A document that
|
|
1038
|
+
* left the table compiles again, and that costs only time.
|
|
1039
|
+
*
|
|
1040
|
+
* @param doc - The value of an equation.
|
|
1041
|
+
* @returns The document, or `undefined` for a value that is not JSON data.
|
|
1042
|
+
*/
|
|
1043
|
+
function internedDocument(doc: unknown): Json | undefined {
|
|
1044
|
+
const object = typeof doc === "object" && doc !== null;
|
|
1045
|
+
const known = object ? internedObjects.get(doc) : undefined;
|
|
1046
|
+
if (known !== undefined) return known;
|
|
1047
|
+
const key = documentKey(doc);
|
|
1048
|
+
if (key === undefined) return undefined;
|
|
1049
|
+
if (!object) return doc as Json;
|
|
1050
|
+
let kept = internedDocuments.get(key);
|
|
1051
|
+
if (kept === undefined) {
|
|
1052
|
+
if (internedDocuments.size >= INTERNED_DOCUMENTS || internedCharacters + key.length > INTERNED_CHARACTERS) {
|
|
1053
|
+
internedDocuments.clear();
|
|
1054
|
+
internedCharacters = 0;
|
|
1055
|
+
}
|
|
1056
|
+
kept = doc as Json;
|
|
1057
|
+
internedDocuments.set(key, kept);
|
|
1058
|
+
internedCharacters += key.length;
|
|
1059
|
+
}
|
|
1060
|
+
internedObjects.set(doc, kept);
|
|
1061
|
+
return kept;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** The depth of each root of a generated document: the generated documents from the root of the tree to it (D126). */
|
|
1065
|
+
const generations = /* @__PURE__ */ new WeakMap<NodeState, number>();
|
|
1066
|
+
|
|
1067
|
+
/** This function gives the depth of the nearest root of a generated document at or above a node of a tree, or 0. */
|
|
1068
|
+
function generationOf(node: HostThing, build: TreeBuild): number {
|
|
1069
|
+
let thing: Thing | undefined = node;
|
|
1070
|
+
for (let at = stateOf(thing); at?.build === build; at = stateOf(thing)) {
|
|
1071
|
+
const depth = generations.get(at);
|
|
1072
|
+
if (depth !== undefined) return depth;
|
|
1073
|
+
thing = (thing as HostThing).deref("parent");
|
|
1074
|
+
}
|
|
1075
|
+
return 0;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* This function gives a higher-order attribute of a node (TREE.HIGHER, D126). The equation gives a document as data.
|
|
1080
|
+
* The document evaluates as an equation, with the node as `self`. Thus its root is a node under the node: it finds the
|
|
1081
|
+
* open tree, and it has the node as its parent. The function is outside the code of `read`, as a fold is.
|
|
1082
|
+
*/
|
|
1083
|
+
function generated(n: NodeState, node: HostThing, key: string, term: Json): Result {
|
|
1084
|
+
const value = n.equation(term, NO_NAMES);
|
|
1085
|
+
if (!value.ok) return value;
|
|
1086
|
+
const doc = internedDocument(value.value);
|
|
1087
|
+
const text = attributeText(key, n.type, n.place);
|
|
1088
|
+
if (doc === undefined) return fail("#VALUE!", `The equation of the higher-order attribute ${text} gives a value that is not JSON data.`);
|
|
1089
|
+
const depth = generationOf(node, n.build) + 1;
|
|
1090
|
+
if (depth > GENERATION_LIMIT) return fail("#DEPTH!", `The higher-order attribute ${text} is under more than ${GENERATION_LIMIT} generated documents.`);
|
|
1091
|
+
const root = n.equation(doc, NO_NAMES);
|
|
1092
|
+
if (!root.ok) return root;
|
|
1093
|
+
const made = stateOf(root.value);
|
|
1094
|
+
if (made === undefined) return fail("#VALUE!", `The document of the higher-order attribute ${text} gives no node of the tree.`);
|
|
1095
|
+
generations.set(made, depth);
|
|
1096
|
+
return root;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* This function gives the last rung of the ladder (TREE.FORWARD, D126). The attribute `forwards` gives a node, and that
|
|
1101
|
+
* node answers the attribute through its own ladder. The attribute `forwards` itself gets no such answer.
|
|
1102
|
+
*/
|
|
1103
|
+
function forwarded(n: NodeState, node: HostThing, key: string, name: unknown): { readonly result: Result; readonly by: AttributeRung } | undefined {
|
|
1104
|
+
if (key === name) return undefined;
|
|
1105
|
+
const text = attributeText(key, n.type, n.place);
|
|
1106
|
+
if (typeof name !== "string") return { result: fail("#VALUE!", `The attribute ${text} has a contract with a bad field forwards. It must be a text.`), by: "forward" };
|
|
1107
|
+
const target = n.read(name);
|
|
1108
|
+
if (target === undefined) return undefined;
|
|
1109
|
+
if (!target.ok) return { result: target, by: "forward" };
|
|
1110
|
+
const to = stateOf(target.value);
|
|
1111
|
+
if (to === undefined) return { result: fail("#VALUE!", `The attribute ${name} that ${text} forwards to gives no node of a tree.`), by: "forward" };
|
|
1112
|
+
// An equation can give a node without a declaration higher, thus the rung counts each node that it reaches.
|
|
1113
|
+
if (!generations.has(to)) {
|
|
1114
|
+
const depth = generationOf(node, n.build) + 1;
|
|
1115
|
+
if (depth > GENERATION_LIMIT) return { result: fail("#DEPTH!", `The attribute ${text} forwards through more than ${GENERATION_LIMIT} generated documents.`), by: "forward" };
|
|
1116
|
+
generations.set(to, depth);
|
|
1117
|
+
}
|
|
1118
|
+
const result = to.read(key);
|
|
1119
|
+
return result === undefined ? undefined : { result, by: "forward" };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
919
1122
|
/**
|
|
920
1123
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
921
1124
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -981,7 +1184,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
981
1184
|
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
982
1185
|
if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
|
|
983
1186
|
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
984
|
-
return
|
|
1187
|
+
if (declared?.higher !== undefined) return { result: generated(state, node, key, declared.higher), by: "default" };
|
|
1188
|
+
return tree?.forwards === undefined ? undefined : forwarded(state, node, key, tree.forwards);
|
|
985
1189
|
};
|
|
986
1190
|
|
|
987
1191
|
/**
|
|
@@ -1078,7 +1282,9 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1078
1282
|
trace(key) {
|
|
1079
1283
|
const cell = cells.get(key);
|
|
1080
1284
|
if (cell === undefined || cell.busy) return undefined;
|
|
1081
|
-
|
|
1285
|
+
// The last rung is on the way only for a kind with the field forwards.
|
|
1286
|
+
const end = cell.by === undefined ? ATTRIBUTE_RUNGS.length - (tree?.forwards === undefined ? 1 : 0) : ATTRIBUTE_RUNGS.indexOf(cell.by) + 1;
|
|
1287
|
+
const rungs = ATTRIBUTE_RUNGS.slice(0, end);
|
|
1082
1288
|
return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
|
|
1083
1289
|
},
|
|
1084
1290
|
json(path) {
|
|
@@ -1256,8 +1462,25 @@ const named: Walker = ({ data, here, deref }) => {
|
|
|
1256
1462
|
return { ok: false, reason: `No registry ${registry} is on the way.` };
|
|
1257
1463
|
};
|
|
1258
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* The walker of the step `tree.at` (TREE.AT, D124). The field `attr` names an attribute with parameters, and the
|
|
1467
|
+
* field `args` gives the arguments. The thing before the step is a node. A failed instance throws its own error.
|
|
1468
|
+
*/
|
|
1469
|
+
const at: Walker = ({ data, here }) => {
|
|
1470
|
+
const attr = isRecord(data) ? field(data, "attr") : undefined;
|
|
1471
|
+
if (typeof attr !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.at needs a text attr." };
|
|
1472
|
+
const args = isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {};
|
|
1473
|
+
const state = stateOf(here);
|
|
1474
|
+
const reader = state === undefined && typeof here === "object" && here !== null ? AT_READERS.get(here) : undefined;
|
|
1475
|
+
if (state === undefined && reader === undefined) return { ok: false, reason: `The step tree.at reads ${attr} of a thing that is not a node of a tree.` };
|
|
1476
|
+
const result = state === undefined ? (reader as AtReader)(attr, args) : atRead(state, attr, args);
|
|
1477
|
+
if (result === undefined) return { ok: false, reason: `No rung gives the attribute ${attr} with parameters.` };
|
|
1478
|
+
if (!result.ok) throw new Failure(result.error);
|
|
1479
|
+
return { ok: true, thing: result.value as Thing };
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1259
1482
|
/** The walkers of the standard world, by name. */
|
|
1260
|
-
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named };
|
|
1483
|
+
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named, "walk.tree.at": at };
|
|
1261
1484
|
|
|
1262
1485
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1263
1486
|
function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {
|