@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
@@ -0,0 +1,498 @@
1
+ import { type Parser, setParserName } from './parser.js';
2
+ import { createUnionParser } from './unionParser.js';
3
+ import { createExactSequenceParser } from './exactSequenceParser.js';
4
+ import { promiseCompose } from './promiseCompose.js';
5
+ import { createTupleParser } from './tupleParser.js';
6
+ import { createDisjunctionParser } from './disjunctionParser.js';
7
+ import { createArrayParser } from './arrayParser.js';
8
+ import { createParserAccessorParser } from './parserAccessorParser.js';
9
+ import { createOptionalParser } from './optionalParser.js';
10
+ import { createRegExpParser } from './regexpParser.js';
11
+ import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
12
+ import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
13
+ import { createObjectParser } from './objectParser.js';
14
+ import {
15
+ type BashWord,
16
+ type BashWordPart,
17
+ type BashWordPartLiteral,
18
+ type BashWordPartSingleQuoted,
19
+ type BashWordPartDoubleQuoted,
20
+ type BashWordPartVariable,
21
+ type BashWordPartVariableBraced,
22
+ type BashWordPartCommandSubstitution,
23
+ type BashWordPartBacktickSubstitution,
24
+ type BashWordPartArithmeticExpansion,
25
+ type BashWordPartProcessSubstitution,
26
+ type BashSimpleCommand,
27
+ type BashSubshell,
28
+ type BashBraceGroup,
29
+ type BashCommandUnit,
30
+ type BashPipeline,
31
+ type BashCommandList,
32
+ type BashRedirect,
33
+ type BashAssignment,
34
+ type BashCommand,
35
+ } from './bash.js';
36
+
37
+ // Whitespace (spaces, tabs, and line continuations - not bare newlines which are significant)
38
+ const bashInlineWhitespaceParser: Parser<string, string> = promiseCompose(
39
+ createRegExpParser(/(?:[ \t]|\\\n)+/),
40
+ match => match[0],
41
+ );
42
+
43
+ const bashOptionalInlineWhitespaceParser: Parser<string, string> = promiseCompose(
44
+ createRegExpParser(/(?:[ \t]|\\\n)*/),
45
+ match => match[0],
46
+ );
47
+
48
+ // Newline
49
+ const bashNewlineParser: Parser<string, string> = promiseCompose(
50
+ createRegExpParser(/\n/),
51
+ match => match[0],
52
+ );
53
+
54
+ // Word characters (unquoted, no special chars)
55
+ // Note: {} are excluded so brace groups are parsed correctly
56
+ // # is excluded from the first character (starts a comment) but allowed mid-word
57
+ const bashUnquotedWordCharsParser: Parser<string, string> = promiseCompose(
58
+ createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#][^\s\n|&;<>(){}$`"'\\]*/),
59
+ match => match[0],
60
+ );
61
+
62
+ // Single quoted string: '...'
63
+ const bashSingleQuotedParser: Parser<BashWordPartSingleQuoted, string> = createObjectParser({
64
+ type: 'singleQuoted' as const,
65
+ _open: createExactSequenceParser("'"),
66
+ value: promiseCompose(
67
+ createRegExpParser(/[^']*/),
68
+ match => match[0],
69
+ ),
70
+ _close: createExactSequenceParser("'"),
71
+ });
72
+
73
+ // Variable name
74
+ const bashVariableNameParser: Parser<string, string> = promiseCompose(
75
+ createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+|[@*#?$!-]/),
76
+ match => match[0],
77
+ );
78
+
79
+ // Simple variable: $var
80
+ const bashSimpleVariableParser: Parser<BashWordPartVariable, string> = createObjectParser({
81
+ type: 'variable' as const,
82
+ _dollar: createExactSequenceParser('$'),
83
+ name: bashVariableNameParser,
84
+ });
85
+
86
+ // Command substitution: $(...)
87
+ const bashCommandSubstitutionParser: Parser<BashWordPartCommandSubstitution, string> = createObjectParser({
88
+ type: 'commandSubstitution' as const,
89
+ _open: createExactSequenceParser('$('),
90
+ _ws1: bashOptionalInlineWhitespaceParser,
91
+ command: createParserAccessorParser(() => bashCommandParser),
92
+ _ws2: bashOptionalInlineWhitespaceParser,
93
+ _close: createExactSequenceParser(')'),
94
+ });
95
+
96
+ // Backtick substitution: `...`
97
+ const bashBacktickSubstitutionParser: Parser<BashWordPartBacktickSubstitution, string> = createObjectParser({
98
+ type: 'backtickSubstitution' as const,
99
+ _open: createExactSequenceParser('`'),
100
+ command: createParserAccessorParser(() => bashCommandParser),
101
+ _close: createExactSequenceParser('`'),
102
+ });
103
+
104
+ // Braced variable expansion: ${VAR} or ${VAR:-default}
105
+ const bashBracedVariableParser: Parser<BashWordPartVariableBraced, string> = createObjectParser({
106
+ type: 'variableBraced' as const,
107
+ _open: createExactSequenceParser('${'),
108
+ name: bashVariableNameParser,
109
+ operator: createOptionalParser(promiseCompose(
110
+ createRegExpParser(/:-|:=|:\+|:\?|-|=|\+|\?|##|#|%%|%/),
111
+ match => match[0],
112
+ )),
113
+ operand: createOptionalParser(createParserAccessorParser(() => bashWordParser)),
114
+ _close: createExactSequenceParser('}'),
115
+ });
116
+
117
+ // Arithmetic expansion: $((expression))
118
+ const bashArithmeticExpansionParser: Parser<BashWordPartArithmeticExpansion, string> = createObjectParser({
119
+ type: 'arithmeticExpansion' as const,
120
+ _open: createExactSequenceParser('$(('),
121
+ expression: promiseCompose(
122
+ createRegExpParser(/(?:[^)]|\)(?!\)))*/),
123
+ match => match[0],
124
+ ),
125
+ _close: createExactSequenceParser('))'),
126
+ });
127
+
128
+ // ANSI-C quoting: $'...'
129
+ const bashAnsiCQuotedParser: Parser<BashWordPartSingleQuoted, string> = createObjectParser({
130
+ type: 'singleQuoted' as const,
131
+ _prefix: createExactSequenceParser('$'),
132
+ _open: createExactSequenceParser("'"),
133
+ value: promiseCompose(
134
+ createRegExpParser(/(?:[^'\\]|\\.)*/),
135
+ match => match[0],
136
+ ),
137
+ _close: createExactSequenceParser("'"),
138
+ });
139
+
140
+ // Process substitution: <(cmd) or >(cmd)
141
+ const bashProcessSubstitutionParser: Parser<BashWordPartProcessSubstitution, string> = createObjectParser({
142
+ type: 'processSubstitution' as const,
143
+ direction: promiseCompose(
144
+ createRegExpParser(/[<>](?=\()/),
145
+ match => match[0] as '<' | '>',
146
+ ),
147
+ _open: createExactSequenceParser('('),
148
+ _ws1: bashOptionalInlineWhitespaceParser,
149
+ command: createParserAccessorParser(() => bashCommandParser),
150
+ _ws2: bashOptionalInlineWhitespaceParser,
151
+ _close: createExactSequenceParser(')'),
152
+ });
153
+
154
+ // Double quoted string parts (inside "...")
155
+ const bashDoubleQuotedPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
156
+ bashBracedVariableParser,
157
+ bashArithmeticExpansionParser,
158
+ bashSimpleVariableParser,
159
+ bashCommandSubstitutionParser,
160
+ bashBacktickSubstitutionParser,
161
+ // Escape sequences in double quotes
162
+ promiseCompose(
163
+ createRegExpParser(/\\[\\$`"!\n]/),
164
+ match => ({
165
+ type: 'literal' as const,
166
+ value: match[0].slice(1),
167
+ }),
168
+ ),
169
+ // Literal text (no special chars)
170
+ promiseCompose(
171
+ createRegExpParser(/[^$`"\\]+/),
172
+ match => ({
173
+ type: 'literal' as const,
174
+ value: match[0],
175
+ }),
176
+ ),
177
+ // Bare $ not followed by a valid expansion start (e.g. $" at end of double-quoted string)
178
+ promiseCompose(
179
+ createRegExpParser(/\$/),
180
+ () => ({
181
+ type: 'literal' as const,
182
+ value: '$',
183
+ }),
184
+ ),
185
+ // Bare \ not followed by a recognized escape character (treated as literal backslash in bash)
186
+ promiseCompose(
187
+ createRegExpParser(/\\/),
188
+ () => ({
189
+ type: 'literal' as const,
190
+ value: '\\',
191
+ }),
192
+ ),
193
+ ]);
194
+
195
+ // Double quoted string: "..."
196
+ const bashDoubleQuotedParser: Parser<BashWordPartDoubleQuoted, string> = createObjectParser({
197
+ type: 'doubleQuoted' as const,
198
+ _open: createExactSequenceParser('"'),
199
+ parts: createArrayParser(bashDoubleQuotedPartParser),
200
+ _close: createExactSequenceParser('"'),
201
+ });
202
+
203
+ // Literal word part (unquoted)
204
+ const bashLiteralWordPartParser: Parser<BashWordPartLiteral, string> = createObjectParser({
205
+ type: 'literal' as const,
206
+ value: bashUnquotedWordCharsParser,
207
+ });
208
+
209
+ // Escape sequence outside quotes
210
+ const bashEscapeParser: Parser<BashWordPartLiteral, string> = promiseCompose(
211
+ createRegExpParser(/\\./),
212
+ match => ({
213
+ type: 'literal' as const,
214
+ value: match[0].slice(1),
215
+ }),
216
+ );
217
+
218
+ // Word part (any part of a word)
219
+ const bashWordPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
220
+ bashAnsiCQuotedParser,
221
+ bashSingleQuotedParser,
222
+ bashDoubleQuotedParser,
223
+ bashBracedVariableParser,
224
+ bashArithmeticExpansionParser,
225
+ bashCommandSubstitutionParser,
226
+ bashBacktickSubstitutionParser,
227
+ bashSimpleVariableParser,
228
+ bashProcessSubstitutionParser,
229
+ bashEscapeParser,
230
+ bashLiteralWordPartParser,
231
+ // Bare $ not followed by a valid expansion start
232
+ promiseCompose(
233
+ createRegExpParser(/\$/),
234
+ () => ({
235
+ type: 'literal' as const,
236
+ value: '$',
237
+ }),
238
+ ),
239
+ ]);
240
+
241
+ // Word (sequence of word parts)
242
+ export const bashWordParser: Parser<BashWord, string> = createObjectParser({
243
+ parts: createNonEmptyArrayParser(bashWordPartParser),
244
+ });
245
+
246
+ setParserName(bashWordParser, 'bashWordParser');
247
+
248
+ // Assignment: NAME=value or NAME=
249
+ const bashAssignmentParser: Parser<BashAssignment, string> = createObjectParser({
250
+ name: promiseCompose(
251
+ createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/),
252
+ match => match[0].slice(0, -1),
253
+ ),
254
+ value: createOptionalParser(bashWordParser),
255
+ });
256
+
257
+ // Redirect operators
258
+ const bashRedirectOperatorParser: Parser<BashRedirect['operator'], string> = createDisjunctionParser([
259
+ promiseCompose(createExactSequenceParser('>>'), () => '>>' as const),
260
+ promiseCompose(createExactSequenceParser('>&'), () => '>&' as const),
261
+ promiseCompose(createExactSequenceParser('>|'), () => '>|' as const),
262
+ promiseCompose(createExactSequenceParser('>'), () => '>' as const),
263
+ promiseCompose(createExactSequenceParser('<<<'), () => '<<<' as const),
264
+ promiseCompose(createExactSequenceParser('<<'), () => '<<' as const),
265
+ promiseCompose(createExactSequenceParser('<&'), () => '<&' as const),
266
+ promiseCompose(createExactSequenceParser('<'), () => '<' as const),
267
+ ]);
268
+
269
+ // Redirect: [n]op word
270
+ const bashRedirectParser: Parser<BashRedirect, string> = createObjectParser({
271
+ fd: createOptionalParser(promiseCompose(
272
+ createRegExpParser(/[0-9]+/),
273
+ match => Number.parseInt(match[0], 10),
274
+ )),
275
+ operator: bashRedirectOperatorParser,
276
+ _ws: bashOptionalInlineWhitespaceParser,
277
+ target: bashWordParser,
278
+ });
279
+
280
+ // Word with optional trailing whitespace - for use in arrays
281
+ const bashWordWithWhitespaceParser: Parser<BashWord, string> = promiseCompose(
282
+ createTupleParser([
283
+ bashWordParser,
284
+ bashOptionalInlineWhitespaceParser,
285
+ ]),
286
+ ([word]) => word,
287
+ );
288
+
289
+ // Redirect with optional trailing whitespace
290
+ const bashRedirectWithWhitespaceParser: Parser<BashRedirect, string> = promiseCompose(
291
+ createTupleParser([
292
+ bashRedirectParser,
293
+ bashOptionalInlineWhitespaceParser,
294
+ ]),
295
+ ([redirect]) => redirect,
296
+ );
297
+
298
+ // Word or redirect - for interleaved parsing in simple commands
299
+ const bashWordOrRedirectParser: Parser<{ type: 'word'; word: BashWord } | { type: 'redirect'; redirect: BashRedirect }, string> = createDisjunctionParser([
300
+ createObjectParser({ type: 'redirect' as const, redirect: bashRedirectWithWhitespaceParser }),
301
+ createObjectParser({ type: 'word' as const, word: bashWordWithWhitespaceParser }),
302
+ ]);
303
+
304
+ // Simple command: [assignments] [name] [args] [redirects]
305
+ export const bashSimpleCommandParser: Parser<BashSimpleCommand, string> = promiseCompose(
306
+ createTupleParser([
307
+ // Assignments at the start
308
+ createArrayParser(promiseCompose(
309
+ createTupleParser([
310
+ bashAssignmentParser,
311
+ bashOptionalInlineWhitespaceParser,
312
+ ]),
313
+ ([assignment]) => assignment,
314
+ )),
315
+ // Command name, args, and redirects (interleaved)
316
+ createArrayParser(bashWordOrRedirectParser),
317
+ ]),
318
+ ([assignments, items]) => {
319
+ const words: BashWord[] = [];
320
+ const redirects: BashRedirect[] = [];
321
+
322
+ for (const item of items) {
323
+ if (item.type === 'word') {
324
+ words.push(item.word);
325
+ } else {
326
+ redirects.push(item.redirect);
327
+ }
328
+ }
329
+
330
+ const [name, ...args] = words;
331
+
332
+ return {
333
+ type: 'simple' as const,
334
+ name,
335
+ args,
336
+ redirects,
337
+ assignments,
338
+ };
339
+ },
340
+ );
341
+
342
+ setParserName(bashSimpleCommandParser, 'bashSimpleCommandParser');
343
+
344
+ // Subshell: ( command )
345
+ const bashSubshellParser: Parser<BashSubshell, string> = createObjectParser({
346
+ type: 'subshell' as const,
347
+ _open: createExactSequenceParser('('),
348
+ _ws1: bashOptionalInlineWhitespaceParser,
349
+ body: createParserAccessorParser(() => bashCommandParser),
350
+ _ws2: bashOptionalInlineWhitespaceParser,
351
+ _close: createExactSequenceParser(')'),
352
+ });
353
+
354
+ setParserName(bashSubshellParser, 'bashSubshellParser');
355
+
356
+ // Brace group: { command; }
357
+ const bashBraceGroupParser: Parser<BashBraceGroup, string> = createObjectParser({
358
+ type: 'braceGroup' as const,
359
+ _open: createExactSequenceParser('{'),
360
+ _ws1: bashInlineWhitespaceParser,
361
+ body: createParserAccessorParser(() => bashCommandParser),
362
+ _ws2: bashOptionalInlineWhitespaceParser,
363
+ _semi: createOptionalParser(createExactSequenceParser(';')),
364
+ _ws3: bashOptionalInlineWhitespaceParser,
365
+ _close: createExactSequenceParser('}'),
366
+ });
367
+
368
+ setParserName(bashBraceGroupParser, 'bashBraceGroupParser');
369
+
370
+ // Command unit: simple command, subshell, or brace group
371
+ const bashCommandUnitParser: Parser<BashCommandUnit, string> = createDisjunctionParser([
372
+ bashSubshellParser,
373
+ bashBraceGroupParser,
374
+ bashSimpleCommandParser,
375
+ ]);
376
+
377
+ setParserName(bashCommandUnitParser, 'bashCommandUnitParser');
378
+
379
+ // Single pipe (not ||) - matches | only when not followed by another |
380
+ const bashSinglePipeParser: Parser<string, string> = promiseCompose(
381
+ createRegExpParser(/\|(?!\|)/),
382
+ match => match[0],
383
+ );
384
+
385
+ // Pipeline: [!] cmd [| cmd]...
386
+ const bashPipelineParser: Parser<BashPipeline, string> = promiseCompose(
387
+ createTupleParser([
388
+ createOptionalParser(promiseCompose(
389
+ createTupleParser([
390
+ createExactSequenceParser('!'),
391
+ bashInlineWhitespaceParser,
392
+ ]),
393
+ () => true,
394
+ )),
395
+ createSeparatedNonEmptyArrayParser(
396
+ bashCommandUnitParser,
397
+ createTupleParser([
398
+ bashOptionalInlineWhitespaceParser,
399
+ bashSinglePipeParser,
400
+ bashOptionalInlineWhitespaceParser,
401
+ ]),
402
+ ),
403
+ ]),
404
+ ([negated, commands]) => ({
405
+ type: 'pipeline' as const,
406
+ negated: negated ?? false,
407
+ commands,
408
+ }),
409
+ );
410
+
411
+ setParserName(bashPipelineParser, 'bashPipelineParser');
412
+
413
+ // Command list separator
414
+ const bashListSeparatorParser: Parser<'&&' | '||' | ';' | '&' | '\n', string> = createDisjunctionParser([
415
+ promiseCompose(createExactSequenceParser('&&'), () => '&&' as const),
416
+ promiseCompose(createExactSequenceParser('||'), () => '||' as const),
417
+ promiseCompose(createExactSequenceParser(';'), () => ';' as const),
418
+ promiseCompose(createExactSequenceParser('&'), () => '&' as const),
419
+ promiseCompose(bashNewlineParser, () => '\n' as const),
420
+ ]);
421
+
422
+ // Command list: pipeline [sep pipeline]...
423
+ const bashCommandListParser: Parser<BashCommandList, string> = promiseCompose(
424
+ createTupleParser([
425
+ bashPipelineParser,
426
+ createArrayParser(createObjectParser({
427
+ _ws1: bashOptionalInlineWhitespaceParser,
428
+ separator: bashListSeparatorParser,
429
+ _ws2: bashOptionalInlineWhitespaceParser,
430
+ pipeline: bashPipelineParser,
431
+ })),
432
+ createOptionalParser(promiseCompose(
433
+ createTupleParser([
434
+ bashOptionalInlineWhitespaceParser,
435
+ bashListSeparatorParser,
436
+ ]),
437
+ ([, separator]) => separator,
438
+ )),
439
+ ]),
440
+ ([firstPipeline, rest, trailingSeparator]) => {
441
+ const entries: BashCommandList['entries'] = [];
442
+
443
+ if (rest.length === 0) {
444
+ entries.push({
445
+ pipeline: firstPipeline,
446
+ separator: trailingSeparator ?? undefined,
447
+ });
448
+ } else {
449
+ entries.push({
450
+ pipeline: firstPipeline,
451
+ separator: rest[0].separator,
452
+ });
453
+
454
+ for (let i = 0; i < rest.length - 1; i++) {
455
+ entries.push({
456
+ pipeline: rest[i].pipeline,
457
+ separator: rest[i + 1].separator,
458
+ });
459
+ }
460
+
461
+ entries.push({
462
+ pipeline: rest[rest.length - 1].pipeline,
463
+ separator: trailingSeparator ?? undefined,
464
+ });
465
+ }
466
+
467
+ return {
468
+ type: 'list' as const,
469
+ entries,
470
+ };
471
+ },
472
+ );
473
+
474
+ setParserName(bashCommandListParser, 'bashCommandListParser');
475
+
476
+ // Top-level command parser
477
+ export const bashCommandParser: Parser<BashCommand, string> = bashCommandListParser;
478
+
479
+ setParserName(bashCommandParser, 'bashCommandParser');
480
+
481
+ // Comment: # through end of line
482
+ const bashOptionalCommentParser: Parser<string | undefined, string> = createOptionalParser(promiseCompose(
483
+ createRegExpParser(/#[^\n]*/),
484
+ match => match[0],
485
+ ));
486
+
487
+ // Script parser (handles leading/trailing whitespace and comments)
488
+ export const bashScriptParser: Parser<BashCommand, string> = promiseCompose(
489
+ createTupleParser([
490
+ bashOptionalInlineWhitespaceParser,
491
+ bashCommandParser,
492
+ bashOptionalInlineWhitespaceParser,
493
+ bashOptionalCommentParser,
494
+ ]),
495
+ ([, command]) => command,
496
+ );
497
+
498
+ setParserName(bashScriptParser, 'bashScriptParser');