@mark1russell7/coeval 0.18.0 → 0.19.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 +4 -4
- package/build/index.d.ts +263 -20
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2686 -1379
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codec.ts +30 -10
- package/src/durable.ts +17 -5
- package/src/facts.ts +55 -19
- package/src/formula.ts +45 -1
- package/src/index.ts +16 -2
- package/src/instances.ts +637 -0
- package/src/reads.ts +186 -2
- package/src/static.ts +357 -50
- package/src/stdlib.ts +127 -6
- package/src/types.ts +6 -2
- package/src/typing.ts +143 -3
- package/src/world.ts +49 -0
package/src/stdlib.ts
CHANGED
|
@@ -236,7 +236,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
|
|
|
236
236
|
const found = resolve(data as readonly Json[]);
|
|
237
237
|
return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
|
|
238
238
|
},
|
|
239
|
-
"tree.node": (input) => treeNode(input),
|
|
239
|
+
"tree.node": (input) => treeNode(input, undefined),
|
|
240
240
|
};
|
|
241
241
|
|
|
242
242
|
/*
|
|
@@ -316,6 +316,57 @@ interface NodeState {
|
|
|
316
316
|
json(path: Set<object>): Result;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
/** The answer of the ladder for one attribute of one node (TREE.CELL, D108). */
|
|
320
|
+
export interface AttributeAnswer {
|
|
321
|
+
/** The result of the attribute. It is `undefined` when no rung answered. */
|
|
322
|
+
readonly result: Result | undefined;
|
|
323
|
+
/** The rung that answered. It is `undefined` when no rung answered. */
|
|
324
|
+
readonly by: AttributeRung | undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* This function gives the memo cell of one attribute of one node, for a host that keeps its cells between evaluations
|
|
329
|
+
* (TREE.CELL, D108). The kit uses it at each demand of an attribute in an evaluation.
|
|
330
|
+
*
|
|
331
|
+
* The array `place` is one object for each document of a plan, and it stays the same across the evaluations of that
|
|
332
|
+
* plan. A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
|
|
333
|
+
* A host that keeps its cells by the keys of `place` can give a stale answer after a change of the document.
|
|
334
|
+
*
|
|
335
|
+
* The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
|
|
336
|
+
* of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
|
|
337
|
+
* a cell that computes again uses the last one.
|
|
338
|
+
*
|
|
339
|
+
* The kit marks an attribute in evaluation before it uses the cell. Thus a cycle gives `#CYCLE!` before it reaches the
|
|
340
|
+
* cell again. An exception of the function that `compute` did not throw gives `#CYCLE!` with the chain of the demands.
|
|
341
|
+
*
|
|
342
|
+
* @param place - The keys from the root document to the document of the node.
|
|
343
|
+
* @param attribute - The name of the attribute.
|
|
344
|
+
* @param compute - The function that visits the rungs of the ladder.
|
|
345
|
+
* @returns The answer.
|
|
346
|
+
*/
|
|
347
|
+
export type AttributeCell = (place: readonly Key[], attribute: string, compute: () => AttributeAnswer) => AttributeAnswer;
|
|
348
|
+
|
|
349
|
+
/** The options of the tree kit (D108). */
|
|
350
|
+
export interface TreeOptions {
|
|
351
|
+
/**
|
|
352
|
+
* The memo cells of the host (TREE.CELL). The default is a plain memo for each node and attribute, for one
|
|
353
|
+
* evaluation (TREE.MEMO).
|
|
354
|
+
*/
|
|
355
|
+
readonly cell?: AttributeCell;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* This function gives the code of the kind of a node of the decorated tree, with the options of the host. A host puts
|
|
360
|
+
* the code at the name `tree.node` of its machine. Without a cell, the code is the standard code.
|
|
361
|
+
*
|
|
362
|
+
* @param options - The options, for example the memo cells of the host.
|
|
363
|
+
* @returns The code.
|
|
364
|
+
*/
|
|
365
|
+
export function treeNodeEvaluator(options: TreeOptions = {}): Evaluator {
|
|
366
|
+
const host = options.cell;
|
|
367
|
+
return host === undefined ? (STD_EVALUATORS["tree.node"] as Evaluator) : (input: EvalInput) => treeNode(input, host);
|
|
368
|
+
}
|
|
369
|
+
|
|
319
370
|
/**
|
|
320
371
|
* The memo cell of one attribute: in evaluation, or done with its result and the rung that answered. A done cell
|
|
321
372
|
* without a result tells that no rung answered.
|
|
@@ -373,6 +424,31 @@ function contractOf(machine: Machine | undefined, typePath: readonly Key[]): Nod
|
|
|
373
424
|
|
|
374
425
|
const BUSY: Cell = /* @__PURE__ */ Object.freeze({ busy: true });
|
|
375
426
|
|
|
427
|
+
/** The answer of the ladder when no rung answers. */
|
|
428
|
+
const NO_ANSWER: AttributeAnswer = /* @__PURE__ */ Object.freeze({ result: undefined, by: undefined });
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* The marks of the attributes in evaluation, for each place of a plan, with the cells of a host (TREE.CELL). Nodes of
|
|
432
|
+
* two evaluations share their place and their kept cells, thus they share their marks.
|
|
433
|
+
*/
|
|
434
|
+
const marksByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], Map<string, Cell>>();
|
|
435
|
+
|
|
436
|
+
/** The trees of the cells of a host, for each place of a root in a plan. Nodes of two evaluations stay in one tree. */
|
|
437
|
+
const buildsByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], TreeBuild>();
|
|
438
|
+
|
|
439
|
+
/** This function gives the value of a weak map, or puts a new value in it. */
|
|
440
|
+
function keptAt<T>(map: WeakMap<readonly Key[], T>, place: readonly Key[], make: () => T): T {
|
|
441
|
+
let value = map.get(place);
|
|
442
|
+
if (value === undefined) {
|
|
443
|
+
value = make();
|
|
444
|
+
map.set(place, value);
|
|
445
|
+
}
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** A value that no exception has. */
|
|
450
|
+
const NOT_THROWN: unique symbol = /* @__PURE__ */ Symbol("coeval.tree.not-thrown") as never;
|
|
451
|
+
|
|
376
452
|
/** One attribute in evaluation. The text names it in the reason of a cycle. */
|
|
377
453
|
interface Demand {
|
|
378
454
|
readonly cells: Map<string, Cell>;
|
|
@@ -451,8 +527,10 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
451
527
|
* the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
|
|
452
528
|
* fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
|
|
453
529
|
* The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
|
|
530
|
+
*
|
|
531
|
+
* The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
|
|
454
532
|
*/
|
|
455
|
-
function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
|
|
533
|
+
function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined, host: AttributeCell | undefined): HostThing {
|
|
456
534
|
const { parts, context, kind: type, place, machine } = input;
|
|
457
535
|
// The equations evaluate in the evaluation that made the node, thus their reads go in its list (READS.EQUATION).
|
|
458
536
|
const runtime = (input as InputWithRuntime)[RUNTIME];
|
|
@@ -461,6 +539,8 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
|
|
|
461
539
|
// Only a node of the same tree is a parent for the rungs `parent` and `copy`.
|
|
462
540
|
const up = above !== undefined && above.build === build ? above : undefined;
|
|
463
541
|
const cells = new Map<string, Cell>();
|
|
542
|
+
// With the cells of a host, the marks of a place serve each node of the place (TREE.CELL).
|
|
543
|
+
const marks = host === undefined ? cells : keptAt(marksByPlace, place, () => new Map<string, Cell>());
|
|
464
544
|
/** The positions of the items of the list `children`, for the names of the children. */
|
|
465
545
|
let positions: { readonly list: readonly unknown[]; readonly at: Map<unknown, number> } | undefined;
|
|
466
546
|
|
|
@@ -505,7 +585,45 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
|
|
|
505
585
|
return undefined;
|
|
506
586
|
};
|
|
507
587
|
|
|
588
|
+
/**
|
|
589
|
+
* This function gives an attribute through the cell of the host (TREE.CELL). The cell is the memo. The node keeps
|
|
590
|
+
* the last answer only for `has` and `trace`.
|
|
591
|
+
*/
|
|
592
|
+
const through =
|
|
593
|
+
host === undefined
|
|
594
|
+
? undefined
|
|
595
|
+
: (key: string): Result | undefined => {
|
|
596
|
+
const text = attributeText(key, type, place);
|
|
597
|
+
if (marks.has(key)) return cycleAt(marks, key, text);
|
|
598
|
+
if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
|
|
599
|
+
marks.set(key, BUSY);
|
|
600
|
+
demands.push({ cells: marks, key, text });
|
|
601
|
+
let thrown: unknown = NOT_THROWN;
|
|
602
|
+
let answer: AttributeAnswer;
|
|
603
|
+
try {
|
|
604
|
+
answer = host(place, key, () => {
|
|
605
|
+
try {
|
|
606
|
+
return climb(key) ?? NO_ANSWER;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
thrown = error;
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
} catch (error) {
|
|
613
|
+
// An exception of the kit or of the stack goes on, as without a host. Another exception is a cycle of the host.
|
|
614
|
+
if (error === thrown) throw error;
|
|
615
|
+
const chain = demands.map((demand) => demand.text).join(" -> ");
|
|
616
|
+
answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
|
|
617
|
+
} finally {
|
|
618
|
+
demands.pop();
|
|
619
|
+
marks.delete(key);
|
|
620
|
+
}
|
|
621
|
+
cells.set(key, { busy: false, result: answer.result, by: answer.by });
|
|
622
|
+
return answer.result;
|
|
623
|
+
};
|
|
624
|
+
|
|
508
625
|
const read = (key: string): Result | undefined => {
|
|
626
|
+
if (through !== undefined) return through(key);
|
|
509
627
|
const cell = cells.get(key);
|
|
510
628
|
if (cell !== undefined) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
|
|
511
629
|
// A node without a contract has only its parts, as before D42. Thus a missing part needs no cell.
|
|
@@ -635,14 +753,17 @@ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
|
635
753
|
* This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
|
|
636
754
|
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
|
|
637
755
|
* node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
|
|
756
|
+
* The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of each node.
|
|
638
757
|
*/
|
|
639
|
-
function treeNode(input: EvalInput): Result {
|
|
758
|
+
function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
|
|
640
759
|
const found = input.resolve(SELF_PATH, NO_HOPS);
|
|
641
760
|
const outer = found.ok ? found.thing : undefined;
|
|
642
761
|
const above = stateOf(outer);
|
|
643
|
-
if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer));
|
|
644
|
-
|
|
645
|
-
const
|
|
762
|
+
if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer, host));
|
|
763
|
+
// With the cells of a host, a kept value can hold a node of an earlier evaluation. Thus that node stays in the tree.
|
|
764
|
+
const build: TreeBuild = host === undefined ? { open: true } : keptAt(buildsByPlace, input.place, () => ({ open: true }));
|
|
765
|
+
build.open = true;
|
|
766
|
+
const root = treeThing(input, build, outer, host);
|
|
646
767
|
try {
|
|
647
768
|
return (stateOf(root) as NodeState).json(new Set());
|
|
648
769
|
} finally {
|
package/src/types.ts
CHANGED
|
@@ -205,8 +205,12 @@ function formOf(type: unknown, domains: Readonly<Record<string, Domain>> | undef
|
|
|
205
205
|
return { t: "record", fields: fields as Readonly<Record<string, ValueType>>, optional: new Set(names), closed };
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
/**
|
|
209
|
-
function
|
|
208
|
+
/**
|
|
209
|
+
* This function gives each base type that holds the members of a base type: the type itself and each parent, through
|
|
210
|
+
* each level. A cycle of parents ends at a name that the line has, and a parent that is not a domain ends its branch.
|
|
211
|
+
* The function is internal: `encodeValue` uses it too (D109).
|
|
212
|
+
*/
|
|
213
|
+
export function lineOf(name: string, domains: Readonly<Record<string, Domain>> | undefined): ReadonlySet<string> {
|
|
210
214
|
const seen = new Set<string>([name]);
|
|
211
215
|
const queue = [name];
|
|
212
216
|
while (queue.length > 0) {
|
package/src/typing.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type Envelope, type Key, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import type { Machine } from "./evaluate.ts";
|
|
3
|
-
import type { DocVisit, Issue } from "./static.ts";
|
|
4
|
-
import { type ValueType, fits, isUnknown, typeIssues, typeText } from "./types.ts";
|
|
3
|
+
import type { DocVisit, Issue, WorldIssue } from "./static.ts";
|
|
4
|
+
import { type ValueType, domainOf, fits, isUnknown, typeIssues, typeText } from "./types.ts";
|
|
5
|
+
import { type Kind, type Shape, eachKind, kindAt } from "./world.ts";
|
|
5
6
|
|
|
6
7
|
/*
|
|
7
8
|
* The check of value types (R5 P3, D25). The pass reads the documents that the walk of `check` visited. It goes down
|
|
@@ -133,8 +134,36 @@ export interface TypeRule {
|
|
|
133
134
|
* @returns The type of the document.
|
|
134
135
|
*/
|
|
135
136
|
readonly synth: (input: TypeInput, report: (problem: TypeProblem) => void) => ValueType;
|
|
137
|
+
/**
|
|
138
|
+
* This function lists the declarations that the rule reads, for example the signatures of a formula profile
|
|
139
|
+
* (TYPE.WORLD, D110). `checkWorld` with the option `types` examines each one before a document exists. A rule
|
|
140
|
+
* without this function declares nothing.
|
|
141
|
+
*
|
|
142
|
+
* @returns The value types that the rule reads, and the problems that the rule finds in its declarations.
|
|
143
|
+
*/
|
|
144
|
+
readonly declared?: () => readonly Declared[];
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
/**
|
|
148
|
+
* One declaration of a type rule (TYPE.WORLD, D110). It is a value type that the rule reads, with what it is. Or it is
|
|
149
|
+
* a problem that the rule finds in a declaration, for example an overload that the check does not select at any time.
|
|
150
|
+
*/
|
|
151
|
+
export type Declared =
|
|
152
|
+
| {
|
|
153
|
+
/** The keys of the declaration in the declarations of the rule, for example `["sigs", "add", "to"]`. */
|
|
154
|
+
readonly at: readonly Key[];
|
|
155
|
+
/** The value type. `checkWorld` examines it with `typeIssues`. */
|
|
156
|
+
readonly type: unknown;
|
|
157
|
+
/** What the type is, for the reason of an issue, for example "result of a signature of ops.add". */
|
|
158
|
+
readonly what: string;
|
|
159
|
+
}
|
|
160
|
+
| {
|
|
161
|
+
/** The keys of the declaration in the declarations of the rule. */
|
|
162
|
+
readonly at: readonly Key[];
|
|
163
|
+
/** The reason of the problem. */
|
|
164
|
+
readonly problem: string;
|
|
165
|
+
};
|
|
166
|
+
|
|
138
167
|
/** The type of one document of a check of types. */
|
|
139
168
|
export interface Typed {
|
|
140
169
|
/** The keys from the root document to the document. */
|
|
@@ -206,7 +235,7 @@ function pathKey(path: readonly Key[]): string {
|
|
|
206
235
|
}
|
|
207
236
|
|
|
208
237
|
/** This function gives the type rule of an evaluator name. A runner hides the evaluator of its name, and a runner has no rule yet (K29). */
|
|
209
|
-
function ruleOf(machine: Machine, name: string | undefined): TypeRule | undefined {
|
|
238
|
+
export function ruleOf(machine: Machine, name: string | undefined): TypeRule | undefined {
|
|
210
239
|
if (name === undefined || (machine.runners !== undefined && Object.hasOwn(machine.runners, name))) return undefined;
|
|
211
240
|
const evaluator = Object.hasOwn(machine.evaluators, name) ? machine.evaluators[name] : undefined;
|
|
212
241
|
const rule = typeof evaluator === "object" && evaluator !== null ? (evaluator as { readonly type?: unknown }).type : undefined;
|
|
@@ -388,3 +417,114 @@ export function typePass(machine: Machine, nodes: readonly TypeNode[], verb: str
|
|
|
388
417
|
const issues = found.sort((a, b) => a.index - b.index).map((item) => item.issue);
|
|
389
418
|
return { type: types[0] ?? "unknown", issues, places };
|
|
390
419
|
}
|
|
420
|
+
|
|
421
|
+
/** The owner of a value type of a machine: a kind, a domain or a type rule. */
|
|
422
|
+
type Owner = Pick<WorldIssue, "kind" | "fallback" | "domain" | "evaluator">;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* This function examines each value type of a machine before a document exists (TYPE.WORLD, D110). It reads the
|
|
426
|
+
* `result` and each `expect` of each kind, the parents and the steps of each domain, and the declarations of each type
|
|
427
|
+
* rule. Each problem of `typeIssues` gives one issue with the keys of the bad term.
|
|
428
|
+
*
|
|
429
|
+
* A term shape with `kinds` and `expect` also gets an issue for each kind of its list whose `result` does not fit
|
|
430
|
+
* `expect`. A check of types gives `#TYPE!` at each document of that kind there.
|
|
431
|
+
*
|
|
432
|
+
* @param machine - The machine.
|
|
433
|
+
* @returns The issues: the kinds in the order of the world, then the domains, then the type rules.
|
|
434
|
+
*/
|
|
435
|
+
export function machineTypeIssues(machine: Machine): readonly WorldIssue[] {
|
|
436
|
+
const domains = machine.domains;
|
|
437
|
+
const world = machine.world;
|
|
438
|
+
const out: WorldIssue[] = [];
|
|
439
|
+
const add = (owner: Owner, at: readonly Key[], reason: string): void => {
|
|
440
|
+
out.push({ code: "#TYPE!", ...owner, verb: "eval", at, reason });
|
|
441
|
+
};
|
|
442
|
+
/** This function examines one value type. It gives `true` when the type has no problem. */
|
|
443
|
+
const examine = (owner: Owner, at: readonly Key[], type: unknown, what: string): boolean => {
|
|
444
|
+
const problems = typeIssues(type, domains);
|
|
445
|
+
for (const problem of problems) add(owner, [...at, ...problem.at], `The ${what} is not a correct value type. ${problem.reason}`);
|
|
446
|
+
return problems.length === 0;
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* This function finds each kind of the list `kinds` of a term whose result does not fit the `expect` of the term.
|
|
450
|
+
* Only a kind at the path itself counts. A path through an alias or to a fallback kind gives no issue.
|
|
451
|
+
*/
|
|
452
|
+
const results = (owner: Owner, kinds: readonly (readonly Key[])[], at: readonly Key[], expect: ValueType): void => {
|
|
453
|
+
if (isUnknown(expect, domains)) return;
|
|
454
|
+
kinds.forEach((path, index) => {
|
|
455
|
+
const found: Kind | undefined = Array.isArray(path) ? kindAt(world, path) : undefined;
|
|
456
|
+
const result = found?.result;
|
|
457
|
+
if (result === undefined || typeIssues(result, domains).length > 0 || fits(result, expect, domains)) return;
|
|
458
|
+
const text = path.join(".");
|
|
459
|
+
add(owner, [...at, "kinds", index], `The kind ${text} has the result ${typeText(result)}, and the part needs the type ${typeText(expect)}. Each document of ${text} here gets #TYPE!.`);
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
eachKind(world, (kind, path, fallback) => {
|
|
464
|
+
const owner: Owner = fallback ? { kind: path, fallback: true } : { kind: path };
|
|
465
|
+
const name = fallback ? (path.length === 0 ? "fallback kind of the world" : `fallback kind of ${path.join(".")}`) : `kind ${path.join(".")}`;
|
|
466
|
+
if (kind.result !== undefined) examine(owner, ["result"], kind.result, `result of the ${name}`);
|
|
467
|
+
// The set holds the shapes on the way, thus a shape that holds itself does not loop.
|
|
468
|
+
const way = new Set<Shape>();
|
|
469
|
+
const shape = (spec: Shape | undefined, at: readonly Key[]): void => {
|
|
470
|
+
if (typeof spec !== "object" || spec === null || way.has(spec)) return;
|
|
471
|
+
way.add(spec);
|
|
472
|
+
const expect = (spec as { readonly expect?: unknown }).expect;
|
|
473
|
+
const where = [...at, "expect"];
|
|
474
|
+
if (expect !== undefined && examine(owner, where, expect, `expected type at ${where.join(".")} of the ${name}`) && spec.shape === "term" && Array.isArray(spec.kinds)) {
|
|
475
|
+
results(owner, spec.kinds, at, expect as ValueType);
|
|
476
|
+
}
|
|
477
|
+
if (spec.shape === "list" || spec.shape === "map") shape(spec.item, [...at, "item"]);
|
|
478
|
+
if (spec.shape === "record") {
|
|
479
|
+
const fields: unknown = spec.fields;
|
|
480
|
+
if (typeof fields === "object" && fields !== null && !Array.isArray(fields)) {
|
|
481
|
+
for (const key of Object.keys(fields)) shape((fields as Readonly<Record<string, Shape>>)[key], [...at, "fields", key]);
|
|
482
|
+
}
|
|
483
|
+
shape(spec.rest, [...at, "rest"]);
|
|
484
|
+
}
|
|
485
|
+
way.delete(spec);
|
|
486
|
+
};
|
|
487
|
+
shape(kind.parts, ["parts"]);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const table = domains ?? {};
|
|
491
|
+
for (const name of Object.keys(table)) {
|
|
492
|
+
// A table entry with the name of the top or the bottom has no effect (TYPE.OPEN).
|
|
493
|
+
if (name === "unknown" || name === "never") continue;
|
|
494
|
+
const owner: Owner = { kind: [], domain: name };
|
|
495
|
+
const domain = domainOf(name, domains);
|
|
496
|
+
if (domain === undefined) {
|
|
497
|
+
add(owner, ["is"], `The domain ${name} has no test is. Each use of the name ${name} reads as unknown.`);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const under: unknown = domain.under;
|
|
501
|
+
if (Array.isArray(under)) {
|
|
502
|
+
under.forEach((parent: unknown, index) => {
|
|
503
|
+
if (parent === "unknown" || parent === "never") return;
|
|
504
|
+
if (typeof parent !== "string" || domainOf(parent, domains) === undefined) add(owner, ["under", index], `The parent ${String(parent)} of the domain ${name} is not a domain of the machine.`);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const steps: unknown = domain.steps;
|
|
508
|
+
if (typeof steps === "object" && steps !== null) {
|
|
509
|
+
for (const key of Object.keys(steps)) examine(owner, ["steps", key], (steps as Readonly<Record<string, unknown>>)[key], `type of the step ${key} of the domain ${name}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
for (const name of Object.keys(machine.evaluators)) {
|
|
514
|
+
const declared = ruleOf(machine, name)?.declared;
|
|
515
|
+
if (typeof declared !== "function") continue;
|
|
516
|
+
const owner: Owner = { kind: [], evaluator: name };
|
|
517
|
+
let list: readonly Declared[];
|
|
518
|
+
try {
|
|
519
|
+
list = declared();
|
|
520
|
+
} catch (error) {
|
|
521
|
+
add(owner, [], `The type rule of ${name} fails: ${message(error)}`);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
for (const item of list) {
|
|
525
|
+
if ("problem" in item) add(owner, item.at, item.problem);
|
|
526
|
+
else examine(owner, item.at, item.type, item.what);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return out;
|
|
530
|
+
}
|
package/src/world.ts
CHANGED
|
@@ -323,6 +323,55 @@ export function isNamespace(entry: WorldEntry | undefined): entry is Namespace {
|
|
|
323
323
|
return entry !== undefined && !isKind(entry) && !isAlias(entry) && Object.hasOwn(entry, "kinds");
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
/**
|
|
327
|
+
* This function visits each kind of a world. These are the kinds of each namespace, at each depth, and the fallback
|
|
328
|
+
* kind of each namespace and of the world. It does not follow an alias, because the kind of an alias has its own place. A
|
|
329
|
+
* fallback kind has the keys of its namespace. It comes after the entries of that namespace. `checkWorld` reads the
|
|
330
|
+
* kinds with this one function.
|
|
331
|
+
*
|
|
332
|
+
* @param world - The world.
|
|
333
|
+
* @param visit - The function that gets each kind, its keys from the root of the world, and `true` for a fallback kind.
|
|
334
|
+
*/
|
|
335
|
+
export function eachKind(world: World, visit: (kind: Kind, path: readonly Key[], fallback: boolean) => void): void {
|
|
336
|
+
// The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
|
|
337
|
+
const way = new Set<Namespace>();
|
|
338
|
+
const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
|
|
339
|
+
for (const key of Object.keys(kinds)) {
|
|
340
|
+
const entry = kinds[key];
|
|
341
|
+
const here = [...path, key];
|
|
342
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
343
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
344
|
+
way.add(entry);
|
|
345
|
+
entries(entry.kinds, here);
|
|
346
|
+
if (entry.fallback !== undefined) visit(entry.fallback, here, true);
|
|
347
|
+
way.delete(entry);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
entries(world.kinds, []);
|
|
352
|
+
if (world.fallback !== undefined) visit(world.fallback, [], true);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* This function gives the kind at a path of a world, without an alias and without a fallback kind. A namespace on the
|
|
357
|
+
* way gives its entries.
|
|
358
|
+
*
|
|
359
|
+
* @param world - The world.
|
|
360
|
+
* @param path - The keys from the root of the world.
|
|
361
|
+
* @returns The kind, or `undefined` when no kind is at the path.
|
|
362
|
+
*/
|
|
363
|
+
export function kindAt(world: World, path: readonly Key[]): Kind | undefined {
|
|
364
|
+
let entries = world.kinds;
|
|
365
|
+
let entry: WorldEntry | undefined;
|
|
366
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
367
|
+
entry = entryAt(entries, path[index] as Key);
|
|
368
|
+
if (index === path.length - 1) break;
|
|
369
|
+
if (!isNamespace(entry)) return undefined;
|
|
370
|
+
entries = entry.kinds;
|
|
371
|
+
}
|
|
372
|
+
return isKind(entry) ? entry : undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
326
375
|
function entryAt(entries: Readonly<Record<string, WorldEntry>>, key: Key): WorldEntry | undefined {
|
|
327
376
|
const name = String(key);
|
|
328
377
|
return Object.hasOwn(entries, name) ? entries[name] : undefined;
|