@mark1russell7/coeval 0.20.1 → 0.21.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.20.1",
4
+ "version": "0.21.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",
package/src/conform.ts CHANGED
@@ -214,6 +214,8 @@ interface Sample {
214
214
  readonly otherSplit: SplitOk;
215
215
  /** `true` for each part that a lazy rule covers. */
216
216
  readonly lazy: readonly boolean[];
217
+ /** `true` for each part that a rule `drives` covers: the runner can evaluate it with its code (D119). */
218
+ readonly driven: readonly boolean[];
217
219
  /** The names that the scope rules give each part. */
218
220
  readonly names: readonly (readonly string[])[];
219
221
  }
@@ -282,6 +284,8 @@ interface Contract {
282
284
  readonly declared: readonly Cap[];
283
285
  readonly pure: boolean;
284
286
  readonly lazy: readonly LazyRule[] | undefined;
287
+ /** The parts that the runner evaluates with its code, without a wait (RUN.DRIVES). */
288
+ readonly drives: readonly LazyRule[] | undefined;
285
289
  /** `true` when the facts describe the verb: they describe only `eval`. */
286
290
  readonly describes: boolean;
287
291
  /** `true` when the contract declares the effects of the verb: for `eval`, and for a verb in `verbEffects`. */
@@ -315,6 +319,8 @@ class Shot implements Runtime {
315
319
  readonly context: Context,
316
320
  /** The key of each part, for the reasons. */
317
321
  readonly keys: readonly string[],
322
+ /** `true` for each part that the runner can evaluate with its code, by the rules `drives` (D119). */
323
+ private readonly driven: readonly boolean[],
318
324
  private readonly perform: Perform | undefined,
319
325
  /** The resolutions of an earlier evaluation. Each resolution with the same path gets the same answer (C4). */
320
326
  private readonly replay: readonly Asked[] | undefined,
@@ -334,8 +340,8 @@ class Shot implements Runtime {
334
340
  this.calls.push({ part, via, context: ctx, later: this.afterwards });
335
341
  const key = this.keys[part] ?? String(part);
336
342
  if (via === "run" && !this.runner) this.problems.push(`The code of an evaluator starts the steps of the part ${key}. Only a runner waits.`);
337
- if (via === "code" && this.runner && this.running) {
338
- this.problems.push(`The runner evaluates the part ${key} with its code, and that code cannot wait. A runner evaluates a part with its steps (K65).`);
343
+ if (via === "code" && this.runner && this.running && this.driven[part] !== true) {
344
+ this.problems.push(`The runner evaluates the part ${key} with its code, and that code cannot wait. A runner evaluates a part with its steps, or its contract declares the part in drives (D119).`);
339
345
  }
340
346
  return this.scenario.results[part] ?? NO_RESULT;
341
347
  }
@@ -498,13 +504,15 @@ function typesNaming(world: World, name: string, verb: string): readonly TypeRef
498
504
  function contractOf(env: Env, kind: Kind, typePath: readonly Key[], caps: readonly Cap[] | undefined): Contract {
499
505
  const facts = env.machine.facts?.(typePath, env.name);
500
506
  // A verb without a declaration keeps the effects of eval for the capabilities of the probes, as before D49.
501
- const own = effectsOfVerb(kind, env.verb);
507
+ // For eval, the effects of the answer of the hook win over the contract, as in check (D118).
508
+ const own = (env.verb === "eval" ? facts?.effects : undefined) ?? effectsOfVerb(kind, env.verb);
502
509
  const effects = own ?? kind.effects;
503
510
  const declared: readonly Cap[] = effects === undefined ? [] : effects === "type" ? [typePath] : effects;
504
511
  return {
505
512
  declared,
506
513
  pure: (facts?.pure ?? kind.pure) === true,
507
514
  lazy: facts?.lazy ?? kind.lazy,
515
+ drives: env.verb === "eval" ? (facts?.drives ?? kind.drives) : undefined,
508
516
  describes: env.verb === "eval",
509
517
  declares: own !== undefined,
510
518
  caps: caps ?? [...declared, "probe"],
@@ -637,6 +645,7 @@ function samplesOf(type: TypeRef, typePath: readonly Key[], kind: Kind, contract
637
645
  other: { type, value: otherValue },
638
646
  otherSplit,
639
647
  lazy: split.parts.map((_slot, index) => contract.lazy?.some((rule) => covers(rule, split.parts, index)) === true),
648
+ driven: split.parts.map((_slot, index) => contract.drives?.some((rule) => covers(rule, split.parts, index)) === true),
640
649
  names: scopesOf(kind.scope, value, split.parts).sees,
641
650
  });
642
651
  }
@@ -769,7 +778,7 @@ function instanceOf(env: Env, under: Under, doc: Envelope, split: SplitOk, sampl
769
778
  /** This function evaluates a probe document one time in a new shot. */
770
779
  function shoot(env: Env, instance: Instance, sample: Sample, scenario: Scenario, more: { readonly context?: Context; readonly replay?: readonly Asked[] } = {}): Shot {
771
780
  const context = more.context ?? rootContext(env.machine, makeContext(env.names, scenario.caps));
772
- const shot = new Shot(env.machine, env.runner, scenario, context, sample.split.parts.map((slot) => slot.key), env.perform, more.replay);
781
+ const shot = new Shot(env.machine, env.runner, scenario, context, sample.split.parts.map((slot) => slot.key), sample.driven, env.perform, more.replay);
773
782
  instance(shot);
774
783
  return shot;
775
784
  }
package/src/deref.ts CHANGED
@@ -2,6 +2,7 @@ import { type Envelope, type Json, type JsonRecord, type Key, type TypeRef, fiel
2
2
  import { type Kind, type World, entriesAsJson, lookupKind } from "./world.ts";
3
3
  import { answerOf } from "./ladder.ts";
4
4
  import type { ErrorCode } from "./errors.ts";
5
+ import type { ValueType } from "./types.ts";
5
6
 
6
7
  /**
7
8
  * The brand of a host thing: an object of the host that answers the steps of a path itself. The brand is a
@@ -169,8 +170,37 @@ export interface WalkInput {
169
170
  readonly visit?: (key: Key, thing: Thing) => void;
170
171
  }
171
172
 
172
- /** A walker: the code of the verb `step` of a kind of step documents. */
173
- export type Walker = (input: WalkInput) => WalkResult;
173
+ /** The input of the type rule of a walker: the data of the step document, and the type of the thing before the step. */
174
+ export interface WalkTypeInput {
175
+ /** The value of the step document. A step document with parts has no type rule, thus this value holds no part. */
176
+ readonly data: Json;
177
+ /** The type of the thing that the path reached before this step. */
178
+ readonly here: ValueType;
179
+ }
180
+
181
+ /**
182
+ * A walker: the code of the verb `step` of a kind of step documents. A plain function is a walker.
183
+ *
184
+ * A walker can hold a type rule in its field `type` (TYPE.WALKER, D121). The rule gives the type of each thing that
185
+ * the walker reaches from a thing of the type `here`. It must be sound, as the rule of an evaluator. A walker
186
+ * without a rule reaches the type `unknown`. A host gives the rule with `Object.assign(walker, { type })`.
187
+ */
188
+ export interface Walker {
189
+ /**
190
+ * This function answers the step document on the thing that the path reached.
191
+ *
192
+ * @param input - The data of the step document, the thing before the step and the ladder.
193
+ * @returns What the step reached, or the reason of a failure.
194
+ */
195
+ (input: WalkInput): WalkResult;
196
+ /**
197
+ * This optional function gives the type of what the walker reaches.
198
+ *
199
+ * @param input - The data of the step document and the type before the step.
200
+ * @returns The type of each thing that the walker reaches.
201
+ */
202
+ readonly type?: (input: WalkTypeInput) => ValueType;
203
+ }
174
204
 
175
205
  /**
176
206
  * What a resolution needs: the world, and the code of the `deref` evaluators and of the walkers.
package/src/effects.ts CHANGED
@@ -46,7 +46,7 @@ function reaching(machine: Machine, doc: unknown, options: EffectsOptions): read
46
46
  doc,
47
47
  (v) => {
48
48
  const caps = v.caps ?? EACH;
49
- for (const kind of declaredEffects(v)) if (rule(caps, kind)) out.push({ kind, visit: v });
49
+ for (const kind of declaredEffects(v, "eval", machine)) if (rule(caps, kind)) out.push({ kind, visit: v });
50
50
  return undefined;
51
51
  },
52
52
  { caps: options.caps ?? EACH, ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }) },
@@ -65,7 +65,9 @@ function reaching(machine: Machine, doc: unknown, options: EffectsOptions): read
65
65
  *
66
66
  * The result covers each effect that an evaluation or a run gives the host (CAP.EFFECTS-SOUND). A procedure value keeps
67
67
  * the capabilities of the document that made it, thus the effects of its body count at the body (CAP.LATENT). The
68
- * contract describes the code of its kind. The code of an own entry for another evaluator is outside the result.
68
+ * contract describes the code of its kind. The contract does not describe the code of an own entry for another
69
+ * evaluator. The answer of the hook `facts` for that code gives its effects (CAP.OWN-EFFECTS, D118). Without an
70
+ * answer, its effects are outside the result.
69
71
  *
70
72
  * @param machine - The world and its contracts, and the hook `permits`.
71
73
  * @param doc - The document, as JSON.
package/src/evaluate.ts CHANGED
@@ -1792,6 +1792,8 @@ function pureCode(c: Compiler, envelope: Envelope | undefined, kind: Kind, typeP
1792
1792
  } catch {
1793
1793
  return false;
1794
1794
  }
1795
+ // The effects of the answer of the hook make the code not pure, also for an own entry (D118).
1796
+ if (answer?.effects !== undefined && answer.effects.length > 0) return false;
1795
1797
  // The contract of the kind describes the code of its own name, or an own entry that names the same code (D24).
1796
1798
  const described = c.verb === "eval" && (envelope === undefined || quick?.rung !== "own" || name === answerOf(c.machine.world, undefined, kind, c.verb)?.name);
1797
1799
  const pure = described ? (answer?.pure ?? kind.pure) : answer?.pure;
package/src/facts.ts CHANGED
@@ -4,7 +4,7 @@ import { answerOf } from "./ladder.ts";
4
4
  import { pathMatches, splitParts } from "./parts.ts";
5
5
  import { type Root, ROOTS, isRef } from "./deref.ts";
6
6
  import type { Machine } from "./evaluate.ts";
7
- import { type DocVisit, type WalkOptions, declaredEffects, equationsOf, walk } from "./static.ts";
7
+ import { type DocVisit, type WalkOptions, declaredEffects, effectKeys, equationsOf, walk } from "./static.ts";
8
8
 
9
9
  /**
10
10
  * One read of a document: a free reference, with its marks (FACTS.READS). A plan resolves the path of the reference
@@ -272,7 +272,8 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
272
272
  }
273
273
  // check and effectsOf read the declared effects with the same function (D27). The verb of the facts selects the
274
274
  // declaration: effects for eval, and the entry of verbEffects for another verb (D49, D111).
275
- const declared = declaredEffects(v, verb);
275
+ // The answer of the hook for the code of eval wins over the contract, also for an own entry (D118).
276
+ const declared = verb === "eval" && answer?.effects !== undefined ? effectKeys(v, answer.effects) : declaredEffects(v, verb);
276
277
  // The field side gives the side of eval. For another verb, a declared effect needs a runner, as in check, thus
277
278
  // the run side (FACTS.VERB-SIDE).
278
279
  const runs = verb === "eval" ? kind.side === "run" : declared.length > 0;
package/src/formula.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { type Json, type Key, docEquals, isKey, isRecord } from "./doc.ts";
1
+ import { type Json, type Key, docEquals, isKey, isRecord, typeName } from "./doc.ts";
2
+ import { partsOf as docParts } from "./parts.ts";
2
3
  import type { Kind, KindFacts, LazyRule, WorldEntry } from "./world.ts";
3
- import { type CompiledPath, type Context, type Resolution, compilePath, valueOfThing } from "./deref.ts";
4
+ import { type CompiledPath, type Context, type Resolution, compilePath, valueOfThing, walkerOf } from "./deref.ts";
4
5
  import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
5
6
  import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled, totalCode } from "./evaluate.ts";
6
7
  import { type MachinePart, mergeMachines } from "./merge.ts";
@@ -278,6 +279,29 @@ function paramsOf(value: unknown): readonly string[] {
278
279
  return Array.isArray(params) ? params.filter((param): param is string => typeof param === "string") : [];
279
280
  }
280
281
 
282
+ /** This function gives the text of a step of a path: a key, or the kind of a step document in parentheses. */
283
+ function stepText(step: unknown): string {
284
+ if (isKey(step)) return String(step);
285
+ return isRecord(step) ? `(${typeName(step["type"] as Parameters<typeof typeName>[0])})` : "(step)";
286
+ }
287
+
288
+ /**
289
+ * This function gives the type of what a step document reaches (TYPE.WALKER, D121). The type rule of its walker gives
290
+ * it. A step document with parts gets its data in the run, thus its type is `unknown`, as for a walker without a rule.
291
+ *
292
+ * @param input - The input of the rule of the reference.
293
+ * @param step - The step document.
294
+ * @param here - The type before the step.
295
+ * @returns The type of what the step reaches.
296
+ */
297
+ function walkType(input: TypeInput, step: unknown, here: ValueType): ValueType {
298
+ const rule = walkerOf(input.machine, step)?.type;
299
+ if (rule === undefined) return "unknown";
300
+ const read = docParts(input.machine.world, step);
301
+ if (!read.ok || !read.split.ok || read.split.parts.length > 0) return "unknown";
302
+ return input.contract(rule({ data: read.doc.value, here }), `type of the step ${typeName(read.doc.type)}`);
303
+ }
304
+
281
305
  /**
282
306
  * This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
283
307
  * holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
@@ -395,12 +419,14 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
395
419
  if (isUnknown(type, domains) || (!plain && holdsEnvelope(type, domains))) return "unknown";
396
420
  if (index >= path.length) return type;
397
421
  const step = path[index];
398
- // A step document gives what its walker reaches. It has no type yet.
399
- if (!isKey(step)) return "unknown";
422
+ if (!isKey(step)) {
423
+ type = walkType(input, step, type);
424
+ continue;
425
+ }
400
426
  const next = stepType(type, step, domains, input.contract);
401
427
  if ("missing" in next) {
402
428
  const fields = next.missing.length === 0 ? "" : ` Its fields are ${listText(next.missing)}.`;
403
- report({ reason: `The value at ${path.slice(0, index).map(String).join(".")} has no field ${String(step)}.${fields}` });
429
+ report({ reason: `The value at ${path.slice(0, index).map(stepText).join(".")} has no field ${String(step)}.${fields}` });
404
430
  return "unknown";
405
431
  }
406
432
  type = next.type;
package/src/index.ts CHANGED
@@ -40,6 +40,8 @@ export {
40
40
  type CapsRule,
41
41
  type CircularDeclaration,
42
42
  type CircularRule,
43
+ type CollectDeclaration,
44
+ type CollectRule,
43
45
  type Kind,
44
46
  type KindFacts,
45
47
  type LazyRule,
@@ -56,6 +58,7 @@ export {
56
58
  type WorldEntry,
57
59
  CIRCULAR_LIMIT,
58
60
  circularOf,
61
+ collectOf,
59
62
  effectsOfVerb,
60
63
  entriesAsJson,
61
64
  equationType,
@@ -108,6 +111,7 @@ export {
108
111
  type Thing,
109
112
  type WalkInput,
110
113
  type WalkResult,
114
+ type WalkTypeInput,
111
115
  type Walker,
112
116
  ROOTS,
113
117
  THING,
package/src/instances.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Json, type Key, field, isKey, isRecord, typeName } from "./doc.ts";
2
- import { type Kind, equationType } from "./world.ts";
2
+ import { type CollectRule, type Kind, collectOf, equationType } from "./world.ts";
3
3
  import { ask } from "./ladder.ts";
4
4
  import { isRef, walkerOf } from "./deref.ts";
5
5
  import type { Machine } from "./evaluate.ts";
@@ -460,9 +460,58 @@ function rungReads(b: Builder, id: string): void {
460
460
  termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.combine`, fold.combine), node, undefined, combined ? later : { ...later, lazy: true });
461
461
  return;
462
462
  }
463
+ const collect = declared?.collect === undefined ? undefined : collectOf(declared.collect);
464
+ if (collect?.ok === true) {
465
+ collectReads(b, node, key, collect, later);
466
+ return;
467
+ }
463
468
  if (declared?.default !== undefined) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
464
469
  }
465
470
 
471
+ /** This function gives the list children of a node before the run. A node without a rung for it has no children. */
472
+ function childrenOf(b: Builder, at: NodeInfo): Value {
473
+ const part = at.parts.get("children");
474
+ if (part !== undefined) return valueOf(part);
475
+ const tree = at.kind.tree;
476
+ if (hasOwnEntry(tree?.equations, "children") || hasOwnEntry(tree?.attributes, "children")) return "unknown";
477
+ if (at.up === undefined) return downNames(b.machine.world).has("children") ? "unknown" : "data";
478
+ return at.up !== null && hasOwnEntry(at.up.kind.tree?.down, "children") ? "unknown" : "data";
479
+ }
480
+
481
+ /**
482
+ * This function adds the reads of a collection (TREE.COLLECT, D122), in the order of the run. Each node of the subtree
483
+ * gives the reads of its contribution and of its list `children`. A list that only the run knows gives an edge with an
484
+ * unknown source. The registry of names reads only the lists `children`.
485
+ */
486
+ function collectReads(b: Builder, node: NodeInfo, key: string, rule: CollectRule & { readonly ok: true }, later: Reader): void {
487
+ if (!rule.names) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.collect.unit`, rule.unit), node, undefined, later);
488
+ let combined = false;
489
+ const done = new Set<NodeInfo>();
490
+ const stack: NodeInfo[] = [node];
491
+ while (stack.length > 0) {
492
+ const at = stack.pop() as NodeInfo;
493
+ if (done.has(at)) continue;
494
+ done.add(at);
495
+ const term = rule.names || !hasOwnEntry(at.kind.tree?.contributes, key) ? undefined : at.kind.tree?.contributes?.[key];
496
+ if (term !== undefined) {
497
+ termReads(b, termRefs(b, at.kind, `tree.contributes.${key}`, term), at, undefined, later);
498
+ combined = true;
499
+ }
500
+ edge(b, vertexOf(b, at, "children"), later);
501
+ const children = childrenOf(b, at);
502
+ if (children === "unknown") edge(b, undefined, later);
503
+ else if (typeof children === "object" && "list" in children) {
504
+ const items = itemsOf(children.list);
505
+ for (let index = items.length - 1; index >= 0; index -= 1) {
506
+ const child = items[index]?.node;
507
+ if (child !== undefined && child.up === at) stack.push(child);
508
+ }
509
+ }
510
+ }
511
+ // The combine evaluates once for each contribution, thus without a known contribution its reads are lazy.
512
+ if (!rule.names) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.collect.combine`, rule.combine), node, undefined, combined ? later : { ...later, lazy: true });
513
+ }
514
+
466
515
  /** The vertices and the edges of a document, and the roots of the demand of the run: the own parts of each tree root. */
467
516
  interface Built {
468
517
  readonly b: Builder;
package/src/parts.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Envelope, type Json, type Key, docEquals, isEnvelope, isRecord, typeName, typePath } from "./doc.ts";
2
- import { type Kind, type NamesSource, type ScopeRule, type Shape, type World, lookupKind } from "./world.ts";
2
+ import { type Kind, type LazyRule, type NamesSource, type ScopeRule, type Shape, type World, lookupKind } from "./world.ts";
3
3
  import type { ValueType } from "./types.ts";
4
4
 
5
5
  /** One part of a value: its place in the value, the JSON of the part, and the type that its position declares. */
@@ -269,6 +269,35 @@ export function pathMatches(path: readonly Key[], pattern: readonly Key[]): bool
269
269
  return true;
270
270
  }
271
271
 
272
+ /**
273
+ * This function tells if a list of rules in the form of the lazy rules covers a part (D24, D119). A rule without
274
+ * `in` covers each part. With `after: n`, a rule covers only the parts after the first `n` parts that it matches.
275
+ *
276
+ * @param rules - The rules.
277
+ * @param shape - The shape of the parts of the parent.
278
+ * @param value - The value of the parent.
279
+ * @param slot - The path of the part in the value.
280
+ * @returns `true` when a rule covers the part.
281
+ */
282
+ export function rulesCover(rules: readonly LazyRule[], shape: Shape | undefined, value: Json, slot: readonly Key[]): boolean {
283
+ for (const rule of rules) {
284
+ if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(slot, pattern))) continue;
285
+ const after = rule.after ?? 0;
286
+ if (after <= 0) return true;
287
+ const split = splitParts(shape, value);
288
+ let count = 0;
289
+ for (const part of split.ok ? split.parts : []) {
290
+ if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(part.path, pattern))) continue;
291
+ if (part.path.length === slot.length && pathMatches(part.path, slot)) {
292
+ if (count >= after) return true;
293
+ break;
294
+ }
295
+ count += 1;
296
+ }
297
+ }
298
+ return false;
299
+ }
300
+
272
301
  function valueAt(value: unknown, path: readonly Key[]): unknown {
273
302
  let here: unknown = value;
274
303
  for (const key of path) {