///|
/// Counting and quantifier helpers for array facts.
///
/// count(xs, path-free predicate placeholder) is deliberately NOT
/// expressible in a path-based language, so the engine provides
/// structured equivalents instead: count_matches scores an array
/// against threshold operators applied to its numeric elements, and
/// any_match / all_match reduce boolean arrays. These are fold-shaped
/// builtins: bounded by array length, charged fuel per element, and
/// type-checked statically as (array, number) -> number and
/// (array) -> boolean respectively.
///|
fn count_matches(
items : Array[Value],
threshold : Double,
op : String,
) -> Value raise {
let operators = ["<", "<=", ">", ">=", "==", "!="]
if !operators.contains(op) {
raise input_error("E4005", "unknown count_matches operator: " + op)
}
let mut hits = 0
for item in items {
let value = num(item)
let hit = match op {
"<" => value < threshold
"<=" => value <= threshold
">" => value > threshold
">=" => value >= threshold
"==" => value == threshold
"!=" => value != threshold
_ => false
}
if hit {
hits += 1
}
}
Number(hits.to_double())
}
///|
fn any_match(items : Array[Value]) -> Value raise {
let mut found = false
for item in items {
if boolean(item) {
found = true
}
}
Bool(found)
}
///|
fn all_match(items : Array[Value]) -> Value raise {
let mut all = true
for item in items {
if !boolean(item) {
all = false
}
}
Bool(all)
}