@mark1russell7/coeval 0.19.0 → 0.20.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
@@ -354,6 +354,54 @@ function describe(value) {
354
354
  }
355
355
  }
356
356
  // src/world.ts
357
+ /** The most rounds of a circular attribute without its own `limit` (TREE.CIRCULAR-LIMIT, D113). */
358
+ const CIRCULAR_LIMIT = 100;
359
+ /** This function tells if a value is JSON data: `null`, a truth value, a finite number, a text, or a container of them. */
360
+ function isJsonData(value) {
361
+ const stack = [value];
362
+ const seen = /* @__PURE__ */ new Set();
363
+ while (stack.length > 0) {
364
+ const item = stack.pop();
365
+ if (item === null || typeof item === "string" || typeof item === "boolean") continue;
366
+ if (typeof item === "number") {
367
+ if (!Number.isFinite(item)) return false;
368
+ continue;
369
+ }
370
+ if (!Array.isArray(item) && !isRecord(item)) return false;
371
+ if (seen.has(item)) continue;
372
+ seen.add(item);
373
+ for (const inner of Object.values(item)) stack.push(inner);
374
+ }
375
+ return true;
376
+ }
377
+ /**
378
+ * This function reads the declaration `circular` of an attribute (D113). The tree kit, the reference, `check` and the
379
+ * graph of attribute instances read it with this one function.
380
+ *
381
+ * @param declared - The value of the field `circular`.
382
+ * @returns The start value and the limit, or the reason when the declaration has no correct form.
383
+ */
384
+ function circularOf(declared) {
385
+ if (!isRecord(declared) || !Object.hasOwn(declared, "start")) return {
386
+ ok: false,
387
+ reason: "The declaration circular needs a record with the field start."
388
+ };
389
+ const start = field(declared, "start");
390
+ if (!isJsonData(start)) return {
391
+ ok: false,
392
+ reason: "The start value of the declaration circular must be JSON data."
393
+ };
394
+ const limit = Object.hasOwn(declared, "limit") ? field(declared, "limit") : 100;
395
+ if (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1) return {
396
+ ok: false,
397
+ reason: "The limit of the declaration circular must be a whole number of 1 or more."
398
+ };
399
+ return {
400
+ ok: true,
401
+ start,
402
+ limit
403
+ };
404
+ }
357
405
  /**
358
406
  * This function gives the type of a bare equation of a kind (D42). It is the type `of` of the shape of its parts, or
359
407
  * else the type `of` of the shape `rest` of a record. The plan, the reference and `check` read it with this one function.
@@ -2388,6 +2436,143 @@ function ask(world, doc, verb, options = {}) {
2388
2436
  steps
2389
2437
  };
2390
2438
  }
2439
+ // src/steps.ts
2440
+ /**
2441
+ * This function makes a ladder of steps, and it checks the declaration (STEPS.LADDER). The list of steps must have one
2442
+ * step or more, with no name two times. The step of each construct must be a step of the list.
2443
+ *
2444
+ * @param steps - The names of the steps, from the lowest to the highest.
2445
+ * @param constructs - The step that each construct needs.
2446
+ * @returns The ladder, frozen.
2447
+ * @throws TypeError for a declaration that is not valid, because it is a defect of the language.
2448
+ */
2449
+ function stepLadder(steps, constructs) {
2450
+ if (steps.length === 0) throw new TypeError("a ladder of steps needs one step or more");
2451
+ const seen = /* @__PURE__ */ new Set();
2452
+ for (const step of steps) {
2453
+ if (typeof step !== "string") throw new TypeError(`the step ${String(step)} is not a name`);
2454
+ if (seen.has(step)) throw new TypeError(`the step "${step}" is in the ladder two times`);
2455
+ seen.add(step);
2456
+ }
2457
+ for (const [construct, step] of Object.entries(constructs)) if (!seen.has(step)) throw new TypeError(`the construct "${construct}" needs the step "${String(step)}", which is not a step of the ladder`);
2458
+ return Object.freeze({
2459
+ steps: Object.freeze([...steps]),
2460
+ constructs: Object.freeze({ ...constructs })
2461
+ });
2462
+ }
2463
+ /**
2464
+ * This function tells if a value is a step of a ladder. A host uses it to check its option for the highest step.
2465
+ *
2466
+ * @param ladder - The ladder.
2467
+ * @param value - The value.
2468
+ * @returns `true` when the value is the name of a step of the ladder.
2469
+ */
2470
+ function isStep(ladder, value) {
2471
+ return typeof value === "string" && ladder.steps.includes(value);
2472
+ }
2473
+ /**
2474
+ * This function gives the position of a step in its ladder. The lowest step has the position 0.
2475
+ *
2476
+ * @param ladder - The ladder.
2477
+ * @param step - The step.
2478
+ * @returns The position.
2479
+ * @throws TypeError for a name that is not a step of the ladder.
2480
+ */
2481
+ function stepRank(ladder, step) {
2482
+ const rank = ladder.steps.indexOf(step);
2483
+ if (rank < 0) throw new TypeError(`"${String(step)}" is not a step of the ladder. The steps are ${ladder.steps.join(", ")}`);
2484
+ return rank;
2485
+ }
2486
+ function pathOf(visit) {
2487
+ const path = [];
2488
+ for (let v = visit; v?.key !== void 0; v = v.parent) path.push(v.key);
2489
+ return path.reverse();
2490
+ }
2491
+ /**
2492
+ * This function gives the lowest step that a document needs, with the reason (STEPS.LOWEST, STEPS.REASON). The step is
2493
+ * the highest step of the constructs of its nodes. The reason is the first node in document order with that step. The
2494
+ * walk uses its own stack, thus a deep document does not overflow (STEPS.LINEAR). It visits a shared node one time,
2495
+ * and it stops at the first node with the highest step of the ladder.
2496
+ *
2497
+ * @param ladder - The ladder of the language.
2498
+ * @param walk - The construct and the child nodes of a node.
2499
+ * @param root - The root node of the document.
2500
+ * @returns The step, and the reason when the step is not the lowest step.
2501
+ */
2502
+ function stepOf(ladder, walk, root) {
2503
+ const top = ladder.steps.length - 1;
2504
+ let best = 0;
2505
+ let reason;
2506
+ const seen = /* @__PURE__ */ new Set();
2507
+ const todo = [{
2508
+ node: root,
2509
+ key: void 0,
2510
+ parent: void 0
2511
+ }];
2512
+ for (let visit = todo.pop(); visit !== void 0; visit = todo.pop()) {
2513
+ const { node } = visit;
2514
+ if (typeof node === "object" && node !== null) {
2515
+ if (seen.has(node)) continue;
2516
+ seen.add(node);
2517
+ }
2518
+ const construct = walk.construct(node);
2519
+ const step = construct === void 0 || !Object.hasOwn(ladder.constructs, construct) ? void 0 : ladder.constructs[construct];
2520
+ const rank = step === void 0 ? 0 : ladder.steps.indexOf(step);
2521
+ if (rank > best) {
2522
+ best = rank;
2523
+ reason = {
2524
+ construct,
2525
+ step,
2526
+ path: pathOf(visit),
2527
+ node
2528
+ };
2529
+ if (best === top) break;
2530
+ }
2531
+ const kids = [...walk.children(node)];
2532
+ for (let i = kids.length - 1; i >= 0; i--) {
2533
+ const [key, child] = kids[i];
2534
+ todo.push({
2535
+ node: child,
2536
+ key,
2537
+ parent: visit
2538
+ });
2539
+ }
2540
+ }
2541
+ const step = ladder.steps[best];
2542
+ return reason === void 0 ? { step } : {
2543
+ step,
2544
+ reason
2545
+ };
2546
+ }
2547
+ /**
2548
+ * This function compares the step of a document with the highest step that the host accepts (STEPS.MAX). The limit
2549
+ * changes only which documents the host accepts. The language evaluates an accepted document as before, thus a limit
2550
+ * does not change a value.
2551
+ *
2552
+ * @param ladder - The ladder of the language.
2553
+ * @param need - The step of the document, from `stepOf`.
2554
+ * @param maxStep - The highest step that the host accepts.
2555
+ * @returns `undefined` when the host accepts the document, or the refusal that names the needed step.
2556
+ * @throws TypeError when `maxStep` or the step of `need` is not a step of the ladder.
2557
+ */
2558
+ function checkStep(ladder, need, maxStep) {
2559
+ const limit = stepRank(ladder, maxStep);
2560
+ if (stepRank(ladder, need.step) <= limit) return void 0;
2561
+ const head = `the document needs the step ${need.step}, and the host accepts the steps up to ${maxStep}`;
2562
+ const reason = need.reason;
2563
+ if (reason === void 0) return {
2564
+ needed: need.step,
2565
+ maxStep,
2566
+ message: head
2567
+ };
2568
+ const where = reason.path.length === 0 ? "at the root" : `at the path ${JSON.stringify(reason.path)}`;
2569
+ return {
2570
+ needed: need.step,
2571
+ maxStep,
2572
+ reason,
2573
+ message: `${head}. The construct "${reason.construct}" ${where} needs the step ${reason.step}`
2574
+ };
2575
+ }
2391
2576
  // src/deref.ts
