@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
package/src/bashParser.ts CHANGED
@@ -10,6 +10,7 @@ import { createOptionalParser } from './optionalParser.js';
10
10
  import { createRegExpParser } from './regexpParser.js';
11
11
  import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
12
12
  import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
13
+ import { createObjectParser } from './objectParser.js';
13
14
  import {
14
15
  type BashWord,
15
16
  type BashWordPart,
@@ -17,8 +18,11 @@ import {
17
18
  type BashWordPartSingleQuoted,
18
19
  type BashWordPartDoubleQuoted,
19
20
  type BashWordPartVariable,
21
+ type BashWordPartVariableBraced,
20
22
  type BashWordPartCommandSubstitution,
21
23
  type BashWordPartBacktickSubstitution,
24
+ type BashWordPartArithmeticExpansion,
25
+ type BashWordPartProcessSubstitution,
22
26
  type BashSimpleCommand,
23
27
  type BashSubshell,
24
28
  type BashBraceGroup,
@@ -30,14 +34,14 @@ import {
30
34
  type BashCommand,
31
35
  } from './bash.js';
32
36
 
33
- // Whitespace (spaces and tabs, not newlines - those are significant)
37
+ // Whitespace (spaces, tabs, and line continuations - not bare newlines which are significant)
34
38
  const bashInlineWhitespaceParser: Parser<string, string> = promiseCompose(
35
- createRegExpParser(/[ \t]+/),
39
+ createRegExpParser(/(?:[ \t]|\\\n)+/),
36
40
  match => match[0],
37
41
  );
38
42
 
39
43
  const bashOptionalInlineWhitespaceParser: Parser<string, string> = promiseCompose(
40
- createRegExpParser(/[ \t]*/),
44
+ createRegExpParser(/(?:[ \t]|\\\n)*/),
41
45
  match => match[0],
42
46
  );
43
47
 
@@ -49,26 +53,22 @@ const bashNewlineParser: Parser<string, string> = promiseCompose(
49
53
 
50
54
  // Word characters (unquoted, no special chars)
51
55
  // Note: {} are excluded so brace groups are parsed correctly
56
+ // # is excluded from the first character (starts a comment) but allowed mid-word
52
57
  const bashUnquotedWordCharsParser: Parser<string, string> = promiseCompose(
53
- createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#]+/),
58
+ createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#][^\s\n|&;<>(){}$`"'\\]*/),
54
59
  match => match[0],
55
60
  );
56
61
 
57
62
  // Single quoted string: '...'
58
- const bashSingleQuotedParser: Parser<BashWordPartSingleQuoted, string> = promiseCompose(
59
- createTupleParser([
60
- createExactSequenceParser("'"),
61
- promiseCompose(
62
- createRegExpParser(/[^']*/),
63
- match => match[0],
64
- ),
65
- createExactSequenceParser("'"),
66
- ]),
67
- ([, value]) => ({
68
- type: 'singleQuoted' as const,
69
- value,
70
- }),
71
- );
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
72
 
73
73
  // Variable name
74
74
  const bashVariableNameParser: Parser<string, string> = promiseCompose(
@@ -77,47 +77,84 @@ const bashVariableNameParser: Parser<string, string> = promiseCompose(
77
77
  );
78
78
 
79
79
  // Simple variable: $var
80
- const bashSimpleVariableParser: Parser<BashWordPartVariable, string> = promiseCompose(
81
- createTupleParser([
82
- createExactSequenceParser('$'),
83
- bashVariableNameParser,
84
- ]),
85
- ([, name]) => ({
86
- type: 'variable' as const,
87
- name,
88
- }),
89
- );
80
+ const bashSimpleVariableParser: Parser<BashWordPartVariable, string> = createObjectParser({
81
+ type: 'variable' as const,
82
+ _dollar: createExactSequenceParser('$'),
83
+ name: bashVariableNameParser,
84
+ });
90
85
 
91
86
  // Command substitution: $(...)
92
- const bashCommandSubstitutionParser: Parser<BashWordPartCommandSubstitution, string> = promiseCompose(
93
- createTupleParser([
94
- createExactSequenceParser('$('),
95
- bashOptionalInlineWhitespaceParser,
96
- createParserAccessorParser(() => bashCommandParser),
97
- bashOptionalInlineWhitespaceParser,
98
- createExactSequenceParser(')'),
99
- ]),
100
- ([, , command]) => ({
101
- type: 'commandSubstitution' as const,
102
- command,
103
- }),
104
- );
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
+ });
105
95
 
106
96
  // Backtick substitution: `...`
107
- const bashBacktickSubstitutionParser: Parser<BashWordPartBacktickSubstitution, string> = promiseCompose(
108
- createTupleParser([
109
- createExactSequenceParser('`'),
110
- createParserAccessorParser(() => bashCommandParser),
111
- createExactSequenceParser('`'),
112
- ]),
113
- ([, command]) => ({
114
- type: 'backtickSubstitution' as const,
115
- command,
116
- }),
117
- );
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
+ });
118
153
 
119
154
  // Double quoted string parts (inside "...")
120
155
  const bashDoubleQuotedPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
156
+ bashBracedVariableParser,
157
+ bashArithmeticExpansionParser,
121
158
  bashSimpleVariableParser,
122
159
  bashCommandSubstitutionParser,
123
160
  bashBacktickSubstitutionParser,
@@ -137,29 +174,37 @@ const bashDoubleQuotedPartParser: Parser<BashWordPart, string> = createDisjuncti
137
174
  value: match[0],
138
175
  }),
