refactor(tvix/eval): Implement value comparison with a method
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>
This commit is contained in:
parent
7b3bda9e08
commit
5bd0e723c1
3 changed files with 45 additions and 24 deletions
|
@ -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()?;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
(@order < $ordering:expr) => {
|
||||
$ordering == Some(Ordering::Less)
|
||||
};
|
||||
|
||||
// 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))),
|
||||
(@order > $ordering:expr) => {
|
||||
$ordering == Some(Ordering::Greater)
|
||||
};
|
||||
|
||||
// unsupported types
|
||||
(lhs, rhs) => Err(ErrorKind::Incomparable {
|
||||
lhs: lhs.type_of(),
|
||||
rhs: rhs.type_of(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
(@order <= $ordering:expr) => {
|
||||
!matches!($ordering, None | Some(Ordering::Greater))
|
||||
};
|
||||
|
||||
(@order >= $ordering:expr) => {
|
||||
!matches!($ordering, None | Some(Ordering::Less))
|
||||
};
|
||||
}
|
||||
|
||||
impl<'o> VM<'o> {
|
||||
|
|
Loading…
Reference in a new issue