dellingr 0.4.0

An embeddable, pure-Rust Lua VM with precise instruction-cost accounting
Documentation
use dellingr::{ArgCount, RetCount, State};

fn run(state: &mut State, source: &str) {
    state.load_string(source).expect("source compiles");
    state
        .call(ArgCount::Fixed(0), RetCount::Fixed(0))
        .expect("source runs");
}

fn call_writer(state: &mut State, name: &str, value: f64) {
    state.get_global(name).expect("writer exists");
    state.push_number(value).expect("argument fits");
    state
        .call(ArgCount::Fixed(1), RetCount::Fixed(0))
        .expect("writer runs");
}

fn global_number(state: &mut State, name: &str) -> f64 {
    state.get_global(name).expect("global exists");
    let value = state.to_number(-1).expect("global is a number");
    state.pop(1).expect("result pops");
    value
}

#[test]
fn warm_writer_inserts_populates_then_hits_and_closures_share_the_site() {
    let mut state = State::new();
    run(
        &mut state,
        "function make() return function(v) cache_target = v end end a = make(); b = make()",
    );
    call_writer(&mut state, "a", 1.0);
    call_writer(&mut state, "b", 2.0);
    call_writer(&mut state, "a", 3.0);
    assert_eq!(global_number(&mut state, "cache_target"), 3.0);
}

#[test]
fn writer_repopulates_across_restricted_environment_swaps() {
    let mut state = State::new();
    run(&mut state, "x = 0; function writer(v) x = v end");
    call_writer(&mut state, "writer", 1.0);
    state.with_restricted_env(&["writer"], |restricted| {
        call_writer(restricted, "writer", 2.0);
        assert_eq!(global_number(restricted, "x"), 2.0);
    });
    assert_eq!(global_number(&mut state, "x"), 1.0);
    call_writer(&mut state, "writer", 3.0);
    assert_eq!(global_number(&mut state, "x"), 3.0);
}

#[test]
fn append_and_g_proxy_writes_do_not_displace_a_warmed_global() {
    let mut state = State::new();
    run(&mut state, "x = 0; function writer(v) x = v end");
    call_writer(&mut state, "writer", 1.0);
    run(&mut state, "y = 2; _G.y = 3");
    call_writer(&mut state, "writer", 4.0);
    assert_eq!(global_number(&mut state, "x"), 4.0);
    assert_eq!(global_number(&mut state, "y"), 3.0);
}

#[test]
fn builtin_rebind_invalidates_its_fallback_without_disturbing_a_writer() {
    let mut state = State::new();
    run(&mut state, "x = 0; function writer(v) x = v end");
    call_writer(&mut state, "writer", 1.0);
    run(
        &mut state,
        "table = { marker = 7 }; fallback_marker = ({}).marker",
    );
    call_writer(&mut state, "writer", 2.0);
    assert_eq!(global_number(&mut state, "fallback_marker"), 7.0);
    assert_eq!(global_number(&mut state, "x"), 2.0);
}

#[test]
fn global_writes_remain_cost_free() {
    let mut state = State::new();
    run(
        &mut state,
        "function writer() x = 1 + 1 + 1 + 1 end; writer()",
    );
    assert_eq!(state.cost_used(), 3);
}

#[cfg(feature = "snapshot")]
#[test]
fn new_save_with_cached_set_global_round_trips() {
    let mut state = State::new();
    run(&mut state, "function writer(v) saved_x = v end; writer(1)");
    let save = state.save_state().expect("state saves");
    let mut loaded = State::load_state(&save.bytes, Box::new(dellingr::DefaultCallbacks), |_| {})
        .expect("new save loads");
    call_writer(&mut loaded, "writer", 2.0);
    assert_eq!(global_number(&mut loaded, "saved_x"), 2.0);
}