about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/builtins/mod.rs11
-rw-r--r--tvix/eval/src/value/mod.rs21
-rw-r--r--tvix/eval/src/vm.rs41
3 files changed, 47 insertions, 26 deletions
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index e7eb4e8bf953..336c320e2f00 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -3,7 +3,7 @@
 //! See //tvix/eval/docs/builtins.md for a some context on the
 //! available builtins in Nix.
 
-use std::cmp;
+use std::cmp::{self, Ordering};
 use std::collections::{BTreeMap, HashMap, HashSet};
 use std::path::PathBuf;
 
@@ -16,7 +16,7 @@ use crate::{
     vm::VM,
 };
 
-use crate::{arithmetic_op, cmp_op};
+use crate::arithmetic_op;
 
 use self::versions::{VersionPart, VersionPartsIter};
 
@@ -407,7 +407,12 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new(
             "lessThan",
             &[false, false],
-            |args: Vec<Value>, vm: &mut VM| cmp_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, <),
+            |args: Vec<Value>, vm: &mut VM| {
+                Ok(Value::Bool(matches!(
+                    args[0].force(vm)?.nix_cmp(&*args[1].force(vm)?)?,
+                    Some(Ordering::Less)
+                )))
+            },
         ),
         Builtin::new("listToAttrs", &[true], |args: Vec<Value>, vm: &mut VM| {
             let list = args[0].to_list()?;
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index 14b1a5c6122e..82be292819ae 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -1,5 +1,6 @@
 //! This module implements the backing representation of runtime
 //! values in the Nix language.
+use std::cmp::Ordering;
 use std::ops::Deref;
 use std::path::PathBuf;
 use std::rc::Rc;
@@ -348,6 +349,26 @@ impl Value {
         }
     }
 
+    /// Compare `self` against other using (fallible) Nix ordering semantics.
+    pub fn nix_cmp(&self, other: &Self) -> Result<Option<Ordering>, ErrorKind> {
+        match (self, other) {
+            // same types
+            (Value::Integer(i1), Value::Integer(i2)) => Ok(i1.partial_cmp(i2)),
+            (Value::Float(f1), Value::Float(f2)) => Ok(f1.partial_cmp(f2)),
+            (Value::String(s1), Value::String(s2)) => Ok(s1.partial_cmp(s2)),
+
+            // different types
+            (Value::Integer(i1), Value::Float(f2)) => Ok((*i1 as f64).partial_cmp(f2)),
+            (Value::Float(f1), Value::Integer(i2)) => Ok(f1.partial_cmp(&(*i2 as f64))),
+
+            // unsupported types
+            (lhs, rhs) => Err(ErrorKind::Incomparable {
+                lhs: lhs.type_of(),
+                rhs: rhs.type_of(),
+            }),
+        }
+    }
+
     /// Ensure `self` is forced if it is a thunk, and return a reference to the resulting value.
     pub(crate) fn force(&self, vm: &mut VM) -> Result<ForceResult, ErrorKind> {
         match self {
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index 08c913ed2d6d..6a5c56dc03bf 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -2,7 +2,7 @@
 //! Tvix bytecode.
 
 use serde_json::json;
-use std::{ops::DerefMut, path::PathBuf, rc::Rc};
+use std::{cmp::Ordering, ops::DerefMut, path::PathBuf, rc::Rc};
 
 use crate::{
     chunk::Chunk,
@@ -123,31 +123,26 @@ macro_rules! cmp_op {
     ( $self:ident, $op:tt ) => {{
         let b = $self.pop();
         let a = $self.pop();
-        let result = fallible!($self, cmp_op!(&a, &b, $op));
+        let ordering = fallible!($self, a.nix_cmp(&b));
+        let result = Value::Bool(cmp_op!(@order $op ordering));
         $self.push(result);
     }};
 
-    ( $a:expr, $b:expr, $op:tt ) => {
-        // Comparable (in terms of ordering) values are numbers and
-        // strings. Numbers need to be coerced similarly to arithmetic
-        // ops if mixed types are encountered.
-        match ($a, $b) {
-            // same types
-            (Value::Integer(i1), Value::Integer(i2)) => Ok(Value::Bool(i1 $op i2)),
-            (Value::Float(f1), Value::Float(f2)) => Ok(Value::Bool(f1 $op f2)),
-            (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 $op s2)),
-
-            // different types
-            (Value::Integer(i1), Value::Float(f2)) => Ok(Value::Bool((*i1 as f64) $op *f2)),
-            (Value::Float(f1), Value::Integer(i2)) => Ok(Value::Bool(*f1 $op (*i2 as f64))),
-
-            // unsupported types
-            (lhs, rhs) => Err(ErrorKind::Incomparable {
-                lhs: lhs.type_of(),
-                rhs: rhs.type_of(),
-            }),
-        }
-    }
+    (@order < $ordering:expr) => {
+        $ordering == Some(Ordering::Less)
+    };
+
+    (@order > $ordering:expr) => {
+        $ordering == Some(Ordering::Greater)
+    };
+
+    (@order <= $ordering:expr) => {
+        !matches!($ordering, None | Some(Ordering::Greater))
+    };
+
+    (@order >= $ordering:expr) => {
+        !matches!($ordering, None | Some(Ordering::Less))
+    };
 }
 
 impl<'o> VM<'o> {