regex
expressões regulares (multi-engine)
use regexTrês engines com garantias diferentes, escolhidos por construção via sub-namespace: RE2 (default, tempo linear, sem ReDoS), PCRE (backtracking, features completas, ReDoS-prone), HyperScan (multi-pattern + streaming, superfície própria). Um núcleo comum de operações (
is_match/find/captures/replace/split) mais os extras por engine. Padrão literal compila em comptime (valida + embarca; inválido = erro de compilação); padrão de runtime compila em runtime. O engine é pesado e volátil, daí tier 1; o algoritmo RE2 é frozen (tier 0), empacotado numa lib que evolui.
Construção e engines
Seção intitulada “Construção e engines”O engine é parte da construção, não um parâmetro depois. regex.compile é o atalho do default (RE2);
os outros vêm dos sub-namespaces. RE2 e PCRE devolvem o mesmo tipo Regex (núcleo comum); HyperScan
funciona de outra forma e tem superfície própria.
regex.compile(pattern) → Regex via RE2 (o default)regex.re2.compile(pattern) → Regex via RE2 (explícito)regex.pcre.compile(pattern) → Regex via PCREregex.hyperscan.compile(pats) → Scanner via HyperScan (ver "HyperScan", abaixo)Assinatura (idêntica em regex.compile/re2.compile/pcre.compile; muda só o engine que valida e casa):
fn compile(pattern: string) -> Result[Regex, RegexError] // RE2 (default)fn compile_with(pattern: string, opts: Options) -> Result[Regex, RegexError]
alias RegexError = error{ Malformed, // sintaxe de padrão inválida Unsupported, // feature ausente no engine escolhido (backref/lookaround no RE2) TooLarge // padrão excede os limites do engine (RE2 tem teto de programa)}Comptime quando o padrão é conhecido
Seção intitulada “Comptime quando o padrão é conhecido”Padrão literal compila em comptime: valida (literal inválido vira erro de compilação, não Err
de runtime) e embarca o programa compilado no binário (custo-zero de compilação em runtime). Por isso um
literal pode ser .or_panic() sem risco: o comptime já provou que casa, ou falhou a build. Padrão de
runtime compila em runtime e devolve Result. (Espelha a história de const/string, §16: o literal é
comptime-na-fonte; o binding decide.)
const WORD := regex.compile("[a-z_][a-z0-9_]*").or_panic() // comptime: valida + embarca; nunca panicauser_re := regex.compile(padrao_do_usuario) catch |e| { return e } // runtime: Result tratado normalmenteFeature não-suportada pelo engine segue a mesma régua: padrão conhecido dá erro de compilação; padrão de
runtime dá Err(Unsupported). (regex.compile("(a)\\1"), uma backreference, falha no RE2; regex.pcre.compile
aceita.)
Núcleo comum (Regex, RE2 e PCRE)
Seção intitulada “Núcleo comum (Regex, RE2 e PCRE)”decl Regex { ... }fn (r: Regex) is_match(text: string) -> boolfn (r: Regex) find(text: string) -> Optional[Match] // 1º matchfn (r: Regex) find_all(text: string) -> Iterator[Match] // todos (lazy)fn (r: Regex) captures(text: string) -> Optional[Captures] // 1º match com gruposfn (r: Regex) captures_all(text: string) -> Iterator[Captures]fn (r: Regex) replace(text: string, replacement: string) -> string // só o 1ºfn (r: Regex) replace_all(text: string, replacement: string) -> string // todosfn (r: Regex) split(text: string) -> Iterator[string]fn (r: Regex) pattern() -> string // o padrão fonteNo replacement, grupos são referenciados por $1 (numerado) e ${nome} (nomeado); $$ é um $ literal.
Match e Captures
Seção intitulada “Match e Captures”Cada Match carrega o trecho casado (view da entrada) e a faixa de bytes:
decl Match { pub text: string // o trecho casado (slice/view da entrada) pub start: usize // offset de byte inicial pub end: usize // offset de byte final (exclusivo)}
decl Captures { ... }fn (c: Captures) get(i: usize) -> Optional[Match] // grupo numerado (0 = o match inteiro)fn (c: Captures) get(name: string) -> Optional[Match] // grupo nomeado, dispatch por tipo (§13)fn (c: Captures) len() -> usize // nº de gruposfn (c: Captures) iter() -> Iterator[Optional[Match]] // grupos em ordem (none = não-participante)get é um nome com dois tipos de argumento (§13): caps.get(1) (numerado) e caps.get("ano") (nomeado).
Grupo opcional que não participou do match é none, daí o Optional.
re := regex.compile("(?P<ano>\\d{4})-(?P<mes>\\d{2})").or_panic()match re.captures("2026-06") { // captures → Optional none => { return } caps => { ano := caps.get("ano").or_panic().text } // "2026"}Opções (Unicode opt-in, ASCII-rápido default)
Seção intitulada “Opções (Unicode opt-in, ASCII-rápido default)”decl Options { pub unicode: bool // true: classes/`.` em codepoints + tabelas Unicode; false: ASCII/bytes pub case_insensitive: bool pub multi_line: bool // ^/$ casam nas quebras de linha pub dot_all: bool // `.` casa '\n' pub swap_greed: bool // inverte greedy/lazy}unicode é false por default (ASCII-rápido, coerente com unicode/strings serem opt-in/heavy): . e
\w/\d operam em bytes. Ligando unicode, . casa um codepoint e as classes usam as tabelas
Unicode (que aí pesam). Você escolhe o custo na construção, em vez de a linguagem escolher por você.
HyperScan: multi-pattern e streaming (superfície própria)
Seção intitulada “HyperScan: multi-pattern e streaming (superfície própria)”HyperScan tem semântica distinta: casar N padrões simultaneamente sobre um fluxo, com callback
por acerto (ferramenta de IDS/DPI, não regex geral). Daí Scanner, não Regex, e é o engine de
streaming (a razão de existir dele):
decl Scanner { ... }fn hyperscan.compile(patterns: []string) -> Result[Scanner, RegexError] // muitos padrões → um Scannerfn hyperscan.compile_ids(patterns: []Pattern) -> Result[Scanner, RegexError] // com id por padrão
decl Pattern { pub expr: string; pub id: u32; pub opts: Options }
fn (s: Scanner) scan(text: string, on_match: fn(MatchEvent)) // sobre stringfn (s: Scanner) scan_stream(r: Readable, on_match: fn(MatchEvent)) -> error{Io} // sobre fluxo (streaming)
decl MatchEvent { pub id: u32 // qual padrão casou (o id do Pattern; índice na lista quando sem id) pub start: usize // offset no fluxo pub end: usize}scan_stream sobre um Readable é o match sobre fluxo: HyperScan casa incrementalmente sem materializar
a entrada. (RE2 faz streaming parcial, match/no-match incremental, sem captures plenos, anotado como
direção futura no Regex; PCRE é string-only, não streama.)
Curadoria
Seção intitulada “Curadoria”- Multi-engine, com o engine escolhido na construção (
regex.re2/pcre/hyperscan;regex.compileé RE2). Os três têm garantias diferentes, e umRegexúnico fingindo que são iguais quebra (backref compila no PCRE, falha no RE2). Núcleo comum mais extras por engine, não um denominador que mente. - RE2 default, com garantia linear (sem ReDoS): padrão de entrada não-confiável é seguro. O custo honesto é
ficar sem backreference/lookaround (
Unsupported). É o diferencial de segurança, então é o default. - PCRE, com features completas mas ReDoS-prone: backref/lookaround/recursão, ao custo de blow-up exponencial. Só com padrão confiável. Mesma API; muda o que compila e a garantia de tempo.
- HyperScan, superfície própria (multi-pattern + streaming,
Scanner/callback), porque tem outra semântica (casar N sobre um fluxo), não regex geral. É onde mora o match sobreReadable. - Unicode configurável na construção, ASCII-rápido por default:
Options.unicodeliga codepoints + tabelas; o default é ASCII/bytes. As tabelas só pesam quando você opta (opt-in, comounicode). - Comptime se conhecido: literal compila em comptime (valida + embarca; inválido = erro de
compilação), então
.or_panic()num literal nunca panica; runtime compila em runtime (Result). - Captures por dispatch de tipo:
get(i)/get(nome), um nome e dois tipos de argumento (§13). CadaMatchcarrega o trecho mais a faixa de bytes; grupo não-participante énone. - Sem allocator nas assinaturas (colorless);
find_all/split/captures_allsãoIteratorlazy;replacealoca pelo@mmdo contexto.