@mark1russell7/coeval 0.22.0 → 0.23.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 +23 -5
- package/build/index.d.ts +359 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5308 -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 +662 -70
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +55 -21
- package/src/formula.ts +60 -17
- package/src/index.ts +10 -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 +67 -2
- package/src/schedule.ts +286 -45
- package/src/static.ts +46 -14
- package/src/stdlib.ts +127 -5
- package/src/syntax.ts +430 -48
- package/src/types.ts +2 -1
- package/src/world.ts +24 -3
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/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
|
@@ -1004,9 +1004,24 @@ function keptPath(nav: Navigator, root: Context, path: readonly unknown[]): Kept
|
|
|
1004
1004
|
}
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* This function resolves the path of a kept read. A reference on the path can reach a node of a schedule, thus the
|
|
1009
|
+
* resolution goes through `keeping` of the root `world` (SCHED.OWN-NODE).
|
|
1010
|
+
*
|
|
1011
|
+
* @param nav - The world and the code of the evaluators.
|
|
1012
|
+
* @param ctx - The context.
|
|
1013
|
+
* @param path - The path of the kept read.
|
|
1014
|
+
* @returns What the path reaches.
|
|
1015
|
+
*/
|
|
1016
|
+
function readKept(nav: Navigator, ctx: Context, path: readonly unknown[]): Result {
|
|
1017
|
+
const read = (): Result => readAgain(nav, ctx, path, false).reached;
|
|
1018
|
+
const world = ctx.world;
|
|
1019
|
+
return isHostThing(world) && world.keeping !== undefined ? world.keeping(read) : read();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1007
1022
|
/**
|
|
1008
1023
|
* 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 `
|
|
1024
|
+
* `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readKept`.
|
|
1010
1025
|
*
|
|
1011
1026
|
* @param nav - The world and the code of the evaluators.
|
|
1012
1027
|
* @param ctx - The new context.
|
|
@@ -1018,7 +1033,7 @@ export function keptAgain(nav: Navigator, ctx: Context, path: readonly unknown[]
|
|
|
1018
1033
|
let here = adopt(ctx[path[0] as Root]);
|
|
1019
1034
|
let known = false;
|
|
1020
1035
|
// 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 :
|
|
1036
|
+
const other = (): Result | undefined => (known ? undefined : readKept(nav, ctx, path));
|
|
1022
1037
|
for (let index = 1; index < path.length; index += 1) {
|
|
1023
1038
|
if (isHostThing(here)) {
|
|
1024
1039
|
if (here.known === undefined) return other();
|
|
@@ -1049,6 +1064,22 @@ function readingOf(list: Read[] | undefined, root: Context): Reading | undefined
|
|
|
1049
1064
|
|
|
1050
1065
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
1051
1066
|
class Session implements Runtime {
|
|
1067
|
+
readonly machine: Machine;
|
|
1068
|
+
/**
|
|
1069
|
+
* The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
|
|
1070
|
+
* keeps its grant only in an evaluation of the same epoch.
|
|
1071
|
+
*/
|
|
1072
|
+
readonly epoch: readonly Cap[];
|
|
1073
|
+
readonly running: boolean;
|
|
1074
|
+
readonly traced: boolean;
|
|
1075
|
+
readonly perform: Perform | undefined;
|
|
1076
|
+
readonly signal: StopSignal | undefined;
|
|
1077
|
+
/**
|
|
1078
|
+
* The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
|
|
1079
|
+
* shares the record of the evaluation around it (READS.EQUATION).
|
|
1080
|
+
*/
|
|
1081
|
+
readonly reading: Reading | undefined;
|
|
1082
|
+
|
|
1052
1083
|
/** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
|
|
1053
1084
|
private list: Effect[] | undefined;
|
|
1054
1085
|
tick = 0;
|
|
@@ -1067,23 +1098,22 @@ class Session implements Runtime {
|
|
|
1067
1098
|
private scope: Scope | undefined;
|
|
1068
1099
|
|
|
1069
1100
|
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,
|
|
1101
|
+
machine: Machine,
|
|
1102
|
+
epoch: readonly Cap[],
|
|
1103
|
+
running: boolean,
|
|
1104
|
+
traced: boolean,
|
|
1105
|
+
perform: Perform | undefined,
|
|
1106
|
+
signal: StopSignal | undefined,
|
|
1080
1107
|
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,
|
|
1108
|
+
reading: Reading | undefined = undefined,
|
|
1086
1109
|
) {
|
|
1110
|
+
this.machine = machine;
|
|
1111
|
+
this.epoch = epoch;
|
|
1112
|
+
this.running = running;
|
|
1113
|
+
this.traced = traced;
|
|
1114
|
+
this.perform = perform;
|
|
1115
|
+
this.signal = signal;
|
|
1116
|
+
this.reading = reading;
|
|
1087
1117
|
this.list = effects;
|
|
1088
1118
|
this.holder = traced ? holderNode() : undefined;
|
|
1089
1119
|
this.current = this.holder;
|
|
@@ -1185,7 +1215,7 @@ class Session implements Runtime {
|
|
|
1185
1215
|
if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
|
|
1186
1216
|
paths.add(text);
|
|
1187
1217
|
if (path.length === 1 && root === "self") reading.self = true;
|
|
1188
|
-
reading.list.push({ path, reached: walk.reached ??
|
|
1218
|
+
reading.list.push({ path, reached: walk.reached ?? readKept(this.machine, reading.root, path), free: true, kept: true });
|
|
1189
1219
|
return true;
|
|
1190
1220
|
}
|
|
1191
1221
|
|
|
@@ -1197,7 +1227,7 @@ class Session implements Runtime {
|
|
|
1197
1227
|
for (const root of roots) {
|
|
1198
1228
|
if (root === "self") reading.self = true;
|
|
1199
1229
|
const path = [root];
|
|
1200
|
-
reading.list.push({ path, reached:
|
|
1230
|
+
reading.list.push({ path, reached: readKept(this.machine, reading.root, path), free: true, kept: true });
|
|
1201
1231
|
}
|
|
1202
1232
|
}
|
|
1203
1233
|
|
|
@@ -1422,7 +1452,9 @@ class Session implements Runtime {
|
|
|
1422
1452
|
|
|
1423
1453
|
/**
|
|
1424
1454
|
* 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
|
|
1455
|
+
* path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path
|
|
1456
|
+
* (PLAN.PATH-ROUTE). The result and the list of reads are the same as for `rt.resolve(ctx, compiled.path)`
|
|
1457
|
+
* (PLAN.PATH-READS).
|
|
1426
1458
|
*
|
|
1427
1459
|
* @param rt - The runtime.
|
|
1428
1460
|
* @param ctx - The context.
|
|
@@ -1831,6 +1863,7 @@ function freeOf(doc: Envelope, kind: Kind, slots: readonly PartSlot[], frees: re
|
|
|
1831
1863
|
|
|
1832
1864
|
/** This class is the runtime of a fold. It reads only the names that the folded part binds, and it causes no effect. */
|
|
1833
1865
|
class Probe implements Runtime {
|
|
1866
|
+
readonly machine: Machine;
|
|
1834
1867
|
readonly running = false;
|
|
1835
1868
|
/** `true` after the part asked for something that a fold cannot give. */
|
|
1836
1869
|
refused = false;
|
|
@@ -1842,7 +1875,8 @@ class Probe implements Runtime {
|
|
|
1842
1875
|
*/
|
|
1843
1876
|
readonly root: Context;
|
|
1844
1877
|
|
|
1845
|
-
constructor(
|
|
1878
|
+
constructor(machine: Machine) {
|
|
1879
|
+
this.machine = machine;
|
|
1846
1880
|
const refuse = (): void => {
|
|
1847
1881
|
this.refused = true;
|
|
1848
1882
|
};
|
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);
|
package/src/index.ts
CHANGED
|
@@ -60,9 +60,11 @@ export {
|
|
|
60
60
|
type World,
|
|
61
61
|
type WorldEntry,
|
|
62
62
|
CIRCULAR_LIMIT,
|
|
63
|
+
GENERATION_LIMIT,
|
|
63
64
|
argsKey,
|
|
64
65
|
atArgsOf,
|
|
65
66
|
atRuleOf,
|
|
67
|
+
documentKey,
|
|
66
68
|
circularOf,
|
|
67
69
|
collectOf,
|
|
68
70
|
effectsOfVerb,
|
|
@@ -104,6 +106,7 @@ export {
|
|
|
104
106
|
export { type StepKey, type StepLadder, type StepNeed, type StepReason, type StepRefusal, type StepWalk, checkStep, isStep, stepLadder, stepOf, stepRank } from "./steps.ts";
|
|
105
107
|
export {
|
|
106
108
|
type Cap,
|
|
109
|
+
type CompiledPath,
|
|
107
110
|
type Context,
|
|
108
111
|
type DerefCode,
|
|
109
112
|
type Hop,
|
|
@@ -121,6 +124,7 @@ export {
|
|
|
121
124
|
type Walker,
|
|
122
125
|
ROOTS,
|
|
123
126
|
THING,
|
|
127
|
+
compilePath,
|
|
124
128
|
context,
|
|
125
129
|
derefStep,
|
|
126
130
|
isHostThing,
|
|
@@ -176,6 +180,7 @@ export {
|
|
|
176
180
|
permits,
|
|
177
181
|
planner,
|
|
178
182
|
prepare,
|
|
183
|
+
resolveCompiled,
|
|
179
184
|
run,
|
|
180
185
|
} from "./evaluate.ts";
|
|
181
186
|
export {
|
|
@@ -255,10 +260,12 @@ export {
|
|
|
255
260
|
jsTruthy,
|
|
256
261
|
} from "./formula.ts";
|
|
257
262
|
export { decodeDomain, decodeValue, encodeValue } from "./codec.ts";
|
|
263
|
+
export { type CodecFinding, type CodecLaw, type CodecLawsOptions, type CodecProbe, codecLaws } from "./codec-laws.ts";
|
|
258
264
|
export {
|
|
259
265
|
type ParseResult,
|
|
260
266
|
type SyntaxAssoc,
|
|
261
267
|
type SyntaxCalls,
|
|
268
|
+
type SyntaxConstructs,
|
|
262
269
|
type SyntaxEnvelopes,
|
|
263
270
|
type SyntaxFixity,
|
|
264
271
|
type SyntaxForm,
|
|
@@ -270,6 +277,7 @@ export {
|
|
|
270
277
|
type SyntaxProfile,
|
|
271
278
|
type SyntaxRefPattern,
|
|
272
279
|
type SyntaxRefs,
|
|
280
|
+
MAX_SYNTAX_ISSUES,
|
|
273
281
|
STD_SYNTAX,
|
|
274
282
|
parse,
|
|
275
283
|
print,
|
|
@@ -282,12 +290,14 @@ export { sameResult } from "./same.ts";
|
|
|
282
290
|
export { type PrintOptions, type ReadPrints, type ReadsCheck, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
|
|
283
291
|
export { type Durable, type DurableCounts, type DurableOptions, type Epochs, durable } from "./durable.ts";
|
|
284
292
|
export {
|
|
293
|
+
type CycleIssue,
|
|
285
294
|
type NodeEdge,
|
|
286
295
|
type NodeInfo,
|
|
287
296
|
type NodeRunOptions,
|
|
288
297
|
type Schedule,
|
|
289
298
|
type ScheduleCounts,
|
|
290
299
|
type ScheduleOptions,
|
|
300
|
+
checkCycles,
|
|
291
301
|
edgesOf,
|
|
292
302
|
schedule,
|
|
293
303
|
} from "./schedule.ts";
|
package/src/instances.ts
CHANGED
|
@@ -504,7 +504,22 @@ function rungReads(b: Builder, id: string): void {
|
|
|
504
504
|
collectReads(b, node, key, collect, later);
|
|
505
505
|
return;
|
|
506
506
|
}
|
|
507
|
-
if (declared?.default !== undefined)
|
|
507
|
+
if (declared?.default !== undefined) {
|
|
508
|
+
termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// The document of a higher-order attribute is known only in the run, thus its reads are not known (TREE.HIGHER-CHECK).
|
|
512
|
+
if (declared?.higher !== undefined) {
|
|
513
|
+
termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.higher`, declared.higher), node, undefined, later);
|
|
514
|
+
edge(b, undefined, later);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
// The last rung reads the attribute `forwards` and an attribute of a node that only the run knows (TREE.FORWARD-CHECK).
|
|
518
|
+
const forwards = tree?.forwards;
|
|
519
|
+
if (typeof forwards === "string" && forwards !== key) {
|
|
520
|
+
edge(b, vertexOf(b, node, forwards), later);
|
|
521
|
+
edge(b, undefined, later);
|
|
522
|
+
}
|
|
508
523
|
}
|
|
509
524
|
|
|
510
525
|
/** This function gives the list children of a node before the run. A node without a rung for it has no children. */
|
package/src/meaning.ts
CHANGED
|
@@ -58,12 +58,14 @@ export interface ScheduleEngine {
|
|
|
58
58
|
read(id: string): Result;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/** What a new engine gets beside its machine
|
|
61
|
+
/** What a new engine gets beside its machine. These are the context of the host, the type of a bare root value and the `self` of each node. */
|
|
62
62
|
export interface ScheduleEngineStart {
|
|
63
63
|
/** The first context of the host, without the root `world`. */
|
|
64
64
|
readonly context: Context;
|
|
65
65
|
/** The type of a bare root value, when the options of the harness name one. */
|
|
66
66
|
readonly declaredType?: string;
|
|
67
|
+
/** The `self` of each node, when the options of the harness have `selves`. It has the form of the option of `schedule`. */
|
|
68
|
+
readonly selfOf?: (id: string, host: Context) => unknown;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -110,6 +112,12 @@ export interface AgreeScheduleOptions {
|
|
|
110
112
|
readonly values?: readonly unknown[];
|
|
111
113
|
/** The contexts of the host at the start of the runs, one after the other. */
|
|
112
114
|
readonly contexts?: readonly Context[];
|
|
115
|
+
/**
|
|
116
|
+
* The values of the `self` of a node (SCHED.NODE-AGREES). With them, the option `selfOf` of each engine gives each
|
|
117
|
+
* node its own `self`. Some nodes then share one document, and a change of data can give a node a new `self`. The
|
|
118
|
+
* value `undefined` keeps the `self` of the host. The default gives each node the context of the host.
|
|
119
|
+
*/
|
|
120
|
+
readonly selves?: readonly unknown[];
|
|
113
121
|
/** The number of runs. The default is 100. */
|
|
114
122
|
readonly count?: number;
|
|
115
123
|
/** The number of commands in one run. The default is 30. */
|
|
@@ -155,6 +163,10 @@ export interface ScheduleAgreementCounts {
|
|
|
155
163
|
readonly same: number;
|
|
156
164
|
/** The new machines. */
|
|
157
165
|
readonly machines: number;
|
|
166
|
+
/** The changes of data with a new `self` of a node, only with the option `selves`. */
|
|
167
|
+
readonly selves: number;
|
|
168
|
+
/** The documents that a node shares with another node, only with the option `selves`. */
|
|
169
|
+
readonly shared: number;
|
|
158
170
|
}
|
|
159
171
|
|
|
160
172
|
/** The outcome of `agreeSchedule`: what the harness reached, or the first disagreement. */
|
|
@@ -266,7 +278,7 @@ function shuffle<T>(items: readonly T[], rng: Random): T[] {
|
|
|
266
278
|
* This function evaluates each node of a graph from scratch, with the meaning of D31. Each node gets a new plan and
|
|
267
279
|
* one value. The algorithm of Tarjan over the demands finds the cycles. The function keeps nothing between calls.
|
|
268
280
|
*/
|
|
269
|
-
function fromScratch(machine: Machine, docs: ReadonlyMap<string, unknown>, base: Context, declaredType: string | undefined): ReadonlyMap<string, Result> {
|
|
281
|
+
function fromScratch(machine: Machine, docs: ReadonlyMap<string, unknown>, base: Context, declaredType: string | undefined, own: ReadonlyMap<string, unknown> | undefined): ReadonlyMap<string, Result> {
|
|
270
282
|
const declared = declaredType === undefined ? {} : { declaredType };
|
|
271
283
|
const values = new Map<string, Result>();
|
|
272
284
|
const index = new Map<string, number>();
|
|
@@ -298,7 +310,8 @@ function fromScratch(machine: Machine, docs: ReadonlyMap<string, unknown>, base:
|
|
|
298
310
|
lowest.set(id, index.get(id) as number);
|
|
299
311
|
component.push(id);
|
|
300
312
|
evaluating.push(id);
|
|
301
|
-
|
|
313
|
+
const self = own?.get(id);
|
|
314
|
+
let result = prepare(machine, docs.get(id), declared).evaluate(self === undefined ? ctx : { ...ctx, self });
|
|
302
315
|
evaluating.pop();
|
|
303
316
|
if ((lowest.get(id) as number) < (index.get(id) as number)) result = cycleOf(id);
|
|
304
317
|
else if (component.splice(component.indexOf(id)).length > 1 || selfReads.has(id)) result = cycleOf(id);
|
|
@@ -340,7 +353,8 @@ function named(result: Result): string {
|
|
|
340
353
|
/**
|
|
341
354
|
* This function examines an engine of a host against an evaluation from scratch (SCHED.AGREES, R4 P4, D58). Each run
|
|
342
355
|
* makes a new engine and a random graph of named nodes from the document generator. Then it gives random commands:
|
|
343
|
-
* edits, new nodes, removed nodes, the same document again, new machines and changes of data.
|
|
356
|
+
* edits, new nodes, removed nodes, the same document again, new machines and changes of data. The option `selves`
|
|
357
|
+
* adds a `self` for each node and documents that nodes share (SCHED.NODE-AGREES).
|
|
344
358
|
*
|
|
345
359
|
* - Each read command evaluates the whole graph from scratch two times, with the meaning of D31.
|
|
346
360
|
* - It then demands each node in a random order, or one node, and compares each answer with `sameResult`.
|
|
@@ -366,6 +380,7 @@ export function agreeSchedule(engine: ScheduleEngineFactory, options: AgreeSched
|
|
|
366
380
|
const hostNames = options.names ?? ["a", "b", "x", "n"];
|
|
367
381
|
const hostValues = options.values ?? standardValues();
|
|
368
382
|
const contexts = options.contexts ?? standardContexts();
|
|
383
|
+
const selves = options.selves;
|
|
369
384
|
const total = options.count ?? 100;
|
|
370
385
|
const steps = options.steps ?? 30;
|
|
371
386
|
const first = options.seed ?? 9_000_011;
|
|
@@ -376,7 +391,7 @@ export function agreeSchedule(engine: ScheduleEngineFactory, options: AgreeSched
|
|
|
376
391
|
if (isClosure(a) || isClosure(b)) return isClosure(a) && isClosure(b) && docEquals(closureParams(a), closureParams(b));
|
|
377
392
|
return undefined;
|
|
378
393
|
};
|
|
379
|
-
const counts = { reads: 0, compared: 0, unrepeatable: 0, values: 0, cycles: 0, runs: 0, data: 0, empty: 0, edits: 0, additions: 0, removes: 0, same: 0, machines: 0 };
|
|
394
|
+
const counts = { reads: 0, compared: 0, unrepeatable: 0, values: 0, cycles: 0, runs: 0, data: 0, empty: 0, edits: 0, additions: 0, removes: 0, same: 0, machines: 0, selves: 0, shared: 0 };
|
|
380
395
|
const declared = declaredType === undefined ? {} : { declaredType };
|
|
381
396
|
for (let run = 0; run < total; run += 1) {
|
|
382
397
|
const seed = first + run;
|
|
@@ -385,7 +400,18 @@ export function agreeSchedule(engine: ScheduleEngineFactory, options: AgreeSched
|
|
|
385
400
|
let at = 0;
|
|
386
401
|
let machine = machines[0] as Machine;
|
|
387
402
|
let made = 0;
|
|
403
|
+
const own = selves === undefined ? undefined : new Map<string, unknown>();
|
|
404
|
+
const pool: unknown[] = [];
|
|
388
405
|
const make = (): unknown => {
|
|
406
|
+
if (selves !== undefined && pool.length > 0 && rng.chance(0.4)) {
|
|
407
|
+
counts.shared += 1;
|
|
408
|
+
return rng.pick(pool);
|
|
409
|
+
}
|
|
410
|
+
const doc = generated();
|
|
411
|
+
pool.push(doc);
|
|
412
|
+
return doc;
|
|
413
|
+
};
|
|
414
|
+
const generated = (): unknown => {
|
|
389
415
|
if (rng.chance(wired)) return wire(rng, node);
|
|
390
416
|
const roll = rng.int(10);
|
|
391
417
|
if (roll === 0) return value(rng);
|
|
@@ -395,15 +421,16 @@ export function agreeSchedule(engine: ScheduleEngineFactory, options: AgreeSched
|
|
|
395
421
|
return runs && roll === 9 ? { type: "log", value: [doc] } : doc;
|
|
396
422
|
};
|
|
397
423
|
const docs = new Map<string, unknown>(nodes.map((id) => [id, make()]));
|
|
424
|
+
if (own !== undefined && selves !== undefined) for (const id of nodes) own.set(id, rng.pick(selves));
|
|
398
425
|
const start = contexts[run % contexts.length] as Context;
|
|
399
426
|
let base: Context = { ...start, names: { ...start.names } };
|
|
400
|
-
const host = engine(machine, { ...declared, context: base });
|
|
427
|
+
const host = engine(machine, { ...declared, context: base, ...(own === undefined ? {} : { selfOf: (id: string) => own.get(id) }) });
|
|
401
428
|
for (const [id, doc] of docs) host.set(id, doc);
|
|
402
429
|
for (let step = 0; step < steps; step += 1) {
|
|
403
430
|
const what = rng.int(12);
|
|
404
431
|
if (what < 4) {
|
|
405
|
-
const fresh = fromScratch(machine, docs, base, declaredType);
|
|
406
|
-
const again = fromScratch(machine, docs, base, declaredType);
|
|
432
|
+
const fresh = fromScratch(machine, docs, base, declaredType, own);
|
|
433
|
+
const again = fromScratch(machine, docs, base, declaredType, own);
|
|
407
434
|
const ids = shuffle([...docs.keys()], rng);
|
|
408
435
|
// Half of the reads demand each node in a random order, and the other half demand one node.
|
|
409
436
|
for (const id of rng.chance(0.5) ? ids : ids.slice(0, 1)) {
|
|
@@ -428,9 +455,15 @@ export function agreeSchedule(engine: ScheduleEngineFactory, options: AgreeSched
|
|
|
428
455
|
}
|
|
429
456
|
}
|
|
430
457
|
} else if (what < 6) {
|
|
431
|
-
|
|
458
|
+
if (own !== undefined && selves !== undefined && rng.chance(0.5)) {
|
|
459
|
+
// A new `self` of one node, with the same context object of the host.
|
|
460
|
+
own.set(rng.pick(nodes), rng.pick(selves));
|
|
461
|
+
counts.selves += 1;
|
|
462
|
+
} else {
|
|
463
|
+
base = { ...base, names: change(base, rng, hostNames, hostValues) };
|
|
464
|
+
counts.data += 1;
|
|
465
|
+
}
|
|
432
466
|
host.changeData(base);
|
|
433
|
-
counts.data += 1;
|
|
434
467
|
} else if (what === 6) {
|
|
435
468
|
// A change of data that changes no value: the same context object again.
|
|
436
469
|
host.changeData(base);
|
package/src/parts.ts
CHANGED
|
@@ -41,10 +41,13 @@ export type Split =
|
|
|
41
41
|
| { readonly ok: false; readonly reason: string; readonly at: readonly Key[] };
|
|
42
42
|
|
|
43
43
|
class Bad {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
) {
|
|
44
|
+
readonly reason: string;
|
|
45
|
+
readonly at: readonly Key[];
|
|
46
|
+
|
|
47
|
+
constructor(reason: string, at: readonly Key[]) {
|
|
48
|
+
this.reason = reason;
|
|
49
|
+
this.at = at;
|
|
50
|
+
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
|
package/src/providers.ts
CHANGED
|
@@ -150,6 +150,7 @@ function unionOf(types: readonly ValueType[]): ValueType {
|
|
|
150
150
|
|
|
151
151
|
/** The state of one reading of a schema: the root, the issues and the definitions that it read. */
|
|
152
152
|
class SchemaReader {
|
|
153
|
+
readonly root: unknown;
|
|
153
154
|
readonly issues: TypeIssue[] = [];
|
|
154
155
|
/** The type of each definition that a reference reached. Each reading of a definition is sound, thus the first stays. */
|
|
155
156
|
readonly defs = new Map<string, ValueType>();
|
|
@@ -158,7 +159,9 @@ class SchemaReader {
|
|
|
158
159
|
/** The number of schemas with an own `$id` around the current schema. A reference inside them has another base. */
|
|
159
160
|
inner = 0;
|
|
160
161
|
|
|
161
|
-
constructor(
|
|
162
|
+
constructor(root: unknown) {
|
|
163
|
+
this.root = root;
|
|
164
|
+
}
|
|
162
165
|
|
|
163
166
|
issue(at: readonly Key[], reason: string): void {
|
|
164
167
|
this.issues.push({ at, reason });
|