use core::cmp::Ordering;
use std::sync::Arc;
use yo_common::re::{Matcher, Regex};
use yo_common::{Code, Error, Result};
use crate::head::Kind;
use crate::query::{Sel, select_from};
use crate::read::Value;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Expr<'a> {
Or(Box<Expr<'a>>, Box<Expr<'a>>),
And(Box<Expr<'a>>, Box<Expr<'a>>),
Not(Box<Expr<'a>>),
Test(Operand<'a>),
Cmp(Operand<'a>, Op, Operand<'a>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Operand<'a> {
Path {
at: bool,
sels: Vec<Sel<'a>>,
},
Lit(Vec<u8>),
Re(Pattern),
Keys(Box<Operand<'a>>),
Call(Box<Operand<'a>>, Fun),
Math(Box<Operand<'a>>, Arith, Box<Operand<'a>>),
Sign(Box<Operand<'a>>, bool),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Fun {
Length,
Count,
Min,
Max,
Sum,
Avg,
Unknown,
}
impl Fun {
pub(crate) fn named(name: &[u8]) -> Fun {
match name {
b"length" => Fun::Length,
b"count" => Fun::Count,
b"min" => Fun::Min,
b"max" => Fun::Max,
b"sum" => Fun::Sum,
b"avg" => Fun::Avg,
_ => Fun::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Arith {
Add,
Sub,
Mul,
Div,
Rem,
}
#[derive(Debug, Clone)]
pub(crate) struct Pattern {
text: Vec<u8>,
re: Arc<Regex>,
}
impl PartialEq for Pattern {
fn eq(&self, other: &Pattern) -> bool {
self.text == other.text
}
}
impl Eq for Pattern {}
impl Pattern {
pub(crate) fn new(text: &[u8]) -> Result<Pattern> {
let re = Regex::new(text, false).map_err(|e| {
Error::new(
Code::Invalid,
format!("a filter pattern that is not a pattern: {e}"),
)
})?;
Ok(Pattern {
text: text.to_vec(),
re: Arc::new(re),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Op {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Re,
In,
Nin,
AnyOf,
NoneOf,
SubsetOf,
Size,
Empty,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Num {
Int(i64),
Float(f64),
}
impl Num {
fn as_f64(self) -> f64 {
#[expect(
clippy::cast_precision_loss,
reason = "a comparison and not a round trip"
)]
match self {
Num::Int(i) => i as f64,
Num::Float(f) => f,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Item<'v> {
Ref(Value<'v>),
Num(Num),
Key(&'v [u8]),
}
impl<'v> Item<'v> {
fn number(&self) -> Option<f64> {
match self {
Item::Ref(v) => number(v),
Item::Num(n) => Some(n.as_f64()),
Item::Key(_) => None,
}
}
fn num(&self) -> Option<Num> {
match self {
Item::Ref(v) => v
.as_int()
.map(Num::Int)
.or_else(|| v.as_float().map(Num::Float)),
Item::Num(n) => Some(*n),
Item::Key(_) => None,
}
}
fn text(&self) -> Option<&'v [u8]> {
match self {
Item::Ref(v) => v.text_bytes(),
Item::Num(_) => None,
Item::Key(k) => Some(k),
}
}
fn size(&self) -> Option<usize> {
match self {
Item::Ref(v) => match v.kind() {
Kind::Text => Some(v.text_bytes().unwrap_or_default().len()),
Kind::Array | Kind::Object => Some(v.len()),
_ => None,
},
Item::Num(_) | Item::Key(_) => None,
}
}
fn elements(&self) -> impl Iterator<Item = Item<'v>> {
let v = match self {
Item::Ref(v) if v.kind() == Kind::Array => Some(*v),
_ => None,
};
v.into_iter().flat_map(|v| v.iter().map(Item::Ref))
}
}
impl Expr<'_> {
pub(crate) fn holds<'v>(&'v self, root: &Value<'v>, cur: &Value<'v>) -> bool {
match self {
Expr::Or(l, r) => l.holds(root, cur) || r.holds(root, cur),
Expr::And(l, r) => l.holds(root, cur) && r.holds(root, cur),
Expr::Not(e) => !e.holds(root, cur),
Expr::Test(o) => match o {
Operand::Lit(_) => values(o, root, cur)
.first()
.is_none_or(|v| v.as_bool() != Some(false)),
_ => !values(o, root, cur).is_empty(),
},
Expr::Cmp(l, op, r) => cmp(l, *op, r, root, cur),
}
}
}
impl Item<'_> {
fn as_bool(&self) -> Option<bool> {
match self {
Item::Ref(v) => v.as_bool(),
_ => None,
}
}
}
fn values<'v>(o: &'v Operand<'_>, root: &Value<'v>, cur: &Value<'v>) -> Vec<Item<'v>> {
let mut out = Vec::new();
match o {
Operand::Path { at, sels } => {
let mut hits = Vec::new();
select_from(sels, if *at { cur } else { root }, root, &mut hits);
out.extend(hits.into_iter().map(Item::Ref));
}
Operand::Lit(bytes) => out.extend(Value::new(bytes).map(Item::Ref)),
Operand::Re(_) => {}
Operand::Keys(inner) => out.extend(keyset(inner, root, cur).unwrap_or_default()),
Operand::Call(inner, fun) => {
let got = values(inner, root, cur);
call(&got, *fun, &mut out);
}
Operand::Math(l, op, r) => {
if let (Some(a), Some(b)) = (only(l, root, cur), only(r, root, cur))
&& let Some(n) = arith(a, *op, b)
{
out.push(Item::Num(n));
}
}
Operand::Sign(inner, neg) => {
if let Some(n) = only(inner, root, cur) {
out.push(Item::Num(if *neg { negate(n) } else { n }));
}
}
}
out
}
fn only<'v>(o: &'v Operand<'_>, root: &Value<'v>, cur: &Value<'v>) -> Option<Num> {
match values(o, root, cur).as_slice() {
[one] => one.num(),
_ => None,
}
}
fn arith(a: Num, op: Arith, b: Num) -> Option<Num> {
if matches!(op, Arith::Div | Arith::Rem) && b.as_f64() == 0.0 {
return None;
}
if op == Arith::Div {
return Some(Num::Float(a.as_f64() / b.as_f64()));
}
if let (Num::Int(x), Num::Int(y)) = (a, b) {
let whole = match op {
Arith::Add => x.checked_add(y),
Arith::Sub => x.checked_sub(y),
Arith::Mul => x.checked_mul(y),
Arith::Rem => x.checked_rem(y),
Arith::Div => unreachable!("answered above"),
};
if let Some(n) = whole {
return Some(Num::Int(n));
}
}
let (x, y) = (a.as_f64(), b.as_f64());
Some(Num::Float(match op {
Arith::Add => x + y,
Arith::Sub => x - y,
Arith::Mul => x * y,
Arith::Rem => x % y,
Arith::Div => unreachable!("answered above"),
}))
}
fn negate(n: Num) -> Num {
match n {
Num::Int(i) => i
.checked_neg()
.map_or(Num::Float(-Num::Int(i).as_f64()), Num::Int),
Num::Float(f) => Num::Float(-f),
}
}
pub(crate) fn project<'v>(o: &'v Operand<'_>, root: &Value<'v>) -> Vec<Item<'v>> {
values(o, root, root)
}
fn keyset<'v>(of: &'v Operand<'_>, root: &Value<'v>, cur: &Value<'v>) -> Option<Vec<Item<'v>>> {
let mut out = None;
for it in values(of, root, cur) {
let Item::Ref(v) = it else { continue };
if v.kind() != Kind::Object {
continue;
}
let into: &mut Vec<Item<'v>> = out.get_or_insert_default();
into.extend((0..v.len()).filter_map(|i| v.key_at(i)).map(Item::Key));
}
out
}
fn call<'v>(got: &[Item<'v>], fun: Fun, out: &mut Vec<Item<'v>>) {
if fun == Fun::Count {
out.push(Item::Num(Num::Int(
i64::try_from(got.len()).unwrap_or(i64::MAX),
)));
return;
}
let [it] = got else {
return;
};
match fun {
Fun::Length => {
out.extend(
it.size()
.and_then(|n| i64::try_from(n).ok())
.map(|n| Item::Num(Num::Int(n))),
);
}
Fun::Min | Fun::Max | Fun::Sum | Fun::Avg => {
let ns: Option<Vec<f64>> = it.elements().map(|e| e.number()).collect();
let Some(ns) = ns.filter(|ns| !ns.is_empty()) else {
return;
};
#[expect(
clippy::cast_precision_loss,
reason = "a count that reaches the precision of a double is not a document"
)]
let n = match fun {
Fun::Min => ns.iter().copied().fold(f64::INFINITY, f64::min),
Fun::Max => ns.iter().copied().fold(f64::NEG_INFINITY, f64::max),
Fun::Sum => ns.iter().sum(),
_ => ns.iter().sum::<f64>() / ns.len() as f64,
};
out.push(Item::Num(Num::Float(n)));
}
Fun::Count | Fun::Unknown => {}
}
}
fn cmp<'v>(
l: &'v Operand<'_>,
op: Op,
r: &'v Operand<'_>,
root: &Value<'v>,
cur: &Value<'v>,
) -> bool {
let (left, right) = (Side::of(l, root, cur), Side::of(r, root, cur));
if op == Op::Re {
let Operand::Re(pat) = r else {
return false;
};
if left.keys {
return false;
}
let mut m = Matcher::new();
m.reserve(&pat.re);
return left
.items
.iter()
.filter_map(Item::text)
.any(|s| m.is_match(&pat.re, s));
}
match op {
Op::Ne => {
return !left
.items
.iter()
.any(|a| right.items.iter().any(|b| same(a, b)));
}
Op::Nin => return !within(&left, &right),
Op::NoneOf => return !shares(&left, &right),
_ => {}
}
match op {
Op::In => return within(&left, &right),
Op::AnyOf => return shares(&left, &right),
Op::SubsetOf => {
if !left.live || !right.live {
return false;
}
let inside = |a: &Item<'v>| right.bag().any(|x| same(a, &x));
if left.keys {
return left.items.iter().all(inside);
}
return left.items.iter().any(|a| {
matches!(a, Item::Ref(v) if v.kind() == Kind::Array)
&& a.elements().all(|e| inside(&e))
});
}
Op::Size => {
let Some(want) = right.items.iter().find_map(|b| b.number()) else {
return false;
};
#[expect(
clippy::cast_precision_loss,
reason = "a length that reaches the precision of a double is not a document"
)]
return if left.keys {
left.live && left.items.len() as f64 == want
} else {
left.items
.iter()
.any(|a| a.size().is_some_and(|n| n as f64 == want))
};
}
Op::Empty => {
let Some(want) = right.items.iter().find_map(|b| b.as_bool()) else {
return false;
};
return if left.keys {
left.live && left.items.is_empty() == want
} else {
left.items
.iter()
.any(|a| a.size().is_some_and(|n| (n == 0) == want))
};
}
_ => {}
}
left.items.iter().any(|a| {
right.items.iter().any(|b| match op {
Op::Eq => same(a, b),
Op::Lt => order(a, b) == Some(Ordering::Less),
Op::Le => matches!(order(a, b), Some(Ordering::Less | Ordering::Equal)),
Op::Gt => order(a, b) == Some(Ordering::Greater),
Op::Ge => matches!(order(a, b), Some(Ordering::Greater | Ordering::Equal)),
Op::Ne
| Op::Re
| Op::In
| Op::Nin
| Op::AnyOf
| Op::NoneOf
| Op::SubsetOf
| Op::Size
| Op::Empty => unreachable!("all of these are answered above"),
})
})
}
struct Side<'v> {
items: Vec<Item<'v>>,
keys: bool,
live: bool,
}
impl<'v> Side<'v> {
fn of(o: &'v Operand<'_>, root: &Value<'v>, cur: &Value<'v>) -> Side<'v> {
if let Operand::Keys(inner) = o {
let got = keyset(inner, root, cur);
return Side {
live: got.is_some(),
items: got.unwrap_or_default(),
keys: true,
};
}
Side {
items: values(o, root, cur),
keys: false,
live: true,
}
}
fn bag(&self) -> impl Iterator<Item = Item<'v>> {
let (mine, elems) = if self.keys {
(Some(self.items.iter().copied()), None)
} else {
(None, Some(self.items.iter().flat_map(Item::elements)))
};
mine.into_iter()
.flatten()
.chain(elems.into_iter().flatten())
}
}
fn within(left: &Side<'_>, right: &Side<'_>) -> bool {
!left.keys && right.live && left.items.iter().any(|a| right.bag().any(|e| same(a, &e)))
}
fn shares(left: &Side<'_>, right: &Side<'_>) -> bool {
left.live && right.live && left.bag().any(|e| right.bag().any(|x| same(&e, &x)))
}
fn order(a: &Item<'_>, b: &Item<'_>) -> Option<Ordering> {
if let (Item::Ref(x), Item::Ref(y)) = (a, b) {
return order_value(x, y);
}
if let (Some(x), Some(y)) = (a.number(), b.number()) {
return x.partial_cmp(&y);
}
match (a.text(), b.text()) {
(Some(x), Some(y)) => Some(x.cmp(y)),
_ => None,
}
}
fn order_value(a: &Value<'_>, b: &Value<'_>) -> Option<Ordering> {
if let (Some(x), Some(y)) = (number(a), number(b)) {
return x.partial_cmp(&y);
}
if a.kind() != b.kind() {
return None;
}
match a.kind() {
Kind::Null => Some(Ordering::Equal),
Kind::Bool => Some(a.as_bool().cmp(&b.as_bool())),
Kind::Text => Some(a.text_bytes().cmp(&b.text_bytes())),
Kind::Int | Kind::Float | Kind::Array | Kind::Object => None,
}
}
fn number(v: &Value<'_>) -> Option<f64> {
#[expect(
clippy::cast_precision_loss,
reason = "a comparison and not a round trip"
)]
v.as_int().map(|i| i as f64).or_else(|| v.as_float())
}
fn same(a: &Item<'_>, b: &Item<'_>) -> bool {
if let (Item::Ref(x), Item::Ref(y)) = (a, b) {
return same_value(x, y);
}
if let (Some(x), Some(y)) = (a.number(), b.number()) {
return x == y;
}
match (a.text(), b.text()) {
(Some(x), Some(y)) => x == y,
_ => false,
}
}
fn same_value(a: &Value<'_>, b: &Value<'_>) -> bool {
if let (Some(x), Some(y)) = (number(a), number(b)) {
return x == y;
}
if a.kind() != b.kind() {
return false;
}
match a.kind() {
Kind::Null => true,
Kind::Bool => a.as_bool() == b.as_bool(),
Kind::Text => a.text_bytes() == b.text_bytes(),
Kind::Array => {
a.len() == b.len()
&& (0..a.len()).all(|i| match (a.at(i), b.at(i)) {
(Some(x), Some(y)) => same_value(&x, &y),
_ => false,
})
}
Kind::Object => object(a, b),
Kind::Int | Kind::Float => false,
}
}
fn object(a: &Value<'_>, b: &Value<'_>) -> bool {
if a.len() != b.len() {
return false;
}
let mut seen = 0;
for (key, x) in a.members() {
let Some(y) = b.get(key) else {
return false;
};
if !same_value(&x, &y) {
return false;
}
seen += 1;
}
seen == a.len()
}