parseman 0.50.1 → 0.50.3

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.
Files changed (53) hide show
  1. package/README.md +14 -10
  2. package/dist/analysis/diagnostics.js +431 -219
  3. package/dist/analysis/diagnostics.js.map +3 -3
  4. package/dist/cli/index.js +421 -184
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/combinators/choice.d.ts.map +1 -1
  7. package/dist/combinators/grammar.d.ts.map +1 -1
  8. package/dist/combinators/keywords.d.ts.map +1 -1
  9. package/dist/combinators/literal.d.ts.map +1 -1
  10. package/dist/combinators/map.d.ts.map +1 -1
  11. package/dist/combinators/ref.d.ts.map +1 -1
  12. package/dist/combinators/regex.d.ts.map +1 -1
  13. package/dist/combinators/repeat.d.ts.map +1 -1
  14. package/dist/combinators/scalar.d.ts +6 -0
  15. package/dist/combinators/scalar.d.ts.map +1 -0
  16. package/dist/combinators/sequence.d.ts.map +1 -1
  17. package/dist/combinators/trivia-skip.d.ts +2 -0
  18. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  19. package/dist/compiler/token-capability.js +0 -3
  20. package/dist/compiler/token-capability.js.map +2 -2
  21. package/dist/index.js +422 -185
  22. package/dist/index.js.map +3 -3
  23. package/dist/language-service/index.js +68 -172
  24. package/dist/language-service/index.js.map +3 -3
  25. package/dist/parse-context.d.ts.map +1 -1
  26. package/dist/plugin/index.js +467 -299
  27. package/dist/plugin/index.js.map +3 -3
  28. package/dist/run/index.js +1 -0
  29. package/dist/run/index.js.map +1 -1
  30. package/dist/spec/index.js +399 -277
  31. package/dist/spec/index.js.map +3 -3
  32. package/dist/table/emit-assembly.d.ts.map +1 -1
  33. package/dist/table/index.js +307 -181
  34. package/dist/table/index.js.map +3 -3
  35. package/dist/types.d.ts +8 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/package.json +4 -2
  39. package/src/combinators/choice.ts +34 -1
  40. package/src/combinators/grammar.ts +39 -0
  41. package/src/combinators/keywords.ts +14 -3
  42. package/src/combinators/literal.ts +26 -0
  43. package/src/combinators/map.ts +60 -0
  44. package/src/combinators/ref.ts +26 -7
  45. package/src/combinators/regex.ts +23 -0
  46. package/src/combinators/repeat.ts +109 -16
  47. package/src/combinators/scalar.ts +30 -0
  48. package/src/combinators/sequence.ts +76 -161
  49. package/src/combinators/trivia-skip.ts +14 -1
  50. package/src/parse-context.ts +1 -0
  51. package/src/table/emit-assembly.ts +39 -16
  52. package/src/types.ts +8 -0
  53. package/src/version.ts +1 -1
@@ -259,6 +259,16 @@ function q(s: string): string {
259
259
  return JSON.stringify(s)
260
260
  }
261
261
 
