@mark1russell7/coeval 0.2.0 → 0.2.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/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +58 -19
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/deref.ts +38 -14
- package/src/evaluate.ts +17 -5
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.2.
|
|
4
|
+
"version": "0.2.2",
|
|
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/deref.ts
CHANGED
|
@@ -112,6 +112,8 @@ export type Walker = (input: WalkInput) => WalkResult;
|
|
|
112
112
|
* - `plain`: `true` when the values are data. Then a step does not ask the kind of an envelope, and a reference
|
|
113
113
|
* that a step reaches stays a value (no collapse).
|
|
114
114
|
* - `roots`: the roots that a path can start at. The default is each root.
|
|
115
|
+
* - `adopt`: the thing of each value that a path reaches. A host gives its own values a host thing with this
|
|
116
|
+
* function, for example a value whose fields read through signals. A host thing is not followed as a reference.
|
|
115
117
|
*/
|
|
116
118
|
export interface Navigator {
|
|
117
119
|
readonly world: World;
|
|
@@ -119,6 +121,7 @@ export interface Navigator {
|
|
|
119
121
|
readonly walkers?: Readonly<Record<string, Walker>>;
|
|
120
122
|
readonly plain?: boolean;
|
|
121
123
|
readonly roots?: readonly Root[];
|
|
124
|
+
readonly adopt?: (value: unknown) => Thing;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/** The way that one hop of a path was answered. */
|
|
@@ -256,21 +259,37 @@ function mayBeRef(thing: unknown): boolean {
|
|
|
256
259
|
return typeof thing === "object" && thing !== null && (thing as { readonly type?: unknown }).type === "ref";
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
/** This function tells if each step of a path from an index is a key. */
|
|
263
|
+
function keysFrom(path: readonly unknown[], start: number): boolean {
|
|
264
|
+
for (let index = start; index < path.length; index += 1) if (!isKey(path[index])) return false;
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
259
268
|
/**
|
|
260
|
-
* This function resolves the common path without a record of hops: a root, then keys
|
|
261
|
-
*
|
|
262
|
-
* follow, a step document and a
|
|
269
|
+
* This function resolves the common path without a record of hops: a root, then keys. Plain values answer the
|
|
270
|
+
* keys, or a host thing answers all of them in one call. It gives `undefined` when the path needs the full
|
|
271
|
+
* algorithm. Examples are a host thing without `resolve`, an envelope, a reference to follow, a step document and a
|
|
272
|
+
* failure of a step (for its exact reason).
|
|
263
273
|
*/
|
|
264
274
|
function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): Resolution | undefined {
|
|
265
275
|
const root = path[0];
|
|
266
|
-
if (root !== "names" && root !== "self" && root !== "event") return undefined;
|
|
276
|
+
if (root !== "names" && root !== "self" && root !== "event" && root !== "world") return undefined;
|
|
267
277
|
if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) return undefined;
|
|
278
|
+
if (nav.adopt !== undefined) return undefined;
|
|
268
279
|
let here: unknown = ctx[root];
|
|
269
280
|
const plain = nav.plain === true;
|
|
270
281
|
for (let index = 1; index < path.length; index += 1) {
|
|
271
282
|
if (typeof here !== "object" || here === null) return undefined;
|
|
272
283
|
if (!plain && mayBeRef(here)) return undefined;
|
|
273
|
-
if ((here as Partial<HostThing>)[THING] === true)
|
|
284
|
+
if ((here as Partial<HostThing>)[THING] === true) {
|
|
285
|
+
const thing = here as HostThing;
|
|
286
|
+
if (thing.resolve === undefined || !keysFrom(path, index)) return undefined;
|
|
287
|
+
const keys = path.slice(index) as readonly Key[];
|
|
288
|
+
const reached = thing.resolve(keys);
|
|
289
|
+
if (reached === undefined) return { ok: false, code: "#N/A", reason: `The path ${keys.join(".")} reaches nothing.`, hops: NO_HOPS };
|
|
290
|
+
if (!plain && mayBeRef(reached.thing)) return undefined;
|
|
291
|
+
return { ok: true, thing: reached.thing, hops: NO_HOPS };
|
|
292
|
+
}
|
|
274
293
|
const step = path[index];
|
|
275
294
|
if (Array.isArray(here)) {
|
|
276
295
|
if (typeof step !== "number" || !Number.isInteger(step) || step < 0 || step >= here.length) return undefined;
|
|
@@ -316,6 +335,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
316
335
|
const done = hops ?? NO_HOPS;
|
|
317
336
|
const failed = (code: ResolveCode, reason: string): Resolution => ({ ok: false, code, reason, hops: done });
|
|
318
337
|
let collapses = 0;
|
|
338
|
+
const adopt = nav.adopt ?? ((value: unknown): Thing => value);
|
|
319
339
|
|
|
320
340
|
const collapse = (thing: Thing): Resolution => {
|
|
321
341
|
let here = thing;
|
|
@@ -325,7 +345,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
325
345
|
if (collapses > MAX_COLLAPSE) return failed("#CYCLE!", "The references form a cycle.");
|
|
326
346
|
const inner = walk(here.value);
|
|
327
347
|
if (!inner.ok) return inner;
|
|
328
|
-
here = inner.thing;
|
|
348
|
+
here = adopt(inner.thing);
|
|
329
349
|
hops?.push({ step: "collapse", thing: here, by: "collapse" });
|
|
330
350
|
}
|
|
331
351
|
return { ok: true, thing: here, hops: done };
|
|
@@ -339,7 +359,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
339
359
|
if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) {
|
|
340
360
|
return failed("#REF!", `The root ${root} is not open here. The open roots are ${nav.roots.join(", ")}.`);
|
|
341
361
|
}
|
|
342
|
-
let here: Thing = ctx[root as Root];
|
|
362
|
+
let here: Thing = adopt(ctx[root as Root]);
|
|
343
363
|
hops?.push({ step: root, thing: here, by: "root" });
|
|
344
364
|
const first = collapse(here);
|
|
345
365
|
if (!first.ok) return first;
|
|
@@ -350,8 +370,9 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
350
370
|
const keys = steps.slice(index) as readonly Key[];
|
|
351
371
|
const reached = here.resolve(keys);
|
|
352
372
|
if (reached === undefined) return failed("#N/A", `The path ${keys.join(".")} reaches nothing.`);
|
|
353
|
-
|
|
354
|
-
|
|
373
|
+
const thing = adopt(reached.thing);
|
|
374
|
+
hops?.push({ step: keys as readonly Json[], thing, by: "own" });
|
|
375
|
+
return collapse(thing);
|
|
355
376
|
}
|
|
356
377
|
if (isKey(step)) {
|
|
357
378
|
const reached = derefStep(nav, here, step);
|
|
@@ -359,8 +380,8 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
359
380
|
if (root === "names" && index === 1) return failed("#NAME?", `The context has no name ${String(step)}.`);
|
|
360
381
|
return failed("#N/A", `The step ${String(step)} reaches nothing.`);
|
|
361
382
|
}
|
|
362
|
-
|
|
363
|
-
here
|
|
383
|
+
here = adopt(reached.thing);
|
|
384
|
+
hops?.push({ step, thing: here, by: reached.by });
|
|
364
385
|
} else {
|
|
365
386
|
const envelope = isEnvelope(step) ? step : undefined;
|
|
366
387
|
const found = envelope === undefined ? undefined : lookupKind(nav.world, envelope.type);
|
|
@@ -373,11 +394,14 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
373
394
|
data: envelope.value,
|
|
374
395
|
here,
|
|
375
396
|
context: ctx,
|
|
376
|
-
deref: (thing, key) =>
|
|
397
|
+
deref: (thing, key) => {
|
|
398
|
+
const reached = derefStep(nav, thing, key);
|
|
399
|
+
return reached === undefined ? undefined : adopt(reached.thing);
|
|
400
|
+
},
|
|
377
401
|
});
|
|
378
402
|
if (!result.ok) return failed("#N/A", result.reason);
|
|
379
|
-
|
|
380
|
-
here
|
|
403
|
+
here = adopt(result.thing);
|
|
404
|
+
hops?.push({ step: envelope as Json, thing: here, by: "step" });
|
|
381
405
|
}
|
|
382
406
|
const next = collapse(here);
|
|
383
407
|
if (!next.ok) return next;
|
package/src/evaluate.ts
CHANGED
|
@@ -445,9 +445,14 @@ export interface EvalOptions {
|
|
|
445
445
|
readonly effects?: Effect[];
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
/** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
|
|
449
|
+
const WITHOUT_HOPS: ResolveOptions = Object.freeze({ hops: false });
|
|
450
|
+
const WITH_HOPS: ResolveOptions = Object.freeze({ hops: true });
|
|
451
|
+
|
|
448
452
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
449
453
|
class Session implements Runtime {
|
|
450
|
-
|
|
454
|
+
/** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
|
|
455
|
+
private list: Effect[] | undefined;
|
|
451
456
|
tick = 0;
|
|
452
457
|
nextId = 0;
|
|
453
458
|
current: Building | undefined;
|
|
@@ -461,13 +466,19 @@ class Session implements Runtime {
|
|
|
461
466
|
readonly signal: StopSignal | undefined,
|
|
462
467
|
effects: Effect[] | undefined,
|
|
463
468
|
) {
|
|
464
|
-
this.
|
|
469
|
+
this.list = effects;
|
|
465
470
|
this.holder = traced ? holderNode() : undefined;
|
|
466
471
|
this.current = this.holder;
|
|
467
472
|
}
|
|
468
473
|
|
|
474
|
+
/** The effects of the evaluation, in their order. */
|
|
475
|
+
get effects(): Effect[] {
|
|
476
|
+
return (this.list ??= []);
|
|
477
|
+
}
|
|
478
|
+
|
|
469
479
|
resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
470
|
-
|
|
480
|
+
const hops = options?.hops ?? this.traced;
|
|
481
|
+
return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
471
482
|
}
|
|
472
483
|
|
|
473
484
|
private permitted(ctx: Context, keys: readonly Key[]): boolean {
|
|
@@ -479,8 +490,9 @@ class Session implements Runtime {
|
|
|
479
490
|
const text = keys.join(".");
|
|
480
491
|
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
481
492
|
const node = this.current;
|
|
482
|
-
const
|
|
483
|
-
this.
|
|
493
|
+
const list = this.effects;
|
|
494
|
+
const effect: Effect = { index: list.length, kind: text, path: keys, payload, node: node?.id ?? -1, at: this.tick, place, context: ctx };
|
|
495
|
+
list.push(effect);
|
|
484
496
|
if (node !== undefined) node.effects.push(effect.index);
|
|
485
497
|
return effect;
|
|
486
498
|
}
|