(P: ParseState)
| 2553 | } |
| 2554 | |
| 2555 | function parseExpansionBody(P: ParseState): TsNode[] { |
| 2556 | const out: TsNode[] = [] |
| 2557 | skipBlanks(P.L) |
| 2558 | // Bizarre cases: ${#!} ${!#} ${!##} ${!# } ${!## } all emit empty (expansion) |
| 2559 | // — both # and ! become anonymous nodes when only combined with each other |
| 2560 | // and optional trailing space before }. Note ${!##/} does NOT match (has |
| 2561 | // content after), so it parses normally as (special_variable_name)(regex). |
| 2562 | { |
| 2563 | const c0 = peek(P.L) |
| 2564 | const c1 = peek(P.L, 1) |
| 2565 | if (c0 === '#' && c1 === '!' && peek(P.L, 2) === '}') { |
| 2566 | advance(P.L) |
| 2567 | advance(P.L) |
| 2568 | return out |
| 2569 | } |
| 2570 | if (c0 === '!' && c1 === '#') { |
| 2571 | // ${!#} ${!##} with optional trailing space then } |
| 2572 | let j = 2 |
| 2573 | if (peek(P.L, j) === '#') j++ |
| 2574 | if (peek(P.L, j) === ' ') j++ |
| 2575 | if (peek(P.L, j) === '}') { |
| 2576 | while (j-- > 0) advance(P.L) |
| 2577 | return out |
| 2578 | } |
| 2579 | } |
| 2580 | } |
| 2581 | // Optional # prefix for length |
| 2582 | if (peek(P.L) === '#') { |
| 2583 | const s = P.L.b |
| 2584 | advance(P.L) |
| 2585 | out.push(mk(P, '#', s, P.L.b, [])) |
| 2586 | } |
| 2587 | // Optional ! prefix for indirect expansion: ${!varname} ${!prefix*} ${!prefix@} |
| 2588 | // Only when followed by an identifier — ${!} alone is special var $! |
| 2589 | // Also = ~ prefixes (zsh-style ${=var} ${~var}) |
| 2590 | const pc = peek(P.L) |
| 2591 | if ( |
| 2592 | (pc === '!' || pc === '=' || pc === '~') && |
| 2593 | (isIdentStart(peek(P.L, 1)) || isDigit(peek(P.L, 1))) |
| 2594 | ) { |
| 2595 | const s = P.L.b |
| 2596 | advance(P.L) |
| 2597 | out.push(mk(P, pc, s, P.L.b, [])) |
| 2598 | } |
| 2599 | skipBlanks(P.L) |
| 2600 | // Variable name |
| 2601 | if (isIdentStart(peek(P.L))) { |
| 2602 | const s = P.L.b |
| 2603 | while (isIdentChar(peek(P.L))) advance(P.L) |
| 2604 | out.push(mk(P, 'variable_name', s, P.L.b, [])) |
| 2605 | } else if (isDigit(peek(P.L))) { |
| 2606 | const s = P.L.b |
| 2607 | while (isDigit(peek(P.L))) advance(P.L) |
| 2608 | out.push(mk(P, 'variable_name', s, P.L.b, [])) |
| 2609 | } else if (SPECIAL_VARS.has(peek(P.L))) { |
| 2610 | const s = P.L.b |
| 2611 | advance(P.L) |
| 2612 | out.push(mk(P, 'special_variable_name', s, P.L.b, [])) |
no test coverage detected