@futpib/parser 1.0.4 → 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 (250) 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 +6 -1
  20. package/build/bashParser.js +131 -90
  21. package/build/bashParser.test.js +162 -0
  22. package/build/bashParserEdgeCases.test.d.ts +1 -0
  23. package/build/bashParserEdgeCases.test.js +117 -0
  24. package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
  25. package/build/dalvikBytecodeParser/addressConversion.js +334 -0
  26. package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
  27. package/build/dalvikBytecodeParser/formatParsers.js +13 -14
  28. package/build/dalvikBytecodeParser.d.ts +60 -31
  29. package/build/dalvikBytecodeParser.js +92 -35
  30. package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
  31. package/build/dalvikBytecodeParser.test-d.js +268 -0
  32. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
  33. package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
  34. package/build/dalvikBytecodeUnparser.d.ts +2 -2
  35. package/build/dalvikBytecodeUnparser.js +23 -23
  36. package/build/dalvikBytecodeUnparser.test.js +7 -7
  37. package/build/dalvikExecutable.d.ts +3 -3
  38. package/build/dalvikExecutable.test-d.d.ts +1 -0
  39. package/build/dalvikExecutable.test-d.js +59 -0
  40. package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
  41. package/build/dalvikExecutableParser/typedNumbers.js +3 -0
  42. package/build/dalvikExecutableParser.d.ts +2 -1
  43. package/build/dalvikExecutableParser.js +96 -77
  44. package/build/dalvikExecutableParser.test.js +24 -3
  45. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
  46. package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
  47. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
  48. package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
  49. package/build/dalvikExecutableUnparser.d.ts +2 -2
  50. package/build/dalvikExecutableUnparser.test.js +2 -1
  51. package/build/disjunctionParser.d.ts +5 -3
  52. package/build/disjunctionParser.js +79 -17
  53. package/build/disjunctionParser.test-d.d.ts +1 -0
  54. package/build/disjunctionParser.test-d.js +72 -0
  55. package/build/elementSwitchParser.d.ts +4 -0
  56. package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
  57. package/build/elementSwitchParser.test-d.d.ts +1 -0
  58. package/build/elementSwitchParser.test-d.js +44 -0
  59. package/build/exactSequenceParser.d.ts +4 -2
  60. package/build/exactSequenceParser.test-d.d.ts +1 -0
  61. package/build/exactSequenceParser.test-d.js +36 -0
  62. package/build/fetchCid.js +2 -66
  63. package/build/index.d.ts +3 -2
  64. package/build/index.js +2 -1
  65. package/build/index.test.js +16 -1
  66. package/build/inputReader.d.ts +10 -0
  67. package/build/inputReader.js +36 -0
  68. package/build/java.d.ts +502 -0
  69. package/build/java.js +2 -0
  70. package/build/javaKeyStoreParser.js +14 -17
  71. package/build/javaParser.d.ts +51 -0
  72. package/build/javaParser.js +1538 -0
  73. package/build/javaParser.test.d.ts +1 -0
  74. package/build/javaParser.test.js +1287 -0
  75. package/build/javaScript.d.ts +35 -0
  76. package/build/javaScript.js +1 -0
  77. package/build/javaScriptParser.d.ts +9 -0
  78. package/build/javaScriptParser.js +34 -0
  79. package/build/javaScriptUnparser.d.ts +3 -0
  80. package/build/javaScriptUnparser.js +4 -0
  81. package/build/javaScriptUnparser.test.d.ts +1 -0
  82. package/build/javaScriptUnparser.test.js +24 -0
  83. package/build/javaUnparser.d.ts +2 -0
  84. package/build/javaUnparser.js +519 -0
  85. package/build/javaUnparser.test.d.ts +1 -0
  86. package/build/javaUnparser.test.js +24 -0
  87. package/build/javascript.d.ts +35 -0
  88. package/build/javascript.js +1 -0
  89. package/build/javascriptParser.d.ts +9 -0
  90. package/build/javascriptParser.js +34 -0
  91. package/build/javascriptUnparser.d.ts +3 -0
  92. package/build/javascriptUnparser.js +4 -0
  93. package/build/javascriptUnparser.test.d.ts +1 -0
  94. package/build/javascriptUnparser.test.js +24 -0
  95. package/build/jsonParser.js +2 -12
  96. package/build/lazyMessageError.d.ts +3 -0
  97. package/build/lookaheadParser.js +60 -3
  98. package/build/negativeLookaheadParser.js +70 -11
  99. package/build/nonEmptyArrayParser.js +72 -13
  100. package/build/objectParser.d.ts +12 -0
  101. package/build/objectParser.js +31 -0
  102. package/build/objectParser.test-d.d.ts +1 -0
  103. package/build/objectParser.test-d.js +112 -0
  104. package/build/objectParser.test.d.ts +1 -0
  105. package/build/objectParser.test.js +55 -0
  106. package/build/optionalParser.js +69 -10
  107. package/build/parser.d.ts +4 -0
  108. package/build/parser.js +3 -1
  109. package/build/parser.test.js +114 -1
  110. package/build/parserConsumedSequenceParser.js +66 -7
  111. package/build/parserContext.d.ts +6 -0
  112. package/build/parserContext.js +20 -11
  113. package/build/parserError.d.ts +119 -27
  114. package/build/parserError.js +16 -8
  115. package/build/regexpParser.js +33 -3
  116. package/build/regexpParser.test.js +31 -0
  117. package/build/regularExpressionParser.js +35 -15
  118. package/build/separatedArrayParser.js +73 -14
  119. package/build/separatedNonEmptyArrayParser.js +73 -14
  120. package/build/sliceBoundedParser.js +62 -5
  121. package/build/smaliParser.d.ts +7 -7
  122. package/build/smaliParser.js +185 -268
  123. package/build/smaliParser.test.js +58 -0
  124. package/build/stringEscapes.d.ts +5 -0
  125. package/build/stringEscapes.js +244 -0
  126. package/build/symbolicExpression.d.ts +29 -0
  127. package/build/symbolicExpression.js +1 -0
  128. package/build/symbolicExpressionParser.d.ts +4 -0
  129. package/build/symbolicExpressionParser.js +123 -0
  130. package/build/symbolicExpressionParser.test.d.ts +1 -0
  131. package/build/symbolicExpressionParser.test.js +289 -0
  132. package/build/terminatedArrayParser.js +113 -38
  133. package/build/terminatedArrayParser.test.js +4 -2
  134. package/build/tupleParser.d.ts +7 -15
  135. package/build/tupleParser.js +1 -0
  136. package/build/unionParser.d.ts +5 -3
  137. package/build/unionParser.js +7 -2
  138. package/build/unionParser.test-d.d.ts +1 -0
  139. package/build/unionParser.test-d.js +72 -0
  140. package/build/unionParser.test.js +10 -11
  141. package/build/zig.d.ts +280 -0
  142. package/build/zig.js +2 -0
  143. package/build/zigParser.d.ts +3 -0
  144. package/build/zigParser.js +1119 -0
  145. package/build/zigParser.test.d.ts +1 -0
  146. package/build/zigParser.test.js +1590 -0
  147. package/build/zigUnparser.d.ts +2 -0
  148. package/build/zigUnparser.js +460 -0
  149. package/build/zigUnparser.test.d.ts +1 -0
  150. package/build/zigUnparser.test.js +24 -0
  151. package/build/zipParser.js +19 -32
  152. package/build/zipUnparser.js +19 -7
  153. package/build/zipUnparser.test.js +1 -1
  154. package/node_modules-@types/s-expression/index.d.ts +5 -0
  155. package/package.json +24 -6
  156. package/src/androidPackageParser.ts +33 -60
  157. package/src/arbitraryDalvikBytecode.ts +39 -31
  158. package/src/arbitraryDalvikExecutable.ts +65 -20
  159. package/src/arbitraryJava.ts +804 -0
  160. package/src/arbitraryJavaScript.ts +410 -0
  161. package/src/arbitraryZig.ts +380 -0
  162. package/src/arrayParser.ts +1 -3
  163. package/src/backsmali.ts +35 -4
  164. package/src/bash.ts +8 -1
  165. package/src/bashParser.test.ts +258 -0
  166. package/src/bashParser.ts +180 -143
  167. package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
  168. package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
  169. package/src/dalvikBytecodeParser.test-d.ts +310 -0
  170. package/src/dalvikBytecodeParser.ts +194 -69
  171. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
  172. package/src/dalvikBytecodeUnparser.test.ts +7 -7
  173. package/src/dalvikBytecodeUnparser.ts +31 -30
  174. package/src/dalvikExecutable.test-d.ts +132 -0
  175. package/src/dalvikExecutable.ts +3 -3
  176. package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
  177. package/src/dalvikExecutableParser.test.ts +37 -3
  178. package/src/dalvikExecutableParser.test.ts.md +163 -2
  179. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  180. package/src/dalvikExecutableParser.ts +121 -139
  181. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
  182. package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
  183. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
  184. package/src/dalvikExecutableUnparser.test.ts +3 -2
  185. package/src/dalvikExecutableUnparser.ts +4 -4
  186. package/src/disjunctionParser.test-d.ts +105 -0
  187. package/src/disjunctionParser.ts +18 -15
  188. package/src/elementSwitchParser.test-d.ts +74 -0
  189. package/src/elementSwitchParser.ts +51 -0
  190. package/src/exactSequenceParser.test-d.ts +43 -0
  191. package/src/exactSequenceParser.ts +13 -8
  192. package/src/fetchCid.ts +2 -76
  193. package/src/index.test.ts +22 -1
  194. package/src/index.ts +7 -1
  195. package/src/inputReader.ts +53 -0
  196. package/src/java.ts +708 -0
  197. package/src/javaKeyStoreParser.ts +18 -32
  198. package/src/javaParser.test.ts +1592 -0
  199. package/src/javaParser.ts +2640 -0
  200. package/src/javaScript.ts +36 -0
  201. package/src/javaScriptParser.ts +57 -0
  202. package/src/javaScriptUnparser.test.ts +37 -0
  203. package/src/javaScriptUnparser.ts +7 -0
  204. package/src/javaUnparser.test.ts +37 -0
  205. package/src/javaUnparser.ts +640 -0
  206. package/src/jsonParser.ts +6 -27
  207. package/src/lookaheadParser.ts +2 -6
  208. package/src/negativeLookaheadParser.ts +1 -3
  209. package/src/nonEmptyArrayParser.ts +1 -3
  210. package/src/objectParser.test-d.ts +152 -0
  211. package/src/objectParser.test.ts +71 -0
  212. package/src/objectParser.ts +69 -0
  213. package/src/optionalParser.ts +1 -3
  214. package/src/parser.test.ts +151 -4
  215. package/src/parser.ts +11 -1
  216. package/src/parserConsumedSequenceParser.ts +2 -4
  217. package/src/parserContext.ts +26 -11
  218. package/src/parserError.ts +17 -3
  219. package/src/regexpParser.test.ts +78 -0
  220. package/src/regexpParser.ts +35 -3
  221. package/src/regularExpressionParser.ts +36 -37
  222. package/src/separatedArrayParser.ts +1 -3
  223. package/src/separatedNonEmptyArrayParser.ts +1 -3
  224. package/src/sliceBoundedParser.test.ts +2 -2
  225. package/src/sliceBoundedParser.ts +15 -19
  226. package/src/smaliParser.test.ts +64 -0
  227. package/src/smaliParser.test.ts.md +12 -12
  228. package/src/smaliParser.test.ts.snap +0 -0
  229. package/src/smaliParser.ts +246 -534
  230. package/src/stringEscapes.ts +253 -0
  231. package/src/symbolicExpression.ts +17 -0
  232. package/src/symbolicExpressionParser.test.ts +466 -0
  233. package/src/symbolicExpressionParser.ts +190 -0
  234. package/src/terminatedArrayParser.test.ts +9 -6
  235. package/src/terminatedArrayParser.ts +25 -29
  236. package/src/tupleParser.ts +21 -18
  237. package/src/unionParser.test-d.ts +105 -0
  238. package/src/unionParser.test.ts +18 -17
  239. package/src/unionParser.ts +28 -16
  240. package/src/zig.ts +411 -0
  241. package/src/zigParser.test.ts +1693 -0
  242. package/src/zigParser.ts +1745 -0
  243. package/src/zigUnparser.test.ts +37 -0
  244. package/src/zigUnparser.ts +615 -0
  245. package/src/zipParser.ts +20 -56
  246. package/src/zipUnparser.test.ts +1 -1
  247. package/src/zipUnparser.ts +22 -7
  248. package/tsconfig.json +2 -2
  249. package/build/exactElementSwitchParser.d.ts +0 -3
  250. 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
 
