parseman 0.37.0 → 0.38.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.
@@ -2358,6 +2358,16 @@ function tagRule(r, key) {
2358
2358
  r._ruleName = key;
2359
2359
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
2360
2360
  }
2361
+ function isDefinableRef(v2) {
2362
+ return !!v2 && typeof v2 === "object" && "_def" in v2 && v2._def.tag === "lazy" && typeof v2.define === "function";
2363
+ }
2364
+ function ruleNameOf(r) {
2365
+ return r._ruleName;
2366
+ }
2367
+ function isNamedRuleRefForAnotherRule(r, key) {
2368
+ const name = r._def.tag === "lazy" ? ruleNameOf(r) : void 0;
2369
+ return name !== void 0 && name !== key;
2370
+ }
2361
2371
  function rules(a, b) {
2362
2372
  const factory = typeof a === "function" ? a : b;
2363
2373
  const options = typeof a === "function" ? b : a;
@@ -2378,11 +2388,21 @@ function rules(a, b) {
2378
2388
  for (const key of Object.keys(definitions)) {
2379
2389
  const placeholder = cache[key];
2380
2390
  const parser2 = definitions[key];
2381
- if (placeholder !== void 0 && typeof placeholder.define === "function") {
2382
- tagRule(parser2, key);
2391
+ if (placeholder === parser2) {
2392
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`);
2393
+ }
2394
+ if (isDefinableRef(placeholder)) {
2395
+ if (!isNamedRuleRefForAnotherRule(parser2, key)) tagRule(parser2, key);
2383
2396
  placeholder.define(parser2);
2384
2397
  placeholder._meta.firstSet = parser2._meta.firstSet;
2385
2398
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
2399
+ } else if (isNamedRuleRefForAnotherRule(parser2, key)) {
2400
+ const alias = ref();
2401
+ tagRule(alias, key);
2402
+ alias.define(parser2);
2403
+ alias._meta.firstSet = parser2._meta.firstSet;
2404
+ alias._meta.canMatchNewline = parser2._meta.canMatchNewline;
2405
+ cache[key] = alias;
2386
2406
  } else {
2387
2407
  tagRule(parser2, key);
2388
2408
  cache[key] = parser2;
@@ -3008,7 +3028,7 @@ function leaf(root, fn) {
3008
3028
  }
3009
3029
 
3010
3030
  // src/version.ts
3011
- var PARSEMAN_VERSION = "0.37.0";
3031
+ var PARSEMAN_VERSION = "0.38.0";
3012
3032
 
3013
3033
  // src/cst/host-mode.ts
3014
3034
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3062,7 +3082,7 @@ function firstSetToString(fs2) {
3062
3082
  if (fs2.kind === "empty") return "(empty)";
3063
3083
  return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3064
3084
  }
3065
- var ruleNameOf = (p) => p._ruleName;
3085
+ var ruleNameOf2 = (p) => p._ruleName;
3066
3086
  var SUGGESTIONS = {
3067
3087
  "broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
3068
3088
  "leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating \u2014 use peek(X), which is zero-width AND carries X first-set.",
@@ -3220,7 +3240,7 @@ function detectAntiPatterns(rule, arms) {
3220
3240
  return out;
3221
3241
  }
3222
3242
  function analyzeGating(entry, opts) {
3223
- return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3243
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3224
3244
  }
3225
3245
  function analyzeGatingRules(ruleMap, opts) {
3226
3246
  const raw = [];
@@ -3253,7 +3273,7 @@ function analyzeGatingRules(ruleMap, opts) {
3253
3273
  return;
3254
3274
  }
3255
3275
  const d = def;
3256
- const rule = ruleNameOf(p) ?? enclosingRule;
3276
+ const rule = ruleNameOf2(p) ?? enclosingRule;
3257
3277
  if (d.tag === "choice") {
3258
3278
  raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3259
3279
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
@@ -3272,7 +3292,7 @@ function analyzeGatingRules(ruleMap, opts) {
3272
3292
  }
3273
3293
  for (const k of kids) visit(k, rule);
3274
3294
  };
3275
- for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3295
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3276
3296
  const perRule = /* @__PURE__ */ new Map();
3277
3297
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3278
3298
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3396,7 +3416,7 @@ function childrenOf(d) {
3396
3416
  }
3397
3417
  }
3398
3418
  }
3399
- var ruleNameOf2 = (p) => p._ruleName;
3419
+ var ruleNameOf3 = (p) => p._ruleName;
3400
3420
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
3401
3421
  function payloadKey(p, d) {
3402
3422
  switch (d.tag) {
@@ -3407,7 +3427,7 @@ function payloadKey(p, d) {
3407
3427
  case "keywords":
3408
3428
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
3409
3429
  case "lazy": {
3410
- const n = ruleNameOf2(p);
3430
+ const n = ruleNameOf3(p);
3411
3431
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
3412
3432
  }
3413
3433
  case "label":
@@ -3474,7 +3494,7 @@ var Interner = class {
3474
3494
  var MAX_RENDER = 160;
3475
3495
  function render(p, depth = 3) {
3476
3496
  const d = p._def;
3477
- const name = ruleNameOf2(p);
3497
+ const name = ruleNameOf3(p);
3478
3498
  switch (d.tag) {
3479
3499
  case "literal":
3480
3500
  return `literal('${d.value}')`;
@@ -3653,7 +3673,7 @@ function keywordAlternationHazards(words, boundary) {
3653
3673
  return out;
3654
3674
  }
3655
3675
  function analyzeDuplication(entry, opts) {
3656
- return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3676
+ return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3657
3677
  }
3658
3678
  function assertAnalyzable(ruleMap) {
3659
3679
  for (const [name, rule] of ruleMap) {
@@ -3694,7 +3714,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
3694
3714
  const visit = (p, rule, path2, enclosingNode, enclosingReducer) => {
3695
3715
  if (visited.has(p)) return;
3696
3716
  const d = p._def;
3697
- const own = ruleNameOf2(p);
3717
+ const own = ruleNameOf3(p);
3698
3718
  const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path2 };
3699
3719
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
3700
3720
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
@@ -3707,12 +3727,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
3707
3727
  if (d.tag === "lazy") {
3708
3728
  try {
3709
3729
  const target = d.thunk();
3710
- visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
3730
+ visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
3711
3731
  } catch {
3712
3732
  }
3713
3733
  }
3714
3734
  };
3715
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
3735
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
3716
3736
  const accept = new Set(opts?.accept ?? []);
3717
3737
  const used = /* @__PURE__ */ new Set();
3718
3738
  const keep = (f) => {
@@ -8567,7 +8587,7 @@ function ruleDependencies(ruleMap) {
8567
8587
  const seen = /* @__PURE__ */ new Set();
8568
8588
  const walk2 = (p, isRoot) => {
8569
8589
  const def = p._def;
8570
- const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
8590
+ const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
8571
8591
  if (!isRoot && boundary !== void 0) {
8572
8592
  found.add(boundary);
8573
8593
  return;
@@ -9332,7 +9352,7 @@ function childrenOf3(def) {
9332
9352
  return [];
9333
9353
  }
9334
9354
  }
9335
- var ruleNameOf3 = (c) => c._ruleName;
9355
+ var ruleNameOf4 = (c) => c._ruleName;
9336
9356
  function lazyTarget(c) {
9337
9357
  if (c._def.tag !== "lazy") return null;
9338
9358
  try {
@@ -9514,12 +9534,16 @@ var Serializer = class {
9514
9534
  emitted = /* @__PURE__ */ new Set();
9515
9535
  decls = [];
9516
9536
  ruleValues = /* @__PURE__ */ new Set();
9537
+ localRuleNames = /* @__PURE__ */ new Map();
9517
9538
  ruleMap;
9518
9539
  scanSkip;
9519
9540
  constructor(ruleMap, scanSkip = []) {
9520
9541
  this.ruleMap = ruleMap;
9521
9542
  this.scanSkip = scanSkip;
9522
- for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
9543
+ for (const [name, c] of ruleMap) {
9544
+ this.localRuleNames.set(c, name);
9545
+ this.ruleValues.add(this.body(c));
9546
+ }
9523
9547
  }
9524
9548
  /** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
9525
9549
  * bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
@@ -9530,7 +9554,7 @@ var Serializer = class {
9530
9554
  if (seen.has(c)) return;
9531
9555
  seen.add(c);
9532
9556
  if (c._def.tag === "lazy") {
9533
- if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9557
+ if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9534
9558
  const target = lazyTarget(c);
9535
9559
  if (target) this.assertNoRuleRef(target, seen);
9536
9560
  return;
@@ -9541,7 +9565,7 @@ var Serializer = class {
9541
9565
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
9542
9566
  * `g[name]` ref — only the top-level entry is unwrapped. */
9543
9567
  body(c) {
9544
- return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
9568
+ return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
9545
9569
  }
9546
9570
  run() {
9547
9571
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -9583,7 +9607,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9583
9607
  analyze(c, active) {
9584
9608
  const def = c._def;
9585
9609
  if (def.tag === "lazy") {
9586
- if (ruleNameOf3(c) !== void 0) return;
9610
+ if (ruleNameOf4(c) !== void 0) return;
9587
9611
  const target = lazyTarget(c);
9588
9612
  if (!target) throw new Unserializable("unresolved unnamed ref");
9589
9613
  if (active.has(target)) {
@@ -9635,7 +9659,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9635
9659
  const kid = (x) => this.ref(x, selfOf);
9636
9660
  switch (def.tag) {
9637
9661
  case "lazy": {
9638
- if (ruleNameOf3(c) !== void 0) return `g[${JSON.stringify(ruleNameOf3(c))}]`;
9662
+ const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
9663
+ if (name !== void 0) return `g[${JSON.stringify(name)}]`;
9639
9664
  const target = lazyTarget(c);
9640
9665
  if (selfOf && target === selfOf.comb) return selfOf.var;
9641
9666
  return this.ref(target, selfOf);
@@ -10145,6 +10170,25 @@ function stripTsFromSource(node2, code) {
10145
10170
  function isWordFactory(v2) {
10146
10171
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10147
10172
  }
10173
+ function wordFactoryFromArgs(args, scope, code, mfs) {
10174
+ const [boundaryOrOptsArg, optsArg] = args;
10175
+ if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10176
+ const rawBoundaryOrOpts = boundaryOrOptsArg === void 0 ? void 0 : anyValue(boundaryOrOptsArg, scope, code, mfs);
10177
+ const boundaryOrOpts = rawBoundaryOrOpts === void 0 ? "_0-9A-Za-z" : rawBoundaryOrOpts;
10178
+ const opts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10179
+ if (typeof boundaryOrOpts === "string") {
10180
+ if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) return null;
10181
+ const caseInsensitive2 = typeof opts === "object" && opts !== null && "caseInsensitive" in opts ? opts.caseInsensitive : false;
10182
+ if (typeof caseInsensitive2 !== "boolean") return null;
10183
+ return { tag: "wordFactory", boundary: boundaryOrOpts, caseInsensitive: caseInsensitive2 };
10184
+ }
10185
+ if (typeof boundaryOrOpts !== "object" || boundaryOrOpts === null || Array.isArray(boundaryOrOpts) || opts !== void 0) {
10186
+ return null;
10187
+ }
10188
+ const caseInsensitive = "caseInsensitive" in boundaryOrOpts ? boundaryOrOpts.caseInsensitive : false;
10189
+ if (typeof caseInsensitive !== "boolean") return null;
10190
+ return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10191
+ }
10148
10192
  var SUPPORTED = {
10149
10193
  literal: (...a) => literal(a[0], a[1]),
10150
10194
  regex: (...a) => regex(a[0], a[1]),
@@ -10228,14 +10272,13 @@ function exprToCombi(node2, scope, code, mfs) {
10228
10272
  if (node2.type !== "CallExpression") return null;
10229
10273
  const callee = node2.callee;
10230
10274
  if (callee.type === "CallExpression" && callee.callee.type === "Identifier" && callee.callee.name === "makeWord") {
10231
- const boundaryArg = callee.arguments[0];
10232
10275
  const strArg = node2.arguments[0];
10233
10276
  if (!strArg || strArg.type === "SpreadElement") return null;
10234
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10277
+ const factory2 = wordFactoryFromArgs(callee.arguments, scope, code, mfs);
10235
10278
  const str = anyValue(strArg, scope, code, mfs);
10236
- if (typeof boundary !== "string" || typeof str !== "string") return null;
10279
+ if (factory2 === null || typeof str !== "string") return null;
10237
10280
  try {
10238
- return word(str, boundary);
10281
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10239
10282
  } catch {
10240
10283
  return null;
10241
10284
  }
@@ -10248,7 +10291,7 @@ function exprToCombi(node2, scope, code, mfs) {
10248
10291
  const str = anyValue(strArg, scope, code, mfs);
10249
10292
  if (typeof str !== "string") return null;
10250
10293
  try {
10251
- return word(str, factory2.boundary);
10294
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10252
10295
  } catch {
10253
10296
  return null;
10254
10297
  }
@@ -10558,10 +10601,7 @@ function anyValue(node2, scope, code, mfs) {
10558
10601
  if (node2.type === "CallExpression") {
10559
10602
  const callee = node2.callee;
10560
10603
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10561
- const boundaryArg = node2.arguments[0];
10562
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10563
- if (typeof boundary !== "string") return null;
10564
- return { tag: "wordFactory", boundary };
10604
+ return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
10565
10605
  }
10566
10606
  return exprToCombi(node2, scope, code, mfs);
10567
10607
  }
@@ -10571,10 +10611,7 @@ function evaluateWordFactory(node2, scope, code) {
10571
10611
  if (node2.type !== "CallExpression") return null;
10572
10612
  const callee = node2.callee;
10573
10613
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
10574
- const boundaryArg = node2.arguments[0];
10575
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code) : "_0-9A-Za-z";
10576
- if (typeof boundary !== "string") return null;
10577
- return { tag: "wordFactory", boundary };
10614
+ return wordFactoryFromArgs(node2.arguments, scope, code);
10578
10615
  }
10579
10616
  function evaluateExpr(node2, scope, code, mapFnSources) {
10580
10617
  return exprToCombi(node2, scope, code, mapFnSources);
@@ -10669,6 +10706,11 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10669
10706
  r._ruleName = name;
10670
10707
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
10671
10708
  };
10709
+ const ruleNameOf5 = (r) => r._ruleName;
10710
+ const isNamedRuleRefForAnotherRule2 = (r, key) => {
10711
+ const name = r._def.tag === "lazy" ? ruleNameOf5(r) : void 0;
10712
+ return name !== void 0 && name !== key;
10713
+ };
10672
10714
  for (const key of keys) {
10673
10715
  if (!ruleRefs.has(key)) {
10674
10716
  const r = ref();
@@ -10699,12 +10741,15 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10699
10741
  for (const [key, e] of finalByKey) {
10700
10742
  const val = anyValue(e.value, e.scope, e.code, mapFnSources);
10701
10743
  if (!isCombinator(val)) return null;
10702
- tagRef(val, key);
10744
+ if (val === ruleRefs.get(key)) return null;
10745
+ if (!isNamedRuleRefForAnotherRule2(val, key)) {
10746
+ tagRef(val, key);
10747
+ }
10703
10748
  ruleRefs.get(key).define(val);
10704
10749
  }
10705
10750
  return ruleRefs;
10706
10751
  }
10707
- function isDefinableRef(v2) {
10752
+ function isDefinableRef2(v2) {
10708
10753
  return isCombinator(v2) && typeof v2.define === "function" && v2._def.tag === "lazy";
10709
10754
  }
10710
10755
  function evaluateRefDeclaration(init, name, scope) {
@@ -10726,7 +10771,7 @@ function applyDefineStatement(callExpr, scope, code) {
10726
10771
  if (mem.object.type !== "Identifier" || !mem.object.name) return false;
10727
10772
  const target = scope.get(mem.object.name);
10728
10773
  const refCombi = isScopeEntry(target) ? target.combi : isCombinator(target) ? target : null;
10729
- if (!refCombi || !isDefinableRef(refCombi)) return false;
10774
+ if (!refCombi || !isDefinableRef2(refCombi)) return false;
10730
10775
  const args = callExpr.arguments;
10731
10776
  if (args.length !== 1 || args[0].type === "SpreadElement") return false;
10732
10777
  const inner = anyValue(args[0], scope, code, []);