@mark1russell7/coeval 0.25.0 → 0.26.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 +2 -0
- package/build/index.d.ts +228 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1373 -100
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +10 -5
- package/src/facts.ts +17 -6
- package/src/formula.ts +11 -2
- package/src/index.ts +2 -0
- package/src/language.ts +147 -0
- package/src/stepper.ts +1150 -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.26.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/agree.ts
CHANGED
|
@@ -5,8 +5,9 @@ import { type CheckOptions, typeCheck } from "./static.ts";
|
|
|
5
5
|
import { isPrecise, member, typeText } from "./types.ts";
|
|
6
6
|
import { boundariesOf } from "./typing.ts";
|
|
7
7
|
import { type Effect, type Machine, type Perform, type Read, type TraceNode, JOURNAL, evaluate, makeContext, prepare, run, sharedCopy } from "./evaluate.ts";
|
|
8
|
-
import { Held, isClosure } from "./formula.ts";
|
|
8
|
+
import { type FormulaProfile, Held, isClosure } from "./formula.ts";
|
|
9
9
|
import { type Definitions, reference, referenceRun } from "./reference.ts";
|
|
10
|
+
import { stepper } from "./stepper.ts";
|
|
10
11
|
import { type ReplaySettle, recorder, relay, replayer } from "./replay.ts";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -21,7 +22,7 @@ import { type ReplaySettle, recorder, relay, replayer } from "./replay.ts";
|
|
|
21
22
|
* each such name, and then with the context of `agree`. `agreeRun` does not have this tier.
|
|
22
23
|
* - `other`: the second tier of a language, from the option `other`.
|
|
23
24
|
*/
|
|
24
|
-
export type Tier = "reference" | "traced" | "again" | "folds" | "fixed" | "other";
|
|
25
|
+
export type Tier = "reference" | "traced" | "again" | "folds" | "fixed" | "stepper" | "other";
|
|
25
26
|
|
|
26
27
|
/** What a second tier gives: its result, and its effects when it records them. */
|
|
27
28
|
export interface TierResult {
|
|
@@ -51,9 +52,11 @@ export interface AgreeOptions {
|
|
|
51
52
|
readonly definitions?: Definitions;
|
|
52
53
|
/**
|
|
53
54
|
* The tiers to compare with the plan. The default is the reference, the traced evaluation, a second evaluation, the
|
|
54
|
-
* plan with the other choice of folds and the plan with fixed names.
|
|
55
|
+
* plan with the other choice of folds and the plan with fixed names. The tier `stepper` is not a default.
|
|
55
56
|
*/
|
|
56
57
|
readonly tiers?: readonly Exclude<Tier, "other">[];
|
|
58
|
+
/** The profile of the formula kit, for the tier `stepper`. */
|
|
59
|
+
readonly profile?: FormulaProfile;
|
|
57
60
|
/**
|
|
58
61
|
* This function gives the result of the second tier of a language, for example a compiler to loops.
|
|
59
62
|
*
|
|
@@ -290,6 +293,8 @@ export function agree(machine: Machine, doc: unknown, context: Context = makeCon
|
|
|
290
293
|
let otherEffects: Effect[] = [];
|
|
291
294
|
if (tier === "reference") {
|
|
292
295
|
other = reference(machine, doc, context, { ...declared, ...mock, effects: otherEffects, ...(options.definitions === undefined ? {} : { definitions: options.definitions }) });
|
|
296
|
+
} else if (tier === "stepper") {
|
|
297
|
+
other = stepper(machine, doc, context, { ...declared, ...mock, effects: otherEffects, ...(options.profile === undefined ? {} : { profile: options.profile }), ...(options.definitions === undefined ? {} : { definitions: options.definitions }) });
|
|
293
298
|
} else if (tier === "traced") {
|
|
294
299
|
const traced = evaluate(machine, doc, context, { ...declared, ...mock });
|
|
295
300
|
other = traced.result;
|
|
@@ -509,8 +514,8 @@ export async function agreeRun(machine: Machine, doc: unknown, context: Context
|
|
|
509
514
|
const replay = replayer(log);
|
|
510
515
|
|
|
511
516
|
for (const tier of options.tiers ?? DEFAULT_TIERS) {
|
|
512
|
-
// A run has no tier with fixed names (PLAN.FIXED).
|
|
513
|
-
if (tier === "fixed") continue;
|
|
517
|
+
// A run has no tier with fixed names (PLAN.FIXED), and the stepper does no runs.
|
|
518
|
+
if (tier === "fixed" || tier === "stepper") continue;
|
|
514
519
|
const tierReplay = watched(replay);
|
|
515
520
|
const host = { perform: tierReplay.perform };
|
|
516
521
|
let other: Result;
|
package/src/facts.ts
CHANGED
|
@@ -185,7 +185,8 @@ export function codeFactsOf(machine: Machine, v: DocVisit, kind: Kind, verb: str
|
|
|
185
185
|
*
|
|
186
186
|
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
187
187
|
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
|
|
188
|
-
* declares `reads` adds a read of each of its roots at each document of the kind.
|
|
188
|
+
* declares `reads` adds a read of each of its roots at each document of the kind. A reference with a step document
|
|
189
|
+
* of the kind also has these reads (FACTS.STEP-READS). The first document of a tree
|
|
189
190
|
* kind also gets the lazy reads of the equations of its kind (FACTS.EQUATIONS). A read from `self` or from a name
|
|
190
191
|
* of a rung is not one of them, because it is not free (READS.EQUATION).
|
|
191
192
|
* - `side` and `effects`: from the contract of each kind, for the verb of the facts.
|
|
@@ -232,6 +233,18 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
232
233
|
reads.push(read);
|
|
233
234
|
if (options.volatile?.(read) === true) volatile = true;
|
|
234
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* This function adds a read of each declared root at the place of a document (FACTS.KIND-READS, FACTS.STEP-READS).
|
|
238
|
+
* It is one function for the facts, thus a visit makes no new function.
|
|
239
|
+
*/
|
|
240
|
+
const declare = (list: readonly Root[] | undefined, node: DocVisit | undefined, lazy: boolean, place: DocVisit): void => {
|
|
241
|
+
if (list === undefined) return;
|
|
242
|
+
for (const root of list) {
|
|
243
|
+
// In an equation, the root self is the node, thus its read is not free (READS.EQUATION).
|
|
244
|
+
if (node !== undefined && root === "self") continue;
|
|
245
|
+
add({ root, keys: NONE, exact: true, lazy, address: false, path: [root], place: tight(place.place), at: tight(place.at) });
|
|
246
|
+
}
|
|
247
|
+
};
|
|
235
248
|
/**
|
|
236
249
|
* This function walks a document. Without `node`, it walks the document of the facts. With `node`, it walks an
|
|
237
250
|
* equation of a tree kind, and each read gets the place of that node: the first document of the kind.
|
|
@@ -265,11 +278,7 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
265
278
|
roots = answer?.reads;
|
|
266
279
|
}
|
|
267
280
|
// The code of the kind resolves each of these roots as a whole, at the place of its document (FACTS.KIND-READS).
|
|
268
|
-
|
|
269
|
-
// In an equation, the root self is the node, thus its read is not free (READS.EQUATION).
|
|
270
|
-
if (node !== undefined && root === "self") continue;
|
|
271
|
-
add({ root, keys: NONE, exact: true, lazy, address: false, path: [root], place: tight(place.place), at: tight(place.at) });
|
|
272
|
-
}
|
|
281
|
+
declare(roots, node, lazy, place);
|
|
273
282
|
// check and effectsOf read the declared effects with the same function (D27). The verb of the facts selects the
|
|
274
283
|
// declaration: effects for eval, and the entry of verbEffects for another verb (D49, D111).
|
|
275
284
|
// The answer of the hook for the code of eval wins over the contract, also for an own entry (D118).
|
|
@@ -322,6 +331,8 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
322
331
|
const stepAnswer = walker.name === undefined ? undefined : hook?.(found.path, walker.name);
|
|
323
332
|
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
324
333
|
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
334
|
+
// A walker can resolve a root of its own. The read has the place of the reference (FACTS.STEP-READS).
|
|
335
|
+
declare(stepAnswer?.reads ?? (walker.described ? found.kind.reads : undefined), node, lazy, place);
|
|
325
336
|
}
|
|
326
337
|
const first = path[1];
|
|
327
338
|
// A reference to a name that a kind around it binds reads inside the document: it is not a read. The names of
|
package/src/formula.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Json, type Key, docEquals, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { partsOf as docParts } from "./parts.ts";
|
|
3
3
|
import type { Kind, KindFacts, LazyRule, WorldEntry } from "./world.ts";
|
|
4
|
-
import { type CompiledPath, type Context, type Resolution, compilePath, valueOfThing, walkerOf } from "./deref.ts";
|
|
4
|
+
import { type CompiledPath, type Context, type Resolution, type Root, compilePath, valueOfThing, walkerOf } from "./deref.ts";
|
|
5
5
|
import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
|
|
6
6
|
import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled, totalCode } from "./evaluate.ts";
|
|
7
7
|
import { type MachinePart, mergeMachines } from "./merge.ts";
|
|
@@ -51,6 +51,9 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
|
|
|
51
51
|
* names no operation.
|
|
52
52
|
* - `pureExt`: the pure extensions of `ext`, by their name, for `factsOf` and the folds of a plan (PLAN.FOLD, D47).
|
|
53
53
|
* The default names no extension. A standard extension is pure without a name here.
|
|
54
|
+
* - `extReads`: the roots that an extension of `ext` reads from its context, by its name, for `factsOf`
|
|
55
|
+
* (FACTS.EXT-READS). A read of a root through the context refuses a fold, thus such a pure extension runs at each
|
|
56
|
+
* evaluation.
|
|
54
57
|
* - `volatile`: the operations whose results can change while their arguments stay the same, for example a clock.
|
|
55
58
|
* - `sigs`: the signatures of the operations, by their name, for a check of types (TYPE.SIGNATURE). A list gives
|
|
56
59
|
* overloads. An operation without a signature takes and gives `unknown` (TYPE.GRADUAL).
|
|
@@ -74,6 +77,7 @@ export interface FormulaProfile {
|
|
|
74
77
|
readonly combine?: (errors: readonly EvalError[]) => EvalError;
|
|
75
78
|
readonly pure?: true | readonly string[];
|
|
76
79
|
readonly pureExt?: readonly string[];
|
|
80
|
+
readonly extReads?: Readonly<Record<string, readonly Root[]>>;
|
|
77
81
|
readonly volatile?: readonly string[];
|
|
78
82
|
readonly sigs?: Readonly<Record<string, Signature | readonly Signature[]>> | ((name: string) => Signature | readonly Signature[] | undefined);
|
|
79
83
|
}
|
|
@@ -776,6 +780,7 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
|
776
780
|
"combine",
|
|
777
781
|
"pure",
|
|
778
782
|
"pureExt",
|
|
783
|
+
"extReads",
|
|
779
784
|
"volatile",
|
|
780
785
|
"sigs",
|
|
781
786
|
] as const satisfies readonly (keyof FormulaProfile)[]);
|
|
@@ -1285,7 +1290,11 @@ function makeFacts(profile: FormulaProfile, known: (path: readonly Key[], evalua
|
|
|
1285
1290
|
if (code !== "formula.op" && code !== "formula.ext") return undefined;
|
|
1286
1291
|
const name = path.slice(1).join(".");
|
|
1287
1292
|
if (code === "formula.ext") {
|
|
1288
|
-
if (profile.ext !== undefined && Object.hasOwn(profile.ext, name))
|
|
1293
|
+
if (profile.ext !== undefined && Object.hasOwn(profile.ext, name)) {
|
|
1294
|
+
const facts = pureExt.has(name) ? PURE : NOT_PURE;
|
|
1295
|
+
const reads = profile.extReads !== undefined && Object.hasOwn(profile.extReads, name) ? profile.extReads[name] : undefined;
|
|
1296
|
+
return reads === undefined ? facts : { ...facts, reads };
|
|
1297
|
+
}
|
|
1289
1298
|
return Object.hasOwn(STD_EXT, name) ? PURE : undefined;
|
|
1290
1299
|
}
|
|
1291
1300
|
// An operation that the language does not have gives #NAME?. The kit gives it no facts.
|
package/src/index.ts
CHANGED
|
@@ -91,6 +91,7 @@ export {
|
|
|
91
91
|
restrictMachine,
|
|
92
92
|
restrictWorld,
|
|
93
93
|
} from "./merge.ts";
|
|
94
|
+
export { type Language, type LanguageCode, type LanguageDefinition, compose, defineLanguage } from "./language.ts";
|
|
94
95
|
export {
|
|
95
96
|
type Answer,
|
|
96
97
|
type AskOptions,
|
|
@@ -292,6 +293,7 @@ export {
|
|
|
292
293
|
print,
|
|
293
294
|
syntaxFormat,
|
|
294
295
|
} from "./syntax.ts";
|
|
296
|
+
export { type Configuration, type Frame, type StepperOptions, stepper, stepperNext, stepperStart } from "./stepper.ts";
|
|
295
297
|
export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
|
|
296
298
|
export { type AgreeOptions, type AgreeRunOptions, type Agreement, type AgreeTypesOptions, type TypeAgreement, agreeTypes, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
|
|
297
299
|
export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
|
package/src/language.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { type Key, isRecord } from "./doc.ts";
|
|
2
|
+
import type { Machine } from "./evaluate.ts";
|
|
3
|
+
import { type MachinePart, type MergeConflict, type WorldPart, MergeError, mergeMachines } from "./merge.ts";
|
|
4
|
+
|
|
5
|
+
/** The code half of a language: each field of a machine part, without the world. */
|
|
6
|
+
export type LanguageCode = Omit<MachinePart, "world">;
|
|
7
|
+
|
|
8
|
+
/** The input of `defineLanguage`. */
|
|
9
|
+
export interface LanguageDefinition {
|
|
10
|
+
/** The name of the language. A composition holds one language of a name. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** The version, as `MAJOR.MINOR.PATCH`. */
|
|
13
|
+
readonly version: string;
|
|
14
|
+
/** The world of the language, with the verbs that it owns. It is JSON. */
|
|
15
|
+
readonly world?: WorldPart;
|
|
16
|
+
/** The lowest compatible version of each language that this language needs, by name (LANG.REQUIRES). */
|
|
17
|
+
readonly requires?: Readonly<Record<string, string>>;
|
|
18
|
+
/** The evaluators, the hooks and the other code. A function reads the merge of the languages before it. */
|
|
19
|
+
readonly code?: LanguageCode | ((prev: Machine) => LanguageCode);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One language as one frozen value, for `compose`. */
|
|
23
|
+
export interface Language {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly version: string;
|
|
26
|
+
readonly world: WorldPart;
|
|
27
|
+
readonly requires: Readonly<Record<string, string>>;
|
|
28
|
+
readonly code?: LanguageCode | ((prev: Machine) => LanguageCode);
|
|
29
|
+
/**
|
|
30
|
+
* The canonical JSON of the data half: `requires` and `world`, with the keys in order. Two equal data halves give
|
|
31
|
+
* one text, in each order of their keys (LANG.IDENTITY). The name, the version and this text identify a language.
|
|
32
|
+
*/
|
|
33
|
+
readonly data: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
37
|
+
|
|
38
|
+
function numbers(version: string): readonly [number, number, number] {
|
|
39
|
+
const found = VERSION.exec(version);
|
|
40
|
+
if (found === null) throw new TypeError(`A language version has the form MAJOR.MINOR.PATCH. The text ${JSON.stringify(version)} does not.`);
|
|
41
|
+
return [Number(found[1]), Number(found[2]), Number(found[3])];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* This function tells if a version satisfies a lowest version, by the rules of a caret range of SemVer. The first
|
|
46
|
+
* number that is not zero stays the same, and the version is not lower.
|
|
47
|
+
*/
|
|
48
|
+
function compatible(version: string, lowest: string): boolean {
|
|
49
|
+
const have = numbers(version);
|
|
50
|
+
const want = numbers(lowest);
|
|
51
|
+
const stable = want.findIndex((part) => part !== 0);
|
|
52
|
+
const fixed = stable < 0 ? 3 : stable + 1;
|
|
53
|
+
for (let index = 0; index < 3; index += 1) {
|
|
54
|
+
const [a, b] = [have[index] as number, want[index] as number];
|
|
55
|
+
if (a !== b) return index >= fixed && a > b;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** This function gives the JSON text of a value with its keys in order. A value that is not JSON is an error. */
|
|
61
|
+
function canonical(value: unknown, path: readonly Key[]): string {
|
|
62
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
63
|
+
if (typeof value === "number" && Number.isFinite(value)) return JSON.stringify(value);
|
|
64
|
+
if (Array.isArray(value)) return `[${value.map((item, index) => canonical(item, [...path, index])).join(",")}]`;
|
|
65
|
+
if (!isRecord(value)) throw new TypeError(`The data half of a language is JSON. The value at ${JSON.stringify(path)} is not.`);
|
|
66
|
+
const keys = Object.keys(value)
|
|
67
|
+
.filter((key) => value[key] !== undefined)
|
|
68
|
+
.sort();
|
|
69
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonical(value[key], [...path, key])}`).join(",")}}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function frozen<T>(value: T): T {
|
|
73
|
+
if (typeof value !== "object" || value === null) return value;
|
|
74
|
+
for (const item of Object.values(value)) frozen(item);
|
|
75
|
+
return Object.freeze(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* This function makes one value of a language. The data half is JSON, thus a host can store it and compare it. The
|
|
80
|
+
* code half binds the evaluator names of the world.
|
|
81
|
+
*
|
|
82
|
+
* The language keeps a frozen copy of its data half, thus a later change to the input changes nothing.
|
|
83
|
+
*
|
|
84
|
+
* @param definition - The name, the version, the world, the needed languages and the code.
|
|
85
|
+
* @returns The language.
|
|
86
|
+
* @throws {TypeError} A version in the definition is not `MAJOR.MINOR.PATCH`, the name is empty, or the
|
|
87
|
+
* data half is not JSON.
|
|
88
|
+
*/
|
|
89
|
+
export function defineLanguage(definition: LanguageDefinition): Language {
|
|
90
|
+
if (definition.name === "") throw new TypeError("A language has a name.");
|
|
91
|
+
numbers(definition.version);
|
|
92
|
+
const requires = definition.requires ?? {};
|
|
93
|
+
for (const name of Object.keys(requires)) numbers(requires[name] as string);
|
|
94
|
+
const data = canonical({ requires, world: definition.world ?? {} }, []);
|
|
95
|
+
const copy = frozen(JSON.parse(data) as { readonly requires: Readonly<Record<string, string>>; readonly world: WorldPart });
|
|
96
|
+
const language = { name: definition.name, version: definition.version, world: copy.world, requires: copy.requires, data };
|
|
97
|
+
return Object.freeze(definition.code === undefined ? language : { ...language, code: definition.code });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function partOf(language: Language): MachinePart | ((prev: Machine) => MachinePart) {
|
|
101
|
+
const { code, world } = language;
|
|
102
|
+
return typeof code === "function" ? (prev) => ({ ...code(prev), world }) : { ...code, world };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* This function merges languages into one machine, in the order of the arguments, with `mergeMachines`.
|
|
107
|
+
*
|
|
108
|
+
* - A language comes after each language that it names in `requires`, at a compatible version (LANG.REQUIRES).
|
|
109
|
+
* - One language that comes twice counts once. Thus two languages can share a base.
|
|
110
|
+
* - Two versions of one name, or two data halves under one name and one version, are conflicts (LANG.COMPOSE).
|
|
111
|
+
*
|
|
112
|
+
* @param languages - The languages.
|
|
113
|
+
* @returns The machine.
|
|
114
|
+
* @throws {MergeError} The languages do not fit, or their merge has a conflict. The error lists each conflict.
|
|
115
|
+
*/
|
|
116
|
+
export function compose(...languages: readonly Language[]): Machine {
|
|
117
|
+
const conflicts: MergeConflict[] = [];
|
|
118
|
+
const parts: (MachinePart | ((prev: Machine) => MachinePart))[] = [];
|
|
119
|
+
const seen = new Map<string, { readonly language: Language; readonly index: number }>();
|
|
120
|
+
languages.forEach((language, index) => {
|
|
121
|
+
const earlier = seen.get(language.name);
|
|
122
|
+
const conflict = (name: string, other: number, reason: string): void => {
|
|
123
|
+
conflicts.push({ path: ["languages", name], parts: [Math.min(other, index), Math.max(other, index)], reason });
|
|
124
|
+
};
|
|
125
|
+
if (earlier !== undefined) {
|
|
126
|
+
if (earlier.language.version !== language.version) conflict(language.name, earlier.index, "Two versions of one language.");
|
|
127
|
+
else if (earlier.language.data !== language.data) conflict(language.name, earlier.index, "Two languages with one name and one version differ.");
|
|
128
|
+
else if (earlier.language.code !== language.code) parts.push(partOf(language));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const name of Object.keys(language.requires)) {
|
|
132
|
+
const needed = language.requires[name] as string;
|
|
133
|
+
const found = seen.get(name);
|
|
134
|
+
if (found === undefined) {
|
|
135
|
+
const later = languages.findIndex((other) => other.name === name);
|
|
136
|
+
if (later < 0) conflict(name, index, `A language requires ${name}, and the composition does not have it.`);
|
|
137
|
+
else conflict(name, later, `A language requires ${name}, and ${name} comes after it.`);
|
|
138
|
+
} else if (!compatible(found.language.version, needed)) {
|
|
139
|
+
conflict(name, found.index, `A language requires ${name} ${needed}, and the composition has ${found.language.version}.`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
seen.set(language.name, { language, index });
|
|
143
|
+
parts.push(partOf(language));
|
|
144
|
+
});
|
|
145
|
+
if (conflicts.length > 0) throw new MergeError(conflicts);
|
|
146
|
+
return mergeMachines(...parts);
|
|
147
|
+
}
|