about summary refs log tree commit diff
path: root/tvix/eval
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2022-10-23T17·24-0400
committergrfn <grfn@gws.fyi>2022-10-24T13·44+0000
commit5bd0e723c13b8a41bfba442bd8ef5351e31099e6 (patch)
treedc258cedc3fbe2f8e8c51e8e40fdd0aa93bf8618 /tvix/eval
parent7b3bda9e089fe9de2d28a28f64ef8532abb3ac9c (diff)
refactor(tvix/eval): Implement value comparison with a method r/5193
Rather than implementing all of the interesting semantics of value
comparison with a macro bound to the VM, implement the bulk of the logic
with a method on Value itself that returns an Ordering, and then use the
macro to implement the comparison against that Ordering. This has no
functional change, but paves the way to implementing lexicographic
comparison of list values, which is supported in the latest version of
upstream nix.

Change-Id: I8af1a020b41577021af5939f5edc160c407d4a9e
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7069
Autosubmit: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
Diffstat (limited to 'tvix/eval')
-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> {