rex 3.9.12

Rex: A strongly-typed, pure, implicitly parallel functional programming language
Documentation
use rex::{
    AdtDecl, BuiltinTypeId, Engine, EngineError, EnumPatch, GasMeter, Heap, JsonOptions, Parser,
    Pointer, Program, ReplState, Rex, Token, Type, TypeSystem, TypeVarSupply, intern, json_to_rex,
    rex_to_json, sym,
};
use serde::Serialize;
use serde_json::json;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;

fn mk_type_system() -> TypeSystem {
    TypeSystem::new_with_prelude().unwrap()
}

fn mk_unit_enum(name: &str, variants: &[&str]) -> AdtDecl {
    let mut supply = TypeVarSupply::new();
    let mut adt = AdtDecl::new(&intern(name), &[], &mut supply);
    for variant in variants {
        adt.add_variant(intern(variant), vec![]);
    }
    adt
}

fn temp_dir(name: &str) -> PathBuf {
    let mut dir = std::env::temp_dir();
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    dir.push(format!("rex-json-eval-{name}-{nanos}"));
    fs::create_dir_all(&dir).unwrap();
    dir
}

fn parse_program(source: &str) -> Program {
    let tokens = Token::tokenize(source).unwrap();
    let mut parser = Parser::new(tokens);
    parser.parse_program(&mut GasMeter::default()).unwrap()
}

fn fixed_uuid() -> Uuid {
    Uuid::parse_str("12345678-1234-5678-90ab-cdef12345678").unwrap()
}

#[derive(Rex, Serialize)]
struct EvalJsonRecord {
    id: i32,
    values: Vec<i32>,
}

fn assert_eval_json(
    engine: &Engine<()>,
    pointer: &Pointer,
    typ: &Type,
    expected: serde_json::Value,
) {
    let actual = rex_to_json(
        &engine.heap,
        pointer,
        typ,
        &engine.type_system,
        &JsonOptions::default(),
    )
    .unwrap();
    assert_eq!(actual, expected);
}

#[test]
fn primitive_roundtrip() {
    let ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();

    let cases = vec![
        (Type::builtin(BuiltinTypeId::Bool), json!(true)),
        (Type::builtin(BuiltinTypeId::I32), json!(-7)),
        (Type::builtin(BuiltinTypeId::String), json!("hello")),
    ];

    for (ty, expected_json) in cases {
        let ptr = json_to_rex(&heap, &expected_json, &ty, &ts, &opts).unwrap();
        let actual_json = rex_to_json(&heap, &ptr, &ty, &ts, &opts).unwrap();
        assert_eq!(actual_json, expected_json);
    }
}

#[test]
fn option_and_result_roundtrip() {
    let ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();

    let opt_ty = Type::option(Type::builtin(BuiltinTypeId::I32));
    let some = json!(9);
    let none = serde_json::Value::Null;

    let some_ptr = json_to_rex(&heap, &some, &opt_ty, &ts, &opts).unwrap();
    let none_ptr = json_to_rex(&heap, &none, &opt_ty, &ts, &opts).unwrap();
    assert_eq!(
        rex_to_json(&heap, &some_ptr, &opt_ty, &ts, &opts).unwrap(),
        some
    );
    assert_eq!(
        rex_to_json(&heap, &none_ptr, &opt_ty, &ts, &opts).unwrap(),
        none
    );

    let res_ty = Type::result(
        Type::builtin(BuiltinTypeId::I32),
        Type::builtin(BuiltinTypeId::String),
    );
    let ok_json = json!({ "Ok": 1 });
    let err_json = json!({ "Err": "bad" });
    let ok_ptr = json_to_rex(&heap, &ok_json, &res_ty, &ts, &opts).unwrap();
    let err_ptr = json_to_rex(&heap, &err_json, &res_ty, &ts, &opts).unwrap();
    assert_eq!(
        rex_to_json(&heap, &ok_ptr, &res_ty, &ts, &opts).unwrap(),
        ok_json
    );
    assert_eq!(
        rex_to_json(&heap, &err_ptr, &res_ty, &ts, &opts).unwrap(),
        err_json
    );
}

#[test]
fn promise_roundtrip_from_json() {
    let ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();
    let promise_ty = Type::promise(Type::builtin(BuiltinTypeId::I32));
    let promise_json = json!(fixed_uuid());

    let promise_ptr = json_to_rex(&heap, &promise_json, &promise_ty, &ts, &opts).unwrap();
    let (tag, args) = heap.pointer_as_adt(&promise_ptr).unwrap();
    assert_eq!(tag.as_ref(), "Promise");
    assert_eq!(args.len(), 1);
    assert_eq!(heap.pointer_as_uuid(&args[0]).unwrap(), fixed_uuid());
    assert_eq!(
        rex_to_json(&heap, &promise_ptr, &promise_ty, &ts, &opts).unwrap(),
        promise_json
    );
}

