Skip to main content

subscript_unescape

Function subscript_unescape 

Source
pub fn subscript_unescape(
    s: &str,
    sub: bool,
    resolve_dollar: bool,
) -> (String, bool)
Expand description

Backslash disposition inside a [...] subscript — the net effect of the re-lex C runs on a subscript’s SOURCE text.

getindex NEVER reads the subscript the way the outer lexer left it. It calls parse_subscript(s, scanflags & SCANPM_DQUOTED, ']') (c:Src/params.c:2029), and parse_subscript untokenizes the text and re-lexes it through dquote_parse(']', sub) (c:Src/lex.c:1751-1769 — untokenize(t = dupstring_wlen(s, l)); inpush(t, 0, NULL); … err = dquote_parse(endchar, sub);). That re-lex is where a backslash inside a subscript acquires its meaning:

c:Src/lex.c:1497-1512
    if (c != '\n') {
        if (c == '$' || c == '\\' || (c == '}' && !intick && bct) ||
            c == endchar || c == '`' ||
            (endchar == ']' && (c == '[' || c == ']' ||
                                c == '(' || c == ')' ||
                                c == '{' || c == '}' ||
                                (c == '"' && sub))))
            add(Bnull);
        else {
            /* lexstop is implicitly handled here */
            add('\\');
            goto cont;
        }
    } else if (sub || unset(CSHJUNKIEQUOTES) || endchar != '"')
        continue;

With endchar == ']' a backslash before one of $ \ ` ] [ ( ) { } (plus " when the subscript is inside double quotes, sub) becomes the Bnull marker + the literal char; a backslash before ANY other char stays a literal backslash. That asymmetry is exactly why A[\[k\]] keys on [k] while A[a\ b] / A[a\*b] keep theirs. Backslash-newline is dropped outright (c:1513).

getarg then disposes of the markers (c:Src/params.c:1538-1551):

    if (inull(c)) {
        c = t[1];
        if (c == '[' || c == ']' || c == '(' || c == ')' ||
            c == '{' || c == '}') {
            if (ishash && i) *t = ztokens[*t - Pound];
            needtok = 1; ++t;
        } else if (c != '"')
            *t = ztokens[*t - Pound];
        continue;
    }

— a marker before a bracket/paren/brace (or before ") is KEPT and later DELETED by remnulargs (c:1583-1584, hash key path), so the escaped bracket reaches the hash table bare. Every other marker is untokenized back to a literal \ (ztokens[Bnull - Pound] is \, c:Src/lex.c:38), which the parsestr + singsub round at c:1585-1593 re-marks and drops one stage later — so \$, \\ and \` also lose their backslash, just further down the pipeline.

zshrs has no equivalent re-lex step on this path (its lex::parse_subscript discards the tokenized text C copies back at c:Src/lex.c:1772), so a source-literal backslash reached the assoc key verbatim: A[\[k\]]=v stored the 5-char key \[k\] where zsh stores [k]. This function is that missing step, expressed as the composite string transform the three C stages add up to.

  • sub — C’s SCANPM_DQUOTED: the subscript sits inside "…".
  • resolve_dollar — the caller has NO parsestr/singsub round after this call (compile-time literal key), so apply that stage’s share of the work here as well.

Returns the rewritten text and whether an UNESCAPED $ / ` (i.e. a live expansion, which C resolves in singsub at c:1592) is still present.