use std::cmp::Ordering;
use super::{
trace::{observe, shows},
value::{Error, R, Val, eq, strict_eq, utf16_cmp},
};
use crate::ast::BinaryOp;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Comparator {
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
ListContains,
}
impl Comparator {
pub fn name(self) -> &'static str {
match self {
Self::Eq => "EQ",
Self::Neq => "NEQ",
Self::Lt => "LT",
Self::Lte => "LTE",
Self::Gt => "GT",
Self::Gte => "GTE",
Self::ListContains => "LIST_CONTAINS",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Simple {
pub comparator: Comparator,
pub value: Val,
}
impl Simple {
pub fn same(&self, other: &Self) -> bool {
self.comparator == other.comparator && shows(&observe(&self.value), &other.value)
}
}
pub(crate) fn binary(op: BinaryOp, l: &Val, r: &Val) -> Option<R> {
let comparison = matches!(
op,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge
);
Some(match (l, r) {
(Val::Constraint(_), Val::Constraint(_)) if comparison || op == BinaryOp::In => {
Err(Error::type_error(
"constraint",
"bool,duration,float,int,latlng,map,null,string,bytes,timestamp,path,list,\
vector,set",
))
}
(Val::Constraint(c), v) if comparison => implies(c, op, v),
(v, Val::Constraint(c)) if comparison => implies(c, flip(op), v),
(v, Val::Constraint(c)) if op == BinaryOp::In => {
let contains = c
.iter()
.any(|s| s.comparator == Comparator::ListContains && strict_eq(&s.value, v));
if contains {
Ok(Val::Bool(true))
} else {
Err(Error::undecided())
}
}
_ => return None,
})
}
pub(crate) fn has_any(c: &[Simple], items: &[Val]) -> bool {
c.iter().any(|s| {
s.comparator == Comparator::ListContains && items.iter().any(|i| strict_eq(&s.value, i))
})
}
fn flip(op: BinaryOp) -> BinaryOp {
match op {
BinaryOp::Lt => BinaryOp::Gt,
BinaryOp::Le => BinaryOp::Ge,
BinaryOp::Gt => BinaryOp::Lt,
BinaryOp::Ge => BinaryOp::Le,
other => other,
}
}
fn implies(c: &[Simple], op: BinaryOp, v: &Val) -> R {
let ordered = matches!(
v,
Val::Timestamp(..)
| Val::Duration(_)
| Val::Float(_)
| Val::Int(_)
| Val::Str(_)
| Val::Bytes(_)
);
if matches!(
op,
BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge
) && !ordered
{
return Err(Error::type_error(
v.type_name(),
"timestamp,duration,float,int,string,bytes",
));
}
let implied =
(op == BinaryOp::Ne && matches!(v, Val::Null)) || c.iter().any(|s| s.implies(op, v));
if implied {
Ok(Val::Bool(true))
} else {
Err(Error::undecided())
}
}
fn is_number(v: &Val) -> bool {
matches!(v, Val::Int(_) | Val::Float(_))
}
fn is_nan(v: &Val) -> bool {
matches!(v, Val::Float(f) if f.is_nan())
}
impl Simple {
fn implies(&self, op: BinaryOp, v: &Val) -> bool {
use BinaryOp::{Eq, Ge, Gt, Le, Lt, Ne};
use Ordering::{Equal, Greater, Less};
if self.comparator == Comparator::ListContains {
return false;
}
if is_number(&self.value) && is_number(v) {
if is_nan(&self.value) {
return self.comparator != Comparator::Neq || (op == Ne && is_nan(v));
}
if is_nan(v) {
return op == Ne;
}
if let (Val::Float(_), Val::Int(_)) = (&self.value, v) {
return self.comparator == Comparator::Eq && op == Ne;
}
}
let Some(order) = relation(&self.value, v) else {
return false;
};
match (self.comparator, order) {
(Comparator::Eq, Equal) => matches!(op, Eq | Le | Ge),
(Comparator::Eq, Less) => matches!(op, Ne | Lt | Le),
(Comparator::Eq, Greater) => matches!(op, Ne | Gt | Ge),
(Comparator::Neq, Equal) => op == Ne,
(Comparator::Gt, Equal | Greater) | (Comparator::Gte, Greater) => {
matches!(op, Gt | Ge | Ne)
}
(Comparator::Gte, Equal) => op == Ge,
(Comparator::Lt, Equal | Less) | (Comparator::Lte, Less) => matches!(op, Lt | Le | Ne),
(Comparator::Lte, Equal) => op == Le,
_ => false,
}
}
}
fn relation(filter: &Val, rule: &Val) -> Option<Ordering> {
match (filter, rule) {
(Val::Int(a), Val::Int(b)) => Some(a.cmp(b)),
(Val::Float(a), Val::Float(b)) => a.partial_cmp(b),
(Val::Int(a), Val::Float(b)) => (*a as f64).partial_cmp(b),
(Val::Float(_), Val::Int(_)) => None,
(Val::Bool(a), Val::Bool(b)) => Some(a.cmp(b)),
(Val::Str(a), Val::Str(b)) => Some(utf16_cmp(a, b)),
(Val::Bytes(a), Val::Bytes(b)) => Some(a.cmp(b)),
(Val::Timestamp(s, n), Val::Timestamp(t, m)) => Some((s, n).cmp(&(t, m))),
(a, b) if eq(a, b) => Some(Ordering::Equal),
_ => None,
}
}