use rex::{
Rex,
ast::Symbol,
engine::{Engine, EngineError, FromRex, Handle, Heap, IntoRex, Module, Value},
json::rex_to_json,
parser::parse as parse_rex,
typesystem::{BuiltinTypeId, RexType, Type},
};
use serde::Serialize;
use std::collections::HashMap;
fn inject_globals(
engine: &mut Engine<()>,
build: impl FnOnce(&mut Module<()>) -> Result<(), EngineError>,
) -> Result<(), EngineError> {
let mut module = Module::global();
build(&mut module)?;
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(())?;
MyInnerStruct::inject_rex(&mut engine)?;
MyStruct::inject_rex(&mut engine)?;
Boxed::<i32>::inject_rex(&mut engine)?;
Maybe::<i32>::inject_rex(&mut engine)?;
Shape::inject_rex(&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))
}
#[derive(Rex, Debug, PartialEq, Serialize, Clone)]
struct MyInnerStruct {
x: bool,
y: i32,
}
#[derive(Rex, Debug, PartialEq, Serialize, Clone)]
struct MyStruct {
x: bool,
y: i32,
tags: Vec<String>,
props: HashMap<String, i32>,
#[serde(default = "xxx")] inner: MyInnerStruct,
#[serde(alias = "ignore")] pair: (i32, String, bool),
#[serde(rename = "renamed")]
renamed_field: i32,
}
#[derive(Rex, Debug, PartialEq)]
struct Boxed<T> {
value: T,
}
#[derive(Rex, Debug, PartialEq)]
enum Maybe<T> {
Just(T),
Nothing,
}
#[derive(Rex, Debug, PartialEq)]
struct SharedLeaf {
value: i32,
}
#[derive(Rex, Debug, PartialEq)]
struct LeftBranch {
leaf: SharedLeaf,
}
#[derive(Rex, Debug, PartialEq)]
struct RightBranch {
leaf: SharedLeaf,
}
#[derive(Rex, Debug, PartialEq)]
struct RootNode {
left: LeftBranch,
right: RightBranch,
}
#[derive(Debug, PartialEq, Clone)]
struct AtomRef(i32);
impl RexType for AtomRef {
fn rex_type() -> Type {
i32::rex_type()
}
}
impl IntoRex for AtomRef {
fn into_rex(self, heap: &Heap) -> Result<Handle, EngineError> {
self.0.into_rex(heap)
}
}
impl FromRex for AtomRef {
fn from_rex(handle: &Handle) -> Result<Self, EngineError> {
Ok(Self(i32::from_rex(handle)?))
}
}
#[derive(Rex, Debug, PartialEq)]
struct Fragment(Vec<AtomRef>);
#[derive(Rex, Debug, PartialEq)]
struct VecFieldSnapshot {
values: Vec<i32>,
}
#[derive(Debug, PartialEq, Clone)]
struct Xyzf32([f32; 3]);
impl RexType for Xyzf32 {
fn rex_type() -> Type {
Type::tuple(vec![f32::rex_type(), f32::rex_type(), f32::rex_type()])
}
}
impl IntoRex for Xyzf32 {
fn into_rex(self, heap: &Heap) -> Result<Handle, EngineError> {
(self.0[0], self.0[1], self.0[2]).into_rex(heap)
}
}
impl FromRex for Xyzf32 {
fn from_rex(handle: &Handle) -> Result<Self, EngineError> {
let (x, y, z) = <(f32, f32, f32)>::from_rex(handle)?;
Ok(Self([x, y, z]))
}
}
#[derive(Rex, Debug, PartialEq)]
struct BoundingBox {
min: Xyzf32,
max: Xyzf32,
}
#[tokio::test]
async fn derive_struct_roundtrip_value() {
let (_heap, v_handle, ty) = eval(
r#"
MyStruct {
x = true,
y = 42,
tags = to_array ["a", "b", "c"],
props = { a = 1, b = 2 },
inner = MyInnerStruct { x = false, y = 7 },
pair = (1, "hi", true),
renamed = 9
}
"#,
)
.await
.unwrap();
assert_eq!(ty, MyStruct::rex_type());
let decoded = MyStruct::from_rex(&v_handle).unwrap();
assert_eq!(
decoded,
MyStruct {
x: true,
y: 42,
tags: vec!["a".into(), "b".into(), "c".into()],
props: HashMap::from([("a".into(), 1), ("b".into(), 2)]),
inner: MyInnerStruct { x: false, y: 7 },
pair: (1, "hi".into(), true),
renamed_field: 9,
}
);
}
#[tokio::test]
async fn derive_generic_struct_roundtrip_value() {
let (_heap, v_handle, ty) = eval("Boxed { value = 123 }").await.unwrap();
assert_eq!(ty, Boxed::<i32>::rex_type());
let decoded = Boxed::<i32>::from_rex(&v_handle).unwrap();
assert_eq!(decoded, Boxed { value: 123 });
}
#[tokio::test]
async fn derive_struct_eval_json_matches_rust_serde_json() {
let code = r#"
MyStruct {
x = true,
y = 42,
tags = to_array ["a", "b", "c"],
props = { a = 1, b = 2 },
inner = MyInnerStruct { x = false, y = 7 },
pair = (1, "hi", true),
renamed = 9
}
"#;
let expected = serde_json::json!({
"x": true,
"y": 42,
"tags": ["a", "b", "c"],
"props": { "a": 1, "b": 2 },
"inner": { "x": false, "y": 7 },
"pair": [1, "hi", true],
"renamed": 9
});
let program = parse_rex(code).unwrap();
let mut engine = Engine::with_prelude(()).unwrap();
MyInnerStruct::inject_rex(&mut engine).unwrap();
MyStruct::inject_rex(&mut engine).unwrap();
let mut module = Module::global();
module.add_decls(program.decls.clone());
engine.inject_module(module).unwrap();
let type_system = engine.type_system.clone();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
let actual_rex = rex_to_json(&v_handle, &ty, &type_system).unwrap();
let actual_serde = serde_json::to_value(MyStruct {
x: true,
y: 42,
tags: vec!["a".into(), "b".into(), "c".into()],
props: HashMap::from([("a".into(), 1), ("b".into(), 2)]),
inner: MyInnerStruct { x: false, y: 7 },
pair: (1, "hi".into(), true),
renamed_field: 9,
})
.unwrap();
assert_eq!(actual_rex, expected);
assert_eq!(actual_serde, expected);
}
#[tokio::test]
async fn derive_generic_worked_example_polymorphic_adt() {
let mut engine = Engine::with_prelude(()).unwrap();
let adt = Maybe::<i32>::rex_adt_decl().unwrap();
assert_eq!(adt.name.as_ref(), "Maybe");
assert_eq!(adt.params.len(), 1);
let t = adt
.param_type(&Symbol::intern("T"))
.expect("expected `T` param type");
let just = adt
.variants
.iter()
.find(|v| v.name.as_ref() == "Just")
.expect("expected `Just` variant");
assert_eq!(just.args, vec![t.clone()]);
let nothing = adt
.variants
.iter()
.find(|v| v.name.as_ref() == "Nothing")
.expect("expected `Nothing` variant");
assert!(nothing.args.is_empty());
let mut module = Module::global();
module.add_adt_decl(adt).unwrap();
engine.inject_module(module).unwrap();
assert_eq!(
Maybe::<i32>::rex_type(),
Type::app(Type::con("Maybe", 1), <i32 as RexType>::rex_type())
);
assert_eq!(
Maybe::<bool>::rex_type(),
Type::app(Type::con("Maybe", 1), <bool as RexType>::rex_type())
);
let program = parse_rex(
r#"
let id = \x -> Just x in
(id 1, id true)
"#,
)
.map_err(|errs| format!("parse error: {errs:?}"))
.unwrap();
let mut module = Module::global();
module.add_decls(program.decls.clone());
engine.inject_module(module).unwrap();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
let expected_ty = Type::tuple(vec![Maybe::<i32>::rex_type(), Maybe::<bool>::rex_type()]);
assert_eq!(ty, expected_ty);
let v = v_handle.value().unwrap();
let Value::Tuple(items) = v else {
panic!("expected tuple, got {}", v_handle.type_name().unwrap());
};
assert_eq!(items.len(), 2);
assert_eq!(Maybe::<i32>::from_rex(&items[0]).unwrap(), Maybe::Just(1));
assert_eq!(
Maybe::<bool>::from_rex(&items[1]).unwrap(),
Maybe::Just(true)
);
}
#[derive(Rex, Debug, PartialEq, Clone)]
enum Shape {
Rectangle(i32, i32),
Circle(i32),
}
#[tokio::test]
async fn derive_can_be_used_in_injected_native_functions() {
let program = parse_rex(
r#"
bump_y (MyStruct {
x = true,
y = 42,
tags = to_array ["a", "b", "c"],
props = { a = 1, b = 2 },
inner = MyInnerStruct { x = false, y = 7 },
pair = (1, "hi", true),
renamed = 9
})
"#,
)
.unwrap();
fn engine_with_struct_exports() -> Engine {
let mut engine = Engine::with_prelude(()).unwrap();
MyInnerStruct::inject_rex(&mut engine).unwrap();
MyStruct::inject_rex(&mut engine).unwrap();
inject_globals(&mut engine, |module| {
module.export("bump_y", |_: &(), mut s: MyStruct| {
s.y += 1;
Ok(s)
})
})
.unwrap();
inject_globals(&mut engine, |module| {
module.export_value(
"const_struct",
MyStruct {
x: false,
y: 100,
tags: vec![],
props: HashMap::new(),
inner: MyInnerStruct { x: true, y: 1 },
pair: (2, "ok".into(), false),
renamed_field: 0,
},
)
})
.unwrap();
engine
}
let (v_handle, ty) = engine_with_struct_exports()
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, MyStruct::rex_type());
let bumped = MyStruct::from_rex(&v_handle).unwrap();
assert_eq!(bumped.y, 43);
let program = parse_rex("const_struct.y").unwrap();
let (v, ty) = engine_with_struct_exports()
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
assert_eq!(v.as_i32().unwrap(), 100);
}
#[tokio::test]
async fn derive_enum_can_be_injected_as_value_and_pattern_matched() {
let mut engine = Engine::with_prelude(()).unwrap();
Shape::inject_rex(&mut engine).unwrap();
inject_globals(&mut engine, |module| {
module.export_value("shape", Shape::Rectangle(3, 4))
})
.unwrap();
let program = parse_rex(
r#"
match shape with {
case Rectangle w h -> w * h;
case Circle r -> r;
}
"#,
)
.unwrap();
let (v, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
assert_eq!(v.as_i32().unwrap(), 12);
}
#[tokio::test]
async fn derive_types_implement_rex_adt_trait() {
let mut engine = Engine::with_prelude(()).unwrap();
engine.inject_rex_adt::<Shape>().unwrap();
let program = parse_rex(
r#"
match (Rectangle 2 5) with {
case Rectangle w h -> w * h;
case Circle r -> r;
}
"#,
)
.unwrap();
let (v, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
assert_eq!(v.as_i32().unwrap(), 10);
}
#[tokio::test]
async fn derive_generic_enum_can_be_used_as_injected_fn_arg_and_return() {
let mut engine = Engine::with_prelude(()).unwrap();
Maybe::<i32>::inject_rex(&mut engine).unwrap();
inject_globals(&mut engine, |module| {
module.export("unwrap_or_zero", |_: &(), m: Maybe<i32>| {
Ok(match m {
Maybe::Just(v) => v,
Maybe::Nothing => 0,
})
})
})
.unwrap();
let program = parse_rex("(unwrap_or_zero (Just 5), unwrap_or_zero Nothing)").unwrap();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(
ty,
Type::tuple(vec![
Type::builtin(BuiltinTypeId::I32),
Type::builtin(BuiltinTypeId::I32)
])
);
let v = v_handle.value().unwrap();
let Value::Tuple(items) = v else {
panic!("expected tuple, got {}", v_handle.type_name().unwrap());
};
assert_eq!(items[0].as_i32().unwrap(), 5);
assert_eq!(items[1].as_i32().unwrap(), 0);
}
#[tokio::test]
async fn derive_enum_constructor_currying() {
let (_heap, v_handle, ty) = eval(
r#"
let partial = Rectangle (2 * 3) in
(partial (3 * 4), partial (2 * 4))
"#,
)
.await
.unwrap();
assert_eq!(ty, Type::tuple(vec![Shape::rex_type(), Shape::rex_type()]));
let value = v_handle.value().unwrap();
let Value::Tuple(items) = value else {
panic!("expected tuple, got {}", v_handle.type_name().unwrap());
};
assert_eq!(items.len(), 2);
let a = Shape::from_rex(&items[0]).unwrap();
let b = Shape::from_rex(&items[1]).unwrap();
assert_eq!(a, Shape::Rectangle(6, 12));
assert_eq!(b, Shape::Rectangle(6, 8));
}
#[tokio::test]
async fn derive_inject_rex_registers_acyclic_dependency_closure() {
let mut engine = Engine::with_prelude(()).unwrap();
RootNode::inject_rex(&mut engine).unwrap();
assert!(
engine
.type_system
.adts
.contains_key(&Symbol::intern("SharedLeaf"))
);
assert!(
engine
.type_system
.adts
.contains_key(&Symbol::intern("LeftBranch"))
);
assert!(
engine
.type_system
.adts
.contains_key(&Symbol::intern("RightBranch"))
);
assert!(
engine
.type_system
.adts
.contains_key(&Symbol::intern("RootNode"))
);
let program = parse_rex(
r#"
RootNode {
left = LeftBranch { leaf = SharedLeaf { value = 1 } },
right = RightBranch { leaf = SharedLeaf { value = 2 } }
}
"#,
)
.unwrap();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, RootNode::rex_type());
let decoded = RootNode::from_rex(&v_handle).unwrap();
assert_eq!(
decoded,
RootNode {
left: LeftBranch {
leaf: SharedLeaf { value: 1 },
},
right: RightBranch {
leaf: SharedLeaf { value: 2 },
},
}
);
}
#[test]
fn derive_vec_fields_serialize_and_deserialize_as_arrays() {
fn array_from_values(heap: &Heap, values: &[i32]) -> Handle {
let items = values
.iter()
.map(|value| heap.alloc_i32(*value).unwrap())
.collect();
heap.alloc_array(items).unwrap()
}
fn assert_values(values: Vec<i32>, expected: &[i32]) {
let heap = Heap::new();
let adt = VecFieldSnapshot::rex_adt_decl().unwrap();
assert_eq!(
adt.variants[0].args,
vec![Type::record(vec![(
Symbol::intern("values"),
Type::array(Type::builtin(BuiltinTypeId::I32)),
)])]
);
let snapshot = VecFieldSnapshot {
values: values.clone(),
}
.into_rex(&heap)
.unwrap();
let Value::Adt(tag, args) = snapshot.value().unwrap() else {
panic!("expected VecFieldSnapshot ADT");
};
assert_eq!(tag.as_ref(), "VecFieldSnapshot");
assert_eq!(args.len(), 1);
let Value::Dict(fields) = args[0].value().unwrap() else {
panic!("expected record payload");
};
let array_handle = fields
.get(&Symbol::intern("values"))
.expect("expected `values` field");
let Value::Array(array_items) = array_handle.value().unwrap() else {
panic!("expected array field");
};
let actual = array_items
.iter()
.map(|item| item.to_rust::<i32>().unwrap())
.collect::<Vec<_>>();
assert_eq!(actual, expected);
let mut fields = std::collections::BTreeMap::new();
fields.insert(Symbol::intern("values"), array_from_values(&heap, expected));
let handle = heap
.alloc_adt(
Symbol::intern("VecFieldSnapshot"),
vec![heap.alloc_dict(fields).unwrap()],
)
.unwrap();
let decoded = VecFieldSnapshot::from_rex(&handle).unwrap();
assert_eq!(decoded, VecFieldSnapshot { values });
}
assert_values(vec![], &[]);
assert_values(vec![7], &[7]);
assert_values(vec![1, 2, 3], &[1, 2, 3]);
}
#[tokio::test]
async fn derive_leaf_rex_type_field_does_not_require_rex_adt_dependency() {
let mut engine = Engine::with_prelude(()).unwrap();
Fragment::inject_rex(&mut engine).unwrap();
let program = parse_rex("Fragment [1, 2, 3]").unwrap();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, Fragment::rex_type());
let decoded = Fragment::from_rex(&v_handle).unwrap();
assert_eq!(decoded, Fragment(vec![AtomRef(1), AtomRef(2), AtomRef(3)]));
}
#[tokio::test]
async fn derive_leaf_rex_type_record_fields_support_manual_leaf_types() {
let mut engine = Engine::with_prelude(()).unwrap();
BoundingBox::inject_rex(&mut engine).unwrap();
let program =
parse_rex("BoundingBox { min = (1.0, 2.0, 3.0), max = (4.0, 5.0, 6.0) }").unwrap();
let (v_handle, ty) = engine
.into_evaluator()
.eval(program.body.as_ref().unwrap().as_ref())
.await
.unwrap();
assert_eq!(ty, BoundingBox::rex_type());
let decoded = BoundingBox::from_rex(&v_handle).unwrap();
assert_eq!(
decoded,
BoundingBox {
min: Xyzf32([1.0, 2.0, 3.0]),
max: Xyzf32([4.0, 5.0, 6.0]),
}
);
}