@mark1russell7/coeval 0.20.1 → 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 +379 -236
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1017 -98
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/conform.ts +13 -4
- package/src/deref.ts +32 -2
- package/src/effects.ts +4 -2
- package/src/evaluate.ts +2 -0
- package/src/facts.ts +3 -2
- package/src/formula.ts +53 -10
- package/src/index.ts +10 -0
- package/src/instances.ts +94 -5
- package/src/parts.ts +30 -1
- package/src/providers.ts +209 -29
- package/src/reference.ts +119 -9
- package/src/static.ts +144 -23
- package/src/stdlib.ts +189 -2
- package/src/types.ts +12 -4
- package/src/world.ts +135 -1
package/src/static.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type Kind, type World, type WorldEntry, circularOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
3
|
-
import { ask } from "./ladder.ts";
|
|
4
|
-
import { type PartSlot, partsOf, scopesOf } from "./parts.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
|
+
import { answerOf, ask } from "./ladder.ts";
|
|
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";
|
|
6
6
|
import type { ErrorCode } from "./errors.ts";
|
|
7
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 {
|
|
@@ -405,6 +406,19 @@ export interface Typing {
|
|
|
405
406
|
readonly places: readonly Typed[];
|
|
406
407
|
}
|
|
407
408
|
|
|
409
|
+
/**
|
|
410
|
+
* This function gives the parts that the code of a runner evaluates itself, without a wait (RUN.DRIVES, D119). The
|
|
411
|
+
* answer of the hook `facts` for the code wins over the contract. The contract describes only the code of its kind for
|
|
412
|
+
* `eval`. Without a declaration, the runner passes each wait of its parts through.
|
|
413
|
+
*/
|
|
414
|
+
function driveRules(machine: Machine, v: DocVisit, kind: Kind, name: string, rung: string | undefined, verb: string): readonly LazyRule[] | undefined {
|
|
415
|
+
if (verb !== "eval") return undefined;
|
|
416
|
+
const hook = machine.facts?.(v.typePath ?? [], name)?.drives;
|
|
417
|
+
if (hook !== undefined) return hook;
|
|
418
|
+
const described = v.declared || rung !== "own" || name === answerOf(machine.world, undefined, kind, verb)?.name;
|
|
419
|
+
return described ? kind.drives : undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
408
422
|
/** The least authority: the capability `[]` permits each effect (CAP.KEYS). */
|
|
409
423
|
const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
|
|
410
424
|
|
|
@@ -412,16 +426,42 @@ const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
|
|
|
412
426
|
const NO_EFFECTS: readonly (readonly Key[])[] = /* @__PURE__ */ Object.freeze([]);
|
|
413
427
|
|
|
414
428
|
/**
|
|
415
|
-
* This function gives the effects that the
|
|
416
|
-
*
|
|
417
|
-
*
|
|
429
|
+
* This function gives the effects that the code of a visit declares for a verb, as keys (FACTS.EFFECTS, VERB.EFFECTS).
|
|
430
|
+
* The text `"type"` gives the type of the document after each alias. A visit without a kind declares no effect.
|
|
431
|
+
* `check`, `factsOf`, `effectsOf` and `checkLatent` read the declarations with this one function (D27).
|
|
432
|
+
*
|
|
433
|
+
* For `eval`, the answer of the hook `facts` of the machine for the code of the visit wins over the contract of the
|
|
434
|
+
* kind (CAP.OWN-EFFECTS, D118). Thus a language gives the effects of an own entry for another evaluator. Without a
|
|
435
|
+
* machine, or without the field `effects` in the answer, the contract gives the effects.
|
|
418
436
|
*
|
|
419
437
|
* @param v - The visit.
|
|
420
438
|
* @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
|
|
421
|
-
* @
|
|
439
|
+
* @param machine - The machine with the hook `facts`, when the caller has it.
|
|
440
|
+
* @param name - The name of the evaluator of the visit, when the caller knows it. Else the ladder gives it.
|
|
441
|
+
* @returns The keys of each declared effect, in the order of the declaration.
|
|
442
|
+
*/
|
|
443
|
+
export function declaredEffects(v: DocVisit, verb = "eval", machine?: Machine, name?: string): readonly (readonly Key[])[] {
|
|
444
|
+
return hookEffects(v, verb, machine, name) ?? verbDeclared(v, verb) ?? NO_EFFECTS;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* This function gives the effects that the hook `facts` gives for the code of a visit, as keys (D118). The result is
|
|
449
|
+
* `undefined` for another verb than `eval`, without the hook, or when the answer has no field `effects`.
|
|
422
450
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
451
|
+
function hookEffects(v: DocVisit, verb: string, machine: Machine | undefined, name: string | undefined): readonly (readonly Key[])[] | undefined {
|
|
452
|
+
if (verb !== "eval" || machine?.facts === undefined || v.kind === undefined) return undefined;
|
|
453
|
+
const code = name ?? answerOf(machine.world, v.declared ? undefined : v.doc.own, v.kind, "eval")?.name;
|
|
454
|
+
const effects = code === undefined ? undefined : machine.facts(v.typePath ?? [], code)?.effects;
|
|
455
|
+
return effects === undefined ? undefined : effectKeys(v, effects);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* This function gives the keys of a declaration of effects at a visit (D27). The text `"type"` gives the type of the
|
|
460
|
+
* document after each alias.
|
|
461
|
+
*/
|
|
462
|
+
export function effectKeys(v: DocVisit, effects: "type" | readonly string[]): readonly (readonly Key[])[] {
|
|
463
|
+
if (effects.length === 0) return NO_EFFECTS;
|
|
464
|
+
return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
|
|
425
465
|
}
|
|
426
466
|
|
|
427
467
|
/**
|
|
@@ -433,8 +473,7 @@ function verbDeclared(v: DocVisit, verb: string): readonly (readonly Key[])[] |
|
|
|
433
473
|
if (v.kind === undefined) return verb === "eval" ? NO_EFFECTS : undefined;
|
|
434
474
|
const effects = effectsOfVerb(v.kind, verb);
|
|
435
475
|
if (effects === undefined) return undefined;
|
|
436
|
-
|
|
437
|
-
return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
|
|
476
|
+
return effectKeys(v, effects);
|
|
438
477
|
}
|
|
439
478
|
|
|
440
479
|
/**
|
|
@@ -637,7 +676,8 @@ const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "ite
|
|
|
637
676
|
|
|
638
677
|
/**
|
|
639
678
|
* This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
|
|
640
|
-
* `equations`, a `unit
|
|
679
|
+
* `equations`, a `unit`, a `default` and a contribution see no names. `check` and `factsOf` read the equations with
|
|
680
|
+
* this one function.
|
|
641
681
|
*/
|
|
642
682
|
export function equationsOf(kind: Kind): readonly Equation[] {
|
|
643
683
|
const tree = kind.tree;
|
|
@@ -651,6 +691,18 @@ export function equationsOf(kind: Kind): readonly Equation[] {
|
|
|
651
691
|
out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
|
|
652
692
|
}
|
|
653
693
|
if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [], attribute: name, down: false });
|
|
694
|
+
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
695
|
+
if (collect?.ok === true && !collect.names) {
|
|
696
|
+
out.push({ label: `tree.attributes.${name}.collect.unit`, term: collect.unit, names: [], attribute: name, down: false });
|
|
697
|
+
out.push({ label: `tree.attributes.${name}.collect.combine`, term: collect.combine, names: FOLD_NAMES, attribute: name, down: false });
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// A contribution is not the attribute of its node, thus an own part of its name does not hide it (D122).
|
|
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 });
|
|
654
706
|
}
|
|
655
707
|
return out;
|
|
656
708
|
}
|
|
@@ -665,6 +717,10 @@ export interface Equation {
|
|
|
665
717
|
readonly attribute: string;
|
|
666
718
|
/** `true` for an equation of `down`: it gives the attribute of a child. */
|
|
667
719
|
readonly down: boolean;
|
|
720
|
+
/** `true` for an equation of `contributes`: it gives a contribution to a collection (TREE.COLLECT). */
|
|
721
|
+
readonly contributes?: true;
|
|
722
|
+
/** `true` for an equation of `at`: it gives an attribute with parameters (TREE.AT). */
|
|
723
|
+
readonly at?: true;
|
|
668
724
|
}
|
|
669
725
|
|
|
670
726
|
/**
|
|
@@ -837,7 +893,7 @@ export function answered(world: World, node: SeenNode, name: string): boolean {
|
|
|
837
893
|
if (hasEntry(tree?.equations, name)) return true;
|
|
838
894
|
if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
|
|
839
895
|
const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
|
|
840
|
-
return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.default !== undefined);
|
|
896
|
+
return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined);
|
|
841
897
|
}
|
|
842
898
|
|
|
843
899
|
/**
|
|
@@ -853,10 +909,10 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
853
909
|
state.needs.set(kind, needs);
|
|
854
910
|
for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
|
|
855
911
|
const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
|
|
856
|
-
for (const { label, term, names, attribute, down } of equationsOf(kind)) {
|
|
912
|
+
for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
|
|
857
913
|
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
858
914
|
const read = (name: string): void => {
|
|
859
|
-
needs.push({ name, why: `the equation ${label} reads it`, hidden: down ? undefined : attribute });
|
|
915
|
+
needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true || at === true ? undefined : attribute });
|
|
860
916
|
};
|
|
861
917
|
for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
|
|
862
918
|
out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
|
|
@@ -868,12 +924,13 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
868
924
|
/**
|
|
869
925
|
* This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
|
|
870
926
|
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
|
|
871
|
-
* declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK).
|
|
927
|
+
* declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK). A declaration
|
|
928
|
+
* `collect` without its form gives `#VALUE!` too (TREE.COLLECT). So does an entry of `at` without its form (TREE.AT-CHECK).
|
|
872
929
|
*/
|
|
873
930
|
function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
874
931
|
const out: Issue[] = [];
|
|
875
932
|
const tree = kind.tree;
|
|
876
|
-
for (const table of ["equations", "down", "attributes"] as const) {
|
|
933
|
+
for (const table of ["equations", "down", "attributes", "contributes", "at"] as const) {
|
|
877
934
|
for (const name of Object.keys(tree?.[table] ?? {})) {
|
|
878
935
|
const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
|
|
879
936
|
if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
|
|
@@ -883,6 +940,12 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
|
883
940
|
for (const [name, declared] of Object.entries(tree?.attributes ?? {})) {
|
|
884
941
|
const rule = declared.circular === undefined ? undefined : circularOf(declared.circular);
|
|
885
942
|
if (rule !== undefined && !rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.circular. ${rule.reason}` });
|
|
943
|
+
const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
|
|
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}` });
|
|
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}` });
|
|
886
949
|
}
|
|
887
950
|
if (kind.side === "run") {
|
|
888
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.` });
|
|
@@ -915,9 +978,14 @@ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVi
|
|
|
915
978
|
* This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
|
|
916
979
|
* nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
|
|
917
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
|
|
918
|
-
* needs of its kind.
|
|
981
|
+
* needs of its kind. A step `tree.at` with literal arguments gets the checks of TREE.AT-CHECK.
|
|
919
982
|
*/
|
|
920
|
-
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 {
|
|
921
989
|
let node = self;
|
|
922
990
|
let index = 1;
|
|
923
991
|
while (node !== undefined && index < path.length && path[index] === "parent") {
|
|
@@ -925,6 +993,10 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
925
993
|
index += 1;
|
|
926
994
|
}
|
|
927
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
|
+
}
|
|
928
1000
|
if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
|
|
929
1001
|
const name = String(step);
|
|
930
1002
|
if (node.own === undefined) {
|
|
@@ -934,6 +1006,46 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
|
|
|
934
1006
|
return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
|
|
935
1007
|
}
|
|
936
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
|
+
|
|
937
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. */
|
|
938
1050
|
interface RootSelf {
|
|
939
1051
|
readonly node: SeenNode;
|
|
@@ -971,6 +1083,9 @@ function checkAll(
|
|
|
971
1083
|
// run, and a drive for a runner under an evaluator. A document without code fails before its parts, thus it is not
|
|
972
1084
|
// in the map.
|
|
973
1085
|
const waits = options.run === true ? new Map<DocVisit, Waits>() : undefined;
|
|
1086
|
+
// The parts that the code of a runner evaluates itself, without a wait (RUN.DRIVES). They get no wait, as under an
|
|
1087
|
+
// evaluator. Only a runner that declares `drives` is in the map.
|
|
1088
|
+
const driven = new Map<DocVisit, readonly LazyRule[]>();
|
|
974
1089
|
const drives: Drive[] = [];
|
|
975
1090
|
const maxDepth = machine.maxDepth ?? 1000;
|
|
976
1091
|
const anyName = options.names === "any";
|
|
@@ -1026,7 +1141,7 @@ function checkAll(
|
|
|
1026
1141
|
if (known !== true) push({ code: "#NAME?", place: v.place, reason: known, at: v.at });
|
|
1027
1142
|
}
|
|
1028
1143
|
const caps = v.caps;
|
|
1029
|
-
const stated = verbDeclared(v, verb);
|
|
1144
|
+
const stated = hookEffects(v, verb, machine, name) ?? verbDeclared(v, verb);
|
|
1030
1145
|
const declared = stated ?? NO_EFFECTS;
|
|
1031
1146
|
if (allowed !== undefined && caps !== undefined) {
|
|
1032
1147
|
for (const kind of declared) {
|
|
@@ -1035,7 +1150,9 @@ function checkAll(
|
|
|
1035
1150
|
}
|
|
1036
1151
|
if (waits !== undefined && coded) {
|
|
1037
1152
|
// The parent of the root is the driver of the run, and it can wait. An evaluator gives its parts no wait.
|
|
1038
|
-
|
|
1153
|
+
let up = v.parent === undefined ? true : waits.get(v.parent);
|
|
1154
|
+
const rules = v.parent === undefined ? undefined : driven.get(v.parent);
|
|
1155
|
+
if (up !== false && rules !== undefined && v.parent !== undefined && rulesCover(rules, v.parent.kind?.parts, v.parent.doc.value as Json, v.slot)) up = false;
|
|
1039
1156
|
let chain: Waits = false;
|
|
1040
1157
|
if (runner && up === false) {
|
|
1041
1158
|
// The run drives a runner under an evaluator, and only a real wait fails (RUN.DRIVE-NESTED). Its issue goes in
|
|
@@ -1047,6 +1164,10 @@ function checkAll(
|
|
|
1047
1164
|
chain = up ?? true;
|
|
1048
1165
|
}
|
|
1049
1166
|
waits.set(v, chain);
|
|
1167
|
+
if (runner && v.kind !== undefined) {
|
|
1168
|
+
const own = driveRules(machine, v, v.kind, name, answer.rung, verb);
|
|
1169
|
+
if (own !== undefined && own.length > 0) driven.set(v, own);
|
|
1170
|
+
}
|
|
1050
1171
|
// A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait. A
|
|
1051
1172
|
// runner without a declaration for the verb can cause each effect, thus it can wait.
|
|
1052
1173
|
if (typeof chain === "object" && !chain.wait) {
|
|
@@ -1089,7 +1210,7 @@ function checkAll(
|
|
|
1089
1210
|
// Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
|
|
1090
1211
|
if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
|
|
1091
1212
|
const issue = readIssue(machine, self, path, rootSelf);
|
|
1092
|
-
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) });
|
|
1093
1214
|
}
|
|
1094
1215
|
path.forEach((step, index) => {
|
|
1095
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, 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
|
|
|
@@ -67,6 +67,20 @@ export const STD_WORLD: World = {
|
|
|
67
67
|
pure: true,
|
|
68
68
|
summary: "A step of a path: it follows a key again and again, until it reaches a node of a type.",
|
|
69
69
|
},
|
|
70
|
+
tree: {
|
|
71
|
+
kinds: {
|
|
72
|
+
named: {
|
|
73
|
+
verbs: { step: "walk.named", view: "view.card" },
|
|
74
|
+
pure: true,
|
|
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
|
+
},
|
|
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
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
70
84
|
panel: {
|
|
71
85
|
verbs: { eval: "tree.node", view: "view.op" },
|
|
72
86
|
parts: { shape: "record", fields: ["width", "height", "children"], of: "number" },
|
|
@@ -167,6 +181,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
167
181
|
{ name: "panel", kind: "panel", args: "named", fields: ["width", "height", "children"] },
|
|
168
182
|
{ name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
|
|
169
183
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
184
|
+
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
185
|
+
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
170
186
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
171
187
|
{ name: "add", kind: "add" },
|
|
172
188
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -272,6 +288,8 @@ export interface AttributeTrace {
|
|
|
272
288
|
/** The state of a node behind its brand. */
|
|
273
289
|
interface NodeState extends LoopNode {
|
|
274
290
|
readonly build: TreeBuild;
|
|
291
|
+
/** The binding name from the header of the document of the node, for the registry of names (TREE.NAMED). */
|
|
292
|
+
readonly name: string | undefined;
|
|
275
293
|
/**
|
|
276
294
|
* This function gives an attribute through its ladder (TREE.LADDER).
|
|
277
295
|
*
|
|
@@ -331,6 +349,10 @@ export interface AttributeAnswer {
|
|
|
331
349
|
* A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
|
|
332
350
|
* A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
|
|
333
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
|
+
*
|
|
334
356
|
* The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
|
|
335
357
|
* of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
|
|
336
358
|
* a cell that computes again uses the last one.
|
|
@@ -853,6 +875,131 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
853
875
|
}
|
|
854
876
|
}
|
|
855
877
|
|
|
878
|
+
/**
|
|
879
|
+
* This function gives the collection of an attribute of a node (TREE.COLLECT, D122). It visits the node, then each node
|
|
880
|
+
* of the same tree in each list `children`, in the order of the lists. Each node is visited one time.
|
|
881
|
+
* The registry of names (TREE.NAMED) gives a record from each binding name to its node.
|
|
882
|
+
*/
|
|
883
|
+
function collected(root: HostThing, key: string, declared: unknown): Result {
|
|
884
|
+
const top = stateOf(root) as NodeState;
|
|
885
|
+
const rule = collectOf(declared);
|
|
886
|
+
if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(key, top.type, top.place)} has a bad declaration collect. ${rule.reason}`);
|
|
887
|
+
const registry: Record<string, unknown> = {};
|
|
888
|
+
const owners = new Map<string, NodeState>();
|
|
889
|
+
let acc = rule.names ? ok(registry) : top.equation(rule.unit, NO_NAMES);
|
|
890
|
+
if (!acc.ok) return acc;
|
|
891
|
+
const seen = new Set<NodeState>();
|
|
892
|
+
const stack: unknown[] = [root];
|
|
893
|
+
while (stack.length > 0) {
|
|
894
|
+
const thing = stack.pop();
|
|
895
|
+
const at = stateOf(thing);
|
|
896
|
+
// An item that is not a node of this tree does not count, and a node counts one time.
|
|
897
|
+
if (at === undefined || at.build !== top.build || seen.has(at)) continue;
|
|
898
|
+
seen.add(at);
|
|
899
|
+
if (rule.names) {
|
|
900
|
+
const name = at.name;
|
|
901
|
+
const earlier = name === undefined ? undefined : owners.get(name);
|
|
902
|
+
if (earlier !== undefined) {
|
|
903
|
+
const both = `${earlier.type} at [${earlier.place.join(", ")}] and ${at.type} at [${at.place.join(", ")}]`;
|
|
904
|
+
return fail("#VALUE!", `Two nodes have the name ${name} in the registry ${attributeText(key, top.type, top.place)}: ${both}.`);
|
|
905
|
+
}
|
|
906
|
+
if (name !== undefined) {
|
|
907
|
+
owners.set(name, at);
|
|
908
|
+
putField(registry, name, thing);
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
const term = entryOf(at.tree?.contributes, key);
|
|
912
|
+
if (term !== undefined) {
|
|
913
|
+
const item = at.equation(term, NO_NAMES);
|
|
914
|
+
if (!item.ok) return item;
|
|
915
|
+
acc = top.equation(rule.combine, { acc: acc.value, item: item.value });
|
|
916
|
+
if (!acc.ok) return acc;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const children = at.read("children");
|
|
920
|
+
if (children === undefined) continue;
|
|
921
|
+
if (!children.ok) return children;
|
|
922
|
+
if (!Array.isArray(children.value)) return fail("#VALUE!", `The collection of ${key} needs a list in children of ${at.type} at [${at.place.join(", ")}].`);
|
|
923
|
+
const list = children.value as readonly unknown[];
|
|
924
|
+
for (let index = list.length - 1; index >= 0; index -= 1) stack.push(list[index]);
|
|
925
|
+
}
|
|
926
|
+
return acc;
|
|
927
|
+
}
|
|
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
|
+
|
|
856
1003
|
/**
|
|
857
1004
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
858
1005
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -916,6 +1063,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
916
1063
|
if (copied !== undefined) return { result: copied, by: "copy" };
|
|
917
1064
|
}
|
|
918
1065
|
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
1066
|
+
if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
|
|
919
1067
|
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
920
1068
|
return undefined;
|
|
921
1069
|
};
|
|
@@ -984,6 +1132,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
984
1132
|
|
|
985
1133
|
const state: NodeState = {
|
|
986
1134
|
build,
|
|
1135
|
+
name: input.doc.name,
|
|
987
1136
|
type,
|
|
988
1137
|
place,
|
|
989
1138
|
tree,
|
|
@@ -1170,8 +1319,46 @@ export function nearestWalker(options: NearestOptions = {}): Walker {
|
|
|
1170
1319
|
|
|
1171
1320
|
const nearest: Walker = /* @__PURE__ */ nearestWalker();
|
|
1172
1321
|
|
|
1322
|
+
/**
|
|
1323
|
+
* The walker of the step `tree.named` (TREE.NAMED, D122). It goes up from the thing before the step through the key
|
|
1324
|
+
* `parent`, to the first thing that answers the registry. The field `in` names the registry, and the default is
|
|
1325
|
+
* `names`. The walker gives the node with the field `name` in that registry.
|
|
1326
|
+
*/
|
|
1327
|
+
const named: Walker = ({ data, here, deref }) => {
|
|
1328
|
+
const name = isRecord(data) ? field(data, "name") : undefined;
|
|
1329
|
+
if (typeof name !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.named needs a text name." };
|
|
1330
|
+
const registry = isRecord(data) && typeof field(data, "in") === "string" ? String(field(data, "in")) : "names";
|
|
1331
|
+
let at: Thing | undefined = here;
|
|
1332
|
+
for (let hops = 0; hops < 256 && at !== undefined && at !== null; hops += 1) {
|
|
1333
|
+
const found = deref(at, registry);
|
|
1334
|
+
if (found !== undefined) {
|
|
1335
|
+
if (!isRecord(found) || !Object.hasOwn(found, name)) return { ok: false, reason: `The registry ${registry} has no node with the name ${name}.` };
|
|
1336
|
+
return { ok: true, thing: field(found, name) as Thing };
|
|
1337
|
+
}
|
|
1338
|
+
at = deref(at, "parent");
|
|
1339
|
+
}
|
|
1340
|
+
return { ok: false, reason: `No registry ${registry} is on the way.` };
|
|
1341
|
+
};
|
|
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
|
+
|
|
1173
1360
|
/** The walkers of the standard world, by name. */
|
|
1174
|
-
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest };
|
|
1361
|
+
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named, "walk.tree.at": at };
|
|
1175
1362
|
|
|
1176
1363
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1177
1364
|
function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {
|
package/src/types.ts
CHANGED
|
@@ -1144,8 +1144,10 @@ function valueStep(value: unknown, key: Key): { readonly found: unknown } | { re
|
|
|
1144
1144
|
*
|
|
1145
1145
|
* - A record gives the type of its field. A closed record without the field has no such step. An open record gives
|
|
1146
1146
|
* `unknown`.
|
|
1147
|
-
* - A list gives the type of its items for an index, and `number` for `length`.
|
|
1148
|
-
* - A base type reads the steps of its domain, then of each parent (`Domain.steps`).
|
|
1147
|
+
* - A list gives the type of its items for an index, and `number` for `length`. Another key has no such step.
|
|
1148
|
+
* - A base type reads the steps of its domain, then of each parent (`Domain.steps`). A base type of the kernel
|
|
1149
|
+
* without such a step has no step, because its values are not objects (TYPE.NO-STEP, D120).
|
|
1150
|
+
* - A function type has no step, because a step into a function reaches nothing.
|
|
1149
1151
|
* - A union joins the steps of its types. `one` gives the values that the step reaches.
|
|
1150
1152
|
* - Each other step gives `unknown`, and `never` gives `never`.
|
|
1151
1153
|
*
|
|
@@ -1171,10 +1173,13 @@ export function stepType(
|
|
|
1171
1173
|
const found = steps[name] as ValueType;
|
|
1172
1174
|
return { type: examine === undefined ? found : examine(found, `type of the step ${name} of the domain ${each}`) };
|
|
1173
1175
|
}
|
|
1174
|
-
|
|
1176
|
+
// A value of a base type of the kernel is not an object, thus a step into it reaches nothing (TYPE.NO-STEP).
|
|
1177
|
+
return Object.hasOwn(STD_DOMAINS, form.name) ? NO_STEP : { type: "unknown" };
|
|
1178
|
+
case "fn":
|
|
1179
|
+
return NO_STEP;
|
|
1175
1180
|
case "list":
|
|
1176
1181
|
if (key === "length") return { type: "number" };
|
|
1177
|
-
return
|
|
1182
|
+
return indexOf(key) >= 0 ? { type: form.item } : NO_STEP;
|
|
1178
1183
|
case "record":
|
|
1179
1184
|
if (Object.hasOwn(form.fields, name)) return { type: form.fields[name] as ValueType };
|
|
1180
1185
|
return form.closed ? { missing: Object.keys(form.fields) } : { type: "unknown" };
|
|
@@ -1204,6 +1209,9 @@ export function stepType(
|
|
|
1204
1209
|
}
|
|
1205
1210
|
}
|
|
1206
1211
|
|
|
1212
|
+
/** The answer of `stepType` for a type whose members have no step with the key, and no fields to name. */
|
|
1213
|
+
const NO_STEP: StepType = { missing: [] };
|
|
1214
|
+
|
|
1207
1215
|
/** A term that `typeIssues` reads later: the term, its keys from the root and its depth. */
|
|
1208
1216
|
interface Pending {
|
|
1209
1217
|
readonly type: unknown;
|