@futpib/parser 1.0.4 → 1.0.7
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.
- package/.claude/settings.local.json +24 -0
- package/.github/workflows/main.yml +1 -0
- package/build/androidPackageParser.js +30 -32
- package/build/arbitraryDalvikBytecode.d.ts +3 -3
- package/build/arbitraryDalvikBytecode.js +33 -27
- package/build/arbitraryDalvikExecutable.js +55 -17
- package/build/arbitraryJava.d.ts +31 -0
- package/build/arbitraryJava.js +532 -0
- package/build/arbitraryJavaScript.d.ts +3 -0
- package/build/arbitraryJavaScript.js +263 -0
- package/build/arbitraryJavascript.d.ts +3 -0
- package/build/arbitraryJavascript.js +263 -0
- package/build/arbitraryZig.d.ts +3 -0
- package/build/arbitraryZig.js +240 -0
- package/build/arbitraryZipStream.d.ts +1 -1
- package/build/arrayParser.js +72 -13
- package/build/backsmali.d.ts +4 -3
- package/build/backsmali.js +26 -6
- package/build/bash.d.ts +6 -1
- package/build/bashParser.js +414 -131
- package/build/bashParser.test.js +233 -0
- package/build/bashParserEdgeCases.test.d.ts +1 -0
- package/build/bashParserEdgeCases.test.js +117 -0
- package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
- package/build/dalvikBytecodeParser/addressConversion.js +334 -0
- package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
- package/build/dalvikBytecodeParser/formatParsers.js +13 -14
- package/build/dalvikBytecodeParser.d.ts +60 -31
- package/build/dalvikBytecodeParser.js +92 -35
- package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
- package/build/dalvikBytecodeParser.test-d.js +268 -0
- package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
- package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
- package/build/dalvikBytecodeUnparser.d.ts +2 -2
- package/build/dalvikBytecodeUnparser.js +23 -23
- package/build/dalvikBytecodeUnparser.test.js +7 -7
- package/build/dalvikExecutable.d.ts +3 -3
- package/build/dalvikExecutable.test-d.d.ts +1 -0
- package/build/dalvikExecutable.test-d.js +59 -0
- package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
- package/build/dalvikExecutableParser/typedNumbers.js +3 -0
- package/build/dalvikExecutableParser.d.ts +2 -1
- package/build/dalvikExecutableParser.js +96 -77
- package/build/dalvikExecutableParser.test.js +24 -3
- package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
- package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
- package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
- package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
- package/build/dalvikExecutableUnparser.d.ts +2 -2
- package/build/dalvikExecutableUnparser.test.js +2 -1
- package/build/disjunctionParser.d.ts +5 -3
- package/build/disjunctionParser.js +79 -17
- package/build/disjunctionParser.test-d.d.ts +1 -0
- package/build/disjunctionParser.test-d.js +72 -0
- package/build/elementSwitchParser.d.ts +4 -0
- package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
- package/build/elementSwitchParser.test-d.d.ts +1 -0
- package/build/elementSwitchParser.test-d.js +44 -0
- package/build/exactSequenceParser.d.ts +4 -2
- package/build/exactSequenceParser.test-d.d.ts +1 -0
- package/build/exactSequenceParser.test-d.js +36 -0
- package/build/fetchCid.js +2 -66
- package/build/index.d.ts +4 -2
- package/build/index.js +3 -1
- package/build/index.test.js +16 -1
- package/build/inputReader.d.ts +10 -0
- package/build/inputReader.js +36 -0
- package/build/java.d.ts +502 -0
- package/build/java.js +2 -0
- package/build/javaKeyStoreParser.js +14 -17
- package/build/javaParser.d.ts +51 -0
- package/build/javaParser.js +1538 -0
- package/build/javaParser.test.d.ts +1 -0
- package/build/javaParser.test.js +1287 -0
- package/build/javaScript.d.ts +35 -0
- package/build/javaScript.js +1 -0
- package/build/javaScriptParser.d.ts +9 -0
- package/build/javaScriptParser.js +34 -0
- package/build/javaScriptUnparser.d.ts +3 -0
- package/build/javaScriptUnparser.js +4 -0
- package/build/javaScriptUnparser.test.d.ts +1 -0
- package/build/javaScriptUnparser.test.js +24 -0
- package/build/javaUnparser.d.ts +2 -0
- package/build/javaUnparser.js +519 -0
- package/build/javaUnparser.test.d.ts +1 -0
- package/build/javaUnparser.test.js +24 -0
- package/build/javascript.d.ts +35 -0
- package/build/javascript.js +1 -0
- package/build/javascriptParser.d.ts +9 -0
- package/build/javascriptParser.js +34 -0
- package/build/javascriptUnparser.d.ts +3 -0
- package/build/javascriptUnparser.js +4 -0
- package/build/javascriptUnparser.test.d.ts +1 -0
- package/build/javascriptUnparser.test.js +24 -0
- package/build/jsonParser.js +2 -12
- package/build/lazyMessageError.d.ts +3 -0
- package/build/lookaheadParser.js +60 -3
- package/build/negativeLookaheadParser.js +70 -11
- package/build/nonEmptyArrayParser.js +72 -13
- package/build/objectParser.d.ts +12 -0
- package/build/objectParser.js +31 -0
- package/build/objectParser.test-d.d.ts +1 -0
- package/build/objectParser.test-d.js +112 -0
- package/build/objectParser.test.d.ts +1 -0
- package/build/objectParser.test.js +55 -0
- package/build/optionalParser.js +69 -10
- package/build/parser.d.ts +4 -0
- package/build/parser.js +3 -1
- package/build/parser.test.js +114 -1
- package/build/parserConsumedSequenceParser.js +66 -7
- package/build/parserContext.d.ts +6 -0
- package/build/parserContext.js +20 -11
- package/build/parserError.d.ts +119 -27
- package/build/parserError.js +16 -8
- package/build/predicateElementParser.d.ts +3 -0
- package/build/predicateElementParser.js +10 -0
- package/build/regexpParser.js +33 -3
- package/build/regexpParser.test.js +31 -0
- package/build/regularExpressionParser.js +35 -15
- package/build/separatedArrayParser.js +73 -14
- package/build/separatedNonEmptyArrayParser.js +73 -14
- package/build/sliceBoundedParser.js +62 -5
- package/build/smaliParser.d.ts +7 -7
- package/build/smaliParser.js +185 -268
- package/build/smaliParser.test.js +58 -0
- package/build/stringEscapes.d.ts +5 -0
- package/build/stringEscapes.js +244 -0
- package/build/symbolicExpression.d.ts +29 -0
- package/build/symbolicExpression.js +1 -0
- package/build/symbolicExpressionParser.d.ts +4 -0
- package/build/symbolicExpressionParser.js +123 -0
- package/build/symbolicExpressionParser.test.d.ts +1 -0
- package/build/symbolicExpressionParser.test.js +289 -0
- package/build/terminatedArrayParser.js +113 -38
- package/build/terminatedArrayParser.test.js +4 -2
- package/build/tupleParser.d.ts +7 -15
- package/build/tupleParser.js +1 -0
- package/build/unionParser.d.ts +5 -3
- package/build/unionParser.js +7 -2
- package/build/unionParser.test-d.d.ts +1 -0
- package/build/unionParser.test-d.js +72 -0
- package/build/unionParser.test.js +10 -11
- package/build/zig.d.ts +280 -0
- package/build/zig.js +2 -0
- package/build/zigParser.d.ts +3 -0
- package/build/zigParser.js +1119 -0
- package/build/zigParser.test.d.ts +1 -0
- package/build/zigParser.test.js +1590 -0
- package/build/zigUnparser.d.ts +2 -0
- package/build/zigUnparser.js +460 -0
- package/build/zigUnparser.test.d.ts +1 -0
- package/build/zigUnparser.test.js +24 -0
- package/build/zipParser.js +19 -32
- package/build/zipUnparser.js +19 -7
- package/build/zipUnparser.test.js +1 -1
- package/node_modules-@types/s-expression/index.d.ts +5 -0
- package/package.json +24 -6
- package/src/androidPackageParser.ts +33 -60
- package/src/arbitraryDalvikBytecode.ts +39 -31
- package/src/arbitraryDalvikExecutable.ts +65 -20
- package/src/arbitraryJava.ts +804 -0
- package/src/arbitraryJavaScript.ts +410 -0
- package/src/arbitraryZig.ts +380 -0
- package/src/arrayParser.ts +1 -3
- package/src/backsmali.ts +35 -4
- package/src/bash.ts +8 -1
- package/src/bashParser.test.ts +396 -0
- package/src/bashParser.ts +564 -199
- package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
- package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
- package/src/dalvikBytecodeParser.test-d.ts +310 -0
- package/src/dalvikBytecodeParser.ts +194 -69
- package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
- package/src/dalvikBytecodeUnparser.test.ts +7 -7
- package/src/dalvikBytecodeUnparser.ts +31 -30
- package/src/dalvikExecutable.test-d.ts +132 -0
- package/src/dalvikExecutable.ts +3 -3
- package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
- package/src/dalvikExecutableParser.test.ts +37 -3
- package/src/dalvikExecutableParser.test.ts.md +163 -2
- package/src/dalvikExecutableParser.test.ts.snap +0 -0
- package/src/dalvikExecutableParser.ts +121 -139
- package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
- package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
- package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
- package/src/dalvikExecutableUnparser.test.ts +3 -2
- package/src/dalvikExecutableUnparser.ts +4 -4
- package/src/disjunctionParser.test-d.ts +105 -0
- package/src/disjunctionParser.ts +18 -15
- package/src/elementSwitchParser.test-d.ts +74 -0
- package/src/elementSwitchParser.ts +51 -0
- package/src/exactSequenceParser.test-d.ts +43 -0
- package/src/exactSequenceParser.ts +13 -8
- package/src/fetchCid.ts +2 -76
- package/src/index.test.ts +22 -1
- package/src/index.ts +11 -1
- package/src/inputReader.ts +53 -0
- package/src/java.ts +708 -0
- package/src/javaKeyStoreParser.ts +18 -32
- package/src/javaParser.test.ts +1592 -0
- package/src/javaParser.ts +2640 -0
- package/src/javaScript.ts +36 -0
- package/src/javaScriptParser.ts +57 -0
- package/src/javaScriptUnparser.test.ts +37 -0
- package/src/javaScriptUnparser.ts +7 -0
- package/src/javaUnparser.test.ts +37 -0
- package/src/javaUnparser.ts +640 -0
- package/src/jsonParser.ts +6 -27
- package/src/lookaheadParser.ts +2 -6
- package/src/negativeLookaheadParser.ts +1 -3
- package/src/nonEmptyArrayParser.ts +1 -3
- package/src/objectParser.test-d.ts +152 -0
- package/src/objectParser.test.ts +71 -0
- package/src/objectParser.ts +69 -0
- package/src/optionalParser.ts +1 -3
- package/src/parser.test.ts +151 -4
- package/src/parser.ts +11 -1
- package/src/parserConsumedSequenceParser.ts +2 -4
- package/src/parserContext.ts +26 -11
- package/src/parserError.ts +17 -3
- package/src/predicateElementParser.ts +22 -0
- package/src/regexpParser.test.ts +78 -0
- package/src/regexpParser.ts +35 -3
- package/src/regularExpressionParser.ts +36 -37
- package/src/separatedArrayParser.ts +1 -3
- package/src/separatedNonEmptyArrayParser.ts +1 -3
- package/src/sliceBoundedParser.test.ts +2 -2
- package/src/sliceBoundedParser.ts +15 -19
- package/src/smaliParser.test.ts +64 -0
- package/src/smaliParser.test.ts.md +12 -12
- package/src/smaliParser.test.ts.snap +0 -0
- package/src/smaliParser.ts +246 -534
- package/src/stringEscapes.ts +253 -0
- package/src/symbolicExpression.ts +17 -0
- package/src/symbolicExpressionParser.test.ts +466 -0
- package/src/symbolicExpressionParser.ts +190 -0
- package/src/terminatedArrayParser.test.ts +9 -6
- package/src/terminatedArrayParser.ts +25 -29
- package/src/tupleParser.ts +21 -18
- package/src/unionParser.test-d.ts +105 -0
- package/src/unionParser.test.ts +18 -17
- package/src/unionParser.ts +28 -16
- package/src/zig.ts +411 -0
- package/src/zigParser.test.ts +1693 -0
- package/src/zigParser.ts +1745 -0
- package/src/zigUnparser.test.ts +37 -0
- package/src/zigUnparser.ts +615 -0
- package/src/zipParser.ts +20 -56
- package/src/zipUnparser.test.ts +1 -1
- package/src/zipUnparser.ts +22 -7
- package/tsconfig.json +2 -2
- package/build/exactElementSwitchParser.d.ts +0 -3
- package/src/exactElementSwitchParser.ts +0 -41
package/src/bashParser.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type Parser, setParserName } from './parser.js';
|
|
2
|
-
import { createUnionParser } from './unionParser.js';
|
|
3
2
|
import { createExactSequenceParser } from './exactSequenceParser.js';
|
|
3
|
+
import { createElementParser } from './elementParser.js';
|
|
4
|
+
import { createPredicateElementParser } from './predicateElementParser.js';
|
|
5
|
+
import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
|
|
6
|
+
import { createLookaheadParser } from './lookaheadParser.js';
|
|
4
7
|
import { promiseCompose } from './promiseCompose.js';
|
|
5
8
|
import { createTupleParser } from './tupleParser.js';
|
|
6
9
|
import { createDisjunctionParser } from './disjunctionParser.js';
|
|
7
10
|
import { createArrayParser } from './arrayParser.js';
|
|
8
11
|
import { createParserAccessorParser } from './parserAccessorParser.js';
|
|
9
12
|
import { createOptionalParser } from './optionalParser.js';
|
|
10
|
-
import { createRegExpParser } from './regexpParser.js';
|
|
11
13
|
import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
|
|
12
14
|
import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
|
|
15
|
+
import { createObjectParser } from './objectParser.js';
|
|
13
16
|
import {
|
|
14
17
|
type BashWord,
|
|
15
18
|
type BashWordPart,
|
|
@@ -17,8 +20,11 @@ import {
|
|
|
17
20
|
type BashWordPartSingleQuoted,
|
|
18
21
|
type BashWordPartDoubleQuoted,
|
|
19
22
|
type BashWordPartVariable,
|
|
23
|
+
type BashWordPartVariableBraced,
|
|
20
24
|
type BashWordPartCommandSubstitution,
|
|
21
25
|
type BashWordPartBacktickSubstitution,
|
|
26
|
+
type BashWordPartArithmeticExpansion,
|
|
27
|
+
type BashWordPartProcessSubstitution,
|
|
22
28
|
type BashSimpleCommand,
|
|
23
29
|
type BashSubshell,
|
|
24
30
|
type BashBraceGroup,
|
|
@@ -30,180 +36,495 @@ import {
|
|
|
30
36
|
type BashCommand,
|
|
31
37
|
} from './bash.js';
|
|
32
38
|
|
|
33
|
-
//
|
|
39
|
+
// Character predicates
|
|
40
|
+
function isDigit(ch: string): boolean {
|
|
41
|
+
return ch >= '0' && ch <= '9';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isLetter(ch: string): boolean {
|
|
45
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isIdentStart(ch: string): boolean {
|
|
49
|
+
return isLetter(ch) || ch === '_';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isIdentChar(ch: string): boolean {
|
|
53
|
+
return isIdentStart(ch) || isDigit(ch);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Whitespace (spaces, tabs, and line continuations - not bare newlines which are significant)
|
|
57
|
+
const bashInlineWhitespaceUnitParser: Parser<string, string> = createDisjunctionParser([
|
|
58
|
+
promiseCompose(createExactSequenceParser(' '), () => ' '),
|
|
59
|
+
promiseCompose(createExactSequenceParser('\t'), () => '\t'),
|
|
60
|
+
promiseCompose(createExactSequenceParser('\\\n'), () => '\\\n'),
|
|
61
|
+
]);
|
|
62
|
+
|
|
34
63
|
const bashInlineWhitespaceParser: Parser<string, string> = promiseCompose(
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
createNonEmptyArrayParser(bashInlineWhitespaceUnitParser),
|
|
65
|
+
parts => parts.join(''),
|
|
37
66
|
);
|
|
38
67
|
|
|
39
68
|
const bashOptionalInlineWhitespaceParser: Parser<string, string> = promiseCompose(
|
|
40
|
-
|
|
41
|
-
|
|
69
|
+
createArrayParser(bashInlineWhitespaceUnitParser),
|
|
70
|
+
parts => parts.join(''),
|
|
42
71
|
);
|
|
43
72
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
// Word characters (unquoted, no special chars)
|
|
74
|
+
// Note: {} and # are excluded from the first character so brace groups and comments are parsed correctly,
|
|
75
|
+
// but allowed as continuation characters for mid-word braces (e.g., file.{c,h}, foo}bar) and hash (foo#bar)
|
|
76
|
+
const bashSpecialCharParser: Parser<unknown, string> = createDisjunctionParser(
|
|
77
|
+
[...' \t\n|&;<>()$`"\'\\'].map(ch => createExactSequenceParser(ch)),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const bashWordStartExcludeParser: Parser<unknown, string> = createDisjunctionParser([
|
|
81
|
+
bashSpecialCharParser,
|
|
82
|
+
createExactSequenceParser('{'),
|
|
83
|
+
createExactSequenceParser('}'),
|
|
84
|
+
createExactSequenceParser('#'),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const bashUnquotedWordStartCharParser: Parser<string, string> = promiseCompose(
|
|
88
|
+
createTupleParser([
|
|
89
|
+
createNegativeLookaheadParser(bashWordStartExcludeParser),
|
|
90
|
+
createElementParser<string>(),
|
|
91
|
+
]),
|
|
92
|
+
([, ch]) => ch,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const bashUnquotedWordContinueCharParser: Parser<string, string> = promiseCompose(
|
|
96
|
+
createTupleParser([
|
|
97
|
+
createNegativeLookaheadParser(bashSpecialCharParser),
|
|
98
|
+
createElementParser<string>(),
|
|
99
|
+
]),
|
|
100
|
+
([, ch]) => ch,
|
|
48
101
|
);
|
|
49
102
|
|
|
50
|
-
// Word characters (unquoted, no special chars)
|
|
51
|
-
// Note: {} are excluded so brace groups are parsed correctly
|
|
52
103
|
const bashUnquotedWordCharsParser: Parser<string, string> = promiseCompose(
|
|
53
|
-
|
|
54
|
-
|
|
104
|
+
createTupleParser([
|
|
105
|
+
bashUnquotedWordStartCharParser,
|
|
106
|
+
createArrayParser(bashUnquotedWordContinueCharParser),
|
|
107
|
+
]),
|
|
108
|
+
([first, rest]) => first + rest.join(''),
|
|
55
109
|
);
|
|
56
110
|
|
|
111
|
+
// Consume characters until a given terminator, returning the accumulated string
|
|
112
|
+
function createUntilCharParser(terminator: string): Parser<string, string> {
|
|
113
|
+
return promiseCompose(
|
|
114
|
+
createArrayParser(promiseCompose(
|
|
115
|
+
createTupleParser([
|
|
116
|
+
createNegativeLookaheadParser(createExactSequenceParser(terminator)),
|
|
117
|
+
createElementParser<string>(),
|
|
118
|
+
]),
|
|
119
|
+
([, ch]) => ch,
|
|
120
|
+
)),
|
|
121
|
+
chars => chars.join(''),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
57
125
|
// Single quoted string: '...'
|
|
58
|
-
const bashSingleQuotedParser: Parser<BashWordPartSingleQuoted, string> =
|
|
126
|
+
const bashSingleQuotedParser: Parser<BashWordPartSingleQuoted, string> = createObjectParser({
|
|
127
|
+
type: 'singleQuoted' as const,
|
|
128
|
+
_open: createExactSequenceParser("'"),
|
|
129
|
+
value: createUntilCharParser("'"),
|
|
130
|
+
_close: createExactSequenceParser("'"),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Variable name: identifiers, positional params ($0, $1...), or special params ($@, $*, $#, $?, $$, $!, $-)
|
|
134
|
+
const bashSpecialParams = new Set(['@', '*', '#', '?', '$', '!', '-']);
|
|
135
|
+
|
|
136
|
+
const bashIdentifierParser: Parser<string, string> = promiseCompose(
|
|
59
137
|
createTupleParser([
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
createRegExpParser(/[^']*/),
|
|
63
|
-
match => match[0],
|
|
64
|
-
),
|
|
65
|
-
createExactSequenceParser("'"),
|
|
138
|
+
createPredicateElementParser<string>(isIdentStart),
|
|
139
|
+
createArrayParser(createPredicateElementParser<string>(isIdentChar)),
|
|
66
140
|
]),
|
|
67
|
-
([,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
141
|
+
([first, rest]) => first + rest.join(''),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const bashDigitsParser: Parser<string, string> = promiseCompose(
|
|
145
|
+
createNonEmptyArrayParser(createPredicateElementParser<string>(isDigit)),
|
|
146
|
+
chars => chars.join(''),
|
|
71
147
|
);
|
|
72
148
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+|[@*#?$!-]/),
|
|
76
|
-
match => match[0],
|
|
149
|
+
const bashSpecialParamParser: Parser<string, string> = createPredicateElementParser<string>(
|
|
150
|
+
ch => bashSpecialParams.has(ch),
|
|
77
151
|
);
|
|
78
152
|
|
|
153
|
+
const bashVariableNameParser: Parser<string, string> = createDisjunctionParser([
|
|
154
|
+
bashIdentifierParser,
|
|
155
|
+
bashDigitsParser,
|
|
156
|
+
bashSpecialParamParser,
|
|
157
|
+
]);
|
|
158
|
+
|
|
79
159
|
// Simple variable: $var
|
|
80
|
-
const bashSimpleVariableParser: Parser<BashWordPartVariable, string> =
|
|
160
|
+
const bashSimpleVariableParser: Parser<BashWordPartVariable, string> = createObjectParser({
|
|
161
|
+
type: 'variable' as const,
|
|
162
|
+
_dollar: createExactSequenceParser('$'),
|
|
163
|
+
name: bashVariableNameParser,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Command substitution: $(...)
|
|
167
|
+
const bashCommandSubstitutionParser: Parser<BashWordPartCommandSubstitution, string> = createObjectParser({
|
|
168
|
+
type: 'commandSubstitution' as const,
|
|
169
|
+
_open: createExactSequenceParser('$('),
|
|
170
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
171
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
172
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
173
|
+
_close: createExactSequenceParser(')'),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Backtick substitution: `...`
|
|
177
|
+
const bashBacktickSubstitutionParser: Parser<BashWordPartBacktickSubstitution, string> = createObjectParser({
|
|
178
|
+
type: 'backtickSubstitution' as const,
|
|
179
|
+
_open: createExactSequenceParser('`'),
|
|
180
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
181
|
+
_close: createExactSequenceParser('`'),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Word characters for use inside ${...} operands (} excluded from continuation to not consume the closing brace)
|
|
185
|
+
const bashBracedVarContinueExcludeParser: Parser<unknown, string> = createDisjunctionParser([
|
|
186
|
+
bashSpecialCharParser,
|
|
187
|
+
createExactSequenceParser('{'),
|
|
188
|
+
createExactSequenceParser('}'),
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
const bashBracedVarUnquotedWordCharsParser: Parser<string, string> = promiseCompose(
|
|
81
192
|
createTupleParser([
|
|
82
|
-
|
|
83
|
-
|
|
193
|
+
bashUnquotedWordStartCharParser,
|
|
194
|
+
createArrayParser(promiseCompose(
|
|
195
|
+
createTupleParser([
|
|
196
|
+
createNegativeLookaheadParser(bashBracedVarContinueExcludeParser),
|
|
197
|
+
createElementParser<string>(),
|
|
198
|
+
]),
|
|
199
|
+
([, ch]) => ch,
|
|
200
|
+
)),
|
|
84
201
|
]),
|
|
85
|
-
([,
|
|
86
|
-
type: 'variable' as const,
|
|
87
|
-
name,
|
|
88
|
-
}),
|
|
202
|
+
([first, rest]) => first + rest.join(''),
|
|
89
203
|
);
|
|
90
204
|
|
|
91
|
-
|
|
92
|
-
|
|
205
|
+
const bashBracedVarLiteralWordPartParser: Parser<BashWordPartLiteral, string> = createObjectParser({
|
|
206
|
+
type: 'literal' as const,
|
|
207
|
+
value: bashBracedVarUnquotedWordCharsParser,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Braced variable expansion: ${VAR} or ${VAR:-default}
|
|
211
|
+
const bashBracedVariableParser: Parser<BashWordPartVariableBraced, string> = createObjectParser({
|
|
212
|
+
type: 'variableBraced' as const,
|
|
213
|
+
_open: createExactSequenceParser('${'),
|
|
214
|
+
name: bashVariableNameParser,
|
|
215
|
+
operator: createOptionalParser(createDisjunctionParser([
|
|
216
|
+
promiseCompose(createExactSequenceParser(':-'), () => ':-'),
|
|
217
|
+
promiseCompose(createExactSequenceParser(':='), () => ':='),
|
|
218
|
+
promiseCompose(createExactSequenceParser(':+'), () => ':+'),
|
|
219
|
+
promiseCompose(createExactSequenceParser(':?'), () => ':?'),
|
|
220
|
+
promiseCompose(createExactSequenceParser('##'), () => '##'),
|
|
221
|
+
promiseCompose(createExactSequenceParser('%%'), () => '%%'),
|
|
222
|
+
promiseCompose(createExactSequenceParser('-'), () => '-'),
|
|
223
|
+
promiseCompose(createExactSequenceParser('='), () => '='),
|
|
224
|
+
promiseCompose(createExactSequenceParser('+'), () => '+'),
|
|
225
|
+
promiseCompose(createExactSequenceParser('?'), () => '?'),
|
|
226
|
+
promiseCompose(createExactSequenceParser('#'), () => '#'),
|
|
227
|
+
promiseCompose(createExactSequenceParser('%'), () => '%'),
|
|
228
|
+
])),
|
|
229
|
+
operand: createOptionalParser(createParserAccessorParser(() => bashBracedVarWordParser)),
|
|
230
|
+
_close: createExactSequenceParser('}'),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Arithmetic expansion: $((expression)) - handles nested parentheses
|
|
234
|
+
const bashArithmeticExpressionParser: Parser<string, string> = async (parserContext) => {
|
|
235
|
+
let result = '';
|
|
236
|
+
let depth = 0;
|
|
237
|
+
for (;;) {
|
|
238
|
+
const ch = await parserContext.peek(0);
|
|
239
|
+
if (ch === undefined) {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (ch === '(') {
|
|
244
|
+
depth++;
|
|
245
|
+
result += ch;
|
|
246
|
+
parserContext.skip(1);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (ch === ')') {
|
|
251
|
+
if (depth > 0) {
|
|
252
|
+
depth--;
|
|
253
|
+
result += ch;
|
|
254
|
+
parserContext.skip(1);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// At depth 0, a ')' means we've hit the closing '))' of $((
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
result += ch;
|
|
263
|
+
parserContext.skip(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return result;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const bashArithmeticExpansionParser: Parser<BashWordPartArithmeticExpansion, string> = createObjectParser({
|
|
270
|
+
type: 'arithmeticExpansion' as const,
|
|
271
|
+
_open: createExactSequenceParser('$(('),
|
|
272
|
+
expression: bashArithmeticExpressionParser,
|
|
273
|
+
_close: createExactSequenceParser('))'),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ANSI-C quoting: $'...' - content can include \' escapes
|
|
277
|
+
// Each unit is either a backslash-escape pair or a non-quote character
|
|
278
|
+
const bashAnsiCContentUnitParser: Parser<string, string> = createDisjunctionParser([
|
|
279
|
+
// Backslash escape: \x (any char after backslash)
|
|
280
|
+
promiseCompose(
|
|
281
|
+
createTupleParser([
|
|
282
|
+
createExactSequenceParser('\\'),
|
|
283
|
+
createElementParser<string>(),
|
|
284
|
+
]),
|
|
285
|
+
([bs, ch]) => bs + ch,
|
|
286
|
+
),
|
|
287
|
+
// Any character that isn't ' (and isn't \ which is handled above)
|
|
288
|
+
promiseCompose(
|
|
289
|
+
createTupleParser([
|
|
290
|
+
createNegativeLookaheadParser(createExactSequenceParser("'")),
|
|
291
|
+
createElementParser<string>(),
|
|
292
|
+
]),
|
|
293
|
+
([, ch]) => ch,
|
|
294
|
+
),
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
const bashAnsiCContentParser: Parser<string, string> = promiseCompose(
|
|
298
|
+
createArrayParser(bashAnsiCContentUnitParser),
|
|
299
|
+
parts => parts.join(''),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const bashAnsiCQuotedParser: Parser<BashWordPartSingleQuoted, string> = createObjectParser({
|
|
303
|
+
type: 'singleQuoted' as const,
|
|
304
|
+
_prefix: createExactSequenceParser('$'),
|
|
305
|
+
_open: createExactSequenceParser("'"),
|
|
306
|
+
value: bashAnsiCContentParser,
|
|
307
|
+
_close: createExactSequenceParser("'"),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Process substitution: <(cmd) or >(cmd)
|
|
311
|
+
const bashProcessSubstitutionDirectionParser: Parser<'<' | '>', string> = promiseCompose(
|
|
93
312
|
createTupleParser([
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
createExactSequenceParser('
|
|
313
|
+
createDisjunctionParser([
|
|
314
|
+
createExactSequenceParser('<' as const),
|
|
315
|
+
createExactSequenceParser('>' as const),
|
|
316
|
+
]),
|
|
317
|
+
createLookaheadParser(createExactSequenceParser('(')),
|
|
99
318
|
]),
|
|
100
|
-
([
|
|
101
|
-
type: 'commandSubstitution' as const,
|
|
102
|
-
command,
|
|
103
|
-
}),
|
|
319
|
+
([dir]) => dir as '<' | '>',
|
|
104
320
|
);
|
|
105
321
|
|
|
106
|
-
|
|
107
|
-
|
|
322
|
+
const bashProcessSubstitutionParser: Parser<BashWordPartProcessSubstitution, string> = createObjectParser({
|
|
323
|
+
type: 'processSubstitution' as const,
|
|
324
|
+
direction: bashProcessSubstitutionDirectionParser,
|
|
325
|
+
_open: createExactSequenceParser('('),
|
|
326
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
327
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
328
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
329
|
+
_close: createExactSequenceParser(')'),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Escape sequences in double quotes: \\ \$ \` \" \! \newline
|
|
333
|
+
const bashDoubleQuotedEscapeCharParser: Parser<string, string> = createDisjunctionParser([
|
|
334
|
+
createExactSequenceParser('\\'),
|
|
335
|
+
createExactSequenceParser('$'),
|
|
336
|
+
createExactSequenceParser('`'),
|
|
337
|
+
createExactSequenceParser('"'),
|
|
338
|
+
createExactSequenceParser('!'),
|
|
339
|
+
createExactSequenceParser('\n'),
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
const bashDoubleQuotedEscapeParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
108
343
|
createTupleParser([
|
|
109
|
-
createExactSequenceParser('
|
|
110
|
-
|
|
111
|
-
createExactSequenceParser('`'),
|
|
344
|
+
createExactSequenceParser('\\'),
|
|
345
|
+
bashDoubleQuotedEscapeCharParser,
|
|
112
346
|
]),
|
|
113
|
-
([,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
347
|
+
([, ch]) => ({ type: 'literal' as const, value: ch }),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Literal text inside double quotes (no special chars)
|
|
351
|
+
const bashDoubleQuotedLiteralCharParser: Parser<string, string> = promiseCompose(
|
|
352
|
+
createTupleParser([
|
|
353
|
+
createNegativeLookaheadParser(createDisjunctionParser([
|
|
354
|
+
createExactSequenceParser('$'),
|
|
355
|
+
createExactSequenceParser('`'),
|
|
356
|
+
createExactSequenceParser('"'),
|
|
357
|
+
createExactSequenceParser('\\'),
|
|
358
|
+
])),
|
|
359
|
+
createElementParser<string>(),
|
|
360
|
+
]),
|
|
361
|
+
([, ch]) => ch,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const bashDoubleQuotedLiteralParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
365
|
+
createNonEmptyArrayParser(bashDoubleQuotedLiteralCharParser),
|
|
366
|
+
chars => ({ type: 'literal' as const, value: chars.join('') }),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Bare $ not followed by a valid expansion start
|
|
370
|
+
const bashBareDollarParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
371
|
+
createExactSequenceParser('$'),
|
|
372
|
+
() => ({ type: 'literal' as const, value: '$' }),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Bare \ not followed by a recognized escape character
|
|
376
|
+
const bashBareBackslashParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
377
|
+
createExactSequenceParser('\\'),
|
|
378
|
+
() => ({ type: 'literal' as const, value: '\\' }),
|
|
117
379
|
);
|
|
118
380
|
|
|
119
381
|
// Double quoted string parts (inside "...")
|
|
120
382
|
const bashDoubleQuotedPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
|
|
383
|
+
bashBracedVariableParser,
|
|
384
|
+
bashArithmeticExpansionParser,
|
|
121
385
|
bashSimpleVariableParser,
|
|
122
386
|
bashCommandSubstitutionParser,
|
|
123
387
|
bashBacktickSubstitutionParser,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
type: 'literal' as const,
|
|
129
|
-
value: match[0].slice(1),
|
|
130
|
-
}),
|
|
131
|
-
),
|
|
132
|
-
// Literal text (no special chars)
|
|
133
|
-
promiseCompose(
|
|
134
|
-
createRegExpParser(/[^$`"\\]+/),
|
|
135
|
-
match => ({
|
|
136
|
-
type: 'literal' as const,
|
|
137
|
-
value: match[0],
|
|
138
|
-
}),
|
|
139
|
-
),
|
|
388
|
+
bashDoubleQuotedEscapeParser,
|
|
389
|
+
bashDoubleQuotedLiteralParser,
|
|
390
|
+
bashBareDollarParser,
|
|
391
|
+
bashBareBackslashParser,
|
|
140
392
|
]);
|
|
141
393
|
|
|
142
394
|
// Double quoted string: "..."
|
|
143
|
-
const bashDoubleQuotedParser: Parser<BashWordPartDoubleQuoted, string> =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
395
|
+
const bashDoubleQuotedParser: Parser<BashWordPartDoubleQuoted, string> = createObjectParser({
|
|
396
|
+
type: 'doubleQuoted' as const,
|
|
397
|
+
_open: createExactSequenceParser('"'),
|
|
398
|
+
parts: createArrayParser(bashDoubleQuotedPartParser),
|
|
399
|
+
_close: createExactSequenceParser('"'),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Literal word part (unquoted)
|
|
403
|
+
const bashLiteralWordPartParser: Parser<BashWordPartLiteral, string> = createObjectParser({
|
|
404
|
+
type: 'literal' as const,
|
|
405
|
+
value: bashUnquotedWordCharsParser,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Bare {} treated as a literal word (e.g., find -exec cmd {} \;)
|
|
409
|
+
const bashBraceWordPartParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
410
|
+
createExactSequenceParser('{}'),
|
|
411
|
+
() => ({
|
|
412
|
+
type: 'literal' as const,
|
|
413
|
+
value: '{}',
|
|
152
414
|
}),
|
|
153
415
|
);
|
|
154
416
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
417
|
+
// Bare { treated as a literal word part (e.g., echo {, echo {.})
|
|
418
|
+
// Note: } is NOT included here because it would break brace group closing
|
|
419
|
+
const bashOpenBraceWordPartParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
420
|
+
createExactSequenceParser('{'),
|
|
421
|
+
() => ({
|
|
159
422
|
type: 'literal' as const,
|
|
160
|
-
value,
|
|
423
|
+
value: '{',
|
|
161
424
|
}),
|
|
162
425
|
);
|
|
163
426
|
|
|
164
|
-
//
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
427
|
+
// Bare } treated as a literal word part (e.g., echo }, echo }hello)
|
|
428
|
+
const bashCloseBraceWordPartParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
429
|
+
createExactSequenceParser('}'),
|
|
430
|
+
() => ({
|
|
168
431
|
type: 'literal' as const,
|
|
169
|
-
value:
|
|
432
|
+
value: '}',
|
|
170
433
|
}),
|
|
171
434
|
);
|
|
172
435
|
|
|
173
|
-
//
|
|
436
|
+
// Escape sequence outside quotes: backslash followed by any character
|
|
437
|
+
const bashEscapeParser: Parser<BashWordPartLiteral, string> = promiseCompose(
|
|
438
|
+
createTupleParser([
|
|
439
|
+
createExactSequenceParser('\\'),
|
|
440
|
+
createElementParser<string>(),
|
|
441
|
+
]),
|
|
442
|
+
([, ch]) => ({ type: 'literal' as const, value: ch }),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
// Word part for use inside ${...} operands (uses literal parser that excludes } from continuation)
|
|
446
|
+
const bashBracedVarWordPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
|
|
447
|
+
bashAnsiCQuotedParser,
|
|
448
|
+
bashSingleQuotedParser,
|
|
449
|
+
bashDoubleQuotedParser,
|
|
450
|
+
bashBracedVariableParser,
|
|
451
|
+
bashArithmeticExpansionParser,
|
|
452
|
+
bashCommandSubstitutionParser,
|
|
453
|
+
bashBacktickSubstitutionParser,
|
|
454
|
+
bashSimpleVariableParser,
|
|
455
|
+
bashEscapeParser,
|
|
456
|
+
bashBracedVarLiteralWordPartParser,
|
|
457
|
+
bashBareDollarParser,
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
const bashBracedVarWordParser: Parser<BashWord, string> = createObjectParser({
|
|
461
|
+
parts: createNonEmptyArrayParser(bashBracedVarWordPartParser),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Word part (any part of a word, } excluded from first position so brace groups work)
|
|
174
465
|
const bashWordPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
|
|
466
|
+
bashAnsiCQuotedParser,
|
|
175
467
|
bashSingleQuotedParser,
|
|
176
468
|
bashDoubleQuotedParser,
|
|
469
|
+
bashBracedVariableParser,
|
|
470
|
+
bashArithmeticExpansionParser,
|
|
177
471
|
bashCommandSubstitutionParser,
|
|
178
472
|
bashBacktickSubstitutionParser,
|
|
179
473
|
bashSimpleVariableParser,
|
|
474
|
+
bashProcessSubstitutionParser,
|
|
180
475
|
bashEscapeParser,
|
|
476
|
+
bashBraceWordPartParser,
|
|
477
|
+
bashOpenBraceWordPartParser,
|
|
181
478
|
bashLiteralWordPartParser,
|
|
479
|
+
bashBareDollarParser,
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
// Word part including } as a starter (for argument positions where } is not reserved)
|
|
483
|
+
const bashArgWordPartParser: Parser<BashWordPart, string> = createDisjunctionParser([
|
|
484
|
+
bashAnsiCQuotedParser,
|
|
485
|
+
bashSingleQuotedParser,
|
|
486
|
+
bashDoubleQuotedParser,
|
|
487
|
+
bashBracedVariableParser,
|
|
488
|
+
bashArithmeticExpansionParser,
|
|
489
|
+
bashCommandSubstitutionParser,
|
|
490
|
+
bashBacktickSubstitutionParser,
|
|
491
|
+
bashSimpleVariableParser,
|
|
492
|
+
bashProcessSubstitutionParser,
|
|
493
|
+
bashEscapeParser,
|
|
494
|
+
bashBraceWordPartParser,
|
|
495
|
+
bashOpenBraceWordPartParser,
|
|
496
|
+
bashCloseBraceWordPartParser,
|
|
497
|
+
bashLiteralWordPartParser,
|
|
498
|
+
bashBareDollarParser,
|
|
182
499
|
]);
|
|
183
500
|
|
|
184
501
|
// Word (sequence of word parts)
|
|
185
|
-
export const bashWordParser: Parser<BashWord, string> =
|
|
186
|
-
createNonEmptyArrayParser(bashWordPartParser),
|
|
187
|
-
|
|
188
|
-
|
|
502
|
+
export const bashWordParser: Parser<BashWord, string> = createObjectParser({
|
|
503
|
+
parts: createNonEmptyArrayParser(bashWordPartParser),
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Argument word (allows } as first character)
|
|
507
|
+
const bashArgWordParser: Parser<BashWord, string> = createObjectParser({
|
|
508
|
+
parts: createNonEmptyArrayParser(bashArgWordPartParser),
|
|
509
|
+
});
|
|
189
510
|
|
|
190
511
|
setParserName(bashWordParser, 'bashWordParser');
|
|
191
512
|
|
|
192
|
-
// Assignment:
|
|
193
|
-
const
|
|
513
|
+
// Assignment name: identifier followed by =
|
|
514
|
+
const bashAssignmentNameParser: Parser<string, string> = promiseCompose(
|
|
194
515
|
createTupleParser([
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
match => match[0].slice(0, -1),
|
|
198
|
-
),
|
|
199
|
-
createOptionalParser(bashWordParser),
|
|
516
|
+
bashIdentifierParser,
|
|
517
|
+
createExactSequenceParser('='),
|
|
200
518
|
]),
|
|
201
|
-
([name
|
|
202
|
-
name,
|
|
203
|
-
value: value ?? undefined,
|
|
204
|
-
}),
|
|
519
|
+
([name]) => name,
|
|
205
520
|
);
|
|
206
521
|
|
|
522
|
+
// Assignment: NAME=value or NAME=
|
|
523
|
+
const bashAssignmentParser: Parser<BashAssignment, string> = createObjectParser({
|
|
524
|
+
name: bashAssignmentNameParser,
|
|
525
|
+
value: createOptionalParser(bashWordParser),
|
|
526
|
+
});
|
|
527
|
+
|
|
207
528
|
// Redirect operators
|
|
208
529
|
const bashRedirectOperatorParser: Parser<BashRedirect['operator'], string> = createDisjunctionParser([
|
|
209
530
|
promiseCompose(createExactSequenceParser('>>'), () => '>>' as const),
|
|
@@ -216,28 +537,33 @@ const bashRedirectOperatorParser: Parser<BashRedirect['operator'], string> = cre
|
|
|
216
537
|
promiseCompose(createExactSequenceParser('<'), () => '<' as const),
|
|
217
538
|
]);
|
|
218
539
|
|
|
540
|
+
// File descriptor number
|
|
541
|
+
const bashFdParser: Parser<number, string> = promiseCompose(
|
|
542
|
+
bashDigitsParser,
|
|
543
|
+
digits => Number.parseInt(digits, 10),
|
|
544
|
+
);
|
|
545
|
+
|
|
219
546
|
// Redirect: [n]op word
|
|
220
|
-
const bashRedirectParser: Parser<BashRedirect, string> =
|
|
547
|
+
const bashRedirectParser: Parser<BashRedirect, string> = createObjectParser({
|
|
548
|
+
fd: createOptionalParser(bashFdParser),
|
|
549
|
+
operator: bashRedirectOperatorParser,
|
|
550
|
+
_ws: bashOptionalInlineWhitespaceParser,
|
|
551
|
+
target: bashWordParser,
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Word with optional trailing whitespace - for use in arrays
|
|
555
|
+
const bashWordWithWhitespaceParser: Parser<BashWord, string> = promiseCompose(
|
|
221
556
|
createTupleParser([
|
|
222
|
-
createOptionalParser(promiseCompose(
|
|
223
|
-
createRegExpParser(/[0-9]+/),
|
|
224
|
-
match => Number.parseInt(match[0], 10),
|
|
225
|
-
)),
|
|
226
|
-
bashRedirectOperatorParser,
|
|
227
|
-
bashOptionalInlineWhitespaceParser,
|
|
228
557
|
bashWordParser,
|
|
558
|
+
bashOptionalInlineWhitespaceParser,
|
|
229
559
|
]),
|
|
230
|
-
([
|
|
231
|
-
fd: fd ?? undefined,
|
|
232
|
-
operator,
|
|
233
|
-
target,
|
|
234
|
-
}),
|
|
560
|
+
([word]) => word,
|
|
235
561
|
);
|
|
236
562
|
|
|
237
|
-
//
|
|
238
|
-
const
|
|
563
|
+
// Arg word (allows }) with optional trailing whitespace
|
|
564
|
+
const bashArgWordWithWhitespaceParser: Parser<BashWord, string> = promiseCompose(
|
|
239
565
|
createTupleParser([
|
|
240
|
-
|
|
566
|
+
bashArgWordParser,
|
|
241
567
|
bashOptionalInlineWhitespaceParser,
|
|
242
568
|
]),
|
|
243
569
|
([word]) => word,
|
|
@@ -252,85 +578,80 @@ const bashRedirectWithWhitespaceParser: Parser<BashRedirect, string> = promiseCo
|
|
|
252
578
|
([redirect]) => redirect,
|
|
253
579
|
);
|
|
254
580
|
|
|
255
|
-
// Word or redirect
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
581
|
+
// Word or redirect for argument position (} allowed)
|
|
582
|
+
const bashArgWordOrRedirectParser: Parser<{ type: 'word'; word: BashWord } | { type: 'redirect'; redirect: BashRedirect }, string> = createDisjunctionParser([
|
|
583
|
+
createObjectParser({ type: 'redirect' as const, redirect: bashRedirectWithWhitespaceParser }),
|
|
584
|
+
createObjectParser({ type: 'word' as const, word: bashArgWordWithWhitespaceParser }),
|
|
259
585
|
]);
|
|
260
586
|
|
|
261
587
|
// Simple command: [assignments] [name] [args] [redirects]
|
|
262
|
-
export const bashSimpleCommandParser: Parser<BashSimpleCommand, string> =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
588
|
+
export const bashSimpleCommandParser: Parser<BashSimpleCommand, string> = async (parserContext) => {
|
|
589
|
+
// Parse assignments at the start
|
|
590
|
+
const assignmentsParser = createArrayParser(promiseCompose(
|
|
591
|
+
createTupleParser([
|
|
592
|
+
bashAssignmentParser,
|
|
593
|
+
bashOptionalInlineWhitespaceParser,
|
|
594
|
+
]),
|
|
595
|
+
([assignment]) => assignment,
|
|
596
|
+
));
|
|
597
|
+
const assignments = await assignmentsParser(parserContext);
|
|
598
|
+
|
|
599
|
+
// Parse leading redirects before command name
|
|
600
|
+
const leadingRedirectsParser = createArrayParser(bashRedirectWithWhitespaceParser);
|
|
601
|
+
const leadingRedirects = await leadingRedirectsParser(parserContext);
|
|
602
|
+
|
|
603
|
+
// Parse command name (} not allowed here, so brace group closing works)
|
|
604
|
+
const name = await createOptionalParser(bashWordWithWhitespaceParser)(parserContext);
|
|
605
|
+
|
|
606
|
+
// Only parse args if we have a command name
|
|
607
|
+
const args: BashWord[] = [];
|
|
608
|
+
const redirects: BashRedirect[] = [...leadingRedirects];
|
|
609
|
+
|
|
610
|
+
if (name !== undefined) {
|
|
611
|
+
const argItems = await createArrayParser(bashArgWordOrRedirectParser)(parserContext);
|
|
612
|
+
for (const item of argItems) {
|
|
280
613
|
if (item.type === 'word') {
|
|
281
|
-
|
|
614
|
+
args.push(item.word);
|
|
282
615
|
} else {
|
|
283
616
|
redirects.push(item.redirect);
|
|
284
617
|
}
|
|
285
618
|
}
|
|
619
|
+
}
|
|
286
620
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
};
|
|
296
|
-
},
|
|
297
|
-
);
|
|
621
|
+
return {
|
|
622
|
+
type: 'simple' as const,
|
|
623
|
+
name,
|
|
624
|
+
args,
|
|
625
|
+
redirects,
|
|
626
|
+
assignments,
|
|
627
|
+
};
|
|
628
|
+
};
|
|
298
629
|
|
|
299
630
|
setParserName(bashSimpleCommandParser, 'bashSimpleCommandParser');
|
|
300
631
|
|
|
301
632
|
// Subshell: ( command )
|
|
302
|
-
const bashSubshellParser: Parser<BashSubshell, string> =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
([, , body]) => ({
|
|
311
|
-
type: 'subshell' as const,
|
|
312
|
-
body,
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
633
|
+
const bashSubshellParser: Parser<BashSubshell, string> = createObjectParser({
|
|
634
|
+
type: 'subshell' as const,
|
|
635
|
+
_open: createExactSequenceParser('('),
|
|
636
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
637
|
+
body: createParserAccessorParser(() => bashCommandParser),
|
|
638
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
639
|
+
_close: createExactSequenceParser(')'),
|
|
640
|
+
});
|
|
315
641
|
|
|
316
642
|
setParserName(bashSubshellParser, 'bashSubshellParser');
|
|
317
643
|
|
|
318
644
|
// Brace group: { command; }
|
|
319
|
-
const bashBraceGroupParser: Parser<BashBraceGroup, string> =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
([, , body]) => ({
|
|
330
|
-
type: 'braceGroup' as const,
|
|
331
|
-
body,
|
|
332
|
-
}),
|
|
333
|
-
);
|
|
645
|
+
const bashBraceGroupParser: Parser<BashBraceGroup, string> = createObjectParser({
|
|
646
|
+
type: 'braceGroup' as const,
|
|
647
|
+
_open: createExactSequenceParser('{'),
|
|
648
|
+
_ws1: bashInlineWhitespaceParser,
|
|
649
|
+
body: createParserAccessorParser(() => bashCommandParser),
|
|
650
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
651
|
+
_semi: createOptionalParser(createExactSequenceParser(';')),
|
|
652
|
+
_ws3: bashOptionalInlineWhitespaceParser,
|
|
653
|
+
_close: createExactSequenceParser('}'),
|
|
654
|
+
});
|
|
334
655
|
|
|
335
656
|
setParserName(bashBraceGroupParser, 'bashBraceGroupParser');
|
|
336
657
|
|
|
@@ -345,8 +666,11 @@ setParserName(bashCommandUnitParser, 'bashCommandUnitParser');
|
|
|
345
666
|
|
|
346
667
|
// Single pipe (not ||) - matches | only when not followed by another |
|
|
347
668
|
const bashSinglePipeParser: Parser<string, string> = promiseCompose(
|
|
348
|
-
|
|
349
|
-
|
|
669
|
+
createTupleParser([
|
|
670
|
+
createExactSequenceParser('|'),
|
|
671
|
+
createNegativeLookaheadParser(createExactSequenceParser('|')),
|
|
672
|
+
]),
|
|
673
|
+
() => '|',
|
|
350
674
|
);
|
|
351
675
|
|
|
352
676
|
// Pipeline: [!] cmd [| cmd]...
|
|
@@ -377,28 +701,63 @@ const bashPipelineParser: Parser<BashPipeline, string> = promiseCompose(
|
|
|
377
701
|
|
|
378
702
|
setParserName(bashPipelineParser, 'bashPipelineParser');
|
|
379
703
|
|
|
704
|
+
// Non-newline character
|
|
705
|
+
const bashNonNewlineCharParser: Parser<string, string> = promiseCompose(
|
|
706
|
+
createTupleParser([
|
|
707
|
+
createNegativeLookaheadParser(createExactSequenceParser('\n')),
|
|
708
|
+
createElementParser<string>(),
|
|
709
|
+
]),
|
|
710
|
+
([, ch]) => ch,
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
// Comment: # through end of line (not consuming the newline)
|
|
714
|
+
const bashCommentParser: Parser<string, string> = promiseCompose(
|
|
715
|
+
createTupleParser([
|
|
716
|
+
createExactSequenceParser('#'),
|
|
717
|
+
createArrayParser(bashNonNewlineCharParser),
|
|
718
|
+
]),
|
|
719
|
+
([hash, chars]) => hash + chars.join(''),
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
// Blank line filler: whitespace, newlines, and comments
|
|
723
|
+
const bashBlankLineFillerParser: Parser<void, string> = promiseCompose(
|
|
724
|
+
createArrayParser(createDisjunctionParser([
|
|
725
|
+
bashInlineWhitespaceUnitParser,
|
|
726
|
+
promiseCompose(createExactSequenceParser('\n'), () => '\n'),
|
|
727
|
+
bashCommentParser,
|
|
728
|
+
])),
|
|
729
|
+
() => {},
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
// Newline separator: consumes a newline plus any following blank lines, comments, and whitespace
|
|
733
|
+
// This allows multi-line scripts with blank lines and mid-script comments
|
|
734
|
+
const bashNewlineSeparatorParser: Parser<'\n', string> = promiseCompose(
|
|
735
|
+
createTupleParser([
|
|
736
|
+
createExactSequenceParser('\n'),
|
|
737
|
+
bashBlankLineFillerParser,
|
|
738
|
+
]),
|
|
739
|
+
() => '\n' as const,
|
|
740
|
+
);
|
|
741
|
+
|
|
380
742
|
// Command list separator
|
|
381
743
|
const bashListSeparatorParser: Parser<'&&' | '||' | ';' | '&' | '\n', string> = createDisjunctionParser([
|
|
382
744
|
promiseCompose(createExactSequenceParser('&&'), () => '&&' as const),
|
|
383
745
|
promiseCompose(createExactSequenceParser('||'), () => '||' as const),
|
|
384
746
|
promiseCompose(createExactSequenceParser(';'), () => ';' as const),
|
|
385
747
|
promiseCompose(createExactSequenceParser('&'), () => '&' as const),
|
|
386
|
-
|
|
748
|
+
bashNewlineSeparatorParser,
|
|
387
749
|
]);
|
|
388
750
|
|
|
389
751
|
// Command list: pipeline [sep pipeline]...
|
|
390
752
|
const bashCommandListParser: Parser<BashCommandList, string> = promiseCompose(
|
|
391
753
|
createTupleParser([
|
|
392
754
|
bashPipelineParser,
|
|
393
|
-
createArrayParser(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
]),
|
|
400
|
-
([, separator, , pipeline]) => ({ separator, pipeline }),
|
|
401
|
-
)),
|
|
755
|
+
createArrayParser(createObjectParser({
|
|
756
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
757
|
+
separator: bashListSeparatorParser,
|
|
758
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
759
|
+
pipeline: bashPipelineParser,
|
|
760
|
+
})),
|
|
402
761
|
createOptionalParser(promiseCompose(
|
|
403
762
|
createTupleParser([
|
|
404
763
|
bashOptionalInlineWhitespaceParser,
|
|
@@ -448,12 +807,18 @@ export const bashCommandParser: Parser<BashCommand, string> = bashCommandListPar
|
|
|
448
807
|
|
|
449
808
|
setParserName(bashCommandParser, 'bashCommandParser');
|
|
450
809
|
|
|
451
|
-
//
|
|
810
|
+
// Trailing whitespace/comments/blank lines at end of script
|
|
811
|
+
const bashTrailingWhitespaceAndCommentsParser: Parser<undefined, string> = promiseCompose(
|
|
812
|
+
bashBlankLineFillerParser,
|
|
813
|
+
() => undefined,
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
// Script parser (handles leading/trailing whitespace and comments)
|
|
452
817
|
export const bashScriptParser: Parser<BashCommand, string> = promiseCompose(
|
|
453
818
|
createTupleParser([
|
|
454
819
|
bashOptionalInlineWhitespaceParser,
|
|
455
820
|
bashCommandParser,
|
|
456
|
-
|
|
821
|
+
bashTrailingWhitespaceAndCommentsParser,
|
|
457
822
|
]),
|
|
458
823
|
([, command]) => command,
|
|
459
824
|
);
|