139
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
+ ),
140
193
  ]);
141
194
 
142
195
  // Double quoted string: "..."
143
- const bashDoubleQuotedParser: Parser<BashWordPartDoubleQuoted, string> = promiseCompose(
144
- createTupleParser([
145
- createExactSequenceParser('"'),
146
- createArrayParser(bashDoubleQuotedPartParser),
147
- createExactSequenceParser('"'),
148
- ]),
149
- ([, parts]) => ({
150
- type: 'doubleQuoted' as const,
151
- parts,
152
- }),
153
- );
196
+ const bashDoubleQuotedParser: Parser<BashWordPartDoubleQuoted, string> = createObjectParser({
197
+ type: 'doubleQuoted' as const,
198
+ _open: createExactSequenceParser('"'),
199
+ parts: createArrayParser(bashDoubleQuotedPartParser),
200
+ _close: createExactSequenceParser('"'),
201
+ });
154
202
 
155
203
  // Literal word part (unquoted)
156
- const bashLiteralWordPartParser: Parser<BashWordPartLiteral, string> = promiseCompose(
157
- bashUnquotedWordCharsParser,
158
- value => ({
159
- type: 'literal' as const,
160
- value,
161
- }),
162
- );
204
+ const bashLiteralWordPartParser: Parser<BashWordPartLiteral, string> = createObjectParser({
205
+ type: 'literal' as const,
206
+ value: bashUnquotedWordCharsParser,
207
+ });
163
208
 
164
209
  // Escape sequence outside quotes
165
210
  const bashEscapeParser: Parser<BashWordPartLiteral, string> = promiseCompose(
@@ -172,37 +217,42 @@ const bashEscapeParser: Parser<BashWordPartLiteral, string> = promiseCompose(
172
217
 
173
218
  // Word part (any part of a word)
174
219
  const bashWordPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
220
+ bashAnsiCQuotedParser,
175
221
  bashSingleQuotedParser,
176
222
  bashDoubleQuotedParser,
223
+ bashBracedVariableParser,
224
+ bashArithmeticExpansionParser,
177
225
  bashCommandSubstitutionParser,
178
226
  bashBacktickSubstitutionParser,
179
227
  bashSimpleVariableParser,
228
+ bashProcessSubstitutionParser,
180
229
  bashEscapeParser,
181
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
+ ),
182
239
  ]);
183
240
 
184
241
  // Word (sequence of word parts)
185
- export const bashWordParser: Parser<BashWord, string> = promiseCompose(
186
- createNonEmptyArrayParser(bashWordPartParser),
187
- parts => ({ parts }),
188
- );
242
+ export const bashWordParser: Parser<BashWord, string> = createObjectParser({
243
+ parts: createNonEmptyArrayParser(bashWordPartParser),
244
+ });
189
245
 
190
246
  setParserName(bashWordParser, 'bashWordParser');
191
247
 
192
248
  // Assignment: NAME=value or NAME=
193
- const bashAssignmentParser: Parser<BashAssignment, string> = promiseCompose(
194
- createTupleParser([
195
- promiseCompose(
196
- createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/),
197
- match => match[0].slice(0, -1),
198
- ),
199
- createOptionalParser(bashWordParser),
200
- ]),
201
- ([name, value]) => ({
202
- name,
203
- value: value ?? undefined,
204
- }),
205
- );
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
+ });
206
256
 
207
257
  // Redirect operators
208
258
  const bashRedirectOperatorParser: Parser<BashRedirect['operator'], string> = createDisjunctionParser([
@@ -217,22 +267,15 @@ const bashRedirectOperatorParser: Parser<BashRedirect['operator'], string> = cre
217
267
  ]);
218
268
 
219
269
  // Redirect: [n]op word
220
- const bashRedirectParser: Parser<BashRedirect, string> = promiseCompose(
221
- createTupleParser([
222
- createOptionalParser(promiseCompose(
223
- createRegExpParser(/[0-9]+/),
224
- match => Number.parseInt(match[0], 10),
225
- )),
226
- bashRedirectOperatorParser,
227
- bashOptionalInlineWhitespaceParser,
228
- bashWordParser,
229
- ]),
230
- ([fd, operator, , target]) => ({
231
- fd: fd ?? undefined,
232
- operator,
233
- target,
234
- }),
235
- );
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
+ });
236
279
 
