use super::time;
use super::types::Type;
use super::value::{Key, Value};
use crate::named::named;
named! {
pub(crate) enum UnaryOp {
Not = "!",
Negate = "-",
}
}
named! {
pub(crate) enum BinaryOp {
Add = "+",
Subtract = "-",
Multiply = "*",
Divide = "/",
Remainder = "%",
Less = "<",
LessOrEqual = "<=",
Greater = ">",
GreaterOrEqual = ">=",
Equal = "==",
NotEqual = "!=",
In = "in",
}
}
impl UnaryOp {
pub(crate) fn result_type(self, operand: &Type) -> Option<Type> {
match self {
Self::Not => operand.fits(&Type::Bool).then_some(Type::Bool),
Self::Negate => matches!(
operand,
Type::Int | Type::Double | Type::Duration | Type::Dyn
)
.then(|| operand.clone()),
}
}
pub(crate) fn apply(self, operand: &Value) -> Result<Value, String> {
match (self, operand) {
(Self::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
(Self::Negate, Value::Int(i)) => i
.checked_neg()
.map(Value::Int)
.ok_or_else(|| "int overflow".into()),
(Self::Negate, Value::Double(d)) => Ok(Value::Double(-d)),
(Self::Negate, Value::Duration(d)) => duration(-d),
(_, other) => Err(format!(
"'{}' can't be applied to {}",
self.name(),
other.kind()
)),
}
}
}
impl BinaryOp {
pub(crate) fn result_type(self, a: &Type, b: &Type) -> Option<Type> {
match self {
Self::Add | Self::Subtract | Self::Multiply | Self::Divide | Self::Remainder => {
self.arithmetic_type(a, b)
}
Self::Less | Self::LessOrEqual | Self::Greater | Self::GreaterOrEqual => {
ordered(a, b).then_some(Type::Bool)
}
Self::Equal | Self::NotEqual => equatable(a, b).then_some(Type::Bool),
Self::In => contained(a, b).then_some(Type::Bool),
}
}
pub(crate) fn apply(self, a: &Value, b: &Value) -> Result<Value, String> {
match self {
Self::Add | Self::Subtract | Self::Multiply | Self::Divide | Self::Remainder => {
self.arithmetic(a, b)
}
Self::Less | Self::LessOrEqual | Self::Greater | Self::GreaterOrEqual => {
self.compare(a, b)
}
Self::Equal => Ok(Value::Bool(a.equals(b))),
Self::NotEqual => Ok(Value::Bool(!a.equals(b))),
Self::In => contains(a, b),
}
}
fn no_overload(self, a: &Value, b: &Value) -> String {
format!(
"'{}' can't be applied to {} and {}",
self.name(),
a.kind(),
b.kind()
)
}
fn arithmetic_type(self, a: &Type, b: &Type) -> Option<Type> {
use Type::{Double, Duration, Dyn, Grammar, Int, List, String, Timestamp, Uint};
match (self, a, b) {
(_, Int, Int) => Some(Int),
(_, Uint, Uint) => Some(Uint),
(_, Dyn, other) | (_, other, Dyn) => self.takes(other).then_some(Dyn),
(Self::Remainder, ..) => None,
(_, Int | Double, Int | Double) => Some(Double),
(Self::Add, String | Grammar(_), String | Grammar(_)) => Some(String),
(Self::Add, List(x), List(y)) => Some(Type::list(match (&**x, &**y) {
(Dyn, other) | (other, Dyn) => other.clone(),
_ => (**x).clone().join((**y).clone()),
})),
(Self::Add | Self::Subtract, Timestamp, Duration)
| (Self::Add, Duration, Timestamp) => Some(Timestamp),
(Self::Add | Self::Subtract, Duration, Duration)
| (Self::Subtract, Timestamp, Timestamp) => Some(Duration),
_ => None,
}
}
fn takes(self, t: &Type) -> bool {
use Type::{Double, Duration, Dyn, Grammar, Int, List, String, Timestamp, Uint};
match self {
Self::Add => matches!(
t,
Int | Uint | Double | String | Grammar(_) | List(_) | Timestamp | Duration | Dyn
),
Self::Subtract => matches!(t, Int | Uint | Double | Timestamp | Duration | Dyn),
Self::Remainder => matches!(t, Int | Uint | Dyn),
_ => matches!(t, Int | Uint | Double | Dyn),
}
}
fn arithmetic(self, a: &Value, b: &Value) -> Result<Value, String> {
match (self, a, b) {
(_, Value::Int(x), Value::Int(y)) => self
.integers(i128::from(*x), i128::from(*y), "int")
.map(Value::Int),
(_, Value::Uint(x), Value::Uint(y)) => self
.integers(i128::from(*x), i128::from(*y), "uint")
.map(Value::Uint),
(Self::Add, Value::String(x), Value::String(y)) => {
Ok(Value::string(&format!("{x}{y}")))
}
(Self::Add, Value::List(x), Value::List(y)) => {
Ok(Value::list(x.iter().chain(y.iter()).cloned().collect()))
}
(Self::Add, Value::Timestamp(t), Value::Duration(d))
| (Self::Add, Value::Duration(d), Value::Timestamp(t)) => timestamp(t + d),
(Self::Subtract, Value::Timestamp(t), Value::Duration(d)) => timestamp(t - d),
(Self::Subtract, Value::Timestamp(t), Value::Timestamp(u)) => duration(t - u),
(Self::Add, Value::Duration(d), Value::Duration(e)) => duration(d + e),
(Self::Subtract, Value::Duration(d), Value::Duration(e)) => duration(d - e),
_ => match (float(a), float(b), self) {
(_, _, Self::Remainder) | (None, ..) | (_, None, _) => Err(self.no_overload(a, b)),
(Some(x), Some(y), Self::Add) => Ok(Value::Double(x + y)),
(Some(x), Some(y), Self::Subtract) => Ok(Value::Double(x - y)),
(Some(x), Some(y), Self::Multiply) => Ok(Value::Double(x * y)),
(Some(x), Some(y), _) => Ok(Value::Double(x / y)),
},
}
}
fn integers<T: TryFrom<i128>>(self, x: i128, y: i128, name: &str) -> Result<T, String> {
let overflow = || format!("{name} overflow");
let n = match self {
Self::Add => x + y,
Self::Subtract => x - y,
Self::Multiply => x * y,
_ if y == 0 => return Err("division by zero".into()),
Self::Divide => x / y,
_ => {
T::try_from(x / y).map_err(|_| overflow())?;
x % y
}
};
T::try_from(n).map_err(|_| overflow())
}
fn compare(self, a: &Value, b: &Value) -> Result<Value, String> {
let ordering = a.order(b).map_err(|()| self.no_overload(a, b))?;
Ok(Value::Bool(ordering.is_some_and(|ordering| match self {
Self::Less => ordering.is_lt(),
Self::LessOrEqual => ordering.is_le(),
Self::Greater => ordering.is_gt(),
_ => ordering.is_ge(),
})))
}
}
#[allow(
clippy::cast_precision_loss,
reason = "CEL converts an int to the nearest double"
)]
fn float(value: &Value) -> Option<f64> {
match value {
Value::Int(i) => Some(*i as f64),
Value::Double(d) => Some(*d),
_ => None,
}
}
fn timestamp(nanos: i128) -> Result<Value, String> {
if time::timestamp_in_range(nanos) {
Ok(Value::Timestamp(nanos))
} else {
Err("timestamp out of range".into())
}
}
fn duration(nanos: i128) -> Result<Value, String> {
if time::duration_in_range(nanos) {
Ok(Value::Duration(nanos))
} else {
Err("duration out of range".into())
}
}
fn contains(element: &Value, collection: &Value) -> Result<Value, String> {
match collection {
Value::List(items) => Ok(Value::Bool(items.iter().any(|item| element.equals(item)))),
Value::Map(entries) => Ok(Value::Bool(
Key::to_find(element).is_some_and(|key| entries.contains_key(&key)),
)),
other => Err(format!("'in' needs a list or a map, not {}", other.kind())),
}
}
fn ordered(a: &Type, b: &Type) -> bool {
use Type::{Bool, Duration, Dyn, String, Timestamp};
let orderable = |t: &Type| t.is_numeric() || matches!(t, String | Bool | Timestamp | Duration);
match (a, b) {
(Dyn, other) | (other, Dyn) => orderable(other),
_ => (a.is_numeric() && b.is_numeric()) || (a == b && orderable(a)),
}
}
fn equatable(a: &Type, b: &Type) -> bool {
a.fits(b)
|| b.fits(a)
|| (a.is_numeric() && b.is_numeric())
|| *a == Type::Null
|| *b == Type::Null
}
fn contained(element: &Type, collection: &Type) -> bool {
match collection {
Type::List(item) => equatable(element, item),
Type::Map(key, _) => equatable(element, key),
Type::Record(_) => element.fits(&Type::String),
Type::Dyn => true,
_ => false,
}
}