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)
debug_inspect(
result.results(),
content=(
#|[Some(<StringView: "abcf">), Some(<StringView: "bc">)]
),
)
// Write a simple split with regexp
fn split(regexp : @regexp.Regexp, target : StringView) -> Array[StringView] {
let result = []
for target = target {
match target {
"" => break
str => {
let res = regexp.execute(str)
result.push(res.before())
continue res.after()
}
}
}
result
}
let re = @regexp.compile("_+")
debug_inspect(
split(re, "1_2__3__4__5_____6"),
content=(
#|[
#| <StringView: "1">,
#| <StringView: "2">,
#| <StringView: "3">,
#| <StringView: "4">,
#| <StringView: "5">,
#| <StringView: "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}+")
debug_inspect(
regex.execute("Hello 世界").results(),
content=(
#|[Some(<StringView: "Hello">)]
),
)
// Matching gc=N
let regex = @regexp.compile("\\p{Number}+")
debug_inspect(
regex.execute("123 and 456").results(),
content=(
#|[Some(<StringView: "123">)]
),
)
}⚠️ Performance Warning: Backreferences can cause exponential time complexity in worst cases!
///|
test "backreferences" {
// Palindrome detection (simple)
let palindrome = @regexp.compile("^(.)(.)\\2\\1")
debug_inspect(
palindrome.execute("abba").results(),
content=(
#|[
#| Some(<StringView: "abba">),
#| Some(<StringView: "a">),
#| Some(<StringView: "b">),
#|]
),
)
// HTML tag matching
let html_regex = @regexp.compile("<([a-zA-Z]+)[^>]*>(.*?)</\\1>")
let result = html_regex.execute("<div class='test'>content</div>")
debug_inspect(
result.results(),
content=(
#|[
#| Some(<StringView: "<div class='test'>content</div>">),
#| Some(<StringView: "div">),
#| Some(<StringView: "content">),
#|]
),
)
}///|
test "character classes" {
// Email validation (simplified)
let email = @regexp.compile(
(
#|[\w-]+@[\w-]+\.\w+
),
)
let email_result = email.execute("user@example.com").results()
debug_inspect(
email_result,
content=(
#|[Some(<StringView: "user@example.com">)]
),
)
// Extract numbers
let numbers = @regexp.compile(
(
#|\d+\.\d{2}
),
)
let result = numbers.execute("Price: $42.99").results()
debug_inspect(
result,
content=(
#|[Some(<StringView: "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")
debug_inspect(
date_result.groups(),
content=(
#|{
#| "year": <StringView: "2024">,
#| "month": <StringView: "03">,
#| "day": <StringView: "15">,
#|}
),
)
}///|
test {
try {
let _ = @regexp.compile("a(b")
// Oops! Missing )
} catch {
RegexpError(err=MissingParenthesis, source_fragment=_) =>
println("Fix your regex! 🔧")
_ => ()
}
}impl Show for RegexpError#deprecated("`RegexpError::to_repr` is deprecated, use `@moonbitlang/core/debug.Debug::to_repr` instead.")
fn RegexpError::to_repr(RegexpError) -> Reprpub enum Err {
InternalError
InvalidCharClass
InvalidEscape
InvalidNamedCapture
InvalidRepeatOp
InvalidRepeatSize
MissingBracket
MissingParenthesis
MissingRepeatArgument
TrailingBackslash
UnexpectedParenthesis
} derive(Debug)type MatchResulttest {
let engine = @regexp.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 = @regexp.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 = @regexp.compile(".(bc)").execute("abc")
debug_inspect(
result.get(0),
content=(
#|Some(<StringView: "abc">)
),
)
let first_group = result.get(1)
debug_inspect(
first_group,
content=(
#|Some(<StringView: "bc">)
),
)
}test {
let engine = @regexp.compile("(?<word>\\w+)\\s+(?<num>\\d+)")
let result = engine.execute("hello 123")
debug_inspect(
result.groups(),
content=(
#|{ "word": <StringView: "hello">, "num": <StringView: "123"> }
),
)
}test {
let result = @regexp.compile("text").execute("text")
inspect(result.matched(), content="true")
}test {
let result = @regexp.compile("(\\w)+").execute("abc")
debug_inspect(
result.results(),
content=(
#|[Some(<StringView: "abc">), Some(<StringView: "c">)]
),
)
}type Regexptest {
let engine = @regexp.compile("a(bc|de)f")
let result = engine.execute("xxabcf")
if result.matched() {
debug_inspect(
result.get(0),
content=(
#|Some(<StringView: "abcf">)
),
)
}
}#deprecated("Use `Regexp::execute` and `MatchResult::before`/`after` instead.")
fn Regexp::execute_with_remainder(self : Regexp, input : StringView) -> (MatchResult, StringView)test {
let engine = @regexp.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 = @regexp.compile("(?<word>\\w+)")
debug_inspect(engine.group_by_name("word"), content="Some(1)")
}test {
let engine = @regexp.compile("(a)(b|c)")
inspect(engine.group_count(), content="3")
}test {
let engine = @regexp.compile("(?<first>a)(?<second>b)")
debug_inspect(
engine.group_names(),
content=(
#|["first", "second"]
),
)
}test {
let engine = @regexp.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 = @regexp.compile("a(bc|de)f")
debug_inspect(
engine.execute("xxabcf").results(),
content=(
#|[Some(<StringView: "abcf">), Some(<StringView: "bc">)]
),
)
}Install
Download zipA regular expression engine for MoonBit based on VM execution designed for predictable time complexity