microcad_lang/value/
mod.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Evaluation entities.
5//!
6//! Every evaluation of any *symbol* leads to a [`Value`] which then might continued
7//! to process or ends up as the overall evaluation result.
8
9mod argument_value;
10mod argument_value_list;
11mod array;
12mod matrix;
13mod parameter_value;
14mod parameter_value_list;
15mod quantity;
16mod target;
17mod tuple;
18mod value_access;
19mod value_error;
20mod value_list;
21
22pub use argument_value::*;
23pub use argument_value_list::*;
24pub use array::*;
25pub use matrix::*;
26pub use parameter_value::*;
27pub use parameter_value_list::*;
28pub use quantity::*;
29pub use target::*;
30pub use tuple::*;
31pub use value_access::*;
32pub use value_error::*;
33pub use value_list::*;
34
35use crate::{model::*, rc::*, src_ref::SrcRef, syntax::*, ty::*};
36use microcad_core::*;
37
38pub(crate) type ValueResult<Type = Value> = std::result::Result<Type, ValueError>;
39
40/// A variant value with attached source code reference.
41#[derive(Clone, Default, PartialEq)]
42pub enum Value {
43    /// Invalid value (used for error handling).
44    #[default]
45    None,
46    /// A quantity value.
47    Quantity(Quantity),
48    /// A boolean value.
49    Bool(bool),
50    /// An integer value.
51    Integer(Integer),
52    /// A string value.
53    String(String),
54    /// A list of values with a common type.
55    Array(Array),
56    /// A tuple of named items.
57    Tuple(Box<Tuple>),
58    /// A matrix.
59    Matrix(Box<Matrix>),
60    /// A model in the model tree.
61    Model(Model),
62    /// Return value
63    Return(Box<Value>),
64    /// Unevaluated const expression.
65    ConstExpression(Rc<Expression>),
66    /// for assert_valid() and assert_invalid()
67    Target(Target),
68}
69
70impl Value {
71    /// Check if the value is invalid.
72    pub fn is_invalid(&self) -> bool {
73        matches!(self, Value::None)
74    }
75
76    /// Calculate the power of two values, if possible.
77    pub fn pow(&self, rhs: &Value) -> ValueResult {
78        match (&self, rhs) {
79            (Value::Quantity(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity(lhs.pow(rhs))),
80            (Value::Quantity(lhs), Value::Integer(rhs)) => Ok(Value::Quantity(lhs.pow_int(rhs))),
81            (Value::Integer(lhs), Value::Integer(rhs)) => Ok(Value::Integer(lhs.pow(*rhs as u32))),
82            _ => Err(ValueError::InvalidOperator("^".to_string())),
83        }
84    }
85
86    /// Binary operation
87    pub fn binary_op(lhs: Value, rhs: Value, op: &str) -> ValueResult {
88        match op {
89            "+" => lhs + rhs,
90            "-" => lhs - rhs,
91            "*" => lhs * rhs,
92            "/" => lhs / rhs,
93            "^" => lhs.pow(&rhs),
94            "&" => lhs & rhs,
95            "|" => lhs | rhs,
96            ">" => Ok(Value::Bool(lhs > rhs)),
97            "<" => Ok(Value::Bool(lhs < rhs)),
98            "≤" => Ok(Value::Bool(lhs <= rhs)),
99            "≥" => Ok(Value::Bool(lhs >= rhs)),
100            "~" => todo!("implement near ~="),
101            "==" => Ok(Value::Bool(lhs == rhs)),
102            "!=" => Ok(Value::Bool(lhs != rhs)),
103            _ => unimplemented!("{op:?}"),
104        }
105    }
106
107    /// Unary operation.
108    pub fn unary_op(self, op: &str) -> ValueResult {
109        match op {
110            "-" => -self,
111            _ => unimplemented!(),
112        }
113    }
114
115    /// Try to get boolean value.
116    ///
117    /// A `Value::None` will return false.
118    pub fn try_bool(&self) -> Result<bool, ValueError> {
119        match self {
120            Value::Bool(b) => Ok(*b),
121            Value::None => Ok(false),
122            value => Err(ValueError::CannotConvertToBool(value.to_string())),
123        }
124    }
125
126    /// Try to convert to [`String`].
127    pub fn try_string(&self) -> Result<String, ValueError> {
128        match self {
129            Value::String(s) => return Ok(s.clone()),
130            Value::Integer(i) => return Ok(i.to_string()),
131            _ => {}
132        }
133
134        Err(ValueError::CannotConvert(self.to_string(), "String".into()))
135    }
136
137    /// Try to convert to [`Scalar`].
138    pub fn try_scalar(&self) -> Result<Scalar, ValueError> {
139        match self {
140            Value::Quantity(q) => return Ok(q.value),
141            Value::Integer(i) => return Ok((*i) as f64),
142            _ => {}
143        }
144
145        Err(ValueError::CannotConvert(self.to_string(), "Scalar".into()))
146    }
147
148    /// Unpack any Value::Return(..)
149    pub fn un_return(&self) -> Value {
150        match self {
151            Value::Return(value) => value.as_ref().clone(),
152            value => value.clone(),
153        }
154    }
155}
156
157impl PartialOrd for Value {
158    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
159        match (self, other) {
160            // integer type
161            (Value::Integer(lhs), Value::Integer(rhs)) => lhs.partial_cmp(rhs),
162            (Value::Quantity(lhs), Value::Quantity(rhs)) => lhs.partial_cmp(rhs),
163            (
164                Value::Quantity(Quantity {
165                    value,
166                    quantity_type: QuantityType::Scalar,
167                }),
168                Value::Integer(rhs),
169            ) => value.partial_cmp(&(*rhs as Scalar)),
170            _ => {
171                log::warn!("unhandled type mismatch between {self} and {other}");
172                None
173            }
174        }
175    }
176}
177
178impl crate::ty::Ty for Value {
179    fn ty(&self) -> Type {
180        match self {
181            Value::None | Value::ConstExpression(_) => Type::Invalid,
182            Value::Integer(_) => Type::Integer,
183            Value::Quantity(q) => q.ty(),
184            Value::Bool(_) => Type::Bool,
185            Value::String(_) => Type::String,
186            Value::Array(list) => list.ty(),
187            Value::Tuple(tuple) => tuple.ty(),
188            Value::Matrix(matrix) => matrix.ty(),
189            Value::Model(_) => Type::Models,
190            Value::Return(r) => r.ty(),
191            Value::Target(..) => Type::Target,
192        }
193    }
194}
195
196impl std::ops::Neg for Value {
197    type Output = ValueResult;
198
199    fn neg(self) -> Self::Output {
200        match self {
201            Value::Integer(n) => Ok(Value::Integer(-n)),
202            Value::Quantity(q) => Ok(Value::Quantity(q.neg())),
203            Value::Array(a) => -a,
204            Value::Tuple(t) => -t.as_ref().clone(),
205            _ => Err(ValueError::InvalidOperator("-".into())),
206        }
207    }
208}
209
210/// Rules for operator `+`.
211impl std::ops::Add for Value {
212    type Output = ValueResult;
213
214    fn add(self, rhs: Self) -> Self::Output {
215        match (self, rhs) {
216            // Add two integers
217            (Value::Integer(lhs), Value::Integer(rhs)) => Ok(Value::Integer(lhs + rhs)),
218            // Add a quantity to an integer
219            (Value::Integer(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs + rhs)?)),
220            // Add an integer to a quantity
221            (Value::Quantity(lhs), Value::Integer(rhs)) => Ok(Value::Quantity((lhs + rhs)?)),
222            // Add two scalars
223            (Value::Quantity(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs + rhs)?)),
224            // Concatenate two strings
225            (Value::String(lhs), Value::String(rhs)) => Ok(Value::String(lhs + &rhs)),
226            // Concatenate two lists
227            (Value::Array(lhs), Value::Array(rhs)) => {
228                if lhs.ty() != rhs.ty() {
229                    return Err(ValueError::CannotCombineVecOfDifferentType(
230                        lhs.ty(),
231                        rhs.ty(),
232                    ));
233                }
234
235                Ok(Value::Array(Array::from_values(
236                    lhs.iter().chain(rhs.iter()).cloned().collect(),
237                    lhs.ty(),
238                )))
239            }
240            // Add a value to an array.
241            (Value::Array(lhs), rhs) => Ok((lhs + rhs)?),
242            // Add two tuples of the same type: (x = 1., y = 2.) + (x = 3., y = 4.)
243            (Value::Tuple(lhs), Value::Tuple(rhs)) => Ok((*lhs + *rhs)?.into()),
244            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} + {rhs}"))),
245        }
246    }
247}
248
249/// Rules for operator `-`.
250impl std::ops::Sub for Value {
251    type Output = ValueResult;
252
253    fn sub(self, rhs: Self) -> Self::Output {
254        match (self, rhs) {
255            // Subtract two integers
256            (Value::Integer(lhs), Value::Integer(rhs)) => Ok(Value::Integer(lhs - rhs)),
257            // Subtract an scalar and an integer
258            (Value::Quantity(lhs), Value::Integer(rhs)) => Ok(Value::Quantity((lhs - rhs)?)),
259            // Subtract an integer and a scalar
260            (Value::Integer(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs - rhs)?)),
261            // Subtract two numbers
262            (Value::Quantity(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs - rhs)?)),
263            // Subtract value to an array: `[1,2,3] - 1 = [0,1,2]`.
264            (Value::Array(lhs), rhs) => Ok((lhs - rhs)?),
265            // Subtract two tuples of the same type: (x = 1., y = 2.) - (x = 3., y = 4.)
266            (Value::Tuple(lhs), Value::Tuple(rhs)) => Ok((*lhs - *rhs)?.into()),
267
268            // Boolean difference operator for models
269            (Value::Model(lhs), Value::Model(rhs)) => Ok(Value::Model(
270                lhs.boolean_op(microcad_core::BooleanOp::Subtract, rhs),
271            )),
272            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} - {rhs}"))),
273        }
274    }
275}
276
277/// Rules for operator `*`.
278impl std::ops::Mul for Value {
279    type Output = ValueResult;
280
281    fn mul(self, rhs: Self) -> Self::Output {
282        match (self, rhs) {
283            (Value::Integer(lhs), Value::Model(rhs)) => Ok(Value::Model(
284                Models::from(rhs.multiply(lhs)).to_multiplicity(SrcRef(None)),
285            )),
286            // Multiply two integers
287            (Value::Integer(lhs), Value::Integer(rhs)) => Ok(Value::Integer(lhs * rhs)),
288            // Multiply an integer and a scalar, result is scalar
289            (Value::Integer(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs * rhs)?)),
290            // Multiply a scalar and an integer, result is scalar
291            (Value::Quantity(lhs), Value::Integer(rhs)) => Ok(Value::Quantity((lhs * rhs)?)),
292            // Multiply two scalars
293            (Value::Quantity(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs * rhs)?)),
294            (Value::Array(array), value) | (value, Value::Array(array)) => Ok((array * value)?),
295
296            (Value::Tuple(tuple), value) | (value, Value::Tuple(tuple)) => {
297                Ok((tuple.as_ref().clone() * value)?.into())
298            }
299            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} * {rhs}"))),
300        }
301    }
302}
303
304/// Multiply a Unit with a value. Used for unit bundling: `[1,2,3]mm`.
305///
306/// `[1,2,3]mm` is a shortcut for `[1,2,3] * 1mm`.
307impl std::ops::Mul<Unit> for Value {
308    type Output = ValueResult;
309
310    fn mul(self, unit: Unit) -> Self::Output {
311        match (self, unit.ty()) {
312            (value, Type::Quantity(QuantityType::Scalar)) | (value, Type::Integer) => Ok(value),
313            (Value::Integer(i), Type::Quantity(quantity_type)) => Ok(Value::Quantity(
314                Quantity::new(unit.normalize(i as Scalar), quantity_type),
315            )),
316            (Value::Quantity(quantity), Type::Quantity(quantity_type)) => Ok(Value::Quantity(
317                (quantity * Quantity::new(unit.normalize(1.0), quantity_type))?,
318            )),
319            (Value::Array(array), Type::Quantity(quantity_type)) => {
320                Ok((array * Value::Quantity(Quantity::new(unit.normalize(1.0), quantity_type)))?)
321            }
322            (value, _) => Err(ValueError::CannotAddUnitToValueWithUnit(value.to_string())),
323        }
324    }
325}
326
327/// Rules for operator `/`.
328impl std::ops::Div for Value {
329    type Output = ValueResult;
330
331    fn div(self, rhs: Self) -> Self::Output {
332        match (self, rhs) {
333            // Division with scalar result
334            (Value::Integer(lhs), Value::Integer(rhs)) => {
335                Ok(Value::Quantity((lhs as Scalar / rhs as Scalar).into()))
336            }
337            (Value::Quantity(lhs), Value::Integer(rhs)) => Ok(Value::Quantity((lhs / rhs)?)),
338            (Value::Integer(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs / rhs)?)),
339            (Value::Quantity(lhs), Value::Quantity(rhs)) => Ok(Value::Quantity((lhs / rhs)?)),
340            (Value::Array(array), value) => Ok((array / value)?),
341            (Value::Tuple(tuple), value) => Ok((tuple.as_ref().clone() / value)?.into()),
342            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} / {rhs}"))),
343        }
344    }
345}
346
347/// Rules for operator `|`` (union).
348impl std::ops::BitOr for Value {
349    type Output = ValueResult;
350
351    fn bitor(self, rhs: Self) -> Self::Output {
352        match (self, rhs) {
353            (Value::Model(lhs), Value::Model(rhs)) => Ok(Value::Model(
354                lhs.boolean_op(microcad_core::BooleanOp::Union, rhs),
355            )),
356            (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs | rhs)),
357            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} | {rhs}"))),
358        }
359    }
360}
361
362/// Rules for operator `&` (intersection).
363impl std::ops::BitAnd for Value {
364    type Output = ValueResult;
365
366    fn bitand(self, rhs: Self) -> Self::Output {
367        match (self, rhs) {
368            (Value::Model(lhs), Value::Model(rhs)) => {
369                Ok(Value::Model(lhs.boolean_op(BooleanOp::Intersect, rhs)))
370            }
371            (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs & rhs)),
372            (lhs, rhs) => Err(ValueError::InvalidOperator(format!("{lhs} & {rhs}"))),
373        }
374    }
375}
376
377impl std::fmt::Display for Value {
378    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
379        match self {
380            Value::None => write!(f, crate::invalid_no_ansi!(VALUE)),
381            Value::Integer(n) => write!(f, "{n}"),
382            Value::Quantity(q) => write!(f, "{q}"),
383            Value::Bool(b) => write!(f, "{b}"),
384            Value::String(s) => write!(f, "{s}"),
385            Value::Array(l) => write!(f, "{l}"),
386            Value::Tuple(t) => write!(f, "{t}"),
387            Value::Matrix(m) => write!(f, "{m}"),
388            Value::Model(n) => write!(f, "{n}"),
389            Value::Return(r) => write!(f, "{r}"),
390            Value::ConstExpression(e) => write!(f, "{e}"),
391            Value::Target(target) => write!(f, "{target}"),
392        }
393    }
394}
395
396impl std::fmt::Debug for Value {
397    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398        match self {
399            Value::None => write!(f, crate::invalid!(VALUE)),
400            Value::Integer(n) => write!(f, "{n}"),
401            Value::Quantity(q) => write!(f, "{q:?}"),
402            Value::Bool(b) => write!(f, "{b}"),
403            Value::String(s) => write!(f, "{s:?}"),
404            Value::Array(l) => write!(f, "{l:?}"),
405            Value::Tuple(t) => write!(f, "{t:?}"),
406            Value::Matrix(m) => write!(f, "{m:?}"),
407            Value::Model(n) => write!(f, "\n {n:?}"),
408            Value::Return(r) => write!(f, "->{r:?}"),
409            Value::ConstExpression(e) => write!(f, "{e:?}"),
410            Value::Target(target) => write!(f, "{target:?}"),
411        }
412    }
413}
414
415impl std::hash::Hash for Value {
416    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
417        match self {
418            Value::None => std::mem::discriminant(&Value::None).hash(state),
419            Value::Quantity(quantity) => quantity.hash(state),
420            Value::Bool(b) => b.hash(state),
421            Value::Integer(i) => i.hash(state),
422            Value::String(s) => s.hash(state),
423            Value::Array(array) => array.hash(state),
424            Value::Tuple(tuple) => tuple.hash(state),
425            Value::Matrix(matrix) => matrix.hash(state),
426            Value::Model(model) => model.hash(state),
427            Value::Return(value) => value.hash(state),
428            Value::ConstExpression(expression) => expression.to_string().hash(state), // TODO: Is this correct?
429            Value::Target(target) => target.hash(state),
430        }
431    }
432}
433
434macro_rules! impl_try_from {
435    ($($variant:ident),+ => $ty:ty ) => {
436        impl TryFrom<Value> for $ty {
437            type Error = ValueError;
438
439            fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
440                match value {
441                    $(Value::$variant(v) => Ok(v),)*
442                    value => Err(ValueError::CannotConvert(value.to_string(), stringify!($ty).into())),
443                }
444            }
445        }
446
447        impl TryFrom<&Value> for $ty {
448            type Error = ValueError;
449
450            fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
451                match value {
452                    $(Value::$variant(v) => Ok(v.clone().into()),)*
453                    value => Err(ValueError::CannotConvert(value.to_string(), stringify!($ty).into())),
454                }
455            }
456        }
457    };
458}
459
460impl_try_from!(Integer => i64);
461impl_try_from!(Bool => bool);
462impl_try_from!(String => String);
463
464impl TryFrom<&Value> for Scalar {
465    type Error = ValueError;
466
467    fn try_from(value: &Value) -> Result<Self, Self::Error> {
468        match value {
469            Value::Integer(i) => Ok(*i as Scalar),
470            Value::Quantity(Quantity {
471                value,
472                quantity_type: QuantityType::Scalar,
473            }) => Ok(*value),
474            _ => Err(ValueError::CannotConvert(
475                value.to_string(),
476                "Scalar".into(),
477            )),
478        }
479    }
480}
481
482impl TryFrom<Value> for Scalar {
483    type Error = ValueError;
484
485    fn try_from(value: Value) -> Result<Self, Self::Error> {
486        match value {
487            Value::Integer(i) => Ok(i as Scalar),
488            Value::Quantity(Quantity {
489                value,
490                quantity_type: QuantityType::Scalar,
491            }) => Ok(value),
492            _ => Err(ValueError::CannotConvert(
493                value.to_string(),
494                "Scalar".into(),
495            )),
496        }
497    }
498}
499
500impl TryFrom<&Value> for Angle {
501    type Error = ValueError;
502
503    fn try_from(value: &Value) -> Result<Self, Self::Error> {
504        match value {
505            Value::Quantity(Quantity {
506                value,
507                quantity_type: QuantityType::Angle,
508            }) => Ok(cgmath::Rad(*value)),
509            _ => Err(ValueError::CannotConvert(value.to_string(), "Angle".into())),
510        }
511    }
512}
513
514impl TryFrom<&Value> for Length {
515    type Error = ValueError;
516
517    fn try_from(value: &Value) -> Result<Self, Self::Error> {
518        match value {
519            Value::Quantity(Quantity {
520                value,
521                quantity_type: QuantityType::Length,
522            }) => Ok(Length(*value)),
523            _ => Err(ValueError::CannotConvert(
524                value.to_string(),
525                "Length".into(),
526            )),
527        }
528    }
529}
530
531impl TryFrom<&Value> for Size2 {
532    type Error = ValueError;
533
534    fn try_from(value: &Value) -> Result<Self, Self::Error> {
535        match value {
536            Value::Tuple(tuple) => Ok(tuple.as_ref().try_into()?),
537            _ => Err(ValueError::CannotConvert(value.to_string(), "Size2".into())),
538        }
539    }
540}
541
542impl TryFrom<&Value> for Mat3 {
543    type Error = ValueError;
544
545    fn try_from(value: &Value) -> Result<Self, Self::Error> {
546        if let Value::Matrix(m) = value {
547            if let Matrix::Matrix3(matrix3) = m.as_ref() {
548                return Ok(*matrix3);
549            }
550        }
551
552        Err(ValueError::CannotConvert(
553            value.to_string(),
554            "Matrix3".into(),
555        ))
556    }
557}
558
559impl From<f32> for Value {
560    fn from(f: f32) -> Self {
561        Value::Quantity((f as Scalar).into())
562    }
563}
564
565impl From<Scalar> for Value {
566    fn from(scalar: Scalar) -> Self {
567        Value::Quantity(scalar.into())
568    }
569}
570
571impl From<Length> for Value {
572    fn from(length: Length) -> Self {
573        Value::Quantity(length.into())
574    }
575}
576
577impl From<Size2> for Value {
578    fn from(value: Size2) -> Self {
579        Self::Tuple(Box::new(value.into()))
580    }
581}
582
583impl From<Quantity> for Value {
584    fn from(qty: Quantity) -> Self {
585        Self::Quantity(qty)
586    }
587}
588
589impl From<String> for Value {
590    fn from(value: String) -> Self {
591        Self::String(value)
592    }
593}
594
595impl From<Color> for Value {
596    fn from(color: Color) -> Self {
597        Self::Tuple(Box::new(color.into()))
598    }
599}
600
601impl FromIterator<Value> for Value {
602    fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
603        Self::Array(iter.into_iter().collect())
604    }
605}
606
607impl From<Model> for Value {
608    fn from(model: Model) -> Self {
609        Self::Model(model)
610    }
611}
612
613impl AttributesAccess for Value {
614    fn get_attributes_by_id(&self, id: &Identifier) -> Vec<crate::model::Attribute> {
615        match self {
616            Value::Model(model) => model.get_attributes_by_id(id),
617            _ => Vec::default(),
618        }
619    }
620}
621
622#[cfg(test)]
623fn integer(value: i64) -> Value {
624    Value::Integer(value)
625}
626
627#[cfg(test)]
628fn scalar(value: f64) -> Value {
629    Value::Quantity(Quantity::new(value, QuantityType::Scalar))
630}
631
632#[cfg(test)]
633fn check(result: ValueResult, value: Value) {
634    let result = result.expect("error result");
635    assert_eq!(result, value);
636}
637
638#[test]
639fn test_value_integer() {
640    let u = || integer(2);
641    let v = || integer(5);
642    let w = || scalar(5.0);
643
644    // symmetric operations
645    check(u() + v(), integer(2 + 5));
646    check(u() - v(), integer(2 - 5));
647    check(u() * v(), integer(2 * 5));
648    check(u() / v(), scalar(2.0 / 5.0));
649    check(-u(), integer(-2));
650
651    // asymmetric operations
652    check(u() + w(), scalar(2 as Scalar + 5.0));
653    check(u() - w(), scalar(2 as Scalar - 5.0));
654    check(u() * w(), scalar(2 as Scalar * 5.0));
655    check(u() / w(), scalar(2.0 / 5.0));
656}
657
658#[test]
659fn test_value_scalar() {
660    let u = || scalar(2.0);
661    let v = || scalar(5.0);
662    let w = || integer(5);
663
664    // symmetric operations
665    check(u() + v(), scalar(2.0 + 5.0));
666    check(u() - v(), scalar(2.0 - 5.0));
667    check(u() * v(), scalar(2.0 * 5.0));
668    check(u() / v(), scalar(2.0 / 5.0));
669    check(-u(), scalar(-2.0));
670
671    // asymmetric operations
672    check(u() + w(), scalar(2.0 + 5.0));
673    check(u() - w(), scalar(2.0 - 5.0));
674    check(u() * w(), scalar(2.0 * 5.0));
675    check(u() / w(), scalar(2.0 / 5.0));
676}