@mark1russell7/coeval 0.8.0 → 0.10.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/build/index.js CHANGED
@@ -368,6 +368,21 @@ function equationType(kind) {
368
368
  return parts?.of ?? parts?.rest?.of;
369
369
  }
370
370
  /**
371
+ * This function gives the effects that the contract of a kind declares for a verb (VERB.EFFECTS, D49). For `eval`,
372
+ * the field `effects` declares them, and a kind without the field declares no effect. For another verb, the entry of
373
+ * `verbEffects` declares them. `check` and `conforms` read the declarations with this one function.
374
+ *
375
+ * @param kind - The kind.
376
+ * @param verb - The verb.
377
+ * @returns The declaration in the form of `effects`, or `undefined` when the contract does not declare the effects of
378
+ * the verb.
379
+ */
380
+ function effectsOfVerb(kind, verb) {
381
+ if (verb === "eval") return kind.effects ?? [];
382
+ const table = kind.verbEffects;
383
+ return table !== void 0 && Object.hasOwn(table, verb) ? table[verb] : void 0;
384
+ }
385
+ /**
371
386
  * This function tells if an entry of the world is a kind.
372
387
  *
373
388
  * @param entry - The entry.
@@ -546,6 +561,10 @@ const STD_DOMAINS = {
546
561
  is: (value) => value === null,
547
562
  summary: "The value null."
548
563
  },
564
+ undefined: {
565
+ is: (value) => value === void 0,
566
+ summary: "The value undefined. A document holds it as the extension ext.undefined."
567
+ },
549
568
  bool: {
550
569
  is: (value) => typeof value === "boolean",
551
570
  summary: "A truth value: true or false."
@@ -2071,7 +2090,7 @@ function context(parts = {}) {
2071
2090
  };
2072
2091
  }
2073
2092
  const MAX_COLLAPSE = 64;
2074
- const NO_HOPS = /* @__PURE__ */ Object.freeze([]);
2093
+ const NO_HOPS$1 = /* @__PURE__ */ Object.freeze([]);
2075
2094
  /** A value that can be a reference document: a record with the type `ref`. The full check comes after. */
2076
2095
  function mayBeRef(thing) {
2077
2096
  return typeof thing === "object" && thing !== null && thing.type === "ref";
@@ -2122,13 +2141,13 @@ function walkQuick(start, path, plain, keyed) {
2122
2141
  ok: false,
2123
2142
  code: "#N/A",
2124
2143
  reason: `The path ${keys.join(".")} reaches nothing.`,
2125
- hops: NO_HOPS
2144
+ hops: NO_HOPS$1
2126
2145
  };
2127
2146
  if (!plain && mayBeRef(reached.thing)) return void 0;
2128
2147
  return {
2129
2148
  ok: true,
2130
2149
  thing: reached.thing,
2131
- hops: NO_HOPS
2150
+ hops: NO_HOPS$1
2132
2151
  };
2133
2152
  }
2134
2153
  const step = path[index];
@@ -2145,7 +2164,7 @@ function walkQuick(start, path, plain, keyed) {
2145
2164
  return {
2146
2165
  ok: true,
2147
2166
  thing: here,
2148
- hops: NO_HOPS
2167
+ hops: NO_HOPS$1
2149
2168
  };
2150
2169
  }
2151
2170
  /** The root whose value starts the short path of a compiled path. `FULL` takes the full algorithm at once. */