2392
2577
  /**
2393
2578
  * The brand of a host thing: an object of the host that answers the steps of a path itself. The brand is a
@@ -5622,12 +5807,55 @@ const NO_ANSWER = /* @__PURE__ */ Object.freeze({
5622
5807
  by: void 0
5623
5808
  });
5624
5809
  /**
5625
- * The marks of the attributes in evaluation, for each place of a plan, with the cells of a host (TREE.CELL). Nodes of
5626
- * two evaluations share their place and their kept cells, thus they share their marks.
5810
+ * The marks of the attributes in evaluation, for each key of a node, with the cells of a host (TREE.CELL). Nodes of
5811
+ * two evaluations share their key and their kept cells, thus they share their marks. Nodes of two documents have
5812
+ * different keys, thus a nested evaluation gets its own marks (TREE.CELL-NESTED, D115).
5627
5813
  */
5628
5814
  const marksByPlace = /* @__PURE__ */ new WeakMap();
5629
- /** The trees of the cells of a host, for each place of a root in a plan. Nodes of two evaluations stay in one tree. */
5815
+ /** The trees of the cells of a host, for each key of a root. Nodes of two evaluations stay in one tree. */
5630
5816
  const buildsByPlace = /* @__PURE__ */ new WeakMap();
5817
+ /** The keys of the nodes with the cells of a host, for each scope and each place of a plan (D115). */
5818
+ const keysByScope = /* @__PURE__ */ new WeakMap();
5819
+ /** The scopes of the roots that a demand made, for each marks of a node and each name of an attribute (D115). */
5820
+ const demandScopes = /* @__PURE__ */ new WeakMap();
5821
+ /**
5822
+ * This function gives the key of a node with the cells of a host (TREE.CELL-DOCUMENT, D115). The key of the node
5823
+ * around the node is its scope. A root without such a node has the scope of the open demand, or no scope.
5824
+ * Without a scope, the key is the place of the plan. Thus a cached plan gives a new key for the nodes of each
5825
+ * document, and each evaluation of a document gives the same keys.
5826
+ *
5827
+ * @param place - The place of the document of the node in its plan.
5828
+ * @param above - The state of the node around the node, or `undefined`.
5829
+ * @returns The key: an array with the keys of `place`.
5830
+ */
5831
+ function cellPlace(place, above) {
5832
+ let scope = above?.place;
5833
+ if (scope === void 0) {
5834
+ const demand = demands[demands.length - 1];
5835
+ if (demand === void 0) return place;
5836
+ let byName = demandScopes.get(demand.cells);
5837
+ if (byName === void 0) {
5838
+ byName = /* @__PURE__ */ new Map();
5839
+ demandScopes.set(demand.cells, byName);
5840
+ }
5841
+ scope = byName.get(demand.key);
5842
+ if (scope === void 0) {
5843
+ scope = {};
5844
+ byName.set(demand.key, scope);
5845
+ }
5846
+ }
5847
+ let keys = keysByScope.get(scope);
5848
+ if (keys === void 0) {
5849
+ keys = /* @__PURE__ */ new WeakMap();
5850
+ keysByScope.set(scope, keys);
5851
+ }
5852
+ let key = keys.get(place);
5853
+ if (key === void 0) {
5854
+ key = Object.freeze([...place]);
5855
+ keys.set(place, key);
5856
+ }
5857
+ return key;
5858
+ }
5631
5859
  /** This function gives the value of a weak map, or puts a new value in it. */
