use rex::{
engine::{Engine, EngineError, Handle, Heap, Module, Value},
parser::parse as parse_rex,
typesystem::{BuiltinTypeId, Type},
};
fn register_integer_literal_natives(engine: &mut Engine<()>) -> Result<(), EngineError> {
let mut module = Module::global();
module.export("num_u8", |_state: &(), x: u8| Ok(format!("{x}:u8")))?;
module.export("num_u16", |_state: &(), x: u16| Ok(format!("{x}:u16")))?;
module.export("num_u32", |_state: &(), x: u32| Ok(format!("{x}:u32")))?;
module.export("num_u64", |_state: &(), x: u64| Ok(format!("{x}:u64")))?;
module.export("num_i8", |_state: &(), x: i8| Ok(format!("{x}:i8")))?;
module.export("num_i16", |_state: &(), x: i16| Ok(format!("{x}:i16")))?;
module.export("num_i32", |_state: &(), x: i32| Ok(format!("{x}:i32")))?;
module.export("num_i64", |_state: &(), x: i64| Ok(format!("{x}:i64")))?;
engine.inject_module(module)
}
async fn eval(code: &str) -> Result<(Heap, Handle, Type), EngineError> {
let program = parse_rex(code).unwrap();
let mut engine = Engine::with_prelude(()).unwrap();
register_integer_literal_natives(&mut engine)?;
let mut module = Module::global();
module.add_decls(program.decls.clone());
engine.inject_module(module)?;
let heap = engine.heap.clone();
let (handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.map_err(|err| err.into_engine_error())?;
Ok((heap, handle, ty))
}
fn expected_values() -> Vec<&'static str> {
vec![
"4:u8", "4:u16", "4:u32", "4:u64", "4:i8", "4:i16", "4:i32", "4:i64",
]
}
fn expected_type() -> Type {
Type::tuple(vec![
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
])
}
fn expected_negative_signed_values() -> Vec<&'static str> {
vec!["-3:i8", "-3:i16", "-3:i32", "-3:i64"]
}
fn expected_negative_signed_type() -> Type {
Type::tuple(vec![
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
Type::builtin(BuiltinTypeId::String),
])
}
fn assert_tuple_of_strings(_heap: &Heap, handle: &Handle) {
let Value::Tuple(parts) = handle.value().unwrap() else {
panic!("expected tuple");
};
let got: Vec<String> = parts
.iter()
.map(|p| p.to_rust::<String>().unwrap())
.collect();
let expected = expected_values()
.into_iter()
.map(str::to_string)
.collect::<Vec<_>>();
assert_eq!(got, expected);
}
fn assert_tuple_of_negative_signed_strings(_heap: &Heap, handle: &Handle) {
let Value::Tuple(parts) = handle.value().unwrap() else {
panic!("expected tuple");
};
let got: Vec<String> = parts
.iter()
.map(|p| p.to_rust::<String>().unwrap())
.collect();
let expected = expected_negative_signed_values()
.into_iter()
.map(str::to_string)
.collect::<Vec<_>>();
assert_eq!(got, expected);
}
fn assert_type_error(err: EngineError) {
assert!(
matches!(err, EngineError::Type(_)),
"expected type error, got {err}"
);
}
#[tokio::test]
async fn integer_literal_calls_each_num_function_directly() {
let code = r#"
(
num_u8 4,
num_u16 4,
num_u32 4,
num_u64 4,
num_i8 4,
num_i16 4,
num_i32 4,
num_i64 4
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_type());
assert_tuple_of_strings(&heap, &handle);
}
#[tokio::test]
async fn negative_integer_literal_calls_each_signed_num_function_directly() {
let code = r#"
(
num_i8 (-3),
num_i16 (-3),
num_i32 (-3),
num_i64 (-3)
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_negative_signed_type());
assert_tuple_of_negative_signed_strings(&heap, &handle);
}
#[tokio::test]
async fn negative_integer_literal_direct_unsigned_calls_are_type_errors() {
for code in [
"num_u8 (-3)",
"num_u16 (-3)",
"num_u32 (-3)",
"num_u64 (-3)",
] {
match eval(code).await {
Ok(_) => panic!("expected type error for `{code}`"),
Err(err) => assert_type_error(err),
}
}
}
#[tokio::test]
async fn integer_literal_let_binding_per_type() {
let code = r#"
(
let x = 4 in num_u8 x,
let x = 4 in num_u16 x,
let x = 4 in num_u32 x,
let x = 4 in num_u64 x,
let x = 4 in num_i8 x,
let x = 4 in num_i16 x,
let x = 4 in num_i32 x,
let x = 4 in num_i64 x
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_type());
assert_tuple_of_strings(&heap, &handle);
}
#[tokio::test]
async fn integer_literal_lambda_binding_per_type() {
let code = r#"
(
let f = \x -> num_u8 x in f 4,
let f = \x -> num_u16 x in f 4,
let f = \x -> num_u32 x in f 4,
let f = \x -> num_u64 x in f 4,
let f = \x -> num_i8 x in f 4,
let f = \x -> num_i16 x in f 4,
let f = \x -> num_i32 x in f 4,
let f = \x -> num_i64 x in f 4
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_type());
assert_tuple_of_strings(&heap, &handle);
}
#[tokio::test]
async fn negative_integer_literal_let_binding_per_signed_type() {
let code = r#"
(
let x: i8 = -3 in num_i8 x,
let x: i16 = -3 in num_i16 x,
let x: i32 = -3 in num_i32 x,
let x: i64 = -3 in num_i64 x
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_negative_signed_type());
assert_tuple_of_negative_signed_strings(&heap, &handle);
}
#[tokio::test]
async fn negative_integer_literal_let_binding_unsigned_calls_are_type_errors() {
for code in [
"let x: u8 = -3 in num_u8 x",
"let x: u16 = -3 in num_u16 x",
"let x: u32 = -3 in num_u32 x",
"let x: u64 = -3 in num_u64 x",
] {
match eval(code).await {
Ok(_) => panic!("expected type error for `{code}`"),
Err(err) => assert_type_error(err),
}
}
}
#[tokio::test]
async fn negative_integer_literal_lambda_binding_per_signed_type() {
let code = r#"
(
let f = \x -> num_i8 x in f (-3),
let f = \x -> num_i16 x in f (-3),
let f = \x -> num_i32 x in f (-3),
let f = \x -> num_i64 x in f (-3)
)
"#;
let (heap, handle, ty) = eval(code).await.unwrap();
assert_eq!(ty, expected_negative_signed_type());
assert_tuple_of_negative_signed_strings(&heap, &handle);
}
#[tokio::test]
async fn negative_integer_literal_lambda_binding_unsigned_calls_are_type_errors() {
for code in [
"let f = \\x -> num_u8 x in f (-3)",
"let f = \\x -> num_u16 x in f (-3)",
"let f = \\x -> num_u32 x in f (-3)",
"let f = \\x -> num_u64 x in f (-3)",
] {
match eval(code).await {
Ok(_) => panic!("expected type error for `{code}`"),
Err(err) => assert_type_error(err),
}
}
}