#[test]
fn promise_roundtrip_from_runtime_value() {
    let ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();
    let promise_ty = Type::promise(Type::builtin(BuiltinTypeId::String));
    let promise_id = heap.alloc_uuid(fixed_uuid()).unwrap();
    let promise_ptr = heap.alloc_adt(sym("Promise"), vec![promise_id]).unwrap();

    let promise_json = rex_to_json(&heap, &promise_ptr, &promise_ty, &ts, &opts).unwrap();
    assert_eq!(promise_json, json!(fixed_uuid()));

    let roundtrip_ptr = json_to_rex(&heap, &promise_json, &promise_ty, &ts, &opts).unwrap();
    let (tag, args) = heap.pointer_as_adt(&roundtrip_ptr).unwrap();
    assert_eq!(tag.as_ref(), "Promise");
    assert_eq!(args.len(), 1);
    assert_eq!(heap.pointer_as_uuid(&args[0]).unwrap(), fixed_uuid());
}

#[test]
fn json_array_maps_to_array_not_list() {
    let ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();
    let array_json = json!([1, 2, 3]);

    let array_ty = Type::array(Type::builtin(BuiltinTypeId::I32));
    let array_ptr = json_to_rex(&heap, &array_json, &array_ty, &ts, &opts).unwrap();
    let items = heap.pointer_as_array(&array_ptr).unwrap();
    assert_eq!(items.len(), 3);
    assert_eq!(
        rex_to_json(&heap, &array_ptr, &array_ty, &ts, &opts).unwrap(),
        array_json
    );

    let list_ty = Type::list(Type::builtin(BuiltinTypeId::I32));
    let list_ptr = json_to_rex(&heap, &array_json, &list_ty, &ts, &opts).unwrap();
    let (tag, _args) = heap.pointer_as_adt(&list_ptr).unwrap();
    assert_eq!(tag.as_ref(), "Cons");
}

#[test]
fn struct_like_single_variant_adt_roundtrip() {
    let mut ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();

    let mut supply = TypeVarSupply::new();
    let mut foo = AdtDecl::new(&sym("Foo"), &[], &mut supply);
    foo.add_variant(
        sym("Foo"),
        vec![Type::record(vec![
            (sym("a"), Type::builtin(BuiltinTypeId::U64)),
            (sym("b"), Type::builtin(BuiltinTypeId::String)),
        ])],
    );
    ts.register_adt(&foo);

    let foo_ty = Type::con("Foo", 0);
    let foo_json = json!({ "a": 42, "b": "Hello" });

    let foo_ptr = json_to_rex(&heap, &foo_json, &foo_ty, &ts, &opts).unwrap();
    let (tag, args) = heap.pointer_as_adt(&foo_ptr).unwrap();
    assert_eq!(tag.as_ref(), "Foo");
    assert_eq!(args.len(), 1);
    assert_eq!(
        rex_to_json(&heap, &foo_ptr, &foo_ty, &ts, &opts).unwrap(),
        foo_json
    );
}

#[test]
fn unit_enum_string_roundtrip() {
    let mut ts = mk_type_system();
    let heap = Heap::new();
    let opts = JsonOptions::default();

    let color = mk_unit_enum("Color", &["Red", "Green", "Blue"]);
    ts.register_adt(&color);
    let color_ty = Type::con("Color", 0);

    for v in [json!("Red"), json!("Green"), json!("Blue")] {
        let ptr = json_to_rex(&heap, &v, &color_ty, &ts, &opts).unwrap();
        let actual = rex_to_json(&heap, &ptr, &color_ty, &ts, &opts).unwrap();
        assert_eq!(actual, v);
    }
}

