@mark1russell7/coeval 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/index.d.ts +52 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +220 -24
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/evaluate.ts +2 -0
- package/src/facts.ts +17 -3
- package/src/formula.ts +121 -7
- package/src/index.ts +2 -0
- package/src/reference.ts +7 -4
- package/src/static.ts +69 -1
- package/src/stdlib.ts +20 -7
- package/src/world.ts +4 -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.9.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/evaluate.ts
CHANGED
|
@@ -913,6 +913,8 @@ class Session implements Runtime {
|
|
|
913
913
|
const free = isFree(reading.root, ctx, path);
|
|
914
914
|
// A kind that sets another self can give the old self on, as the parent of a node of a tree does (READS.KEPT).
|
|
915
915
|
if (!free && path[0] === "self" && !reading.self) this.keep(reading, ["self"]);
|
|
916
|
+
// A free read of the whole root self already holds what a kept read of self holds, as the root of a tree reads it.
|
|
917
|
+
else if (free && path.length === 1 && path[0] === "self") reading.self = true;
|
|
916
918
|
let found: Resolution;
|
|
917
919
|
try {
|
|
918
920
|
found = this.find(ctx, path, compiled, hops);
|
package/src/facts.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { type DocVisit, declaredEffects, walk } from "./static.ts";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* One read of a document: a free reference, with its marks (FACTS.READS). A plan resolves the path of the reference
|
|
11
|
-
* when it evaluates the reference. The static read covers each path that the plan resolves there (FACTS.SOUND).
|
|
11
|
+
* when it evaluates the reference. The static read covers each path that the plan resolves there (FACTS.SOUND). A
|
|
12
|
+
* root that a kind declares in `reads` is a read too, with the path of the root alone.
|
|
12
13
|
*/
|
|
13
14
|
export interface StaticRead {
|
|
14
15
|
/** The root of the path. */
|
|
@@ -34,7 +35,10 @@ export interface StaticRead {
|
|
|
34
35
|
|
|
35
36
|
/** The static facts of one document (D24). They depend only on the document and the machine, not on a context. */
|
|
36
37
|
export interface DocFacts {
|
|
37
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* The free references, in the order of the document (FACTS.READS). A reference to a name that a kind binds is not
|
|
40
|
+
* here. The roots that a kind declares in `reads` are here too, each one at the place of its document.
|
|
41
|
+
*/
|
|
38
42
|
readonly reads: readonly StaticRead[];
|
|
39
43
|
/** `"run"` when one kind of the document is on the run side (FACTS.SIDE). */
|
|
40
44
|
readonly side: Side;
|
|
@@ -146,7 +150,8 @@ function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean
|
|
|
146
150
|
* by the contracts of its kinds:
|
|
147
151
|
*
|
|
148
152
|
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
149
|
-
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`.
|
|
153
|
+
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
|
|
154
|
+
* declares `reads` adds a read of each of its roots at each document of the kind.
|
|
150
155
|
* - `side` and `effects`: from the contract of each kind.
|
|
151
156
|
* - `pure`: each kind declares `pure`, and the document has no effect and is not volatile.
|
|
152
157
|
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
@@ -200,13 +205,22 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
200
205
|
}
|
|
201
206
|
const code = codeOf(world, v.declared ? undefined : v.doc.own, kind, verb, "eval");
|
|
202
207
|
const answer = code.name === undefined ? undefined : hook?.(v.typePath ?? [], code.name);
|
|
208
|
+
let roots: readonly Root[] | undefined;
|
|
203
209
|
if (code.described) {
|
|
204
210
|
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
205
211
|
above.set(v, { lazy, rules: answer?.lazy ?? kind.lazy });
|
|
212
|
+
roots = answer?.reads ?? kind.reads;
|
|
206
213
|
} else {
|
|
207
214
|
// The contract of the kind does not describe this code. Only the hook can.
|
|
208
215
|
note(answer?.pure, answer?.volatile);
|
|
209
216
|
above.set(v, { lazy, rules: answer?.lazy ?? EACH });
|
|
217
|
+
roots = answer?.reads;
|
|
218
|
+
}
|
|
219
|
+
// The code of the kind resolves each of these roots as a whole, at the place of its document (FACTS.KIND-READS).
|
|
220
|
+
for (const root of roots ?? NONE) {
|
|
221
|
+
const read: StaticRead = { root, keys: NONE, exact: true, lazy, address: false, path: [root], place: tight(v.place), at: tight(v.at) };
|
|
222
|
+
reads.push(read);
|
|
223
|
+
if (options.volatile?.(read) === true) volatile = true;
|
|
210
224
|
}
|
|
211
225
|
if (kind.side === "run") side = "run";
|
|
212
226
|
// check and effectsOf read the declared effects with the same function (D27).
|
package/src/formula.ts
CHANGED
|
@@ -498,19 +498,118 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
498
498
|
* This function makes the evaluators of the formula kit for one language. Each evaluator compiles its document
|
|
499
499
|
* one time into code. Each evaluator also holds its type rule, for a check of types (TYPE.SYNTH).
|
|
500
500
|
*
|
|
501
|
+
* Equal profiles give the same evaluator objects (COMPOSE.UNIFY, D46). Thus two machines of one profile merge without
|
|
502
|
+
* a conflict. Each call gives a new record. Each evaluator object is frozen, because many machines can share it.
|
|
503
|
+
*
|
|
501
504
|
* @param profile - The operations and the rules of the language.
|
|
502
505
|
* @returns The evaluators, by their name.
|
|
503
506
|
*/
|
|
504
507
|
export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record<string, Evaluator>> {
|
|
508
|
+
return { ...kitOf(profile).evaluators };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** This function makes new evaluators of the formula kit for the fields of one profile. */
|
|
512
|
+
function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evaluator>> {
|
|
505
513
|
const evaluators = formulaCode(profile);
|
|
506
514
|
const types = formulaTypes(profile);
|
|
507
515
|
const out: Record<string, Evaluator> = {};
|
|
508
516
|
for (const name of Object.keys(evaluators)) {
|
|
509
517
|
const evaluator = evaluators[name] as CompiledEvaluator;
|
|
510
518
|
const type = Object.hasOwn(types, name) ? types[name] : undefined;
|
|
511
|
-
out[name] = type === undefined ? evaluator : { compile: evaluator.compile, type };
|
|
519
|
+
out[name] = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
|
|
520
|
+
}
|
|
521
|
+
return Object.freeze(out);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** The keys of a profile that the kit reads. The kit ignores each other key. */
|
|
525
|
+
const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
526
|
+
"ops",
|
|
527
|
+
"lazy",
|
|
528
|
+
"ext",
|
|
529
|
+
"truthy",
|
|
530
|
+
"otherwise",
|
|
531
|
+
"logic",
|
|
532
|
+
"omit",
|
|
533
|
+
"missing",
|
|
534
|
+
"check",
|
|
535
|
+
"combine",
|
|
536
|
+
"pure",
|
|
537
|
+
"volatile",
|
|
538
|
+
"sigs",
|
|
539
|
+
] as const satisfies readonly (keyof FormulaProfile)[]);
|
|
540
|
+
|
|
541
|
+
/** The evaluators and the hooks of the formula kit for one profile, with the fields that made them. */
|
|
542
|
+
interface Kit {
|
|
543
|
+
readonly fields: FormulaProfile;
|
|
544
|
+
readonly evaluators: Readonly<Record<string, Evaluator>>;
|
|
545
|
+
readonly known: (path: readonly Key[], evaluator: string) => true | string;
|
|
546
|
+
readonly facts: (path: readonly Key[], code: string) => KindFacts | undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** The number of recent kits that a profile in a new object can find by its fields. */
|
|
550
|
+
const RECENT_KITS = 16;
|
|
551
|
+
|
|
552
|
+
/** The kit of each profile object. A kit goes when its profile object goes. */
|
|
553
|
+
const kitsByProfile = /* @__PURE__ */ new WeakMap<object, Kit>();
|
|
554
|
+
|
|
555
|
+
/** The recent kits, the most recent first. The list holds `RECENT_KITS` kits or fewer. */
|
|
556
|
+
const recentKits: Kit[] = [];
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* This function gives the canonical form of a profile: a frozen record of the fields that the kit reads. A field
|
|
560
|
+
* with the value `undefined` is absent, but `otherwise` is present when the profile has it as an own key. A list
|
|
561
|
+
* is a frozen copy. Each other value stays the same object.
|
|
562
|
+
*/
|
|
563
|
+
function fieldsOf(profile: FormulaProfile): FormulaProfile {
|
|
564
|
+
const out: Record<string, unknown> = {};
|
|
565
|
+
for (const key of PROFILE_KEYS) {
|
|
566
|
+
if (key === "otherwise" ? !Object.hasOwn(profile, key) : profile[key] === undefined) continue;
|
|
567
|
+
const value: unknown = profile[key];
|
|
568
|
+
out[key] = Array.isArray(value) ? Object.freeze([...(value as unknown[])]) : value;
|
|
512
569
|
}
|
|
513
|
-
return out;
|
|
570
|
+
return Object.freeze(out);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* This function tells if two canonical forms are equal. They must have the same keys. A function, a record and a host
|
|
575
|
+
* value are equal only to themselves. Two lists are equal when their items are the same, in order.
|
|
576
|
+
*/
|
|
577
|
+
function sameFields(a: FormulaProfile, b: FormulaProfile): boolean {
|
|
578
|
+
const left = a as Readonly<Record<string, unknown>>;
|
|
579
|
+
const right = b as Readonly<Record<string, unknown>>;
|
|
580
|
+
const keys = Object.keys(left);
|
|
581
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
582
|
+
return keys.every((key) => {
|
|
583
|
+
if (!Object.hasOwn(right, key)) return false;
|
|
584
|
+
const x = left[key];
|
|
585
|
+
const y = right[key];
|
|
586
|
+
if (Object.is(x, y)) return true;
|
|
587
|
+
return Array.isArray(x) && Array.isArray(y) && x.length === y.length && x.every((item, index) => Object.is(item, y[index]));
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* This function gives the kit of a profile (D46). A profile object that the kit knows, with the same fields, gives its
|
|
593
|
+
* kit. Then a recent kit with equal fields serves a new profile object. Else the function makes a new kit from the
|
|
594
|
+
* canonical form. Thus a later change of a field of the profile object gives a new kit, and an old kit does not change.
|
|
595
|
+
*/
|
|
596
|
+
function kitOf(profile: FormulaProfile): Kit {
|
|
597
|
+
const fields = fieldsOf(profile);
|
|
598
|
+
const known = kitsByProfile.get(profile);
|
|
599
|
+
if (known !== undefined && sameFields(known.fields, fields)) return known;
|
|
600
|
+
const index = recentKits.findIndex((kit) => sameFields(kit.fields, fields));
|
|
601
|
+
let kit: Kit;
|
|
602
|
+
if (index >= 0) {
|
|
603
|
+
kit = recentKits[index] as Kit;
|
|
604
|
+
recentKits.splice(index, 1);
|
|
605
|
+
} else {
|
|
606
|
+
const hook = makeKnown(fields);
|
|
607
|
+
kit = { fields, evaluators: makeEvaluators(fields), known: hook, facts: makeFacts(fields, hook) };
|
|
608
|
+
}
|
|
609
|
+
recentKits.unshift(kit);
|
|
610
|
+
if (recentKits.length > RECENT_KITS) recentKits.length = RECENT_KITS;
|
|
611
|
+
kitsByProfile.set(profile, kit);
|
|
612
|
+
return kit;
|
|
514
613
|
}
|
|
515
614
|
|
|
516
615
|
/** This function makes the code of the evaluators of the formula kit, without their type rules. */
|
|
@@ -848,10 +947,17 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
848
947
|
* `formula.ext` knows an extension of `ext` and each standard extension. The hook knows each name of the other
|
|
849
948
|
* evaluators.
|
|
850
949
|
*
|
|
950
|
+
* Equal profiles give the same hook (D46).
|
|
951
|
+
*
|
|
851
952
|
* @param profile - The operations and the extensions of the language.
|
|
852
953
|
* @returns The hook: `true`, or the reason of the `#NAME?` that the evaluator gives in a run.
|
|
853
954
|
*/
|
|
854
955
|
export function formulaKnown(profile: FormulaProfile = {}): (path: readonly Key[], evaluator: string) => true | string {
|
|
956
|
+
return kitOf(profile).known;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/** This function makes a new hook `known` for the fields of one profile. */
|
|
960
|
+
function makeKnown(profile: FormulaProfile): (path: readonly Key[], evaluator: string) => true | string {
|
|
855
961
|
const hasOp = (name: string): boolean => {
|
|
856
962
|
if (profile.lazy !== undefined && Object.hasOwn(profile.lazy, name)) return true;
|
|
857
963
|
const ops = profile.ops;
|
|
@@ -890,13 +996,19 @@ const EACH_PART: readonly LazyRule[] = /* @__PURE__ */ Object.freeze([/* @__PURE
|
|
|
890
996
|
* - An operation is pure when the profile names it in `pure`, or when `pure` is `true`. A volatile operation is not pure.
|
|
891
997
|
* - A standard extension is pure. An extension of the profile gets the context, thus it is not pure.
|
|
892
998
|
*
|
|
999
|
+
* Equal profiles give the same hook (D46).
|
|
1000
|
+
*
|
|
893
1001
|
* @param profile - The operations, the extensions and their facts.
|
|
894
1002
|
* @returns The hook: the facts of a name, or `undefined` for the facts of the contract.
|
|
895
1003
|
*/
|
|
896
1004
|
export function formulaFacts(profile: FormulaProfile = {}): (path: readonly Key[], code: string) => KindFacts | undefined {
|
|
1005
|
+
return kitOf(profile).facts;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/** This function makes a new hook `facts` for the fields of one profile, with the hook `known` of the same fields. */
|
|
1009
|
+
function makeFacts(profile: FormulaProfile, known: (path: readonly Key[], evaluator: string) => true | string): (path: readonly Key[], code: string) => KindFacts | undefined {
|
|
897
1010
|
const volatile = new Set(profile.volatile ?? []);
|
|
898
1011
|
const pure = profile.pure === true ? undefined : new Set(profile.pure ?? []);
|
|
899
|
-
const known = formulaKnown(profile);
|
|
900
1012
|
return (path, code) => {
|
|
901
1013
|
if (code !== "formula.op" && code !== "formula.ext") return undefined;
|
|
902
1014
|
const name = path.slice(1).join(".");
|
|
@@ -922,14 +1034,16 @@ export function formulaFacts(profile: FormulaProfile = {}): (path: readonly Key[
|
|
|
922
1034
|
* The kit gives the domains of the kernel (`STD_DOMAINS`). Thus a domain of the language with the name `number` is a
|
|
923
1035
|
* conflict. The language names `["domains", "number"]` in `replaces` to replace it.
|
|
924
1036
|
*
|
|
1037
|
+
* Equal profiles give the same evaluators and the same hooks (COMPOSE.UNIFY, D46). Thus two machines of one profile
|
|
1038
|
+
* merge, also when each adds its own language. Two profiles with different fields give different evaluators, and
|
|
1039
|
+
* their machines conflict at each evaluator name of the kit.
|
|
1040
|
+
*
|
|
925
1041
|
* @param profile - The operations and the rules of the language.
|
|
926
1042
|
* @param more - The parts of a machine that the language adds, with the paths of the entries that it replaces.
|
|
927
1043
|
* @returns The machine.
|
|
928
1044
|
* @throws {MergeError} The language and the kit have a conflict.
|
|
929
1045
|
*/
|
|
930
1046
|
export function formulaMachine(profile: FormulaProfile = {}, more: MachinePart = {}): Machine {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
more,
|
|
934
|
-
);
|
|
1047
|
+
const kit = kitOf(profile);
|
|
1048
|
+
return mergeMachines({ world: { kinds: formulaKinds() }, evaluators: { ...kit.evaluators }, domains: STD_DOMAINS, known: kit.known, facts: kit.facts }, more);
|
|
935
1049
|
}
|
package/src/index.ts
CHANGED
package/src/reference.ts
CHANGED
|
@@ -634,10 +634,13 @@ function plainValue(value: unknown, open: readonly object[]): Result {
|
|
|
634
634
|
|
|
635
635
|
/**
|
|
636
636
|
* TREE.LAZY, TREE.PARENT, TREE.SIBLING and TREE.ERROR: a node of an open tree gives its thing, and the root of a new
|
|
637
|
-
* tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
|
|
637
|
+
* tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error. The node
|
|
638
|
+
* reads `self` only through `resolve` (C4, TREE.SELF).
|
|
638
639
|
*/
|
|
639
|
-
const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath }) => {
|
|
640
|
-
const
|
|
640
|
+
const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath, resolve: walk }) => {
|
|
641
|
+
const reached = walk(["self"]);
|
|
642
|
+
const parent: Thing | undefined = reached.ok ? reached.thing : undefined;
|
|
643
|
+
const outer = refNodeOf(parent);
|
|
641
644
|
const nested = outer !== undefined && outer.tree.open;
|
|
642
645
|
const found = machine === undefined || typePath.length === 0 ? undefined : lookupKind(machine.world, typePath);
|
|
643
646
|
const contract = found?.ok === true ? found.kind.tree : undefined;
|
|
@@ -661,7 +664,7 @@ const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath
|
|
|
661
664
|
type: kind,
|
|
662
665
|
label: `node ${kind}`,
|
|
663
666
|
deref: (key): Thing | undefined => {
|
|
664
|
-
if (key === "parent") return
|
|
667
|
+
if (key === "parent") return parent;
|
|
665
668
|
if (key === "type") return kind;
|
|
666
669
|
const result = attributeOf(node, String(key));
|
|
667
670
|
if (result === undefined || result.ok) return result?.ok === true ? result.value : undefined;
|
package/src/static.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type Kind, type World, equationType } from "./world.ts";
|
|
2
|
+
import { type Kind, type Namespace, type World, type WorldEntry, equationType, isKind, isNamespace } from "./world.ts";
|
|
3
3
|
import { ask } from "./ladder.ts";
|
|
4
4
|
import { type PartSlot, partsOf, scopesOf } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
|
|
@@ -508,6 +508,74 @@ export function typeCheck(machine: Machine, doc: unknown, options: CheckOptions
|
|
|
508
508
|
return { type: pass.type, issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues], places: pass.places };
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
/**
|
|
512
|
+
* A problem of a world that `checkWorld` finds before a document exists (STATIC.COVERAGE). Each document of the
|
|
513
|
+
* kind gives this problem for the verb, thus `check` with the verb finds it too.
|
|
514
|
+
*/
|
|
515
|
+
export interface WorldIssue {
|
|
516
|
+
/** The code of the problem. `#NOEVAL`: no rung of the ladder gives an evaluator for the verb. */
|
|
517
|
+
readonly code: "#NOEVAL";
|
|
518
|
+
/** The keys of the kind from the root of the world. For a fallback kind, the keys of its namespace. */
|
|
519
|
+
readonly kind: readonly Key[];
|
|
520
|
+
/** `true` for the fallback kind of a namespace or of the world. */
|
|
521
|
+
readonly fallback?: true;
|
|
522
|
+
/** The verb without an evaluator. */
|
|
523
|
+
readonly verb: string;
|
|
524
|
+
readonly reason: string;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** This function tells if a table of verbs has an entry for a verb. */
|
|
528
|
+
function hasVerb(table: Readonly<Record<string, string>> | undefined, verb: string): boolean {
|
|
529
|
+
return table !== undefined && Object.hasOwn(table, verb);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
|
|
534
|
+
* verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
|
|
535
|
+
* evaluator for the verb, thus the check gives the same proof of coverage as a type system.
|
|
536
|
+
*
|
|
537
|
+
* The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
|
|
538
|
+
* world. It does not follow an alias, because the kind of an alias has its own place. The check reads only the world.
|
|
539
|
+
*
|
|
540
|
+
* @param machine - The machine, for its world.
|
|
541
|
+
* @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
|
|
542
|
+
* @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
|
|
543
|
+
* order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
|
|
544
|
+
*/
|
|
545
|
+
export function checkWorld(machine: Machine, verbs: readonly string[]): readonly WorldIssue[] {
|
|
546
|
+
const world = machine.world;
|
|
547
|
+
const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
|
|
548
|
+
const out: WorldIssue[] = [];
|
|
549
|
+
if (open.length === 0) return out;
|
|
550
|
+
const visit = (kind: Kind, path: readonly Key[], fallback: boolean): void => {
|
|
551
|
+
const text = path.join(".");
|
|
552
|
+
const name = fallback ? (path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}`) : `the kind ${text}`;
|
|
553
|
+
for (const verb of open) {
|
|
554
|
+
if (hasVerb(kind.verbs, verb)) continue;
|
|
555
|
+
const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
|
|
556
|
+
out.push(fallback ? { code: "#NOEVAL", kind: path, fallback: true, verb, reason } : { code: "#NOEVAL", kind: path, verb, reason });
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
// The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
|
|
560
|
+
const way = new Set<Namespace>();
|
|
561
|
+
const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
|
|
562
|
+
for (const key of Object.keys(kinds)) {
|
|
563
|
+
const entry = kinds[key];
|
|
564
|
+
const here = [...path, key];
|
|
565
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
566
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
567
|
+
way.add(entry);
|
|
568
|
+
entries(entry.kinds, here);
|
|
569
|
+
if (entry.fallback !== undefined) visit(entry.fallback, here, true);
|
|
570
|
+
way.delete(entry);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
entries(world.kinds, []);
|
|
575
|
+
if (world.fallback !== undefined) visit(world.fallback, [], true);
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
|
|
511
579
|
/**
|
|
512
580
|
* A runner under an evaluator in a check of a run. The run drives its steps without a wait, thus only a wait for an
|
|
513
581
|
* effect of its runners fails (RUN.DRIVE-NESTED).
|
package/src/stdlib.ts
CHANGED
|
@@ -71,6 +71,8 @@ export const STD_WORLD: World = {
|
|
|
71
71
|
// A node that nothing reads keeps its attributes without an evaluation (TREE.LAZY).
|
|
72
72
|
pure: true,
|
|
73
73
|
lazy: [{}],
|
|
74
|
+
// The code resolves self to find an open tree and the parent (C4, D44).
|
|
75
|
+
reads: ["self"],
|
|
74
76
|
summary: "A node of a tree. Each part is an attribute that evaluates on demand, one time, with the node as self.",
|
|
75
77
|
},
|
|
76
78
|
box: {
|
|
@@ -79,6 +81,7 @@ export const STD_WORLD: World = {
|
|
|
79
81
|
caps: [{ rule: "mask" }],
|
|
80
82
|
pure: true,
|
|
81
83
|
lazy: [{}],
|
|
84
|
+
reads: ["self"],
|
|
82
85
|
summary: "A node of a tree, as panel. A formula in a box can read its parent and its siblings.",
|
|
83
86
|
},
|
|
84
87
|
math: {
|
|
@@ -396,11 +399,12 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
396
399
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
397
400
|
* the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
|
|
398
401
|
* fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
|
|
402
|
+
* The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
|
|
399
403
|
*/
|
|
400
|
-
function treeThing(input: EvalInput, build: TreeBuild): HostThing {
|
|
404
|
+
function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
|
|
401
405
|
const { parts, context, kind: type, place, machine } = input;
|
|
402
406
|
const { tree, of } = contractOf(machine, input.typePath);
|
|
403
|
-
const above = stateOf(
|
|
407
|
+
const above = stateOf(outer);
|
|
404
408
|
// Only a node of the same tree is a parent for the rungs `parent` and `copy`.
|
|
405
409
|
const up = above !== undefined && above.build === build ? above : undefined;
|
|
406
410
|
const cells = new Map<string, Cell>();
|
|
@@ -522,7 +526,7 @@ function treeThing(input: EvalInput, build: TreeBuild): HostThing {
|
|
|
522
526
|
type,
|
|
523
527
|
label: `node ${type}`,
|
|
524
528
|
deref(key: Key): Thing | undefined {
|
|
525
|
-
if (key === "parent") return
|
|
529
|
+
if (key === "parent") return outer;
|
|
526
530
|
if (key === "type") return type;
|
|
527
531
|
const result = read(String(key));
|
|
528
532
|
if (result === undefined) return undefined;
|
|
@@ -556,15 +560,24 @@ export function attributeTrace(node: Thing, attribute: string): AttributeTrace |
|
|
|
556
560
|
return stateOf(node)?.trace(attribute);
|
|
557
561
|
}
|
|
558
562
|
|
|
563
|
+
/** The path of the root `self`. A node of the tree resolves it, thus the list of reads holds the read (C4). */
|
|
564
|
+
const SELF_PATH: readonly Json[] = /* @__PURE__ */ Object.freeze(["self"]);
|
|
565
|
+
|
|
566
|
+
/** The options of the resolution of `self`: the node needs no hops. */
|
|
567
|
+
const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
568
|
+
|
|
559
569
|
/**
|
|
560
570
|
* This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
|
|
561
|
-
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree.
|
|
571
|
+
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
|
|
572
|
+
* node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
|
|
562
573
|
*/
|
|
563
574
|
function treeNode(input: EvalInput): Result {
|
|
564
|
-
const
|
|
565
|
-
|
|
575
|
+
const found = input.resolve(SELF_PATH, NO_HOPS);
|
|
576
|
+
const outer = found.ok ? found.thing : undefined;
|
|
577
|
+
const above = stateOf(outer);
|
|
578
|
+
if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer));
|
|
566
579
|
const build: TreeBuild = { open: true };
|
|
567
|
-
const root = treeThing(input, build);
|
|
580
|
+
const root = treeThing(input, build, outer);
|
|
568
581
|
try {
|
|
569
582
|
return (stateOf(root) as NodeState).json(new Set());
|
|
570
583
|
} finally {
|
package/src/world.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, type TypeRef, field, isRecord, typePath } from "./doc.ts";
|
|
2
|
-
import type { Cap } from "./deref.ts";
|
|
2
|
+
import type { Cap, Root } from "./deref.ts";
|
|
3
3
|
import type { ValueType } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
/** The two sides of evaluation. `derive` is pure. `run` causes effects in order. */
|
|
@@ -114,11 +114,14 @@ export interface LazyRule {
|
|
|
114
114
|
* because the static facts do not see that read.
|
|
115
115
|
* - `volatile`: `true` when its result can change while each read gives the same thing, for example a clock.
|
|
116
116
|
* - `lazy`: the parts that an evaluation can skip and still give a value.
|
|
117
|
+
* - `reads`: the roots that the code of the kind resolves as a whole, with the path of one root. The static facts
|
|
118
|
+
* give a read of each root at each document of the kind (D44). The tree node reads `self` this way.
|
|
117
119
|
*/
|
|
118
120
|
export interface KindFacts {
|
|
119
121
|
readonly pure?: boolean;
|
|
120
122
|
readonly volatile?: boolean;
|
|
121
123
|
readonly lazy?: readonly LazyRule[];
|
|
124
|
+
readonly reads?: readonly Root[];
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/**
|