runmat-vm 0.6.0

RunMat virtual machine and bytecode interpreter
Documentation
use runmat_builtins::{LogicalArray, StringArray, Tensor, Value};

#[path = "support/mod.rs"]
mod test_helpers;
use test_helpers::execute_source;

fn has_tensor(vars: &[Value], expected: &[f64]) -> bool {
    vars.iter().any(|value| match value {
        Value::Tensor(Tensor { data, .. }) => {
            data.len() == expected.len()
                && data.iter().zip(expected.iter()).all(|(actual, expected)| {
                    if expected.is_nan() {
                        actual.is_nan()
                    } else {
                        (*actual - *expected).abs() < 1e-9
                    }
                })
        }
        _ => false,
    })
}

fn has_logical(vars: &[Value], expected: &[u8]) -> bool {
    vars.iter().any(|value| match value {
        Value::LogicalArray(LogicalArray { data, .. }) => data == expected,
        _ => false,
    })
}

#[test]
fn missing_constructs_and_detects_string_sentinel() {
    let vars = execute_source("s = missing(1, 2); tf = ismissing(s); anytf = anymissing(s);")
        .expect("missing string script");
    assert!(vars.iter().any(|value| matches!(
        value,
        Value::StringArray(StringArray { data, .. })
            if data == &["<missing>".to_string(), "<missing>".to_string()]
    )));
    assert!(has_logical(&vars, &[1, 1]));
    assert!(vars.iter().any(|value| matches!(value, Value::Bool(true))));
}

#[test]
fn missing_numeric_cleanup_and_fill_execute_from_scripts() {
    let vars = execute_source(
        "A = [1 NaN 3; 4 5 6]; tf = ismissing(A); B = rmmissing(A, 2); C = fillmissing(A, 'constant', 9);",
    )
    .expect("numeric missing cleanup");
    assert!(has_logical(&vars, &[0, 0, 1, 0, 0, 0]));
    assert!(has_tensor(&vars, &[1.0, 4.0, 3.0, 6.0]));
    assert!(has_tensor(&vars, &[1.0, 4.0, 9.0, 5.0, 3.0, 6.0]));
}

#[test]
fn standardize_missing_and_nan_aliases_execute_from_scripts() {
    let vars = execute_source(
        "A = [-99 2 4]; B = standardizeMissing(A, -99); m = nanmean(B, 'all'); s = nansum(B, 'all');",
    )
    .expect("standardize missing and nan aliases");
    assert!(vars
        .iter()
        .any(|value| matches!(value, Value::Num(n) if (n - 3.0).abs() < 1e-9)));
    assert!(vars
        .iter()
        .any(|value| matches!(value, Value::Num(n) if (n - 6.0).abs() < 1e-9)));
}

#[test]
fn movmad_executes_from_scripts() {
    let vars = execute_source("A = [1; 2; 100; 4; 5]; M = movmad(A, 3);").expect("movmad script");
    assert!(has_tensor(&vars, &[0.5, 1.0, 2.0, 1.0, 0.5]));
}

#[test]
fn table_missing_interop_executes_from_scripts() {
    let vars = execute_source(
        "T = table([1; NaN; 3], strings(3, 1, 'missing'), 'VariableNames', {'A','S'}); M = ismissing(T); R = rmmissing(T);",
    )
    .expect("table missing interop");
    assert!(has_logical(&vars, &[0, 1, 0, 1, 1, 1]));
    assert!(vars
        .iter()
        .any(|value| matches!(value, Value::Object(obj) if obj.class_name == "table")));
}

#[test]
fn numeric_table_fillmissing_executes_from_scripts() {
    let vars = execute_source(
        "T = table([1; NaN; 3], [10; 20; NaN], 'VariableNames', {'A','B'}); [F, M] = fillmissing(T, 'constant', 0);",
    )
    .expect("numeric table fillmissing");
    assert!(has_logical(&vars, &[0, 1, 0, 0, 0, 1]));
    assert!(vars
        .iter()
        .any(|value| matches!(value, Value::Object(obj) if obj.class_name == "table")));
}