Move comparisons into standard traits

This commit is contained in:
Laurenz 2021-07-10 20:22:52 +02:00
parent 6a4823461f
commit 982ce85976
3 changed files with 100 additions and 71 deletions

View File

@ -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<Value, Value> {
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<Ordering> {
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<Value, Value> {
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<Value, Value> {
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),
})
}

View File

@ -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<Ordering> {
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<T>(self) -> CastResult<T, Self>
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<Ordering> {
ops::compare(self, other)
}
}
/// A wrapper around a dynamic value.
pub struct AnyValue(Box<dyn Bounds>);

View File

@ -82,7 +82,7 @@ fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value {
while let Some(value) = args.eat::<Value>(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 => {