262
+ /**
263
+ * A code-unit lookup is a complete first-code-point decision only for a class
264
+ * with no non-ASCII range. At EOF `charCodeAt` yields NaN; lone surrogates and
265
+ * the leading surrogate of an astral point are all outside the 128-slot array,
266
+ * so each reads `undefined` and declines exactly as `classHas(lead(...))` does.
267
+ */
268
+ function isAsciiOnlyClass(cls: ResolvedClass | null | undefined): cls is ResolvedClass {
269
+ return cls !== null && cls !== undefined && cls.hi.length === 0
270
+ }
271
+
262
272
  /**
263
273
  * WHICH SIDE SINKS A MARK IN THIS ASSEMBLY HAS TO COVER.
264
274
  *
@@ -1259,12 +1269,15 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
1259
1269
 
1260
1270
  case OP_GATE: {
1261
1271
  const child = link(code[ip + 2]!)
1262
- const ci = classes.push([t.cc[code[ip + 1]!]!]) - 1
1272
+ const resolved = t.cc[code[ip + 1]!]!
1273
+ const ci = classes.push([resolved]) - 1
1263
1274
  classPlan.push([code[ip + 1]!])
1264
1275
  const cls = hoist('cl', `CLS[${ci}][0]`)
1265
1276
  const xf = fxRef(code[ip + 3]!)
1266
1277
  return `${head}
1267
- if(!classHas(${cls},lead(input,pos))){ctx._fe=pos;ctx._fx=${xf};return FAIL}
1278
+ if(${staticBuild && isAsciiOnlyClass(resolved)
1279
+ ? `${cls}.ascii[input.charCodeAt(pos)]!==1`
1280
+ : `!classHas(${cls},lead(input,pos))`}){ctx._fe=pos;ctx._fx=${xf};return FAIL}
1268
1281
  return ${child}(input,pos,ctx)
1269
1282
  }`
1270
1283
  }
@@ -1700,10 +1713,11 @@ return v
1700
1713
  // array index feeding ONE call site, which is the megamorphic site
1701
1714
  // this unit exists to remove.
1702
1715
  const armSwitch = arms.map((a, i) => `case ${i}:v=${a}(input,pos,ctx);break`).join('\n')
1716
+ const directAscii = staticBuild && table.hi.length === 0
1703
1717
  return `${head}
1704
- const c=lead(input,pos)
1718
+ const c=${directAscii ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
1705
1719
  let arm=-1
1706
- if(c>=0&&c<128){const a=${asc}[c];if(a!==0)arm=a-1}
1720
+ if(${directAscii ? 'c<128' : 'c>=0&&c<128'}){const a=${asc}[c];if(a!==0)arm=a-1}
1707
1721
  ${hiArr.length === 0 ? '' : `else if(c>=128){const h=${hoist('hi', `DISP[${di}].hi`)}
1708
1722
  for(let i=0;i<h.length;i+=3){if(c>=h[i]&&c<=h[i+1]){arm=h[i+2];break}}}`}
1709
1723
  if(arm>=0){
@@ -1729,7 +1743,11 @@ return FAIL
1729
1743
  const di = code[ip + 1]!
1730
1744
  const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
1731
1745
  const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
1732
- const gateRefs = Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
1746
+ const maskable = n <= 32
1747
+ const directAsciiMask = staticBuild && maskable && gates.every(isAsciiOnlyClass)
1748
+ const gateRefs = directAsciiMask
1749
+ ? []
1750
+ : Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
1733
1751
  type ChoicePretest = {
1734
1752
  readonly scalar?: readonly string[]
1735
1753
  readonly sequence?: ScalarSequenceRef
@@ -1754,7 +1772,6 @@ return FAIL
1754
1772
  const sequence = largeChoiceScalarSequences.get(code[base + i]!)
1755
1773
  return sequence === undefined ? undefined : { sequence: scalarSequenceRef(sequence) }
1756
1774
  })
1757
- const maskable = n <= 32
1758
1775
  const maskName = maskable
1759
1776
  ? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
1760
1777
  : ''
@@ -1856,16 +1873,16 @@ ${merge}
1856
1873
  ctx._fe=pos;ctx._fx=acc??${choiceFx}
1857
1874
  return FAIL`
1858
1875
  return `${head}
1859
- const c=lead(input,pos)
1876
+ const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
1860
1877
  ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
1861
1878
  ${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join('\n')}
1862
1879
  ${maskable ? `if(c<128){
1863
- const bits=${maskName}[c<0?128:c]
1880
+ const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
1864
1881
  if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
1865
1882
  ${lazyArms(true)}
1866
- }else{
1883
+ }${directAsciiMask ? '' : `else{
1867
1884
  ${lazyArms(false)}
1868
- }
1885
+ }`}
1869
1886
  ` : lazyArms(false)}
1870
1887
  ${finish}
1871
1888
  }`
@@ -1920,7 +1937,7 @@ if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0
1920
1937
  ${rollbackFor(i)}
1921
1938
  }${routeMiss}${sequenceMiss}`
1922
1939
  }).join('\n') : ''
1923
- const generalArms = arms.map((arm, i) => {
1940
+ const generalArms = directAsciiMask ? '' : arms.map((arm, i) => {
1924
1941
  const pretest = pretests[i]
1925
1942
  const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
1926
1943
  const scalarCondition = pretest?.scalar === undefined ? ''
@@ -1963,12 +1980,12 @@ ${rollbackFor(i)}
1963
1980
  // arm can run, so the choice's encoded flat expected set is already
1964
1981
  // the exact result. Do not walk the arm ladder merely to rebuild it.
1965
1982
  return `${head}
1966
- const c=lead(input,pos)
1983
+ const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
1967
1984
  ${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
1968
1985
  let acc
1969
1986
  let best=pos
1970
1987
  ${maskable ? `if(c<128){
1971
- const bits=${maskName}[c<0?128:c]
1988
+ const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
1972
1989
  if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
1973
1990
  ${startFailureExact ? '' : 'let prev=0'}
1974
1991
  ${maskArms}
@@ -2076,14 +2093,20 @@ return [key,v]
2076
2093
  const itemFx = reportItem ? fxRef(code[ip + 6]!) : 'EMPTY_FX'
2077
2094
  const collect = op === OP_REP
2078
2095
  let itemCls: string | undefined
2096
+ let itemAscii = false
2079
2097
  if (!REC) {
2080
2098
  const itemClassIndex = sepIp < 0 ? code[ip + 7]! : -1
2081
2099
  if (itemClassIndex >= 0) {
2082
- const ci = classes.push([t.cc[itemClassIndex]!]) - 1
2100
+ const resolved = t.cc[itemClassIndex]!
2101
+ const ci = classes.push([resolved]) - 1
2083
2102
  classPlan.push([itemClassIndex])
2084
2103
  itemCls = hoist('ri', `CLS[${ci}][0]`)
2104
+ itemAscii = staticBuild && isAsciiOnlyClass(resolved)
2085
2105
  }
2086
2106
  }
2107
+ const itemMiss = (at: string): string => itemAscii
2108
+ ? `${itemCls}.ascii[input.charCodeAt(${at})]!==1`
2109
+ : `!classHas(${itemCls},lead(input,${at}))`
2087
2110
  const skipBeforeFirst = sepIp < 0 && min === 0
2088
2111
  const p = tmp()
2089
2112
  // `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
@@ -2199,7 +2222,7 @@ const out=${collect ? '[]' : 'undefined'}
2199
2222
  ${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
2200
2223
  let count=0
2201
2224
  for(;;){
2202
- ${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}&&!classHas(${itemCls},lead(input,cur)))break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
2225
+ ${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}&&${itemMiss('cur')})break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
2203
2226
  ${markBody}
2204
2227
  let itemStart=cur
2205
2228
  let sepEnd=-1
@@ -2221,7 +2244,7 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
2221
2244
  ${rb}
2222
2245
  ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
2223
2246
  }
2224
- ${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&!classHas(${itemCls},lead(input,itemStart))){
2247
+ ${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${itemMiss('itemStart')}){
2225
2248
  ${rb}
2226
2249
  ${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
2227
2250
  }
package/src/types.ts CHANGED
@@ -115,6 +115,10 @@ export type Combinator<T> = {
115
115
  readonly _tag: string
116
116
  readonly _meta: ParserMeta
117
117
  readonly _def: ParserDef
118
+ /** Experimental strict interpreter ABI: end >= 0 succeeds, ~failurePos fails. */
119
+ readonly _parseScalar?: ((input: string, pos: number, ctx: ParseContext) => number) | undefined
120
+ /** Construction-owned sticky matcher for exact terminal fusion. */
121
+ readonly _stickyRegex?: RegExp
118
122
  parse(input: string, pos: number, ctx: ParseContext): ParseResult<T>
119
123
  }
120
124
 
@@ -311,6 +315,8 @@ export type ParseContext = {
311
315
  _fx?: string[] | undefined
312
316
  /** Framework-internal compiled-output committed-failure flag. */
313
317
  _fc?: boolean | undefined
318
+ /** Experimental strict interpreter semantic-value register. */
319
+ _sv?: unknown
314
320
  /**
315
321
  * When set by completionsAt(), tracks the highest-position ParseFail seen
316
322
  * during parsing up to _probe.offset. Used to return completions at the cursor
@@ -427,6 +433,8 @@ export type ParserMeta = {
427
433
  isTrivia: boolean
428
434
  /** User-defined labels for labeled trivia arms (`label(name, parser)`). */
429
435
  triviaKindLabels?: readonly string[]
436
+ /** Preclassified lightweight trivia scanner for the ordinary skip path. */
437
+ triviaScanner?: ((input: string, cur: number) => number) | null
430
438
  /** Set only by `classifiedTrivia()`: each root-visible category is a separate
431
439
  * grammar arm rather than an arbitrary label on a broad recognizer. */
432
440
  rootTriviaClassified?: true
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.50.1'
16
+ export const PARSEMAN_VERSION = '0.50.3'