@@ -184,3 +184,81 @@ test('regexpParser with negative lookahead should match single char', async t =>
184
184
  t.is(position, 1); // Consumed 1 character
185
185
  t.truthy(remainingInput); // There's remaining input (the space)
186
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
+ );
@@ -8,17 +8,49 @@ export const createRegExpParser = (
8
8
  let window = 1;
9
9
  let lastMatch: RegExpExecArray | undefined;
10
10
  let reachedEndOfInput = false;
11
+ let inputLength: number | undefined;
11
12
 
12
13
  while (true) {
13
14
  const sequence = await parserContext.peekSequence(start, start + window);
14
15
 
15
16
  if (sequence === undefined) {
16
- reachedEndOfInput = true;
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
+
17
49
  window = Math.floor(window / 2);
18
50
 
19
51
  if (window === 0) {
20
52
  // Get the full sequence we've accumulated to verify matches
21
- const fullSequence = await parserContext.peekSequence(0, start);
53
+ const fullSequence = await parserContext.peekSequence(0, inputLength ?? start);
22
54
 
23
55
  // Verify any previous match is still valid with full context
24
56
  // For lookahead/lookbehind assertions, additional input might invalidate a match
@@ -46,7 +78,7 @@ export const createRegExpParser = (
46
78
  continue;
47
79
  }
48
80
 
49
- const fullSequence = await parserContext.peekSequence(0, start + window);
81
+ const fullSequence = await parserContext.peekSequence(0, inputLength ?? start + window);
50
82
 
51
83
  if (fullSequence === undefined) {
52
84
  continue;
@@ -12,6 +12,7 @@ import { createFixedLengthSequenceParser } from './fixedLengthSequenceParser.js'
12
12
  import { createTerminatedArrayParser } from './terminatedArrayParser.js';
13
13
  import { createDisjunctionParser } from './disjunctionParser.js';
14
14
  import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
15
+ import { createObjectParser } from './objectParser.js';
15
16
  import {
16
17
  type CharacterSet,
17
18
  type CodePointRange,
@@ -648,20 +649,20 @@ type Quantifier =
648
649
  | { type: 'optional' }
649
650
  | { type: 'repeat'; bounds: RepeatBounds };
650
651
 
651
- const starQuantifierParser: Parser<Quantifier, string> = promiseCompose(
652
- createExactSequenceParser('*'),
653
- () => ({ type: 'star' as const }),
654
- );
652
+ const starQuantifierParser: Parser<Quantifier, string> = createObjectParser({
653
+ type: 'star' as const,
654
+ _marker: createExactSequenceParser('*'),
655
+ });
655
656
 
656
- const plusQuantifierParser: Parser<Quantifier, string> = promiseCompose(
657
- createExactSequenceParser('+'),
658
- () => ({ type: 'plus' as const }),
659
- );
657
+ const plusQuantifierParser: Parser<Quantifier, string> = createObjectParser({
658
+ type: 'plus' as const,
659
+ _marker: createExactSequenceParser('+'),
660
+ });
660
661
 
661
- const optionalQuantifierParser: Parser<Quantifier, string> = promiseCompose(
662
- createExactSequenceParser('?'),
663
- () => ({ type: 'optional' as const }),
664
- );
662
+ const optionalQuantifierParser: Parser<Quantifier, string> = createObjectParser({
663
+ type: 'optional' as const,
664
+ _marker: createExactSequenceParser('?'),
665
+ });
665
666
 
666
667
  // Parse a number for quantifiers
667
668
  const numberParser: Parser<number, string> = parserCreatorCompose(
@@ -759,24 +760,22 @@ const nonCaptureGroupParser: Parser<RegularExpression, string> = promiseCompose(
759
760
  type LookaheadMarker = { type: 'lookahead-marker'; isPositive: boolean; inner: RegularExpression };
760
761
 
761
762
  // Positive lookahead (?=...)
762
- const positiveLookaheadMarkerParser: Parser<LookaheadMarker, string> = promiseCompose(
763
- createTupleParser([
764
- createExactSequenceParser('(?='),
765
- createParserAccessorParser(() => alternationParser),
766
- createExactSequenceParser(')'),
767
- ]),
768
- ([, inner]) => ({ type: 'lookahead-marker' as const, isPositive: true, inner }),
769
- );
763
+ const positiveLookaheadMarkerParser: Parser<LookaheadMarker, string> = createObjectParser({
764
+ type: 'lookahead-marker' as const,
765
+ isPositive: true as const,
766
+ _open: createExactSequenceParser('(?='),
767
+ inner: createParserAccessorParser(() => alternationParser),
768
+ _close: createExactSequenceParser(')'),
769
+ });
770
770
 
771
771
  // Negative lookahead (?!...)
772
- const negativeLookaheadMarkerParser: Parser<LookaheadMarker, string> = promiseCompose(
773
- createTupleParser([
774
- createExactSequenceParser('(?!'),
775
- createParserAccessorParser(() => alternationParser),
776
- createExactSequenceParser(')'),
777
- ]),
778
- ([, inner]) => ({ type: 'lookahead-marker' as const, isPositive: false, inner }),
779
- );
772
+ const negativeLookaheadMarkerParser: Parser<LookaheadMarker, string> = createObjectParser({
773
+ type: 'lookahead-marker' as const,
774
+ isPositive: false as const,
775
+ _open: createExactSequenceParser('(?!'),
776
+ inner: createParserAccessorParser(() => alternationParser),
777
+ _close: createExactSequenceParser(')'),
778
+ });
780
779
 
781
780
  const groupParser: Parser<RegularExpression, string> = createUnionParser([
782
781
  namedCaptureGroupParser,
@@ -789,15 +788,15 @@ const groupParser: Parser<RegularExpression, string> = createUnionParser([
789
788
  type AnchorMarker = { type: 'start-anchor-marker' } | { type: 'end-anchor-marker' };
790
789
  type ParsedElement = RegularExpression | AnchorMarker | LookaheadMarker;
791
790
 
792
- const startAnchorMarkerParser: Parser<AnchorMarker, string> = promiseCompose(
793
- createExactSequenceParser('^'),
794
- () => ({ type: 'start-anchor-marker' as const }),
795
- );
791
+ const startAnchorMarkerParser: Parser<AnchorMarker, string> = createObjectParser({
792
+ type: 'start-anchor-marker' as const,
793
+ _marker: createExactSequenceParser('^'),
794
+ });
796
795
 
797
- const endAnchorMarkerParser: Parser<AnchorMarker, string> = promiseCompose(
798
- createExactSequenceParser('$'),
799
- () => ({ type: 'end-anchor-marker' as const }),
800
- );
796
+ const endAnchorMarkerParser: Parser<AnchorMarker, string> = createObjectParser({
797
+ type: 'end-anchor-marker' as const,
798
+ _marker: createExactSequenceParser('$'),
799
+ });
801
800
 
802
801
  // Atom: the basic unit that can be quantified (excluding anchors)
803
802
  const atomParser: Parser<RegularExpression, string> = createUnionParser([
@@ -832,7 +831,7 @@ const quantifiedParser: Parser<RegularExpression, string> = promiseCompose(
832
831
  );
833
832
 
834
833
  // Element in a sequence: either a quantified atom, anchor marker, or lookahead marker
835
- const sequenceElementParser: Parser<ParsedElement, string> = createUnionParser<ParsedElement, string>([
834
+ const sequenceElementParser: Parser<ParsedElement, string> = createUnionParser([
836
835
  startAnchorMarkerParser,
837
836
  endAnchorMarkerParser,
838
837
  positiveLookaheadMarkerParser,
@@ -21,7 +21,7 @@ export const createSeparatedArrayParser = <ElementOutput, Sequence>(
21
21
  const elements: ElementOutput[] = [];
22
22
 
23
23
  while (true) {
24
- const elementParserContext = parserContext.lookahead();
24
+ using elementParserContext = parserContext.lookahead();
25
25
  const initialPosition = elementParserContext.position;
26
26
 
27
27
  try {
@@ -39,8 +39,6 @@ export const createSeparatedArrayParser = <ElementOutput, Sequence>(
39
39
  }
40
40
 
41
41
  throw error;
42
- } finally {
43
- elementParserContext.dispose();
44
42
  }
45
43
 
46
44
  parser = separatorThenElementParser;
@@ -21,7 +21,7 @@ export const createSeparatedNonEmptyArrayParser = <ElementOutput, Sequence>(
21
21
  const elements: ElementOutput[] = [];
22
22
 
23
23
  while (true) {
24
- const elementParserContext = parserContext.lookahead();
24
+ using elementParserContext = parserContext.lookahead();
25
25
  const initialPosition = elementParserContext.position;
26
26
 
27
27
  try {
@@ -39,8 +39,6 @@ export const createSeparatedNonEmptyArrayParser = <ElementOutput, Sequence>(
39
39
  }
40
40
 
41
41
  throw error;
42
- } finally {
43
- elementParserContext.dispose();
44
42
  }
45
43
 
46
44
  parser = separatorThenElementParser;
@@ -32,7 +32,7 @@ test('sliceBoundedParser', async t => {
32
32
  test('sliceBoundedParser mustConsumeAll: true fail to cosume all', async t => {
33
33
  const parser = createTupleParser([
34
34
  createElementParser<string>(),
35
- createSliceBoundedParser(createArrayParser(createExactElementParser('b' as string)), 2),
35
+ createSliceBoundedParser(createArrayParser(createExactElementParser<string>('b')), 2),
36
36
  createElementParser(),
37
37
  ]);
38
38
 
@@ -45,7 +45,7 @@ test('sliceBoundedParser mustConsumeAll: true fail to cosume all', async t => {
45
45
  test('sliceBoundedParser mustConsumeAll: false', async t => {
46
46
  const parser = createTupleParser([
47
47
  createElementParser<string>(),
48
- createSliceBoundedParser(createArrayParser(createExactElementParser('b' as string)), 2, false),
48
+ createSliceBoundedParser(createArrayParser(createExactElementParser<string>('b')), 2, false),
49
49
  createElementParser(),
50
50
  ]);
51
51
 
@@ -8,30 +8,26 @@ export const createSliceBoundedParser = <Output, Sequence>(
8
8
  const sliceBoundedParser: typeof childParser = async parserContext => {
9
9
  const absoluteSliceEnd = parserContext.position + sliceEnd;
10
10
 
11
- const childParserContext = parserContext.lookahead({
11
+ using childParserContext = parserContext.lookahead({
12
12
  sliceEnd: absoluteSliceEnd,
13
13
  });
14
14
 
15
- try {
16
- const value = await childParser(childParserContext);
15
+ const value = await childParser(childParserContext);
17
16
 
18
- childParserContext.invariant(
19
- (
20
- !mustConsumeAll
21
- || childParserContext.position === absoluteSliceEnd
22
- ),
23
- 'child parser must consume all input in the slice (%s in total, up to position %s), instead consumed %s up to position %s',
24
- sliceEnd,
25
- absoluteSliceEnd,
26
- childParserContext.position - parserContext.position,
27
- childParserContext.position,
28
- );
17
+ childParserContext.invariant(
18
+ (
19
+ !mustConsumeAll
20
+ || childParserContext.position === absoluteSliceEnd
21
+ ),
22
+ 'child parser must consume all input in the slice (%s in total, up to position %s), instead consumed %s up to position %s',
23
+ sliceEnd,
24
+ absoluteSliceEnd,
25
+ childParserContext.position - parserContext.position,
26
+ childParserContext.position,
27
+ );
29
28
 
30
- childParserContext.unlookahead();
31
- return value;
32
- } finally {
33
- childParserContext.dispose();
34
- }
29
+ childParserContext.unlookahead();
30
+ return value;
35
31
  };
36
32
 
37
33
  setParserName(sliceBoundedParser, [
@@ -441,3 +441,67 @@ test('parse smali with virtual method after direct methods (a0/n issue)', async
441
441
  t.truthy(actual);
442
442
  t.is(actual.classData?.virtualMethods.length, 1);
443
443
  });
444
+
445
+ // Minimal failing test case for .end local directive
446
+ test('parse smali with .end local directive', async t => {
447
+ const smali = `.class public Lpl/czak/minimal/MainActivity;
448
+ .super Landroid/app/Activity;
449
+
450
+ # direct methods
451
+ .method private sumToN(I)I
452
+ .registers 4
453
+ .param p1, "n" # I
454
+
455
+ const/4 v0, 0x0
456
+
457
+ .local v0, "sum":I
458
+ const/4 v1, 0x1
459
+
460
+ .local v1, "i":I
461
+ :goto_2
462
+ if-gt v1, p1, :cond_8
463
+
464
+ add-int/2addr v0, v1
465
+
466
+ add-int/lit8 v1, v1, 0x1
467
+
468
+ goto :goto_2
469
+
470
+ .end local v1 # "i":I
471
+ :cond_8
472
+ return v0
473
+ .end method
474
+ `;
475
+
476
+ const actual = await runParser(smaliParser, smali, stringParserInputCompanion, {
477
+ errorJoinMode: 'all',
478
+ });
479
+
480
+ t.truthy(actual);
481
+ t.is(actual.classData?.directMethods.length, 1);
482
+ });
483
+
484
+ // Minimal test case for .local with generic type signature
485
+ test('parse smali with .local directive with generic type signature', async t => {
486
+ const smali = `.class public Lpl/czak/minimal/MainActivity;
487
+ .super Landroid/app/Activity;
488
+
489
+ # direct methods
490
+ .method private useLambda()Ljava/lang/String;
491
+ .registers 3
492
+
493
+ new-instance v0, Lpl/czak/minimal/MainActivity$$ExternalSyntheticLambda0;
494
+
495
+ .local v0, "supplier":Ljava/util/function/Supplier;, "Ljava/util/function/Supplier<Ljava/lang/String;>;"
496
+
497
+ return-object v0
498
+ .end method
499
+ `;
500
+
501
+ const actual = await runParser(smaliParser, smali, stringParserInputCompanion, {
502
+ errorJoinMode: 'all',
503
+ });
504
+
505
+ t.truthy(actual);
506
+ t.is(actual.classData?.directMethods.length, 1);
507
+ });