* Parse pipeline chains joined by && ||. Left-associative nesting. * tree-sitter quirk: trailing redirect on the last pipeline wraps the ENTIRE * list in a redirected_statement — `a > x && b > y` becomes * redirected_statement(list(redirected_statement(a,>x), &&, b), >y).
(P: ParseState)
| 877 | * redirected_statement(list(redirected_statement(a,>x), &&, b), >y). |
| 878 | */ |
| 879 | function parseAndOr(P: ParseState): TsNode | null { |
| 880 | let left = parsePipeline(P) |
| 881 | if (!left) return null |
| 882 | while (true) { |
| 883 | const save = saveLex(P.L) |
| 884 | const t = nextToken(P.L, 'cmd') |
| 885 | if (t.type === 'OP' && (t.value === '&&' || t.value === '||')) { |
| 886 | const op = leaf(P, t.value, t) |
| 887 | skipNewlines(P) |
| 888 | const right = parsePipeline(P) |
| 889 | if (!right) { |
| 890 | left = mk(P, 'list', left.startIndex, op.endIndex, [left, op]) |
| 891 | break |
| 892 | } |
| 893 | // If right is a redirected_statement, hoist its redirects to wrap the list. |
| 894 | if (right.type === 'redirected_statement' && right.children.length >= 2) { |
| 895 | const inner = right.children[0]! |
| 896 | const redirs = right.children.slice(1) |
| 897 | const listNode = mk(P, 'list', left.startIndex, inner.endIndex, [ |
| 898 | left, |
| 899 | op, |
| 900 | inner, |
| 901 | ]) |
| 902 | const lastR = redirs[redirs.length - 1]! |
| 903 | left = mk( |
| 904 | P, |
| 905 | 'redirected_statement', |
| 906 | listNode.startIndex, |
| 907 | lastR.endIndex, |
| 908 | [listNode, ...redirs], |
| 909 | ) |
| 910 | } else { |
| 911 | left = mk(P, 'list', left.startIndex, right.endIndex, [left, op, right]) |
| 912 | } |
| 913 | } else { |
| 914 | restoreLex(P.L, save) |
| 915 | break |
| 916 | } |
| 917 | } |
| 918 | return left |
| 919 | } |
| 920 | |
| 921 | function skipNewlines(P: ParseState): void { |
| 922 | while (true) { |
no test coverage detected