@futpib/parser 1.0.3 → 1.0.6

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 (262) hide show
  1. package/.claude/settings.local.json +24 -0
  2. package/.github/workflows/main.yml +1 -0
  3. package/build/androidPackageParser.js +30 -32
  4. package/build/arbitraryDalvikBytecode.d.ts +3 -3
  5. package/build/arbitraryDalvikBytecode.js +33 -27
  6. package/build/arbitraryDalvikExecutable.js +55 -17
  7. package/build/arbitraryJava.d.ts +31 -0
  8. package/build/arbitraryJava.js +532 -0
  9. package/build/arbitraryJavaScript.d.ts +3 -0
  10. package/build/arbitraryJavaScript.js +263 -0
  11. package/build/arbitraryJavascript.d.ts +3 -0
  12. package/build/arbitraryJavascript.js +263 -0
  13. package/build/arbitraryZig.d.ts +3 -0
  14. package/build/arbitraryZig.js +240 -0
  15. package/build/arbitraryZipStream.d.ts +1 -1
  16. package/build/arrayParser.js +72 -13
  17. package/build/backsmali.d.ts +4 -3
  18. package/build/backsmali.js +26 -6
  19. package/build/bash.d.ts +89 -0
  20. package/build/bash.js +1 -0
  21. package/build/bashParser.d.ts +6 -0
  22. package/build/bashParser.js +335 -0
  23. package/build/bashParser.test.d.ts +1 -0
  24. package/build/bashParser.test.js +343 -0
  25. package/build/bashParserEdgeCases.test.d.ts +1 -0
  26. package/build/bashParserEdgeCases.test.js +117 -0
  27. package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
  28. package/build/dalvikBytecodeParser/addressConversion.js +334 -0
  29. package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
  30. package/build/dalvikBytecodeParser/formatParsers.js +13 -14
  31. package/build/dalvikBytecodeParser.d.ts +60 -31
  32. package/build/dalvikBytecodeParser.js +92 -35
  33. package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
  34. package/build/dalvikBytecodeParser.test-d.js +268 -0
  35. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
  36. package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
  37. package/build/dalvikBytecodeUnparser.d.ts +2 -2
  38. package/build/dalvikBytecodeUnparser.js +23 -23
  39. package/build/dalvikBytecodeUnparser.test.js +7 -7
  40. package/build/dalvikExecutable.d.ts +3 -3
  41. package/build/dalvikExecutable.test-d.d.ts +1 -0
  42. package/build/dalvikExecutable.test-d.js +59 -0
  43. package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
  44. package/build/dalvikExecutableParser/typedNumbers.js +3 -0
  45. package/build/dalvikExecutableParser.d.ts +2 -1
  46. package/build/dalvikExecutableParser.js +96 -77
  47. package/build/dalvikExecutableParser.test.js +24 -3
  48. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
  49. package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
  50. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
  51. package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
  52. package/build/dalvikExecutableUnparser.d.ts +2 -2
  53. package/build/dalvikExecutableUnparser.test.js +2 -1
  54. package/build/disjunctionParser.d.ts +5 -3
  55. package/build/disjunctionParser.js +79 -17
  56. package/build/disjunctionParser.test-d.d.ts +1 -0
  57. package/build/disjunctionParser.test-d.js +72 -0
  58. package/build/elementSwitchParser.d.ts +4 -0
  59. package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
  60. package/build/elementSwitchParser.test-d.d.ts +1 -0
  61. package/build/elementSwitchParser.test-d.js +44 -0
  62. package/build/exactSequenceParser.d.ts +4 -2
  63. package/build/exactSequenceParser.test-d.d.ts +1 -0
  64. package/build/exactSequenceParser.test-d.js +36 -0
  65. package/build/fetchCid.js +2 -66
  66. package/build/index.d.ts +25 -2
  67. package/build/index.js +23 -1
  68. package/build/index.test.js +16 -1
  69. package/build/inputReader.d.ts +10 -0
  70. package/build/inputReader.js +36 -0
  71. package/build/java.d.ts +502 -0
  72. package/build/java.js +2 -0
  73. package/build/javaKeyStoreParser.js +14 -17
  74. package/build/javaParser.d.ts +51 -0
  75. package/build/javaParser.js +1538 -0
  76. package/build/javaParser.test.d.ts +1 -0
  77. package/build/javaParser.test.js +1287 -0
  78. package/build/javaScript.d.ts +35 -0
  79. package/build/javaScript.js +1 -0
  80. package/build/javaScriptParser.d.ts +9 -0
  81. package/build/javaScriptParser.js +34 -0
  82. package/build/javaScriptUnparser.d.ts +3 -0
  83. package/build/javaScriptUnparser.js +4 -0
  84. package/build/javaScriptUnparser.test.d.ts +1 -0
  85. package/build/javaScriptUnparser.test.js +24 -0
  86. package/build/javaUnparser.d.ts +2 -0
  87. package/build/javaUnparser.js +519 -0
  88. package/build/javaUnparser.test.d.ts +1 -0
  89. package/build/javaUnparser.test.js +24 -0
  90. package/build/javascript.d.ts +35 -0
  91. package/build/javascript.js +1 -0
  92. package/build/javascriptParser.d.ts +9 -0
  93. package/build/javascriptParser.js +34 -0
  94. package/build/javascriptUnparser.d.ts +3 -0
  95. package/build/javascriptUnparser.js +4 -0
  96. package/build/javascriptUnparser.test.d.ts +1 -0
  97. package/build/javascriptUnparser.test.js +24 -0
  98. package/build/jsonParser.js +2 -12
  99. package/build/lazyMessageError.d.ts +3 -0
  100. package/build/lookaheadParser.js +60 -3
  101. package/build/negativeLookaheadParser.js +70 -11
  102. package/build/nonEmptyArrayParser.js +72 -13
  103. package/build/objectParser.d.ts +12 -0
  104. package/build/objectParser.js +31 -0
  105. package/build/objectParser.test-d.d.ts +1 -0
  106. package/build/objectParser.test-d.js +112 -0
  107. package/build/objectParser.test.d.ts +1 -0
  108. package/build/objectParser.test.js +55 -0
  109. package/build/optionalParser.js +69 -10
  110. package/build/parser.d.ts +4 -0
  111. package/build/parser.js +3 -1
  112. package/build/parser.test.js +114 -1
  113. package/build/parserConsumedSequenceParser.js +66 -7
  114. package/build/parserContext.d.ts +6 -0
  115. package/build/parserContext.js +20 -11
  116. package/build/parserError.d.ts +119 -27
  117. package/build/parserError.js +16 -8
  118. package/build/regexpParser.d.ts +2 -0
  119. package/build/regexpParser.js +101 -0
  120. package/build/regexpParser.test.d.ts +1 -0
  121. package/build/regexpParser.test.js +114 -0
  122. package/build/regularExpression.d.ts +63 -0
  123. package/build/regularExpression.js +1 -0
  124. package/build/regularExpressionParser.d.ts +3 -0
  125. package/build/regularExpressionParser.js +600 -0
  126. package/build/regularExpressionParser.test.d.ts +1 -0
  127. package/build/regularExpressionParser.test.js +89 -0
  128. package/build/separatedArrayParser.js +73 -14
  129. package/build/separatedNonEmptyArrayParser.js +73 -14
  130. package/build/sliceBoundedParser.js +62 -5
  131. package/build/smaliParser.d.ts +7 -7
  132. package/build/smaliParser.js +185 -268
  133. package/build/smaliParser.test.js +58 -0
  134. package/build/stringEscapes.d.ts +5 -0
  135. package/build/stringEscapes.js +244 -0
  136. package/build/symbolicExpression.d.ts +29 -0
  137. package/build/symbolicExpression.js +1 -0
  138. package/build/symbolicExpressionParser.d.ts +4 -0
  139. package/build/symbolicExpressionParser.js +123 -0
  140. package/build/symbolicExpressionParser.test.d.ts +1 -0
  141. package/build/symbolicExpressionParser.test.js +289 -0
  142. package/build/terminatedArrayParser.js +113 -38
  143. package/build/terminatedArrayParser.test.js +4 -2
  144. package/build/tupleParser.d.ts +7 -15
  145. package/build/tupleParser.js +1 -0
  146. package/build/unionParser.d.ts +5 -3
  147. package/build/unionParser.js +7 -2
  148. package/build/unionParser.test-d.d.ts +1 -0
  149. package/build/unionParser.test-d.js +72 -0
  150. package/build/unionParser.test.js +10 -11
  151. package/build/zig.d.ts +280 -0
  152. package/build/zig.js +2 -0
  153. package/build/zigParser.d.ts +3 -0
  154. package/build/zigParser.js +1119 -0
  155. package/build/zigParser.test.d.ts +1 -0
  156. package/build/zigParser.test.js +1590 -0
  157. package/build/zigUnparser.d.ts +2 -0
  158. package/build/zigUnparser.js +460 -0
  159. package/build/zigUnparser.test.d.ts +1 -0
  160. package/build/zigUnparser.test.js +24 -0
  161. package/build/zipParser.js +19 -32
  162. package/build/zipUnparser.js +19 -7
  163. package/build/zipUnparser.test.js +1 -1
  164. package/node_modules-@types/s-expression/index.d.ts +5 -0
  165. package/package.json +25 -6
  166. package/src/androidPackageParser.ts +33 -60
  167. package/src/arbitraryDalvikBytecode.ts +39 -31
  168. package/src/arbitraryDalvikExecutable.ts +65 -20
  169. package/src/arbitraryJava.ts +804 -0
  170. package/src/arbitraryJavaScript.ts +410 -0
  171. package/src/arbitraryZig.ts +380 -0
  172. package/src/arrayParser.ts +1 -3
  173. package/src/backsmali.ts +35 -4
  174. package/src/bash.ts +127 -0
  175. package/src/bashParser.test.ts +590 -0
  176. package/src/bashParser.ts +498 -0
  177. package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
  178. package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
  179. package/src/dalvikBytecodeParser.test-d.ts +310 -0
  180. package/src/dalvikBytecodeParser.ts +194 -69
  181. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
  182. package/src/dalvikBytecodeUnparser.test.ts +7 -7
  183. package/src/dalvikBytecodeUnparser.ts +31 -30
  184. package/src/dalvikExecutable.test-d.ts +132 -0
  185. package/src/dalvikExecutable.ts +3 -3
  186. package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
  187. package/src/dalvikExecutableParser.test.ts +37 -3
  188. package/src/dalvikExecutableParser.test.ts.md +163 -2
  189. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  190. package/src/dalvikExecutableParser.ts +121 -139
  191. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
  192. package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
  193. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
  194. package/src/dalvikExecutableUnparser.test.ts +3 -2
  195. package/src/dalvikExecutableUnparser.ts +4 -4
  196. package/src/disjunctionParser.test-d.ts +105 -0
  197. package/src/disjunctionParser.ts +18 -15
  198. package/src/elementSwitchParser.test-d.ts +74 -0
  199. package/src/elementSwitchParser.ts +51 -0
  200. package/src/exactSequenceParser.test-d.ts +43 -0
  201. package/src/exactSequenceParser.ts +13 -8
  202. package/src/fetchCid.ts +2 -76
  203. package/src/index.test.ts +22 -1
  204. package/src/index.ts +119 -2
  205. package/src/inputReader.ts +53 -0
  206. package/src/java.ts +708 -0
  207. package/src/javaKeyStoreParser.ts +18 -32
  208. package/src/javaParser.test.ts +1592 -0
  209. package/src/javaParser.ts +2640 -0
  210. package/src/javaScript.ts +36 -0
  211. package/src/javaScriptParser.ts +57 -0
  212. package/src/javaScriptUnparser.test.ts +37 -0
  213. package/src/javaScriptUnparser.ts +7 -0
  214. package/src/javaUnparser.test.ts +37 -0
  215. package/src/javaUnparser.ts +640 -0
  216. package/src/jsonParser.ts +6 -27
  217. package/src/lookaheadParser.ts +2 -6
  218. package/src/negativeLookaheadParser.ts +1 -3
  219. package/src/nonEmptyArrayParser.ts +1 -3
  220. package/src/objectParser.test-d.ts +152 -0
  221. package/src/objectParser.test.ts +71 -0
  222. package/src/objectParser.ts +69 -0
  223. package/src/optionalParser.ts +1 -3
  224. package/src/parser.test.ts +151 -4
  225. package/src/parser.ts +11 -1
  226. package/src/parserConsumedSequenceParser.ts +2 -4
  227. package/src/parserContext.ts +26 -11
  228. package/src/parserError.ts +17 -3
  229. package/src/regexpParser.test.ts +264 -0
  230. package/src/regexpParser.ts +126 -0
  231. package/src/regularExpression.ts +24 -0
  232. package/src/regularExpressionParser.test.ts +102 -0
  233. package/src/regularExpressionParser.ts +920 -0
  234. package/src/separatedArrayParser.ts +1 -3
  235. package/src/separatedNonEmptyArrayParser.ts +1 -3
  236. package/src/sliceBoundedParser.test.ts +2 -2
  237. package/src/sliceBoundedParser.ts +15 -19
  238. package/src/smaliParser.test.ts +64 -0
  239. package/src/smaliParser.test.ts.md +12 -12
  240. package/src/smaliParser.test.ts.snap +0 -0
  241. package/src/smaliParser.ts +246 -534
  242. package/src/stringEscapes.ts +253 -0
  243. package/src/symbolicExpression.ts +17 -0
  244. package/src/symbolicExpressionParser.test.ts +466 -0
  245. package/src/symbolicExpressionParser.ts +190 -0
  246. package/src/terminatedArrayParser.test.ts +9 -6
  247. package/src/terminatedArrayParser.ts +25 -29
  248. package/src/tupleParser.ts +21 -18
  249. package/src/unionParser.test-d.ts +105 -0
  250. package/src/unionParser.test.ts +18 -17
  251. package/src/unionParser.ts +28 -16
  252. package/src/zig.ts +411 -0
  253. package/src/zigParser.test.ts +1693 -0
  254. package/src/zigParser.ts +1745 -0
  255. package/src/zigUnparser.test.ts +37 -0
  256. package/src/zigUnparser.ts +615 -0
  257. package/src/zipParser.ts +20 -56
  258. package/src/zipUnparser.test.ts +1 -1
  259. package/src/zipUnparser.ts +22 -7
  260. package/tsconfig.json +2 -2
  261. package/build/exactElementSwitchParser.d.ts +0 -3
  262. package/src/exactElementSwitchParser.ts +0 -41
