@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
@@ -9,55 +9,85 @@ import { createOptionalParser } from './optionalParser.js';
9
9
  import { createRegExpParser } from './regexpParser.js';
10
10
  import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
11
11
  import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
12
- // Whitespace (spaces and tabs, not newlines - those are significant)
13
- const bashInlineWhitespaceParser = promiseCompose(createRegExpParser(/[ \t]+/), match => match[0]);
14
- const bashOptionalInlineWhitespaceParser = promiseCompose(createRegExpParser(/[ \t]*/), match => match[0]);
12
+ import { createObjectParser } from './objectParser.js';
13
+ // Whitespace (spaces, tabs, and line continuations - not bare newlines which are significant)
14
+ const bashInlineWhitespaceParser = promiseCompose(createRegExpParser(/(?:[ \t]|\\\n)+/), match => match[0]);
15
+ const bashOptionalInlineWhitespaceParser = promiseCompose(createRegExpParser(/(?:[ \t]|\\\n)*/), match => match[0]);
15
16
  // Newline
16
17
  const bashNewlineParser = promiseCompose(createRegExpParser(/\n/), match => match[0]);
17
18
  // Word characters (unquoted, no special chars)
18
19
  // Note: {} are excluded so brace groups are parsed correctly
19
- const bashUnquotedWordCharsParser = promiseCompose(createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#]+/), match => match[0]);
20
+ // # is excluded from the first character (starts a comment) but allowed mid-word
21
+ const bashUnquotedWordCharsParser = promiseCompose(createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#][^\s\n|&;<>(){}$`"'\\]*/), match => match[0]);
20
22
  // Single quoted string: '...'
21
- const bashSingleQuotedParser = promiseCompose(createTupleParser([
22
- createExactSequenceParser("'"),
23
- promiseCompose(createRegExpParser(/[^']*/), match => match[0]),
24
- createExactSequenceParser("'"),
25
- ]), ([, value]) => ({
23
+ const bashSingleQuotedParser = createObjectParser({
26
24
  type: 'singleQuoted',
27
- value,
28
- }));
25
+ _open: createExactSequenceParser("'"),
26
+ value: promiseCompose(createRegExpParser(/[^']*/), match => match[0]),
27
+ _close: createExactSequenceParser("'"),
28
+ });
29
29
  // Variable name
30
30
  const bashVariableNameParser = promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+|[@*#?$!-]/), match => match[0]);
31
31
  // Simple variable: $var
32
- const bashSimpleVariableParser = promiseCompose(createTupleParser([
33
- createExactSequenceParser('$'),
34
- bashVariableNameParser,
35
- ]), ([, name]) => ({
32
+ const bashSimpleVariableParser = createObjectParser({
36
33
  type: 'variable',
37
- name,
38
- }));
34
+ _dollar: createExactSequenceParser('$'),
35
+ name: bashVariableNameParser,
36
+ });
39
37
  // Command substitution: $(...)
40
- const bashCommandSubstitutionParser = promiseCompose(createTupleParser([
41
- createExactSequenceParser('$('),
42
- bashOptionalInlineWhitespaceParser,
43
- createParserAccessorParser(() => bashCommandParser),
44
- bashOptionalInlineWhitespaceParser,
45
- createExactSequenceParser(')'),
46
- ]), ([, , command]) => ({
38
+ const bashCommandSubstitutionParser = createObjectParser({
47
39
  type: 'commandSubstitution',
48
- command,
49
- }));
40
+ _open: createExactSequenceParser('$('),
41
+ _ws1: bashOptionalInlineWhitespaceParser,
42
+ command: createParserAccessorParser(() => bashCommandParser),
43
+ _ws2: bashOptionalInlineWhitespaceParser,
44
+ _close: createExactSequenceParser(')'),
45
+ });
50
46
  // Backtick substitution: `...`
51
- const bashBacktickSubstitutionParser = promiseCompose(createTupleParser([
52
- createExactSequenceParser('`'),
53
- createParserAccessorParser(() => bashCommandParser),
54
- createExactSequenceParser('`'),
55
- ]), ([, command]) => ({
47
+ const bashBacktickSubstitutionParser = createObjectParser({
56
48
  type: 'backtickSubstitution',
57
- command,
58
- }));
49
+ _open: createExactSequenceParser('`'),
50
+ command: createParserAccessorParser(() => bashCommandParser),
51
+ _close: createExactSequenceParser('`'),
52
+ });
53
+ // Braced variable expansion: ${VAR} or ${VAR:-default}
54
+ const bashBracedVariableParser = createObjectParser({
55
+ type: 'variableBraced',
56
+ _open: createExactSequenceParser('${'),
57
+ name: bashVariableNameParser,
58
+ operator: createOptionalParser(promiseCompose(createRegExpParser(/:-|:=|:\+|:\?|-|=|\+|\?|##|#|%%|%/), match => match[0])),
59
+ operand: createOptionalParser(createParserAccessorParser(() => bashWordParser)),
60
+ _close: createExactSequenceParser('}'),
61
+ });
62
+ // Arithmetic expansion: $((expression))
63
+ const bashArithmeticExpansionParser = createObjectParser({
64
+ type: 'arithmeticExpansion',
65
+ _open: createExactSequenceParser('$(('),
66
+ expression: promiseCompose(createRegExpParser(/(?:[^)]|\)(?!\)))*/), match => match[0]),
67
+ _close: createExactSequenceParser('))'),
68
+ });
69
+ // ANSI-C quoting: $'...'
70
+ const bashAnsiCQuotedParser = createObjectParser({
71
+ type: 'singleQuoted',
72
+ _prefix: createExactSequenceParser('$'),
73
+ _open: createExactSequenceParser("'"),
74
+ value: promiseCompose(createRegExpParser(/(?:[^'\\]|\\.)*/), match => match[0]),
75
+ _close: createExactSequenceParser("'"),
76
+ });
77
+ // Process substitution: <(cmd) or >(cmd)
78
+ const bashProcessSubstitutionParser = createObjectParser({
79
+ type: 'processSubstitution',
80
+ direction: promiseCompose(createRegExpParser(/[<>](?=\()/), match => match[0]),
81
+ _open: createExactSequenceParser('('),
82
+ _ws1: bashOptionalInlineWhitespaceParser,
83
+ command: createParserAccessorParser(() => bashCommandParser),
84
+ _ws2: bashOptionalInlineWhitespaceParser,
85
+ _close: createExactSequenceParser(')'),
86
+ });
59
87
  // Double quoted string parts (inside "...")
60
88
  const bashDoubleQuotedPartParser = createDisjunctionParser([
89
+ bashBracedVariableParser,
90
+ bashArithmeticExpansionParser,
61
91
  bashSimpleVariableParser,
62
92
  bashCommandSubstitutionParser,
63
93
  bashBacktickSubstitutionParser,
@@ -71,21 +101,29 @@ const bashDoubleQuotedPartParser = createDisjunctionParser([
71
101
  type: 'literal',
72
102
  value: match[0],
73
103
  })),
104
+ // Bare $ not followed by a valid expansion start (e.g. $" at end of double-quoted string)
105
+ promiseCompose(createRegExpParser(/\$/), () => ({
106
+ type: 'literal',
107
+ value: '$',
108
+ })),
109
+ // Bare \ not followed by a recognized escape character (treated as literal backslash in bash)
110
+ promiseCompose(createRegExpParser(/\\/), () => ({
111
+ type: 'literal',
112
+ value: '\\',
113
+ })),
74
114
  ]);
75
115
  // Double quoted string: "..."
76
- const bashDoubleQuotedParser = promiseCompose(createTupleParser([
77
- createExactSequenceParser('"'),
78
- createArrayParser(bashDoubleQuotedPartParser),
79
- createExactSequenceParser('"'),
80
- ]), ([, parts]) => ({
116
+ const bashDoubleQuotedParser = createObjectParser({
81
117
  type: 'doubleQuoted',
82
- parts,
83
- }));
118
+ _open: createExactSequenceParser('"'),
119
+ parts: createArrayParser(bashDoubleQuotedPartParser),
120
+ _close: createExactSequenceParser('"'),
121
+ });
84
122
  // Literal word part (unquoted)
85
- const bashLiteralWordPartParser = promiseCompose(bashUnquotedWordCharsParser, value => ({
123
+ const bashLiteralWordPartParser = createObjectParser({
86
124
  type: 'literal',
87
- value,
88
- }));
125
+ value: bashUnquotedWordCharsParser,
126
+ });
89
127
  // Escape sequence outside quotes
90
128
  const bashEscapeParser = promiseCompose(createRegExpParser(/\\./), match => ({
91
129
  type: 'literal',
@@ -93,25 +131,33 @@ const bashEscapeParser = promiseCompose(createRegExpParser(/\\./), match => ({
93
131
  }));
94
132
  // Word part (any part of a word)
95
133
  const bashWordPartParser = createDisjunctionParser([
134
+ bashAnsiCQuotedParser,
96
135
  bashSingleQuotedParser,
97
136
  bashDoubleQuotedParser,
137
+ bashBracedVariableParser,
138
+ bashArithmeticExpansionParser,
98
139
  bashCommandSubstitutionParser,
99
140
  bashBacktickSubstitutionParser,
100
141
  bashSimpleVariableParser,
142
+ bashProcessSubstitutionParser,
101
143
  bashEscapeParser,
102
144
  bashLiteralWordPartParser,
145
+ // Bare $ not followed by a valid expansion start
146
+ promiseCompose(createRegExpParser(/\$/), () => ({
147
+ type: 'literal',
148
+ value: '$',
149
+ })),
103
150
  ]);
104
151
  // Word (sequence of word parts)
105
- export const bashWordParser = promiseCompose(createNonEmptyArrayParser(bashWordPartParser), parts => ({ parts }));
152
+ export const bashWordParser = createObjectParser({
153
+ parts: createNonEmptyArrayParser(bashWordPartParser),
154
+ });
106
155
  setParserName(bashWordParser, 'bashWordParser');
107
156
  // Assignment: NAME=value or NAME=
108
- const bashAssignmentParser = promiseCompose(createTupleParser([
109
- promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/), match => match[0].slice(0, -1)),
110
- createOptionalParser(bashWordParser),
111
- ]), ([name, value]) => ({
112
- name,
113
- value: value ?? undefined,
114
- }));
157
+ const bashAssignmentParser = createObjectParser({
158
+ name: promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/), match => match[0].slice(0, -1)),
159
+ value: createOptionalParser(bashWordParser),
160
+ });
115
161
  // Redirect operators
116
162
  const bashRedirectOperatorParser = createDisjunctionParser([
117
163
  promiseCompose(createExactSequenceParser('>>'), () => '>>'),
@@ -124,16 +170,12 @@ const bashRedirectOperatorParser = createDisjunctionParser([
124
170
  promiseCompose(createExactSequenceParser('<'), () => '<'),
125
171
  ]);
126
172
  // Redirect: [n]op word
127
- const bashRedirectParser = promiseCompose(createTupleParser([
128
- createOptionalParser(promiseCompose(createRegExpParser(/[0-9]+/), match => Number.parseInt(match[0], 10))),
129
- bashRedirectOperatorParser,
130
- bashOptionalInlineWhitespaceParser,
131
- bashWordParser,
132
- ]), ([fd, operator, , target]) => ({
133
- fd: fd ?? undefined,
134
- operator,
135
- target,
136
- }));
173
+ const bashRedirectParser = createObjectParser({
174
+ fd: createOptionalParser(promiseCompose(createRegExpParser(/[0-9]+/), match => Number.parseInt(match[0], 10))),
175
+ operator: bashRedirectOperatorParser,
176
+ _ws: bashOptionalInlineWhitespaceParser,
177
+ target: bashWordParser,
178
+ });
137
179
  // Word with optional trailing whitespace - for use in arrays
138
180
  const bashWordWithWhitespaceParser = promiseCompose(createTupleParser([
139
181
  bashWordParser,
@@ -146,8 +188,8 @@ const bashRedirectWithWhitespaceParser = promiseCompose(createTupleParser([
146
188
  ]), ([redirect]) => redirect);
147
189
  // Word or redirect - for interleaved parsing in simple commands
148
190
  const bashWordOrRedirectParser = createDisjunctionParser([
149
- promiseCompose(bashRedirectWithWhitespaceParser, redirect => ({ type: 'redirect', redirect })),
150
- promiseCompose(bashWordWithWhitespaceParser, word => ({ type: 'word', word })),
191
+ createObjectParser({ type: 'redirect', redirect: bashRedirectWithWhitespaceParser }),
192
+ createObjectParser({ type: 'word', word: bashWordWithWhitespaceParser }),
151
193
  ]);
152
194
  // Simple command: [assignments] [name] [args] [redirects]
153
195
  export const bashSimpleCommandParser = promiseCompose(createTupleParser([
@@ -180,30 +222,26 @@ export const bashSimpleCommandParser = promiseCompose(createTupleParser([
180
222
  });
181
223
  setParserName(bashSimpleCommandParser, 'bashSimpleCommandParser');
182
224
  // Subshell: ( command )
183
- const bashSubshellParser = promiseCompose(createTupleParser([
184
- createExactSequenceParser('('),
185
- bashOptionalInlineWhitespaceParser,
186
- createParserAccessorParser(() => bashCommandParser),
187
- bashOptionalInlineWhitespaceParser,
188
- createExactSequenceParser(')'),
189
- ]), ([, , body]) => ({
225
+ const bashSubshellParser = createObjectParser({
190
226
  type: 'subshell',
191
- body,
192
- }));
227
+ _open: createExactSequenceParser('('),
228
+ _ws1: bashOptionalInlineWhitespaceParser,
229
+ body: createParserAccessorParser(() => bashCommandParser),
230
+ _ws2: bashOptionalInlineWhitespaceParser,
231
+ _close: createExactSequenceParser(')'),
232
+ });
193
233
  setParserName(bashSubshellParser, 'bashSubshellParser');
194
234
  // Brace group: { command; }
195
- const bashBraceGroupParser = promiseCompose(createTupleParser([
196
- createExactSequenceParser('{'),
197
- bashInlineWhitespaceParser,
198
- createParserAccessorParser(() => bashCommandParser),
199
- bashOptionalInlineWhitespaceParser,
200
- createOptionalParser(createExactSequenceParser(';')),
201
- bashOptionalInlineWhitespaceParser,
202
- createExactSequenceParser('}'),
203
- ]), ([, , body]) => ({
235
+ const bashBraceGroupParser = createObjectParser({
204
236
  type: 'braceGroup',
205
- body,
206
- }));
237
+ _open: createExactSequenceParser('{'),
238
+ _ws1: bashInlineWhitespaceParser,
239
+ body: createParserAccessorParser(() => bashCommandParser),
240
+ _ws2: bashOptionalInlineWhitespaceParser,
241
+ _semi: createOptionalParser(createExactSequenceParser(';')),
242
+ _ws3: bashOptionalInlineWhitespaceParser,
243
+ _close: createExactSequenceParser('}'),
244
+ });
207
245
  setParserName(bashBraceGroupParser, 'bashBraceGroupParser');
208
246
  // Command unit: simple command, subshell, or brace group
209
247
  const bashCommandUnitParser = createDisjunctionParser([
@@ -242,12 +280,12 @@ const bashListSeparatorParser = createDisjunctionParser([
242
280
  // Command list: pipeline [sep pipeline]...
243
281
  const bashCommandListParser = promiseCompose(createTupleParser([
244
282
  bashPipelineParser,
245
- createArrayParser(promiseCompose(createTupleParser([
246
- bashOptionalInlineWhitespaceParser,
247
- bashListSeparatorParser,
248
- bashOptionalInlineWhitespaceParser,
249
- bashPipelineParser,
250
- ]), ([, separator, , pipeline]) => ({ separator, pipeline }))),
283
+ createArrayParser(createObjectParser({
284
+ _ws1: bashOptionalInlineWhitespaceParser,
285
+ separator: bashListSeparatorParser,
286
+ _ws2: bashOptionalInlineWhitespaceParser,
287
+ pipeline: bashPipelineParser,
288
+ })),
251
289
  createOptionalParser(promiseCompose(createTupleParser([
252
290
  bashOptionalInlineWhitespaceParser,
253
291
  bashListSeparatorParser,
@@ -285,10 +323,13 @@ setParserName(bashCommandListParser, 'bashCommandListParser');
285
323
  // Top-level command parser
286
324
  export const bashCommandParser = bashCommandListParser;
287
325
  setParserName(bashCommandParser, 'bashCommandParser');
288
- // Script parser (handles leading/trailing whitespace)
326
+ // Comment: # through end of line
327
+ const bashOptionalCommentParser = createOptionalParser(promiseCompose(createRegExpParser(/#[^\n]*/), match => match[0]));
328
+ // Script parser (handles leading/trailing whitespace and comments)
289
329
  export const bashScriptParser = promiseCompose(createTupleParser([
290
330
  bashOptionalInlineWhitespaceParser,
291
331
  bashCommandParser,
292
332
  bashOptionalInlineWhitespaceParser,
333
+ bashOptionalCommentParser,
293
334
  ]), ([, command]) => command);
294
335
  setParserName(bashScriptParser, 'bashScriptParser');
@@ -98,6 +98,55 @@ test('double quoted string with variable', async (t) => {
98
98
  });
99
99
  }
100
100
  });
101
+ test('double quoted string with trailing dollar', async (t) => {
102
+ const result = await runParser(bashScriptParser, 'echo "hello$"', stringParserInputCompanion);
103
+ const cmd = result.entries[0].pipeline.commands[0];
104
+ if (cmd.type === 'simple') {
105
+ t.deepEqual(cmd.args[0], {
106
+ parts: [{
107
+ type: 'doubleQuoted',
108
+ parts: [
109
+ { type: 'literal', value: 'hello' },
110
+ { type: 'literal', value: '$' },
111
+ ],
112
+ }],
113
+ });
114
+ }
115
+ });
116
+ test('double quoted string with only dollar', async (t) => {
117
+ const result = await runParser(bashScriptParser, 'echo "$"', stringParserInputCompanion);
118
+ const cmd = result.entries[0].pipeline.commands[0];
119
+ if (cmd.type === 'simple') {
120
+ t.deepEqual(cmd.args[0], {
121
+ parts: [{
122
+ type: 'doubleQuoted',
123
+ parts: [
124
+ { type: 'literal', value: '$' },
125
+ ],
126
+ }],
127
+ });
128
+ }
129
+ });
130
+ test('grep with dollar anchor in double quotes', async (t) => {
131
+ const result = await runParser(bashScriptParser, 'grep "\\.ts$"', stringParserInputCompanion);
132
+ const cmd = result.entries[0].pipeline.commands[0];
133
+ if (cmd.type === 'simple') {
134
+ t.deepEqual(cmd.args[0], {
135
+ parts: [{
136
+ type: 'doubleQuoted',
137
+ parts: [
138
+ { type: 'literal', value: '\\' },
139
+ { type: 'literal', value: '.ts' },
140
+ { type: 'literal', value: '$' },
141
+ ],
142
+ }],
143
+ });
144
+ }
145
+ });
146
+ test('pipeline with dollar anchor in double quoted grep pattern', async (t) => {
147
+ const result = await runParser(bashScriptParser, 'ls -la /home | grep "\\.ts$" | grep -v "\\.test\\.ts"', stringParserInputCompanion);
148
+ t.is(result.entries[0].pipeline.commands.length, 3);
149
+ });
101
150
  test('simple variable', async (t) => {
102
151
  const result = await runParser(bashScriptParser, 'echo $HOME', stringParserInputCompanion);
103
152
  const cmd = result.entries[0].pipeline.commands[0];
@@ -172,6 +221,119 @@ test('[[ treated as command name', async (t) => {
172
221
  t.is(cmd.args.length, 3); // -f, file, ]]
173
222
  }
174
223
  });
224
+ // Braced variable expansion: ${VAR}
225
+ test('braced variable expansion', async (t) => {
226
+ const result = await runParser(bashScriptParser, 'echo ${HOME}', stringParserInputCompanion);
227
+ const cmd = result.entries[0].pipeline.commands[0];
228
+ if (cmd.type === 'simple') {
229
+ t.is(cmd.args[0].parts[0].type, 'variableBraced');
230
+ }
231
+ });
232
+ // Braced variable with default: ${VAR:-default}
233
+ test('braced variable with default value', async (t) => {
234
+ const result = await runParser(bashScriptParser, 'echo ${VAR:-default}', stringParserInputCompanion);
235
+ const cmd = result.entries[0].pipeline.commands[0];
236
+ if (cmd.type === 'simple') {
237
+ t.is(cmd.args[0].parts[0].type, 'variableBraced');
238
+ }
239
+ });
240
+ // Arithmetic expansion: $((1+2))
241
+ test('arithmetic expansion', async (t) => {
242
+ const result = await runParser(bashScriptParser, 'echo $((1+2))', stringParserInputCompanion);
243
+ const cmd = result.entries[0].pipeline.commands[0];
244
+ if (cmd.type === 'simple') {
245
+ t.is(cmd.args[0].parts[0].type, 'arithmeticExpansion');
246
+ }
247
+ });
248
+ // Bare $ at end of unquoted word
249
+ test('bare dollar at end of unquoted word', async (t) => {
250
+ const result = await runParser(bashScriptParser, 'echo foo$', stringParserInputCompanion);
251
+ const cmd = result.entries[0].pipeline.commands[0];
252
+ if (cmd.type === 'simple') {
253
+ t.deepEqual(cmd.args[0], {
254
+ parts: [
255
+ { type: 'literal', value: 'foo' },
256
+ { type: 'literal', value: '$' },
257
+ ],
258
+ });
259
+ }
260
+ });
261
+ // Bare $ as its own unquoted word
262
+ test('bare dollar as standalone unquoted word', async (t) => {
263
+ const result = await runParser(bashScriptParser, 'echo $', stringParserInputCompanion);
264
+ const cmd = result.entries[0].pipeline.commands[0];
265
+ if (cmd.type === 'simple') {
266
+ t.deepEqual(cmd.args[0], {
267
+ parts: [
268
+ { type: 'literal', value: '$' },
269
+ ],
270
+ });
271
+ }
272
+ });
273
+ // Comment after command
274
+ test('comment after command', async (t) => {
275
+ const result = await runParser(bashScriptParser, 'echo hello # this is a comment', stringParserInputCompanion);
276
+ const cmd = result.entries[0].pipeline.commands[0];
277
+ if (cmd.type === 'simple') {
278
+ t.is(cmd.args.length, 1);
279
+ t.deepEqual(cmd.args[0], {
280
+ parts: [{ type: 'literal', value: 'hello' }],
281
+ });
282
+ }
283
+ });
284
+ // ANSI-C quoting: $'...'
285
+ test('ansi-c quoting', async (t) => {
286
+ const result = await runParser(bashScriptParser, "echo $'hello\\nworld'", stringParserInputCompanion);
287
+ const cmd = result.entries[0].pipeline.commands[0];
288
+ if (cmd.type === 'simple') {
289
+ t.is(cmd.args.length, 1);
290
+ }
291
+ });
292
+ // Braced variable in double quotes: "${VAR}"
293
+ test('braced variable in double quotes', async (t) => {
294
+ const result = await runParser(bashScriptParser, 'echo "${HOME}"', stringParserInputCompanion);
295
+ const cmd = result.entries[0].pipeline.commands[0];
296
+ if (cmd.type === 'simple') {
297
+ const dq = cmd.args[0].parts[0];
298
+ if (dq.type === 'doubleQuoted') {
299
+ t.is(dq.parts[0].type, 'variableBraced');
300
+ }
301
+ }
302
+ });
303
+ // Arithmetic expansion in double quotes
304
+ test('arithmetic expansion in double quotes', async (t) => {
305
+ const result = await runParser(bashScriptParser, 'echo "$((1+2))"', stringParserInputCompanion);
306
+ const cmd = result.entries[0].pipeline.commands[0];
307
+ if (cmd.type === 'simple') {
308
+ const dq = cmd.args[0].parts[0];
309
+ if (dq.type === 'doubleQuoted') {
310
+ t.is(dq.parts[0].type, 'arithmeticExpansion');
311
+ }
312
+ }
313
+ });
314
+ // Process substitution: <(cmd)
315
+ test('process substitution input', async (t) => {
316
+ const result = await runParser(bashScriptParser, 'diff <(sort file1) <(sort file2)', stringParserInputCompanion);
317
+ t.truthy(result);
318
+ });
319
+ // Line continuation (backslash-newline)
320
+ test('line continuation', async (t) => {
321
+ const result = await runParser(bashScriptParser, 'echo hello \\\nworld', stringParserInputCompanion);
322
+ const cmd = result.entries[0].pipeline.commands[0];
323
+ if (cmd.type === 'simple') {
324
+ t.is(cmd.args.length, 2);
325
+ }
326
+ });
327
+ // Hash in middle of unquoted word is literal, not a comment
328
+ test('hash in middle of unquoted word', async (t) => {
329
+ const result = await runParser(bashScriptParser, 'echo foo#bar', stringParserInputCompanion);
330
+ const cmd = result.entries[0].pipeline.commands[0];
331
+ if (cmd.type === 'simple') {
332
+ t.deepEqual(cmd.args[0], {
333
+ parts: [{ type: 'literal', value: 'foo#bar' }],
334
+ });
335
+ }
336
+ });
175
337
  test('if treated as command name', async (t) => {
176
338
  const result = await runParser(bashScriptParser, 'if true', stringParserInputCompanion);
177
339
  const cmd = result.entries[0].pipeline.commands[0];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,117 @@
1
+ import test from 'ava';
2
+ import { runParser } from './parser.js';
3
+ import { stringParserInputCompanion } from './parserInputCompanion.js';
4
+ import { bashScriptParser } from './bashParser.js';
5
+ // Braced variable expansion: ${VAR}
6
+ test('braced variable expansion', async (t) => {
7
+ const result = await runParser(bashScriptParser, 'echo ${HOME}', stringParserInputCompanion);
8
+ const cmd = result.entries[0].pipeline.commands[0];
9
+ if (cmd.type === 'simple') {
10
+ t.is(cmd.args[0].parts[0].type, 'variableBraced');
11
+ }
12
+ });
13
+ // Braced variable with default: ${VAR:-default}
14
+ test('braced variable with default value', async (t) => {
15
+ const result = await runParser(bashScriptParser, 'echo ${VAR:-default}', stringParserInputCompanion);
16
+ const cmd = result.entries[0].pipeline.commands[0];
17
+ if (cmd.type === 'simple') {
18
+ t.is(cmd.args[0].parts[0].type, 'variableBraced');
19
+ }
20
+ });
21
+ // Arithmetic expansion: $((1+2))
22
+ test('arithmetic expansion', async (t) => {
23
+ const result = await runParser(bashScriptParser, 'echo $((1+2))', stringParserInputCompanion);
24
+ const cmd = result.entries[0].pipeline.commands[0];
25
+ if (cmd.type === 'simple') {
26
+ t.is(cmd.args[0].parts[0].type, 'arithmeticExpansion');
27
+ }
28
+ });
29
+ // Bare $ at end of unquoted word
30
+ test('bare dollar at end of unquoted word', async (t) => {
31
+ const result = await runParser(bashScriptParser, 'echo foo$', stringParserInputCompanion);
32
+ const cmd = result.entries[0].pipeline.commands[0];
33
+ if (cmd.type === 'simple') {
34
+ t.deepEqual(cmd.args[0], {
35
+ parts: [
36
+ { type: 'literal', value: 'foo' },
37
+ { type: 'literal', value: '$' },
38
+ ],
39
+ });
40
+ }
41
+ });
42
+ // Bare $ as its own unquoted word
43
+ test('bare dollar as standalone unquoted word', async (t) => {
44
+ const result = await runParser(bashScriptParser, 'echo $', stringParserInputCompanion);
45
+ const cmd = result.entries[0].pipeline.commands[0];
46
+ if (cmd.type === 'simple') {
47
+ t.deepEqual(cmd.args[0], {
48
+ parts: [
49
+ { type: 'literal', value: '$' },
50
+ ],
51
+ });
52
+ }
53
+ });
54
+ // Comment after command
55
+ test('comment after command', async (t) => {
56
+ const result = await runParser(bashScriptParser, 'echo hello # this is a comment', stringParserInputCompanion);
57
+ const cmd = result.entries[0].pipeline.commands[0];
58
+ if (cmd.type === 'simple') {
59
+ t.is(cmd.args.length, 1);
60
+ t.deepEqual(cmd.args[0], {
61
+ parts: [{ type: 'literal', value: 'hello' }],
62
+ });
63
+ }
64
+ });
65
+ // ANSI-C quoting: $'...'
66
+ test('ansi-c quoting', async (t) => {
67
+ const result = await runParser(bashScriptParser, "echo $'hello\\nworld'", stringParserInputCompanion);
68
+ const cmd = result.entries[0].pipeline.commands[0];
69
+ if (cmd.type === 'simple') {
70
+ t.is(cmd.args.length, 1);
71
+ }
72
+ });
73
+ // Braced variable in double quotes: "${VAR}"
74
+ test('braced variable in double quotes', async (t) => {
75
+ const result = await runParser(bashScriptParser, 'echo "${HOME}"', stringParserInputCompanion);
76
+ const cmd = result.entries[0].pipeline.commands[0];
77
+ if (cmd.type === 'simple') {
78
+ const dq = cmd.args[0].parts[0];
79
+ if (dq.type === 'doubleQuoted') {
80
+ t.is(dq.parts[0].type, 'variableBraced');
81
+ }
82
+ }
83
+ });
84
+ // Arithmetic expansion in double quotes
85
+ test('arithmetic expansion in double quotes', async (t) => {
86
+ const result = await runParser(bashScriptParser, 'echo "$((1+2))"', stringParserInputCompanion);
87
+ const cmd = result.entries[0].pipeline.commands[0];
88
+ if (cmd.type === 'simple') {
89
+ const dq = cmd.args[0].parts[0];
90
+ if (dq.type === 'doubleQuoted') {
91
+ t.is(dq.parts[0].type, 'arithmeticExpansion');
92
+ }
93
+ }
94
+ });
95
+ // Process substitution: <(cmd)
96
+ test('process substitution input', async (t) => {
97
+ const result = await runParser(bashScriptParser, 'diff <(sort file1) <(sort file2)', stringParserInputCompanion);
98
+ t.truthy(result);
99
+ });
100
+ // Line continuation (backslash-newline)
101
+ test('line continuation', async (t) => {
102
+ const result = await runParser(bashScriptParser, 'echo hello \\\nworld', stringParserInputCompanion);
103
+ const cmd = result.entries[0].pipeline.commands[0];
104
+ if (cmd.type === 'simple') {
105
+ t.is(cmd.args.length, 2);
106
+ }
107
+ });
108
+ // Hash in middle of unquoted word is literal, not a comment
109
+ test('hash in middle of unquoted word', async (t) => {
110
+ const result = await runParser(bashScriptParser, 'echo foo#bar', stringParserInputCompanion);
111
+ const cmd = result.entries[0].pipeline.commands[0];
112
+ if (cmd.type === 'simple') {
113
+ t.deepEqual(cmd.args[0], {
114
+ parts: [{ type: 'literal', value: 'foo#bar' }],
115
+ });
116
+ }
117
+ });