// Regex AST with Brzozowski derivatives and smart constructors
// (Owens-Reppy-Turon style simplification keeps the derivative state space finite).
///|
pub enum Regex {
REmpty
REps
RChar(Char)
RRange(Char, Char)
RUnion(Array[Regex])
RConcat(Regex, Regex)
RStar(Regex)
} derive(Eq, Hash, @debug.Debug)
///|
pub extend Regex with Eq::{ equal, not_equal }
///|
pub extend Regex with Hash::{ hash, hash_combine }
///|
pub extend Regex with @debug.Debug::{ to_repr }
///|
/// Smart union: flatten nested unions, drop empty branches, dedup structurally.
fn mk_union(rs : Array[Regex]) -> Regex {
let out : Array[Regex] = []
fn go(r : Regex) -> Unit {
match r {
RUnion(inner) => for x in inner {
go(x)
}
REmpty => ()
other => {
// linear dedup: skip structurally equal duplicates (r | r = r)
let mut dup = false
for x in out {
if x == other {
dup = true
break
}
}
if !dup {
out.push(other)
}
}
}
}
for r in rs {
go(r)
}
match out.length() {
0 => REmpty
1 => out[0]
_ => RUnion(out)
}
}
///|
/// Smart concat: empty absorbs, eps is identity.
fn mk_concat(r1 : Regex, r2 : Regex) -> Regex {
match r1 {
REmpty => REmpty
REps => r2
_ =>
match r2 {
REmpty => REmpty
REps => r1
_ => RConcat(r1, r2)
}
}
}
///|
/// Smart star: empty* = eps, eps* = eps, (r*)* = r*.
fn mk_star(r : Regex) -> Regex {
match r {
REmpty => REps
REps => REps
RStar(inner) => mk_star(inner)
other => RStar(other)
}
}
///|
/// Does the language contain the empty word?
fn nullable(r : Regex) -> Bool {
match r {
REmpty => false
REps => true
RChar(_) => false
RRange(_, _) => false
RUnion(rs) => {
let mut any = false
for x in rs {
if nullable(x) {
any = true
break
}
}
any
}
RConcat(r1, r2) => nullable(r1) && nullable(r2)
RStar(_) => true
}
}
///|
/// Brzozowski derivative of r with respect to character c.
fn derivative(c : Char, r : Regex) -> Regex {
match r {
REmpty => REmpty
REps => REmpty
RChar(a) => if a == c { REps } else { REmpty }
RRange(lo, hi) => if lo <= c && c <= hi { REps } else { REmpty }
RUnion(rs) => mk_union(rs.map(fn(x) { derivative(c, x) }))
RConcat(r1, r2) => {
let left = mk_concat(derivative(c, r1), r2)
if nullable(r1) {
mk_union([left, derivative(c, r2)])
} else {
left
}
}
RStar(inner) => mk_concat(derivative(c, inner), RStar(inner))
}
}
///|
/// Canonical rendering: used for debugging and as memoization key.
fn render(r : Regex) -> String {
let buf = StringBuilder()
fn go(r : Regex) -> Unit {
match r {
REmpty => buf.write_string("0")
REps => buf.write_string("1")
RChar(c) => {
buf.write_string("'")
buf.write_char(c)
buf.write_string("'")
}
RRange(lo, hi) => {
buf.write_string("[")
buf.write_char(lo)
buf.write_string("-")
buf.write_char(hi)
buf.write_string("]")
}
RUnion(rs) => {
buf.write_string("(")
for i, x in rs {
if i > 0 {
buf.write_string("|")
}
go(x)
}
buf.write_string(")")
}
RConcat(r1, r2) => {
buf.write_string("(")
go(r1)
go(r2)
buf.write_string(")")
}
RStar(inner) => {
buf.write_string("(")
go(inner)
buf.write_string(")*")
}
}
}
go(r)
buf.to_string()
}
///|
/// Public combinators for building rule regexes.
///|
pub fn range(lo : Char, hi : Char) -> Regex {
RRange(lo, hi)
}
///|
/// Union of the single characters in `s`.
pub fn chars(s : String) -> Regex {
let rs : Array[Regex] = []
for c in s {
rs.push(RChar(c))
}
mk_union(rs)
}
///|
/// Concatenation of single characters: the exact literal string.
pub fn str(s : String) -> Regex {
let mut r : Regex = REps
for c in s {
r = mk_concat(r, RChar(c))
}
r
}
///|
/// One or more: r ยท r*.
pub fn plus(r : Regex) -> Regex {
mk_concat(r, mk_star(r))
}
///|
/// Concatenation of two regexes.
pub fn concat(r1 : Regex, r2 : Regex) -> Regex {
mk_concat(r1, r2)
}
///|
/// Union (alternation) of several regexes.
pub fn alt(rs : Array[Regex]) -> Regex {
mk_union(rs)
}