@mark1russell7/coeval 0.24.1 → 0.25.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/build/index.js CHANGED
@@ -1353,6 +1353,61 @@ function narrowAt(type, path, at, narrowed, domains) {
1353
1353
  ...form.closed ? { closed: true } : {}
1354
1354
  };
1355
1355
  }
1356
+ /** This function gives the type of a value that is a member of both types. The result is the narrower known type. */
1357
+ function meetOf(type, proved, domains) {
1358
+ if (isUnknown(proved, domains)) return type;
1359
+ const form = formOf(type, domains);
1360
+ if (form.t === "one") return { one: form.values.filter((value) => member(value, proved, domains)) };
1361
+ return !isUnknown(type, domains) && isPrecise(type, domains) && fits(type, proved, domains) ? type : proved;
1362
+ }
1363
+ /** This function gives the type of a value of the first type that is not a member of the second type. */
1364
+ function withoutOf(type, removed, domains) {
1365
+ if (isUnknown(removed, domains)) return type;
1366
+ const form = formOf(type, domains);
1367
+ if (form.t === "one") return { one: form.values.filter((value) => !member(value, removed, domains)) };
1368
+ if (form.t === "union") {
1369
+ const members = form.members.map((each) => withoutOf(each, removed, domains)).filter((each) => each !== "never");
1370
+ return members.length === 0 ? "never" : members.length === 1 ? members[0] : { union: members };
1371
+ }
1372
+ return isPrecise(type, domains) && fits(type, removed, domains) ? "never" : type;
1373
+ }
1374
+ /**
1375
+ * This function gives a type with one proof at a path (STATIC.OCCURRENCE). A path that leaves the records of the type
1376
+ * gives the type back. A fact about a field also tells if the field exists. A value of a type without `undefined`
1377
+ * exists. An optional field without a remaining type does not.
1378
+ *
1379
+ * @param type - The type of the value at the first `at` keys of the path.
1380
+ * @param proof - The proof.
1381
+ * @param at - The number of keys of the path that the type already holds.
1382
+ * @param domains - The domains of the machine.
1383
+ * @returns The narrowed type.
1384
+ */
1385
+ function narrowType(type, proof, at, domains) {
1386
+ if (at === proof.path.length) {
1387
+ if (proof.fact === "is") return meetOf(type, proof.type, domains);
1388
+ return proof.fact === "not" ? withoutOf(type, proof.type, domains) : type;
1389
+ }
1390
+ const form = formOf(type, domains);
1391
+ const key = proof.path[at];
1392
+ if (form.t !== "record" || !Object.hasOwn(form.fields, key)) return type;
1393
+ const before = form.fields[key];
1394
+ const optional = new Set(form.optional);
1395
+ let after = narrowType(before, proof, at + 1, domains);
1396
+ if (at + 1 === proof.path.length && optional.has(key)) {
1397
+ if (proof.fact === "present" || proof.fact === "is" && !member(void 0, proof.type, domains)) optional.delete(key);
1398
+ else if (proof.fact === "absent" || after === "never") after = void 0;
1399
+ }
1400
+ const fields = {};
1401
+ for (const name of Object.keys(form.fields)) if (name !== key) setOwn$6(fields, name, form.fields[name]);
1402
+ if (after !== void 0) setOwn$6(fields, key, after);
1403
+ else if (form.closed) optional.delete(key);
1404
+ else setOwn$6(fields, key, "never");
1405
+ return {
1406
+ record: fields,
1407
+ ...optional.size === 0 ? {} : { optional: [...optional] },
1408
+ ...form.closed ? { closed: true } : {}
1409
+ };
1410
+ }
1356
1411
  /**
1357
1412
  * This function is the split rule of `fits` (TYPE.FITS-FINITE, D84, TYPE.FITS-PATH, D89). A record type fits when each
1358
1413
  * record of its split fits. A split takes the first field in the order of `splitsOf`. Each member of the field gives a
@@ -1882,6 +1937,12 @@ var MergeError = class extends Error {
1882
1937
  }
1883
1938
  };
1884
1939
  const NONE$2 = /* @__PURE__ */ new Set();
1940
+ /** The verbs that the kernel reads. Thus no language owns them (COMPOSE.PIRACY). */
1941
+ const KERNEL_VERBS = /* @__PURE__ */ new Set([
1942
+ "eval",
1943
+ "deref",
1944
+ "step"
1945
+ ]);
1885
1946
  function keyOf(path) {
1886
1947
  return JSON.stringify(path.map(String));
1887
1948
  }
@@ -2072,16 +2133,123 @@ function mergeEntry(m, earlier, later, path, part, replaces) {
2072
2133
  m.clash(path, part, a === b ? `Two parts give two different ${a === "an alias" ? "aliases" : "kinds"}.` : `One part gives ${a}, and the other part gives ${b}.`);
2073
2134
  return earlier;
2074
2135
  }
