@mark1russell7/coeval 0.29.0 → 0.30.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/coeval",
4
- "version": "0.29.0",
4
+ "version": "0.30.0",
5
5
  "description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
6
6
  "type": "module",
7
7
  "main": "./build/index.js",
@@ -39,7 +39,7 @@
39
39
  "@mark1russell7/rigor-bench": "^0.2.34",
40
40
  "@mark1russell7/rigor-statistics": "^0.1.16",
41
41
  "@types/node": "^26.6.4",
42
- "@vitest/coverage-v8": "5.1.0",
42
+ "@vitest/coverage-v8": "^5.0.3",
43
43
  "esbuild": "^0.28.2",
44
44
  "typescript": "^7.0.2",
45
45
  "vitest": "^5.0.3"
package/src/index.ts CHANGED
@@ -294,7 +294,7 @@ export {
294
294
  print,
295
295
  syntaxFormat,
296
296
  } from "./syntax.ts";
297
- export { type Configuration, type Frame, type StepperOptions, stepper, stepperNext, stepperStart } from "./stepper.ts";
297
+ export { type Configuration, type Frame, type StepperOptions, decodeConfiguration, encodeConfiguration, stepper, stepperNext, stepperStart } from "./stepper.ts";
298
298
  export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
299
299
  export { type AgreeOptions, type AgreeRunOptions, type Agreement, type AgreeTypesOptions, type TypeAgreement, agreeTypes, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
300
300
  export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
package/src/stepper.ts CHANGED
@@ -110,7 +110,7 @@ interface Shared {
110
110
  readonly stack: readonly Frame[];
111
111
  /** The count of the active applications of functions (FORMULA.CALL-DEPTH). */
112
112
  readonly applying: number;
113
- /** `true` after a closure or a held error goes in a value. Only then does a value need a change for the host. */
113
+ /** `true` after a closure goes in a value. Only then does a value need a change for the host. */
114
114
  readonly marked: boolean;
115
115
  /** The work of the evaluation after the last call of `stepperNext` (WORK.LIMIT). A configuration without it has done no work. */
116
116
  readonly work?: number;
@@ -121,8 +121,9 @@ interface Shared {
121
121
  }
122
122
 
123
123
  /**
124
- * One configuration of the stepper. It is data: for a pure document of the formula kit, it survives `JSON.stringify` and
125
- * `JSON.parse` (STEPPER.DATA).
124
+ * One configuration of the stepper. A closure and a held error in it are values that only the stepper makes
125
+ * (STEPPER.FORGE). Thus a configuration goes through JSON with `encodeConfiguration` and `decodeConfiguration`
126
+ * (STEPPER.DATA).
126
127
  *
127
128
  * - `evaluate`: the document `value` starts, with a new evaluation at its depth.
128
129
  * - `resume`: a document gave `result`, and the top frame takes it.
@@ -183,8 +184,11 @@ interface Program {
183
184
  ended: boolean;
184
185
  }
185
186
 
187
+ /** The keys of the JSON form of a configuration: a closure, a held error, and a record of data with one of these keys. */
186
188
  const CLOSURE_MARK = "coeval.closure";
187
189
  const HELD_MARK = "coeval.held";
190
+ const DATA_MARK = "coeval.data";
191
+ const MARKS: readonly string[] = /* @__PURE__ */ Object.freeze([CLOSURE_MARK, HELD_MARK, DATA_MARK]);
188
192
  const CLOSURE: unique symbol = /* @__PURE__ */ Symbol.for("coeval.closure") as never;
189
193
  const MAX_ALIASES = 32;
190
194
  const MAX_COLLAPSE = 64;
@@ -207,13 +211,25 @@ function setField(target: Record<string, unknown>, key: string, value: unknown):
207
211
  Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
208
212
  }
209
213
 
214
+ /**
215
+ * A closure of the stepper as a value. Only the construct `fn` and `decodeConfiguration` make one. A document gives only
216
+ * data, thus it cannot make a closure with a context of its choice (STEPPER.FORGE, D199).
217
+ */
218
+ class StepClosure {
219
+ readonly closure: Closure;
220
+
221
+ constructor(closure: Closure) {
222
+ this.closure = closure;
223
+ }
224
+ }
225
+
210
226
  function closureOf(value: unknown): Closure | undefined {
211
- return isRecord(value) && Object.hasOwn(value, CLOSURE_MARK) ? (value[CLOSURE_MARK] as unknown as Closure) : undefined;
227
+ return value instanceof StepClosure ? value.closure : undefined;
212
228
  }
213
229
 
230
+ /** A held error is a `Held`, as in the plan. A record of data with the old key is data (STEPPER.FORGE, D199). */
214
231
  function heldOf(value: unknown): EvalError | undefined {
215
- if (value instanceof Held) return value.error;
216
- return isRecord(value) && Object.hasOwn(value, HELD_MARK) ? (value[HELD_MARK] as unknown as EvalError) : undefined;
232
+ return value instanceof Held ? value.error : undefined;
217
233
  }
218
234
 
219
235
  /** The judgment 2: K-START and K-COMPUTED. */
@@ -466,7 +482,7 @@ function popped(c: Configuration): void {
466
482
  }
467
483
 
468
484
  /** H-SEAL: the evaluation of a part, with the frame that waits for it on top of the stack. */
469
- function enter(c: Configuration, frame: { at: readonly Key[] }, site: Site, slot: Slot, given?: Context, marked: boolean = c.marked): Evaluating {
485
+ function enter(c: Configuration, frame: { at: readonly Key[] }, site: Site, slot: Slot, given?: Context): Evaluating {
470
486
  frame.at = slot.path;
471
487
  return {
472
488
  mode: "evaluate",
@@ -477,7 +493,7 @@ function enter(c: Configuration, frame: { at: readonly Key[] }, site: Site, slot
477
493
  ctx: contextOf(site, slot, given),
478
494
  stack: c.stack,
479
495
  applying: c.applying,
480
- marked,
496
+ marked: c.marked,
481
497
  };
482
498
  }
483
499
 
@@ -615,7 +631,7 @@ function hostClosure(p: Program, value: object, closure: Closure): unknown {
615
631
  return fn;
616
632
  }
617
633
 
618
- /** This function gives a value to host code: a closure is a function, and a held error is a `Held`. */
634
+ /** This function gives a value to host code: a closure is a function. A held error is a `Held` in the stepper too. */
619
635
  function forHost(p: Program, c: Configuration, value: unknown): unknown {
620
636
  p.applying = c.applying;
621
637
  return c.marked ? hostValue(p, value) : value;
@@ -626,11 +642,9 @@ function hostValue(p: Program, value: unknown): unknown {
626
642
  const items = (value as readonly unknown[]).map((item) => hostValue(p, item));
627
643
  return items.some((item, index) => item !== value[index]) ? items : value;
628
644
  }
629
- if (!isRecord(value)) return value;
630
645
  const closure = closureOf(value);
631
- if (closure !== undefined) return hostClosure(p, value, closure);
632
- const held = heldOf(value);
633
- if (held !== undefined) return new Held(held);
646
+ if (closure !== undefined) return hostClosure(p, value as StepClosure, closure);
647
+ if (!isRecord(value)) return value;
634
648
  let out: Record<string, unknown> | undefined;
635
649
  for (const key of Object.keys(value)) {
636
650
  const item = hostValue(p, value[key]);
@@ -811,7 +825,7 @@ function construct(p: Program, c: Evaluating, name: string, doc: Envelope, keys:
811
825
  return back(c, fail("#VALUE!", "The params of fn are a list of different texts."));
812
826
  }
813
827
  const closure: Closure = { params: params as readonly string[], body: partNamed(slots, "body") as Slot, site, place: placeOf(c.stack) };
814
- return back({ ...c, marked: true }, ok({ [CLOSURE_MARK]: closure }));
828
+ return back({ ...c, marked: true }, ok(new StepClosure(closure)));
815
829
  }
816
830
  case "formula.call": {
817
831
  const frame: CallFrame = { f: "call", at: NO_KEYS, site, args: partsUnder(slots, "args") };
@@ -1034,13 +1048,12 @@ function take(p: Program, c: Resuming, frame: Frame): Configuration {
1034
1048
  return resumeValues(p, c, frame);
1035
1049
  case "let": {
1036
1050
  const key = String((frame.slots[frame.next - 1] as Slot).path[1]);
1037
- setField(frame.names, key, result.ok ? result.value : { [HELD_MARK]: result.error });
1038
- const marked = c.marked || !result.ok;
1051
+ setField(frame.names, key, result.ok ? result.value : new Held(result.error));
1039
1052
  const slot = nextOf(frame);
1040
- if (slot !== undefined) return enter(c, frame, frame.site, slot, undefined, marked);
1053
+ if (slot !== undefined) return enter(c, frame, frame.site, slot);
1041
1054
  const tail: Frame = { f: "tail", at: NO_KEYS };
1042
1055
  stack[stack.length - 1] = tail;
1043
- return enter(c, tail, frame.site, frame.body, { ...frame.site.ctx, names: frame.names }, marked);
1056
+ return enter(c, tail, frame.site, frame.body, { ...frame.site.ctx, names: frame.names });
1044
1057
  }
1045
1058
  case "call": {
1046
1059
  popped(c);
@@ -1169,6 +1182,65 @@ export function stepperNext(machine: Machine, configuration: Configuration, opti
1169
1182
  };
1170
1183
  }
1171
1184
 
1185
+
1186
+ /**
1187
+ * This function gives the JSON form of a configuration (STEPPER.DATA). A closure becomes a record with the key
1188
+ * `coeval.closure`, and a held error a record with the key `coeval.held`. A record of data with one of the keys of the
1189
+ * form goes in a record with the key `coeval.data`. Thus `decodeConfiguration` tells data from a closure. A value that is
1190
+ * not plain JSON stays as it is. A shared object stays shared, and `JSON.stringify` copies it.
1191
+ */
1192
+ export function encodeConfiguration(configuration: Configuration): Json {
1193
+ const done = new Map<object, unknown>();
1194
+ const encode = (value: unknown): unknown => {
1195
+ if (typeof value !== "object" || value === null) return value;
1196
+ const known = done.get(value);
1197
+ if (known !== undefined) return known;
1198
+ let out: unknown = value;
1199
+ if (value instanceof StepClosure) out = { [CLOSURE_MARK]: encode(value.closure) };
1200
+ else if (value instanceof Held) out = { [HELD_MARK]: encode(value.error) };
1201
+ else if (Array.isArray(value)) out = (value as readonly unknown[]).map(encode);
1202
+ else if (isRecord(value)) {
1203
+ const fields: Record<string, unknown> = {};
1204
+ for (const key of Object.keys(value)) setField(fields, key, encode(value[key]));
1205
+ out = Object.keys(value).some((key) => MARKS.includes(key)) ? { [DATA_MARK]: fields } : fields;
1206
+ }
1207
+ done.set(value, out);
1208
+ return out;
1209
+ };
1210
+ return encode(configuration) as Json;
1211
+ }
1212
+
1213
+ /**
1214
+ * This function gives the configuration of a JSON form from `encodeConfiguration` (STEPPER.DATA). Only a record with the
1215
+ * one key `coeval.closure` or `coeval.held` becomes a closure or a held error. The record in a record with the one key
1216
+ * `coeval.data` is data, also with these keys. The host gives the form, thus the stepper trusts it as it trusts a context.
1217
+ */
1218
+ export function decodeConfiguration(data: Json): Configuration {
1219
+ const done = new Map<object, unknown>();
1220
+ const decode = (value: unknown, data: boolean): unknown => {
1221
+ if (typeof value !== "object" || value === null) return value;
1222
+ const known = data ? undefined : done.get(value);
1223
+ if (known !== undefined) return known;
1224
+ let out: unknown = value;
1225
+ if (Array.isArray(value)) out = (value as readonly unknown[]).map((item) => decode(item, false));
1226
+ else if (isRecord(value)) {
1227
+ const keys = Object.keys(value);
1228
+ const mark = !data && keys.length === 1 ? keys[0] : undefined;
1229
+ if (mark === CLOSURE_MARK) out = new StepClosure(decode(value[mark], false) as Closure);
1230
+ else if (mark === HELD_MARK) out = new Held(decode(value[mark], false) as EvalError);
1231
+ else if (mark === DATA_MARK && isRecord(value[mark])) out = decode(value[mark], true);
1232
+ else {
1233
+ const fields: Record<string, unknown> = {};
1234
+ for (const key of keys) setField(fields, key, decode(value[key], false));
1235
+ out = fields;
1236
+ }
1237
+ }
1238
+ if (!data) done.set(value, out);
1239
+ return out;
1240
+ };
1241
+ return decode(data, false) as Configuration;
1242
+ }
1243
+
1172
1244
  /**
1173
1245
  * This function evaluates a document with an explicit stack (K95, D158). It gives the result of `reference` for each
1174
1246
  * document of the kernel and the formula kit (AGREE.STEPPER). The nesting of a document does not use the stack of the