@mark1russell7/coeval 0.2.2 → 0.3.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/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.2",
4
+ "version": "0.3.1",
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/evaluate.ts CHANGED
@@ -145,10 +145,22 @@ export interface Runtime {
145
145
  * This function drives several steps at the same time. When one waits, the others go on.
146
146
  *
147
147
  * @param list - One function for each item. Each function starts the steps of its item.
148
- * @param options - The most items that run at the same time. The default is each item.
148
+ * @param options - The most items that run at the same time, and the stop at the first failure.
149
149
  * @returns The steps that give each result, in the order of the list.
150
150
  */
151
- all(list: readonly (() => Steps)[], options?: { readonly concurrency?: number }): Generator<Promise<unknown>, readonly Result[], unknown>;
151
+ all(list: readonly (() => Steps)[], options?: AllOptions): Generator<Promise<unknown>, readonly Result[], unknown>;
152
+ }
153
+
154
+ /**
155
+ * The options of a drive of parts at the same time.
156
+ *
157
+ * - `concurrency`: the most parts that run at the same time. The default is each part.
158
+ * - `failFast`: `true` stops at the first part that fails. The parts that did not start do not start, the parts that
159
+ * run stop at their wait, and the results come at once. Each part without a result gives `#ABORT`.
160
+ */
161
+ export interface AllOptions {
162
+ readonly concurrency?: number;
163
+ readonly failFast?: boolean;
152
164
  }
153
165
 
154
166
  /** Compiled code: the evaluation of a document in a context. */
@@ -187,6 +199,14 @@ export interface CompileInput {
187
199
  /** The keys from the root document to this document. */
188
200
  readonly place: readonly Key[];
189
201
  readonly machine: Machine;
202
+ /**
203
+ * This function gives the value of the document with an item in the place of each part. The containers on the way
204
+ * to a part are new. The data around the parts stays the same objects.
205
+ *
206
+ * @param items - One item for each part, in the order of `parts`, for example the results of the parts.
207
+ * @returns The value with the items.
208
+ */
209
+ fill(items: readonly unknown[]): unknown;
190
210
  }
191
211
 
192
212
  /**
@@ -243,6 +263,14 @@ export interface EvalInput {
243
263
  * @returns The payload, or the error `#CAP!`.
244
264
  */
245
265
  effect(kind: Cap, payload: unknown): Result;
266
+ /**
267
+ * This function gives the value of the document with an item in the place of each part, for example the result of
268
+ * each part. The containers on the way to a part are new. The data around the parts stays the same objects.
269
+ *
270
+ * @param items - One item for each part, in the order of `parts`.
271
+ * @returns The value with the items.
272
+ */
273
+ fill(items: readonly unknown[]): unknown;
246
274
  }
247
275
 
248
276
  /** A part for a runner. Its evaluation can wait for the effects in it. */
@@ -284,13 +312,22 @@ export interface RunInput {
284
312
  */
285
313
  effect(kind: Cap, payload: unknown): Steps;
286
314
  /**
287
- * This function evaluates parts at the same time. Take the results with `yield*`.
315
+ * This function evaluates parts at the same time. Take the results with `yield*`. A runner can give its own parts,
316
+ * for example a part with a different context: each part needs only the function `evaluate`.
288
317
  *
289
318
  * @param parts - The parts.
290
- * @param options - The most parts that run at the same time.
319
+ * @param options - The most parts that run at the same time, and the stop at the first failure.
291
320
  * @returns The steps that give each result, in the order of the parts.
292
321
  */
293
- all(parts: readonly RunPart[], options?: { readonly concurrency?: number }): Generator<Promise<unknown>, readonly Result[], unknown>;
322
+ all(parts: readonly RunPart[], options?: AllOptions): Generator<Promise<unknown>, readonly Result[], unknown>;
323
+ /**
324
+ * This function gives the value of the document with an item in the place of each part, for example the result of
325
+ * each part. The containers on the way to a part are new. The data around the parts stays the same objects.
326
+ *
327
+ * @param items - One item for each part, in the order of `parts`.
328
+ * @returns The value with the items.
329
+ */
330
+ fill(items: readonly unknown[]): unknown;
294
331
  }
295
332
 
296
333
  /** An evaluator that is a function of its input. The kernel calls it for each evaluation. */
@@ -520,9 +557,11 @@ class Session implements Runtime {
520
557
  return (yield done.then((value: unknown) => ok(value), failed)) as Result;
521
558
  }
522
559
 
