Parse a single command: simple, compound, or control structure.
(P: ParseState)
| 995 | |
| 996 | /** Parse a single command: simple, compound, or control structure. */ |
| 997 | function parseCommand(P: ParseState): TsNode | null { |
| 998 | skipBlanks(P.L) |
| 999 | const save = saveLex(P.L) |
| 1000 | const t = nextToken(P.L, 'cmd') |
| 1001 | |
| 1002 | if (t.type === 'EOF') { |
| 1003 | restoreLex(P.L, save) |
| 1004 | return null |
| 1005 | } |
| 1006 | |
| 1007 | // Negation — tree-sitter wraps just the command, redirects go outside. |
| 1008 | // `! cmd > out` → redirected_statement(negated_command(!, cmd), >out) |
| 1009 | if (t.type === 'OP' && t.value === '!') { |
| 1010 | const bang = leaf(P, '!', t) |
| 1011 | const inner = parseCommand(P) |
| 1012 | if (!inner) { |
| 1013 | restoreLex(P.L, save) |
| 1014 | return null |
| 1015 | } |
| 1016 | // If inner is a redirected_statement, hoist redirects outside negation |
| 1017 | if (inner.type === 'redirected_statement' && inner.children.length >= 2) { |
| 1018 | const cmd = inner.children[0]! |
| 1019 | const redirs = inner.children.slice(1) |
| 1020 | const neg = mk(P, 'negated_command', bang.startIndex, cmd.endIndex, [ |
| 1021 | bang, |
| 1022 | cmd, |
| 1023 | ]) |
| 1024 | const lastR = redirs[redirs.length - 1]! |
| 1025 | return mk(P, 'redirected_statement', neg.startIndex, lastR.endIndex, [ |
| 1026 | neg, |
| 1027 | ...redirs, |
| 1028 | ]) |
| 1029 | } |
| 1030 | return mk(P, 'negated_command', bang.startIndex, inner.endIndex, [ |
| 1031 | bang, |
| 1032 | inner, |
| 1033 | ]) |
| 1034 | } |
| 1035 | |
| 1036 | if (t.type === 'OP' && t.value === '(') { |
| 1037 | const open = leaf(P, '(', t) |
| 1038 | const body = parseStatements(P, ')') |
| 1039 | const closeTok = nextToken(P.L, 'cmd') |
| 1040 | const close = |
| 1041 | closeTok.type === 'OP' && closeTok.value === ')' |
| 1042 | ? leaf(P, ')', closeTok) |
| 1043 | : mk(P, ')', open.endIndex, open.endIndex, []) |
| 1044 | const node = mk(P, 'subshell', open.startIndex, close.endIndex, [ |
| 1045 | open, |
| 1046 | ...body, |
| 1047 | close, |
| 1048 | ]) |
| 1049 | return maybeRedirect(P, node) |
| 1050 | } |
| 1051 | |
| 1052 | if (t.type === 'OP' && t.value === '((') { |
| 1053 | const open = leaf(P, '((', t) |
| 1054 | const exprs = parseArithCommaList(P, '))', 'var') |
no test coverage detected