grammatik 0.1.0 → 0.1.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/dist/cjs/index.js +175 -44
- package/dist/cjs/runtime.js +7 -9
- package/dist/esm/index.js +175 -44
- package/dist/esm/runtime.js +7 -9
- package/package.json +3 -2
- package/src/analyze.js +111 -14
- package/src/codegen.js +75 -20
- package/src/emit.js +14 -2
- package/src/lexer.js +38 -16
- package/src/parser.js +9 -2
- package/src/plan.js +58 -8
package/dist/cjs/index.js
CHANGED
|
@@ -407,7 +407,7 @@ function createLexer(defs, options = {}) {
|
|
|
407
407
|
return id;
|
|
408
408
|
};
|
|
409
409
|
for (const def of defs) for (const mode of modesOf(def)) idOf(mode);
|
|
410
|
-
const compiled = defs.map((def) => {
|
|
410
|
+
const compiled = defs.map((def, id) => {
|
|
411
411
|
if (!def.name) throw new Error("createLexer: every token definition needs a name");
|
|
412
412
|
if (!def.pattern && !def.literal) throw new Error(`createLexer: token '${def.name}' needs a pattern or a literal`);
|
|
413
413
|
if (def.pattern && def.literal) throw new Error(`createLexer: token '${def.name}' has both a pattern and a literal; pick one`);
|
|
@@ -422,6 +422,11 @@ function createLexer(defs, options = {}) {
|
|
|
422
422
|
}
|
|
423
423
|
return {
|
|
424
424
|
name: def.name,
|
|
425
|
+
// The declaration index. `createParser` derives the identical mapping
|
|
426
|
+
// from `tokenNames` independently (same array, same order), so a
|
|
427
|
+
// combinator that resolves a `consume` target's id at build time agrees
|
|
428
|
+
// with what a token actually carries without the two ever comparing notes.
|
|
429
|
+
id,
|
|
425
430
|
skip: !!def.skip,
|
|
426
431
|
literal: def.literal,
|
|
427
432
|
longerAlt: def.longerAlt || null,
|
|
@@ -520,14 +525,7 @@ function createLexer(defs, options = {}) {
|
|
|
520
525
|
}
|
|
521
526
|
if (!winner.skip) {
|
|
522
527
|
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
523
|
-
tokens.push({
|
|
524
|
-
type: winner.name,
|
|
525
|
-
image,
|
|
526
|
-
start: offset,
|
|
527
|
-
end,
|
|
528
|
-
line: trackLines ? line : 0,
|
|
529
|
-
col: trackLines ? offset - lineStart + 1 : 0
|
|
530
|
-
});
|
|
528
|
+
tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
|
|
531
529
|
}
|
|
532
530
|
if (trackLines) {
|
|
533
531
|
for (let j = offset; j < end; j++) {
|
|
@@ -803,6 +801,47 @@ function isPlainParser(node) {
|
|
|
803
801
|
const op = opOf(node);
|
|
804
802
|
return op !== null && op !== "label" && op !== "action";
|
|
805
803
|
}
|
|
804
|
+
function deepEqual(a, b) {
|
|
805
|
+
if (a === b) return true;
|
|
806
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
807
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
808
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
812
|
+
const keysA = Object.keys(a);
|
|
813
|
+
const keysB = Object.keys(b);
|
|
814
|
+
if (keysA.length !== keysB.length) return false;
|
|
815
|
+
for (const key of keysA) {
|
|
816
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
817
|
+
}
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
function recognizeAltPrefixIdiom(branches) {
|
|
821
|
+
if (!Array.isArray(branches) || branches.length < 2) return null;
|
|
822
|
+
if (!branches.every((b) => opOf(b) === "seq" && Array.isArray(b.seq))) return null;
|
|
823
|
+
const seqs = branches.map((b) => b.seq);
|
|
824
|
+
const minLen = Math.min(...seqs.map((s) => s.length));
|
|
825
|
+
let n = 0;
|
|
826
|
+
while (n < minLen) {
|
|
827
|
+
const candidate = seqs[0][n];
|
|
828
|
+
const op = opOf(candidate);
|
|
829
|
+
if (op === "label" || op === "action") break;
|
|
830
|
+
if (!seqs.every((s) => deepEqual(s[n], candidate))) break;
|
|
831
|
+
n++;
|
|
832
|
+
}
|
|
833
|
+
if (n === 0) return null;
|
|
834
|
+
if (!seqs.every((s) => s.slice(n).some((child) => opOf(child) === "action"))) return null;
|
|
835
|
+
const prefix = seqs[0].slice(0, n);
|
|
836
|
+
const remainders = seqs.map((s) => ({ seq: s.slice(n) }));
|
|
837
|
+
return {
|
|
838
|
+
seq: [
|
|
839
|
+
...prefix,
|
|
840
|
+
{ label: ["__grammatik_rest", { alt: remainders }] },
|
|
841
|
+
{ action: { val: "__grammatik_rest" } }
|
|
842
|
+
]
|
|
843
|
+
};
|
|
844
|
+
}
|
|
806
845
|
var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
|
|
807
846
|
function rebaseOnLabel(logic, name) {
|
|
808
847
|
if (Array.isArray(logic)) {
|
|
@@ -1052,16 +1091,38 @@ function analyze(grammar, options = {}) {
|
|
|
1052
1091
|
return { firsts, errors, leftRecursive };
|
|
1053
1092
|
}
|
|
1054
1093
|
function buildDispatch(branches, firsts) {
|
|
1055
|
-
const
|
|
1056
|
-
for (const
|
|
1057
|
-
const first = firstOf(branch, firsts);
|
|
1094
|
+
const perBranch = branches.map((branch) => firstOf(branch, firsts));
|
|
1095
|
+
for (const first of perBranch) {
|
|
1058
1096
|
if (first.nullable || first.unknown || first.tokens.size === 0) return null;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1097
|
+
}
|
|
1098
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (let i = 0; i < branches.length; i++) {
|
|
1100
|
+
for (const token of perBranch[i].tokens) {
|
|
1101
|
+
let list = byToken.get(token);
|
|
1102
|
+
if (list === void 0) {
|
|
1103
|
+
list = [];
|
|
1104
|
+
byToken.set(token, list);
|
|
1105
|
+
}
|
|
1106
|
+
list.push(branches[i]);
|
|
1062
1107
|
}
|
|
1063
1108
|
}
|
|
1064
|
-
|
|
1109
|
+
if (byToken.size === 0) return null;
|
|
1110
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
1111
|
+
const table = /* @__PURE__ */ new Map();
|
|
1112
|
+
for (const [token, list] of byToken) {
|
|
1113
|
+
if (list.length === 1) {
|
|
1114
|
+
table.set(token, list[0]);
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const key = list.map((branch) => branches.indexOf(branch)).join(",");
|
|
1118
|
+
let group = canonical.get(key);
|
|
1119
|
+
if (group === void 0) {
|
|
1120
|
+
group = list;
|
|
1121
|
+
canonical.set(key, group);
|
|
1122
|
+
}
|
|
1123
|
+
table.set(token, group);
|
|
1124
|
+
}
|
|
1125
|
+
return table;
|
|
1065
1126
|
}
|
|
1066
1127
|
|
|
1067
1128
|
// src/plan.js
|
|
@@ -1096,7 +1157,7 @@ function mayReturnContext(logic) {
|
|
|
1096
1157
|
return false;
|
|
1097
1158
|
}
|
|
1098
1159
|
function createPlanner(config) {
|
|
1099
|
-
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true } = config;
|
|
1160
|
+
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null } = config;
|
|
1100
1161
|
const slots = /* @__PURE__ */ new Map();
|
|
1101
1162
|
const planCache = /* @__PURE__ */ new WeakMap();
|
|
1102
1163
|
const opCaches = /* @__PURE__ */ new Map();
|
|
@@ -1179,9 +1240,10 @@ function createPlanner(config) {
|
|
|
1179
1240
|
}
|
|
1180
1241
|
const buildConsume = (arg) => {
|
|
1181
1242
|
const name = typeof arg === "string" ? arg : arg[0];
|
|
1243
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1182
1244
|
return (state) => {
|
|
1183
1245
|
const token = state.tokens[state.idx];
|
|
1184
|
-
if (token !== void 0 && token.type === name) {
|
|
1246
|
+
if (token !== void 0 && (token.id === id || token.id === void 0 && token.type === name)) {
|
|
1185
1247
|
state.idx++;
|
|
1186
1248
|
return token;
|
|
1187
1249
|
}
|
|
@@ -1277,10 +1339,11 @@ function createPlanner(config) {
|
|
|
1277
1339
|
const buildOneOf = (arg) => {
|
|
1278
1340
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1279
1341
|
const set = new Set(names);
|
|
1342
|
+
const idSet = tokenIds ? new Set(names.map((n) => tokenIds.get(n))) : null;
|
|
1280
1343
|
const count = names.length;
|
|
1281
1344
|
return (state) => {
|
|
1282
1345
|
const token = state.tokens[state.idx];
|
|
1283
|
-
if (token !== void 0 && set.has(token.type)) {
|
|
1346
|
+
if (token !== void 0 && (idSet ? token.id !== void 0 ? idSet.has(token.id) : set.has(token.type) : set.has(token.type))) {
|
|
1284
1347
|
state.idx++;
|
|
1285
1348
|
return token;
|
|
1286
1349
|
}
|
|
@@ -1556,6 +1619,7 @@ function createPlanner(config) {
|
|
|
1556
1619
|
const kinds = [];
|
|
1557
1620
|
const names = [];
|
|
1558
1621
|
const tokens = [];
|
|
1622
|
+
const tokenIdList = [];
|
|
1559
1623
|
const fns = [];
|
|
1560
1624
|
const actionLogic = [];
|
|
1561
1625
|
let hasAction = false;
|
|
@@ -1576,11 +1640,13 @@ function createPlanner(config) {
|
|
|
1576
1640
|
kinds.push(LABELLED_TERMINAL);
|
|
1577
1641
|
names.push(name);
|
|
1578
1642
|
tokens.push(terminal2);
|
|
1643
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal2) : void 0);
|
|
1579
1644
|
fns.push(null);
|
|
1580
1645
|
} else {
|
|
1581
1646
|
kinds.push(LABELLED);
|
|
1582
1647
|
names.push(name);
|
|
1583
1648
|
tokens.push(null);
|
|
1649
|
+
tokenIdList.push(void 0);
|
|
1584
1650
|
fns.push(plan(inner));
|
|
1585
1651
|
}
|
|
1586
1652
|
continue;
|
|
@@ -1589,6 +1655,7 @@ function createPlanner(config) {
|
|
|
1589
1655
|
kinds.push(ACTION);
|
|
1590
1656
|
names.push(null);
|
|
1591
1657
|
tokens.push(null);
|
|
1658
|
+
tokenIdList.push(void 0);
|
|
1592
1659
|
fns.push(planAction(child.action));
|
|
1593
1660
|
actionLogic.push(child.action);
|
|
1594
1661
|
hasAction = true;
|
|
@@ -1599,11 +1666,13 @@ function createPlanner(config) {
|
|
|
1599
1666
|
kinds.push(TERMINAL);
|
|
1600
1667
|
names.push(null);
|
|
1601
1668
|
tokens.push(terminal);
|
|
1669
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal) : void 0);
|
|
1602
1670
|
fns.push(null);
|
|
1603
1671
|
} else {
|
|
1604
1672
|
kinds.push(PARSER);
|
|
1605
1673
|
names.push(null);
|
|
1606
1674
|
tokens.push(null);
|
|
1675
|
+
tokenIdList.push(void 0);
|
|
1607
1676
|
fns.push(plan(child));
|
|
1608
1677
|
}
|
|
1609
1678
|
}
|
|
@@ -1624,7 +1693,7 @@ function createPlanner(config) {
|
|
|
1624
1693
|
let value;
|
|
1625
1694
|
if (kind === TERMINAL || kind === LABELLED_TERMINAL) {
|
|
1626
1695
|
const token = state.tokens[state.idx];
|
|
1627
|
-
if (token !== void 0 && token.type === tokens[i]) {
|
|
1696
|
+
if (token !== void 0 && (token.id === tokenIdList[i] || token.id === void 0 && token.type === tokens[i])) {
|
|
1628
1697
|
state.idx++;
|
|
1629
1698
|
value = token;
|
|
1630
1699
|
} else {
|
|
@@ -1654,18 +1723,44 @@ function createPlanner(config) {
|
|
|
1654
1723
|
}
|
|
1655
1724
|
function planAlt(branches) {
|
|
1656
1725
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
1726
|
+
const factored = recognizeAltPrefixIdiom(branches);
|
|
1727
|
+
if (factored !== null) return planUncached(factored);
|
|
1657
1728
|
const planned = branches.map(plan);
|
|
1658
1729
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
1659
1730
|
if (table !== null) {
|
|
1660
1731
|
const byToken = /* @__PURE__ */ Object.create(null);
|
|
1661
|
-
|
|
1732
|
+
const byId = tokenIds ? [] : null;
|
|
1733
|
+
const groupClosures = /* @__PURE__ */ new Map();
|
|
1734
|
+
for (const [token, branch] of table) {
|
|
1735
|
+
let closure = groupClosures.get(branch);
|
|
1736
|
+
if (closure === void 0) {
|
|
1737
|
+
closure = Array.isArray(branch) ? (() => {
|
|
1738
|
+
const group = branch.map((b) => planned[branches.indexOf(b)]);
|
|
1739
|
+
return (state) => {
|
|
1740
|
+
const start = state.idx;
|
|
1741
|
+
for (let i = 0; i < group.length; i++) {
|
|
1742
|
+
const value = group[i](state);
|
|
1743
|
+
if (value !== FAIL) return value;
|
|
1744
|
+
state.idx = start;
|
|
1745
|
+
}
|
|
1746
|
+
return FAIL;
|
|
1747
|
+
};
|
|
1748
|
+
})() : planned[branches.indexOf(branch)];
|
|
1749
|
+
groupClosures.set(branch, closure);
|
|
1750
|
+
}
|
|
1751
|
+
byToken[token] = closure;
|
|
1752
|
+
if (byId) {
|
|
1753
|
+
const id = tokenIds.get(token);
|
|
1754
|
+
if (id !== void 0) byId[id] = closure;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1662
1757
|
const expectedTokens = [...table.keys()];
|
|
1663
1758
|
const expectedCount = expectedTokens.length;
|
|
1664
1759
|
return (state) => {
|
|
1665
1760
|
tick(state);
|
|
1666
1761
|
const token = state.tokens[state.idx];
|
|
1667
1762
|
if (token !== void 0) {
|
|
1668
|
-
const branch = byToken[token.type];
|
|
1763
|
+
const branch = byId && token.id !== void 0 ? byId[token.id] : byToken[token.type];
|
|
1669
1764
|
if (branch !== void 0) return branch(state);
|
|
1670
1765
|
}
|
|
1671
1766
|
for (let i = 0; i < expectedCount; i++) expect(state, expectedTokens[i]);
|
|
@@ -1804,7 +1899,12 @@ var createValueMethods = () => ({
|
|
|
1804
1899
|
|
|
1805
1900
|
// src/codegen.js
|
|
1806
1901
|
function createCodegen(config) {
|
|
1807
|
-
const { planner, rules, firsts, memo, maxSteps } = config;
|
|
1902
|
+
const { planner, rules, firsts, memo, maxSteps, tokenIds = null } = config;
|
|
1903
|
+
const typeCheck = (tokenVar, name) => {
|
|
1904
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1905
|
+
if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
|
|
1906
|
+
return `(${tokenVar}.id === ${id} || (${tokenVar}.id === undefined && ${tokenVar}.type === ${JSON.stringify(name)}))`;
|
|
1907
|
+
};
|
|
1808
1908
|
const HELPERS = [];
|
|
1809
1909
|
const ORIGINS = [];
|
|
1810
1910
|
const RULES = [];
|
|
@@ -1846,7 +1946,7 @@ function createCodegen(config) {
|
|
|
1846
1946
|
out.push(
|
|
1847
1947
|
`let ${result};`,
|
|
1848
1948
|
`const ${token} = c.tokens[c.idx];`,
|
|
1849
|
-
`if (${token} !== undefined && ${token
|
|
1949
|
+
`if (${token} !== undefined && ${typeCheck(token, name)}) { c.idx++; ${result} = ${token}; }`,
|
|
1850
1950
|
`else { E(c, ${JSON.stringify(name)}); ${result} = F; }`
|
|
1851
1951
|
);
|
|
1852
1952
|
return result;
|
|
@@ -1942,7 +2042,7 @@ function createCodegen(config) {
|
|
|
1942
2042
|
case "oneOf": {
|
|
1943
2043
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1944
2044
|
const token = nextId();
|
|
1945
|
-
const test = names.map((n) =>
|
|
2045
|
+
const test = names.map((n) => typeCheck(token, n)).join(" || ");
|
|
1946
2046
|
out.push(
|
|
1947
2047
|
`let ${result};`,
|
|
1948
2048
|
`const ${token} = c.tokens[c.idx];`,
|
|
@@ -2221,24 +2321,51 @@ function createCodegen(config) {
|
|
|
2221
2321
|
out.push(`${result} = ${hasAction ? actionResult : "[" + collected.join(", ") + "]"};`, "}");
|
|
2222
2322
|
return result;
|
|
2223
2323
|
}
|
|
2324
|
+
function genOrderedBacktrack(list, out, bindingsVar) {
|
|
2325
|
+
const result = nextId();
|
|
2326
|
+
const label2 = "L" + nextId();
|
|
2327
|
+
const start = nextId();
|
|
2328
|
+
out.push(`let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
|
|
2329
|
+
for (const branch of list) {
|
|
2330
|
+
out.push("{");
|
|
2331
|
+
const value = gen(branch, out, bindingsVar);
|
|
2332
|
+
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
2333
|
+
}
|
|
2334
|
+
out.push(`${result} = F;`, "}");
|
|
2335
|
+
return result;
|
|
2336
|
+
}
|
|
2224
2337
|
function genAlt(branches, out, result, bindingsVar) {
|
|
2225
2338
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
2339
|
+
const factored = recognizeAltPrefixIdiom(branches);
|
|
2340
|
+
if (factored !== null) return gen(factored, out, bindingsVar);
|
|
2226
2341
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
2227
2342
|
const label2 = "L" + nextId();
|
|
2228
2343
|
if (table !== null) {
|
|
2229
2344
|
const expected = [...table.keys()];
|
|
2345
|
+
const expectedIds = tokenIds ? expected.map((t) => tokenIds.get(t)) : null;
|
|
2346
|
+
const numeric = expectedIds !== null && expectedIds.every((id) => id !== void 0);
|
|
2230
2347
|
const token = nextId();
|
|
2231
2348
|
out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${token} = c.tokens[c.idx];`);
|
|
2232
2349
|
out.push(`if (${token} === undefined) { ${expected.map((t) => `E(c, ${JSON.stringify(t)});`).join(" ")} ${result} = F; break ${label2}; }`);
|
|
2233
|
-
|
|
2350
|
+
let discriminant;
|
|
2351
|
+
if (numeric) {
|
|
2352
|
+
discriminant = nextId();
|
|
2353
|
+
out.push(`const ${discriminant} = ${token}.id !== undefined ? ${token}.id : I[${token}.type];`);
|
|
2354
|
+
out.push(`switch (${discriminant}) {`);
|
|
2355
|
+
} else {
|
|
2356
|
+
discriminant = `${token}.type`;
|
|
2357
|
+
out.push(`switch (${discriminant}) {`);
|
|
2358
|
+
}
|
|
2234
2359
|
const byBranch = /* @__PURE__ */ new Map();
|
|
2235
|
-
for (const
|
|
2360
|
+
for (const tokenName of expected) {
|
|
2361
|
+
const branch = table.get(tokenName);
|
|
2236
2362
|
if (!byBranch.has(branch)) byBranch.set(branch, []);
|
|
2237
2363
|
byBranch.get(branch).push(tokenName);
|
|
2238
2364
|
}
|
|
2365
|
+
const caseLabel = (t) => numeric ? tokenIds.get(t) : JSON.stringify(t);
|
|
2239
2366
|
for (const [branch, tokenNames] of byBranch) {
|
|
2240
|
-
out.push(tokenNames.map((t) => `case ${
|
|
2241
|
-
const value = gen(branch, out, bindingsVar);
|
|
2367
|
+
out.push(tokenNames.map((t) => `case ${caseLabel(t)}:`).join(" ") + " {");
|
|
2368
|
+
const value = Array.isArray(branch) ? genOrderedBacktrack(branch, out, bindingsVar) : gen(branch, out, bindingsVar);
|
|
2242
2369
|
out.push(`${result} = ${value}; break ${label2};`, "}");
|
|
2243
2370
|
}
|
|
2244
2371
|
out.push(
|
|
@@ -2248,16 +2375,10 @@ function createCodegen(config) {
|
|
|
2248
2375
|
);
|
|
2249
2376
|
return result;
|
|
2250
2377
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
for (const branch of branches) {
|
|
2254
|
-
out.push("{");
|
|
2255
|
-
const value = gen(branch, out, bindingsVar);
|
|
2256
|
-
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
2257
|
-
}
|
|
2258
|
-
out.push(`${result} = F;`, "}");
|
|
2259
|
-
return result;
|
|
2378
|
+
out.push(tickSrc);
|
|
2379
|
+
return genOrderedBacktrack(branches, out, bindingsVar);
|
|
2260
2380
|
}
|
|
2381
|
+
const idByName = tokenIds ? Object.fromEntries(tokenIds) : null;
|
|
2261
2382
|
function buildRule(name) {
|
|
2262
2383
|
const out = [];
|
|
2263
2384
|
const value = gen(rules[name], out, null);
|
|
@@ -2267,8 +2388,8 @@ return ${value};
|
|
|
2267
2388
|
}`;
|
|
2268
2389
|
SOURCES[ruleIndex.get(name)] = body;
|
|
2269
2390
|
const source = `return ${body}`;
|
|
2270
|
-
const factory = new Function("F", "E", "P", "T", "R", "H", source);
|
|
2271
|
-
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS);
|
|
2391
|
+
const factory = new Function("F", "E", "P", "T", "R", "H", "I", source);
|
|
2392
|
+
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS, idByName);
|
|
2272
2393
|
}
|
|
2273
2394
|
function progressError(op, at) {
|
|
2274
2395
|
throw new Error(
|
|
@@ -2350,9 +2471,10 @@ function createParser(spec, options = {}) {
|
|
|
2350
2471
|
for (const [name, method] of Object.entries(extraMethods || {})) engine.addMethod(name, method);
|
|
2351
2472
|
const startRule = rules[start];
|
|
2352
2473
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2353
|
-
const
|
|
2474
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2475
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds });
|
|
2354
2476
|
engine.__jlGrammar = { planner };
|
|
2355
|
-
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps }) : null;
|
|
2477
|
+
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds }) : null;
|
|
2356
2478
|
const compiledStart = codegen && codegen.compile(start);
|
|
2357
2479
|
if (codegen && !engine.disableInterpretedOptimization) {
|
|
2358
2480
|
for (const name of Object.keys(rules)) {
|
|
@@ -2795,12 +2917,13 @@ function emitModule(spec, options = {}) {
|
|
|
2795
2917
|
const analysis = analyze(rules, { tokenNames: lexer.tokenNames });
|
|
2796
2918
|
if (validate && analysis.errors.length > 0) throw new GrammarError(analysis.errors);
|
|
2797
2919
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2920
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2798
2921
|
const engine = { build: () => {
|
|
2799
2922
|
throw new Error("not compiled during emit");
|
|
2800
2923
|
}, run: () => {
|
|
2801
2924
|
} };
|
|
2802
|
-
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false });
|
|
2803
|
-
const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps });
|
|
2925
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds });
|
|
2926
|
+
const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds });
|
|
2804
2927
|
codegen.compile(start);
|
|
2805
2928
|
const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
|
|
2806
2929
|
const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
|
|
@@ -2943,6 +3066,14 @@ const H = [
|
|
|
2943
3066
|
${helpers.map((source, index) => ` /* ${index} */ ${source}`).join(",\n")}
|
|
2944
3067
|
]
|
|
2945
3068
|
|
|
3069
|
+
/**
|
|
3070
|
+
* Name -> id, the same numbering \`lexer\` assigns. A generated \`alt\`'s switch
|
|
3071
|
+
* dispatches on a token's id when it has one; this is only read as a fallback,
|
|
3072
|
+
* for a hand-built token from \`parseTokens\` that bypassed this file's own
|
|
3073
|
+
* lexer and so has no id of its own.
|
|
3074
|
+
*/
|
|
3075
|
+
const I = ${literal(Object.fromEntries(tokenIds))}
|
|
3076
|
+
|
|
2946
3077
|
${ruleSources}
|
|
2947
3078
|
|
|
2948
3079
|
const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name, index])))}
|
package/dist/cjs/runtime.js
CHANGED
|
@@ -442,7 +442,7 @@ function createLexer(defs, options = {}) {
|
|
|
442
442
|
return id;
|
|
443
443
|
};
|
|
444
444
|
for (const def of defs) for (const mode of modesOf(def)) idOf(mode);
|
|
445
|
-
const compiled = defs.map((def) => {
|
|
445
|
+
const compiled = defs.map((def, id) => {
|
|
446
446
|
if (!def.name) throw new Error("createLexer: every token definition needs a name");
|
|
447
447
|
if (!def.pattern && !def.literal) throw new Error(`createLexer: token '${def.name}' needs a pattern or a literal`);
|
|
448
448
|
if (def.pattern && def.literal) throw new Error(`createLexer: token '${def.name}' has both a pattern and a literal; pick one`);
|
|
@@ -457,6 +457,11 @@ function createLexer(defs, options = {}) {
|
|
|
457
457
|
}
|
|
458
458
|
return {
|
|
459
459
|
name: def.name,
|
|
460
|
+
// The declaration index. `createParser` derives the identical mapping
|
|
461
|
+
// from `tokenNames` independently (same array, same order), so a
|
|
462
|
+
// combinator that resolves a `consume` target's id at build time agrees
|
|
463
|
+
// with what a token actually carries without the two ever comparing notes.
|
|
464
|
+
id,
|
|
460
465
|
skip: !!def.skip,
|
|
461
466
|
literal: def.literal,
|
|
462
467
|
longerAlt: def.longerAlt || null,
|
|
@@ -555,14 +560,7 @@ function createLexer(defs, options = {}) {
|
|
|
555
560
|
}
|
|
556
561
|
if (!winner.skip) {
|
|
557
562
|
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
558
|
-
tokens.push({
|
|
559
|
-
type: winner.name,
|
|
560
|
-
image,
|
|
561
|
-
start: offset,
|
|
562
|
-
end,
|
|
563
|
-
line: trackLines ? line : 0,
|
|
564
|
-
col: trackLines ? offset - lineStart + 1 : 0
|
|
565
|
-
});
|
|
563
|
+
tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
|
|
566
564
|
}
|
|
567
565
|
if (trackLines) {
|
|
568
566
|
for (let j = offset; j < end; j++) {
|