@mark1russell7/coeval 0.7.0 → 0.7.1
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 +31 -0
- package/build/index.d.ts +225 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1038 -27
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codec.ts +185 -0
- package/src/conform.ts +1 -1
- package/src/evaluate.ts +8 -2
- package/src/formula.ts +19 -8
- package/src/index.ts +19 -1
- package/src/reference.ts +4 -2
- package/src/syntax.ts +846 -0
- package/src/types.ts +28 -1
package/build/index.js
CHANGED
|
@@ -759,7 +759,7 @@ function fits(found, expected, domains) {
|
|
|
759
759
|
return fitsAt(found, expected, domains, 0);
|
|
760
760
|
}
|
|
761
761
|
/** This function sets an own field of a new record. Only the key `__proto__` needs `defineProperty`. */
|
|
762
|
-
function setOwn$
|
|
762
|
+
function setOwn$5(target, key, value) {
|
|
763
763
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
764
764
|
value,
|
|
765
765
|
enumerable: true,
|
|
@@ -778,10 +778,10 @@ function joinRecords(a, b, domains, depth) {
|
|
|
778
778
|
const inA = Object.hasOwn(a.fields, key);
|
|
779
779
|
const inB = Object.hasOwn(b.fields, key);
|
|
780
780
|
if (inA && inB) {
|
|
781
|
-
setOwn$
|
|
781
|
+
setOwn$5(fields, key, joinAt(a.fields[key], b.fields[key], domains, depth));
|
|
782
782
|
if (a.optional.has(key) || b.optional.has(key)) optional.push(key);
|
|
783
783
|
} else if (closed) {
|
|
784
|
-
setOwn$
|
|
784
|
+
setOwn$5(fields, key, inA ? a.fields[key] : b.fields[key]);
|
|
785
785
|
optional.push(key);
|
|
786
786
|
}
|
|
787
787
|
}
|
|
@@ -1185,7 +1185,7 @@ function keyOf(path) {
|
|
|
1185
1185
|
return JSON.stringify(path.map(String));
|
|
1186
1186
|
}
|
|
1187
1187
|
/** This function sets an own data property. An assignment to the key `__proto__` changes the prototype. */
|
|
1188
|
-
function setOwn$
|
|
1188
|
+
function setOwn$4(target, key, value) {
|
|
1189
1189
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
1190
1190
|
value,
|
|
1191
1191
|
enumerable: true,
|
|
@@ -1202,7 +1202,7 @@ function defined(value) {
|
|
|
1202
1202
|
if (Array.isArray(value)) return value.map(defined);
|
|
1203
1203
|
if (!isRecord(value)) return value;
|
|
1204
1204
|
const out = {};
|
|
1205
|
-
for (const key of Object.keys(value)) if (value[key] !== void 0) setOwn$
|
|
1205
|
+
for (const key of Object.keys(value)) if (value[key] !== void 0) setOwn$4(out, key, defined(value[key]));
|
|
1206
1206
|
return out;
|
|
1207
1207
|
}
|
|
1208
1208
|
/** Two values are the same when they are equal by `docEquals`. A function and a host value are equal only to themselves. */
|
|
@@ -1247,7 +1247,7 @@ function extendDomain(name, more) {
|
|
|
1247
1247
|
...Object.keys(steps).length === 0 ? {} : { steps }
|
|
1248
1248
|
};
|
|
1249
1249
|
const domains = {};
|
|
1250
|
-
setOwn$
|
|
1250
|
+
setOwn$4(domains, name, domain);
|
|
1251
1251
|
return {
|
|
1252
1252
|
domains,
|
|
1253
1253
|
replaces: [["domains", name]]
|
|
@@ -1297,10 +1297,10 @@ function mergeLeaf(m, table, key, later, path, part, replaces, equal, reason) {
|
|
|
1297
1297
|
if (later === void 0) return;
|
|
1298
1298
|
const earlier = own$2(table, key);
|
|
1299
1299
|
if (earlier === void 0) {
|
|
1300
|
-
setOwn$
|
|
1300
|
+
setOwn$4(table, key, later);
|
|
1301
1301
|
m.give(path, part);
|
|
1302
1302
|
} else if (replaces.has(keyOf(path))) {
|
|
1303
|
-
setOwn$
|
|
1303
|
+
setOwn$4(table, key, later);
|
|
1304
1304
|
m.replace(path, part);
|
|
1305
1305
|
} else if (!equal(earlier, later)) m.clash(path, part, reason);
|
|
1306
1306
|
}
|
|
@@ -1321,9 +1321,9 @@ function mergeEntries(m, into, from, path, part, replaces) {
|
|
|
1321
1321
|
const at = [...path, key];
|
|
1322
1322
|
const earlier = own$2(into, key);
|
|
1323
1323
|
if (earlier === void 0) {
|
|
1324
|
-
setOwn$
|
|
1324
|
+
setOwn$4(into, key, later);
|
|
1325
1325
|
m.give(at, part);
|
|
1326
|
-
} else setOwn$
|
|
1326
|
+
} else setOwn$4(into, key, mergeEntry(m, earlier, later, at, part, replaces));
|
|
1327
1327
|
}
|
|
1328
1328
|
}
|
|
1329
1329
|
/** This function merges two entries at one path. Two namespaces merge key by key, and their fallbacks merge too. */
|
|
@@ -1476,8 +1476,8 @@ function mergeCode(m, draft, part, index, replaces) {
|
|
|
1476
1476
|
const runnerPath = ["runners", name];
|
|
1477
1477
|
if (replaces.has(keyOf(evaluatorPath)) || replaces.has(keyOf(runnerPath))) {
|
|
1478
1478
|
if (evaluator === void 0) delete draft.evaluators[name];
|
|
1479
|
-
else setOwn$
|
|
1480
|
-
if (runner !== void 0) setOwn$
|
|
1479
|
+
else setOwn$4(draft.evaluators, name, evaluator);
|
|
1480
|
+
if (runner !== void 0) setOwn$4(draft.runners ??= {}, name, runner);
|
|
1481
1481
|
else if (draft.runners !== void 0) delete draft.runners[name];
|
|
1482
1482
|
m.replace(evaluatorPath, index);
|
|
1483
1483
|
m.replace(runnerPath, index);
|
|
@@ -1600,7 +1600,7 @@ function removeEntry(entries, path) {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
const out = { ...entries };
|
|
1602
1602
|
if (next === void 0) delete out[key];
|
|
1603
|
-
else setOwn$
|
|
1603
|
+
else setOwn$4(out, key, next);
|
|
1604
1604
|
return out;
|
|
1605
1605
|
}
|
|
1606
1606
|
/**
|
|
@@ -2412,7 +2412,7 @@ var Bad = class {
|
|
|
2412
2412
|
}
|
|
2413
2413
|
};
|
|
2414
2414
|
/** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
|
|
2415
|
-
function setOwn$
|
|
2415
|
+
function setOwn$3(target, key, value) {
|
|
2416
2416
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
2417
2417
|
value,
|
|
2418
2418
|
enumerable: true,
|
|
@@ -2584,7 +2584,7 @@ function splitParts(shape, value) {
|
|
|
2584
2584
|
const out = {};
|
|
2585
2585
|
for (const [key, child] of node.fields) {
|
|
2586
2586
|
const kept = strip(child);
|
|
2587
|
-
if (kept !== void 0) setOwn$
|
|
2587
|
+
if (kept !== void 0) setOwn$3(out, key, kept);
|
|
2588
2588
|
}
|
|
2589
2589
|
return out;
|
|
2590
2590
|
};
|
|
@@ -2593,7 +2593,7 @@ function splitParts(shape, value) {
|
|
|
2593
2593
|
if (node.t === "slot") return handles[node.index];
|
|
2594
2594
|
if (node.t === "list") return node.items.map((child) => fillWith(child, handles));
|
|
2595
2595
|
const out = {};
|
|
2596
|
-
for (const [key, child] of node.fields) setOwn$
|
|
2596
|
+
for (const [key, child] of node.fields) setOwn$3(out, key, fillWith(child, handles));
|
|
2597
2597
|
return out;
|
|
2598
2598
|
};
|
|
2599
2599
|
return {
|
|
@@ -3555,7 +3555,7 @@ function fillOf(split, doc) {
|
|
|
3555
3555
|
return (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
3556
3556
|
}
|
|
3557
3557
|
function interpret(fn, input, fill) {
|
|
3558
|
-
const { kind, typePath, doc, data, place } = input;
|
|
3558
|
+
const { kind, typePath, doc, data, place, machine } = input;
|
|
3559
3559
|
const address = input.address === true ? { address: true } : {};
|
|
3560
3560
|
const refill = input.fill;
|
|
3561
3561
|
const compiled = input.parts;
|
|
@@ -3576,6 +3576,7 @@ function interpret(fn, input, fill) {
|
|
|
3576
3576
|
parts,
|
|
3577
3577
|
context: ctx,
|
|
3578
3578
|
place,
|
|
3579
|
+
machine,
|
|
3579
3580
|
...address,
|
|
3580
3581
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
3581
3582
|
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
@@ -3584,7 +3585,7 @@ function interpret(fn, input, fill) {
|
|
|
3584
3585
|
};
|
|
3585
3586
|
}
|
|
3586
3587
|
function interpretRun(fn, input, fill) {
|
|
3587
|
-
const { kind, typePath, doc, data, place } = input;
|
|
3588
|
+
const { kind, typePath, doc, data, place, machine } = input;
|
|
3588
3589
|
const refill = input.fill;
|
|
3589
3590
|
const compiled = input.parts;
|
|
3590
3591
|
const hasParts = compiled.length > 0;
|
|
@@ -3604,6 +3605,7 @@ function interpretRun(fn, input, fill) {
|
|
|
3604
3605
|
parts,
|
|
3605
3606
|
context: ctx,
|
|
3606
3607
|
place,
|
|
3608
|
+
machine,
|
|
3607
3609
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
3608
3610
|
effect: (effectKind, payload) => rt.runEffect(ctx, effectKind, payload, place),
|
|
3609
3611
|
all: (list, options) => rt.all(list.map((part) => () => part.evaluate()), options),
|
|
@@ -5873,6 +5875,178 @@ function checkLatent(machine, declared, body, options = {}) {
|
|
|
5873
5875
|
return issues;
|
|
5874
5876
|
}
|
|
5875
5877
|
//#endregion
|
|
5878
|
+
//#region src/codec.ts
|
|
5879
|
+
/** This function gives the codec of a domain, or `undefined` when the domain has no codec. */
|
|
5880
|
+
function codecOf(domain) {
|
|
5881
|
+
const codec = domain.codec;
|
|
5882
|
+
if (typeof codec !== "object" || codec === null) return void 0;
|
|
5883
|
+
const { encode, decode } = codec;
|
|
5884
|
+
return typeof encode === "function" && typeof decode === "function" ? codec : void 0;
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* This function reads the value of an extension `ext.domain` (EXT.DOMAIN). The value is a record with only the text
|
|
5888
|
+
* `domain` and the JSON `data`. The codec of the named domain reads the data, and the result must be a member of the
|
|
5889
|
+
* domain.
|
|
5890
|
+
*
|
|
5891
|
+
* @param data - The value of the extension document.
|
|
5892
|
+
* @param domains - The domains of the machine.
|
|
5893
|
+
* @returns The member, `#NAME?` for a domain without a codec, or `#VALUE!` for data that the codec cannot read.
|
|
5894
|
+
*/
|
|
5895
|
+
function decodeDomain(data, domains) {
|
|
5896
|
+
if (!isRecord(data) || typeof data["domain"] !== "string" || !Object.hasOwn(data, "data") || Object.keys(data).length !== 2) return fail("#VALUE!", "The value of ext.domain is a record with only the text domain and the data.");
|
|
5897
|
+
const name = data["domain"];
|
|
5898
|
+
const domain = domainOf(name, domains);
|
|
5899
|
+
if (domain === void 0) return fail("#NAME?", `The machine has no domain ${name}.`);
|
|
5900
|
+
const codec = codecOf(domain);
|
|
5901
|
+
if (codec === void 0) return fail("#NAME?", `The domain ${name} has no codec.`);
|
|
5902
|
+
let value;
|
|
5903
|
+
try {
|
|
5904
|
+
value = codec.decode(data["data"]);
|
|
5905
|
+
} catch (error) {
|
|
5906
|
+
return fail("#VALUE!", `The codec of the domain ${name} cannot read the data: ${describe(error)}`, { cause: error });
|
|
5907
|
+
}
|
|
5908
|
+
if (!member(value, name, domains)) return fail("#VALUE!", `The codec of the domain ${name} gives a value that is not a member of the domain.`);
|
|
5909
|
+
return ok(value);
|
|
5910
|
+
}
|
|
5911
|
+
/**
|
|
5912
|
+
* This function gives the static type of an extension `ext.domain`: the name of its domain when that domain has a
|
|
5913
|
+
* codec. A run then gives a member of the domain or an error, because `decodeDomain` tests the membership.
|
|
5914
|
+
*
|
|
5915
|
+
* @param data - The value of the extension document.
|
|
5916
|
+
* @param domains - The domains of the machine.
|
|
5917
|
+
* @returns The name of the domain, or `unknown`.
|
|
5918
|
+
*/
|
|
5919
|
+
function domainTypeOf(data, domains) {
|
|
5920
|
+
if (!isRecord(data) || typeof data["domain"] !== "string") return "unknown";
|
|
5921
|
+
const domain = domainOf(data["domain"], domains);
|
|
5922
|
+
return domain !== void 0 && codecOf(domain) !== void 0 ? data["domain"] : "unknown";
|
|
5923
|
+
}
|
|
5924
|
+
/** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
|
|
5925
|
+
function setOwn$2(target, key, value) {
|
|
5926
|
+
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
5927
|
+
value,
|
|
5928
|
+
enumerable: true,
|
|
5929
|
+
writable: true,
|
|
5930
|
+
configurable: true
|
|
5931
|
+
});
|
|
5932
|
+
else target[key] = value;
|
|
5933
|
+
}
|
|
5934
|
+
/**
|
|
5935
|
+
* This function tells if JSON writes a value exactly. Such a value is a finite number other than `-0`, a text, a truth
|
|
5936
|
+
* value, `null`, or a container of such values.
|
|
5937
|
+
*/
|
|
5938
|
+
function isJson(value, encoder) {
|
|
5939
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
5940
|
+
if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0);
|
|
5941
|
+
const list = Array.isArray(value);
|
|
5942
|
+
if (!list && !isRecord(value)) return false;
|
|
5943
|
+
const container = value;
|
|
5944
|
+
const known = encoder.json.get(container);
|
|
5945
|
+
if (known !== void 0) return known;
|
|
5946
|
+
encoder.json.set(container, false);
|
|
5947
|
+
let all = true;
|
|
5948
|
+
if (list) {
|
|
5949
|
+
const items = value;
|
|
5950
|
+
for (let index = 0; index < items.length && all; index += 1) all = isJson(items[index], encoder);
|
|
5951
|
+
} else {
|
|
5952
|
+
const record = value;
|
|
5953
|
+
for (const key of Object.keys(record)) if (!isJson(record[key], encoder)) {
|
|
5954
|
+
all = false;
|
|
5955
|
+
break;
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
encoder.json.set(container, all);
|
|
5959
|
+
return all;
|
|
5960
|
+
}
|
|
5961
|
+
/** This function gives the text of a number that JSON cannot write, for `ext.number`. */
|
|
5962
|
+
function numberText(value) {
|
|
5963
|
+
if (Number.isNaN(value)) return "NaN";
|
|
5964
|
+
if (value === Number.POSITIVE_INFINITY) return "Infinity";
|
|
5965
|
+
if (value === Number.NEGATIVE_INFINITY) return "-Infinity";
|
|
5966
|
+
return "-0";
|
|
5967
|
+
}
|
|
5968
|
+
/** This function gives the term document of a value in one call of `encodeValue`. */
|
|
5969
|
+
function encodeTerm(value, encoder) {
|
|
5970
|
+
if (isJson(value, encoder)) return isEnvelope(value) ? {
|
|
5971
|
+
type: "lit",
|
|
5972
|
+
value
|
|
5973
|
+
} : value;
|
|
5974
|
+
if (value === void 0) return {
|
|
5975
|
+
type: ["ext", "undefined"],
|
|
5976
|
+
value: null
|
|
5977
|
+
};
|
|
5978
|
+
if (typeof value === "number") return {
|
|
5979
|
+
type: ["ext", "number"],
|
|
5980
|
+
value: numberText(value)
|
|
5981
|
+
};
|
|
5982
|
+
for (const [name, codec] of encoder.codecs) if (member(value, name, encoder.domains)) return {
|
|
5983
|
+
type: ["ext", "domain"],
|
|
5984
|
+
value: {
|
|
5985
|
+
domain: name,
|
|
5986
|
+
data: codec.encode(value)
|
|
5987
|
+
}
|
|
5988
|
+
};
|
|
5989
|
+
if (!Array.isArray(value) && !isRecord(value)) throw new TypeError(`encodeValue cannot write the value ${describe(value)}: no domain of the machine with a codec holds it.`);
|
|
5990
|
+
if (encoder.open.has(value)) throw new TypeError("encodeValue cannot write a value with a cycle.");
|
|
5991
|
+
encoder.open.add(value);
|
|
5992
|
+
let out;
|
|
5993
|
+
if (Array.isArray(value)) out = {
|
|
5994
|
+
type: "list",
|
|
5995
|
+
value: Array.from(value, (item) => encodeTerm(item, encoder))
|
|
5996
|
+
};
|
|
5997
|
+
else {
|
|
5998
|
+
const fields = {};
|
|
5999
|
+
const record = value;
|
|
6000
|
+
for (const key of Object.keys(record)) setOwn$2(fields, key, encodeTerm(record[key], encoder));
|
|
6001
|
+
out = {
|
|
6002
|
+
type: "record",
|
|
6003
|
+
value: fields
|
|
6004
|
+
};
|
|
6005
|
+
}
|
|
6006
|
+
encoder.open.delete(value);
|
|
6007
|
+
return out;
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* This function writes a host value as a term document of the formula kit (EXT.ENCODE, D40). Thus the same value has
|
|
6011
|
+
* the same document in each language (D12).
|
|
6012
|
+
*
|
|
6013
|
+
* - A value that JSON writes exactly stays as it is. A literal with the shape of an envelope goes into `lit`.
|
|
6014
|
+
* - `NaN`, an infinity and `-0` become `ext.number`, and `undefined` becomes `ext.undefined`.
|
|
6015
|
+
* - A member of a domain with a codec becomes `ext.domain`. The domains go in the order of their names.
|
|
6016
|
+
* - A list or a plain record with such a value becomes the construct `list` or `record`.
|
|
6017
|
+
*
|
|
6018
|
+
* @param machine - The machine, or only its domains.
|
|
6019
|
+
* @param value - The host value.
|
|
6020
|
+
* @returns The document. `decodeValue` gives the value again (EXT.ROUNDTRIP).
|
|
6021
|
+
* @throws {TypeError} The value has a cycle, or it is not JSON and no domain with a codec holds it.
|
|
6022
|
+
*/
|
|
6023
|
+
function encodeValue(machine, value) {
|
|
6024
|
+
const domains = machine.domains;
|
|
6025
|
+
const codecs = [];
|
|
6026
|
+
if (domains !== void 0) for (const name of Object.keys(domains).sort()) {
|
|
6027
|
+
const domain = domainOf(name, domains);
|
|
6028
|
+
const codec = domain === void 0 ? void 0 : codecOf(domain);
|
|
6029
|
+
if (codec !== void 0) codecs.push([name, codec]);
|
|
6030
|
+
}
|
|
6031
|
+
return encodeTerm(value, {
|
|
6032
|
+
domains,
|
|
6033
|
+
codecs,
|
|
6034
|
+
json: /* @__PURE__ */ new WeakMap(),
|
|
6035
|
+
open: /* @__PURE__ */ new Set()
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
6038
|
+
/**
|
|
6039
|
+
* This function reads a document of `encodeValue` again: it evaluates the document as a term, in an empty context.
|
|
6040
|
+
* A bare root value is a literal.
|
|
6041
|
+
*
|
|
6042
|
+
* @param machine - A machine of the formula kit, with the domains of the document.
|
|
6043
|
+
* @param doc - The document.
|
|
6044
|
+
* @returns The value, or the error of the evaluation.
|
|
6045
|
+
*/
|
|
6046
|
+
function decodeValue(machine, doc) {
|
|
6047
|
+
return prepare(machine, doc, { declaredType: "lit" }).evaluate(makeContext());
|
|
6048
|
+
}
|
|
6049
|
+
//#endregion
|
|
5876
6050
|
//#region src/formula.ts
|
|
5877
6051
|
/** The mark of a field that the profile leaves out of a record. */
|
|
5878
6052
|
const SKIPPED = /* @__PURE__ */ Symbol("skipped");
|
|
@@ -5893,7 +6067,10 @@ const CLOSURE$2 = /* @__PURE__ */ Symbol.for("coeval.closure");
|
|
|
5893
6067
|
function isClosure(value) {
|
|
5894
6068
|
return typeof value === "function" && value[CLOSURE$2] !== void 0;
|
|
5895
6069
|
}
|
|
5896
|
-
/**
|
|
6070
|
+
/**
|
|
6071
|
+
* The standard extensions: the values that JSON cannot write. `ext.domain` reads a member of a domain through the
|
|
6072
|
+
* codec of the domain (EXT.DOMAIN, D40).
|
|
6073
|
+
*/
|
|
5897
6074
|
const STD_EXT = {
|
|
5898
6075
|
undefined: () => ok(void 0),
|
|
5899
6076
|
number: (data) => {
|
|
@@ -5902,7 +6079,8 @@ const STD_EXT = {
|
|
|
5902
6079
|
if (data === "-Infinity") return ok(Number.NEGATIVE_INFINITY);
|
|
5903
6080
|
if (data === "-0") return ok(-0);
|
|
5904
6081
|
return fail("#VALUE!", "The value of ext.number is NaN, Infinity, -Infinity or -0.");
|
|
5905
|
-
}
|
|
6082
|
+
},
|
|
6083
|
+
domain: (data, _context, domains) => decodeDomain(data, domains)
|
|
5906
6084
|
};
|
|
5907
6085
|
/** The truth of a value in the rules of JavaScript: `0`, `""`, `null`, `undefined` and `NaN` are false. */
|
|
5908
6086
|
function jsTruthy(value) {
|
|
@@ -6369,8 +6547,9 @@ function formulaTypes(profile) {
|
|
|
6369
6547
|
},
|
|
6370
6548
|
"formula.ext": { synth: (input) => {
|
|
6371
6549
|
const name = nameOf(input);
|
|
6372
|
-
|
|
6373
|
-
|
|
6550
|
+
if (profile.ext !== void 0 && Object.hasOwn(profile.ext, name)) return "unknown";
|
|
6551
|
+
if (name === "number") return "number";
|
|
6552
|
+
return name === "domain" ? domainTypeOf(input.doc.value, input.machine.domains) : "unknown";
|
|
6374
6553
|
} }
|
|
6375
6554
|
};
|
|
6376
6555
|
}
|
|
@@ -6688,7 +6867,7 @@ function formulaCode(profile) {
|
|
|
6688
6867
|
}
|
|
6689
6868
|
};
|
|
6690
6869
|
}),
|
|
6691
|
-
"formula.ext": compiled(({ typePath, doc }) => {
|
|
6870
|
+
"formula.ext": compiled(({ typePath, doc, machine }) => {
|
|
6692
6871
|
const name = typePath.slice(1).join(".");
|
|
6693
6872
|
const handler = (profile.ext !== void 0 && Object.hasOwn(profile.ext, name) ? profile.ext[name] : void 0) ?? (Object.hasOwn(STD_EXT, name) ? STD_EXT[name] : void 0);
|
|
6694
6873
|
if (handler === void 0) {
|
|
@@ -6697,7 +6876,7 @@ function formulaCode(profile) {
|
|
|
6697
6876
|
}
|
|
6698
6877
|
return (ctx) => {
|
|
6699
6878
|
try {
|
|
6700
|
-
return handler(doc.value, ctx);
|
|
6879
|
+
return handler(doc.value, ctx, machine.domains);
|
|
6701
6880
|
} catch (error) {
|
|
6702
6881
|
return thrown(error, `the extension ${name}`);
|
|
6703
6882
|
}
|
|
@@ -6798,6 +6977,835 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
6798
6977
|
}, more);
|
|
6799
6978
|
}
|
|
6800
6979
|
//#endregion
|
|
6980
|
+
//#region src/syntax.ts
|
|
6981
|
+
/**
|
|
6982
|
+
* The standard syntax of the formula kit. It gives the site and the playground a text form. The operators are
|
|
6983
|
+
* `or`, `and`, `not`, the comparisons, `+ - * / %`, a minus sign and `^`, from the loosest to the tightest. A call
|
|
6984
|
+
* `name(args)` applies the operation `ops.name`. `if(test, then, else)` and `list(items)` make the constructs.
|
|
6985
|
+
*/
|
|
6986
|
+
const STD_SYNTAX = /* @__PURE__ */ Object.freeze({
|
|
6987
|
+
operators: [
|
|
6988
|
+
{
|
|
6989
|
+
symbol: "or",
|
|
6990
|
+
fixity: "infix",
|
|
6991
|
+
prec: 1,
|
|
6992
|
+
assoc: "flat",
|
|
6993
|
+
kind: "or"
|
|
6994
|
+
},
|
|
6995
|
+
{
|
|
6996
|
+
symbol: "and",
|
|
6997
|
+
fixity: "infix",
|
|
6998
|
+
prec: 2,
|
|
6999
|
+
assoc: "flat",
|
|
7000
|
+
kind: "and"
|
|
7001
|
+
},
|
|
7002
|
+
{
|
|
7003
|
+
symbol: "not",
|
|
7004
|
+
fixity: "prefix",
|
|
7005
|
+
prec: 3,
|
|
7006
|
+
kind: "ops.not"
|
|
7007
|
+
},
|
|
7008
|
+
{
|
|
7009
|
+
symbol: "==",
|
|
7010
|
+
fixity: "infix",
|
|
7011
|
+
prec: 4,
|
|
7012
|
+
assoc: "none",
|
|
7013
|
+
kind: "ops.eq"
|
|
7014
|
+
},
|
|
7015
|
+
{
|
|
7016
|
+
symbol: "!=",
|
|
7017
|
+
fixity: "infix",
|
|
7018
|
+
prec: 4,
|
|
7019
|
+
assoc: "none",
|
|
7020
|
+
kind: "ops.ne"
|
|
7021
|
+
},
|
|
7022
|
+
{
|
|
7023
|
+
symbol: "<",
|
|
7024
|
+
fixity: "infix",
|
|
7025
|
+
prec: 4,
|
|
7026
|
+
assoc: "none",
|
|
7027
|
+
kind: "ops.lt"
|
|
7028
|
+
},
|
|
7029
|
+
{
|
|
7030
|
+
symbol: "<=",
|
|
7031
|
+
fixity: "infix",
|
|
7032
|
+
prec: 4,
|
|
7033
|
+
assoc: "none",
|
|
7034
|
+
kind: "ops.le"
|
|
7035
|
+
},
|
|
7036
|
+
{
|
|
7037
|
+
symbol: ">",
|
|
7038
|
+
fixity: "infix",
|
|
7039
|
+
prec: 4,
|
|
7040
|
+
assoc: "none",
|
|
7041
|
+
kind: "ops.gt"
|
|
7042
|
+
},
|
|
7043
|
+
{
|
|
7044
|
+
symbol: ">=",
|
|
7045
|
+
fixity: "infix",
|
|
7046
|
+
prec: 4,
|
|
7047
|
+
assoc: "none",
|
|
7048
|
+
kind: "ops.ge"
|
|
7049
|
+
},
|
|
7050
|
+
{
|
|
7051
|
+
symbol: "+",
|
|
7052
|
+
fixity: "infix",
|
|
7053
|
+
prec: 5,
|
|
7054
|
+
kind: "ops.add"
|
|
7055
|
+
},
|
|
7056
|
+
{
|
|
7057
|
+
symbol: "-",
|
|
7058
|
+
fixity: "infix",
|
|
7059
|
+
prec: 5,
|
|
7060
|
+
kind: "ops.sub"
|
|
7061
|
+
},
|
|
7062
|
+
{
|
|
7063
|
+
symbol: "*",
|
|
7064
|
+
fixity: "infix",
|
|
7065
|
+
prec: 6,
|
|
7066
|
+
kind: "ops.mul"
|
|
7067
|
+
},
|
|
7068
|
+
{
|
|
7069
|
+
symbol: "/",
|
|
7070
|
+
fixity: "infix",
|
|
7071
|
+
prec: 6,
|
|
7072
|
+
kind: "ops.div"
|
|
7073
|
+
},
|
|
7074
|
+
{
|
|
7075
|
+
symbol: "%",
|
|
7076
|
+
fixity: "infix",
|
|
7077
|
+
prec: 6,
|
|
7078
|
+
kind: "ops.mod"
|
|
7079
|
+
},
|
|
7080
|
+
{
|
|
7081
|
+
symbol: "-",
|
|
7082
|
+
fixity: "prefix",
|
|
7083
|
+
prec: 7,
|
|
7084
|
+
kind: "ops.neg"
|
|
7085
|
+
},
|
|
7086
|
+
{
|
|
7087
|
+
symbol: "^",
|
|
7088
|
+
fixity: "infix",
|
|
7089
|
+
prec: 8,
|
|
7090
|
+
assoc: "right",
|
|
7091
|
+
kind: "ops.pow"
|
|
7092
|
+
}
|
|
7093
|
+
],
|
|
7094
|
+
literals: { words: {
|
|
7095
|
+
true: true,
|
|
7096
|
+
false: false,
|
|
7097
|
+
null: null,
|
|
7098
|
+
undefined: void 0,
|
|
7099
|
+
NaN: NaN,
|
|
7100
|
+
Infinity: Infinity
|
|
7101
|
+
} },
|
|
7102
|
+
calls: { forms: [{
|
|
7103
|
+
name: "if",
|
|
7104
|
+
kind: "if",
|
|
7105
|
+
fields: [
|
|
7106
|
+
"test",
|
|
7107
|
+
"then",
|
|
7108
|
+
"else"
|
|
7109
|
+
],
|
|
7110
|
+
min: 2
|
|
7111
|
+
}, {
|
|
7112
|
+
name: "list",
|
|
7113
|
+
kind: "list"
|
|
7114
|
+
}] }
|
|
7115
|
+
});
|
|
7116
|
+
const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7117
|
+
const NAME_AT = /[A-Za-z_][A-Za-z0-9_]*/y;
|
|
7118
|
+
const DIGITS_AT = /[0-9]+/y;
|
|
7119
|
+
const NUMBER_AT = /(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
7120
|
+
const SYMBOL = /^[^\sA-Za-z0-9_"'`()$.]+$/;
|
|
7121
|
+
const DEFAULT_WORDS = {
|
|
7122
|
+
true: true,
|
|
7123
|
+
false: false,
|
|
7124
|
+
null: null
|
|
7125
|
+
};
|
|
7126
|
+
const DEFAULT_ROOTS = [
|
|
7127
|
+
"self",
|
|
7128
|
+
"world",
|
|
7129
|
+
"event",
|
|
7130
|
+
"caps"
|
|
7131
|
+
];
|
|
7132
|
+
const compiledProfiles = /* @__PURE__ */ new WeakMap();
|
|
7133
|
+
function typeOfKind(kind) {
|
|
7134
|
+
const keys = kind.split(".");
|
|
7135
|
+
if (kind === "" || keys.some((key) => key === "")) throw new TypeError(`The kind "${kind}" of a syntax profile has an empty key.`);
|
|
7136
|
+
return {
|
|
7137
|
+
type: keys.length === 1 ? keys[0] : keys,
|
|
7138
|
+
key: JSON.stringify(keys)
|
|
7139
|
+
};
|
|
7140
|
+
}
|
|
7141
|
+
function addTo(map, key, value) {
|
|
7142
|
+
const list = map.get(key);
|
|
7143
|
+
if (list === void 0) map.set(key, [value]);
|
|
7144
|
+
else list.push(value);
|
|
7145
|
+
}
|
|
7146
|
+
function compileOps(profile, separator) {
|
|
7147
|
+
const prefix = /* @__PURE__ */ new Map();
|
|
7148
|
+
const binary = /* @__PURE__ */ new Map();
|
|
7149
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
7150
|
+
const assocAt = /* @__PURE__ */ new Map();
|
|
7151
|
+
const opWords = /* @__PURE__ */ new Set();
|
|
7152
|
+
for (const given of profile.operators ?? []) {
|
|
7153
|
+
const word = NAME.test(given.symbol);
|
|
7154
|
+
if (!word && (!SYMBOL.test(given.symbol) || given.symbol.includes(separator))) throw new TypeError(`The operator "${given.symbol}" is not a plain name or a group of symbols.`);
|
|
7155
|
+
if (!Number.isFinite(given.prec)) throw new TypeError(`The operator "${given.symbol}" has no finite precedence.`);
|
|
7156
|
+
const { type, key } = typeOfKind(given.kind);
|
|
7157
|
+
const op = {
|
|
7158
|
+
symbol: given.symbol,
|
|
7159
|
+
fixity: given.fixity,
|
|
7160
|
+
prec: given.prec,
|
|
7161
|
+
assoc: given.assoc ?? "left",
|
|
7162
|
+
type,
|
|
7163
|
+
kindKey: key,
|
|
7164
|
+
word
|
|
7165
|
+
};
|
|
7166
|
+
const table = op.fixity === "prefix" ? prefix : binary;
|
|
7167
|
+
if (table.has(op.symbol)) throw new TypeError(`The operator "${op.symbol}" has two entries of the same place.`);
|
|
7168
|
+
table.set(op.symbol, op);
|
|
7169
|
+
if (op.fixity === "infix") {
|
|
7170
|
+
const assoc = assocAt.get(op.prec);
|
|
7171
|
+
if (assoc !== void 0 && assoc !== op.assoc) throw new TypeError(`The infix operators of the precedence ${String(op.prec)} have different groupings.`);
|
|
7172
|
+
assocAt.set(op.prec, op.assoc);
|
|
7173
|
+
}
|
|
7174
|
+
if (word) opWords.add(op.symbol);
|
|
7175
|
+
addTo(byKind, key, op);
|
|
7176
|
+
}
|
|
7177
|
+
const longestFirst = (table) => [...table.keys()].filter((symbol) => !NAME.test(symbol)).sort((a, b) => b.length - a.length);
|
|
7178
|
+
return {
|
|
7179
|
+
prefix,
|
|
7180
|
+
binary,
|
|
7181
|
+
prefixSymbols: longestFirst(prefix),
|
|
7182
|
+
binarySymbols: longestFirst(binary),
|
|
7183
|
+
byKind,
|
|
7184
|
+
opWords
|
|
7185
|
+
};
|
|
7186
|
+
}
|
|
7187
|
+
function compileProfile(profile) {
|
|
7188
|
+
const cached = compiledProfiles.get(profile);
|
|
7189
|
+
if (cached !== void 0) return cached;
|
|
7190
|
+
const calls = profile.calls ?? {};
|
|
7191
|
+
const separator = calls.separator ?? ",";
|
|
7192
|
+
const ops = compileOps(profile, separator);
|
|
7193
|
+
const literals = profile.literals ?? {};
|
|
7194
|
+
const strings = literals.strings === false ? void 0 : literals.strings ?? {};
|
|
7195
|
+
const words = new Map(Object.entries(literals.words ?? DEFAULT_WORDS));
|
|
7196
|
+
const refs = profile.refs ?? {};
|
|
7197
|
+
const roots = new Set(refs.roots ?? DEFAULT_ROOTS);
|
|
7198
|
+
const forms = /* @__PURE__ */ new Map();
|
|
7199
|
+
const formsByKind = /* @__PURE__ */ new Map();
|
|
7200
|
+
for (const given of calls.forms ?? []) {
|
|
7201
|
+
if (!NAME.test(given.name)) throw new TypeError(`The call form "${given.name}" is not a plain name.`);
|
|
7202
|
+
const { type, key } = typeOfKind(given.kind);
|
|
7203
|
+
const form = {
|
|
7204
|
+
name: given.name,
|
|
7205
|
+
type,
|
|
7206
|
+
fields: given.fields,
|
|
7207
|
+
min: given.min ?? given.fields?.length ?? 0
|
|
7208
|
+
};
|
|
7209
|
+
forms.set(form.name, form);
|
|
7210
|
+
addTo(formsByKind, key, form);
|
|
7211
|
+
}
|
|
7212
|
+
const compiled = {
|
|
7213
|
+
...ops,
|
|
7214
|
+
numbers: literals.numbers ?? true,
|
|
7215
|
+
signed: literals.signed ?? true,
|
|
7216
|
+
quote: strings === void 0 ? void 0 : strings.quote ?? "\"",
|
|
7217
|
+
doubled: strings !== void 0 && strings.escape === "double",
|
|
7218
|
+
words,
|
|
7219
|
+
bare: refs.bare ?? "names",
|
|
7220
|
+
roots,
|
|
7221
|
+
fields: refs.fields ?? true,
|
|
7222
|
+
patterns: (refs.patterns ?? []).map((p) => {
|
|
7223
|
+
const flags = p.pattern.flags.replace(/[gy]/g, "");
|
|
7224
|
+
return {
|
|
7225
|
+
sticky: new RegExp(p.pattern.source, `${flags}y`),
|
|
7226
|
+
whole: new RegExp(`^(?:${p.pattern.source})$`, flags),
|
|
7227
|
+
read: p.read,
|
|
7228
|
+
write: p.write
|
|
7229
|
+
};
|
|
7230
|
+
}),
|
|
7231
|
+
ops: calls.ops === void 0 || calls.ops === true ? true : new Set(calls.ops === false ? [] : calls.ops),
|
|
7232
|
+
forms,
|
|
7233
|
+
formsByKind,
|
|
7234
|
+
separator,
|
|
7235
|
+
reserved: /* @__PURE__ */ new Set([
|
|
7236
|
+
...words.keys(),
|
|
7237
|
+
...ops.opWords,
|
|
7238
|
+
...roots
|
|
7239
|
+
]),
|
|
7240
|
+
maxDepth: profile.maxDepth ?? 256
|
|
7241
|
+
};
|
|
7242
|
+
compiledProfiles.set(profile, compiled);
|
|
7243
|
+
return compiled;
|
|
7244
|
+
}
|
|
7245
|
+
/** The stop of a parse. The parser throws it, and `parse` gives its issue. */
|
|
7246
|
+
var Stop = class {
|
|
7247
|
+
issue;
|
|
7248
|
+
constructor(issue) {
|
|
7249
|
+
this.issue = issue;
|
|
7250
|
+
}
|
|
7251
|
+
};
|
|
7252
|
+
function literalDoc(value) {
|
|
7253
|
+
return encodeValue({}, value);
|
|
7254
|
+
}
|
|
7255
|
+
function opDoc(op, value) {
|
|
7256
|
+
return {
|
|
7257
|
+
type: op.type,
|
|
7258
|
+
value
|
|
7259
|
+
};
|
|
7260
|
+
}
|
|
7261
|
+
const ESCAPES = {
|
|
7262
|
+
"\"": "\"",
|
|
7263
|
+
"'": "'",
|
|
7264
|
+
"\\": "\\",
|
|
7265
|
+
"/": "/",
|
|
7266
|
+
b: "\b",
|
|
7267
|
+
f: "\f",
|
|
7268
|
+
n: "\n",
|
|
7269
|
+
r: "\r",
|
|
7270
|
+
t: " "
|
|
7271
|
+
};
|
|
7272
|
+
var Parser = class {
|
|
7273
|
+
syn;
|
|
7274
|
+
text;
|
|
7275
|
+
pos = 0;
|
|
7276
|
+
depth = 0;
|
|
7277
|
+
constructor(syn, text) {
|
|
7278
|
+
this.syn = syn;
|
|
7279
|
+
this.text = text;
|
|
7280
|
+
}
|
|
7281
|
+
parse() {
|
|
7282
|
+
this.skip();
|
|
7283
|
+
if (this.pos >= this.text.length) this.stop("The text is empty.", 0, 0);
|
|
7284
|
+
const doc = this.expr(0);
|
|
7285
|
+
this.skip();
|
|
7286
|
+
if (this.pos < this.text.length) this.stop("The text continues after the expression.", this.pos, this.tokenLength());
|
|
7287
|
+
return doc;
|
|
7288
|
+
}
|
|
7289
|
+
position() {
|
|
7290
|
+
return this.pos;
|
|
7291
|
+
}
|
|
7292
|
+
stop(message, offset, length) {
|
|
7293
|
+
throw new Stop({
|
|
7294
|
+
offset,
|
|
7295
|
+
length,
|
|
7296
|
+
message
|
|
7297
|
+
});
|
|
7298
|
+
}
|
|
7299
|
+
skip() {
|
|
7300
|
+
while (this.pos < this.text.length && /\s/.test(this.text[this.pos])) this.pos += 1;
|
|
7301
|
+
}
|
|
7302
|
+
at(pattern, pos = this.pos) {
|
|
7303
|
+
pattern.lastIndex = pos;
|
|
7304
|
+
return pattern.exec(this.text)?.[0];
|
|
7305
|
+
}
|
|
7306
|
+
tokenLength() {
|
|
7307
|
+
return (this.at(NAME_AT) ?? this.at(DIGITS_AT) ?? "x").length;
|
|
7308
|
+
}
|
|
7309
|
+
/** This method reads an expression by precedence climbing. It stops at an operator below `min`. */
|
|
7310
|
+
expr(min) {
|
|
7311
|
+
const start = this.pos;
|
|
7312
|
+
this.depth += 1;
|
|
7313
|
+
if (this.depth > this.syn.maxDepth) this.stop(`The text is deeper than ${String(this.syn.maxDepth)} levels.`, start, 0);
|
|
7314
|
+
let left = this.operand();
|
|
7315
|
+
for (;;) {
|
|
7316
|
+
this.skip();
|
|
7317
|
+
const found = this.operatorAt();
|
|
7318
|
+
if (found === void 0 || found.op.prec < min) break;
|
|
7319
|
+
const op = found.op;
|
|
7320
|
+
this.pos += found.length;
|
|
7321
|
+
if (op.fixity === "postfix") {
|
|
7322
|
+
left = opDoc(op, [left]);
|
|
7323
|
+
continue;
|
|
7324
|
+
}
|
|
7325
|
+
if (op.assoc === "flat") {
|
|
7326
|
+
const terms = [left, this.expr(op.prec + 1)];
|
|
7327
|
+
for (;;) {
|
|
7328
|
+
this.skip();
|
|
7329
|
+
const next = this.operatorAt();
|
|
7330
|
+
if (next === void 0 || next.op.fixity !== "infix" || next.op.kindKey !== op.kindKey) break;
|
|
7331
|
+
this.pos += next.length;
|
|
7332
|
+
terms.push(this.expr(op.prec + 1));
|
|
7333
|
+
}
|
|
7334
|
+
left = opDoc(op, terms);
|
|
7335
|
+
continue;
|
|
7336
|
+
}
|
|
7337
|
+
left = opDoc(op, [left, this.expr(op.assoc === "right" ? op.prec : op.prec + 1)]);
|
|
7338
|
+
if (op.assoc === "none") {
|
|
7339
|
+
this.skip();
|
|
7340
|
+
const next = this.operatorAt();
|
|
7341
|
+
if (next !== void 0 && next.op.fixity === "infix" && next.op.prec === op.prec) this.stop(`The operator ${next.op.symbol} does not chain after ${op.symbol}. Add parentheses.`, this.pos, next.length);
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
this.depth -= 1;
|
|
7345
|
+
return left;
|
|
7346
|
+
}
|
|
7347
|
+
/** This method finds an infix or a postfix operator at the position. */
|
|
7348
|
+
operatorAt() {
|
|
7349
|
+
const name = this.at(NAME_AT);
|
|
7350
|
+
if (name !== void 0) {
|
|
7351
|
+
const op = this.syn.binary.get(name);
|
|
7352
|
+
return op !== void 0 && op.word ? {
|
|
7353
|
+
op,
|
|
7354
|
+
length: name.length
|
|
7355
|
+
} : void 0;
|
|
7356
|
+
}
|
|
7357
|
+
for (const symbol of this.syn.binarySymbols) if (this.text.startsWith(symbol, this.pos)) return {
|
|
7358
|
+
op: this.syn.binary.get(symbol),
|
|
7359
|
+
length: symbol.length
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
operand() {
|
|
7363
|
+
this.skip();
|
|
7364
|
+
const start = this.pos;
|
|
7365
|
+
const c = this.text[start];
|
|
7366
|
+
if (c === void 0) return this.stop("A term is missing at the end of the text.", start, 0);
|
|
7367
|
+
for (const pattern of this.syn.patterns) {
|
|
7368
|
+
const match = this.at(pattern.sticky);
|
|
7369
|
+
if (match !== void 0 && match !== "" && !/[A-Za-z0-9_$(]/.test(this.text[start + match.length] ?? "")) {
|
|
7370
|
+
this.pos += match.length;
|
|
7371
|
+
return {
|
|
7372
|
+
type: "ref",
|
|
7373
|
+
value: pattern.read(match)
|
|
7374
|
+
};
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
if (/[0-9]/.test(c)) return this.number(false);
|
|
7378
|
+
if (c === "-" && this.syn.signed && /[0-9]/.test(this.text[start + 1] ?? "")) return this.number(true);
|
|
7379
|
+
if (c === this.syn.quote) return this.string();
|
|
7380
|
+
if (c === "(") return this.group();
|
|
7381
|
+
if (c === "`" || /[A-Za-z_]/.test(c)) return this.named();
|
|
7382
|
+
for (const symbol of this.syn.prefixSymbols) if (this.text.startsWith(symbol, start)) {
|
|
7383
|
+
this.pos += symbol.length;
|
|
7384
|
+
const op = this.syn.prefix.get(symbol);
|
|
7385
|
+
return opDoc(op, [this.expr(op.prec + 1)]);
|
|
7386
|
+
}
|
|
7387
|
+
if (c === ")" || this.text.startsWith(this.syn.separator, start)) return this.stop("A term is missing.", start, 0);
|
|
7388
|
+
return this.stop(`The text "${c}" cannot start a term.`, start, 1);
|
|
7389
|
+
}
|
|
7390
|
+
number(negative) {
|
|
7391
|
+
const start = this.pos;
|
|
7392
|
+
if (!this.syn.numbers) this.stop("This syntax has no numbers.", start, this.tokenLength());
|
|
7393
|
+
if (negative) this.pos += 1;
|
|
7394
|
+
const digits = this.at(NUMBER_AT);
|
|
7395
|
+
this.pos += digits.length;
|
|
7396
|
+
const value = Number(digits);
|
|
7397
|
+
return literalDoc(negative ? -value : value);
|
|
7398
|
+
}
|
|
7399
|
+
string() {
|
|
7400
|
+
const start = this.pos;
|
|
7401
|
+
const quote = this.syn.quote;
|
|
7402
|
+
let out = "";
|
|
7403
|
+
this.pos += 1;
|
|
7404
|
+
for (;;) {
|
|
7405
|
+
const c = this.text[this.pos];
|
|
7406
|
+
if (c === void 0) return this.stop("A closing quote is missing.", start, this.text.length - start);
|
|
7407
|
+
if (c === quote) {
|
|
7408
|
+
if (this.syn.doubled && this.text[this.pos + 1] === quote) {
|
|
7409
|
+
out += quote;
|
|
7410
|
+
this.pos += 2;
|
|
7411
|
+
continue;
|
|
7412
|
+
}
|
|
7413
|
+
this.pos += 1;
|
|
7414
|
+
return literalDoc(out);
|
|
7415
|
+
}
|
|
7416
|
+
if (c === "\\" && !this.syn.doubled) {
|
|
7417
|
+
out += this.escape();
|
|
7418
|
+
continue;
|
|
7419
|
+
}
|
|
7420
|
+
out += c;
|
|
7421
|
+
this.pos += 1;
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
escape() {
|
|
7425
|
+
const start = this.pos;
|
|
7426
|
+
const c = this.text[start + 1] ?? "";
|
|
7427
|
+
if (c === "u") {
|
|
7428
|
+
const hex = this.text.slice(start + 2, start + 6);
|
|
7429
|
+
if (!/^[0-9A-Fa-f]{4}$/.test(hex)) return this.stop("The escape \\u needs four hex digits.", start, 2);
|
|
7430
|
+
this.pos += 6;
|
|
7431
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
7432
|
+
}
|
|
7433
|
+
const out = Object.hasOwn(ESCAPES, c) ? ESCAPES[c] : void 0;
|
|
7434
|
+
if (out === void 0) return this.stop(`The escape \\${c} is not valid.`, start, 2);
|
|
7435
|
+
this.pos += 2;
|
|
7436
|
+
return out;
|
|
7437
|
+
}
|
|
7438
|
+
group() {
|
|
7439
|
+
const open = this.pos;
|
|
7440
|
+
this.pos += 1;
|
|
7441
|
+
const inner = this.expr(0);
|
|
7442
|
+
this.skip();
|
|
7443
|
+
if (this.text[this.pos] !== ")") {
|
|
7444
|
+
if (this.pos >= this.text.length) this.stop("A closing parenthesis is missing.", open, 1);
|
|
7445
|
+
this.stop("A closing parenthesis is missing.", this.pos, this.tokenLength());
|
|
7446
|
+
}
|
|
7447
|
+
this.pos += 1;
|
|
7448
|
+
return inner;
|
|
7449
|
+
}
|
|
7450
|
+
/** This method reads a name in backticks. Two backticks in the name are one backtick. */
|
|
7451
|
+
quoted() {
|
|
7452
|
+
const start = this.pos;
|
|
7453
|
+
let out = "";
|
|
7454
|
+
this.pos += 1;
|
|
7455
|
+
for (;;) {
|
|
7456
|
+
const c = this.text[this.pos];
|
|
7457
|
+
if (c === void 0) return this.stop("A closing backtick is missing.", start, this.text.length - start);
|
|
7458
|
+
if (c === "`") {
|
|
7459
|
+
if (this.text[this.pos + 1] !== "`") {
|
|
7460
|
+
this.pos += 1;
|
|
7461
|
+
return out;
|
|
7462
|
+
}
|
|
7463
|
+
this.pos += 1;
|
|
7464
|
+
}
|
|
7465
|
+
out += c;
|
|
7466
|
+
this.pos += 1;
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
/** This method reads a name: a word operator, a call, a literal word or a reference. */
|
|
7470
|
+
named() {
|
|
7471
|
+
const start = this.pos;
|
|
7472
|
+
const plain = this.text[start] !== "`";
|
|
7473
|
+
const first = plain ? this.at(NAME_AT) : this.quoted();
|
|
7474
|
+
if (plain) {
|
|
7475
|
+
this.pos += first.length;
|
|
7476
|
+
const prefix = this.syn.prefix.get(first);
|
|
7477
|
+
if (prefix !== void 0 && prefix.word) return opDoc(prefix, [this.expr(prefix.prec + 1)]);
|
|
7478
|
+
if (this.syn.binary.get(first)?.word === true) this.stop(`A term is missing before ${first}.`, start, first.length);
|
|
7479
|
+
}
|
|
7480
|
+
const path = [first];
|
|
7481
|
+
let allPlain = plain;
|
|
7482
|
+
for (;;) {
|
|
7483
|
+
const back = this.pos;
|
|
7484
|
+
this.skip();
|
|
7485
|
+
if (!this.syn.fields || this.text[this.pos] !== ".") {
|
|
7486
|
+
this.pos = back;
|
|
7487
|
+
break;
|
|
7488
|
+
}
|
|
7489
|
+
this.pos += 1;
|
|
7490
|
+
this.skip();
|
|
7491
|
+
if (this.text[this.pos] === "`") {
|
|
7492
|
+
path.push(this.quoted());
|
|
7493
|
+
allPlain = false;
|
|
7494
|
+
continue;
|
|
7495
|
+
}
|
|
7496
|
+
const name = this.at(NAME_AT);
|
|
7497
|
+
const digits = name === void 0 ? this.at(DIGITS_AT) : void 0;
|
|
7498
|
+
if (name === void 0 && digits === void 0) this.stop("A name is missing after the dot.", this.pos, 0);
|
|
7499
|
+
this.pos += (name ?? digits).length;
|
|
7500
|
+
path.push(name ?? Number(digits));
|
|
7501
|
+
if (name === void 0) allPlain = false;
|
|
7502
|
+
}
|
|
7503
|
+
const back = this.pos;
|
|
7504
|
+
this.skip();
|
|
7505
|
+
if (this.text[this.pos] === "(") {
|
|
7506
|
+
if (!allPlain) this.stop("The name of a call is a plain name.", start, back - start);
|
|
7507
|
+
return this.call(path.join("."), start, back - start);
|
|
7508
|
+
}
|
|
7509
|
+
this.pos = back;
|
|
7510
|
+
if (plain && path.length === 1 && this.syn.words.has(first)) return literalDoc(this.syn.words.get(first));
|
|
7511
|
+
return {
|
|
7512
|
+
type: "ref",
|
|
7513
|
+
value: plain && this.syn.roots.has(first) ? path : [this.syn.bare, ...path]
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
call(name, start, nameLength) {
|
|
7517
|
+
const open = this.pos;
|
|
7518
|
+
const args = [];
|
|
7519
|
+
this.pos += 1;
|
|
7520
|
+
this.skip();
|
|
7521
|
+
if (this.text[this.pos] === ")") this.pos += 1;
|
|
7522
|
+
else for (;;) {
|
|
7523
|
+
args.push(this.expr(0));
|
|
7524
|
+
this.skip();
|
|
7525
|
+
if (this.text.startsWith(this.syn.separator, this.pos)) {
|
|
7526
|
+
this.pos += this.syn.separator.length;
|
|
7527
|
+
continue;
|
|
7528
|
+
}
|
|
7529
|
+
if (this.text[this.pos] === ")") {
|
|
7530
|
+
this.pos += 1;
|
|
7531
|
+
break;
|
|
7532
|
+
}
|
|
7533
|
+
if (this.pos >= this.text.length) this.stop("A closing parenthesis is missing.", open, 1);
|
|
7534
|
+
this.stop(`A "${this.syn.separator}" or a closing parenthesis is missing.`, this.pos, this.tokenLength());
|
|
7535
|
+
}
|
|
7536
|
+
const form = this.syn.forms.get(name);
|
|
7537
|
+
if (form !== void 0) {
|
|
7538
|
+
const most = form.fields?.length ?? Number.POSITIVE_INFINITY;
|
|
7539
|
+
if (args.length < form.min || args.length > most) {
|
|
7540
|
+
const range = most === form.min ? String(most) : most === Number.POSITIVE_INFINITY ? `${String(form.min)} or more` : `${String(form.min)} to ${String(most)}`;
|
|
7541
|
+
this.stop(`The form ${name} takes ${range} arguments.`, start, this.pos - start);
|
|
7542
|
+
}
|
|
7543
|
+
if (form.fields === void 0) return opDoc(form, args);
|
|
7544
|
+
const value = {};
|
|
7545
|
+
args.forEach((arg, index) => {
|
|
7546
|
+
value[form.fields?.[index]] = arg;
|
|
7547
|
+
});
|
|
7548
|
+
return opDoc(form, value);
|
|
7549
|
+
}
|
|
7550
|
+
if (this.syn.ops !== true && !this.syn.ops.has(name)) this.stop(`This syntax has no function ${name}.`, start, nameLength);
|
|
7551
|
+
return {
|
|
7552
|
+
type: ["ops", ...name.split(".")],
|
|
7553
|
+
value: args
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
};
|
|
7557
|
+
/**
|
|
7558
|
+
* This function reads a text into a document with the rules of a profile (SYNTAX.PARSE). It does not throw for a
|
|
7559
|
+
* bad text. The issue gives the offset, the length and a message (SYNTAX.SPAN).
|
|
7560
|
+
*
|
|
7561
|
+
* @param profile - The syntax profile.
|
|
7562
|
+
* @param text - The text.
|
|
7563
|
+
* @returns The document, or the issues of the text.
|
|
7564
|
+
* @throws {TypeError} The profile is not valid, for example an operator has two entries of the same place.
|
|
7565
|
+
*/
|
|
7566
|
+
function parse(profile, text) {
|
|
7567
|
+
const parser = new Parser(compileProfile(profile), text);
|
|
7568
|
+
try {
|
|
7569
|
+
return {
|
|
7570
|
+
ok: true,
|
|
7571
|
+
doc: parser.parse()
|
|
7572
|
+
};
|
|
7573
|
+
} catch (error) {
|
|
7574
|
+
if (error instanceof Stop) return {
|
|
7575
|
+
ok: false,
|
|
7576
|
+
issues: [error.issue]
|
|
7577
|
+
};
|
|
7578
|
+
if (isStackOverflow(error)) return {
|
|
7579
|
+
ok: false,
|
|
7580
|
+
issues: [{
|
|
7581
|
+
offset: parser.position(),
|
|
7582
|
+
length: 0,
|
|
7583
|
+
message: "The text is too deep for the stack of the host."
|
|
7584
|
+
}]
|
|
7585
|
+
};
|
|
7586
|
+
throw error;
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
function none(reason) {
|
|
7590
|
+
return {
|
|
7591
|
+
tag: "none",
|
|
7592
|
+
reason
|
|
7593
|
+
};
|
|
7594
|
+
}
|
|
7595
|
+
function quoteName(name) {
|
|
7596
|
+
return `\`${name.replaceAll("`", "``")}\``;
|
|
7597
|
+
}
|
|
7598
|
+
function wordFor(syn, value) {
|
|
7599
|
+
for (const [word, held] of syn.words) if (Object.is(held, value)) return word;
|
|
7600
|
+
}
|
|
7601
|
+
function textOfString(syn, value) {
|
|
7602
|
+
const quote = syn.quote;
|
|
7603
|
+
if (quote === void 0) return void 0;
|
|
7604
|
+
if (syn.doubled) return `${quote}${value.replaceAll(quote, quote + quote)}${quote}`;
|
|
7605
|
+
const json = JSON.stringify(value);
|
|
7606
|
+
return quote === "\"" ? json : `'${json.slice(1, -1).replaceAll("\\\"", "\"").replaceAll("'", "\\'")}'`;
|
|
7607
|
+
}
|
|
7608
|
+
function textOfNumber(syn, value) {
|
|
7609
|
+
if (!syn.numbers) return wordFor(syn, value);
|
|
7610
|
+
const negative = value < 0 || Object.is(value, -0);
|
|
7611
|
+
if (negative && !syn.signed) return wordFor(syn, value);
|
|
7612
|
+
if (Number.isFinite(value)) return negative ? `-${String(-value)}` : String(value);
|
|
7613
|
+
return wordFor(syn, value) ?? (Number.isNaN(value) ? void 0 : negative ? "-1e999" : "1e999");
|
|
7614
|
+
}
|
|
7615
|
+
function literalShape(syn, value) {
|
|
7616
|
+
const text = typeof value === "string" ? textOfString(syn, value) : typeof value === "number" ? textOfNumber(syn, value) : wordFor(syn, value);
|
|
7617
|
+
return text === void 0 ? none(`This syntax has no text for the literal ${describe(value)}.`) : {
|
|
7618
|
+
tag: "text",
|
|
7619
|
+
text
|
|
7620
|
+
};
|
|
7621
|
+
}
|
|
7622
|
+
function plainSegment(syn, name) {
|
|
7623
|
+
return NAME.test(name) && !syn.reserved.has(name) && !syn.patterns.some((p) => p.whole.test(name));
|
|
7624
|
+
}
|
|
7625
|
+
function laterSegment(step) {
|
|
7626
|
+
if (typeof step === "string") return NAME.test(step) ? step : quoteName(step);
|
|
7627
|
+
return typeof step === "number" && Number.isSafeInteger(step) && step >= 0 ? String(step) : void 0;
|
|
7628
|
+
}
|
|
7629
|
+
function refShape(syn, value) {
|
|
7630
|
+
if (!Array.isArray(value) || value.length === 0) return none("The path of the reference is empty.");
|
|
7631
|
+
for (const pattern of syn.patterns) {
|
|
7632
|
+
const text = pattern.write(value);
|
|
7633
|
+
if (text !== void 0) return {
|
|
7634
|
+
tag: "text",
|
|
7635
|
+
text
|
|
7636
|
+
};
|
|
7637
|
+
}
|
|
7638
|
+
const [root, ...rest] = value;
|
|
7639
|
+
if ((rest.length > 1 || root !== syn.bare && rest.length > 0) && !syn.fields) return none("This syntax has no field steps.");
|
|
7640
|
+
const later = rest.slice(1).map(laterSegment);
|
|
7641
|
+
let first;
|
|
7642
|
+
let tail = later;
|
|
7643
|
+
if (root === syn.bare && typeof rest[0] === "string") first = plainSegment(syn, rest[0]) ? rest[0] : quoteName(rest[0]);
|
|
7644
|
+
else if (typeof root === "string" && syn.roots.has(root) && !syn.patterns.some((p) => p.whole.test(root))) {
|
|
7645
|
+
first = root;
|
|
7646
|
+
tail = rest.map(laterSegment);
|
|
7647
|
+
}
|
|
7648
|
+
if (first === void 0 || tail.includes(void 0)) return none("This syntax has no text for the path of the reference.");
|
|
7649
|
+
return {
|
|
7650
|
+
tag: "text",
|
|
7651
|
+
text: [first, ...tail].join(".")
|
|
7652
|
+
};
|
|
7653
|
+
}
|
|
7654
|
+
function callName(syn, keys) {
|
|
7655
|
+
if (keys.length === 0 || !keys.every((key) => typeof key === "string" && NAME.test(key))) return void 0;
|
|
7656
|
+
const name = keys.join(".");
|
|
7657
|
+
if (syn.forms.has(name) || syn.opWords.has(keys[0])) return void 0;
|
|
7658
|
+
return syn.ops === true || syn.ops.has(name) ? name : void 0;
|
|
7659
|
+
}
|
|
7660
|
+
function formShape(form, value) {
|
|
7661
|
+
if (form.fields === void 0) {
|
|
7662
|
+
if (!Array.isArray(value) || value.length < form.min) return void 0;
|
|
7663
|
+
return {
|
|
7664
|
+
tag: "call",
|
|
7665
|
+
name: form.name,
|
|
7666
|
+
args: value.map((item, index) => [index, item])
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
if (!isRecord(value)) return void 0;
|
|
7670
|
+
const count = Object.keys(value).length;
|
|
7671
|
+
const present = form.fields.slice(0, count);
|
|
7672
|
+
if (count < form.min || present.length < count || !present.every((key) => Object.hasOwn(value, key))) return void 0;
|
|
7673
|
+
return {
|
|
7674
|
+
tag: "call",
|
|
7675
|
+
name: form.name,
|
|
7676
|
+
args: present.map((key) => [key, value[key]])
|
|
7677
|
+
};
|
|
7678
|
+
}
|
|
7679
|
+
function opShape(syn, key, terms) {
|
|
7680
|
+
for (const op of syn.byKind.get(key) ?? []) if (op.fixity === "infix" ? terms.length === 2 || op.assoc === "flat" && terms.length > 2 : terms.length === 1) return {
|
|
7681
|
+
tag: "op",
|
|
7682
|
+
op,
|
|
7683
|
+
terms
|
|
7684
|
+
};
|
|
7685
|
+
}
|
|
7686
|
+
/** This function gives the shape of a document: a literal or a reference, an operator, a call, or no text. */
|
|
7687
|
+
function shapeOf(syn, doc) {
|
|
7688
|
+
if (!isEnvelope(doc)) return Array.isArray(doc) || isRecord(doc) ? none("This syntax has no text for a list or a record literal.") : literalShape(syn, doc);
|
|
7689
|
+
if (doc.name !== void 0 || doc.own !== void 0) return none("This syntax has no text for the header of a document.");
|
|
7690
|
+
const path = typePath(doc.type);
|
|
7691
|
+
if (path === void 0) return none("This syntax has no text for a computed type.");
|
|
7692
|
+
const value = doc.value;
|
|
7693
|
+
const name = path.join(".");
|
|
7694
|
+
if (name === "lit") return isEnvelope(value) || Array.isArray(value) || isRecord(value) ? none("This syntax has no text for a list or a record literal.") : literalShape(syn, value);
|
|
7695
|
+
if (name === "ext.undefined") return literalShape(syn, void 0);
|
|
7696
|
+
if (name === "ext.number") {
|
|
7697
|
+
const numbers = {
|
|
7698
|
+
NaN: NaN,
|
|
7699
|
+
Infinity: Number.POSITIVE_INFINITY,
|
|
7700
|
+
"-Infinity": Number.NEGATIVE_INFINITY,
|
|
7701
|
+
"-0": -0
|
|
7702
|
+
};
|
|
7703
|
+
return typeof value === "string" && Object.hasOwn(numbers, value) ? literalShape(syn, numbers[value]) : none("The value of ext.number is not valid.");
|
|
7704
|
+
}
|
|
7705
|
+
if (name === "ref") return refShape(syn, value);
|
|
7706
|
+
const key = JSON.stringify(path);
|
|
7707
|
+
const op = Array.isArray(value) ? opShape(syn, key, value) : void 0;
|
|
7708
|
+
if (op !== void 0) return op;
|
|
7709
|
+
for (const form of syn.formsByKind.get(key) ?? []) {
|
|
7710
|
+
const shape = formShape(form, value);
|
|
7711
|
+
if (shape !== void 0) return shape;
|
|
7712
|
+
}
|
|
7713
|
+
const called = path[0] === "ops" && Array.isArray(value) ? callName(syn, path.slice(1)) : void 0;
|
|
7714
|
+
if (called !== void 0) return {
|
|
7715
|
+
tag: "call",
|
|
7716
|
+
name: called,
|
|
7717
|
+
args: value.map((item, index) => [index, item])
|
|
7718
|
+
};
|
|
7719
|
+
return none(`This syntax has no text for the kind ${name}.`);
|
|
7720
|
+
}
|
|
7721
|
+
function levelOf(syn, doc) {
|
|
7722
|
+
const shape = shapeOf(syn, doc);
|
|
7723
|
+
return shape.tag === "op" ? shape.op.prec : Number.POSITIVE_INFINITY;
|
|
7724
|
+
}
|
|
7725
|
+
/** This function tells if a term needs parentheses in its position, so that `parse` reads the same tree. */
|
|
7726
|
+
function needsGroup(op, index, level) {
|
|
7727
|
+
if (op.fixity === "postfix") return level < op.prec;
|
|
7728
|
+
if (op.fixity === "prefix" || op.assoc === "none" || op.assoc === "flat") return level <= op.prec;
|
|
7729
|
+
return (op.assoc === "left" ? index === 0 : index !== 0) ? level < op.prec : level <= op.prec;
|
|
7730
|
+
}
|
|
7731
|
+
function opText(syn, op, terms, sub) {
|
|
7732
|
+
const texts = [];
|
|
7733
|
+
for (let index = 0; index < terms.length; index += 1) {
|
|
7734
|
+
const term = terms[index];
|
|
7735
|
+
const result = sub(term, [index]);
|
|
7736
|
+
if (!result.ok) return result;
|
|
7737
|
+
let text = result.value;
|
|
7738
|
+
const signedDigit = op.fixity === "prefix" && op.symbol === "-" && syn.signed && /^[0-9]/.test(text);
|
|
7739
|
+
if (needsGroup(op, index, levelOf(syn, term)) || signedDigit) text = `(${text})`;
|
|
7740
|
+
texts.push(text);
|
|
7741
|
+
}
|
|
7742
|
+
const [only = ""] = texts;
|
|
7743
|
+
if (op.fixity === "prefix") return ok(op.word || /^[^A-Za-z0-9_("'`$]/.test(only) ? `${op.symbol} ${only}` : `${op.symbol}${only}`);
|
|
7744
|
+
if (op.fixity === "postfix") return ok(op.word ? `${only} ${op.symbol}` : `${only}${op.symbol}`);
|
|
7745
|
+
return ok(texts.join(` ${op.symbol} `));
|
|
7746
|
+
}
|
|
7747
|
+
/** This function writes one document. `sub` gives the text of each part. */
|
|
7748
|
+
function printNode(syn, doc, sub) {
|
|
7749
|
+
const shape = shapeOf(syn, doc);
|
|
7750
|
+
if (shape.tag === "text") return ok(shape.text);
|
|
7751
|
+
if (shape.tag === "none") return fail("#VALUE!", shape.reason);
|
|
7752
|
+
if (shape.tag === "op") return opText(syn, shape.op, shape.terms, sub);
|
|
7753
|
+
const texts = [];
|
|
7754
|
+
for (const [key, arg] of shape.args) {
|
|
7755
|
+
const result = sub(arg, [key]);
|
|
7756
|
+
if (!result.ok) return result;
|
|
7757
|
+
texts.push(result.value);
|
|
7758
|
+
}
|
|
7759
|
+
return ok(`${shape.name}(${texts.join(`${syn.separator} `)})`);
|
|
7760
|
+
}
|
|
7761
|
+
function printTree(syn, doc, place) {
|
|
7762
|
+
const result = printNode(syn, doc, (child, path) => printTree(syn, child, [...place, ...path]));
|
|
7763
|
+
return result.ok || result.error.place !== void 0 ? result : fail(result.error.code, result.error.reason, { place });
|
|
7764
|
+
}
|
|
7765
|
+
/**
|
|
7766
|
+
* This function writes a document as text with the rules of a profile (SYNTAX.PRINT). It writes the fewest
|
|
7767
|
+
* parentheses that keep the tree. `parse` reads the text back to the same document (SYNTAX.ROUNDTRIP).
|
|
7768
|
+
*
|
|
7769
|
+
* @param profile - The syntax profile.
|
|
7770
|
+
* @param doc - The document.
|
|
7771
|
+
* @returns The text, or `#VALUE!` at the place of a document that the profile cannot write.
|
|
7772
|
+
* @throws {TypeError} The profile is not valid.
|
|
7773
|
+
*/
|
|
7774
|
+
function print(profile, doc) {
|
|
7775
|
+
const syn = compileProfile(profile);
|
|
7776
|
+
try {
|
|
7777
|
+
return printTree(syn, doc, []);
|
|
7778
|
+
} catch (error) {
|
|
7779
|
+
if (isStackOverflow(error)) return fail("#DEPTH!", "The document is too deep for the stack of the host.", { cause: error });
|
|
7780
|
+
throw error;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
/**
|
|
7784
|
+
* This function makes the part of a merge that adds the verb `format` to a machine (D23, D41). Its default writes
|
|
7785
|
+
* each document with the printer of the profile. A part gives its own text through the ladder, thus an own entry or
|
|
7786
|
+
* a kind entry of `format` still wins.
|
|
7787
|
+
*
|
|
7788
|
+
* @param profile - The syntax profile.
|
|
7789
|
+
* @returns The part for `mergeMachines`.
|
|
7790
|
+
*/
|
|
7791
|
+
function syntaxFormat(profile) {
|
|
7792
|
+
const syn = compileProfile(profile);
|
|
7793
|
+
const code = ({ doc, parts, place }) => {
|
|
7794
|
+
const byPath = new Map(parts.map((part) => [JSON.stringify(part.path), part]));
|
|
7795
|
+
return printNode(syn, doc, (child, path) => {
|
|
7796
|
+
const part = byPath.get(JSON.stringify(path));
|
|
7797
|
+
if (part === void 0) return printTree(syn, child, [...place, ...path]);
|
|
7798
|
+
const result = part.evaluate();
|
|
7799
|
+
if (!result.ok) return result;
|
|
7800
|
+
return typeof result.value === "string" ? ok(result.value) : fail("#VALUE!", `The verb format of a part gave ${describe(result.value)}, not a text.`, { place: [...place, ...path] });
|
|
7801
|
+
});
|
|
7802
|
+
};
|
|
7803
|
+
return {
|
|
7804
|
+
world: { defaults: { format: "syntax.format" } },
|
|
7805
|
+
evaluators: { "syntax.format": code }
|
|
7806
|
+
};
|
|
7807
|
+
}
|
|
7808
|
+
//#endregion
|
|
6801
7809
|
//#region src/reference.ts
|
|
6802
7810
|
function own$1(table, name) {
|
|
6803
7811
|
return table !== void 0 && Object.hasOwn(table, name) ? table[name] : void 0;
|
|
@@ -7008,6 +8016,7 @@ function evalInput(env, seen, at) {
|
|
|
7008
8016
|
parts,
|
|
7009
8017
|
context: at.ctx,
|
|
7010
8018
|
place: at.place,
|
|
8019
|
+
machine: env.machine,
|
|
7011
8020
|
...at.address ? { address: true } : {},
|
|
7012
8021
|
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
7013
8022
|
effect: (kind, payload) => cause(env, at.ctx, kind, payload, at.place),
|
|
@@ -7035,6 +8044,7 @@ function runInput(env, seen, at) {
|
|
|
7035
8044
|
parts,
|
|
7036
8045
|
context: at.ctx,
|
|
7037
8046
|
place: at.place,
|
|
8047
|
+
machine: env.machine,
|
|
7038
8048
|
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
7039
8049
|
effect: function* (kind, payload) {
|
|
7040
8050
|
return cause(env, at.ctx, kind, payload, at.place);
|
|
@@ -7437,12 +8447,12 @@ function formulaDefinitions(profile = {}) {
|
|
|
7437
8447
|
return thrown(error, `the operation ${name}`);
|
|
7438
8448
|
}
|
|
7439
8449
|
};
|
|
7440
|
-
const ext = ({ typePath, doc, context }) => {
|
|
8450
|
+
const ext = ({ typePath, doc, context, machine }) => {
|
|
7441
8451
|
const name = typePath.slice(1).join(".");
|
|
7442
8452
|
const handler = own$1(profile.ext, name) ?? own$1(STD_EXT, name);
|
|
7443
8453
|
if (handler === void 0) return fail("#NAME?", `The language has no extension ${name}.`);
|
|
7444
8454
|
try {
|
|
7445
|
-
return handler(doc.value, context);
|
|
8455
|
+
return handler(doc.value, context, machine?.domains);
|
|
7446
8456
|
} catch (error) {
|
|
7447
8457
|
return thrown(error, `the extension ${name}`);
|
|
7448
8458
|
}
|
|
@@ -8633,6 +9643,7 @@ function instanceOf(env, under, doc, split, sample) {
|
|
|
8633
9643
|
doc,
|
|
8634
9644
|
data: split.data,
|
|
8635
9645
|
place: PLACE,
|
|
9646
|
+
machine: env.machine,
|
|
8636
9647
|
fill
|
|
8637
9648
|
};
|
|
8638
9649
|
const who = `the ${env.runner ? "runner" : "evaluator"} ${env.name}`;
|
|
@@ -10244,6 +11255,6 @@ function typeFromSamples(values, options = {}) {
|
|
|
10244
11255
|
return samplesAt(values, options.closed === true, 0);
|
|
10245
11256
|
}
|
|
10246
11257
|
//#endregion
|
|
10247
|
-
export { CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkLatent, checkSteps, classify, components, conforms, context, cycles, dataEdges, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, entriesAsJson, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
11258
|
+
export { CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkLatent, checkSteps, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, encodeValue, entriesAsJson, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, print, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
10248
11259
|
|
|
10249
11260
|
//# sourceMappingURL=index.js.map
|