* Parse a word-position element: bare word, string, expansion, or concatenation * thereof. Returns a single node; if multiple adjacent fragments, wraps in * concatenation.
(P: ParseState, _ctx: 'cmd' | 'arg')
| 2006 | * concatenation. |
| 2007 | */ |
| 2008 | function parseWord(P: ParseState, _ctx: 'cmd' | 'arg'): TsNode | null { |
| 2009 | skipBlanks(P.L) |
| 2010 | const parts: TsNode[] = [] |
| 2011 | while (P.L.i < P.L.len) { |
| 2012 | const c = peek(P.L) |
| 2013 | if ( |
| 2014 | c === ' ' || |
| 2015 | c === '\t' || |
| 2016 | c === '\n' || |
| 2017 | c === '\r' || |
| 2018 | c === '' || |
| 2019 | c === '|' || |
| 2020 | c === '&' || |
| 2021 | c === ';' || |
| 2022 | c === '(' || |
| 2023 | c === ')' |
| 2024 | ) { |
| 2025 | break |
| 2026 | } |
| 2027 | // < > are redirect operators unless <( >( (process substitution) |
| 2028 | if (c === '<' || c === '>') { |
| 2029 | if (peek(P.L, 1) === '(') { |
| 2030 | const ps = parseProcessSub(P) |
| 2031 | if (ps) parts.push(ps) |
| 2032 | continue |
| 2033 | } |
| 2034 | break |
| 2035 | } |
| 2036 | if (c === '"') { |
| 2037 | parts.push(parseDoubleQuoted(P)) |
| 2038 | continue |
| 2039 | } |
| 2040 | if (c === "'") { |
| 2041 | const tok = nextToken(P.L, 'arg') |
| 2042 | parts.push(leaf(P, 'raw_string', tok)) |
| 2043 | continue |
| 2044 | } |
| 2045 | if (c === '$') { |
| 2046 | const c1 = peek(P.L, 1) |
| 2047 | if (c1 === "'") { |
| 2048 | const tok = nextToken(P.L, 'arg') |
| 2049 | parts.push(leaf(P, 'ansi_c_string', tok)) |
| 2050 | continue |
| 2051 | } |
| 2052 | if (c1 === '"') { |
| 2053 | // Translated string: emit $ leaf + string node |
| 2054 | const dTok: Token = { |
| 2055 | type: 'DOLLAR', |
| 2056 | value: '$', |
| 2057 | start: P.L.b, |
| 2058 | end: P.L.b + 1, |
| 2059 | } |
| 2060 | advance(P.L) |
| 2061 | parts.push(leaf(P, '$', dTok)) |
| 2062 | parts.push(parseDoubleQuoted(P)) |
| 2063 | continue |
| 2064 | } |
| 2065 | if (c1 === '`') { |
no test coverage detected