@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/evaluate.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, typeName } from "./doc.ts";
|
|
1
|
+
import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, isKey, typeName } from "./doc.ts";
|
|
2
2
|
import { type Split, partsOf, pathMatches } from "./parts.ts";
|
|
3
|
-
import { ask, type Answer } from "./ladder.ts";
|
|
4
|
-
import type { CapsRule,
|
|
5
|
-
import
|
|
3
|
+
import { answerOf, ask, type Answer } from "./ladder.ts";
|
|
4
|
+
import type { CapsRule, KindFacts, Side } from "./world.ts";
|
|
5
|
+
import type { Domain } from "./types.ts";
|
|
6
|
+
import type { TypeRule } from "./typing.ts";
|
|
7
|
+
import { type ErrorCode, type EvalError, type Result, Failure, fail, ok, placed, thrown, describe } from "./errors.ts";
|
|
6
8
|
import {
|
|
7
9
|
type Cap,
|
|
10
|
+
type CompiledPath,
|
|
8
11
|
type Context,
|
|
12
|
+
type Hop,
|
|
9
13
|
type Navigator,
|
|
10
14
|
type Resolution,
|
|
11
15
|
type ResolveOptions,
|
|
16
|
+
type Root,
|
|
12
17
|
type Thing,
|
|
18
|
+
ROOTS,
|
|
13
19
|
context as makeRootContext,
|
|
14
20
|
isHostThing,
|
|
15
21
|
resolve,
|
|
@@ -126,6 +132,40 @@ export function partCaps(rules: readonly CapsRule[] | undefined, path: readonly
|
|
|
126
132
|
return capsUnder(rulesFor(rules, path), caps);
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The key of the grant of a context (CAP.NARROW-CODE): the capabilities that the kernel gave the document of the
|
|
137
|
+
* context. A spread of a context keeps its grant. Thus compiled code passes the grant on in each context that it makes
|
|
138
|
+
* from the context of its document. Only the kernel writes a grant.
|
|
139
|
+
*/
|
|
140
|
+
const GRANT = /* @__PURE__ */ Symbol("coeval.grant");
|
|
141
|
+
|
|
142
|
+
/** A context with the grant of the kernel. */
|
|
143
|
+
type Granted = Context & { readonly [GRANT]?: readonly Cap[] };
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* This function gives a context whose capabilities its grant permits (CAP.NARROW-CODE). A context whose grant is its
|
|
147
|
+
* own list of capabilities stays the same object. Each other context keeps only the capabilities that its grant
|
|
148
|
+
* permits, and a context without a grant keeps none. The result has the grant of its capabilities.
|
|
149
|
+
*/
|
|
150
|
+
function granted(ctx: Context): Context {
|
|
151
|
+
const grant = (ctx as Granted)[GRANT];
|
|
152
|
+
if (grant === ctx.caps) return ctx;
|
|
153
|
+
const caps = grant === undefined ? NO_CAPS : ctx.caps.every((cap) => permits(grant, cap)) ? ctx.caps : ctx.caps.filter((cap) => permits(grant, cap));
|
|
154
|
+
const out: Granted = { ...ctx, caps, [GRANT]: caps };
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* This function gives the context of a part of a function evaluator. It is the context that the evaluator gives,
|
|
160
|
+
* narrowed by the context of the document (CAP.NARROW), with the grant of its capabilities.
|
|
161
|
+
*/
|
|
162
|
+
function handed(parent: Context, next: Context): Context {
|
|
163
|
+
const ctx = narrow(parent, next);
|
|
164
|
+
if ((ctx as Granted)[GRANT] === ctx.caps) return ctx;
|
|
165
|
+
const out: Granted = { ...ctx, [GRANT]: ctx.caps };
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
129
169
|
/**
|
|
130
170
|
* The steps of an evaluation that can wait. It yields a promise only when the host makes it wait. A runner takes the
|
|
131
171
|
* result of a part with `yield*`, thus a run goes on at once until an effect waits. A runner does not yield a
|
|
@@ -135,6 +175,7 @@ export type Steps = Generator<Promise<unknown>, Result, unknown>;
|
|
|
135
175
|
|
|
136
176
|
/** An effect that an evaluation caused, in the order of the effects. */
|
|
137
177
|
export interface Effect {
|
|
178
|
+
/** The number of effects before this effect. Under `all`, this number follows the order of the host. */
|
|
138
179
|
readonly index: number;
|
|
139
180
|
/** The kind of the effect, as a text with dots. */
|
|
140
181
|
readonly kind: string;
|
|
@@ -149,11 +190,180 @@ export interface Effect {
|
|
|
149
190
|
readonly place: readonly Key[];
|
|
150
191
|
/** The context of the document that caused the effect: its names, its node and its capabilities. */
|
|
151
192
|
readonly context: Context;
|
|
193
|
+
/**
|
|
194
|
+
* The stable address of the effect (RUN.ADDRESS). The order of the host does not change it. The address has the
|
|
195
|
+
* keys of the place, a `#` and the count of the earlier effects at that place in its scope. An example is
|
|
196
|
+
* `1/bind/x#0`. Each item of `all` is a scope, and its prefix comes first: `all0.1/` is the item 1 of the first `all`
|
|
197
|
+
* of the scope. The kernel gives each effect its address.
|
|
198
|
+
*/
|
|
199
|
+
readonly address?: string;
|
|
200
|
+
/**
|
|
201
|
+
* The outcome of the effect: the result that it gave its document (RUN.OUTCOME). The kernel sets it when the answer
|
|
202
|
+
* comes. It stays `undefined` when the run stops first, for example at an abort.
|
|
203
|
+
*/
|
|
204
|
+
readonly outcome?: Result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* One read of an evaluation: a path that code resolved, and what the path reached (READS.RECORD, D26). A schedule
|
|
209
|
+
* keeps the list of the reads of an evaluation. `verifyReads` resolves each free read again in a new context, and it
|
|
210
|
+
* tells if the old result stays valid.
|
|
211
|
+
*/
|
|
212
|
+
export interface Read {
|
|
213
|
+
/** The path that the code resolved: its root, and its steps with the value of each step document. */
|
|
214
|
+
readonly path: readonly unknown[];
|
|
215
|
+
/**
|
|
216
|
+
* What the path reached: the thing, or the error of the resolution. A host thing stays a thing. An exception of the
|
|
217
|
+
* host gives its error value.
|
|
218
|
+
*/
|
|
219
|
+
readonly reached: Result;
|
|
220
|
+
/**
|
|
221
|
+
* `true` when the read reads the context of the evaluation (READS.FREE). A read of a name that a kind binds, for
|
|
222
|
+
* example a name of `let`, is not free. A read of a `self` that a kind sets is not free either, for example a node
|
|
223
|
+
* of a tree. A path without a root is not free. `verifyReads` resolves only the free reads again.
|
|
224
|
+
*/
|
|
225
|
+
readonly free: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* `true` when the kernel adds the read, because a value keeps a root of the context for later (READS.KEPT). Such a
|
|
228
|
+
* read has the whole root as its path.
|
|
229
|
+
*/
|
|
230
|
+
readonly kept?: true;
|
|
231
|
+
/** The hops of the resolution, only when the code asked for them. `verifyReads` then compares each hop too. */
|
|
232
|
+
readonly hops?: readonly Hop[];
|
|
152
233
|
}
|
|
153
234
|
|
|
154
|
-
/** The
|
|
235
|
+
/** The text that names a resolution in the error of an exception of the host. */
|
|
236
|
+
const READ_FAILED = "the resolution of a path";
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* This function gives what a resolution reached as a result: the thing, or the error with its code and its reason.
|
|
240
|
+
* The record of a read and `verifyReads` share it.
|
|
241
|
+
*
|
|
242
|
+
* @param found - The resolution.
|
|
243
|
+
* @returns The result.
|
|
244
|
+
*/
|
|
245
|
+
export function reachedOf(found: Resolution): Result {
|
|
246
|
+
return found.ok ? ok(found.thing) : fail(found.code, found.reason);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* This function resolves the path of a read, and it gives an exception of the host as an error value. The record of
|
|
251
|
+
* a kept read and `verifyReads` use it.
|
|
252
|
+
*
|
|
253
|
+
* @param nav - The world and the code of the evaluators.
|
|
254
|
+
* @param ctx - The context.
|
|
255
|
+
* @param path - The path.
|
|
256
|
+
* @param hops - `true` records each hop.
|
|
257
|
+
* @returns What the path reached, and the hops when the resolution gave them.
|
|
258
|
+
*/
|
|
259
|
+
export function readAgain(nav: Navigator, ctx: Context, path: readonly unknown[], hops: boolean): { readonly reached: Result; readonly hops?: readonly Hop[] } {
|
|
260
|
+
try {
|
|
261
|
+
const found = resolve(nav, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
262
|
+
return hops ? { reached: reachedOf(found), hops: found.hops } : { reached: reachedOf(found) };
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return { reached: thrown(error, READ_FAILED) };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* This function tells if a read reads the context at the root of the evaluation (READS.FREE). A kind can bind a name
|
|
270
|
+
* or set `self` for its parts, and a read of that binding is not free. A path without a root reads no context. A read
|
|
271
|
+
* of each other root is free. When a kind changes such a root, the read resolves again to another value, thus
|
|
272
|
+
* `verifyReads` only does more work.
|
|
273
|
+
*/
|
|
274
|
+
function isFree(root: Context, ctx: Context, path: readonly unknown[]): boolean {
|
|
275
|
+
const head = path[0];
|
|
276
|
+
if (head === "self") return ctx === root || Object.is(ctx.self, root.self);
|
|
277
|
+
if (head !== "names") return typeof head === "string" && (ROOTS as readonly string[]).includes(head);
|
|
278
|
+
if (ctx === root || ctx.names === root.names) return true;
|
|
279
|
+
const name = path[1];
|
|
280
|
+
// A read of the whole record of the names reads each free name too.
|
|
281
|
+
if (!isKey(name)) return true;
|
|
282
|
+
const key = String(name);
|
|
283
|
+
const has = Object.hasOwn(ctx.names, key);
|
|
284
|
+
return has === Object.hasOwn(root.names, key) && (!has || Object.is(ctx.names[key], root.names[key]));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The host side of a run: it does one effect, and gives its result. A throw or a rejection is an error value, and a
|
|
289
|
+
* `Failure` gives its own error value. In an evaluation, this function is a mock: it answers at once, and a promise
|
|
290
|
+
* from it gives `#CALC!` (RUN.MOCK).
|
|
291
|
+
*/
|
|
155
292
|
export type Perform = (effect: Effect) => unknown;
|
|
156
293
|
|
|
294
|
+
/** The key of the journal of a recorder or of a replayer, on its function. Two copies of the kernel share it. */
|
|
295
|
+
export const JOURNAL: unique symbol = /* @__PURE__ */ Symbol.for("coeval.journal") as never;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The journal of a recorder or of a replayer, on its function `perform`. The kernel tells it the order in which the
|
|
299
|
+
* waits of each `all` settle. In a replay, the kernel asks the journal for that order (RUN.RECORD, RUN.REPLAY).
|
|
300
|
+
*/
|
|
301
|
+
export interface Journal {
|
|
302
|
+
/**
|
|
303
|
+
* This function gets the item whose wait settled.
|
|
304
|
+
*
|
|
305
|
+
* @param all - The address of the `all`, for example `all0` or `all0.1/all0`.
|
|
306
|
+
* @param item - The index of the item.
|
|
307
|
+
*/
|
|
308
|
+
readonly settled?: (all: string, item: number) => void;
|
|
309
|
+
/**
|
|
310
|
+
* This function gives the item whose wait settles next.
|
|
311
|
+
*
|
|
312
|
+
* @param all - The address of the `all`.
|
|
313
|
+
* @param waiting - The indices of the items that wait.
|
|
314
|
+
* @returns The index of an item that waits, or `undefined` for the order of the host.
|
|
315
|
+
*/
|
|
316
|
+
readonly next?: (all: string, waiting: readonly number[]) => number | undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** This function gives the journal of a function `perform`, when it has one. */
|
|
320
|
+
function journalOf(perform: Perform | undefined): Journal | undefined {
|
|
321
|
+
return perform === undefined ? undefined : (perform as { readonly [JOURNAL]?: Journal })[JOURNAL];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* This function gives the outcome of an effect that the host failed. A `Failure` gives its own error value, for
|
|
326
|
+
* example the recorded error of a replay or `#REPLAY!`. Each other exception gives `#CALC!`, with the exception as the
|
|
327
|
+
* cause (RUN.CAUSE).
|
|
328
|
+
*/
|
|
329
|
+
function hostFailed(kind: string, place: readonly Key[], error: unknown): Result {
|
|
330
|
+
if (error instanceof Failure) return placed({ ok: false, error: error.error }, place);
|
|
331
|
+
return fail("#CALC!", `The host failed the effect ${kind}: ${describe(error)}`, { place, cause: error });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The scope of the addresses of effects (RUN.ADDRESS). The root of an evaluation is one scope, and each item of `all`
|
|
336
|
+
* is a scope. The code of one scope does not interleave with itself, thus its counts do not depend on the host.
|
|
337
|
+
*/
|
|
338
|
+
interface Scope {
|
|
339
|
+
/** The text before each address of the scope, for example `all0.1/`. */
|
|
340
|
+
readonly prefix: string;
|
|
341
|
+
/** The number of earlier effects at each place, by the text of the place. It is made at the first effect. */
|
|
342
|
+
counts: Map<string, number> | undefined;
|
|
343
|
+
/** The number of earlier uses of `all` in the scope. */
|
|
344
|
+
alls: number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* This function gives the text of a place in an address: each key, with a slash between two keys. A text key with
|
|
349
|
+
* other characters than letters, digits, `_`, `$` and `-` gets quotes.
|
|
350
|
+
*
|
|
351
|
+
* @param place - The keys from the root document to a document.
|
|
352
|
+
* @returns The text of the place.
|
|
353
|
+
*/
|
|
354
|
+
export function placeText(place: readonly Key[]): string {
|
|
355
|
+
let text = "";
|
|
356
|
+
for (let index = 0; index < place.length; index += 1) {
|
|
357
|
+
const key = place[index] as Key;
|
|
358
|
+
if (index > 0) text += "/";
|
|
359
|
+
text += typeof key === "number" || /^[\w$-]+$/.test(key) ? String(key) : JSON.stringify(key);
|
|
360
|
+
}
|
|
361
|
+
return text;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** An effect while the kernel waits for its outcome. */
|
|
365
|
+
type Recorded = { -readonly [K in keyof Effect]: Effect[K] };
|
|
366
|
+
|
|
157
367
|
/**
|
|
158
368
|
* The runtime of one evaluation or run. Compiled code gets it with the context, and asks it to resolve paths and
|
|
159
369
|
* to cause effects.
|
|
@@ -175,18 +385,18 @@ export interface Runtime {
|
|
|
175
385
|
* This function causes an effect from an evaluator. In a run, an evaluator cannot cause an effect: the host does
|
|
176
386
|
* each effect, thus only a runner can wait for it.
|
|
177
387
|
*
|
|
178
|
-
* @param ctx - The context of the document.
|
|
388
|
+
* @param ctx - The context of the document. It gives only the capabilities that the document has (CAP.NARROW-CODE).
|
|
179
389
|
* @param kind - The kind of the effect.
|
|
180
390
|
* @param payload - The data of the effect.
|
|
181
391
|
* @param place - The place of the document.
|
|
182
|
-
* @returns The payload, or an error value.
|
|
392
|
+
* @returns The answer of the mock, the payload without a mock, or an error value.
|
|
183
393
|
*/
|
|
184
394
|
effect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result;
|
|
185
395
|
/**
|
|
186
396
|
* This function causes an effect from a runner. In a run, the host does it, and the steps wait when the host
|
|
187
397
|
* waits.
|
|
188
398
|
*
|
|
189
|
-
* @param ctx - The context of the document.
|
|
399
|
+
* @param ctx - The context of the document. It gives only the capabilities that the document has (CAP.NARROW-CODE).
|
|
190
400
|
* @param kind - The kind of the effect.
|
|
191
401
|
* @param payload - The data of the effect.
|
|
192
402
|
* @param place - The place of the document.
|
|
@@ -201,6 +411,12 @@ export interface Runtime {
|
|
|
201
411
|
* @returns The steps that give each result, in the order of the list.
|
|
202
412
|
*/
|
|
203
413
|
all(list: readonly (() => Steps)[], options?: AllOptions): Generator<Promise<unknown>, readonly Result[], unknown>;
|
|
414
|
+
/**
|
|
415
|
+
* This optional function tells the runtime that a value keeps the context for later, for example a closure of
|
|
416
|
+
* `fn`. A later use of the value reads that context, and no evaluation records these reads. Thus the list of reads
|
|
417
|
+
* gets a read of each root of the context (READS.KEPT).
|
|
418
|
+
*/
|
|
419
|
+
keepsContext?(): void;
|
|
204
420
|
}
|
|
205
421
|
|
|
206
422
|
/**
|
|
@@ -215,10 +431,17 @@ export interface AllOptions {
|
|
|
215
431
|
readonly failFast?: boolean;
|
|
216
432
|
}
|
|
217
433
|
|
|
218
|
-
/**
|
|
434
|
+
/**
|
|
435
|
+
* Compiled code: the evaluation of a document in a context. The code gives each part a context that it makes from
|
|
436
|
+
* its own context with a spread. The part then keeps only the capabilities of the document (CAP.NARROW-CODE). A
|
|
437
|
+
* context that the code makes without a spread gives the part no capabilities.
|
|
438
|
+
*/
|
|
219
439
|
export type Code = (ctx: Context, rt: Runtime) => Result;
|
|
220
440
|
|
|
221
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* Compiled code that can wait: the run of a document in a context. Its parts keep only the capabilities of the
|
|
443
|
+
* document, as for `Code`.
|
|
444
|
+
*/
|
|
222
445
|
export type RunCode = (ctx: Context, rt: Runtime) => Steps;
|
|
223
446
|
|
|
224
447
|
/** A compiled part: its place in its parent, its binding name, and its code. It has no field with its value. */
|
|
@@ -316,7 +539,7 @@ export interface EvalInput {
|
|
|
316
539
|
*
|
|
317
540
|
* @param kind - The kind of the effect, for example `log`.
|
|
318
541
|
* @param payload - The data of the effect.
|
|
319
|
-
* @returns The payload, or
|
|
542
|
+
* @returns The answer of the mock, the payload without a mock, or an error value, for example `#CAP!`.
|
|
320
543
|
*/
|
|
321
544
|
effect(kind: Cap, payload: unknown): Result;
|
|
322
545
|
/**
|
|
@@ -360,7 +583,7 @@ export interface RunInput {
|
|
|
360
583
|
resolve(path: readonly unknown[], options?: ResolveOptions): Resolution;
|
|
361
584
|
/**
|
|
362
585
|
* This function causes an effect. In a run, the host does the effect, and the steps wait when the host waits. In
|
|
363
|
-
* an evaluation, the effect gives its payload. Take its result with `yield*`.
|
|
586
|
+
* an evaluation, a mock answers the effect at once, or the effect gives its payload. Take its result with `yield*`.
|
|
364
587
|
*
|
|
365
588
|
* @param kind - The kind of the effect, for example `log`.
|
|
366
589
|
* @param payload - The data of the effect.
|
|
@@ -398,6 +621,11 @@ export interface CompiledEvaluator {
|
|
|
398
621
|
* @returns The code that evaluates the document.
|
|
399
622
|
*/
|
|
400
623
|
readonly compile: (input: CompileInput) => Code;
|
|
624
|
+
/**
|
|
625
|
+
* The type rule of the evaluator: the value types that a check of types gives its documents and their parts
|
|
626
|
+
* (TYPE.RULE). Only a check reads it. An evaluator without a rule gives `unknown`.
|
|
627
|
+
*/
|
|
628
|
+
readonly type?: TypeRule;
|
|
401
629
|
}
|
|
402
630
|
|
|
403
631
|
/** An evaluator: a function of its input, or a compiler. */
|
|
@@ -427,8 +655,11 @@ export type Runner = RunFn | CompiledRunner;
|
|
|
427
655
|
* A machine: a world of kinds, and the code of each evaluator name, of each `deref` and of each walker.
|
|
428
656
|
*
|
|
429
657
|
* - `maxDepth`: the deepest nesting of documents. The default is 1000.
|
|
430
|
-
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities.
|
|
658
|
+
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities. A run,
|
|
659
|
+
* `check` and `effectsOf` ask it (CAP.PERMITS). In a check, its context has only the capabilities and the world.
|
|
431
660
|
* - `known`: the names that the evaluators know, for `check`. Without it, `check` does not check such names.
|
|
661
|
+
* - `domains`: the value domains of the language, by the name of their base type (D22).
|
|
662
|
+
* - `facts`: the static facts of the names that a fallback kind covers, for `factsOf` (D24).
|
|
432
663
|
*/
|
|
433
664
|
export interface Machine extends Navigator {
|
|
434
665
|
readonly evaluators: Readonly<Record<string, Evaluator>>;
|
|
@@ -442,10 +673,26 @@ export interface Machine extends Navigator {
|
|
|
442
673
|
* `check` asks it for each document with an evaluator. The function must be pure.
|
|
443
674
|
*
|
|
444
675
|
* @param path - The keys of the kind of the document, after each alias.
|
|
445
|
-
* @param evaluator - The name of the evaluator that the ladder gives the document.
|
|
676
|
+
* @param evaluator - The name of the evaluator that the ladder of the verb of the check gives the document.
|
|
446
677
|
* @returns `true` when the evaluator knows the name, or else the reason of the `#NAME?` that a run gives.
|
|
447
678
|
*/
|
|
448
679
|
readonly known?: (path: readonly Key[], evaluator: string) => true | string;
|
|
680
|
+
/**
|
|
681
|
+
* The value domains of the language, by the name of their base type, for example a color or a unit of length. A
|
|
682
|
+
* check reads them through `fits` and `member`. A domain of the kernel that the table does not name comes from
|
|
683
|
+
* `STD_DOMAINS`.
|
|
684
|
+
*/
|
|
685
|
+
readonly domains?: Readonly<Record<string, Domain>>;
|
|
686
|
+
/**
|
|
687
|
+
* This function gives the static facts of the code of a name, for `factsOf` (D24). An example is the operation
|
|
688
|
+
* `["ops", "now"]` of a formula language: a fallback kind names it, and only the language knows that it is volatile.
|
|
689
|
+
* Each field of the answer wins over the same field of the contract of the kind. The function must be pure.
|
|
690
|
+
*
|
|
691
|
+
* @param path - The keys of the kind of the document, after each alias.
|
|
692
|
+
* @param code - The name of the evaluator of the document, or of the walker of a step document.
|
|
693
|
+
* @returns The facts, or `undefined` for the facts of the contract.
|
|
694
|
+
*/
|
|
695
|
+
readonly facts?: (path: readonly Key[], code: string) => KindFacts | undefined;
|
|
449
696
|
}
|
|
450
697
|
|
|
451
698
|
/** One evaluation in a trace. The children are the evaluations of its parts, in the order that they started. */
|
|
@@ -457,7 +704,7 @@ export interface TraceNode {
|
|
|
457
704
|
readonly doc: Envelope;
|
|
458
705
|
/** `true` when the type came from the container and not from an envelope. */
|
|
459
706
|
readonly declared: boolean;
|
|
460
|
-
/** The answer of the ladder to the verb `eval
|
|
707
|
+
/** The answer of the ladder to the verb of the plan, `eval` by default. */
|
|
461
708
|
readonly answer: Answer;
|
|
462
709
|
readonly side: Side;
|
|
463
710
|
/** The names that the document could read when its evaluation started. */
|
|
@@ -539,20 +786,68 @@ export interface RunOptions {
|
|
|
539
786
|
readonly perform?: Perform;
|
|
540
787
|
/** The type of a bare root value. */
|
|
541
788
|
readonly declaredType?: string;
|
|
789
|
+
/**
|
|
790
|
+
* The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). A plan keeps the verb of its
|
|
791
|
+
* `prepare`, thus `plan.run` does not read this option.
|
|
792
|
+
*/
|
|
793
|
+
readonly verb?: string;
|
|
542
794
|
/** A signal of the host. When it aborts, the run stops with `#ABORT`. */
|
|
543
795
|
readonly signal?: StopSignal;
|
|
796
|
+
/**
|
|
797
|
+
* A list that gets each read, in the order of the resolutions (READS.RECORD). Under `all`, the order of the host
|
|
798
|
+
* decides the order of the reads of the items.
|
|
799
|
+
*/
|
|
800
|
+
readonly reads?: Read[];
|
|
544
801
|
}
|
|
545
802
|
|
|
546
803
|
/** The options of an evaluation of a plan. */
|
|
547
804
|
export interface EvalOptions {
|
|
548
805
|
/** A list that gets each effect, in its order. */
|
|
549
806
|
readonly effects?: Effect[];
|
|
807
|
+
/**
|
|
808
|
+
* The mock of the host (RUN.MOCK). It answers each effect at once, and its answer is the result of the effect.
|
|
809
|
+
* Without it, an effect gives its payload (RUN.EVAL).
|
|
810
|
+
*/
|
|
811
|
+
readonly perform?: Perform;
|
|
812
|
+
/**
|
|
813
|
+
* A list that gets each read, in the order of the resolutions (READS.RECORD). Without it, the plan records no read,
|
|
814
|
+
* and each resolution costs one more test.
|
|
815
|
+
*/
|
|
816
|
+
readonly reads?: Read[];
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** The options of `evaluate`. */
|
|
820
|
+
export interface EvaluateOptions {
|
|
821
|
+
/** The type of a bare root value. */
|
|
822
|
+
readonly declaredType?: string;
|
|
823
|
+
/** The verb that goes up the ladder at each document, for example `format`. The default is `eval` (VERB.PREPARE). */
|
|
824
|
+
readonly verb?: string;
|
|
825
|
+
/** The mock of the host (RUN.MOCK). Without it, an effect gives its payload (RUN.EVAL). */
|
|
826
|
+
readonly perform?: Perform;
|
|
827
|
+
/** A list that gets each read, in the order of the resolutions (READS.RECORD). */
|
|
828
|
+
readonly reads?: Read[];
|
|
550
829
|
}
|
|
551
830
|
|
|
552
831
|
/** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
|
|
553
832
|
const WITHOUT_HOPS: ResolveOptions = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
554
833
|
const WITH_HOPS: ResolveOptions = /* @__PURE__ */ Object.freeze({ hops: true });
|
|
555
834
|
|
|
835
|
+
/** The record of the reads of one evaluation (READS.RECORD). */
|
|
836
|
+
interface Reading {
|
|
837
|
+
readonly list: Read[];
|
|
838
|
+
/** The context at the root of the evaluation. A free read has the binding of this context. */
|
|
839
|
+
readonly root: Context;
|
|
840
|
+
/** `true` after the kernel added a read of each root (READS.KEPT). */
|
|
841
|
+
kept: boolean;
|
|
842
|
+
/** `true` after the kernel added the read of the root `self`. */
|
|
843
|
+
self: boolean;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** This function makes the record of the reads of an evaluation, when the host gives a list. */
|
|
847
|
+
function readingOf(list: Read[] | undefined, root: Context): Reading | undefined {
|
|
848
|
+
return list === undefined ? undefined : { list, root, kept: false, self: false };
|
|
849
|
+
}
|
|
850
|
+
|
|
556
851
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
557
852
|
class Session implements Runtime {
|
|
558
853
|
/** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
|
|
@@ -561,6 +856,11 @@ class Session implements Runtime {
|
|
|
561
856
|
nextId = 0;
|
|
562
857
|
current: Building | undefined;
|
|
563
858
|
readonly holder: Building | undefined;
|
|
859
|
+
/**
|
|
860
|
+
* The scope of the addresses of the code that runs at this time (RUN.ADDRESS). `all` changes it for each item. The
|
|
861
|
+
* root scope is made at the first effect or `all`, thus an evaluation without effects makes no scope.
|
|
862
|
+
*/
|
|
863
|
+
private scope: Scope | undefined;
|
|
564
864
|
|
|
565
865
|
constructor(
|
|
566
866
|
readonly machine: Machine,
|
|
@@ -569,6 +869,8 @@ class Session implements Runtime {
|
|
|
569
869
|
readonly perform: Perform | undefined,
|
|
570
870
|
readonly signal: StopSignal | undefined,
|
|
571
871
|
effects: Effect[] | undefined,
|
|
872
|
+
/** The record of the reads. Without it, a resolution records nothing. */
|
|
873
|
+
private readonly reading: Reading | undefined = undefined,
|
|
572
874
|
) {
|
|
573
875
|
this.list = effects;
|
|
574
876
|
this.holder = traced ? holderNode() : undefined;
|
|
@@ -581,53 +883,159 @@ class Session implements Runtime {
|
|
|
581
883
|
}
|
|
582
884
|
|
|
583
885
|
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
886
|
+
return this.read(ctx, path, undefined, options);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* This function resolves a compiled path (PLAN.PATH). Without a list of reads and without a trace, the compiled
|
|
891
|
+
* path answers at once. Each other resolution records its read, as `resolve` does.
|
|
892
|
+
*/
|
|
893
|
+
resolvePath(ctx: Context, compiled: CompiledPath): Resolution {
|
|
894
|
+
if (this.reading === undefined && !this.traced && compiled.nav === this.machine) return compiled.resolve(ctx);
|
|
895
|
+
return this.read(ctx, compiled.path, compiled, undefined);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** This function resolves a path, and records its read when the evaluation has a list of reads (READS.RECORD). */
|
|
899
|
+
private read(ctx: Context, path: readonly unknown[], compiled: CompiledPath | undefined, options: ResolveOptions | undefined): Resolution {
|
|
584
900
|
const hops = options?.hops ?? this.traced;
|
|
901
|
+
const reading = this.reading;
|
|
902
|
+
if (reading === undefined) return this.find(ctx, path, compiled, hops);
|
|
903
|
+
const free = isFree(reading.root, ctx, path);
|
|
904
|
+
// A kind that sets another self can give the old self on, as the parent of a node of a tree does (READS.KEPT).
|
|
905
|
+
if (!free && path[0] === "self" && !reading.self) this.keep(reading, ["self"]);
|
|
906
|
+
let found: Resolution;
|
|
907
|
+
try {
|
|
908
|
+
found = this.find(ctx, path, compiled, hops);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
913
|
+
const asked = options?.hops === true;
|
|
914
|
+
reading.list.push(asked ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
|
|
915
|
+
return found;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* This function resolves the steps of a path. A compiled path of this machine answers a resolution without hops. A
|
|
920
|
+
* resolution with hops, and a path that compiled for another navigator, resolve the steps again.
|
|
921
|
+
*/
|
|
922
|
+
private find(ctx: Context, path: readonly unknown[], compiled: CompiledPath | undefined, hops: boolean): Resolution {
|
|
923
|
+
if (compiled !== undefined && !hops && compiled.nav === this.machine) return compiled.resolve(ctx);
|
|
585
924
|
return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
586
925
|
}
|
|
587
926
|
|
|
927
|
+
keepsContext(): void {
|
|
928
|
+
const reading = this.reading;
|
|
929
|
+
if (reading === undefined || reading.kept) return;
|
|
930
|
+
reading.kept = true;
|
|
931
|
+
this.keep(reading, reading.self ? ROOTS.filter((root) => root !== "self") : ROOTS);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* This function adds a read of each whole root to the list, from the context at the root (READS.KEPT). An exception
|
|
936
|
+
* of the host gives the error value of the read, because the evaluation did not ask for the read.
|
|
937
|
+
*/
|
|
938
|
+
private keep(reading: Reading, roots: readonly Root[]): void {
|
|
939
|
+
for (const root of roots) {
|
|
940
|
+
if (root === "self") reading.self = true;
|
|
941
|
+
const path = [root];
|
|
942
|
+
reading.list.push({ path, reached: readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
588
946
|
private permitted(ctx: Context, keys: readonly Key[]): boolean {
|
|
589
947
|
return this.machine.permits === undefined ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
|
|
590
948
|
}
|
|
591
949
|
|
|
592
|
-
|
|
950
|
+
/**
|
|
951
|
+
* This function checks the capability of an effect, and records the effect with its address (RUN.ADDRESS). The
|
|
952
|
+
* context keeps only the capabilities of its grant, thus compiled code cannot widen the context of its own effect
|
|
953
|
+
* (CAP.NARROW-CODE).
|
|
954
|
+
*/
|
|
955
|
+
private record(given: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result | Recorded {
|
|
956
|
+
const ctx = granted(given);
|
|
593
957
|
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
594
958
|
const text = keys.join(".");
|
|
595
959
|
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
596
960
|
const node = this.current;
|
|
597
961
|
const list = this.effects;
|
|
598
|
-
const
|
|
962
|
+
const scope = (this.scope ??= { prefix: "", counts: undefined, alls: 0 });
|
|
963
|
+
const at = placeText(place);
|
|
964
|
+
const counts = (scope.counts ??= new Map<string, number>());
|
|
965
|
+
const count = counts.get(at) ?? 0;
|
|
966
|
+
counts.set(at, count + 1);
|
|
967
|
+
const effect: Recorded = {
|
|
968
|
+
index: list.length,
|
|
969
|
+
kind: text,
|
|
970
|
+
path: keys,
|
|
971
|
+
payload,
|
|
972
|
+
node: node?.id ?? -1,
|
|
973
|
+
at: this.tick,
|
|
974
|
+
place,
|
|
975
|
+
context: ctx,
|
|
976
|
+
address: `${scope.prefix}${at}#${count}`,
|
|
977
|
+
};
|
|
599
978
|
list.push(effect);
|
|
600
979
|
if (node !== undefined) node.effects.push(effect.index);
|
|
601
980
|
return effect;
|
|
602
981
|
}
|
|
603
982
|
|
|
983
|
+
/**
|
|
984
|
+
* This function answers an effect at once, without a wait: the mock gives the result, or the effect gives its
|
|
985
|
+
* payload without a mock (RUN.MOCK, RUN.EVAL). A promise from the mock gives `#CALC!`.
|
|
986
|
+
*/
|
|
987
|
+
private answer(effect: Recorded, place: readonly Key[]): Result {
|
|
988
|
+
const perform = this.perform;
|
|
989
|
+
if (perform === undefined) return (effect.outcome = ok(effect.payload));
|
|
990
|
+
let done: unknown;
|
|
991
|
+
try {
|
|
992
|
+
done = perform(effect);
|
|
993
|
+
} catch (error) {
|
|
994
|
+
return (effect.outcome = hostFailed(effect.kind, place, error));
|
|
995
|
+
}
|
|
996
|
+
if (!(done instanceof Promise)) return (effect.outcome = ok(done));
|
|
997
|
+
// The evaluation does not read the promise, thus its rejection must not be unhandled.
|
|
998
|
+
void done.catch(() => undefined);
|
|
999
|
+
return (effect.outcome = fail("#CALC!", `The mock gave a promise for the effect ${effect.kind}, and an evaluation does not wait.`, { place }));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
604
1002
|
effect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result {
|
|
605
1003
|
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
606
1004
|
if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
607
1005
|
const made = this.record(ctx, kind, payload, place);
|
|
608
|
-
return "ok" in made ? made :
|
|
1006
|
+
return "ok" in made ? made : this.answer(made, place);
|
|
609
1007
|
}
|
|
610
1008
|
|
|
611
1009
|
*runEffect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Steps {
|
|
612
1010
|
const made = this.record(ctx, kind, payload, place);
|
|
613
1011
|
if ("ok" in made) return made;
|
|
614
1012
|
const perform = this.perform;
|
|
615
|
-
if (!this.running || perform === undefined) return
|
|
616
|
-
const failed = (error: unknown): Result => fail("#CALC!", `The host failed the effect ${made.kind}: ${describe(error)}`, { place, cause: error });
|
|
1013
|
+
if (!this.running || perform === undefined) return this.answer(made, place);
|
|
617
1014
|
let done: unknown;
|
|
618
1015
|
try {
|
|
619
1016
|
done = perform(made);
|
|
620
1017
|
} catch (error) {
|
|
621
|
-
return
|
|
1018
|
+
return (made.outcome = hostFailed(made.kind, place, error));
|
|
622
1019
|
}
|
|
623
|
-
if (!(done instanceof Promise)) return ok(done);
|
|
624
|
-
|
|
1020
|
+
if (!(done instanceof Promise)) return (made.outcome = ok(done));
|
|
1021
|
+
const result = (yield done.then(
|
|
1022
|
+
(value: unknown) => ok(value),
|
|
1023
|
+
(error: unknown) => hostFailed(made.kind, place, error),
|
|
1024
|
+
)) as Result;
|
|
1025
|
+
// A run that stops at this wait does not reach this line, thus the effect keeps no outcome (RUN.OUTCOME).
|
|
1026
|
+
return (made.outcome = result);
|
|
625
1027
|
}
|
|
626
1028
|
|
|
627
1029
|
*all(list: readonly (() => Steps)[], options: AllOptions = {}): Generator<Promise<unknown>, readonly Result[], unknown> {
|
|
628
1030
|
const results: Result[] = new Array<Result>(list.length);
|
|
629
1031
|
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
630
1032
|
const failFast = options.failFast === true;
|
|
1033
|
+
// The address of this call, and a scope for each item (RUN.ADDRESS). The journal of a replay reads the address.
|
|
1034
|
+
const outer = (this.scope ??= { prefix: "", counts: undefined, alls: 0 });
|
|
1035
|
+
const address = `${outer.prefix}all${outer.alls}`;
|
|
1036
|
+
outer.alls += 1;
|
|
1037
|
+
const scopes: Scope[] = new Array<Scope>(list.length);
|
|
1038
|
+
const journal = journalOf(this.perform);
|
|
631
1039
|
let failed = false;
|
|
632
1040
|
interface Active {
|
|
633
1041
|
readonly index: number;
|
|
@@ -642,11 +1050,15 @@ class Session implements Runtime {
|
|
|
642
1050
|
};
|
|
643
1051
|
const advance = (index: number, steps: Steps, input: { readonly value?: unknown; readonly error?: unknown; readonly threw: boolean }): void => {
|
|
644
1052
|
let step: IteratorResult<Promise<unknown>, Result>;
|
|
1053
|
+
const before = this.scope;
|
|
1054
|
+
this.scope = scopes[index] as Scope;
|
|
645
1055
|
try {
|
|
646
1056
|
step = input.threw ? steps.throw(input.error) : steps.next(input.value);
|
|
647
1057
|
} catch (error) {
|
|
648
1058
|
settle(index, thrown(error, "a part"));
|
|
649
1059
|
return;
|
|
1060
|
+
} finally {
|
|
1061
|
+
this.scope = before;
|
|
650
1062
|
}
|
|
651
1063
|
if (step.done === true) {
|
|
652
1064
|
settle(index, step.value);
|
|
@@ -663,25 +1075,35 @@ class Session implements Runtime {
|
|
|
663
1075
|
while (!failed && active.length < limit && next < list.length) {
|
|
664
1076
|
const index = next;
|
|
665
1077
|
next += 1;
|
|
1078
|
+
const scope: Scope = { prefix: `${address}.${index}/`, counts: undefined, alls: 0 };
|
|
1079
|
+
scopes[index] = scope;
|
|
666
1080
|
const begin = list[index] as () => Steps;
|
|
667
1081
|
let steps: Steps;
|
|
1082
|
+
const before = this.scope;
|
|
1083
|
+
this.scope = scope;
|
|
668
1084
|
try {
|
|
669
1085
|
steps = begin();
|
|
670
1086
|
} catch (error) {
|
|
671
1087
|
settle(index, thrown(error, "a part"));
|
|
672
1088
|
continue;
|
|
1089
|
+
} finally {
|
|
1090
|
+
this.scope = before;
|
|
673
1091
|
}
|
|
674
1092
|
advance(index, steps, { threw: false });
|
|
675
1093
|
}
|
|
676
1094
|
};
|
|
677
1095
|
start();
|
|
678
1096
|
while (active.length > 0 && !failed) {
|
|
679
|
-
|
|
1097
|
+
// A replay settles the waits in the recorded order. Each other run takes the first wait that settles.
|
|
1098
|
+
const chosen = journal?.next?.(address, active.map((item) => item.index));
|
|
1099
|
+
const pick = chosen === undefined ? undefined : active.find((item) => item.index === chosen);
|
|
1100
|
+
const settled = (yield pick === undefined ? Promise.race(active.map((item) => item.pending)) : pick.pending) as {
|
|
680
1101
|
readonly item: Active;
|
|
681
1102
|
readonly value?: unknown;
|
|
682
1103
|
readonly error?: unknown;
|
|
683
1104
|
readonly threw: boolean;
|
|
684
1105
|
};
|
|
1106
|
+
journal?.settled?.(address, settled.item.index);
|
|
685
1107
|
active.splice(active.indexOf(settled.item), 1);
|
|
686
1108
|
advance(settled.item.index, settled.item.steps, settled);
|
|
687
1109
|
start();
|
|
@@ -730,6 +1152,19 @@ class Session implements Runtime {
|
|
|
730
1152
|
}
|
|
731
1153
|
}
|
|
732
1154
|
|
|
1155
|
+
/**
|
|
1156
|
+
* This function resolves a compiled path through a runtime (PLAN.PATH). The runtime of the kernel uses the compiled
|
|
1157
|
+
* path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path.
|
|
1158
|
+
*
|
|
1159
|
+
* @param rt - The runtime.
|
|
1160
|
+
* @param ctx - The context.
|
|
1161
|
+
* @param compiled - The compiled path, from `compilePath`.
|
|
1162
|
+
* @returns What the path reached, or an error.
|
|
1163
|
+
*/
|
|
1164
|
+
export function resolveCompiled(rt: Runtime, ctx: Context, compiled: CompiledPath): Resolution {
|
|
1165
|
+
return rt instanceof Session ? rt.resolvePath(ctx, compiled) : rt.resolve(ctx, compiled.path);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
733
1168
|
function holderNode(): Building {
|
|
734
1169
|
return {
|
|
735
1170
|
id: -1,
|
|
@@ -750,13 +1185,25 @@ function holderNode(): Building {
|
|
|
750
1185
|
};
|
|
751
1186
|
}
|
|
752
1187
|
|
|
753
|
-
/**
|
|
754
|
-
function
|
|
1188
|
+
/**
|
|
1189
|
+
* This function gives the reason of a runner that waits where its parent cannot wait (RUN.WAIT). The plan, the
|
|
1190
|
+
* reference and `check` with the option `run` give the same reason.
|
|
1191
|
+
*
|
|
1192
|
+
* @param kindText - The text of the type of the runner document.
|
|
1193
|
+
* @returns The reason.
|
|
1194
|
+
*/
|
|
1195
|
+
export function waitedReason(kindText: string): string {
|
|
1196
|
+
return `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/** This function drives steps that cannot wait. A step that waits stops the steps, and gives `#CALC!`. */
|
|
1200
|
+
function drive(steps: Steps, kindText: string): Result {
|
|
755
1201
|
const step = steps.next();
|
|
756
1202
|
if (step.done === true) return step.value;
|
|
757
1203
|
void step.value.catch(() => undefined);
|
|
758
|
-
|
|
759
|
-
return
|
|
1204
|
+
const waited = fail("#CALC!", waitedReason(kindText));
|
|
1205
|
+
void steps.return(waited);
|
|
1206
|
+
return waited;
|
|
760
1207
|
}
|
|
761
1208
|
|
|
762
1209
|
const ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
@@ -809,6 +1256,11 @@ export interface PrepareOptions {
|
|
|
809
1256
|
readonly declaredType?: string;
|
|
810
1257
|
/** `true`: each evaluation records a trace. The default is `false`. */
|
|
811
1258
|
readonly trace?: boolean;
|
|
1259
|
+
/**
|
|
1260
|
+
* The verb that goes up the ladder at each document, for example `format`. Each part evaluates with the same verb.
|
|
1261
|
+
* The default is `eval` (VERB.PREPARE).
|
|
1262
|
+
*/
|
|
1263
|
+
readonly verb?: string;
|
|
812
1264
|
}
|
|
813
1265
|
|
|
814
1266
|
/** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
|
|
@@ -819,10 +1271,11 @@ export interface Plan {
|
|
|
819
1271
|
/** The compiled root document. */
|
|
820
1272
|
readonly root: CompiledPart;
|
|
821
1273
|
/**
|
|
822
|
-
* This function evaluates the document. It does not wait, and the host does no effect.
|
|
1274
|
+
* This function evaluates the document. It does not wait, and the host does no effect. A mock can answer each
|
|
1275
|
+
* effect at once (RUN.MOCK).
|
|
823
1276
|
*
|
|
824
1277
|
* @param context - The names and the capabilities at the root.
|
|
825
|
-
* @param options - A list that gets the effects.
|
|
1278
|
+
* @param options - A list that gets the effects, the mock of the host and a list that gets the reads.
|
|
826
1279
|
* @returns The result.
|
|
827
1280
|
*/
|
|
828
1281
|
evaluate(context?: Context, options?: EvalOptions): Result;
|
|
@@ -830,7 +1283,7 @@ export interface Plan {
|
|
|
830
1283
|
* This function runs the document: the host does each effect, and the run waits when the host waits.
|
|
831
1284
|
*
|
|
832
1285
|
* @param context - The names and the capabilities at the root.
|
|
833
|
-
* @param options - The host code that does the effects,
|
|
1286
|
+
* @param options - The host code that does the effects, a signal to stop the run and a list that gets the reads.
|
|
834
1287
|
* @returns The result, when the run ends.
|
|
835
1288
|
*/
|
|
836
1289
|
run(context?: Context, options?: RunOptions): Promise<Result>;
|
|
@@ -840,23 +1293,31 @@ interface Compiler {
|
|
|
840
1293
|
readonly machine: Machine;
|
|
841
1294
|
readonly traced: boolean;
|
|
842
1295
|
readonly maxDepth: number;
|
|
1296
|
+
/** The verb that goes up the ladder at each document. */
|
|
1297
|
+
readonly verb: string;
|
|
1298
|
+
/** The answer of a document that the ladder did not see, in a trace. */
|
|
1299
|
+
readonly noAnswer: Answer;
|
|
843
1300
|
}
|
|
844
1301
|
|
|
845
|
-
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string):
|
|
1302
|
+
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string): Compiled {
|
|
846
1303
|
return failingWith(c, meta, fail(code, reason, { place }));
|
|
847
1304
|
}
|
|
848
1305
|
|
|
849
1306
|
/** This function gives the code of a document that fails with one result at each evaluation. */
|
|
850
|
-
function failingWith(c: Compiler, meta: Meta, result: Result):
|
|
1307
|
+
function failingWith(c: Compiler, meta: Meta, result: Result): Compiled {
|
|
851
1308
|
const plain: Code = () => result;
|
|
852
|
-
return finish(c, meta, plain,
|
|
853
|
-
return result;
|
|
854
|
-
});
|
|
1309
|
+
return finish(c, meta, plain, undefined);
|
|
855
1310
|
}
|
|
856
1311
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1312
|
+
/**
|
|
1313
|
+
* This function gives the code of a document in a trace. Each node records the context that its grant permits
|
|
1314
|
+
* (CAP.NARROW-CODE), as the code of the document gets it.
|
|
1315
|
+
*/
|
|
1316
|
+
function finish(c: Compiler, meta: Meta, code: Code, steps: RunCode | undefined): Compiled {
|
|
1317
|
+
if (!c.traced) return { code, run: steps };
|
|
1318
|
+
const run = steps ?? runOf(code);
|
|
1319
|
+
const tracedCode: Code = (given, rt) => {
|
|
1320
|
+
const ctx = granted(given);
|
|
860
1321
|
const s = rt as Session;
|
|
861
1322
|
const node = s.enter(meta, ctx);
|
|
862
1323
|
const before = s.current;
|
|
@@ -870,7 +1331,8 @@ function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly c
|
|
|
870
1331
|
s.exit(node, result);
|
|
871
1332
|
return result;
|
|
872
1333
|
};
|
|
873
|
-
const tracedRun: RunCode = function* (
|
|
1334
|
+
const tracedRun: RunCode = function* (given, rt) {
|
|
1335
|
+
const ctx = granted(given);
|
|
874
1336
|
const s = rt as Session;
|
|
875
1337
|
const node = s.enter(meta, ctx);
|
|
876
1338
|
const steps = run(ctx, rt);
|
|
@@ -901,47 +1363,34 @@ function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly c
|
|
|
901
1363
|
return { code: tracedCode, run: tracedRun };
|
|
902
1364
|
}
|
|
903
1365
|
|
|
904
|
-
/**
|
|
905
|
-
* This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
|
|
906
|
-
* trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
|
|
907
|
-
*/
|
|
908
|
-
function evaluatorOf(world: World, envelope: Envelope | undefined, kind: Kind | undefined): { readonly name?: string; readonly unknown: boolean } {
|
|
909
|
-
const own = envelope?.own;
|
|
910
|
-
const ownName = own !== undefined && Object.hasOwn(own, "eval") ? own["eval"] : undefined;
|
|
911
|
-
if (ownName !== undefined) return { name: ownName, unknown: false };
|
|
912
|
-
if (kind === undefined) return { unknown: true };
|
|
913
|
-
const verbs = kind.verbs;
|
|
914
|
-
const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : undefined;
|
|
915
|
-
return name === undefined ? { unknown: false } : { name, unknown: false };
|
|
916
|
-
}
|
|
917
|
-
|
|
918
1366
|
/** The meta of a node of a compilation without a trace. Only a trace reads the meta of a node. */
|
|
919
1367
|
const NO_META: Meta = /* @__PURE__ */ Object.freeze({ path: [], doc: { type: "?", value: null }, declared: false, answer: NO_ANSWER, side: "derive" });
|
|
920
1368
|
|
|
921
|
-
function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number, address = false):
|
|
1369
|
+
function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number, address = false): Compiled {
|
|
922
1370
|
const world = c.machine.world;
|
|
923
1371
|
const read = partsOf(world, value, declared);
|
|
924
1372
|
if (!read.ok) {
|
|
925
|
-
const meta: Meta = c.traced ? { path: place.map(String), doc: { type: "?", value: value as Json }, declared: true, answer:
|
|
1373
|
+
const meta: Meta = c.traced ? { path: place.map(String), doc: { type: "?", value: value as Json }, declared: true, answer: c.noAnswer, side: "derive" } : NO_META;
|
|
926
1374
|
return failing(c, meta, place, read.code, read.reason);
|
|
927
1375
|
}
|
|
928
1376
|
const { doc, kind, split } = read;
|
|
929
1377
|
const envelope = read.declared ? undefined : doc;
|
|
930
|
-
const meta0: Meta = c.traced ? { path: place.map(String), doc, declared: read.declared, answer:
|
|
1378
|
+
const meta0: Meta = c.traced ? { path: place.map(String), doc, declared: read.declared, answer: c.noAnswer, side: "derive" } : NO_META;
|
|
931
1379
|
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
932
1380
|
|
|
933
1381
|
let meta = meta0;
|
|
934
1382
|
let name: string | undefined;
|
|
935
1383
|
let unknown: boolean;
|
|
936
1384
|
if (c.traced) {
|
|
937
|
-
const answer = ask(world, envelope ?? {},
|
|
1385
|
+
const answer = ask(world, envelope ?? {}, c.verb, declared === undefined ? {} : { declaredType: declared });
|
|
938
1386
|
meta = { ...meta0, answer, side: kind?.side ?? "derive" };
|
|
939
1387
|
name = answer.evaluator;
|
|
940
1388
|
unknown = answer.error === "unknown-kind";
|
|
941
1389
|
} else {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1390
|
+
// A document has a type here, thus a kind that the read did not find is a kind that the world does not have.
|
|
1391
|
+
const quick = answerOf(world, envelope?.own, kind ?? null, c.verb);
|
|
1392
|
+
name = quick?.name;
|
|
1393
|
+
unknown = quick === undefined && kind === undefined;
|
|
945
1394
|
}
|
|
946
1395
|
const kindText = typeName(doc.type);
|
|
947
1396
|
if (unknown) return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
|
|
@@ -961,9 +1410,10 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
961
1410
|
const compiled = rules.length === 0 ? own : gated(rules, own);
|
|
962
1411
|
// The binding name is in the header of an envelope. Most parts have no name, thus the cheap test comes first.
|
|
963
1412
|
const header = typeof (item as { readonly name?: unknown } | null)?.name === "string" && isEnvelope(item) ? (item as { readonly name: string }).name : undefined;
|
|
1413
|
+
const run = compiled.run ?? runOf(compiled.code);
|
|
964
1414
|
return header === undefined
|
|
965
|
-
? { key: slot.key, path: slot.path, code: compiled.code, run
|
|
966
|
-
: { key: slot.key, path: slot.path, name: header, code: compiled.code, run
|
|
1415
|
+
? { key: slot.key, path: slot.path, code: compiled.code, run }
|
|
1416
|
+
: { key: slot.key, path: slot.path, name: header, code: compiled.code, run };
|
|
967
1417
|
});
|
|
968
1418
|
const typePath = read.typePath ?? [];
|
|
969
1419
|
const input: CompileInput = {
|
|
@@ -996,8 +1446,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
996
1446
|
} catch (error) {
|
|
997
1447
|
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
998
1448
|
}
|
|
999
|
-
|
|
1000
|
-
return finish(c, meta, code, runOf(code));
|
|
1449
|
+
return finish(c, meta, placedCode(inner, place, name), undefined);
|
|
1001
1450
|
}
|
|
1002
1451
|
|
|
1003
1452
|
/*
|
|
@@ -1006,11 +1455,14 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1006
1455
|
* the split of the value. Thus a plan with these helpers keeps only its code.
|
|
1007
1456
|
*/
|
|
1008
1457
|
|
|
1009
|
-
/**
|
|
1458
|
+
/**
|
|
1459
|
+
* This function gives the code of a document: the code of its evaluator, with the place of each error and of each
|
|
1460
|
+
* exception. The code gets the context that its grant permits, thus a compiled parent cannot widen it (CAP.NARROW-CODE).
|
|
1461
|
+
*/
|
|
1010
1462
|
function placedCode(inner: Code, place: readonly Key[], name: string): Code {
|
|
1011
1463
|
return (ctx, rt) => {
|
|
1012
1464
|
try {
|
|
1013
|
-
return placed(inner(ctx, rt), place);
|
|
1465
|
+
return placed(inner(granted(ctx), rt), place);
|
|
1014
1466
|
} catch (error) {
|
|
1015
1467
|
return placed(thrown(error, `the evaluator ${name}`), place);
|
|
1016
1468
|
}
|
|
@@ -1019,23 +1471,31 @@ function placedCode(inner: Code, place: readonly Key[], name: string): Code {
|
|
|
1019
1471
|
|
|
1020
1472
|
/**
|
|
1021
1473
|
* This function gives the code of a part that capability rules cover. The code narrows the context of the part by
|
|
1022
|
-
* the rules before the part starts.
|
|
1474
|
+
* the rules before the part starts. First the context keeps only the capabilities of its grant, thus a compiled
|
|
1475
|
+
* parent cannot widen the capabilities that the rules narrow (CAP.NARROW-CODE).
|
|
1023
1476
|
*/
|
|
1024
|
-
function gated(rules: readonly CapsRule[], part:
|
|
1477
|
+
function gated(rules: readonly CapsRule[], part: Compiled): Compiled {
|
|
1025
1478
|
const inner = part.code;
|
|
1026
1479
|
const steps = part.run;
|
|
1027
|
-
const under = (
|
|
1480
|
+
const under = (given: Context): Context => {
|
|
1481
|
+
const ctx = granted(given);
|
|
1028
1482
|
const caps = capsUnder(rules, ctx.caps);
|
|
1029
|
-
|
|
1483
|
+
if (caps === ctx.caps || (caps.length === 0 && ctx.caps.length === 0)) return ctx;
|
|
1484
|
+
const out: Granted = { ...ctx, caps, [GRANT]: caps };
|
|
1485
|
+
return out;
|
|
1030
1486
|
};
|
|
1031
|
-
|
|
1487
|
+
// The steps of an evaluator are `runOf` of its code, thus `runOf` of this code narrows the context the same way.
|
|
1488
|
+
return { code: (ctx, rt) => inner(under(ctx), rt), run: steps === undefined ? undefined : (ctx, rt) => steps(under(ctx), rt) };
|
|
1032
1489
|
}
|
|
1033
1490
|
|
|
1034
|
-
/**
|
|
1491
|
+
/**
|
|
1492
|
+
* This function gives the steps of a runner, with the place of each error and of each exception. The steps get the
|
|
1493
|
+
* context that its grant permits (CAP.NARROW-CODE).
|
|
1494
|
+
*/
|
|
1035
1495
|
function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode {
|
|
1036
1496
|
return function* (ctx, rt) {
|
|
1037
1497
|
try {
|
|
1038
|
-
return placed(yield* inner(ctx, rt), place);
|
|
1498
|
+
return placed(yield* inner(granted(ctx), rt), place);
|
|
1039
1499
|
} catch (error) {
|
|
1040
1500
|
return placed(thrown(error, `the runner ${name}`), place);
|
|
1041
1501
|
}
|
|
@@ -1043,14 +1503,21 @@ function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode
|
|
|
1043
1503
|
}
|
|
1044
1504
|
|
|
1045
1505
|
/**
|
|
1046
|
-
* This function gives the code of a runner
|
|
1047
|
-
*
|
|
1506
|
+
* This function gives the code of a runner under a parent that cannot wait: its steps, driven without a wait. An
|
|
1507
|
+
* evaluation and a run both drive them (RUN.DRIVE-NESTED). Only a step that really waits gives `#CALC!`, at the place
|
|
1508
|
+
* of the runner, where the error started (RUN.WAIT).
|
|
1048
1509
|
*/
|
|
1049
1510
|
function runnerCode(run: RunCode, place: readonly Key[], kindText: string): Code {
|
|
1050
|
-
return (ctx, rt) =>
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1511
|
+
return (ctx, rt) => placed(drive(run(ctx, rt), kindText), place);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* The code of a compiled document. The steps of a run are `undefined` for an evaluator: they are `runOf(code)`, and a
|
|
1516
|
+
* plan makes them only for a run (PLAN.LEAN).
|
|
1517
|
+
*/
|
|
1518
|
+
interface Compiled {
|
|
1519
|
+
readonly code: Code;
|
|
1520
|
+
readonly run: RunCode | undefined;
|
|
1054
1521
|
}
|
|
1055
1522
|
|
|
1056
1523
|
/** This function gives the steps of a run of an evaluator. The code runs at the first step, and it does not wait. */
|
|
@@ -1069,6 +1536,8 @@ function fillOf(split: Extract<Split, { readonly ok: true }>, doc: Envelope): <P
|
|
|
1069
1536
|
function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): Code {
|
|
1070
1537
|
const { kind, typePath, doc, data, place } = input;
|
|
1071
1538
|
const address = input.address === true ? { address: true } : {};
|
|
1539
|
+
// The code keeps the fill and not the whole input, thus it does not keep the compiled value of the document (PLAN.LEAN).
|
|
1540
|
+
const refill = input.fill;
|
|
1072
1541
|
const compiled = input.parts;
|
|
1073
1542
|
const hasParts = compiled.length > 0;
|
|
1074
1543
|
return (ctx, rt) => {
|
|
@@ -1076,7 +1545,7 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
|
|
|
1076
1545
|
key: part.key,
|
|
1077
1546
|
path: part.path,
|
|
1078
1547
|
...(part.name === undefined ? {} : { name: part.name }),
|
|
1079
|
-
evaluate: (override?: Context) => part.code(override === undefined ? ctx :
|
|
1548
|
+
evaluate: (override?: Context) => part.code(override === undefined ? ctx : handed(ctx, override), rt),
|
|
1080
1549
|
}));
|
|
1081
1550
|
return fn({
|
|
1082
1551
|
kind,
|
|
@@ -1090,13 +1559,14 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
|
|
|
1090
1559
|
...address,
|
|
1091
1560
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1092
1561
|
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
1093
|
-
fill:
|
|
1562
|
+
fill: refill,
|
|
1094
1563
|
});
|
|
1095
1564
|
};
|
|
1096
1565
|
}
|
|
1097
1566
|
|
|
1098
1567
|
function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): RunCode {
|
|
1099
1568
|
const { kind, typePath, doc, data, place } = input;
|
|
1569
|
+
const refill = input.fill;
|
|
1100
1570
|
const compiled = input.parts;
|
|
1101
1571
|
const hasParts = compiled.length > 0;
|
|
1102
1572
|
return (ctx, rt) => {
|
|
@@ -1104,7 +1574,7 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
1104
1574
|
key: part.key,
|
|
1105
1575
|
path: part.path,
|
|
1106
1576
|
...(part.name === undefined ? {} : { name: part.name }),
|
|
1107
|
-
evaluate: (override?: Context) => part.run(override === undefined ? ctx :
|
|
1577
|
+
evaluate: (override?: Context) => part.run(override === undefined ? ctx : handed(ctx, override), rt),
|
|
1108
1578
|
}));
|
|
1109
1579
|
return fn({
|
|
1110
1580
|
kind,
|
|
@@ -1118,57 +1588,102 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
1118
1588
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1119
1589
|
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
1120
1590
|
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options),
|
|
1121
|
-
fill:
|
|
1591
|
+
fill: refill,
|
|
1122
1592
|
});
|
|
1123
1593
|
};
|
|
1124
1594
|
}
|
|
1125
1595
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1596
|
+
/**
|
|
1597
|
+
* This function gives the context at the root of an evaluation. It is the context of the host, with the world of
|
|
1598
|
+
* the machine when the context has none. The host gives the capabilities, thus the root has the grant of each one
|
|
1599
|
+
* (CAP.NARROW-CODE). An evaluation and `verifyReads` share it.
|
|
1600
|
+
*
|
|
1601
|
+
* @param machine - The machine.
|
|
1602
|
+
* @param ctx - The context of the host.
|
|
1603
|
+
* @returns The context at the root.
|
|
1604
|
+
*/
|
|
1605
|
+
export function rootContext(machine: Machine, ctx: Context): Context {
|
|
1606
|
+
if (ctx.world !== null && (ctx as Granted)[GRANT] === ctx.caps) return ctx;
|
|
1607
|
+
const root: Granted = { ...ctx, world: ctx.world === null ? worldThing(machine.world) : ctx.world, [GRANT]: ctx.caps };
|
|
1608
|
+
return root;
|
|
1128
1609
|
}
|
|
1129
1610
|
|
|
1130
1611
|
/**
|
|
1131
1612
|
* This function compiles a document into a plan. The ladder, the kinds and the parts are known after it. A plan
|
|
1132
1613
|
* evaluates many times. A document that does not compile gives a plan that fails with the reason, when it evaluates.
|
|
1133
1614
|
*
|
|
1615
|
+
* The verb of the options goes up the ladder at each document, and each part evaluates with the same verb. The
|
|
1616
|
+
* default verb is `eval`. A plan of another verb, for example `format`, evaluates, runs and traces as a plan of
|
|
1617
|
+
* `eval` does (VERB.PREPARE).
|
|
1618
|
+
*
|
|
1134
1619
|
* @param machine - The world and the code of the evaluators.
|
|
1135
1620
|
* @param doc - The document, as JSON.
|
|
1136
|
-
* @param options - The type of a bare root value, and
|
|
1621
|
+
* @param options - The type of a bare root value, a trace and the verb.
|
|
1137
1622
|
* @returns The plan.
|
|
1138
1623
|
*/
|
|
1139
1624
|
export function prepare(machine: Machine, doc: unknown, options: PrepareOptions = {}): Plan {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1625
|
+
const verb = options.verb ?? "eval";
|
|
1626
|
+
const noAnswer: Answer = verb === "eval" ? NO_ANSWER : { verb, error: "no-answer", steps: [] };
|
|
1627
|
+
const c: Compiler = { machine, traced: options.trace === true, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer };
|
|
1628
|
+
let root: Compiled;
|
|
1142
1629
|
try {
|
|
1143
1630
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
1144
1631
|
} catch (error) {
|
|
1145
1632
|
const result = placed(thrown(error, "the compilation"), []);
|
|
1146
|
-
root = {
|
|
1147
|
-
code: () => result,
|
|
1148
|
-
run: function* () {
|
|
1149
|
-
return result;
|
|
1150
|
-
},
|
|
1151
|
-
};
|
|
1633
|
+
root = { code: () => result, run: undefined };
|
|
1152
1634
|
}
|
|
1153
|
-
|
|
1154
|
-
return planOf(machine, c.traced, compiled);
|
|
1635
|
+
return new CompiledPlan(machine, c.traced, root.code, root.run);
|
|
1155
1636
|
}
|
|
1156
1637
|
|
|
1157
|
-
/**
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1638
|
+
/**
|
|
1639
|
+
* The object of a plan (PLAN.LEAN). The code of `evaluate` and `run` is on the class, thus a plan keeps only its
|
|
1640
|
+
* machine and its compiled code. A plan makes its compiled root and the steps of a run at their first use.
|
|
1641
|
+
*/
|
|
1642
|
+
class CompiledPlan implements Plan {
|
|
1643
|
+
/*
|
|
1644
|
+
* The constructor writes each field one time, and the class declares the fields with `declare`. A field that a class
|
|
1645
|
+
* defines first as `undefined` and then writes again is not constant for V8, and the code of an evaluation then
|
|
1646
|
+
* loads it at each call.
|
|
1647
|
+
*/
|
|
1648
|
+
declare readonly machine: Machine;
|
|
1649
|
+
declare readonly traced: boolean;
|
|
1650
|
+
/** The code of the root document. */
|
|
1651
|
+
declare readonly code: Code;
|
|
1652
|
+
/** The steps of a run of the root document. `undefined` gives `runOf(code)` at the first `run`. */
|
|
1653
|
+
declare private steps: RunCode | undefined;
|
|
1654
|
+
/** The compiled root, after the first read of `root`. */
|
|
1655
|
+
declare private part: CompiledPart | undefined;
|
|
1656
|
+
|
|
1657
|
+
constructor(machine: Machine, traced: boolean, code: Code, steps: RunCode | undefined) {
|
|
1658
|
+
this.machine = machine;
|
|
1659
|
+
this.traced = traced;
|
|
1660
|
+
this.code = code;
|
|
1661
|
+
this.steps = steps;
|
|
1662
|
+
this.part = undefined;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
get root(): CompiledPart {
|
|
1666
|
+
return (this.part ??= { key: "", path: [], code: this.code, run: this.runCode() });
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/** This function gives the steps of a run of the root document. */
|
|
1670
|
+
runCode(): RunCode {
|
|
1671
|
+
return (this.steps ??= runOf(this.code));
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
evaluate(context: Context = makeContext(), evalOptions?: EvalOptions): Result {
|
|
1675
|
+
const machine = this.machine;
|
|
1676
|
+
const ctx = rootContext(machine, context);
|
|
1677
|
+
const s = new Session(machine, false, this.traced, evalOptions?.perform, undefined, evalOptions?.effects, readingOf(evalOptions?.reads, ctx));
|
|
1678
|
+
return this.code(ctx, s);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
run(context: Context = makeContext(), runOptions: RunOptions = {}): Promise<Result> {
|
|
1682
|
+
const machine = this.machine;
|
|
1683
|
+
const ctx = rootContext(machine, context);
|
|
1684
|
+
const s = new Session(machine, true, this.traced, runOptions.perform, runOptions.signal, undefined, readingOf(runOptions.reads, ctx));
|
|
1685
|
+
return driveWaiting(this.runCode()(ctx, s), runOptions.signal);
|
|
1686
|
+
}
|
|
1172
1687
|
}
|
|
1173
1688
|
|
|
1174
1689
|
/**
|
|
@@ -1191,6 +1706,8 @@ export function planner(machine: Machine, options: PrepareOptions = {}): (doc: u
|
|
|
1191
1706
|
}
|
|
1192
1707
|
return plan;
|
|
1193
1708
|
}
|
|
1709
|
+
// A map treats -0 and 0 as one key, so -0 gets its own plan each time.
|
|
1710
|
+
if (Object.is(doc, -0)) return prepare(machine, doc, options);
|
|
1194
1711
|
let plan = values.get(doc);
|
|
1195
1712
|
if (plan === undefined) {
|
|
1196
1713
|
plan = prepare(machine, doc, options);
|
|
@@ -1210,20 +1727,30 @@ function record(s: Session, result: Result): Run {
|
|
|
1210
1727
|
/**
|
|
1211
1728
|
* This function evaluates a document, and records each evaluation in a trace.
|
|
1212
1729
|
*
|
|
1213
|
-
* The verb
|
|
1214
|
-
* evaluator gets each part sealed: it can evaluate the part, and it cannot read its
|
|
1215
|
-
* value
|
|
1730
|
+
* The verb of the options goes up the ladder for each document. The default verb is `eval`. The kind of the document
|
|
1731
|
+
* tells where its parts are. The evaluator gets each part sealed: it can evaluate the part, and it cannot read its
|
|
1732
|
+
* value.
|
|
1733
|
+
*
|
|
1734
|
+
* A failure gives an error value, and the function does not throw. An effect is recorded in its order, and the host
|
|
1735
|
+
* does not do it. A mock can answer each effect at once (RUN.MOCK).
|
|
1216
1736
|
*
|
|
1217
1737
|
* @param machine - The world and the code of the evaluators.
|
|
1218
1738
|
* @param doc - The document, as JSON.
|
|
1219
1739
|
* @param context - The names and the capabilities at the root.
|
|
1220
|
-
* @param
|
|
1740
|
+
* @param options - The type of a bare root value, or the options: that type, the mock of the host, the verb and a list for the reads.
|
|
1221
1741
|
* @returns The result, the trace and the effects in their order.
|
|
1222
1742
|
*/
|
|
1223
|
-
export function evaluate(machine: Machine, doc: unknown, context: Context = makeContext(),
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1743
|
+
export function evaluate(machine: Machine, doc: unknown, context: Context = makeContext(), options?: string | EvaluateOptions): Run {
|
|
1744
|
+
const given: EvaluateOptions = typeof options === "string" ? { declaredType: options } : (options ?? {});
|
|
1745
|
+
const declaredType = given.declaredType;
|
|
1746
|
+
const plan = prepare(machine, doc, {
|
|
1747
|
+
trace: true,
|
|
1748
|
+
...(declaredType === undefined ? {} : { declaredType }),
|
|
1749
|
+
...(given.verb === undefined ? {} : { verb: given.verb }),
|
|
1750
|
+
});
|
|
1751
|
+
const ctx = rootContext(machine, context);
|
|
1752
|
+
const s = new Session(machine, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
1753
|
+
const result = (plan as CompiledPlan).code(ctx, s);
|
|
1227
1754
|
return record(s, result);
|
|
1228
1755
|
}
|
|
1229
1756
|
|
|
@@ -1235,13 +1762,18 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
|
|
|
1235
1762
|
* @param machine - The world, the evaluators and the runners.
|
|
1236
1763
|
* @param doc - The document, as JSON.
|
|
1237
1764
|
* @param context - The names and the capabilities at the root.
|
|
1238
|
-
* @param options - The host code that does the effects, the type of a bare root value, and a
|
|
1765
|
+
* @param options - The host code that does the effects, the type of a bare root value, a signal, the verb and a list for the reads.
|
|
1239
1766
|
* @returns The result, the trace and the effects in their order, when the run ends.
|
|
1240
1767
|
*/
|
|
1241
1768
|
export async function run(machine: Machine, doc: unknown, context: Context = makeContext(), options: RunOptions = {}): Promise<Run> {
|
|
1242
|
-
const plan = prepare(machine, doc, {
|
|
1243
|
-
|
|
1244
|
-
|
|
1769
|
+
const plan = prepare(machine, doc, {
|
|
1770
|
+
trace: true,
|
|
1771
|
+
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
1772
|
+
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
1773
|
+
});
|
|
1774
|
+
const ctx = rootContext(machine, context);
|
|
1775
|
+
const s = new Session(machine, true, true, options.perform, options.signal, undefined, readingOf(options.reads, ctx));
|
|
1776
|
+
const result = await driveWaiting((plan as CompiledPlan).runCode()(ctx, s), options.signal);
|
|
1245
1777
|
return record(s, result);
|
|
1246
1778
|
}
|
|
1247
1779
|
|