@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/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 contract declares the effects and the value types of `eval` only, thus only `eval`
368
- * checks them (VERB.CHECK). The type rule of an evaluator serves each verb.
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 (FACTS.EFFECTS). The
412
- * text `"type"` gives the type of the document after each alias. A visit without a kind declares no effect. `check`,
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
- const effects = v.kind?.effects;
420
- if (effects === undefined || effects.length === 0) return NO_EFFECTS;
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. Only `eval` checks the declared effects.
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 of a contract are the effects of eval (VERB.CONTRACT). An effect of another verb meets its test when
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 declared = effects ? declaredEffects(v) : NO_EFFECTS;
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: !effects };
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(context.self);
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 context.self;
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 above = stateOf(input.context.self);
565
- if (above !== undefined && above.build.open) return ok(treeThing(input, above.build));
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`, `text`), or a
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>>;