@mark1russell7/coeval 0.18.0 → 0.20.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 +307 -20
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3362 -1537
- package/build/index.js.map +1 -1
- package/package.json +3 -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 +20 -2
- package/src/instances.ts +665 -0
- package/src/reads.ts +186 -2
- package/src/reference.ts +142 -12
- package/src/static.ts +363 -50
- package/src/stdlib.ts +480 -15
- package/src/types.ts +6 -2
- package/src/typing.ts +143 -3
- package/src/world.ts +108 -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
|
@@ -134,11 +134,70 @@ export interface KindFacts {
|
|
|
134
134
|
* Then the term `combine` adds the attribute of each node in the list `children`, one at a time (TREE.FOLD).
|
|
135
135
|
* `combine` sees the names `acc` and `item`.
|
|
136
136
|
* - `default`: the term that gives the value when no rung before it answers and the declaration has no fold.
|
|
137
|
+
* - `circular`: a cycle of this attribute gives its fixed point, not `#CYCLE!` (TREE.CIRCULAR, D113). Only a cycle
|
|
138
|
+
* whose attributes each declare `circular` gives a fixed point.
|
|
137
139
|
*/
|
|
138
140
|
export interface AttributeDeclaration {
|
|
139
141
|
readonly copy?: boolean;
|
|
140
142
|
readonly fold?: { readonly unit: Json; readonly combine: Json };
|
|
141
143
|
readonly default?: Json;
|
|
144
|
+
readonly circular?: CircularDeclaration;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The declaration `circular` of an attribute (TREE.CIRCULAR, D113). The attribute that a cycle reaches first leads it.
|
|
149
|
+
* The leader evaluates again until no value of the cycle changes.
|
|
150
|
+
*
|
|
151
|
+
* - `start`: the value of the attribute before its first round, as JSON data. The kit does not evaluate it.
|
|
152
|
+
* - `limit`: the most rounds of the leader, a whole number of 1 or more. The default is `CIRCULAR_LIMIT`. A cycle that
|
|
153
|
+
* is not stable after the last round gives `#CYCLE!` (TREE.CIRCULAR-LIMIT).
|
|
154
|
+
*/
|
|
155
|
+
export interface CircularDeclaration {
|
|
156
|
+
readonly start: Json;
|
|
157
|
+
readonly limit?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The most rounds of a circular attribute without its own `limit` (TREE.CIRCULAR-LIMIT, D113). */
|
|
161
|
+
export const CIRCULAR_LIMIT = 100;
|
|
162
|
+
|
|
163
|
+
/** A declaration `circular` that the kit can use: its start value and its limit, or the reason of a bad declaration. */
|
|
164
|
+
export type CircularRule = { readonly ok: true; readonly start: Json; readonly limit: number } | { readonly ok: false; readonly reason: string };
|
|
165
|
+
|
|
166
|
+
/** This function tells if a value is JSON data: `null`, a truth value, a finite number, a text, or a container of them. */
|
|
167
|
+
function isJsonData(value: unknown): boolean {
|
|
168
|
+
const stack: unknown[] = [value];
|
|
169
|
+
const seen = new Set<object>();
|
|
170
|
+
while (stack.length > 0) {
|
|
171
|
+
const item = stack.pop();
|
|
172
|
+
if (item === null || typeof item === "string" || typeof item === "boolean") continue;
|
|
173
|
+
if (typeof item === "number") {
|
|
174
|
+
if (!Number.isFinite(item)) return false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!Array.isArray(item) && !isRecord(item)) return false;
|
|
178
|
+
if (seen.has(item)) continue;
|
|
179
|
+
seen.add(item);
|
|
180
|
+
for (const inner of Object.values(item)) stack.push(inner);
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* This function reads the declaration `circular` of an attribute (D113). The tree kit, the reference, `check` and the
|
|
187
|
+
* graph of attribute instances read it with this one function.
|
|
188
|
+
*
|
|
189
|
+
* @param declared - The value of the field `circular`.
|
|
190
|
+
* @returns The start value and the limit, or the reason when the declaration has no correct form.
|
|
191
|
+
*/
|
|
192
|
+
export function circularOf(declared: unknown): CircularRule {
|
|
193
|
+
if (!isRecord(declared) || !Object.hasOwn(declared, "start")) return { ok: false, reason: "The declaration circular needs a record with the field start." };
|
|
194
|
+
const start = field(declared, "start");
|
|
195
|
+
if (!isJsonData(start)) return { ok: false, reason: "The start value of the declaration circular must be JSON data." };
|
|
196
|
+
const limit: unknown = Object.hasOwn(declared, "limit") ? field(declared, "limit") : CIRCULAR_LIMIT;
|
|
197
|
+
if (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1) {
|
|
198
|
+
return { ok: false, reason: "The limit of the declaration circular must be a whole number of 1 or more." };
|
|
199
|
+
}
|
|
200
|
+
return { ok: true, start: start as Json, limit };
|
|
142
201
|
}
|
|
143
202
|
|
|
144
203
|
/**
|
|
@@ -323,6 +382,55 @@ export function isNamespace(entry: WorldEntry | undefined): entry is Namespace {
|
|
|
323
382
|
return entry !== undefined && !isKind(entry) && !isAlias(entry) && Object.hasOwn(entry, "kinds");
|
|
324
383
|
}
|
|
325
384
|
|
|
385
|
+
/**
|
|
386
|
+
* This function visits each kind of a world. These are the kinds of each namespace, at each depth, and the fallback
|
|
387
|
+
* 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
|
|
388
|
+
* fallback kind has the keys of its namespace. It comes after the entries of that namespace. `checkWorld` reads the
|
|
389
|
+
* kinds with this one function.
|
|
390
|
+
*
|
|
391
|
+
* @param world - The world.
|
|
392
|
+
* @param visit - The function that gets each kind, its keys from the root of the world, and `true` for a fallback kind.
|
|
393
|
+
*/
|
|
394
|
+
export function eachKind(world: World, visit: (kind: Kind, path: readonly Key[], fallback: boolean) => void): void {
|
|
395
|
+
// The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
|
|
396
|
+
const way = new Set<Namespace>();
|
|
397
|
+
const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
|
|
398
|
+
for (const key of Object.keys(kinds)) {
|
|
399
|
+
const entry = kinds[key];
|
|
400
|
+
const here = [...path, key];
|
|
401
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
402
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
403
|
+
way.add(entry);
|
|
404
|
+
entries(entry.kinds, here);
|
|
405
|
+
if (entry.fallback !== undefined) visit(entry.fallback, here, true);
|
|
406
|
+
way.delete(entry);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
entries(world.kinds, []);
|
|
411
|
+
if (world.fallback !== undefined) visit(world.fallback, [], true);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* This function gives the kind at a path of a world, without an alias and without a fallback kind. A namespace on the
|
|
416
|
+
* way gives its entries.
|
|
417
|
+
*
|
|
418
|
+
* @param world - The world.
|
|
419
|
+
* @param path - The keys from the root of the world.
|
|
420
|
+
* @returns The kind, or `undefined` when no kind is at the path.
|
|
421
|
+
*/
|
|
422
|
+
export function kindAt(world: World, path: readonly Key[]): Kind | undefined {
|
|
423
|
+
let entries = world.kinds;
|
|
424
|
+
let entry: WorldEntry | undefined;
|
|
425
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
426
|
+
entry = entryAt(entries, path[index] as Key);
|
|
427
|
+
if (index === path.length - 1) break;
|
|
428
|
+
if (!isNamespace(entry)) return undefined;
|
|
429
|
+
entries = entry.kinds;
|
|
430
|
+
}
|
|
431
|
+
return isKind(entry) ? entry : undefined;
|
|
432
|
+
}
|
|
433
|
+
|
|
326
434
|
function entryAt(entries: Readonly<Record<string, WorldEntry>>, key: Key): WorldEntry | undefined {
|
|
327
435
|
const name = String(key);
|
|
328
436
|
return Object.hasOwn(entries, name) ? entries[name] : undefined;
|