@@ -36,6 +36,8 @@ export type ParserContext<Sequence, Element> = {
36
36
  equals(sequenceA: Sequence, sequenceB: Sequence): boolean;
37
37
 
38
38
  get position(): number;
39
+ get furthestReadPosition(): number;
40
+ get furthestPeekedPosition(): number;
39
41
  peek(offset: number): Promise<Element | undefined>;
40
42
  peekSequence(start: number, end: number): Promise<Sequence | undefined>;
41
43
  skip(offset: number): void;
@@ -46,6 +48,7 @@ export type ParserContext<Sequence, Element> = {
46
48
  lookahead(options?: LookaheadOptions): ParserContext<Sequence, Element>;
47
49
  unlookahead(): void;
48
50
  dispose(): void;
51
+ [Symbol.dispose](): void;
49
52
 
50
53
  invariant<T>(value: T, format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy>;
51
54
  invariantJoin<T>(value: T, childErrors: ParserParsingFailedError[], format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy>;
@@ -117,6 +120,14 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
117
120
  return this._inputReader.position;
118
121
  }
119
122
 
123
+ get furthestReadPosition() {
124
+ return this._inputReader.furthestReadPosition;
125
+ }
126
+
127
+ get furthestPeekedPosition() {
128
+ return this._inputReader.furthestPeekedPosition;
129
+ }
130
+
120
131
  async peek(offset: number): Promise<Element | undefined> {
121
132
  if (
122
133
  this._options.sliceEnd !== undefined
@@ -169,7 +180,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
169
180
  const element = await this.peek(offset);
170
181
 
171
182
  if (element === undefined) {
172
- throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position);
183
+ throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position, this._inputReader.furthestReadPosition, this._inputReader.furthestPeekedPosition);
173
184
  }
174
185
 
175
186
  this.skip(offset + 1);
@@ -181,7 +192,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
181
192
  const sequence = await this.peekSequence(start, end);
182
193
 
183
194
  if (sequence === undefined) {
184
- throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position);
195
+ throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position, this._inputReader.furthestReadPosition, this._inputReader.furthestPeekedPosition);
185
196
  }
186
197
 
187
198
  this.skip(end);
@@ -300,11 +311,15 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
300
311
  this._parentParserContext = undefined;
301
312
  }
