@mark1russell7/coeval 0.22.0 → 0.24.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 +24 -5
- package/build/index.d.ts +442 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5848 -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 +663 -71
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/effects.ts +1 -1
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +188 -24
- package/src/formula.ts +60 -17
- package/src/index.ts +15 -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 +122 -2
- package/src/schedule.ts +286 -45
- package/src/static.ts +59 -16
- package/src/stdlib.ts +188 -6
- package/src/syntax.ts +637 -48
- package/src/types.ts +2 -1
- package/src/world.ts +31 -4
package/src/stdlib.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type Json, type Key, docEquals, field, isRecord } from "./doc.ts";
|
|
1
|
+
import { type Json, type Key, docEquals, field, isKey, isRecord } from "./doc.ts";
|
|
2
2
|
import {
|
|
3
3
|
type EvalInput,
|
|
4
4
|
type Evaluator,
|
|
5
|
+
type Clause,
|
|
5
6
|
type InputWithRuntime,
|
|
6
7
|
type Machine,
|
|
7
8
|
type Plan,
|
|
@@ -18,7 +19,19 @@ import {
|
|
|
18
19
|
import { Failure } from "./errors.ts";
|
|
19
20
|
import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
|
|
20
21
|
import type { World } from "./ladder.ts";
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
type AttributeDeclaration,
|
|
24
|
+
type CircularRule,
|
|
25
|
+
type TreeContract,
|
|
26
|
+
GENERATION_LIMIT,
|
|
27
|
+
atArgsOf,
|
|
28
|
+
atRuleOf,
|
|
29
|
+
circularOf,
|
|
30
|
+
collectOf,
|
|
31
|
+
documentKey,
|
|
32
|
+
equationType,
|
|
33
|
+
lookupKind,
|
|
34
|
+
} from "./world.ts";
|
|
22
35
|
import { STD_DOMAINS } from "./types.ts";
|
|
23
36
|
import type { SyntaxProfile } from "./syntax.ts";
|
|
24
37
|
|
|
@@ -79,6 +92,11 @@ export const STD_WORLD: World = {
|
|
|
79
92
|
pure: true,
|
|
80
93
|
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
94
|
},
|
|
95
|
+
quote: {
|
|
96
|
+
verbs: { eval: "data.quote", view: "view.value" },
|
|
97
|
+
pure: true,
|
|
98
|
+
summary: "A document as data. Its value is the result, and nothing in it evaluates. A higher-order attribute uses it (TREE.HIGHER).",
|
|
99
|
+
},
|
|
82
100
|
},
|
|
83
101
|
},
|
|
84
102
|
panel: {
|
|
@@ -127,6 +145,27 @@ export const STD_WORLD: World = {
|
|
|
127
145
|
lazy: [{ after: 1 }],
|
|
128
146
|
summary: "It evaluates its part without capabilities, thus an effect in it fails.",
|
|
129
147
|
},
|
|
148
|
+
handle: {
|
|
149
|
+
verbs: { eval: "control.handle", view: "view.op" },
|
|
150
|
+
parts: {
|
|
151
|
+
shape: "record",
|
|
152
|
+
fields: {
|
|
153
|
+
with: { shape: "list", item: { shape: "record", fields: { op: { shape: "none" }, do: { shape: "term" }, abort: { shape: "term" } }, required: ["op"], closed: true } },
|
|
154
|
+
return: { shape: "term" },
|
|
155
|
+
body: { shape: "term" },
|
|
156
|
+
},
|
|
157
|
+
required: ["with", "body"],
|
|
158
|
+
closed: true,
|
|
159
|
+
},
|
|
160
|
+
scope: [
|
|
161
|
+
{ names: { fixed: ["payload"] }, in: [["with", "*", "do"], ["with", "*", "abort"]] },
|
|
162
|
+
{ names: { fixed: ["result"] }, in: [["return"]] },
|
|
163
|
+
],
|
|
164
|
+
caps: [{ rule: "grant", from: ["with", "*", "op"], in: [["body"]] }],
|
|
165
|
+
// A clause runs at an effect of the body, and the clause return runs only after a body without a failure.
|
|
166
|
+
lazy: [{ in: [["with", "*", "do"], ["with", "*", "abort"], ["return"]] }],
|
|
167
|
+
summary: "It answers the effects of its body with its clauses, in place, thus a mock, a retry or a log is a document.",
|
|
168
|
+
},
|
|
130
169
|
log: {
|
|
131
170
|
verbs: { eval: "effect.log", view: "view.op" },
|
|
132
171
|
parts: { shape: "list", of: "text" },
|
|
@@ -183,6 +222,7 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
183
222
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
184
223
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
185
224
|
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
225
|
+
{ name: "tree.quote", kind: "tree.quote", args: "literal" },
|
|
186
226
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
187
227
|
{ name: "add", kind: "add" },
|
|
188
228
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -224,6 +264,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
|
|
|
224
264
|
"data.number": /* @__PURE__ */ dataOf("number"),
|
|
225
265
|
"data.text": /* @__PURE__ */ dataOf("string"),
|
|
226
266
|
"data.bool": /* @__PURE__ */ dataOf("boolean"),
|
|
267
|
+
"data.quote": ({ data }) => ok(data ?? null),
|
|
227
268
|
"math.add": ({ parts }) => {
|
|
228
269
|
const all = allValues(parts.map((part) => part.evaluate()));
|
|
229
270
|
if ("error" in all) return all.error;
|
|
@@ -271,10 +312,10 @@ interface TreeBuild {
|
|
|
271
312
|
}
|
|
272
313
|
|
|
273
314
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
274
|
-
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default";
|
|
315
|
+
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default" | "forward";
|
|
275
316
|
|
|
276
317
|
/** 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);
|
|
318
|
+
export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default", "forward"] as const);
|
|
278
319
|
|
|
279
320
|
/** The record of the demand of one attribute of a node (TREE.RUNGS). */
|
|
280
321
|
export interface AttributeTrace {
|
|
@@ -1000,6 +1041,106 @@ function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
|
|
|
1000
1041
|
return answer.result;
|
|
1001
1042
|
}
|
|
1002
1043
|
|
|
1044
|
+
/** The most documents and the most characters of the keys in the table of generated documents. */
|
|
1045
|
+
const INTERNED_DOCUMENTS = 1000;
|
|
1046
|
+
const INTERNED_CHARACTERS = 16_000_000;
|
|
1047
|
+
|
|
1048
|
+
/** The first document of each structural key (TREE.HIGHER-PLAN, D126). */
|
|
1049
|
+
const internedDocuments = /* @__PURE__ */ new Map<string, Json>();
|
|
1050
|
+
|
|
1051
|
+
/** The kept document of each document object. An object gets its key one time, as PLAN.CACHE says. */
|
|
1052
|
+
const internedObjects = /* @__PURE__ */ new WeakMap<object, Json>();
|
|
1053
|
+
|
|
1054
|
+
/** The characters of the keys of `internedDocuments`. */
|
|
1055
|
+
let internedCharacters = 0;
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* This function gives the one object of a document (TREE.HIGHER-PLAN, D126). `equationPlan` caches by object, thus a
|
|
1059
|
+
* new object with a known key compiles nothing. The table has a limit, and a full table starts again. A document that
|
|
1060
|
+
* left the table compiles again, and that costs only time.
|
|
1061
|
+
*
|
|
1062
|
+
* @param doc - The value of an equation.
|
|
1063
|
+
* @returns The document, or `undefined` for a value that is not JSON data.
|
|
1064
|
+
*/
|
|
1065
|
+
function internedDocument(doc: unknown): Json | undefined {
|
|
1066
|
+
const object = typeof doc === "object" && doc !== null;
|
|
1067
|
+
const known = object ? internedObjects.get(doc) : undefined;
|
|
1068
|
+
if (known !== undefined) return known;
|
|
1069
|
+
const key = documentKey(doc);
|
|
1070
|
+
if (key === undefined) return undefined;
|
|
1071
|
+
if (!object) return doc as Json;
|
|
1072
|
+
let kept = internedDocuments.get(key);
|
|
1073
|
+
if (kept === undefined) {
|
|
1074
|
+
if (internedDocuments.size >= INTERNED_DOCUMENTS || internedCharacters + key.length > INTERNED_CHARACTERS) {
|
|
1075
|
+
internedDocuments.clear();
|
|
1076
|
+
internedCharacters = 0;
|
|
1077
|
+
}
|
|
1078
|
+
kept = doc as Json;
|
|
1079
|
+
internedDocuments.set(key, kept);
|
|
1080
|
+
internedCharacters += key.length;
|
|
1081
|
+
}
|
|
1082
|
+
internedObjects.set(doc, kept);
|
|
1083
|
+
return kept;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/** The depth of each root of a generated document: the generated documents from the root of the tree to it (D126). */
|
|
1087
|
+
const generations = /* @__PURE__ */ new WeakMap<NodeState, number>();
|
|
1088
|
+
|
|
1089
|
+
/** This function gives the depth of the nearest root of a generated document at or above a node of a tree, or 0. */
|
|
1090
|
+
function generationOf(node: HostThing, build: TreeBuild): number {
|
|
1091
|
+
let thing: Thing | undefined = node;
|
|
1092
|
+
for (let at = stateOf(thing); at?.build === build; at = stateOf(thing)) {
|
|
1093
|
+
const depth = generations.get(at);
|
|
1094
|
+
if (depth !== undefined) return depth;
|
|
1095
|
+
thing = (thing as HostThing).deref("parent");
|
|
1096
|
+
}
|
|
1097
|
+
return 0;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* This function gives a higher-order attribute of a node (TREE.HIGHER, D126). The equation gives a document as data.
|
|
1102
|
+
* The document evaluates as an equation, with the node as `self`. Thus its root is a node under the node: it finds the
|
|
1103
|
+
* open tree, and it has the node as its parent. The function is outside the code of `read`, as a fold is.
|
|
1104
|
+
*/
|
|
1105
|
+
function generated(n: NodeState, node: HostThing, key: string, term: Json): Result {
|
|
1106
|
+
const value = n.equation(term, NO_NAMES);
|
|
1107
|
+
if (!value.ok) return value;
|
|
1108
|
+
const doc = internedDocument(value.value);
|
|
1109
|
+
const text = attributeText(key, n.type, n.place);
|
|
1110
|
+
if (doc === undefined) return fail("#VALUE!", `The equation of the higher-order attribute ${text} gives a value that is not JSON data.`);
|
|
1111
|
+
const depth = generationOf(node, n.build) + 1;
|
|
1112
|
+
if (depth > GENERATION_LIMIT) return fail("#DEPTH!", `The higher-order attribute ${text} is under more than ${GENERATION_LIMIT} generated documents.`);
|
|
1113
|
+
const root = n.equation(doc, NO_NAMES);
|
|
1114
|
+
if (!root.ok) return root;
|
|
1115
|
+
const made = stateOf(root.value);
|
|
1116
|
+
if (made === undefined) return fail("#VALUE!", `The document of the higher-order attribute ${text} gives no node of the tree.`);
|
|
1117
|
+
generations.set(made, depth);
|
|
1118
|
+
return root;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* This function gives the last rung of the ladder (TREE.FORWARD, D126). The attribute `forwards` gives a node, and that
|
|
1123
|
+
* node answers the attribute through its own ladder. The attribute `forwards` itself gets no such answer.
|
|
1124
|
+
*/
|
|
1125
|
+
function forwarded(n: NodeState, node: HostThing, key: string, name: unknown): { readonly result: Result; readonly by: AttributeRung } | undefined {
|
|
1126
|
+
if (key === name) return undefined;
|
|
1127
|
+
const text = attributeText(key, n.type, n.place);
|
|
1128
|
+
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" };
|
|
1129
|
+
const target = n.read(name);
|
|
1130
|
+
if (target === undefined) return undefined;
|
|
1131
|
+
if (!target.ok) return { result: target, by: "forward" };
|
|
1132
|
+
const to = stateOf(target.value);
|
|
1133
|
+
if (to === undefined) return { result: fail("#VALUE!", `The attribute ${name} that ${text} forwards to gives no node of a tree.`), by: "forward" };
|
|
1134
|
+
// An equation can give a node without a declaration higher, thus the rung counts each node that it reaches.
|
|
1135
|
+
if (!generations.has(to)) {
|
|
1136
|
+
const depth = generationOf(node, n.build) + 1;
|
|
1137
|
+
if (depth > GENERATION_LIMIT) return { result: fail("#DEPTH!", `The attribute ${text} forwards through more than ${GENERATION_LIMIT} generated documents.`), by: "forward" };
|
|
1138
|
+
generations.set(to, depth);
|
|
1139
|
+
}
|
|
1140
|
+
const result = to.read(key);
|
|
1141
|
+
return result === undefined ? undefined : { result, by: "forward" };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1003
1144
|
/**
|
|
1004
1145
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
1005
1146
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -1065,7 +1206,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1065
1206
|
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
1066
1207
|
if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
|
|
1067
1208
|
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
1068
|
-
return
|
|
1209
|
+
if (declared?.higher !== undefined) return { result: generated(state, node, key, declared.higher), by: "default" };
|
|
1210
|
+
return tree?.forwards === undefined ? undefined : forwarded(state, node, key, tree.forwards);
|
|
1069
1211
|
};
|
|
1070
1212
|
|
|
1071
1213
|
/**
|
|
@@ -1162,7 +1304,9 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1162
1304
|
trace(key) {
|
|
1163
1305
|
const cell = cells.get(key);
|
|
1164
1306
|
if (cell === undefined || cell.busy) return undefined;
|
|
1165
|
-
|
|
1307
|
+
// The last rung is on the way only for a kind with the field forwards.
|
|
1308
|
+
const end = cell.by === undefined ? ATTRIBUTE_RUNGS.length - (tree?.forwards === undefined ? 1 : 0) : ATTRIBUTE_RUNGS.indexOf(cell.by) + 1;
|
|
1309
|
+
const rungs = ATTRIBUTE_RUNGS.slice(0, end);
|
|
1166
1310
|
return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
|
|
1167
1311
|
},
|
|
1168
1312
|
json(path) {
|
|
@@ -1371,6 +1515,18 @@ function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { read
|
|
|
1371
1515
|
return { values };
|
|
1372
1516
|
}
|
|
1373
1517
|
|
|
1518
|
+
/** The value of a `handle` document, with a part in the place of each part. */
|
|
1519
|
+
interface HandleValue {
|
|
1520
|
+
readonly with: readonly { readonly op: Cap; readonly do?: RunPart; readonly abort?: RunPart }[];
|
|
1521
|
+
readonly return?: RunPart;
|
|
1522
|
+
readonly body?: RunPart;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/** This function tells if a value names a kind of effects: a text, or a list of keys. */
|
|
1526
|
+
function isOp(value: unknown): value is Cap {
|
|
1527
|
+
return typeof value === "string" || (Array.isArray(value) && value.every(isKey));
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1374
1530
|
/**
|
|
1375
1531
|
* The runners of the standard world, by name: the kinds that can hold effects. Each one evaluates its parts in
|
|
1376
1532
|
* order. In a run, an effect ends before the next step starts.
|
|
@@ -1394,6 +1550,32 @@ export const STD_RUNNERS: Readonly<Record<string, Runner>> = {
|
|
|
1394
1550
|
if (only === undefined || parts.length > 1) return fail("#VALUE!", "pure needs exactly one part.");
|
|
1395
1551
|
return yield* only.evaluate();
|
|
1396
1552
|
},
|
|
1553
|
+
// A clause gets the context of the handle document, thus it has no more authority than its parent (HANDLE.CLAUSE).
|
|
1554
|
+
"control.handle": function* ({ value, context }) {
|
|
1555
|
+
const { with: list, return: after, body } = value as HandleValue;
|
|
1556
|
+
if (body?.handled === undefined) return fail("#VALUE!", "handle needs a body.");
|
|
1557
|
+
let stopped: Result | undefined;
|
|
1558
|
+
const clauses: Clause[] = [];
|
|
1559
|
+
for (const item of list) {
|
|
1560
|
+
const part = item.do ?? item.abort;
|
|
1561
|
+
if (!isOp(item.op)) return fail("#VALUE!", "The op of a clause of handle must be a text or a list of keys.");
|
|
1562
|
+
if (part === undefined || (item.do !== undefined && item.abort !== undefined)) return fail("#VALUE!", "Each clause of handle needs exactly one of do and abort.");
|
|
1563
|
+
const stops = item.do === undefined;
|
|
1564
|
+
clauses.push({
|
|
1565
|
+
op: item.op,
|
|
1566
|
+
answer: function* (payload) {
|
|
1567
|
+
const result = yield* part.evaluate({ ...context, names: { ...context.names, payload } });
|
|
1568
|
+
if (!stops) return result;
|
|
1569
|
+
stopped ??= result;
|
|
1570
|
+
return fail("#CALC!", "A clause abort of handle stopped the body.");
|
|
1571
|
+
},
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
const result = yield* body.handled(clauses);
|
|
1575
|
+
if (stopped !== undefined) return stopped;
|
|
1576
|
+
if (!result.ok || after === undefined) return result;
|
|
1577
|
+
return yield* after.evaluate({ ...context, names: { ...context.names, result: result.value } });
|
|
1578
|
+
},
|
|
1397
1579
|
"control.if": function* ({ parts }) {
|
|
1398
1580
|
const test = parts.find((part) => part.key === "test");
|
|
1399
1581
|
if (test === undefined) return fail("#VALUE!", "if needs a test.");
|