use super::*;
pub(super) fn retype_shaped_lit(op: &Operand, v: Value, scale: UnitScale) -> Value {
if !matches!(op, Operand::Lit(_)) {
return v;
}
let Value::Str(s) = &v else {
return v;
};
if let Some((secs, nanos, offset_min)) = crate::temporal::parse_iso(s) {
return Value::Instant {
secs,
nanos,
offset_min,
};
}
if let Some((secs, nanos)) =
crate::temporal::parse_span(s).or_else(|| crate::temporal::span_from_units(s, scale))
{
return Value::Duration { secs, nanos };
}
if let Some((value, base, wv, wu)) = crate::quantity::parse_unit_text_with(s, scale) {
return Value::Quantity {
value,
base,
written: Some((wv, wu)),
};
}
v
}
pub(super) fn durational_with(v: &Value, scale: UnitScale) -> Option<(i64, u32)> {
v.durational_reading().or_else(|| match v {
Value::Str(s) => crate::temporal::span_from_units(s, scale),
_ => None,
})
}
pub(super) fn set_probe(ys: &std::sync::Arc<Vec<Value>>, needle: &str) -> Option<bool> {
use std::cell::RefCell;
use std::collections::HashSet;
thread_local! {
static SET: RefCell<Option<(std::sync::Weak<Vec<Value>>, HashSet<String>)>> =
const { RefCell::new(None) };
}
if ys.len() < 64 {
return None;
}
SET.with(|cell| {
let mut slot = cell.borrow_mut();
let fresh = match slot.as_ref() {
Some((w, _)) => w.upgrade().is_none_or(|a| !std::sync::Arc::ptr_eq(&a, ys)),
None => true,
};
if fresh {
let mut set = HashSet::with_capacity(ys.len());
for y in ys.iter() {
match y {
Value::Str(s) => {
set.insert(s.clone());
}
_ => return None,
}
}
*slot = Some((std::sync::Arc::downgrade(ys), set));
}
slot.as_ref().map(|(_, set)| set.contains(needle))
})
}
pub(super) fn compare(a: &Value, op: CmpOp, b: &Value, scale: UnitScale) -> bool {
match (a, b) {
(Value::List(xs), Value::List(ys)) if matches!(op, CmpOp::Eq | CmpOp::Ne) => {
let same = xs.len() == ys.len()
&& xs.iter().zip(ys.iter()).all(|(x, y)| value_eq(x, y, scale));
return if op == CmpOp::Eq { same } else { !same };
}
(Value::List(xs), _) => {
return match op {
CmpOp::Ne => !xs.iter().any(|x| value_eq(x, b, scale)),
_ => xs.iter().any(|x| compare(x, op, b, scale)),
};
}
(_, Value::List(ys)) => {
if matches!(op, CmpOp::Eq | CmpOp::Ne)
&& let Value::Str(needle) = a
&& let Some(hit) = set_probe(ys, needle)
{
return if op == CmpOp::Eq { hit } else { !hit };
}
return match op {
CmpOp::Ne => !ys.iter().any(|y| value_eq(a, y, scale)),
_ => ys.iter().any(|y| compare(a, op, y, scale)),
};
}
_ => {}
}
match op {
CmpOp::Eq => value_eq(a, b, scale),
CmpOp::Ne => !value_eq(a, b, scale),
CmpOp::Lt => value_cmp(a, b, scale) == Some(Ordering::Less),
CmpOp::Le => matches!(
value_cmp(a, b, scale),
Some(Ordering::Less | Ordering::Equal)
),
CmpOp::Gt => value_cmp(a, b, scale) == Some(Ordering::Greater),
CmpOp::Ge => matches!(
value_cmp(a, b, scale),
Some(Ordering::Greater | Ordering::Equal)
),
CmpOp::Match => regex_test(a, b, true),
CmpOp::NotMatch => regex_test(a, b, false),
CmpOp::Contains => match (a, b) {
(Value::Null, _) | (_, Value::Null) => false,
_ => a.to_string().contains(&b.to_string()),
},
}
}
pub(super) fn pattern_test(segs: &[PatSeg], v: &Value) -> bool {
if matches!(v, Value::Null) {
return false;
}
glob_match(segs, &v.to_string())
}
pub(super) fn glob_match(segs: &[PatSeg], text: &str) -> bool {
match segs.split_first() {
None => text.is_empty(),
Some((PatSeg::Lit(l), rest)) => text
.strip_prefix(l.as_str())
.is_some_and(|t| glob_match(rest, t)),
Some((PatSeg::Star, rest)) => {
let Some((PatSeg::Lit(l), after)) = rest.split_first() else {
return rest.is_empty();
};
let mut from = 0;
while let Some(hit) = text[from..].find(l.as_str()) {
let at = from + hit;
if glob_match(after, &text[at + l.len()..]) {
return true;
}
from = at
+ if l.is_empty() {
text[at..].chars().next().map_or(1, char::len_utf8)
} else {
l.len()
};
if from > text.len() {
break;
}
}
false
}
}
}
pub(super) fn value_eq(a: &Value, b: &Value, scale: UnitScale) -> bool {
if matches!(a, Value::Period { .. }) || matches!(b, Value::Period { .. }) {
return match (a.calendar_reading(), b.calendar_reading()) {
(Some(x), Some(y)) => x.interval_cmp(&y) == Ordering::Equal,
_ => false,
};
}
if matches!(a, Value::Instant { .. }) || matches!(b, Value::Instant { .. }) {
return match (a.temporal_reading(), b.temporal_reading()) {
(Some(x), Some(y)) => x == y,
_ => false,
};
}
if matches!(a, Value::Duration { .. }) || matches!(b, Value::Duration { .. }) {
return match (durational_with(a, scale), durational_with(b, scale)) {
(Some(x), Some(y)) => x == y,
_ => false,
};
}
if matches!(a, Value::Quantity { .. }) || matches!(b, Value::Quantity { .. }) {
return match crate::value::quantital_pair_with(a, b, scale) {
Some((x, y)) => x == y,
None => false,
};
}
match (a.numeric_reading(), b.numeric_reading()) {
(Some(Value::Int(x)), Some(Value::Int(y))) => return x == y,
(Some(x), Some(y)) => return x.numeric() == y.numeric(),
_ => {}
}
match (a, b) {
(Value::Str(x), Value::Str(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Null, Value::Null) => true,
_ => false,
}
}
pub(super) fn value_cmp(a: &Value, b: &Value, scale: UnitScale) -> Option<Ordering> {
if matches!(a, Value::Period { .. }) || matches!(b, Value::Period { .. }) {
return match (a.calendar_reading(), b.calendar_reading()) {
(Some(x), Some(y)) => Some(x.interval_cmp(&y)),
_ => None,
};
}
if matches!(a, Value::Instant { .. }) || matches!(b, Value::Instant { .. }) {
return match (a.temporal_reading(), b.temporal_reading()) {
(Some(x), Some(y)) => Some(x.cmp(&y)),
_ => None,
};
}
if matches!(a, Value::Duration { .. }) || matches!(b, Value::Duration { .. }) {
return match (durational_with(a, scale), durational_with(b, scale)) {
(Some(x), Some(y)) => Some(x.cmp(&y)),
_ => None,
};
}
if matches!(a, Value::Quantity { .. }) || matches!(b, Value::Quantity { .. }) {
return crate::value::quantital_pair_with(a, b, scale).and_then(|(x, y)| x.partial_cmp(&y));
}
match (a.numeric_reading(), b.numeric_reading()) {
(Some(Value::Int(x)), Some(Value::Int(y))) => return Some(x.cmp(&y)),
(Some(x), Some(y)) => return x.numeric()?.partial_cmp(&y.numeric()?),
_ => {}
}
match (a, b) {
(Value::Str(x), Value::Str(y)) => Some(x.cmp(y)),
_ => None,
}
}