523
- *all(list: readonly (() => Steps)[], options: { readonly concurrency?: number } = {}): Generator<Promise<unknown>, readonly Result[], unknown> {
560
+ *all(list: readonly (() => Steps)[], options: AllOptions = {}): Generator<Promise<unknown>, readonly Result[], unknown> {
524
561
  const results: Result[] = new Array<Result>(list.length);
525
562
  const limit = Math.max(1, options.concurrency ?? list.length);
563
+ const failFast = options.failFast === true;
564
+ let failed = false;
526
565
  interface Active {
527
566
  readonly index: number;
528
567
  readonly steps: Steps;
@@ -530,16 +569,20 @@ class Session implements Runtime {
530
569
  }
531
570
  const active: Active[] = [];
532
571
  let next = 0;
572
+ const settle = (index: number, result: Result): void => {
573
+ results[index] = result;
574
+ if (failFast && !result.ok) failed = true;
575
+ };
533
576
  const advance = (index: number, steps: Steps, input: { readonly value?: unknown; readonly error?: unknown; readonly threw: boolean }): void => {
534
577
  let step: IteratorResult<Promise<unknown>, Result>;
535
578
  try {
536
579
  step = input.threw ? steps.throw(input.error) : steps.next(input.value);
537
580
  } catch (error) {
538
- results[index] = thrown(error, "a part");
581
+ settle(index, thrown(error, "a part"));
539
582
  return;
540
583
  }
541
584
  if (step.done === true) {
542
- results[index] = step.value;
585
+ settle(index, step.value);
543
586
  return;
544
587
  }
545
588
  const item: Active = { index, steps, pending: Promise.resolve() };
@@ -550,7 +593,7 @@ class Session implements Runtime {
550
593
  active.push(item);
551
594
  };
552
595
  const start = (): void => {
553
- while (active.length < limit && next < list.length) {
596
+ while (!failed && active.length < limit && next < list.length) {
554
597
  const index = next;
555
598
  next += 1;
556
599
  const begin = list[index] as () => Steps;
@@ -558,14 +601,14 @@ class Session implements Runtime {
558
601
  try {
559
602
  steps = begin();
560
603
  } catch (error) {
561
- results[index] = thrown(error, "a part");
604
+ settle(index, thrown(error, "a part"));
562
605
  continue;
563
606
  }
564
607
  advance(index, steps, { threw: false });
565
608
  }
566
609
  };
567
610
  start();
568
- while (active.length > 0) {
611
+ while (active.length > 0 && !failed) {
569
612
  const settled = (yield Promise.race(active.map((item) => item.pending))) as {
570
613
  readonly item: Active;
571
614
  readonly value?: unknown;
@@ -576,6 +619,16 @@ class Session implements Runtime {
576
619
  advance(settled.item.index, settled.item.steps, settled);
577
620
  start();
578
621
  }
622
+ if (!failed) return results;
623
+ const stopped = fail("#ABORT", "The part stopped: a part before it failed, and the parts run with failFast.");
624
+ for (const item of active) {
625
+ try {
626
+ item.steps.return(stopped);
627
+ } catch {
628
+ // The finally code of a part threw. The part stops with the abort.
629
+ }
630
+ }
631
+ for (let index = 0; index < list.length; index += 1) if (results[index] === undefined) results[index] = stopped;
579
632
  return results;
580
633
  }
581
634
 
@@ -821,6 +874,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
821
874
  parts,
822
875
  place,
823
876
  machine: c.machine,
877
+ fill: (items) => (split.parts.length === 0 ? doc.value : split.fill(items)),
824
878
  };
825
879
  const who = `the evaluator ${name}`;
826
880
 
@@ -877,6 +931,7 @@ function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly
877
931
  place,
878
932
  resolve: (path, options) => rt.resolve(ctx, path, options),
879
933
  effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
934
+ fill: input.fill,
880
935
  });
881
936
  };
882
937
  }
@@ -904,6 +959,7 @@ function interpretRun(fn: RunFn, input: CompileInput, fill: <P>(handles: readonl
904
959
  resolve: (path, options) => rt.resolve(ctx, path, options),
905
960
  effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
906
961
  all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options),
962
+ fill: input.fill,
907
963
  });
908
964
  };
909
965
  }
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;
package/src/index.ts CHANGED
@@ -96,6 +96,7 @@ export {
96
96
  export { type Classification, type Shape, classify, tag } from "./classify.ts";
97
97
  export { type PartSlot, type Split, pathMatches, splitParts } from "./parts.ts";
98
98
  export {
99
+ type AllOptions,
99
100
  type Code,
100
101
  type CompileInput,
101
102
  type CompiledEvaluator,
package/src/world.ts CHANGED
@@ -90,10 +90,14 @@ export type Alias = { readonly type: "ref"; readonly value: readonly Json[] };
90
90
  /** One entry of the world: a kind, a namespace, or an alias. */
91
91
  export type WorldEntry = Kind | Namespace | Alias;
92
92
 
93
- /** A world holds the kinds and the defaults that answer the questions. */
93
+ /**
94
+ * A world holds the kinds and the defaults that answer the questions. `fallback` is the kind of each first key that
95
+ * the world does not have. A world with a fallback is open: each path that starts with an unknown key names a kind.
96
+ */
94
97
  export interface World {
95
98
  readonly kinds: Readonly<Record<string, WorldEntry>>;
96
99
  readonly defaults: Readonly<Record<string, string>>;
100
+ readonly fallback?: Kind;
97
101
  }
98
102
 
99
103
  /** The result of a lookup of a type in the world. */
@@ -139,7 +143,8 @@ function entryAt(entries: Readonly<Record<string, WorldEntry>>, key: Key): World
139
143
  /**
140
144
  * This function finds the kind that a type names. It walks the path from the root of the world, one key at a
141
145
  * time. An alias on the way is followed until a kind answers (collapse). A cycle of aliases stops the lookup. In a
142
- * namespace with a fallback, a key that the namespace does not have gets the fallback kind.
146
+ * namespace with a fallback, a key that the namespace does not have gets the fallback kind. In an open world, a first
147
+ * key that the world does not have gets the fallback kind of the world.
143
148
  *
144
149
  * @param world - The world.
145
150
  * @param type - The reference to the type.
@@ -152,7 +157,7 @@ export function lookupKind(world: World, type: TypeRef): Lookup {
152
157
  let aliases = 0;
153
158
  for (;;) {
154
159
  let entries = world.kinds;
155
- let fallback: Kind | undefined;
160
+ let fallback: Kind | undefined = world.fallback;
156
161
  let entry: WorldEntry | undefined;
157
162
  let followed = false;
158
163
  for (let index = 0; index < path.length; index += 1) {