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