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 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 table = /* @__PURE__ */ new Map();
1056
- for (const branch of branches) {
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
- for (const token of first.tokens) {
1060
- if (table.has(token)) return null;
1061
- table.set(token, branch);
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
- return table.size > 0 ? table : null;
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
- for (const [token, branch] of table) byToken[token] = planned[branches.indexOf(branch)];
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}.type === ${JSON.stringify(name)}) { c.idx++; ${result} = ${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) => `${token}.type === ${JSON.stringify(n)}`).join(" || ");
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
- out.push(`switch (${token}.type) {`);
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 [tokenName, branch] of table) {
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 ${JSON.stringify(t)}:`).join(" ") + " {");
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
- const start = nextId();
2252
- out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
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 planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval });
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])))}
@@ -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++) {