///|
priv enum PatternAtom {
Literal(Int32)
Any
Star
Class(Bool, Array[(Int32, Int32)])
Choices(Array[String])
}
///|
pub struct Pattern {
priv atoms : Array[PatternAtom]
priv source_ : String
}
///|
pub fn Pattern::source(self : Pattern) -> String {
self.source_
}
///|
fn parse_class(
text : String,
cursor : Ref[Int32],
) -> PatternAtom raise OscError {
let start = cursor.val
cursor.val += 1
let negated = cursor.val < text.length() && text[cursor.val] == 33
if negated {
cursor.val += 1
}
let ranges : Array[(Int32, Int32)] = []
while cursor.val < text.length() && text[cursor.val] != 93 {
let lo = text[cursor.val].to_int()
cursor.val += 1
if lo == 47 || lo == 91 {
raise Invalid("illegal class character", cursor.val - 1)
}
let mut hi = lo
if cursor.val + 1 < text.length() &&
text[cursor.val] == 45 &&
text[cursor.val + 1] != 93 {
hi = text[cursor.val + 1].to_int()
cursor.val += 2
if hi < lo || hi == 47 {
raise Invalid("invalid class range", cursor.val - 2)
}
}
ranges.push((lo, hi))
}
if cursor.val >= text.length() || ranges.is_empty() {
raise Invalid("unclosed or empty class", start)
}
cursor.val += 1
Class(negated, ranges)
}
///|
fn parse_choices(
text : String,
cursor : Ref[Int32],
) -> PatternAtom raise OscError {
let start = cursor.val
cursor.val += 1
let choices : Array[String] = []
let word : Array[Char] = []
while cursor.val < text.length() {
let c = text[cursor.val].to_int()
cursor.val += 1
if c == 44 || c == 125 {
if word.is_empty() {
raise Invalid("empty brace alternative", cursor.val - 1)
}
choices.push(String::from_array(word[:]))
word.clear()
if c == 125 {
return Choices(choices)
}
} else {
match c {
47 | 42 | 63 | 91 | 93 | 123 =>
raise Invalid(
"brace alternatives must be literal segment strings",
cursor.val - 1,
)
_ => word.push(c.to_char().unwrap())
}
}
}
raise Invalid("unclosed brace alternatives", start)
}
///|
pub fn compile_pattern(text : String) -> Pattern raise OscError {
validate_address(text)
if text.length() > 4096 {
raise Limit("address pattern length")
}
let atoms : Array[PatternAtom] = []
let cursor = Ref(0)
while cursor.val < text.length() {
let c = text[cursor.val].to_int()
match c {
91 => atoms.push(parse_class(text, cursor))
123 => atoms.push(parse_choices(text, cursor))
93 | 125 | 44 => raise Invalid("unmatched pattern delimiter", cursor.val)
_ => {
cursor.val += 1
if c == 42 {
// Consecutive stars have the same language and need only one state.
match atoms.last() {
Some(Star) => ()
_ => atoms.push(Star)
}
} else if c == 63 {
atoms.push(Any)
} else {
atoms.push(Literal(c))
}
}
}
}
{ atoms, source_: text, }
}