@mark1russell7/coeval 0.3.1 → 0.3.2

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.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
6
6
  "type": "module",
7
7
  "main": "./build/index.js",
package/src/doc.ts CHANGED
@@ -106,6 +106,7 @@ export function typePath(type: TypeRef): readonly Key[] | undefined {
106
106
  * @returns The text, for example `git.push`.
107
107
  */
108
108
  export function typeName(type: TypeRef): string {
109
+ if (typeof type === "string") return type;
109
110
  return typePath(type)?.join(".") ?? "(computed)";
110
111
  }
111
112
 
@@ -126,9 +127,12 @@ function headerFits(value: JsonRecord): boolean {
126
127
  * @returns `true` when the value has the form of an envelope.
127
128
  */
128
129
  export function isEnvelope(value: unknown): value is JsonRecord & Envelope {
129
- if (!isRecord(value) || !headerFits(value)) return false;
130
- for (const key in value) if (Object.hasOwn(value, key) && !(ENVELOPE_KEYS as readonly string[]).includes(key)) return false;
131
- return true;
130
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
131
+ // The keys come first: most values that are not envelopes have another key, thus the check stops early.
132
+ for (const key in value) {
133
+ if (key !== "type" && key !== "value" && key !== "name" && key !== "own" && Object.hasOwn(value, key)) return false;
134
+ }
135
+ return isRecord(value) && headerFits(value);
132
136
  }
133
137
 
134
138
  /** A stored document: an envelope, and the keys of its store beside it, for example `_id`. */
package/src/evaluate.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, isRecord, typeName } from "./doc.ts";
1
+ import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, typeName } from "./doc.ts";
2
2
  import { splitParts } from "./parts.ts";
3
3
  import { ask, type Answer } from "./ladder.ts";
4
- import { type Side, lookupKind } from "./world.ts";
4
+ import { type Lookup, type Side, type World, lookupKind } from "./world.ts";
5
5
  import { type ErrorCode, type EvalError, type Result, fail, ok, placed, thrown, describe } from "./errors.ts";
6
6
  import {
7
7
  type Cap,
@@ -830,23 +830,50 @@ function finish(c: Compiler, meta: Meta, code: Code, run: RunCode): { readonly c
830
830
  return { code: tracedCode, run: tracedRun };
831
831
  }
832
832
 
833
+ /**
834
+ * This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
835
+ * trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
836
+ */
837
+ function evaluatorOf(world: World, envelope: Envelope | undefined, found: Lookup): { readonly name?: string; readonly unknown: boolean } {
838
+ const own = envelope?.own;
839
+ const ownName = own !== undefined && Object.hasOwn(own, "eval") ? own["eval"] : undefined;
840
+ if (ownName !== undefined) return { name: ownName, unknown: false };
841
+ if (!found.ok) return { unknown: true };
842
+ const verbs = found.kind.verbs;
843
+ const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : undefined;
844
+ return name === undefined ? { unknown: false } : { name, unknown: false };
845
+ }
846
+
847
+ /** The meta of a node of a compilation without a trace. Only a trace reads the meta of a node. */
848
+ const NO_META: Meta = Object.freeze({ path: [], doc: { type: "?", value: null }, declared: false, answer: NO_ANSWER, side: "derive" });
849
+
833
850
  function compileNode(c: Compiler, value: unknown, place: readonly Key[], declared: string | undefined, depth: number): { readonly code: Code; readonly run: RunCode } {
834
851
  const envelope = isEnvelope(value) ? (value as Envelope) : undefined;
835
852
  const doc: Envelope = envelope ?? { type: declared ?? "?", value: value as Json };
836
- const path = place.map(String);
837
- const meta0: Meta = { path, doc, declared: envelope === undefined, answer: NO_ANSWER, side: "derive" };
853
+ const meta0: Meta = c.traced ? { path: place.map(String), doc, declared: envelope === undefined, answer: NO_ANSWER, side: "derive" } : NO_META;
838
854
  if (envelope === undefined && declared === undefined) {
839
855
  return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
840
856
  }
841
857
  if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
842
858
 
843
- const answer = ask(c.machine.world, envelope ?? {}, "eval", declared === undefined ? {} : { declaredType: declared });
844
- const found = lookupKind(c.machine.world, doc.type);
859
+ const world = c.machine.world;
860
+ const found = lookupKind(world, doc.type);
845
861
  const kind = found.ok ? found.kind : undefined;
846
- const meta: Meta = { ...meta0, answer, side: kind?.side ?? "derive" };
862
+ let meta = meta0;
863
+ let name: string | undefined;
864
+ let unknown: boolean;
865
+ if (c.traced) {
866
+ const answer = ask(world, envelope ?? {}, "eval", declared === undefined ? {} : { declaredType: declared });
867
+ meta = { ...meta0, answer, side: kind?.side ?? "derive" };
868
+ name = answer.evaluator;
869
+ unknown = answer.error === "unknown-kind";
870
+ } else {
871
+ const quick = evaluatorOf(world, envelope, found);
872
+ name = quick.name;
873
+ unknown = quick.unknown;
874
+ }
847
875
  const kindText = typeName(doc.type);
848
- if (answer.error === "unknown-kind") return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
849
- const name = answer.evaluator;
876
+ if (unknown) return failing(c, meta, place, "#KIND?", `The world has no kind ${kindText}.`);
850
877
  const runners = c.machine.runners ?? {};
851
878
  const runner = name !== undefined && Object.hasOwn(runners, name) ? runners[name] : undefined;
852
879
  const evaluator = name !== undefined && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : undefined;
@@ -860,9 +887,13 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
860
887
  return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
861
888
  }
862
889
  const parts: CompiledPart[] = split.parts.map((slot) => {
863
- const compiled = compileNode(c, slot.item, [...place, ...slot.path], slot.of, depth + 1);
864
- const header = isEnvelope(slot.item) && isRecord(slot.item) && typeof slot.item["name"] === "string" ? slot.item["name"] : undefined;
865
- return { key: slot.key, path: slot.path, ...(header === undefined ? {} : { name: header }), code: compiled.code, run: compiled.run };
890
+ const item = slot.item;
891
+ const compiled = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1);
892
+ // The binding name is in the header of an envelope. Most parts have no name, thus the cheap test comes first.
893
+ const header = typeof (item as { readonly name?: unknown } | null)?.name === "string" && isEnvelope(item) ? (item as { readonly name: string }).name : undefined;
894
+ return header === undefined
895
+ ? { key: slot.key, path: slot.path, code: compiled.code, run: compiled.run }
896
+ : { key: slot.key, path: slot.path, name: header, code: compiled.code, run: compiled.run };
866
897
  });
