#regex
Regular expressions, matched in linear time.
Patterns use the RE2 dialect: Perl syntax without backreferences or
lookaround. A match costs O(n * m) in subject length and pattern size,
whatever the subject. When more than one match is possible the leftmost
one wins, and among the matches starting there the pattern's preference
decides: alternation order first, greedy before lazy. So a|ab matches
"a" in "ab", not the longest alternative.
Positions are codepoint offsets, as in string.indexOf. . matches any
codepoint but \n, and \d, \w, \s, \b and (?i) folding are
ASCII only, matching string.isDigit and string.toUpper.
A subject may also be a UTF-8 byte buffer rather than a String:
isFullMatchBytes and findBytes match a window of a Bytes, with each
byte a code from 0 to 255.
compile reports a bad pattern as an Err; mustCompile panics, which
suits a pattern written as a literal. A top-level binding is evaluated
once, so wordRe = mustCompile "\\w+" compiles one time however often
it is used.
Every escape in a pattern needs its backslash doubled in Medaka source,
because a plain string literal accepts only \n, \t, \r, \0, \\,
\" and \u{...}. Write \d as "\\d" and a literal brace as "\\{".
The syntax accepted is:
x,\.,\\,\n,\t,\r,\xHH,\u{HEX}: one literal codepoint..: any codepoint but\n, or any at all under(?s).[abc],[a-z0-9],[^...], with\d,\w,\sand their complements usable inside.\d,\D,\w,\W,\s,\S: ASCII digit, word ([A-Za-z0-9_]), space ([ \t\n\r]), and complements.^,$: start and end of the subject, or of a line under(?m).\b,\B: ASCII word boundary and its complement.ab,a|b,(...),(?:...): concatenation, alternation, capturing and non-capturing group.*,+,?,{n},{n,},{n,m}: greedy repetition, and*?,+?,??,{n,m}?for the lazy forms.(?i),(?m),(?s)and combinations, at the start of the pattern only.
A { that does not open a valid bound is a literal {. Named groups,
POSIX classes, Unicode classes and inline flag scoping are rejected with
a message naming the unsupported feature.
#Types
#Regex
data Regex -- abstract: the constructors are not exportedA compiled pattern.
Values are immutable and safe to share. Debug renders the source
pattern. There is no Eq or Ord instance.
Instances: Debug
#RegexError
data RegexError
= RegexError { message : String, position : Int }Why a pattern would not compile, and where.
position is the codepoint offset in the pattern at which the parser
gave up.
Instances: Eq, Debug
#Group
data Group
= Group { start : Int, end : Int, text : String }One capture group's span and text.
start and end are codepoint offsets into the subject, end
exclusive.
Instances: Eq, Debug
#Match
data Match
= Match { start : Int, end : Int, text : String, groups : List (Option Group) }One match: its span, its text, and its capture groups.
groups holds group 1 onwards in pattern order, with None for a group
that did not take part in the match.
Instances: Eq, Debug
#Compiling a pattern
#compile
compile : String -> Result RegexError Regex
compile patternThe pattern compiled, or an Err naming what is wrong and where.
> map source (compile "a+b")
Ok "a+b"
> compile "a("
Err (RegexError { message = "pattern is missing a closing )", position = 2 })#mustCompile
mustCompile : String -> Regex
mustCompile patternThe pattern compiled, panicking when it does not compile.
Use it for a pattern written as a literal in the program, and compile
for a pattern that comes from input. A top-level binding is evaluated
once, so the pattern compiles one time.
> source (mustCompile "[0-9]+")
"[0-9]+"#source
source : Regex -> String
source reThe pattern the regex was compiled from.
> source (mustCompile "^\\d+$")
"^\\d+$"#escape
escape : String -> String
escape sThe text as a pattern matching exactly itself, with every metacharacter quoted.
Use it to embed input, such as a search string or the literal parts of a glob, in a larger pattern.
> escape "a.b*c"
"a\\.b\\*c"
> isFullMatch (mustCompile (escape "1+1=2")) "1+1=2"
True#Matching
#isMatch
isMatch : Regex -> String -> Bool
isMatch re sWhether the pattern matches anywhere in the subject.
> isMatch (mustCompile "\\d") "abc7"
True
> isMatch (mustCompile "\\d") "abc"
False#isFullMatch
isFullMatch : Regex -> String -> Bool
isFullMatch re sWhether the pattern matches the whole subject.
The pattern is anchored at both ends, so a pattern that matches only a prefix or a suffix does not pass.
> isFullMatch (mustCompile "[a-z]+") "abc"
True
> isFullMatch (mustCompile "[a-z]+") "abc1"
False#find
find : Regex -> String -> Option Match
find re sThe leftmost match, or None when the pattern does not match.
> find (mustCompile "\\d+") "ab123cd"
Some (Match { start = 2, end = 5, text = "123", groups = [] })
> find (mustCompile "z") "ab"
None#findFrom
findFrom : Int -> Regex -> String -> Option Match
findFrom from re sThe leftmost match that starts at or after from.
from is clamped to the subject. ^ and $ still mean the ends of the
whole subject, not of the searched tail.
> map (m => (m : Match).start) (findFrom 3 (mustCompile "a") "aaaaa")
Some 3
> findFrom 3 (mustCompile "^a") "aaaaa"
None#fullMatch
fullMatch : Regex -> String -> Option Match
fullMatch re sThe match covering the whole subject, or None.
> map (m => (m : Match).text) (fullMatch (mustCompile "a|ab") "ab")
Some "ab"
> fullMatch (mustCompile "a") "ab"
None#findAll
findAll : Regex -> String -> List Match
findAll re sEvery match, left to right, none of them overlapping.
An empty match is kept, except directly at the end of the previous match:
\d* over "a1b" reports the empty match before "a", "1", and the
empty match at the end.
> map (m => (m : Match).text) (findAll (mustCompile "\\d+") "a1b22c")
["1", "22"]
> map (m => (m : Match).text) (findAll (mustCompile "\\d*") "a1b")
["", "1", ""]#Byte subjects
#isFullMatchBytes
isFullMatchBytes : Regex -> Bytes -> Int -> Int -> Bool
isFullMatchBytes re bytes start endWhether the pattern matches the whole of bytes[start..end), each byte
taken as a code from 0 to 255.
Only the window is read, and it is copied once; the rest of bytes is
not. A bound in the pattern counts bytes rather than codepoints. start
and end are clamped to the buffer, and ^, $ and \b refer to the
ends of the window.
A pattern applied to bytes should name only ASCII: [a-z] matches the
byte 97, and a non-ASCII codepoint arrives as two or more UTF-8 bytes, none
of which any ASCII class matches. B.encodeUtf8 "abc" is the bytes 97, 98
and 99.
> isFullMatchBytes (mustCompile "[a-z]+") (B.encodeUtf8 "abc") 0 3
True
> isFullMatchBytes (mustCompile "[a-z]+") (B.encodeUtf8 "abc.") 0 3
True
> isFullMatchBytes (mustCompile "[a-z]+") (B.encodeUtf8 "abc.") 0 4
False#findBytes
findBytes : Regex -> Bytes -> Int -> Int -> Option Match
findBytes re bytes start endThe leftmost match in bytes[start..end), or None.
find over a byte buffer. start, end and the reported offsets are
byte offsets into the whole buffer. The match's text and each group's
text are the matched bytes decoded as UTF-8, so a span that cuts a
codepoint decodes as string.fromUtf8 decodes malformed input. An
ASCII-only pattern never produces such a span.
> map (m => (m : Match).text) (findBytes (mustCompile "[0-9]+") (B.encodeUtf8 "a12b") 0 4)
Some "12"
> map (m => (m : Match).start) (findBytes (mustCompile "[0-9]+") (B.encodeUtf8 "a12b") 0 4)
Some 1
> findBytes (mustCompile "[0-9]+") (B.encodeUtf8 "a12b") 0 1
None#Replacing and splitting
#replace
replace : Regex -> String -> String -> String
replace re repl sThe subject with the first match replaced by repl.
In repl, $0 is the whole match, $1 to $9 are the capture groups,
and $$ is a literal $. A group that did not take part expands to the
empty string, and a $ before anything else is itself.
> replace (mustCompile "\\d+") "N" "a1b2"
"aNb2"
> replace (mustCompile "(\\w+)@(\\w+)") "$2/$1" "user@host"
"host/user"#replaceAll
replaceAll : Regex -> String -> String -> String
replaceAll re repl sThe subject with every match replaced by repl.
repl expands as in replace.
> replaceAll (mustCompile "\\s+") " " "a b\tc"
"a b c"
> replaceAll (mustCompile "a*") "-" "abc"
"-b-c-"#replaceAllWith
replaceAllWith : Regex -> (Match -> <e> String) -> String -> <e> String
replaceAllWith re f sThe subject with every match replaced by f applied to it.
Nothing in the result is rescanned, so a replacement that looks like the pattern is left alone.
> replaceAllWith (mustCompile "\\d") (m => "[" ++ (m : Match).text ++ "]") "a1b2"
"a[1]b[2]"#split
split : Regex -> String -> List String
split re sThe subject cut at every match, with the matches dropped.
A leading or trailing separator leaves an empty piece, as string.split
does, so joining the pieces back with a literal separator recovers the
subject. A subject with no match is returned whole.
> split (mustCompile ",\\s*") "a, b,c"
["a", "b", "c"]
> split (mustCompile ",") ",a,"
["", "a", ""]