@mark1russell7/coeval 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -7
- package/build/index.d.ts +1767 -303
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4989 -417
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +56 -55
- package/src/conform.ts +1068 -0
- package/src/deref.ts +298 -50
- package/src/doc.ts +23 -2
- package/src/durable.ts +238 -0
- package/src/effects.ts +110 -0
- package/src/errors.ts +3 -1
- package/src/evaluate.ts +656 -124
- package/src/facts.ts +252 -0
- package/src/formula.ts +433 -36
- package/src/graph.ts +399 -0
- package/src/index.ts +34 -1
- package/src/ladder.ts +40 -1
- package/src/merge.ts +91 -4
- package/src/parts.ts +26 -11
- package/src/providers.ts +286 -0
- package/src/reads.ts +105 -0
- package/src/reference.ts +101 -18
- package/src/replay.ts +159 -0
- package/src/same.ts +42 -0
- package/src/schedule.ts +436 -0
- package/src/static.ts +266 -29
- package/src/stdlib.ts +28 -7
- package/src/types.ts +741 -0
- package/src/typing.ts +390 -0
- package/src/world.ts +63 -9
package/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.7.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,7 +1,7 @@
|
|
|
1
|
-
import { type Key, docEquals,
|
|
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, evaluate, makeContext, prepare } from "./evaluate.ts";
|
|
4
|
+
import { type Effect, type Machine, type Perform, evaluate, makeContext, prepare } from "./evaluate.ts";
|
|
5
5
|
import { Held, isClosure } from "./formula.ts";
|
|
6
6
|
import { type Definitions, reference } from "./reference.ts";
|
|
7
7
|
|
|
@@ -21,10 +21,24 @@ export interface TierResult {
|
|
|
21
21
|
readonly effects?: readonly Effect[];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** What `agree` gives the second tier of a language beside the document and the context. */
|
|
25
|
+
export interface TierInput {
|
|
26
|
+
/** The mock of the host, when `agree` has one (AGREE.MOCK). The tier answers each effect with it. */
|
|
27
|
+
readonly perform?: Perform;
|
|
28
|
+
/** The verb of `agree`, when its options name one (VERB.AGREE). A tier that does not serve the verb fails. */
|
|
29
|
+
readonly verb?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
/** The options of `agree`. */
|
|
25
33
|
export interface AgreeOptions {
|
|
26
34
|
/** The type of a bare root value. */
|
|
27
35
|
readonly declaredType?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The verb of each tier, as in `prepare`. The default is `eval`. The plan, the reference, the traced evaluation and a
|
|
38
|
+
* second evaluation each ask the ladder for this verb (VERB.AGREE). The second tier of a language gets it in its
|
|
39
|
+
* input.
|
|
40
|
+
*/
|
|
41
|
+
readonly verb?: string;
|
|
28
42
|
/** The definitional code of the compiled evaluators of the machine, for the reference. */
|
|
29
43
|
readonly definitions?: Definitions;
|
|
30
44
|
/** The tiers to compare with the plan. The default is the reference, the traced evaluation and a second evaluation. */
|
|
@@ -34,9 +48,15 @@ export interface AgreeOptions {
|
|
|
34
48
|
*
|
|
35
49
|
* @param doc - The document.
|
|
36
50
|
* @param context - The context.
|
|
51
|
+
* @param input - The mock of the host and the verb, when `agree` has them.
|
|
37
52
|
* @returns The result, or the result with the effects.
|
|
38
53
|
*/
|
|
39
|
-
readonly other?: (doc: unknown, context: Context) => Result | TierResult;
|
|
54
|
+
readonly other?: (doc: unknown, context: Context, input: TierInput) => Result | TierResult;
|
|
55
|
+
/**
|
|
56
|
+
* The mock of the host (RUN.MOCK, AGREE.MOCK). Each tier gets it, thus the tiers agree on documents with effects. A
|
|
57
|
+
* mock must give the same answer to the same effect, because each tier asks it again.
|
|
58
|
+
*/
|
|
59
|
+
readonly perform?: Perform;
|
|
40
60
|
/**
|
|
41
61
|
* This function compares two values that the rules of `sameValue` do not know, for example two dates.
|
|
42
62
|
*
|
|
@@ -80,8 +100,9 @@ export type Agreement =
|
|
|
80
100
|
* errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
|
|
81
101
|
* tells otherwise.
|
|
82
102
|
*
|
|
83
|
-
* `docEquals`
|
|
84
|
-
*
|
|
103
|
+
* `docEquals` and `sameResult` use the same walk (D20). `docEquals` has no rules for closures, for `Held` values and
|
|
104
|
+
* for a rule of the caller. `sameResult` is for an early cutoff, thus a closure is the same there only as itself, and
|
|
105
|
+
* two errors also need the same reason.
|
|
85
106
|
*
|
|
86
107
|
* @param a - The first value.
|
|
87
108
|
* @param b - The second value.
|
|
@@ -89,47 +110,14 @@ export type Agreement =
|
|
|
89
110
|
* @returns `true` when the values are the same.
|
|
90
111
|
*/
|
|
91
112
|
export function sameValue(a: unknown, b: unknown, equal?: (a: unknown, b: unknown) => boolean | undefined): boolean {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
if (typeof x !== "object" && typeof x !== "function") return false;
|
|
103
|
-
if (typeof y !== "object" && typeof y !== "function") return false;
|
|
104
|
-
if (x === null || y === null) return false;
|
|
105
|
-
// A pair that the comparison already visits is the same, unless another pair shows a difference.
|
|
106
|
-
const pairs = seen.get(x) ?? new Set<object>();
|
|
107
|
-
if (pairs.has(y)) continue;
|
|
108
|
-
pairs.add(y);
|
|
109
|
-
seen.set(x, pairs);
|
|
110
|
-
if (isClosure(x) || isClosure(y)) {
|
|
111
|
-
if (!(isClosure(x) && isClosure(y))) return false;
|
|
112
|
-
stack.push([closureParams(x), closureParams(y)]);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (x instanceof Held || y instanceof Held) {
|
|
116
|
-
if (!(x instanceof Held && y instanceof Held) || errorDiffers(x.error, y.error, false) !== undefined) return false;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (Array.isArray(x) || Array.isArray(y)) {
|
|
120
|
-
if (!Array.isArray(x) || !Array.isArray(y) || x.length !== y.length) return false;
|
|
121
|
-
for (let index = 0; index < x.length; index += 1) stack.push([x[index], y[index]]);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (!isRecord(x) || !isRecord(y)) return false;
|
|
125
|
-
const keys = Object.keys(x);
|
|
126
|
-
if (keys.length !== Object.keys(y).length) return false;
|
|
127
|
-
for (const key of keys) {
|
|
128
|
-
if (!Object.hasOwn(y, key)) return false;
|
|
129
|
-
stack.push([x[key], y[key]]);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return true;
|
|
113
|
+
return equalWith(a, b, equal === undefined ? sameInTiers : (x, y) => equal(x, y) ?? sameInTiers(x, y));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The rules of `sameValue` for the values that two tiers make again: closures and held errors. */
|
|
117
|
+
function sameInTiers(x: unknown, y: unknown): boolean | undefined {
|
|
118
|
+
if (isClosure(x) || isClosure(y)) return isClosure(x) && isClosure(y) && docEquals(closureParams(x), closureParams(y));
|
|
119
|
+
if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && errorDiffers(x.error, y.error, false) === undefined;
|
|
120
|
+
return undefined;
|
|
133
121
|
}
|
|
134
122
|
|
|
135
123
|
const CLOSURE = /* @__PURE__ */ Symbol.for("coeval.closure");
|
|
@@ -165,7 +153,10 @@ function resultDiffers(a: Result, b: Result, options: AgreeOptions): string | un
|
|
|
165
153
|
return a.ok ? `The plan gives a value, and the tier gives ${codeAndPlace((b as { readonly error: EvalError }).error)}.` : `The plan gives ${codeAndPlace(a.error)}, and the tier gives a value.`;
|
|
166
154
|
}
|
|
167
155
|
|
|
168
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* This function gives the difference of two lists of effects, or `undefined` when they agree (EFFECT.ORDER). When both
|
|
158
|
+
* tiers record the address or the outcome of an effect, these must agree too (AGREE.MOCK).
|
|
159
|
+
*/
|
|
169
160
|
function effectsDiffer(a: readonly Effect[], b: readonly Effect[], options: AgreeOptions): string | undefined {
|
|
170
161
|
if (a.length !== b.length) return `The plan causes ${a.length} effects, and the tier causes ${b.length}.`;
|
|
171
162
|
for (let index = 0; index < a.length; index += 1) {
|
|
@@ -174,6 +165,11 @@ function effectsDiffer(a: readonly Effect[], b: readonly Effect[], options: Agre
|
|
|
174
165
|
if (x.kind !== y.kind || !docEquals(x.place as readonly Key[], y.place as readonly Key[]) || !sameValue(x.payload, y.payload, options.equal)) {
|
|
175
166
|
return `The effect ${index} differs: ${x.kind} at [${x.place.join(", ")}] in the plan, and ${y.kind} at [${y.place.join(", ")}] in the tier.`;
|
|
176
167
|
}
|
|
168
|
+
if (x.address !== undefined && y.address !== undefined && x.address !== y.address) {
|
|
169
|
+
return `The effect ${index} has the address ${x.address} in the plan, and ${y.address} in the tier.`;
|
|
170
|
+
}
|
|
171
|
+
const why = x.outcome !== undefined && y.outcome !== undefined ? resultDiffers(x.outcome, y.outcome, options) : undefined;
|
|
172
|
+
if (why !== undefined) return `The outcome of the effect ${index} differs. ${why}`;
|
|
177
173
|
}
|
|
178
174
|
return undefined;
|
|
179
175
|
}
|
|
@@ -182,21 +178,26 @@ function effectsDiffer(a: readonly Effect[], b: readonly Effect[], options: Agre
|
|
|
182
178
|
* This function evaluates a document with a plan and with other tiers, and compares the results (PLAN.AGREES). Two
|
|
183
179
|
* results agree when they have the same value by `sameValue`, or errors with the same code, and the same place when
|
|
184
180
|
* both have one. The effects must have the same kinds, places and payloads, in the same order. A language gives its
|
|
185
|
-
* own tier with the option `other`.
|
|
181
|
+
* own tier with the option `other`. Each tier gets the mock of the option `perform` (AGREE.MOCK).
|
|
186
182
|
*
|
|
187
183
|
* @param machine - The world and the code of the evaluators.
|
|
188
184
|
* @param doc - The document.
|
|
189
185
|
* @param context - The names and the capabilities at the root.
|
|
190
|
-
* @param options - The tiers, the definitions for the reference, the rules of the comparison
|
|
186
|
+
* @param options - The tiers, the definitions for the reference, the rules of the comparison, the second tier and the mock.
|
|
191
187
|
* @returns The result of the plan, or the first tier that disagrees and the reason.
|
|
192
188
|
*/
|
|
193
189
|
export function agree(machine: Machine, doc: unknown, context: Context = makeContext(), options: AgreeOptions = {}): Agreement {
|
|
194
|
-
const declared =
|
|
190
|
+
const declared = {
|
|
191
|
+
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
192
|
+
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
193
|
+
};
|
|
194
|
+
const mock = options.perform === undefined ? {} : { perform: options.perform };
|
|
195
|
+
const tierInput: TierInput = { ...mock, ...(options.verb === undefined ? {} : { verb: options.verb }) };
|
|
195
196
|
const observe = options.observe;
|
|
196
197
|
observe?.();
|
|
197
198
|
const plan = prepare(machine, doc, declared);
|
|
198
199
|
const effects: Effect[] = [];
|
|
199
|
-
const result = plan.evaluate(context, { effects });
|
|
200
|
+
const result = plan.evaluate(context, { effects, ...mock });
|
|
200
201
|
const saw = observe?.();
|
|
201
202
|
|
|
202
203
|
const differs = (other: Result, otherEffects: readonly Effect[] | undefined): string | undefined => {
|
|
@@ -210,13 +211,13 @@ export function agree(machine: Machine, doc: unknown, context: Context = makeCon
|
|
|
210
211
|
let other: Result;
|
|
211
212
|
let otherEffects: Effect[] = [];
|
|
212
213
|
if (tier === "reference") {
|
|
213
|
-
other = reference(machine, doc, context, { ...declared, effects: otherEffects, ...(options.definitions === undefined ? {} : { definitions: options.definitions }) });
|
|
214
|
+
other = reference(machine, doc, context, { ...declared, ...mock, effects: otherEffects, ...(options.definitions === undefined ? {} : { definitions: options.definitions }) });
|
|
214
215
|
} else if (tier === "traced") {
|
|
215
|
-
const traced = evaluate(machine, doc, context,
|
|
216
|
+
const traced = evaluate(machine, doc, context, { ...declared, ...mock });
|
|
216
217
|
other = traced.result;
|
|
217
218
|
otherEffects = [...traced.effects];
|
|
218
219
|
} else {
|
|
219
|
-
other = plan.evaluate(context, { effects: otherEffects });
|
|
220
|
+
other = plan.evaluate(context, { effects: otherEffects, ...mock });
|
|
220
221
|
}
|
|
221
222
|
const why = differs(other, otherEffects);
|
|
222
223
|
if (why !== undefined) return { ok: false, tier, plan: result, other, reason: why };
|
|
@@ -225,7 +226,7 @@ export function agree(machine: Machine, doc: unknown, context: Context = makeCon
|
|
|
225
226
|
if (options.other !== undefined) {
|
|
226
227
|
let given: Result | TierResult;
|
|
227
228
|
try {
|
|
228
|
-
given = options.other(doc, context);
|
|
229
|
+
given = options.other(doc, context, tierInput);
|
|
229
230
|
} catch (error) {
|
|
230
231
|
given = thrown(error, "the tier");
|
|
231
232
|
}
|