March Docs

Regex

Regex module: simple regular expression engine (pure March implementation).

Supports: literal chars exact character match . any single character

  • zero or more of preceding atom (greedy)
+                   one or more of preceding atom (greedy)
?                   zero or one of preceding atom (greedy)
^                   anchor at start of string
$                   anchor at end of string
[abc]               character class (any of a, b, c)
[a-z]               character range in a class
[^abc]              negated character class
\d  \D              digit / non-digit
\w  \W              word char (alphanumeric + _) / non-word
\s  \S              whitespace / non-whitespace
\\  \.  \*  etc.    literal special characters

Functions: matches(pattern, string) -> Bool find(pattern, string) -> Option(String) find_all(pattern, string) -> List(String) replace(pattern, replacement, string) -> String replace_all(pattern, replacement, string) -> String split(pattern, string) -> List(String)

Types

ptypeRegexAtomRegexAtom =#
ptypeRegexQuantRegexQuant = QOne | QZeroOrMore | QOneOrMore | QOptional#
ptypeRegexPatternRegexPattern = RegexPattern(Bool, List(RegexItem), Bool)#
typeRegexOptsRegexOpts = { case_insensitive : Bool, multiline : Bool }#

Regex matching options. Pass to the *_opts variants of the public match functions.

  • case_insensitive: when true, ASCII letters in both the pattern
and the input are folded to lowercase before matching.
  • multiline: reserved for future use. Currently ^ always matches
string start and `$` always matches string end; line-aware
behaviour is not yet implemented.

Functions

fndefault_optsdefault_opts() : RegexOpts#

Default options: all flags disabled (case-sensitive, single-line). Update a field to turn a flag on.

Examples

march> Regex.default_opts() == { case_insensitive: false, multiline: false }
true
march> Regex.matches_opts("ABC", "abc", { Regex.default_opts() with case_insensitive: true })
true
fnfindfind(pattern, string)#

Find the first match of pattern in string. Returns Some(matched_substring) or None.

march> Regex.find("\\d+", "price: 42 dollars")
Some("42")
march> Regex.find("xyz", "hello")
None
fnfind_allfind_all(pattern, string)#

Find all non-overlapping matches of pattern in string. Returns a list of matched substrings in order.

march> Regex.find_all("\\d+", "a1 b22 c333")
["1", "22", "333"]
fnfind_backtrackingfind_backtracking(pattern : String, string : String) : Option(String)#

The original backtracking find, retained for differential testing.

Returns the same leftmost match as find (Some(substring), None when nothing matches or the pattern is invalid), but via a backtracking matcher whose running time can grow exponentially with the input on nested-quantifier patterns. Kept public and named so the parity tests can compare the two engines; a test that compared the linear engine against itself would prove nothing. Do not use it on untrusted input — see matches_linear for why.

Examples

march> Regex.find_backtracking("a+", "baaab")
Some("aaa")
march> Regex.find_backtracking("\\d", "abc")
None
fnfind_linearfind_linear(pattern : String, string : String) : Option(String)#

The linear-time (NFA simulation) find: the engine find itself uses, exposed under an explicit name for differential testing against find_backtracking. Returns Some(substring) for the leftmost match, or None when nothing matches or the pattern is invalid. Safe on untrusted input.

Examples

march> Regex.find_linear("a+", "baaab")
Some("aaa")
march> Regex.find_linear("a*", "")
Some("")
march> Regex.find_linear("[a", "[a")
None
fnmatchesmatches(pattern, string)#

Test whether pattern matches anywhere in string. If pattern starts with '^', the match is anchored to the start. If pattern ends with '$', the match is anchored to the end.

march> Regex.matches("\\d+", "hello 42 world")
true
march> Regex.matches("^hello", "hello world")
true
march> Regex.matches("^hello", "say hello")
false
fnmatches_backtrackingmatches_backtracking(pattern : String, string : String) : Bool#

The original backtracking matches, retained for differential testing.

Same answer as matches (false for an invalid pattern), but its running time can grow exponentially with the input on nested-quantifier patterns. Kept public and named so the parity tests can compare the two engines. Do not use it on untrusted input — see matches_linear for why.

Examples

march> Regex.matches_backtracking("^a+$", "aaa")
true
march> Regex.matches_backtracking("^a+$", "aab")
false
fnmatches_linearmatches_linear(pattern : String, s : String) : Bool#

Whether pattern matches anywhere in s, in time linear in the input.

Same answer as matches, without the backtracking blowup: this simulates the set of reachable NFA states, so the work per input byte is bounded by the pattern length no matter what the input is. An invalid pattern (e.g. an unterminated [ class) gives false.

Examples

march> Regex.matches_linear("[0-9]+", "abc123")
true
march> Regex.matches_linear("^a*$", "aaab")
false
march> Regex.matches_linear("[a", "[a")
false
fnmatches_optsmatches_opts(pattern, string, opts)#

Like matches, but accepts a RegexOpts record for per-call flags.

case_insensitive lowercases the whole pattern and input (ASCII only) before matching, so the uppercase escapes \D, \W, \S are folded to \d, \w, \s and change meaning. multiline currently has no effect.

march> Regex.matches_opts("HELLO", "hello world", { case_insensitive: true, multiline: false })
true
fnreplacereplace(pattern, replacement, string)#

Replace the first match of pattern in string with replacement. Returns the resulting string.

march> Regex.replace("\\d+", "NUM", "price 42")
"price NUM"
fnreplace_allreplace_all(pattern, replacement, string)#

Replace all non-overlapping matches of pattern in string with replacement. Returns the resulting string; string unchanged when nothing matches or the pattern is invalid. replacement is literal (no group references). A pattern that matches the empty string (e.g. x*) skips one input byte after each empty match, and that byte is dropped from the result.

march> Regex.replace_all("\\d", "X", "a1b2c3")
"aXbXcX"
march> Regex.replace_all("\\s+", " ", "a  b\t\tc")
"a b c"
march> Regex.replace_all("z", "X", "abc")
"abc"
fnsplitsplit(pattern, string)#

Split string on matches of pattern. Returns the list of substrings between matches, including empty strings for adjacent or leading/trailing matches; [string] when nothing matches or the pattern is invalid. As with replace_all, a pattern that matches the empty string drops the byte after each empty match.

march> Regex.split("\\s+", "hello   world  foo")
["hello", "world", "foo"]
march> Regex.split(",", "a,b,c")
["a", "b", "c"]
march> Regex.split(",", ",a,,b")
["", "a", "", "b"]
march> Regex.split(";", "abc")
["abc"]