@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
@@ -25,8 +25,16 @@ import {
25
25
  isDalvikExecutableField,
26
26
  isDalvikExecutableMethod,
27
27
  } from '../dalvikExecutable.js';
28
- import { type DalvikBytecode } from '../dalvikBytecodeParser.js';
29
- import { dalvikBytecodeUnparser } from '../dalvikBytecodeUnparser.js';
28
+ import { type RawDalvikBytecodeOperation } from '../dalvikBytecodeParser.js';
29
+ import { rawDalvikBytecodeUnparser } from '../dalvikBytecodeUnparser.js';
30
+ import {
31
+ type DalvikBytecodeOperation,
32
+ buildIndexToCodeUnitMapFromResolved,
33
+ instructionIndexToCodeUnit,
34
+ wrapBranchOffsets,
35
+ convertInstructionOffsetsToBranchOffsets,
36
+ } from '../dalvikBytecodeParser/addressConversion.js';
37
+ import { isoCodeUnit, isoInstructionIndex } from '../dalvikExecutableParser/typedNumbers.js';
30
38
  import { ubyteUnparser, ushortUnparser, uintUnparser } from '../dalvikBytecodeUnparser/formatUnparsers.js';
31
39
  import { type PoolBuilders } from './poolBuilders.js';
32
40
  import { WriteLater } from '../unparserContext.js';
@@ -521,7 +529,7 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
521
529
  yield * ubyteUnparser(0x00, unparserContext);
522
530
  };
523
531
 