5632
5860
  function keptAt(map, place, make) {
5633
5861
  let value = map.get(place);
@@ -5641,6 +5869,277 @@ function keptAt(map, place, make) {
5641
5869
  const NOT_THROWN = /* @__PURE__ */ Symbol("coeval.tree.not-thrown");
5642
5870
  /** The attributes in evaluation, in the order of their demands. An evaluation does not wait, thus one list serves. */
5643
5871
  const demands = [];
5872
+ /** The open fixed point, or `undefined`. */
5873
+ let openPoint;
5874
+ /** For each table `tree.attributes`, `true` when an attribute of it declares `circular`. */
5875
+ const circularTables = /* @__PURE__ */ new WeakMap();
5876
+ /** For each declaration `circular` that is an object, its rule. */
5877
+ const circularRules = /* @__PURE__ */ new WeakMap();
5878
+ /** This function tells if a tree contract declares a circular attribute. The answer for a table is computed one time. */
5879
+ function hasCircular(tree) {
5880
+ const table = tree?.attributes;
5881
+ if (table === void 0) return false;
5882
+ let known = circularTables.get(table);
5883
+ if (known === void 0) {
5884
+ known = Object.keys(table).some((key) => {
5885
+ const declared = entryOf(table, key);
5886
+ return typeof declared === "object" && declared !== null && declared.circular !== void 0;
5887
+ });
5888
+ circularTables.set(table, known);
5889
+ }
5890
+ return known;
5891
+ }
5892
+ /** This function gives the declaration `circular` of an attribute of a tree contract (D113), or `undefined`. */
5893
+ function circularRule(tree, key) {
5894
+ const declared = entryOf(tree?.attributes, key)?.circular;
5895
+ if (declared === void 0) return void 0;
5896
+ if (typeof declared !== "object" || declared === null) return circularOf(declared);
5897
+ let rule = circularRules.get(declared);
5898
+ if (rule === void 0) {
5899
+ rule = circularOf(declared);
5900
+ circularRules.set(declared, rule);
5901
+ }
5902
+ return rule;
5903
+ }
5904
+ /** This function gives the values of a fixed point for the marks of a node. */
5905
+ function loopValues(loop, marks) {
5906
+ let values = loop.values.get(marks);
5907
+ if (values === void 0) {
5908
+ values = /* @__PURE__ */ new Map();
5909
+ loop.values.set(marks, values);
5910
+ }
5911
+ return values;
5912
+ }
5913
+ /** This function marks each demand above the leader: each one read an approximation, thus its result is not final. */
5914
+ function markTentative(loop) {
5915
+ for (let index = loop.depth + 1; index < demands.length; index += 1) loop.tentative.add(demands[index]);
5916
+ }
5917
+ /** This function tells if two approximations are the same: two values by DOC.EQUALS, two errors by code and reason. */
5918
+ function sameApproximation$1(a, b) {
5919
+ if (a === void 0 || b === void 0) return a === b;
5920
+ if (a.ok && b.ok) return docEquals(a.value, b.value);
5921
+ if (!a.ok && !b.ok) return a.error.code === b.error.code && a.error.reason === b.error.reason;
5922
+ return false;
5923
+ }
5924
+ /** This function writes the final value of each circular attribute of the last round in the plain memo of its node. */
5925
+ function settle(loop) {
5926
+ for (const values of loop.values.values()) for (const [key, value] of values) if (value.memo !== void 0 && value.round === loop.round) value.memo.set(key, {
5927
+ busy: false,
5928
+ result: value.result,
5929
+ by: value.by
5930
+ });
5931
+ }
5932
+ /** The text of the fixed point of a circular attribute that does not become stable (TREE.CIRCULAR-LIMIT). */
5933
+ function limitReason(text, limit) {
5934
+ return `The cycle of the circular attribute ${text} does not become stable within its limit of ${limit} ${limit === 1 ? "round" : "rounds"}.`;
5935
+ }
5936
+ /**
5937
+ * This function answers a demand that reached an attribute in evaluation in an open fixed point (TREE.CYCLE). A chain
5938
+ * of circular attributes gives the current approximation (TREE.CIRCULAR). Each other chain gives `#CYCLE!`.
5939
+ */
5940
+ function reachedIn(loop, n, key) {
5941
+ const first = demands.findIndex((demand) => demand.cells === n.marks && demand.key === key);
5942
+ let circular = first >= loop.depth;
5943
+ for (let index = first; circular && index < demands.length; index += 1) circular = loop.circular.has(demands[index]);
5944
+ if (!circular) return cycleAt(n.marks, key, attributeText(key, n.type, n.place));
5945
+ const rule = circularRule(n.tree, key);
5946
+ const kept = loop.values.get(n.marks)?.get(key);
5947
+ markTentative(loop);
5948
+ return kept !== void 0 ? kept.result : ok(rule?.ok === true ? rule.start : null);
5949
+ }
5950
+ /**
5951
+ * This function evaluates the leader of a fixed point (TREE.CIRCULAR, D113). Its demand is the last one. Each round
5952
+ * visits the ladder again, and the rounds end when no circular attribute gets a new value. An error ends the rounds
5953
+ * at once. After the last round of the limit, the leader gives `#CYCLE!` at the place of its node.
5954
+ */
5955
+ function roundsOf(n, key, rule) {
5956
+ const leader = demands[demands.length - 1];
5957
+ const loop = {
5958
+ depth: demands.length - 1,
5959
+ round: 0,
5960
+ changed: false,
5961
+ values: /* @__PURE__ */ new Map(),
5962
+ circular: /* @__PURE__ */ new Set([leader]),
5963
+ tentative: /* @__PURE__ */ new Set()
5964
+ };
5965
+ openPoint = loop;
5966
+ try {
5967
+ let before = ok(rule.start);
5968
+ for (;;) {
5969
+ loop.round += 1;
5970
+ loop.changed = false;
5971
+ const answer = n.climb(key) ?? NO_ANSWER;
5972
+ if (answer.result !== void 0 && !answer.result.ok) return answer;
5973
+ if (!sameApproximation$1(before, answer.result)) loop.changed = true;
5974
+ before = answer.result;
5975
+ loopValues(loop, n.marks).set(key, {
5976
+ result: answer.result,
5977
+ by: answer.by,
5978
+ round: loop.round,
5979
+ memo: void 0
5980
+ });
5981
+ if (!loop.changed) {
5982
+ settle(loop);
5983
+ return answer;
5984
+ }
5985
+ if (loop.round >= rule.limit) return {
5986
+ result: fail("#CYCLE!", limitReason(attributeText(key, n.type, n.place), rule.limit), { place: n.place }),
5987
+ by: answer.by
5988
+ };
5989
+ }
5990
+ } finally {
5991
+ openPoint = void 0;
5992
+ }
5993
+ }
5994
+ /**
5995
+ * This function evaluates a circular attribute in the open fixed point of another leader, one time in each round
5996
+ * (D113). A later demand in the same round gets the value of the round. Each value is an approximation.
5997
+ */
5998
+ function roundIn(loop, n, key, rule, kept) {
5999
+ let value = kept;
6000
+ if (value === void 0 || value.round !== loop.round) {
6001
+ const demand = {
6002
+ cells: n.marks,
6003
+ key,
6004
+ text: attributeText(key, n.type, n.place)
6005
+ };
6006
+ n.marks.set(key, BUSY);
6007
+ demands.push(demand);
6008
+ loop.circular.add(demand);
6009
+ let answer;
6010
+ try {
6011
+ answer = n.climb(key) ?? NO_ANSWER;
6012
+ } finally {
6013
+ demands.pop();
6014
+ n.marks.delete(key);
6015
+ }
6016
+ if (!sameApproximation$1(value === void 0 ? ok(rule.start) : value.result, answer.result)) loop.changed = true;
6017
+ value = {
6018
+ result: answer.result,
6019
+ by: answer.by,
6020
+ round: loop.round,
6021
+ memo: n.host === void 0 ? n.cells : void 0
6022
+ };
6023
+ loopValues(loop, n.marks).set(key, value);
6024
+ }
6025
+ markTentative(loop);
6026
+ return value.result;
6027
+ }
6028
+ /**
6029
+ * This function evaluates an attribute without the cell of the host: with the plain memo, or in an open fixed point
6030
+ * (D113). A result that read an approximation is not final, thus no memo keeps it.
6031
+ */
6032
+ function localIn(n, key, rule) {
6033
+ const demand = {
6034
+ cells: n.marks,
6035
+ key,
6036
+ text: attributeText(key, n.type, n.place)
6037
+ };
6038
+ const loop = openPoint;
6039
+ n.marks.set(key, BUSY);
6040
+ demands.push(demand);
6041
+ let answer;
6042
+ try {
6043
+ answer = rule === void 0 ? n.climb(key) ?? NO_ANSWER : roundsOf(n, key, rule);
6044
+ } catch (error) {
6045
+ n.marks.delete(key);
6046
+ throw error;
6047
+ } finally {
6048
+ demands.pop();
6049
+ }
6050
+ if (loop !== void 0 && loop.tentative.has(demand)) {
6051
+ n.marks.delete(key);
6052
+ return answer.result;
6053
+ }
6054
+ if (n.host !== void 0) {
6055
+ n.marks.delete(key);
6056
+ if (loop !== void 0) loopValues(loop, n.marks).set(key, {
6057
+ result: answer.result,
6058
+ by: answer.by,
6059
+ round: 0,
6060
+ memo: void 0
6061
+ });
6062
+ }
6063
+ n.cells.set(key, {
6064
+ busy: false,
6065
+ result: answer.result,
6066
+ by: answer.by
6067
+ });
6068
+ return answer.result;
6069
+ }
6070
+ /**
6071
+ * This function answers a demand that reached an attribute in evaluation (TREE.CYCLE). Outside a fixed point, it gives
6072
+ * `#CYCLE!` with the chain. In an open fixed point, `reachedIn` answers (TREE.CIRCULAR).
6073
+ */
6074
+ function busyAt(n, key) {
6075
+ const loop = openPoint;
6076
+ return loop === void 0 ? cycleAt(n.marks, key, attributeText(key, n.type, n.place)) : reachedIn(loop, n, key);
6077
+ }
6078
+ /**
6079
+ * This function evaluates the leader of a fixed point through the cell of the host (TREE.CELL, D113). The cell of the
6080
+ * leader gets the final value of the fixed point, from a computation that contains each round.
6081
+ */
6082
+ function leaderOnHost(n, key, rule, host) {
6083
+ const text = attributeText(key, n.type, n.place);
6084
+ n.marks.set(key, BUSY);
6085
+ demands.push({
6086
+ cells: n.marks,
6087
+ key,
6088
+ text
6089
+ });
6090
+ let thrown = NOT_THROWN;
6091
+ let answer;
6092
+ try {
6093
+ answer = host(n.place, key, () => {
6094
+ try {
6095
+ return roundsOf(n, key, rule);
6096
+ } catch (error) {
6097
+ thrown = error;
6098
+ throw error;
6099
+ }
6100
+ });
6101
+ } catch (error) {
6102
+ if (error === thrown) throw error;
6103
+ answer = {
6104
+ result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${demands.map((demand) => demand.text).join(" -> ")}.`, { cause: error }),
6105
+ by: void 0
6106
+ };
6107
+ } finally {
6108
+ demands.pop();
6109
+ n.marks.delete(key);
6110
+ }
6111
+ n.cells.set(key, {
6112
+ busy: false,
6113
+ result: answer.result,
6114
+ by: answer.by
6115
+ });
6116
+ return answer.result;
6117
+ }
6118
+ /**
6119
+ * This function gives an attribute of a node with circular attributes, or an attribute in an open fixed point (D113).
6120
+ * The memo and the marks of the node do not hold the attribute. An open fixed point does not use the cells of the host.
6121
+ */
6122
+ function circularRead(n, key) {
6123
+ const rule = circularRule(n.tree, key);
6124
+ if (rule !== void 0 && !rule.ok) {
6125
+ const result = fail("#VALUE!", `The attribute ${attributeText(key, n.type, n.place)} has a bad declaration circular. ${rule.reason}`);
6126
+ n.cells.set(key, {
6127
+ busy: false,
6128
+ result,
6129
+ by: void 0
6130
+ });
6131
+ return result;
6132
+ }
6133
+ const loop = openPoint;
6134
+ if (loop !== void 0) {
6135
+ const kept = loop.values.get(n.marks)?.get(key);
6136
+ if (rule !== void 0) return roundIn(loop, n, key, rule, kept);
6137
+ if (kept !== void 0) return kept.result;
6138
+ return localIn(n, key, void 0);
6139
+ }
6140
+ if (rule === void 0 || n.host === void 0) return localIn(n, key, rule);
6141
+ return leaderOnHost(n, key, rule, n.host);
6142
+ }
5644
6143
  function attributeText(key, type, place) {
5645
6144
  return `${key} of ${type} at [${place.join(", ")}]`;
5646
6145
  }
@@ -5710,16 +6209,18 @@ function plainOf(value, path) {
5710
6209
  * fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
5711
6210
  * The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
5712
6211
  *
5713
- * The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
6212
+ * The argument `place` is the key of the node. With the cells of a host, `cellPlace` gives it. Else it is the place of
6213
+ * the plan. The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
5714
6214
  */
5715
- function treeThing(input, build, outer, host) {
5716
- const { parts, context, kind: type, place, machine } = input;
6215
+ function treeThing(input, place, build, outer, host) {
6216
+ const { parts, context, kind: type, machine } = input;
5717
6217
  const runtime = input[RUNTIME];
5718
6218
  const { tree, of } = contractOf$1(machine, input.typePath);
5719
6219
  const above = stateOf(outer);
5720
6220
  const up = above !== void 0 && above.build === build ? above : void 0;
5721
6221
  const cells = /* @__PURE__ */ new Map();
5722
6222
  const marks = host === void 0 ? cells : keptAt(marksByPlace, place, () => /* @__PURE__ */ new Map());
6223
+ const circular = hasCircular(tree);
5723
6224
  /** The positions of the items of the list `children`, for the names of the children. */
5724
6225
  let positions;
5725
6226
  const fold = (key, declared) => {
@@ -5789,8 +6290,9 @@ function treeThing(input, build, outer, host) {
5789
6290
  */
5790
6291
  const through = host === void 0 ? void 0 : (key) => {
5791
6292
  const text = attributeText(key, type, place);
5792
- if (marks.has(key)) return cycleAt(marks, key, text);
6293
+ if (marks.has(key)) return busyAt(state, key);
5793
6294
  if (tree === void 0 && up?.tree === void 0 && !parts.some((item) => item.key === key)) return void 0;
6295
+ if (circular || openPoint !== void 0) return circularRead(state, key);
5794
6296
  marks.set(key, BUSY);
5795
6297
  demands.push({
5796
6298
  cells: marks,
@@ -5828,8 +6330,9 @@ function treeThing(input, build, outer, host) {
5828
6330
  const read = (key) => {
5829
6331
  if (through !== void 0) return through(key);
5830
6332
  const cell = cells.get(key);
5831
- if (cell !== void 0) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
6333
+ if (cell !== void 0) return cell.busy ? busyAt(state, key) : cell.result;
5832
6334
  if (tree === void 0 && up?.tree === void 0 && !parts.some((item) => item.key === key)) return void 0;
6335
+ if (circular || openPoint !== void 0) return circularRead(state, key);
5833
6336
  cells.set(key, BUSY);
5834
6337
  demands.push({
5835
6338
  cells,
@@ -5857,6 +6360,10 @@ function treeThing(input, build, outer, host) {
5857
6360
  type,
5858
6361
  place,
5859
6362
  tree,
6363
+ marks,
6364
+ cells,
6365
+ host,
6366
+ climb,
5860
6367
  read,
5861
6368
  equation(term, names) {
5862
6369
  if (machine === void 0) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
@@ -5980,10 +6487,11 @@ function treeNode(input, host) {
5980
6487
  const found = input.resolve(SELF_PATH, NO_HOPS);
5981
6488
  const outer = found.ok ? found.thing : void 0;
5982
6489
  const above = stateOf(outer);
5983
- if (above !== void 0 && above.build.open) return ok(treeThing(input, above.build, outer, host));
5984
- const build = host === void 0 ? { open: true } : keptAt(buildsByPlace, input.place, () => ({ open: true }));
6490
+ const place = host === void 0 ? input.place : cellPlace(input.place, above);
6491
+ if (above !== void 0 && above.build.open) return ok(treeThing(input, place, above.build, outer, host));
6492
+ const build = host === void 0 ? { open: true } : keptAt(buildsByPlace, place, () => ({ open: true }));
5985
6493
  build.open = true;
5986
- const root = treeThing(input, build, outer, host);
6494
+ const root = treeThing(input, place, build, outer, host);
5987
6495
  try {
5988
6496
  return stateOf(root).json(/* @__PURE__ */ new Set());
5989
6497
  } finally {
@@ -7465,16 +7973,24 @@ function valueOf$1(info) {
7465
7973
  function hasOwnEntry(table, name) {
7466
7974
  return table !== void 0 && Object.hasOwn(table, name);
7467
7975
  }
7976
+ /** This function tells if the kind of a node declares an attribute `circular` (D113). */
7977
+ function isCircular(kind, attribute) {
7978
+ return (hasOwnEntry(kind.tree?.attributes, attribute) ? kind.tree?.attributes?.[attribute] : void 0)?.circular !== void 0;
7979
+ }
7468
7980
  /** This function gives the vertex of an attribute of a node. A new vertex goes on the work list for its rungs. */
7469
7981
  function vertexOf(b, node, attribute) {
7470
7982
  const id = attributeVertexId(node.visit.place, attribute);
7471
7983
  if (!b.vertices.has(id)) {
7472
- b.vertices.set(id, {
7984
+ const vertex = {
7473
7985
  id,
7474
7986
  place: node.visit.place,
7475
7987
  attribute,
7476
7988
  type: node.text
7477
- });
7989
+ };
7990
+ b.vertices.set(id, isCircular(node.kind, attribute) ? {
7991
+ ...vertex,
7992
+ circular: true
7993
+ } : vertex);
7478
7994
  b.nodes.set(id, node);
7479
7995
  if (!node.parts.has(attribute)) b.work.push(id);
7480
7996
  }
@@ -7873,12 +8389,22 @@ function attributeGraph(machine, doc, options = {}) {
7873
8389
  edges: b.edges,
7874
8390
  graph,
7875
8391
  unknown: b.edges.filter((e) => e.from === void 0),
7876
- cycles: cycles(graph).map((cycle) => ({
7877
- vertices: cycle.nodes,
7878
- certain: cycle.certain
7879
- }))
8392
+ cycles: cycles(graph).map((cycle) => {
8393
+ const found = {
8394
+ vertices: cycle.nodes,
8395
+ certain: cycle.certain
8396
+ };
8397
+ return allCircular(b, cycle.nodes) ? {
8398
+ ...found,
8399
+ circular: true
8400
+ } : found;
8401
+ })
7880
8402
  };
7881
8403
  }
8404
+ /** This function tells if each vertex of a list is a circular attribute (D113). */
8405
+ function allCircular(b, ids) {
8406
+ return ids.every((id) => b.vertices.get(id)?.circular === true);
8407
+ }
7882
8408
  function vertexText(vertex) {
7883
8409
  return `${vertex.attribute} of ${vertex.type} at [${vertex.place.join(", ")}]`;
7884
8410
  }
@@ -7888,6 +8414,9 @@ function vertexText(vertex) {
7888
8414
  * certain cycle that these reads reach always gives `#CYCLE!`. The issue gets the place of the read that closes the
7889
8415
  * cycle in the order of the run, and the reason of the run. A cycle through a lazy read or an unknown read is not an issue.
7890
8416
  *
8417
+ * A cycle whose attributes are each circular gives a fixed point, thus it is not an issue (TREE.CIRCULAR-CHECK). In a
8418
+ * cycle with a plain attribute, the run gives `#CYCLE!` at the first read that closes a chain with a plain attribute.
8419
+ *
7891
8420
  * @param machine - The world and the code of the evaluators.
7892
8421
  * @param doc - The document, as JSON.
7893
8422
  * @param options - The type of a bare root value.
@@ -7898,7 +8427,7 @@ function cycleIssues(machine, doc, options) {
7898
8427
  const graph = nodeGraph(b);
7899
8428
  const certain = /* @__PURE__ */ new Map();
7900
8429
  cycles(graph).forEach((cycle, index) => {
7901
- if (cycle.certain) for (const id of cycle.nodes) certain.set(id, index);
8430
+ if (cycle.certain && !allCircular(b, cycle.nodes)) for (const id of cycle.nodes) certain.set(id, index);
7902
8431
  });
7903
8432
  if (certain.size === 0) return [];
7904
8433
  const eager = /* @__PURE__ */ new Map();
@@ -7946,9 +8475,11 @@ function cycleIssues(machine, doc, options) {
7946
8475
  if (seen === "done") continue;
7947
8476
  const cycle = certain.get(from);
7948
8477
  if (cycle === void 0 || reported.has(cycle)) continue;
7949
- reported.add(cycle);
7950
8478
  const first = stack.findIndex((frame) => frame.id === from);
7951
- const chain = [...stack.slice(first).map((frame) => frame.id), from].map((id) => vertexText(b.vertices.get(id)));
8479
+ const ids = stack.slice(first).map((frame) => frame.id);
8480
+ if (allCircular(b, ids)) continue;
8481
+ reported.add(cycle);
8482
+ const chain = [...ids, from].map((id) => vertexText(b.vertices.get(id)));
7952
8483
  out.push({
7953
8484
  code: "#CYCLE!",
7954
8485
  place: e.place,
@@ -8592,7 +9123,8 @@ function equationIssues(machine, kind, kindText, options, state) {
8592
9123
  }
8593
9124
  /**
8594
9125
  * This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
8595
- * structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes.
9126
+ * structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
9127
+ * declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK).
8596
9128
  */
8597
9129
  function contractIssues(kind, kindText) {
8598
9130
  const out = [];
@@ -8609,6 +9141,14 @@ function contractIssues(kind, kindText) {
8609
9141
  reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.`
8610
9142
  });
8611
9143
  }
9144
+ for (const [name, declared] of Object.entries(tree?.attributes ?? {})) {
9145
+ const rule = declared.circular === void 0 ? void 0 : circularOf(declared.circular);
9146
+ if (rule !== void 0 && !rule.ok) out.push({
9147
+ code: "#VALUE!",
9148
+ place: [],
9149
+ reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.circular. ${rule.reason}`
9150
+ });
9151
+ }
8612
9152
  if (kind.side === "run") out.push({
8613
9153
  code: "#CALC!",
8614
9154
  place: [],
@@ -12124,31 +12664,146 @@ const REF_RUNGS = [
12124
12664
  return declared?.default === void 0 ? void 0 : equationOf(node, declared.default, {});
12125
12665
  }]
12126
12666
  ];
12127
- /** TREE.ONCE, TREE.MEMO and TREE.CYCLE: the result of one attribute, or `undefined` when no rung of its ladder answers. */
12667
+ /** TREE.LADDER: the answer of the first rung that answers, or `undefined`. */
12668
+ function ladderOf(node, name) {
12669
+ for (const [, rung] of REF_RUNGS) {
12670
+ const result = rung(node, name);
12671
+ if (result !== void 0) return result;
12672
+ }
12673
+ }
12674
+ /** The open fixed point of the reference, or `undefined`. */
12675
+ let refPoint;
12676
+ /** TREE.CIRCULAR-CHECK: the declaration `circular` of an attribute of a node, or `undefined`. */
12677
+ function circularRuleOf(node, name) {
12678
+ const declared = termIn(node.contract?.attributes, name)?.circular;
12679
+ return declared === void 0 ? void 0 : circularOf(declared);
12680
+ }
12681
+ /** TREE.CIRCULAR: two approximations are the same when two values are equal (DOC.EQUALS) or two errors have one code and reason. */
12682
+ function sameApproximation(a, b) {
12683
+ if (a === void 0 || b === void 0) return a === b;
12684
+ if (a.ok && b.ok) return docEquals(a.value, b.value);
12685
+ if (!a.ok && !b.ok) return a.error.code === b.error.code && a.error.reason === b.error.reason;
12686
+ return false;
12687
+ }
12688
+ /** TREE.CIRCULAR: the attributes above the leader read an approximation, thus their results are not final. */
12689
+ function readApproximation(point) {
12690
+ for (const entry of inEvaluation.slice(point.depth + 1)) entry.tentative = true;
12691
+ }
12692
+ function approximationsOf(point, node) {
12693
+ const known = point.values.get(node);
12694
+ if (known !== void 0) return known;
12695
+ const made = /* @__PURE__ */ new Map();
12696
+ point.values.set(node, made);
12697
+ return made;
12698
+ }
12699
+ /**
12700
+ * TREE.CIRCULAR and TREE.CIRCULAR-LIMIT: the rounds of the leader of a fixed point. A round without a new value ends
12701
+ * the rounds, and each circular attribute of that round keeps its value. An error ends the rounds at once. The last
12702
+ * round of the limit gives `#CYCLE!` at the place of the node of the leader.
12703
+ */
12704
+ function fixedPointOf(node, name, rule) {
12705
+ const point = {
12706
+ depth: inEvaluation.length - 1,
12707
+ round: 0,
12708
+ changed: false,
12709
+ values: /* @__PURE__ */ new Map()
12710
+ };
12711
+ refPoint = point;
12712
+ try {
12713
+ let before = ok(rule.start);
12714
+ for (;;) {
12715
+ point.round += 1;
12716
+ point.changed = false;
12717
+ const result = ladderOf(node, name);
12718
+ if (result !== void 0 && !result.ok) return result;
12719
+ if (!sameApproximation(before, result)) point.changed = true;
12720
+ before = result;
12721
+ approximationsOf(point, node).set(name, {
12722
+ result,
12723
+ round: point.round
12724
+ });
12725
+ if (!point.changed) {
12726
+ for (const [member, values] of point.values) for (const [key, value] of values) if (value.round === point.round && value.result !== void 0) member.values.set(key, value.result);
12727
+ return result;
12728
+ }
12729
+ if (point.round >= rule.limit) return fail("#CYCLE!", `The cycle of the circular attribute ${nameOfAttribute(node, name)} does not become stable within its limit of ${rule.limit} ${rule.limit === 1 ? "round" : "rounds"}.`, { place: node.place });
12730
+ }
12731
+ } finally {
12732
+ refPoint = void 0;
12733
+ }
12734
+ }
12735
+ /** TREE.CIRCULAR: a circular attribute in the open fixed point of another leader evaluates one time in each round. */
12736
+ function roundOf(point, node, name, rule) {
12737
+ let value = point.values.get(node)?.get(name);
12738
+ if (value === void 0 || value.round !== point.round) {
12739
+ node.pending.add(name);
12740
+ inEvaluation.push({
12741
+ node,
12742
+ name,
12743
+ circular: true,
12744
+ tentative: false
12745
+ });
12746
+ let result;
12747
+ try {
12748
+ result = ladderOf(node, name);
12749
+ } finally {
12750
+ node.pending.delete(name);
12751
+ inEvaluation.pop();
12752
+ }
12753
+ if (!sameApproximation(value === void 0 ? ok(rule.start) : value.result, result)) point.changed = true;
12754
+ value = {
12755
+ result,
12756
+ round: point.round
12757
+ };
12758
+ approximationsOf(point, node).set(name, value);
12759
+ }
12760
+ readApproximation(point);
12761
+ return value.result;
12762
+ }
12763
+ /**
12764
+ * TREE.ONCE, TREE.MEMO, TREE.CYCLE and TREE.CIRCULAR: the result of one attribute, or `undefined` when no rung of its
12765
+ * ladder answers. A result that read an approximation of an open fixed point is not kept.
12766
+ */
12128
12767
  function attributeOf(node, name) {
12129
12768
  const done = node.values.get(name);
12130
12769
  if (done !== void 0) return done;
12131
12770
  if (node.pending.has(name)) {
12132
12771
  const from = inEvaluation.findIndex((entry) => entry.node === node && entry.name === name);
12772
+ const point = refPoint;
12773
+ if (point !== void 0 && from >= point.depth && inEvaluation.slice(from).every((entry) => entry.circular)) {
12774
+ readApproximation(point);
12775
+ const kept = point.values.get(node)?.get(name);
12776
+ if (kept !== void 0) return kept.result;
12777
+ const rule = circularRuleOf(node, name);
12778
+ return ok(rule?.ok === true ? rule.start : null);
12779
+ }
12133
12780
  return fail("#CYCLE!", `The attributes form a cycle: ${[...from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name)), nameOfAttribute(node, name)].join(" -> ")}.`);
12134
12781
  }
12135
- node.pending.add(name);
12136
- inEvaluation.push({
12782
+ const rule = circularRuleOf(node, name);
12783
+ if (rule !== void 0 && !rule.ok) {
12784
+ const bad = fail("#VALUE!", `The attribute ${nameOfAttribute(node, name)} has a bad declaration circular. ${rule.reason}`);
12785
+ node.values.set(name, bad);
12786
+ return bad;
12787
+ }
12788
+ const point = refPoint;
12789
+ if (point !== void 0 && rule !== void 0) return roundOf(point, node, name, rule);
12790
+ const entry = {
12137
12791
  node,
12138
- name
12139
- });
12792
+ name,
12793
+ circular: rule !== void 0,
12794
+ tentative: false
12795
+ };
12796
+ node.pending.add(name);
12797
+ inEvaluation.push(entry);
12798
+ let result;
12140
12799
  try {
12141
- for (const [, rung] of REF_RUNGS) {
12142
- const result = rung(node, name);
12143
- if (result === void 0) continue;
12144
- node.values.set(name, result);
12145
- return result;
12146
- }
12147
- return;
12800
+ result = rule === void 0 ? ladderOf(node, name) : fixedPointOf(node, name, rule);
12148
12801
  } finally {
12149
12802
  node.pending.delete(name);
12150
12803
  inEvaluation.pop();
12151
12804
  }
12805
+ if (!entry.tentative && result !== void 0) node.values.set(name, result);
12806
+ return result;
12152
12807
  }
12153
12808
  /** TREE.RESULT: the JSON of a node. `open` holds the nodes and the containers whose JSON is in progress. */
12154
12809
  function jsonOfNode(node, self, open) {
@@ -16814,6 +17469,6 @@ function samplesAt(values, closed, depth) {
16814
17469
  function typeFromSamples(values, options = {}) {
16815
17470
  return samplesAt(values, options.closed === true, 0);
16816
17471
  }
16817
- 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, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, ask, attributeGraph, attributeTrace, attributeVertexId, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, 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, 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, placeOf, placed, planner, prepare, print, printReads, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, treeNodeEvaluator, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
17472
+ export { ATTRIBUTE_RUNGS, CAPS_TYPE, CIRCULAR_LIMIT, 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, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, ask, attributeGraph, attributeTrace, attributeVertexId, capKeys, check, checkLatent, checkStep, checkSteps, checkWorld, circularOf, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, 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, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isStep, 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, placeOf, placed, planner, prepare, print, printReads, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, 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 };
16818
17473
 
16819
17474
  //# sourceMappingURL=index.js.map