302
313
 
314
+ [Symbol.dispose]() {
315
+ this.dispose();
316
+ }
317
+
303
318
  invariant<T>(value: T, format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy> {
304
319
  const parserContext = this;
305
320
 
306
321
  return customInvariant(function (lazyMessage: LazyMessage) {
307
- return new parserContext._options.errorsModule.ParserParsingInvariantError(lazyMessage, parserContext._depth, parserContext.position);
322
+ return new parserContext._options.errorsModule.ParserParsingInvariantError(lazyMessage, parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition);
308
323
  }, value, format, ...formatArguments);
309
324
  }
310
325
 
@@ -316,22 +331,22 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
316
331
 
317
332
  if (errorJoinMode === 'none') {
318
333
  return customInvariant(function (lazyMessage: LazyMessage) {
319
- return new parserContext._options.errorsModule.ParserParsingJoinNoneError(lazyMessage, parserContext._depth, parserContext.position);
334
+ return new parserContext._options.errorsModule.ParserParsingJoinNoneError(lazyMessage, parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition);
320
335
  }, value, format, ...formatArguments);
321
336
  }
322
337
 
323
338
  if (errorJoinMode === 'furthest') {
324
339
  return customInvariant(function (userLazyMessage: LazyMessage) {
325
- let furthestPosition = 0;
340
+ let furthestChildErrorPosition = 0;
326
341
  let furthestChildErrors: ParserParsingFailedError[] = [];
327
342
 
328
343
  for (const childError of childErrors) {
329
- if (childError.position < furthestPosition) {
344
+ if (childError.position < furthestChildErrorPosition) {
330
345
  continue;
331
346
  }
332
347
 
333
- if (childError.position > furthestPosition) {
334
- furthestPosition = childError.position;
348
+ if (childError.position > furthestChildErrorPosition) {
349
+ furthestChildErrorPosition = childError.position;
335
350
  furthestChildErrors = [ childError ];
336
351
  continue;
337
352
  }
@@ -354,7 +369,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
354
369
 
355
370
  return furthestChildError.stack?.split('\n').map(line => ' ' + line);
356
371
  }).join('\n'),
357
- ], parserContext._depth, furthestPosition, furthestChildErrors);
372
+ ], parserContext._depth, furthestChildErrorPosition, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, furthestChildErrors);
358
373
  }, value, format, ...formatArguments);
