@mark1russell7/coeval 0.20.1 → 0.22.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/build/index.d.ts +379 -236
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1017 -98
- 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 +53 -10
- package/src/index.ts +10 -0
- package/src/instances.ts +94 -5
- package/src/parts.ts +30 -1
- package/src/providers.ts +209 -29
- package/src/reference.ts +119 -9
- package/src/static.ts +144 -23
- package/src/stdlib.ts +189 -2
- package/src/types.ts +12 -4
- package/src/world.ts +135 -1
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.22.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";
|
|
@@ -114,6 +115,12 @@ export function isClosure(value: unknown): value is Closure {
|
|
|
114
115
|
return typeof value === "function" && (value as Partial<Closure>)[CLOSURE] !== undefined;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The runtime of an evaluation with the clock of `call` (FORMULA.CALL-DEPTH, D125): the count of the applications of
|
|
120
|
+
* closures that run at this time. The first application adds the field, thus an evaluation without one has no field.
|
|
121
|
+
*/
|
|
122
|
+
type Clocked = Runtime & { applying?: number };
|
|
123
|
+
|
|
117
124
|
/**
|
|
118
125
|
* The standard extensions: the values that JSON cannot write. `ext.domain` reads a member of a domain through the
|
|
119
126
|
* codec of the domain (EXT.DOMAIN, D40).
|
|
@@ -278,6 +285,29 @@ function paramsOf(value: unknown): readonly string[] {
|
|
|
278
285
|
return Array.isArray(params) ? params.filter((param): param is string => typeof param === "string") : [];
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
/** This function gives the text of a step of a path: a key, or the kind of a step document in parentheses. */
|
|
289
|
+
function stepText(step: unknown): string {
|
|
290
|
+
if (isKey(step)) return String(step);
|
|
291
|
+
return isRecord(step) ? `(${typeName(step["type"] as Parameters<typeof typeName>[0])})` : "(step)";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* This function gives the type of what a step document reaches (TYPE.WALKER, D121). The type rule of its walker gives
|
|
296
|
+
* it. A step document with parts gets its data in the run, thus its type is `unknown`, as for a walker without a rule.
|
|
297
|
+
*
|
|
298
|
+
* @param input - The input of the rule of the reference.
|
|
299
|
+
* @param step - The step document.
|
|
300
|
+
* @param here - The type before the step.
|
|
301
|
+
* @returns The type of what the step reaches.
|
|
302
|
+
*/
|
|
303
|
+
function walkType(input: TypeInput, step: unknown, here: ValueType): ValueType {
|
|
304
|
+
const rule = walkerOf(input.machine, step)?.type;
|
|
305
|
+
if (rule === undefined) return "unknown";
|
|
306
|
+
const read = docParts(input.machine.world, step);
|
|
307
|
+
if (!read.ok || !read.split.ok || read.split.parts.length > 0) return "unknown";
|
|
308
|
+
return input.contract(rule({ data: read.doc.value, here }), `type of the step ${typeName(read.doc.type)}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
281
311
|
/**
|
|
282
312
|
* This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
|
|
283
313
|
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
|
|
@@ -395,12 +425,14 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
395
425
|
if (isUnknown(type, domains) || (!plain && holdsEnvelope(type, domains))) return "unknown";
|
|
396
426
|
if (index >= path.length) return type;
|
|
397
427
|
const step = path[index];
|
|
398
|
-
|
|
399
|
-
|
|
428
|
+
if (!isKey(step)) {
|
|
429
|
+
type = walkType(input, step, type);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
400
432
|
const next = stepType(type, step, domains, input.contract);
|
|
401
433
|
if ("missing" in next) {
|
|
402
434
|
const fields = next.missing.length === 0 ? "" : ` Its fields are ${listText(next.missing)}.`;
|
|
403
|
-
report({ reason: `The value at ${path.slice(0, index).map(
|
|
435
|
+
report({ reason: `The value at ${path.slice(0, index).map(stepText).join(".")} has no field ${String(step)}.${fields}` });
|
|
404
436
|
return "unknown";
|
|
405
437
|
}
|
|
406
438
|
type = next.type;
|
|
@@ -899,15 +931,26 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
899
931
|
}
|
|
900
932
|
return known;
|
|
901
933
|
};
|
|
934
|
+
// The clock of call permits a quarter of the depth limit, because an application nests its body (D125).
|
|
935
|
+
const limit = Math.ceil((machine.maxDepth ?? 1000) / 4);
|
|
902
936
|
return (ctx, rt) => {
|
|
903
937
|
// A closure keeps the context for each later use, thus the list of reads gets each path that it can read (READS.CLOSURE).
|
|
904
938
|
rt.keepsContext?.(ctx, reads);
|
|
939
|
+
const clock = rt as Clocked;
|
|
905
940
|
const closure = (...args: readonly unknown[]): unknown => {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
941
|
+
// Each application counts one level on the runtime of its evaluation, and the count goes down after it (FORMULA.CALL-DEPTH).
|
|
942
|
+
const level = (clock.applying ?? 0) + 1;
|
|
943
|
+
if (level > limit) throw new Failure(fail("#DEPTH!", `The applications of functions nest deeper than ${limit} levels.`).error);
|
|
944
|
+
clock.applying = level;
|
|
945
|
+
try {
|
|
946
|
+
const scope: Record<string, unknown> = { ...ctx.names };
|
|
947
|
+
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
948
|
+
const result = body.code({ ...ctx, names: scope }, rt);
|
|
949
|
+
if (!result.ok) throw new Failure(result.error);
|
|
950
|
+
return result.value;
|
|
951
|
+
} finally {
|
|
952
|
+
clock.applying = level - 1;
|
|
953
|
+
}
|
|
911
954
|
};
|
|
912
955
|
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
913
956
|
return ok(closure);
|
package/src/index.ts
CHANGED
|
@@ -36,10 +36,15 @@ export {
|
|
|
36
36
|
} from "./errors.ts";
|
|
37
37
|
export {
|
|
38
38
|
type Alias,
|
|
39
|
+
type AtArgs,
|
|
40
|
+
type AtEquation,
|
|
41
|
+
type AtRule,
|
|
39
42
|
type AttributeDeclaration,
|
|
40
43
|
type CapsRule,
|
|
41
44
|
type CircularDeclaration,
|
|
42
45
|
type CircularRule,
|
|
46
|
+
type CollectDeclaration,
|
|
47
|
+
type CollectRule,
|
|
43
48
|
type Kind,
|
|
44
49
|
type KindFacts,
|
|
45
50
|
type LazyRule,
|
|
@@ -55,7 +60,11 @@ export {
|
|
|
55
60
|
type World,
|
|
56
61
|
type WorldEntry,
|
|
57
62
|
CIRCULAR_LIMIT,
|
|
63
|
+
argsKey,
|
|
64
|
+
atArgsOf,
|
|
65
|
+
atRuleOf,
|
|
58
66
|
circularOf,
|
|
67
|
+
collectOf,
|
|
59
68
|
effectsOfVerb,
|
|
60
69
|
entriesAsJson,
|
|
61
70
|
equationType,
|
|
@@ -108,6 +117,7 @@ export {
|
|
|
108
117
|
type Thing,
|
|
109
118
|
type WalkInput,
|
|
110
119
|
type WalkResult,
|
|
120
|
+
type WalkTypeInput,
|
|
111
121
|
type Walker,
|
|
112
122
|
ROOTS,
|
|
113
123
|
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, argsKey, 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";
|
|
@@ -7,13 +7,16 @@ import { type Above, codeFactsOf, partLazy } from "./facts.ts";
|
|
|
7
7
|
import { type NodeGraph, type GraphEdge, cycles } from "./graph.ts";
|
|
8
8
|
import { STD_WALKERS } from "./stdlib.ts";
|
|
9
9
|
import {
|
|
10
|
+
type AtStep,
|
|
10
11
|
type CheckOptions,
|
|
11
12
|
type DocVisit,
|
|
12
13
|
type Issue,
|
|
13
14
|
type SeenNode,
|
|
14
15
|
REF_CODE,
|
|
15
16
|
TREE_NODE,
|
|
17
|
+
atStepOf,
|
|
16
18
|
downNames,
|
|
19
|
+
isAtStep,
|
|
17
20
|
isStructural,
|
|
18
21
|
keepsSelf,
|
|
19
22
|
selvesOf,
|
|
@@ -39,6 +42,8 @@ export interface AttributeVertex {
|
|
|
39
42
|
readonly type: string;
|
|
40
43
|
/** `true` when the kind of the node declares the attribute `circular` (TREE.CIRCULAR-CHECK). Else the field is absent. */
|
|
41
44
|
readonly circular?: true;
|
|
45
|
+
/** The arguments of an instance of an attribute with parameters (TREE.AT-CHECK). Else the field is absent. */
|
|
46
|
+
readonly args?: Readonly<Record<string, Json>>;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/** One read of an attribute by the code of another attribute (D107). */
|
|
@@ -220,6 +225,20 @@ function vertexOf(b: Builder, node: NodeInfo, attribute: string): string {
|
|
|
220
225
|
return id;
|
|
221
226
|
}
|
|
222
227
|
|
|
228
|
+
/**
|
|
229
|
+
* This function gives the vertex of an instance of an attribute with parameters (TREE.AT-CHECK, D124). Its name is the
|
|
230
|
+
* name of a plain vertex with the key of the arguments after the name of the attribute.
|
|
231
|
+
*/
|
|
232
|
+
function atVertexOf(b: Builder, node: NodeInfo, at: AtStep & { readonly kind: "exact" }): string {
|
|
233
|
+
const id = `${attributeVertexId(node.visit.place, at.attr).slice(0, -1)},${at.key}]`;
|
|
234
|
+
if (!b.vertices.has(id)) {
|
|
235
|
+
b.vertices.set(id, { id, place: node.visit.place, attribute: at.attr, type: node.text, args: at.names });
|
|
236
|
+
b.nodes.set(id, node);
|
|
237
|
+
b.work.push(id);
|
|
238
|
+
}
|
|
239
|
+
return id;
|
|
240
|
+
}
|
|
241
|
+
|
|
223
242
|
function edge(b: Builder, from: string | undefined, r: Reader, lazy = false): void {
|
|
224
243
|
b.edges.push({ from, to: r.to, lazy: r.lazy || lazy, place: r.place });
|
|
225
244
|
b.ats.push(r.at);
|
|
@@ -310,6 +329,15 @@ function follow(b: Builder, start: Value, path: readonly Json[], from: number, r
|
|
|
310
329
|
}
|
|
311
330
|
continue;
|
|
312
331
|
}
|
|
332
|
+
// A step tree.at with literal arguments reads one instance (TREE.AT-CHECK). A step that fails gives no read.
|
|
333
|
+
if (isAtStep(b.machine, step)) {
|
|
334
|
+
const at = atStepOf(node.kind, node.text, step.value);
|
|
335
|
+
if (at.kind === "unknown") edge(b, undefined, r);
|
|
336
|
+
if (at.kind !== "exact") return;
|
|
337
|
+
edge(b, atVertexOf(b, node, at), r);
|
|
338
|
+
here = "unknown";
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
313
341
|
// The standard walker nearest depends only on the structure (R3 P5). Each other walker can read each attribute.
|
|
314
342
|
if (walkerOf(b.machine, step) !== STD_WALKERS["walk.nearest"]) {
|
|
315
343
|
edge(b, undefined, r);
|
|
@@ -361,10 +389,11 @@ function termRefs(b: Builder, kind: Kind, label: string, term: Json): readonly T
|
|
|
361
389
|
return out;
|
|
362
390
|
}
|
|
363
391
|
|
|
364
|
-
/** The names of the equation of a rung, before the run. */
|
|
392
|
+
/** The names of the equation of a rung, before the run. The parameters of an attribute hold JSON data (TREE.AT). */
|
|
365
393
|
interface TermNames {
|
|
366
394
|
readonly child?: NodeInfo;
|
|
367
395
|
readonly prev?: Value;
|
|
396
|
+
readonly params?: readonly string[];
|
|
368
397
|
}
|
|
369
398
|
|
|
370
399
|
/** This function adds the reads of an equation of a rung: self is a node, and the names of the rung have their values. */
|
|
@@ -386,7 +415,10 @@ function termReads(b: Builder, refs: readonly TermRef[], self: NodeInfo, names:
|
|
|
386
415
|
// A name that a kind inside the equation binds holds a value of the run. The others are the names of the rung.
|
|
387
416
|
let start: Value;
|
|
388
417
|
if (ref.bound.has(name)) start = "unknown";
|
|
389
|
-
else if (names?.
|
|
418
|
+
else if (names?.params !== undefined) {
|
|
419
|
+
if (!names.params.includes(name)) continue;
|
|
420
|
+
start = "data";
|
|
421
|
+
} else if (names?.child !== undefined && name === "child") start = { node: names.child };
|
|
390
422
|
else if (names?.prev !== undefined && name === "prev") start = names.prev;
|
|
391
423
|
else if (name === "index") start = "data";
|
|
392
424
|
else if (name === "acc" || name === "item") start = "unknown";
|
|
@@ -412,9 +444,16 @@ function prevOf(parent: NodeInfo, child: NodeInfo): Value {
|
|
|
412
444
|
*/
|
|
413
445
|
function rungReads(b: Builder, id: string): void {
|
|
414
446
|
const node = b.nodes.get(id) as NodeInfo;
|
|
415
|
-
const
|
|
447
|
+
const vertex = b.vertices.get(id) as AttributeVertex;
|
|
448
|
+
const key = vertex.attribute;
|
|
416
449
|
const tree = node.kind.tree;
|
|
417
450
|
const r: Reader = { to: id, lazy: false, place: node.visit.place, at: node.visit.at };
|
|
451
|
+
// An instance of an attribute with parameters has one rung: its equation in tree.at (TREE.AT).
|
|
452
|
+
if (vertex.args !== undefined) {
|
|
453
|
+
const at = atStepOf(node.kind, node.text, { attr: key, args: vertex.args });
|
|
454
|
+
if (at.kind === "exact") termReads(b, termRefs(b, node.kind, `tree.at.${key}`, at.equation), node, { params: at.params }, r);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
418
457
|
const equation = hasOwnEntry(tree?.equations, key) ? tree?.equations?.[key] : undefined;
|
|
419
458
|
if (equation !== undefined) {
|
|
420
459
|
termReads(b, termRefs(b, node.kind, `tree.equations.${key}`, equation), node, undefined, r);
|
|
@@ -460,9 +499,58 @@ function rungReads(b: Builder, id: string): void {
|
|
|
460
499
|
termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.combine`, fold.combine), node, undefined, combined ? later : { ...later, lazy: true });
|
|
461
500
|
return;
|
|
462
501
|
}
|
|
502
|
+
const collect = declared?.collect === undefined ? undefined : collectOf(declared.collect);
|
|
503
|
+
if (collect?.ok === true) {
|
|
504
|
+
collectReads(b, node, key, collect, later);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
463
507
|
if (declared?.default !== undefined) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
|
|
464
508
|
}
|
|
465
509
|
|
|
510
|
+
/** This function gives the list children of a node before the run. A node without a rung for it has no children. */
|
|
511
|
+
function childrenOf(b: Builder, at: NodeInfo): Value {
|
|
512
|
+
const part = at.parts.get("children");
|
|
513
|
+
if (part !== undefined) return valueOf(part);
|
|
514
|
+
const tree = at.kind.tree;
|
|
515
|
+
if (hasOwnEntry(tree?.equations, "children") || hasOwnEntry(tree?.attributes, "children")) return "unknown";
|
|
516
|
+
if (at.up === undefined) return downNames(b.machine.world).has("children") ? "unknown" : "data";
|
|
517
|
+
return at.up !== null && hasOwnEntry(at.up.kind.tree?.down, "children") ? "unknown" : "data";
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* This function adds the reads of a collection (TREE.COLLECT, D122), in the order of the run. Each node of the subtree
|
|
522
|
+
* gives the reads of its contribution and of its list `children`. A list that only the run knows gives an edge with an
|
|
523
|
+
* unknown source. The registry of names reads only the lists `children`.
|
|
524
|
+
*/
|
|
525
|
+
function collectReads(b: Builder, node: NodeInfo, key: string, rule: CollectRule & { readonly ok: true }, later: Reader): void {
|
|
526
|
+
if (!rule.names) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.collect.unit`, rule.unit), node, undefined, later);
|
|
527
|
+
let combined = false;
|
|
528
|
+
const done = new Set<NodeInfo>();
|
|
529
|
+
const stack: NodeInfo[] = [node];
|
|
530
|
+
while (stack.length > 0) {
|
|
531
|
+
const at = stack.pop() as NodeInfo;
|
|
532
|
+
if (done.has(at)) continue;
|
|
533
|
+
done.add(at);
|
|
534
|
+
const term = rule.names || !hasOwnEntry(at.kind.tree?.contributes, key) ? undefined : at.kind.tree?.contributes?.[key];
|
|
535
|
+
if (term !== undefined) {
|
|
536
|
+
termReads(b, termRefs(b, at.kind, `tree.contributes.${key}`, term), at, undefined, later);
|
|
537
|
+
combined = true;
|
|
538
|
+
}
|
|
539
|
+
edge(b, vertexOf(b, at, "children"), later);
|
|
540
|
+
const children = childrenOf(b, at);
|
|
541
|
+
if (children === "unknown") edge(b, undefined, later);
|
|
542
|
+
else if (typeof children === "object" && "list" in children) {
|
|
543
|
+
const items = itemsOf(children.list);
|
|
544
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
545
|
+
const child = items[index]?.node;
|
|
546
|
+
if (child !== undefined && child.up === at) stack.push(child);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// The combine evaluates once for each contribution, thus without a known contribution its reads are lazy.
|
|
551
|
+
if (!rule.names) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.collect.combine`, rule.combine), node, undefined, combined ? later : { ...later, lazy: true });
|
|
552
|
+
}
|
|
553
|
+
|
|
466
554
|
/** 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
555
|
interface Built {
|
|
468
556
|
readonly b: Builder;
|
|
@@ -585,7 +673,8 @@ function allCircular(b: Builder, ids: readonly string[]): boolean {
|
|
|
585
673
|
}
|
|
586
674
|
|
|
587
675
|
function vertexText(vertex: AttributeVertex): string {
|
|
588
|
-
|
|
676
|
+
const name = vertex.args === undefined ? vertex.attribute : `${vertex.attribute} with ${String(argsKey(vertex.args))}`;
|
|
677
|
+
return `${name} of ${vertex.type} at [${vertex.place.join(", ")}]`;
|
|
589
678
|
}
|
|
590
679
|
|
|
591
680
|
/**
|
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) {
|