A regular expression engine for MoonBit based on VM execution designed for predictable time complexity
⚠️ API STABILITY NOTICE This is a alpha release with a stabilizing API. While core functionality is complete and well-tested, API changes may occur in future versions as we refine the implementation.
///|
test {
// Compile once, use everywhere
let regexp = @regexp.compile("a(bc|de)f")
guard regexp.match_("xxabcf") is Some(result)
inspect(
result.results(),
content=(
#|[Some("abcf"), Some("bc")]
),
)
// Write a simple split with regexp
fn split(regexp : @regexp.Regexp, target : StringView) -> Array[StringView] {
let result = []
loop target {
"" => ()
str => {
let res = regexp.execute(str)
result.push(res.before())
continue res.after()
}
}
result
}
let re = @regexp.compile("_+")
inspect(
split(re, "1_2__3__4__5_____6"),
content=(
#|["1", "2", "3", "4", "5", "6"]
),
)
}| Feature | Example | What it does |
|---|---|---|
| Literals | abc | Match exact text |
| Wildcards | a.c | . matches any character |
| Quantifiers | a+, b*, c? | One or more, zero or more, optional |
| Ranges | a{2,5} | Between 2-5 repetitions |
| Classes | [a-z], [^0-9] | Character sets, negated sets |
| Groups | (abc), (?:xyz) | Capturing, non-capturing |
| Named | (?<word>abc) | Named capture groups |
| Choice | cat\|dog | Match either option |
| Anchors | ^start, end$ | Line boundaries |
| Escapes | \\u{41}, \\u0041 | Unicode escapes, standard escapes |
| Unicode Props | \\p{L}, \\p{Nd} | Unicode general categories |
| Backrefs ⚠️ | (.)\\1 | Reference previous captures |
///|
test "unicode properties" {
// Matching gc=L
let regex = @regexp.compile("\\p{Letter}+")
inspect(
regex.execute("Hello 世界").results(),
content=(
#|[Some("Hello")]
),
)
// Matching gc=N
let regex = @regexp.compile("\\p{Number}+")
inspect(
regex.execute("123 and 456").results(),
content=(
#|[Some("123")]
),
)
}⚠️ Performance Warning: Backreferences can cause exponential time complexity in worst cases!
///|
test "backreferences" {
// Palindrome detection (simple)
let palindrome = @regexp.compile("^(.)(.)\\2\\1")
inspect(
palindrome.execute("abba").results(),
content=(
#|[Some("abba"), Some("a"), Some("b")]
),
)
// HTML tag matching
let html_regex = @regexp.compile("<([a-zA-Z]+)[^>]*>(.*?)</\\1>")
let result = html_regex.execute("<div class='test'>content</div>")
inspect(
result.results(),
content=(
#|[Some("<div class='test'>content</div>"), Some("div"), Some("content")]
),
)
}///|
test "character classes" {
// Email validation (simplified)
let email = @regexp.compile(
(
#|[\w-]+@[\w-]+\.\w+
),
)
let email_result = email.execute("user@example.com").results()
inspect(
email_result,
content=(
#|[Some("user@example.com")]
),
)
// Extract numbers
let numbers = @regexp.compile(
(
#|\d+\.\d{2}
),
)
let result = numbers.execute("Price: $42.99").results()
inspect(
result,
content=(
#|[Some("42.99")]
),
)
// Named captures for parsing
let parser = @regexp.compile(
(
#|(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
),
)
let date_result = parser.execute("2024-03-15")
inspect(
date_result.groups(),
content=(
#|{"year": "2024", "month": "03", "day": "15"}
),
)
}///|
test {
try {
let _ = @regexp.compile("a(b")
// Oops! Missing )
} catch {
RegexpError(err=MissingParenthesis, source_fragment=_) =>
println("Fix your regex! 🔧")
_ => ()
}
}impl Show for RegexpErrorpub enum Err {
InternalError
InvalidCharClass
InvalidEscape
InvalidNamedCapture
InvalidRepeatOp
InvalidRepeatSize
MissingBracket
MissingParenthesis
MissingRepeatArgument
TrailingBackslash
UnexpectedParenthesis
}type MatchResulttest {
let engine = compile("hello")
let result = engine.execute("hello world")
inspect(result.after(), content=" world")
let result = engine.execute("say yes")
inspect(result.after(), content="")
}test {
let engine = compile("world")
let result = engine.execute("hello world")
inspect(result.before(), content="hello ")
let result = engine.execute("say hello")
inspect(result.before(), content="say hello")
}test {
let result = compile(".(bc)").execute("abc")
inspect(
result.get(0),
content=(
#|Some("abc")
),
)
let first_group = result.get(1)
inspect(
first_group,
content=(
#|Some("bc")
),
)
}test {
let engine = compile("(?<word>\\w+)\\s+(?<num>\\d+)")
let result = engine.execute("hello 123")
inspect(
result.groups(),
content=(
#|{"word": "hello", "num": "123"}
),
)
}test {
let result = compile("text").execute("text")
inspect(result.matched(), content="true")
}test {
let result = compile("(\\w)+").execute("abc")
inspect(
result.results(),
content=(
#|[Some("abc"), Some("c")]
),
)
}type Regexptest {
let engine = compile("a(bc|de)f")
let result = engine.execute("xxabcf")
if result.matched() {
inspect(
result.get(0),
content=(
#|Some("abcf")
),
)
}
}#deprecated("Use `Regexp::execute` and `MatchResult::before`/`after` instead.")
fn Regexp::execute_with_remainder(self : Regexp, input : StringView) -> (MatchResult, StringView)test {
let engine = compile("a(bc|de)f")
let result = engine.execute("abcfxx")
guard result.matched()
inspect(result.before(), content="")
inspect(result.after(), content="xx")
let result = engine.execute("axxf")
guard !result.matched()
inspect(result.before(), content="axxf")
inspect(result.after(), content="")
}test {
let engine = compile("(?<word>\\w+)")
inspect(engine.group_by_name("word"), content="Some(1)")
}test {
let engine = compile("(a)(b|c)")
inspect(engine.group_count(), content="3")
}test {
let engine = compile("(?<first>a)(?<second>b)")
inspect(
engine.group_names(),
content=(
#|["first", "second"]
),
)
}test {
let engine = compile("a(bc|de)f")
guard engine.match_("abcfxx") is Some(result)
inspect(result.after(), content="xx")
assert_true(engine.match_("axxf") is None)
}test {
let engine = compile("a(bc|de)f")
inspect(
engine.execute("xxabcf").results(),
content=(
#|[Some("abcf"), Some("bc")]
),
)
}A regular expression engine for MoonBit based on VM execution designed for predictable time complexity