@mark1russell7/coeval 0.6.2 → 0.7.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 +36 -7
- package/build/index.d.ts +1767 -303
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4989 -417
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +56 -55
- package/src/conform.ts +1068 -0
- package/src/deref.ts +298 -50
- package/src/doc.ts +23 -2
- package/src/durable.ts +238 -0
- package/src/effects.ts +110 -0
- package/src/errors.ts +3 -1
- package/src/evaluate.ts +656 -124
- package/src/facts.ts +252 -0
- package/src/formula.ts +433 -36
- package/src/graph.ts +399 -0
- package/src/index.ts +34 -1
- package/src/ladder.ts +40 -1
- package/src/merge.ts +91 -4
- package/src/parts.ts +26 -11
- package/src/providers.ts +286 -0
- package/src/reads.ts +105 -0
- package/src/reference.ts +101 -18
- package/src/replay.ts +159 -0
- package/src/same.ts +42 -0
- package/src/schedule.ts +436 -0
- package/src/static.ts +266 -29
- package/src/stdlib.ts +28 -7
- package/src/types.ts +741 -0
- package/src/typing.ts +390 -0
- package/src/world.ts +63 -9
package/src/typing.ts
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { type Envelope, type Key, isRecord, typeName } from "./doc.ts";
|
|
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";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* The check of value types (R5 P3, D25). The pass reads the documents that the walk of `check` visited. It goes down
|
|
8
|
+
* to give each part the type that its position needs, and it goes up to synthesize the type of each document from the
|
|
9
|
+
* types of its parts. A type issue is a static finding: it does not change an evaluation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** What a type rule gets for one document. */
|
|
13
|
+
export interface TypeInput {
|
|
14
|
+
/** The keys of the kind of the document, after each alias. */
|
|
15
|
+
readonly typePath: readonly Key[];
|
|
16
|
+
/** The document. A bare value has the type that its position declares. */
|
|
17
|
+
readonly doc: Envelope;
|
|
18
|
+
/** The type that the position of the document needs. It is `unknown` when the position needs no type. */
|
|
19
|
+
readonly expected: ValueType;
|
|
20
|
+
/** The paths of the parts in the value, in the order of the value. */
|
|
21
|
+
readonly paths: readonly (readonly Key[])[];
|
|
22
|
+
/** `true` when the position declares an address. A reference there gives a thing, not a value. */
|
|
23
|
+
readonly address: boolean;
|
|
24
|
+
readonly machine: Machine;
|
|
25
|
+
/**
|
|
26
|
+
* This function gives the type of a part that the check typed before. It gives `unknown` for a part that the check
|
|
27
|
+
* did not type yet, and for a part with an issue.
|
|
28
|
+
*
|
|
29
|
+
* @param path - The path of the part in the value.
|
|
30
|
+
* @returns The type of the part.
|
|
31
|
+
*/
|
|
32
|
+
typeOf(path: readonly Key[]): ValueType;
|
|
33
|
+
/**
|
|
34
|
+
* This function gives the type of a name: the type that a kind around the document binds. Without such a kind, it
|
|
35
|
+
* gives the type from the option `rootTypes` of the host.
|
|
36
|
+
*
|
|
37
|
+
* @param name - The name.
|
|
38
|
+
* @returns The type, or `undefined` when neither a kind around the document nor the host gives the name a type.
|
|
39
|
+
*/
|
|
40
|
+
nameType(name: string): ValueType | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* This function gives the type of a root of the context at the document (TYPE.ROOTS). The host gives the types of
|
|
43
|
+
* `self`, `world` and `event` in the option `rootTypes`. A rule can give other types to its parts. The root `caps`
|
|
44
|
+
* always has the type `CAPS_TYPE`. A rule reads the type of a name with `nameType`, thus the root `names` gives
|
|
45
|
+
* `unknown`.
|
|
46
|
+
*
|
|
47
|
+
* @param root - The root.
|
|
48
|
+
* @returns The type. It is `unknown` for a root without a type.
|
|
49
|
+
*/
|
|
50
|
+
rootType(root: string): ValueType;
|
|
51
|
+
/**
|
|
52
|
+
* This function examines a value type of a contract with `typeIssues`, one time in a check. The first problem of a
|
|
53
|
+
* type becomes an issue at the document. A term with a problem reads as `unknown`.
|
|
54
|
+
*
|
|
55
|
+
* @param type - The value type of the contract.
|
|
56
|
+
* @param what - What the type is, for the reason, for example "the result of ops.add".
|
|
57
|
+
* @returns The type.
|
|
58
|
+
*/
|
|
59
|
+
contract(type: unknown, what: string): ValueType;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What a type rule tells for one part: the type that the part must have, and the types of the names of the part. */
|
|
63
|
+
export interface PartType {
|
|
64
|
+
/** The type that the part must have. */
|
|
65
|
+
readonly expect?: ValueType;
|
|
66
|
+
/** What the part is, for the reason of an issue, for example "argument 2 of ops.add". */
|
|
67
|
+
readonly what?: string;
|
|
68
|
+
/** The types of the names that the scope rules of the kind bind for the part. Each other name of the rules gets `unknown`. */
|
|
69
|
+
readonly names?: Readonly<Record<string, ValueType>>;
|
|
70
|
+
/**
|
|
71
|
+
* The types of the roots that the evaluator changes for the part, for example `self` for the attributes of a node.
|
|
72
|
+
* Each other root keeps its type. The parts of an evaluator without a rule get `unknown` for each of these roots.
|
|
73
|
+
*/
|
|
74
|
+
readonly roots?: Omit<RootTypes, "names">;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The value types of the roots of a context, for a check of types (TYPE.ROOTS). The host gives them in the option
|
|
79
|
+
* `rootTypes` of `check` and `typeCheck`. A root without a type has the type `unknown`. The root `caps` always has the
|
|
80
|
+
* type `CAPS_TYPE`, because the kernel narrows it.
|
|
81
|
+
*
|
|
82
|
+
* The host keeps the types true: each root of the context holds a member of its type. A host thing, or a machine
|
|
83
|
+
* with `adopt`, keeps each step true too. The context binds each name of `names`, thus the option `names` of `check`
|
|
84
|
+
* lists it too.
|
|
85
|
+
*/
|
|
86
|
+
export interface RootTypes {
|
|
87
|
+
/** The type of each name that the host binds. A name of the host without a type here has the type `unknown`. */
|
|
88
|
+
readonly names?: Readonly<Record<string, ValueType>>;
|
|
89
|
+
/** The type of `self`: the node or the position that the document is part of. */
|
|
90
|
+
readonly self?: ValueType;
|
|
91
|
+
/** The type of `world`, for example the schema of a store. The context then gives the world. */
|
|
92
|
+
readonly world?: ValueType;
|
|
93
|
+
/** The type of `event`: the event that started a run. */
|
|
94
|
+
readonly event?: ValueType;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The value type of the root `caps`: a list of capabilities. A capability is a text, or a list of keys. The kernel
|
|
99
|
+
* narrows the capabilities for each part, thus the host does not give this type.
|
|
100
|
+
*/
|
|
101
|
+
export const CAPS_TYPE: ValueType = { list: { union: ["text", { list: { union: ["text", "number"] } }] } };
|
|
102
|
+
|
|
103
|
+
/** A problem that a type rule finds. Its place is a part of the document, or the document itself. */
|
|
104
|
+
export interface TypeProblem {
|
|
105
|
+
/** The path of the part in the value. Without it, the problem is at the document. */
|
|
106
|
+
readonly path?: readonly Key[];
|
|
107
|
+
readonly reason: string;
|
|
108
|
+
readonly expected?: ValueType;
|
|
109
|
+
readonly found?: ValueType;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The type rule of an evaluator: what a check of types knows about the values that the evaluator gives. An evaluator
|
|
114
|
+
* object holds its rule in its field `type`. Thus the rule follows the evaluator that the ladder gives a document
|
|
115
|
+
* (TYPE.RULE).
|
|
116
|
+
*/
|
|
117
|
+
export interface TypeRule {
|
|
118
|
+
/**
|
|
119
|
+
* This function tells the type that a part must have, and the types of the names for the part. The check asks it
|
|
120
|
+
* before it types the part. The parts that give the names of a rule `keysAt` come first.
|
|
121
|
+
*
|
|
122
|
+
* @param input - The document, the type that its position needs, and the types of the parts before.
|
|
123
|
+
* @param path - The path of the part in the value.
|
|
124
|
+
* @returns The type of the part and the types of its names.
|
|
125
|
+
*/
|
|
126
|
+
readonly part?: (input: TypeInput, path: readonly Key[]) => PartType;
|
|
127
|
+
/**
|
|
128
|
+
* This function synthesizes the type of the document from the types of its parts. It must be sound: each value that
|
|
129
|
+
* the evaluator gives is a member of the type.
|
|
130
|
+
*
|
|
131
|
+
* @param input - The document, the type that its position needs, and the types of its parts.
|
|
132
|
+
* @param report - The function that gets each problem that the rule finds.
|
|
133
|
+
* @returns The type of the document.
|
|
134
|
+
*/
|
|
135
|
+
readonly synth: (input: TypeInput, report: (problem: TypeProblem) => void) => ValueType;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The type of one document of a check of types. */
|
|
139
|
+
export interface Typed {
|
|
140
|
+
/** The keys from the root document to the document. */
|
|
141
|
+
readonly place: readonly Key[];
|
|
142
|
+
/** The keys from the root JSON value to the document. */
|
|
143
|
+
readonly at: readonly Key[];
|
|
144
|
+
/** The type that the check synthesized. A document with an issue has the type `unknown`. */
|
|
145
|
+
readonly type: ValueType;
|
|
146
|
+
/** The type that the position of the document needs, when it needs one. */
|
|
147
|
+
readonly expected?: ValueType;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** One document that the walk of a check visited, with what the pass of types needs. The package does not export it. */
|
|
151
|
+
export interface TypeNode {
|
|
152
|
+
readonly visit: DocVisit;
|
|
153
|
+
/** The names that the scope rules of the parent bind for this document. */
|
|
154
|
+
readonly bound: readonly string[];
|
|
155
|
+
/** The type that the shape of the parent declares for this part. */
|
|
156
|
+
readonly expect: ValueType | undefined;
|
|
157
|
+
/** The indices of the parts that the walk visited, in the order of the value. */
|
|
158
|
+
readonly children: number[];
|
|
159
|
+
/** The evaluator name that the ladder gives for the verb of the check. */
|
|
160
|
+
evaluator: string | undefined;
|
|
161
|
+
/** `true` when the check found an issue at this document. Then its type is `unknown`. */
|
|
162
|
+
marked: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The result of the pass of types. */
|
|
166
|
+
export interface TypePass {
|
|
167
|
+
readonly type: ValueType;
|
|
168
|
+
readonly issues: readonly Issue[];
|
|
169
|
+
readonly places: readonly Typed[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A type that a position needs, and what the position is. */
|
|
173
|
+
interface Need {
|
|
174
|
+
readonly type: ValueType;
|
|
175
|
+
readonly what: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The types of the roots that an evaluator can change for its parts. */
|
|
179
|
+
interface Roots {
|
|
180
|
+
readonly self: ValueType;
|
|
181
|
+
readonly world: ValueType;
|
|
182
|
+
readonly event: ValueType;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The roots that can change, as a rule gives them in `PartType.roots`. */
|
|
186
|
+
const CHANGING = ["self", "world", "event"] as const;
|
|
187
|
+
|
|
188
|
+
/** The roots of the parts of an evaluator without a rule: such an evaluator can change each root. */
|
|
189
|
+
const UNKNOWN_ROOTS: Roots = { self: "unknown", world: "unknown", event: "unknown" };
|
|
190
|
+
|
|
191
|
+
/** One document on the stack of the pass. */
|
|
192
|
+
interface Frame {
|
|
193
|
+
readonly index: number;
|
|
194
|
+
readonly rule: TypeRule | undefined;
|
|
195
|
+
readonly input: TypeInput;
|
|
196
|
+
readonly order: readonly number[];
|
|
197
|
+
next: number;
|
|
198
|
+
readonly typed: Map<string, ValueType>;
|
|
199
|
+
readonly needs: readonly Need[];
|
|
200
|
+
readonly names: ReadonlyMap<string, ValueType>;
|
|
201
|
+
readonly roots: Roots;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pathKey(path: readonly Key[]): string {
|
|
205
|
+
return JSON.stringify(path.map(String));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 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 {
|
|
210
|
+
if (name === undefined || (machine.runners !== undefined && Object.hasOwn(machine.runners, name))) return undefined;
|
|
211
|
+
const evaluator = Object.hasOwn(machine.evaluators, name) ? machine.evaluators[name] : undefined;
|
|
212
|
+
const rule = typeof evaluator === "object" && evaluator !== null ? (evaluator as { readonly type?: unknown }).type : undefined;
|
|
213
|
+
return typeof rule === "object" && rule !== null && typeof (rule as TypeRule).synth === "function" ? (rule as TypeRule) : undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** This function gives the order of the parts of a document: first the parts that give the names of a rule `keysAt`. */
|
|
217
|
+
function orderOf(node: TypeNode, nodes: readonly TypeNode[]): readonly number[] {
|
|
218
|
+
const sources = (node.visit.kind?.scope ?? []).flatMap((rule) => ("keysAt" in rule.names ? [rule.names.keysAt] : []));
|
|
219
|
+
if (sources.length === 0 || node.children.length < 2) return node.children;
|
|
220
|
+
const gives = (index: number): boolean => {
|
|
221
|
+
const slot = (nodes[index] as TypeNode).visit.slot;
|
|
222
|
+
return sources.some((prefix) => prefix.length <= slot.length && prefix.every((key, at) => String(key) === String(slot[at])));
|
|
223
|
+
};
|
|
224
|
+
return [...node.children.filter(gives), ...node.children.filter((index) => !gives(index))];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function message(error: unknown): string {
|
|
228
|
+
return error instanceof Error ? error.message : String(error);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* This function checks the value types of the documents that the walk of a check visited (TYPE.CHECK). The walk gives
|
|
233
|
+
* the documents in the order of the document. The pass uses its own stack, thus a deep document cannot overflow the
|
|
234
|
+
* stack of the host.
|
|
235
|
+
*
|
|
236
|
+
* @param machine - The machine of the check.
|
|
237
|
+
* @param nodes - The documents, in the order of the document. The root is the first.
|
|
238
|
+
* @param verb - The verb of the check. The value types of a contract describe the verb `eval` only.
|
|
239
|
+
* @param expect - The type that the root must have.
|
|
240
|
+
* @param rootTypes - The types of the roots and of the names of the host.
|
|
241
|
+
* @returns The type of the root, the type issues in the order of the document, and the type of each document.
|
|
242
|
+
*/
|
|
243
|
+
export function typePass(machine: Machine, nodes: readonly TypeNode[], verb: string, expect: ValueType | undefined, rootTypes: RootTypes = {}): TypePass {
|
|
244
|
+
if (nodes.length === 0) return { type: "unknown", issues: [], places: [] };
|
|
245
|
+
const domains = machine.domains;
|
|
246
|
+
const contracts = verb === "eval";
|
|
247
|
+
const found: { readonly index: number; readonly issue: Issue }[] = [];
|
|
248
|
+
const types = new Array<ValueType>(nodes.length);
|
|
249
|
+
const needed = new Array<ValueType | undefined>(nodes.length);
|
|
250
|
+
const seen = new Set<unknown>();
|
|
251
|
+
|
|
252
|
+
const issueAt = (index: number, problem: TypeProblem): void => {
|
|
253
|
+
const visit = (nodes[index] as TypeNode).visit;
|
|
254
|
+
const issue: Issue = {
|
|
255
|
+
code: "#TYPE!",
|
|
256
|
+
place: visit.place,
|
|
257
|
+
reason: problem.reason,
|
|
258
|
+
at: visit.at,
|
|
259
|
+
...(problem.expected === undefined ? {} : { expected: problem.expected }),
|
|
260
|
+
...(problem.found === undefined ? {} : { found: problem.found }),
|
|
261
|
+
};
|
|
262
|
+
found.push({ index, issue });
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const contract = (index: number, type: unknown, what: string): ValueType => {
|
|
266
|
+
if (!seen.has(type)) {
|
|
267
|
+
seen.add(type);
|
|
268
|
+
const problem = typeIssues(type, domains)[0];
|
|
269
|
+
if (problem !== undefined) issueAt(index, { reason: `The ${what} is not a correct value type. ${problem.reason}` });
|
|
270
|
+
}
|
|
271
|
+
return type as ValueType;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const enter = (index: number, needs: readonly Need[], names: ReadonlyMap<string, ValueType>, roots: Roots): Frame => {
|
|
275
|
+
const node = nodes[index] as TypeNode;
|
|
276
|
+
const visit = node.visit;
|
|
277
|
+
const typed = new Map<string, ValueType>();
|
|
278
|
+
const input: TypeInput = {
|
|
279
|
+
typePath: visit.typePath ?? [],
|
|
280
|
+
doc: visit.doc,
|
|
281
|
+
// The need of the rule of the parent comes last. It is the type that the rule of this document reads.
|
|
282
|
+
expected: needs.length === 0 ? "unknown" : (needs[needs.length - 1] as Need).type,
|
|
283
|
+
paths: node.children.map((child) => (nodes[child] as TypeNode).visit.slot),
|
|
284
|
+
address: visit.address === true,
|
|
285
|
+
machine,
|
|
286
|
+
typeOf: (path) => typed.get(pathKey(path)) ?? "unknown",
|
|
287
|
+
nameType: (name) => names.get(name),
|
|
288
|
+
rootType: (root) => (root === "caps" ? CAPS_TYPE : root === "self" || root === "world" || root === "event" ? roots[root] : "unknown"),
|
|
289
|
+
contract: (type, what) => contract(index, type, what),
|
|
290
|
+
};
|
|
291
|
+
return { index, rule: node.marked ? undefined : ruleOf(machine, node.evaluator), input, order: orderOf(node, nodes), next: 0, typed, needs, names, roots };
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const ruleFailed = (frame: Frame, error: unknown): void => {
|
|
295
|
+
issueAt(frame.index, { reason: `The type rule of ${(nodes[frame.index] as TypeNode).evaluator ?? "the evaluator"} fails: ${message(error)}` });
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const report = (frame: Frame, problem: TypeProblem): void => {
|
|
299
|
+
const node = nodes[frame.index] as TypeNode;
|
|
300
|
+
const key = problem.path === undefined ? undefined : pathKey(problem.path);
|
|
301
|
+
const part = key === undefined ? undefined : node.children.find((child) => pathKey((nodes[child] as TypeNode).visit.slot) === key);
|
|
302
|
+
issueAt(part ?? frame.index, problem);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const rootNeeds: Need[] = expect === undefined || isUnknown(contract(0, expect, "expected type of the check"), domains) ? [] : [{ type: expect, what: "document" }];
|
|
306
|
+
// The check examines each type of the host at the root, one time. A kind that binds a name hides the name of the host.
|
|
307
|
+
const hostNames = new Map<string, ValueType>();
|
|
308
|
+
const given: Readonly<Record<string, ValueType>> = isRecord(rootTypes.names) ? rootTypes.names : {};
|
|
309
|
+
for (const name of Object.keys(given)) hostNames.set(name, contract(0, given[name], `type of the name ${name} of the host`));
|
|
310
|
+
const hostRoot = (root: (typeof CHANGING)[number]): ValueType => (rootTypes[root] === undefined ? "unknown" : contract(0, rootTypes[root], `type of the root ${root}`));
|
|
311
|
+
const stack: Frame[] = [enter(0, rootNeeds, hostNames, { self: hostRoot("self"), world: hostRoot("world"), event: hostRoot("event") })];
|
|
312
|
+
while (stack.length > 0) {
|
|
313
|
+
const frame = stack[stack.length - 1] as Frame;
|
|
314
|
+
if (frame.next < frame.order.length) {
|
|
315
|
+
const child = frame.order[frame.next] as number;
|
|
316
|
+
frame.next += 1;
|
|
317
|
+
const node = nodes[child] as TypeNode;
|
|
318
|
+
const slot = node.visit.slot;
|
|
319
|
+
const what = `part ${slot.join(".")} of ${typeName((nodes[frame.index] as TypeNode).visit.doc.type)}`;
|
|
320
|
+
let part: PartType = {};
|
|
321
|
+
if (frame.rule?.part !== undefined) {
|
|
322
|
+
try {
|
|
323
|
+
part = frame.rule.part(frame.input, slot) ?? {};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
ruleFailed(frame, error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const needs: Need[] = [];
|
|
329
|
+
// A need of the type `unknown` needs nothing, thus the pass leaves it out.
|
|
330
|
+
const need = (type: ValueType, text: string): void => {
|
|
331
|
+
if (!isUnknown(type, domains)) needs.push({ type, what: text });
|
|
332
|
+
};
|
|
333
|
+
if (contracts && node.expect !== undefined) need(contract(child, node.expect, `expected type of the ${what}`), what);
|
|
334
|
+
if (part.expect !== undefined) need(part.expect, part.what ?? what);
|
|
335
|
+
let names = frame.names;
|
|
336
|
+
if (node.bound.length > 0) {
|
|
337
|
+
// Each name that the scope rules bind hides the name around it. A name without a type from the rule gets `unknown`.
|
|
338
|
+
const copy = new Map(names);
|
|
339
|
+
for (const name of node.bound) copy.set(name, part.names !== undefined && Object.hasOwn(part.names, name) ? (part.names[name] as ValueType) : "unknown");
|
|
340
|
+
names = copy;
|
|
341
|
+
}
|
|
342
|
+
// No contract tells how an evaluator changes the roots, thus only a rule keeps their types for its parts (TYPE.ROOTS).
|
|
343
|
+
let roots = frame.rule === undefined ? UNKNOWN_ROOTS : frame.roots;
|
|
344
|
+
const changes = part.roots;
|
|
345
|
+
if (typeof changes === "object" && changes !== null) {
|
|
346
|
+
const copy: Record<(typeof CHANGING)[number], ValueType> = { ...roots };
|
|
347
|
+
for (const root of CHANGING) if (changes[root] !== undefined) copy[root] = changes[root] as ValueType;
|
|
348
|
+
roots = copy;
|
|
349
|
+
}
|
|
350
|
+
stack.push(enter(child, needs, names, roots));
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
stack.pop();
|
|
354
|
+
const node = nodes[frame.index] as TypeNode;
|
|
355
|
+
let type: ValueType = "unknown";
|
|
356
|
+
if (!node.marked) {
|
|
357
|
+
let synthesized: ValueType = "unknown";
|
|
358
|
+
if (frame.rule !== undefined) {
|
|
359
|
+
try {
|
|
360
|
+
synthesized = frame.rule.synth(frame.input, (problem) => report(frame, problem));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
ruleFailed(frame, error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const result = contracts ? node.visit.kind?.result : undefined;
|
|
366
|
+
type = result === undefined ? synthesized : contract(frame.index, result, `result of the kind ${typeName(node.visit.doc.type)}`);
|
|
367
|
+
}
|
|
368
|
+
for (const need of frame.needs) {
|
|
369
|
+
if (fits(type, need.type, domains)) continue;
|
|
370
|
+
issueAt(frame.index, { reason: `The ${need.what} has the type ${typeText(type)}. It must have the type ${typeText(need.type)}.`, expected: need.type, found: type });
|
|
371
|
+
// A part with an issue gets the type `unknown`, thus one problem does not give a chain of issues.
|
|
372
|
+
type = "unknown";
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
types[frame.index] = type;
|
|
376
|
+
needed[frame.index] = frame.needs.length === 0 ? undefined : (frame.needs[frame.needs.length - 1] as Need).type;
|
|
377
|
+
stack[stack.length - 1]?.typed.set(pathKey(node.visit.slot), type);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const places = nodes.map((node, index) => {
|
|
381
|
+
const want = needed[index];
|
|
382
|
+
const entry = { type: types[index] ?? "unknown", ...(want === undefined ? {} : { expected: want }) } as Typed;
|
|
383
|
+
// The keys of a place are made only when a reader asks for them, as a visit does.
|
|
384
|
+
Object.defineProperty(entry, "place", { enumerable: true, get: () => node.visit.place });
|
|
385
|
+
Object.defineProperty(entry, "at", { enumerable: true, get: () => node.visit.at });
|
|
386
|
+
return entry;
|
|
387
|
+
});
|
|
388
|
+
const issues = found.sort((a, b) => a.index - b.index).map((item) => item.issue);
|
|
389
|
+
return { type: types[0] ?? "unknown", issues, places };
|
|
390
|
+
}
|
package/src/world.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, type TypeRef, field, isRecord, typePath } from "./doc.ts";
|
|
2
2
|
import type { Cap } from "./deref.ts";
|
|
3
|
+
import type { ValueType } from "./types.ts";
|
|
3
4
|
|
|
4
5
|
/** The two sides of evaluation. `derive` is pure. `run` causes effects in order. */
|
|
5
6
|
export type Side = "derive" | "run";
|
|
@@ -20,12 +21,15 @@ export type Side = "derive" | "run";
|
|
|
20
21
|
* A `term` with `address: true` is an address: a reference there gives what its path reaches, not the value. A
|
|
21
22
|
* `term` with `kinds` permits only a part whose type starts with one of the paths. A `record` with `closed: true`
|
|
22
23
|
* permits only its named fields. A `list` with `distinct: true` permits no two equal items.
|
|
24
|
+
*
|
|
25
|
+
* `expect` is the value type that a part must have, as `of` is its type (TYPE.CHECK). A shape with parts gives it to
|
|
26
|
+
* each part that `of` covers. Only a check of types reads it, and it describes the verb `eval`.
|
|
23
27
|
*/
|
|
24
28
|
export type Shape =
|
|
25
29
|
| { readonly shape: "none" }
|
|
26
|
-
| { readonly shape: "term"; readonly of?: string; readonly address?: boolean; readonly kinds?: readonly (readonly Key[])[] }
|
|
27
|
-
| { readonly shape: "list"; readonly of?: string; readonly item?: Shape; readonly distinct?: boolean }
|
|
28
|
-
| { readonly shape: "map"; readonly of?: string; readonly item?: Shape }
|
|
30
|
+
| { readonly shape: "term"; readonly of?: string; readonly address?: boolean; readonly kinds?: readonly (readonly Key[])[]; readonly expect?: ValueType }
|
|
31
|
+
| { readonly shape: "list"; readonly of?: string; readonly item?: Shape; readonly distinct?: boolean; readonly expect?: ValueType }
|
|
32
|
+
| { readonly shape: "map"; readonly of?: string; readonly item?: Shape; readonly expect?: ValueType }
|
|
29
33
|
| {
|
|
30
34
|
readonly shape: "record";
|
|
31
35
|
readonly fields: readonly string[] | Readonly<Record<string, Shape>>;
|
|
@@ -33,8 +37,9 @@ export type Shape =
|
|
|
33
37
|
readonly required?: readonly string[];
|
|
34
38
|
readonly rest?: Shape;
|
|
35
39
|
readonly closed?: boolean;
|
|
40
|
+
readonly expect?: ValueType;
|
|
36
41
|
}
|
|
37
|
-
| { readonly shape: "deep"; readonly of?: string }
|
|
42
|
+
| { readonly shape: "deep"; readonly of?: string; readonly expect?: ValueType }
|
|
38
43
|
| { readonly shape: "text" }
|
|
39
44
|
| { readonly shape: "number"; readonly whole?: boolean; readonly min?: number; readonly max?: number }
|
|
40
45
|
| { readonly shape: "one"; readonly values: readonly Json[] };
|
|
@@ -86,8 +91,46 @@ export type CapsRule =
|
|
|
86
91
|
| { readonly rule: "mask"; readonly in?: readonly (readonly Key[])[] }
|
|
87
92
|
| { readonly rule: "only"; readonly caps: readonly Cap[]; readonly in?: readonly (readonly Key[])[] };
|
|
88
93
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
94
|
+
/**
|
|
95
|
+
* A lazy rule of a kind names the parts that an evaluation can skip and still give a value. An example is the
|
|
96
|
+
* branches of `if`. The static facts mark each read under such a part (D24).
|
|
97
|
+
*
|
|
98
|
+
* `in` names the parts, as patterns of their paths in the value. The key `"*"` matches each key. A rule without `in`
|
|
99
|
+
* covers each part. With `after: n`, the rule covers only the parts after the first `n` parts that its patterns
|
|
100
|
+
* match. Thus `{ after: 1 }` makes each part lazy except the first part, as in `and`.
|
|
101
|
+
*/
|
|
102
|
+
export interface LazyRule {
|
|
103
|
+
readonly in?: readonly (readonly Key[])[];
|
|
104
|
+
readonly after?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The static facts that a kind declares for its code (D24). The contract of a kind holds them, and the hook `facts`
|
|
109
|
+
* of a machine can give them for a name under a fallback kind. The facts describe the code of `eval`, and of `step`
|
|
110
|
+
* for a step document.
|
|
111
|
+
*
|
|
112
|
+
* - `pure`: `true` when the code of the kind causes no effect. Its result depends only on its data, the results
|
|
113
|
+
* of its parts and what its paths reach. A kind that reads the context in another way does not declare it,
|
|
114
|
+
* because the static facts do not see that read.
|
|
115
|
+
* - `volatile`: `true` when its result can change while each read gives the same thing, for example a clock.
|
|
116
|
+
* - `lazy`: the parts that an evaluation can skip and still give a value.
|
|
117
|
+
*/
|
|
118
|
+
export interface KindFacts {
|
|
119
|
+
readonly pure?: boolean;
|
|
120
|
+
readonly volatile?: boolean;
|
|
121
|
+
readonly lazy?: readonly LazyRule[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A kind gives the name of an evaluator for each verb, and the contract of its value. The contract serves each verb.
|
|
126
|
+
* For each verb, the kernel splits the parts and narrows their capabilities by it, and `check` reads its scope rules.
|
|
127
|
+
* `side` and `effects` tell what the verb `eval` does (VERB.CONTRACT).
|
|
128
|
+
*/
|
|
129
|
+
export interface Kind extends KindFacts {
|
|
130
|
+
/**
|
|
131
|
+
* The evaluator names, by verb. `eval` evaluates a document, `deref` answers a step on it, and `step` names the
|
|
132
|
+
* walker of a step document. A language adds its own verbs, for example `view` or `format`.
|
|
133
|
+
*/
|
|
91
134
|
readonly verbs: Readonly<Record<string, string>>;
|
|
92
135
|
/** Where the parts of the value are. The default is `none`. */
|
|
93
136
|
readonly parts?: Shape;
|
|
@@ -100,13 +143,20 @@ export interface Kind {
|
|
|
100
143
|
* itself.
|
|
101
144
|
*/
|
|
102
145
|
readonly caps?: readonly CapsRule[];
|
|
103
|
-
/** The side of the kind. A kind of the run side causes effects. The default is `derive`. */
|
|
146
|
+
/** The side of the verb `eval` of the kind. A kind of the run side causes effects. The default is `derive`. */
|
|
104
147
|
readonly side?: Side;
|
|
105
148
|
/**
|
|
106
|
-
* The effects that a document of this kind can cause. It is a list of the kinds of the effects.
|
|
107
|
-
* `"type"` means that the kind of the effect is the type of the document itself, as for a call.
|
|
149
|
+
* The effects that the verb `eval` of a document of this kind can cause. It is a list of the kinds of the effects.
|
|
150
|
+
* The text `"type"` means that the kind of the effect is the type of the document itself, as for a call. For a
|
|
151
|
+
* procedure entry, the list holds the latent effects of the procedure, and `checkLatent` compares them with its body
|
|
152
|
+
* (CAP.LATENT).
|
|
108
153
|
*/
|
|
109
154
|
readonly effects?: "type" | readonly string[];
|
|
155
|
+
/**
|
|
156
|
+
* The value type of the result of the verb `eval`, when it is fixed (TYPE.SYNTH). A check of types gives it to each
|
|
157
|
+
* document of the kind, in the place of the type that the rule of the evaluator synthesizes.
|
|
158
|
+
*/
|
|
159
|
+
readonly result?: ValueType;
|
|
110
160
|
/** One sentence that tells what the kind is. */
|
|
111
161
|
readonly summary?: string;
|
|
112
162
|
}
|
|
@@ -129,6 +179,10 @@ export type WorldEntry = Kind | Namespace | Alias;
|
|
|
129
179
|
*/
|
|
130
180
|
export interface World {
|
|
131
181
|
readonly kinds: Readonly<Record<string, WorldEntry>>;
|
|
182
|
+
/**
|
|
183
|
+
* The default evaluator of each verb: the last rung of the ladder (LADDER.DEFAULT). It serves each verb, also
|
|
184
|
+
* `deref` and `step`. Without a default of `deref`, a step goes into the value of a document (VERB.LADDER).
|
|
185
|
+
*/
|
|
132
186
|
readonly defaults: Readonly<Record<string, string>>;
|
|
133
187
|
readonly fallback?: Kind;
|
|
134
188
|
}
|