359
374
  }
360
375
 
@@ -396,7 +411,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
396
411
 
397
412
  return deepestChildError.stack?.split('\n').map(line => ' ' + line);
398
413
  }).join('\n'),
399
- ], deepestDepth, parserContext.position, deepestChildErrors);
414
+ ], deepestDepth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, deepestChildErrors);
400
415
  }, value, format, ...formatArguments);
401
416
  }
402
417
 
@@ -417,7 +432,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
417
432
 
418
433
  return childError.stack?.split('\n').map(line => ' ' + line);
419
434
  }).join('\n'),
420
- ], parserContext._depth, parserContext.position, childErrors);
435
+ ], parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, childErrors);
421
436
  }, value, format, ...formatArguments);
422
437
  }
423
438
 
@@ -23,6 +23,8 @@ type ParserErrorInterface = ParserError;
23
23
  export interface ParserParsingFailedError extends ParserErrorInterface {
24
24
  depth: number;
25
25
  position: number;
26
+ furthestReadPosition: number;
27
+ furthestPeekedPosition: number;
26
28
  }
27
29
 
28
30
  export function isParserParsingFailedError(value: unknown): value is ParserParsingFailedError {
@@ -34,6 +36,10 @@ export function isParserParsingFailedError(value: unknown): value is ParserParsi
34
36
  && typeof value.depth === 'number'
35
37
  && 'position' in value
36
38
  && typeof value.position === 'number'
39
+ && 'furthestReadPosition' in value
40
+ && typeof value.furthestReadPosition === 'number'
41
+ && 'furthestPeekedPosition' in value
42
+ && typeof value.furthestPeekedPosition === 'number'
37
43
  );
38
44
  }
39
45
 
