@mark1russell7/coeval 0.8.0 → 0.10.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 +2 -2
- package/build/index.d.ts +100 -16
- package/build/index.d.ts.map +1 -1
- package/build/index.js +295 -47
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/conform.ts +15 -7
- package/src/evaluate.ts +2 -0
- package/src/facts.ts +17 -3
- package/src/formula.ts +151 -19
- package/src/index.ts +4 -0
- package/src/providers.ts +4 -3
- package/src/reference.ts +7 -4
- package/src/static.ts +103 -17
- package/src/stdlib.ts +20 -7
- package/src/types.ts +4 -2
- package/src/world.ts +35 -2
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, effectsOfVerb, 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";
|
|
@@ -364,8 +364,11 @@ export interface CheckOptions {
|
|
|
364
364
|
readonly caps?: readonly Cap[];
|
|
365
365
|
/**
|
|
366
366
|
* The verb that goes up the ladder at each document, as in `prepare`. The default is `eval`. `#NOEVAL` and the hook
|
|
367
|
-
* `known` follow this verb. The
|
|
368
|
-
*
|
|
367
|
+
* `known` follow this verb. The type rule of an evaluator serves each verb.
|
|
368
|
+
*
|
|
369
|
+
* The check tests the effects that the contract declares for this verb (VERB.EFFECTS). A kind without a declaration
|
|
370
|
+
* for the verb gives no effect issue. The contract declares the value types of `eval` only, thus only `eval` checks
|
|
371
|
+
* them (VERB.CHECK).
|
|
369
372
|
*/
|
|
370
373
|
readonly verb?: string;
|
|
371
374
|
/**
|
|
@@ -408,16 +411,28 @@ const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
|
|
|
408
411
|
const NO_EFFECTS: readonly (readonly Key[])[] = /* @__PURE__ */ Object.freeze([]);
|
|
409
412
|
|
|
410
413
|
/**
|
|
411
|
-
* This function gives the effects that the contract of the kind of a visit declares, as keys
|
|
412
|
-
* text `"type"` gives the type of the document after each alias. A visit without a
|
|
413
|
-
* `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
414
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
|
|
415
|
+
* (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
|
|
416
|
+
* kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
414
417
|
*
|
|
415
418
|
* @param v - The visit.
|
|
419
|
+
* @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
|
|
416
420
|
* @returns The keys of each declared effect, in the order of the contract.
|
|
417
421
|
*/
|
|
418
|
-
export function declaredEffects(v: DocVisit): readonly (readonly Key[])[] {
|
|
419
|
-
|
|
420
|
-
|
|
422
|
+
export function declaredEffects(v: DocVisit, verb = "eval"): readonly (readonly Key[])[] {
|
|
423
|
+
return verbDeclared(v, verb) ?? NO_EFFECTS;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys. The result
|
|
428
|
+
* is `undefined` when the contract has no declaration for the verb (D49). A visit without a kind declares no effect
|
|
429
|
+
* for `eval` and no declaration for another verb.
|
|
430
|
+
*/
|
|
431
|
+
function verbDeclared(v: DocVisit, verb: string): readonly (readonly Key[])[] | undefined {
|
|
432
|
+
if (v.kind === undefined) return verb === "eval" ? NO_EFFECTS : undefined;
|
|
433
|
+
const effects = effectsOfVerb(v.kind, verb);
|
|
434
|
+
if (effects === undefined) return undefined;
|
|
435
|
+
if (effects.length === 0) return NO_EFFECTS;
|
|
421
436
|
return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
|
|
422
437
|
}
|
|
423
438
|
|
|
@@ -472,7 +487,9 @@ function capReason(v: DocVisit, kind: readonly Key[], allowed: (caps: readonly C
|
|
|
472
487
|
* A run does not always fail there, thus a type issue is a static finding, not an error of a run.
|
|
473
488
|
*
|
|
474
489
|
* The check asks the ladder for the verb of the options, `eval` by default (VERB.CHECK). The shapes, the paths and the
|
|
475
|
-
* names follow the contracts, thus they are the same for each verb.
|
|
490
|
+
* names follow the contracts, thus they are the same for each verb. The check tests the effects that each contract
|
|
491
|
+
* declares for the verb: `effects` for `eval`, and the entry of `verbEffects` for another verb (VERB.EFFECTS). A kind
|
|
492
|
+
* without a declaration for the verb gives no `#CAP!` and no `#CALC!` for an effect.
|
|
476
493
|
*
|
|
477
494
|
* @param machine - The world and the code of the evaluators.
|
|
478
495
|
* @param doc - The document, as JSON.
|
|
@@ -508,6 +525,74 @@ export function typeCheck(machine: Machine, doc: unknown, options: CheckOptions
|
|
|
508
525
|
return { type: pass.type, issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues], places: pass.places };
|
|
509
526
|
}
|
|
510
527
|
|
|
528
|
+
/**
|
|
529
|
+
* A problem of a world that `checkWorld` finds before a document exists (STATIC.COVERAGE). Each document of the
|
|
530
|
+
* kind gives this problem for the verb, thus `check` with the verb finds it too.
|
|
531
|
+
*/
|
|
532
|
+
export interface WorldIssue {
|
|
533
|
+
/** The code of the problem. `#NOEVAL`: no rung of the ladder gives an evaluator for the verb. */
|
|
534
|
+
readonly code: "#NOEVAL";
|
|
535
|
+
/** The keys of the kind from the root of the world. For a fallback kind, the keys of its namespace. */
|
|
536
|
+
readonly kind: readonly Key[];
|
|
537
|
+
/** `true` for the fallback kind of a namespace or of the world. */
|
|
538
|
+
readonly fallback?: true;
|
|
539
|
+
/** The verb without an evaluator. */
|
|
540
|
+
readonly verb: string;
|
|
541
|
+
readonly reason: string;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** This function tells if a table of verbs has an entry for a verb. */
|
|
545
|
+
function hasVerb(table: Readonly<Record<string, string>> | undefined, verb: string): boolean {
|
|
546
|
+
return table !== undefined && Object.hasOwn(table, verb);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
|
|
551
|
+
* verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
|
|
552
|
+
* evaluator for the verb, thus the check gives the same proof of coverage as a type system.
|
|
553
|
+
*
|
|
554
|
+
* The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
|
|
555
|
+
* world. It does not follow an alias, because the kind of an alias has its own place. The check reads only the world.
|
|
556
|
+
*
|
|
557
|
+
* @param machine - The machine, for its world.
|
|
558
|
+
* @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
|
|
559
|
+
* @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
|
|
560
|
+
* order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
|
|
561
|
+
*/
|
|
562
|
+
export function checkWorld(machine: Machine, verbs: readonly string[]): readonly WorldIssue[] {
|
|
563
|
+
const world = machine.world;
|
|
564
|
+
const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
|
|
565
|
+
const out: WorldIssue[] = [];
|
|
566
|
+
if (open.length === 0) return out;
|
|
567
|
+
const visit = (kind: Kind, path: readonly Key[], fallback: boolean): void => {
|
|
568
|
+
const text = path.join(".");
|
|
569
|
+
const name = fallback ? (path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}`) : `the kind ${text}`;
|
|
570
|
+
for (const verb of open) {
|
|
571
|
+
if (hasVerb(kind.verbs, verb)) continue;
|
|
572
|
+
const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
|
|
573
|
+
out.push(fallback ? { code: "#NOEVAL", kind: path, fallback: true, verb, reason } : { code: "#NOEVAL", kind: path, verb, reason });
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
// The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
|
|
577
|
+
const way = new Set<Namespace>();
|
|
578
|
+
const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
|
|
579
|
+
for (const key of Object.keys(kinds)) {
|
|
580
|
+
const entry = kinds[key];
|
|
581
|
+
const here = [...path, key];
|
|
582
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
583
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
584
|
+
way.add(entry);
|
|
585
|
+
entries(entry.kinds, here);
|
|
586
|
+
if (entry.fallback !== undefined) visit(entry.fallback, here, true);
|
|
587
|
+
way.delete(entry);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
entries(world.kinds, []);
|
|
592
|
+
if (world.fallback !== undefined) visit(world.fallback, [], true);
|
|
593
|
+
return out;
|
|
594
|
+
}
|
|
595
|
+
|
|
511
596
|
/**
|
|
512
597
|
* 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
598
|
* effect of its runners fails (RUN.DRIVE-NESTED).
|
|
@@ -583,9 +668,8 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
583
668
|
issues.push(issue);
|
|
584
669
|
if (current !== undefined) current.marked = true;
|
|
585
670
|
};
|
|
586
|
-
// The effects
|
|
587
|
-
// it happens (RUN.CAP).
|
|
588
|
-
const effects = verb === "eval";
|
|
671
|
+
// The field effects declares eval, and verbEffects declares the other verbs (VERB.EFFECTS). An effect of a verb
|
|
672
|
+
// without a declaration meets its test when it happens (RUN.CAP).
|
|
589
673
|
const allowed = options.caps === undefined ? undefined : permitsOf(machine);
|
|
590
674
|
// In a run, a part that can wait needs a parent that can wait (RUN.CAN-WAIT). The map tells for each document with
|
|
591
675
|
// code if its code passes a wait on: false for an evaluator, true for a runner whose waits reach the driver of the
|
|
@@ -636,7 +720,8 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
636
720
|
if (known !== true) push({ code: "#NAME?", place: v.place, reason: known, at: v.at });
|
|
637
721
|
}
|
|
638
722
|
const caps = v.caps;
|
|
639
|
-
const
|
|
723
|
+
const stated = verbDeclared(v, verb);
|
|
724
|
+
const declared = stated ?? NO_EFFECTS;
|
|
640
725
|
if (allowed !== undefined && caps !== undefined) {
|
|
641
726
|
for (const kind of declared) {
|
|
642
727
|
if (!allowed(caps, kind)) push({ code: "#CAP!", place: v.place, reason: capReason(v, kind, allowed), at: v.at });
|
|
@@ -650,16 +735,17 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
650
735
|
// The run drives a runner under an evaluator, and only a real wait fails (RUN.DRIVE-NESTED). Its issue goes in
|
|
651
736
|
// here, in the order of the document, when an effect of its runners can wait.
|
|
652
737
|
const issue: Issue = { code: "#CALC!", place: v.place, reason: waitedReason(typeName(v.doc.type)), at: v.at };
|
|
653
|
-
chain = { issue, index: issues.length, node: current, wait:
|
|
738
|
+
chain = { issue, index: issues.length, node: current, wait: false };
|
|
654
739
|
drives.push(chain);
|
|
655
740
|
} else if (runner) {
|
|
656
741
|
chain = up ?? true;
|
|
657
742
|
}
|
|
658
743
|
waits.set(v, chain);
|
|
659
|
-
// A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait.
|
|
744
|
+
// A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait. A
|
|
745
|
+
// runner without a declaration for the verb can cause each effect, thus it can wait.
|
|
660
746
|
if (typeof chain === "object" && !chain.wait) {
|
|
661
747
|
const rule = allowed ?? permits;
|
|
662
|
-
chain.wait = declared.some((kind) => caps === undefined || rule(caps, kind));
|
|
748
|
+
chain.wait = stated === undefined || declared.some((kind) => caps === undefined || rule(caps, kind));
|
|
663
749
|
}
|
|
664
750
|
// In a run, the host does each effect, thus an evaluator cannot cause one.
|
|
665
751
|
const first = declared[0];
|
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/types.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { type Json, type Key, docEquals, isEnvelope, isRecord } from "./doc.ts";
|
|
|
9
9
|
/**
|
|
10
10
|
* A value type: a set of values that a result can have, as a JSON term.
|
|
11
11
|
*
|
|
12
|
-
* - A text names a base type: `unknown`, `never`, a domain of the kernel (`null`, `bool`, `number`,
|
|
13
|
-
* domain of a language.
|
|
12
|
+
* - A text names a base type: `unknown`, `never`, a domain of the kernel (`null`, `undefined`, `bool`, `number`,
|
|
13
|
+
* `text`), or a domain of a language.
|
|
14
14
|
* - `list`: a list whose items have the type.
|
|
15
15
|
* - `record`: a plain record with the named fields. `optional` names the fields that can be absent. A `closed`
|
|
16
16
|
* record has no other fields. The other fields of an open record can hold each value.
|
|
@@ -95,6 +95,8 @@ export interface TypeIssue {
|
|
|
95
95
|
*/
|
|
96
96
|
export const STD_DOMAINS: Readonly<Record<string, Domain>> = {
|
|
97
97
|
null: { is: (value) => value === null, summary: "The value null." },
|
|
98
|
+
// JSON has no undefined, thus a document holds it as ext.undefined (D48). The domain has no codec: the codec of values writes undefined first.
|
|
99
|
+
undefined: { is: (value) => value === undefined, summary: "The value undefined. A document holds it as the extension ext.undefined." },
|
|
98
100
|
bool: { is: (value) => typeof value === "boolean", summary: "A truth value: true or false." },
|
|
99
101
|
number: { is: (value) => typeof value === "number", summary: "A number of the host, also NaN, an infinity and -0." },
|
|
100
102
|
text: { is: (value) => typeof value === "string", summary: "A text." },
|
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
|
/**
|
|
@@ -169,7 +172,8 @@ export function equationType(kind: Kind): string | undefined {
|
|
|
169
172
|
/**
|
|
170
173
|
* A kind gives the name of an evaluator for each verb, and the contract of its value. The contract serves each verb.
|
|
171
174
|
* For each verb, the kernel splits the parts and narrows their capabilities by it, and `check` reads its scope rules.
|
|
172
|
-
* `side` and `effects` tell what the verb `eval` does (VERB.CONTRACT).
|
|
175
|
+
* `side` and `effects` tell what the verb `eval` does (VERB.CONTRACT). `verbEffects` tells the effects of the other
|
|
176
|
+
* verbs (VERB.EFFECTS).
|
|
173
177
|
*/
|
|
174
178
|
export interface Kind extends KindFacts {
|
|
175
179
|
/**
|
|
@@ -197,6 +201,13 @@ export interface Kind extends KindFacts {
|
|
|
197
201
|
* (CAP.LATENT).
|
|
198
202
|
*/
|
|
199
203
|
readonly effects?: "type" | readonly string[];
|
|
204
|
+
/**
|
|
205
|
+
* The effects that the other verbs of a document of this kind can cause, by verb, in the form of `effects`
|
|
206
|
+
* (VERB.EFFECTS, D49). An entry `view: []` declares no effect for `view`. A verb without an entry has no
|
|
207
|
+
* declaration, thus `check` does not test its effects. The field `effects` declares `eval`, thus this field has no
|
|
208
|
+
* entry `eval`.
|
|
209
|
+
*/
|
|
210
|
+
readonly verbEffects?: VerbEffects;
|
|
200
211
|
/**
|
|
201
212
|
* The value type of the result of the verb `eval`, when it is fixed (TYPE.SYNTH). A check of types gives it to each
|
|
202
213
|
* document of the kind, in the place of the type that the rule of the evaluator synthesizes.
|
|
@@ -211,6 +222,28 @@ export interface Kind extends KindFacts {
|
|
|
211
222
|
readonly summary?: string;
|
|
212
223
|
}
|
|
213
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The effects that a contract declares for the verbs other than `eval`, by verb (D49). Each entry has the form of the
|
|
227
|
+
* field `effects` of a kind. The type has no entry `eval`, because the field `effects` declares `eval`.
|
|
228
|
+
*/
|
|
229
|
+
export type VerbEffects = Readonly<Record<string, "type" | readonly string[]>> & { readonly eval?: never };
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* This function gives the effects that the contract of a kind declares for a verb (VERB.EFFECTS, D49). For `eval`,
|
|
233
|
+
* the field `effects` declares them, and a kind without the field declares no effect. For another verb, the entry of
|
|
234
|
+
* `verbEffects` declares them. `check` and `conforms` read the declarations with this one function.
|
|
235
|
+
*
|
|
236
|
+
* @param kind - The kind.
|
|
237
|
+
* @param verb - The verb.
|
|
238
|
+
* @returns The declaration in the form of `effects`, or `undefined` when the contract does not declare the effects of
|
|
239
|
+
* the verb.
|
|
240
|
+
*/
|
|
241
|
+
export function effectsOfVerb(kind: Kind, verb: string): "type" | readonly string[] | undefined {
|
|
242
|
+
if (verb === "eval") return kind.effects ?? [];
|
|
243
|
+
const table = kind.verbEffects as Readonly<Record<string, "type" | readonly string[]>> | undefined;
|
|
244
|
+
return table !== undefined && Object.hasOwn(table, verb) ? table[verb] : undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
214
247
|
/** A namespace: a group of entries under one key of the world. `fallback` is the kind of each other key in it. */
|
|
215
248
|
export interface Namespace {
|
|
216
249
|
readonly kinds: Readonly<Record<string, WorldEntry>>;
|