@@ -2256,7 +2275,7 @@ function resolve(nav, ctx, path, options = {}) {
2256
2275
  * @returns What the path reached and each hop, or an error.
2257
2276
  */
2258
2277
  function resolveFully$1(nav, ctx, path, hops, known) {
2259
- const done = hops ?? NO_HOPS;
2278
+ const done = hops ?? NO_HOPS$1;
2260
2279
  const failed = (code, reason) => ({
2261
2280
  ok: false,
2262
2281
  code,
@@ -3054,6 +3073,7 @@ var Session = class {
3054
3073
  if (reading === void 0) return this.find(ctx, path, compiled, hops);
3055
3074
  const free = isFree(reading.root, ctx, path);
3056
3075
  if (!free && path[0] === "self" && !reading.self) this.keep(reading, ["self"]);
3076
+ else if (free && path.length === 1 && path[0] === "self") reading.self = true;
3057
3077
  let found;
3058
3078
  try {
3059
3079
  found = this.find(ctx, path, compiled, hops);
@@ -4290,6 +4310,7 @@ const STD_WORLD = {
4290
4310
  caps: [{ rule: "mask" }],
4291
4311
  pure: true,
4292
4312
  lazy: [{}],
4313
+ reads: ["self"],
4293
4314
  summary: "A node of a tree. Each part is an attribute that evaluates on demand, one time, with the node as self."
4294
4315
  },
4295
4316
  box: {
@@ -4309,6 +4330,7 @@ const STD_WORLD = {
4309
4330
  caps: [{ rule: "mask" }],
4310
4331
  pure: true,
4311
4332
  lazy: [{}],
4333
+ reads: ["self"],
4312
4334
  summary: "A node of a tree, as panel. A formula in a box can read its parent and its siblings."
4313
4335
  },
4314
4336
  math: { kinds: {
@@ -4553,11 +4575,12 @@ function plainOf(value, path) {
4553
4575
  * step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
4554
4576
  * the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
4555
4577
  * fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
4578
+ * The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
4556
4579
  */
4557
- function treeThing(input, build) {
4580
+ function treeThing(input, build, outer) {
4558
4581
  const { parts, context, kind: type, place, machine } = input;
4559
4582
  const { tree, of } = contractOf$1(machine, input.typePath);
4560
- const above = stateOf(context.self);
4583
+ const above = stateOf(outer);
4561
4584
  const up = above !== void 0 && above.build === build ? above : void 0;
4562
4585
  const cells = /* @__PURE__ */ new Map();
4563
4586
  /** The positions of the items of the list `children`, for the names of the children. */
@@ -4721,7 +4744,7 @@ function treeThing(input, build) {
4721
4744
  type,
4722
4745
  label: `node ${type}`,
4723
4746
  deref(key) {
4724
- if (key === "parent") return context.self;
4747
+ if (key === "parent") return outer;
4725
4748
  if (key === "type") return type;
4726
4749
  const result = read(String(key));
4727
4750
  if (result === void 0) return void 0;
@@ -4752,15 +4775,22 @@ function treeThing(input, build) {
4752
4775
  function attributeTrace(node, attribute) {
4753
4776
  return stateOf(node)?.trace(attribute);
4754
4777
  }
4778
+ /** The path of the root `self`. A node of the tree resolves it, thus the list of reads holds the read (C4). */
4779
+ const SELF_PATH = /* @__PURE__ */ Object.freeze(["self"]);
4780
+ /** The options of the resolution of `self`: the node needs no hops. */
4781
+ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
4755
4782
  /**
4756
4783
  * This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
4757
- * thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree.
4784
+ * thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
4785
+ * node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
4758
4786
  */
4759
4787
  function treeNode(input) {
4760
- const above = stateOf(input.context.self);
4761
- if (above !== void 0 && above.build.open) return ok(treeThing(input, above.build));
4788
+ const found = input.resolve(SELF_PATH, NO_HOPS);
4789
+ const outer = found.ok ? found.thing : void 0;
4790
+ const above = stateOf(outer);
4791
+ if (above !== void 0 && above.build.open) return ok(treeThing(input, above.build, outer));
4762
4792
  const build = { open: true };
4763
- const root = treeThing(input, build);
4793
+ const root = treeThing(input, build, outer);
4764
4794
  try {
4765
4795
  return stateOf(root).json(/* @__PURE__ */ new Set());
4766
4796
  } finally {
@@ -5749,16 +5779,27 @@ const EACH$2 = /* @__PURE__ */ Object.freeze([[]]);
5749
5779
  /** No declared effect. One list serves each visit without effects. */
5750
5780
  const NO_EFFECTS = /* @__PURE__ */ Object.freeze([]);
5751
5781
  /**
5752
- * This function gives the effects that the contract of the kind of a visit declares, as keys (FACTS.EFFECTS). The
5753
- * text `"type"` gives the type of the document after each alias. A visit without a kind declares no effect. `check`,
5754
- * `factsOf` and `effectsOf` read the declarations with this one function (D27).
5782
+ * This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
5783
+ * (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
5784
+ * kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
5755
5785
  *
5756
5786
  * @param v - The visit.
5787
+ * @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
5757
5788
  * @returns The keys of each declared effect, in the order of the contract.
5758
5789
  */
5759
- function declaredEffects(v) {
5760
- const effects = v.kind?.effects;
5761
- if (effects === void 0 || effects.length === 0) return NO_EFFECTS;
5790
+ function declaredEffects(v, verb = "eval") {
5791
+ return verbDeclared(v, verb) ?? NO_EFFECTS;
5792
+ }
5793
+ /**
5794
+ * This function gives the effects that the contract of the kind of a visit declares for a verb, as keys. The result
5795
+ * is `undefined` when the contract has no declaration for the verb (D49). A visit without a kind declares no effect
5796
+ * for `eval` and no declaration for another verb.
5797
+ */
5798
+ function verbDeclared(v, verb) {
5799
+ if (v.kind === void 0) return verb === "eval" ? NO_EFFECTS : void 0;
5800
+ const effects = effectsOfVerb(v.kind, verb);
5801
+ if (effects === void 0) return void 0;
5802
+ if (effects.length === 0) return NO_EFFECTS;
5762
5803
  return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
5763
5804
  }
5764
5805
  /**
@@ -5809,7 +5850,9 @@ function capReason(v, kind, allowed) {
5809
5850
  * A run does not always fail there, thus a type issue is a static finding, not an error of a run.
5810
5851
  *
5811
5852
  * The check asks the ladder for the verb of the options, `eval` by default (VERB.CHECK). The shapes, the paths and the
5812
- * names follow the contracts, thus they are the same for each verb. Only `eval` checks the declared effects.
5853
+ * names follow the contracts, thus they are the same for each verb. The check tests the effects that each contract
5854
+ * declares for the verb: `effects` for `eval`, and the entry of `verbEffects` for another verb (VERB.EFFECTS). A kind
5855
+ * without a declaration for the verb gives no `#CAP!` and no `#CALC!` for an effect.
5813
5856
  *
5814
5857
  * @param machine - The world and the code of the evaluators.
5815
5858
  * @param doc - The document, as JSON.
@@ -5847,6 +5890,66 @@ function typeCheck(machine, doc, options = {}) {
5847
5890
  places: pass.places
5848
5891
  };
5849
5892
  }
5893
+ /** This function tells if a table of verbs has an entry for a verb. */
5894
+ function hasVerb(table, verb) {
5895
+ return table !== void 0 && Object.hasOwn(table, verb);
5896
+ }
5897
+ /**
5898
+ * This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
5899
+ * verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
5900
+ * evaluator for the verb, thus the check gives the same proof of coverage as a type system.
5901
+ *
5902
+ * The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
5903
+ * world. It does not follow an alias, because the kind of an alias has its own place. The check reads only the world.
5904
+ *
5905
+ * @param machine - The machine, for its world.
5906
+ * @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
5907
+ * @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
5908
+ * order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
5909
+ */
5910
+ function checkWorld(machine, verbs) {
5911
+ const world = machine.world;
5912
+ const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
5913
+ const out = [];
5914
+ if (open.length === 0) return out;
5915
+ const visit = (kind, path, fallback) => {
5916
+ const text = path.join(".");
5917
+ const name = fallback ? path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}` : `the kind ${text}`;
5918
+ for (const verb of open) {
5919
+ if (hasVerb(kind.verbs, verb)) continue;
5920
+ const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
5921
+ out.push(fallback ? {
5922
+ code: "#NOEVAL",
5923
+ kind: path,
5924
+ fallback: true,
5925
+ verb,
5926
+ reason
5927
+ } : {
5928
+ code: "#NOEVAL",
5929
+ kind: path,
5930
+ verb,
5931
+ reason
5932
+ });
5933
+ }
5934
+ };
5935
+ const way = /* @__PURE__ */ new Set();
5936
+ const entries = (kinds, path) => {
5937
+ for (const key of Object.keys(kinds)) {
5938
+ const entry = kinds[key];
5939
+ const here = [...path, key];
5940
+ if (isKind(entry)) visit(entry, here, false);
5941
+ else if (isNamespace(entry) && !way.has(entry)) {
5942
+ way.add(entry);
5943
+ entries(entry.kinds, here);
5944
+ if (entry.fallback !== void 0) visit(entry.fallback, here, true);
5945
+ way.delete(entry);
5946
+ }
5947
+ }
5948
+ };
5949
+ entries(world.kinds, []);
5950
+ if (world.fallback !== void 0) visit(world.fallback, [], true);
5951
+ return out;
5952
+ }
5850
5953
  /** The names that an equation of `down` sees (TREE.DOWN). */
5851
5954
  const DOWN_NAMES = /* @__PURE__ */ Object.freeze([
5852
5955
  "child",
@@ -5930,7 +6033,6 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
5930
6033
  issues.push(issue);
5931
6034
  if (current !== void 0) current.marked = true;
5932
6035
  };
5933
- const effects = verb === "eval";
5934
6036
  const allowed = options.caps === void 0 ? void 0 : permitsOf(machine);
5935
6037
  const waits = options.run === true ? /* @__PURE__ */ new Map() : void 0;
5936
6038
  const drives = [];
@@ -6001,7 +6103,8 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
6001
6103
  });
6002
6104
  }
6003
6105
  const caps = v.caps;
6004
- const declared = effects ? declaredEffects(v) : NO_EFFECTS;
6106
+ const stated = verbDeclared(v, verb);
6107
+ const declared = stated ?? NO_EFFECTS;
6005
6108
  if (allowed !== void 0 && caps !== void 0) {
6006
6109
  for (const kind of declared) if (!allowed(caps, kind)) push({
6007
6110
  code: "#CAP!",
@@ -6023,14 +6126,14 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
6023
6126
  },
6024
6127
  index: issues.length,
6025
6128
  node: current,
6026
- wait: !effects
6129
+ wait: false
6027
6130
  };
6028
6131
  drives.push(chain);
6029
6132
  } else if (runner) chain = up ?? true;
6030
6133
  waits.set(v, chain);
6031
6134
  if (typeof chain === "object" && !chain.wait) {
6032
6135
  const rule = allowed ?? permits;
6033
- chain.wait = declared.some((kind) => caps === void 0 || rule(caps, kind));
6136
+ chain.wait = stated === void 0 || declared.some((kind) => caps === void 0 || rule(caps, kind));
6034
6137
  }
6035
6138
  const first = declared[0];
6036
6139
  if (!runner && first !== void 0) push({
@@ -6178,7 +6281,8 @@ function partLazy(above, parent, slot) {
6178
6281
  * by the contracts of its kinds:
6179
6282
  *
6180
6283
  * - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
6181
- * `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`.
6284
+ * `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
6285
+ * declares `reads` adds a read of each of its roots at each document of the kind.
6182
6286
  * - `side` and `effects`: from the contract of each kind.
6183
6287
  * - `pure`: each kind declares `pure`, and the document has no effect and is not volatile.
6184
6288
  * - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
@@ -6231,18 +6335,35 @@ function factsOf(machine, doc, options = {}) {
6231
6335
  }
6232
6336
  const code = codeOf(world, v.declared ? void 0 : v.doc.own, kind, verb, "eval");
6233
6337
  const answer = code.name === void 0 ? void 0 : hook?.(v.typePath ?? [], code.name);
6338
+ let roots;
6234
6339
  if (code.described) {
6235
6340
  note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
6236
6341
  above.set(v, {
6237
6342
  lazy,
6238
6343
  rules: answer?.lazy ?? kind.lazy
6239
6344
  });
6345
+ roots = answer?.reads ?? kind.reads;
6240
6346
  } else {
6241
6347
  note(answer?.pure, answer?.volatile);
6242
6348
  above.set(v, {
6243
6349
  lazy,
6244
6350
  rules: answer?.lazy ?? EACH$1
6245
6351
  });
6352
+ roots = answer?.reads;
6353
+ }
6354
+ for (const root of roots ?? NONE) {
6355
+ const read = {
6356
+ root,
6357
+ keys: NONE,
6358
+ exact: true,
6359
+ lazy,
6360
+ address: false,
6361
+ path: [root],
6362
+ place: tight(v.place),
6363
+ at: tight(v.at)
6364
+ };
6365
+ reads.push(read);
6366
+ if (options.volatile?.(read) === true) volatile = true;
6246
6367
  }
6247
6368
  if (kind.side === "run") side = "run";
6248
6369
  for (const keys of declaredEffects(v)) {
@@ -6764,9 +6885,13 @@ function formulaKinds() {
6764
6885
  }
6765
6886
  };
6766
6887
  }
6767
- /** This function gives the type of a value of a profile: the value itself as `one`. `undefined` and a function give `unknown`. */
6888
+ /**
6889
+ * This function gives the type of a value of a profile: the value itself as `one`. `undefined` gives the base type
6890
+ * `undefined`, because JSON and thus `one` cannot hold it (D48). A function gives `unknown`.
6891
+ */
6768
6892
  function valueType(value) {
6769
- return value === void 0 || typeof value === "function" ? "unknown" : { one: [value] };
6893
+ if (value === void 0) return "undefined";
6894
+ return typeof value === "function" ? "unknown" : { one: [value] };
6770
6895
  }
6771
6896
  /** This function tells if a value has the form of a signature: a record with `params` as a list, and `to`. */
6772
6897
  function isSignature(value) {
@@ -6786,14 +6911,14 @@ function listText(names) {
6786
6911
  return names.length < 2 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
6787
6912
  }
6788
6913
  /**
6789
- * This function tells if a signature takes a count of arguments. A parameter of the type `unknown` at the end is
6790
- * optional, because a missing argument is `undefined`.
6914
+ * This function tells if a signature takes a count of arguments. A parameter at the end is optional when its type
6915
+ * holds `undefined`, because a missing argument is `undefined`. `unknown` and the base type `undefined` hold it.
6791
6916
  *
6792
6917
  * @returns `undefined` when the number is correct, or else the text of the numbers that the signature takes.
6793
6918
  */
6794
6919
  function arityOf(sig, count, domains) {
6795
6920
  let least = sig.params.length;
6796
- while (least > 0 && isUnknown(sig.params[least - 1], domains)) least -= 1;
6921
+ while (least > 0 && member(void 0, sig.params[least - 1], domains)) least -= 1;
6797
6922
  const most = sig.rest === void 0 ? sig.params.length : Number.POSITIVE_INFINITY;
6798
6923
  if (count >= least && count <= most) return void 0;
6799
6924
  if (most === Number.POSITIVE_INFINITY) return `at least ${argumentsText(least)}`;
@@ -7051,6 +7176,7 @@ function formulaTypes(profile) {
7051
7176
  "formula.ext": { synth: (input) => {
7052
7177
  const name = nameOf(input);
7053
7178
  if (profile.ext !== void 0 && Object.hasOwn(profile.ext, name)) return "unknown";
7179
+ if (name === "undefined") return "undefined";
7054
7180
  if (name === "number") return "number";
7055
7181
  return name === "domain" ? domainTypeOf(input.doc.value, input.machine.domains) : "unknown";
7056
7182
  } }
@@ -7060,22 +7186,111 @@ function formulaTypes(profile) {
7060
7186
  * This function makes the evaluators of the formula kit for one language. Each evaluator compiles its document
7061
7187
  * one time into code. Each evaluator also holds its type rule, for a check of types (TYPE.SYNTH).
7062
7188
  *
7189
+ * Equal profiles give the same evaluator objects (COMPOSE.UNIFY, D46). Thus two machines of one profile merge without
7190
+ * a conflict. Each call gives a new record. Each evaluator object is frozen, because many machines can share it.
7191
+ *
7063
7192
  * @param profile - The operations and the rules of the language.
7064
7193
  * @returns The evaluators, by their name.
7065
7194
  */
7066
7195
  function formulaEvaluators(profile = {}) {
7196
+ return { ...kitOf(profile).evaluators };
7197
+ }
7198
+ /** This function makes new evaluators of the formula kit for the fields of one profile. */
7199
+ function makeEvaluators(profile) {
7067
7200
  const evaluators = formulaCode(profile);
7068
7201
  const types = formulaTypes(profile);
7069
7202
  const out = {};
7070
7203
  for (const name of Object.keys(evaluators)) {
7071
7204
  const evaluator = evaluators[name];
7072
7205
  const type = Object.hasOwn(types, name) ? types[name] : void 0;
7073
- out[name] = type === void 0 ? evaluator : {
7206
+ out[name] = Object.freeze(type === void 0 ? evaluator : {
7074
7207
  compile: evaluator.compile,
7075
7208
  type
7209
+ });
7210
+ }
7211
+ return Object.freeze(out);
7212
+ }
7213
+ /** The keys of a profile that the kit reads. The kit ignores each other key. */
7214
+ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
7215
+ "ops",
7216
+ "lazy",
7217
+ "ext",
7218
+ "truthy",
7219
+ "otherwise",
7220
+ "logic",
7221
+ "omit",
7222
+ "missing",
7223
+ "check",
7224
+ "combine",
7225
+ "pure",
7226
+ "pureExt",
7227
+ "volatile",
7228
+ "sigs"
7229
+ ]);
7230
+ /** The number of recent kits that a profile in a new object can find by its fields. */
7231
+ const RECENT_KITS = 16;
7232
+ /** The kit of each profile object. A kit goes when its profile object goes. */
7233
+ const kitsByProfile = /* @__PURE__ */ new WeakMap();
7234
+ /** The recent kits, the most recent first. The list holds `RECENT_KITS` kits or fewer. */
7235
+ const recentKits = [];
7236
+ /**
7237
+ * This function gives the canonical form of a profile: a frozen record of the fields that the kit reads. A field
7238
+ * with the value `undefined` is absent, but `otherwise` is present when the profile has it as an own key. A list
7239
+ * is a frozen copy. Each other value stays the same object.
7240
+ */
7241
+ function fieldsOf(profile) {
7242
+ const out = {};
7243
+ for (const key of PROFILE_KEYS) {
7244
+ if (key === "otherwise" ? !Object.hasOwn(profile, key) : profile[key] === void 0) continue;
7245
+ const value = profile[key];
7246
+ out[key] = Array.isArray(value) ? Object.freeze([...value]) : value;
7247
+ }
7248
+ return Object.freeze(out);
7249
+ }
7250
+ /**
7251
+ * This function tells if two canonical forms are equal. They must have the same keys. A function, a record and a host
7252
+ * value are equal only to themselves. Two lists are equal when their items are the same, in order.
7253
+ */
7254
+ function sameFields(a, b) {
7255
+ const left = a;
7256
+ const right = b;
7257
+ const keys = Object.keys(left);
7258
+ if (keys.length !== Object.keys(right).length) return false;
7259
+ return keys.every((key) => {
7260
+ if (!Object.hasOwn(right, key)) return false;
7261
+ const x = left[key];
7262
+ const y = right[key];
7263
+ if (Object.is(x, y)) return true;
7264
+ return Array.isArray(x) && Array.isArray(y) && x.length === y.length && x.every((item, index) => Object.is(item, y[index]));
7265
+ });
7266
+ }
7267
+ /**
7268
+ * This function gives the kit of a profile (D46). A profile object that the kit knows, with the same fields, gives its
7269
+ * kit. Then a recent kit with equal fields serves a new profile object. Else the function makes a new kit from the
7270
+ * canonical form. Thus a later change of a field of the profile object gives a new kit, and an old kit does not change.
7271
+ */
7272
+ function kitOf(profile) {
7273
+ const fields = fieldsOf(profile);
7274
+ const known = kitsByProfile.get(profile);
7275
+ if (known !== void 0 && sameFields(known.fields, fields)) return known;
7276
+ const index = recentKits.findIndex((kit) => sameFields(kit.fields, fields));
7277
+ let kit;
7278
+ if (index >= 0) {
7279
+ kit = recentKits[index];
7280
+ recentKits.splice(index, 1);
7281
+ } else {
7282
+ const hook = makeKnown(fields);
7283
+ kit = {
7284
+ fields,
7285
+ evaluators: makeEvaluators(fields),
7286
+ known: hook,
7287
+ facts: makeFacts(fields, hook)
7076
7288
  };
7077
7289
  }
7078
- return out;
7290
+ recentKits.unshift(kit);
7291
+ if (recentKits.length > RECENT_KITS) recentKits.length = RECENT_KITS;
7292
+ kitsByProfile.set(profile, kit);
7293
+ return kit;
7079
7294
  }
7080
7295
  /** This function makes the code of the evaluators of the formula kit, without their type rules. */
7081
7296
  function formulaCode(profile) {
@@ -7394,10 +7609,16 @@ function formulaCode(profile) {
7394
7609
  * `formula.ext` knows an extension of `ext` and each standard extension. The hook knows each name of the other
7395
7610
  * evaluators.
7396
7611
  *
7612
+ * Equal profiles give the same hook (D46).
7613
+ *
7397
7614
  * @param profile - The operations and the extensions of the language.
7398
7615
  * @returns The hook: `true`, or the reason of the `#NAME?` that the evaluator gives in a run.
7399
7616
  */
7400
7617
  function formulaKnown(profile = {}) {
7618
+ return kitOf(profile).known;
7619
+ }
7620
+ /** This function makes a new hook `known` for the fields of one profile. */
7621
+ function makeKnown(profile) {
7401
7622
  const hasOp = (name) => {
7402
7623
  if (profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name)) return true;
7403
7624
  const ops = profile.ops;
@@ -7417,9 +7638,12 @@ function formulaKnown(profile = {}) {
7417
7638
  return hasExt(name) ? true : `The language has no extension ${name}.`;
7418
7639
  };
7419
7640
  }
7420
- /** The facts of an extension of the profile. It gets the context, thus it can read outside `resolve`. */
7641
+ /**
7642
+ * The facts of an extension of the profile that `pureExt` does not name. It gets the context, thus it can read outside
7643
+ * `resolve`.
7644
+ */
7421
7645
  const NOT_PURE = /* @__PURE__ */ Object.freeze({ pure: false });
7422
- /** The facts of a standard extension. */
7646
+ /** The facts of a standard extension, and of an extension that `pureExt` names. */
7423
7647
  const PURE = /* @__PURE__ */ Object.freeze({ pure: true });
7424
7648
  /** The lazy rule of a lazy operation: it covers each argument. */
7425
7649
  const EACH_PART = /* @__PURE__ */ Object.freeze([/* @__PURE__ */ Object.freeze({})]);
@@ -7429,19 +7653,29 @@ const EACH_PART = /* @__PURE__ */ Object.freeze([/* @__PURE__ */ Object.freeze({
7429
7653
  *
7430
7654
  * - An operation of `lazy` has a lazy rule for each argument.
7431
7655
  * - An operation is pure when the profile names it in `pure`, or when `pure` is `true`. A volatile operation is not pure.
7432
- * - A standard extension is pure. An extension of the profile gets the context, thus it is not pure.
7656
+ * - A standard extension is pure. An extension of the profile is pure only when the profile names it in `pureExt` (D47).
7657
+ * Such an extension keeps C4: it reads the context only through `resolve`.
7658
+ *
7659
+ * Equal profiles give the same hook (D46).
7433
7660
  *
7434
7661
  * @param profile - The operations, the extensions and their facts.
7435
7662
  * @returns The hook: the facts of a name, or `undefined` for the facts of the contract.
7436
7663
  */
7437
7664
  function formulaFacts(profile = {}) {
7665
+ return kitOf(profile).facts;
7666
+ }
7667
+ /** This function makes a new hook `facts` for the fields of one profile, with the hook `known` of the same fields. */
7668
+ function makeFacts(profile, known) {
7438
7669
  const volatile = new Set(profile.volatile ?? []);
7439
7670
  const pure = profile.pure === true ? void 0 : new Set(profile.pure ?? []);
7440
- const known = formulaKnown(profile);
7671
+ const pureExt = new Set(profile.pureExt ?? []);
7441
7672
  return (path, code) => {
7442
7673
  if (code !== "formula.op" && code !== "formula.ext") return void 0;
7443
7674
  const name = path.slice(1).join(".");
7444
- if (code === "formula.ext") return profile.ext !== void 0 && Object.hasOwn(profile.ext, name) ? NOT_PURE : Object.hasOwn(STD_EXT, name) ? PURE : void 0;
7675
+ if (code === "formula.ext") {
7676
+ if (profile.ext !== void 0 && Object.hasOwn(profile.ext, name)) return pureExt.has(name) ? PURE : NOT_PURE;
7677
+ return Object.hasOwn(STD_EXT, name) ? PURE : void 0;
7678
+ }
7445
7679
  if (known(path, code) !== true) return void 0;
7446
7680
  const isVolatile = volatile.has(name);
7447
7681
  const lazy = profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name);
@@ -7465,18 +7699,23 @@ function formulaFacts(profile = {}) {
7465
7699
  * The kit gives the domains of the kernel (`STD_DOMAINS`). Thus a domain of the language with the name `number` is a
7466
7700
  * conflict. The language names `["domains", "number"]` in `replaces` to replace it.
7467
7701
  *
7702
+ * Equal profiles give the same evaluators and the same hooks (COMPOSE.UNIFY, D46). Thus two machines of one profile
7703
+ * merge, also when each adds its own language. Two profiles with different fields give different evaluators, and
7704
+ * their machines conflict at each evaluator name of the kit.
7705
+ *
7468
7706
  * @param profile - The operations and the rules of the language.
7469
7707
  * @param more - The parts of a machine that the language adds, with the paths of the entries that it replaces.
7470
7708
  * @returns The machine.
7471
7709
  * @throws {MergeError} The language and the kit have a conflict.
7472
7710
  */
7473
7711
  function formulaMachine(profile = {}, more = {}) {
7712
+ const kit = kitOf(profile);
7474
7713
  return mergeMachines({
7475
7714
  world: { kinds: formulaKinds() },
7476
- evaluators: formulaEvaluators(profile),
7715
+ evaluators: { ...kit.evaluators },
7477
7716
  domains: STD_DOMAINS,
7478
- known: formulaKnown(profile),
7479
- facts: formulaFacts(profile)
7717
+ known: kit.known,
7718
+ facts: kit.facts
7480
7719
  }, more);
7481
7720
  }
7482
7721
  //#endregion
@@ -8822,10 +9061,13 @@ function plainValue(value, open) {
8822
9061
  }
8823
9062
  /**
8824
9063
  * TREE.LAZY, TREE.PARENT, TREE.SIBLING and TREE.ERROR: a node of an open tree gives its thing, and the root of a new
8825
- * tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
9064
+ * tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error. The node
9065
+ * reads `self` only through `resolve` (C4, TREE.SELF).
8826
9066
  */
8827
- const treeDefinition = ({ parts, context, kind, place, machine, typePath }) => {
8828
- const outer = refNodeOf(context.self);
9067
+ const treeDefinition = ({ parts, context, kind, place, machine, typePath, resolve: walk }) => {
9068
+ const reached = walk(["self"]);
9069
+ const parent = reached.ok ? reached.thing : void 0;
9070
+ const outer = refNodeOf(parent);
8829
9071
  const nested = outer !== void 0 && outer.tree.open;
8830
9072
  const found = machine === void 0 || typePath.length === 0 ? void 0 : lookupKind(machine.world, typePath);
8831
9073
  const contract = found?.ok === true ? found.kind.tree : void 0;
@@ -8849,7 +9091,7 @@ const treeDefinition = ({ parts, context, kind, place, machine, typePath }) => {
8849
9091
  type: kind,
8850
9092
  label: `node ${kind}`,
8851
9093
  deref: (key) => {
8852
- if (key === "parent") return context.self;
9094
+ if (key === "parent") return parent;
8853
9095
  if (key === "type") return kind;
8854
9096
  const result = attributeOf(node, String(key));
8855
9097
  if (result === void 0 || result.ok) return result?.ok === true ? result.value : void 0;
@@ -10056,13 +10298,15 @@ function typesNaming(world, name, verb) {
10056
10298
  /** This function gives the facts of the kind of a type. The hook `facts` of the machine wins over the contract. */
10057
10299
  function contractOf(env, kind, typePath, caps) {
10058
10300
  const facts = env.machine.facts?.(typePath, env.name);
10059
- const effects = kind.effects;
10301
+ const own = effectsOfVerb(kind, env.verb);
10302
+ const effects = own ?? kind.effects;
10060
10303
  const declared = effects === void 0 ? [] : effects === "type" ? [typePath] : effects;
10061
10304
  return {
10062
10305
  declared,
10063
10306
  pure: (facts?.pure ?? kind.pure) === true,
10064
10307
  lazy: facts?.lazy ?? kind.lazy,
10065
10308
  describes: env.verb === "eval",
10309
+ declares: own !== void 0,
10066
10310
  caps: caps ?? [...declared, "probe"]
10067
10311
  };
10068
10312
  }
@@ -10458,8 +10702,10 @@ function checkShot(env, sample, contract, scenario, shot) {
10458
10702
  const keyOf = (part) => shot.keys[part] ?? String(part);
10459
10703
  for (const call of shot.calls) if (!call.context.caps.every((cap) => permits(docCaps, cap))) report(env, "C1", `The code gives the part ${keyOf(call.part)} ${capsText(call.context.caps)}, and its document has ${capsText(docCaps)}.`, probe);
10460
10704
  for (const effect of shot.caused) if (!effect.context.caps.every((cap) => permits(docCaps, cap))) report(env, "C1", `The code gives its effect ${effect.kind} ${capsText(effect.context.caps)}, and its document has ${capsText(docCaps)}.`, probe);
10705
+ if (contract.declares) {
10706
+ for (const effect of shot.caused) if (!permits(contract.declared, effect.kind)) report(env, "C2", `The code causes the effect ${effect.kind}, and the contract of ${sample.type} declares ${contract.declared.length === 0 ? "no effects" : capsText(contract.declared)}${env.verb === "eval" ? "" : ` for the verb ${env.verb}`}.`, probe);
10707
+ }
10461
10708
  if (contract.describes) {
10462
- for (const effect of shot.caused) if (!permits(contract.declared, effect.kind)) report(env, "C2", `The code causes the effect ${effect.kind}, and the contract of ${sample.type} declares ${contract.declared.length === 0 ? "no effects" : capsText(contract.declared)}.`, probe);
10463
10709
  const first = shot.caused[0];
10464
10710
  if (contract.pure && first !== void 0) report(env, "C4", `The kind ${sample.type} is pure, and the code causes the effect ${first.kind}.`, probe);
10465
10711
  if (shot.result.ok) sample.split.parts.forEach((_slot, index) => {
@@ -11836,6 +12082,7 @@ function samplesAt(values, closed, depth) {
11836
12082
  const records = [];
11837
12083
  let lists = false;
11838
12084
  for (const value of values) if (value === null) bases.add("null");
12085
+ else if (value === void 0) bases.add("undefined");
11839
12086
  else if (typeof value === "boolean") bases.add("bool");
11840
12087
  else if (typeof value === "number") bases.add("number");
11841
12088
  else if (typeof value === "string") bases.add("text");
@@ -11846,6 +12093,7 @@ function samplesAt(values, closed, depth) {
11846
12093
  else return "unknown";
11847
12094
  for (const base of [
11848
12095
  "null",
12096
+ "undefined",
11849
12097
  "bool",
11850
12098
  "number",
11851
12099
  "text"
@@ -11873,7 +12121,7 @@ function samplesAt(values, closed, depth) {
11873
12121
  * This function infers a value type from sample values (TYPE.SAMPLES), as a type provider of F# does. A host uses it
11874
12122
  * for the type of a root, for example from some events.
11875
12123
  *
11876
- * - `null`, a truth value, a number and a text give their base types, not their values.
12124
+ * - `null`, `undefined`, a truth value, a number and a text give their base types, not their values.
11877
12125
  * - The items of each list join into one type of items.
11878
12126
  * - All records join into one record with each key of the samples. A key that a record does not have is optional.
11879
12127
  * - Values of different forms give a union, as `join` gives. No samples give `never`.
@@ -11890,6 +12138,6 @@ function typeFromSamples(values, options = {}) {
11890
12138
  return samplesAt(values, options.closed === true, 0);
11891
12139
  }
11892
12140
  //#endregion
11893
- export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, 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, agree, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, print, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
12141
+ export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, 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, agree, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, print, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
11894
12142
 
11895
12143
  //# sourceMappingURL=index.js.map