#[test]
fn unit_enum_integer_roundtrip_with_patches() {
    let mut ts = mk_type_system();
    let heap = Heap::new();
    let color = mk_unit_enum("Color", &["Red", "Green", "Blue"]);
    ts.register_adt(&color);
    let color_ty = Type::con("Color", 0);

    let mut opts = JsonOptions::default();
    opts.add_int_enum("Color");

    let red = heap.alloc_adt(sym("Red"), vec![]).unwrap();
    let green = heap.alloc_adt(sym("Green"), vec![]).unwrap();
    let blue = heap.alloc_adt(sym("Blue"), vec![]).unwrap();
    assert_eq!(
        rex_to_json(&heap, &red, &color_ty, &ts, &opts).unwrap(),
        json!(0)
    );
    assert_eq!(
        rex_to_json(&heap, &green, &color_ty, &ts, &opts).unwrap(),
        json!(1)
    );
    assert_eq!(
        rex_to_json(&heap, &blue, &color_ty, &ts, &opts).unwrap(),
        json!(2)
    );

    let ptr = json_to_rex(&heap, &json!(2), &color_ty, &ts, &opts).unwrap();
    let (tag, args) = heap.pointer_as_adt(&ptr).unwrap();
    assert_eq!(tag.as_ref(), "Blue");
    assert!(args.is_empty());

    opts.add_int_enum_with_patches(
        "Color",
        vec![
            EnumPatch {
                enum_name: "Red".to_string(),
                discriminant: 10,
            },
            EnumPatch {
                enum_name: "Blue".to_string(),
                discriminant: 42,
            },
        ],
    );

    assert_eq!(
        rex_to_json(&heap, &red, &color_ty, &ts, &opts).unwrap(),
        json!(10)
    );
    assert_eq!(
        rex_to_json(&heap, &green, &color_ty, &ts, &opts).unwrap(),
        json!(1)
    );
    assert_eq!(
        rex_to_json(&heap, &blue, &color_ty, &ts, &opts).unwrap(),
        json!(42)
    );

    let blue_from_patch = json_to_rex(&heap, &json!(42), &color_ty, &ts, &opts).unwrap();
    let (tag, args) = heap.pointer_as_adt(&blue_from_patch).unwrap();
    assert_eq!(tag.as_ref(), "Blue");
    assert!(args.is_empty());
}

#[test]
fn unit_enum_integer_unknown_discriminant_errors() {
    let mut ts = mk_type_system();
    let heap = Heap::new();
    let color = mk_unit_enum("Color", &["Red", "Green", "Blue"]);
    ts.register_adt(&color);
    let color_ty = Type::con("Color", 0);

    let mut opts = JsonOptions::default();
    opts.add_int_enum("Color");

    let err = json_to_rex(&heap, &json!(99), &color_ty, &ts, &opts).unwrap_err();
    let EngineError::Custom(msg) = err else {
        panic!("expected EngineError::Custom");
    };
    assert!(msg.contains("expected integer enum JSON for `Color`"));
}

#[tokio::test]
async fn eval_entry_points_return_type_for_json_eval() {
    let mut engine = Engine::with_prelude(()).unwrap();
    EvalJsonRecord::inject_rex(&mut engine).unwrap();
    let rex_code = "EvalJsonRecord { id = 7, values = [1, 2, 3, 5, 8] }";
    let expected_json = json!({
        "id": 7,
        "values": [1, 2, 3, 5, 8]
    });
    assert_eq!(
        serde_json::to_value(EvalJsonRecord {
            id: 7,
            values: vec![1, 2, 3, 5, 8],
        })
        .unwrap(),
        expected_json
    );

    let mut gas = GasMeter::default();
    let expr_program = parse_program(rex_code);
    let (ptr_eval, ty_eval) = rex::Evaluator::new_with_compiler(
        rex::RuntimeEnv::new(engine.clone()),
        rex::Compiler::new(engine.clone()),
    )
    .eval(expr_program.expr.as_ref(), &mut gas)
    .await
    .unwrap();
    assert_eval_json(&engine, &ptr_eval, &ty_eval, expected_json.clone());

    let mut gas = GasMeter::default();
    let (ptr_snippet, ty_snippet) = rex::Evaluator::new_with_compiler(
        rex::RuntimeEnv::new(engine.clone()),
        rex::Compiler::new(engine.clone()),
    )
    .eval_snippet(rex_code, &mut gas)
    .await
    .unwrap();
    assert_eval_json(&engine, &ptr_snippet, &ty_snippet, expected_json.clone());

    let dir = temp_dir("snippet-at");
    let importer = dir.join("main.rex");
    fs::write(&importer, "()").unwrap();
    let mut gas = GasMeter::default();
    let (ptr_snippet_at, ty_snippet_at) = rex::Evaluator::new_with_compiler(
        rex::RuntimeEnv::new(engine.clone()),
        rex::Compiler::new(engine.clone()),
    )
    .eval_snippet_at(rex_code, &importer, &mut gas)
    .await
    .unwrap();
    assert_eval_json(
        &engine,
        &ptr_snippet_at,
        &ty_snippet_at,
        expected_json.clone(),
    );

    let repl_program = parse_program(rex_code);
    let mut repl_state = ReplState::new();
    let mut gas = GasMeter::default();
    let (ptr_repl, ty_repl) = rex::Evaluator::new_with_compiler(
        rex::RuntimeEnv::new(engine.clone()),
        rex::Compiler::new(engine.clone()),
    )
    .eval_repl_program(&repl_program, &mut repl_state, &mut gas)
    .await
    .unwrap();
    assert_eval_json(&engine, &ptr_repl, &ty_repl, expected_json);
}