@mark1russell7/coeval 0.4.2 → 0.4.3
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 +4 -3
- package/build/index.d.ts +85 -10
- package/build/index.d.ts.map +1 -1
- package/build/index.js +219 -85
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +8 -2
- package/src/doc.ts +26 -8
- package/src/evaluate.ts +27 -20
- package/src/formula.ts +45 -3
- package/src/generate.ts +26 -5
- package/src/index.ts +2 -1
- package/src/parts.ts +59 -3
- package/src/reference.ts +10 -15
- package/src/static.ts +17 -16
package/build/index.js
CHANGED
|
@@ -175,9 +175,17 @@ function fromJson(value) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
|
-
* This function tells if two
|
|
179
|
-
* the same items in the same order, and the same fields. The order of the fields
|
|
180
|
-
*
|
|
178
|
+
* This function tells if two values are equal. Plain JSON compares by its structure. Equal values have the same
|
|
179
|
+
* texts, numbers and truth values, the same items in the same order, and the same fields. The order of the fields
|
|
180
|
+
* does not matter. Each leaf compares with `Object.is`, thus `-0` is not `0`, and `NaN` is `NaN`.
|
|
181
|
+
*
|
|
182
|
+
* Only lists and plain records (`isRecord`) compare by their items and fields. Each other object compares by
|
|
183
|
+
* identity: a date, a map, a function and an instance of a class are equal only to themselves. Thus an early cutoff
|
|
184
|
+
* with this function does not keep a stale host value.
|
|
185
|
+
*
|
|
186
|
+
* The function does not use the stack for the depth, thus a deep document cannot overflow it. A cycle of lists or
|
|
187
|
+
* records does not make it loop. `sameValue` of `agree` uses the same rules, and it adds rules for closures and for
|
|
188
|
+
* a rule of the caller.
|
|
181
189
|
*
|
|
182
190
|
* @param a - The first value.
|
|
183
191
|
* @param b - The second value.
|
|
@@ -185,15 +193,23 @@ function fromJson(value) {
|
|
|
185
193
|
*/
|
|
186
194
|
function docEquals(a, b) {
|
|
187
195
|
const stack = [[a, b]];
|
|
196
|
+
let seen;
|
|
188
197
|
while (stack.length > 0) {
|
|
189
198
|
const [x, y] = stack.pop();
|
|
190
199
|
if (Object.is(x, y)) continue;
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
if (
|
|
200
|
+
const list = Array.isArray(x);
|
|
201
|
+
if (list !== Array.isArray(y)) return false;
|
|
202
|
+
if (!list && (!isRecord(x) || !isRecord(y))) return false;
|
|
203
|
+
seen ??= /* @__PURE__ */ new Map();
|
|
204
|
+
const pairs = seen.get(x);
|
|
205
|
+
if (pairs?.has(y) === true) continue;
|
|
206
|
+
if (pairs === void 0) seen.set(x, /* @__PURE__ */ new Set([y]));
|
|
207
|
+
else pairs.add(y);
|
|
208
|
+
if (list) {
|
|
209
|
+
const xs = x;
|
|
194
210
|
const ys = y;
|
|
195
|
-
if (
|
|
196
|
-
for (let index = 0; index <
|
|
211
|
+
if (xs.length !== ys.length) return false;
|
|
212
|
+
for (let index = 0; index < xs.length; index += 1) stack.push([xs[index], ys[index]]);
|
|
197
213
|
continue;
|
|
198
214
|
}
|
|
199
215
|
const xr = x;
|
|
@@ -1040,6 +1056,10 @@ const NO_SLOTS = Object.freeze([]);
|
|
|
1040
1056
|
function hasSlots(template) {
|
|
1041
1057
|
return template.t !== "data";
|
|
1042
1058
|
}
|
|
1059
|
+
/** This function tells if a shape holds only data: no value of the shape can hold a part. */
|
|
1060
|
+
function isData(shape) {
|
|
1061
|
+
return shape.shape === "none" || shape.shape === "text" || shape.shape === "number" || shape.shape === "one";
|
|
1062
|
+
}
|
|
1043
1063
|
/** This function gives the reason why a value is not data of a shape, or `undefined` when it is. */
|
|
1044
1064
|
function dataFault(spec, item) {
|
|
1045
1065
|
if (spec.shape === "text") return typeof item === "string" ? void 0 : "The value must be a text.";
|
|
@@ -1141,9 +1161,13 @@ function splitParts(shape, value) {
|
|
|
1141
1161
|
shape: "term",
|
|
1142
1162
|
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1143
1163
|
};
|
|
1144
|
-
|
|
1164
|
+
const items = list.map((child, index) => walk(each, child, [...path, index]));
|
|
1165
|
+
return isData(each) ? {
|
|
1166
|
+
t: "data",
|
|
1167
|
+
value: item
|
|
1168
|
+
} : {
|
|
1145
1169
|
t: "list",
|
|
1146
|
-
items
|
|
1170
|
+
items
|
|
1147
1171
|
};
|
|
1148
1172
|
}
|
|
1149
1173
|
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
@@ -1211,6 +1235,54 @@ function splitParts(shape, value) {
|
|
|
1211
1235
|
};
|
|
1212
1236
|
}
|
|
1213
1237
|
/**
|
|
1238
|
+
* This function reads one document by the contracts of the world. It finds the kind of the type. A bare value gets
|
|
1239
|
+
* the type that its position declares. Then the function splits the value by the shape of the kind. The plan, the
|
|
1240
|
+
* reference interpreter and the static kit read each document with this function. Thus they see the same parts.
|
|
1241
|
+
*
|
|
1242
|
+
* @param world - The world of the kinds.
|
|
1243
|
+
* @param value - The document, or a bare value.
|
|
1244
|
+
* @param declaredType - The type that the position of a bare value declares.
|
|
1245
|
+
* @returns The document, its kind and its parts, or the reason why the value is not a document.
|
|
1246
|
+
*/
|
|
1247
|
+
function partsOf(world, value, declaredType) {
|
|
1248
|
+
const envelope = isEnvelope(value) ? value : void 0;
|
|
1249
|
+
if (envelope === void 0 && declaredType === void 0) return {
|
|
1250
|
+
ok: false,
|
|
1251
|
+
code: "#PART?",
|
|
1252
|
+
reason: "The value has no envelope, and its position declares no type."
|
|
1253
|
+
};
|
|
1254
|
+
const doc = envelope ?? {
|
|
1255
|
+
type: declaredType,
|
|
1256
|
+
value
|
|
1257
|
+
};
|
|
1258
|
+
const found = lookupKind(world, doc.type);
|
|
1259
|
+
const kind = found.ok ? found.kind : void 0;
|
|
1260
|
+
const split = splitParts(kind?.parts, doc.value);
|
|
1261
|
+
const typePath = found.ok ? found.path : void 0;
|
|
1262
|
+
if (split.ok) return {
|
|
1263
|
+
ok: true,
|
|
1264
|
+
doc,
|
|
1265
|
+
declared: envelope === void 0,
|
|
1266
|
+
kind,
|
|
1267
|
+
typePath,
|
|
1268
|
+
split
|
|
1269
|
+
};
|
|
1270
|
+
const where = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1271
|
+
const reason = `${split.reason}${where} The kind is ${typeName(doc.type)}.`;
|
|
1272
|
+
return {
|
|
1273
|
+
ok: true,
|
|
1274
|
+
doc,
|
|
1275
|
+
declared: envelope === void 0,
|
|
1276
|
+
kind,
|
|
1277
|
+
typePath,
|
|
1278
|
+
split: {
|
|
1279
|
+
ok: false,
|
|
1280
|
+
reason,
|
|
1281
|
+
at: split.at
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1214
1286
|
* This function tells if the path of a part matches a pattern of a scope rule. The key `"*"` of a pattern matches
|
|
1215
1287
|
* each key.
|
|
1216
1288
|
*
|
|
@@ -1655,15 +1727,15 @@ function finish(c, meta, code, run) {
|
|
|
1655
1727
|
* This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
|
|
1656
1728
|
* trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
|
|
1657
1729
|
*/
|
|
1658
|
-
function evaluatorOf(world, envelope,
|
|
1730
|
+
function evaluatorOf(world, envelope, kind) {
|
|
1659
1731
|
const own = envelope?.own;
|
|
1660
1732
|
const ownName = own !== void 0 && Object.hasOwn(own, "eval") ? own["eval"] : void 0;
|
|
1661
1733
|
if (ownName !== void 0) return {
|
|
1662
1734
|
name: ownName,
|
|
1663
1735
|
unknown: false
|
|
1664
1736
|
};
|
|
1665
|
-
if (
|
|
1666
|
-
const verbs =
|
|
1737
|
+
if (kind === void 0) return { unknown: true };
|
|
1738
|
+
const verbs = kind.verbs;
|
|
1667
1739
|
const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : void 0;
|
|
1668
1740
|
return name === void 0 ? { unknown: false } : {
|
|
1669
1741
|
name,
|
|
@@ -1682,23 +1754,28 @@ const NO_META = Object.freeze({
|
|
|
1682
1754
|
side: "derive"
|
|
1683
1755
|
});
|
|
1684
1756
|
function compileNode(c, value, place, declared, depth, address = false) {
|
|
1685
|
-
const
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1757
|
+
const world = c.machine.world;
|
|
1758
|
+
const read = partsOf(world, value, declared);
|
|
1759
|
+
if (!read.ok) return failing(c, c.traced ? {
|
|
1760
|
+
path: place.map(String),
|
|
1761
|
+
doc: {
|
|
1762
|
+
type: "?",
|
|
1763
|
+
value
|
|
1764
|
+
},
|
|
1765
|
+
declared: true,
|
|
1766
|
+
answer: NO_ANSWER,
|
|
1767
|
+
side: "derive"
|
|
1768
|
+
} : NO_META, place, read.code, read.reason);
|
|
1769
|
+
const { doc, kind, split } = read;
|
|
1770
|
+
const envelope = read.declared ? void 0 : doc;
|
|
1690
1771
|
const meta0 = c.traced ? {
|
|
1691
1772
|
path: place.map(String),
|
|
1692
1773
|
doc,
|
|
1693
|
-
declared:
|
|
1774
|
+
declared: read.declared,
|
|
1694
1775
|
answer: NO_ANSWER,
|
|
1695
1776
|
side: "derive"
|
|
1696
1777
|
} : NO_META;
|
|
1697
|
-
if (envelope === void 0 && declared === void 0) return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
1698
1778
|
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1699
|
-
const world = c.machine.world;
|
|
1700
|
-
const found = lookupKind(world, doc.type);
|
|
1701
|
-
const kind = found.ok ? found.kind : void 0;
|
|
1702
1779
|
let meta = meta0;
|
|
1703
1780
|
let name;
|
|
1704
1781
|
let unknown;
|
|
@@ -1712,7 +1789,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1712
1789
|
name = answer.evaluator;
|
|
1713
1790
|
unknown = answer.error === "unknown-kind";
|
|
1714
1791
|
} else {
|
|
1715
|
-
const quick = evaluatorOf(world, envelope,
|
|
1792
|
+
const quick = evaluatorOf(world, envelope, kind);
|
|
1716
1793
|
name = quick.name;
|
|
1717
1794
|
unknown = quick.unknown;
|
|
1718
1795
|
}
|
|
@@ -1722,11 +1799,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1722
1799
|
const runner = name !== void 0 && Object.hasOwn(runners, name) ? runners[name] : void 0;
|
|
1723
1800
|
const evaluator = name !== void 0 && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : void 0;
|
|
1724
1801
|
if (name === void 0 || runner === void 0 && evaluator === void 0) return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
1725
|
-
|
|
1726
|
-
if (!split.ok) {
|
|
1727
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1728
|
-
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
1729
|
-
}
|
|
1802
|
+
if (!split.ok) return failing(c, meta, place, "#VALUE!", split.reason);
|
|
1730
1803
|
const parts = split.parts.map((slot) => {
|
|
1731
1804
|
const item = slot.item;
|
|
1732
1805
|
const own = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
|
|
@@ -1748,7 +1821,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1748
1821
|
});
|
|
1749
1822
|
const input = {
|
|
1750
1823
|
kind: kindText,
|
|
1751
|
-
typePath:
|
|
1824
|
+
typePath: read.typePath ?? [],
|
|
1752
1825
|
doc,
|
|
1753
1826
|
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
1754
1827
|
data: split.data,
|
|
@@ -3020,40 +3093,32 @@ function walk(world, doc, visit, options = {}) {
|
|
|
3020
3093
|
}];
|
|
3021
3094
|
while (stack.length > 0) {
|
|
3022
3095
|
const item = stack.pop();
|
|
3023
|
-
const
|
|
3024
|
-
if (
|
|
3096
|
+
const read = partsOf(world, item.value, item.declared);
|
|
3097
|
+
if (!read.ok) {
|
|
3025
3098
|
options.problem?.({
|
|
3026
3099
|
place: keysOf(item.place),
|
|
3027
3100
|
at: keysOf(item.at),
|
|
3028
|
-
code:
|
|
3029
|
-
reason:
|
|
3101
|
+
code: read.code,
|
|
3102
|
+
reason: read.reason
|
|
3030
3103
|
});
|
|
3031
3104
|
continue;
|
|
3032
3105
|
}
|
|
3033
|
-
const doc
|
|
3034
|
-
|
|
3035
|
-
value: item.value
|
|
3036
|
-
};
|
|
3037
|
-
const found = lookupKind(world, doc.type);
|
|
3038
|
-
const kind = found.ok ? found.kind : void 0;
|
|
3039
|
-
const here = new Visit(item.place, item.at, doc, envelope === void 0, item.depth, item.names, item.later, item.slot, item.parent, kind, found.ok ? found.path : void 0, item.address ? true : void 0, item.caps);
|
|
3106
|
+
const { doc, kind, split } = read;
|
|
3107
|
+
const here = new Visit(item.place, item.at, doc, read.declared, item.depth, item.names, item.later, item.slot, item.parent, kind, read.typePath, item.address ? true : void 0, item.caps);
|
|
3040
3108
|
if (visit(here) === "skip" || kind === void 0) continue;
|
|
3041
|
-
const
|
|
3042
|
-
const base = envelope === void 0 ? item.at : {
|
|
3109
|
+
const base = read.declared ? item.at : {
|
|
3043
3110
|
up: item.at,
|
|
3044
3111
|
keys: ["value"]
|
|
3045
3112
|
};
|
|
3046
3113
|
if (!split.ok) {
|
|
3047
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
3048
|
-
const where = keysOf({
|
|
3049
|
-
up: base,
|
|
3050
|
-
keys: split.at
|
|
3051
|
-
});
|
|
3052
3114
|
options.problem?.({
|
|
3053
3115
|
place: keysOf(item.place),
|
|
3054
|
-
at:
|
|
3116
|
+
at: keysOf({
|
|
3117
|
+
up: base,
|
|
3118
|
+
keys: split.at
|
|
3119
|
+
}),
|
|
3055
3120
|
code: "#VALUE!",
|
|
3056
|
-
reason:
|
|
3121
|
+
reason: split.reason
|
|
3057
3122
|
});
|
|
3058
3123
|
continue;
|
|
3059
3124
|
}
|
|
@@ -3185,7 +3250,8 @@ function capReason(v, kind) {
|
|
|
3185
3250
|
* - `#PART?`: a value without a type.
|
|
3186
3251
|
* - `#DEPTH!`: too much nesting.
|
|
3187
3252
|
* - `#REF!` and `#STEP?`: a bad path.
|
|
3188
|
-
* - `#NAME?`: a name that nothing binds.
|
|
3253
|
+
* - `#NAME?`: a name that nothing binds. With the hook `known` of the machine, also a name that the language does not
|
|
3254
|
+
* have, for example an operation.
|
|
3189
3255
|
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
3190
3256
|
*
|
|
3191
3257
|
* @param machine - The world and the code of the evaluators.
|
|
@@ -3227,6 +3293,15 @@ function check(machine, doc, options = {}) {
|
|
|
3227
3293
|
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
|
|
3228
3294
|
at: v.at
|
|
3229
3295
|
});
|
|
3296
|
+
else if (machine.known !== void 0) {
|
|
3297
|
+
const known = machine.known(v.typePath ?? [], name);
|
|
3298
|
+
if (known !== true) issues.push({
|
|
3299
|
+
code: "#NAME?",
|
|
3300
|
+
place: v.place,
|
|
3301
|
+
reason: known,
|
|
3302
|
+
at: v.at
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3230
3305
|
const caps = v.caps;
|
|
3231
3306
|
if (caps !== void 0 && v.kind?.effects !== void 0) {
|
|
3232
3307
|
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
@@ -3259,7 +3334,7 @@ function check(machine, doc, options = {}) {
|
|
|
3259
3334
|
reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
|
|
3260
3335
|
at: stepAt(0)
|
|
3261
3336
|
});
|
|
3262
|
-
return
|
|
3337
|
+
return;
|
|
3263
3338
|
}
|
|
3264
3339
|
if (!roots.includes(root)) issues.push({
|
|
3265
3340
|
code: "#REF!",
|
|
@@ -3366,7 +3441,7 @@ function valuesOf(parts, ctx, rt) {
|
|
|
3366
3441
|
}
|
|
3367
3442
|
return values;
|
|
3368
3443
|
}
|
|
3369
|
-
function partsOf(value) {
|
|
3444
|
+
function partsOf$1(value) {
|
|
3370
3445
|
return isRecord(value) ? value : {};
|
|
3371
3446
|
}
|
|
3372
3447
|
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
@@ -3430,7 +3505,14 @@ function formulaKinds() {
|
|
|
3430
3505
|
verbs: { eval: "formula.fn" },
|
|
3431
3506
|
parts: {
|
|
3432
3507
|
shape: "record",
|
|
3433
|
-
fields: {
|
|
3508
|
+
fields: {
|
|
3509
|
+
params: {
|
|
3510
|
+
shape: "list",
|
|
3511
|
+
item: { shape: "text" },
|
|
3512
|
+
distinct: true
|
|
3513
|
+
},
|
|
3514
|
+
body: term
|
|
3515
|
+
},
|
|
3434
3516
|
required: ["params", "body"]
|
|
3435
3517
|
},
|
|
3436
3518
|
scope: [{
|
|
@@ -3582,7 +3664,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3582
3664
|
};
|
|
3583
3665
|
}),
|
|
3584
3666
|
"formula.record": compiled(({ value }) => {
|
|
3585
|
-
const fields = partsOf(value);
|
|
3667
|
+
const fields = partsOf$1(value);
|
|
3586
3668
|
const keys = Object.keys(fields);
|
|
3587
3669
|
const codes = keys.map((key) => fields[key].code);
|
|
3588
3670
|
if (combine !== void 0) {
|
|
@@ -3615,8 +3697,8 @@ function formulaEvaluators(profile = {}) {
|
|
|
3615
3697
|
return Array.isArray(values) ? ok(values) : values;
|
|
3616
3698
|
}),
|
|
3617
3699
|
"formula.let": compiled(({ value }) => {
|
|
3618
|
-
const record = partsOf(value);
|
|
3619
|
-
const bindings = Object.entries(partsOf(record["bind"]));
|
|
3700
|
+
const record = partsOf$1(value);
|
|
3701
|
+
const bindings = Object.entries(partsOf$1(record["bind"]));
|
|
3620
3702
|
const body = record["body"];
|
|
3621
3703
|
return (ctx, rt) => {
|
|
3622
3704
|
const names = { ...ctx.names };
|
|
@@ -3637,7 +3719,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3637
3719
|
return () => bad;
|
|
3638
3720
|
}
|
|
3639
3721
|
const names = params;
|
|
3640
|
-
const body = partsOf(value)["body"];
|
|
3722
|
+
const body = partsOf$1(value)["body"];
|
|
3641
3723
|
return (ctx, rt) => {
|
|
3642
3724
|
const closure = (...args) => {
|
|
3643
3725
|
const scope = { ...ctx.names };
|
|
@@ -3654,7 +3736,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3654
3736
|
};
|
|
3655
3737
|
}),
|
|
3656
3738
|
"formula.call": compiled(({ value }) => {
|
|
3657
|
-
const record = partsOf(value);
|
|
3739
|
+
const record = partsOf$1(value);
|
|
3658
3740
|
const fnPart = record["fn"];
|
|
3659
3741
|
const args = Array.isArray(record["args"]) ? record["args"] : [];
|
|
3660
3742
|
return (ctx, rt) => {
|
|
@@ -3675,7 +3757,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3675
3757
|
};
|
|
3676
3758
|
}),
|
|
3677
3759
|
"formula.if": compiled(({ value }) => {
|
|
3678
|
-
const record = partsOf(value);
|
|
3760
|
+
const record = partsOf$1(value);
|
|
3679
3761
|
const test = record["test"];
|
|
3680
3762
|
const then = record["then"];
|
|
3681
3763
|
const other = record["else"];
|
|
@@ -3794,14 +3876,52 @@ function formulaEvaluators(profile = {}) {
|
|
|
3794
3876
|
};
|
|
3795
3877
|
}
|
|
3796
3878
|
/**
|
|
3879
|
+
* This function makes the hook `known` of the formula kit for one profile (STATIC.KNOWN). The evaluator
|
|
3880
|
+
* `formula.op` knows an operation of `lazy` or of `ops`. A profile whose `ops` is a function answers through a call
|
|
3881
|
+
* of that function. A function that throws counts as an answer, because the run then gives `#CALC!`. The evaluator
|
|
3882
|
+
* `formula.ext` knows an extension of `ext` and each standard extension. The hook knows each name of the other
|
|
3883
|
+
* evaluators.
|
|
3884
|
+
*
|
|
3885
|
+
* @param profile - The operations and the extensions of the language.
|
|
3886
|
+
* @returns The hook: `true`, or the reason of the `#NAME?` that the evaluator gives in a run.
|
|
3887
|
+
*/
|
|
3888
|
+
function formulaKnown(profile = {}) {
|
|
3889
|
+
const hasOp = (name) => {
|
|
3890
|
+
if (profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name)) return true;
|
|
3891
|
+
const ops = profile.ops;
|
|
3892
|
+
if (ops === void 0) return false;
|
|
3893
|
+
if (typeof ops !== "function") return Object.hasOwn(ops, name);
|
|
3894
|
+
try {
|
|
3895
|
+
return ops(name) !== void 0;
|
|
3896
|
+
} catch {
|
|
3897
|
+
return true;
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
const hasExt = (name) => profile.ext !== void 0 && Object.hasOwn(profile.ext, name) || Object.hasOwn(STD_EXT, name);
|
|
3901
|
+
return (path, evaluator) => {
|
|
3902
|
+
if (evaluator !== "formula.op" && evaluator !== "formula.ext") return true;
|
|
3903
|
+
const name = path.slice(1).join(".");
|
|
3904
|
+
if (evaluator === "formula.op") return hasOp(name) ? true : `The language has no operation ${name}.`;
|
|
3905
|
+
return hasExt(name) ? true : `The language has no extension ${name}.`;
|
|
3906
|
+
};
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3797
3909
|
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
3798
|
-
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
3910
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds. Its hook `known` comes
|
|
3911
|
+
* from the profile (`formulaKnown`). A hook `known` of the language answers each name that the kit knows.
|
|
3799
3912
|
*
|
|
3800
3913
|
* @param profile - The operations and the rules of the language.
|
|
3801
3914
|
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
3802
3915
|
* @returns The machine.
|
|
3803
3916
|
*/
|
|
3804
3917
|
function formulaMachine(profile = {}, more = {}) {
|
|
3918
|
+
const kit = formulaKnown(profile);
|
|
3919
|
+
const replaced = (name) => more.evaluators !== void 0 && Object.hasOwn(more.evaluators, name) || more.runners !== void 0 && Object.hasOwn(more.runners, name);
|
|
3920
|
+
const theirs = more.known;
|
|
3921
|
+
const known = (path, evaluator) => {
|
|
3922
|
+
const answer = replaced(evaluator) ? true : kit(path, evaluator);
|
|
3923
|
+
return answer === true && theirs !== void 0 ? theirs(path, evaluator) : answer;
|
|
3924
|
+
};
|
|
3805
3925
|
return {
|
|
3806
3926
|
...more,
|
|
3807
3927
|
world: {
|
|
@@ -3814,7 +3934,8 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
3814
3934
|
evaluators: {
|
|
3815
3935
|
...formulaEvaluators(profile),
|
|
3816
3936
|
...more.evaluators
|
|
3817
|
-
}
|
|
3937
|
+
},
|
|
3938
|
+
known
|
|
3818
3939
|
};
|
|
3819
3940
|
}
|
|
3820
3941
|
//#endregion
|
|
@@ -3843,31 +3964,22 @@ function choose(env, name) {
|
|
|
3843
3964
|
*/
|
|
3844
3965
|
function examine(env, at) {
|
|
3845
3966
|
const { place, declared } = at;
|
|
3846
|
-
const
|
|
3847
|
-
|
|
3967
|
+
const world = env.machine.world;
|
|
3968
|
+
const read = partsOf(world, at.value, declared);
|
|
3969
|
+
if (!read.ok) return fail(read.code, read.reason, { place });
|
|
3848
3970
|
if (at.depth > env.maxDepth) return fail("#DEPTH!", `The nesting is deeper than ${env.maxDepth} documents.`, { place });
|
|
3849
|
-
const doc
|
|
3850
|
-
type: declared,
|
|
3851
|
-
value: at.value
|
|
3852
|
-
};
|
|
3971
|
+
const { doc, kind, split } = read;
|
|
3853
3972
|
const kindText = typeName(doc.type);
|
|
3854
|
-
const
|
|
3855
|
-
const answer = ask(world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
3973
|
+
const answer = ask(world, read.declared ? {} : doc, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
3856
3974
|
if (answer.error === "unknown-kind") return fail("#KIND?", `The world has no kind ${kindText}.`, { place });
|
|
3857
3975
|
const name = answer.evaluator;
|
|
3858
3976
|
const chosen = name === void 0 ? void 0 : choose(env, name);
|
|
3859
3977
|
if (name === void 0 || chosen === void 0) return fail("#NOEVAL", `No rung gives an evaluator for ${kindText}.`, { place });
|
|
3860
|
-
|
|
3861
|
-
const kind = found.ok ? found.kind : void 0;
|
|
3862
|
-
const split = splitParts(kind?.parts, doc.value);
|
|
3863
|
-
if (!split.ok) {
|
|
3864
|
-
const where = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
3865
|
-
return fail("#VALUE!", `${split.reason}${where} The kind is ${kindText}.`, { place });
|
|
3866
|
-
}
|
|
3978
|
+
if (!split.ok) return fail("#VALUE!", split.reason, { place });
|
|
3867
3979
|
return {
|
|
3868
3980
|
doc,
|
|
3869
3981
|
kindText,
|
|
3870
|
-
typePath:
|
|
3982
|
+
typePath: read.typePath ?? [],
|
|
3871
3983
|
kind,
|
|
3872
3984
|
name,
|
|
3873
3985
|
chosen,
|
|
@@ -4327,8 +4439,14 @@ function valuesOfStrict(parts) {
|
|
|
4327
4439
|
/**
|
|
4328
4440
|
* This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
|
|
4329
4441
|
* by field, and the order of the fields does not matter. Each other leaf compares with `Object.is`, thus `-0` is not
|
|
4330
|
-
* `0`, and `NaN` is `NaN`.
|
|
4331
|
-
*
|
|
4442
|
+
* `0`, and `NaN` is `NaN`. The identity of a list or of a plain record is not compared.
|
|
4443
|
+
*
|
|
4444
|
+
* Two closures of `fn` are the same when they have the same parameters. Two `Held` values are the same when their
|
|
4445
|
+
* errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
|
|
4446
|
+
* tells otherwise.
|
|
4447
|
+
*
|
|
4448
|
+
* `docEquals` uses the same rules for lists, plain records and the other objects. It has no rules for closures, for
|
|
4449
|
+
* `Held` values and for a rule of the caller. Thus two closures are equal there only when they are the same function.
|
|
4332
4450
|
*
|
|
4333
4451
|
* @param a - The first value.
|
|
4334
4452
|
* @param b - The second value.
|
|
@@ -4564,8 +4682,9 @@ function leafKind(kind) {
|
|
|
4564
4682
|
/**
|
|
4565
4683
|
* This function builds a random document for a world from the contracts of its kinds. It reads the shapes of the
|
|
4566
4684
|
* parts, the declared types, the permitted kinds and the names that the scope rules bind. Some choices are malformed
|
|
4567
|
-
* on purpose
|
|
4568
|
-
* nothing binds. The depth and the size have
|
|
4685
|
+
* on purpose, for example an unknown kind or an unknown name under a namespace with a fallback. Other examples are a
|
|
4686
|
+
* value without its shape, a bare value without a type and a name that nothing binds. The depth and the size have
|
|
4687
|
+
* limits.
|
|
4569
4688
|
*
|
|
4570
4689
|
* @param world - The world.
|
|
4571
4690
|
* @param options - The seed, the types, the limits and the data of the leaves.
|
|
@@ -4582,6 +4701,10 @@ function generate(world, options) {
|
|
|
4582
4701
|
const found = lookupKind(world, type);
|
|
4583
4702
|
return found.ok && Object.hasOwn(found.kind.verbs, "step");
|
|
4584
4703
|
});
|
|
4704
|
+
const unknownNames = Object.keys(world.kinds).filter((key) => {
|
|
4705
|
+
const entry = world.kinds[key];
|
|
4706
|
+
return isNamespace(entry) && entry.fallback !== void 0;
|
|
4707
|
+
}).map((key) => [key, "nope"]);
|
|
4585
4708
|
let size = 0;
|
|
4586
4709
|
const kindOf = (type) => {
|
|
4587
4710
|
const found = lookupKind(world, type);
|
|
@@ -4804,9 +4927,17 @@ function generate(world, options) {
|
|
|
4804
4927
|
case "one": return rng.chance(malformed) ? "other" : rng.pick(shape.values);
|
|
4805
4928
|
}
|
|
4806
4929
|
};
|
|
4807
|
-
/**
|
|
4930
|
+
/**
|
|
4931
|
+
* A malformed document: an unknown kind, or an unknown name under a namespace with a fallback. It can also be a
|
|
4932
|
+
* bad shape, a bare value without a type, or a bad reference.
|
|
4933
|
+
*/
|
|
4808
4934
|
const brokenAt = (pos) => {
|
|
4809
4935
|
const roll = rng.int(5);
|
|
4936
|
+
if (roll === 0 && unknownNames.length > 0 && rng.chance(.5)) {
|
|
4937
|
+
const type = rng.pick(unknownNames);
|
|
4938
|
+
const kind = kindOf(type);
|
|
4939
|
+
return envelope(type, kind?.parts === void 0 ? dataOf(type) : valueOf(kind.parts, [], pos, scopeOf(kind), type));
|
|
4940
|
+
}
|
|
4810
4941
|
if (roll === 0) return {
|
|
4811
4942
|
type: rng.pick(["nothing", ["no", "such"]]),
|
|
4812
4943
|
value: 1
|
|
@@ -4844,7 +4975,10 @@ function generate(world, options) {
|
|
|
4844
4975
|
size += 1;
|
|
4845
4976
|
const leaf = pos.depth >= maxDepth || size >= maxSize;
|
|
4846
4977
|
if (rng.chance(malformed)) return brokenAt(pos);
|
|
4847
|
-
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3))
|
|
4978
|
+
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3)) {
|
|
4979
|
+
const bare = dataOf([pos.declared]);
|
|
4980
|
+
if (!isEnvelope(bare) || permitted(typePath(bare.type) ?? [], pos.kinds)) return bare;
|
|
4981
|
+
}
|
|
4848
4982
|
let candidates = types.filter((type) => permitted(type, pos.kinds) && (!leaf || leafKind(kindOf(type))));
|
|
4849
4983
|
if (pos.hint !== void 0 && rng.chance(.6)) {
|
|
4850
4984
|
const hinted = candidates.filter((type) => pos.hint?.some((hint) => hint.length === type.length && hint.every((key, index) => key === type[index])));
|
|
@@ -4866,6 +5000,6 @@ function generate(world, options) {
|
|
|
4866
5000
|
});
|
|
4867
5001
|
}
|
|
4868
5002
|
//#endregion
|
|
4869
|
-
export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaDefinitions, formulaEvaluators, formulaKinds, formulaMachine, fromJson, generate, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, partCaps, pathMatches, permits, placed, planner, prepare, random, ref, reference, refsOf, resolve, run, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
|
|
5003
|
+
export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaDefinitions, formulaEvaluators, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, random, ref, reference, refsOf, resolve, run, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
|
|
4870
5004
|
|
|
4871
5005
|
//# sourceMappingURL=index.js.map
|