@mark1russell7/coeval 0.22.0 → 0.24.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 +24 -5
- package/build/index.d.ts +442 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5848 -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 +663 -71
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/effects.ts +1 -1
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +188 -24
- package/src/formula.ts +60 -17
- package/src/index.ts +15 -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 +122 -2
- package/src/schedule.ts +286 -45
- package/src/static.ts +59 -16
- package/src/stdlib.ts +188 -6
- package/src/syntax.ts +637 -48
- package/src/types.ts +2 -1
- package/src/world.ts +31 -4
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,
|
|
@@ -157,6 +161,7 @@ export {
|
|
|
157
161
|
type RunFn,
|
|
158
162
|
type RunInput,
|
|
159
163
|
type RunOptions,
|
|
164
|
+
type Clause,
|
|
160
165
|
type RunPart,
|
|
161
166
|
type Runner,
|
|
162
167
|
type Runtime,
|
|
@@ -173,9 +178,11 @@ export {
|
|
|
173
178
|
narrow,
|
|
174
179
|
nodeAt,
|
|
175
180
|
partCaps,
|
|
181
|
+
partGrants,
|
|
176
182
|
permits,
|
|
177
183
|
planner,
|
|
178
184
|
prepare,
|
|
185
|
+
resolveCompiled,
|
|
179
186
|
run,
|
|
180
187
|
} from "./evaluate.ts";
|
|
181
188
|
export {
|
|
@@ -255,11 +262,15 @@ export {
|
|
|
255
262
|
jsTruthy,
|
|
256
263
|
} from "./formula.ts";
|
|
257
264
|
export { decodeDomain, decodeValue, encodeValue } from "./codec.ts";
|
|
265
|
+
export { type CodecFinding, type CodecLaw, type CodecLawsOptions, type CodecProbe, codecLaws } from "./codec-laws.ts";
|
|
258
266
|
export {
|
|
259
267
|
type ParseResult,
|
|
260
268
|
type SyntaxAssoc,
|
|
261
269
|
type SyntaxCalls,
|
|
270
|
+
type SyntaxConstructs,
|
|
262
271
|
type SyntaxEnvelopes,
|
|
272
|
+
type SyntaxFinding,
|
|
273
|
+
type SyntaxFindingCode,
|
|
263
274
|
type SyntaxFixity,
|
|
264
275
|
type SyntaxForm,
|
|
265
276
|
type SyntaxFormArgs,
|
|
@@ -270,7 +281,9 @@ export {
|
|
|
270
281
|
type SyntaxProfile,
|
|
271
282
|
type SyntaxRefPattern,
|
|
272
283
|
type SyntaxRefs,
|
|
284
|
+
MAX_SYNTAX_ISSUES,
|
|
273
285
|
STD_SYNTAX,
|
|
286
|
+
checkSyntax,
|
|
274
287
|
parse,
|
|
275
288
|
print,
|
|
276
289
|
syntaxFormat,
|
|
@@ -282,12 +295,14 @@ export { sameResult } from "./same.ts";
|
|
|
282
295
|
export { type PrintOptions, type ReadPrints, type ReadsCheck, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
|
|
283
296
|
export { type Durable, type DurableCounts, type DurableOptions, type Epochs, durable } from "./durable.ts";
|
|
284
297
|
export {
|
|
298
|
+
type CycleIssue,
|
|
285
299
|
type NodeEdge,
|
|
286
300
|
type NodeInfo,
|
|
287
301
|
type NodeRunOptions,
|
|
288
302
|
type Schedule,
|
|
289
303
|
type ScheduleCounts,
|
|
290
304
|
type ScheduleOptions,
|
|
305
|
+
checkCycles,
|
|
291
306
|
edgesOf,
|
|
292
307
|
schedule,
|
|
293
308
|
} 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 });
|
package/src/reference.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, docEquals, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { ask } from "./ladder.ts";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AttributeDeclaration,
|
|
5
|
+
type CapsRule,
|
|
6
|
+
type CircularRule,
|
|
7
|
+
type Kind,
|
|
8
|
+
type TreeContract,
|
|
9
|
+
GENERATION_LIMIT,
|
|
10
|
+
atArgsOf,
|
|
11
|
+
atRuleOf,
|
|
12
|
+
circularOf,
|
|
13
|
+
collectOf,
|
|
14
|
+
documentKey,
|
|
15
|
+
equationType,
|
|
16
|
+
lookupKind,
|
|
17
|
+
} from "./world.ts";
|
|
4
18
|
import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
|
|
5
19
|
import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, ROOTS, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
6
20
|
import { type AttributeRung, AT_READERS, STD_EVALUATORS } from "./stdlib.ts";
|
|
7
21
|
import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
|
|
8
22
|
import {
|
|
9
23
|
type AllOptions,
|
|
24
|
+
type Clause,
|
|
10
25
|
type Effect,
|
|
11
26
|
type EvalFn,
|
|
12
27
|
type EvalInput,
|
|
@@ -281,6 +296,44 @@ function freeIn(root: Context, ctx: Context, path: readonly unknown[]): boolean
|
|
|
281
296
|
return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
|
|
282
297
|
}
|
|
283
298
|
|
|
299
|
+
/** The clauses of one active `handle`, as SPEC tells: the frame around it, and if its body has ended. */
|
|
300
|
+
interface RefFrame {
|
|
301
|
+
readonly clauses: readonly Clause[];
|
|
302
|
+
readonly outer: RefFrame | undefined;
|
|
303
|
+
/** The list of the effects of the evaluation: a frame of another evaluation has ended. */
|
|
304
|
+
readonly owner: unknown;
|
|
305
|
+
ended: boolean;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** The key of the frame of a context. */
|
|
309
|
+
const REF_FRAME = /* @__PURE__ */ Symbol("coeval.reference.frame");
|
|
310
|
+
|
|
311
|
+
/** This function gives the frame of a context. */
|
|
312
|
+
function refFrame(ctx: Context): RefFrame | undefined {
|
|
313
|
+
return (ctx as Context & { readonly [REF_FRAME]?: RefFrame })[REF_FRAME];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* This function finds the clause for an effect (HANDLE.DO, HANDLE.FORWARD). An effect without a capability gets no clause
|
|
318
|
+
* (HANDLE.NARROW). A clause of a frame that has ended gives `#CAP!` (HANDLE.ENDED).
|
|
319
|
+
*/
|
|
320
|
+
function clauseAt(env: Env, ctx: Context, kind: Cap, place: readonly Key[]): Clause | Failed | undefined {
|
|
321
|
+
let frame = refFrame(ctx);
|
|
322
|
+
if (frame === undefined) return undefined;
|
|
323
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
324
|
+
const machine = env.machine;
|
|
325
|
+
const allowed = machine.permits === undefined ? permits(ctx.caps, keys) : machine.permits(ctx.caps, keys, ctx);
|
|
326
|
+
if (!allowed) return undefined;
|
|
327
|
+
for (; frame !== undefined; frame = frame.outer) {
|
|
328
|
+
for (const clause of frame.clauses) {
|
|
329
|
+
if (!permits([clause.op], keys)) continue;
|
|
330
|
+
if (frame.ended || frame.owner !== env.effects) return fail("#CAP!", `The handler of the effect ${keys.join(".")} has ended.`, { place });
|
|
331
|
+
return clause;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
284
337
|
/**
|
|
285
338
|
* This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
|
|
286
339
|
* address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
|
|
@@ -300,6 +353,8 @@ function cause(env: Env, ctx: Context, kind: Cap, payload: unknown, place: reado
|
|
|
300
353
|
function causeFromEvaluator(env: Env, ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Result {
|
|
301
354
|
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
302
355
|
if (env.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
|
|
356
|
+
const clause = clauseAt(env, ctx, kind, place);
|
|
357
|
+
if (clause !== undefined) return "ok" in clause ? clause : drive(clause.answer(payload), place, text);
|
|
303
358
|
return cause(env, ctx, kind, payload, place);
|
|
304
359
|
}
|
|
305
360
|
|
|
@@ -309,6 +364,8 @@ function causeFromEvaluator(env: Env, ctx: Context, kind: Cap, payload: unknown,
|
|
|
309
364
|
* (RUN.OUTCOME).
|
|
310
365
|
*/
|
|
311
366
|
function* causeAndWait(env: Env, ctx: Context, kind: Cap, payload: unknown, place: readonly Key[]): Steps {
|
|
367
|
+
const clause = clauseAt(env, ctx, kind, place);
|
|
368
|
+
if (clause !== undefined) return "ok" in clause ? clause : yield* clause.answer(payload);
|
|
312
369
|
if (!env.running) return cause(env, ctx, kind, payload, place);
|
|
313
370
|
const effect = recordEffect(env, ctx, kind, payload, place);
|
|
314
371
|
if ("ok" in effect) return effect;
|
|
@@ -466,6 +523,17 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
|
|
|
466
523
|
};
|
|
467
524
|
}
|
|
468
525
|
|
|
526
|
+
/** This function evaluates a part under a frame: the part also gets the kinds of the clauses (CAP.GRANT). */
|
|
527
|
+
function* handledAt(env: Env, part: At, clauses: readonly Clause[]): Steps {
|
|
528
|
+
const frame: RefFrame = { clauses, outer: refFrame(part.ctx), owner: env.effects, ended: false };
|
|
529
|
+
const ctx = { ...part.ctx, caps: [...part.ctx.caps, ...clauses.map((clause) => clause.op)], [REF_FRAME]: frame };
|
|
530
|
+
try {
|
|
531
|
+
return yield* stepsAt(env, { ...part, ctx });
|
|
532
|
+
} finally {
|
|
533
|
+
frame.ended = true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
469
537
|
function runInput(env: Env, seen: Seen, at: At): RunInput {
|
|
470
538
|
const { split, doc } = seen;
|
|
471
539
|
const parts: RunPart[] = split.parts.map((slot) => {
|
|
@@ -475,6 +543,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
|
|
|
475
543
|
path: slot.path,
|
|
476
544
|
...(name === undefined ? {} : { name }),
|
|
477
545
|
evaluate: (given?: Context) => stepsAt(env, partAt(seen, at, slot, given)),
|
|
546
|
+
handled: (clauses: readonly Clause[]) => handledAt(env, partAt(seen, at, slot, undefined), clauses),
|
|
478
547
|
};
|
|
479
548
|
});
|
|
480
549
|
const fill = (items: readonly unknown[]): unknown => (split.parts.length === 0 ? doc.value : split.fill(items));
|
|
@@ -882,6 +951,55 @@ function collectIn(node: RefNode, name: string, declared: unknown): Result {
|
|
|
882
951
|
return visit(node) ?? acc;
|
|
883
952
|
}
|
|
884
953
|
|
|
954
|
+
/** TREE.HIGHER-DEPTH: the depth of each root of a generated document in the reference. */
|
|
955
|
+
const refGenerations = /* @__PURE__ */ new WeakMap<RefNode, number>();
|
|
956
|
+
|
|
957
|
+
/** TREE.HIGHER-DEPTH: the depth of the nearest root of a generated document at or above a node of the reference, or 0. */
|
|
958
|
+
function generationIn(node: RefNode): number {
|
|
959
|
+
for (let at: RefNode | undefined = node; at !== undefined; at = at.up) {
|
|
960
|
+
const depth = refGenerations.get(at);
|
|
961
|
+
if (depth !== undefined) return depth;
|
|
962
|
+
}
|
|
963
|
+
return 0;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* TREE.HIGHER: a higher-order attribute. The equation gives a document as data, and the document evaluates with the
|
|
968
|
+
* node as `self`. The reference compiles each document again, thus a structural key is not a part of the meaning.
|
|
969
|
+
*/
|
|
970
|
+
function higherOf(node: RefNode, name: string, term: Json): Result {
|
|
971
|
+
const value = equationOf(node, term, {});
|
|
972
|
+
if (!value.ok) return value;
|
|
973
|
+
const text = nameOfAttribute(node, name);
|
|
974
|
+
if (documentKey(value.value) === undefined) return fail("#VALUE!", `The equation of the higher-order attribute ${text} gives a value that is not JSON data.`);
|
|
975
|
+
const depth = generationIn(node) + 1;
|
|
976
|
+
if (depth > GENERATION_LIMIT) return fail("#DEPTH!", `The higher-order attribute ${text} is under more than ${GENERATION_LIMIT} generated documents.`);
|
|
977
|
+
const root = equationOf(node, value.value as Json, {});
|
|
978
|
+
if (!root.ok) return root;
|
|
979
|
+
const made = refNodeOf(root.value);
|
|
980
|
+
if (made === undefined) return fail("#VALUE!", `The document of the higher-order attribute ${text} gives no node of the tree.`);
|
|
981
|
+
refGenerations.set(made, depth);
|
|
982
|
+
return root;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/** TREE.FORWARD: the last rung. The attribute `forwards` gives a node, and that node answers through its own ladder. */
|
|
986
|
+
function forwardOf(node: RefNode, name: string): Result | undefined {
|
|
987
|
+
const target: unknown = node.contract?.forwards;
|
|
988
|
+
if (target === undefined || target === name) return undefined;
|
|
989
|
+
const text = nameOfAttribute(node, name);
|
|
990
|
+
if (typeof target !== "string") return fail("#VALUE!", `The attribute ${text} has a contract with a bad field forwards. It must be a text.`);
|
|
991
|
+
const value = attributeOf(node, target);
|
|
992
|
+
if (value === undefined || !value.ok) return value;
|
|
993
|
+
const to = refNodeOf(value.value);
|
|
994
|
+
if (to === undefined) return fail("#VALUE!", `The attribute ${target} that ${text} forwards to gives no node of a tree.`);
|
|
995
|
+
if (!refGenerations.has(to)) {
|
|
996
|
+
const depth = generationIn(node) + 1;
|
|
997
|
+
if (depth > GENERATION_LIMIT) return fail("#DEPTH!", `The attribute ${text} forwards through more than ${GENERATION_LIMIT} generated documents.`);
|
|
998
|
+
refGenerations.set(to, depth);
|
|
999
|
+
}
|
|
1000
|
+
return attributeOf(to, name);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
885
1003
|
/**
|
|
886
1004
|
* TREE.LADDER: the rungs of the ladder of an attribute, in their order. A rung gives `undefined` when it does not
|
|
887
1005
|
* answer, and the next rung then tries.
|
|
@@ -912,9 +1030,11 @@ const REF_RUNGS: readonly (readonly [AttributeRung, (node: RefNode, name: string
|
|
|
912
1030
|
const declared = termIn(node.contract?.attributes, name);
|
|
913
1031
|
if (declared?.fold !== undefined) return foldOf(node, name, declared.fold);
|
|
914
1032
|
if (declared?.collect !== undefined) return collectIn(node, name, declared.collect);
|
|
915
|
-
|
|
1033
|
+
if (declared?.default !== undefined) return equationOf(node, declared.default, {});
|
|
1034
|
+
return declared?.higher === undefined ? undefined : higherOf(node, name, declared.higher);
|
|
916
1035
|
},
|
|
917
1036
|
],
|
|
1037
|
+
["forward", forwardOf],
|
|
918
1038
|
];
|
|
919
1039
|
|
|
920
1040
|
/** TREE.LADDER: the answer of the first rung that answers, or `undefined`. */
|