2075
- function worldOf(draft) {
2136
+ function worldDraft() {
2137
+ return {
2138
+ kinds: {},
2139
+ defaults: {},
2140
+ owners: /* @__PURE__ */ new Map(),
2141
+ entries: []
2142
+ };
2143
+ }
2144
+ /**
2145
+ * This function gives a copy of a table with a change to the kind at a path, as `replaces` names the path. It gives
2146
+ * `undefined` when no kind is at the path. An alias is not a kind.
2147
+ */
2148
+ function changeKind(table, path, change) {
2149
+ const [first, key, ...rest] = path;
2150
+ if (String(first) === "fallback" && key === void 0) return table.fallback === void 0 ? void 0 : {
2151
+ ...table,
2152
+ fallback: change(table.fallback)
2153
+ };
2154
+ if (String(first) !== "kinds" || key === void 0) return void 0;
2155
+ const entry = own$2(table.kinds, String(key));
2156
+ const next = rest.length === 0 ? isKind(entry) ? change(entry) : void 0 : isNamespace(entry) ? changeKind(entry, rest, change) : void 0;
2157
+ if (next === void 0) return void 0;
2158
+ const kinds = { ...table.kinds };
2159
+ setOwn$5(kinds, String(key), next);
2160
+ return {
2161
+ ...table,
2162
+ kinds
2163
+ };
2164
+ }
2165
+ /**
2166
+ * This function adds the entries of `extends` to the kinds of a draft. A later part can give the kind, thus this step
2167
+ * reads the whole draft. Without `m`, it reports no conflict: a function part sees the entries of the parts before it.
2168
+ */
2169
+ function addVerbs(draft, m) {
2170
+ let table = draft.fallback === void 0 ? { kinds: draft.kinds } : {
2171
+ kinds: draft.kinds,
2172
+ fallback: draft.fallback
2173
+ };
2174
+ for (const entry of draft.entries) {
2175
+ if (!entry.allowed && !entry.replaced) {
2176
+ m?.clash(entry.path, entry.part, "A part adds a verb that it does not own to a kind that it does not give.");
2177
+ continue;
2178
+ }
2179
+ const next = changeKind(table, entry.kind, (kind) => {
2180
+ const verbs = { ...kind.verbs };
2181
+ const earlier = own$2(verbs, entry.verb);
2182
+ if (earlier === void 0) m?.give(entry.path, entry.part);
2183
+ else if (entry.replaced) m?.replace(entry.path, entry.part);
2184
+ else if (earlier !== entry.code) m?.clash(entry.path, entry.part, "Two parts give two different evaluators for this verb of this kind.");
2185
+ if (earlier === void 0 || entry.replaced) setOwn$5(verbs, entry.verb, entry.code);
2186
+ return {
2187
+ ...kind,
2188
+ verbs
2189
+ };
2190
+ });
2191
+ if (next !== void 0) table = next;
2192
+ else if (!entry.replaced) m?.clash(entry.path, entry.part, "A part adds a verb to a path without a kind.");
2193
+ }
2194
+ return table;
2195
+ }
2196
+ function worldOf(draft, m) {
2197
+ const table = addVerbs(draft, m);
2076
2198
  const world = {
2077
- kinds: { ...draft.kinds },
2199
+ kinds: { ...table.kinds },
2078
2200
  defaults: { ...draft.defaults }
2079
2201
  };
2080
- return draft.fallback === void 0 ? world : {
2202
+ return table.fallback === void 0 ? world : {
2081
2203
  ...world,
2082
- fallback: draft.fallback
2204
+ fallback: table.fallback
2083
2205
  };
2084
2206
  }
2207
+ /** This function records the verbs that a part owns, and the entries that it adds to kinds. */
2208
+ function mergeVerbs(m, draft, part, index, replaces, base) {
2209
+ const owned = part.verbs ?? [];
2210
+ for (const verb of owned) {
2211
+ const path = [
2212
+ ...base,
2213
+ "verbs",
2214
+ verb
2215
+ ];
2216
+ const owner = draft.owners.get(verb);
2217
+ if (replaces.has(keyOf(path))) {
2218
+ draft.owners.set(verb, part);
2219
+ m.replace(path, index);
2220
+ } else if (KERNEL_VERBS.has(verb)) m.clash(path, index, "The kernel owns this verb.");
2221
+ else if (owner === void 0) {
2222
+ draft.owners.set(verb, part);
2223
+ m.give(path, index);
2224
+ } else if (!same(owner, part)) m.clash(path, index, "Two parts own this verb.");
2225
+ }
2226
+ const table = part.fallback === void 0 ? { kinds: part.kinds ?? {} } : {
2227
+ kinds: part.kinds ?? {},
2228
+ fallback: part.fallback
2229
+ };
2230
+ for (const { kind, verbs } of part.extends ?? []) {
2231
+ const gives = changeKind(table, kind, (found) => found) !== void 0;
2232
+ for (const verb of Object.keys(verbs)) {
2233
+ const code = verbs[verb];
2234
+ if (code === void 0) continue;
2235
+ const path = [
2236
+ ...base,
2237
+ ...kind,
2238
+ "verbs",
2239
+ verb
2240
+ ];
2241
+ draft.entries.push({
2242
+ kind,
2243
+ verb,
2244
+ code,
2245
+ path,
2246
+ part: index,
2247
+ allowed: gives || owned.includes(verb),
2248
+ replaced: replaces.has(keyOf(path))
2249
+ });
2250
+ }
2251
+ }
2252
+ }
2085
2253
  function mergeWorldInto(m, draft, part, index, replaces, base) {
2086
2254
  if (part === void 0) return;
2087
2255
  if (part.kinds !== void 0) mergeEntries(m, draft.kinds, part.kinds, [...base, "kinds"], index, replaces);
@@ -2093,6 +2261,7 @@ function mergeWorldInto(m, draft, part, index, replaces, base) {
2093
2261
  ], index, replaces, Object.is, "Two parts give two different defaults for this verb.");
2094
2262
  const fallback = mergeFallback(m, draft.fallback, part.fallback, [...base, "fallback"], index, replaces);
2095
2263
  if (fallback !== void 0) draft.fallback = fallback;
2264
+ mergeVerbs(m, draft, part, index, replaces, base);
2096
2265
  }
2097
2266
  /**
2098
2267
  * This function merges worlds into one world, in the order of the parts. It does not change a part.
@@ -2102,6 +2271,8 @@ function mergeWorldInto(m, draft, part, index, replaces, base) {
2102
2271
  * - The defaults merge verb by verb, and the world keeps one fallback.
2103
2272
  * - Each other pair of entries at one path is a conflict, unless the later part names the path in `replaces`.
2104
2273
  * Then the later entry wins, with no merge below it.
2274
+ * - The entries of `extends` join their kinds after each part, thus the order of the parts does not change them. A
2275
+ * verb that a part does not own on a kind that it does not give is a conflict (COMPOSE.PIRACY).
2105
2276
  *
2106
2277
  * A part can be a function of the merge of the parts before it (`prev`, as an overlay of Nix). Thus a part can read
2107
2278
  * the entry that it replaces.
@@ -2112,16 +2283,14 @@ function mergeWorldInto(m, draft, part, index, replaces, base) {
2112
2283
  */
2113
2284
  function mergeWorlds(...parts) {
2114
2285
  const m = new Merger();
2115
- const draft = {
2116
- kinds: {},
2117
- defaults: {}
2118
- };
2286
+ const draft = worldDraft();
2119
2287
  parts.forEach((input, index) => {
2120
2288
  const part = typeof input === "function" ? input(worldOf(draft)) : input;
2121
2289
  mergeWorldInto(m, draft, part, index, new Set((part.replaces ?? []).map(keyOf)), []);
2122
2290
  });
2291
+ const world = worldOf(draft, m);
2123
2292
  if (m.conflicts.length > 0) throw new MergeError(m.conflicts);
2124
- return worldOf(draft);
2293
+ return world;
2125
2294
  }
2126
2295
  /** The fields of a machine that merge by their own rules. Each other field is one value, or a conflict. */
2127
2296
  const TABLES = /* @__PURE__ */ new Set([
@@ -2174,12 +2343,12 @@ function composeFacts(hooks, replaced) {
2174
2343
  }
2175
2344
  };
2176
2345
  }
2177
- function machineOf(draft) {
2346
+ function machineOf(draft, m) {
2178
2347
  const known = composeKnown(draft.hooks, draft.replaced);
2179
2348
  const facts = composeFacts(draft.factHooks, draft.replaced);
2180
2349
  return {
2181
2350
  ...draft.rest,
2182
- world: worldOf(draft.world),
2351
+ world: worldOf(draft.world, m),
2183
2352
  evaluators: { ...draft.evaluators },
2184
2353
  ...draft.runners === void 0 ? {} : { runners: { ...draft.runners } },
2185
2354
  ...draft.derefs === void 0 ? {} : { derefs: { ...draft.derefs } },
@@ -2253,10 +2422,7 @@ function checkHidden(m, draft) {
2253
2422
  function mergeMachines(...parts) {
2254
2423
  const m = new Merger();
2255
2424
  const draft = {
2256
- world: {
2257
- kinds: {},
2258
- defaults: {}
2259
- },
2425
+ world: worldDraft(),
2260
2426
  evaluators: {},
2261
2427
  hooks: [],
2262
2428
  factHooks: [],
@@ -2297,8 +2463,9 @@ function mergeMachines(...parts) {
2297
2463
  }
2298
2464
  });
2299
2465
  checkHidden(m, draft);
2466
+ const machine = machineOf(draft, m);
2300
2467
  if (m.conflicts.length > 0) throw new MergeError(m.conflicts);
2301
- return machineOf(draft);
2468
+ return machine;
2302
2469
  }
2303
2470
  /** This function removes the entry at a path from a table of entries. It gives the same table when no entry is there. */
2304
2471
  function removeEntry(entries, path) {
@@ -2411,6 +2578,136 @@ function restrictMachine(machine, paths) {
2411
2578
  ...tables.domains === void 0 ? {} : { domains: tables.domains }
2412
2579
  };
2413
2580
  }
2581
+ // src/language.ts
2582
+ const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
2583
+ function numbers$1(version) {
2584
+ const found = VERSION.exec(version);
2585
+ if (found === null) throw new TypeError(`A language version has the form MAJOR.MINOR.PATCH. The text ${JSON.stringify(version)} does not.`);
2586
+ return [
2587
+ Number(found[1]),
2588
+ Number(found[2]),
2589
+ Number(found[3])
2590
+ ];
2591
+ }
2592
+ /**
2593
+ * This function tells if a version satisfies a lowest version, by the rules of a caret range of SemVer. The first
2594
+ * number that is not zero stays the same, and the version is not lower.
2595
+ */
2596
+ function compatible(version, lowest) {
2597
+ const have = numbers$1(version);
2598
+ const want = numbers$1(lowest);
2599
+ const stable = want.findIndex((part) => part !== 0);
2600
+ const fixed = stable < 0 ? 3 : stable + 1;
2601
+ for (let index = 0; index < 3; index += 1) {
2602
+ const [a, b] = [have[index], want[index]];
2603
+ if (a !== b) return index >= fixed && a > b;
2604
+ }
2605
+ return true;
2606
+ }
2607
+ /** This function gives the JSON text of a value with its keys in order. A value that is not JSON is an error. */
2608
+ function canonical(value, path) {
2609
+ if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
2610
+ if (typeof value === "number" && Number.isFinite(value)) return JSON.stringify(value);
2611
+ if (Array.isArray(value)) return `[${value.map((item, index) => canonical(item, [...path, index])).join(",")}]`;
2612
+ if (!isRecord(value)) throw new TypeError(`The data half of a language is JSON. The value at ${JSON.stringify(path)} is not.`);
2613
+ return `{${Object.keys(value).filter((key) => value[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key], [...path, key])}`).join(",")}}`;
2614
+ }
2615
+ function frozen(value) {
2616
+ if (typeof value !== "object" || value === null) return value;
2617
+ for (const item of Object.values(value)) frozen(item);
2618
+ return Object.freeze(value);
2619
+ }
2620
+ /**
2621
+ * This function makes one value of a language. The data half is JSON, thus a host can store it and compare it. The
2622
+ * code half binds the evaluator names of the world.
2623
+ *
2624
+ * The language keeps a frozen copy of its data half, thus a later change to the input changes nothing.
2625
+ *
2626
+ * @param definition - The name, the version, the world, the needed languages and the code.
2627
+ * @returns The language.
2628
+ * @throws {TypeError} A version in the definition is not `MAJOR.MINOR.PATCH`, the name is empty, or the
2629
+ * data half is not JSON.
2630
+ */
2631
+ function defineLanguage(definition) {
2632
+ if (definition.name === "") throw new TypeError("A language has a name.");
2633
+ numbers$1(definition.version);
2634
+ const requires = definition.requires ?? {};
2635
+ for (const name of Object.keys(requires)) numbers$1(requires[name]);
2636
+ const data = canonical({
2637
+ requires,
2638
+ world: definition.world ?? {}
2639
+ }, []);
2640
+ const copy = frozen(JSON.parse(data));
2641
+ const language = {
2642
+ name: definition.name,
2643
+ version: definition.version,
2644
+ world: copy.world,
2645
+ requires: copy.requires,
2646
+ data
2647
+ };
2648
+ return Object.freeze(definition.code === void 0 ? language : {
2649
+ ...language,
2650
+ code: definition.code
2651
+ });
2652
+ }
2653
+ function partOf(language) {
2654
+ const { code, world } = language;
2655
+ return typeof code === "function" ? (prev) => ({
2656
+ ...code(prev),
2657
+ world
2658
+ }) : {
2659
+ ...code,
2660
+ world
2661
+ };
2662
+ }
2663
+ /**
2664
+ * This function merges languages into one machine, in the order of the arguments, with `mergeMachines`.
2665
+ *
2666
+ * - A language comes after each language that it names in `requires`, at a compatible version (LANG.REQUIRES).
2667
+ * - One language that comes twice counts once. Thus two languages can share a base.
2668
+ * - Two versions of one name, or two data halves under one name and one version, are conflicts (LANG.COMPOSE).
2669
+ *
2670
+ * @param languages - The languages.
2671
+ * @returns The machine.
2672
+ * @throws {MergeError} The languages do not fit, or their merge has a conflict. The error lists each conflict.
2673
+ */
2674
+ function compose(...languages) {
2675
+ const conflicts = [];
2676
+ const parts = [];
2677
+ const seen = /* @__PURE__ */ new Map();
2678
+ languages.forEach((language, index) => {
2679
+ const earlier = seen.get(language.name);
2680
+ const conflict = (name, other, reason) => {
2681
+ conflicts.push({
2682
+ path: ["languages", name],
2683
+ parts: [Math.min(other, index), Math.max(other, index)],
2684
+ reason
2685
+ });
2686
+ };
2687
+ if (earlier !== void 0) {
2688
+ if (earlier.language.version !== language.version) conflict(language.name, earlier.index, "Two versions of one language.");
2689
+ else if (earlier.language.data !== language.data) conflict(language.name, earlier.index, "Two languages with one name and one version differ.");
2690
+ else if (earlier.language.code !== language.code) parts.push(partOf(language));
2691
+ return;
2692
+ }
2693
+ for (const name of Object.keys(language.requires)) {
2694
+ const needed = language.requires[name];
2695
+ const found = seen.get(name);
2696
+ if (found === void 0) {
2697
+ const later = languages.findIndex((other) => other.name === name);
2698
+ if (later < 0) conflict(name, index, `A language requires ${name}, and the composition does not have it.`);
2699
+ else conflict(name, later, `A language requires ${name}, and ${name} comes after it.`);
2700
+ } else if (!compatible(found.language.version, needed)) conflict(name, found.index, `A language requires ${name} ${needed}, and the composition has ${found.language.version}.`);
2701
+ }
2702
+ seen.set(language.name, {
2703
+ language,
2704
+ index
2705
+ });
2706
+ parts.push(partOf(language));
2707
+ });
2708
+ if (conflicts.length > 0) throw new MergeError(conflicts);
2709
+ return mergeMachines(...parts);
2710
+ }
2414
2711
  // src/ladder.ts
2415
2712
  /** The rungs of the ladder, in the order that a question visits them. */
2416
2713
  const RUNGS = [
@@ -6177,11 +6474,17 @@ const STD_EVALUATORS = {
6177
6474
  const [a, b] = values;
6178
6475
  return ok((a ?? 0) > (b ?? 0));
6179
6476
  },
6180
- "ref.resolve": ({ data, resolve }) => {
6181
- if (!Array.isArray(data) || data.length === 0) return fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
6182
- const found = resolve(data);
6183
- return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
6184
- },
6477
+ "ref.resolve": { compile: ({ data, machine }) => {
6478
+ if (!Array.isArray(data) || data.length === 0) {
6479
+ const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
6480
+ return () => bad;
6481
+ }
6482
+ const path = compilePath(machine, data);
6483
+ return (ctx, rt) => {
6484
+ const found = resolveCompiled(rt, ctx, path);
6485
+ return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
6486
+ };
6487
+ } },
6185
6488
  "tree.node": (input) => treeNode(input, void 0)
6186
6489
  };
6187
6490
  /** The brand of a node of the decorated tree. Its value is the state that the kit reads. */
@@ -7843,6 +8146,11 @@ function boundariesOf(typing, domains) {
7843
8146
  });
7844
8147
  return found;
7845
8148
  }
8149
+ /** The result of a test that proves nothing. */
8150
+ const NO_PROOFS$1 = {
8151
+ yes: [],
8152
+ no: []
8153
+ };
7846
8154
  /** The roots that can change, as a rule gives them in `PartType.roots`. */
7847
8155
  const CHANGING = [
7848
8156
  "self",
@@ -7865,8 +8173,12 @@ function ruleOf(machine, name) {
7865
8173
  const rule = typeof evaluator === "object" && evaluator !== null || typeof evaluator === "function" ? evaluator.type : void 0;
7866
8174
  return typeof rule === "object" && rule !== null && typeof rule.synth === "function" ? rule : void 0;
7867
8175
  }
7868
- /** This function gives the order of the parts of a document: first the parts that give the names of a rule `keysAt`. */
7869
- function orderOf(node, nodes) {
8176
+ /** This function gives the order of the parts of a document. The part `first` of the rule comes first, or else the parts that give the names of a rule `keysAt`. */
8177
+ function orderOf(node, nodes, first) {
8178
+ if (first !== void 0) {
8179
+ const isFirst = (index) => String(nodes[index].visit.slot[0]) === first;
8180
+ return [...node.children.filter(isFirst), ...node.children.filter((index) => !isFirst(index))];
8181
+ }
7870
8182
  const sources = (node.visit.kind?.scope ?? []).flatMap((rule) => "keysAt" in rule.names ? [rule.names.keysAt] : []);
7871
8183
  if (sources.length === 0 || node.children.length < 2) return node.children;
7872
8184
  const gives = (index) => {
@@ -7890,7 +8202,7 @@ function message(error) {
7890
8202
  * @param rootTypes - The types of the roots and of the names of the host.
7891
8203
  * @returns The type of the root, the type issues in the order of the document, and the type of each document.
7892
8204
  */
7893
- function typePass(machine, nodes, verb, expect, rootTypes = {}) {
8205
+ function typePass(machine, nodes, verb, expect, rootTypes = {}, hasEffect = () => false) {
7894
8206
  if (nodes.length === 0) return {
7895
8207
  type: "unknown",
7896
8208
  issues: [],
@@ -7902,6 +8214,29 @@ function typePass(machine, nodes, verb, expect, rootTypes = {}) {
7902
8214
  const types = new Array(nodes.length);
7903
8215
  const needed = new Array(nodes.length);
7904
8216
  const seen = /* @__PURE__ */ new Set();
8217
+ const inputs = new Array(nodes.length);
8218
+ let changes;
8219
+ const rootsChange = () => changes ??= nodes.some(hasEffect);
8220
+ const narrowed = (names, roots, proofs) => {
8221
+ let nextNames = names;
8222
+ let nextRoots = roots;
8223
+ for (const proof of proofs) {
8224
+ const head = proof.path[0];
8225
+ if (head === "names") {
8226
+ const name = proof.path[1];
8227
+ if (name === void 0) continue;
8228
+ if (nextNames === names) nextNames = new Map(names);
8229
+ nextNames.set(name, narrowType(nextNames.get(name) ?? "unknown", proof, 2, domains));
8230
+ } else if ((head === "self" || head === "world" || head === "event") && !rootsChange()) nextRoots = {
8231
+ ...nextRoots,
8232
+ [head]: narrowType(nextRoots[head], proof, 1, domains)
8233
+ };
8234
+ }
8235
+ return {
8236
+ names: nextNames,
8237
+ roots: nextRoots
8238
+ };
8239
+ };
7905
8240
  const issueAt = (index, problem) => {
7906
8241
  const visit = nodes[index].visit;
7907
8242
  const issue = {
@@ -7939,19 +8274,30 @@ function typePass(machine, nodes, verb, expect, rootTypes = {}) {
7939
8274
  typeOf: (path) => typed.get(pathKey(path)) ?? "unknown",
7940
8275
  nameType: (name) => names.get(name),
7941
8276
  rootType: (root) => root === "caps" ? CAPS_TYPE : root === "self" || root === "world" || root === "event" ? roots[root] : "unknown",
7942
- contract: (type, what) => contract(index, type, what)
8277
+ contract: (type, what) => contract(index, type, what),
8278
+ readOf: (path) => {
8279
+ const key = pathKey(path);
8280
+ const part = node.children.find((child) => pathKey(nodes[child].visit.slot) === key);
8281
+ const read = part === void 0 ? void 0 : ruleOf(machine, nodes[part].evaluator)?.reads;
8282
+ return read === void 0 || nodes[part].marked ? void 0 : read(inputs[part]);
8283
+ },
8284
+ provenBy: (path) => frame.proven?.get(pathKey(path)) ?? NO_PROOFS$1
7943
8285
  };
7944
- return {
8286
+ inputs[index] = input;
8287
+ const rule = node.marked ? void 0 : ruleOf(machine, node.evaluator);
8288
+ const frame = {
7945
8289
  index,
7946
- rule: node.marked ? void 0 : ruleOf(machine, node.evaluator),
8290
+ rule,
7947
8291
  input,
7948
- order: orderOf(node, nodes),
8292
+ order: orderOf(node, nodes, rule?.first),
7949
8293
  next: 0,
7950
8294
  typed,
7951
8295
  needs,
7952
8296
  names,
7953
- roots
8297
+ roots,
8298
+ proven: void 0
7954
8299
  };
8300
+ return frame;
7955
8301
  };
7956
8302
  const ruleFailed = (frame, error) => {
7957
8303
  issueAt(frame.index, { reason: `The type rule of ${nodes[frame.index].evaluator ?? "the evaluator"} fails: ${message(error)}` });
@@ -7999,12 +8345,22 @@ function typePass(machine, nodes, verb, expect, rootTypes = {}) {
7999
8345
  if (contracts && node.expect !== void 0) need(contract(child, node.expect, `expected type of the ${what}`), what);
8000
8346
  if (part.expect !== void 0) need(part.expect, part.what ?? what);
8001
8347
  let names = frame.names;
8348
+ let kept = frame.roots;
8349
+ if (part.narrow !== void 0 && part.narrow.length > 0) {
8350
+ const after = narrowed(names, kept, part.narrow);
8351
+ names = after.names;
8352
+ kept = after.roots;
8353
+ if (part.keep === true) {
8354
+ frame.names = names;
8355
+ frame.roots = kept;
8356
+ }
8357
+ }
8002
8358
  if (node.bound.length > 0) {
8003
8359
  const copy = new Map(names);
8004
8360
  for (const name of node.bound) copy.set(name, part.names !== void 0 && Object.hasOwn(part.names, name) ? part.names[name] : "unknown");
8005
8361
  names = copy;
8006
8362
  }
8007
- let roots = frame.rule === void 0 ? UNKNOWN_ROOTS : frame.roots;
8363
+ let roots = frame.rule === void 0 ? UNKNOWN_ROOTS : kept;
8008
8364
  const changes = part.roots;
8009
8365
  if (typeof changes === "object" && changes !== null) {
8010
8366
  const copy = { ...roots };
@@ -8039,7 +8395,15 @@ function typePass(machine, nodes, verb, expect, rootTypes = {}) {
8039
8395
  }
8040
8396
  types[frame.index] = type;
8041
8397
  needed[frame.index] = frame.needs.length === 0 ? void 0 : frame.needs[frame.needs.length - 1].type;
8042
- stack[stack.length - 1]?.typed.set(pathKey(node.visit.slot), type);
8398
+ const parent = stack[stack.length - 1];
8399
+ if (parent === void 0) continue;
8400
+ const key = pathKey(node.visit.slot);
8401
+ parent.typed.set(key, type);
8402
+ if (parent.rule?.usesProofs === true && frame.rule?.proves !== void 0) try {
8403
+ (parent.proven ??= /* @__PURE__ */ new Map()).set(key, frame.rule.proves(frame.input));
8404
+ } catch (error) {
8405
+ ruleFailed(frame, error);
8406
+ }
8043
8407
  }
8044
8408
  const places = nodes.map((node, index) => {
8045
8409
  const want = needed[index];
@@ -9838,7 +10202,8 @@ function check(machine, doc, options = {}) {
9838
10202
  function typeCheck(machine, doc, options = {}) {
9839
10203
  const nodes = [];
9840
10204
  const issues = checkAll(machine, doc, options, nodes);
9841
- const pass = typePass(machine, nodes, options.verb ?? "eval", options.expect, options.rootTypes);
10205
+ const verb = options.verb ?? "eval";
10206
+ const pass = typePass(machine, nodes, verb, options.expect, options.rootTypes, (node) => declaredEffects(node.visit, verb, machine, node.evaluator).length > 0);
9842
10207
  return {
9843
10208
  type: pass.type,
9844
10209
  issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues],
@@ -10843,6 +11208,13 @@ function decodeValue(machine, doc) {
10843
11208
  return prepare(machine, doc, { declaredType: "lit" }).evaluate(makeContext());
10844
11209
  }
10845
11210
  // src/formula.ts
11211
+ /** The most proofs that one test keeps. A proof only adds knowledge, thus the check can leave one out. */
11212
+ const MAX_PROOFS = 32;
11213
+ /** The result of a test that proves nothing. */
11214
+ const NO_PROOFS = {
11215
+ yes: [],
11216
+ no: []
11217
+ };
10846
11218
  /** The mark of a field that the profile leaves out of a record. */
10847
11219
  const SKIPPED = /* @__PURE__ */ Symbol("skipped");
10848
11220
  /** The value of a name whose binding failed. A reference to the name gives the error. */
@@ -11243,6 +11615,11 @@ function formulaTypes(profile) {
11243
11615
  type: sig.to,
11244
11616
  what: `result of a signature of ops.${name}`
11245
11617
  });
11618
+ if (sig.proves !== void 0) out.push({
11619
+ at: [...at, "proves"],
11620
+ type: sig.proves,
11621
+ what: `proved type of a signature of ops.${name}`
11622
+ });
11246
11623
  const earlier = list.slice(0, index).findIndex((other) => isSignature(other) && sameParams(other, sig));
11247
11624
  if (earlier >= 0) out.push({
11248
11625
  at,
@@ -11253,7 +11630,30 @@ function formulaTypes(profile) {
11253
11630
  return out;
11254
11631
  };
11255
11632
  const logic = (word) => ({
11256
- part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
11633
+ usesProofs: true,
11634
+ part: (input, path) => {
11635
+ const index = Number(path[0]);
11636
+ const part = truthPart(input, `term ${index + 1} of ${word}`);
11637
+ if (index === 0) return part;
11638
+ const before = input.provenBy([index - 1]);
11639
+ return {
11640
+ ...part,
11641
+ narrow: word === "and" ? before.yes : before.no,
11642
+ keep: true
11643
+ };
11644
+ },
11645
+ proves: (input) => {
11646
+ const each = input.paths.map((path) => input.provenBy(path));
11647
+ const all = (side) => each.flatMap((proven) => proven[side]).slice(0, MAX_PROOFS);
11648
+ const one = each.length === 1 ? each[0] : void 0;
11649
+ return word === "and" ? {
11650
+ yes: all("yes"),
11651
+ no: one?.no ?? []
11652
+ } : {
11653
+ yes: one?.yes ?? [],
11654
+ no: all("no")
11655
+ };
11656
+ },
11257
11657
  synth: (input) => {
11258
11658
  if (!logicValue) return "bool";
11259
11659
  if (input.paths.length === 0) return { one: [word === "and"] };
@@ -11264,6 +11664,11 @@ function formulaTypes(profile) {
11264
11664
  "formula.lit": { synth: (input) => valueType(input.doc.value) },
11265
11665
  "formula.ref": {
11266
11666
  read: true,
11667
+ reads: (input) => {
11668
+ const path = input.doc.value;
11669
+ if (input.address || !Array.isArray(path) || path.length === 0) return void 0;
11670
+ return path.every((step) => typeof step === "string") ? path : void 0;
11671
+ },
11267
11672
  synth: (input, report) => {
11268
11673
  const path = input.doc.value;
11269
11674
  if (input.address || !Array.isArray(path) || path.length === 0) return "unknown";
@@ -11407,11 +11812,16 @@ function formulaTypes(profile) {
11407
11812
  type: truth,
11408
11813
  what: "truth type of the profile"
11409
11814
  }] : [],
11815
+ first: "test",
11816
+ usesProofs: true,
11410
11817
  part: (input, path) => {
11411
11818
  if (path[0] === "test") return truthPart(input, "test of if");
11412
- return isUnknown(input.expected, input.machine.domains) ? {} : {
11819
+ const proven = input.provenBy(["test"]);
11820
+ const narrow = path[0] === "then" ? proven.yes : proven.no;
11821
+ return isUnknown(input.expected, input.machine.domains) ? { narrow } : {
11413
11822
  expect: input.expected,
11414
- what: `${String(path[0])} branch of if`
11823
+ what: `${String(path[0])} branch of if`,
11824
+ narrow
11415
11825
  };
11416
11826
  },
11417
11827
  synth: (input) => {
@@ -11424,6 +11834,41 @@ function formulaTypes(profile) {
11424
11834
  "formula.or": logic("or"),
11425
11835
  "formula.op": {
11426
11836
  declared: sigsDeclared,
11837
+ proves: (input) => {
11838
+ const sigs = signatures(input)?.sigs;
11839
+ const sig = sigs?.length === 1 ? sigs[0] : void 0;
11840
+ if (sig === void 0 || sig.proves === void 0 && sig.present !== true || input.paths.length === 0) return NO_PROOFS;
11841
+ const path = input.readOf(input.paths[0]);
11842
+ if (path === void 0) return NO_PROOFS;
11843
+ const yes = [];
11844
+ const no = [];
11845
+ if (sig.proves !== void 0 && typeIssues(input.contract(sig.proves, `proved type of ops.${nameOf(input)}`), input.machine.domains).length === 0) {
11846
+ yes.push({
11847
+ path,
11848
+ fact: "is",
11849
+ type: sig.proves
11850
+ });
11851
+ no.push({
11852
+ path,
11853
+ fact: "not",
11854
+ type: sig.proves
11855
+ });
11856
+ }
11857
+ if (sig.present === true) {
11858
+ yes.push({
11859
+ path,
11860
+ fact: "present"
11861
+ });
11862
+ no.push({
11863
+ path,
11864
+ fact: "absent"
11865
+ });
11866
+ }
11867
+ return {
11868
+ yes,
11869
+ no
11870
+ };
11871
+ },
11427
11872
  part: (input, path) => {
11428
11873
  const read = signatures(input);
11429
11874
  if (read === void 0 || read.sigs.length !== 1) return {};
@@ -13211,10 +13656,23 @@ const treeDefinition = ({ parts, context, kind, place, machine, typePath, resolv
13211
13656
  }
13212
13657
  };
13213
13658
  /**
13659
+ * The definition of the standard evaluator `ref.resolve` (REF.RESOLVE, D133). It resolves the steps of the path at each
13660
+ * evaluation. The plan compiles the same path one time.
13661
+ */
13662
+ const refDefinition = ({ data, resolve: walk }) => {
13663
+ if (!Array.isArray(data) || data.length === 0) return fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
13664
+ const found = walk(data);
13665
+ return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
13666
+ };
13667
+ /**
13214
13668
  * The definitions of the reference for the evaluators of the standard machine that need their own reading. The
13215
- * reference uses them for the standard evaluators, also without the option `definitions`. It holds `tree.node`.
13669
+ * reference uses them for the standard evaluators, also without the option `definitions`. It holds `tree.node` and
13670
+ * `ref.resolve`. A host that wraps `ref.resolve` wraps this function, because the standard evaluator is compiled.
13216
13671
  */
13217
- const STD_DEFINITIONS = { evaluators: { "tree.node": treeDefinition } };
13672
+ const STD_DEFINITIONS = { evaluators: {
13673
+ "tree.node": treeDefinition,
13674
+ "ref.resolve": refDefinition
13675
+ } };
13218
13676
  const CLOSURE$1 = /* @__PURE__ */ Symbol.for("coeval.closure");
13219
13677
  /** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
13220
13678
  function setField$1(target, key, value) {
@@ -20838,6 +21296,6 @@ function samplesAt(values, closed, depth) {
20838
21296
  function typeFromSamples(values, options = {}) {
20839
21297
  return samplesAt(values, options.closed === true, 0);
20840
21298
  }
20841
- export { ATTRIBUTE_RUNGS, CAPS_TYPE, CIRCULAR_LIMIT, CONSTRUCTS, ENVELOPE_KEYS, Failure, GENERATION_LIMIT, Held, JOURNAL, MAX_SYNTAX_ISSUES, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, agreeTypes, argsKey, ask, atArgsOf, atRuleOf, attributeGraph, attributeTrace, attributeVertexId, boundariesOf, capKeys, check, checkCycles, checkLatent, checkStep, checkSteps, checkSyntax, checkWorld, circularOf, classify, codecLaws, collectOf, compilePath, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, documentKey, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeLog, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, ifRunner, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isStep, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapDocs, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partGrants, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, printReads, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, resolveCompiled, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stepLadder, stepOf, stepRank, stored, syntaxFormat, tag, thrown, toJson, treeNodeEvaluator, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
21299
+ export { ATTRIBUTE_RUNGS, CAPS_TYPE, CIRCULAR_LIMIT, CONSTRUCTS, ENVELOPE_KEYS, Failure, GENERATION_LIMIT, Held, JOURNAL, MAX_SYNTAX_ISSUES, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, agreeTypes, argsKey, ask, atArgsOf, atRuleOf, attributeGraph, attributeTrace, attributeVertexId, boundariesOf, capKeys, check, checkCycles, checkLatent, checkStep, checkSteps, checkSyntax, checkWorld, circularOf, classify, codecLaws, collectOf, compilePath, components, compose, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, defineLanguage, derefStep, describe, docEquals, documentKey, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeLog, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, ifRunner, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isStep, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapDocs, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partGrants, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, printReads, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, resolveCompiled, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stepLadder, stepOf, stepRank, stored, syntaxFormat, tag, thrown, toJson, treeNodeEvaluator, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
20842
21300
 
20843
21301
  //# sourceMappingURL=index.js.map