Introdução
Zig não inclui uma biblioteca de regex na stdlib. Isso é intencional — regex é complexo e difícil de implementar com performance previsível. Em vez disso, Zig oferece funções de busca e manipulação de strings eficientes na std.mem que cobrem a maioria dos casos de uso sem a complexidade de um motor regex.
Para manipulação de strings em geral, veja Buscar Substrings, Split String e Comparar Strings.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Busca Simples
Verificar se Contém Substring
const std = @import("std");
fn contem(haystack: []const u8, needle: []const u8) bool {
return std.mem.indexOf(u8, haystack, needle) != null;
}
fn comecaCom(texto: []const u8, prefixo: []const u8) bool {
return std.mem.startsWith(u8, texto, prefixo);
}
fn terminaCom(texto: []const u8, sufixo: []const u8) bool {
return std.mem.endsWith(u8, texto, sufixo);
}
test "busca simples" {
try std.testing.expect(contem("Zig Brasil", "Brasil"));
try std.testing.expect(comecaCom("hello.zig", "hello"));
try std.testing.expect(terminaCom("hello.zig", ".zig"));
}
Validação de Padrões
Validar Email (simplificado)
fn validarEmail(email: []const u8) bool {
// Encontrar @
const arroba_pos = std.mem.indexOf(u8, email, "@") orelse return false;
// Parte antes do @ não pode ser vazia
if (arroba_pos == 0) return false;
// Parte depois do @
const dominio = email[arroba_pos + 1 ..];
if (dominio.len == 0) return false;
// Domínio deve ter pelo menos um ponto
const ponto_pos = std.mem.indexOf(u8, dominio, ".") orelse return false;
// Parte antes e depois do ponto não podem ser vazias
if (ponto_pos == 0) return false;
if (ponto_pos >= dominio.len - 1) return false;
// Verificar caracteres válidos
for (email[0..arroba_pos]) |c| {
if (!std.ascii.isAlphanumeric(c) and c != '.' and c != '_' and c != '-') {
return false;
}
}
return true;
}
test "validar email" {
try std.testing.expect(validarEmail("user@example.com"));
try std.testing.expect(validarEmail("nome.sobrenome@dominio.com.br"));
try std.testing.expect(!validarEmail("invalido"));
try std.testing.expect(!validarEmail("@semlocal.com"));
try std.testing.expect(!validarEmail("sem@dominio"));
}
Validar Número
fn ehNumero(texto: []const u8) bool {
if (texto.len == 0) return false;
var inicio: usize = 0;
if (texto[0] == '-' or texto[0] == '+') {
inicio = 1;
if (texto.len == 1) return false;
}
var tem_ponto = false;
for (texto[inicio..]) |c| {
if (c == '.' and !tem_ponto) {
tem_ponto = true;
} else if (!std.ascii.isDigit(c)) {
return false;
}
}
return true;
}
test "validar número" {
try std.testing.expect(ehNumero("42"));
try std.testing.expect(ehNumero("-3.14"));
try std.testing.expect(ehNumero("+100"));
try std.testing.expect(!ehNumero("abc"));
try std.testing.expect(!ehNumero(""));
try std.testing.expect(!ehNumero("1.2.3"));
}
Extrair Padrões
Extrair Valores entre Delimitadores
fn extrairEntre(texto: []const u8, inicio: []const u8, fim: []const u8) ?[]const u8 {
const pos_inicio = std.mem.indexOf(u8, texto, inicio) orelse return null;
const conteudo_inicio = pos_inicio + inicio.len;
const pos_fim = std.mem.indexOf(u8, texto[conteudo_inicio..], fim) orelse return null;
return texto[conteudo_inicio .. conteudo_inicio + pos_fim];
}
fn extrairTodosEntre(
allocator: std.mem.Allocator,
texto: []const u8,
inicio: []const u8,
fim: []const u8,
) ![][]const u8 {
var resultados = std.ArrayList([]const u8).init(allocator);
errdefer resultados.deinit();
var pos: usize = 0;
while (pos < texto.len) {
const pos_inicio = std.mem.indexOf(u8, texto[pos..], inicio) orelse break;
const abs_inicio = pos + pos_inicio + inicio.len;
const pos_fim = std.mem.indexOf(u8, texto[abs_inicio..], fim) orelse break;
const abs_fim = abs_inicio + pos_fim;
try resultados.append(texto[abs_inicio..abs_fim]);
pos = abs_fim + fim.len;
}
return resultados.toOwnedSlice();
}
test "extrair entre delimitadores" {
const resultado = extrairEntre("<title>Zig Brasil</title>", "<title>", "</title>");
try std.testing.expectEqualStrings("Zig Brasil", resultado.?);
}
Extrair Números de uma String
fn extrairNumeros(allocator: std.mem.Allocator, texto: []const u8) ![]i64 {
var numeros = std.ArrayList(i64).init(allocator);
errdefer numeros.deinit();
var inicio: ?usize = null;
for (texto, 0..) |c, i| {
if (std.ascii.isDigit(c) or (c == '-' and inicio == null)) {
if (inicio == null) inicio = i;
} else {
if (inicio) |s| {
const num = std.fmt.parseInt(i64, texto[s..i], 10) catch {
inicio = null;
continue;
};
try numeros.append(num);
inicio = null;
}
}
}
// Verificar último número
if (inicio) |s| {
if (std.fmt.parseInt(i64, texto[s..], 10)) |num| {
try numeros.append(num);
} else |_| {}
}
return numeros.toOwnedSlice();
}
test "extrair números" {
const nums = try extrairNumeros(std.testing.allocator, "Tenho 3 gatos e 2 cachorros");
defer std.testing.allocator.free(nums);
try std.testing.expectEqual(@as(usize, 2), nums.len);
try std.testing.expectEqual(@as(i64, 3), nums[0]);
try std.testing.expectEqual(@as(i64, 2), nums[1]);
}
Matching com Wildcards
Glob-style Pattern Matching
fn matchGlob(pattern: []const u8, texto: []const u8) bool {
var pi: usize = 0;
var ti: usize = 0;
var star_pi: ?usize = null;
var star_ti: usize = 0;
while (ti < texto.len) {
if (pi < pattern.len and (pattern[pi] == '?' or pattern[pi] == texto[ti])) {
pi += 1;
ti += 1;
} else if (pi < pattern.len and pattern[pi] == '*') {
star_pi = pi;
star_ti = ti;
pi += 1;
} else if (star_pi) |sp| {
pi = sp + 1;
star_ti += 1;
ti = star_ti;
} else {
return false;
}
}
while (pi < pattern.len and pattern[pi] == '*') {
pi += 1;
}
return pi == pattern.len;
}
test "glob matching" {
try std.testing.expect(matchGlob("*.zig", "main.zig"));
try std.testing.expect(matchGlob("src/*.zig", "src/lib.zig"));
try std.testing.expect(matchGlob("test_?", "test_1"));
try std.testing.expect(!matchGlob("*.zig", "main.c"));
}
Substituição
fn substituir(
allocator: std.mem.Allocator,
texto: []const u8,
buscar: []const u8,
novo: []const u8,
) ![]u8 {
var resultado = std.ArrayList(u8).init(allocator);
errdefer resultado.deinit();
var pos: usize = 0;
while (pos < texto.len) {
if (std.mem.indexOf(u8, texto[pos..], buscar)) |found| {
try resultado.appendSlice(texto[pos .. pos + found]);
try resultado.appendSlice(novo);
pos += found + buscar.len;
} else {
try resultado.appendSlice(texto[pos..]);
break;
}
}
return resultado.toOwnedSlice();
}
test "substituir" {
const resultado = try substituir(
std.testing.allocator,
"Olá Mundo! Olá Zig!",
"Olá",
"Oi",
);
defer std.testing.allocator.free(resultado);
try std.testing.expectEqualStrings("Oi Mundo! Oi Zig!", resultado);
}
Conclusão
A maioria dos casos de uso de regex pode ser resolvida com funções de std.mem e lógica de parsing manual em Zig. Para casos que realmente exigem regex (como validação de formatos complexos), considere usar uma biblioteca C de regex via @cImport.
Para mais manipulação de strings, veja Formatar Strings e Converter String para Número. Para testes dessas funções, consulte Testes Unitários.