///|
fn numeric_digits(text : String) -> String? {
let chars = text.to_array()
let mut end = 0
while end < chars.length() && chars[end].is_ascii_digit() {
end += 1
}
if end == 0 {
return None
}
let mut start = 0
while start + 1 < end && chars[start] == '0' {
start += 1
}
Some(chars_text(chars, start, end))
}
///|
fn ordered_compare(
left : String,
right : String,
comparator : String,
budget : Budget,
span : Span,
) -> Int raise SieveError {
budget.compare(left.length() + right.length() + 1, span)
if comparator == "i;ascii-numeric" {
match (numeric_digits(left), numeric_digits(right)) {
(None, None) => 0
(None, Some(_)) => 1
(Some(_), None) => -1
(Some(a), Some(b)) =>
if a.length() < b.length() {
-1
} else if a.length() > b.length() {
1
} else {
a.lexical_compare(b)
}
}
} else if comparator == "i;ascii-casemap" {
ascii_lower(left).lexical_compare(ascii_lower(right))
} else {
left.lexical_compare(right)
}
}
///|
fn relation_holds(order : Int, relation : String) -> Bool {
match relation {
"gt" => order > 0
"ge" => order >= 0
"lt" => order < 0
"le" => order <= 0
"eq" => order == 0
"ne" => order != 0
_ => false
}
}
///|
fn check_relational(
a : Arguments,
caps : Array[String],
span : Span,
) -> Unit raise SieveError {
let relation = match a.options.get("value") {
Some(v) => Some(v)
None => a.options.get("count")
}
match relation {
Some(op) => {
needs(caps, "relational", span)
if !["gt", "ge", "lt", "le", "eq", "ne"].contains(op) {
fail("check.relation", "invalid relational operator", span)
}
}
None => ()
}
if a.options.get("comparator") == Some("i;ascii-numeric") {
needs(caps, "comparator-i;ascii-numeric", span)
if a.flags.contains("contains") || a.flags.contains("matches") {
fail(
"check.numeric_match", "numeric comparator does not support contains/matches",
span,
)
}
}
}
///|
fn Runtime::match_collection(
self : Runtime,
values : Array[String],
keys : Array[String],
a : Arguments,
span : Span,
) -> Bool raise SieveError {
let comparator = a.options.get("comparator").unwrap_or("i;ascii-casemap")
let relation = match a.options.get("count") {
Some(v) => Some(v)
None => a.options.get("value")
}
let candidates = if a.flags.contains("count") {
[values.length().to_string()]
} else {
values
}
for value in candidates {
for key in keys {
self.budget.step(span)
match relation {
Some(op) =>
if relation_holds(
ordered_compare(value, key, comparator, self.budget, span),
op,
) {
return true
}
None =>
if comparator == "i;ascii-numeric" {
if ordered_compare(value, key, comparator, self.budget, span) == 0 {
return true
}
} else if self.matching(value, key, a, span) {
return true
}
}
}
}
false
}