Skip to main content

Module pattern_data_escape

Module pattern_data_escape 

Source
Expand description

pattern_data_escape submodule (Rust-only; see the module docs). Rust-only utility (NOT a port — lives outside src/ported/ by design).

The DATA half of docs/BUGS.md #1090: how a backslash that is a CHARACTER OF A VALUE has to be spelled before it reaches ported::pattern::patcompile.

C never needs this transform. Its pattern compiler consumes the LEXER’s encoding, where a source-level quote already arrived as Bnull/Bnullkeep + payload (c:Src/zsh.h:195-200), so a RAW backslash in patcompile’s input can only be data. A substituted value acquires its pattern meaning in zshtokenize (c:Src/glob.c:3585-3653), reached from strcatsub’s if (glbsub) shtokenize(dest) (c:Src/subst.c:822/830) for ${~spec} / GLOB_SUBST, and that function rewrites a backslash into a quote marker ONLY when the next character reaches its ztokens scan:

c:Src/glob.c:3597-3605   case Bnull: case Bnullkeep: case '\\':
                             if (bslash) { s[-1] = … Bnullkeep/Bnull; break; }
                             bslash = 1; continue;
c:Src/glob.c:3640-3648   for (t = ztokens; *t; t++)
                             if (*t == *s) {
                                 if (bslash) s[-1] = … Bnullkeep/Bnull;
                                 else *s = (t - ztokens) + Pound;
                                 break;
                             }
c:Src/glob.c:3651        bslash = 0;

Before anything else — a space, a $, a { — no switch arm fires, c:3651 just clears bslash, and BOTH bytes survive in the string as ordinary literal data. That is why real zsh answers

p='a\ b'; [[ 'a b'  == ${~p} ]]   # no match — the pattern holds a backslash
p='a\ b'; [[ 'a\ b' == ${~p} ]]   # match

ported::pattern’s input normalizer (src/ported/pattern.rs, the \\ arm) reads a lone raw \X as a QUOTE of X — the spelling every SOURCE-level pattern path in zshrs hands it (the cond/case pattern builder in extensions::compile_zsh, ${v//\%/%%}’s builder in ported::subst) — and spells a literal backslash as the pair \\. So doubling exactly the backslashes zshtokenize declines to consume is what carries C’s Bnull-vs-raw split into the Rust encoding. Backslashes the tokenizer WOULD consume are left in place so the downstream tokenizer/normalizer still folds them into a quote at their original position.

Callers are the “this pattern text came out of a VALUE” sites:

  • ported::subst::paramsubst — the search-subscript patterns (${a[(I)…]} / (i) / (r) / (R) / (K)), which reach patcompile through tokenize alone (c:Src/params.c:1727).
  • fusevm_bridge’s BUILTIN_GLOB_SUBST_GUARD / BUILTIN_PAT_DATA_BACKSLASH — the ${~spec} and setopt globsubst legs of a [[ … == pat ]] RHS and a case arm, the strcatsub shtokenize C runs at c:Src/subst.c:822/830.

Functions§

escape_data_backslashes
Rewrite a pattern string that came out of a VALUE so ported::pattern’s normalizer reads its backslashes the way zshtokenize does — see the module docs.