@mark1russell7/coeval 0.13.0 → 0.15.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/build/index.d.ts +157 -104
- package/build/index.d.ts.map +1 -1
- package/build/index.js +533 -173
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +110 -2
- package/src/evaluate.ts +114 -22
- package/src/formula.ts +17 -3
- package/src/index.ts +2 -2
- package/src/reference.ts +247 -42
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/coeval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./build/index.js",
|
package/src/agree.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Key, docEquals, equalWith } from "./doc.ts";
|
|
2
2
|
import type { Context } from "./deref.ts";
|
|
3
3
|
import { type EvalError, type Result, isStackOverflow, thrown } from "./errors.ts";
|
|
4
|
-
import { type Effect, type Machine, type Perform, evaluate, makeContext, prepare } from "./evaluate.ts";
|
|
4
|
+
import { type Effect, type Journal, type Machine, type Perform, JOURNAL, evaluate, makeContext, prepare, run } from "./evaluate.ts";
|
|
5
5
|
import { Held, isClosure } from "./formula.ts";
|
|
6
|
-
import { type Definitions, reference } from "./reference.ts";
|
|
6
|
+
import { type Definitions, reference, referenceRun } from "./reference.ts";
|
|
7
|
+
import { type ReplaySettle, recorder, replayer } from "./replay.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A tier that `agree` compares with the plan.
|
|
@@ -257,3 +258,110 @@ export function agree(machine: Machine, doc: unknown, context: Context = makeCon
|
|
|
257
258
|
}
|
|
258
259
|
return { ok: true, result, effects };
|
|
259
260
|
}
|
|
261
|
+
|
|
262
|
+
/** The options of `agreeRun`: the options of `agree` without the second tier of a language. */
|
|
263
|
+
export interface AgreeRunOptions extends Omit<AgreeOptions, "other" | "perform"> {
|
|
264
|
+
/**
|
|
265
|
+
* The host of the run of the plan (RUN.AGREES). A recorder wraps it, and each other tier replays the log of that run.
|
|
266
|
+
* Thus the host does each effect one time. Without it, an effect gives its payload.
|
|
267
|
+
*/
|
|
268
|
+
readonly perform?: Perform;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The function `perform` of one run, with the effects that it got and the waits of `all` that settled. */
|
|
272
|
+
interface Watched {
|
|
273
|
+
readonly perform: Perform;
|
|
274
|
+
readonly effects: Effect[];
|
|
275
|
+
readonly settles: ReplaySettle[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* This function wraps the function `perform` of a run. The wrapper keeps each effect and each wait of `all` that
|
|
280
|
+
* settles. It gives each effect to the inner function, and each call of its journal to the inner journal.
|
|
281
|
+
*/
|
|
282
|
+
function watched(perform: Perform): Watched {
|
|
283
|
+
const effects: Effect[] = [];
|
|
284
|
+
const settles: ReplaySettle[] = [];
|
|
285
|
+
const inner = (perform as { readonly [JOURNAL]?: Journal })[JOURNAL];
|
|
286
|
+
const settled = (all: string, item: number): void => {
|
|
287
|
+
settles.push({ all, item });
|
|
288
|
+
inner?.settled?.(all, item);
|
|
289
|
+
};
|
|
290
|
+
const journal: Journal = inner?.next === undefined ? { settled } : { settled, next: (all, waiting) => inner.next?.(all, waiting) };
|
|
291
|
+
const host = (effect: Effect): unknown => {
|
|
292
|
+
effects.push(effect);
|
|
293
|
+
return perform(effect);
|
|
294
|
+
};
|
|
295
|
+
return { perform: Object.assign(host, { [JOURNAL]: journal }), effects, settles };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function settleText(settle: ReplaySettle | undefined): string {
|
|
299
|
+
return settle === undefined ? "no item" : `the item ${settle.item} of ${settle.all}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** This function gives the first difference of two orders of the waits of `all`, or `undefined` when they agree. */
|
|
303
|
+
function settlesDiffer(a: readonly ReplaySettle[], b: readonly ReplaySettle[]): string | undefined {
|
|
304
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
305
|
+
const x = a[index];
|
|
306
|
+
const y = b[index];
|
|
307
|
+
if (x?.all !== y?.all || x?.item !== y?.item) return `The wait ${index} settles ${settleText(x)} in the plan, and ${settleText(y)} in the tier.`;
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* This function compares the run of a document in a plan with its run in other tiers (RUN.AGREES). First the plan
|
|
314
|
+
* starts the run with a recorder around the host. Then each tier replays the log of that run. These tiers are the
|
|
315
|
+
* reference run, the traced run, a second run of the plan and the plan with the other choice of folds.
|
|
316
|
+
*
|
|
317
|
+
* The rules of `agree` compare the results and the effects, with their addresses and outcomes. A tier must also
|
|
318
|
+
* settle the waits of each `all` in the order of the plan, thus a replay keeps the order of the waits.
|
|
319
|
+
*
|
|
320
|
+
* @param machine - The world, the evaluators and the runners.
|
|
321
|
+
* @param doc - The document.
|
|
322
|
+
* @param context - The names and the capabilities at the root.
|
|
323
|
+
* @param options - The tiers, the definitions for the reference, the rules of the comparison and the host.
|
|
324
|
+
* @returns The result of the plan, or the first tier that disagrees and the reason.
|
|
325
|
+
*/
|
|
326
|
+
export async function agreeRun(machine: Machine, doc: unknown, context: Context = makeContext(), options: AgreeRunOptions = {}): Promise<Agreement> {
|
|
327
|
+
const declared = {
|
|
328
|
+
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
329
|
+
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
330
|
+
};
|
|
331
|
+
const observe = options.observe;
|
|
332
|
+
observe?.();
|
|
333
|
+
// A fold moves host calls to the compilation, thus a plan whose calls the host compares has no folds (PLAN.FOLD).
|
|
334
|
+
const folds = observe === undefined;
|
|
335
|
+
const plan = prepare(machine, doc, { ...declared, fold: folds });
|
|
336
|
+
const recording = recorder(options.perform);
|
|
337
|
+
const first = watched(recording.perform);
|
|
338
|
+
const result = await plan.run(context, { perform: first.perform });
|
|
339
|
+
const saw = observe?.();
|
|
340
|
+
const log = recording.log();
|
|
341
|
+
|
|
342
|
+
for (const tier of options.tiers ?? DEFAULT_TIERS) {
|
|
343
|
+
const replay = watched(replayer(log));
|
|
344
|
+
const host = { perform: replay.perform };
|
|
345
|
+
let other: Result;
|
|
346
|
+
if (tier === "reference") {
|
|
347
|
+
other = await referenceRun(machine, doc, context, { ...declared, ...host, ...(options.definitions === undefined ? {} : { definitions: options.definitions }) });
|
|
348
|
+
} else if (tier === "traced") {
|
|
349
|
+
other = (await run(machine, doc, context, { ...declared, ...host })).result;
|
|
350
|
+
} else if (tier === "folds") {
|
|
351
|
+
other = await prepare(machine, doc, { ...declared, fold: !folds }).run(context, host);
|
|
352
|
+
} else {
|
|
353
|
+
other = await plan.run(context, host);
|
|
354
|
+
}
|
|
355
|
+
// The folds of the tier folds call the host when it compiles. Thus only its result, effects and waits compare.
|
|
356
|
+
const compareCalls = observe !== undefined && tier !== "folds";
|
|
357
|
+
const calls = observe?.();
|
|
358
|
+
if (overflowed(result) || overflowed(other)) continue;
|
|
359
|
+
const why =
|
|
360
|
+
resultDiffers(result, other, options) ??
|
|
361
|
+
effectsDiffer(first.effects, replay.effects, options) ??
|
|
362
|
+
settlesDiffer(first.settles, replay.settles) ??
|
|
363
|
+
(compareCalls && !sameValue(saw, calls, options.equal) ? "The host saw other calls in the tier." : undefined);
|
|
364
|
+
if (why !== undefined) return { ok: false, tier, plan: result, other, reason: why };
|
|
365
|
+
}
|
|
366
|
+
return { ok: true, result, effects: first.effects };
|
|
367
|
+
}
|
package/src/evaluate.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { answerOf, ask, type Answer } from "./ladder.ts";
|
|
|
4
4
|
import type { CapsRule, Kind, KindFacts, Side } from "./world.ts";
|
|
5
5
|
import type { Domain } from "./types.ts";
|
|
6
6
|
import type { TypeRule } from "./typing.ts";
|
|
7
|
+
import type { StaticRead } from "./facts.ts";
|
|
7
8
|
import { type ErrorCode, type EvalError, type Result, Failure, fail, ok, placed, thrown, describe } from "./errors.ts";
|
|
8
9
|
import {
|
|
9
10
|
type Cap,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
type Thing,
|
|
18
19
|
ROOTS,
|
|
19
20
|
context as makeRootContext,
|
|
21
|
+
derefStep,
|
|
20
22
|
isHostThing,
|
|
21
23
|
isRef,
|
|
22
24
|
resolve,
|
|
@@ -141,8 +143,15 @@ export function partCaps(rules: readonly CapsRule[] | undefined, path: readonly
|
|
|
141
143
|
*/
|
|
142
144
|
const GRANT = /* @__PURE__ */ Symbol("coeval.grant");
|
|
143
145
|
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The key of the epoch of a grant (CAP.NARROW-KEPT). The epoch is the list of capabilities that the host gave the root
|
|
148
|
+
* of the evaluation of the grant. A spread of a context keeps its epoch, as it keeps its grant. An
|
|
149
|
+
* effect and a resolution read a context of another epoch as a context without a grant.
|
|
150
|
+
*/
|
|
151
|
+
const EPOCH = /* @__PURE__ */ Symbol("coeval.epoch");
|
|
152
|
+
|
|
153
|
+
/** A context with the grant of the kernel and the epoch of that grant. */
|
|
154
|
+
type Granted = Context & { readonly [GRANT]?: readonly Cap[]; readonly [EPOCH]?: readonly Cap[] | undefined };
|
|
146
155
|
|
|
147
156
|
/**
|
|
148
157
|
* This function gives the capabilities that a context and its grant both permit, the meet `intersectCaps` (CAP.MEET).
|
|
@@ -166,13 +175,25 @@ function granted(ctx: Context): Context {
|
|
|
166
175
|
return out;
|
|
167
176
|
}
|
|
168
177
|
|
|
178
|
+
/**
|
|
179
|
+
* This function gives a context of another epoch the epoch of the evaluation, without capabilities (CAP.NARROW-KEPT).
|
|
180
|
+
* Thus the context reads as a context without a grant. The kernel cannot know the document of the context in this
|
|
181
|
+
* evaluation.
|
|
182
|
+
*/
|
|
183
|
+
function ungranted(ctx: Context, epoch: readonly Cap[]): Context {
|
|
184
|
+
const out: Granted = { ...ctx, caps: NO_CAPS, [GRANT]: NO_CAPS, [EPOCH]: epoch };
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
/**
|
|
170
189
|
* This function gives the context in which the runtime resolves a path of compiled code (CAP.CAPS-GRANT, CAP.MEET).
|
|
171
190
|
* The context has the meet of its list and its grant, and a context without a grant has none. Thus a read of the root
|
|
172
191
|
* `caps` and a walker of a step document get the capabilities of the document from a widened context.
|
|
173
192
|
* A context that the code narrowed keeps its own list. A context whose list does not change stays the same object.
|
|
193
|
+
* A context of another epoch has no capabilities (CAP.NARROW-KEPT).
|
|
174
194
|
*/
|
|
175
|
-
function readable(ctx: Context): Context {
|
|
195
|
+
function readable(ctx: Context, epoch: readonly Cap[]): Context {
|
|
196
|
+
if ((ctx as Granted)[EPOCH] !== epoch) return ctx.caps.length === 0 ? ctx : ungranted(ctx, epoch);
|
|
176
197
|
const caps = grantedCaps(ctx);
|
|
177
198
|
if (caps === ctx.caps || (caps.length === 0 && ctx.caps.length === 0)) return ctx;
|
|
178
199
|
const out: Granted = { ...ctx, caps, [GRANT]: caps };
|
|
@@ -181,12 +202,14 @@ function readable(ctx: Context): Context {
|
|
|
181
202
|
|
|
182
203
|
/**
|
|
183
204
|
* This function gives the context of a part of a function evaluator. It is the context that the evaluator gives,
|
|
184
|
-
* narrowed by the context of the document (CAP.NARROW), with the grant of its capabilities.
|
|
205
|
+
* narrowed by the context of the document (CAP.NARROW), with the grant of its capabilities. The grant has the epoch
|
|
206
|
+
* of the document, because the document permits each of its capabilities (CAP.NARROW-KEPT).
|
|
185
207
|
*/
|
|
186
208
|
function handed(parent: Context, next: Context): Context {
|
|
187
209
|
const ctx = narrow(parent, next);
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const epoch = (parent as Granted)[EPOCH];
|
|
211
|
+
if ((ctx as Granted)[GRANT] === ctx.caps && (ctx as Granted)[EPOCH] === epoch) return ctx;
|
|
212
|
+
const out: Granted = { ...ctx, [GRANT]: ctx.caps, [EPOCH]: epoch };
|
|
190
213
|
return out;
|
|
191
214
|
}
|
|
192
215
|
|
|
@@ -248,8 +271,8 @@ export interface Read {
|
|
|
248
271
|
*/
|
|
249
272
|
readonly free: boolean;
|
|
250
273
|
/**
|
|
251
|
-
* `true` when the kernel adds the read, because a value keeps
|
|
252
|
-
*
|
|
274
|
+
* `true` when the kernel adds the read, because a value keeps the context for later (READS.KEPT). Such a read has
|
|
275
|
+
* a whole root as its path, or a path that a closure can read (READS.CLOSURE).
|
|
253
276
|
*/
|
|
254
277
|
readonly kept?: true;
|
|
255
278
|
/** The hops of the resolution, only when the code asked for them. `verifyReads` then compares each hop too. */
|
|
@@ -438,9 +461,13 @@ export interface Runtime {
|
|
|
438
461
|
/**
|
|
439
462
|
* This optional function tells the runtime that a value keeps the context for later, for example a closure of
|
|
440
463
|
* `fn`. A later use of the value reads that context, and no evaluation records these reads. Thus the list of reads
|
|
441
|
-
* gets a read of each
|
|
464
|
+
* gets a kept read of each path that the value can read (READS.CLOSURE). Without the static reads, the list gets a
|
|
465
|
+
* read of each root of the context (READS.KEPT).
|
|
466
|
+
*
|
|
467
|
+
* @param context - The context that the value keeps.
|
|
468
|
+
* @param reads - This function gives the static reads of the value, or `undefined` when they are not known.
|
|
442
469
|
*/
|
|
443
|
-
keepsContext?(): void;
|
|
470
|
+
keepsContext?(context?: Context, reads?: () => readonly StaticRead[] | undefined): void;
|
|
444
471
|
}
|
|
445
472
|
|
|
446
473
|
/**
|
|
@@ -873,6 +900,32 @@ interface Reading {
|
|
|
873
900
|
kept: boolean;
|
|
874
901
|
/** `true` after the kernel added the read of the root `self`. */
|
|
875
902
|
self: boolean;
|
|
903
|
+
/** The paths of the kept reads of closures, as JSON texts (READS.CLOSURE). The set is made at the first such read. */
|
|
904
|
+
paths?: Set<string>;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* This function gives the path of a kept read (READS.CLOSURE). The evaluation did not ask for the read. Thus the read
|
|
909
|
+
* does not step into a host thing, and the path ends at the first host thing. The host gives a new thing for a new state.
|
|
910
|
+
* A reference in the data reads the context of its reader, thus the function then gives `undefined`.
|
|
911
|
+
*
|
|
912
|
+
* @param nav - The world and the code of the evaluators.
|
|
913
|
+
* @param root - The context at the root of the evaluation.
|
|
914
|
+
* @param path - The root and the keys of the static read.
|
|
915
|
+
* @returns The path of the kept read, or `undefined` for a reference on the way.
|
|
916
|
+
*/
|
|
917
|
+
function keptPath(nav: Navigator, root: Context, path: readonly unknown[]): readonly unknown[] | undefined {
|
|
918
|
+
const adopt = nav.adopt ?? ((value: unknown): Thing => value);
|
|
919
|
+
let here = adopt(root[path[0] as Root]);
|
|
920
|
+
for (let index = 1; ; index += 1) {
|
|
921
|
+
if (isHostThing(here)) return index === path.length ? path : path.slice(0, index);
|
|
922
|
+
if (nav.plain !== true && isRef(here)) return undefined;
|
|
923
|
+
if (index >= path.length) return path;
|
|
924
|
+
const reached = derefStep(nav, here, path[index] as Key);
|
|
925
|
+
// A step that reaches nothing fails, and the resolution of the path fails at the same step.
|
|
926
|
+
if (reached === undefined) return path;
|
|
927
|
+
here = adopt(reached.thing);
|
|
928
|
+
}
|
|
876
929
|
}
|
|
877
930
|
|
|
878
931
|
/** This function makes the record of the reads of an evaluation, when the host gives a list. */
|
|
@@ -896,6 +949,11 @@ class Session implements Runtime {
|
|
|
896
949
|
|
|
897
950
|
constructor(
|
|
898
951
|
readonly machine: Machine,
|
|
952
|
+
/**
|
|
953
|
+
* The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
|
|
954
|
+
* keeps its grant only in an evaluation of the same epoch.
|
|
955
|
+
*/
|
|
956
|
+
readonly epoch: readonly Cap[],
|
|
899
957
|
readonly running: boolean,
|
|
900
958
|
readonly traced: boolean,
|
|
901
959
|
readonly perform: Perform | undefined,
|
|
@@ -924,7 +982,7 @@ class Session implements Runtime {
|
|
|
924
982
|
* `caps` and a walker of a step document get the capabilities of the document.
|
|
925
983
|
*/
|
|
926
984
|
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
927
|
-
return this.read(readable(ctx), path, undefined, options);
|
|
985
|
+
return this.read(readable(ctx, this.epoch), path, undefined, options);
|
|
928
986
|
}
|
|
929
987
|
|
|
930
988
|
/**
|
|
@@ -933,7 +991,7 @@ class Session implements Runtime {
|
|
|
933
991
|
* that the grant permits (CAP.CAPS-GRANT, CAP.MEET).
|
|
934
992
|
*/
|
|
935
993
|
resolvePath(given: Context, compiled: CompiledPath): Resolution {
|
|
936
|
-
const ctx = readable(given);
|
|
994
|
+
const ctx = readable(given, this.epoch);
|
|
937
995
|
if (this.reading === undefined && !this.traced && compiled.nav === this.machine) return compiled.resolve(ctx);
|
|
938
996
|
return this.read(ctx, compiled.path, compiled, undefined);
|
|
939
997
|
}
|
|
@@ -969,13 +1027,45 @@ class Session implements Runtime {
|
|
|
969
1027
|
return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
970
1028
|
}
|
|
971
1029
|
|
|
972
|
-
keepsContext(): void {
|
|
1030
|
+
keepsContext(ctx?: Context, reads?: () => readonly StaticRead[] | undefined): void {
|
|
973
1031
|
const reading = this.reading;
|
|
974
1032
|
if (reading === undefined || reading.kept) return;
|
|
1033
|
+
const known = ctx === undefined ? undefined : reads?.();
|
|
1034
|
+
// A value without its static reads can read each root later (READS.KEPT).
|
|
1035
|
+
if (ctx !== undefined && known !== undefined && known.every((read) => this.keepRead(reading, ctx, read))) return;
|
|
975
1036
|
reading.kept = true;
|
|
976
1037
|
this.keep(reading, reading.self ? ROOTS.filter((root) => root !== "self") : ROOTS);
|
|
977
1038
|
}
|
|
978
1039
|
|
|
1040
|
+
/**
|
|
1041
|
+
* This function adds the kept read of one static read of a closure (READS.CLOSURE). The read resolves from the
|
|
1042
|
+
* context at the root, one time in an evaluation. A name that a kind binds in the kept context gives no read,
|
|
1043
|
+
* because the reads of its binding are already in the list.
|
|
1044
|
+
*
|
|
1045
|
+
* @returns `false` when the path meets a reference in the data. Then the closure keeps each whole root.
|
|
1046
|
+
*/
|
|
1047
|
+
private keepRead(reading: Reading, ctx: Context, read: StaticRead): boolean {
|
|
1048
|
+
const root = read.root;
|
|
1049
|
+
const exact: readonly unknown[] = [root, ...read.keys];
|
|
1050
|
+
if (!isFree(reading.root, ctx, exact)) {
|
|
1051
|
+
// A kind that sets another self can give the old self on, as for a direct read (READS.KEPT).
|
|
1052
|
+
if (root === "self" && !reading.self) this.keep(reading, ["self"]);
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
// A read of the whole root self already holds each path of self.
|
|
1056
|
+
if (root === "self" && reading.self) return true;
|
|
1057
|
+
// A step document can walk outside the keys of its path, thus such a read keeps the whole root.
|
|
1058
|
+
const path = keptPath(this.machine, reading.root, read.exact ? exact : [root]);
|
|
1059
|
+
if (path === undefined) return false;
|
|
1060
|
+
const paths = (reading.paths ??= new Set());
|
|
1061
|
+
const text = JSON.stringify(path);
|
|
1062
|
+
if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
|
|
1063
|
+
paths.add(text);
|
|
1064
|
+
if (path.length === 1 && root === "self") reading.self = true;
|
|
1065
|
+
reading.list.push({ path, reached: readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
979
1069
|
/**
|
|
980
1070
|
* This function adds a read of each whole root to the list, from the context at the root (READS.KEPT). An exception
|
|
981
1071
|
* of the host gives the error value of the read, because the evaluation did not ask for the read.
|
|
@@ -995,10 +1085,11 @@ class Session implements Runtime {
|
|
|
995
1085
|
/**
|
|
996
1086
|
* This function checks the capability of an effect, and records the effect with its address (RUN.ADDRESS). The
|
|
997
1087
|
* context keeps only the capabilities of its grant, thus compiled code cannot widen the context of its own effect
|
|
998
|
-
* (CAP.NARROW-CODE).
|
|
1088
|
+
* (CAP.NARROW-CODE). A context of another epoch has no capabilities, thus code cannot keep a context for a later
|
|
1089
|
+
* evaluation with fewer capabilities (CAP.NARROW-KEPT).
|
|
999
1090
|
*/
|
|
1000
1091
|
private record(given: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result | Recorded {
|
|
1001
|
-
const ctx = granted(given);
|
|
1092
|
+
const ctx = (given as Granted)[EPOCH] === this.epoch ? granted(given) : ungranted(given, this.epoch);
|
|
1002
1093
|
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
1003
1094
|
const text = keys.join(".");
|
|
1004
1095
|
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
@@ -1921,15 +2012,16 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
1921
2012
|
/**
|
|
1922
2013
|
* This function gives the context at the root of an evaluation. It is the context of the host, with the world of
|
|
1923
2014
|
* the machine when the context has none. The host gives the capabilities, thus the root has the grant of each one
|
|
1924
|
-
* (CAP.NARROW-CODE).
|
|
2015
|
+
* (CAP.NARROW-CODE). The list of the host is also the epoch of the grant (CAP.NARROW-KEPT). An evaluation and
|
|
2016
|
+
* `verifyReads` share it.
|
|
1925
2017
|
*
|
|
1926
2018
|
* @param machine - The machine.
|
|
1927
2019
|
* @param ctx - The context of the host.
|
|
1928
2020
|
* @returns The context at the root.
|
|
1929
2021
|
*/
|
|
1930
2022
|
export function rootContext(machine: Machine, ctx: Context): Context {
|
|
1931
|
-
if (ctx.world !== null && (ctx as Granted)[GRANT] === ctx.caps) return ctx;
|
|
1932
|
-
const root: Granted = { ...ctx, world: ctx.world === null ? worldThing(machine.world) : ctx.world, [GRANT]: ctx.caps };
|
|
2023
|
+
if (ctx.world !== null && (ctx as Granted)[GRANT] === ctx.caps && (ctx as Granted)[EPOCH] === ctx.caps) return ctx;
|
|
2024
|
+
const root: Granted = { ...ctx, world: ctx.world === null ? worldThing(machine.world) : ctx.world, [GRANT]: ctx.caps, [EPOCH]: ctx.caps };
|
|
1933
2025
|
return root;
|
|
1934
2026
|
}
|
|
1935
2027
|
|
|
@@ -2000,14 +2092,14 @@ class CompiledPlan implements Plan {
|
|
|
2000
2092
|
evaluate(context: Context = makeContext(), evalOptions?: EvalOptions): Result {
|
|
2001
2093
|
const machine = this.machine;
|
|
2002
2094
|
const ctx = rootContext(machine, context);
|
|
2003
|
-
const s = new Session(machine, false, this.traced, evalOptions?.perform, undefined, evalOptions?.effects, readingOf(evalOptions?.reads, ctx));
|
|
2095
|
+
const s = new Session(machine, ctx.caps, false, this.traced, evalOptions?.perform, undefined, evalOptions?.effects, readingOf(evalOptions?.reads, ctx));
|
|
2004
2096
|
return this.code(ctx, s);
|
|
2005
2097
|
}
|
|
2006
2098
|
|
|
2007
2099
|
run(context: Context = makeContext(), runOptions: PlanRunOptions = {}): Promise<Result> {
|
|
2008
2100
|
const machine = this.machine;
|
|
2009
2101
|
const ctx = rootContext(machine, context);
|
|
2010
|
-
const s = new Session(machine, true, this.traced, runOptions.perform, runOptions.signal, undefined, readingOf(runOptions.reads, ctx));
|
|
2102
|
+
const s = new Session(machine, ctx.caps, true, this.traced, runOptions.perform, runOptions.signal, undefined, readingOf(runOptions.reads, ctx));
|
|
2011
2103
|
return driveWaiting(this.runCode()(ctx, s), runOptions.signal);
|
|
2012
2104
|
}
|
|
2013
2105
|
}
|
|
@@ -2075,7 +2167,7 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
|
|
|
2075
2167
|
...(given.verb === undefined ? {} : { verb: given.verb }),
|
|
2076
2168
|
});
|
|
2077
2169
|
const ctx = rootContext(machine, context);
|
|
2078
|
-
const s = new Session(machine, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
2170
|
+
const s = new Session(machine, ctx.caps, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
2079
2171
|
const result = (plan as CompiledPlan).code(ctx, s);
|
|
2080
2172
|
return record(s, result);
|
|
2081
2173
|
}
|
|
@@ -2098,7 +2190,7 @@ export async function run(machine: Machine, doc: unknown, context: Context = mak
|
|
|
2098
2190
|
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
2099
2191
|
});
|
|
2100
2192
|
const ctx = rootContext(machine, context);
|
|
2101
|
-
const s = new Session(machine, true, true, options.perform, options.signal, undefined, readingOf(options.reads, ctx));
|
|
2193
|
+
const s = new Session(machine, ctx.caps, true, true, options.perform, options.signal, undefined, readingOf(options.reads, ctx));
|
|
2102
2194
|
const result = await driveWaiting((plan as CompiledPlan).runCode()(ctx, s), options.signal);
|
|
2103
2195
|
return record(s, result);
|
|
2104
2196
|
}
|
package/src/formula.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type MachinePart, mergeMachines } from "./merge.ts";
|
|
|
7
7
|
import { type Domain, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeText } from "./types.ts";
|
|
8
8
|
import type { TypeInput, TypeRule } from "./typing.ts";
|
|
9
9
|
import { decodeDomain, domainTypeOf } from "./codec.ts";
|
|
10
|
+
import { type StaticRead, factsOf } from "./facts.ts";
|
|
10
11
|
|
|
11
12
|
/** An operation of a formula language: a function of the values of its arguments. */
|
|
12
13
|
export type OpFn = (...args: never[]) => unknown;
|
|
@@ -777,7 +778,7 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
777
778
|
};
|
|
778
779
|
}),
|
|
779
780
|
|
|
780
|
-
"formula.fn": compiled(({ value, data }) => {
|
|
781
|
+
"formula.fn": compiled(({ value, data, doc, machine }) => {
|
|
781
782
|
const params = isRecord(data) ? data["params"] : undefined;
|
|
782
783
|
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
|
|
783
784
|
const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
@@ -785,9 +786,22 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
785
786
|
}
|
|
786
787
|
const names = params as readonly string[];
|
|
787
788
|
const body = partsOf(value)["body"] as CompiledPart;
|
|
789
|
+
// The static reads of the function come at the first closure of an evaluation with a list of reads.
|
|
790
|
+
let known: readonly StaticRead[] | undefined | null = null;
|
|
791
|
+
const reads = (): readonly StaticRead[] | undefined => {
|
|
792
|
+
if (known !== null) return known;
|
|
793
|
+
// Only a pure document reads its context through the runtime alone (FACTS.PURE). Each other closure keeps each root.
|
|
794
|
+
try {
|
|
795
|
+
const facts = factsOf(machine, doc);
|
|
796
|
+
known = facts.pure ? facts.reads : undefined;
|
|
797
|
+
} catch {
|
|
798
|
+
known = undefined;
|
|
799
|
+
}
|
|
800
|
+
return known;
|
|
801
|
+
};
|
|
788
802
|
return (ctx, rt) => {
|
|
789
|
-
// A closure keeps the context for each later use, thus the list of reads gets each
|
|
790
|
-
rt.keepsContext?.();
|
|
803
|
+
// A closure keeps the context for each later use, thus the list of reads gets each path that it can read (READS.CLOSURE).
|
|
804
|
+
rt.keepsContext?.(ctx, reads);
|
|
791
805
|
const closure = (...args: readonly unknown[]): unknown => {
|
|
792
806
|
const scope: Record<string, unknown> = { ...ctx.names };
|
|
793
807
|
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
package/src/index.ts
CHANGED
|
@@ -250,8 +250,8 @@ export {
|
|
|
250
250
|
print,
|
|
251
251
|
syntaxFormat,
|
|
252
252
|
} from "./syntax.ts";
|
|
253
|
-
export { type Definitions, type ReferenceOptions, STD_DEFINITIONS, formulaDefinitions, reference } from "./reference.ts";
|
|
254
|
-
export { type AgreeOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, sameValue } from "./agree.ts";
|
|
253
|
+
export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
|
|
254
|
+
export { type AgreeOptions, type AgreeRunOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
|
|
255
255
|
export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
|
|
256
256
|
export { sameResult } from "./same.ts";
|
|
257
257
|
export { type ReadsCheck, type VerifyOptions, verifyReads } from "./reads.ts";
|