@@ -80,6 +86,8 @@ function createParserErrorModule(
80
86
  message: LazyMessage,
81
87
  public readonly depth: number,
82
88
  public readonly position: number,
89
+ public readonly furthestReadPosition: number,
90
+ public readonly furthestPeekedPosition: number,
83
91
  ) {
84
92
  super(message);
85
93
  }
@@ -102,9 +110,11 @@ function createParserErrorModule(
102
110
  message: LazyMessage,
103
111
  depth: number,
104
112
  position: number,
113
+ furthestReadPosition: number,
114
+ furthestPeekedPosition: number,
105
115
  public readonly childErrors: ParserParsingFailedErrorInterface[],
106
116
  ) {
107
- super(message, depth, position);
117
+ super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
108
118
  }
109
119
  }
110
120
 
@@ -115,9 +125,11 @@ function createParserErrorModule(
115
125
  message: LazyMessage,
116
126
  depth: number,
117
127
  position: number,
128
+ furthestReadPosition: number,
129
+ furthestPeekedPosition: number,
118
130
  public readonly childErrors: ParserParsingFailedErrorInterface[],
119
131
  ) {
120
- super(message, depth, position);
132
+ super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
121
133
  }
122
134
  }
123
135
 
@@ -128,9 +140,11 @@ function createParserErrorModule(
128
140
  message: LazyMessage,
129
141
  depth: number,
130
142
  position: number,
143
+ furthestReadPosition: number,
144
+ furthestPeekedPosition: number,
131
145
  public readonly childErrors: ParserParsingFailedErrorInterface[],
132
146
  ) {
133
- super(message, depth, position);
147
+ super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
134
148
  }
135
149
  }
136
150
 
