@mark1russell7/coeval 0.22.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 +359 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5308 -3680
- 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 +60 -17
- package/src/index.ts +10 -0
- package/src/instances.ts +16 -1
- package/src/meaning.ts +43 -10
- package/src/parts.ts +7 -4
- package/src/providers.ts +4 -1
- package/src/reference.ts +67 -2
- package/src/schedule.ts +286 -45
- package/src/static.ts +46 -14
- package/src/stdlib.ts +127 -5
- package/src/syntax.ts +430 -48
- package/src/types.ts +2 -1
- package/src/world.ts +24 -3
package/src/static.ts
CHANGED
|
@@ -103,6 +103,17 @@ function keysOf(trail: Trail | undefined): readonly Key[] {
|
|
|
103
103
|
|
|
104
104
|
/** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
|
|
105
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;
|
|
106
117
|
readonly #place: Trail | undefined;
|
|
107
118
|
readonly #at: Trail | undefined;
|
|
108
119
|
readonly #bound: readonly string[];
|
|
@@ -111,20 +122,31 @@ class Visit implements DocVisit {
|
|
|
111
122
|
constructor(
|
|
112
123
|
place: Trail | undefined,
|
|
113
124
|
at: Trail | undefined,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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,
|
|
125
136
|
bound: readonly string[],
|
|
126
137
|
expect: ValueType | undefined,
|
|
127
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;
|
|
128
150
|
this.#place = place;
|
|
129
151
|
this.#at = at;
|
|
130
152
|
this.#bound = bound;
|
|
@@ -691,6 +713,8 @@ export function equationsOf(kind: Kind): readonly Equation[] {
|
|
|
691
713
|
out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
|
|
692
714
|
}
|
|
693
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 });
|
|
694
718
|
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
695
719
|
if (collect?.ok === true && !collect.names) {
|
|
696
720
|
out.push({ label: `tree.attributes.${name}.collect.unit`, term: collect.unit, names: [], attribute: name, down: false });
|
|
@@ -885,15 +909,16 @@ function hasEntry(table: Readonly<Record<string, unknown>> | undefined, name: st
|
|
|
885
909
|
/**
|
|
886
910
|
* This function tells if a rung of the ladder can answer an attribute of a node (TREE.LADDER). An unknown parent can
|
|
887
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,
|
|
888
|
-
* 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).
|
|
889
914
|
*/
|
|
890
915
|
export function answered(world: World, node: SeenNode, name: string): boolean {
|
|
891
916
|
if (node.own?.has(name) === true) return true;
|
|
892
917
|
const tree = node.kind.tree;
|
|
893
|
-
if (hasEntry(tree?.equations, name)) return true;
|
|
918
|
+
if (hasEntry(tree?.equations, name) || (tree?.forwards !== undefined && tree.forwards !== name)) return true;
|
|
894
919
|
if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
|
|
895
920
|
const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
|
|
896
|
-
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);
|
|
897
922
|
}
|
|
898
923
|
|
|
899
924
|
/**
|
|
@@ -908,6 +933,7 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
908
933
|
const needs: Need[] = [];
|
|
909
934
|
state.needs.set(kind, needs);
|
|
910
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 });
|
|
911
937
|
const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
|
|
912
938
|
for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
|
|
913
939
|
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
@@ -926,6 +952,7 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
926
952
|
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
|
|
927
953
|
* declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK). A declaration
|
|
928
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).
|
|
929
956
|
*/
|
|
930
957
|
function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
931
958
|
const out: Issue[] = [];
|
|
@@ -947,6 +974,11 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
|
947
974
|
const rule = atRuleOf(declared);
|
|
948
975
|
if (!rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad entry tree.at.${name}. ${rule.reason}` });
|
|
949
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
|
+
}
|
|
950
982
|
if (kind.side === "run") {
|
|
951
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.` });
|
|
952
984
|
}
|
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
|
|
|
@@ -79,6 +91,11 @@ export const STD_WORLD: World = {
|
|
|
79
91
|
pure: true,
|
|
80
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).",
|
|
81
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
|
+
},
|
|
82
99
|
},
|
|
83
100
|
},
|
|
84
101
|
panel: {
|
|
@@ -183,6 +200,7 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
183
200
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
184
201
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
185
202
|
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
203
|
+
{ name: "tree.quote", kind: "tree.quote", args: "literal" },
|
|
186
204
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
187
205
|
{ name: "add", kind: "add" },
|
|
188
206
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -224,6 +242,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
|
|
|
224
242
|
"data.number": /* @__PURE__ */ dataOf("number"),
|
|
225
243
|
"data.text": /* @__PURE__ */ dataOf("string"),
|
|
226
244
|
"data.bool": /* @__PURE__ */ dataOf("boolean"),
|
|
245
|
+
"data.quote": ({ data }) => ok(data ?? null),
|
|
227
246
|
"math.add": ({ parts }) => {
|
|
228
247
|
const all = allValues(parts.map((part) => part.evaluate()));
|
|
229
248
|
if ("error" in all) return all.error;
|
|
@@ -271,10 +290,10 @@ interface TreeBuild {
|
|
|
271
290
|
}
|
|
272
291
|
|
|
273
292
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
274
|
-
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default";
|
|
293
|
+
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default" | "forward";
|
|
275
294
|
|
|
276
295
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
277
|
-
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);
|
|
278
297
|
|
|
279
298
|
/** The record of the demand of one attribute of a node (TREE.RUNGS). */
|
|
280
299
|
export interface AttributeTrace {
|
|
@@ -1000,6 +1019,106 @@ function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
|
|
|
1000
1019
|
return answer.result;
|
|
1001
1020
|
}
|
|
1002
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
|
+
|
|
1003
1122
|
/**
|
|
1004
1123
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
1005
1124
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -1065,7 +1184,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1065
1184
|
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
1066
1185
|
if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
|
|
1067
1186
|
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
1068
|
-
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);
|
|
1069
1189
|
};
|
|
1070
1190
|
|
|
1071
1191
|
/**
|
|
@@ -1162,7 +1282,9 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1162
1282
|
trace(key) {
|
|
1163
1283
|
const cell = cells.get(key);
|
|
1164
1284
|
if (cell === undefined || cell.busy) return undefined;
|
|
1165
|
-
|
|
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);
|
|
1166
1288
|
return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
|
|
1167
1289
|
},
|
|
1168
1290
|
json(path) {
|