Regular expressions for MoonBit.
let year = @moonregex.search("\\d{4}", "copyright 2026").unwrap()
assert_eq(year.content(), "2026")
assert_eq(year.start(0), 10)
assert_eq(@moonregex.sub("\\s+", " ", "a b c"), "a b c")| Python (the default) | re as Python 3.15 documents it, plus everything the regex package adds |
| Ecma262 | ECMA-262 §22.2, the grammar JSON Schema and OpenAPI patterns are written in |
| Lexmatch | the grammar the core library's @string.Regex and the lexer speak |
moon add moonbitstack/moonregex| Python | moonregex | where the match must be |
|---|---|---|
| re.search | search | anywhere, leftmost first |
| re.match | prefix | at the position the scan starts |
| re.fullmatch | full | the whole region |
| re.sub with a function | sub_with | — |
| re.subn with a function | subn_with | — |
let word = @moonregex.compile("[a-z]+")
assert_true(word.search(" hello ") is Some(_))
assert_true(word.prefix(" hello ") is None)
assert_true(word.full("hello") is Some(_))
assert_eq(word.search(" hello ").unwrap().start(0), 2)| Function | Returns |
|---|---|
| compile(pattern, flags~, dialect~, lists?, partial~) | Pattern |
| search / prefix / full | Match? |
| findall | Array[String?] |
| finditer | Array[Match] |
| split | Array[String?] |
| sub / subn / subf / subfn | String or (String, Int) |
| sub_with / subn_with | String or (String, Int) |
| escape(string, dialect~, special_only~, literal_spaces~) | String |
let ambiguous = try {
let _ = @moonregex.findall("(\\w+)=(\\d+)", "a=1")
0
} catch {
@moonregex.Refused::Ambiguous(groups~) => groups
e => raise e
}
assert_eq(ambiguous, 2)
assert_eq(@moonregex.findall("(\\w+)=(\\d+)", "a=1", group=Num(n=1)), [Some("a")])| Dialect | escape("a.b") | rule |
|---|---|---|
| Python | a\.b | re.escape: the characters that can mean something take a backslash |
| Ecma262 | \x61\.b | RegExp.escape: punctuation is spelled in hex, and a leading alphanumeric too |
| Lexmatch | a\.b | the @string.Regex metacharacters, with \u{…} for the invisible ones |
assert_eq(@moonregex.escape("a.b"), "a\\.b")
assert_eq(@moonregex.escape("a.b", dialect=@moonregex.ecma262), "\\x61\\.b")
assert_eq(@moonregex.escape("a.b", dialect=@moonregex.lexmatch_dialect), "a\\.b")// Python: named group, `\A`, backreference — the default.
assert_eq(@moonregex.findall("(?P<w>\\w+)\\s+\\1", "go go no"), [Some("go")])
// ECMA-262: what a JSON Schema `pattern` means.
let schema = @moonregex.compile("^[a-z]+$", dialect=@moonregex.ecma262)
assert_true(schema.full("abc") is Some(_))
assert_true(schema.full("ABC") is None)
// lexmatch: a pattern already written for the core library.
let posix = @moonregex.compile("[[:digit:]]+", dialect=@moonregex.lexmatch_dialect)
assert_eq(posix.findall("a12b3"), [Some("12"), Some("3")])let date = @moonregex.compile("\\d{4}").capture("year") +
@moonregex.compile("-") +
@moonregex.compile("\\d{2}").capture("month")
let stamp = date.prefix("2026-10").unwrap()
assert_eq(stamp.group("month"), Some("10"))
assert_true(date.lower() is Some(_))
// `lower` hands the pattern to the core engine when it fits.
assert_true(@moonregex.compile("(a)\\1").lower() is None)let runaway = try {
let _ = @moonregex.full("(a+)+b", "aaaaaaaaaaaaaaaaaaaa", limit=100)
"no error"
} catch {
@moonregex.Refused::Exceeded(limit~, got~) => "stopped at \{limit} of \{got} steps"
e => raise e
}
assert_eq(runaway, "stopped at 100 of 101 steps")let stream = @moonregex.compile("\\d{4}-\\d{2}", partial=true)
let cut = stream.prefix("2026-").unwrap()
assert_eq(cut.content(), "2026-")
assert_true(cut.partial())
assert_true(stream.prefix("2026-10").unwrap().partial() is false)| Not implemented | Why |
|---|---|
| re.LOCALE | MoonBit has no locale; re itself only offers it for bytes and advises against it |
| re.DEBUG | the library does not print; Pattern::dump returns the tree as text instead |
| re.purge | there is no global pattern cache to clear |
| ECMA's g, y, d | they are ways of calling, not properties of a pattern: g is findall/finditer/count=0, y is prefix(pos=), d is what span has always answered |
| Match.detach_string, Match.regs | strings are immutable here, and spans covers regs |
| Case | Here | The reference |
|---|---|---|
| match | not offered; prefix is the same thing | re.match, soft-deprecated in 3.15 |
| Runaway patterns | Exceeded after ten million steps | no limit |
| \N without a name | any character but a newline | re refuses it; regex reads a literal N under V0 |
| \v, \V | the vertical tab under V0, the vertical-space class under V1 | re always means the tab |
| \e, \Q…\E, \K | accepted | re refuses them |
| (?<name>…) | accepted in the Python dialect too | re wants (?P<name>…) |
| [[:alpha:]] | a class escape in every dialect | re reads it as a set of characters |
| Refusal offsets | where reading stopped | Python's convention differs per error |
| A group repeated over an empty tail | keeps the captures of the empty turn ((a*)* leaves group 1 at the end) | ECMA-262 clears them |
| Astral characters | offsets count code points | ECMA-262 counts UTF-16 code units |
| Property names | Unicode loose matching in every dialect | ECMA-262 is case- and space-sensitive |
| A name used twice | allowed in the ECMA dialect anywhere | re refuses it; ECMA-262 only across alternatives |
| v-mode nested classes | [[a-z]--[aeiou]] is not read as a class expression | ECMA-262 reads them |
| Repeated captures | one span per group: captures and spans answer the last turn | regex lists every turn |
pub(all) enum Group {
Whole
Num(n~ : Int)
Name(n~ : String)
}pub(all) struct Match {
// private fields
}pub(all) struct Pattern {
// private fields
}pub struct Scanner {
// private fields
}fn escape(pattern : StringView, dialect? : Dialect, special_only? : Bool, literal_spaces? : Bool) -> Stringlet version_info : (Int, Int, Int)Install
Download zipRegular expressions for MoonBit.