@mark1russell7/coeval 0.6.0 → 0.6.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.6.0",
4
+ "version": "0.6.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/index.ts CHANGED
@@ -55,7 +55,17 @@ export {
55
55
  looksLikeKind,
56
56
  lookupKind,
57
57
  } from "./world.ts";
58
- export { type MachinePart, type MergeConflict, type MergePath, type WorldPart, MergeError, mergeMachines, mergeWorlds } from "./merge.ts";
58
+ export {
59
+ type MachinePart,
60
+ type MergeConflict,
61
+ type MergePath,
62
+ type WorldPart,
63
+ MergeError,
64
+ mergeMachines,
65
+ mergeWorlds,
66
+ restrictMachine,
67
+ restrictWorld,
68
+ } from "./merge.ts";
59
69
  export {
60
70
  type Answer,
61
71
  type AskOptions,
package/src/merge.ts CHANGED
@@ -385,3 +385,102 @@ export function mergeMachines(...parts: readonly (MachinePart | ((prev: Machine)
385
385
  if (m.conflicts.length > 0) throw new MergeError(m.conflicts);
386
386
  return machineOf(draft);
387
387
  }
388
+
389
+ /** This function removes the entry at a path from a table of entries. It gives the same table when no entry is there. */
390
+ function removeEntry(entries: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): Readonly<Record<string, WorldEntry>> {
391
+ const [first, ...rest] = path;
392
+ if (first === undefined) return entries;
393
+ const key = String(first);
394
+ const entry = own(entries, key);
395
+ if (entry === undefined) return entries;
396
+ let next: WorldEntry | undefined;
397
+ if (rest.length > 0) {
398
+ if (!isNamespace(entry)) return entries;
399
+ if (rest.length === 1 && String(rest[0]) === "fallback") {
400
+ if (entry.fallback === undefined) return entries;
401
+ next = { kinds: entry.kinds };
402
+ } else {
403
+ if (String(rest[0]) !== "kinds") return entries;
404
+ const kinds = removeEntry(entry.kinds, rest.slice(1));
405
+ if (kinds === entry.kinds) return entries;
406
+ next = entry.fallback === undefined ? { kinds } : { kinds, fallback: entry.fallback };
407
+ }
408
+ }
409
+ const out: Record<string, WorldEntry> = { ...entries };
410
+ if (next === undefined) delete out[key];
411
+ else setOwn(out, key, next);
412
+ return out;
413
+ }
414
+
415
+ /**
416
+ * This function removes entries from a world, for a language that restricts another language. A path names an entry
417
+ * as `replaces` names it, for example `["kinds", "fn"]`, `["kinds", "ops", "kinds", "mul"]`,
418
+ * `["kinds", "ops", "fallback"]`, `["defaults", "view"]` or `["fallback"]`. A path that names no entry changes
419
+ * nothing. The function does not change the world.
420
+ *
421
+ * A removed kind gives `#KIND?`, also through an alias to it. A namespace without its fallback knows only its own keys.
422
+ * A world without its fallback is closed.
423
+ *
424
+ * @param world - The world.
425
+ * @param paths - The paths of the entries to remove.
426
+ * @returns The world without the entries.
427
+ */
428
+ export function restrictWorld(world: World, paths: readonly MergePath[]): World {
429
+ let kinds = world.kinds;
430
+ const defaults: Record<string, string> = { ...world.defaults };
431
+ let fallback = world.fallback;
432
+ for (const path of paths) {
433
+ const [first, ...rest] = path.map(String);
434
+ if (first === "kinds") kinds = removeEntry(kinds, rest);
435
+ else if (first === "defaults" && rest.length === 1) delete defaults[rest[0] as string];
436
+ else if (first === "fallback" && rest.length === 0) fallback = undefined;
437
+ }
438
+ const out = { kinds: { ...kinds }, defaults };
439
+ return fallback === undefined ? out : { ...out, fallback };
440
+ }
441
+
442
+ /** The tables of the code of a machine, as the paths of a restriction name them. */
443
+ const CODE_TABLES = ["evaluators", "runners", "derefs", "walkers"] as const;
444
+
445
+ /**
446
+ * This function removes entries from a machine, for a language that restricts another language. A path names an
447
+ * entry as `replaces` names it:
448
+ *
449
+ * - A path that starts with `"world"` removes an entry of the world, with the rules of `restrictWorld`.
450
+ * - `["evaluators", name]` or `["runners", name]` removes the code of the name: its evaluator and its runner.
451
+ * - `["derefs", name]` and `["walkers", name]` remove one entry of their tables.
452
+ *
453
+ * A path that names no entry changes nothing. The function does not change the machine. An own entry names an
454
+ * evaluator directly, thus only a machine without the code of a name refuses each document that names it.
455
+ *
456
+ * @param machine - The machine.
457
+ * @param paths - The paths of the entries to remove.
458
+ * @returns The machine without the entries.
459
+ */
460
+ export function restrictMachine(machine: Machine, paths: readonly MergePath[]): Machine {
461
+ const world = paths.filter((path) => String(path[0]) === "world").map((path) => path.slice(1));
462
+ const tables: Record<(typeof CODE_TABLES)[number], Record<string, unknown> | undefined> = {
463
+ evaluators: { ...machine.evaluators },
464
+ runners: machine.runners === undefined ? undefined : { ...machine.runners },
465
+ derefs: machine.derefs === undefined ? undefined : { ...machine.derefs },
466
+ walkers: machine.walkers === undefined ? undefined : { ...machine.walkers },
467
+ };
468
+ for (const path of paths) {
469
+ const [table, name, ...rest] = path.map(String);
470
+ if (name === undefined || rest.length > 0 || !(CODE_TABLES as readonly (string | undefined)[]).includes(table)) continue;
471
+ // The evaluator and the runner of a name are its code, as for a replacement (MACHINE.RUNNER).
472
+ const from = table === "evaluators" || table === "runners" ? ["evaluators", "runners"] : [table as string];
473
+ for (const key of from) {
474
+ const entries = tables[key as (typeof CODE_TABLES)[number]];
475
+ if (entries !== undefined) delete entries[name];
476
+ }
477
+ }
478
+ return {
479
+ ...machine,
480
+ world: world.length === 0 ? machine.world : restrictWorld(machine.world, world),
481
+ evaluators: tables.evaluators as Machine["evaluators"],
482
+ ...(tables.runners === undefined ? {} : { runners: tables.runners as NonNullable<Machine["runners"]> }),
483
+ ...(tables.derefs === undefined ? {} : { derefs: tables.derefs as NonNullable<Machine["derefs"]> }),
484
+ ...(tables.walkers === undefined ? {} : { walkers: tables.walkers as NonNullable<Machine["walkers"]> }),
485
+ };
486
+ }