@@ -0,0 +1,264 @@
1
+ import test from 'ava';
2
+ import * as fc from 'fast-check';
3
+ import { testProp } from '@fast-check/ava';
4
+ import { runParser, runParserWithRemainingInput } from './parser.js';
5
+ import { stringParserInputCompanion } from './parserInputCompanion.js';
6
+ import { createRegExpParser } from './regexpParser.js';
7
+
8
+ test('regexpParser matches digits', async t => {
9
+ const regexpParser = createRegExpParser(/\d+/);
10
+
11
+ const result = await runParser(
12
+ regexpParser,
13
+ '123',
14
+ stringParserInputCompanion,
15
+ );
16
+
17
+ t.is(result[0], '123');
18
+ });
19
+
20
+ test('regexpParser matches at start only', async t => {
21
+ const regexpParser = createRegExpParser(/\d+/);
22
+
23
+ const { output, remainingInput } = await runParserWithRemainingInput(
24
+ regexpParser,
25
+ '123abc',
26
+ stringParserInputCompanion,
27
+ );
28
+
29
+ t.is(output[0], '123');
30
+ t.truthy(remainingInput);
31
+ });
32
+
33
+ test('regexpParser fails when no match at start', async t => {
34
+ const regexpParser = createRegExpParser(/\d+/);
35
+
36
+ await t.throwsAsync(
37
+ runParser(
38
+ regexpParser,
39
+ 'abc123',
40
+ stringParserInputCompanion,
41
+ ),
42
+ );
43
+ });
44
+
45
+ test('regexpParser with capture groups', async t => {
46
+ const regexpParser = createRegExpParser(/(\d+)-(\d+)/);
47
+
48
+ const result = await runParser(
49
+ regexpParser,
50
+ '123-456',
51
+ stringParserInputCompanion,
52
+ );
53
+
54
+ t.is(result[0], '123-456');
55
+ t.is(result[1], '123');
56
+ t.is(result[2], '456');
57
+ });
58
+
59
+ test('regexpParser greedy matching', async t => {
60
+ const regexpParser = createRegExpParser(/a+/);
61
+
62
+ const { output } = await runParserWithRemainingInput(
63
+ regexpParser,
64
+ 'aaab',
65
+ stringParserInputCompanion,
66
+ );
67
+
68
+ t.is(output[0], 'aaa');
69
+ });
70
+
71
+ test('regexpParser with anchored regexp', async t => {
72
+ const regexpParser = createRegExpParser(/^hello/);
73
+
74
+ const { output } = await runParserWithRemainingInput(
75
+ regexpParser,
76
+ 'hello world',
77
+ stringParserInputCompanion,
78
+ );
79
+
80
+ t.is(output[0], 'hello');
81
+ });
82
+
83
+ testProp.serial(
84
+ 'regexpParser matches word characters',
85
+ [
86
+ fc.tuple(
87
+ fc.stringMatching(/^\w+$/),
88
+ fc.stringMatching(/^\W*$/),
89
+ ),
90
+ ],
91
+ async (t, [ word, nonWord ]) => {
92
+ const regexpParser = createRegExpParser(/\w+/);
93
+
94
+ const { output, position } = await runParserWithRemainingInput(
95
+ regexpParser,
96
+ word + nonWord,
97
+ stringParserInputCompanion,
98
+ );
99
+
100
+ t.is(output[0], word);
101
+ t.is(position, word.length);
102
+ },
103
+ {
104
+ verbose: true,
105
+ },
106
+ );
107
+
108
+ // Tests for zero-width/optional patterns at end of input
109
+
110
+ test('regexpParser with star quantifier on empty input', async t => {
111
+ const regexpParser = createRegExpParser(/a*/);
112
+
113
+ const result = await runParser(
114
+ regexpParser,
115
+ '',
116
+ stringParserInputCompanion,
117
+ );
118
+
119
+ t.is(result[0], '');
120
+ });
121
+
122
+ test('regexpParser with optional whitespace on empty input', async t => {
123
+ const regexpParser = createRegExpParser(/[ \t]*/);
124
+
125
+ const result = await runParser(
126
+ regexpParser,
127
+ '',
128
+ stringParserInputCompanion,
129
+ );
130
+
131
+ t.is(result[0], '');
132
+ });
133
+
134
+ test('regexpParser with star quantifier at end of input (no match)', async t => {
135
+ const regexpParser = createRegExpParser(/a*/);
136
+
137
+ const { output } = await runParserWithRemainingInput(
138
+ regexpParser,
139
+ 'bbb',
140
+ stringParserInputCompanion,
141
+ );
142
+
143
+ t.is(output[0], '');
144
+ });
145
+
146
+ test('regexpParser with optional group on empty input', async t => {
147
+ const regexpParser = createRegExpParser(/(?:foo)?/);
148
+
149
+ const result = await runParser(
150
+ regexpParser,
151
+ '',
152
+ stringParserInputCompanion,
153
+ );
154
+
155
+ t.is(result[0], '');
156
+ });
157
+
158
+ // Tests for negative lookahead
159
+
160
+ test('regexpParser with negative lookahead should not match when followed by same char', async t => {
161
+ // This regex should NOT match anything in '||' - the | is followed by another |
162
+ const regexpParser = createRegExpParser(/\|(?!\|)/);
163
+
164
+ await t.throwsAsync(
165
+ runParser(
166
+ regexpParser,
167
+ '||',
168
+ stringParserInputCompanion,
169
+ ),
170
+ );
171
+ });
172
+
173
+ test('regexpParser with negative lookahead should match single char', async t => {
174
+ // This regex should match single '|' when followed by something else
175
+ const regexpParser = createRegExpParser(/\|(?!\|)/);
176
+
177
+ const { output, position, remainingInput } = await runParserWithRemainingInput(
178
+ regexpParser,
179
+ '| ',
180
+ stringParserInputCompanion,
181
+ );
182
+
183
+ t.is(output[0], '|');
184
+ t.is(position, 1); // Consumed 1 character
185
+ t.truthy(remainingInput); // There's remaining input (the space)
186
+ });
187
+
188
+ test('regexpParser should match exact 6-character input', async t => {
189
+ const regexpParser = createRegExpParser(/abcdef/);
190
+
191
+ const result = await runParser(
192
+ regexpParser,
193
+ 'abcdef',
194
+ stringParserInputCompanion,
195
+ );
196
+
197
+ t.is(result[0], 'abcdef');
198
+ });
199
+
200
+ test('regexpParser should match quoted string of length 6', async t => {
201
+ const regexpParser = createRegExpParser(/"[^"]*"/);
202
+
203
+ const result = await runParser(
204
+ regexpParser,
205
+ '"abcd"', // 6 characters total
206
+ stringParserInputCompanion,
207
+ );
208
+
209
+ t.is(result[0], '"abcd"');
210
+ });
211
+
212
+ // Property-based tests for fixed-length patterns
213
+
214
+ testProp(
215
+ 'regexpParser should match exact n-character input for any length',
216
+ [fc.integer({ min: 1, max: 20 })],
217
+ async (t, length) => {
218
+ const input = 'a'.repeat(length);
219
+ const regex = new RegExp(`a{${length}}`);
220
+ const regexpParser = createRegExpParser(regex);
221
+
222
+ const result = await runParser(
223
+ regexpParser,
224
+ input,
225
+ stringParserInputCompanion,
226
+ );
227
+
228
+ t.is(result[0], input);
229
+ },
230
+ );
231
+
232
+ testProp(
233
+ 'regexpParser should match quoted strings of any length',
234
+ [fc.integer({ min: 0, max: 20 })],
235
+ async (t, contentLength) => {
236
+ const content = 'x'.repeat(contentLength);
237
+ const input = `"${content}"`;
238
+ const regexpParser = createRegExpParser(/"[^"]*"/);
239
+
240
+ const result = await runParser(
241
+ regexpParser,
242
+ input,
243
+ stringParserInputCompanion,
244
+ );
245
+
246
+ t.is(result[0], input);
247
+ },
248
+ );
249
+
250
+ testProp(
251
+ 'regexpParser greedy patterns should match any length input',
252
+ [fc.stringMatching(/^[a-z]+$/).filter(s => s.length > 0 && s.length <= 20)],
253
+ async (t, input) => {
254
+ const regexpParser = createRegExpParser(/[a-z]+/);
255
+
256
+ const result = await runParser(
257
+ regexpParser,
258
+ input,
259
+ stringParserInputCompanion,
260
+ );
261
+
262
+ t.is(result[0], input);
263
+ },
264
+ );
@@ -0,0 +1,126 @@
1
+ import { type Parser, setParserName } from './parser.js';
2
+
3
+ export const createRegExpParser = (
4
+ regexp: RegExp,
5
+ ): Parser<RegExpExecArray, string, string> => {
6
+ const regexpParser: Parser<RegExpExecArray, string, string> = async parserContext => {
7
+ let start = 0;
8
+ let window = 1;
9
+ let lastMatch: RegExpExecArray | undefined;
10
+ let reachedEndOfInput = false;
11
+ let inputLength: number | undefined;
12
+
13
+ while (true) {
14
+ const sequence = await parserContext.peekSequence(start, start + window);
15
+
16
+ if (sequence === undefined) {
17
+ if (!reachedEndOfInput) {
18
+ reachedEndOfInput = true;
19
+
20
+ // Binary search to find actual input length
21
+ // We know: length >= start (we've peeked successfully before)
22
+ // We know: length < start + window (current peek failed)
23
+ let lo = start;
24
+ let hi = start + window;
25
+ while (hi - lo > 1) {
26
+ const mid = (lo + hi) >> 1;
27
+ const probe = await parserContext.peekSequence(0, mid);
28
+ if (probe !== undefined) {
29
+ lo = mid;
30
+ } else {
31
+ hi = mid;
32
+ }
33
+ }
34
+ inputLength = lo;
35
+
36
+ // Try matching against the full input
37
+ if (inputLength > 0) {
38
+ const fullInput = await parserContext.peekSequence(0, inputLength);
39
+ if (fullInput !== undefined) {
40
+ const match = regexp.exec(fullInput);
41
+ if (match !== null && match.index === 0) {
42
+ parserContext.skip(match[0].length);
43
+ return match;
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ window = Math.floor(window / 2);
50
+
51
+ if (window === 0) {
52
+ // Get the full sequence we've accumulated to verify matches
53
+ const fullSequence = await parserContext.peekSequence(0, inputLength ?? start);
54
+
55
+ // Verify any previous match is still valid with full context
56
+ // For lookahead/lookbehind assertions, additional input might invalidate a match
57
+ if (fullSequence !== undefined) {
58
+ const verifyMatch = regexp.exec(fullSequence);
59
+ if (verifyMatch !== null && verifyMatch.index === 0) {
60
+ parserContext.skip(verifyMatch[0].length);
61
+ return verifyMatch;
62
+ }
63
+ } else if (lastMatch !== undefined) {
64
+ // No full sequence available but we have a previous match
65
+ parserContext.skip(lastMatch[0].length);
66
+ return lastMatch;
67
+ }
68
+
69
+ // No previous match - try matching against empty string for zero-width patterns (e.g., /a*/, /[ \t]*/)
70
+ const emptyMatch = regexp.exec('');
71
+ if (emptyMatch !== null && emptyMatch.index === 0) {
72
+ return emptyMatch;
73
+ }
74
+
75
+ return parserContext.invariant(false, 'Unexpected end of input without regex match');
76
+ }
77
+
78
+ continue;
79
+ }
80
+
81
+ const fullSequence = await parserContext.peekSequence(0, inputLength ?? start + window);
82
+
83
+ if (fullSequence === undefined) {
84
+ continue;
85
+ }
86
+
87
+ const match = regexp.exec(fullSequence);
88
+
89
+ if (match === null || match.index !== 0) {
90
+ if (lastMatch !== undefined) {
91
+ // Verify lastMatch is still valid with current full context
92
+ // For lookahead/lookbehind assertions, a match on shorter input might be
93
+ // invalidated by additional input (e.g., /\|(?!\|)/ matches '|' but not '||')
94
+ const verifyMatch = regexp.exec(fullSequence);
95
+ if (verifyMatch !== null && verifyMatch.index === 0) {
96
+ parserContext.skip(verifyMatch[0].length);
97
+ return verifyMatch;
98
+ }
99
+ // lastMatch was invalidated by additional context
100
+ lastMatch = undefined;
101
+ }
102
+
103
+ if (reachedEndOfInput) {
104
+ parserContext.invariant(
105
+ false,
106
+ 'Regex did not match at start of input',
107
+ );
108
+ }
109
+
110
+ start += window;
111
+ window *= 2;
112
+
113
+ continue;
114
+ }
115
+
116
+ lastMatch = match;
117
+
118
+ start += window;
119
+ window *= 2;
120
+ }
121
+ };
122
+
123
+ setParserName(regexpParser, regexp.toString());
124
+
125
+ return regexpParser;
126
+ };
@@ -0,0 +1,24 @@
1
+ export type CodePointRange = {
2
+ start: number;
3
+ end: number;
4
+ };
5
+
6
+ export type CharacterSet =
7
+ | { type: 'empty' }
8
+ | { type: 'node'; range: CodePointRange; left: CharacterSet; right: CharacterSet };
9
+
10
+ export type RepeatBounds = number | { min: number; max?: number } | { min?: number; max: number };
11
+
12
+ export type RegularExpression =
13
+ | { type: 'epsilon' }
14
+ | { type: 'literal'; charset: CharacterSet }
15
+ | { type: 'concat'; left: RegularExpression; right: RegularExpression }
16
+ | { type: 'union'; left: RegularExpression; right: RegularExpression }
17
+ | { type: 'star'; inner: RegularExpression }
18
+ | { type: 'plus'; inner: RegularExpression }
19
+ | { type: 'optional'; inner: RegularExpression }
20
+ | { type: 'repeat'; inner: RegularExpression; bounds: RepeatBounds }
21
+ | { type: 'capture-group'; inner: RegularExpression; name?: string }
22
+ | { type: 'lookahead'; isPositive: boolean; inner: RegularExpression; right: RegularExpression }
23
+ | { type: 'start-anchor'; left: RegularExpression; right: RegularExpression }
24
+ | { type: 'end-anchor'; left: RegularExpression; right: RegularExpression };
@@ -0,0 +1,102 @@
1
+ import { testProp, fc } from '@fast-check/ava';
2
+ import { regularExpressionParser } from './regularExpressionParser.js';
3
+
4
+ const seed = process.env.SEED ? Number(process.env.SEED) : undefined;
5
+
6
+ // Import directly from file path to bypass package exports
7
+ // eslint-disable-next-line import/no-unresolved
8
+ import { parseRegExpString } from '../node_modules/@gruhn/regex-utils/dist/regex-parser.js';
9
+ import { runParser } from './parser.js';
10
+ import { stringParserInputCompanion } from './parserInputCompanion.js';
11
+ import { arbitrarilySlicedAsyncIterator } from './arbitrarilySlicedAsyncInterator.js';
12
+ import type { RegularExpression, CharacterSet } from './regularExpression.js';
13
+
14
+ // Normalize AST for comparison - removes hashes from CharSets and normalizes structure
15
+ function normalizeCharacterSet(charset: CharacterSet): CharacterSet {
16
+ if (charset.type === 'empty') {
17
+ return { type: 'empty' };
18
+ }
19
+ return {
20
+ type: 'node',
21
+ range: { start: charset.range.start, end: charset.range.end },
22
+ left: normalizeCharacterSet(charset.left),
23
+ right: normalizeCharacterSet(charset.right),
24
+ };
25
+ }
26
+
27
+ function normalizeRegularExpression(ast: RegularExpression): RegularExpression {
28
+ switch (ast.type) {
29
+ case 'epsilon':
30
+ return { type: 'epsilon' };
31
+ case 'literal':
32
+ return { type: 'literal', charset: normalizeCharacterSet(ast.charset) };
33
+ case 'concat':
34
+ return { type: 'concat', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
35
+ case 'union':
36
+ return { type: 'union', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
37
+ case 'star':
38
+ return { type: 'star', inner: normalizeRegularExpression(ast.inner) };
39
+ case 'plus':
40
+ return { type: 'plus', inner: normalizeRegularExpression(ast.inner) };
41
+ case 'optional':
42
+ return { type: 'optional', inner: normalizeRegularExpression(ast.inner) };
43
+ case 'repeat':
44
+ return { type: 'repeat', inner: normalizeRegularExpression(ast.inner), bounds: ast.bounds };
45
+ case 'capture-group':
46
+ if (ast.name !== undefined) {
47
+ return { type: 'capture-group', inner: normalizeRegularExpression(ast.inner), name: ast.name };
48
+ }
49
+ return { type: 'capture-group', inner: normalizeRegularExpression(ast.inner) };
50
+ case 'lookahead':
51
+ return { type: 'lookahead', isPositive: ast.isPositive, inner: normalizeRegularExpression(ast.inner), right: normalizeRegularExpression(ast.right) };
52
+ case 'start-anchor':
53
+ return { type: 'start-anchor', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
54
+ case 'end-anchor':
55
+ return { type: 'end-anchor', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
56
+ }
57
+ }
58
+
59
+ // Generate regex patterns that are likely to be supported
60
+ const supportedRegexArbitrary = fc.stringMatching(
61
+ /^([a-zA-Z0-9]|\\[dDwWsS.]|\[(\^)?([a-zA-Z0-9](-[a-zA-Z0-9])?|\\[dDwWsS])*\]|\.|\((\?[:=!])?[a-zA-Z0-9]*\)|[*+?]|\{[0-9]+(,[0-9]*)?\}|\||\^|\$)*$/,
62
+ ).filter(s => {
63
+ // Filter out patterns that JavaScript doesn't support
64
+ try {
65
+ new RegExp(s);
66
+ } catch {
67
+ return false;
68
+ }
69
+ // Filter out patterns that @gruhn/regex-utils doesn't support
70
+ try {
71
+ parseRegExpString(s);
72
+ } catch {
73
+ return false;
74
+ }
75
+ // Filter out quantified lookaheads - @gruhn/regex-utils has a bug where it treats
76
+ // quantifiers after lookaheads as literals instead of quantifiers.
77
+ // See: https://github.com/gruhn/regex-utils/issues/13
78
+ // JavaScript allows (?=a){2} but @gruhn/regex-utils parses {2} as literal text.
79
+ if (/\(\?[=!][^)]*\)[*+?]|\(\?[=!][^)]*\)\{[0-9]/.test(s)) {
80
+ return false;
81
+ }
82
+ return true;
83
+ });
84
+
85
+ testProp(
86
+ 'regularExpressionParser matches @gruhn/regex-utils',
87
+ [
88
+ arbitrarilySlicedAsyncIterator(supportedRegexArbitrary),
89
+ ],
90
+ async (t, [regexStr, regexStringChunkIterator]) => {
91
+ const expected = normalizeRegularExpression(parseRegExpString(regexStr));
92
+ const actual = normalizeRegularExpression(await runParser(regularExpressionParser, regexStringChunkIterator, stringParserInputCompanion, {
93
+ errorJoinMode: 'none',
94
+ }));
95
+
96
+ t.deepEqual(actual, expected);
97
+ },
98
+ {
99
+ verbose: true,
100
+ seed,
101
+ },
102
+ );