@mark1russell7/coeval 0.18.0 → 0.19.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 +4 -4
- package/build/index.d.ts +263 -20
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2686 -1379
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codec.ts +30 -10
- package/src/durable.ts +17 -5
- package/src/facts.ts +55 -19
- package/src/formula.ts +45 -1
- package/src/index.ts +16 -2
- package/src/instances.ts +637 -0
- package/src/reads.ts +186 -2
- package/src/static.ts +357 -50
- package/src/stdlib.ts +127 -6
- package/src/types.ts +6 -2
- package/src/typing.ts +143 -3
- package/src/world.ts +49 -0
package/src/reads.ts
CHANGED
|
@@ -22,8 +22,41 @@ export type ReadsCheck =
|
|
|
22
22
|
readonly read: Read;
|
|
23
23
|
/** What the path of that read reaches in the new context. */
|
|
24
24
|
readonly now: Result;
|
|
25
|
+
/**
|
|
26
|
+
* `true` when the read reaches the same thing by `sameResult`, but the print of that thing changed
|
|
27
|
+
* (READS.IN-PLACE). Only the option `prints` gives this mark.
|
|
28
|
+
*/
|
|
29
|
+
readonly inPlace?: true;
|
|
25
30
|
};
|
|
26
31
|
|
|
32
|
+
/** The options of `printReads` (READS.IN-PLACE, D112). */
|
|
33
|
+
export interface PrintOptions {
|
|
34
|
+
/**
|
|
35
|
+
* The most entries in the print of one value. Each value, each field name and each mark counts as one entry. A
|
|
36
|
+
* larger value gets no print, and `verifyReads` then gives its read as changed. The default is 10000.
|
|
37
|
+
*/
|
|
38
|
+
readonly limit?: number;
|
|
39
|
+
/**
|
|
40
|
+
* The stamp of the host for an object, for example the version of a host thing. A stamp other than `undefined`
|
|
41
|
+
* takes the place of the fields of that object in the print. A stamp compares with `Object.is`.
|
|
42
|
+
*
|
|
43
|
+
* @param value - An object of the value of a read.
|
|
44
|
+
* @returns The stamp, or `undefined` for the walk of the fields.
|
|
45
|
+
*/
|
|
46
|
+
readonly stamp?: (value: object) => unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The prints of a list of reads, from `printReads`. `verifyReads` takes them with the option `prints`. The prints
|
|
51
|
+
* keep their options, thus the verification walks each value with the same rules.
|
|
52
|
+
*/
|
|
53
|
+
export interface ReadPrints {
|
|
54
|
+
/** The print of each read, at the index of the read. A read without an object in its value has no print. */
|
|
55
|
+
readonly prints: readonly (readonly unknown[] | undefined)[];
|
|
56
|
+
/** The options of the prints. */
|
|
57
|
+
readonly options: PrintOptions;
|
|
58
|
+
}
|
|
59
|
+
|
|
27
60
|
/** The options of `verifyReads`. */
|
|
28
61
|
export interface VerifyOptions {
|
|
29
62
|
/**
|
|
@@ -35,11 +68,154 @@ export interface VerifyOptions {
|
|
|
35
68
|
* @returns `true` when the two things are the same, or `undefined` for the rules of `sameResult`.
|
|
36
69
|
*/
|
|
37
70
|
readonly equal?: (a: unknown, b: unknown) => boolean | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* The prints of the reads, from `printReads` after the evaluation (READS.IN-PLACE). When the prints are given, a
|
|
73
|
+
* read that reaches the same thing also compares its print. Thus a change in place of a host value is a changed
|
|
74
|
+
* read. Without the prints, the verification does no work for them.
|
|
75
|
+
*/
|
|
76
|
+
readonly prints?: ReadPrints;
|
|
38
77
|
}
|
|
39
78
|
|
|
40
79
|
/** One answer serves each list whose reads are all the same. */
|
|
41
80
|
const SAME: ReadsCheck = /* @__PURE__ */ Object.freeze({ same: true });
|
|
42
81
|
|
|
82
|
+
/** The default limit of the entries in the print of one value. */
|
|
83
|
+
const PRINT_LIMIT = 10_000;
|
|
84
|
+
|
|
85
|
+
/** The marks of a print. Each mark is a private object, thus no value of a host is the same as a mark. */
|
|
86
|
+
const OBJECT = /* @__PURE__ */ Object.freeze({ mark: "object" });
|
|
87
|
+
const LIST = /* @__PURE__ */ Object.freeze({ mark: "list" });
|
|
88
|
+
const DATE = /* @__PURE__ */ Object.freeze({ mark: "date" });
|
|
89
|
+
const MAP = /* @__PURE__ */ Object.freeze({ mark: "map" });
|
|
90
|
+
const SET = /* @__PURE__ */ Object.freeze({ mark: "set" });
|
|
91
|
+
const SEEN = /* @__PURE__ */ Object.freeze({ mark: "seen" });
|
|
92
|
+
const STAMP = /* @__PURE__ */ Object.freeze({ mark: "stamp" });
|
|
93
|
+
|
|
94
|
+
/** The print of a value that the walk cannot finish: it is too large, or the host threw. It is not the same as any print. */
|
|
95
|
+
const NO_PRINT: readonly unknown[] = /* @__PURE__ */ Object.freeze([]);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* This function gives the print of a value: a flat list of its parts, in the order of a walk (D112). Two prints are
|
|
99
|
+
* the same when they have the same length, and each entry is the same by `Object.is`.
|
|
100
|
+
*
|
|
101
|
+
* - An object starts with a mark and its prototype. A list adds its length, a date its time, and a map or a set
|
|
102
|
+
* its size and its entries. Then come the count of the own enumerable fields, each name and the kind of each field.
|
|
103
|
+
* - A field with a getter gives the getter function. The walk does not start host code of a field.
|
|
104
|
+
* - A primitive value and a function are entries. A function compares by its identity.
|
|
105
|
+
* - An object that the walk already visited gives the mark `SEEN` and its number. Thus a cycle ends.
|
|
106
|
+
* - A value with more than `limit` entries, or an exception of the host, gives `NO_PRINT`.
|
|
107
|
+
*
|
|
108
|
+
* @param value - The value of a read.
|
|
109
|
+
* @param options - The limit and the stamp of the host.
|
|
110
|
+
* @returns The print.
|
|
111
|
+
*/
|
|
112
|
+
function printOf(value: unknown, options: PrintOptions): readonly unknown[] {
|
|
113
|
+
const limit = options.limit ?? PRINT_LIMIT;
|
|
114
|
+
const stamp = options.stamp;
|
|
115
|
+
const out: unknown[] = [];
|
|
116
|
+
const ids = new Map<object, number>();
|
|
117
|
+
const stack: unknown[] = [value];
|
|
118
|
+
try {
|
|
119
|
+
while (stack.length > 0) {
|
|
120
|
+
if (out.length > limit) return NO_PRINT;
|
|
121
|
+
const x = stack.pop();
|
|
122
|
+
if (typeof x !== "object" || x === null) {
|
|
123
|
+
out.push(x);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const id = ids.get(x);
|
|
127
|
+
if (id !== undefined) {
|
|
128
|
+
out.push(SEEN, id);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
ids.set(x, ids.size);
|
|
132
|
+
const stamped = stamp?.(x);
|
|
133
|
+
if (stamped !== undefined) {
|
|
134
|
+
out.push(STAMP, stamped);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const children: unknown[] = [];
|
|
138
|
+
out.push(OBJECT, Object.getPrototypeOf(x));
|
|
139
|
+
if (Array.isArray(x)) out.push(LIST, x.length);
|
|
140
|
+
else if (x instanceof Date) out.push(DATE, Date.prototype.getTime.call(x));
|
|
141
|
+
else if (x instanceof Map) {
|
|
142
|
+
out.push(MAP, x.size);
|
|
143
|
+
if (out.length + 2 * x.size > limit) return NO_PRINT;
|
|
144
|
+
for (const [key, item] of Map.prototype.entries.call(x) as IterableIterator<[unknown, unknown]>) children.push(key, item);
|
|
145
|
+
} else if (x instanceof Set) {
|
|
146
|
+
out.push(SET, x.size);
|
|
147
|
+
if (out.length + x.size > limit) return NO_PRINT;
|
|
148
|
+
for (const item of Set.prototype.values.call(x) as IterableIterator<unknown>) children.push(item);
|
|
149
|
+
}
|
|
150
|
+
const keys = Object.keys(x);
|
|
151
|
+
if (out.length + 3 * keys.length > limit) return NO_PRINT;
|
|
152
|
+
out.push(keys.length);
|
|
153
|
+
for (const key of keys) {
|
|
154
|
+
const field = Object.getOwnPropertyDescriptor(x, key);
|
|
155
|
+
// A field that a proxy hides between `keys` and its descriptor reads as a field without a value.
|
|
156
|
+
const data = field === undefined || "value" in field;
|
|
157
|
+
out.push(key, data);
|
|
158
|
+
children.push(field === undefined ? undefined : "value" in field ? field.value : field.get);
|
|
159
|
+
}
|
|
160
|
+
// The stack gives the last entry first, thus the children go on it in the reverse order.
|
|
161
|
+
for (let index = children.length - 1; index >= 0; index -= 1) stack.push(children[index]);
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
return NO_PRINT;
|
|
165
|
+
}
|
|
166
|
+
return out.length > limit ? NO_PRINT : out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** This function tells if two prints are the same. `NO_PRINT` is not the same as any print, also not as itself. */
|
|
170
|
+
function samePrint(a: readonly unknown[], b: readonly unknown[]): boolean {
|
|
171
|
+
if (a === NO_PRINT || b === NO_PRINT || a.length !== b.length) return false;
|
|
172
|
+
for (let index = 0; index < a.length; index += 1) if (!Object.is(a[index], b[index])) return false;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** This function gives the print of the value of a result, or `undefined` when the value holds no object. */
|
|
177
|
+
function printOfResult(result: Result, options: PrintOptions, cache: Map<object, readonly unknown[]>): readonly unknown[] | undefined {
|
|
178
|
+
if (!result.ok || typeof result.value !== "object" || result.value === null) return undefined;
|
|
179
|
+
const value = result.value;
|
|
180
|
+
let print = cache.get(value);
|
|
181
|
+
if (print === undefined) {
|
|
182
|
+
print = printOf(value, options);
|
|
183
|
+
cache.set(value, print);
|
|
184
|
+
}
|
|
185
|
+
return print;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* This function gives the prints of the values of a list of reads (READS.IN-PLACE, D112). A host starts it after
|
|
190
|
+
* the evaluation, and it gives the prints to `verifyReads` with the option `prints`.
|
|
191
|
+
*
|
|
192
|
+
* - The print of a value walks its lists, its plain records and the own enumerable fields of each other object. A
|
|
193
|
+
* date adds its time, and a map or a set adds its entries.
|
|
194
|
+
* - A read that is not free, a read that reached an error and a read with a primitive value get no print.
|
|
195
|
+
* - A cycle ends, because the walk visits each object one time.
|
|
196
|
+
* - A value with more entries than the limit gets no print. `verifyReads` then gives that read as changed.
|
|
197
|
+
*
|
|
198
|
+
* The walk does not see the state in a closure or a private field. It does not see a field with a symbol name, a
|
|
199
|
+
* field that is not enumerable or the value of a getter. A host gives a stamp for such an object, for example its version.
|
|
200
|
+
*
|
|
201
|
+
* @param reads - The reads of the evaluation, from the option `reads`.
|
|
202
|
+
* @param options - The limit and the stamp of the host.
|
|
203
|
+
* @returns The prints, for the option `prints` of `verifyReads`.
|
|
204
|
+
*/
|
|
205
|
+
export function printReads(reads: readonly Read[], options: PrintOptions = {}): ReadPrints {
|
|
206
|
+
const cache = new Map<object, readonly unknown[]>();
|
|
207
|
+
const prints = reads.map((read) => (read.free ? printOfResult(read.reached, options, cache) : undefined));
|
|
208
|
+
return { prints, options };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** This function tells if the print of a read changed, when the read has a print. */
|
|
212
|
+
function changedInPlace(prints: ReadPrints, index: number, now: Result, cache: Map<object, readonly unknown[]>): boolean {
|
|
213
|
+
const old = prints.prints[index];
|
|
214
|
+
if (old === undefined) return false;
|
|
215
|
+
const print = printOfResult(now, prints.options, cache);
|
|
216
|
+
return print === undefined || !samePrint(old, print);
|
|
217
|
+
}
|
|
218
|
+
|
|
43
219
|
/** This function tells if two lists of hops are the same: the same steps, rungs and things, in order. */
|
|
44
220
|
function sameHops(a: readonly Hop[], b: readonly Hop[] | undefined, equal: VerifyOptions["equal"]): boolean {
|
|
45
221
|
if (b === undefined || a.length !== b.length) return false;
|
|
@@ -78,18 +254,22 @@ function heldStop(root: Context, path: readonly unknown[]): Result | undefined {
|
|
|
78
254
|
* extension of a profile can read outside `resolve`. The schedule asks `factsOf` first.
|
|
79
255
|
*
|
|
80
256
|
* The values of the context must not change in place, as PLAN.CACHE says for the documents. A value that changed in
|
|
81
|
-
* place is the same object, thus
|
|
257
|
+
* place is the same object, thus `sameResult` cannot see the change. The option `prints` finds such a change
|
|
258
|
+
* (READS.IN-PLACE). The prints come from `printReads` after the evaluation, and the answer then has the mark `inPlace`.
|
|
82
259
|
*
|
|
83
260
|
* @param target - The machine of the evaluation, or its plan.
|
|
84
261
|
* @param reads - The reads of the evaluation, from the option `reads`.
|
|
85
262
|
* @param context - The new context.
|
|
86
|
-
* @param options - The rule of the host for two things.
|
|
263
|
+
* @param options - The rule of the host for two things, and the prints of the reads.
|
|
87
264
|
* @returns `{ same: true }` when each free read reaches the same thing, or else the first changed read.
|
|
88
265
|
*/
|
|
89
266
|
export function verifyReads(target: Machine | Plan, reads: readonly Read[], context: Context, options: VerifyOptions = {}): ReadsCheck {
|
|
90
267
|
const machine = "evaluators" in target ? target : target.machine;
|
|
91
268
|
const root = rootContext(machine, context);
|
|
92
269
|
const equal = options.equal;
|
|
270
|
+
const prints = options.prints;
|
|
271
|
+
// The prints of the new values, made only with the option `prints`. Two reads of one object share its print.
|
|
272
|
+
let printed: Map<object, readonly unknown[]> | undefined;
|
|
93
273
|
for (let index = 0; index < reads.length; index += 1) {
|
|
94
274
|
const read = reads[index] as Read;
|
|
95
275
|
if (!read.free) continue;
|
|
@@ -100,6 +280,7 @@ export function verifyReads(target: Machine | Plan, reads: readonly Read[], cont
|
|
|
100
280
|
const now = keptAgain(machine, root, read.path);
|
|
101
281
|
if (now === undefined) return { same: false, index, read, now: fail("#N/A", "The host does not know the value of the kept read yet.") };
|
|
102
282
|
if (!sameResult(read.reached, now, equal)) return { same: false, index, read, now };
|
|
283
|
+
if (prints !== undefined && changedInPlace(prints, index, now, (printed ??= new Map()))) return { same: false, index, read, now, inPlace: true };
|
|
103
284
|
continue;
|
|
104
285
|
}
|
|
105
286
|
const asked = read.hops !== undefined;
|
|
@@ -107,6 +288,9 @@ export function verifyReads(target: Machine | Plan, reads: readonly Read[], cont
|
|
|
107
288
|
if (!sameResult(read.reached, now.reached, equal) || (read.hops !== undefined && !sameHops(read.hops, now.hops, equal))) {
|
|
108
289
|
return { same: false, index, read, now: now.reached };
|
|
109
290
|
}
|
|
291
|
+
if (prints !== undefined && changedInPlace(prints, index, now.reached, (printed ??= new Map()))) {
|
|
292
|
+
return { same: false, index, read, now: now.reached, inPlace: true };
|
|
293
|
+
}
|
|
110
294
|
}
|
|
111
295
|
return SAME;
|
|
112
296
|
}
|