524
- const codeItemUnparser = (callback?: (result: { debugInfoOffsetWriteLater?: WriteLater<Uint8Array, number> }) => void): Unparser<DalvikExecutableCode<DalvikBytecode>, Uint8Array> => {
532
+ const codeItemUnparser = (callback?: (result: { debugInfoOffsetWriteLater?: WriteLater<Uint8Array, number> }) => void): Unparser<DalvikExecutableCode<DalvikBytecodeOperation[]>, Uint8Array> => {
525
533
  return async function * (input, unparserContext) {
526
534
  yield * ushortUnparser(input.registersSize, unparserContext);
527
535
  yield * ushortUnparser(input.insSize, unparserContext);
@@ -540,21 +548,33 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
540
548
  callback({ debugInfoOffsetWriteLater });
541
549
  }
542
550
 
543
- const instructionsSizeInShorts = await calculateInstructionsSize(input.instructions);
551
+ // Convert branch offsets from instruction offsets back to code units
552
+ // Tier 3 (plain numbers) -> Tier 2 (InstructionIndex) -> Tier 1 (CodeUnit)
553
+ const rawInstructions = convertInstructionOffsetsToBranchOffsets(wrapBranchOffsets(input.instructions));
554
+
555
+ const instructionsSizeInShorts = await calculateRawInstructionsSize(rawInstructions);
544
556
  yield * uintUnparser(instructionsSizeInShorts, unparserContext);
545
557
 
546
- yield * dalvikBytecodeUnparser(input.instructions, unparserContext);
558
+ yield * rawDalvikBytecodeUnparser(rawInstructions, unparserContext);
547
559
 
548
560
  if (input.tries.length > 0 && instructionsSizeInShorts % 2 !== 0) {
549
561
  yield * ushortUnparser(0, unparserContext);
550
562
  }
551
563
 
552
564
  if (input.tries.length > 0) {
565
+ // Build index to code unit mapping for address conversion
566
+ const indexToCodeUnitMap = buildIndexToCodeUnitMapFromResolved(input.instructions);
567
+
553
568
  const handlerOffsetWriteLaters: Array<WriteLater<Uint8Array, number>> = [];
554
569
 
555
570
  for (const tryBlock of input.tries) {
556
- yield * uintUnparser(tryBlock.startAddress, unparserContext);
557
- yield * ushortUnparser(tryBlock.instructionCount, unparserContext);
571
+ // Convert instruction indices back to code unit offsets
572
+ const startAddressCodeUnit = isoCodeUnit.unwrap(instructionIndexToCodeUnit(isoInstructionIndex.wrap(tryBlock.startInstructionIndex), indexToCodeUnitMap));
573
+ const endAddressCodeUnit = isoCodeUnit.unwrap(instructionIndexToCodeUnit(isoInstructionIndex.wrap(tryBlock.startInstructionIndex + tryBlock.instructionCount), indexToCodeUnitMap));
574
+ const instructionCountCodeUnits = endAddressCodeUnit - startAddressCodeUnit;
575
+
576
+ yield * uintUnparser(startAddressCodeUnit, unparserContext);
577
+ yield * ushortUnparser(instructionCountCodeUnits, unparserContext);
558
578
  const handlerOffsetWriteLater = yield * yieldAndCapture(unparserContext.writeLater(2));
559
579
  handlerOffsetWriteLaters.push(handlerOffsetWriteLater);
560
580
  }
@@ -569,7 +589,7 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
569
589
  const handlerOffset = unparserContext.position - handlersStartOffset;
570
590
  yield * unparserContext.writeEarlier(handlerOffsetWriteLaters[i], ushortUnparser, handlerOffset);
571
591
 
572
- if (handler.catchAllAddress !== undefined) {
592
+ if (handler.catchAllInstructionIndex !== undefined) {
573
593
  yield * sleb128Unparser(-handler.handlers.length, unparserContext);
574
594
  } else {
575
595
  yield * sleb128Unparser(handler.handlers.length, unparserContext);
@@ -578,18 +598,22 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
578
598
  for (const handlerItem of handler.handlers) {
579
599
  const typeIndex = getTypeIndex(handlerItem.type);
580
600
  yield * uleb128Unparser(typeIndex, unparserContext);
581
- yield * uleb128Unparser(handlerItem.address, unparserContext);
601
+ // Convert handler address from instruction index to code unit offset
602
+ const handlerAddressCodeUnit = isoCodeUnit.unwrap(instructionIndexToCodeUnit(isoInstructionIndex.wrap(handlerItem.handlerInstructionIndex), indexToCodeUnitMap));
603
+ yield * uleb128Unparser(handlerAddressCodeUnit, unparserContext);
582
604
  }
583
605
 
584
- if (handler.catchAllAddress !== undefined) {
585
- yield * uleb128Unparser(handler.catchAllAddress, unparserContext);
606
+ if (handler.catchAllInstructionIndex !== undefined) {
607
+ // Convert catchAllInstructionIndex from instruction index to code unit offset
608
+ const catchAllAddressCodeUnit = isoCodeUnit.unwrap(instructionIndexToCodeUnit(isoInstructionIndex.wrap(handler.catchAllInstructionIndex), indexToCodeUnitMap));
609
+ yield * uleb128Unparser(catchAllAddressCodeUnit, unparserContext);
586
610
  }
587
611
  }
588
612
  }
589
613
  };
590
614
  };
591
615
 
592
- async function calculateInstructionsSize(instructions: DalvikBytecode): Promise<number> {
616
+ async function calculateRawInstructionsSize(instructions: RawDalvikBytecodeOperation[]): Promise<number> {
593
617
  let totalSize = 0;
594
618
 
595
619
  const mockContext: UnparserContext<Uint8Array, number> = {
@@ -598,7 +622,7 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
598
622
  writeEarlier: () => { throw new Error('Not supported'); },
599
623
  };
600
624
 
601
- for await (const chunk of dalvikBytecodeUnparser(instructions, mockContext)) {
625
+ for await (const chunk of rawDalvikBytecodeUnparser(instructions, mockContext)) {
602
626
  if (chunk instanceof Uint8Array) {
603
627
  totalSize += chunk.length;
604
628
  }
@@ -607,7 +631,7 @@ export function createSectionUnparsers(poolBuilders: PoolBuilders) {
607
631
  return Math.floor(totalSize / 2);
608
632
  }
609
633
 
610
- const classDataUnparser = (codeOffsetMap: Map<DalvikExecutableCode<DalvikBytecode>, number>): Unparser<DalvikExecutableClassData<DalvikBytecode>, Uint8Array> => {
634
+ const classDataUnparser = (codeOffsetMap: Map<DalvikExecutableCode<DalvikBytecodeOperation[]>, number>): Unparser<DalvikExecutableClassData<DalvikBytecodeOperation[]>, Uint8Array> => {
611
635
  return async function * (input, unparserContext) {
612
636
  yield * uleb128Unparser(input.staticFields.length, unparserContext);
613
637
  yield * uleb128Unparser(input.instanceFields.length, unparserContext);
@@ -8,11 +8,12 @@ import { runUnparser } from './unparser.js';
8
8
  import { uint8ArrayParserInputCompanion } from './parserInputCompanion.js';
9
9
  import { uint8ArrayUnparserOutputCompanion } from './unparserOutputCompanion.js';
10
10
  import { uint8ArrayAsyncIterableToUint8Array } from './uint8Array.js';
11
- import { type DalvikBytecodeOperation } from './dalvikBytecodeParser.js';
11
+ import { type DalvikBytecodeOperation } from './dalvikBytecodeParser/addressConversion.js';
12
12
 
13
13
  const seed = process.env.SEED ? Number(process.env.SEED) : undefined;
14
14
 
15
15
  // Use minimal bytecode for testing - simple nop and return-void instructions
16
+ // These operations have no branch offsets, so they work the same in all tiers
16
17
  const arbitraryMinimalBytecode = fc.array(
17
18
  fc.oneof(
18
19
  fc.constant<DalvikBytecodeOperation>({
@@ -37,7 +38,7 @@ testProp(
37
38
  );
38
39
  const bytes = await uint8ArrayAsyncIterableToUint8Array(unparsedIterable);
39
40
 
40
- // Re-parse
41
+ // Re-parse (parser now outputs Tier 3 directly)
41
42
  const reparsed = await runParser(
42
43
  dalvikExecutableParser,
43
44
  bytes,
@@ -1,6 +1,6 @@
1
1
  import { type Unparser } from './unparser.js';
2
2
  import { type DalvikExecutable, type DalvikExecutableClassDefinition, type DalvikExecutableCode, type DalvikExecutableDebugInfo, type DalvikExecutableAnnotation, type DalvikExecutableEncodedValue } from './dalvikExecutable.js';
3
- import { type DalvikBytecode } from './dalvikBytecodeParser.js';
3
+ import { type DalvikBytecodeOperation } from './dalvikBytecodeParser/addressConversion.js';
4
4
  import { uintUnparser, ushortUnparser } from './dalvikBytecodeUnparser/formatUnparsers.js';
5
5
  import { createPoolBuilders } from './dalvikExecutableUnparser/poolBuilders.js';
6
6
  import { scanForPoolReferences } from './dalvikExecutableUnparser/poolScanners.js';
@@ -76,7 +76,7 @@ class SectionTracker {
76
76
  }
77
77
  }
78
78
 
79
- export const dalvikExecutableUnparser: Unparser<DalvikExecutable<DalvikBytecode>, Uint8Array> = async function * (input, unparserContext) {
79
+ export const dalvikExecutableUnparser: Unparser<DalvikExecutable<DalvikBytecodeOperation[]>, Uint8Array> = async function * (input, unparserContext) {
80
80
  const poolBuilders = createPoolBuilders();
81
81
 
82
82
  scanForPoolReferences(input, poolBuilders);
@@ -263,7 +263,7 @@ export const dalvikExecutableUnparser: Unparser<DalvikExecutable<DalvikBytecode>
263
263
  // Track classDataItem, codeItem, and debugInfoItem for later writing
264
264
  // Collect data to write items grouped by type (required by DEX format)
265
265
  const classDataToWrite: Array<{
266
- classDef: DalvikExecutableClassDefinition<DalvikBytecode>;
266
+ classDef: DalvikExecutableClassDefinition<DalvikBytecodeOperation[]>;
267
267
  classDefItem: {
268
268
  interfacesOffsetWriteLater?: WriteLater<Uint8Array, number>;
269
269
  annotationsOffsetWriteLater?: WriteLater<Uint8Array, number>;
@@ -272,7 +272,7 @@ export const dalvikExecutableUnparser: Unparser<DalvikExecutable<DalvikBytecode>
272
272
  };
273
273
  classIdx: number;
274
274
  }> = [];
275
- const codeToWrite: Array<{ code: DalvikExecutableCode<DalvikBytecode> }> = [];
275
+ const codeToWrite: Array<{ code: DalvikExecutableCode<DalvikBytecodeOperation[]> }> = [];
276
276
  const debugInfoToWrite: Array<{ debugInfo: DalvikExecutableDebugInfo; offsetWriteLater: WriteLater<Uint8Array, number> }> = [];
277
277
 
278
278
  // First pass: collect type lists, encoded arrays, and classData/code/debugInfo to write
@@ -0,0 +1,105 @@
1
+ import { expectAssignable } from 'tsd';
2
+ import { createDisjunctionParser } from './disjunctionParser.js';
3
+ import { type Parser, type ParserOutput } from './parser.js';
4
+ import { createExactSequenceParser } from './exactSequenceParser.js';
5
+ import { createExactElementParser } from './exactElementParser.js';
6
+ import { createFixedLengthSequenceParser } from './fixedLengthSequenceParser.js';
7
+
8
+ // Test: basic disjunction of string parsers - output inferred as string
9
+ {
10
+ const parser = createDisjunctionParser([
11
+ createExactElementParser('a'),
12
+ createExactElementParser('b'),
13
+ ]);
14
+
15
+ type Output = ParserOutput<typeof parser>;
16
+
17
+ expectAssignable<string>(null! as Output);
18
+ expectAssignable<Output>(null! as string);
19
+ }
20
+
21
+ // Test: disjunction preserves literal types when parsers have explicit literal output types
22
+ {
23
+ const parserA: Parser<'a', string> = async () => 'a' as const;
24
+ const parserB: Parser<'b', string> = async () => 'b' as const;
25
+
26
+ const parser = createDisjunctionParser([parserA, parserB]);
27
+
28
+ type Output = ParserOutput<typeof parser>;
29
+
30
+ // Output should be 'a' | 'b', not string
31
+ expectAssignable<'a' | 'b'>(null! as Output);
32
+ expectAssignable<Output>(null! as 'a' | 'b');
33
+ }
34
+
35
+ // Test: disjunction of parsers with different output types
36
+ {
37
+ const stringParser: Parser<string, string> = createFixedLengthSequenceParser(3);
38
+ const numberParser: Parser<number, string> = () => 42;
39
+
40
+ const parser = createDisjunctionParser([
41
+ stringParser,
42
+ numberParser,
43
+ ]);
44
+
45
+ type Output = ParserOutput<typeof parser>;
46
+
47
+ // Output should be string | number
48
+ expectAssignable<string | number>(null! as Output);
49
+ expectAssignable<Output>(null! as string | number);
50
+ }
51
+
52
+ // Test: nested disjunctions
53
+ {
54
+ const inner = createDisjunctionParser([
55
+ createExactElementParser('a'),
56
+ createExactElementParser('b'),
57
+ ]);
58
+
59
+ const parser = createDisjunctionParser([
60
+ inner,
61
+ createExactElementParser('c'),
62
+ ]);
63
+
64
+ type Output = ParserOutput<typeof parser>;
65
+
66
+ expectAssignable<string>(null! as Output);
67
+ expectAssignable<Output>(null! as string);
68
+ }
69
+
70
+ // Test: sequence type inferred from child parsers
71
+ {
72
+ const parser = createDisjunctionParser([
73
+ createExactSequenceParser('hello'),
74
+ createExactSequenceParser('world'),
75
+ ]);
76
+
77
+ type Output = ParserOutput<typeof parser>;
78
+
79
+ // Parser should be for string sequences, output is string (widened from literals)
80
+ expectAssignable<string>(null! as Output);
81
+ }
82
+
83
+ // Test: single parser in disjunction
84
+ {
85
+ const parser = createDisjunctionParser([
86
+ createExactSequenceParser('only'),
87
+ ]);
88
+
89
+ type Output = ParserOutput<typeof parser>;
90
+
91
+ // Output is string (widened from literal 'only')
92
+ expectAssignable<string>(null! as Output);
93
+ }
94
+
95
+ // Test: disjunction of object-producing parsers
96
+ {
97
+ const parser1: Parser<{ type: 'a'; value: number }, string> = async () => ({ type: 'a', value: 1 });
98
+ const parser2: Parser<{ type: 'b'; name: string }, string> = async () => ({ type: 'b', name: 'test' });
99
+
100
+ const parser = createDisjunctionParser([parser1, parser2]);
101
+
102
+ type Output = ParserOutput<typeof parser>;
103
+
104
+ expectAssignable<{ type: 'a'; value: number } | { type: 'b'; name: string }>(null! as Output);
105
+ }
@@ -1,39 +1,42 @@
1
- import { getParserName, setParserName, type Parser } from './parser.js';
1
+ import { getParserName, setParserName, type Parser, type ParserOutput, type ParserSequence } from './parser.js';
2
2
  import { isParserParsingFailedError, ParserParsingFailedError } from './parserError.js';
3
3
  import { parserImplementationInvariant } from './parserImplementationInvariant.js';
4
4
  import { promiseSettled } from './promiseSettled.js';
5
- import { type DeriveSequenceElement } from './sequence.js';
6
5
 
7
- export const createDisjunctionParser = <
8
- Output,
9
- Sequence,
10
- Element = DeriveSequenceElement<Sequence>,
6
+ // Union all output types from an array of parsers
7
+ type DisjunctionParserOutput<Parsers extends readonly unknown[]> = ParserOutput<Parsers[number]>;
8
+
9
+ // Infer Sequence from parser array
10
+ type InferSequenceFromParserArray<T extends readonly unknown[]> = ParserSequence<T[number]>;
11
+
12
+ export function createDisjunctionParser<
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ const Parsers extends readonly Parser<any, any, any>[],
11
15
  >(
12
- childParsers: Array<Parser<unknown, Sequence, Element>>,
13
- ): Parser<Output, Sequence, Element> => {
16
+ childParsers: Parsers,
17
+ ): Parser<DisjunctionParserOutput<Parsers>, InferSequenceFromParserArray<Parsers>> {
14
18
  parserImplementationInvariant(childParsers.length > 0, 'Disjunction parser must have at least one child parser.');
15
19
 
16
- const disjunctionParser: Parser<Output, Sequence, Element> = async parserContext => {
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ const disjunctionParser: Parser<any, any, any> = async parserContext => {
17
22
  const parserParsingFailedErrors: ParserParsingFailedError[] = [];
18
23
 
19
24
  for (const childParser of childParsers) {
20
- const childParserContext = parserContext.lookahead({
25
+ using childParserContext = parserContext.lookahead({
21
26
  debugName: getParserName(childParser, 'anonymousDisjunctionChild'),
22
27
  });
23
28
 
24
- const childParserResult = await promiseSettled<Output>(childParser(childParserContext) as Promise<Output>);
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ const childParserResult = await promiseSettled<any>(childParser(childParserContext) as Promise<any>);
25
31
 
26
32
  if (childParserResult.status === 'fulfilled') {
27
33
  const successfulParserOutput = childParserResult.value;
28
34
 
29
35
  childParserContext.unlookahead();
30
- childParserContext.dispose();
31
36
 
32
37
  return successfulParserOutput;
33
38
  }
34
39
 
35
- childParserContext.dispose();
36
-
37
40
  const error = childParserResult.reason;
38
41
 
39
42
  if (isParserParsingFailedError(error)) {
@@ -57,4 +60,4 @@ export const createDisjunctionParser = <
57
60
  ].join('');
58
61
 
59
62
  return setParserName(disjunctionParser, name);
60
- };
63
+ }
@@ -0,0 +1,74 @@
1
+ import { expectAssignable, expectType } from 'tsd';
2
+ import { createElementSwitchParser } from './elementSwitchParser.js';
3
+ import { type Parser, type ParserOutput } from './parser.js';
4
+
5
+ // Test: basic element switch with inferred output type
6
+ {
7
+ const parserA: Parser<'a', string> = async () => 'a' as const;
8
+ const parserB: Parser<'b', string> = async () => 'b' as const;
9
+
10
+ const parser = createElementSwitchParser(
11
+ new Map<string, typeof parserA | typeof parserB>([
12
+ ['x', parserA],
13
+ ['y', parserB],
14
+ ]),
15
+ );
16
+
17
+ type Output = ParserOutput<typeof parser>;
18
+
19
+ // Output should be 'a' | 'b'
20
+ expectAssignable<'a' | 'b'>(null! as Output);
21
+ expectAssignable<Output>(null! as 'a' | 'b');
22
+ }
23
+
24
+ // Test: element switch with default parser
25
+ {
26
+ const parserA: Parser<'a', string> = async () => 'a' as const;
27
+ const defaultParser: Parser<'default', string> = async () => 'default' as const;
28
+
29
+ const parser = createElementSwitchParser(
30
+ new Map([
31
+ ['x', parserA],
32
+ ]),
33
+ defaultParser,
34
+ );
35
+
36
+ type Output = ParserOutput<typeof parser>;
37
+
38
+ // Output should be 'a' | 'default'
39
+ expectAssignable<'a' | 'default'>(null! as Output);
40
+ expectAssignable<Output>(null! as 'a' | 'default');
41
+ }
42
+
43
+ // Test: element switch with number keys (Uint8Array sequence)
44
+ {
45
+ const parser1: Parser<{ type: 'one' }, Uint8Array> = async () => ({ type: 'one' });
46
+ const parser2: Parser<{ type: 'two' }, Uint8Array> = async () => ({ type: 'two' });
47
+
48
+ const parser = createElementSwitchParser(
49
+ new Map<number, typeof parser1 | typeof parser2>([
50
+ [1, parser1],
51
+ [2, parser2],
52
+ ]),
53
+ );
54
+
55
+ type Output = ParserOutput<typeof parser>;
56
+
57
+ expectAssignable<{ type: 'one' } | { type: 'two' }>(null! as Output);
58
+ }
59
+
60
+ // Test: element switch without default parser has no 'never' in output
61
+ {
62
+ const parserA: Parser<string, string> = async () => 'result';
63
+
64
+ const parser = createElementSwitchParser(
65
+ new Map([
66
+ ['key', parserA],
67
+ ]),
68
+ );
69
+
70
+ type Output = ParserOutput<typeof parser>;
71
+
72
+ // Output should just be string, not string | never
73
+ expectType<string>(null! as Output);
74
+ }
@@ -0,0 +1,51 @@
1
+ import invariant from "invariant";
2
+ import { getParserName, Parser, ParserOutput, ParserSequence, setParserName } from "./parser.js";
3
+ import { parserImplementationInvariant } from "./parserImplementationInvariant.js";
4
+
5
+ // Output type: union of child parser outputs and default parser output (if present)
6
+ type ElementSwitchOutput<
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ ChildParser extends Parser<any, any, any>,
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ DefaultParser extends Parser<any, any, any> | undefined,
11
+ > = ParserOutput<ChildParser> | (DefaultParser extends undefined ? never : ParserOutput<NonNullable<DefaultParser>>);
12
+
13
+ export function createElementSwitchParser<
14
+ Element,
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ ChildParser extends Parser<any, any, Element>,
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ DefaultParser extends Parser<any, any, Element> | undefined = undefined,
19
+ >(
20
+ childParsers: Map<Element, ChildParser>,
21
+ defaultParser?: DefaultParser,
22
+ ): Parser<ElementSwitchOutput<ChildParser, DefaultParser>, ParserSequence<ChildParser>, Element> {
23
+ parserImplementationInvariant(childParsers.size > 0, 'Element switch parser must have at least one child parser.');
24
+
25
+ type Output = ElementSwitchOutput<ChildParser, DefaultParser>;
26
+ type Sequence = ParserSequence<ChildParser>;
27
+
28
+ const elementSwitchParser: Parser<Output, Sequence, Element> = async parserContext => {
29
+ const currentElement = await parserContext.peek(0);
30
+
31
+ parserContext.invariant(currentElement !== undefined, 'Unexpected end of input.');
32
+ invariant(currentElement !== undefined, 'Unexpected end of input.');
33
+
34
+ const childParser = childParsers.get(currentElement) ?? defaultParser;
35
+
36
+ parserContext.invariant(childParser, `No child parser found for element: ${String(currentElement)}`);
37
+
38
+ return childParser!(parserContext) as Promise<Output>;
39
+ };
40
+
41
+ const name = [
42
+ 'elementSwitch(',
43
+ ...Array.from(childParsers.entries()).map(
44
+ ([ element, childParser ]) => `${String(element)}=>${getParserName(childParser, 'anonymousElementSwitchChild')}`,
45
+ ),
46
+ defaultParser ? `|default=>${getParserName(defaultParser, 'anonymousElementSwitchDefaultChild')}` : '',
47
+ ')',
48
+ ].join('');
49
+
50
+ return setParserName(elementSwitchParser, name);
51
+ }
@@ -0,0 +1,43 @@
1
+ import { expectType, expectAssignable } from 'tsd';
2
+ import { createExactSequenceParser, createExactSequenceNaiveParser } from './exactSequenceParser.js';
3
+ import { type Parser } from './parser.js';
4
+
5
+ // Test: string literal output type is preserved, sequence type is string
6
+ {
7
+ const parser = createExactSequenceParser('foo');
8
+ expectType<Parser<'foo', string, unknown>>(parser);
9
+ }
10
+
11
+ // Test: string literal with 'as const' also works
12
+ {
13
+ const parser = createExactSequenceParser('bar' as const);
14
+ expectType<Parser<'bar', string, unknown>>(parser);
15
+ }
16
+
17
+ // Test: plain string variable widens to string
18
+ {
19
+ const value: string = 'baz';
20
+ const parser = createExactSequenceParser(value);
21
+ expectType<Parser<string, string, unknown>>(parser);
22
+ }
23
+
24
+ // Test: Uint8Array sequence type is preserved
25
+ {
26
+ const bytes = new Uint8Array([1, 2, 3]);
27
+ const parser = createExactSequenceParser(bytes);
28
+ // Output is the specific Uint8Array instance type, Sequence is Uint8Array
29
+ expectAssignable<Parser<Uint8Array, Uint8Array, unknown>>(parser);
30
+ }
31
+
32
+ // Test: naive parser has same type behavior
33
+ {
34
+ const parser = createExactSequenceNaiveParser('hello');
35
+ expectType<Parser<'hello', string, unknown>>(parser);
36
+ }
37
+
38
+ // Test: naive parser with Uint8Array
39
+ {
40
+ const bytes = new Uint8Array([0xff]);
41
+ const parser = createExactSequenceNaiveParser(bytes);
42
+ expectAssignable<Parser<Uint8Array, Uint8Array, unknown>>(parser);
43
+ }
@@ -1,13 +1,17 @@
1
1
  import { setParserName, type Parser } from './parser.js';
2
2
  import { inspect } from './inspect.js';
3
3
 
4
- export const createExactSequenceNaiveParser = <Sequence>(sequence: Sequence) => {
5
- const exactSequenceParser: Parser<Sequence, Sequence, unknown> = async parserContext => {
6
- const length = parserContext.length(sequence);
4
+ // Derive the base sequence type from a literal (e.g., 'foo' -> string, Uint8Array -> Uint8Array)
5
+ type DeriveBaseSequence<T> = T extends string ? string : T extends Uint8Array ? Uint8Array : T;
6
+
7
+ export const createExactSequenceNaiveParser = <const Output>(sequence: Output) => {
8
+ type Sequence = DeriveBaseSequence<Output>;
9
+ const exactSequenceParser: Parser<Output, Sequence, unknown> = async parserContext => {
10
+ const length = parserContext.length(sequence as Sequence);
7
11
 
8
12
  for (let index = 0; index < length; index++) {
9
13
  const element = await parserContext.read(0);
10
- const expectedElement = parserContext.at(sequence, index);
14
+ const expectedElement = parserContext.at(sequence as Sequence, index);
11
15
 
12
16
  parserContext.invariant(
13
17
  element === expectedElement,
@@ -26,14 +30,15 @@ export const createExactSequenceNaiveParser = <Sequence>(sequence: Sequence) =>
26
30
  return exactSequenceParser;
27
31
  };
28
32
 
29
- export const createExactSequenceParser = <Sequence>(expectedSequence: Sequence) => {
30
- const exactSequenceParser: Parser<Sequence, Sequence, unknown> = async parserContext => {
31
- const length = parserContext.length(expectedSequence);
33
+ export const createExactSequenceParser = <const Output>(expectedSequence: Output) => {
34
+ type Sequence = DeriveBaseSequence<Output>;
35
+ const exactSequenceParser: Parser<Output, Sequence, unknown> = async parserContext => {
36
+ const length = parserContext.length(expectedSequence as Sequence);
32
37
 
33
38
  const actualSequence = await parserContext.readSequence(0, length);
34
39
 
35
40
  parserContext.invariant(
36
- parserContext.equals(actualSequence, expectedSequence),
41
+ parserContext.equals(actualSequence, expectedSequence as Sequence),
37
42
  'Expected "%s", got "%s"',
38
43
  () => inspect(expectedSequence),
39
44
  () => inspect(actualSequence),
package/src/fetchCid.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import fsPromises from 'node:fs/promises';
2
- import path from 'node:path';
3
- import pMemoize from 'p-memoize';
4
- import envPaths from 'env-paths';
5
-
6
- const paths = envPaths('parser.futpib.github.io');
2
+ import { fetchCid as fetchCidFromIpfs } from '@futpib/fetch-cid';
7
3
 
8
4
  function readableWebStreamOnFinish<T>(readableWebStream: ReadableStream<T>, onFinish: () => void): ReadableStream<T> {
9
5
  const reader = readableWebStream.getReader();
@@ -34,74 +30,6 @@ function readableWebStreamOnFinish<T>(readableWebStream: ReadableStream<T>, onFi
34
30
  return stream;
35
31
  }
36
32
 
37
- class FsCache {
38
- private get _basePath() {
39
- return path.join(paths.cache, 'fetchCid');
40
- }
41
-
42
- private _getKeyPath(key: string) {
43
- return path.join(this._basePath, key.replaceAll('/', '_'));
44
- }
45
-
46
- async get(key: string): Promise<[ ReadableStream<Uint8Array>, ReadableStream<Uint8Array> ] | undefined> {
47
- try {
48
- const file = await fsPromises.open(this._getKeyPath(key), 'r');
49
-
50
- const stream = file.readableWebStream() as ReadableStream<Uint8Array>;
51
-
52
- const streamWithClose = readableWebStreamOnFinish(stream, () => {
53
- file.close();
54
- });
55
-
56
- return [ streamWithClose, undefined as unknown as ReadableStream<Uint8Array> ];
57
- } catch (error) {
58
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
59
- return undefined;
60
- }
61
-
62
- throw error;
63
- }
64
- }
65
-
66
- async has(key: string) {
67
- const streams = await this.get(key);
68
- try {
69
- return streams !== undefined;
70
- } finally {
71
- for (const stream of streams ?? []) {
72
- await stream?.cancel();
73
- }
74
- }
75
- }
76
-
77
- async set(key: string, [ _, value ]: [ ReadableStream<Uint8Array>, ReadableStream<Uint8Array> ]) {
78
- await fsPromises.mkdir(this._basePath, {
79
- recursive: true,
80
- });
81
- const file = await fsPromises.open(this._getKeyPath(key), 'w');
82
- try {
83
- for await (const chunk of value) {
84
- await file.write(chunk);
85
- }
86
- } finally {
87
- await file.close();
88
- }
89
- }
90
-
91
- async delete(key: string) {
92
- await fsPromises.unlink(this._getKeyPath(key));
93
- }
94
- }
95
-
96
- async function reallyFetchCid(cid: string): Promise<[ ReadableStream<Uint8Array>, ReadableStream<Uint8Array> ]> {
97
- const response = await fetch('https://ipfs.io/ipfs/' + cid);
98
- return response.body!.tee();
99
- }
100
-
101
- const cachedReallyFetchCid = pMemoize(reallyFetchCid, {
102
- cache: new FsCache(),
103
- });
104
-
105
33
  export async function fetchCid(cidOrPath: string): Promise<AsyncIterable<Uint8Array>> {
106
34
  if (cidOrPath.includes('/')) {
107
35
  const file = await fsPromises.open(cidOrPath, 'r');
@@ -115,7 +43,5 @@ export async function fetchCid(cidOrPath: string): Promise<AsyncIterable<Uint8Ar
115
43
  return streamWithClose;
116
44
  }
117
45
 
118
- const [ readable, unused ] = await cachedReallyFetchCid(cidOrPath);
119
- await unused?.cancel();
120
- return readable;
46
+ return fetchCidFromIpfs(cidOrPath);
121
47
  }