@mark1russell7/coeval 0.21.0 → 0.22.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/build/index.d.ts +62 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +421 -35
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/formula.ts +22 -5
- package/src/index.ts +6 -0
- package/src/instances.ts +45 -5
- package/src/reference.ts +68 -8
- package/src/static.ts +70 -9
- package/src/stdlib.ts +103 -2
- package/src/world.ts +85 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/coeval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.22.0",
|
|
5
5
|
"description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./build/index.js",
|
package/src/formula.ts
CHANGED
|
@@ -115,6 +115,12 @@ export function isClosure(value: unknown): value is Closure {
|
|
|
115
115
|
return typeof value === "function" && (value as Partial<Closure>)[CLOSURE] !== undefined;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The runtime of an evaluation with the clock of `call` (FORMULA.CALL-DEPTH, D125): the count of the applications of
|
|
120
|
+
* closures that run at this time. The first application adds the field, thus an evaluation without one has no field.
|
|
121
|
+
*/
|
|
122
|
+
type Clocked = Runtime & { applying?: number };
|
|
123
|
+
|
|
118
124
|
/**
|
|
119
125
|
* The standard extensions: the values that JSON cannot write. `ext.domain` reads a member of a domain through the
|
|
120
126
|
* codec of the domain (EXT.DOMAIN, D40).
|
|
@@ -925,15 +931,26 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
925
931
|
}
|
|
926
932
|
return known;
|
|
927
933
|
};
|
|
934
|
+
// The clock of call permits a quarter of the depth limit, because an application nests its body (D125).
|
|
935
|
+
const limit = Math.ceil((machine.maxDepth ?? 1000) / 4);
|
|
928
936
|
return (ctx, rt) => {
|
|
929
937
|
// A closure keeps the context for each later use, thus the list of reads gets each path that it can read (READS.CLOSURE).
|
|
930
938
|
rt.keepsContext?.(ctx, reads);
|
|
939
|
+
const clock = rt as Clocked;
|
|
931
940
|
const closure = (...args: readonly unknown[]): unknown => {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
941
|
+
// Each application counts one level on the runtime of its evaluation, and the count goes down after it (FORMULA.CALL-DEPTH).
|
|
942
|
+
const level = (clock.applying ?? 0) + 1;
|
|
943
|
+
if (level > limit) throw new Failure(fail("#DEPTH!", `The applications of functions nest deeper than ${limit} levels.`).error);
|
|
944
|
+
clock.applying = level;
|
|
945
|
+
try {
|
|
946
|
+
const scope: Record<string, unknown> = { ...ctx.names };
|
|
947
|
+
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
948
|
+
const result = body.code({ ...ctx, names: scope }, rt);
|
|
949
|
+
if (!result.ok) throw new Failure(result.error);
|
|
950
|
+
return result.value;
|
|
951
|
+
} finally {
|
|
952
|
+
clock.applying = level - 1;
|
|
953
|
+
}
|
|
937
954
|
};
|
|
938
955
|
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
939
956
|
return ok(closure);
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,9 @@ export {
|
|
|
36
36
|
} from "./errors.ts";
|
|
37
37
|
export {
|
|
38
38
|
type Alias,
|
|
39
|
+
type AtArgs,
|
|
40
|
+
type AtEquation,
|
|
41
|
+
type AtRule,
|
|
39
42
|
type AttributeDeclaration,
|
|
40
43
|
type CapsRule,
|
|
41
44
|
type CircularDeclaration,
|
|
@@ -57,6 +60,9 @@ export {
|
|
|
57
60
|
type World,
|
|
58
61
|
type WorldEntry,
|
|
59
62
|
CIRCULAR_LIMIT,
|
|
63
|
+
argsKey,
|
|
64
|
+
atArgsOf,
|
|
65
|
+
atRuleOf,
|
|
60
66
|
circularOf,
|
|
61
67
|
collectOf,
|
|
62
68
|
effectsOfVerb,
|
package/src/instances.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Json, type Key, field, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type CollectRule, type Kind, collectOf, equationType } from "./world.ts";
|
|
2
|
+
import { type CollectRule, type Kind, argsKey, collectOf, equationType } from "./world.ts";
|
|
3
3
|
import { ask } from "./ladder.ts";
|
|
4
4
|
import { isRef, walkerOf } from "./deref.ts";
|
|
5
5
|
import type { Machine } from "./evaluate.ts";
|
|
@@ -7,13 +7,16 @@ import { type Above, codeFactsOf, partLazy } from "./facts.ts";
|
|
|
7
7
|
import { type NodeGraph, type GraphEdge, cycles } from "./graph.ts";
|
|
8
8
|
import { STD_WALKERS } from "./stdlib.ts";
|
|
9
9
|
import {
|
|
10
|
+
type AtStep,
|
|
10
11
|
type CheckOptions,
|
|
11
12
|
type DocVisit,
|
|
12
13
|
type Issue,
|
|
13
14
|
type SeenNode,
|
|
14
15
|
REF_CODE,
|
|
15
16
|
TREE_NODE,
|
|
17
|
+
atStepOf,
|
|
16
18
|
downNames,
|
|
19
|
+
isAtStep,
|
|
17
20
|
isStructural,
|
|
18
21
|
keepsSelf,
|
|
19
22
|
selvesOf,
|
|
@@ -39,6 +42,8 @@ export interface AttributeVertex {
|
|
|
39
42
|
readonly type: string;
|
|
40
43
|
/** `true` when the kind of the node declares the attribute `circular` (TREE.CIRCULAR-CHECK). Else the field is absent. */
|
|
41
44
|
readonly circular?: true;
|
|
45
|
+
/** The arguments of an instance of an attribute with parameters (TREE.AT-CHECK). Else the field is absent. */
|
|
46
|
+
readonly args?: Readonly<Record<string, Json>>;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/** One read of an attribute by the code of another attribute (D107). */
|
|
@@ -220,6 +225,20 @@ function vertexOf(b: Builder, node: NodeInfo, attribute: string): string {
|
|
|
220
225
|
return id;
|
|
221
226
|
}
|
|
222
227
|
|
|
228
|
+
/**
|
|
229
|
+
* This function gives the vertex of an instance of an attribute with parameters (TREE.AT-CHECK, D124). Its name is the
|
|
230
|
+
* name of a plain vertex with the key of the arguments after the name of the attribute.
|
|
231
|
+
*/
|
|
232
|
+
function atVertexOf(b: Builder, node: NodeInfo, at: AtStep & { readonly kind: "exact" }): string {
|
|
233
|
+
const id = `${attributeVertexId(node.visit.place, at.attr).slice(0, -1)},${at.key}]`;
|
|
234
|
+
if (!b.vertices.has(id)) {
|
|
235
|
+
b.vertices.set(id, { id, place: node.visit.place, attribute: at.attr, type: node.text, args: at.names });
|
|
236
|
+
b.nodes.set(id, node);
|
|
237
|
+
b.work.push(id);
|
|
238
|
+
}
|
|
239
|
+
return id;
|
|
240
|
+
}
|
|
241
|
+
|
|
223
242
|
function edge(b: Builder, from: string | undefined, r: Reader, lazy = false): void {
|
|
224
243
|
b.edges.push({ from, to: r.to, lazy: r.lazy || lazy, place: r.place });
|
|
225
244
|
b.ats.push(r.at);
|
|
@@ -310,6 +329,15 @@ function follow(b: Builder, start: Value, path: readonly Json[], from: number, r
|
|
|
310
329
|
}
|
|
311
330
|
continue;
|
|
312
331
|
}
|
|
332
|
+
// A step tree.at with literal arguments reads one instance (TREE.AT-CHECK). A step that fails gives no read.
|
|
333
|
+
if (isAtStep(b.machine, step)) {
|
|
334
|
+
const at = atStepOf(node.kind, node.text, step.value);
|
|
335
|
+
if (at.kind === "unknown") edge(b, undefined, r);
|
|
336
|
+
if (at.kind !== "exact") return;
|
|
337
|
+
edge(b, atVertexOf(b, node, at), r);
|
|
338
|
+
here = "unknown";
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
313
341
|
// The standard walker nearest depends only on the structure (R3 P5). Each other walker can read each attribute.
|
|
314
342
|
if (walkerOf(b.machine, step) !== STD_WALKERS["walk.nearest"]) {
|
|
315
343
|
edge(b, undefined, r);
|
|
@@ -361,10 +389,11 @@ function termRefs(b: Builder, kind: Kind, label: string, term: Json): readonly T
|
|
|
361
389
|
return out;
|
|
362
390
|
}
|
|
363
391
|
|
|
364
|
-
/** The names of the equation of a rung, before the run. */
|
|
392
|
+
/** The names of the equation of a rung, before the run. The parameters of an attribute hold JSON data (TREE.AT). */
|
|
365
393
|
interface TermNames {
|
|
366
394
|
readonly child?: NodeInfo;
|
|
367
395
|
readonly prev?: Value;
|
|
396
|
+
readonly params?: readonly string[];
|
|
368
397
|
}
|
|
369
398
|
|
|
370
399
|
/** This function adds the reads of an equation of a rung: self is a node, and the names of the rung have their values. */
|
|
@@ -386,7 +415,10 @@ function termReads(b: Builder, refs: readonly TermRef[], self: NodeInfo, names:
|
|
|
386
415
|
// A name that a kind inside the equation binds holds a value of the run. The others are the names of the rung.
|
|
387
416
|
let start: Value;
|
|
388
417
|
if (ref.bound.has(name)) start = "unknown";
|
|
389
|
-
else if (names?.
|
|
418
|
+
else if (names?.params !== undefined) {
|
|
419
|
+
if (!names.params.includes(name)) continue;
|
|
420
|
+
start = "data";
|
|
421
|
+
} else if (names?.child !== undefined && name === "child") start = { node: names.child };
|
|
390
422
|
else if (names?.prev !== undefined && name === "prev") start = names.prev;
|
|
391
423
|
else if (name === "index") start = "data";
|
|
392
424
|
else if (name === "acc" || name === "item") start = "unknown";
|
|
@@ -412,9 +444,16 @@ function prevOf(parent: NodeInfo, child: NodeInfo): Value {
|
|
|
412
444
|
*/
|
|
413
445
|
function rungReads(b: Builder, id: string): void {
|
|
414
446
|
const node = b.nodes.get(id) as NodeInfo;
|
|
415
|
-
const
|
|
447
|
+
const vertex = b.vertices.get(id) as AttributeVertex;
|
|
448
|
+
const key = vertex.attribute;
|
|
416
449
|
const tree = node.kind.tree;
|
|
417
450
|
const r: Reader = { to: id, lazy: false, place: node.visit.place, at: node.visit.at };
|
|
451
|
+
// An instance of an attribute with parameters has one rung: its equation in tree.at (TREE.AT).
|
|
452
|
+
if (vertex.args !== undefined) {
|
|
453
|
+
const at = atStepOf(node.kind, node.text, { attr: key, args: vertex.args });
|
|
454
|
+
if (at.kind === "exact") termReads(b, termRefs(b, node.kind, `tree.at.${key}`, at.equation), node, { params: at.params }, r);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
418
457
|
const equation = hasOwnEntry(tree?.equations, key) ? tree?.equations?.[key] : undefined;
|
|
419
458
|
if (equation !== undefined) {
|
|
420
459
|
termReads(b, termRefs(b, node.kind, `tree.equations.${key}`, equation), node, undefined, r);
|
|
@@ -634,7 +673,8 @@ function allCircular(b: Builder, ids: readonly string[]): boolean {
|
|
|
634
673
|
}
|
|
635
674
|
|
|
636
675
|
function vertexText(vertex: AttributeVertex): string {
|
|
637
|
-
|
|
676
|
+
const name = vertex.args === undefined ? vertex.attribute : `${vertex.attribute} with ${String(argsKey(vertex.args))}`;
|
|
677
|
+
return `${name} of ${vertex.type} at [${vertex.place.join(", ")}]`;
|
|
638
678
|
}
|
|
639
679
|
|
|
640
680
|
/**
|
package/src/reference.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, docEquals, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { ask } from "./ladder.ts";
|
|
3
|
-
import { type AttributeDeclaration, type CapsRule, type CircularRule, type Kind, type TreeContract, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
|
|
3
|
+
import { type AttributeDeclaration, type CapsRule, type CircularRule, type Kind, type TreeContract, atArgsOf, atRuleOf, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
|
|
4
4
|
import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, ROOTS, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
6
|
-
import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
|
|
6
|
+
import { type AttributeRung, AT_READERS, STD_EVALUATORS } from "./stdlib.ts";
|
|
7
7
|
import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
|
|
8
8
|
import {
|
|
9
9
|
type AllOptions,
|
|
@@ -113,6 +113,8 @@ interface Env {
|
|
|
113
113
|
scope: Scope;
|
|
114
114
|
/** The count of the drives without a wait that are active. In a run, an effect under one does not reach the host (RUN.HELD). */
|
|
115
115
|
held: number;
|
|
116
|
+
/** The count of the applications of closures that run at this time: the clock of `call` (FORMULA.CALL-DEPTH, D125). */
|
|
117
|
+
applying: number;
|
|
116
118
|
/** The record of the reads. A nested evaluation for an equation of a tree keeps the record of its tree. */
|
|
117
119
|
readonly reading: Reading | undefined;
|
|
118
120
|
}
|
|
@@ -674,6 +676,7 @@ function envOf(machine: Machine, options: ReferenceOptions, running: boolean, ro
|
|
|
674
676
|
verb: options.verb ?? "eval",
|
|
675
677
|
scope: { prefix: "", counts: new Map(), alls: 0 },
|
|
676
678
|
held: 0,
|
|
679
|
+
applying: 0,
|
|
677
680
|
reading: outer ?? (options.reads === undefined ? undefined : { list: options.reads, root }),
|
|
678
681
|
};
|
|
679
682
|
}
|
|
@@ -1069,6 +1072,53 @@ function attributeOf(node: RefNode, name: string): Result | undefined {
|
|
|
1069
1072
|
return result;
|
|
1070
1073
|
}
|
|
1071
1074
|
|
|
1075
|
+
/** TREE.AT: one instance of an attribute with parameters of a node. Its result is `undefined` while it evaluates. */
|
|
1076
|
+
interface RefInstance {
|
|
1077
|
+
readonly attr: string;
|
|
1078
|
+
readonly names: unknown;
|
|
1079
|
+
result: Result | undefined;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/** TREE.AT: the instances of each node of the reference. Two arguments are the same instance by DOC.EQUALS. */
|
|
1083
|
+
const refInstances = /* @__PURE__ */ new WeakMap<RefNode, RefInstance[]>();
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* TREE.AT: the instance of an attribute with parameters, or `undefined` when the kind has no entry in `tree.at`. The
|
|
1087
|
+
* equation sees the arguments as names. A demand of an instance in evaluation gives `#CYCLE!`, also in a fixed point.
|
|
1088
|
+
*/
|
|
1089
|
+
function instanceOf(node: RefNode, attr: string, args: unknown): Result | undefined {
|
|
1090
|
+
const declared = termIn(node.contract?.at, attr);
|
|
1091
|
+
if (declared === undefined) return undefined;
|
|
1092
|
+
const rule = atRuleOf(declared);
|
|
1093
|
+
if (!rule.ok) return fail("#VALUE!", `The attribute ${nameOfAttribute(node, attr)} has a bad entry tree.at. ${rule.reason}`);
|
|
1094
|
+
const given = atArgsOf(rule.params, args);
|
|
1095
|
+
if (!given.ok) return fail("#VALUE!", `The step tree.at of ${nameOfAttribute(node, attr)} has bad arguments. ${given.reason}`);
|
|
1096
|
+
const name = `${attr} with ${given.key}`;
|
|
1097
|
+
const list = refInstances.get(node) ?? [];
|
|
1098
|
+
refInstances.set(node, list);
|
|
1099
|
+
const found = list.find((item) => item.attr === attr && docEquals(item.names, given.names));
|
|
1100
|
+
if (found?.result !== undefined) return found.result;
|
|
1101
|
+
if (found !== undefined) {
|
|
1102
|
+
const from = inEvaluation.findIndex((entry) => entry.node === node && entry.name === name);
|
|
1103
|
+
const steps = inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name));
|
|
1104
|
+
return fail("#CYCLE!", `The attributes form a cycle: ${[...steps, nameOfAttribute(node, name)].join(" -> ")}.`);
|
|
1105
|
+
}
|
|
1106
|
+
const item: RefInstance = { attr, names: given.names, result: undefined };
|
|
1107
|
+
list.push(item);
|
|
1108
|
+
const entry = { node, name, circular: false, tentative: false };
|
|
1109
|
+
inEvaluation.push(entry);
|
|
1110
|
+
let result: Result | undefined;
|
|
1111
|
+
try {
|
|
1112
|
+
result = equationOf(node, rule.equation, given.names);
|
|
1113
|
+
} finally {
|
|
1114
|
+
inEvaluation.pop();
|
|
1115
|
+
// A result that read an approximation is not kept (TREE.CIRCULAR).
|
|
1116
|
+
if (result === undefined || entry.tentative) list.splice(list.indexOf(item), 1);
|
|
1117
|
+
else item.result = result;
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1072
1122
|
/** TREE.RESULT: the JSON of a node. `open` holds the nodes and the containers whose JSON is in progress. */
|
|
1073
1123
|
function jsonOfNode(node: RefNode, self: HostThing, open: readonly object[]): Result {
|
|
1074
1124
|
const record: Record<string, unknown> = {};
|
|
@@ -1157,6 +1207,7 @@ const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath
|
|
|
1157
1207
|
};
|
|
1158
1208
|
node.thing = self;
|
|
1159
1209
|
refNodes.set(self, node);
|
|
1210
|
+
AT_READERS.set(self, (attr, args) => instanceOf(node, attr, args));
|
|
1160
1211
|
if (nested) return ok(self);
|
|
1161
1212
|
try {
|
|
1162
1213
|
return jsonOfNode(node, self, []);
|
|
@@ -1285,13 +1336,22 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
|
|
|
1285
1336
|
}
|
|
1286
1337
|
const names = params as readonly string[];
|
|
1287
1338
|
const body = partNamed(parts, "body") as Part;
|
|
1339
|
+
// FORMULA.CALL-DEPTH: the evaluation that makes the closure counts its applications. A quarter of maxDepth is the limit.
|
|
1340
|
+
const clock = activeEnvs[activeEnvs.length - 1] ?? { applying: 0, maxDepth: 1000 };
|
|
1341
|
+
const limit = Math.ceil(clock.maxDepth / 4);
|
|
1288
1342
|
const closure = (...args: readonly unknown[]): unknown => {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1343
|
+
if (clock.applying >= limit) throw new Failure(fail("#DEPTH!", `The applications of functions nest deeper than ${limit} levels.`).error);
|
|
1344
|
+
clock.applying += 1;
|
|
1345
|
+
try {
|
|
1346
|
+
const scope: Record<string, unknown> = {};
|
|
1347
|
+
for (const key of Object.keys(context.names)) setField(scope, key, context.names[key]);
|
|
1348
|
+
names.forEach((name, index) => setField(scope, name, args[index]));
|
|
1349
|
+
const result = body.evaluate({ ...context, names: scope });
|
|
1350
|
+
if (!result.ok) throw new Failure(result.error);
|
|
1351
|
+
return result.value;
|
|
1352
|
+
} finally {
|
|
1353
|
+
clock.applying -= 1;
|
|
1354
|
+
}
|
|
1295
1355
|
};
|
|
1296
1356
|
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
1297
1357
|
return ok(closure);
|
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 {
|
|
@@ -698,6 +699,11 @@ export function equationsOf(kind: Kind): readonly Equation[] {
|
|
|
698
699
|
}
|
|
699
700
|
// A contribution is not the attribute of its node, thus an own part of its name does not hide it (D122).
|
|
700
701
|
for (const [name, term] of Object.entries(tree.contributes ?? {})) out.push({ label: `tree.contributes.${name}`, term, names: [], attribute: name, down: false, contributes: true });
|
|
702
|
+
// An equation of an attribute with parameters sees each parameter as a name (TREE.AT, D124).
|
|
703
|
+
for (const [name, declared] of Object.entries(tree.at ?? {})) {
|
|
704
|
+
const rule = atRuleOf(declared);
|
|
705
|
+
if (rule.ok) out.push({ label: `tree.at.${name}`, term: rule.equation, names: rule.params, attribute: name, down: false, at: true });
|
|
706
|
+
}
|
|
701
707
|
return out;
|
|
702
708
|
}
|
|
703
709
|
|
|
@@ -713,6 +719,8 @@ export interface Equation {
|
|
|
713
719
|
readonly down: boolean;
|
|
714
720
|
/** `true` for an equation of `contributes`: it gives a contribution to a collection (TREE.COLLECT). */
|
|
715
721
|
readonly contributes?: true;
|
|
722
|
+
/** `true` for an equation of `at`: it gives an attribute with parameters (TREE.AT). */
|
|
723
|
+
readonly at?: true;
|
|
716
724
|
}
|
|
717
725
|
|
|
718
726
|
/**
|
|
@@ -901,10 +909,10 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
901
909
|
state.needs.set(kind, needs);
|
|
902
910
|
for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
|
|
903
911
|
const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
|
|
904
|
-
for (const { label, term, names, attribute, down, contributes } of equationsOf(kind)) {
|
|
912
|
+
for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
|
|
905
913
|
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
906
914
|
const read = (name: string): void => {
|
|
907
|
-
needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true ? undefined : attribute });
|
|
915
|
+
needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true || at === true ? undefined : attribute });
|
|
908
916
|
};
|
|
909
917
|
for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
|
|
910
918
|
out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
|
|
@@ -917,12 +925,12 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
917
925
|
* This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
|
|
918
926
|
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
|
|
919
927
|
* 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).
|
|
928
|
+
* `collect` without its form gives `#VALUE!` too (TREE.COLLECT). So does an entry of `at` without its form (TREE.AT-CHECK).
|
|
921
929
|
*/
|
|
922
930
|
function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
923
931
|
const out: Issue[] = [];
|
|
924
932
|
const tree = kind.tree;
|
|
925
|
-
for (const table of ["equations", "down", "attributes", "contributes"] as const) {
|
|
933
|
+
for (const table of ["equations", "down", "attributes", "contributes", "at"] as const) {
|
|
926
934
|
for (const name of Object.keys(tree?.[table] ?? {})) {
|
|
927
935
|
const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
|
|
928
936
|
if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
|
|
@@ -935,6 +943,10 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
|
935
943
|
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
936
944
|
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
945
|
}
|
|
946
|
+
for (const [name, declared] of Object.entries(tree?.at ?? {})) {
|
|
947
|
+
const rule = atRuleOf(declared);
|
|
948
|
+
if (!rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad entry tree.at.${name}. ${rule.reason}` });
|
|
949
|
+
}
|
|
938
950
|
if (kind.side === "run") {
|
|
939
951
|
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
952
|
}
|
|
@@ -966,9 +978,14 @@ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVi
|
|
|
966
978
|
* This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
|
|
967
979
|
* nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
|
|
968
980
|
* 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.
|
|
981
|
+
* needs of its kind. A step `tree.at` with literal arguments gets the checks of TREE.AT-CHECK.
|
|
970
982
|
*/
|
|
971
|
-
function readIssue(
|
|
983
|
+
function readIssue(
|
|
984
|
+
machine: Machine,
|
|
985
|
+
self: SeenNode | undefined,
|
|
986
|
+
path: readonly Json[],
|
|
987
|
+
rootSelf: RootSelf | undefined,
|
|
988
|
+
): { readonly index: number; readonly reason: string; readonly code?: ErrorCode } | undefined {
|
|
972
989
|
let node = self;
|
|
973
990
|
let index = 1;
|
|
974
991
|
while (node !== undefined && index < path.length && path[index] === "parent") {
|
|
@@ -976,6 +993,10 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
976
993
|
index += 1;
|
|
977
994
|
}
|
|
978
995
|
const step = path[index];
|
|
996
|
+
if (node !== undefined && isAtStep(machine, step)) {
|
|
997
|
+
const at = atStepOf(node.kind, node.text, step.value);
|
|
998
|
+
return at.kind === "issue" ? { index, reason: at.reason, code: at.code } : undefined;
|
|
999
|
+
}
|
|
979
1000
|
if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
|
|
980
1001
|
const name = String(step);
|
|
981
1002
|
if (node.own === undefined) {
|
|
@@ -985,6 +1006,46 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
985
1006
|
return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
|
|
986
1007
|
}
|
|
987
1008
|
|
|
1009
|
+
/** This function tells if a step of a path is a step `tree.at` of the standard walker (TREE.AT, D124). */
|
|
1010
|
+
export function isAtStep(machine: Machine, step: unknown): step is Envelope {
|
|
1011
|
+
return isEnvelope(step) && walkerOf(machine, step) === STD_WALKERS["walk.tree.at"];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* 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
|
|
1016
|
+
* step with an issue fails at the step. Only the run knows a step with a document in its data, or a bad entry.
|
|
1017
|
+
*/
|
|
1018
|
+
export type AtStep =
|
|
1019
|
+
| { readonly kind: "exact"; readonly attr: string; readonly key: string; readonly equation: Json; readonly params: readonly string[]; readonly names: Readonly<Record<string, Json>> }
|
|
1020
|
+
| { readonly kind: "issue"; readonly code: ErrorCode; readonly reason: string }
|
|
1021
|
+
| { readonly kind: "unknown" };
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* This function examines the data of a step `tree.at` on a node of a known kind (TREE.AT-CHECK, D124). `check` and the
|
|
1025
|
+
* graph of attribute instances read the step with this one function.
|
|
1026
|
+
*/
|
|
1027
|
+
export function atStepOf(kind: Kind, kindText: string, data: unknown): AtStep {
|
|
1028
|
+
if (holdsDocument(data)) return { kind: "unknown" };
|
|
1029
|
+
const attr = isRecord(data) ? field(data, "attr") : undefined;
|
|
1030
|
+
if (typeof attr !== "string") return { kind: "issue", code: "#VALUE!", reason: "The step tree.at needs a text attr." };
|
|
1031
|
+
const table = kind.tree?.at;
|
|
1032
|
+
const declared = table !== undefined && Object.hasOwn(table, attr) ? table[attr] : undefined;
|
|
1033
|
+
if (declared === undefined) return { kind: "issue", code: "#N/A", reason: `No rung gives the attribute ${attr} with parameters of the kind ${kindText}.` };
|
|
1034
|
+
const rule = atRuleOf(declared);
|
|
1035
|
+
// The issue of a bad entry is an issue of the kind (contractIssues).
|
|
1036
|
+
if (!rule.ok) return { kind: "unknown" };
|
|
1037
|
+
const given = atArgsOf(rule.params, isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {});
|
|
1038
|
+
if (!given.ok) return { kind: "issue", code: "#VALUE!", reason: `The step tree.at of ${attr} of the kind ${kindText} has bad arguments. ${given.reason}` };
|
|
1039
|
+
return { kind: "exact", attr, key: given.key, equation: rule.equation, params: rule.params, names: given.names };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** 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. */
|
|
1043
|
+
function holdsDocument(value: unknown): boolean {
|
|
1044
|
+
if (isEnvelope(value)) return true;
|
|
1045
|
+
if (Array.isArray(value)) return value.some(holdsDocument);
|
|
1046
|
+
return isRecord(value) && Object.values(value).some(holdsDocument);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
988
1049
|
/** 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
1050
|
interface RootSelf {
|
|
990
1051
|
readonly node: SeenNode;
|
|
@@ -1149,7 +1210,7 @@ function checkAll(
|
|
|
1149
1210
|
// Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
|
|
1150
1211
|
if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
|
|
1151
1212
|
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) });
|
|
1213
|
+
if (issue !== undefined) push({ code: issue.code ?? "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
|
|
1153
1214
|
}
|
|
1154
1215
|
path.forEach((step, index) => {
|
|
1155
1216
|
if (index === 0 || isKey(step)) return;
|
package/src/stdlib.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { Failure } from "./errors.ts";
|
|
19
19
|
import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
|
|
20
20
|
import type { World } from "./ladder.ts";
|
|
21
|
-
import { type AttributeDeclaration, type CircularRule, type TreeContract, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
|
|
21
|
+
import { type AttributeDeclaration, type CircularRule, type TreeContract, atArgsOf, atRuleOf, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
|
|
22
22
|
import { STD_DOMAINS } from "./types.ts";
|
|
23
23
|
import type { SyntaxProfile } from "./syntax.ts";
|
|
24
24
|
|
|
@@ -74,6 +74,11 @@ export const STD_WORLD: World = {
|
|
|
74
74
|
pure: true,
|
|
75
75
|
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
76
|
},
|
|
77
|
+
at: {
|
|
78
|
+
verbs: { step: "walk.tree.at", view: "view.card" },
|
|
79
|
+
pure: true,
|
|
80
|
+
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
|
+
},
|
|
77
82
|
},
|
|
78
83
|
},
|
|
79
84
|
panel: {
|
|
@@ -177,6 +182,7 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
177
182
|
{ name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
|
|
178
183
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
179
184
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
185
|
+
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
180
186
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
181
187
|
{ name: "add", kind: "add" },
|
|
182
188
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -343,6 +349,10 @@ export interface AttributeAnswer {
|
|
|
343
349
|
* A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
|
|
344
350
|
* A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
|
|
345
351
|
*
|
|
352
|
+
* An attribute with parameters gets one cell for each key of its arguments (TREE.AT-CELL, D124). Its `place` is a
|
|
353
|
+
* second array with the same keys, one for each key of a node. Its `attribute` is the JSON text of the name and the
|
|
354
|
+
* arguments, for example `["height",{"width":200}]`.
|
|
355
|
+
*
|
|
346
356
|
* The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
|
|
347
357
|
* of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
|
|
348
358
|
* a cell that computes again uses the last one.
|
|
@@ -916,6 +926,80 @@ function collected(root: HostThing, key: string, declared: unknown): Result {
|
|
|
916
926
|
return acc;
|
|
917
927
|
}
|
|
918
928
|
|
|
929
|
+
/** A function from the name of an attribute and the arguments to the result, or `undefined` when no rung answers (D124). */
|
|
930
|
+
export type AtReader = (attr: string, args: unknown) => Result | undefined;
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* The host things that answer the step `tree.at` with their own function (D124). A node of the reference is in it, and
|
|
934
|
+
* its fields do not change. The walker reads the state of a node of the kit.
|
|
935
|
+
*/
|
|
936
|
+
export const AT_READERS: WeakMap<object, AtReader> = /* @__PURE__ */ new WeakMap();
|
|
937
|
+
|
|
938
|
+
/** The memo of the instances of the attributes with parameters of each node, without the cells of a host (TREE.AT). */
|
|
939
|
+
const atMemos = /* @__PURE__ */ new WeakMap<NodeState, Map<string, Cell>>();
|
|
940
|
+
|
|
941
|
+
/** The second key of each key of a node, for the cells of the attributes with parameters (TREE.AT-CELL). */
|
|
942
|
+
const atPlaces = /* @__PURE__ */ new WeakMap<readonly Key[], readonly Key[]>();
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* This function gives an instance of an attribute with parameters of a node (TREE.AT, D124). The equation in
|
|
946
|
+
* `tree.at` of the kind of the node evaluates with the arguments as names. The memo keeps one result for each key of
|
|
947
|
+
* the arguments, and a demand of an instance in evaluation gives `#CYCLE!`. The function is outside the code of
|
|
948
|
+
* `read`, thus a node without such attributes does no new work.
|
|
949
|
+
*/
|
|
950
|
+
function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
|
|
951
|
+
const declared: unknown = entryOf(n.tree?.at, attr);
|
|
952
|
+
if (declared === undefined) return undefined;
|
|
953
|
+
const rule = atRuleOf(declared);
|
|
954
|
+
if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(attr, n.type, n.place)} has a bad entry tree.at. ${rule.reason}`);
|
|
955
|
+
const given = atArgsOf(rule.params, args);
|
|
956
|
+
if (!given.ok) return fail("#VALUE!", `The step tree.at of ${attributeText(attr, n.type, n.place)} has bad arguments. ${given.reason}`);
|
|
957
|
+
// An open fixed point does not use the cells of the host (D113).
|
|
958
|
+
const host = openPoint === undefined ? n.host : undefined;
|
|
959
|
+
const place = n.host === undefined ? n.place : keptAt(atPlaces, n.place, () => Object.freeze([...n.place]));
|
|
960
|
+
let marks: Map<string, Cell> | undefined = n.host === undefined ? atMemos.get(n) : keptAt(marksByPlace, place, () => new Map<string, Cell>());
|
|
961
|
+
if (marks === undefined) {
|
|
962
|
+
marks = new Map();
|
|
963
|
+
atMemos.set(n, marks);
|
|
964
|
+
}
|
|
965
|
+
const key = `[${JSON.stringify(attr)},${given.key}]`;
|
|
966
|
+
const text = attributeText(`${attr} with ${given.key}`, n.type, n.place);
|
|
967
|
+
const cell = marks.get(key);
|
|
968
|
+
if (cell !== undefined) return cell.busy ? cycleAt(marks, key, text) : cell.result;
|
|
969
|
+
const demand: Demand = { cells: marks, key, text };
|
|
970
|
+
const loop = openPoint;
|
|
971
|
+
const compute = (): AttributeAnswer => ({ result: n.equation(rule.equation, given.names), by: "kind" });
|
|
972
|
+
marks.set(key, BUSY);
|
|
973
|
+
demands.push(demand);
|
|
974
|
+
let thrown: unknown = NOT_THROWN;
|
|
975
|
+
let answer: AttributeAnswer;
|
|
976
|
+
try {
|
|
977
|
+
answer =
|
|
978
|
+
host === undefined
|
|
979
|
+
? compute()
|
|
980
|
+
: host(place, key, () => {
|
|
981
|
+
try {
|
|
982
|
+
return compute();
|
|
983
|
+
} catch (error) {
|
|
984
|
+
thrown = error;
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
} catch (error) {
|
|
989
|
+
marks.delete(key);
|
|
990
|
+
// An exception of the kit or of the stack goes on. Another exception of the cell is a cycle of the host (D108).
|
|
991
|
+
if (host === undefined || error === thrown) throw error;
|
|
992
|
+
const chain = demands.map((item) => item.text).join(" -> ");
|
|
993
|
+
answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
|
|
994
|
+
} finally {
|
|
995
|
+
demands.pop();
|
|
996
|
+
}
|
|
997
|
+
// A result that read an approximation is not final. With the cells of a host, the cell is the memo.
|
|
998
|
+
if (n.host !== undefined || loop?.tentative.has(demand) === true) marks.delete(key);
|
|
999
|
+
else marks.set(key, { busy: false, result: answer.result, by: answer.by });
|
|
1000
|
+
return answer.result;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
919
1003
|
/**
|
|
920
1004
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
921
1005
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -1256,8 +1340,25 @@ const named: Walker = ({ data, here, deref }) => {
|
|
|
1256
1340
|
return { ok: false, reason: `No registry ${registry} is on the way.` };
|
|
1257
1341
|
};
|
|
1258
1342
|
|
|
1343
|
+
/**
|
|
1344
|
+
* The walker of the step `tree.at` (TREE.AT, D124). The field `attr` names an attribute with parameters, and the
|
|
1345
|
+
* field `args` gives the arguments. The thing before the step is a node. A failed instance throws its own error.
|
|
1346
|
+
*/
|
|
1347
|
+
const at: Walker = ({ data, here }) => {
|
|
1348
|
+
const attr = isRecord(data) ? field(data, "attr") : undefined;
|
|
1349
|
+
if (typeof attr !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.at needs a text attr." };
|
|
1350
|
+
const args = isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {};
|
|
1351
|
+
const state = stateOf(here);
|
|
1352
|
+
const reader = state === undefined && typeof here === "object" && here !== null ? AT_READERS.get(here) : undefined;
|
|
1353
|
+
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.` };
|
|
1354
|
+
const result = state === undefined ? (reader as AtReader)(attr, args) : atRead(state, attr, args);
|
|
1355
|
+
if (result === undefined) return { ok: false, reason: `No rung gives the attribute ${attr} with parameters.` };
|
|
1356
|
+
if (!result.ok) throw new Failure(result.error);
|
|
1357
|
+
return { ok: true, thing: result.value as Thing };
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1259
1360
|
/** The walkers of the standard world, by name. */
|
|
1260
|
-
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named };
|
|
1361
|
+
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named, "walk.tree.at": at };
|
|
1261
1362
|
|
|
1262
1363
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1263
1364
|
function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {
|