From 4eafaae9e66c66a7765ff3854890039d7536a9b9 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 11 Aug 2022 11:37:04 +0300 Subject: feat(tvix/eval): implement binary comparison operators This is accomplished by simply delegating to the Rust implementations of (Partial)Ord and (Partial)Eq, which are implemented for Value and underlying wrapper types to behave like they do in Nix. To ease the implementation overhead, a new comparison operator macro has been added to the VM module. Incomparable types will raise a new error variant when a comparison is attempted, containing both supplied types. This mimics the information carried in the error thrown by C++ Nix. Change-Id: Ia19634d69119d40722f3ca672387bc3a80096998 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6143 Reviewed-by: sterni Tested-by: BuildkiteCI --- tvix/eval/src/compiler.rs | 4 ++ tvix/eval/src/errors.rs | 5 +++ tvix/eval/src/opcode.rs | 4 ++ .../tests/tvix_tests/eval-okay-cmp-float-false.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-float-false.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-float-true.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-float-true.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-int-false.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-int-false.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-int-true.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-int-true.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-num-false.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-num-false.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-num-true.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-num-true.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-str-false.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-str-false.nix | 8 ++++ .../tests/tvix_tests/eval-okay-cmp-str-true.exp | 1 + .../tests/tvix_tests/eval-okay-cmp-str-true.nix | 8 ++++ tvix/eval/src/vm.rs | 43 ++++++++++++++++++---- 20 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix (limited to 'tvix/eval/src') diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs index d48490dde7a6..6054bd3fdaff 100644 --- a/tvix/eval/src/compiler.rs +++ b/tvix/eval/src/compiler.rs @@ -137,6 +137,10 @@ impl Compiler { BinOpKind::Div => self.chunk.add_op(OpCode::OpDiv), BinOpKind::Update => self.chunk.add_op(OpCode::OpAttrsUpdate), BinOpKind::Equal => self.chunk.add_op(OpCode::OpEqual), + BinOpKind::Less => self.chunk.add_op(OpCode::OpLess), + BinOpKind::LessOrEq => self.chunk.add_op(OpCode::OpLessOrEq), + BinOpKind::More => self.chunk.add_op(OpCode::OpMore), + BinOpKind::MoreOrEq => self.chunk.add_op(OpCode::OpMoreOrEq), BinOpKind::NotEqual => { self.chunk.add_op(OpCode::OpEqual); diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index fb9f3b6ec589..f7f64f4e687a 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -14,6 +14,11 @@ pub enum Error { expected: &'static str, actual: &'static str, }, + + Incomparable { + lhs: &'static str, + rhs: &'static str, + }, } impl Display for Error { diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs index f682cfc8b83f..6800e8411f5c 100644 --- a/tvix/eval/src/opcode.rs +++ b/tvix/eval/src/opcode.rs @@ -29,6 +29,10 @@ pub enum OpCode { // Logical binary operators OpEqual, + OpLess, + OpLessOrEq, + OpMore, + OpMoreOrEq, // Attribute sets OpAttrs(usize), diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp new file mode 100644 index 000000000000..95a0e7378b5e --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp @@ -0,0 +1 @@ +{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix new file mode 100644 index 000000000000..2b511f56eecb --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix @@ -0,0 +1,8 @@ +{ + eq = 6.9 == 4.2; + ne = 4.2 != 4.2; + lt = 2.5 < 1.5; + le = 2.5 <= 1.5; + gt = 1.5 > 2.5; + ge = 1.5 >= 2.5; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp new file mode 100644 index 000000000000..9160829dde78 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp @@ -0,0 +1 @@ +{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix new file mode 100644 index 000000000000..c505a85b1f3c --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix @@ -0,0 +1,8 @@ +{ + eq = 4.2 == 4.2; + ne = 6.9 != 4.2; + lt = 1.5 < 2.5; + le = 2.5 <= 2.5; + gt = 2.3 > 1.2; + ge = 2.3 >= 2.3; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp new file mode 100644 index 000000000000..95a0e7378b5e --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp @@ -0,0 +1 @@ +{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix new file mode 100644 index 000000000000..7d6b30419fac --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix @@ -0,0 +1,8 @@ +{ + eq = 69 == 42; + ne = 42 != 42; + lt = 2 < 1; + le = 2 <= 1; + gt = 1 > 2; + ge = 1 >= 2; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp new file mode 100644 index 000000000000..9160829dde78 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp @@ -0,0 +1 @@ +{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix new file mode 100644 index 000000000000..0bf474e53fd0 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix @@ -0,0 +1,8 @@ +{ + eq = 42 == 42; + ne = 69 != 42; + lt = 1 < 2; + le = 2 <= 2; + gt = 2 > 1; + ge = 2 >= 2; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp new file mode 100644 index 000000000000..95a0e7378b5e --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp @@ -0,0 +1 @@ +{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix new file mode 100644 index 000000000000..61b206c03376 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix @@ -0,0 +1,8 @@ +{ + eq = 6.9 == 4; + ne = 4.0 != 4; + lt = 2.5 < 1; + le = 2 <= 1.5; + gt = 1 > 1.1; + ge = 1.5 >= 2; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp new file mode 100644 index 000000000000..9160829dde78 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp @@ -0,0 +1 @@ +{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix new file mode 100644 index 000000000000..ad77074710ca --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix @@ -0,0 +1,8 @@ +{ + eq = 42.0 == 42; + ne = 6.9 != 4; + lt = 1.5 < 2; + le = 2.0 <= 2.0; + gt = 1.1 > 1; + ge = 2.3 >= 2.3; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp new file mode 100644 index 000000000000..95a0e7378b5e --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp @@ -0,0 +1 @@ +{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix new file mode 100644 index 000000000000..b5773a21d374 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix @@ -0,0 +1,8 @@ +{ + eq = "test" == "not test"; + ne = "test" != "test"; + lt = "bcd" < "abc"; + le = "bcd" <= "abc"; + gt = "abc" > "bcd"; + ge = "abc" >= "bcd"; +} diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp new file mode 100644 index 000000000000..9160829dde78 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp @@ -0,0 +1 @@ +{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; } diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix new file mode 100644 index 000000000000..172d2237e9e2 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix @@ -0,0 +1,8 @@ +{ + eq = "test" == "test"; + ne = "test" != "not test"; + lt = "abc" < "bcd"; + le = "bcd" <= "bcd"; + gt = "bcd" > "abc"; + ge = "bcd" >= "bcd"; +} diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index db2f76d73145..30f2c5a4a414 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -18,15 +18,14 @@ pub struct VM { macro_rules! arithmetic_op { ( $self:ident, $op:tt ) => {{ - let result = arithmetic_op!($self.pop(), $self.pop(), $op); + let b = $self.pop(); + let a = $self.pop(); + let result = arithmetic_op!(a, b, $op); $self.push(result); }}; - ( $b:expr, $a:expr, $op:tt ) => {{ - let b = $b; - let a = $a; - - match (a, b) { + ( $a:ident, $b:ident, $op:tt ) => {{ + match ($a, $b) { (Value::Integer(i1), Value::Integer(i2)) => Value::Integer(i1 $op i2), (Value::Float(f1), Value::Float(f2)) => Value::Float(f1 $op f2), (Value::Integer(i1), Value::Float(f2)) => Value::Float(i1 as f64 $op f2), @@ -44,6 +43,31 @@ macro_rules! arithmetic_op { }}; } +macro_rules! cmp_op { + ( $self:ident, $op:tt ) => {{ + let b = $self.pop(); + let a = $self.pop(); + + // Comparable (in terms of ordering) values are numbers and + // strings. Numbers need to be coerced similarly to arithmetic + // ops if mixed types are encountered. + let result = match (a, b) { + (Value::Integer(i1), Value::Integer(i2)) => i1 $op i2, + (Value::Float(f1), Value::Float(f2)) => f1 $op f2, + (Value::Integer(i1), Value::Float(f2)) => (i1 as f64) $op f2, + (Value::Float(f1), Value::Integer(i2)) => f1 $op (i2 as f64), + (Value::String(s1), Value::String(s2)) => s1 $op s2, + + (lhs, rhs) => return Err(Error::Incomparable { + lhs: lhs.type_of(), + rhs: rhs.type_of(), + }), + }; + + $self.push(Value::Bool(result)); + }}; +} + impl VM { fn inc_ip(&mut self) -> OpCode { let op = self.chunk.code[self.ip]; @@ -74,7 +98,7 @@ impl VM { let result = if let (Value::String(s1), Value::String(s2)) = (&a, &b) { Value::String(s1.concat(s2)) } else { - arithmetic_op!(b, a, +) + arithmetic_op!(a, b, +) }; self.push(result) @@ -107,6 +131,11 @@ impl VM { self.push(Value::Bool(v1 == v2)) } + OpCode::OpLess => cmp_op!(self, <), + OpCode::OpLessOrEq => cmp_op!(self, <=), + OpCode::OpMore => cmp_op!(self, >), + OpCode::OpMoreOrEq => cmp_op!(self, >=), + OpCode::OpNull => self.push(Value::Null), OpCode::OpTrue => self.push(Value::Bool(true)), OpCode::OpFalse => self.push(Value::Bool(false)), -- cgit 1.4.1