diff --git a/src/eval/ops.rs b/src/eval/ops.rs index c1dd726b6..53205ce89 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,8 +1,27 @@ -use std::cmp::Ordering::*; +use std::cmp::Ordering; use super::Value; use Value::*; +/// Join a value with another value. +pub fn join(lhs: Value, rhs: Value) -> Result { + Ok(match (lhs, rhs) { + (_, Error) => Error, + (Error, _) => Error, + (a, None) => a, + (None, b) => b, + + (Str(a), Str(b)) => Str(a + &b), + (Array(a), Array(b)) => Array(a + &b), + (Dict(a), Dict(b)) => Dict(a + &b), + (Template(a), Template(b)) => Template(a + &b), + (Template(a), Str(b)) => Template(a + b), + (Str(a), Template(b)) => Template(a + b), + + (lhs, _) => return Err(lhs), + }) +} + /// Apply the plus operator to a value. pub fn pos(value: Value) -> Value { match value { @@ -55,7 +74,14 @@ pub fn add(lhs: Value, rhs: Value) -> Value { (Fractional(a), Fractional(b)) => Fractional(a + b), - (a, b) => concat(a, b).unwrap_or(Value::Error), + (Str(a), Str(b)) => Str(a + &b), + (Array(a), Array(b)) => Array(a + &b), + (Dict(a), Dict(b)) => Dict(a + &b), + (Template(a), Template(b)) => Template(a + &b), + (Template(a), Str(b)) => Template(a + b), + (Str(a), Template(b)) => Template(a + b), + + _ => Error, } } @@ -184,30 +210,80 @@ pub fn or(lhs: Value, rhs: Value) -> Value { } } +/// Determine whether two values are equal. +pub fn equal(lhs: &Value, rhs: &Value) -> bool { + match (lhs, rhs) { + // Compare reflexively. + (None, None) => true, + (Auto, Auto) => true, + (Bool(a), Bool(b)) => a == b, + (Int(a), Int(b)) => a == b, + (Float(a), Float(b)) => a == b, + (Length(a), Length(b)) => a == b, + (Angle(a), Angle(b)) => a == b, + (Relative(a), Relative(b)) => a == b, + (Linear(a), Linear(b)) => a == b, + (Fractional(a), Fractional(b)) => a == b, + (Color(a), Color(b)) => a == b, + (Str(a), Str(b)) => a == b, + (Array(a), Array(b)) => a == b, + (Dict(a), Dict(b)) => a == b, + (Template(a), Template(b)) => a == b, + (Func(a), Func(b)) => a == b, + (Any(a), Any(b)) => a == b, + (Error, Error) => true, + + // Some technically different things should compare equal. + (&Int(a), &Float(b)) => a as f64 == b, + (&Float(a), &Int(b)) => a == b as f64, + (&Length(a), &Linear(b)) => a == b.abs && b.rel.is_zero(), + (&Relative(a), &Linear(b)) => a == b.rel && b.abs.is_zero(), + (&Linear(a), &Length(b)) => a.abs == b && a.rel.is_zero(), + (&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(), + + _ => false, + } +} + /// Compute whether two values are equal. pub fn eq(lhs: Value, rhs: Value) -> Value { - Bool(lhs.eq(&rhs)) + Bool(equal(&lhs, &rhs)) } /// Compute whether two values are equal. pub fn neq(lhs: Value, rhs: Value) -> Value { - Bool(!lhs.eq(&rhs)) + Bool(!equal(&lhs, &rhs)) +} + +/// Compare two values. +pub fn compare(lhs: &Value, rhs: &Value) -> Option { + match (lhs, rhs) { + (Bool(a), Bool(b)) => a.partial_cmp(b), + (Int(a), Int(b)) => a.partial_cmp(b), + (Int(a), Float(b)) => (*a as f64).partial_cmp(b), + (Float(a), Int(b)) => a.partial_cmp(&(*b as f64)), + (Float(a), Float(b)) => a.partial_cmp(b), + (Angle(a), Angle(b)) => a.partial_cmp(b), + (Length(a), Length(b)) => a.partial_cmp(b), + (Str(a), Str(b)) => a.partial_cmp(b), + _ => Option::None, + } } macro_rules! comparison { ($name:ident, $($pat:tt)*) => { /// Compute how a value compares with another value. pub fn $name(lhs: Value, rhs: Value) -> Value { - lhs.cmp(&rhs) - .map_or(Value::Error, |x| Value::Bool(matches!(x, $($pat)*))) + compare(&lhs, &rhs) + .map_or(Error, |x| Bool(matches!(x, $($pat)*))) } }; } -comparison!(lt, Less); -comparison!(leq, Less | Equal); -comparison!(gt, Greater); -comparison!(geq, Greater | Equal); +comparison!(lt, Ordering::Less); +comparison!(leq, Ordering::Less | Ordering::Equal); +comparison!(gt, Ordering::Greater); +comparison!(geq, Ordering::Greater | Ordering::Equal); /// Compute the range from `lhs` to `rhs`. pub fn range(lhs: Value, rhs: Value) -> Value { @@ -216,29 +292,3 @@ pub fn range(lhs: Value, rhs: Value) -> Value { _ => Error, } } - -/// Join a value with another value. -pub fn join(lhs: Value, rhs: Value) -> Result { - Ok(match (lhs, rhs) { - (_, Error) => Error, - (Error, _) => Error, - - (a, None) => a, - (None, b) => b, - - (a, b) => return concat(a, b), - }) -} - -/// Concatentate two values. -fn concat(lhs: Value, rhs: Value) -> Result { - Ok(match (lhs, rhs) { - (Str(a), Str(b)) => Str(a + &b), - (Array(a), Array(b)) => Array(a + &b), - (Dict(a), Dict(b)) => Dict(a + &b), - (Template(a), Template(b)) => Template(a + &b), - (Template(a), Str(b)) => Template(a + b), - (Str(a), Template(b)) => Template(a + b), - (a, _) => return Err(a), - }) -} diff --git a/src/eval/value.rs b/src/eval/value.rs index fe9494b1b..7c35fdbd8 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -10,7 +10,7 @@ use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::syntax::{Span, Spanned}; /// A computational value. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Value { /// The value that indicates the absence of a meaningful value. None, @@ -83,39 +83,6 @@ impl Value { } } - /// Recursively compute whether two values are equal. - pub fn eq(&self, rhs: &Self) -> bool { - match (self, rhs) { - (&Self::Int(a), &Self::Float(b)) => a as f64 == b, - (&Self::Float(a), &Self::Int(b)) => a == b as f64, - (&Self::Length(a), &Self::Linear(b)) => a == b.abs && b.rel.is_zero(), - (&Self::Relative(a), &Self::Linear(b)) => a == b.rel && b.abs.is_zero(), - (&Self::Linear(a), &Self::Length(b)) => a.abs == b && a.rel.is_zero(), - (&Self::Linear(a), &Self::Relative(b)) => a.rel == b && a.abs.is_zero(), - (Self::Array(a), Self::Array(b)) => { - a.len() == b.len() && a.iter().zip(b).all(|(x, y)| x.eq(y)) - } - (Self::Dict(a), Self::Dict(b)) => { - a.len() == b.len() - && a.iter().all(|(k, x)| b.get(k).map_or(false, |y| x.eq(y))) - } - (a, b) => a == b, - } - } - - /// Compare a value with another value. - pub fn cmp(&self, rhs: &Self) -> Option { - match (self, rhs) { - (Self::Int(a), Self::Int(b)) => a.partial_cmp(b), - (Self::Int(a), Self::Float(b)) => (*a as f64).partial_cmp(b), - (Self::Float(a), Self::Int(b)) => a.partial_cmp(&(*b as f64)), - (Self::Float(a), Self::Float(b)) => a.partial_cmp(b), - (Self::Angle(a), Self::Angle(b)) => a.partial_cmp(b), - (Self::Length(a), Self::Length(b)) => a.partial_cmp(b), - _ => None, - } - } - /// Try to cast the value into a specific type. pub fn cast(self) -> CastResult where @@ -143,6 +110,18 @@ impl Default for Value { } } +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + ops::equal(self, other) + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + ops::compare(self, other) + } +} + /// A wrapper around a dynamic value. pub struct AnyValue(Box); diff --git a/src/library/utility.rs b/src/library/utility.rs index c1f20cc6d..eaa6146d4 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -82,7 +82,7 @@ fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value { while let Some(value) = args.eat::(ctx) { if let Some(prev) = &extremum { - match value.cmp(&prev) { + match value.partial_cmp(&prev) { Some(ordering) if ordering == goal => extremum = Some(value), Some(_) => {} None => {