@mark1russell7/coeval 0.22.0 → 0.24.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 +24 -5
- package/build/index.d.ts +442 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5848 -3680
- package/build/index.js.map +1 -1
- package/package.json +4 -4
- package/src/codec-laws.ts +152 -0
- package/src/codec.ts +2 -2
- package/src/conform.ts +663 -71
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/effects.ts +1 -1
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +188 -24
- package/src/formula.ts +60 -17
- package/src/index.ts +15 -0
- package/src/instances.ts +16 -1
- package/src/meaning.ts +43 -10
- package/src/parts.ts +7 -4
- package/src/providers.ts +4 -1
- package/src/reference.ts +122 -2
- package/src/schedule.ts +286 -45
- package/src/static.ts +59 -16
- package/src/stdlib.ts +188 -6
- package/src/syntax.ts +637 -48
- package/src/types.ts +2 -1
- package/src/world.ts +31 -4
package/src/deref.ts
CHANGED
|
@@ -58,6 +58,15 @@ export interface HostThing {
|
|
|
58
58
|
* @returns What the step reaches, or `undefined` when the host does not know it yet. Then the kept read ends at the thing.
|
|
59
59
|
*/
|
|
60
60
|
known?(key: Key): { readonly thing: Thing } | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* This optional function holds a kept read (SCHED.OWN-NODE). The evaluation did not ask for that read. Thus a host
|
|
63
|
+
* that follows the reads of an evaluation can tell the two apart. The kernel calls this function of the root `world`
|
|
64
|
+
* around each kept read that resolves its path.
|
|
65
|
+
*
|
|
66
|
+
* @param read - This function does the read.
|
|
67
|
+
* @returns What `read` gives.
|
|
68
|
+
*/
|
|
69
|
+
keeping?<T>(read: () => T): T;
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/** Something that a path can reach: any value of the host, or a host thing. */
|
|
@@ -522,7 +531,8 @@ function walkQuick(start: unknown, path: readonly unknown[], plain: boolean, key
|
|
|
522
531
|
/**
|
|
523
532
|
* A compiled path: one static path for one navigator (PLAN.PATH). `compilePath` reads the root, the open roots and
|
|
524
533
|
* the steps one time. It asks the ladder of the verb `step` for each step document one time. Each resolution then
|
|
525
|
-
* examines only the values of the context.
|
|
534
|
+
* examines only the values of the context. Code of a language reads a compiled path with `resolveCompiled`, thus the
|
|
535
|
+
* list of reads gets the read (PLAN.PATH-READS).
|
|
526
536
|
*/
|
|
527
537
|
export interface CompiledPath {
|
|
528
538
|
/** The steps of the path, as the document gives them. The list of reads records these steps. */
|
|
@@ -530,7 +540,8 @@ export interface CompiledPath {
|
|
|
530
540
|
/** The navigator of the compilation. A runtime with another navigator resolves the steps again. */
|
|
531
541
|
readonly nav: Navigator;
|
|
532
542
|
/**
|
|
533
|
-
* This function resolves the path from a context, without a record of hops.
|
|
543
|
+
* This function resolves the path from a context, without a record of hops. It records no read and it does not
|
|
544
|
+
* apply the grant of the evaluation, as `resolve` does not. Use it only outside an evaluation.
|
|
534
545
|
*
|
|
535
546
|
* @param ctx - The context.
|
|
536
547
|
* @returns The same resolution as `resolve(nav, ctx, path, { hops: false })`.
|
|
@@ -592,8 +603,11 @@ class StaticPath implements CompiledPath {
|
|
|
592
603
|
* - The walker of each step document comes from the ladder one time, here. The navigator and the step document do
|
|
593
604
|
* not change, thus the answer does not change either.
|
|
594
605
|
*
|
|
606
|
+
* An evaluator of a language calls this function in its `compile`, with the field `machine` of the input, and reads the
|
|
607
|
+
* result with `resolveCompiled` (PLAN.PATH-EXPORT).
|
|
608
|
+
*
|
|
595
609
|
* @param nav - The navigator. A plan compiles its paths for its machine.
|
|
596
|
-
* @param path - The steps. The first step is a root.
|
|
610
|
+
* @param path - The steps. The first step is a root. The compiled path keeps this list, thus the host must not change it.
|
|
597
611
|
* @returns The compiled path.
|
|
598
612
|
*/
|
|
599
613
|
export function compilePath(nav: Navigator, path: readonly unknown[]): CompiledPath {
|
package/src/durable.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Context } from "./deref.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type Result, overflowCount } from "./errors.ts";
|
|
3
3
|
import { type Machine, type Plan, type PrepareOptions, type Read, prepare } from "./evaluate.ts";
|
|
4
4
|
import { type DocFacts, type FactsOptions, factsOf } from "./facts.ts";
|
|
5
5
|
import { type PrintOptions, type ReadPrints, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
|
|
@@ -126,20 +126,24 @@ const NO_READS: readonly Read[] = /* @__PURE__ */ Object.freeze([]);
|
|
|
126
126
|
/** The key of the document `-0` in a cache of value documents. A `Map` finds the entry of `0` for `-0`. */
|
|
127
127
|
const NEGATIVE_ZERO = /* @__PURE__ */ Symbol("coeval.negative-zero");
|
|
128
128
|
|
|
129
|
-
/**
|
|
130
|
-
function
|
|
129
|
+
/**
|
|
130
|
+
* This function makes a cache by document. A document object gives its value one time, as PLAN.CACHE says. A value
|
|
131
|
+
* that `lasts` refuses stays out of the cache, and the next call makes it again.
|
|
132
|
+
*/
|
|
133
|
+
function byDocument<T>(make: (doc: unknown) => T, lasts?: () => boolean): (doc: unknown) => T {
|
|
131
134
|
const objects = new WeakMap<object, T>();
|
|
132
135
|
const values = new Map<unknown, T>();
|
|
133
136
|
return (doc) => {
|
|
134
137
|
if (typeof doc === "object" && doc !== null) {
|
|
135
138
|
if (objects.has(doc)) return objects.get(doc) as T;
|
|
136
139
|
const made = make(doc);
|
|
137
|
-
objects.set(doc, made);
|
|
140
|
+
if (lasts === undefined || lasts()) objects.set(doc, made);
|
|
138
141
|
return made;
|
|
139
142
|
}
|
|
140
143
|
const key = Object.is(doc, -0) ? NEGATIVE_ZERO : doc;
|
|
141
144
|
if (values.has(key)) return values.get(key) as T;
|
|
142
145
|
const made = make(doc);
|
|
146
|
+
if (lasts !== undefined && !lasts()) return made;
|
|
143
147
|
if (values.size >= MAX_VALUES) values.clear();
|
|
144
148
|
values.set(key, made);
|
|
145
149
|
return made;
|
|
@@ -180,11 +184,19 @@ export function durable(machine: Machine, options: DurableOptions = {}): Durable
|
|
|
180
184
|
const results = new Map<unknown, Entry>();
|
|
181
185
|
let current = machine;
|
|
182
186
|
let epochs: Epochs = { structure: 0, data: 0 };
|
|
183
|
-
const plansOf = (m: Machine): ((doc: unknown) => Plan) =>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
const plansOf = (m: Machine): ((doc: unknown) => Plan) => {
|
|
188
|
+
let before = 0;
|
|
189
|
+
// A compilation near the limit of the stack gives a plan that fails. That failure depends on the depth of the
|
|
190
|
+
// demand, not on the document, thus such a plan stays out of the cache (SCHED.DEEP-CYCLE-CHANGE).
|
|
191
|
+
return byDocument(
|
|
192
|
+
(doc) => {
|
|
193
|
+
counts.compiles += 1;
|
|
194
|
+
before = overflowCount();
|
|
195
|
+
return prepare(m, doc, prepareOptions);
|
|
196
|
+
},
|
|
197
|
+
() => overflowCount() === before,
|
|
198
|
+
);
|
|
199
|
+
};
|
|
188
200
|
const factsFor = (m: Machine): ((doc: unknown) => DocFacts) =>
|
|
189
201
|
byDocument((doc) => {
|
|
190
202
|
counts.facts += 1;
|
package/src/effects.ts
CHANGED
|
@@ -46,7 +46,7 @@ function reaching(machine: Machine, doc: unknown, options: EffectsOptions): read
|
|
|
46
46
|
doc,
|
|
47
47
|
(v) => {
|
|
48
48
|
const caps = v.caps ?? EACH;
|
|
49
|
-
for (const kind of declaredEffects(v, "eval", machine)) if (rule(caps, kind)) out.push({ kind, visit: v });
|
|
49
|
+
for (const kind of declaredEffects(v, "eval", machine)) if (rule(caps, kind) && (v.handled === undefined || !permits(v.handled, kind))) out.push({ kind, visit: v });
|
|
50
50
|
return undefined;
|
|
51
51
|
},
|
|
52
52
|
{ caps: options.caps ?? EACH, ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }) },
|
package/src/errors.ts
CHANGED
|
@@ -116,6 +116,19 @@ export function isStackOverflow(error: unknown): boolean {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/** The count of the stack overflows that `thrown` changed into `#DEPTH!`. */
|
|
120
|
+
let overflows = 0;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* This function gives the count of the stack overflows that `thrown` changed into `#DEPTH!`. A schedule compares two
|
|
124
|
+
* counts. Thus it finds an overflow also when a document catches the error (SCHED.DEEP-CYCLE).
|
|
125
|
+
*
|
|
126
|
+
* @returns The count since the start of the process.
|
|
127
|
+
*/
|
|
128
|
+
export function overflowCount(): number {
|
|
129
|
+
return overflows;
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
/**
|
|
120
133
|
* This function changes an exception into an error value. A `Failure` gives its error value. A stack overflow
|
|
121
134
|
* gives `#DEPTH!`. Each other exception gives `#CALC!`, with the exception as the cause.
|
|
@@ -126,7 +139,10 @@ export function isStackOverflow(error: unknown): boolean {
|
|
|
126
139
|
*/
|
|
127
140
|
export function thrown(error: unknown, who: string): Failed {
|
|
128
141
|
if (error instanceof Failure) return { ok: false, error: error.error };
|
|
129
|
-
if (isStackOverflow(error))
|
|
142
|
+
if (isStackOverflow(error)) {
|
|
143
|
+
overflows += 1;
|
|
144
|
+
return fail("#DEPTH!", `The nesting of ${who} is too deep for the stack of the host.`, { cause: error });
|
|
145
|
+
}
|
|
130
146
|
return fail("#CALC!", `${upper(who)} threw: ${describe(error)}`, { cause: error });
|
|
131
147
|
}
|
|
132
148
|
|
package/src/evaluate.ts
CHANGED
|
@@ -109,7 +109,7 @@ export function intersectCaps(a: readonly Cap[], b: readonly Cap[]): readonly Ca
|
|
|
109
109
|
/** This function gives the capability rules of a kind that cover a part and change its capabilities. */
|
|
110
110
|
function rulesFor(rules: readonly CapsRule[] | undefined, path: readonly Key[]): readonly CapsRule[] {
|
|
111
111
|
if (rules === undefined || rules.length === 0) return NO_RULES;
|
|
112
|
-
const out = rules.filter((rule) => rule.rule !== "same" && (rule.in === undefined || rule.in.some((pattern) => pathMatches(path, pattern))));
|
|
112
|
+
const out = rules.filter((rule) => rule.rule !== "same" && rule.rule !== "grant" && (rule.in === undefined || rule.in.some((pattern) => pathMatches(path, pattern))));
|
|
113
113
|
return out.length === 0 ? NO_RULES : out;
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -136,6 +136,87 @@ export function partCaps(rules: readonly CapsRule[] | undefined, path: readonly
|
|
|
136
136
|
return capsUnder(rulesFor(rules, path), caps);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/** This function collects the values at a path in a value. The key `"*"` steps into each item of a list. */
|
|
140
|
+
function collectAt(value: unknown, path: readonly Key[], index: number, out: Cap[]): void {
|
|
141
|
+
if (index === path.length) {
|
|
142
|
+
if (typeof value === "string" || (Array.isArray(value) && value.every((key) => typeof key === "string" || typeof key === "number"))) out.push(value as Cap);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const key = path[index] as Key;
|
|
146
|
+
if (key === "*") {
|
|
147
|
+
if (Array.isArray(value)) for (const item of value) collectAt(item, path, index + 1, out);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (typeof value === "object" && value !== null) collectAt((value as Record<Key, unknown>)[key], path, index + 1, out);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* This function gives the capabilities that the rules `grant` of a kind give one part (CAP.GRANT). The check and the
|
|
155
|
+
* walk read them from the document. The kernel gets the same list from the clauses of the runner.
|
|
156
|
+
*
|
|
157
|
+
* @param rules - The capability rules of the kind (`Kind.caps`).
|
|
158
|
+
* @param path - The path of the part in the value of the document.
|
|
159
|
+
* @param value - The value of the document.
|
|
160
|
+
* @returns The capabilities that the rules add, or an empty list.
|
|
161
|
+
*/
|
|
162
|
+
export function partGrants(rules: readonly CapsRule[] | undefined, path: readonly Key[], value: unknown): readonly Cap[] {
|
|
163
|
+
if (rules === undefined) return NO_CAPS;
|
|
164
|
+
let out: Cap[] | undefined;
|
|
165
|
+
for (const rule of rules) {
|
|
166
|
+
if (rule.rule !== "grant" || (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(path, pattern)))) continue;
|
|
167
|
+
collectAt(value, rule.from, 0, (out ??= []));
|
|
168
|
+
}
|
|
169
|
+
return out ?? NO_CAPS;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* One clause of a handler (D146): the effects under `op` go to `answer` and not to the host. The clause runs in place,
|
|
174
|
+
* as a part of the effect that it answers. Its result is the result of that effect.
|
|
175
|
+
*/
|
|
176
|
+
export interface Clause {
|
|
177
|
+
/** The kind of the effects: the clause answers this kind and each kind under it (CAP.KEYS). */
|
|
178
|
+
readonly op: Cap;
|
|
179
|
+
/**
|
|
180
|
+
* This function answers one effect. It sees only the payload (HANDLE.SEALED).
|
|
181
|
+
*
|
|
182
|
+
* @param payload - The data of the effect.
|
|
183
|
+
* @returns The steps that give the result of the effect.
|
|
184
|
+
*/
|
|
185
|
+
answer(payload: unknown): Steps;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The clauses of one active `handle`, and the frame around it. */
|
|
189
|
+
interface Frame {
|
|
190
|
+
readonly clauses: readonly Clause[];
|
|
191
|
+
readonly outer: Frame | undefined;
|
|
192
|
+
/** The runtime of the evaluation that started the frame. A frame of another evaluation has ended. */
|
|
193
|
+
readonly owner: Runtime;
|
|
194
|
+
/** `true` after the body ends. A closure can leave the body with the context of the frame (HANDLE.ENDED). */
|
|
195
|
+
ended: boolean;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The key of the frame of a context. Like the grant, it stays through a spread of a context, and only the kernel writes
|
|
200
|
+
* it. It is not a root (D10): a document cannot read it (HANDLE.SEALED).
|
|
201
|
+
*/
|
|
202
|
+
const FRAME = /* @__PURE__ */ Symbol("coeval.frame");
|
|
203
|
+
|
|
204
|
+
/** A context that can have a frame. */
|
|
205
|
+
type Framed = Context & { readonly [FRAME]?: Frame };
|
|
206
|
+
|
|
207
|
+
/** This function gives the frame of a context, when it has one. */
|
|
208
|
+
function frameOf(ctx: Context): Frame | undefined {
|
|
209
|
+
return (ctx as Framed)[FRAME];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** This function finds the frame and the clause that answer an effect. The innermost frame wins, then the first clause. */
|
|
213
|
+
function clauseFor(start: Frame, keys: readonly Key[]): { readonly frame: Frame; readonly clause: Clause } | undefined {
|
|
214
|
+
for (let frame: Frame | undefined = start; frame !== undefined; frame = frame.outer) {
|
|
215
|
+
for (const clause of frame.clauses) if (permits([clause.op], keys)) return { frame, clause };
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
139
220
|
/**
|
|
140
221
|
* The key of the grant of a context (CAP.NARROW-CODE): the capabilities that the kernel gave the document of the
|
|
141
222
|
* context. A spread of a context keeps its grant. Thus compiled code passes the grant on in each context that it makes
|
|
@@ -669,6 +750,14 @@ export interface RunPart {
|
|
|
669
750
|
* @returns The steps of the evaluation of the part.
|
|
670
751
|
*/
|
|
671
752
|
evaluate(context?: Context): Steps;
|
|
753
|
+
/**
|
|
754
|
+
* This function evaluates the part under clauses that answer its effects (D146). The kernel sets it on each part of
|
|
755
|
+
* a kind with a rule `grant`. A part that a runner makes itself has none, and a compiled runner gets no such part.
|
|
756
|
+
*
|
|
757
|
+
* @param clauses - The clauses, in order. An effect goes to the first clause that names its kind.
|
|
758
|
+
* @returns The steps of the evaluation of the part.
|
|
759
|
+
*/
|
|
760
|
+
handled?(clauses: readonly Clause[]): Steps;
|
|
672
761
|
}
|
|
673
762
|
|
|
674
763
|
/** The input of a runner: the input of an evaluator, with parts and effects that can wait. */
|
|
@@ -1004,9 +1093,24 @@ function keptPath(nav: Navigator, root: Context, path: readonly unknown[]): Kept
|
|
|
1004
1093
|
}
|
|
1005
1094
|
}
|
|
1006
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* This function resolves the path of a kept read. A reference on the path can reach a node of a schedule, thus the
|
|
1098
|
+
* resolution goes through `keeping` of the root `world` (SCHED.OWN-NODE).
|
|
1099
|
+
*
|
|
1100
|
+
* @param nav - The world and the code of the evaluators.
|
|
1101
|
+
* @param ctx - The context.
|
|
1102
|
+
* @param path - The path of the kept read.
|
|
1103
|
+
* @returns What the path reaches.
|
|
1104
|
+
*/
|
|
1105
|
+
function readKept(nav: Navigator, ctx: Context, path: readonly unknown[]): Result {
|
|
1106
|
+
const read = (): Result => readAgain(nav, ctx, path, false).reached;
|
|
1107
|
+
const world = ctx.world;
|
|
1108
|
+
return isHostThing(world) && world.keeping !== undefined ? world.keeping(read) : read();
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1007
1111
|
/**
|
|
1008
1112
|
* This function resolves the path of a kept read again, for `verifyReads` (SCHED.CLOSURE). It walks the path as
|
|
1009
|
-
* `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `
|
|
1113
|
+
* `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readKept`.
|
|
1010
1114
|
*
|
|
1011
1115
|
* @param nav - The world and the code of the evaluators.
|
|
1012
1116
|
* @param ctx - The new context.
|
|
@@ -1018,7 +1122,7 @@ export function keptAgain(nav: Navigator, ctx: Context, path: readonly unknown[]
|
|
|
1018
1122
|
let here = adopt(ctx[path[0] as Root]);
|
|
1019
1123
|
let known = false;
|
|
1020
1124
|
// Before a step of `known`, each other answer of the walk is the answer of `readAgain`, as before SCHED.CLOSURE.
|
|
1021
|
-
const other = (): Result | undefined => (known ? undefined :
|
|
1125
|
+
const other = (): Result | undefined => (known ? undefined : readKept(nav, ctx, path));
|
|
1022
1126
|
for (let index = 1; index < path.length; index += 1) {
|
|
1023
1127
|
if (isHostThing(here)) {
|
|
1024
1128
|
if (here.known === undefined) return other();
|
|
@@ -1049,6 +1153,22 @@ function readingOf(list: Read[] | undefined, root: Context): Reading | undefined
|
|
|
1049
1153
|
|
|
1050
1154
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
1051
1155
|
class Session implements Runtime {
|
|
1156
|
+
readonly machine: Machine;
|
|
1157
|
+
/**
|
|
1158
|
+
* The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
|
|
1159
|
+
* keeps its grant only in an evaluation of the same epoch.
|
|
1160
|
+
*/
|
|
1161
|
+
readonly epoch: readonly Cap[];
|
|
1162
|
+
readonly running: boolean;
|
|
1163
|
+
readonly traced: boolean;
|
|
1164
|
+
readonly perform: Perform | undefined;
|
|
1165
|
+
readonly signal: StopSignal | undefined;
|
|
1166
|
+
/**
|
|
1167
|
+
* The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
|
|
1168
|
+
* shares the record of the evaluation around it (READS.EQUATION).
|
|
1169
|
+
*/
|
|
1170
|
+
readonly reading: Reading | undefined;
|
|
1171
|
+
|
|
1052
1172
|
/** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
|
|
1053
1173
|
private list: Effect[] | undefined;
|
|
1054
1174
|
tick = 0;
|
|
@@ -1067,23 +1187,22 @@ class Session implements Runtime {
|
|
|
1067
1187
|
private scope: Scope | undefined;
|
|
1068
1188
|
|
|
1069
1189
|
constructor(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
readonly running: boolean,
|
|
1077
|
-
readonly traced: boolean,
|
|
1078
|
-
readonly perform: Perform | undefined,
|
|
1079
|
-
readonly signal: StopSignal | undefined,
|
|
1190
|
+
machine: Machine,
|
|
1191
|
+
epoch: readonly Cap[],
|
|
1192
|
+
running: boolean,
|
|
1193
|
+
traced: boolean,
|
|
1194
|
+
perform: Perform | undefined,
|
|
1195
|
+
signal: StopSignal | undefined,
|
|
1080
1196
|
effects: Effect[] | undefined,
|
|
1081
|
-
|
|
1082
|
-
* The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
|
|
1083
|
-
* shares the record of the evaluation around it (READS.EQUATION).
|
|
1084
|
-
*/
|
|
1085
|
-
readonly reading: Reading | undefined = undefined,
|
|
1197
|
+
reading: Reading | undefined = undefined,
|
|
1086
1198
|
) {
|
|
1199
|
+
this.machine = machine;
|
|
1200
|
+
this.epoch = epoch;
|
|
1201
|
+
this.running = running;
|
|
1202
|
+
this.traced = traced;
|
|
1203
|
+
this.perform = perform;
|
|
1204
|
+
this.signal = signal;
|
|
1205
|
+
this.reading = reading;
|
|
1087
1206
|
this.list = effects;
|
|
1088
1207
|
this.holder = traced ? holderNode() : undefined;
|
|
1089
1208
|
this.current = this.holder;
|
|
@@ -1185,7 +1304,7 @@ class Session implements Runtime {
|
|
|
1185
1304
|
if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
|
|
1186
1305
|
paths.add(text);
|
|
1187
1306
|
if (path.length === 1 && root === "self") reading.self = true;
|
|
1188
|
-
reading.list.push({ path, reached: walk.reached ??
|
|
1307
|
+
reading.list.push({ path, reached: walk.reached ?? readKept(this.machine, reading.root, path), free: true, kept: true });
|
|
1189
1308
|
return true;
|
|
1190
1309
|
}
|
|
1191
1310
|
|
|
@@ -1197,7 +1316,7 @@ class Session implements Runtime {
|
|
|
1197
1316
|
for (const root of roots) {
|
|
1198
1317
|
if (root === "self") reading.self = true;
|
|
1199
1318
|
const path = [root];
|
|
1200
|
-
reading.list.push({ path, reached:
|
|
1319
|
+
reading.list.push({ path, reached: readKept(this.machine, reading.root, path), free: true, kept: true });
|
|
1201
1320
|
}
|
|
1202
1321
|
}
|
|
1203
1322
|
|
|
@@ -1258,14 +1377,35 @@ class Session implements Runtime {
|
|
|
1258
1377
|
return (effect.outcome = fail("#CALC!", `The mock gave a promise for the effect ${effect.kind}, and an evaluation does not wait.`, { place }));
|
|
1259
1378
|
}
|
|
1260
1379
|
|
|
1380
|
+
/**
|
|
1381
|
+
* This function finds the clause that answers an effect (D146). Without it, the effect goes on to the host. An effect
|
|
1382
|
+
* without a capability gets no clause, thus `record` gives its `#CAP!` (HANDLE.NARROW). A clause of a frame that has
|
|
1383
|
+
* ended gives `#CAP!`, because the capability came from that frame (HANDLE.ENDED).
|
|
1384
|
+
*/
|
|
1385
|
+
private clauseOf(given: Context, start: Frame, kind: Cap, place: readonly Key[]): Clause | Result | undefined {
|
|
1386
|
+
const ctx = inEpoch(given, this.epoch) ? granted(given) : ungranted(given, this.epoch);
|
|
1387
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
1388
|
+
if (!this.permitted(ctx, keys)) return undefined;
|
|
1389
|
+
const found = clauseFor(start, keys);
|
|
1390
|
+
if (found === undefined) return undefined;
|
|
1391
|
+
if (found.frame.ended || found.frame.owner !== this) return fail("#CAP!", `The handler of the effect ${keys.join(".")} has ended.`, { place });
|
|
1392
|
+
return found.clause;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1261
1395
|
effect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result {
|
|
1262
1396
|
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
1263
1397
|
if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
1398
|
+
const frame = frameOf(ctx);
|
|
1399
|
+
const clause = frame === undefined ? undefined : this.clauseOf(ctx, frame, kind, place);
|
|
1400
|
+
if (clause !== undefined) return "ok" in clause ? clause : drive(clause.answer(payload), text);
|
|
1264
1401
|
const made = this.record(ctx, kind, payload, place);
|
|
1265
1402
|
return "ok" in made ? made : this.answer(made, place);
|
|
1266
1403
|
}
|
|
1267
1404
|
|
|
1268
1405
|
*runEffect(ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Steps {
|
|
1406
|
+
const frame = frameOf(ctx);
|
|
1407
|
+
const clause = frame === undefined ? undefined : this.clauseOf(ctx, frame, kind, place);
|
|
1408
|
+
if (clause !== undefined) return "ok" in clause ? clause : yield* clause.answer(payload);
|
|
1269
1409
|
const made = this.record(ctx, kind, payload, place);
|
|
1270
1410
|
if ("ok" in made) return made;
|
|
1271
1411
|
const perform = this.perform;
|
|
@@ -1422,7 +1562,9 @@ class Session implements Runtime {
|
|
|
1422
1562
|
|
|
1423
1563
|
/**
|
|
1424
1564
|
* This function resolves a compiled path through a runtime (PLAN.PATH). The runtime of the kernel uses the compiled
|
|
1425
|
-
* path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path
|
|
1565
|
+
* path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path
|
|
1566
|
+
* (PLAN.PATH-ROUTE). The result and the list of reads are the same as for `rt.resolve(ctx, compiled.path)`
|
|
1567
|
+
* (PLAN.PATH-READS).
|
|
1426
1568
|
*
|
|
1427
1569
|
* @param rt - The runtime.
|
|
1428
1570
|
* @param ctx - The context.
|
|
@@ -1739,7 +1881,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1739
1881
|
if (runner !== undefined) {
|
|
1740
1882
|
let inner: RunCode;
|
|
1741
1883
|
try {
|
|
1742
|
-
inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
1884
|
+
inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill, kind?.caps?.some((rule) => rule.rule === "grant") === true);
|
|
1743
1885
|
} catch (error) {
|
|
1744
1886
|
return failingWith(c, meta, placed(thrown(error, `the runner ${name}`), place));
|
|
1745
1887
|
}
|
|
@@ -1831,6 +1973,7 @@ function freeOf(doc: Envelope, kind: Kind, slots: readonly PartSlot[], frees: re
|
|
|
1831
1973
|
|
|
1832
1974
|
/** This class is the runtime of a fold. It reads only the names that the folded part binds, and it causes no effect. */
|
|
1833
1975
|
class Probe implements Runtime {
|
|
1976
|
+
readonly machine: Machine;
|
|
1834
1977
|
readonly running = false;
|
|
1835
1978
|
/** `true` after the part asked for something that a fold cannot give. */
|
|
1836
1979
|
refused = false;
|
|
@@ -1842,7 +1985,8 @@ class Probe implements Runtime {
|
|
|
1842
1985
|
*/
|
|
1843
1986
|
readonly root: Context;
|
|
1844
1987
|
|
|
1845
|
-
constructor(
|
|
1988
|
+
constructor(machine: Machine) {
|
|
1989
|
+
this.machine = machine;
|
|
1846
1990
|
const refuse = (): void => {
|
|
1847
1991
|
this.refused = true;
|
|
1848
1992
|
};
|
|
@@ -2282,7 +2426,25 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
|
|
|
2282
2426
|
};
|
|
2283
2427
|
}
|
|
2284
2428
|
|
|
2285
|
-
|
|
2429
|
+
/**
|
|
2430
|
+
* This function runs a part under a new frame (D146). The part gets the capabilities of the document and the kinds of
|
|
2431
|
+
* the clauses (CAP.GRANT), with the grant of the kernel. A rule around an effect narrows them as it narrows any other
|
|
2432
|
+
* capability (HANDLE.NARROW). The frame ends with the steps.
|
|
2433
|
+
*/
|
|
2434
|
+
function* handling(given: Context, clauses: readonly Clause[], rt: Runtime, run: (ctx: Context) => Steps): Steps {
|
|
2435
|
+
const ctx = granted(given);
|
|
2436
|
+
const frame: Frame = { clauses, outer: frameOf(ctx), owner: rt, ended: false };
|
|
2437
|
+
const caps = [...ctx.caps];
|
|
2438
|
+
for (const clause of clauses) if (!permits(caps, clause.op)) caps.push(clause.op);
|
|
2439
|
+
const inner: Granted & Framed = { ...ctx, caps, [GRANT]: caps, [EPOCH]: epochOf(ctx), [FRAME]: frame };
|
|
2440
|
+
try {
|
|
2441
|
+
return yield* run(inner);
|
|
2442
|
+
} finally {
|
|
2443
|
+
frame.ended = true;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown, grants: boolean): RunCode {
|
|
2286
2448
|
const { kind, typePath, doc, data, place, machine } = input;
|
|
2287
2449
|
const refill = input.fill;
|
|
2288
2450
|
const compiled = input.parts;
|
|
@@ -2293,6 +2455,8 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
|
|
|
2293
2455
|
path: part.path,
|
|
2294
2456
|
...(part.name === undefined ? {} : { name: part.name }),
|
|
2295
2457
|
evaluate: (override?: Context) => part.run(override === undefined ? ctx : handed(ctx, override), rt),
|
|
2458
|
+
// Only a kind with a rule grant pays for this function: most runners stay as fast as before.
|
|
2459
|
+
...(grants ? { handled: (clauses: readonly Clause[]) => handling(ctx, clauses, rt, (inner) => part.run(inner, rt)) } : {}),
|
|
2296
2460
|
}));
|
|
2297
2461
|
return fn({
|
|
2298
2462
|
kind,
|
package/src/formula.ts
CHANGED
|
@@ -97,7 +97,11 @@ const SKIPPED: unique symbol = /* @__PURE__ */ Symbol("skipped");
|
|
|
97
97
|
|
|
98
98
|
/** The value of a name whose binding failed. A reference to the name gives the error. */
|
|
99
99
|
export class Held {
|
|
100
|
-
|
|
100
|
+
readonly error: EvalError;
|
|
101
|
+
|
|
102
|
+
constructor(error: EvalError) {
|
|
103
|
+
this.error = error;
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
const CLOSURE: unique symbol = /* @__PURE__ */ Symbol.for("coeval.closure") as never;
|
|
@@ -242,6 +246,36 @@ function isSignature(value: unknown): value is Signature {
|
|
|
242
246
|
return typeof value === "object" && value !== null && Array.isArray((value as { readonly params?: unknown }).params) && Object.hasOwn(value, "to");
|
|
243
247
|
}
|
|
244
248
|
|
|
249
|
+
/** This function gives the signatures of an operation as the profile holds them, or `undefined` for no entry. */
|
|
250
|
+
function rawSignatures(profile: FormulaProfile, name: string): readonly unknown[] | undefined {
|
|
251
|
+
const sigs = profile.sigs;
|
|
252
|
+
if (sigs === undefined) return undefined;
|
|
253
|
+
let found: unknown;
|
|
254
|
+
if (typeof sigs === "function") {
|
|
255
|
+
try {
|
|
256
|
+
found = sigs(name);
|
|
257
|
+
} catch {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
found = Object.hasOwn(sigs, name) ? sigs[name] : undefined;
|
|
262
|
+
}
|
|
263
|
+
if (found === undefined) return undefined;
|
|
264
|
+
return Array.isArray(found) ? found : [found];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* This function gives each signature of an operation that has the form of a signature, for `conforms` (D137). The
|
|
269
|
+
* kernel does not export the function.
|
|
270
|
+
*
|
|
271
|
+
* @param profile - The profile.
|
|
272
|
+
* @param name - The name of the operation.
|
|
273
|
+
* @returns The signatures.
|
|
274
|
+
*/
|
|
275
|
+
export function signaturesOf(profile: FormulaProfile, name: string): readonly Signature[] {
|
|
276
|
+
return (rawSignatures(profile, name) ?? []).filter(isSignature);
|
|
277
|
+
}
|
|
278
|
+
|
|
245
279
|
/** This function tells if two signatures have the same parameters and the same rest parameter. */
|
|
246
280
|
function sameParams(a: Signature, b: Signature): boolean {
|
|
247
281
|
if (!docEquals(a.params, b.params)) return false;
|
|
@@ -329,22 +363,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
329
363
|
};
|
|
330
364
|
const logicValue = profile.logic === "value";
|
|
331
365
|
const otherwise = Object.hasOwn(profile, "otherwise") ? valueType(profile.otherwise) : "null";
|
|
332
|
-
const lookup = (name: string): readonly unknown[] | undefined =>
|
|
333
|
-
const sigs = profile.sigs;
|
|
334
|
-
if (sigs === undefined) return undefined;
|
|
335
|
-
let found: unknown;
|
|
336
|
-
if (typeof sigs === "function") {
|
|
337
|
-
try {
|
|
338
|
-
found = sigs(name);
|
|
339
|
-
} catch {
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
342
|
-
} else {
|
|
343
|
-
found = Object.hasOwn(sigs, name) ? sigs[name] : undefined;
|
|
344
|
-
}
|
|
345
|
-
if (found === undefined) return undefined;
|
|
346
|
-
return Array.isArray(found) ? found : [found];
|
|
347
|
-
};
|
|
366
|
+
const lookup = (name: string): readonly unknown[] | undefined => rawSignatures(profile, name);
|
|
348
367
|
const nameOf = (input: TypeInput): string => input.typePath.slice(1).join(".");
|
|
349
368
|
/** The signatures of an operation. Each type of a signature is a contract, thus the check examines it one time. */
|
|
350
369
|
const signatures = (input: TypeInput): { readonly sigs: readonly Signature[]; readonly bad: boolean } | undefined => {
|
|
@@ -644,6 +663,29 @@ export function declaredTruthOf(code: unknown): DeclaredTruth | undefined {
|
|
|
644
663
|
return (typeof code === "object" || typeof code === "function") && code !== null ? truthByCode.get(code) : undefined;
|
|
645
664
|
}
|
|
646
665
|
|
|
666
|
+
/** An evaluator of the kit that reads functions of a profile: its name, and the fields of the profile (D137). */
|
|
667
|
+
export interface ProfileReader {
|
|
668
|
+
readonly name: string;
|
|
669
|
+
readonly profile: FormulaProfile;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** The evaluators of the kit that `conforms` examines the functions of a profile with. */
|
|
673
|
+
const PROFILE_READERS: readonly string[] = ["formula.op", "formula.ext", "formula.record", "formula.ref"];
|
|
674
|
+
|
|
675
|
+
/** The profile of each evaluator of the kit that reads its functions, for `conforms` (D137). */
|
|
676
|
+
const readerByCode = /* @__PURE__ */ new WeakMap<object, ProfileReader>();
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* This function gives the profile of an evaluator of the kit, when that evaluator reads functions of the profile
|
|
680
|
+
* (D137). The kernel does not export the function.
|
|
681
|
+
*
|
|
682
|
+
* @param code - The evaluator or the runner.
|
|
683
|
+
* @returns The name of the evaluator and the profile, or `undefined`.
|
|
684
|
+
*/
|
|
685
|
+
export function profileReaderOf(code: unknown): ProfileReader | undefined {
|
|
686
|
+
return (typeof code === "object" || typeof code === "function") && code !== null ? readerByCode.get(code) : undefined;
|
|
687
|
+
}
|
|
688
|
+
|
|
647
689
|
/** This function makes new evaluators of the formula kit for the fields of one profile. */
|
|
648
690
|
function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evaluator>> {
|
|
649
691
|
const evaluators = formulaCode(profile);
|
|
@@ -656,6 +698,7 @@ function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evalua
|
|
|
656
698
|
const type = Object.hasOwn(types, name) ? types[name] : undefined;
|
|
657
699
|
const made = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
|
|
658
700
|
if (truth !== undefined && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
|
|
701
|
+
if (PROFILE_READERS.includes(name)) readerByCode.set(made, { name, profile });
|
|
659
702
|
out[name] = made;
|
|
660
703
|
}
|
|
661
704
|
return Object.freeze(out);
|