@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/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 { type AttributeDeclaration, type CircularRule, type TreeContract, atArgsOf, atRuleOf, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
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 undefined;
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
- const rungs = cell.by === undefined ? ATTRIBUTE_RUNGS : ATTRIBUTE_RUNGS.slice(0, ATTRIBUTE_RUNGS.indexOf(cell.by) + 1);
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.");