As expressões regulares são uma forma extremamente versátil de fazer correspondência de padrões em strings, recorrendo a uma Linguagem de Domínio Específico (DSL) concebida para esse fim.
Tal como várias outras linguagens de programação, a Julia não tenta implementar a sua própria biblioteca de expressões regulares. Em vez disso, usa a popular biblioteca PCRE2, o que lhe dá uma sintaxe de expressões regulares idêntica à de (por exemplo) Gleam e muito semelhante à de JavaScript.
Este programa de estudos da Julia pressupõe que já conheces a sintaxe básica de expressões regulares. Vamos concentrar-nos exclusivamente nas funcionalidades específicas da Julia.
Abaixo estão listados alguns recursos para refrescares os teus conhecimentos de expressões regulares.
A interface da Julia para expressões regulares está descrita no manual.
Uma expressão regular em Julia é simplesmente uma string precedida de r antes das aspas de abertura ".
Toda a funcionalidade básica faz parte da biblioteca padrão.
De facto, muitas das funções já abordadas no Conceito Strings são concebidas, por predefinição, para pesquisas com expressões regulares, como occursin().
julia> re = r"test$"
r"test$"
julia> typeof(re)
Regex
# Does a string end with "test"?
julia> occursin(re, "this is a test")
true
julia> occursin(re, "these are tests")
false
Após as aspas de fecho podem vir caracteres modificadores, como o i para uma correspondência que não distingue maiúsculas de minúsculas.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
Muitas vezes, queremos saber o que corresponde ao padrão.
Isso consegue-se incluindo grupos de captura entre parênteses dentro da expressão regular e usando depois a função match().
julia> m = match(r"(\d+g) .* (\d+ml)", "dissolve 25g sugar in 200ml water")
RegexMatch("25g sugar in 200ml", 1="25g", 2="200ml")
julia> m.captures
2-element Vector{Union{Nothing, SubString{String}}}:
"25g"
"200ml"
# how many matches?
julia> length(m.captures)
2
# what matched?
julia> m[1], m[2]
("25g", "200ml")
# Starting positions of the matches (character index)
julia> m.offsets
2-element Vector{Int64}:
10
23
Claro que uma correspondência pode falhar.
Nesse caso, o resultado é o valor especial Nothing em vez de um RegexMatch, por isso prepara-te para testar isso.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
Embora, por predefinição, match comece no início da string, também podemos indicar um deslocamento n para ignorar os primeiros n carateres.
# capture first match
julia> m = match(r"(\wat)", "cat, sat, mat")
RegexMatch("cat", 1="cat")
# ignore first 5 characters, then match
julia> m = match(r"(\wat)", "cat, sat, mat", 5)
RegexMatch("sat", 1="sat")
Na Julia, match() só encontra a primeira correspondência dentro da string de destino: não existe um modificador global como nalgumas outras linguagens.
Em vez disso, temos eachmatch(), que devolve um iterador de correspondências.
A avaliação é preguiçosa, por isso podes ter de o converter no formato que pretendes.
julia> matches = eachmatch(r"(\wat)", "cat, sat, mat")
Base.RegexMatchIterator{String}(r"(\wat)", "cat, sat, mat", false)
# convert to vector
julia> collect(matches)
3-element Vector{RegexMatch}:
RegexMatch("cat", 1="cat")
RegexMatch("sat", 1="sat")
RegexMatch("mat", 1="mat")
# convert with comprehension
julia> [m.match for m in matches]
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
# broadcast an anonymous function
julia> (m -> m.match).(matches)
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
Por predefinição, não são permitidas correspondências sobrepostas.
Acrescenta overlap = true como argumento de palavra-chave para alterar este comportamento.
julia> eachmatch(r"aba", "abababa") |> collect # matches at positions 1, 5
2-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
julia> eachmatch(r"aba", "abababa"; overlap = true) |> collect # also matches at position 3
3-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
RegexMatch("aba")
Uma razão frequente para usar uma expressão regular é substituir a correspondência por uma string diferente.
A função replace() foi abordada no Conceito Strings, onde usámos literais de string para pesquisar.
A mesma função pode tirar partido de todo o poder da correspondência com expressões regulares.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
O segundo exemplo acima mostra como se podem usar grupos de captura numerados e nomeados na substituição, dentro de uma string s" ".
Consulta o manual para mais detalhes: este é um tema que obriga constantemente a maioria dos programadores a voltar à documentação!