Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce some APIs requiring total ordering #612

Merged
merged 2 commits into from
Aug 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benches/benches/benchmarks/aoc_2020_1a.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn aoc_2020_1a(b: &mut Criterion) {
struct NoSolution;

fn part1(v, target) {
v.sort::<i64>();
v.sort();

let a = 0;
let b = v.len() - 1;
Expand All @@ -48,7 +48,7 @@ fn aoc_2020_1a(b: &mut Criterion) {
}

fn part2(v, target) {
v.sort::<i64>();
v.sort();

let a = 0;
let c = v.len() - 1;
Expand Down
2 changes: 1 addition & 1 deletion benches/benches/benchmarks/aoc_2020_1b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn aoc_2020_1b(b: &mut Criterion) {
}

pub fn main(lines) {
lines.sort::<i64>();
lines.sort();
(filter_inner(iter::all_pairs(lines)), filter_inner(iter::all_triples(lines)))
}
};
Expand Down
133 changes: 91 additions & 42 deletions crates/rune/src/modules/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,101 @@

use core::cmp::Ordering;

use crate::runtime::Protocol;
use crate as rune;
use crate::runtime::{Protocol, Value, VmResult};
use crate::{ContextError, Module};

/// Construct the `std::cmp` module.
pub fn module() -> Result<Module, ContextError> {
let mut module = Module::with_crate_item("std", ["cmp"]);

let ty = module.ty::<Ordering>()?.docs([
"An `Ordering` is the result of a comparison between two values.",
"",
"# Examples",
"",
"```",
"use std::cmp::Ordering;",
"",
"let result = 1.cmp(2);",
"assert_eq!(Ordering::Less, result);",
"",
"let result = 1.cmp(1);",
"assert_eq!(Ordering::Equal, result);",
"",
"let result = 2.cmp(1);",
"assert_eq!(Ordering::Greater, result);",
"```",
]);

let mut ty = ty.make_enum(&["Less", "Equal", "Greater"])?;

ty.variant_mut(0)?
.make_empty()?
.constructor(|| Ordering::Less)?
.docs(["An ordering where a compared value is less than another."]);

ty.variant_mut(1)?
.make_empty()?
.constructor(|| Ordering::Equal)?
.docs(["An ordering where a compared value is equal to another."]);

ty.variant_mut(2)?
.make_empty()?
.constructor(|| Ordering::Greater)?
.docs(["An ordering where a compared value is greater than another."]);

module.associated_function(Protocol::PARTIAL_EQ, |lhs: Ordering, rhs: Ordering| {
let mut m = Module::with_crate_item("std", ["cmp"]);

{
let ty = m.ty::<Ordering>()?.docs([
"An `Ordering` is the result of a comparison between two values.",
"",
"# Examples",
"",
"```",
"use std::cmp::Ordering;",
"",
"let result = 1.cmp(2);",
"assert_eq!(Ordering::Less, result);",
"",
"let result = 1.cmp(1);",
"assert_eq!(Ordering::Equal, result);",
"",
"let result = 2.cmp(1);",
"assert_eq!(Ordering::Greater, result);",
"```",
]);

let mut ty = ty.make_enum(&["Less", "Equal", "Greater"])?;

ty.variant_mut(0)?
.make_empty()?
.constructor(|| Ordering::Less)?
.docs(["An ordering where a compared value is less than another."]);

ty.variant_mut(1)?
.make_empty()?
.constructor(|| Ordering::Equal)?
.docs(["An ordering where a compared value is equal to another."]);

ty.variant_mut(2)?
.make_empty()?
.constructor(|| Ordering::Greater)?
.docs(["An ordering where a compared value is greater than another."]);
}

m.associated_function(Protocol::PARTIAL_EQ, |lhs: Ordering, rhs: Ordering| {
lhs == rhs
})?;
module.associated_function(Protocol::EQ, |lhs: Ordering, rhs: Ordering| lhs == rhs)?;
Ok(module)
m.associated_function(Protocol::EQ, |lhs: Ordering, rhs: Ordering| lhs == rhs)?;
m.function_meta(min)?;
m.function_meta(max)?;
Ok(m)
}

/// Compares and returns the maximum of two values.
///
/// Returns the second argument if the comparison determines them to be equal.
///
/// Internally uses the [`CMP`] protocol.
///
/// # Examples
///
/// ```rune
/// use std::cmp::max;
///
/// assert_eq!(max(1, 2), 2);
/// assert_eq!(max(2, 2), 2);
/// ```
#[rune::function]
fn max(v1: Value, v2: Value) -> VmResult<Value> {
VmResult::Ok(match vm_try!(Value::cmp(&v1, &v2)) {
Ordering::Less | Ordering::Equal => v2,
Ordering::Greater => v1,
})
}

/// Compares and returns the minimum of two values.
///
/// Returns the first argument if the comparison determines them to be equal.
///
/// Internally uses the [`CMP`] protocol.
///
/// # Examples
///
/// ```rune
/// use std::cmp::min;
///
/// assert_eq!(min(1, 2), 1);
/// assert_eq!(min(2, 2), 2);
/// ```
#[rune::function]
fn min(v1: Value, v2: Value) -> VmResult<Value> {
VmResult::Ok(match vm_try!(Value::cmp(&v1, &v2)) {
Ordering::Less | Ordering::Equal => v1,
Ordering::Greater => v2,
})
}
4 changes: 2 additions & 2 deletions crates/rune/src/modules/collections/hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl HashMap {
/// ]);
///
/// let keys = map.keys().collect::<Vec>();
/// keys.sort::<String>();
/// keys.sort();
/// assert_eq!(keys, ["a", "b", "c"]);
/// ```
///
Expand Down Expand Up @@ -275,7 +275,7 @@ impl HashMap {
/// ]);
///
/// let values = map.values().collect::<Vec>();
/// values.sort::<i64>();
/// values.sort();
/// assert_eq!(values, [1, 2, 3]);
/// ```
///
Expand Down
4 changes: 2 additions & 2 deletions crates/rune/src/modules/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn get(object: &Object, key: &str) -> Option<Value> {
/// vec.push(key);
/// }
///
/// vec.sort::<String>();
/// vec.sort();
/// assert_eq!(vec, ["a", "b", "c"]);
/// ```
#[rune::function(instance)]
Expand All @@ -116,7 +116,7 @@ fn keys(object: &Object) -> Iterator {
/// vec.push(key);
/// }
///
/// vec.sort::<i64>();
/// vec.sort();
/// assert_eq!(vec, [1, 2, 3]);
/// ```
#[rune::function(instance)]
Expand Down
104 changes: 37 additions & 67 deletions crates/rune/src/modules/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use core::cmp::Ordering;

use crate as rune;
use crate::no_std::prelude::*;
use crate::runtime::{
EnvProtocolCaller, FromValue, Function, Iterator, Protocol, Ref, Value, Vec, VmErrorKind,
VmResult,
Expand Down Expand Up @@ -45,8 +44,7 @@ pub fn module() -> Result<Module, ContextError> {
m.function_meta(insert)?;
m.function_meta(clone)?;
m.function_meta(sort_by)?;
m.function_meta(sort_int)?;
m.function_meta(sort_string)?;
m.function_meta(sort)?;
m.associated_function(Protocol::INTO_ITER, Vec::iter_ref)?;
m.associated_function(Protocol::INDEX_SET, Vec::set)?;
m.associated_function(Protocol::PARTIAL_EQ, partial_eq)?;
Expand Down Expand Up @@ -263,75 +261,47 @@ fn sort_by(vec: &mut Vec, comparator: &Function) -> VmResult<()> {
}
}

/// Sort a vector of integers.
/// Sort the vector.
///
/// Errors if any contained type is not an integer.
#[rune::function(instance, path = sort::<i64>)]
fn sort_int(vec: &mut Vec) -> VmResult<()> {
let mut err = None;

vec.sort_by(|a, b| {
let result = (|| {
if let (Value::Integer(a), Value::Integer(b)) = (a, b) {
return VmResult::Ok(a.cmp(b));
};

let v = match (a, b) {
(Value::String(..), b) => b,
(a, _) => a,
};

VmResult::expected::<i64>(vm_try!(v.type_info()))
})();

match result {
VmResult::Ok(cmp) => cmp,
VmResult::Err(e) => {
if err.is_none() {
err = Some(e);
}

// NB: fall back to sorting by address.
(a as *const _ as usize).cmp(&(b as *const _ as usize))
}
}
});

if let Some(err) = err {
return VmResult::Err(err);
}

VmResult::Ok(())
}

/// Sort a vector of strings.
/// This require all elements to be of the same type, and implement total
/// ordering per the [`CMP`] protocol.
///
/// # Panics
///
/// If any elements present are not comparable, this method will panic.
///
/// This will panic because a tuple and a string is not comparable:
///
/// ```rune,should_panic
/// let values = [(3, 1), "hello"];
/// values.sort();
/// ```
///
/// This too will panic because floating point values do not have a total
/// ordering:
///
/// ```rune,should_panic
/// let values = [1.0, 2.0];
/// values.sort();
/// ```
///
/// Errors if any contained type is not an integer.
#[rune::function(instance, path = sort::<String>)]
fn sort_string(vec: &mut Vec) -> VmResult<()> {
/// # Examples
///
/// ```rune
/// let values = [3, 2, 1];
/// values.sort();
/// assert_eq!(values, [1, 2, 3]);
///
/// let values = [(3, 1), (2, 1), (1, 1)];
/// values.sort();
/// assert_eq!(values, [(1, 1), (2, 1), (3, 1)]);
/// ```
#[rune::function(instance)]
fn sort(vec: &mut Vec) -> VmResult<()> {
let mut err = None;

vec.sort_by(|a, b| {
let result = (|| {
match (a, b) {
(Value::String(a), Value::String(b)) => {
let a = vm_try!(a.borrow_ref());
let b = vm_try!(b.borrow_ref());
return VmResult::Ok(a.cmp(&b));
}
(Value::StaticString(a), Value::StaticString(b)) => {
return VmResult::Ok(a.cmp(b));
}
_ => {}
}

let v = match (a, b) {
(Value::String(..) | Value::StaticString(..), b) => b,
(a, _) => a,
};

VmResult::expected::<String>(vm_try!(v.type_info()))
})();
let result: VmResult<Ordering> = Value::cmp(a, b);

match result {
VmResult::Ok(cmp) => cmp,
Expand Down
6 changes: 3 additions & 3 deletions crates/rune/src/tests/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ fn test_sort() {
let values: Vec<i64> = rune! {
pub fn main() {
let vec = [4, 3, 2, 1];
vec.sort::<i64>();
vec.sort();
vec
}
};
Expand All @@ -105,7 +105,7 @@ fn test_sort() {
let values: Vec<i64> = rune! {
pub fn main() {
let vec = [4, 3, 2, 1];
Vec::sort::<i64>(vec);
Vec::sort(vec);
vec
}
};
Expand All @@ -114,7 +114,7 @@ fn test_sort() {
let values: Vec<i64> = rune! {
pub fn main() {
let vec = [4, 3, 2, 1];
let f = Vec::sort::<i64>;
let f = Vec::sort;
f(vec);
vec
}
Expand Down