summaryrefslogtreecommitdiff
path: root/src/model/ops.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-10-17 19:26:24 +0200
committerLaurenz <laurmaedje@gmail.com>2022-10-17 20:04:22 +0200
commite21822665591dc19766275da1e185215a6b945ef (patch)
tree7788e211c3c33c8b5a8ad7d5eb7574e33631eb16 /src/model/ops.rs
parent4fd031a256b2ecfe524859d5599fafb386395572 (diff)
Merge some modules
Diffstat (limited to 'src/model/ops.rs')
-rw-r--r--src/model/ops.rs398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/model/ops.rs b/src/model/ops.rs
new file mode 100644
index 00000000..c521f704
--- /dev/null
+++ b/src/model/ops.rs
@@ -0,0 +1,398 @@
+//! Operations on values.
+
+use std::cmp::Ordering;
+
+use super::{RawAlign, RawLength, RawStroke, Regex, Smart, Value};
+use crate::diag::StrResult;
+use crate::geom::{Numeric, Relative, Spec, SpecAxis};
+use Value::*;
+
+/// Bail with a type mismatch error.
+macro_rules! mismatch {
+ ($fmt:expr, $($value:expr),* $(,)?) => {
+ return Err(format!($fmt, $($value.type_name()),*))
+ };
+}
+
+/// Join a value with another value.
+pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(match (lhs, rhs) {
+ (a, None) => a,
+ (None, b) => b,
+ (Str(a), Str(b)) => Str(a + b),
+ (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b),
+ (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())),
+ (Content(a), Content(b)) => Content(a + b),
+ (Array(a), Array(b)) => Array(a + b),
+ (Dict(a), Dict(b)) => Dict(a + b),
+ (a, b) => mismatch!("cannot join {} with {}", a, b),
+ })
+}
+
+/// Apply the unary plus operator to a value.
+pub fn pos(value: Value) -> StrResult<Value> {
+ Ok(match value {
+ Int(v) => Int(v),
+ Float(v) => Float(v),
+ Length(v) => Length(v),
+ Angle(v) => Angle(v),
+ Ratio(v) => Ratio(v),
+ Relative(v) => Relative(v),
+ Fraction(v) => Fraction(v),
+ v => mismatch!("cannot apply '+' to {}", v),
+ })
+}
+
+/// Compute the negation of a value.
+pub fn neg(value: Value) -> StrResult<Value> {
+ Ok(match value {
+ Int(v) => Int(-v),
+ Float(v) => Float(-v),
+ Length(v) => Length(-v),
+ Angle(v) => Angle(-v),
+ Ratio(v) => Ratio(-v),
+ Relative(v) => Relative(-v),
+ Fraction(v) => Fraction(-v),
+ v => mismatch!("cannot apply '-' to {}", v),
+ })
+}
+
+/// Compute the sum of two values.
+pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(match (lhs, rhs) {
+ (a, None) => a,
+ (None, b) => b,
+
+ (Int(a), Int(b)) => Int(a + b),
+ (Int(a), Float(b)) => Float(a as f64 + b),
+ (Float(a), Int(b)) => Float(a + b as f64),
+ (Float(a), Float(b)) => Float(a + b),
+
+ (Angle(a), Angle(b)) => Angle(a + b),
+
+ (Length(a), Length(b)) => Length(a + b),
+ (Length(a), Ratio(b)) => Relative(b + a),
+ (Length(a), Relative(b)) => Relative(b + a),
+
+ (Ratio(a), Length(b)) => Relative(a + b),
+ (Ratio(a), Ratio(b)) => Ratio(a + b),
+ (Ratio(a), Relative(b)) => Relative(b + a),
+
+ (Relative(a), Length(b)) => Relative(a + b),
+ (Relative(a), Ratio(b)) => Relative(a + b),
+ (Relative(a), Relative(b)) => Relative(a + b),
+
+ (Fraction(a), Fraction(b)) => Fraction(a + b),
+
+ (Str(a), Str(b)) => Str(a + b),
+ (Content(a), Content(b)) => Content(a + b),
+ (Content(a), Str(b)) => Content(a + super::Content::Text(b.into())),
+ (Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b),
+
+ (Array(a), Array(b)) => Array(a + b),
+ (Dict(a), Dict(b)) => Dict(a + b),
+
+ (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
+ Value::dynamic(RawStroke {
+ paint: Smart::Custom(color.into()),
+ thickness: Smart::Custom(thickness),
+ })
+ }
+
+ (Dyn(a), Dyn(b)) => {
+ // 1D alignments can be summed into 2D alignments.
+ if let (Some(&a), Some(&b)) =
+ (a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
+ {
+ if a.axis() != b.axis() {
+ Value::dynamic(match a.axis() {
+ SpecAxis::Horizontal => Spec { x: a, y: b },
+ SpecAxis::Vertical => Spec { x: b, y: a },
+ })
+ } else {
+ return Err(format!("cannot add two {:?} alignments", a.axis()));
+ }
+ } else {
+ mismatch!("cannot add {} and {}", a, b);
+ }
+ }
+
+ (a, b) => mismatch!("cannot add {} and {}", a, b),
+ })
+}
+
+/// Compute the difference of two values.
+pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(match (lhs, rhs) {
+ (Int(a), Int(b)) => Int(a - b),
+ (Int(a), Float(b)) => Float(a as f64 - b),
+ (Float(a), Int(b)) => Float(a - b as f64),
+ (Float(a), Float(b)) => Float(a - b),
+
+ (Angle(a), Angle(b)) => Angle(a - b),
+
+ (Length(a), Length(b)) => Length(a - b),
+ (Length(a), Ratio(b)) => Relative(-b + a),
+ (Length(a), Relative(b)) => Relative(-b + a),
+
+ (Ratio(a), Length(b)) => Relative(a + -b),
+ (Ratio(a), Ratio(b)) => Ratio(a - b),
+ (Ratio(a), Relative(b)) => Relative(-b + a),
+
+ (Relative(a), Length(b)) => Relative(a + -b),
+ (Relative(a), Ratio(b)) => Relative(a + -b),
+ (Relative(a), Relative(b)) => Relative(a - b),
+
+ (Fraction(a), Fraction(b)) => Fraction(a - b),
+
+ (a, b) => mismatch!("cannot subtract {1} from {0}", a, b),
+ })
+}
+
+/// Compute the product of two values.
+pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(match (lhs, rhs) {
+ (Int(a), Int(b)) => Int(a * b),
+ (Int(a), Float(b)) => Float(a as f64 * b),
+ (Float(a), Int(b)) => Float(a * b as f64),
+ (Float(a), Float(b)) => Float(a * b),
+
+ (Length(a), Int(b)) => Length(a * b as f64),
+ (Length(a), Float(b)) => Length(a * b),
+ (Int(a), Length(b)) => Length(b * a as f64),
+ (Float(a), Length(b)) => Length(b * a),
+
+ (Angle(a), Int(b)) => Angle(a * b as f64),
+ (Angle(a), Float(b)) => Angle(a * b),
+ (Int(a), Angle(b)) => Angle(a as f64 * b),
+ (Float(a), Angle(b)) => Angle(a * b),
+
+ (Ratio(a), Int(b)) => Ratio(a * b as f64),
+ (Ratio(a), Float(b)) => Ratio(a * b),
+ (Float(a), Ratio(b)) => Ratio(a * b),
+ (Int(a), Ratio(b)) => Ratio(a as f64 * b),
+
+ (Relative(a), Int(b)) => Relative(a * b as f64),
+ (Relative(a), Float(b)) => Relative(a * b),
+ (Int(a), Relative(b)) => Relative(a as f64 * b),
+ (Float(a), Relative(b)) => Relative(a * b),
+
+ (Float(a), Fraction(b)) => Fraction(a * b),
+ (Fraction(a), Int(b)) => Fraction(a * b as f64),
+ (Fraction(a), Float(b)) => Fraction(a * b),
+ (Int(a), Fraction(b)) => Fraction(a as f64 * b),
+
+ (Str(a), Int(b)) => Str(a.repeat(b)?),
+ (Int(a), Str(b)) => Str(b.repeat(a)?),
+ (Array(a), Int(b)) => Array(a.repeat(b)?),
+ (Int(a), Array(b)) => Array(b.repeat(a)?),
+ (Content(a), Int(b)) => Content(a.repeat(b)?),
+ (Int(a), Content(b)) => Content(b.repeat(a)?),
+
+ (a, b) => mismatch!("cannot multiply {} with {}", a, b),
+ })
+}
+
+/// Compute the quotient of two values.
+pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(match (lhs, rhs) {
+ (Int(a), Int(b)) => Float(a as f64 / b as f64),
+ (Int(a), Float(b)) => Float(a as f64 / b),
+ (Float(a), Int(b)) => Float(a / b as f64),
+ (Float(a), Float(b)) => Float(a / b),
+
+ (Length(a), Int(b)) => Length(a / b as f64),
+ (Length(a), Float(b)) => Length(a / b),
+ (Length(a), Length(b)) => Float(div_length(a, b)?),
+ (Length(a), Relative(b)) if b.rel.is_zero() => Float(div_length(a, b.abs)?),
+
+ (Angle(a), Int(b)) => Angle(a / b as f64),
+ (Angle(a), Float(b)) => Angle(a / b),
+ (Angle(a), Angle(b)) => Float(a / b),
+
+ (Ratio(a), Int(b)) => Ratio(a / b as f64),
+ (Ratio(a), Float(b)) => Ratio(a / b),
+ (Ratio(a), Ratio(b)) => Float(a / b),
+ (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel),
+
+ (Relative(a), Int(b)) => Relative(a / b as f64),
+ (Relative(a), Float(b)) => Relative(a / b),
+ (Relative(a), Length(b)) if a.rel.is_zero() => Float(div_length(a.abs, b)?),
+ (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b),
+ (Relative(a), Relative(b)) => Float(div_relative(a, b)?),
+
+ (Fraction(a), Int(b)) => Fraction(a / b as f64),
+ (Fraction(a), Float(b)) => Fraction(a / b),
+ (Fraction(a), Fraction(b)) => Float(a / b),
+
+ (a, b) => mismatch!("cannot divide {} by {}", a, b),
+ })
+}
+
+/// Try to divide two lengths.
+fn div_length(a: RawLength, b: RawLength) -> StrResult<f64> {
+ if a.length.is_zero() && b.length.is_zero() {
+ Ok(a.em / b.em)
+ } else if a.em.is_zero() && b.em.is_zero() {
+ Ok(a.length / b.length)
+ } else {
+ return Err("cannot divide these two lengths".into());
+ }
+}
+
+/// Try to divide two relative lengths.
+fn div_relative(a: Relative<RawLength>, b: Relative<RawLength>) -> StrResult<f64> {
+ if a.rel.is_zero() && b.rel.is_zero() {
+ div_length(a.abs, b.abs)
+ } else if a.abs.is_zero() && b.abs.is_zero() {
+ Ok(a.rel / b.rel)
+ } else {
+ return Err("cannot divide these two relative lengths".into());
+ }
+}
+
+/// Compute the logical "not" of a value.
+pub fn not(value: Value) -> StrResult<Value> {
+ match value {
+ Bool(b) => Ok(Bool(!b)),
+ v => mismatch!("cannot apply 'not' to {}", v),
+ }
+}
+
+/// Compute the logical "and" of two values.
+pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
+ match (lhs, rhs) {
+ (Bool(a), Bool(b)) => Ok(Bool(a && b)),
+ (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b),
+ }
+}
+
+/// Compute the logical "or" of two values.
+pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> {
+ match (lhs, rhs) {
+ (Bool(a), Bool(b)) => Ok(Bool(a || b)),
+ (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
+ }
+}
+
+/// Compute whether two values are equal.
+pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(Bool(equal(&lhs, &rhs)))
+}
+
+/// Compute whether two values are unequal.
+pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> {
+ Ok(Bool(!equal(&lhs, &rhs)))
+}
+
+macro_rules! comparison {
+ ($name:ident, $op:tt, $($pat:tt)*) => {
+ /// Compute how a value compares with another value.
+ pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> {
+ if let Some(ordering) = compare(&lhs, &rhs) {
+ Ok(Bool(matches!(ordering, $($pat)*)))
+ } else {
+ mismatch!(concat!("cannot apply '", $op, "' to {} and {}"), lhs, rhs);
+ }
+ }
+ };
+}
+
+comparison!(lt, "<", Ordering::Less);
+comparison!(leq, "<=", Ordering::Less | Ordering::Equal);
+comparison!(gt, ">", Ordering::Greater);
+comparison!(geq, ">=", Ordering::Greater | Ordering::Equal);
+
+/// 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,
+ (Ratio(a), Ratio(b)) => a == b,
+ (Relative(a), Relative(b)) => a == b,
+ (Fraction(a), Fraction(b)) => a == b,
+ (Color(a), Color(b)) => a == b,
+ (Str(a), Str(b)) => a == b,
+ (Content(a), Content(b)) => a == b,
+ (Array(a), Array(b)) => a == b,
+ (Dict(a), Dict(b)) => a == b,
+ (Func(a), Func(b)) => a == b,
+ (Dyn(a), Dyn(b)) => a == b,
+
+ // 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), &Relative(b)) => a == b.abs && b.rel.is_zero(),
+ (&Ratio(a), &Relative(b)) => a == b.rel && b.abs.is_zero(),
+ (&Relative(a), &Length(b)) => a.abs == b && a.rel.is_zero(),
+ (&Relative(a), &Ratio(b)) => a.rel == b && a.abs.is_zero(),
+
+ _ => false,
+ }
+}
+
+/// 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),
+ (Float(a), Float(b)) => a.partial_cmp(b),
+ (Length(a), Length(b)) => a.partial_cmp(b),
+ (Angle(a), Angle(b)) => a.partial_cmp(b),
+ (Ratio(a), Ratio(b)) => a.partial_cmp(b),
+ (Relative(a), Relative(b)) => a.partial_cmp(b),
+ (Fraction(a), Fraction(b)) => a.partial_cmp(b),
+ (Str(a), Str(b)) => a.partial_cmp(b),
+
+ // Some technically different things should be comparable.
+ (&Int(a), &Float(b)) => (a as f64).partial_cmp(&b),
+ (&Float(a), &Int(b)) => a.partial_cmp(&(b as f64)),
+ (&Length(a), &Relative(b)) if b.rel.is_zero() => a.partial_cmp(&b.abs),
+ (&Ratio(a), &Relative(b)) if b.abs.is_zero() => a.partial_cmp(&b.rel),
+ (&Relative(a), &Length(b)) if a.rel.is_zero() => a.abs.partial_cmp(&b),
+ (&Relative(a), &Ratio(b)) if a.abs.is_zero() => a.rel.partial_cmp(&b),
+
+ _ => Option::None,
+ }
+}
+
+/// Test whether one value is "in" another one.
+pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
+ if let Some(b) = contains(&lhs, &rhs) {
+ Ok(Bool(b))
+ } else {
+ mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
+ }
+}
+
+/// Test whether one value is "not in" another one.
+pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
+ if let Some(b) = contains(&lhs, &rhs) {
+ Ok(Bool(!b))
+ } else {
+ mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
+ }
+}
+
+/// Test for containment.
+pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
+ Some(match (lhs, rhs) {
+ (Str(a), Str(b)) => b.as_str().contains(a.as_str()),
+ (Dyn(a), Str(b)) => {
+ if let Some(regex) = a.downcast::<Regex>() {
+ regex.is_match(b)
+ } else {
+ return Option::None;
+ }
+ }
+ (Str(a), Dict(b)) => b.contains(a),
+ (a, Array(b)) => b.contains(a),
+ _ => return Option::None,
+ })
+}