about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-11T08·37+0300
committertazjin <tazjin@tvl.su>2022-08-25T12·07+0000
commit4eafaae9e66c66a7765ff3854890039d7536a9b9 (patch)
tree2a2e77a1963c9e458bb38ac164e327b5d6541284
parent5a15ee3f5dec9d3bb50283fb6219b489bdd7205d (diff)
feat(tvix/eval): implement binary comparison operators r/4478
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 <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
-rw-r--r--tvix/eval/src/compiler.rs4
-rw-r--r--tvix/eval/src/errors.rs5
-rw-r--r--tvix/eval/src/opcode.rs4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix8
-rw-r--r--tvix/eval/src/vm.rs43
20 files changed, 121 insertions, 7 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index d48490dde7..6054bd3fda 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 fb9f3b6ec5..f7f64f4e68 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 f682cfc8b8..6800e8411f 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 0000000000..95a0e7378b
--- /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 0000000000..2b511f56ee
--- /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 0000000000..9160829dde
--- /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 0000000000..c505a85b1f
--- /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 0000000000..95a0e7378b
--- /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 0000000000..7d6b30419f
--- /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 0000000000..9160829dde
--- /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 0000000000..0bf474e53f
--- /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 0000000000..95a0e7378b
--- /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 0000000000..61b206c033
--- /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 0000000000..9160829dde
--- /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 0000000000..ad77074710
--- /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 0000000000..95a0e7378b
--- /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 0000000000..b5773a21d3
--- /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 0000000000..9160829dde
--- /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 0000000000..172d2237e9
--- /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 db2f76d731..30f2c5a4a4 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)),