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’sSCANPM_DQUOTED: the subscript sits inside"…".resolve_dollar— the caller has NOparsestr/singsubround 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.