@mark1russell7/coeval 0.4.0 → 0.4.2
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 -3
- package/build/index.d.ts +597 -331
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1195 -41
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +231 -0
- package/src/errors.ts +6 -1
- package/src/evaluate.ts +96 -11
- package/src/formula.ts +10 -8
- package/src/generate.ts +367 -0
- package/src/index.ts +6 -0
- package/src/reference.ts +571 -0
- package/src/static.ts +39 -7
- package/src/stdlib.ts +6 -2
- package/src/world.ts +24 -0
package/src/static.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ask } from "./ladder.ts";
|
|
|
4
4
|
import { type PartSlot, pathMatches, splitParts } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Root, ROOTS, isRef } from "./deref.ts";
|
|
6
6
|
import type { ErrorCode } from "./errors.ts";
|
|
7
|
-
import { type Machine, permits } from "./evaluate.ts";
|
|
7
|
+
import { type Machine, partCaps, permits } from "./evaluate.ts";
|
|
8
8
|
|
|
9
9
|
/** One document that a walk visits. */
|
|
10
10
|
export interface DocVisit {
|
|
@@ -32,6 +32,11 @@ export interface DocVisit {
|
|
|
32
32
|
readonly later: ReadonlySet<string>;
|
|
33
33
|
/** `true` when the position of the document declares an address: a reference there is not a read of a value. */
|
|
34
34
|
readonly address?: true | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The capabilities of the document: the capabilities of the walk, narrowed by the capability rules of the kinds
|
|
37
|
+
* around it (`Kind.caps`). A walk without capabilities gives none here.
|
|
38
|
+
*/
|
|
39
|
+
readonly caps?: readonly Cap[] | undefined;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/** A position that a walk cannot read as a document. */
|
|
@@ -50,6 +55,8 @@ export interface WalkOptions {
|
|
|
50
55
|
readonly declaredType?: string;
|
|
51
56
|
/** The names that the host binds at the root. */
|
|
52
57
|
readonly names?: readonly string[];
|
|
58
|
+
/** The capabilities at the root. With them, each visit gets the capabilities of its document. */
|
|
59
|
+
readonly caps?: readonly Cap[];
|
|
53
60
|
/**
|
|
54
61
|
* This function gets each position that is not a document.
|
|
55
62
|
*
|
|
@@ -169,6 +176,7 @@ class Visit implements DocVisit {
|
|
|
169
176
|
readonly kind: Kind | undefined,
|
|
170
177
|
readonly typePath: readonly Key[] | undefined,
|
|
171
178
|
readonly address: true | undefined,
|
|
179
|
+
readonly caps: readonly Cap[] | undefined,
|
|
172
180
|
) {
|
|
173
181
|
this.#place = place;
|
|
174
182
|
this.#at = at;
|
|
@@ -185,14 +193,15 @@ class Visit implements DocVisit {
|
|
|
185
193
|
|
|
186
194
|
/**
|
|
187
195
|
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
188
|
-
* kinds. A part of a kind that the world does not have is not visited.
|
|
196
|
+
* kinds. A part of a kind that the world does not have is not visited. With capabilities in the options, the walk
|
|
197
|
+
* narrows them for each part by the rules of its kind, as a run does. The walk uses no stack of the host for the
|
|
189
198
|
* depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
|
|
190
199
|
* document. A visit makes the keys of its place and of its path only when a reader asks for them.
|
|
191
200
|
*
|
|
192
201
|
* @param world - The world of the kinds.
|
|
193
202
|
* @param doc - The document, as JSON.
|
|
194
203
|
* @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
|
|
195
|
-
* @param options - The type of a bare root value, the names
|
|
204
|
+
* @param options - The type of a bare root value, the names, the capabilities and a function for the problems.
|
|
196
205
|
*/
|
|
197
206
|
export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => void | "skip", options: WalkOptions = {}): void {
|
|
198
207
|
interface Item {
|
|
@@ -206,6 +215,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
206
215
|
readonly address: boolean;
|
|
207
216
|
readonly slot: readonly Key[];
|
|
208
217
|
readonly parent: DocVisit | undefined;
|
|
218
|
+
readonly caps: readonly Cap[] | undefined;
|
|
209
219
|
}
|
|
210
220
|
const stack: Item[] = [
|
|
211
221
|
{
|
|
@@ -219,6 +229,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
219
229
|
address: false,
|
|
220
230
|
slot: [],
|
|
221
231
|
parent: undefined,
|
|
232
|
+
caps: options.caps,
|
|
222
233
|
},
|
|
223
234
|
];
|
|
224
235
|
while (stack.length > 0) {
|
|
@@ -244,6 +255,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
244
255
|
kind,
|
|
245
256
|
found.ok ? found.path : undefined,
|
|
246
257
|
item.address ? true : undefined,
|
|
258
|
+
item.caps,
|
|
247
259
|
);
|
|
248
260
|
const answer = visit(here);
|
|
249
261
|
if (answer === "skip" || kind === undefined) continue;
|
|
@@ -271,6 +283,7 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
271
283
|
address: slot.address === true,
|
|
272
284
|
slot: slot.path,
|
|
273
285
|
parent: here,
|
|
286
|
+
caps: item.caps === undefined || kind.caps === undefined ? item.caps : partCaps(kind.caps, slot.path, item.caps),
|
|
274
287
|
});
|
|
275
288
|
}
|
|
276
289
|
}
|
|
@@ -380,10 +393,27 @@ export interface CheckOptions {
|
|
|
380
393
|
readonly declaredType?: string;
|
|
381
394
|
/** The names that the host binds at the root. `"any"` does not check the names. The default is no names. */
|
|
382
395
|
readonly names?: readonly string[] | "any";
|
|
383
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* The capabilities at the root. Without them, the check does not check the effects. The check narrows them for
|
|
398
|
+
* each part by the capability rules of the kinds, as a run does.
|
|
399
|
+
*/
|
|
384
400
|
readonly caps?: readonly Cap[];
|
|
385
401
|
}
|
|
386
402
|
|
|
403
|
+
/**
|
|
404
|
+
* This function gives the reason of a `#CAP!`. When a kind around the document narrowed the capabilities, the
|
|
405
|
+
* reason names the nearest such kind.
|
|
406
|
+
*/
|
|
407
|
+
function capReason(v: DocVisit, kind: readonly Key[]): string {
|
|
408
|
+
const text = kind.join(".");
|
|
409
|
+
for (let up = v.parent; up !== undefined; up = up.parent) {
|
|
410
|
+
if (up.caps !== undefined && permits(up.caps, kind)) {
|
|
411
|
+
return `The capabilities do not permit the effect ${text} here. The kind ${typeName(up.doc.type)} around it narrows them for its parts.`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return `The capabilities do not permit the effect ${text}.`;
|
|
415
|
+
}
|
|
416
|
+
|
|
387
417
|
/**
|
|
388
418
|
* This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
|
|
389
419
|
* that an evaluation finds when it reaches them:
|
|
@@ -395,7 +425,7 @@ export interface CheckOptions {
|
|
|
395
425
|
* - `#DEPTH!`: too much nesting.
|
|
396
426
|
* - `#REF!` and `#STEP?`: a bad path.
|
|
397
427
|
* - `#NAME?`: a name that nothing binds.
|
|
398
|
-
* - `#CAP!`: an effect that the capabilities do not permit.
|
|
428
|
+
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
399
429
|
*
|
|
400
430
|
* @param machine - The world and the code of the evaluators.
|
|
401
431
|
* @param doc - The document, as JSON.
|
|
@@ -426,10 +456,11 @@ export function check(machine: Machine, doc: unknown, options: CheckOptions = {}
|
|
|
426
456
|
if (name === undefined || (!has(machine.evaluators, name) && !has(machine.runners, name))) {
|
|
427
457
|
issues.push({ code: "#NOEVAL", place: v.place, reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`, at: v.at });
|
|
428
458
|
}
|
|
429
|
-
|
|
459
|
+
const caps = v.caps;
|
|
460
|
+
if (caps !== undefined && v.kind?.effects !== undefined) {
|
|
430
461
|
const kinds: readonly (readonly Key[])[] = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
431
462
|
for (const kind of kinds) {
|
|
432
|
-
if (!permits(
|
|
463
|
+
if (!permits(caps, kind)) issues.push({ code: "#CAP!", place: v.place, reason: capReason(v, kind), at: v.at });
|
|
433
464
|
}
|
|
434
465
|
}
|
|
435
466
|
const isPath = isRef(v.doc);
|
|
@@ -472,6 +503,7 @@ export function check(machine: Machine, doc: unknown, options: CheckOptions = {}
|
|
|
472
503
|
{
|
|
473
504
|
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
474
505
|
...(Array.isArray(options.names) ? { names: options.names } : {}),
|
|
506
|
+
...(options.caps === undefined ? {} : { caps: options.caps }),
|
|
475
507
|
problem: (problem) => issues.push({ code: problem.code, place: problem.place, reason: problem.reason, at: problem.at }),
|
|
476
508
|
},
|
|
477
509
|
);
|
package/src/stdlib.ts
CHANGED
|
@@ -69,18 +69,21 @@ export const STD_WORLD: World = {
|
|
|
69
69
|
pure: {
|
|
70
70
|
verbs: { eval: "control.pure", view: "view.op" },
|
|
71
71
|
parts: { shape: "list" },
|
|
72
|
+
caps: [{ rule: "mask" }],
|
|
72
73
|
summary: "It evaluates its part without capabilities, thus an effect in it fails.",
|
|
73
74
|
},
|
|
74
75
|
log: {
|
|
75
76
|
verbs: { eval: "effect.log", view: "view.op" },
|
|
76
77
|
parts: { shape: "list", of: "text" },
|
|
77
78
|
side: "run",
|
|
79
|
+
effects: ["log"],
|
|
78
80
|
summary: "It writes the result of its part to the log. This is an effect.",
|
|
79
81
|
},
|
|
80
82
|
write: {
|
|
81
83
|
verbs: { eval: "effect.write", view: "view.op" },
|
|
82
84
|
parts: { shape: "record", fields: ["value"] },
|
|
83
85
|
side: "run",
|
|
86
|
+
effects: ["write"],
|
|
84
87
|
summary: "It writes the result of its part to the store, at its key. This is an effect.",
|
|
85
88
|
},
|
|
86
89
|
},
|
|
@@ -269,10 +272,11 @@ export const STD_RUNNERS: Readonly<Record<string, Runner>> = {
|
|
|
269
272
|
}
|
|
270
273
|
return ok(last);
|
|
271
274
|
},
|
|
272
|
-
|
|
275
|
+
// The contract of pure masks the capabilities of its part, and the kernel applies it. Thus the runner does not.
|
|
276
|
+
"control.pure": function* ({ parts }) {
|
|
273
277
|
const only = parts[0];
|
|
274
278
|
if (only === undefined) return fail("#VALUE!", "pure needs one part.");
|
|
275
|
-
return yield* only.evaluate(
|
|
279
|
+
return yield* only.evaluate();
|
|
276
280
|
},
|
|
277
281
|
"control.if": function* ({ parts }) {
|
|
278
282
|
const test = parts.find((part) => part.key === "test");
|
package/src/world.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, type TypeRef, field, isRecord, typePath } from "./doc.ts";
|
|
2
|
+
import type { Cap } from "./deref.ts";
|
|
2
3
|
|
|
3
4
|
/** The two sides of evaluation. `derive` is pure. `run` causes effects in order. */
|
|
4
5
|
export type Side = "derive" | "run";
|
|
@@ -69,6 +70,22 @@ export interface ScopeRule {
|
|
|
69
70
|
readonly sequential?: boolean;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* A capability rule of a kind: what the kind gives some of its parts from the capabilities of its document. A rule
|
|
75
|
+
* only narrows. A part does not get a capability that its document does not have (CAP.NARROW).
|
|
76
|
+
*
|
|
77
|
+
* - `same`: the parts get the capabilities of the document. This is the default.
|
|
78
|
+
* - `mask`: the parts get no capabilities, as the part of `pure`.
|
|
79
|
+
* - `only`: the parts get only the effects that the capabilities of the document and the list `caps` both permit.
|
|
80
|
+
*
|
|
81
|
+
* `in` names the parts that the rule covers, as patterns of their paths in the value. The key `"*"` matches each
|
|
82
|
+
* key. A rule without `in` covers each part.
|
|
83
|
+
*/
|
|
84
|
+
export type CapsRule =
|
|
85
|
+
| { readonly rule: "same"; readonly in?: readonly (readonly Key[])[] }
|
|
86
|
+
| { readonly rule: "mask"; readonly in?: readonly (readonly Key[])[] }
|
|
87
|
+
| { readonly rule: "only"; readonly caps: readonly Cap[]; readonly in?: readonly (readonly Key[])[] };
|
|
88
|
+
|
|
72
89
|
/** A kind gives the name of an evaluator for each verb, and the contract of its value. */
|
|
73
90
|
export interface Kind {
|
|
74
91
|
readonly verbs: Readonly<Record<string, string>>;
|
|
@@ -76,6 +93,13 @@ export interface Kind {
|
|
|
76
93
|
readonly parts?: Shape;
|
|
77
94
|
/** The names that the kind binds for its parts. */
|
|
78
95
|
readonly scope?: readonly ScopeRule[];
|
|
96
|
+
/**
|
|
97
|
+
* The capabilities that the kind gives its parts, as rules. A part that no rule covers gets the capabilities of
|
|
98
|
+
* the document. A part that several rules cover gets only what each rule permits. The static kit reads the rules,
|
|
99
|
+
* and the kernel narrows the context of each part by them. Thus an evaluator does not narrow the capabilities
|
|
100
|
+
* itself.
|
|
101
|
+
*/
|
|
102
|
+
readonly caps?: readonly CapsRule[];
|
|
79
103
|
/** The side of the kind. A kind of the run side causes effects. The default is `derive`. */
|
|
80
104
|
readonly side?: Side;
|
|
81
105
|
/**
|