lovm2 0.4.8

a lightweight virtual machine with a focus on simplicity and extendability.
Documentation
use test_utils::*;

use lovm2::create_vm_with_std;
use lovm2::prelude::*;
use lovm2::value::Value;
use lovm2::vm::Vm;

#[test]
fn load_hook_none() {
    let mut builder = ModuleBuilder::new();
    let hir = builder.entry();
    hir.step(Include::import("notfound"));
    hir.step(Interrupt::new(10));

    let module = builder.build().unwrap();

    let mut vm = create_vm_with_std();
    vm.set_load_hook(|_name| Ok(None));

    assert!(run_module_test(vm, module, |_| ()).is_err());
}

#[test]
fn load_custom_module() {
    let mut builder = ModuleBuilder::named("main");
    let n = &lv2_var!(n);

    builder
        .entry()
        .step(Include::import("extern"))
        .step(Assign::local(n, Call::new("extern.calc")))
        .step(Interrupt::new(10));

    let module = builder.build().unwrap();

    let mut vm = create_vm_with_std();
    vm.set_load_hook(|req| {
        assert_eq!("extern", req.module);
        let mut builder = ModuleBuilder::named("extern");

        builder.add("calc").step(Return::value(Expr::add(1, 1)));

        Ok(Some(builder.build().unwrap().into()))
    });

    run_module_test(vm, module, |ctx| {
        let frame = ctx.frame_mut().unwrap();
        assert_eq!(Value::Int(2), *frame.value_of("n").unwrap());
    })
    .unwrap();
}

#[test]
fn import_global_scope() {
    let mut builder = ModuleBuilder::named("main");
    let n = &lv2_var!(n);

    builder
        .entry()
        .step(Include::import_global("extern"))
        .step(Assign::local(n, Call::new("calc")))
        .step(Interrupt::new(10));

    let module = builder.build().unwrap();

    let mut vm = create_vm_with_std();
    vm.set_load_hook(|req| {
        assert_eq!("extern", req.module);
        let mut builder = ModuleBuilder::named("extern");

        builder.add("calc").step(Return::value(Expr::add(1, 1)));

        Ok(Some(builder.build().unwrap().into()))
    });

    run_module_test(vm, module, |ctx| {
        let frame = ctx.frame_mut().unwrap();
        assert_eq!(Value::Int(2), *frame.value_of("n").unwrap());
    })
    .unwrap();
}

#[test]
fn import_vice_versa() {
    const PASSED_VALUE: Value = Value::Int(72);
    let (n, result) = &lv2_var!(n, result);

    let mut builder = ModuleBuilder::named("main");
    builder
        .entry()
        .step(Include::import("extern"))
        .step(Call::new("extern.callextern"))
        .step(Interrupt::new(10));

    builder
        .add_with_args("callmain", vec![n.clone()])
        .step(Assign::global(result, n))
        .step(Return::value(2));

    let module = builder.build().unwrap();

    let mut vm = create_vm_with_std();
    vm.set_load_hook(|req| {
        assert_eq!("extern", req.module);
        let mut builder = ModuleBuilder::named("extern");

        builder
            .add("callextern")
            .step(Include::import("main"))
            .step(Call::new("main.callmain").arg(PASSED_VALUE));

        Ok(Some(builder.build().unwrap().into()))
    });

    run_module_test(vm, module, |ctx| {
        assert_eq!(PASSED_VALUE, *ctx.value_of("result").unwrap());
    })
    .unwrap();
}

#[test]
fn custom_naming_scheme() {
    let mut module = ModuleBuilder::named("main");
    module.add("call_main");
    let module = module.build().unwrap();

    let mut ex = ModuleBuilder::named("extern");
    ex.add("call_extern");
    ex.add("call");
    let ex = ex.build().unwrap();

    let mut vm = create_vm_with_std();

    vm.set_import_hook(|module, name| {
        let key = to_lower_camel_case(name);
        let key = match module {
            // use dash as module name separator
            Some(module) => format!("{}-{}", module, key),
            _ => key,
        };
        println!("{}", key);
        Ok(Some(key))
    });

    vm.add_module(module, false).unwrap();
    vm.add_module(ex, true).unwrap();

    vm.call("callMain", &[]).unwrap();
    vm.call("extern-call", &[]).unwrap();
    vm.call("extern-callExtern", &[]).unwrap();
}

#[test]
fn main_has_no_entry_point() {
    let module = ModuleBuilder::named("main").build().unwrap();
    let mut vm = Vm::new();

    let e = vm.add_main_module(module).err().unwrap();
    assert!(matches!(
        e,
        Lovm2Error {
            ty: Lovm2ErrorTy::NoEntryPoint,
            ..
        }
    ));
}

#[test]
fn main_import_as_global() {
    let mut module = ModuleBuilder::named("main");
    module.add("main");
    module.add("myfunc");
    let module = module.build().unwrap();

    let mut vm = Vm::new();
    vm.add_main_module(module).unwrap();

    assert!(vm.call("myfunc", &[]).is_ok());
    assert!(vm.call("main.myfunc", &[]).is_ok());
}

#[test]
fn namespaced_imports() {
    let mut a = ModuleBuilder::named("a");
    a.add("ina");
    let a = a.build().unwrap();

    let mut b = ModuleBuilder::named("b");
    b.add("inb");
    let b = b.build().unwrap();

    let mut vm = Vm::new();

    vm.add_module(a, true).unwrap();
    vm.add_module(b, false).unwrap();

    assert!(vm.call("a.ina", &[]).is_ok());
    assert!(vm.call("ina", &[]).is_err());
    assert!(vm.call("b.inb", &[]).is_ok());
    assert!(vm.call("inb", &[]).is_ok());
}

#[test]
fn setting_interrupts() {
    let mut vm = Vm::new();

    assert!(vm.set_interrupt(1, |_| { unreachable!() }).is_err());
    assert!(vm.set_interrupt(10, |_| { unreachable!() }).is_ok());
    assert!(vm.set_interrupt(11, |_| { unreachable!() }).is_err());
    assert!(vm.set_interrupt(64, |_| { unreachable!() }).is_ok());
}

#[test]
fn call_stdlib_functions() {
    let mut vm = create_vm_with_std();

    assert_eq!(
        Value::from("a"),
        vm.call("trim", &["    a ".into()]).unwrap(),
    );
    assert!(vm.call("new_request", &["".into()]).is_ok());
}