867
898
  const typePath = found.ok ? found.path : [];
868
899
  const input: CompileInput = {
@@ -876,8 +907,6 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
876
907
  machine: c.machine,
877
908
  fill: (items) => (split.parts.length === 0 ? doc.value : split.fill(items)),
878
909
  };
879
- const who = `the evaluator ${name}`;
880
-
881
910
  if (runner !== undefined) {
882
911
  const inner: RunCode = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
883
912
  const run: RunCode = function* (ctx, rt) {
@@ -901,7 +930,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
901
930
  try {
902
931
  return placed(inner(ctx, rt), place);
903
932
  } catch (error) {
904
- return placed(thrown(error, who), place);
933
+ return placed(thrown(error, `the evaluator ${name}`), place);
905
934
  }
906
935
  };
907
936
  return finish(c, meta, code, function* (ctx, rt) {
@@ -996,8 +1025,8 @@ export function prepare(machine: Machine, doc: unknown, options: PrepareOptions
996
1025
  machine,
997
1026
  traced: c.traced,
998
1027
  root: compiled,
999
- evaluate(context: Context = makeContext(), evalOptions: EvalOptions = {}): Result {
1000
- const s = new Session(machine, false, c.traced, undefined, undefined, evalOptions.effects);
1028
+ evaluate(context: Context = makeContext(), evalOptions?: EvalOptions): Result {
1029
+ const s = new Session(machine, false, c.traced, undefined, undefined, evalOptions?.effects);
1001
1030
  return root.code(rootContext(machine, context), s);
1002
1031
  },
1003
1032
  run(context: Context = makeContext(), runOptions: RunOptions = {}): Promise<Result> {
package/src/formula.ts CHANGED
@@ -84,8 +84,13 @@ function strictTruthy(value: unknown): boolean | undefined {
84
84
  return typeof value === "boolean" ? value : undefined;
85
85
  }
86
86
 
87
+ /**
88
+ * This function sets an own data property. Only the key `__proto__` needs `defineProperty`: an assignment to it
89
+ * changes the prototype. An assignment is faster for each other key of a new plain object.
90
+ */
87
91
  function setOwn(target: Record<string, unknown>, key: string, value: unknown): void {
88
- Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
92
+ if (key === "__proto__") Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
93
+ else target[key] = value;
89
94
  }
90
95
 
91
96
  function compiled(compile: (input: CompileInput) => Code): CompiledEvaluator {
@@ -193,16 +198,18 @@ export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record
193
198
  }),
194
199
 
195
200
  "formula.record": compiled(({ value }) => {
196
- const fields = Object.entries(partsOf(value));
201
+ const fields = partsOf(value);
202
+ const keys = Object.keys(fields);
203
+ const codes = keys.map((key) => (fields[key] as CompiledPart).code);
197
204
  return (ctx, rt) => {
198
205
  const out: Record<string, unknown> = {};
199
- for (const [key, part] of fields) {
200
- const result = part.code(ctx, rt);
206
+ for (let index = 0; index < keys.length; index += 1) {
207
+ const result = (codes[index] as Code)(ctx, rt);
201
208
  if (!result.ok) {
202
209
  if (omit !== undefined && omit(result.error)) continue;
203
210
  return result;
204
211
  }
205
- setOwn(out, key, result.value);
212
+ setOwn(out, keys[index] as string, result.value);
206
213
  }
207
214
  return ok(out);
208
215
  };
package/src/parts.ts CHANGED
@@ -42,10 +42,14 @@ class Bad {
42
42
  ) {}
43
43
  }
44
44
 
45
+ /** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
45
46
  function setOwn(target: Record<string, unknown>, key: string, value: unknown): void {
46
- Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
47
+ if (key === "__proto__") Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
48
+ else target[key] = value;
47
49
  }
48
50
 
51
+ const NO_SLOTS: readonly PartSlot[] = Object.freeze([]);
52
+
49
53
  function hasSlots(template: Template): boolean {
50
54
  return template.t !== "data";
51
55
  }
@@ -59,6 +63,8 @@ function hasSlots(template: Template): boolean {
59
63
  * @returns The parts and a way to fill their places, or the reason why the value does not have the shape.
60
64
  */
61
65
  export function splitParts(shape: Shape | undefined, value: Json): Split {
66
+ // A value without parts is data: the split needs no template.
67
+ if (shape === undefined || shape.shape === "none") return { ok: true, parts: NO_SLOTS, data: value, fill: () => value };
62
68
  const parts: PartSlot[] = [];
63
69
  const slot = (item: Json, path: readonly Key[], of: string | undefined): Template => {
64
70
  parts.push({ path, key: path.join("."), item, ...(of === undefined ? {} : { of }) });
package/src/static.ts CHANGED
@@ -111,6 +111,15 @@ function scopesOf(rules: readonly ScopeRule[] | undefined, value: unknown, slots
111
111
  return { sees, later };
112
112
  }
113
113
 
114
+ /** The names of a walk without names. A walk does not change a set at any time: it makes a new set to add names. */
115
+ const NO_NAMES: ReadonlySet<string> = new Set();
116
+
117
+ /** This function gives the options of a walk without the names of the host. */
118
+ function withoutNames(options: WalkOptions): WalkOptions {
119
+ const { names: _host, ...rest } = options;
120
+ return rest;
121
+ }
122
+
114
123
  /**
115
124
  * This function visits each document of a document: the root and each part at each depth, by the contracts of the
116
125
  * kinds. A part of a kind that the world does not have is not visited. The walk uses no stack of the host for the
@@ -131,9 +140,8 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
131
140
  readonly names: ReadonlySet<string>;
132
141
  readonly later: ReadonlySet<string>;
133
142
  }
134
- const stack: Item[] = [
135
- { value: doc, place: [], at: [], declared: options.declaredType, depth: 0, names: new Set(options.names ?? []), later: new Set() },
136
- ];
143
+ const names: ReadonlySet<string> = options.names === undefined || options.names.length === 0 ? NO_NAMES : new Set(options.names);
144
+ const stack: Item[] = [{ value: doc, place: [], at: [], declared: options.declaredType, depth: 0, names, later: NO_NAMES }];
137
145
  while (stack.length > 0) {
138
146
  const item = stack.pop() as Item;
139
147
  const envelope = isEnvelope(item.value) ? (item.value as Envelope) : undefined;
@@ -144,16 +152,21 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
144
152
  const doc: Envelope = envelope ?? { type: item.declared ?? "?", value: item.value as Json };
145
153
  const found = lookupKind(world, doc.type);
146
154
  const kind = found.ok ? found.kind : undefined;
147
- const answer = visit({
148
- place: item.place,
149
- at: item.at,
150
- doc,
151
- declared: envelope === undefined,
152
- ...(kind === undefined ? {} : { kind, typePath: found.ok ? found.path : [] }),
153
- depth: item.depth,
154
- names: item.names,
155
- later: item.later,
156
- });
155
+ const seen: DocVisit =
156
+ kind === undefined
157
+ ? { place: item.place, at: item.at, doc, declared: envelope === undefined, depth: item.depth, names: item.names, later: item.later }
158
+ : {
159
+ place: item.place,
160
+ at: item.at,
161
+ doc,
162
+ declared: envelope === undefined,
163
+ kind,
164
+ typePath: found.ok ? found.path : [],
165
+ depth: item.depth,
166
+ names: item.names,
167
+ later: item.later,
168
+ };
169
+ const answer = visit(seen);
157
170
  if (answer === "skip" || kind === undefined) continue;
158
171
  const split = splitParts(kind.parts, doc.value);
159
172
  if (!split.ok) {
@@ -206,7 +219,7 @@ export interface RefUse {
206
219
  export function refsOf(world: World, doc: unknown, options: WalkOptions = {}): readonly RefUse[] {
207
220
  const out: RefUse[] = [];
208
221
  // The walk gets no names of the host: a name that the host binds is a dependency, thus its reference is free.
209
- const { names: _host, ...rest } = options;
222
+ const rest: WalkOptions = options.names === undefined ? options : withoutNames(options);
210
223
  walk(
211
224
  world,
212
225
  doc,