237
280
  // Word with optional trailing whitespace - for use in arrays
238
281
  const bashWordWithWhitespaceParser: Parser<BashWord, string> = promiseCompose(
@@ -254,8 +297,8 @@ const bashRedirectWithWhitespaceParser: Parser<BashRedirect, string> = promiseCo
254
297
 
255
298
  // Word or redirect - for interleaved parsing in simple commands
256
299
  const bashWordOrRedirectParser: Parser<{ type: 'word'; word: BashWord } | { type: 'redirect'; redirect: BashRedirect }, string> = createDisjunctionParser([
257
- promiseCompose(bashRedirectWithWhitespaceParser, redirect => ({ type: 'redirect' as const, redirect })),
258
- promiseCompose(bashWordWithWhitespaceParser, word => ({ type: 'word' as const, word })),
300
+ createObjectParser({ type: 'redirect' as const, redirect: bashRedirectWithWhitespaceParser }),
301
+ createObjectParser({ type: 'word' as const, word: bashWordWithWhitespaceParser }),
259
302
  ]);
260
303
 
261
304
  // Simple command: [assignments] [name] [args] [redirects]
@@ -299,38 +342,28 @@ export const bashSimpleCommandParser: Parser<BashSimpleCommand, string> = promis
299
342
  setParserName(bashSimpleCommandParser, 'bashSimpleCommandParser');
300
343
 
301
344
  // Subshell: ( command )
302
- const bashSubshellParser: Parser<BashSubshell, string> = promiseCompose(
303
- createTupleParser([
304
- createExactSequenceParser('('),
305
- bashOptionalInlineWhitespaceParser,
306
- createParserAccessorParser(() => bashCommandParser),
307
- bashOptionalInlineWhitespaceParser,
308
- createExactSequenceParser(')'),
309
- ]),
310
- ([, , body]) => ({
311
- type: 'subshell' as const,
312
- body,
313
- }),
314
- );
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
+ });
315
353
 
316
354
  setParserName(bashSubshellParser, 'bashSubshellParser');
317
355
 
318
356
  // Brace group: { command; }
319
- const bashBraceGroupParser: Parser<BashBraceGroup, string> = promiseCompose(
320
- createTupleParser([
321
- createExactSequenceParser('{'),
322
- bashInlineWhitespaceParser,
323
- createParserAccessorParser(() => bashCommandParser),
324
- bashOptionalInlineWhitespaceParser,
325
- createOptionalParser(createExactSequenceParser(';')),
326
- bashOptionalInlineWhitespaceParser,
327
- createExactSequenceParser('}'),
328
- ]),
329
- ([, , body]) => ({
330
- type: 'braceGroup' as const,
331
- body,
332
- }),
333
- );
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
+ });
334
367
 
335
368
  setParserName(bashBraceGroupParser, 'bashBraceGroupParser');
336
369
 
@@ -390,15 +423,12 @@ const bashListSeparatorParser: Parser<'&&' | '||' | ';' | '&' | '\n', string> =
390
423
  const bashCommandListParser: Parser<BashCommandList, string> = promiseCompose(
391
424
  createTupleParser([
392
425
  bashPipelineParser,
393
- createArrayParser(promiseCompose(
394
- createTupleParser([
395
- bashOptionalInlineWhitespaceParser,
396
- bashListSeparatorParser,
397
- bashOptionalInlineWhitespaceParser,
398
- bashPipelineParser,
399
- ]),
400
- ([, separator, , pipeline]) => ({ separator, pipeline }),
401
- )),
426
+ createArrayParser(createObjectParser({
427
+ _ws1: bashOptionalInlineWhitespaceParser,
428
+ separator: bashListSeparatorParser,
429
+ _ws2: bashOptionalInlineWhitespaceParser,
430
+ pipeline: bashPipelineParser,
431
+ })),
402
432
  createOptionalParser(promiseCompose(
403
433
  createTupleParser([
404
434
  bashOptionalInlineWhitespaceParser,
@@ -448,12 +478,19 @@ export const bashCommandParser: Parser<BashCommand, string> = bashCommandListPar
448
478
 
449
479
  setParserName(bashCommandParser, 'bashCommandParser');
450
480
 
451
- // Script parser (handles leading/trailing whitespace)
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)
452
488
  export const bashScriptParser: Parser<BashCommand, string> = promiseCompose(
453
489
  createTupleParser([
454
490
  bashOptionalInlineWhitespaceParser,
455
491
  bashCommandParser,
456
492
  bashOptionalInlineWhitespaceParser,
493
+ bashOptionalCommentParser,
457
494
  ]),
458
495
  ([, command]) => command,
459
496
  );