use lex_ast::canonicalize_program;
use lex_bytecode::vm::Vm;
use lex_bytecode::Value;
use lex_runtime::{DefaultHandler, Policy};
use lex_syntax::parse_source;
fn run(src: &str, entry: &str, args: Vec<Value>) -> Value {
let prog = parse_source(src).expect("parses");
let stages = canonicalize_program(&prog);
if let Err(errs) = lex_types::check_program(&stages) {
panic!("type errors:\n{errs:#?}");
}
let bc = lex_bytecode::compile_program(&stages);
let handler = DefaultHandler::new(Policy::pure());
let mut vm = Vm::with_handler(&bc, Box::new(handler));
vm.call(entry, args).unwrap_or_else(|e| panic!("call {entry}: {e:?}"))
}
#[test]
fn sort_by_int_ascending() {
let src = r#"
import "std.list" as list
fn r(xs :: List[Int]) -> List[Int] {
list.sort_by(xs, fn(x :: Int) -> Int { x })
}
"#;
let xs = Value::List(vec![
Value::Int(3), Value::Int(1), Value::Int(4), Value::Int(1),
Value::Int(5), Value::Int(9), Value::Int(2), Value::Int(6),
]);
let out = run(src, "r", vec![xs]);
assert_eq!(
out,
Value::List(vec![
Value::Int(1), Value::Int(1), Value::Int(2), Value::Int(3),
Value::Int(4), Value::Int(5), Value::Int(6), Value::Int(9),
]),
);
}
#[test]
fn sort_by_int_descending_via_negated_key() {
let src = r#"
import "std.list" as list
fn r(xs :: List[Int]) -> List[Int] {
list.sort_by(xs, fn(x :: Int) -> Int { 0 - x })
}
"#;
let xs = Value::List(vec![Value::Int(2), Value::Int(7), Value::Int(1), Value::Int(4)]);
assert_eq!(
run(src, "r", vec![xs]),
Value::List(vec![Value::Int(7), Value::Int(4), Value::Int(2), Value::Int(1)]),
);
}
#[test]
fn sort_by_str_lexicographic() {
let src = r#"
import "std.list" as list
fn r(xs :: List[Str]) -> List[Str] {
list.sort_by(xs, fn(s :: Str) -> Str { s })
}
"#;
let xs = Value::List(vec![
Value::Str("banana".into()),
Value::Str("apple".into()),
Value::Str("cherry".into()),
]);
assert_eq!(
run(src, "r", vec![xs]),
Value::List(vec![
Value::Str("apple".into()),
Value::Str("banana".into()),
Value::Str("cherry".into()),
]),
);
}
#[test]
fn sort_by_record_field() {
let src = r#"
import "std.list" as list
type Order = { name :: Str, qty :: Int }
fn r(xs :: List[Order]) -> List[Order] {
list.sort_by(xs, fn(o :: Order) -> Int { o.qty })
}
"#;
let mk = |name: &str, qty: i64| {
let mut fields = indexmap::IndexMap::new();
fields.insert("name".into(), Value::Str(name.into()));
fields.insert("qty".into(), Value::Int(qty));
Value::Record(fields)
};
let xs = Value::List(vec![mk("bob", 5), mk("alice", 2), mk("carl", 3)]);
let Value::List(out) = run(src, "r", vec![xs]) else { panic!() };
let qtys: Vec<i64> = out
.iter()
.map(|v| match v {
Value::Record(f) => match f.get("qty") {
Some(Value::Int(n)) => *n,
_ => panic!(),
},
_ => panic!(),
})
.collect();
assert_eq!(qtys, vec![2, 3, 5]);
}
#[test]
fn sort_by_is_stable_for_equal_keys() {
let src = r#"
import "std.list" as list
type R = { k :: Int, tag :: Str }
fn r(xs :: List[R]) -> List[R] {
list.sort_by(xs, fn(rec :: R) -> Int { rec.k })
}
"#;
let mk = |k: i64, tag: &str| {
let mut fields = indexmap::IndexMap::new();
fields.insert("k".into(), Value::Int(k));
fields.insert("tag".into(), Value::Str(tag.into()));
Value::Record(fields)
};
let xs = Value::List(vec![
mk(1, "a"), mk(2, "b"), mk(1, "c"), mk(2, "d"), mk(1, "e"),
]);
let Value::List(out) = run(src, "r", vec![xs]) else { panic!() };
let tags: Vec<String> = out
.iter()
.map(|v| match v {
Value::Record(f) => match f.get("tag") {
Some(Value::Str(t)) => t.clone(),
_ => panic!(),
},
_ => panic!(),
})
.collect();
assert_eq!(tags, vec!["a", "c", "e", "b", "d"]);
}
#[test]
fn sort_by_empty_and_singleton() {
let src = r#"
import "std.list" as list
fn r(xs :: List[Int]) -> List[Int] {
list.sort_by(xs, fn(x :: Int) -> Int { x })
}
"#;
assert_eq!(run(src, "r", vec![Value::List(vec![])]), Value::List(vec![]));
assert_eq!(
run(src, "r", vec![Value::List(vec![Value::Int(42)])]),
Value::List(vec![Value::Int(42)]),
);
}
#[test]
fn sort_by_float_keys() {
let src = r#"
import "std.list" as list
fn r(xs :: List[Float]) -> List[Float] {
list.sort_by(xs, fn(x :: Float) -> Float { x })
}
"#;
let xs = Value::List(vec![Value::Float(3.5), Value::Float(1.25), Value::Float(2.75)]);
assert_eq!(
run(src, "r", vec![xs]),
Value::List(vec![Value::Float(1.25), Value::Float(2.75), Value::Float(3.5)]),
);
}