parseman 0.61.1 → 0.61.2

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.
@@ -3628,7 +3628,7 @@ function regex(pattern, flags = "") {
3628
3628
  }
3629
3629
 
3630
3630
  // src/version.ts
3631
- var PARSEMAN_VERSION = "0.61.1";
3631
+ var PARSEMAN_VERSION = "0.61.2";
3632
3632
 
3633
3633
  // src/table/lex-body.ts
3634
3634
  function buildLexBodyRecognizer(spec, constants) {
package/dist/version.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export declare const PARSEMAN_VERSION = "0.61.1";
16
+ export declare const PARSEMAN_VERSION = "0.61.2";
17
17
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parseman",
3
- "version": "0.61.1",
3
+ "version": "0.61.2",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "engines": {
6
6
  "node": "^20.19.0 || >=22.13.0"
@@ -184,7 +184,9 @@ export type AssemblyProfile = {
184
184
  /** The rule each corpus file is parsed from, e.g. `'Stylesheet'`. A grammar
185
185
  * without this rule keeps the static ranking. */
186
186
  readonly entry: string
187
- /** Paths of representative inputs. */
187
+ /** Paths of representative inputs. At least one must have content, and each that
188
+ * does has to parse in full without recovery, or the build fails. A file with only
189
+ * whitespace counts as empty. */
188
190
  readonly files: readonly string[]
189
191
  }
190
192
 
@@ -2376,29 +2378,44 @@ function transformMacroImpl(
2376
2378
  return sites
2377
2379
  }
2378
2380
  /** Measured site calls over the optional corpus, or `undefined` for the static
2379
- * ranking. A file whose parse throws still contributes the calls it made. */
2381
+ * ranking. A corpus that cannot run fails the build: a static ranking standing in
2382
+ * for the measured one configured is a slower parser that nothing reports. */
2380
2383
  const profileOf = (compiled: CompiledRuleMapTable): Int32Array | undefined => {
2381
2384
  // Only the default AST/no-lines table carries an assembly, so only it is profiled.
2382
2385
  if (assemblyProfile === undefined || compiled.hostMode !== 'ast' || compiled.prog.lines === 1
2383
2386
  || !(assemblyProfile.entry in compiled.prog.rules)) return undefined
2384
- let parsed = 0
2387
+ const refuse = (why: string): never => { throw new Error(`${id} — assemblyProfile: ${why}`) }
2388
+ if (corpusTexts(assemblyProfile).every(text => text.trim() === '')) refuse('`files` has no non-empty file')
2389
+ let thrower: unknown
2385
2390
  // A builder re-lowered from carried IR is inert at build time; for COUNTING
2386
2391
  // only, a plain node stands in for it, so the corpus can run past it.
2387
2392
  const fns = compiled.prog.fns.map(fn => {
2388
2393
  const type = (fn as { [INERT_BUILDER]?: string } | null)?.[INERT_BUILDER]
2389
- return type === undefined ? fn
2390
- : (children: unknown, _fields: unknown, span: unknown) => ({ type, span, children })
2394
+ if (type !== undefined) return (children: unknown, _fields: unknown, span: unknown) => ({ type, span, children })
2395
+ return typeof fn !== 'function' ? fn : new Proxy(fn, {
2396
+ apply(target, self, args) {
2397
+ try { return Reflect.apply(target, self, args) as unknown } catch (e) { thrower ??= target; throw e }
2398
+ },
2399
+ })
2391
2400
  })
2392
- const counts = countSiteVisits(compiled.prog, rules => {
2401
+ return countSiteVisits(compiled.prog, rules => {
2393
2402
  const entry = rules[assemblyProfile.entry]!
2394
- for (const text of corpusTexts(assemblyProfile)) {
2395
- try { if (run(entry, text).ok) parsed++ } catch { /* the calls it made still count */ }
2396
- }
2403
+ corpusTexts(assemblyProfile).forEach((text, i) => {
2404
+ const file = assemblyProfile.files[i]!
2405
+ if (text.trim() === '') return
2406
+ let result
2407
+ try { result = run(entry, text) } catch (e) {
2408
+ const source = String(thrower).replace(/\s+/g, ' ').slice(0, 200)
2409
+ throw new Error(`${id} — assemblyProfile: ${thrower === undefined ? 'the parse' : `gate or reducer \`${source}\``} threw on ${file}: ${String(e)}`, { cause: e })
2410
+ }
2411
+ if (!result.ok || result.unconsumedFrom !== null) {
2412
+ refuse(`${file} does not parse from rule "${assemblyProfile.entry}": it stops at offset ${result.ok ? result.unconsumedFrom : result.span.end}`)
2413
+ }
2414
+ if (result.errors.length > 0) {
2415
+ refuse(`${file} parses only by recovering from ${result.errors.length} error(s), first at offset ${result.errors[0]!.span.start}`)
2416
+ }
2417
+ })
2397
2418
  }, fns)
2398
- if (parsed > 0) return counts
2399
- // Not `warn()`: nothing fell back to the interpreter — the build is only untuned.
2400
- warnings.push(`${id} — assemblyProfile: no corpus file parsed from rule "${assemblyProfile.entry}"; using the static ranking`)
2401
- return undefined
2402
2419
  }
2403
2420
  /** The fused table's replacement, with its selective default assembly if any. */
2404
2421
  const assembledReplacement = (compiled: CompiledRuleMapTable, metadata: string, select: ReadonlySet<number>): string =>
package/src/version.ts CHANGED
@@ -13,4 +13,4 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export const PARSEMAN_VERSION = '0.61.1'
16
+ export const PARSEMAN_VERSION = '0.61.2'