@mark1russell7/coeval 0.2.1 → 0.3.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/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.1",
4
+ "version": "0.3.0",
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
@@ -259,14 +259,21 @@ function mayBeRef(thing: unknown): boolean {
259
259
  return typeof thing === "object" && thing !== null && (thing as { readonly type?: unknown }).type === "ref";
260
260
  }
261
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
+
262
268
  /**
263
- * This function resolves the common path without a record of hops: a root, then keys, through plain values. It
264
- * gives `undefined` when the path needs the full algorithm. Examples are a host thing, an envelope, a reference to
265
- * follow, a step document and a failure (for its exact reason).
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).
266
273
  */
267
274
  function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): Resolution | undefined {
268
275
  const root = path[0];
269
- if (root !== "names" && root !== "self" && root !== "event") return undefined;
276
+ if (root !== "names" && root !== "self" && root !== "event" && root !== "world") return undefined;
270
277
  if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) return undefined;
271
278
  if (nav.adopt !== undefined) return undefined;
272
279
  let here: unknown = ctx[root];
@@ -274,7 +281,15 @@ function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): R
274
281
  for (let index = 1; index < path.length; index += 1) {
275
282
  if (typeof here !== "object" || here === null) return undefined;
276
283
  if (!plain && mayBeRef(here)) return undefined;
277
- if ((here as Partial<HostThing>)[THING] === true) return undefined;
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
+ }
278
293
  const step = path[index];
279
294
  if (Array.isArray(here)) {
280
295
  if (typeof step !== "number" || !Number.isInteger(step) || step < 0 || step >= here.length) return undefined;
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
- readonly effects: Effect[];
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.effects = effects ?? [];
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
- return resolve(this.machine, ctx, path, { hops: options?.hops ?? this.traced });
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 effect: Effect = { index: this.effects.length, kind: text, path: keys, payload, node: node?.id ?? -1, at: this.tick, place, context: ctx };
483
- this.effects.push(effect);
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
  }
package/src/formula.ts CHANGED
@@ -24,7 +24,8 @@ export type ExtFn = (data: Json) => Result;
24
24
  * - `ext`: the extensions of the namespace `ext`, by their name. The kit has `ext.undefined` and `ext.number`.
25
25
  * - `truthy`: the truth of a value for `if`, `and` and `or`. `undefined` is an error `#VALUE!`. The default
26
26
  * accepts only `true` and `false`.
27
- * - `otherwise`: the value of an `if` without `else`. The default is `null`.
27
+ * - `otherwise`: the value of an `if` without `else`. The default is `null`. A profile with this key gives its value,
28
+ * also `undefined`.
28
29
  * - `logic`: what `and` and `or` give: a truth value (the default), or the value that decided.
29
30
  * - `omit`: the errors that leave a field out of a `record`, as an absent value.
30
31
  * - `missing`: the values that a reference cannot give. Such a value gives `#N/A`.
@@ -150,7 +151,7 @@ export function formulaKinds(): Readonly<Record<string, WorldEntry>> {
150
151
  */
151
152
  export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record<string, Evaluator>> {
152
153
  const truthy = profile.truthy ?? strictTruthy;
153
- const otherwise = ok(profile.otherwise ?? null);
154
+ const otherwise = ok(Object.hasOwn(profile, "otherwise") ? profile.otherwise : null);
154
155
  const logicValue = profile.logic === "value";
155
156
  const omit = profile.omit;
156
157
  const missing = profile.missing;