parseman 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,6 +69,63 @@ function any() {
69
69
  function empty() {
70
70
  return { kind: "empty" };
71
71
  }
72
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
73
+ if (seen.has(p)) return true;
74
+ seen.add(p);
75
+ const me = (c) => matchesEmpty(c, seen);
76
+ const d = p._def;
77
+ switch (d.tag) {
78
+ case "literal":
79
+ return d.value.length === 0;
80
+ case "keywords":
81
+ return false;
82
+ case "regex":
83
+ try {
84
+ const m = new RegExp(d.source).exec("");
85
+ return m != null && m[0] === "";
86
+ } catch {
87
+ return true;
88
+ }
89
+ case "many":
90
+ case "optional":
91
+ case "not":
92
+ return true;
93
+ // zero repetitions / absent / lookahead
94
+ case "oneOrMore":
95
+ return me(d.parser);
96
+ case "sequence":
97
+ return d.parsers.every(me);
98
+ case "choice":
99
+ return d.parsers.some(me);
100
+ case "transform":
101
+ case "label":
102
+ case "trivia":
103
+ case "expect":
104
+ case "withCtx":
105
+ case "node":
106
+ case "grammar":
107
+ case "recover":
108
+ return me(d.parser);
109
+ case "skip":
110
+ return me(d.main);
111
+ case "lazy":
112
+ try {
113
+ return me(d.thunk());
114
+ } catch {
115
+ return true;
116
+ }
117
+ default:
118
+ return true;
119
+ }
120
+ }
121
+ function sequenceFirstSet(parsers) {
122
+ let fs2 = empty();
123
+ for (const p of parsers) {
124
+ fs2 = union(fs2, p._meta.firstSet);
125
+ if (!matchesEmpty(p)) return fs2;
126
+ }
127
+ return fs2;
128
+ }
72
129
  function mergeRanges(ranges) {
73
130
  if (ranges.length === 0) return [];
74
131
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -917,16 +974,19 @@ function scanTrivia(input, cur, ctx) {
917
974
  // src/combinators/sequence.ts
918
975
  function sequence(...parsers) {
919
976
  const meta = {
920
- firstSet: parsers[0]?._meta.firstSet ?? empty(),
977
+ // Union through the nullable prefix — a leading optional()/many() lets a later
978
+ // term's first char start the sequence. Just `parsers[0]` under-approximates.
979
+ firstSet: sequenceFirstSet(parsers),
921
980
  canMatchNewline: parsers.some((p) => p._meta.canMatchNewline),
922
981
  isTrivia: false
923
982
  };
983
+ const def = { tag: "sequence", parsers };
924
984
  return {
925
985
  _tag: "sequence",
926
986
  _meta: meta,
927
- _def: { tag: "sequence", parsers },
987
+ _def: def,
928
988
  parse(input, pos, ctx) {
929
- const values = [];
989
+ const values = def.valueUnused ? void 0 : [];
930
990
  let cur = pos;
931
991
  for (let i = 0; i < parsers.length; i++) {
932
992
  if (ctx.trivia && i > 0) {
@@ -946,17 +1006,18 @@ function sequence(...parsers) {
946
1006
  } else {
947
1007
  rollbackTrivia(ctx, mark);
948
1008
  }
949
- values.push(result2.value);
1009
+ if (values !== void 0) values.push(result2.value);
950
1010
  continue;
951
1011
  }
952
1012
  const result = parsers[i].parse(input, cur, ctx);
953
1013
  if (!result.ok) return result;
954
- values.push(result.value);
1014
+ if (values !== void 0) values.push(result.value);
955
1015
  cur = result.span.end;
956
1016
  }
957
1017
  return {
958
1018
  ok: true,
959
- value: values,
1019
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1020
+ value: values ?? void 0,
960
1021
  span: { start: pos, end: cur }
961
1022
  };
962
1023
  }
@@ -997,20 +1058,21 @@ function many(combinator) {
997
1058
  canMatchNewline: combinator._meta.canMatchNewline,
998
1059
  isTrivia: false
999
1060
  };
1061
+ const def = { tag: "many", parser: combinator, min: 0 };
1000
1062
  return {
1001
1063
  _tag: "many",
1002
1064
  _meta: meta,
1003
- _def: { tag: "many", parser: combinator, min: 0 },
1065
+ _def: def,
1004
1066
  parse(input, pos, ctx) {
1005
- const values = [];
1067
+ const values = def.valueUnused ? void 0 : [];
1006
1068
  let cur = pos;
1007
1069
  while (cur < input.length) {
1008
1070
  const item = repItem(combinator, input, cur, ctx);
1009
1071
  if (item === "stop" || "fail" in item) break;
1010
- values.push(item.value);
1072
+ if (values !== void 0) values.push(item.value);
1011
1073
  cur = item.end;
1012
1074
  }
1013
- return { ok: true, value: values, span: { start: pos, end: cur } };
1075
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1014
1076
  }
1015
1077
  };
1016
1078
  }
@@ -1020,22 +1082,23 @@ function oneOrMore(combinator) {
1020
1082
  canMatchNewline: combinator._meta.canMatchNewline,
1021
1083
  isTrivia: false
1022
1084
  };
1085
+ const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1023
1086
  return {
1024
1087
  _tag: "oneOrMore",
1025
1088
  _meta: meta,
1026
- _def: { tag: "oneOrMore", parser: combinator, min: 1 },
1089
+ _def: def,
1027
1090
  parse(input, pos, ctx) {
1028
1091
  const first = combinator.parse(input, pos, ctx);
1029
1092
  if (!first.ok) return first;
1030
- const values = [first.value];
1093
+ const values = def.valueUnused ? void 0 : [first.value];
1031
1094
  let cur = first.span.end;
1032
1095
  while (cur < input.length) {
1033
1096
  const item = repItem(combinator, input, cur, ctx);
1034
1097
  if (item === "stop" || "fail" in item) break;
1035
- values.push(item.value);
1098
+ if (values !== void 0) values.push(item.value);
1036
1099
  cur = item.end;
1037
1100
  }
1038
- return { ok: true, value: values, span: { start: pos, end: cur } };
1101
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1039
1102
  }
1040
1103
  };
1041
1104
  }
@@ -1113,6 +1176,90 @@ function sepBy(combinator, separator) {
1113
1176
  };
1114
1177
  }
1115
1178
 
1179
+ // src/compiler/value-usage.ts
1180
+ function markUnusedValues(root) {
1181
+ const seenConsumed = /* @__PURE__ */ new WeakSet();
1182
+ const seenUnconsumed = /* @__PURE__ */ new WeakSet();
1183
+ const mark = (def, consumed) => {
1184
+ if (consumed) def.valueUnused = false;
1185
+ else if (def.valueUnused !== false) def.valueUnused = true;
1186
+ };
1187
+ const visit = (c, consumed) => {
1188
+ const seen = consumed ? seenConsumed : seenUnconsumed;
1189
+ if (seen.has(c)) return;
1190
+ seen.add(c);
1191
+ const def = c._def;
1192
+ switch (def.tag) {
1193
+ case "many":
1194
+ case "oneOrMore":
1195
+ mark(def, consumed);
1196
+ visit(def.parser, consumed);
1197
+ return;
1198
+ case "optional":
1199
+ visit(def.parser, consumed);
1200
+ return;
1201
+ case "sequence":
1202
+ mark(def, consumed);
1203
+ for (const p of def.parsers) visit(p, consumed);
1204
+ return;
1205
+ case "choice":
1206
+ for (const p of def.parsers) visit(p, consumed);
1207
+ return;
1208
+ case "node":
1209
+ visit(def.parser, false);
1210
+ return;
1211
+ case "transform":
1212
+ visit(def.parser, true);
1213
+ return;
1214
+ case "skip":
1215
+ visit(def.main, consumed);
1216
+ visit(def.skipped, false);
1217
+ return;
1218
+ case "sepBy":
1219
+ visit(def.parser, true);
1220
+ visit(def.separator, false);
1221
+ return;
1222
+ case "not":
1223
+ visit(def.parser, false);
1224
+ return;
1225
+ case "grammar":
1226
+ visit(def.parser, consumed);
1227
+ if (def.triviaParser) visit(def.triviaParser, false);
1228
+ return;
1229
+ case "recover":
1230
+ visit(def.parser, consumed);
1231
+ visit(def.sentinel, false);
1232
+ return;
1233
+ case "label":
1234
+ case "trivia":
1235
+ case "withCtx":
1236
+ case "expect":
1237
+ visit(def.parser, consumed);
1238
+ return;
1239
+ case "lazy":
1240
+ return;
1241
+ default:
1242
+ return;
1243
+ }
1244
+ };
1245
+ const resolveRoot = (c) => {
1246
+ const guard2 = /* @__PURE__ */ new Set();
1247
+ let cur = c;
1248
+ while (!guard2.has(cur)) {
1249
+ const d = cur._def;
1250
+ if (d.tag !== "lazy") return cur;
1251
+ guard2.add(cur);
1252
+ try {
1253
+ cur = d.thunk();
1254
+ } catch {
1255
+ return c;
1256
+ }
1257
+ }
1258
+ return cur;
1259
+ };
1260
+ visit(resolveRoot(root), true);
1261
+ }
1262
+
1116
1263
  // src/combinators/not.ts
1117
1264
  function not(combinator) {
1118
1265
  const meta = {
@@ -3251,6 +3398,7 @@ function emitSeqValues(def, ctx, pos) {
3251
3398
  }
3252
3399
  function emitSeq(def, ctx, pos) {
3253
3400
  const { stmts, endVar, valueVars } = emitSeqValues(def, ctx, pos);
3401
+ if (def.valueUnused) return { stmts, valueVar: "undefined", endVar };
3254
3402
  const arrV = v(ctx, "_arr");
3255
3403
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3256
3404
  return { stmts, valueVar: arrV, endVar };
@@ -3457,7 +3605,8 @@ function emitFirstMatch(def, ctx, pos) {
3457
3605
  const autoNot = def.autoNot[i];
3458
3606
  const atStart = failsAtStart(p);
3459
3607
  const staticFx = armStaticExpected(ctx, p);
3460
- const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3608
+ const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3609
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3461
3610
  let gateCond = null;
3462
3611
  if (gate) {
3463
3612
  const gateIdx = ctx.mapFns.length;
@@ -3548,19 +3697,18 @@ function emitLiteralCondition(litVal, pos) {
3548
3697
  return checks.join(" && ");
3549
3698
  }
3550
3699
  function emitMany(def, ctx, pos) {
3700
+ const wantValue = !def.valueUnused;
3551
3701
  const arrV = v(ctx, "_arr");
3552
3702
  const curV = v(ctx, "_cur");
3553
3703
  const stmts = [
3554
- `${ind(ctx)}const ${arrV} = []`,
3704
+ ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
3555
3705
  `${ind(ctx)}let ${curV} = ${pos}`
3556
3706
  ];
3557
3707
  if (def.min === 1) {
3558
3708
  const firstR = emit(def.parser, ctx, curV);
3559
3709
  stmts.push(...firstR.stmts);
3560
- stmts.push(
3561
- `${ind(ctx)}${arrV}.push(${firstR.valueVar})`,
3562
- `${ind(ctx)}${curV} = ${firstR.endVar}`
3563
- );
3710
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
3711
+ stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
3564
3712
  }
3565
3713
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
3566
3714
  ctx.indent++;
@@ -3591,13 +3739,13 @@ function emitMany(def, ctx, pos) {
3591
3739
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
3592
3740
  stmts.push(
3593
3741
  ...iterStmts,
3594
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
3595
- `${ind(ctx)}${arrV}.push(${iterVal})`,
3596
- `${ind(ctx)}${curV} = ${iterEnd}`
3742
+ `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
3597
3743
  );
3744
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
3745
+ stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
3598
3746
  ctx.indent--;
3599
3747
  stmts.push(`${ind(ctx)}}`);
3600
- return { stmts, valueVar: arrV, endVar: curV };
3748
+ return { stmts, valueVar: wantValue ? arrV : "undefined", endVar: curV };
3601
3749
  }
3602
3750
  function emitOptional(def, ctx, pos) {
3603
3751
  const valV = v(ctx, "_opt");
@@ -4207,6 +4355,7 @@ function ruleDependencies(ruleMap) {
4207
4355
  return deps;
4208
4356
  }
4209
4357
  function compile(combinator, mapFnSources) {
4358
+ markUnusedValues(combinator);
4210
4359
  const ctx = {
4211
4360
  vars: 0,
4212
4361
  indent: 1,
@@ -4289,7 +4438,16 @@ function compile(combinator, mapFnSources) {
4289
4438
  }
4290
4439
  };
4291
4440
  }
4441
+ var RULE_NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
4442
+ function assertRuleName(name) {
4443
+ if (!RULE_NAME_RE.test(name)) {
4444
+ throw new Error(
4445
+ `parseman: grammar rule name ${JSON.stringify(name)} is not a valid JS identifier. Rule names compile to _r_<Name> functions and dispatch guards \u2014 use letters, digits, _ or $ (and don't start with a digit).`
4446
+ );
4447
+ }
4448
+ }
4292
4449
  function compileRuleMap(ruleMap) {
4450
+ for (const [, rule] of ruleMap) markUnusedValues(rule);
4293
4451
  const ctx = {
4294
4452
  vars: 0,
4295
4453
  indent: 1,
@@ -4314,7 +4472,8 @@ function compileRuleMap(ruleMap) {
4314
4472
  const seenNames = /* @__PURE__ */ new Map();
4315
4473
  const ruleNames = /* @__PURE__ */ new Map();
4316
4474
  for (const [key, rule] of ruleMap) {
4317
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4475
+ assertRuleName(key);
4476
+ const base = `_r_${key}`;
4318
4477
  const n = seenNames.get(base) ?? 0;
4319
4478
  seenNames.set(base, n + 1);
4320
4479
  ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
@@ -4365,6 +4524,7 @@ function compileRuleMap(ruleMap) {
4365
4524
  }
4366
4525
  function compileLinkable(ruleMapArg, ns) {
4367
4526
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4527
+ for (const [, rule] of ruleMapArg) markUnusedValues(rule);
4368
4528
  const ruleMap = ruleMapArg.filter(([, val]) => {
4369
4529
  const d = val._def;
4370
4530
  if (d.tag !== "lazy") return true;
@@ -4395,13 +4555,15 @@ function compileLinkable(ruleMapArg, ns) {
4395
4555
  namedFnDecls: [],
4396
4556
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4397
4557
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4398
- ns
4558
+ ns,
4559
+ deferFirstSetRefs: true
4399
4560
  };
4400
4561
  const seen = /* @__PURE__ */ new Map();
4401
4562
  const ruleNames = /* @__PURE__ */ new Map();
4402
4563
  const fnNameToKey = /* @__PURE__ */ new Map();
4403
4564
  for (const [key, rule] of ruleMap) {
4404
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4565
+ assertRuleName(key);
4566
+ const base = `_r_${key}`;
4405
4567
  const n = seen.get(base) ?? 0;
4406
4568
  seen.set(base, n + 1);
4407
4569
  const fn = n === 0 ? base : `${base}$${n}`;
@@ -4425,7 +4587,8 @@ function compileLinkable(ruleMapArg, ns) {
4425
4587
  if (resolved === void 0) {
4426
4588
  const name = p._ruleName;
4427
4589
  if (name && !ctx.namedParsers.has(p)) {
4428
- const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4590
+ assertRuleName(name);
4591
+ const fn = `_r_${name}`;
4429
4592
  ruleNames.set(p, fn);
4430
4593
  ctx.namedParsers.set(p, fn);
4431
4594
  }
@@ -4494,12 +4657,26 @@ function compileLinkable(ruleMapArg, ns) {
4494
4657
  `}`
4495
4658
  ].join("\n"));
4496
4659
  }
4660
+ const firstSets = /* @__PURE__ */ new Map();
4661
+ for (const [key, rule] of ruleMap) {
4662
+ let resolved = rule;
4663
+ const d = rule._def;
4664
+ if (d.tag === "lazy") {
4665
+ try {
4666
+ resolved = d.thunk();
4667
+ } catch {
4668
+ resolved = rule;
4669
+ }
4670
+ }
4671
+ firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
4672
+ }
4497
4673
  return {
4498
4674
  ns,
4499
4675
  keys: perEntry.map((e) => e.key),
4500
4676
  prelude,
4501
4677
  ruleFns,
4502
4678
  wrappers,
4679
+ firstSets,
4503
4680
  deps: ruleDependencies(ruleMap),
4504
4681
  needsEmptyTl: !!ctx.needsEmptyTl,
4505
4682
  needsCollator: ctx.needsCollator,
@@ -4569,7 +4746,23 @@ function fusedBody(pieces) {
4569
4746
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4570
4747
  ];
4571
4748
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4572
- const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4749
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4750
+ const finalFS = /* @__PURE__ */ new Map();
4751
+ for (const [k, p] of winner) {
4752
+ const fs2 = p.firstSets?.get(k);
4753
+ if (fs2) finalFS.set(k, fs2);
4754
+ }
4755
+ const body = rawBody.replace(
4756
+ // Rule name + code-point var are both JS identifiers (rule names are validated
4757
+ // at compile time — see assertRuleName in codegen), so an identifier class
4758
+ // matches every well-formed placeholder.
4759
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4760
+ (_m, name, codevar) => {
4761
+ const fs2 = finalFS.get(name);
4762
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4763
+ return `(${firstSetCond(codevar, fs2)})`;
4764
+ }
4765
+ );
4573
4766
  const env = {};
4574
4767
  for (const p of contributing) {
4575
4768
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -5349,7 +5542,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5349
5542
  const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5350
5543
  const serializePieces = (p) => {
5351
5544
  const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5352
- return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
5545
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
5353
5546
  };
5354
5547
  const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5355
5548
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;