@mark1russell7/coeval 0.20.0 → 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/README.md +1 -0
- package/build/index.d.ts +420 -236
- package/build/index.d.ts.map +1 -1
- package/build/index.js +746 -76
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/conform.ts +13 -4
- package/src/deref.ts +32 -2
- package/src/effects.ts +4 -2
- package/src/evaluate.ts +2 -0
- package/src/facts.ts +3 -2
- package/src/formula.ts +31 -5
- package/src/index.ts +5 -0
- package/src/instances.ts +50 -1
- package/src/parts.ts +30 -1
- package/src/providers.ts +209 -29
- package/src/reference.ts +52 -2
- package/src/static.ts +79 -19
- package/src/stdlib.ts +88 -2
- package/src/steps.ts +181 -0
- package/src/types.ts +12 -4
- package/src/world.ts +50 -0
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
/**
|
|
173
|
-
export
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
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(
|
|
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,
|
|
@@ -92,6 +95,7 @@ export {
|
|
|
92
95
|
kindNamed,
|
|
93
96
|
kindOf,
|
|
94
97
|
} from "./ladder.ts";
|
|
98
|
+
export { type StepKey, type StepLadder, type StepNeed, type StepReason, type StepRefusal, type StepWalk, checkStep, isStep, stepLadder, stepOf, stepRank } from "./steps.ts";
|
|
95
99
|
export {
|
|
96
100
|
type Cap,
|
|
97
101
|
type Context,
|
|
@@ -107,6 +111,7 @@ export {
|
|
|
107
111
|
type Thing,
|
|
108
112
|
type WalkInput,
|
|
109
113
|
type WalkResult,
|
|
114
|
+
type WalkTypeInput,
|
|
110
115
|
type Walker,
|
|
111
116
|
ROOTS,
|
|
112
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) {
|