@mark1russell7/coeval 0.1.0 → 0.2.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 +41 -0
- package/build/index.d.ts +832 -148
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1898 -376
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/context.ts +25 -21
- package/src/deref.ts +165 -54
- package/src/doc.ts +86 -17
- package/src/errors.ts +141 -0
- package/src/evaluate.ts +797 -281
- package/src/formula.ts +409 -0
- package/src/index.ts +72 -7
- package/src/parts.ts +140 -27
- package/src/static.ts +364 -0
- package/src/stdlib.ts +60 -33
- package/src/world.ts +96 -22
package/src/evaluate.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { type Envelope, type Json,
|
|
1
|
+
import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { splitParts } from "./parts.ts";
|
|
3
|
-
import { ask,
|
|
4
|
-
import type
|
|
3
|
+
import { ask, type Answer } from "./ladder.ts";
|
|
4
|
+
import { type Side, lookupKind } from "./world.ts";
|
|
5
|
+
import { type ErrorCode, type EvalError, type Result, fail, ok, placed, thrown, describe } from "./errors.ts";
|
|
5
6
|
import {
|
|
7
|
+
type Cap,
|
|
6
8
|
type Context,
|
|
7
9
|
type Navigator,
|
|
8
10
|
type Resolution,
|
|
11
|
+
type ResolveOptions,
|
|
9
12
|
type Thing,
|
|
10
13
|
context as makeRootContext,
|
|
11
14
|
isHostThing,
|
|
@@ -14,63 +17,176 @@ import {
|
|
|
14
17
|
} from "./deref.ts";
|
|
15
18
|
|
|
16
19
|
export type { Context } from "./deref.ts";
|
|
20
|
+
export type { ErrorCode, EvalError, Result } from "./errors.ts";
|
|
21
|
+
export { fail, ok } from "./errors.ts";
|
|
17
22
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| "#CALC!"
|
|
31
|
-
| "#DEPTH!";
|
|
32
|
-
|
|
33
|
-
/** An error value: a code and a reason. An evaluation gives an error value, and it does not throw. */
|
|
34
|
-
export interface EvalError {
|
|
35
|
-
readonly code: ErrorCode;
|
|
36
|
-
readonly reason: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The result of an evaluation: a value, or an error value. */
|
|
40
|
-
export type Result = { readonly ok: true; readonly value: Json } | { readonly ok: false; readonly error: EvalError };
|
|
23
|
+
/**
|
|
24
|
+
* This function makes a context with names and capabilities. The other roots are empty, and an evaluation sets
|
|
25
|
+
* the world.
|
|
26
|
+
*
|
|
27
|
+
* @param names - The names and their values.
|
|
28
|
+
* @param caps - The effects that the term can cause.
|
|
29
|
+
* @param self - The node of the term, if it has one.
|
|
30
|
+
* @returns The context.
|
|
31
|
+
*/
|
|
32
|
+
export function makeContext(names: Readonly<Record<string, unknown>> = {}, caps: readonly Cap[] = [], self: Thing = null): Context {
|
|
33
|
+
return makeRootContext({ names, caps, self });
|
|
34
|
+
}
|
|
41
35
|
|
|
42
36
|
/**
|
|
43
|
-
* This function
|
|
37
|
+
* This function gives the keys of a capability or of a kind of effect: a text is split at its dots.
|
|
44
38
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @returns The
|
|
39
|
+
* @param kind - The text or the keys.
|
|
40
|
+
* @returns The keys, as texts.
|
|
47
41
|
*/
|
|
48
|
-
export function
|
|
49
|
-
return
|
|
42
|
+
export function capKeys(kind: Cap): readonly string[] {
|
|
43
|
+
return typeof kind === "string" ? kind.split(".") : kind.map(String);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
/**
|
|
53
|
-
* This function
|
|
47
|
+
* This function tells if the capabilities permit an effect. A capability permits the kind of its name, and each kind
|
|
48
|
+
* under it: `store` permits `store.update`. The comparison is key by key, thus `fs.read` does not permit the
|
|
49
|
+
* kind with the keys `fs` and `read.json`.
|
|
54
50
|
*
|
|
55
|
-
* @param
|
|
56
|
-
* @param
|
|
57
|
-
* @returns
|
|
51
|
+
* @param caps - The capabilities of the context.
|
|
52
|
+
* @param kind - The kind of the effect: a text with dots, or its keys.
|
|
53
|
+
* @returns `true` when one capability permits it.
|
|
58
54
|
*/
|
|
59
|
-
export function
|
|
60
|
-
|
|
55
|
+
export function permits(caps: readonly Cap[], kind: Cap): boolean {
|
|
56
|
+
const want = capKeys(kind);
|
|
57
|
+
return caps.some((cap) => {
|
|
58
|
+
const have = capKeys(cap);
|
|
59
|
+
return have.length <= want.length && have.every((key, index) => key === want[index]);
|
|
60
|
+
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* This function
|
|
65
|
-
*
|
|
64
|
+
* This function narrows a context for a part: the part keeps only the capabilities that the context of its parent
|
|
65
|
+
* permits. Thus an evaluator cannot give a part more effects than it has.
|
|
66
66
|
*
|
|
67
|
-
* @param
|
|
68
|
-
* @param
|
|
69
|
-
* @
|
|
70
|
-
* @returns The context.
|
|
67
|
+
* @param parent - The context of the parent.
|
|
68
|
+
* @param next - The context that the evaluator gives the part.
|
|
69
|
+
* @returns The context of the part.
|
|
71
70
|
*/
|
|
72
|
-
export function
|
|
73
|
-
|
|
71
|
+
export function narrow(parent: Context, next: Context): Context {
|
|
72
|
+
if (next === parent || next.caps === parent.caps) return next;
|
|
73
|
+
const caps = next.caps.filter((cap) => permits(parent.caps, cap));
|
|
74
|
+
return caps.length === next.caps.length ? next : { ...next, caps };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The steps of an evaluation that can wait. It yields a promise only when the host makes it wait. A runner takes the
|
|
79
|
+
* result of a part with `yield*`, thus a run goes on at once until an effect waits. A runner does not yield a
|
|
80
|
+
* promise itself: it uses `yield*` on the steps that the kernel gives.
|
|
81
|
+
*/
|
|
82
|
+
export type Steps = Generator<Promise<unknown>, Result, unknown>;
|
|
83
|
+
|
|
84
|
+
/** An effect that an evaluation caused, in the order of the effects. */
|
|
85
|
+
export interface Effect {
|
|
86
|
+
readonly index: number;
|
|
87
|
+
/** The kind of the effect, as a text with dots. */
|
|
88
|
+
readonly kind: string;
|
|
89
|
+
/** The keys of the kind of the effect. */
|
|
90
|
+
readonly path: readonly Key[];
|
|
91
|
+
readonly payload: unknown;
|
|
92
|
+
/** The ID of the trace node that caused the effect, or -1 without a trace. */
|
|
93
|
+
readonly node: number;
|
|
94
|
+
/** The number of events before the effect. The effect comes before the end of its node. */
|
|
95
|
+
readonly at: number;
|
|
96
|
+
/** The keys from the root document to the document that caused the effect. */
|
|
97
|
+
readonly place: readonly Key[];
|
|
98
|
+
/** The context of the document that caused the effect: its names, its node and its capabilities. */
|
|
99
|
+
readonly context: Context;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The host side of a run: it does one effect, and gives its result. A throw or a rejection is an error value. */
|
|
103
|
+
export type Perform = (effect: Effect) => unknown;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The runtime of one evaluation or run. Compiled code gets it with the context, and asks it to resolve paths and
|
|
107
|
+
* to cause effects.
|
|
108
|
+
*/
|
|
109
|
+
export interface Runtime {
|
|
110
|
+
readonly machine: Machine;
|
|
111
|
+
/** `true` in a run: the host does each effect, and a part that waits needs a parent that can wait. */
|
|
112
|
+
readonly running: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* This function resolves a path from a context.
|
|
115
|
+
*
|
|
116
|
+
* @param ctx - The context.
|
|
117
|
+
* @param path - The steps. The first step is a root.
|
|
118
|
+
* @param options - `hops: true` records each hop. The default records them only in a traced evaluation.
|
|
119
|
+
* @returns What the path reached, or an error.
|
|
120
|
+
*/
|
|
121
|
+
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution;
|
|
122
|
+
/**
|
|
123
|
+
* This function causes an effect from an evaluator. In a run, an evaluator cannot cause an effect: the host does
|
|
124
|
+
* each effect, thus only a runner can wait for it.
|
|
125
|
+
*
|
|
126
|
+
* @param ctx - The context of the document.
|
|
127
|
+
* @param kind - The kind of the effect.
|
|
128
|
+
* @param payload - The data of the effect.
|
|
129
|
+
* @param place - The place of the document.
|
|
130
|
+
* @returns The payload, or an error value.
|
|
131
|
+
*/
|
|
132
|
+
effect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result;
|
|
133
|
+
/**
|
|
134
|
+
* This function causes an effect from a runner. In a run, the host does it, and the steps wait when the host
|
|
135
|
+
* waits.
|
|
136
|
+
*
|
|
137
|
+
* @param ctx - The context of the document.
|
|
138
|
+
* @param kind - The kind of the effect.
|
|
139
|
+
* @param payload - The data of the effect.
|
|
140
|
+
* @param place - The place of the document.
|
|
141
|
+
* @returns The steps that give the result of the host, or an error value.
|
|
142
|
+
*/
|
|
143
|
+
runEffect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Steps;
|
|
144
|
+
/**
|
|
145
|
+
* This function drives several steps at the same time. When one waits, the others go on.
|
|
146
|
+
*
|
|
147
|
+
* @param list - One function for each item. Each function starts the steps of its item.
|
|
148
|
+
* @param options - The most items that run at the same time. The default is each item.
|
|
149
|
+
* @returns The steps that give each result, in the order of the list.
|
|
150
|
+
*/
|
|
151
|
+
all(list: readonly (() => Steps)[], options?: { readonly concurrency?: number }): Generator<Promise<unknown>, readonly Result[], unknown>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Compiled code: the evaluation of a document in a context. */
|
|
155
|
+
export type Code = (ctx: Context, rt: Runtime) => Result;
|
|
156
|
+
|
|
157
|
+
/** Compiled code that can wait: the run of a document in a context. */
|
|
158
|
+
export type RunCode = (ctx: Context, rt: Runtime) => Steps;
|
|
159
|
+
|
|
160
|
+
/** A compiled part: its place in its parent, its binding name, and its code. It has no field with its value. */
|
|
161
|
+
export interface CompiledPart {
|
|
162
|
+
/** The path in the value of the parent as one text, for example `0` or `bind.x`. */
|
|
163
|
+
readonly key: string;
|
|
164
|
+
/** The keys from the value of the parent to the part. */
|
|
165
|
+
readonly path: readonly Key[];
|
|
166
|
+
/** The binding name from the header of the part. */
|
|
167
|
+
readonly name?: string;
|
|
168
|
+
/** The code that evaluates the part. */
|
|
169
|
+
readonly code: Code;
|
|
170
|
+
/** The code that runs the part. Its steps can wait. */
|
|
171
|
+
readonly run: RunCode;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The input of a compiler: the document, its kind, and its parts, compiled. */
|
|
175
|
+
export interface CompileInput {
|
|
176
|
+
/** The text of the type of the document, for example `add` or `math.sum` after its alias. */
|
|
177
|
+
readonly kind: string;
|
|
178
|
+
/** The keys of the kind after each alias. */
|
|
179
|
+
readonly typePath: readonly Key[];
|
|
180
|
+
readonly doc: Envelope;
|
|
181
|
+
/** The value of the document with a compiled part in the place of each part. */
|
|
182
|
+
readonly value: unknown;
|
|
183
|
+
/** The value of the document without its parts. */
|
|
184
|
+
readonly data: Json;
|
|
185
|
+
/** The compiled parts, in the order of the value. */
|
|
186
|
+
readonly parts: readonly CompiledPart[];
|
|
187
|
+
/** The keys from the root document to this document. */
|
|
188
|
+
readonly place: readonly Key[];
|
|
189
|
+
readonly machine: Machine;
|
|
74
190
|
}
|
|
75
191
|
|
|
76
192
|
/**
|
|
@@ -78,8 +194,10 @@ export function makeContext(names: Readonly<Record<string, Json>> = {}, caps: re
|
|
|
78
194
|
* part, thus two parts that give the same results can replace each other.
|
|
79
195
|
*/
|
|
80
196
|
export interface Part {
|
|
81
|
-
/** The
|
|
197
|
+
/** The path in the value of the parent as one text, for example `0` or `bind.x`. */
|
|
82
198
|
readonly key: string;
|
|
199
|
+
/** The keys from the value of the parent to the part. */
|
|
200
|
+
readonly path: readonly Key[];
|
|
83
201
|
/** The binding name from the header of the part. */
|
|
84
202
|
readonly name?: string;
|
|
85
203
|
/**
|
|
@@ -95,19 +213,27 @@ export interface Part {
|
|
|
95
213
|
export interface EvalInput {
|
|
96
214
|
/** The text of the type of the document, for example `add` or `math.sum` after its alias. */
|
|
97
215
|
readonly kind: string;
|
|
216
|
+
/** The keys of the kind after each alias. */
|
|
217
|
+
readonly typePath: readonly Key[];
|
|
218
|
+
readonly doc: Envelope;
|
|
219
|
+
/** The value of the document with a sealed part in the place of each part. */
|
|
220
|
+
readonly value: unknown;
|
|
98
221
|
/** The value of the document without its parts. */
|
|
99
222
|
readonly data: Json;
|
|
100
223
|
/** The parts, in the order of the value. */
|
|
101
224
|
readonly parts: readonly Part[];
|
|
102
225
|
/** The context of the document. */
|
|
103
226
|
readonly context: Context;
|
|
227
|
+
/** The keys from the root document to this document. */
|
|
228
|
+
readonly place: readonly Key[];
|
|
104
229
|
/**
|
|
105
230
|
* This function resolves a path from the context of the document.
|
|
106
231
|
*
|
|
107
232
|
* @param path - The steps. The first step is a root.
|
|
108
|
-
* @
|
|
233
|
+
* @param options - `hops: true` records each hop.
|
|
234
|
+
* @returns What the path reached, or an error.
|
|
109
235
|
*/
|
|
110
|
-
resolve(path: readonly
|
|
236
|
+
resolve(path: readonly unknown[], options?: ResolveOptions): Resolution;
|
|
111
237
|
/**
|
|
112
238
|
* This function causes an effect. The context must have the capability for the kind of the effect. In a run, an
|
|
113
239
|
* evaluator cannot cause an effect: the host does each effect, thus only a runner can wait for it.
|
|
@@ -116,22 +242,15 @@ export interface EvalInput {
|
|
|
116
242
|
* @param payload - The data of the effect.
|
|
117
243
|
* @returns The payload, or the error `#CAP!`.
|
|
118
244
|
*/
|
|
119
|
-
effect(kind:
|
|
245
|
+
effect(kind: Cap, payload: unknown): Result;
|
|
120
246
|
}
|
|
121
247
|
|
|
122
|
-
/** An evaluator: a function from the input of a document to a result. */
|
|
123
|
-
export type Evaluator = (input: EvalInput) => Result;
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The steps of an evaluation that can wait. It yields a promise only when the host makes it wait. A runner takes the
|
|
127
|
-
* result of a part with `yield*`, thus a run goes on at once until an effect waits.
|
|
128
|
-
*/
|
|
129
|
-
export type Steps = Generator<Promise<Result>, Result, Result>;
|
|
130
|
-
|
|
131
248
|
/** A part for a runner. Its evaluation can wait for the effects in it. */
|
|
132
249
|
export interface RunPart {
|
|
133
|
-
/** The
|
|
250
|
+
/** The path in the value of the parent as one text. */
|
|
134
251
|
readonly key: string;
|
|
252
|
+
/** The keys from the value of the parent to the part. */
|
|
253
|
+
readonly path: readonly Key[];
|
|
135
254
|
/** The binding name from the header of the part. */
|
|
136
255
|
readonly name?: string;
|
|
137
256
|
/**
|
|
@@ -145,86 +264,90 @@ export interface RunPart {
|
|
|
145
264
|
|
|
146
265
|
/** The input of a runner: the input of an evaluator, with parts and effects that can wait. */
|
|
147
266
|
export interface RunInput {
|
|
148
|
-
/** The text of the type of the document. */
|
|
149
267
|
readonly kind: string;
|
|
150
|
-
|
|
268
|
+
readonly typePath: readonly Key[];
|
|
269
|
+
readonly doc: Envelope;
|
|
270
|
+
/** The value of the document with a part for a runner in the place of each part. */
|
|
271
|
+
readonly value: unknown;
|
|
151
272
|
readonly data: Json;
|
|
152
|
-
/** The parts, in the order of the value. */
|
|
153
273
|
readonly parts: readonly RunPart[];
|
|
154
|
-
/** The context of the document. */
|
|
155
274
|
readonly context: Context;
|
|
275
|
+
readonly place: readonly Key[];
|
|
276
|
+
resolve(path: readonly unknown[], options?: ResolveOptions): Resolution;
|
|
156
277
|
/**
|
|
157
|
-
* This function
|
|
158
|
-
*
|
|
159
|
-
* @param path - The steps. The first step is a root.
|
|
160
|
-
* @returns What the path reached, with each hop.
|
|
161
|
-
*/
|
|
162
|
-
resolve(path: readonly Json[]): Resolution;
|
|
163
|
-
/**
|
|
164
|
-
* This function causes an effect. The context must have the capability. In a run, the host does the effect,
|
|
165
|
-
* and the steps wait when the host waits. In an evaluation, the effect gives its payload. Take its result with
|
|
166
|
-
* `yield*`.
|
|
278
|
+
* This function causes an effect. In a run, the host does the effect, and the steps wait when the host waits. In
|
|
279
|
+
* an evaluation, the effect gives its payload. Take its result with `yield*`.
|
|
167
280
|
*
|
|
168
281
|
* @param kind - The kind of the effect, for example `log`.
|
|
169
282
|
* @param payload - The data of the effect.
|
|
170
283
|
* @returns The steps that give the result of the host, or an error value.
|
|
171
284
|
*/
|
|
172
|
-
effect(kind:
|
|
285
|
+
effect(kind: Cap, payload: unknown): Steps;
|
|
286
|
+
/**
|
|
287
|
+
* This function evaluates parts at the same time. Take the results with `yield*`.
|
|
288
|
+
*
|
|
289
|
+
* @param parts - The parts.
|
|
290
|
+
* @param options - The most parts that run at the same time.
|
|
291
|
+
* @returns The steps that give each result, in the order of the parts.
|
|
292
|
+
*/
|
|
293
|
+
all(parts: readonly RunPart[], options?: { readonly concurrency?: number }): Generator<Promise<unknown>, readonly Result[], unknown>;
|
|
173
294
|
}
|
|
174
295
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
* evaluation, which does not wait at any time, and a run, in which the host does each effect.
|
|
178
|
-
*/
|
|
179
|
-
export type Runner = (input: RunInput) => Steps;
|
|
296
|
+
/** An evaluator that is a function of its input. The kernel calls it for each evaluation. */
|
|
297
|
+
export type EvalFn = (input: EvalInput) => Result;
|
|
180
298
|
|
|
181
|
-
/**
|
|
182
|
-
export interface
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
299
|
+
/** An evaluator that compiles a document one time into code. The code then evaluates it many times. */
|
|
300
|
+
export interface CompiledEvaluator {
|
|
301
|
+
/**
|
|
302
|
+
* This function compiles a document.
|
|
303
|
+
*
|
|
304
|
+
* @param input - The document, its kind and its compiled parts.
|
|
305
|
+
* @returns The code that evaluates the document.
|
|
306
|
+
*/
|
|
307
|
+
readonly compile: (input: CompileInput) => Code;
|
|
188
308
|
}
|
|
189
309
|
|
|
190
|
-
/** An
|
|
191
|
-
export
|
|
192
|
-
readonly index: number;
|
|
193
|
-
readonly kind: string;
|
|
194
|
-
readonly payload: Json;
|
|
195
|
-
/** The ID of the trace node that caused the effect. */
|
|
196
|
-
readonly node: number;
|
|
197
|
-
/** The number of events before the effect. The effect comes before the end of its node. */
|
|
198
|
-
readonly at: number;
|
|
199
|
-
}
|
|
310
|
+
/** An evaluator: a function of its input, or a compiler. */
|
|
311
|
+
export type Evaluator = EvalFn | CompiledEvaluator;
|
|
200
312
|
|
|
201
|
-
/**
|
|
202
|
-
export type
|
|
313
|
+
/** A runner that is a generator function of its input. */
|
|
314
|
+
export type RunFn = (input: RunInput) => Steps;
|
|
203
315
|
|
|
204
|
-
/**
|
|
205
|
-
export interface
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
316
|
+
/** A runner that compiles a document one time into code that can wait. */
|
|
317
|
+
export interface CompiledRunner {
|
|
318
|
+
/**
|
|
319
|
+
* This function compiles a document.
|
|
320
|
+
*
|
|
321
|
+
* @param input - The document, its kind and its compiled parts.
|
|
322
|
+
* @returns The code that runs the document.
|
|
323
|
+
*/
|
|
324
|
+
readonly compileRun: (input: CompileInput) => RunCode;
|
|
210
325
|
}
|
|
211
326
|
|
|
212
327
|
/**
|
|
213
|
-
*
|
|
214
|
-
* in
|
|
328
|
+
* A runner: the code of a kind whose parts or effects can wait. The same runner serves an evaluation, which does
|
|
329
|
+
* not wait at any time, and a run, in which the host does each effect.
|
|
330
|
+
*/
|
|
331
|
+
export type Runner = RunFn | CompiledRunner;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* A machine: a world of kinds, and the code of each evaluator name, of each `deref` and of each walker.
|
|
215
335
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* @returns `true` when one capability permits it.
|
|
336
|
+
* - `maxDepth`: the deepest nesting of documents. The default is 1000.
|
|
337
|
+
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities.
|
|
219
338
|
*/
|
|
220
|
-
export
|
|
221
|
-
|
|
339
|
+
export interface Machine extends Navigator {
|
|
340
|
+
readonly evaluators: Readonly<Record<string, Evaluator>>;
|
|
341
|
+
/** The runners, by evaluator name. A runner wins over an evaluator of the same name. */
|
|
342
|
+
readonly runners?: Readonly<Record<string, Runner>>;
|
|
343
|
+
readonly maxDepth?: number;
|
|
344
|
+
readonly permits?: (caps: readonly Cap[], kind: readonly Key[], context: Context) => boolean;
|
|
222
345
|
}
|
|
223
346
|
|
|
224
347
|
/** One evaluation in a trace. The children are the evaluations of its parts, in the order that they started. */
|
|
225
348
|
export interface TraceNode {
|
|
226
349
|
readonly id: number;
|
|
227
|
-
/** The keys from the root document to this document. */
|
|
350
|
+
/** The keys from the root document to this document, as texts. */
|
|
228
351
|
readonly path: readonly string[];
|
|
229
352
|
/** The document. A bare value has the type that its position declares. */
|
|
230
353
|
readonly doc: Envelope;
|
|
@@ -236,9 +359,9 @@ export interface TraceNode {
|
|
|
236
359
|
/** The names that the document could read when its evaluation started. */
|
|
237
360
|
readonly names: readonly string[];
|
|
238
361
|
/** The names and their values when the evaluation started. */
|
|
239
|
-
readonly scope: Readonly<Record<string,
|
|
362
|
+
readonly scope: Readonly<Record<string, unknown>>;
|
|
240
363
|
/** The capabilities that the document had when its evaluation started. */
|
|
241
|
-
readonly caps: readonly
|
|
364
|
+
readonly caps: readonly Cap[];
|
|
242
365
|
/** A short text for the root `self` when the evaluation started. */
|
|
243
366
|
readonly self: string;
|
|
244
367
|
/** The time of the start. Each start and each end takes one tick. */
|
|
@@ -268,8 +391,8 @@ interface Building {
|
|
|
268
391
|
answer: Answer;
|
|
269
392
|
side: Side;
|
|
270
393
|
names: readonly string[];
|
|
271
|
-
scope: Readonly<Record<string,
|
|
272
|
-
caps: readonly
|
|
394
|
+
scope: Readonly<Record<string, unknown>>;
|
|
395
|
+
caps: readonly Cap[];
|
|
273
396
|
self: string;
|
|
274
397
|
enter: number;
|
|
275
398
|
exit: number;
|
|
@@ -278,22 +401,221 @@ interface Building {
|
|
|
278
401
|
effects: number[];
|
|
279
402
|
}
|
|
280
403
|
|
|
404
|
+
/** What a trace node knows from the compilation. */
|
|
405
|
+
interface Meta {
|
|
406
|
+
readonly path: readonly string[];
|
|
407
|
+
readonly doc: Envelope;
|
|
408
|
+
readonly declared: boolean;
|
|
409
|
+
readonly answer: Answer;
|
|
410
|
+
readonly side: Side;
|
|
411
|
+
}
|
|
412
|
+
|
|
281
413
|
function selfLabel(self: Thing): string {
|
|
282
|
-
|
|
283
|
-
|
|
414
|
+
try {
|
|
415
|
+
if (isHostThing(self)) return self.label;
|
|
416
|
+
if (self === null || self === undefined) return "none";
|
|
417
|
+
return (JSON.stringify(self) ?? describe(self)).slice(0, 40);
|
|
418
|
+
} catch {
|
|
419
|
+
return describe(self).slice(0, 40);
|
|
420
|
+
}
|
|
284
421
|
}
|
|
285
422
|
|
|
286
423
|
const NO_ANSWER: Answer = { verb: "eval", error: "no-answer", steps: [] };
|
|
287
424
|
|
|
288
|
-
/**
|
|
289
|
-
interface
|
|
290
|
-
readonly
|
|
291
|
-
|
|
292
|
-
readonly
|
|
293
|
-
|
|
425
|
+
/** A signal of the host that stops a run: the shape of the standard `AbortSignal`. */
|
|
426
|
+
export interface StopSignal {
|
|
427
|
+
readonly aborted: boolean;
|
|
428
|
+
readonly reason?: unknown;
|
|
429
|
+
addEventListener(type: "abort", listener: () => void, options?: { readonly once?: boolean }): void;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** The options of a run. */
|
|
433
|
+
export interface RunOptions {
|
|
434
|
+
/** The host code that does each effect. Without it, an effect gives its payload, as in an evaluation. */
|
|
435
|
+
readonly perform?: Perform;
|
|
436
|
+
/** The type of a bare root value. */
|
|
437
|
+
readonly declaredType?: string;
|
|
438
|
+
/** A signal of the host. When it aborts, the run stops with `#ABORT`. */
|
|
439
|
+
readonly signal?: StopSignal;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** The options of an evaluation of a plan. */
|
|
443
|
+
export interface EvalOptions {
|
|
444
|
+
/** A list that gets each effect, in its order. */
|
|
445
|
+
readonly effects?: Effect[];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
449
|
+
class Session implements Runtime {
|
|
294
450
|
readonly effects: Effect[];
|
|
295
|
-
tick
|
|
296
|
-
nextId
|
|
451
|
+
tick = 0;
|
|
452
|
+
nextId = 0;
|
|
453
|
+
current: Building | undefined;
|
|
454
|
+
readonly holder: Building | undefined;
|
|
455
|
+
|
|
456
|
+
constructor(
|
|
457
|
+
readonly machine: Machine,
|
|
458
|
+
readonly running: boolean,
|
|
459
|
+
readonly traced: boolean,
|
|
460
|
+
readonly perform: Perform | undefined,
|
|
461
|
+
readonly signal: StopSignal | undefined,
|
|
462
|
+
effects: Effect[] | undefined,
|
|
463
|
+
) {
|
|
464
|
+
this.effects = effects ?? [];
|
|
465
|
+
this.holder = traced ? holderNode() : undefined;
|
|
466
|
+
this.current = this.holder;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
470
|
+
return resolve(this.machine, ctx, path, { hops: options?.hops ?? this.traced });
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private permitted(ctx: Context, keys: readonly Key[]): boolean {
|
|
474
|
+
return this.machine.permits === undefined ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private record(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result | Effect {
|
|
478
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
479
|
+
const text = keys.join(".");
|
|
480
|
+
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
481
|
+
const node = this.current;
|
|
482
|
+
const effect: Effect = { index: this.effects.length, kind: text, path: keys, payload, node: node?.id ?? -1, at: this.tick, place, context: ctx };
|
|
483
|
+
this.effects.push(effect);
|
|
484
|
+
if (node !== undefined) node.effects.push(effect.index);
|
|
485
|
+
return effect;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
effect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result {
|
|
489
|
+
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
490
|
+
if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
491
|
+
const made = this.record(ctx, kind, payload, place);
|
|
492
|
+
return "ok" in made ? made : ok(payload);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
*runEffect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Steps {
|
|
496
|
+
const made = this.record(ctx, kind, payload, place);
|
|
497
|
+
if ("ok" in made) return made;
|
|
498
|
+
const perform = this.perform;
|
|
499
|
+
if (!this.running || perform === undefined) return ok(payload);
|
|
500
|
+
const failed = (error: unknown): Result => fail("#CALC!", `The host failed the effect ${made.kind}: ${describe(error)}`, { place, cause: error });
|
|
501
|
+
let done: unknown;
|
|
502
|
+
try {
|
|
503
|
+
done = perform(made);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
return failed(error);
|
|
506
|
+
}
|
|
507
|
+
if (!(done instanceof Promise)) return ok(done);
|
|
508
|
+
return (yield done.then((value: unknown) => ok(value), failed)) as Result;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
*all(list: readonly (() => Steps)[], options: { readonly concurrency?: number } = {}): Generator<Promise<unknown>, readonly Result[], unknown> {
|
|
512
|
+
const results: Result[] = new Array<Result>(list.length);
|
|
513
|
+
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
514
|
+
interface Active {
|
|
515
|
+
readonly index: number;
|
|
516
|
+
readonly steps: Steps;
|
|
517
|
+
pending: Promise<unknown>;
|
|
518
|
+
}
|
|
519
|
+
const active: Active[] = [];
|
|
520
|
+
let next = 0;
|
|
521
|
+
const advance = (index: number, steps: Steps, input: { readonly value?: unknown; readonly error?: unknown; readonly threw: boolean }): void => {
|
|
522
|
+
let step: IteratorResult<Promise<unknown>, Result>;
|
|
523
|
+
try {
|
|
524
|
+
step = input.threw ? steps.throw(input.error) : steps.next(input.value);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
results[index] = thrown(error, "a part");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (step.done === true) {
|
|
530
|
+
results[index] = step.value;
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const item: Active = { index, steps, pending: Promise.resolve() };
|
|
534
|
+
item.pending = step.value.then(
|
|
535
|
+
(value) => ({ item, value, threw: false }),
|
|
536
|
+
(error: unknown) => ({ item, error, threw: true }),
|
|
537
|
+
);
|
|
538
|
+
active.push(item);
|
|
539
|
+
};
|
|
540
|
+
const start = (): void => {
|
|
541
|
+
while (active.length < limit && next < list.length) {
|
|
542
|
+
const index = next;
|
|
543
|
+
next += 1;
|
|
544
|
+
const begin = list[index] as () => Steps;
|
|
545
|
+
let steps: Steps;
|
|
546
|
+
try {
|
|
547
|
+
steps = begin();
|
|
548
|
+
} catch (error) {
|
|
549
|
+
results[index] = thrown(error, "a part");
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
advance(index, steps, { threw: false });
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
start();
|
|
556
|
+
while (active.length > 0) {
|
|
557
|
+
const settled = (yield Promise.race(active.map((item) => item.pending))) as {
|
|
558
|
+
readonly item: Active;
|
|
559
|
+
readonly value?: unknown;
|
|
560
|
+
readonly error?: unknown;
|
|
561
|
+
readonly threw: boolean;
|
|
562
|
+
};
|
|
563
|
+
active.splice(active.indexOf(settled.item), 1);
|
|
564
|
+
advance(settled.item.index, settled.item.steps, settled);
|
|
565
|
+
start();
|
|
566
|
+
}
|
|
567
|
+
return results;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
enter(meta: Meta, ctx: Context): Building {
|
|
571
|
+
const node: Building = {
|
|
572
|
+
id: this.nextId,
|
|
573
|
+
path: meta.path,
|
|
574
|
+
doc: meta.doc,
|
|
575
|
+
declared: meta.declared,
|
|
576
|
+
answer: meta.answer,
|
|
577
|
+
side: meta.side,
|
|
578
|
+
names: Object.keys(ctx.names),
|
|
579
|
+
scope: ctx.names,
|
|
580
|
+
caps: ctx.caps,
|
|
581
|
+
self: selfLabel(ctx.self),
|
|
582
|
+
enter: this.tick,
|
|
583
|
+
exit: this.tick,
|
|
584
|
+
result: fail("#NOEVAL", "The evaluation did not end."),
|
|
585
|
+
children: [],
|
|
586
|
+
effects: [],
|
|
587
|
+
};
|
|
588
|
+
this.nextId += 1;
|
|
589
|
+
this.tick += 1;
|
|
590
|
+
(this.current ?? (this.holder as Building)).children.push(node);
|
|
591
|
+
return node;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
exit(node: Building, result: Result): void {
|
|
595
|
+
node.result = result;
|
|
596
|
+
node.exit = this.tick;
|
|
597
|
+
this.tick += 1;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function holderNode(): Building {
|
|
602
|
+
return {
|
|
603
|
+
id: -1,
|
|
604
|
+
path: [],
|
|
605
|
+
doc: { type: "?", value: null },
|
|
606
|
+
declared: false,
|
|
607
|
+
answer: NO_ANSWER,
|
|
608
|
+
side: "derive",
|
|
609
|
+
names: [],
|
|
610
|
+
scope: {},
|
|
611
|
+
caps: [],
|
|
612
|
+
self: "none",
|
|
613
|
+
enter: 0,
|
|
614
|
+
exit: 0,
|
|
615
|
+
result: ok(null),
|
|
616
|
+
children: [],
|
|
617
|
+
effects: [],
|
|
618
|
+
};
|
|
297
619
|
}
|
|
298
620
|
|
|
299
621
|
/** This function drives steps that cannot wait. */
|
|
@@ -301,185 +623,356 @@ function drive(steps: Steps): Result {
|
|
|
301
623
|
const step = steps.next();
|
|
302
624
|
if (step.done === true) return step.value;
|
|
303
625
|
void step.value.catch(() => undefined);
|
|
626
|
+
void steps.return(fail("#CALC!", "A part waited, and its parent cannot wait."));
|
|
304
627
|
return fail("#CALC!", "A part waited, and its parent cannot wait.");
|
|
305
628
|
}
|
|
306
629
|
|
|
630
|
+
const ABORTED = Symbol("aborted");
|
|
631
|
+
|
|
307
632
|
/** This function drives steps that can wait. The steps before the first wait happen at once, in the call. */
|
|
308
|
-
async function driveWaiting(steps: Steps): Promise<Result> {
|
|
633
|
+
async function driveWaiting(steps: Steps, signal: StopSignal | undefined): Promise<Result> {
|
|
634
|
+
const abort = (): Result => {
|
|
635
|
+
const reason = fail("#ABORT", `The host stopped the run: ${describe(signal?.reason ?? "no reason")}`, { cause: signal?.reason });
|
|
636
|
+
try {
|
|
637
|
+
steps.return(reason);
|
|
638
|
+
} catch {
|
|
639
|
+
// The finally code of a runner threw. The run stops with the abort.
|
|
640
|
+
}
|
|
641
|
+
return reason;
|
|
642
|
+
};
|
|
643
|
+
const aborted =
|
|
644
|
+
signal === undefined
|
|
645
|
+
? undefined
|
|
646
|
+
: new Promise<typeof ABORTED>((resolve) => {
|
|
647
|
+
if (signal.aborted) resolve(ABORTED);
|
|
648
|
+
else signal.addEventListener("abort", () => resolve(ABORTED), { once: true });
|
|
649
|
+
});
|
|
650
|
+
if (signal?.aborted === true) return abort();
|
|
309
651
|
let step = steps.next();
|
|
310
652
|
while (step.done !== true) {
|
|
311
|
-
let value:
|
|
653
|
+
let value: unknown;
|
|
312
654
|
try {
|
|
313
|
-
value = await step.value;
|
|
655
|
+
value = aborted === undefined ? await step.value : await Promise.race([step.value, aborted]);
|
|
314
656
|
} catch (error) {
|
|
315
657
|
step = steps.throw(error);
|
|
316
658
|
continue;
|
|
317
659
|
}
|
|
660
|
+
if (value === ABORTED) return abort();
|
|
318
661
|
step = steps.next(value);
|
|
319
662
|
}
|
|
320
663
|
return step.value;
|
|
321
664
|
}
|
|
322
665
|
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
666
|
+
function isCompiledEvaluator(evaluator: Evaluator): evaluator is CompiledEvaluator {
|
|
667
|
+
return typeof evaluator === "object" && evaluator !== null && typeof (evaluator as CompiledEvaluator).compile === "function";
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function isCompiledRunner(runner: Runner): runner is CompiledRunner {
|
|
671
|
+
return typeof runner === "object" && runner !== null && typeof (runner as CompiledRunner).compileRun === "function";
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** The options of a compilation. */
|
|
675
|
+
export interface PrepareOptions {
|
|
676
|
+
/** The type of a bare root value. */
|
|
677
|
+
readonly declaredType?: string;
|
|
678
|
+
/** `true`: each evaluation records a trace. The default is `false`. */
|
|
679
|
+
readonly trace?: boolean;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
|
|
683
|
+
export interface Plan {
|
|
684
|
+
readonly machine: Machine;
|
|
685
|
+
/** `true` when each evaluation records a trace. */
|
|
686
|
+
readonly traced: boolean;
|
|
687
|
+
/** The compiled root document. */
|
|
688
|
+
readonly root: CompiledPart;
|
|
689
|
+
/**
|
|
690
|
+
* This function evaluates the document. It does not wait, and the host does no effect.
|
|
691
|
+
*
|
|
692
|
+
* @param context - The names and the capabilities at the root.
|
|
693
|
+
* @param options - A list that gets the effects.
|
|
694
|
+
* @returns The result.
|
|
695
|
+
*/
|
|
696
|
+
evaluate(context?: Context, options?: EvalOptions): Result;
|
|
697
|
+
/**
|
|
698
|
+
* This function runs the document: the host does each effect, and the run waits when the host waits.
|
|
699
|
+
*
|
|
700
|
+
* @param context - The names and the capabilities at the root.
|
|
701
|
+
* @param options - The host code that does the effects, and a signal to stop the run.
|
|
702
|
+
* @returns The result, when the run ends.
|
|
703
|
+
*/
|
|
704
|
+
run(context?: Context, options?: RunOptions): Promise<Result>;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
interface Compiler {
|
|
708
|
+
readonly machine: Machine;
|
|
709
|
+
readonly traced: boolean;
|
|
710
|
+
readonly maxDepth: number;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string): { readonly code: Code; readonly run: RunCode } {
|
|
714
|
+
const result = fail(code, reason, { place });
|
|
715
|
+
const plain: Code = () => result;
|
|
716
|
+
return finish(c, meta, plain, function* () {
|
|
717
|
+
return result;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly code: Code; readonly run: RunCode } {
|
|
722
|
+
if (!c.traced) return { code, run };
|
|
723
|
+
const tracedCode: Code = (ctx, rt) => {
|
|
724
|
+
const s = rt as Session;
|
|
725
|
+
const node = s.enter(meta, ctx);
|
|
726
|
+
const before = s.current;
|
|
727
|
+
s.current = node;
|
|
728
|
+
let result: Result;
|
|
729
|
+
try {
|
|
730
|
+
result = code(ctx, rt);
|
|
731
|
+
} finally {
|
|
732
|
+
s.current = before;
|
|
733
|
+
}
|
|
734
|
+
s.exit(node, result);
|
|
358
735
|
return result;
|
|
359
736
|
};
|
|
737
|
+
const tracedRun: RunCode = function* (ctx, rt) {
|
|
738
|
+
const s = rt as Session;
|
|
739
|
+
const node = s.enter(meta, ctx);
|
|
740
|
+
const steps = run(ctx, rt);
|
|
741
|
+
let input: unknown;
|
|
742
|
+
let threw = false;
|
|
743
|
+
for (;;) {
|
|
744
|
+
const before = s.current;
|
|
745
|
+
s.current = node;
|
|
746
|
+
let step: IteratorResult<Promise<unknown>, Result>;
|
|
747
|
+
try {
|
|
748
|
+
step = threw ? steps.throw(input) : steps.next(input);
|
|
749
|
+
} finally {
|
|
750
|
+
s.current = before;
|
|
751
|
+
}
|
|
752
|
+
if (step.done === true) {
|
|
753
|
+
s.exit(node, step.value);
|
|
754
|
+
return step.value;
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
input = yield step.value;
|
|
758
|
+
threw = false;
|
|
759
|
+
} catch (error) {
|
|
760
|
+
input = error;
|
|
761
|
+
threw = true;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
return { code: tracedCode, run: tracedRun };
|
|
766
|
+
}
|
|
360
767
|
|
|
768
|
+
function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number): { readonly code: Code; readonly run: RunCode } {
|
|
769
|
+
const envelope = isEnvelope(value) ? (value as Envelope) : undefined;
|
|
770
|
+
const doc: Envelope = envelope ?? { type: declared ?? "?", value: value as Json };
|
|
771
|
+
const path = place.map(String);
|
|
772
|
+
const meta0: Meta = { path, doc, declared: envelope === undefined, answer: NO_ANSWER, side: "derive" };
|
|
361
773
|
if (envelope === undefined && declared === undefined) {
|
|
362
|
-
return
|
|
774
|
+
return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
363
775
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
if (answer.error === "unknown-kind") return
|
|
776
|
+
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
777
|
+
|
|
778
|
+
const answer = ask(c.machine.world, envelope ?? {}, "eval", declared === undefined ? {} : { declaredType: declared });
|
|
779
|
+
const found = lookupKind(c.machine.world, doc.type);
|
|
780
|
+
const kind = found.ok ? found.kind : undefined;
|
|
781
|
+
const meta: Meta = { ...meta0, answer, side: kind?.side ?? "derive" };
|
|
782
|
+
const kindText = typeName(doc.type);
|
|
783
|
+
if (answer.error === "unknown-kind") return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
|
|
372
784
|
const name = answer.evaluator;
|
|
373
|
-
const runners =
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return finish(fail("#NOEVAL", `No rung gives an evaluator for ${typeName(doc.type)}.`));
|
|
379
|
-
}
|
|
380
|
-
if (runner !== undefined && s.running && !canWait) {
|
|
381
|
-
return finish(fail("#CALC!", `${typeName(doc.type)} waits for effects, and its parent cannot wait. Give the parent a runner.`));
|
|
785
|
+
const runners = c.machine.runners ?? {};
|
|
786
|
+
const runner = name !== undefined && Object.hasOwn(runners, name) ? runners[name] : undefined;
|
|
787
|
+
const evaluator = name !== undefined && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : undefined;
|
|
788
|
+
if (name === undefined || (runner === undefined && evaluator === undefined)) {
|
|
789
|
+
return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
382
790
|
}
|
|
383
791
|
|
|
384
792
|
const split = splitParts(kind?.parts, doc.value);
|
|
385
|
-
if (!split.ok)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return
|
|
793
|
+
if (!split.ok) {
|
|
794
|
+
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
795
|
+
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
796
|
+
}
|
|
797
|
+
const parts: CompiledPart[] = split.parts.map((slot) => {
|
|
798
|
+
const compiled = compileNode(c, slot.item, [...place, ...slot.path], slot.of, depth + 1);
|
|
799
|
+
const header = isEnvelope(slot.item) && isRecord(slot.item) && typeof slot.item["name"] === "string" ? slot.item["name"] : undefined;
|
|
800
|
+
return { key: slot.key, path: slot.path, ...(header === undefined ? {} : { name: header }), code: compiled.code, run: compiled.run };
|
|
801
|
+
});
|
|
802
|
+
const typePath = found.ok ? found.path : [];
|
|
803
|
+
const input: CompileInput = {
|
|
804
|
+
kind: kindText,
|
|
805
|
+
typePath,
|
|
806
|
+
doc,
|
|
807
|
+
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
808
|
+
data: split.data,
|
|
809
|
+
parts,
|
|
810
|
+
place,
|
|
811
|
+
machine: c.machine,
|
|
393
812
|
};
|
|
394
|
-
const
|
|
395
|
-
const resolveHere = (steps: readonly Json[]): Resolution => resolve(s.machine, ctx, steps);
|
|
813
|
+
const who = `the evaluator ${name}`;
|
|
396
814
|
|
|
397
815
|
if (runner !== undefined) {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
data: split.data,
|
|
406
|
-
parts,
|
|
407
|
-
context: ctx,
|
|
408
|
-
resolve: resolveHere,
|
|
409
|
-
effect: function* (effectKind: string, payload: Json): Steps {
|
|
410
|
-
const made = record(effectKind, payload);
|
|
411
|
-
if ("ok" in made) return made;
|
|
412
|
-
if (!s.running || s.perform === undefined) return ok(payload);
|
|
413
|
-
const failed = (error: unknown): Result => fail("#CALC!", `The host failed the effect ${effectKind}: ${String(error)}`);
|
|
414
|
-
let done: Json | Promise<Json>;
|
|
415
|
-
try {
|
|
416
|
-
done = s.perform(made);
|
|
417
|
-
} catch (error) {
|
|
418
|
-
return failed(error);
|
|
419
|
-
}
|
|
420
|
-
if (!(done instanceof Promise)) return ok(done ?? null);
|
|
421
|
-
return yield done.then((result) => ok(result ?? null), failed);
|
|
422
|
-
},
|
|
816
|
+
const inner: RunCode = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
817
|
+
const run: RunCode = function* (ctx, rt) {
|
|
818
|
+
try {
|
|
819
|
+
return placed(yield* inner(ctx, rt), place);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
return placed(thrown(error, `the runner ${name}`), place);
|
|
822
|
+
}
|
|
423
823
|
};
|
|
824
|
+
const code: Code = (ctx, rt) =>
|
|
825
|
+
rt.running
|
|
826
|
+
? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place })
|
|
827
|
+
: drive(run(ctx, rt));
|
|
828
|
+
return finish(c, meta, code, run);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const inner: Code = isCompiledEvaluator(evaluator as Evaluator)
|
|
832
|
+
? (evaluator as CompiledEvaluator).compile(input)
|
|
833
|
+
: interpret(evaluator as EvalFn, input, split.fill);
|
|
834
|
+
const code: Code = (ctx, rt) => {
|
|
424
835
|
try {
|
|
425
|
-
return
|
|
836
|
+
return placed(inner(ctx, rt), place);
|
|
426
837
|
} catch (error) {
|
|
427
|
-
return
|
|
838
|
+
return placed(thrown(error, who), place);
|
|
428
839
|
}
|
|
429
|
-
}
|
|
840
|
+
};
|
|
841
|
+
return finish(c, meta, code, function* (ctx, rt) {
|
|
842
|
+
return code(ctx, rt);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
430
845
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
846
|
+
function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): Code {
|
|
847
|
+
const { kind, typePath, doc, data, place } = input;
|
|
848
|
+
const compiled = input.parts;
|
|
849
|
+
const hasParts = compiled.length > 0;
|
|
850
|
+
return (ctx, rt) => {
|
|
851
|
+
const parts: Part[] = compiled.map((part) => ({
|
|
852
|
+
key: part.key,
|
|
853
|
+
path: part.path,
|
|
854
|
+
...(part.name === undefined ? {} : { name: part.name }),
|
|
855
|
+
evaluate: (override?: Context) => part.code(override === undefined ? ctx : narrow(ctx, override), rt),
|
|
856
|
+
}));
|
|
857
|
+
return fn({
|
|
858
|
+
kind,
|
|
859
|
+
typePath,
|
|
860
|
+
doc,
|
|
861
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
862
|
+
data,
|
|
863
|
+
parts,
|
|
864
|
+
context: ctx,
|
|
865
|
+
place,
|
|
866
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
867
|
+
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
868
|
+
});
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): RunCode {
|
|
873
|
+
const { kind, typePath, doc, data, place } = input;
|
|
874
|
+
const compiled = input.parts;
|
|
875
|
+
const hasParts = compiled.length > 0;
|
|
876
|
+
return (ctx, rt) => {
|
|
877
|
+
const parts: RunPart[] = compiled.map((part) => ({
|
|
878
|
+
key: part.key,
|
|
879
|
+
path: part.path,
|
|
880
|
+
...(part.name === undefined ? {} : { name: part.name }),
|
|
881
|
+
evaluate: (override?: Context) => part.run(override === undefined ? ctx : narrow(ctx, override), rt),
|
|
882
|
+
}));
|
|
883
|
+
return fn({
|
|
884
|
+
kind,
|
|
885
|
+
typePath,
|
|
886
|
+
doc,
|
|
887
|
+
value: hasParts ? fill(parts) : doc.value,
|
|
888
|
+
data,
|
|
889
|
+
parts,
|
|
890
|
+
context: ctx,
|
|
891
|
+
place,
|
|
892
|
+
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
893
|
+
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
894
|
+
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options),
|
|
895
|
+
});
|
|
447
896
|
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function rootContext(machine: Machine, ctx: Context): Context {
|
|
900
|
+
return ctx.world === null ? { ...ctx, world: worldThing(machine.world) } : ctx;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* This function compiles a document into a plan. The ladder, the kinds and the parts are known after it. A plan
|
|
905
|
+
* evaluates many times. A document that does not compile gives a plan that fails with the reason, when it evaluates.
|
|
906
|
+
*
|
|
907
|
+
* @param machine - The world and the code of the evaluators.
|
|
908
|
+
* @param doc - The document, as JSON.
|
|
909
|
+
* @param options - The type of a bare root value, and a trace.
|
|
910
|
+
* @returns The plan.
|
|
911
|
+
*/
|
|
912
|
+
export function prepare(machine: Machine, doc: unknown, options: PrepareOptions = {}): Plan {
|
|
913
|
+
const c: Compiler = { machine, traced: options.trace === true, maxDepth: machine.maxDepth ?? 1000 };
|
|
914
|
+
let root: { readonly code: Code; readonly run: RunCode };
|
|
448
915
|
try {
|
|
449
|
-
|
|
916
|
+
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
450
917
|
} catch (error) {
|
|
451
|
-
|
|
918
|
+
const result = thrown(error, "the compilation");
|
|
919
|
+
root = {
|
|
920
|
+
code: () => result,
|
|
921
|
+
run: function* () {
|
|
922
|
+
return result;
|
|
923
|
+
},
|
|
924
|
+
};
|
|
452
925
|
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function holderNode(): Building {
|
|
926
|
+
const compiled: CompiledPart = { key: "", path: [], code: root.code, run: root.run };
|
|
456
927
|
return {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
exit: 0,
|
|
469
|
-
result: ok(null),
|
|
470
|
-
children: [],
|
|
471
|
-
effects: [],
|
|
928
|
+
machine,
|
|
929
|
+
traced: c.traced,
|
|
930
|
+
root: compiled,
|
|
931
|
+
evaluate(context: Context = makeContext(), evalOptions: EvalOptions = {}): Result {
|
|
932
|
+
const s = new Session(machine, false, c.traced, undefined, undefined, evalOptions.effects);
|
|
933
|
+
return root.code(rootContext(machine, context), s);
|
|
934
|
+
},
|
|
935
|
+
run(context: Context = makeContext(), runOptions: RunOptions = {}): Promise<Result> {
|
|
936
|
+
const s = new Session(machine, true, c.traced, runOptions.perform, runOptions.signal, undefined);
|
|
937
|
+
return driveWaiting(root.run(rootContext(machine, context), s), runOptions.signal);
|
|
938
|
+
},
|
|
472
939
|
};
|
|
473
940
|
}
|
|
474
941
|
|
|
475
|
-
|
|
476
|
-
|
|
942
|
+
/**
|
|
943
|
+
* This function makes a cache of plans for one machine. A document object compiles one time. Use it only for
|
|
944
|
+
* documents that do not change after they are made.
|
|
945
|
+
*
|
|
946
|
+
* @param machine - The machine.
|
|
947
|
+
* @param options - The options of each compilation.
|
|
948
|
+
* @returns A function that gives the plan of a document.
|
|
949
|
+
*/
|
|
950
|
+
export function planner(machine: Machine, options: PrepareOptions = {}): (doc: unknown) => Plan {
|
|
951
|
+
const objects = new WeakMap<object, Plan>();
|
|
952
|
+
const values = new Map<unknown, Plan>();
|
|
953
|
+
return (doc) => {
|
|
954
|
+
if (typeof doc === "object" && doc !== null) {
|
|
955
|
+
let plan = objects.get(doc);
|
|
956
|
+
if (plan === undefined) {
|
|
957
|
+
plan = prepare(machine, doc, options);
|
|
958
|
+
objects.set(doc, plan);
|
|
959
|
+
}
|
|
960
|
+
return plan;
|
|
961
|
+
}
|
|
962
|
+
let plan = values.get(doc);
|
|
963
|
+
if (plan === undefined) {
|
|
964
|
+
plan = prepare(machine, doc, options);
|
|
965
|
+
if (values.size > 1000) values.clear();
|
|
966
|
+
values.set(doc, plan);
|
|
967
|
+
}
|
|
968
|
+
return plan;
|
|
969
|
+
};
|
|
477
970
|
}
|
|
478
971
|
|
|
479
|
-
function record(s: Session,
|
|
480
|
-
const root = holder
|
|
972
|
+
function record(s: Session, result: Result): Run {
|
|
973
|
+
const root = s.holder?.children[0];
|
|
481
974
|
if (root === undefined) throw new Error("The evaluation made no trace.");
|
|
482
|
-
return { result, trace: root, effects: s.effects, time: s.tick };
|
|
975
|
+
return { result, trace: root as TraceNode, effects: s.effects, time: s.tick };
|
|
483
976
|
}
|
|
484
977
|
|
|
485
978
|
/**
|
|
@@ -495,29 +988,29 @@ function record(s: Session, holder: Building, result: Result): Run {
|
|
|
495
988
|
* @param declaredType - The type of a bare root value.
|
|
496
989
|
* @returns The result, the trace and the effects in their order.
|
|
497
990
|
*/
|
|
498
|
-
export function evaluate(machine: Machine, doc:
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
const result =
|
|
502
|
-
return record(s,
|
|
991
|
+
export function evaluate(machine: Machine, doc: unknown, context: Context = makeContext(), declaredType?: string): Run {
|
|
992
|
+
const plan = prepare(machine, doc, { trace: true, ...(declaredType === undefined ? {} : { declaredType }) });
|
|
993
|
+
const s = new Session(machine, false, true, undefined, undefined, undefined);
|
|
994
|
+
const result = plan.root.code(rootContext(machine, context), s);
|
|
995
|
+
return record(s, result);
|
|
503
996
|
}
|
|
504
997
|
|
|
505
998
|
/**
|
|
506
999
|
* This function runs a document: an evaluation in which the host does each effect. A runner waits for its
|
|
507
1000
|
* parts and for its effects, thus a step can read the result of the step before. The part of the run before the
|
|
508
|
-
* first wait happens at once, in the call.
|
|
1001
|
+
* first wait happens at once, in the call. The run records a trace.
|
|
509
1002
|
*
|
|
510
1003
|
* @param machine - The world, the evaluators and the runners.
|
|
511
1004
|
* @param doc - The document, as JSON.
|
|
512
1005
|
* @param context - The names and the capabilities at the root.
|
|
513
|
-
* @param options - The host code that does the effects,
|
|
1006
|
+
* @param options - The host code that does the effects, the type of a bare root value, and a signal.
|
|
514
1007
|
* @returns The result, the trace and the effects in their order, when the run ends.
|
|
515
1008
|
*/
|
|
516
|
-
export async function run(machine: Machine, doc:
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
const result = await driveWaiting(
|
|
520
|
-
return record(s,
|
|
1009
|
+
export async function run(machine: Machine, doc: unknown, context: Context = makeContext(), options: RunOptions = {}): Promise<Run> {
|
|
1010
|
+
const plan = prepare(machine, doc, { trace: true, ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }) });
|
|
1011
|
+
const s = new Session(machine, true, true, options.perform, options.signal, undefined);
|
|
1012
|
+
const result = await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal);
|
|
1013
|
+
return record(s, result);
|
|
521
1014
|
}
|
|
522
1015
|
|
|
523
1016
|
/**
|
|
@@ -527,11 +1020,17 @@ export async function run(machine: Machine, doc: Json, context: Context = makeCo
|
|
|
527
1020
|
* @param path - The keys from the root.
|
|
528
1021
|
* @returns The first evaluation at that path, or `undefined` when the document did not evaluate.
|
|
529
1022
|
*/
|
|
530
|
-
export function nodeAt(trace: TraceNode, path: readonly
|
|
1023
|
+
export function nodeAt(trace: TraceNode, path: readonly Key[]): TraceNode | undefined {
|
|
1024
|
+
const want = path.map(String);
|
|
531
1025
|
let node: TraceNode | undefined = trace;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
1026
|
+
let at = 0;
|
|
1027
|
+
while (node !== undefined && at < want.length) {
|
|
1028
|
+
const here: TraceNode = node;
|
|
1029
|
+
node = here.children.find((child) => {
|
|
1030
|
+
const tail = child.path.slice(here.path.length);
|
|
1031
|
+
return tail.length > 0 && tail.every((key, index) => key === want[at + index]);
|
|
1032
|
+
});
|
|
1033
|
+
if (node !== undefined) at += node.path.length - here.path.length;
|
|
535
1034
|
}
|
|
536
1035
|
return node;
|
|
537
1036
|
}
|
|
@@ -544,10 +1043,27 @@ export function nodeAt(trace: TraceNode, path: readonly string[]): TraceNode | u
|
|
|
544
1043
|
*/
|
|
545
1044
|
export function flatten(trace: TraceNode): readonly TraceNode[] {
|
|
546
1045
|
const out: TraceNode[] = [];
|
|
547
|
-
const
|
|
1046
|
+
const stack: TraceNode[] = [trace];
|
|
1047
|
+
while (stack.length > 0) {
|
|
1048
|
+
const node = stack.pop() as TraceNode;
|
|
548
1049
|
out.push(node);
|
|
549
|
-
node.children.
|
|
550
|
-
}
|
|
551
|
-
walk(trace);
|
|
1050
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) stack.push(node.children[index] as TraceNode);
|
|
1051
|
+
}
|
|
552
1052
|
return out.sort((a, b) => a.enter - b.enter);
|
|
553
1053
|
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* This function lists the errors of a trace: each node whose result is an error and whose parts did not fail
|
|
1057
|
+
* the same way. These nodes are where each error started.
|
|
1058
|
+
*
|
|
1059
|
+
* @param trace - The root of the trace.
|
|
1060
|
+
* @returns The errors, in the order of their start.
|
|
1061
|
+
*/
|
|
1062
|
+
export function failures(trace: TraceNode): readonly EvalError[] {
|
|
1063
|
+
return flatten(trace)
|
|
1064
|
+
.filter((node) => !node.result.ok && !node.children.some((child) => !child.result.ok && child.result.error === (node.result as { error: EvalError }).error))
|
|
1065
|
+
.map((node) => (node.result as { readonly error: EvalError }).error);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** The type of a document as a reference: re-exported for the compilers. */
|
|
1069
|
+
export type { TypeRef };
|