Les expressions régulières sont un moyen extrêmement polyvalent de faire de la correspondance de motifs dans des strings, grâce à un langage dédié (DSL) conçu pour cet usage.
Comme plusieurs autres langages de programmation, Julia ne cherche pas à implémenter sa propre bibliothèque Regex. Elle encapsule à la place la populaire bibliothèque PCRE2, ce qui lui donne une syntaxe Regex identique à celle de (par exemple) Gleam, et très proche de celle de JavaScript.
Ce programme Julia suppose que tu connais déjà la syntaxe de base des Regex. On se concentrera uniquement sur les fonctionnalités propres à Julia.
Voici quelques ressources pour rafraîchir tes connaissances en expressions régulières.
L'interface de Julia avec les expressions régulières est décrite dans le manuel.
Une expression régulière en Julia est simplement une string précédée d'un r avant le guillemet ouvrant ".
Toutes les fonctionnalités de base font partie de la bibliothèque standard.
En fait, beaucoup des fonctions déjà présentées dans le concept Strings sont conçues pour la recherche par Regex par défaut, comme 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
Des caractères de modification peuvent suivre le guillemet fermant, comme i pour une correspondance insensible à la casse.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
Souvent, on veut savoir ce qui correspond. Pour cela, on inclut des groupes de capture entre parenthèses dans l'expression régulière, puis on utilise la fonction 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
Bien sûr, une correspondance peut échouer.
Le résultat est alors la valeur spéciale Nothing au lieu d'un RegexMatch, alors prépare-toi à le tester.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
Bien que match commence par défaut au début de la string, on peut aussi préciser un décalage n pour ignorer les n premiers caractères.
# 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")
En Julia, match() ne trouve que la première correspondance dans la string cible : il n'existe pas de modificateur global comme dans certains autres langages.
À la place, on dispose de eachmatch(), qui renvoie un itérateur de correspondances.
Il est évalué de manière paresseuse, tu devras donc peut-être le convertir dans le format de ton choix.
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"
Les correspondances qui se chevauchent ne sont pas autorisées par défaut.
Ajoute overlap = true comme argument nommé pour changer ce comportement.
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")
Une raison fréquente d'utiliser une Regex est de remplacer la correspondance par une autre string.
La fonction replace() a été présentée dans le concept Strings, avec des littéraux de chaîne comme motif de recherche.
La même fonction peut exploiter toute la puissance de la correspondance Regex.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
Le deuxième exemple ci-dessus montre comment utiliser à la fois des groupes de capture numérotés et nommés dans le remplacement, au sein d'une string s" ".
Voir le manuel pour plus de détails : c'est un sujet qui renvoie sans cesse la plupart des programmeurs à la documentation !