std.re
import std.re
re.test("^a.c$", "abc") # true
re.find("[0-9]+", "abc 123 def") # "123"
re.find_all("[0-9]+", "1 a 2 b 3") # ["1", "2", "3"]
re.captures("(\\w+)@(\\w+)", "ada@ecko") # ["ada@ecko", "ada", "ecko"]
re.captures_all(r"(\w)(\d)", "a1 b2") # [["a1", "a", "1"], ["b2", "b", "2"]]
re.split("[,;]", "a,b;c") # ["a", "b", "c"]
re.replace("[0-9]", "a1b2", "#") # "a#b#"
re.replace_first("[0-9]", "a1b2", "#") # "a#b2"
Every function takes the pattern first and the text second. replace and replace_first take the replacement third.
Replacing
replace changes every match and replace_first only the first. The replacement can refer to capture groups as $1, $2 and so on:
import std.re
re.replace(r"(\w+)@(\w+)", "ada@ecko", "$2/$1") # "ecko/ada"
Write $$ for a literal $.
Captures
captures returns a list where index 0 is the whole match and the rest are the groups, in order. A pattern that does not match returns null, so check before indexing:
c = re.captures(pattern, text)
unless c == null { print(c[1]) }
Singular returns null on a miss, plural returns an empty list: find and captures answer "nothing found" with null, while find_all and captures_all answer it with [], because a list of every match with none found is legitimately empty.
captures_all returns one such list per match.
Escaping in patterns
A regex backslash needs escaping in a normal Ecko string, so \d is written "\\d". Raw strings avoid the doubling:
re.test(r"\d+", "123")
Raw strings also suppress {expr} interpolation, which matters because {2,3} is a regex repetition and would otherwise be read as an interpolation.
Speed
A pattern is compiled the first time it is used and kept, keyed by its text, so calling re.test in a loop over a million lines compiles the pattern once. There is nothing to hold on to yourself. The cache is bounded: a program that builds thousands of different patterns at run time pays a recompile now and then, not unbounded memory.
Errors
An invalid pattern raises { kind: "parse" }, catchable like any other operational failure. Compile-time validation is not possible for a pattern built at runtime, so validate patterns that come from configuration or user input:
try { re.test(user_pattern, s) } catch (e) { reject("bad pattern") }
When not to reach for regex
Parsing HTML, XML or JSON. Use html, xml or std.json - nested structure is not a regular language, and a pattern that appears to work will fail on real input.
For fixed-string work, contains, starts_with, split and str.partition are faster and clearer than a pattern.