@mark1russell7/coeval 0.6.3 → 0.7.1
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 +63 -5
- package/build/index.d.ts +1771 -254
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5809 -511
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +15 -3
- package/src/codec.ts +185 -0
- package/src/conform.ts +1068 -0
- package/src/deref.ts +284 -47
- package/src/durable.ts +238 -0
- package/src/effects.ts +110 -0
- package/src/errors.ts +2 -1
- package/src/evaluate.ts +480 -113
- package/src/facts.ts +252 -0
- package/src/formula.ts +450 -42
- package/src/graph.ts +399 -0
- package/src/index.ts +46 -0
- 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 +23 -9
- package/src/schedule.ts +436 -0
- package/src/static.ts +266 -29
- package/src/stdlib.ts +24 -7
- package/src/syntax.ts +846 -0
- package/src/types.ts +768 -0
- package/src/typing.ts +390 -0
- package/src/world.ts +63 -9
package/src/facts.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { type Json, type Key, isEnvelope, isKey } from "./doc.ts";
|
|
2
|
+
import { type Kind, type LazyRule, type Side, type World, lookupKind } from "./world.ts";
|
|
3
|
+
import { answerOf } from "./ladder.ts";
|
|
4
|
+
import { pathMatches, splitParts } from "./parts.ts";
|
|
5
|
+
import { type Root, ROOTS, isRef } from "./deref.ts";
|
|
6
|
+
import type { Machine } from "./evaluate.ts";
|
|
7
|
+
import { type DocVisit, declaredEffects, walk } from "./static.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One read of a document: a free reference, with its marks (FACTS.READS). A plan resolves the path of the reference
|
|
11
|
+
* when it evaluates the reference. The static read covers each path that the plan resolves there (FACTS.SOUND).
|
|
12
|
+
*/
|
|
13
|
+
export interface StaticRead {
|
|
14
|
+
/** The root of the path. */
|
|
15
|
+
readonly root: Root;
|
|
16
|
+
/** The keys after the root, up to the first step that is not a key. */
|
|
17
|
+
readonly keys: readonly Key[];
|
|
18
|
+
/**
|
|
19
|
+
* `true` when each step after the root is a key. Then the plan resolves this path and no other. A step document,
|
|
20
|
+
* for example `nearest`, makes it `false`: the plan resolves a path that starts with the keys.
|
|
21
|
+
*/
|
|
22
|
+
readonly exact: boolean;
|
|
23
|
+
/** `true` when only a lazy part reaches the reference. An evaluation can then give a value without this read (FACTS.LAZY). */
|
|
24
|
+
readonly lazy: boolean;
|
|
25
|
+
/** `true` when the position of the reference declares an address. The read gives what the path reaches, not its value. */
|
|
26
|
+
readonly address: boolean;
|
|
27
|
+
/** The path of the reference: its root and its steps. */
|
|
28
|
+
readonly path: readonly Json[];
|
|
29
|
+
/** The keys from the root document to the reference document, through the parts. */
|
|
30
|
+
readonly place: readonly Key[];
|
|
31
|
+
/** The keys from the root JSON value to the reference document. */
|
|
32
|
+
readonly at: readonly Key[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The static facts of one document (D24). They depend only on the document and the machine, not on a context. */
|
|
36
|
+
export interface DocFacts {
|
|
37
|
+
/** The free references, in the order of the document (FACTS.READS). A reference to a name that a kind binds is not here. */
|
|
38
|
+
readonly reads: readonly StaticRead[];
|
|
39
|
+
/** `"run"` when one kind of the document is on the run side (FACTS.SIDE). */
|
|
40
|
+
readonly side: Side;
|
|
41
|
+
/** The kinds of the effects that the contracts declare, as texts with dots, each one time (FACTS.EFFECTS). */
|
|
42
|
+
readonly effects: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* `true` when each kind of the document declares `pure`, and the document has no effect and is not volatile. Then
|
|
45
|
+
* its result depends only on what its reads give (FACTS.PURE).
|
|
46
|
+
*/
|
|
47
|
+
readonly pure: boolean;
|
|
48
|
+
/** `true` when the result can change while each read gives the same thing, for example with a clock (FACTS.VOLATILE). */
|
|
49
|
+
readonly volatile: boolean;
|
|
50
|
+
/** The number of documents in the document: a hint of the cost of an evaluation. */
|
|
51
|
+
readonly size: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The options of `factsOf`. */
|
|
55
|
+
export interface FactsOptions {
|
|
56
|
+
/** The type of a bare root value. */
|
|
57
|
+
readonly declaredType?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The verb whose evaluators the facts describe, as in `prepare`. The default is `eval`. The contract of a kind
|
|
60
|
+
* describes the code of `eval` only. For another verb, only the hook `facts` describes the code (FACTS.VERB).
|
|
61
|
+
*/
|
|
62
|
+
readonly verb?: string;
|
|
63
|
+
/**
|
|
64
|
+
* This function tells if a read reaches a volatile value of the host, for example a function that reads a clock.
|
|
65
|
+
* A read that it marks makes the document volatile (FACTS.VOLATILE).
|
|
66
|
+
*
|
|
67
|
+
* @param read - The read.
|
|
68
|
+
* @returns `true` when the read reaches a volatile value.
|
|
69
|
+
*/
|
|
70
|
+
readonly volatile?: (read: StaticRead) => boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The lazy rules that cover each part: the rules of a document whose evaluator the contract does not describe. */
|
|
74
|
+
const EACH = "each";
|
|
75
|
+
|
|
76
|
+
/** An empty list. One frozen list serves each empty list of the facts, thus a store of many facts keeps less (PLAN.LEAN). */
|
|
77
|
+
const NONE: readonly never[] = /* @__PURE__ */ Object.freeze([]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* This function gives a list of the facts with no free room. A list that grows by `push` keeps room for more items,
|
|
81
|
+
* thus a copy of a short list is smaller.
|
|
82
|
+
*/
|
|
83
|
+
function tight<T>(list: readonly T[]): readonly T[] {
|
|
84
|
+
return list.length === 0 ? NONE : list.slice();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** What a document gives to its parts: its own lazy mark, and the lazy rules of its kind for the parts. */
|
|
88
|
+
interface Above {
|
|
89
|
+
readonly lazy: boolean;
|
|
90
|
+
readonly rules: readonly LazyRule[] | typeof EACH | undefined;
|
|
91
|
+
/** The position of each part among the parts that a rule `after` covers. It is made at the first need. */
|
|
92
|
+
positions?: Map<LazyRule, Map<string, number>>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* This function gives the name of the evaluator that the ladder finds for a verb (VERB.LADDER). It also tells if
|
|
97
|
+
* the contract of the kind describes that code. The contract describes the code of its kind for its verb `contract`.
|
|
98
|
+
* An own entry for another evaluator has code that the contract does not describe.
|
|
99
|
+
*/
|
|
100
|
+
function codeOf(
|
|
101
|
+
world: World,
|
|
102
|
+
own: Readonly<Record<string, string>> | undefined,
|
|
103
|
+
kind: Kind,
|
|
104
|
+
verb: string,
|
|
105
|
+
contract: string,
|
|
106
|
+
): { readonly name: string | undefined; readonly described: boolean } {
|
|
107
|
+
const found = answerOf(world, own, kind, verb);
|
|
108
|
+
if (verb !== contract) return { name: found?.name, described: false };
|
|
109
|
+
if (found?.rung !== "own") return { name: found?.name, described: true };
|
|
110
|
+
return { name: found.name, described: found.name === answerOf(world, undefined, kind, verb)?.name };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** This function gives the position of a part among the parts that a rule covers, by the split of its document. */
|
|
114
|
+
function positionOf(above: Above, parent: DocVisit, rule: LazyRule, slot: readonly Key[]): number {
|
|
115
|
+
const positions = (above.positions ??= new Map());
|
|
116
|
+
let table = positions.get(rule);
|
|
117
|
+
if (table === undefined) {
|
|
118
|
+
table = new Map();
|
|
119
|
+
const split = splitParts(parent.kind?.parts, parent.doc.value);
|
|
120
|
+
let count = 0;
|
|
121
|
+
for (const part of split.ok ? split.parts : []) {
|
|
122
|
+
if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(part.path, pattern))) continue;
|
|
123
|
+
table.set(JSON.stringify(part.path), count);
|
|
124
|
+
count += 1;
|
|
125
|
+
}
|
|
126
|
+
positions.set(rule, table);
|
|
127
|
+
}
|
|
128
|
+
return table.get(JSON.stringify(slot)) ?? 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** This function tells if the lazy rules of a document cover one of its parts. */
|
|
132
|
+
function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean {
|
|
133
|
+
const rules = above.rules;
|
|
134
|
+
if (rules === undefined) return false;
|
|
135
|
+
if (rules === EACH) return true;
|
|
136
|
+
for (const rule of rules) {
|
|
137
|
+
if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(slot, pattern))) continue;
|
|
138
|
+
const after = rule.after ?? 0;
|
|
139
|
+
if (after <= 0 || positionOf(above, parent, rule, slot) >= after) return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* This function gives the static facts of a document, for a schedule (R4 P2, D24). It walks the document one time,
|
|
146
|
+
* by the contracts of its kinds:
|
|
147
|
+
*
|
|
148
|
+
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
149
|
+
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`.
|
|
150
|
+
* - `side` and `effects`: from the contract of each kind.
|
|
151
|
+
* - `pure`: each kind declares `pure`, and the document has no effect and is not volatile.
|
|
152
|
+
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
153
|
+
*
|
|
154
|
+
* The hook `facts` of the machine answers for a name that a fallback kind covers, for example an operation. Its
|
|
155
|
+
* answer wins over the contract of the kind. The contract of a kind does not describe the evaluator of an own
|
|
156
|
+
* entry. Thus a document with an own entry for another evaluator is not pure, and each of its parts is lazy. The
|
|
157
|
+
* hook can still answer for that evaluator.
|
|
158
|
+
*
|
|
159
|
+
* The option `verb` names the verb whose code the facts describe (FACTS.VERB). A contract describes the code of
|
|
160
|
+
* `eval`, thus the rule of an own entry applies to each document for another verb. The side and the effects come
|
|
161
|
+
* from the contracts for each verb.
|
|
162
|
+
*
|
|
163
|
+
* A read is one resolution of its path. A reference that the resolution reaches and follows is a part of that read
|
|
164
|
+
* (DEREF.COLLAPSE), not a separate read. The facts depend only on the document and the machine. Thus a host computes
|
|
165
|
+
* them again only after an edit of the document or of the machine.
|
|
166
|
+
*
|
|
167
|
+
* @param machine - The world, the code and the hook `facts`.
|
|
168
|
+
* @param doc - The document, as JSON.
|
|
169
|
+
* @param options - The type of a bare root value, the verb and the volatile reads of the host.
|
|
170
|
+
* @returns The facts.
|
|
171
|
+
*/
|
|
172
|
+
export function factsOf(machine: Machine, doc: unknown, options: FactsOptions = {}): DocFacts {
|
|
173
|
+
const world = machine.world;
|
|
174
|
+
const hook = machine.facts;
|
|
175
|
+
const verb = options.verb ?? "eval";
|
|
176
|
+
const reads: StaticRead[] = [];
|
|
177
|
+
const effects: string[] = [];
|
|
178
|
+
const above = new Map<DocVisit, Above>();
|
|
179
|
+
let side: Side = "derive";
|
|
180
|
+
let pure = true;
|
|
181
|
+
let volatile = false;
|
|
182
|
+
let size = 0;
|
|
183
|
+
const note = (isPure: boolean | undefined, isVolatile: boolean | undefined): void => {
|
|
184
|
+
if (isPure !== true) pure = false;
|
|
185
|
+
if (isVolatile === true) volatile = true;
|
|
186
|
+
};
|
|
187
|
+
walk(
|
|
188
|
+
world,
|
|
189
|
+
doc,
|
|
190
|
+
(v) => {
|
|
191
|
+
size += 1;
|
|
192
|
+
const up = v.parent === undefined ? undefined : above.get(v.parent);
|
|
193
|
+
const lazy = up !== undefined && v.parent !== undefined && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
194
|
+
const kind = v.kind;
|
|
195
|
+
if (kind === undefined) {
|
|
196
|
+
// A kind that the world does not have gives #KIND?, and the walk does not visit its value.
|
|
197
|
+
pure = false;
|
|
198
|
+
above.set(v, { lazy, rules: undefined });
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const code = codeOf(world, v.declared ? undefined : v.doc.own, kind, verb, "eval");
|
|
202
|
+
const answer = code.name === undefined ? undefined : hook?.(v.typePath ?? [], code.name);
|
|
203
|
+
if (code.described) {
|
|
204
|
+
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
205
|
+
above.set(v, { lazy, rules: answer?.lazy ?? kind.lazy });
|
|
206
|
+
} else {
|
|
207
|
+
// The contract of the kind does not describe this code. Only the hook can.
|
|
208
|
+
note(answer?.pure, answer?.volatile);
|
|
209
|
+
above.set(v, { lazy, rules: answer?.lazy ?? EACH });
|
|
210
|
+
}
|
|
211
|
+
if (kind.side === "run") side = "run";
|
|
212
|
+
// check and effectsOf read the declared effects with the same function (D27).
|
|
213
|
+
for (const keys of declaredEffects(v)) {
|
|
214
|
+
const text = keys.join(".");
|
|
215
|
+
if (!effects.includes(text)) effects.push(text);
|
|
216
|
+
}
|
|
217
|
+
if (!isRef(v.doc)) return undefined;
|
|
218
|
+
const path = v.doc.value;
|
|
219
|
+
const root = path[0];
|
|
220
|
+
if (typeof root !== "string" || !(ROOTS as readonly string[]).includes(root)) return undefined;
|
|
221
|
+
let exact = true;
|
|
222
|
+
const keys: Key[] = [];
|
|
223
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
224
|
+
const step = path[index];
|
|
225
|
+
if (exact && isKey(step)) {
|
|
226
|
+
keys.push(step);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
exact = false;
|
|
230
|
+
// A step document runs the walker that the ladder finds for the verb step.
|
|
231
|
+
const found = isEnvelope(step) ? lookupKind(world, step.type) : undefined;
|
|
232
|
+
if (found?.ok !== true) {
|
|
233
|
+
pure = false;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const walker = codeOf(world, (step as { readonly own?: Readonly<Record<string, string>> }).own, found.kind, "step", "step");
|
|
237
|
+
const stepAnswer = walker.name === undefined ? undefined : hook?.(found.path, walker.name);
|
|
238
|
+
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
239
|
+
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
240
|
+
}
|
|
241
|
+
const first = path[1];
|
|
242
|
+
// A reference to a name that a kind around it binds reads inside the document: it is not a read.
|
|
243
|
+
if (root === "names" && isKey(first) && v.names.has(String(first))) return undefined;
|
|
244
|
+
const read: StaticRead = { root: root as Root, keys: tight(keys), exact, lazy, address: v.address === true, path, place: tight(v.place), at: tight(v.at) };
|
|
245
|
+
reads.push(read);
|
|
246
|
+
if (options.volatile?.(read) === true) volatile = true;
|
|
247
|
+
return undefined;
|
|
248
|
+
},
|
|
249
|
+
options.declaredType === undefined ? {} : { declaredType: options.declaredType },
|
|
250
|
+
);
|
|
251
|
+
return { reads: tight(reads), side, effects: tight(effects), pure: pure && !volatile && effects.length === 0 && side === "derive", volatile, size };
|
|
252
|
+
}
|