@mark1russell7/coeval 0.6.3 → 0.7.1
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 +63 -5
- package/build/index.d.ts +1771 -254
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5809 -511
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +15 -3
- package/src/codec.ts +185 -0
- package/src/conform.ts +1068 -0
- package/src/deref.ts +284 -47
- package/src/durable.ts +238 -0
- package/src/effects.ts +110 -0
- package/src/errors.ts +2 -1
- package/src/evaluate.ts +480 -113
- package/src/facts.ts +252 -0
- package/src/formula.ts +450 -42
- package/src/graph.ts +399 -0
- package/src/index.ts +46 -0
- 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 +23 -9
- package/src/schedule.ts +436 -0
- package/src/static.ts +266 -29
- package/src/stdlib.ts +24 -7
- package/src/syntax.ts +846 -0
- package/src/types.ts +768 -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,
|
|
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";
|
|
5
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
|
|
@@ -164,6 +204,86 @@ export interface Effect {
|
|
|
164
204
|
readonly outcome?: Result;
|
|
165
205
|
}
|
|
166
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[];
|
|
233
|
+
}
|
|
234
|
+
|
|
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
|
+
|
|
167
287
|
/**
|
|
168
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
|
|
169
289
|
* `Failure` gives its own error value. In an evaluation, this function is a mock: it answers at once, and a promise
|
|
@@ -265,7 +385,7 @@ export interface Runtime {
|
|
|
265
385
|
* This function causes an effect from an evaluator. In a run, an evaluator cannot cause an effect: the host does
|
|
266
386
|
* each effect, thus only a runner can wait for it.
|
|
267
387
|
*
|
|
268
|
-
* @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).
|
|
269
389
|
* @param kind - The kind of the effect.
|
|
270
390
|
* @param payload - The data of the effect.
|
|
271
391
|
* @param place - The place of the document.
|
|
@@ -276,7 +396,7 @@ export interface Runtime {
|
|
|
276
396
|
* This function causes an effect from a runner. In a run, the host does it, and the steps wait when the host
|
|
277
397
|
* waits.
|
|
278
398
|
*
|
|
279
|
-
* @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).
|
|
280
400
|
* @param kind - The kind of the effect.
|
|
281
401
|
* @param payload - The data of the effect.
|
|
282
402
|
* @param place - The place of the document.
|
|
@@ -291,6 +411,12 @@ export interface Runtime {
|
|
|
291
411
|
* @returns The steps that give each result, in the order of the list.
|
|
292
412
|
*/
|
|
293
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;
|
|
294
420
|
}
|
|
295
421
|
|
|
296
422
|
/**
|
|
@@ -305,10 +431,17 @@ export interface AllOptions {
|
|
|
305
431
|
readonly failFast?: boolean;
|
|
306
432
|
}
|
|
307
433
|
|
|
308
|
-
/**
|
|
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
|
+
*/
|
|
309
439
|
export type Code = (ctx: Context, rt: Runtime) => Result;
|
|
310
440
|
|
|
311
|
-
/**
|
|
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
|
+
*/
|
|
312
445
|
export type RunCode = (ctx: Context, rt: Runtime) => Steps;
|
|
313
446
|
|
|
314
447
|
/** A compiled part: its place in its parent, its binding name, and its code. It has no field with its value. */
|
|
@@ -390,6 +523,8 @@ export interface EvalInput {
|
|
|
390
523
|
readonly context: Context;
|
|
391
524
|
/** The keys from the root document to this document. */
|
|
392
525
|
readonly place: readonly Key[];
|
|
526
|
+
/** The machine of the evaluation, for example for its domains (D40). The kernel sets it. */
|
|
527
|
+
readonly machine?: Machine;
|
|
393
528
|
/** `true` when the position of the document declares an address. */
|
|
394
529
|
readonly address?: boolean;
|
|
395
530
|
/**
|
|
@@ -447,6 +582,8 @@ export interface RunInput {
|
|
|
447
582
|
readonly parts: readonly RunPart[];
|
|
448
583
|
readonly context: Context;
|
|
449
584
|
readonly place: readonly Key[];
|
|
585
|
+
/** The machine of the evaluation, for example for its domains (D40). The kernel sets it. */
|
|
586
|
+
readonly machine?: Machine;
|
|
450
587
|
resolve(path: readonly unknown[], options?: ResolveOptions): Resolution;
|
|
451
588
|
/**
|
|
452
589
|
* This function causes an effect. In a run, the host does the effect, and the steps wait when the host waits. In
|
|
@@ -488,6 +625,11 @@ export interface CompiledEvaluator {
|
|
|
488
625
|
* @returns The code that evaluates the document.
|
|
489
626
|
*/
|
|
490
627
|
readonly compile: (input: CompileInput) => Code;
|
|
628
|
+
/**
|
|
629
|
+
* The type rule of the evaluator: the value types that a check of types gives its documents and their parts
|
|
630
|
+
* (TYPE.RULE). Only a check reads it. An evaluator without a rule gives `unknown`.
|
|
631
|
+
*/
|
|
632
|
+
readonly type?: TypeRule;
|
|
491
633
|
}
|
|
492
634
|
|
|
493
635
|
/** An evaluator: a function of its input, or a compiler. */
|
|
@@ -517,8 +659,11 @@ export type Runner = RunFn | CompiledRunner;
|
|
|
517
659
|
* A machine: a world of kinds, and the code of each evaluator name, of each `deref` and of each walker.
|
|
518
660
|
*
|
|
519
661
|
* - `maxDepth`: the deepest nesting of documents. The default is 1000.
|
|
520
|
-
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities.
|
|
662
|
+
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities. A run,
|
|
663
|
+
* `check` and `effectsOf` ask it (CAP.PERMITS). In a check, its context has only the capabilities and the world.
|
|
521
664
|
* - `known`: the names that the evaluators know, for `check`. Without it, `check` does not check such names.
|
|
665
|
+
* - `domains`: the value domains of the language, by the name of their base type (D22).
|
|
666
|
+
* - `facts`: the static facts of the names that a fallback kind covers, for `factsOf` (D24).
|
|
522
667
|
*/
|
|
523
668
|
export interface Machine extends Navigator {
|
|
524
669
|
readonly evaluators: Readonly<Record<string, Evaluator>>;
|
|
@@ -532,10 +677,26 @@ export interface Machine extends Navigator {
|
|
|
532
677
|
* `check` asks it for each document with an evaluator. The function must be pure.
|
|
533
678
|
*
|
|
534
679
|
* @param path - The keys of the kind of the document, after each alias.
|
|
535
|
-
* @param evaluator - The name of the evaluator that the ladder gives the document.
|
|
680
|
+
* @param evaluator - The name of the evaluator that the ladder of the verb of the check gives the document.
|
|
536
681
|
* @returns `true` when the evaluator knows the name, or else the reason of the `#NAME?` that a run gives.
|
|
537
682
|
*/
|
|
538
683
|
readonly known?: (path: readonly Key[], evaluator: string) => true | string;
|
|
684
|
+
/**
|
|
685
|
+
* The value domains of the language, by the name of their base type, for example a color or a unit of length. A
|
|
686
|
+
* check reads them through `fits` and `member`. A domain of the kernel that the table does not name comes from
|
|
687
|
+
* `STD_DOMAINS`.
|
|
688
|
+
*/
|
|
689
|
+
readonly domains?: Readonly<Record<string, Domain>>;
|
|
690
|
+
/**
|
|
691
|
+
* This function gives the static facts of the code of a name, for `factsOf` (D24). An example is the operation
|
|
692
|
+
* `["ops", "now"]` of a formula language: a fallback kind names it, and only the language knows that it is volatile.
|
|
693
|
+
* Each field of the answer wins over the same field of the contract of the kind. The function must be pure.
|
|
694
|
+
*
|
|
695
|
+
* @param path - The keys of the kind of the document, after each alias.
|
|
696
|
+
* @param code - The name of the evaluator of the document, or of the walker of a step document.
|
|
697
|
+
* @returns The facts, or `undefined` for the facts of the contract.
|
|
698
|
+
*/
|
|
699
|
+
readonly facts?: (path: readonly Key[], code: string) => KindFacts | undefined;
|
|
539
700
|
}
|
|
540
701
|
|
|
541
702
|
/** One evaluation in a trace. The children are the evaluations of its parts, in the order that they started. */
|
|
@@ -547,7 +708,7 @@ export interface TraceNode {
|
|
|
547
708
|
readonly doc: Envelope;
|
|
548
709
|
/** `true` when the type came from the container and not from an envelope. */
|
|
549
710
|
readonly declared: boolean;
|
|
550
|
-
/** The answer of the ladder to the verb `eval
|
|
711
|
+
/** The answer of the ladder to the verb of the plan, `eval` by default. */
|
|
551
712
|
readonly answer: Answer;
|
|
552
713
|
readonly side: Side;
|
|
553
714
|
/** The names that the document could read when its evaluation started. */
|
|
@@ -629,8 +790,18 @@ export interface RunOptions {
|
|
|
629
790
|
readonly perform?: Perform;
|
|
630
791
|
/** The type of a bare root value. */
|
|
631
792
|
readonly declaredType?: string;
|
|
793
|
+
/**
|
|
794
|
+
* The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). A plan keeps the verb of its
|
|
795
|
+
* `prepare`, thus `plan.run` does not read this option.
|
|
796
|
+
*/
|
|
797
|
+
readonly verb?: string;
|
|
632
798
|
/** A signal of the host. When it aborts, the run stops with `#ABORT`. */
|
|
633
799
|
readonly signal?: StopSignal;
|
|
800
|
+
/**
|
|
801
|
+
* A list that gets each read, in the order of the resolutions (READS.RECORD). Under `all`, the order of the host
|
|
802
|
+
* decides the order of the reads of the items.
|
|
803
|
+
*/
|
|
804
|
+
readonly reads?: Read[];
|
|
634
805
|
}
|
|
635
806
|
|
|
636
807
|
/** The options of an evaluation of a plan. */
|
|
@@ -642,20 +813,45 @@ export interface EvalOptions {
|
|
|
642
813
|
* Without it, an effect gives its payload (RUN.EVAL).
|
|
643
814
|
*/
|
|
644
815
|
readonly perform?: Perform;
|
|
816
|
+
/**
|
|
817
|
+
* A list that gets each read, in the order of the resolutions (READS.RECORD). Without it, the plan records no read,
|
|
818
|
+
* and each resolution costs one more test.
|
|
819
|
+
*/
|
|
820
|
+
readonly reads?: Read[];
|
|
645
821
|
}
|
|
646
822
|
|
|
647
823
|
/** The options of `evaluate`. */
|
|
648
824
|
export interface EvaluateOptions {
|
|
649
825
|
/** The type of a bare root value. */
|
|
650
826
|
readonly declaredType?: string;
|
|
827
|
+
/** The verb that goes up the ladder at each document, for example `format`. The default is `eval` (VERB.PREPARE). */
|
|
828
|
+
readonly verb?: string;
|
|
651
829
|
/** The mock of the host (RUN.MOCK). Without it, an effect gives its payload (RUN.EVAL). */
|
|
652
830
|
readonly perform?: Perform;
|
|
831
|
+
/** A list that gets each read, in the order of the resolutions (READS.RECORD). */
|
|
832
|
+
readonly reads?: Read[];
|
|
653
833
|
}
|
|
654
834
|
|
|
655
835
|
/** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
|
|
656
836
|
const WITHOUT_HOPS: ResolveOptions = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
657
837
|
const WITH_HOPS: ResolveOptions = /* @__PURE__ */ Object.freeze({ hops: true });
|
|
658
838
|
|
|
839
|
+
/** The record of the reads of one evaluation (READS.RECORD). */
|
|
840
|
+
interface Reading {
|
|
841
|
+
readonly list: Read[];
|
|
842
|
+
/** The context at the root of the evaluation. A free read has the binding of this context. */
|
|
843
|
+
readonly root: Context;
|
|
844
|
+
/** `true` after the kernel added a read of each root (READS.KEPT). */
|
|
845
|
+
kept: boolean;
|
|
846
|
+
/** `true` after the kernel added the read of the root `self`. */
|
|
847
|
+
self: boolean;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** This function makes the record of the reads of an evaluation, when the host gives a list. */
|
|
851
|
+
function readingOf(list: Read[] | undefined, root: Context): Reading | undefined {
|
|
852
|
+
return list === undefined ? undefined : { list, root, kept: false, self: false };
|
|
853
|
+
}
|
|
854
|
+
|
|
659
855
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
660
856
|
class Session implements Runtime {
|
|
661
857
|
/** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
|
|
@@ -677,6 +873,8 @@ class Session implements Runtime {
|
|
|
677
873
|
readonly perform: Perform | undefined,
|
|
678
874
|
readonly signal: StopSignal | undefined,
|
|
679
875
|
effects: Effect[] | undefined,
|
|
876
|
+
/** The record of the reads. Without it, a resolution records nothing. */
|
|
877
|
+
private readonly reading: Reading | undefined = undefined,
|
|
680
878
|
) {
|
|
681
879
|
this.list = effects;
|
|
682
880
|
this.holder = traced ? holderNode() : undefined;
|
|
@@ -689,16 +887,77 @@ class Session implements Runtime {
|
|
|
689
887
|
}
|
|
690
888
|
|
|
691
889
|
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
890
|
+
return this.read(ctx, path, undefined, options);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* This function resolves a compiled path (PLAN.PATH). Without a list of reads and without a trace, the compiled
|
|
895
|
+
* path answers at once. Each other resolution records its read, as `resolve` does.
|
|
896
|
+
*/
|
|
897
|
+
resolvePath(ctx: Context, compiled: CompiledPath): Resolution {
|
|
898
|
+
if (this.reading === undefined && !this.traced && compiled.nav === this.machine) return compiled.resolve(ctx);
|
|
899
|
+
return this.read(ctx, compiled.path, compiled, undefined);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/** This function resolves a path, and records its read when the evaluation has a list of reads (READS.RECORD). */
|
|
903
|
+
private read(ctx: Context, path: readonly unknown[], compiled: CompiledPath | undefined, options: ResolveOptions | undefined): Resolution {
|
|
692
904
|
const hops = options?.hops ?? this.traced;
|
|
905
|
+
const reading = this.reading;
|
|
906
|
+
if (reading === undefined) return this.find(ctx, path, compiled, hops);
|
|
907
|
+
const free = isFree(reading.root, ctx, path);
|
|
908
|
+
// A kind that sets another self can give the old self on, as the parent of a node of a tree does (READS.KEPT).
|
|
909
|
+
if (!free && path[0] === "self" && !reading.self) this.keep(reading, ["self"]);
|
|
910
|
+
let found: Resolution;
|
|
911
|
+
try {
|
|
912
|
+
found = this.find(ctx, path, compiled, hops);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
const asked = options?.hops === true;
|
|
918
|
+
reading.list.push(asked ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
|
|
919
|
+
return found;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* This function resolves the steps of a path. A compiled path of this machine answers a resolution without hops. A
|
|
924
|
+
* resolution with hops, and a path that compiled for another navigator, resolve the steps again.
|
|
925
|
+
*/
|
|
926
|
+
private find(ctx: Context, path: readonly unknown[], compiled: CompiledPath | undefined, hops: boolean): Resolution {
|
|
927
|
+
if (compiled !== undefined && !hops && compiled.nav === this.machine) return compiled.resolve(ctx);
|
|
693
928
|
return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
694
929
|
}
|
|
695
930
|
|
|
931
|
+
keepsContext(): void {
|
|
932
|
+
const reading = this.reading;
|
|
933
|
+
if (reading === undefined || reading.kept) return;
|
|
934
|
+
reading.kept = true;
|
|
935
|
+
this.keep(reading, reading.self ? ROOTS.filter((root) => root !== "self") : ROOTS);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* This function adds a read of each whole root to the list, from the context at the root (READS.KEPT). An exception
|
|
940
|
+
* of the host gives the error value of the read, because the evaluation did not ask for the read.
|
|
941
|
+
*/
|
|
942
|
+
private keep(reading: Reading, roots: readonly Root[]): void {
|
|
943
|
+
for (const root of roots) {
|
|
944
|
+
if (root === "self") reading.self = true;
|
|
945
|
+
const path = [root];
|
|
946
|
+
reading.list.push({ path, reached: readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
696
950
|
private permitted(ctx: Context, keys: readonly Key[]): boolean {
|
|
697
951
|
return this.machine.permits === undefined ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
|
|
698
952
|
}
|
|
699
953
|
|
|
700
|
-
/**
|
|
701
|
-
|
|
954
|
+
/**
|
|
955
|
+
* This function checks the capability of an effect, and records the effect with its address (RUN.ADDRESS). The
|
|
956
|
+
* context keeps only the capabilities of its grant, thus compiled code cannot widen the context of its own effect
|
|
957
|
+
* (CAP.NARROW-CODE).
|
|
958
|
+
*/
|
|
959
|
+
private record(given: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result | Recorded {
|
|
960
|
+
const ctx = granted(given);
|
|
702
961
|
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
703
962
|
const text = keys.join(".");
|
|
704
963
|
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
@@ -897,6 +1156,19 @@ class Session implements Runtime {
|
|
|
897
1156
|
}
|
|
898
1157
|
}
|
|
899
1158
|
|
|
1159
|
+
/**
|
|
1160
|
+
* This function resolves a compiled path through a runtime (PLAN.PATH). The runtime of the kernel uses the compiled
|
|
1161
|
+
* path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path.
|
|
1162
|
+
*
|
|
1163
|
+
* @param rt - The runtime.
|
|
1164
|
+
* @param ctx - The context.
|
|
1165
|
+
* @param compiled - The compiled path, from `compilePath`.
|
|
1166
|
+
* @returns What the path reached, or an error.
|
|
1167
|
+
*/
|
|
1168
|
+
export function resolveCompiled(rt: Runtime, ctx: Context, compiled: CompiledPath): Resolution {
|
|
1169
|
+
return rt instanceof Session ? rt.resolvePath(ctx, compiled) : rt.resolve(ctx, compiled.path);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
900
1172
|
function holderNode(): Building {
|
|
901
1173
|
return {
|
|
902
1174
|
id: -1,
|
|
@@ -917,13 +1189,25 @@ function holderNode(): Building {
|
|
|
917
1189
|
};
|
|
918
1190
|
}
|
|
919
1191
|
|
|
920
|
-
/**
|
|
921
|
-
function
|
|
1192
|
+
/**
|
|
1193
|
+
* This function gives the reason of a runner that waits where its parent cannot wait (RUN.WAIT). The plan, the
|
|
1194
|
+
* reference and `check` with the option `run` give the same reason.
|
|
1195
|
+
*
|
|
1196
|
+
* @param kindText - The text of the type of the runner document.
|
|
1197
|
+
* @returns The reason.
|
|
1198
|
+
*/
|
|
1199
|
+
export function waitedReason(kindText: string): string {
|
|
1200
|
+
return `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/** This function drives steps that cannot wait. A step that waits stops the steps, and gives `#CALC!`. */
|
|
1204
|
+
function drive(steps: Steps, kindText: string): Result {
|
|
922
1205
|
const step = steps.next();
|
|
923
1206
|
if (step.done === true) return step.value;
|
|
924
1207
|
void step.value.catch(() => undefined);
|
|
925
|
-
|
|
926
|
-
return
|
|
1208
|
+
const waited = fail("#CALC!", waitedReason(kindText));
|
|
1209
|
+
void steps.return(waited);
|
|
1210
|
+
return waited;
|
|
927
1211
|
}
|
|
928
1212
|
|
|
929
1213
|
const ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
@@ -976,6 +1260,11 @@ export interface PrepareOptions {
|
|
|
976
1260
|
readonly declaredType?: string;
|
|
977
1261
|
/** `true`: each evaluation records a trace. The default is `false`. */
|
|
978
1262
|
readonly trace?: boolean;
|
|
1263
|
+
/**
|
|
1264
|
+
* The verb that goes up the ladder at each document, for example `format`. Each part evaluates with the same verb.
|
|
1265
|
+
* The default is `eval` (VERB.PREPARE).
|
|
1266
|
+
*/
|
|
1267
|
+
readonly verb?: string;
|
|
979
1268
|
}
|
|
980
1269
|
|
|
981
1270
|
/** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
|
|
@@ -990,7 +1279,7 @@ export interface Plan {
|
|
|
990
1279
|
* effect at once (RUN.MOCK).
|
|
991
1280
|
*
|
|
992
1281
|
* @param context - The names and the capabilities at the root.
|
|
993
|
-
* @param options - A list that gets the effects,
|
|
1282
|
+
* @param options - A list that gets the effects, the mock of the host and a list that gets the reads.
|
|
994
1283
|
* @returns The result.
|
|
995
1284
|
*/
|
|
996
1285
|
evaluate(context?: Context, options?: EvalOptions): Result;
|
|
@@ -998,7 +1287,7 @@ export interface Plan {
|
|
|
998
1287
|
* This function runs the document: the host does each effect, and the run waits when the host waits.
|
|
999
1288
|
*
|
|
1000
1289
|
* @param context - The names and the capabilities at the root.
|
|
1001
|
-
* @param options - The host code that does the effects,
|
|
1290
|
+
* @param options - The host code that does the effects, a signal to stop the run and a list that gets the reads.
|
|
1002
1291
|
* @returns The result, when the run ends.
|
|
1003
1292
|
*/
|
|
1004
1293
|
run(context?: Context, options?: RunOptions): Promise<Result>;
|
|
@@ -1008,23 +1297,31 @@ interface Compiler {
|
|
|
1008
1297
|
readonly machine: Machine;
|
|
1009
1298
|
readonly traced: boolean;
|
|
1010
1299
|
readonly maxDepth: number;
|
|
1300
|
+
/** The verb that goes up the ladder at each document. */
|
|
1301
|
+
readonly verb: string;
|
|
1302
|
+
/** The answer of a document that the ladder did not see, in a trace. */
|
|
1303
|
+
readonly noAnswer: Answer;
|
|
1011
1304
|
}
|
|
1012
1305
|
|
|
1013
|
-
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string):
|
|
1306
|
+
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string): Compiled {
|
|
1014
1307
|
return failingWith(c, meta, fail(code, reason, { place }));
|
|
1015
1308
|
}
|
|
1016
1309
|
|
|
1017
1310
|
/** This function gives the code of a document that fails with one result at each evaluation. */
|
|
1018
|
-
function failingWith(c: Compiler, meta: Meta, result: Result):
|
|
1311
|
+
function failingWith(c: Compiler, meta: Meta, result: Result): Compiled {
|
|
1019
1312
|
const plain: Code = () => result;
|
|
1020
|
-
return finish(c, meta, plain,
|
|
1021
|
-
return result;
|
|
1022
|
-
});
|
|
1313
|
+
return finish(c, meta, plain, undefined);
|
|
1023
1314
|
}
|
|
1024
1315
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1316
|
+
/**
|
|
1317
|
+
* This function gives the code of a document in a trace. Each node records the context that its grant permits
|
|
1318
|
+
* (CAP.NARROW-CODE), as the code of the document gets it.
|
|
1319
|
+
*/
|
|
1320
|
+
function finish(c: Compiler, meta: Meta, code: Code, steps: RunCode | undefined): Compiled {
|
|
1321
|
+
if (!c.traced) return { code, run: steps };
|
|
1322
|
+
const run = steps ?? runOf(code);
|
|
1323
|
+
const tracedCode: Code = (given, rt) => {
|
|
1324
|
+
const ctx = granted(given);
|
|
1028
1325
|
const s = rt as Session;
|
|
1029
1326
|
const node = s.enter(meta, ctx);
|
|
1030
1327
|
const before = s.current;
|
|
@@ -1038,7 +1335,8 @@ function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly c
|
|
|
1038
1335
|
s.exit(node, result);
|
|
1039
1336
|
return result;
|
|
1040
1337
|
};
|
|
1041
|
-
const tracedRun: RunCode = function* (
|
|
1338
|
+
const tracedRun: RunCode = function* (given, rt) {
|
|
1339
|
+
const ctx = granted(given);
|
|
1042
1340
|
const s = rt as Session;
|
|
1043
1341
|
const node = s.enter(meta, ctx);
|
|
1044
1342
|
const steps = run(ctx, rt);
|
|
@@ -1069,47 +1367,34 @@ function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly c
|
|
|
1069
1367
|
return { code: tracedCode, run: tracedRun };
|
|
1070
1368
|
}
|
|
1071
1369
|
|
|
1072
|
-
/**
|
|
1073
|
-
* This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
|
|
1074
|
-
* trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
|
|
1075
|
-
*/
|
|
1076
|
-
function evaluatorOf(world: World, envelope: Envelope | undefined, kind: Kind | undefined): { readonly name?: string; readonly unknown: boolean } {
|
|
1077
|
-
const own = envelope?.own;
|
|
1078
|
-
const ownName = own !== undefined && Object.hasOwn(own, "eval") ? own["eval"] : undefined;
|
|
1079
|
-
if (ownName !== undefined) return { name: ownName, unknown: false };
|
|
1080
|
-
if (kind === undefined) return { unknown: true };
|
|
1081
|
-
const verbs = kind.verbs;
|
|
1082
|
-
const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : undefined;
|
|
1083
|
-
return name === undefined ? { unknown: false } : { name, unknown: false };
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
1370
|
/** The meta of a node of a compilation without a trace. Only a trace reads the meta of a node. */
|
|
1087
1371
|
const NO_META: Meta = /* @__PURE__ */ Object.freeze({ path: [], doc: { type: "?", value: null }, declared: false, answer: NO_ANSWER, side: "derive" });
|
|
1088
1372
|
|
|
1089
|
-
function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number, address = false):
|
|
1373
|
+
function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number, address = false): Compiled {
|
|
1090
1374
|
const world = c.machine.world;
|
|
1091
1375
|
const read = partsOf(world, value, declared);
|
|
1092
1376
|
if (!read.ok) {
|
|
1093
|
-
const meta: Meta = c.traced ? { path: place.map(String), doc: { type: "?", value: value as Json }, declared: true, answer:
|
|
1377
|
+
const meta: Meta = c.traced ? { path: place.map(String), doc: { type: "?", value: value as Json }, declared: true, answer: c.noAnswer, side: "derive" } : NO_META;
|
|
1094
1378
|
return failing(c, meta, place, read.code, read.reason);
|
|
1095
1379
|
}
|
|
1096
1380
|
const { doc, kind, split } = read;
|
|
1097
1381
|
const envelope = read.declared ? undefined : doc;
|
|
1098
|
-
const meta0: Meta = c.traced ? { path: place.map(String), doc, declared: read.declared, answer:
|
|
1382
|
+
const meta0: Meta = c.traced ? { path: place.map(String), doc, declared: read.declared, answer: c.noAnswer, side: "derive" } : NO_META;
|
|
1099
1383
|
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1100
1384
|
|
|
1101
1385
|
let meta = meta0;
|
|
1102
1386
|
let name: string | undefined;
|
|
1103
1387
|
let unknown: boolean;
|
|
1104
1388
|
if (c.traced) {
|
|
1105
|
-
const answer = ask(world, envelope ?? {},
|
|
1389
|
+
const answer = ask(world, envelope ?? {}, c.verb, declared === undefined ? {} : { declaredType: declared });
|
|
1106
1390
|
meta = { ...meta0, answer, side: kind?.side ?? "derive" };
|
|
1107
1391
|
name = answer.evaluator;
|
|
1108
1392
|
unknown = answer.error === "unknown-kind";
|
|
1109
1393
|
} else {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1394
|
+
// A document has a type here, thus a kind that the read did not find is a kind that the world does not have.
|
|
1395
|
+
const quick = answerOf(world, envelope?.own, kind ?? null, c.verb);
|
|
1396
|
+
name = quick?.name;
|
|
1397
|
+
unknown = quick === undefined && kind === undefined;
|
|
1113
1398
|
}
|
|
1114
1399
|
const kindText = typeName(doc.type);
|
|
1115
1400
|
if (unknown) return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
|
|
@@ -1129,9 +1414,10 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1129
1414
|
const compiled = rules.length === 0 ? own : gated(rules, own);
|
|
1130
1415
|
// The binding name is in the header of an envelope. Most parts have no name, thus the cheap test comes first.
|
|
1131
1416
|
const header = typeof (item as { readonly name?: unknown } | null)?.name === "string" && isEnvelope(item) ? (item as { readonly name: string }).name : undefined;
|
|
1417
|
+
const run = compiled.run ?? runOf(compiled.code);
|
|
1132
1418
|
return header === undefined
|
|
1133
|
-
? { key: slot.key, path: slot.path, code: compiled.code, run
|
|
1134
|
-
: { key: slot.key, path: slot.path, name: header, code: compiled.code, run
|
|
1419
|
+
? { key: slot.key, path: slot.path, code: compiled.code, run }
|
|
1420
|
+
: { key: slot.key, path: slot.path, name: header, code: compiled.code, run };
|
|
1135
1421
|
});
|
|
1136
1422
|
const typePath = read.typePath ?? [];
|
|
1137
1423
|
const input: CompileInput = {
|
|
@@ -1164,8 +1450,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1164
1450
|
} catch (error) {
|
|
1165
1451
|
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
1166
1452
|
}
|
|
1167
|
-
|
|
1168
|
-
return finish(c, meta, code, runOf(code));
|
|
1453
|
+
return finish(c, meta, placedCode(inner, place, name), undefined);
|
|
1169
1454
|
}
|
|
1170
1455
|
|
|
1171
1456
|
/*
|
|
@@ -1174,11 +1459,14 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1174
1459
|
* the split of the value. Thus a plan with these helpers keeps only its code.
|
|
1175
1460
|
*/
|
|
1176
1461
|
|
|
1177
|
-
/**
|
|
1462
|
+
/**
|
|
1463
|
+
* This function gives the code of a document: the code of its evaluator, with the place of each error and of each
|
|
1464
|
+
* exception. The code gets the context that its grant permits, thus a compiled parent cannot widen it (CAP.NARROW-CODE).
|
|
1465
|
+
*/
|
|
1178
1466
|
function placedCode(inner: Code, place: readonly Key[], name: string): Code {
|
|
1179
1467
|
return (ctx, rt) => {
|
|
1180
1468
|
try {
|
|
1181
|
-
return placed(inner(ctx, rt), place);
|
|
1469
|
+
return placed(inner(granted(ctx), rt), place);
|
|
1182
1470
|
} catch (error) {
|
|
1183
1471
|
return placed(thrown(error, `the evaluator ${name}`), place);
|
|
1184
1472
|
}
|
|
@@ -1187,23 +1475,31 @@ function placedCode(inner: Code, place: readonly Key[], name: string): Code {
|
|
|
1187
1475
|
|
|
1188
1476
|
/**
|
|
1189
1477
|
* This function gives the code of a part that capability rules cover. The code narrows the context of the part by
|
|
1190
|
-
* the rules before the part starts.
|
|
1478
|
+
* the rules before the part starts. First the context keeps only the capabilities of its grant, thus a compiled
|
|
1479
|
+
* parent cannot widen the capabilities that the rules narrow (CAP.NARROW-CODE).
|
|
1191
1480
|
*/
|
|
1192
|
-
function gated(rules: readonly CapsRule[], part:
|
|
1481
|
+
function gated(rules: readonly CapsRule[], part: Compiled): Compiled {
|
|
1193
1482
|
const inner = part.code;
|
|
1194
1483
|
const steps = part.run;
|
|
1195
|
-
const under = (
|
|
1484
|
+
const under = (given: Context): Context => {
|
|
1485
|
+
const ctx = granted(given);
|
|
1196
1486
|
const caps = capsUnder(rules, ctx.caps);
|
|
1197
|
-
|
|
1487
|
+
if (caps === ctx.caps || (caps.length === 0 && ctx.caps.length === 0)) return ctx;
|
|
1488
|
+
const out: Granted = { ...ctx, caps, [GRANT]: caps };
|
|
1489
|
+
return out;
|
|
1198
1490
|
};
|
|
1199
|
-
|
|
1491
|
+
// The steps of an evaluator are `runOf` of its code, thus `runOf` of this code narrows the context the same way.
|
|
1492
|
+
return { code: (ctx, rt) => inner(under(ctx), rt), run: steps === undefined ? undefined : (ctx, rt) => steps(under(ctx), rt) };
|
|
1200
1493
|
}
|
|
1201
1494
|
|
|
1202
|
-
/**
|
|
1495
|
+
/**
|
|
1496
|
+
* This function gives the steps of a runner, with the place of each error and of each exception. The steps get the
|
|
1497
|
+
* context that its grant permits (CAP.NARROW-CODE).
|
|
1498
|
+
*/
|
|
1203
1499
|
function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode {
|
|
1204
1500
|
return function* (ctx, rt) {
|
|
1205
1501
|
try {
|
|
1206
|
-
return placed(yield* inner(ctx, rt), place);
|
|
1502
|
+
return placed(yield* inner(granted(ctx), rt), place);
|
|
1207
1503
|
} catch (error) {
|
|
1208
1504
|
return placed(thrown(error, `the runner ${name}`), place);
|
|
1209
1505
|
}
|
|
@@ -1211,14 +1507,21 @@ function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode
|
|
|
1211
1507
|
}
|
|
1212
1508
|
|
|
1213
1509
|
/**
|
|
1214
|
-
* This function gives the code of a runner
|
|
1215
|
-
*
|
|
1510
|
+
* This function gives the code of a runner under a parent that cannot wait: its steps, driven without a wait. An
|
|
1511
|
+
* evaluation and a run both drive them (RUN.DRIVE-NESTED). Only a step that really waits gives `#CALC!`, at the place
|
|
1512
|
+
* of the runner, where the error started (RUN.WAIT).
|
|
1216
1513
|
*/
|
|
1217
1514
|
function runnerCode(run: RunCode, place: readonly Key[], kindText: string): Code {
|
|
1218
|
-
return (ctx, rt) =>
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1515
|
+
return (ctx, rt) => placed(drive(run(ctx, rt), kindText), place);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* The code of a compiled document. The steps of a run are `undefined` for an evaluator: they are `runOf(code)`, and a
|
|
1520
|
+
* plan makes them only for a run (PLAN.LEAN).
|
|
1521
|
+
*/
|
|
1522
|
+
interface Compiled {
|
|
1523
|
+
readonly code: Code;
|
|
1524
|
+
readonly run: RunCode | undefined;
|
|
1222
1525
|
}
|
|
1223
1526
|
|
|
1224
1527
|
/** This function gives the steps of a run of an evaluator. The code runs at the first step, and it does not wait. */
|
|
@@ -1235,8 +1538,10 @@ function fillOf(split: Extract<Split, { readonly ok: true }>, doc: Envelope): <P
|
|
|
1235
1538
|
|
|
1236
1539
|
|
|
1237
1540
|
function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): Code {
|
|
1238
|
-
const { kind, typePath, doc, data, place } = input;
|
|
1541
|
+
const { kind, typePath, doc, data, place, machine } = input;
|
|
1239
1542
|
const address = input.address === true ? { address: true } : {};
|
|
1543
|
+
// The code keeps the fill and not the whole input, thus it does not keep the compiled value of the document (PLAN.LEAN).
|
|
1544
|
+
const refill = input.fill;
|
|
1240
1545
|
const compiled = input.parts;
|
|
1241
1546
|
const hasParts = compiled.length > 0;
|
|
1242
1547
|
return (ctx, rt) => {
|
|
@@ -1244,7 +1549,7 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
|
|
|
1244
1549
|
key: part.key,
|
|
1245
1550
|
path: part.path,
|
|
1246
1551
|
...(part.name === undefined ? {} : { name: part.name }),
|
|
1247
|
-
evaluate: (override?: Context) => part.code(override === undefined ? ctx :
|
|
1552
|
+
evaluate: (override?: Context) => part.code(override === undefined ? ctx : handed(ctx, override), rt),
|
|
1248
1553
|
}));
|
|
1249
1554
|
return fn({
|
|
1250
1555
|
kind,
|
|
@@ -1255,16 +1560,18 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
|
|
|
1255
1560
|
parts,
|
|
1256
1561
|
context: ctx,
|
|
1257
1562
|
place,
|
|
1563
|
+
machine,
|
|
1258
1564
|
...address,
|
|
1259
1565
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1260
1566
|
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
1261
|
-
fill:
|
|
1567
|
+
fill: refill,
|
|
1262
1568
|
});
|
|
1263
1569
|
};
|
|
1264
1570
|
}
|
|
1265
1571
|
|
|
1266
1572
|
function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): RunCode {
|
|
1267
|
-
const { kind, typePath, doc, data, place } = input;
|
|
1573
|
+
const { kind, typePath, doc, data, place, machine } = input;
|
|
1574
|
+
const refill = input.fill;
|
|
1268
1575
|
const compiled = input.parts;
|
|
1269
1576
|
const hasParts = compiled.length > 0;
|
|
1270
1577
|
return (ctx, rt) => {
|
|
@@ -1272,7 +1579,7 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
1272
1579
|
key: part.key,
|
|
1273
1580
|
path: part.path,
|
|
1274
1581
|
...(part.name === undefined ? {} : { name: part.name }),
|
|
1275
|
-
evaluate: (override?: Context) => part.run(override === undefined ? ctx :
|
|
1582
|
+
evaluate: (override?: Context) => part.run(override === undefined ? ctx : handed(ctx, override), rt),
|
|
1276
1583
|
}));
|
|
1277
1584
|
return fn({
|
|
1278
1585
|
kind,
|
|
@@ -1283,60 +1590,106 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
1283
1590
|
parts,
|
|
1284
1591
|
context: ctx,
|
|
1285
1592
|
place,
|
|
1593
|
+
machine,
|
|
1286
1594
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1287
1595
|
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
1288
1596
|
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options),
|
|
1289
|
-
fill:
|
|
1597
|
+
fill: refill,
|
|
1290
1598
|
});
|
|
1291
1599
|
};
|
|
1292
1600
|
}
|
|
1293
1601
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1602
|
+
/**
|
|
1603
|
+
* This function gives the context at the root of an evaluation. It is the context of the host, with the world of
|
|
1604
|
+
* the machine when the context has none. The host gives the capabilities, thus the root has the grant of each one
|
|
1605
|
+
* (CAP.NARROW-CODE). An evaluation and `verifyReads` share it.
|
|
1606
|
+
*
|
|
1607
|
+
* @param machine - The machine.
|
|
1608
|
+
* @param ctx - The context of the host.
|
|
1609
|
+
* @returns The context at the root.
|
|
1610
|
+
*/
|
|
1611
|
+
export function rootContext(machine: Machine, ctx: Context): Context {
|
|
1612
|
+
if (ctx.world !== null && (ctx as Granted)[GRANT] === ctx.caps) return ctx;
|
|
1613
|
+
const root: Granted = { ...ctx, world: ctx.world === null ? worldThing(machine.world) : ctx.world, [GRANT]: ctx.caps };
|
|
1614
|
+
return root;
|
|
1296
1615
|
}
|
|
1297
1616
|
|
|
1298
1617
|
/**
|
|
1299
1618
|
* This function compiles a document into a plan. The ladder, the kinds and the parts are known after it. A plan
|
|
1300
1619
|
* evaluates many times. A document that does not compile gives a plan that fails with the reason, when it evaluates.
|
|
1301
1620
|
*
|
|
1621
|
+
* The verb of the options goes up the ladder at each document, and each part evaluates with the same verb. The
|
|
1622
|
+
* default verb is `eval`. A plan of another verb, for example `format`, evaluates, runs and traces as a plan of
|
|
1623
|
+
* `eval` does (VERB.PREPARE).
|
|
1624
|
+
*
|
|
1302
1625
|
* @param machine - The world and the code of the evaluators.
|
|
1303
1626
|
* @param doc - The document, as JSON.
|
|
1304
|
-
* @param options - The type of a bare root value, and
|
|
1627
|
+
* @param options - The type of a bare root value, a trace and the verb.
|
|
1305
1628
|
* @returns The plan.
|
|
1306
1629
|
*/
|
|
1307
1630
|
export function prepare(machine: Machine, doc: unknown, options: PrepareOptions = {}): Plan {
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1631
|
+
const verb = options.verb ?? "eval";
|
|
1632
|
+
const noAnswer: Answer = verb === "eval" ? NO_ANSWER : { verb, error: "no-answer", steps: [] };
|
|
1633
|
+
const c: Compiler = { machine, traced: options.trace === true, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer };
|
|
1634
|
+
let root: Compiled;
|
|
1310
1635
|
try {
|
|
1311
1636
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
1312
1637
|
} catch (error) {
|
|
1313
1638
|
const result = placed(thrown(error, "the compilation"), []);
|
|
1314
|
-
root = {
|
|
1315
|
-
code: () => result,
|
|
1316
|
-
run: function* () {
|
|
1317
|
-
return result;
|
|
1318
|
-
},
|
|
1319
|
-
};
|
|
1639
|
+
root = { code: () => result, run: undefined };
|
|
1320
1640
|
}
|
|
1321
|
-
|
|
1322
|
-
return planOf(machine, c.traced, compiled);
|
|
1641
|
+
return new CompiledPlan(machine, c.traced, root.code, root.run);
|
|
1323
1642
|
}
|
|
1324
1643
|
|
|
1325
|
-
/**
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1644
|
+
/**
|
|
1645
|
+
* The object of a plan (PLAN.LEAN). The code of `evaluate` and `run` is on the class, thus a plan keeps only its
|
|
1646
|
+
* machine and its compiled code. A plan makes its compiled root and the steps of a run at their first use.
|
|
1647
|
+
*/
|
|
1648
|
+
class CompiledPlan implements Plan {
|
|
1649
|
+
/*
|
|
1650
|
+
* The constructor writes each field one time, and the class declares the fields with `declare`. A field that a class
|
|
1651
|
+
* defines first as `undefined` and then writes again is not constant for V8, and the code of an evaluation then
|
|
1652
|
+
* loads it at each call.
|
|
1653
|
+
*/
|
|
1654
|
+
declare readonly machine: Machine;
|
|
1655
|
+
declare readonly traced: boolean;
|
|
1656
|
+
/** The code of the root document. */
|
|
1657
|
+
declare readonly code: Code;
|
|
1658
|
+
/** The steps of a run of the root document. `undefined` gives `runOf(code)` at the first `run`. */
|
|
1659
|
+
declare private steps: RunCode | undefined;
|
|
1660
|
+
/** The compiled root, after the first read of `root`. */
|
|
1661
|
+
declare private part: CompiledPart | undefined;
|
|
1662
|
+
|
|
1663
|
+
constructor(machine: Machine, traced: boolean, code: Code, steps: RunCode | undefined) {
|
|
1664
|
+
this.machine = machine;
|
|
1665
|
+
this.traced = traced;
|
|
1666
|
+
this.code = code;
|
|
1667
|
+
this.steps = steps;
|
|
1668
|
+
this.part = undefined;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
get root(): CompiledPart {
|
|
1672
|
+
return (this.part ??= { key: "", path: [], code: this.code, run: this.runCode() });
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/** This function gives the steps of a run of the root document. */
|
|
1676
|
+
runCode(): RunCode {
|
|
1677
|
+
return (this.steps ??= runOf(this.code));
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
evaluate(context: Context = makeContext(), evalOptions?: EvalOptions): Result {
|
|
1681
|
+
const machine = this.machine;
|
|
1682
|
+
const ctx = rootContext(machine, context);
|
|
1683
|
+
const s = new Session(machine, false, this.traced, evalOptions?.perform, undefined, evalOptions?.effects, readingOf(evalOptions?.reads, ctx));
|
|
1684
|
+
return this.code(ctx, s);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
run(context: Context = makeContext(), runOptions: RunOptions = {}): Promise<Result> {
|
|
1688
|
+
const machine = this.machine;
|
|
1689
|
+
const ctx = rootContext(machine, context);
|
|
1690
|
+
const s = new Session(machine, true, this.traced, runOptions.perform, runOptions.signal, undefined, readingOf(runOptions.reads, ctx));
|
|
1691
|
+
return driveWaiting(this.runCode()(ctx, s), runOptions.signal);
|
|
1692
|
+
}
|
|
1340
1693
|
}
|
|
1341
1694
|
|
|
1342
1695
|
/**
|
|
@@ -1359,6 +1712,8 @@ export function planner(machine: Machine, options: PrepareOptions = {}): (doc: u
|
|
|
1359
1712
|
}
|
|
1360
1713
|
return plan;
|
|
1361
1714
|
}
|
|
1715
|
+
// A map treats -0 and 0 as one key, so -0 gets its own plan each time.
|
|
1716
|
+
if (Object.is(doc, -0)) return prepare(machine, doc, options);
|
|
1362
1717
|
let plan = values.get(doc);
|
|
1363
1718
|
if (plan === undefined) {
|
|
1364
1719
|
plan = prepare(machine, doc, options);
|
|
@@ -1378,23 +1733,30 @@ function record(s: Session, result: Result): Run {
|
|
|
1378
1733
|
/**
|
|
1379
1734
|
* This function evaluates a document, and records each evaluation in a trace.
|
|
1380
1735
|
*
|
|
1381
|
-
* The verb
|
|
1382
|
-
* evaluator gets each part sealed: it can evaluate the part, and it cannot read its
|
|
1383
|
-
* value
|
|
1384
|
-
*
|
|
1736
|
+
* The verb of the options goes up the ladder for each document. The default verb is `eval`. The kind of the document
|
|
1737
|
+
* tells where its parts are. The evaluator gets each part sealed: it can evaluate the part, and it cannot read its
|
|
1738
|
+
* value.
|
|
1739
|
+
*
|
|
1740
|
+
* A failure gives an error value, and the function does not throw. An effect is recorded in its order, and the host
|
|
1741
|
+
* does not do it. A mock can answer each effect at once (RUN.MOCK).
|
|
1385
1742
|
*
|
|
1386
1743
|
* @param machine - The world and the code of the evaluators.
|
|
1387
1744
|
* @param doc - The document, as JSON.
|
|
1388
1745
|
* @param context - The names and the capabilities at the root.
|
|
1389
|
-
* @param options - The type of a bare root value, or the options: that type
|
|
1746
|
+
* @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.
|
|
1390
1747
|
* @returns The result, the trace and the effects in their order.
|
|
1391
1748
|
*/
|
|
1392
1749
|
export function evaluate(machine: Machine, doc: unknown, context: Context = makeContext(), options?: string | EvaluateOptions): Run {
|
|
1393
1750
|
const given: EvaluateOptions = typeof options === "string" ? { declaredType: options } : (options ?? {});
|
|
1394
1751
|
const declaredType = given.declaredType;
|
|
1395
|
-
const plan = prepare(machine, doc, {
|
|
1396
|
-
|
|
1397
|
-
|
|
1752
|
+
const plan = prepare(machine, doc, {
|
|
1753
|
+
trace: true,
|
|
1754
|
+
...(declaredType === undefined ? {} : { declaredType }),
|
|
1755
|
+
...(given.verb === undefined ? {} : { verb: given.verb }),
|
|
1756
|
+
});
|
|
1757
|
+
const ctx = rootContext(machine, context);
|
|
1758
|
+
const s = new Session(machine, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
1759
|
+
const result = (plan as CompiledPlan).code(ctx, s);
|
|
1398
1760
|
return record(s, result);
|
|
1399
1761
|
}
|
|
1400
1762
|
|
|
@@ -1406,13 +1768,18 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
|
|
|
1406
1768
|
* @param machine - The world, the evaluators and the runners.
|
|
1407
1769
|
* @param doc - The document, as JSON.
|
|
1408
1770
|
* @param context - The names and the capabilities at the root.
|
|
1409
|
-
* @param options - The host code that does the effects, the type of a bare root value, and a
|
|
1771
|
+
* @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.
|
|
1410
1772
|
* @returns The result, the trace and the effects in their order, when the run ends.
|
|
1411
1773
|
*/
|
|
1412
1774
|
export async function run(machine: Machine, doc: unknown, context: Context = makeContext(), options: RunOptions = {}): Promise<Run> {
|
|
1413
|
-
const plan = prepare(machine, doc, {
|
|
1414
|
-
|
|
1415
|
-
|
|
1775
|
+
const plan = prepare(machine, doc, {
|
|
1776
|
+
trace: true,
|
|
1777
|
+
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
1778
|
+
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
1779
|
+
});
|
|
1780
|
+
const ctx = rootContext(machine, context);
|
|
1781
|
+
const s = new Session(machine, true, true, options.perform, options.signal, undefined, readingOf(options.reads, ctx));
|
|
1782
|
+
const result = await driveWaiting((plan as CompiledPlan).runCode()(ctx, s), options.signal);
|
|
1416
1783
|
return record(s, result);
|
|
1417
1784
|
}
|
|
1418
1785
|
|