regex
Regular expressions, using Rust regex syntax.
introduce regex| Function | Signature | Returns |
|---|---|---|
matches | matches(pat, text) | bool |
find | find(pat, text) | str or None |
find_all | find_all(pat, text) | array |
captures | captures(pat, text) | array or None |
replace | replace(pat, text, replacement) | str |
split | split(pat, text) | array |
The pattern comes first in every function, and the argument is named pat
because pattern is a reserved keyword in Oxigen.
Backslashes need doubling in a normal string: "\\d+", not "\d+".
The syntax is Rust's regex crate: no backtracking, and therefore no
backreferences and no lookaround. Every pattern runs in time linear in the
input, so a hostile input cannot make one blow up.
#matches(pat, text)
True if the pattern matches anywhere in the text. Anchor with ^/$ if
you mean the whole string.
println(regex.matches("\\d+", "abc123")) // True
println(regex.matches("^\\d+$", "abc123")) // False#find(pat, text)
The first match as a string, or None.
println(regex.find("\\d+", "a1 b22")) // 1
println(regex.find("\\d+", "abc")) // None#find_all(pat, text)
Every non-overlapping match, in order. An empty array if there are none.
println(regex.find_all("\\d+", "a1 b22 c333")) // [1, 22, 333]#captures(pat, text)
The capture groups of the first match, as an array: index 0 is the whole
match, index 1 the first group, and so on. None if nothing matched. A group
that did not participate in the match comes back as "".
println(regex.captures("(\\d+)-(\\d+)", "tel 555-1234"))
// [555-1234, 555, 1234]
println(regex.captures("(\\d+)", "abc")) // Noneintroduce regex
fun parse_version(s <str>) {
c := regex.captures("^v?(\\d+)\\.(\\d+)\\.(\\d+)$", s)
option {
c == None -> { None }
{ (int(c[1]), int(c[2]), int(c[3])) }
}
}
println(parse_version("v1.4.2")) // (1, 4, 2)
println(parse_version("nope")) // None#replace(pat, text, replacement)
Replace every match. Note the argument order: pattern, then text, then replacement.
$1, $2, … in the replacement refer to capture groups; $0 is the whole
match. Use ${1} when a digit would otherwise run into following text.
println(regex.replace("(\\w+)@(\\w+)", "x@y", "$2.$1")) // y.x
println(regex.replace("\\s+", "a b\tc", " ")) // a b cFor a literal (non-pattern) replacement, use
strings.replace — it is faster and needs no
escaping.
#split(pat, text)
Split on a pattern — what strings.split cannot do.
println(regex.split("\\s*,\\s*", "a , b,c")) // [a, b, c]#Worked example
introduce regex
introduce io
introduce array
// Pull every URL out of a document and keep the unique hosts.
url_pat <str> = "https?://([\\w.-]+)[^\\s]*"
fun hosts(text <str>) {
out <array> := []
each url in regex.find_all(url_pat, text) {
c := regex.captures(url_pat, url)
out = push(out, c[1]) unless c == None
}
array.unique(out)
}
println(hosts("see https://oxigen.dev/docs and http://oxigen.dev/blog and https://example.com"))
// [oxigen.dev, example.com]Each call compiles its pattern, so hoisting a hot pattern out of a loop is not
possible yet — prefer one find_all over calling matches per line when you
can.
See also: strings for literal search and replace, parse_args for command-line flags.