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