use super::*;
fn empty_env() -> Env {
Env::new()
}
fn env_with_ans(val: f64) -> Env {
let mut env = Env::new();
env.insert("ans".to_string(), Value::Scalar(val));
env
}
fn eval_s(expr: &Expr, env: &Env) -> f64 {
match eval(expr, env).unwrap() {
Value::Scalar(n) => n,
_ => panic!("expected scalar"),
}
}
#[test]
fn test_eval_number() {
assert_eq!(eval_s(&Expr::Number(42.0), &empty_env()), 42.0);
}
#[test]
fn test_eval_var_found() {
let mut env = Env::new();
env.insert("x".to_string(), Value::Scalar(7.0));
assert_eq!(eval_s(&Expr::Var("x".to_string()), &env), 7.0);
}
#[test]
fn test_eval_var_not_found() {
assert!(eval(&Expr::Var("z".to_string()), &empty_env()).is_err());
}
#[test]
fn test_eval_ans() {
assert_eq!(
eval_s(&Expr::Var("ans".to_string()), &env_with_ans(42.0)),
42.0
);
}
#[test]
fn test_eval_add() {
let expr = Expr::BinOp(
Box::new(Expr::Number(1.0)),
Op::Add,
Box::new(Expr::Number(2.0)),
);
assert_eq!(eval_s(&expr, &empty_env()), 3.0);
}
#[test]
fn test_eval_sub() {
let expr = Expr::BinOp(
Box::new(Expr::Number(10.0)),
Op::Sub,
Box::new(Expr::Number(4.0)),
);
assert_eq!(eval_s(&expr, &empty_env()), 6.0);
}
#[test]
fn test_eval_mul() {
let expr = Expr::BinOp(
Box::new(Expr::Number(3.0)),
Op::Mul,
Box::new(Expr::Number(7.0)),
);
assert_eq!(eval_s(&expr, &empty_env()), 21.0);
}
#[test]
fn test_eval_div() {
let expr = Expr::BinOp(
Box::new(Expr::Number(10.0)),
Op::Div,
Box::new(Expr::Number(4.0)),
);
assert_eq!(eval_s(&expr, &empty_env()), 2.5);
}
#[test]
fn test_eval_div_by_zero() {
let expr = Expr::BinOp(
Box::new(Expr::Number(1.0)),
Op::Div,
Box::new(Expr::Number(0.0)),
);
assert!(eval(&expr, &empty_env()).is_err());
}
#[test]
fn test_eval_unary_minus() {
let expr = Expr::UnaryMinus(Box::new(Expr::Number(5.0)));
assert_eq!(eval_s(&expr, &empty_env()), -5.0);
}
#[test]
fn test_eval_pow() {
let expr = Expr::BinOp(
Box::new(Expr::Number(2.0)),
Op::Pow,
Box::new(Expr::Number(10.0)),
);
assert_eq!(eval_s(&expr, &empty_env()), 1024.0);
}
#[test]
fn test_eval_call_sqrt() {
let expr = Expr::Call("sqrt".to_string(), vec![Expr::Number(144.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 12.0);
}
#[test]
fn test_eval_call_abs() {
let expr = Expr::Call("abs".to_string(), vec![Expr::Number(-7.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 7.0);
}
#[test]
fn test_eval_call_floor() {
let expr = Expr::Call("floor".to_string(), vec![Expr::Number(3.9)]);
assert_eq!(eval_s(&expr, &empty_env()), 3.0);
}
#[test]
fn test_eval_call_ceil() {
let expr = Expr::Call("ceil".to_string(), vec![Expr::Number(3.1)]);
assert_eq!(eval_s(&expr, &empty_env()), 4.0);
}
#[test]
fn test_eval_call_round() {
let expr = Expr::Call("round".to_string(), vec![Expr::Number(3.5)]);
assert_eq!(eval_s(&expr, &empty_env()), 4.0);
}
#[test]
fn test_eval_call_log() {
let expr = Expr::Call("log".to_string(), vec![Expr::Number(std::f64::consts::E)]);
assert!((eval_s(&expr, &empty_env()) - 1.0).abs() < 1e-10);
}
#[test]
fn test_eval_call_log10() {
let expr = Expr::Call("log10".to_string(), vec![Expr::Number(1000.0)]);
assert!((eval_s(&expr, &empty_env()) - 3.0).abs() < 1e-10);
}
#[test]
fn test_eval_call_exp() {
let expr = Expr::Call("exp".to_string(), vec![Expr::Number(0.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 1.0);
}
#[test]
fn test_eval_call_sin() {
let expr = Expr::Call("sin".to_string(), vec![Expr::Number(0.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 0.0);
}
#[test]
fn test_eval_call_cos() {
let expr = Expr::Call("cos".to_string(), vec![Expr::Number(0.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 1.0);
}
#[test]
fn test_eval_call_tan() {
let expr = Expr::Call("tan".to_string(), vec![Expr::Number(0.0)]);
assert_eq!(eval_s(&expr, &empty_env()), 0.0);
}
#[test]
fn test_eval_call_unknown() {
let expr = Expr::Call("foo".to_string(), vec![Expr::Number(1.0)]);
assert!(eval(&expr, &empty_env()).is_err());
}
#[test]
fn test_eval_call_atan2() {
let expr = Expr::Call(
"atan2".to_string(),
vec![Expr::Number(1.0), Expr::Number(1.0)],
);
assert!((eval_s(&expr, &empty_env()) - std::f64::consts::FRAC_PI_4).abs() < 1e-10);
}
#[test]
fn test_eval_call_mod() {
let expr = Expr::Call(
"mod".to_string(),
vec![Expr::Number(10.0), Expr::Number(3.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), 1.0);
}
#[test]
fn test_eval_call_mod_negative() {
let expr = Expr::Call(
"mod".to_string(),
vec![Expr::Number(-1.0), Expr::Number(3.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), 2.0);
}
#[test]
fn test_eval_call_rem() {
let expr = Expr::Call(
"rem".to_string(),
vec![Expr::Number(-1.0), Expr::Number(3.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), -1.0);
}
#[test]
fn test_eval_call_max() {
let expr = Expr::Call(
"max".to_string(),
vec![Expr::Number(3.0), Expr::Number(7.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), 7.0);
}
#[test]
fn test_eval_call_min() {
let expr = Expr::Call(
"min".to_string(),
vec![Expr::Number(3.0), Expr::Number(7.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), 3.0);
}
#[test]
fn test_eval_call_hypot() {
let expr = Expr::Call(
"hypot".to_string(),
vec![Expr::Number(3.0), Expr::Number(4.0)],
);
assert_eq!(eval_s(&expr, &empty_env()), 5.0);
}
#[test]
fn test_eval_call_log_two_arg() {
let expr = Expr::Call(
"log".to_string(),
vec![Expr::Number(8.0), Expr::Number(2.0)],
);
assert!((eval_s(&expr, &empty_env()) - 3.0).abs() < 1e-10);
}
#[test]
fn test_eval_call_asin_acos_atan() {
let env = empty_env();
let asin = Expr::Call("asin".to_string(), vec![Expr::Number(1.0)]);
assert!((eval_s(&asin, &env) - std::f64::consts::FRAC_PI_2).abs() < 1e-10);
let acos = Expr::Call("acos".to_string(), vec![Expr::Number(1.0)]);
assert!(eval_s(&acos, &env).abs() < 1e-10);
let atan = Expr::Call("atan".to_string(), vec![Expr::Number(1.0)]);
assert!((eval_s(&atan, &env) - std::f64::consts::FRAC_PI_4).abs() < 1e-10);
}
#[test]
fn test_format_number_integer() {
assert_eq!(format_number(0.0), "0");
assert_eq!(format_number(42.0), "42");
assert_eq!(format_number(-5.0), "-5");
assert_eq!(format_number(400.0), "400");
}
#[allow(clippy::approx_constant)]
#[test]
fn test_format_number_float() {
assert_eq!(format_number(2.5), "2.5");
assert_eq!(format_number(3.14), "3.14");
assert_eq!(format_number(0.1 + 0.2), "0.3");
}
#[test]
fn test_format_number_sci() {
let s = format_number(1e-12);
assert!(s.contains('e'), "expected sci notation, got: {s}");
assert!((s.parse::<f64>().unwrap() - 1e-12).abs() < 1e-25);
let s = format_number(1e20);
assert!(s.contains('e'), "expected sci notation, got: {s}");
assert!((s.parse::<f64>().unwrap() - 1e20).abs() < 1e10);
}
#[test]
fn test_format_value_dec_integer() {
assert_eq!(
format_scalar(42.0, Base::Dec, &FormatMode::Custom(10)),
"42"
);
assert_eq!(
format_scalar(-5.0, Base::Dec, &FormatMode::Custom(10)),
"-5"
);
}
#[allow(clippy::approx_constant)]
#[test]
fn test_format_value_dec_float() {
assert_eq!(
format_scalar(3.14, Base::Dec, &FormatMode::Custom(2)),
"3.14"
);
assert_eq!(
format_scalar(1.0 / 3.0, Base::Dec, &FormatMode::Custom(4)),
"0.3333"
);
}
#[test]
fn test_format_value_dec_sci_large() {
let result = format_scalar(1e20, Base::Dec, &FormatMode::Custom(2));
assert!(
result.contains('e'),
"expected scientific notation, got: {result}"
);
}
#[test]
fn test_format_value_dec_sci_small() {
let result = format_scalar(1e-10, Base::Dec, &FormatMode::Custom(4));
assert!(
result.contains('e'),
"expected scientific notation, got: {result}"
);
}
#[test]
fn test_format_value_hex() {
assert_eq!(
format_scalar(255.0, Base::Hex, &FormatMode::Custom(10)),
"0xFF"
);
assert_eq!(
format_scalar(256.0, Base::Hex, &FormatMode::Custom(10)),
"0x100"
);
assert_eq!(
format_scalar(0.0, Base::Hex, &FormatMode::Custom(10)),
"0x0"
);
}
#[test]
fn test_format_value_bin() {
assert_eq!(
format_scalar(10.0, Base::Bin, &FormatMode::Custom(10)),
"0b1010"
);
assert_eq!(
format_scalar(1.0, Base::Bin, &FormatMode::Custom(10)),
"0b1"
);
}
#[test]
fn test_format_value_oct() {
assert_eq!(
format_scalar(8.0, Base::Oct, &FormatMode::Custom(10)),
"0o10"
);
assert_eq!(
format_scalar(255.0, Base::Oct, &FormatMode::Custom(10)),
"0o377"
);
}
#[test]
fn test_format_non_dec_negative() {
assert_eq!(format_non_dec(-16.0, Base::Hex), "-0x10");
assert_eq!(format_non_dec(-2.0, Base::Bin), "-0b10");
}
#[test]
fn test_format_value_hex_rounds() {
assert_eq!(
format_scalar(255.6, Base::Hex, &FormatMode::Custom(10)),
"0x100"
);
}
#[test]
fn test_format_short() {
let m = &FormatMode::Short;
assert_eq!(format_scalar(std::f64::consts::PI, Base::Dec, m), "3.1416");
assert_eq!(format_scalar(1.0 / 3.0, Base::Dec, m), "0.33333");
assert_eq!(format_scalar(42.0, Base::Dec, m), "42");
assert_eq!(format_scalar(0.001, Base::Dec, m), "0.001");
assert_eq!(format_scalar(0.0001, Base::Dec, m), "1e-04");
assert_eq!(format_scalar(1234567.89, Base::Dec, m), "1.2346e+06");
}
#[test]
fn test_format_long() {
let m = &FormatMode::Long;
assert_eq!(
format_scalar(std::f64::consts::PI, Base::Dec, m),
"3.14159265358979"
);
assert_eq!(format_scalar(42.0, Base::Dec, m), "42");
}
#[test]
fn test_format_shorte() {
let m = &FormatMode::ShortE;
assert_eq!(
format_scalar(std::f64::consts::PI, Base::Dec, m),
"3.1416e+00"
);
assert_eq!(format_scalar(1234.5, Base::Dec, m), "1.2345e+03");
}
#[test]
fn test_format_bank() {
let m = &FormatMode::Bank;
assert_eq!(format_scalar(1.0 / 3.0, Base::Dec, m), "0.33");
assert_eq!(format_scalar(199.999, Base::Dec, m), "200.00");
assert_eq!(format_scalar(3.0, Base::Dec, m), "3.00");
}
#[test]
fn test_format_rat() {
let m = &FormatMode::Rat;
assert_eq!(format_scalar(std::f64::consts::PI, Base::Dec, m), "355/113");
assert_eq!(format_scalar(1.0 / 3.0, Base::Dec, m), "1/3");
assert_eq!(format_scalar(42.0, Base::Dec, m), "42");
assert_eq!(format_scalar(0.125, Base::Dec, m), "1/8");
}
#[test]
fn test_format_hex_ieee754() {
let m = &FormatMode::Hex;
assert_eq!(format_scalar(1.0, Base::Dec, m), "3FF0000000000000");
assert_eq!(format_scalar(1.0, Base::Hex, m), "3FF0000000000000");
}
#[test]
fn test_format_plus() {
let m = &FormatMode::Plus;
assert_eq!(format_scalar(3.0, Base::Dec, m), "+");
assert_eq!(format_scalar(-2.0, Base::Dec, m), "-");
assert_eq!(format_scalar(0.0, Base::Dec, m), " ");
}
#[test]
fn test_format_custom() {
assert_eq!(
format_scalar(1.0 / 3.0, Base::Dec, &FormatMode::Custom(4)),
"0.3333"
);
assert_eq!(
format_scalar(1.0 / 3.0, Base::Dec, &FormatMode::Custom(2)),
"0.33"
);
}
#[test]
fn test_format_nan_inf() {
let m = &FormatMode::Short;
assert_eq!(format_scalar(f64::NAN, Base::Dec, m), "NaN");
assert_eq!(format_scalar(f64::INFINITY, Base::Dec, m), "Inf");
assert_eq!(format_scalar(f64::NEG_INFINITY, Base::Dec, m), "-Inf");
}
#[test]
fn test_eval_matrix_row_vector() {
let expr = Expr::Matrix(vec![vec![
Expr::Number(1.0),
Expr::Number(2.0),
Expr::Number(3.0),
]]);
let env = empty_env();
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[1, 3]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 1]], 2.0);
assert_eq!(m[[0, 2]], 3.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_col_vector() {
let expr = Expr::Matrix(vec![
vec![Expr::Number(1.0)],
vec![Expr::Number(2.0)],
vec![Expr::Number(3.0)],
]);
let env = empty_env();
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[3, 1]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 0]], 2.0);
assert_eq!(m[[2, 0]], 3.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_2x2() {
let expr = Expr::Matrix(vec![
vec![Expr::Number(1.0), Expr::Number(2.0)],
vec![Expr::Number(3.0), Expr::Number(4.0)],
]);
let env = empty_env();
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 2]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 1]], 2.0);
assert_eq!(m[[1, 0]], 3.0);
assert_eq!(m[[1, 1]], 4.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_add() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]);
let b = Value::Matrix(array![[5.0, 6.0], [7.0, 8.0]]);
let mut env = empty_env();
env.insert("a".to_string(), a);
env.insert("b".to_string(), b);
let expr = Expr::BinOp(
Box::new(Expr::Var("a".to_string())),
Op::Add,
Box::new(Expr::Var("b".to_string())),
);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 6.0);
assert_eq!(m[[0, 1]], 8.0);
assert_eq!(m[[1, 0]], 10.0);
assert_eq!(m[[1, 1]], 12.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_scalar_mul() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]);
let mut env = empty_env();
env.insert("a".to_string(), a);
let expr = Expr::BinOp(
Box::new(Expr::Number(2.0)),
Op::Mul,
Box::new(Expr::Var("a".to_string())),
);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 2.0);
assert_eq!(m[[0, 1]], 4.0);
assert_eq!(m[[1, 0]], 6.0);
assert_eq!(m[[1, 1]], 8.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_mul() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]);
let b = Value::Matrix(array![[1.0], [1.0]]);
let mut env = empty_env();
env.insert("a".to_string(), a);
env.insert("b".to_string(), b);
let expr = Expr::BinOp(
Box::new(Expr::Var("a".to_string())),
Op::Mul,
Box::new(Expr::Var("b".to_string())),
);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 1]);
assert_eq!(m[[0, 0]], 3.0);
assert_eq!(m[[1, 0]], 7.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_mul_inner_mismatch() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0]]);
let b = Value::Matrix(array![[1.0, 2.0]]);
assert!(eval_binop(a, &Op::Mul, b).is_err());
}
#[test]
fn test_eval_matrix_elem_mul() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]);
let b = Value::Matrix(array![[2.0, 3.0], [4.0, 5.0]]);
match eval_binop(a, &Op::ElemMul, b).unwrap() {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 2.0);
assert_eq!(m[[0, 1]], 6.0);
assert_eq!(m[[1, 0]], 12.0);
assert_eq!(m[[1, 1]], 20.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_elem_div() {
use ndarray::array;
let a = Value::Matrix(array![[6.0, 8.0]]);
let b = Value::Matrix(array![[2.0, 4.0]]);
match eval_binop(a, &Op::ElemDiv, b).unwrap() {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 3.0);
assert_eq!(m[[0, 1]], 2.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_matrix_elem_pow() {
use ndarray::array;
let a = Value::Matrix(array![[2.0, 3.0]]);
let b = Value::Matrix(array![[3.0, 2.0]]);
match eval_binop(a, &Op::ElemPow, b).unwrap() {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 8.0);
assert_eq!(m[[0, 1]], 9.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_transpose_matrix() {
use ndarray::array;
let a = Value::Matrix(array![[1.0, 2.0, 3.0]]);
let mut env = empty_env();
env.insert("a".to_string(), a);
let expr = Expr::Transpose(Box::new(Expr::Var("a".to_string())));
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[3, 1]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 0]], 2.0);
assert_eq!(m[[2, 0]], 3.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_transpose_scalar() {
let expr = Expr::Transpose(Box::new(Expr::Number(5.0)));
match eval(&expr, &empty_env()).unwrap() {
Value::Scalar(n) => assert_eq!(n, 5.0),
_ => panic!("expected scalar"),
}
}
#[test]
fn test_eval_zeros() {
let expr = Expr::Call(
"zeros".to_string(),
vec![Expr::Number(2.0), Expr::Number(3.0)],
);
match eval(&expr, &empty_env()).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 3]);
assert!(m.iter().all(|&x| x == 0.0));
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_ones() {
let expr = Expr::Call(
"ones".to_string(),
vec![Expr::Number(2.0), Expr::Number(2.0)],
);
match eval(&expr, &empty_env()).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 2]);
assert!(m.iter().all(|&x| x == 1.0));
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_eye() {
let expr = Expr::Call("eye".to_string(), vec![Expr::Number(3.0)]);
match eval(&expr, &empty_env()).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[3, 3]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 1]], 1.0);
assert_eq!(m[[2, 2]], 1.0);
assert_eq!(m[[0, 1]], 0.0);
assert_eq!(m[[1, 0]], 0.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_size() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),
);
let expr = Expr::Call("size".to_string(), vec![Expr::Var("a".to_string())]);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[1, 2]);
assert_eq!(m[[0, 0]], 2.0);
assert_eq!(m[[0, 1]], 3.0);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_length_numel() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),
);
let len = Expr::Call("length".to_string(), vec![Expr::Var("a".to_string())]);
let num = Expr::Call("numel".to_string(), vec![Expr::Var("a".to_string())]);
assert_eq!(eval_s(&len, &env), 3.0);
assert_eq!(eval_s(&num, &env), 6.0);
}
#[test]
fn test_eval_trace() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]),
);
let expr = Expr::Call("trace".to_string(), vec![Expr::Var("a".to_string())]);
assert_eq!(eval_s(&expr, &env), 5.0);
}
#[test]
fn test_eval_det_2x2() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]),
);
let expr = Expr::Call("det".to_string(), vec![Expr::Var("a".to_string())]);
assert!((eval_s(&expr, &env) - (-2.0)).abs() < 1e-10);
}
#[test]
fn test_eval_det_singular() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0], [2.0, 4.0]]),
);
let expr = Expr::Call("det".to_string(), vec![Expr::Var("a".to_string())]);
assert_eq!(eval_s(&expr, &env), 0.0);
}
#[test]
fn test_eval_inv_2x2() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]),
);
let expr = Expr::Call("inv".to_string(), vec![Expr::Var("a".to_string())]);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => {
assert!((m[[0, 0]] - (-2.0)).abs() < 1e-10);
assert!((m[[0, 1]] - 1.0).abs() < 1e-10);
assert!((m[[1, 0]] - 1.5).abs() < 1e-10);
assert!((m[[1, 1]] - (-0.5)).abs() < 1e-10);
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_eval_inv_singular() {
use ndarray::array;
let mut env = empty_env();
env.insert(
"a".to_string(),
Value::Matrix(array![[1.0, 2.0], [2.0, 4.0]]),
);
let expr = Expr::Call("inv".to_string(), vec![Expr::Var("a".to_string())]);
assert!(eval(&expr, &env).is_err());
}
fn env_with_ij() -> Env {
let mut env = Env::new();
env.insert("i".to_string(), Value::Complex(0.0, 1.0));
env.insert("j".to_string(), Value::Complex(0.0, 1.0));
env.insert("ans".to_string(), Value::Scalar(0.0));
env
}
fn eval_parse(input: &str, env: &Env) -> Result<Value, String> {
use crate::parser::{Stmt, parse};
let stmt = parse(input)?;
let expr = match stmt {
Stmt::Expr(e) | Stmt::Assign(_, e) => e,
_ => return Err("Block statements not valid in expression context".to_string()),
};
eval(&expr, env)
}
#[test]
fn test_complex_imaginary_unit() {
let env = env_with_ij();
assert_eq!(env.get("i"), Some(&Value::Complex(0.0, 1.0)));
assert_eq!(env.get("j"), Some(&Value::Complex(0.0, 1.0)));
}
#[test]
fn test_complex_literal_4i() {
let env = env_with_ij();
let result = eval_parse("4*i", &env).unwrap();
assert_eq!(result, Value::Complex(0.0, 4.0));
}
#[test]
fn test_complex_literal_3_plus_4i() {
let env = env_with_ij();
let result = eval_parse("3 + 4*i", &env).unwrap();
assert_eq!(result, Value::Complex(3.0, 4.0));
}
#[test]
fn test_complex_add() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(1.0, 2.0));
env.insert("z2".to_string(), Value::Complex(3.0, 4.0));
let result = eval_parse("z1 + z2", &env).unwrap();
assert_eq!(result, Value::Complex(4.0, 6.0));
}
#[test]
fn test_complex_sub() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(5.0, 6.0));
env.insert("z2".to_string(), Value::Complex(1.0, 2.0));
let result = eval_parse("z1 - z2", &env).unwrap();
assert_eq!(result, Value::Complex(4.0, 4.0));
}
#[test]
fn test_complex_mul() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(1.0, 2.0));
env.insert("z2".to_string(), Value::Complex(3.0, 4.0));
let result = eval_parse("z1 * z2", &env).unwrap();
assert_eq!(result, Value::Complex(-5.0, 10.0));
}
#[test]
fn test_complex_mul_gives_real() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(1.0, 1.0));
env.insert("z2".to_string(), Value::Complex(1.0, -1.0));
let result = eval_parse("z1 * z2", &env).unwrap();
assert_eq!(result, Value::Scalar(2.0));
}
#[test]
fn test_complex_div() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(1.0, 2.0));
env.insert("z2".to_string(), Value::Complex(1.0, 1.0));
let result = eval_parse("z1 / z2", &env).unwrap();
assert_eq!(result, Value::Complex(1.5, 0.5));
}
#[test]
fn test_complex_plus_scalar() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(1.0, 2.0));
env.insert("x".to_string(), Value::Scalar(3.0));
let result = eval_parse("z + x", &env).unwrap();
assert_eq!(result, Value::Complex(4.0, 2.0));
}
#[test]
fn test_scalar_plus_complex() {
let mut env = empty_env();
env.insert("x".to_string(), Value::Scalar(5.0));
env.insert("z".to_string(), Value::Complex(1.0, 2.0));
let result = eval_parse("x + z", &env).unwrap();
assert_eq!(result, Value::Complex(6.0, 2.0));
}
#[test]
fn test_complex_unary_minus() {
let expr = Expr::UnaryMinus(Box::new(Expr::Var("z".to_string())));
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, -4.0));
assert_eq!(eval(&expr, &env).unwrap(), Value::Complex(-3.0, 4.0));
}
#[test]
fn test_complex_unary_not() {
let expr = Expr::UnaryNot(Box::new(Expr::Var("z".to_string())));
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, 4.0));
assert_eq!(eval(&expr, &env).unwrap(), Value::Scalar(0.0));
env.insert("z".to_string(), Value::Complex(0.0, 0.0));
assert_eq!(eval(&expr, &env).unwrap(), Value::Scalar(1.0));
}
#[test]
fn test_complex_transpose_is_conjugate() {
let expr = Expr::Transpose(Box::new(Expr::Var("z".to_string())));
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, 4.0));
assert_eq!(eval(&expr, &env).unwrap(), Value::Complex(3.0, -4.0));
}
#[test]
fn test_complex_eq() {
let mut env = empty_env();
env.insert("z1".to_string(), Value::Complex(1.0, 2.0));
env.insert("z2".to_string(), Value::Complex(1.0, 2.0));
env.insert("z3".to_string(), Value::Complex(1.0, 3.0));
let eq = eval_parse("z1 == z2", &env).unwrap();
assert_eq!(eq, Value::Scalar(1.0));
let ne = eval_parse("z1 == z3", &env).unwrap();
assert_eq!(ne, Value::Scalar(0.0));
}
#[test]
fn test_complex_ordering_error() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(1.0, 2.0));
assert!(eval_parse("z > 0", &env).is_err());
assert!(eval_parse("z < 0", &env).is_err());
}
#[test]
fn test_complex_pow_squared() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(1.0, 1.0));
let result = eval_parse("z^2", &env).unwrap();
match result {
Value::Complex(re, im) => {
assert!((re).abs() < 1e-10, "re = {re}");
assert!((im - 2.0).abs() < 1e-10, "im = {im}");
}
Value::Scalar(n) if n.abs() < 1e-10 => {} other => panic!("unexpected: {:?}", other),
}
}
#[test]
fn test_builtin_real_imag() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, 4.0));
let re = eval_parse("real(z)", &env).unwrap();
let im = eval_parse("imag(z)", &env).unwrap();
assert_eq!(re, Value::Scalar(3.0));
assert_eq!(im, Value::Scalar(4.0));
env.insert("x".to_string(), Value::Scalar(5.0));
let im2 = eval_parse("imag(x)", &env).unwrap();
assert_eq!(im2, Value::Scalar(0.0));
}
#[test]
fn test_builtin_abs_complex() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, 4.0));
let result = eval_parse("abs(z)", &env).unwrap();
assert_eq!(result, Value::Scalar(5.0));
}
#[test]
fn test_builtin_angle() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(0.0, 1.0)); let result = eval_parse("angle(z)", &env).unwrap();
match result {
Value::Scalar(n) => assert!((n - std::f64::consts::FRAC_PI_2).abs() < 1e-10),
other => panic!("{:?}", other),
}
}
#[test]
fn test_builtin_conj() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(3.0, 4.0));
let result = eval_parse("conj(z)", &env).unwrap();
assert_eq!(result, Value::Complex(3.0, -4.0));
}
#[test]
fn test_builtin_complex_construct() {
let env = empty_env();
let result = eval_parse("complex(3, 4)", &env).unwrap();
assert_eq!(result, Value::Complex(3.0, 4.0));
let result2 = eval_parse("complex(5, 0)", &env).unwrap();
assert_eq!(result2, Value::Scalar(5.0));
}
#[test]
fn test_builtin_isreal() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(1.0, 2.0));
env.insert("x".to_string(), Value::Scalar(3.0));
assert_eq!(eval_parse("isreal(z)", &env).unwrap(), Value::Scalar(0.0));
assert_eq!(eval_parse("isreal(x)", &env).unwrap(), Value::Scalar(1.0));
}
#[test]
fn test_format_complex_display() {
let m = &FormatMode::Custom(10);
assert_eq!(format_complex(3.0, 4.0, m), "3 + 4i");
assert_eq!(format_complex(3.0, -4.0, m), "3 - 4i");
assert_eq!(format_complex(0.0, 1.0, m), "i");
assert_eq!(format_complex(0.0, -1.0, m), "-i");
assert_eq!(format_complex(0.0, 2.0, m), "2i");
assert_eq!(format_complex(3.0, 0.0, m), "3");
assert_eq!(format_complex(1.0, 1.0, m), "1 + i");
assert_eq!(format_complex(1.0, -1.0, m), "1 - i");
}
#[test]
fn test_complex_matrix_literal_error() {
let env = env_with_ij();
let result = eval_parse("[1+2*i, 3]", &env);
assert!(result.is_err());
}
#[test]
fn test_scalar_arg_accepts_complex_with_zero_im() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(4.0, 0.0));
let result = eval_parse("sqrt(z)", &env).unwrap();
assert_eq!(result, Value::Scalar(2.0));
}
#[test]
fn test_sin_vector() {
let env = empty_env();
let result = eval_parse("sin([0, pi/2, pi])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 3));
assert!((m[[0, 0]] - 0.0).abs() < 1e-15);
assert!((m[[0, 1]] - 1.0).abs() < 1e-15);
assert!((m[[0, 2]] - 0.0).abs() < 1e-14);
}
#[test]
fn test_cos_vector() {
let env = empty_env();
let result = eval_parse("cos([0, pi/2, pi])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 3));
assert!((m[[0, 0]] - 1.0).abs() < 1e-15);
assert!((m[[0, 2]] - (-1.0)).abs() < 1e-15);
}
#[test]
fn test_exp_vector() {
let env = empty_env();
let result = eval_parse("exp([0, 1, 2])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 3));
assert!((m[[0, 0]] - 1.0).abs() < 1e-15);
assert!((m[[0, 1]] - std::f64::consts::E).abs() < 1e-14);
}
#[test]
fn test_sqrt_vector() {
let env = empty_env();
let result = eval_parse("sqrt([1, 4, 9, 16])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 4));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 1]], 2.0);
assert_eq!(m[[0, 2]], 3.0);
assert_eq!(m[[0, 3]], 4.0);
}
#[test]
fn test_floor_ceil_matrix() {
let env = empty_env();
let r = eval_parse("floor([1.2, 2.7; -0.5, 3.9])", &env).unwrap();
let Value::Matrix(m) = r else {
panic!("expected matrix")
};
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 1]], 2.0);
assert_eq!(m[[1, 0]], -1.0);
assert_eq!(m[[1, 1]], 3.0);
}
#[test]
fn test_complex_pow_zero_base() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(0.0, 0.0));
let result = eval_parse("z^2", &env).unwrap();
assert_eq!(result, Value::Scalar(0.0));
}
#[test]
fn test_complex_inv_builtin() {
let mut env = empty_env();
env.insert("z".to_string(), Value::Complex(2.0, 0.0));
let result = eval_parse("inv(z)", &env).unwrap();
assert_eq!(result, Value::Scalar(0.5));
}
#[test]
fn test_str_literal_basic() {
let env = empty_env();
let expr = Expr::StrLiteral("hello".to_string());
assert_eq!(eval(&expr, &env), Ok(Value::Str("hello".to_string())));
}
#[test]
fn test_string_obj_literal_basic() {
let env = empty_env();
let expr = Expr::StringObjLiteral("world".to_string());
assert_eq!(eval(&expr, &env), Ok(Value::StringObj("world".to_string())));
}
#[test]
fn test_str_arithmetic_single_char() {
let env = empty_env();
let expr = Expr::BinOp(
Box::new(Expr::StrLiteral("a".to_string())),
Op::Add,
Box::new(Expr::Number(0.0)),
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(97.0)));
}
#[test]
fn test_str_arithmetic_multi_char() {
use ndarray::array;
let env = empty_env();
let expr = Expr::BinOp(
Box::new(Expr::StrLiteral("ab".to_string())),
Op::Add,
Box::new(Expr::Number(0.0)),
);
match eval(&expr, &env).unwrap() {
Value::Matrix(m) => assert_eq!(m, array![[97.0, 98.0]]),
_ => panic!("expected matrix"),
}
}
#[test]
fn test_string_obj_concat() {
let env = empty_env();
let expr = Expr::BinOp(
Box::new(Expr::StringObjLiteral("hello".to_string())),
Op::Add,
Box::new(Expr::StringObjLiteral(" world".to_string())),
);
assert_eq!(
eval(&expr, &env),
Ok(Value::StringObj("hello world".to_string()))
);
}
#[test]
fn test_string_obj_eq() {
let env = empty_env();
let expr = Expr::BinOp(
Box::new(Expr::StringObjLiteral("abc".to_string())),
Op::Eq,
Box::new(Expr::StringObjLiteral("abc".to_string())),
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_num2str() {
let env = empty_env();
let expr = Expr::Call("num2str".to_string(), vec![Expr::Number(42.0)]);
assert_eq!(eval(&expr, &env), Ok(Value::Str("42".to_string())));
}
#[allow(clippy::approx_constant)]
#[test]
fn test_str2double_valid() {
let env = empty_env();
let expr = Expr::Call(
"str2double".to_string(),
vec![Expr::StrLiteral("3.14".to_string())],
);
match eval(&expr, &env).unwrap() {
Value::Scalar(n) => assert!((n - 3.14).abs() < 1e-10),
_ => panic!("expected scalar"),
}
}
#[test]
fn test_str2double_invalid() {
let env = empty_env();
let expr = Expr::Call(
"str2double".to_string(),
vec![Expr::StrLiteral("abc".to_string())],
);
match eval(&expr, &env).unwrap() {
Value::Scalar(n) => assert!(n.is_nan()),
_ => panic!("expected scalar"),
}
}
#[test]
fn test_strcat_char_arrays() {
let env = empty_env();
let expr = Expr::Call(
"strcat".to_string(),
vec![
Expr::StrLiteral("hello".to_string()),
Expr::StrLiteral(" world".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Str("hello world".to_string())));
}
#[test]
fn test_strcmp_equal() {
let env = empty_env();
let expr = Expr::Call(
"strcmp".to_string(),
vec![
Expr::StrLiteral("abc".to_string()),
Expr::StrLiteral("abc".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_strcmp_not_equal() {
let env = empty_env();
let expr = Expr::Call(
"strcmp".to_string(),
vec![
Expr::StrLiteral("abc".to_string()),
Expr::StrLiteral("def".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_length_of_str() {
let env = empty_env();
let expr = Expr::Call(
"length".to_string(),
vec![Expr::StrLiteral("hello".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(5.0)));
}
#[test]
fn test_ischar_true() {
let env = empty_env();
let expr = Expr::Call(
"ischar".to_string(),
vec![Expr::StrLiteral("hi".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_ischar_false_for_number() {
let env = empty_env();
let expr = Expr::Call("ischar".to_string(), vec![Expr::Number(5.0)]);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_isstring_true() {
let env = empty_env();
let expr = Expr::Call(
"isstring".to_string(),
vec![Expr::StringObjLiteral("hi".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_lower_upper() {
let env = empty_env();
let lower_expr = Expr::Call(
"lower".to_string(),
vec![Expr::StrLiteral("HELLO".to_string())],
);
assert_eq!(eval(&lower_expr, &env), Ok(Value::Str("hello".to_string())));
let upper_expr = Expr::Call(
"upper".to_string(),
vec![Expr::StrLiteral("hello".to_string())],
);
assert_eq!(eval(&upper_expr, &env), Ok(Value::Str("HELLO".to_string())));
}
#[test]
fn test_strtrim() {
let env = empty_env();
let expr = Expr::Call(
"strtrim".to_string(),
vec![Expr::StrLiteral(" hello ".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Str("hello".to_string())));
}
#[test]
fn test_strrep() {
let env = empty_env();
let expr = Expr::Call(
"strrep".to_string(),
vec![
Expr::StrLiteral("hello world".to_string()),
Expr::StrLiteral("world".to_string()),
Expr::StrLiteral("Rust".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Str("hello Rust".to_string())));
}
#[test]
fn test_strcmpi_case_insensitive() {
let env = empty_env();
let expr = Expr::Call(
"strcmpi".to_string(),
vec![
Expr::StrLiteral("Hello".to_string()),
Expr::StrLiteral("hello".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_printf_no_args() {
assert_eq!(format_printf("hello", &[]).unwrap(), "hello");
}
#[test]
fn test_printf_escape_sequences() {
assert_eq!(format_printf("a\\nb\\tc", &[]).unwrap(), "a\nb\tc");
assert_eq!(format_printf("back\\\\slash", &[]).unwrap(), "back\\slash");
}
#[test]
fn test_printf_percent_literal() {
assert_eq!(format_printf("100%%", &[]).unwrap(), "100%");
}
#[test]
fn test_printf_d() {
let args = vec![Value::Scalar(42.0)];
assert_eq!(format_printf("%d", &args).unwrap(), "42");
}
#[test]
fn test_printf_d_negative() {
let args = vec![Value::Scalar(-7.0)];
assert_eq!(format_printf("%d", &args).unwrap(), "-7");
}
#[test]
fn test_printf_d_float_truncated() {
let args = vec![Value::Scalar(3.9)];
assert_eq!(format_printf("%d", &args).unwrap(), "3");
}
#[allow(clippy::approx_constant)]
#[test]
fn test_printf_f_default() {
let args = vec![Value::Scalar(3.14159)];
let s = format_printf("%f", &args).unwrap();
assert_eq!(s, "3.141590");
}
#[allow(clippy::approx_constant)]
#[test]
fn test_printf_f_precision() {
let args = vec![Value::Scalar(3.14159)];
assert_eq!(format_printf("%.2f", &args).unwrap(), "3.14");
}
#[test]
fn test_printf_f_precision_zero() {
let args = vec![Value::Scalar(3.7)];
assert_eq!(format_printf("%.0f", &args).unwrap(), "4");
}
#[test]
fn test_printf_e() {
let args = vec![Value::Scalar(12345.6789)];
let s = format_printf("%e", &args).unwrap();
assert_eq!(s, "1.234568e+04");
}
#[test]
fn test_printf_e_precision() {
let args = vec![Value::Scalar(1.0)];
assert_eq!(format_printf("%.2e", &args).unwrap(), "1.00e+00");
}
#[allow(clippy::approx_constant)]
#[test]
fn test_printf_g_small() {
let args = vec![Value::Scalar(3.14)];
let s = format_printf("%g", &args).unwrap();
assert_eq!(s, "3.14");
}
#[test]
fn test_printf_g_large() {
let args = vec![Value::Scalar(1234567.0)];
let s = format_printf("%g", &args).unwrap();
assert!(s.contains('e'), "expected scientific notation, got {s}");
}
#[test]
fn test_printf_g_trailing_zeros_removed() {
let args = vec![Value::Scalar(1.0)];
assert_eq!(format_printf("%g", &args).unwrap(), "1");
}
#[test]
fn test_printf_s_char_array() {
let args = vec![Value::Str("hello".to_string())];
assert_eq!(format_printf("%s", &args).unwrap(), "hello");
}
#[test]
fn test_printf_s_string_obj() {
let args = vec![Value::StringObj("world".to_string())];
assert_eq!(format_printf("%s", &args).unwrap(), "world");
}
#[test]
fn test_printf_width_right_align() {
let args = vec![Value::Scalar(42.0)];
assert_eq!(format_printf("%6d", &args).unwrap(), " 42");
}
#[test]
fn test_printf_width_left_align() {
let args = vec![Value::Scalar(42.0)];
assert_eq!(format_printf("%-6d", &args).unwrap(), "42 ");
}
#[test]
fn test_printf_zero_pad() {
let args = vec![Value::Scalar(42.0)];
assert_eq!(format_printf("%06d", &args).unwrap(), "000042");
}
#[test]
fn test_printf_force_sign() {
let args = vec![Value::Scalar(42.0)];
assert_eq!(format_printf("%+d", &args).unwrap(), "+42");
}
#[test]
fn test_printf_multiple_args() {
let args = vec![Value::Scalar(3.0), Value::Scalar(4.0)];
assert_eq!(format_printf("%d + %d", &args).unwrap(), "3 + 4");
}
#[test]
fn test_printf_repeat_format_octave() {
let args = vec![Value::Scalar(1.0), Value::Scalar(2.0), Value::Scalar(3.0)];
assert_eq!(format_printf("%d ", &args).unwrap(), "1 2 3 ");
}
#[test]
fn test_printf_more_specifiers_than_args() {
let args = vec![Value::Scalar(1.0)];
assert_eq!(format_printf("%d %d", &args).unwrap(), "1 ");
}
#[test]
fn test_printf_mixed_types() {
let args = vec![
Value::Str("pi".to_string()),
Value::Scalar(std::f64::consts::PI),
];
let s = format_printf("%s = %.4f", &args).unwrap();
assert_eq!(s, "pi = 3.1416");
}
#[test]
fn test_printf_s_precision_truncate() {
let args = vec![Value::Str("hello".to_string())];
assert_eq!(format_printf("%.3s", &args).unwrap(), "hel");
}
#[test]
fn test_sprintf_via_eval() {
let env = empty_env();
let expr = Expr::Call(
"sprintf".to_string(),
vec![Expr::StrLiteral("x = %d".to_string()), Expr::Number(5.0)],
);
assert_eq!(eval(&expr, &env), Ok(Value::Str("x = 5".to_string())));
}
#[test]
fn test_sprintf_no_args_escape() {
let env = empty_env();
let expr = Expr::Call(
"sprintf".to_string(),
vec![Expr::StrLiteral("a\\nb".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Str("a\nb".to_string())));
}
#[test]
fn test_fprintf_returns_void() {
let env = empty_env();
let expr = Expr::Call(
"fprintf".to_string(),
vec![Expr::StrLiteral("".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Void));
}
#[test]
fn test_fopen_write_and_fclose() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let tmp = std::env::temp_dir().join("ccalc_test_fopen_write.txt");
let path = tmp.to_string_lossy().to_string();
let open_expr = Expr::Call(
"fopen".to_string(),
vec![
Expr::StrLiteral(path.clone()),
Expr::StrLiteral("w".to_string()),
],
);
let fd_val = eval_with_io(&open_expr, &env, &mut io).unwrap();
let fd = match fd_val {
Value::Scalar(n) => n,
_ => panic!("expected scalar fd"),
};
assert!(fd >= 3.0, "expected fd >= 3, got {fd}");
let close_expr = Expr::Call("fclose".to_string(), vec![Expr::Number(fd)]);
let result = eval_with_io(&close_expr, &env, &mut io).unwrap();
assert_eq!(result, Value::Scalar(0.0));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_fopen_nonexistent_returns_minus_one() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let expr = Expr::Call(
"fopen".to_string(),
vec![
Expr::StrLiteral("/nonexistent/path/file.txt".to_string()),
Expr::StrLiteral("r".to_string()),
],
);
let result = eval_with_io(&expr, &env, &mut io).unwrap();
assert_eq!(result, Value::Scalar(-1.0));
}
#[test]
fn test_fgetl_reads_lines() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let tmp = std::env::temp_dir().join("ccalc_test_fgetl.txt");
std::fs::write(&tmp, "hello\nworld\n").unwrap();
let path = tmp.to_string_lossy().to_string();
let fd = io.fopen(&path, "r");
assert!(fd >= 3);
let expr_fgetl = |fd: i32| Expr::Call("fgetl".to_string(), vec![Expr::Number(fd as f64)]);
let line1 = eval_with_io(&expr_fgetl(fd), &env, &mut io).unwrap();
assert_eq!(line1, Value::Str("hello".to_string()));
let line2 = eval_with_io(&expr_fgetl(fd), &env, &mut io).unwrap();
assert_eq!(line2, Value::Str("world".to_string()));
let eof = eval_with_io(&expr_fgetl(fd), &env, &mut io).unwrap();
assert_eq!(eof, Value::Scalar(-1.0));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_fgets_keeps_newline() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let tmp = std::env::temp_dir().join("ccalc_test_fgets.txt");
std::fs::write(&tmp, "hello\n").unwrap();
let path = tmp.to_string_lossy().to_string();
let fd = io.fopen(&path, "r");
let expr = Expr::Call("fgets".to_string(), vec![Expr::Number(fd as f64)]);
let result = eval_with_io(&expr, &env, &mut io).unwrap();
assert_eq!(result, Value::Str("hello\n".to_string()));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_fclose_all() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let tmp1 = std::env::temp_dir().join("ccalc_test_fclose_all_1.txt");
let tmp2 = std::env::temp_dir().join("ccalc_test_fclose_all_2.txt");
io.fopen(&tmp1.to_string_lossy(), "w");
io.fopen(&tmp2.to_string_lossy(), "w");
let expr = Expr::Call(
"fclose".to_string(),
vec![Expr::StrLiteral("all".to_string())],
);
let result = eval_with_io(&expr, &env, &mut io).unwrap();
assert_eq!(result, Value::Scalar(0.0));
let _ = std::fs::remove_file(&tmp1);
let _ = std::fs::remove_file(&tmp2);
}
#[test]
fn test_fprintf_to_file() {
use crate::io::IoContext;
let env = empty_env();
let mut io = IoContext::new();
let tmp = std::env::temp_dir().join("ccalc_test_fprintf_file.txt");
let path = tmp.to_string_lossy().to_string();
let fd = io.fopen(&path, "w") as f64;
let expr = Expr::Call(
"fprintf".to_string(),
vec![
Expr::Number(fd),
Expr::StrLiteral("value = %d\n".to_string()),
Expr::Number(42.0),
],
);
let result = eval_with_io(&expr, &env, &mut io).unwrap();
assert_eq!(result, Value::Void);
io.fclose(fd as i32);
let content = std::fs::read_to_string(&tmp).unwrap();
assert_eq!(content, "value = 42\n");
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_dlmwrite_and_dlmread_comma() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_comma.csv");
let path = tmp.to_string_lossy().to_string();
let write_expr = Expr::Call(
"dlmwrite".to_string(),
vec![
Expr::StrLiteral(path.clone()),
Expr::Matrix(vec![
vec![Expr::Number(1.0), Expr::Number(2.0), Expr::Number(3.0)],
vec![Expr::Number(4.0), Expr::Number(5.0), Expr::Number(6.0)],
]),
],
);
assert_eq!(eval(&write_expr, &env), Ok(Value::Void));
let content = std::fs::read_to_string(&tmp).unwrap();
assert_eq!(content, "1,2,3\n4,5,6\n");
let read_expr = Expr::Call("dlmread".to_string(), vec![Expr::StrLiteral(path.clone())]);
match eval(&read_expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 3]);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 2]], 6.0);
}
other => panic!("expected matrix, got {other:?}"),
}
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_dlmwrite_tab_delimiter() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_tab.tsv");
let path = tmp.to_string_lossy().to_string();
let write_expr = Expr::Call(
"dlmwrite".to_string(),
vec![
Expr::StrLiteral(path.clone()),
Expr::Matrix(vec![vec![Expr::Number(10.0), Expr::Number(20.0)]]),
Expr::StrLiteral(r"\t".to_string()),
],
);
assert_eq!(eval(&write_expr, &env), Ok(Value::Void));
let content = std::fs::read_to_string(&tmp).unwrap();
assert_eq!(content, "10\t20\n");
let read_expr = Expr::Call(
"dlmread".to_string(),
vec![
Expr::StrLiteral(path.clone()),
Expr::StrLiteral(r"\t".to_string()),
],
);
match eval(&read_expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[1, 2]);
assert_eq!(m[[0, 0]], 10.0);
assert_eq!(m[[0, 1]], 20.0);
}
other => panic!("expected matrix, got {other:?}"),
}
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_dlmread_whitespace_auto() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_ws.txt");
std::fs::write(&tmp, "1 2 3\n4 5 6\n").unwrap();
let read_expr = Expr::Call(
"dlmread".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
match eval(&read_expr, &env).unwrap() {
Value::Matrix(m) => {
assert_eq!(m.shape(), &[2, 3]);
assert_eq!(m[[0, 2]], 3.0);
assert_eq!(m[[1, 0]], 4.0);
}
other => panic!("expected matrix, got {other:?}"),
}
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_dlmread_empty_file() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_empty.csv");
std::fs::write(&tmp, "").unwrap();
let read_expr = Expr::Call(
"dlmread".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
match eval(&read_expr, &env).unwrap() {
Value::Matrix(m) => assert_eq!(m.shape(), &[0, 0]),
other => panic!("expected empty matrix, got {other:?}"),
}
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_dlmread_non_numeric_error() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_bad.csv");
std::fs::write(&tmp, "1,2,three\n").unwrap();
let read_expr = Expr::Call(
"dlmread".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
let result = eval(&read_expr, &env);
assert!(result.is_err());
assert!(result.unwrap_err().contains("non-numeric"));
let _ = std::fs::remove_file(&tmp);
}
#[allow(clippy::approx_constant)]
#[test]
fn test_dlmwrite_scalar() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_dlm_scalar.csv");
let path = tmp.to_string_lossy().to_string();
let write_expr = Expr::Call(
"dlmwrite".to_string(),
vec![Expr::StrLiteral(path.clone()), Expr::Number(3.14)],
);
assert_eq!(eval(&write_expr, &env), Ok(Value::Void));
let content = std::fs::read_to_string(&tmp).unwrap();
assert!(content.starts_with("3.14"));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_isfile_existing_file() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_isfile.txt");
std::fs::write(&tmp, "").unwrap();
let expr = Expr::Call(
"isfile".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_isfile_nonexistent() {
let env = empty_env();
let expr = Expr::Call(
"isfile".to_string(),
vec![Expr::StrLiteral("/no/such/file.txt".to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_isfile_directory_is_false() {
let env = empty_env();
let dir = std::env::temp_dir();
let expr = Expr::Call(
"isfile".to_string(),
vec![Expr::StrLiteral(dir.to_string_lossy().to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_isfolder_existing_dir() {
let env = empty_env();
let dir = std::env::temp_dir();
let expr = Expr::Call(
"isfolder".to_string(),
vec![Expr::StrLiteral(dir.to_string_lossy().to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_isfolder_file_is_false() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_isfolder.txt");
std::fs::write(&tmp, "").unwrap();
let expr = Expr::Call(
"isfolder".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_pwd_returns_string() {
let env = empty_env();
let expr = Expr::Call("pwd".to_string(), vec![]);
let result = eval(&expr, &env).unwrap();
match result {
Value::Str(s) => assert!(!s.is_empty()),
other => panic!("expected Str, got {other:?}"),
}
}
#[test]
fn test_exist_var_found() {
let mut env = empty_env();
env.insert("myvar".to_string(), Value::Scalar(42.0));
let expr = Expr::Call(
"exist".to_string(),
vec![
Expr::StrLiteral("myvar".to_string()),
Expr::StrLiteral("var".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
}
#[test]
fn test_exist_var_not_found() {
let env = empty_env();
let expr = Expr::Call(
"exist".to_string(),
vec![
Expr::StrLiteral("novar".to_string()),
Expr::StrLiteral("var".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_exist_file_found() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_test_exist_file.txt");
std::fs::write(&tmp, "").unwrap();
let expr = Expr::Call(
"exist".to_string(),
vec![
Expr::StrLiteral(tmp.to_string_lossy().to_string()),
Expr::StrLiteral("file".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(2.0)));
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_exist_file_not_found() {
let env = empty_env();
let expr = Expr::Call(
"exist".to_string(),
vec![
Expr::StrLiteral("/no/such/file.txt".to_string()),
Expr::StrLiteral("file".to_string()),
],
);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_exist_one_arg_checks_var_then_file() {
let mut env = empty_env();
env.insert("x".to_string(), Value::Scalar(1.0));
let expr = Expr::Call("exist".to_string(), vec![Expr::StrLiteral("x".to_string())]);
assert_eq!(eval(&expr, &env), Ok(Value::Scalar(1.0)));
let expr2 = Expr::Call(
"exist".to_string(),
vec![Expr::StrLiteral("novar".to_string())],
);
assert_eq!(eval(&expr2, &env), Ok(Value::Scalar(0.0)));
}
#[test]
fn test_genpath_includes_root() {
let env = empty_env();
let tmp = std::env::temp_dir();
let expr = Expr::Call(
"genpath".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
let result = eval(&expr, &env).unwrap();
match result {
Value::Str(s) => {
let sep = if cfg!(windows) { ';' } else { ':' };
let parts: Vec<&str> = s.split(sep).collect();
assert!(
parts[0] == tmp.to_string_lossy().as_ref(),
"root dir must be first entry"
);
}
other => panic!("expected Str, got {other:?}"),
}
}
#[test]
fn test_genpath_includes_subdirs() {
let env = empty_env();
let tmp = std::env::temp_dir().join("ccalc_genpath_test");
let sub = tmp.join("sub");
std::fs::create_dir_all(&sub).unwrap();
let expr = Expr::Call(
"genpath".to_string(),
vec![Expr::StrLiteral(tmp.to_string_lossy().to_string())],
);
let result = eval(&expr, &env).unwrap();
let _ = std::fs::remove_dir_all(&tmp);
match result {
Value::Str(s) => {
let sep = if cfg!(windows) { ';' } else { ':' };
let parts: Vec<&str> = s.split(sep).collect();
assert!(
parts.len() >= 2,
"should include root and at least one subdir"
);
assert!(parts.iter().any(|p| p.ends_with("sub")));
}
other => panic!("expected Str, got {other:?}"),
}
}
#[test]
fn test_genpath_nonexistent_returns_empty() {
let env = empty_env();
let expr = Expr::Call(
"genpath".to_string(),
vec![Expr::StrLiteral("/does/not/exist/ccalc_xyz".to_string())],
);
let result = eval(&expr, &env).unwrap();
assert_eq!(result, Value::Str(String::new()));
}
#[test]
fn test_log_is_natural_log() {
let env = empty_env();
let result = eval(
&Expr::Call("log".to_string(), vec![Expr::Number(std::f64::consts::E)]),
&env,
)
.unwrap();
assert_eq!(result, Value::Scalar(1.0));
}
#[test]
fn test_log10() {
let env = empty_env();
let result = eval(
&Expr::Call("log10".to_string(), vec![Expr::Number(100.0)]),
&env,
)
.unwrap();
assert_eq!(result, Value::Scalar(2.0));
}
#[test]
fn test_log2() {
let env = empty_env();
let result = eval(
&Expr::Call("log2".to_string(), vec![Expr::Number(8.0)]),
&env,
)
.unwrap();
assert_eq!(result, Value::Scalar(3.0));
}
#[test]
fn test_inf_capital() {
let env = empty_env();
let result = eval_parse("Inf", &env).unwrap();
assert!(matches!(result, Value::Scalar(v) if v.is_infinite() && v > 0.0));
}
#[test]
fn test_nan_capital() {
let env = empty_env();
let result = eval_parse("NaN", &env).unwrap();
assert!(matches!(result, Value::Scalar(v) if v.is_nan()));
}
#[test]
fn test_diag_row_vector_to_matrix() {
let env = empty_env();
let result = eval_parse("diag([1 2 3])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 3));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 1]], 2.0);
assert_eq!(m[[2, 2]], 3.0);
assert_eq!(m[[0, 1]], 0.0);
assert_eq!(m[[1, 0]], 0.0);
}
#[test]
fn test_diag_col_vector_to_matrix() {
let env = empty_env();
let result = eval_parse("diag([4; 5; 6])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 3));
assert_eq!(m[[0, 0]], 4.0);
assert_eq!(m[[1, 1]], 5.0);
assert_eq!(m[[2, 2]], 6.0);
}
#[test]
fn test_diag_square_matrix_extract() {
let env = empty_env();
let result = eval_parse("diag([1 2 3; 4 5 6; 7 8 9])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 1));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 0]], 5.0);
assert_eq!(m[[2, 0]], 9.0);
}
#[test]
fn test_diag_nonsquare_matrix_extract() {
let env = empty_env();
let result = eval_parse("diag([1 2 3 4; 5 6 7 8])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 1));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 0]], 6.0);
}
#[test]
fn test_diag_scalar() {
let env = empty_env();
let result = eval_parse("diag([7])", &env).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 1));
assert_eq!(m[[0, 0]], 7.0);
}
#[test]
fn test_diag_roundtrip() {
let env = empty_env();
let d = eval_parse("diag([1 2 3])", &env).unwrap();
let Value::Matrix(dm) = &d else {
panic!("expected matrix")
};
assert_eq!(dm[[0, 0]], 1.0);
assert_eq!(dm[[1, 1]], 2.0);
assert_eq!(dm[[2, 2]], 3.0);
let mut env2 = env.clone();
env2.insert("D".to_string(), d);
let result = eval_parse("diag(D)", &env2).unwrap();
let Value::Matrix(v) = result else {
panic!("expected matrix")
};
assert_eq!(v.dim(), (3, 1));
assert_eq!(v[[0, 0]], 1.0);
assert_eq!(v[[1, 0]], 2.0);
assert_eq!(v[[2, 0]], 3.0);
}
#[test]
fn test_matrix_horzcat_col_vector() {
let env = empty_env();
let result = eval_parse("[1 2; 3 4]", &env).unwrap();
let mut env2 = env.clone();
env2.insert("A".to_string(), result);
let b = eval_parse("[5; 6]", &env).unwrap();
env2.insert("b".to_string(), b);
let aug = eval_parse("[A, b]", &env2).unwrap();
let Value::Matrix(m) = aug else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 3));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 2]], 5.0);
assert_eq!(m[[1, 2]], 6.0);
}
#[test]
fn test_matrix_horzcat_two_matrices() {
let env = empty_env();
let a = eval_parse("[1 2; 3 4]", &env).unwrap();
let b = eval_parse("[5 6; 7 8]", &env).unwrap();
let mut env2 = env.clone();
env2.insert("A".to_string(), a);
env2.insert("B".to_string(), b);
let result = eval_parse("[A, B]", &env2).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 4));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 2]], 5.0);
assert_eq!(m[[1, 3]], 8.0);
}
#[test]
fn test_matrix_vertcat_two_matrices() {
let env = empty_env();
let a = eval_parse("[1 2; 3 4]", &env).unwrap();
let b = eval_parse("[5 6; 7 8]", &env).unwrap();
let mut env2 = env.clone();
env2.insert("A".to_string(), a);
env2.insert("B".to_string(), b);
let result = eval_parse("[A; B]", &env2).unwrap();
let Value::Matrix(m) = result else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (4, 2));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[2, 0]], 5.0);
assert_eq!(m[[3, 1]], 8.0);
}
#[test]
fn test_matrix_horzcat_height_mismatch_error() {
let env = empty_env();
let a = eval_parse("[1 2; 3 4]", &env).unwrap();
let b = eval_parse("[5; 6; 7]", &env).unwrap();
let mut env2 = env.clone();
env2.insert("A".to_string(), a);
env2.insert("b".to_string(), b);
assert!(eval_parse("[A, b]", &env2).is_err());
}
#[test]
fn test_matrix_vertcat_width_mismatch_error() {
let env = empty_env();
let a = eval_parse("[1 2; 3 4]", &env).unwrap();
let b = eval_parse("[5 6 7; 8 9 10]", &env).unwrap();
let mut env2 = env.clone();
env2.insert("A".to_string(), a);
env2.insert("B".to_string(), b);
assert!(eval_parse("[A; B]", &env2).is_err());
}
#[test]
fn test_rand_scalar_in_range() {
let env = empty_env();
let v = eval_parse("rand()", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((0.0..1.0).contains(&x));
}
#[test]
fn test_rand_square_matrix() {
let env = empty_env();
let v = eval_parse("rand(3)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 3));
for &x in m.iter() {
assert!((0.0..1.0).contains(&x));
}
}
#[test]
fn test_rand_rect_matrix() {
let env = empty_env();
let v = eval_parse("rand(2, 5)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 5));
for &x in m.iter() {
assert!((0.0..1.0).contains(&x));
}
}
#[test]
fn test_randn_scalar() {
let env = empty_env();
let v = eval_parse("randn()", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!(x.is_finite());
}
#[test]
fn test_randn_square_matrix() {
let env = empty_env();
let v = eval_parse("randn(4)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (4, 4));
for &x in m.iter() {
assert!(x.is_finite());
}
}
#[test]
fn test_randn_rect_matrix() {
let env = empty_env();
let v = eval_parse("randn(2, 6)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 6));
}
#[test]
fn test_randi_scalar_max() {
let env = empty_env();
let v = eval_parse("randi(10)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((1.0..=10.0).contains(&x) && x.fract() == 0.0);
}
#[test]
fn test_randi_square_matrix() {
let env = empty_env();
let v = eval_parse("randi(6, 3)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 3));
for &x in m.iter() {
assert!((1.0..=6.0).contains(&x) && x.fract() == 0.0);
}
}
#[test]
fn test_randi_rect_matrix() {
let env = empty_env();
let v = eval_parse("randi(100, 2, 4)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (2, 4));
for &x in m.iter() {
assert!((1.0..=100.0).contains(&x) && x.fract() == 0.0);
}
}
#[test]
fn test_rng_seed_reproducible() {
let env = empty_env();
eval_parse("rng(42)", &env).unwrap();
let v1 = eval_parse("rand()", &env).unwrap();
eval_parse("rng(42)", &env).unwrap();
let v2 = eval_parse("rand()", &env).unwrap();
assert_eq!(v1, v2);
}
#[test]
fn test_rng_shuffle_returns_void() {
let env = empty_env();
let r = eval_parse("rng('shuffle')", &env).unwrap();
assert_eq!(r, Value::Void);
}
#[test]
fn test_std_vector_sample() {
let env = empty_env();
let v = eval_parse("std([0 2 4])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.0).abs() < 1e-10);
}
#[test]
fn test_std_vector_population() {
let env = empty_env();
let v = eval_parse("std([2 4 4 4 5 5 7 9], 1)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.0).abs() < 1e-10);
}
#[test]
fn test_std_scalar_is_zero() {
let env = empty_env();
let v = eval_parse("std(5)", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_std_matrix_columnwise() {
let env = empty_env();
let v = eval_parse("std([1 10; 2 20; 3 30])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 2));
assert!((m[[0, 0]] - 1.0).abs() < 1e-10);
assert!((m[[0, 1]] - 10.0).abs() < 1e-10);
}
#[test]
fn test_var_vector_sample() {
let env = empty_env();
let v = eval_parse("var([0 2 4])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 4.0).abs() < 1e-10);
}
#[test]
fn test_var_vector_population() {
let env = empty_env();
let v = eval_parse("var([2 4 4 4 5 5 7 9], 1)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 4.0).abs() < 1e-10);
}
#[test]
fn test_var_two_elements() {
let env = empty_env();
let v = eval_parse("var([1 3])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.0).abs() < 1e-10);
}
#[test]
fn test_cov_vector_equals_var() {
let env = empty_env();
let v = eval_parse("cov([1 2 3 4 5])", &env).unwrap();
let var_v = eval_parse("var([1 2 3 4 5])", &env).unwrap();
assert_eq!(v, var_v);
}
#[test]
fn test_cov_matrix_shape() {
let env = empty_env();
let v = eval_parse("cov([1 2 3; 4 5 6; 7 8 9; 10 11 12])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 3));
}
#[test]
fn test_cov_matrix_symmetric() {
let env = empty_env();
let v = eval_parse("cov([1 4; 2 5; 3 6; 4 7])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert!((m[[0, 1]] - m[[1, 0]]).abs() < 1e-12);
}
#[test]
fn test_median_odd_length() {
let env = empty_env();
let v = eval_parse("median([3 1 4 1 5 9 2 6 5])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 4.0).abs() < 1e-10);
}
#[test]
fn test_median_even_length() {
let env = empty_env();
let v = eval_parse("median([1 2 3 4])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.5).abs() < 1e-10);
}
#[test]
fn test_median_scalar() {
let env = empty_env();
let v = eval_parse("median(7)", &env).unwrap();
assert_eq!(v, Value::Scalar(7.0));
}
#[test]
fn test_median_matrix_columnwise() {
let env = empty_env();
let v = eval_parse("median([1 10; 3 30; 2 20])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 2));
assert!((m[[0, 0]] - 2.0).abs() < 1e-10);
assert!((m[[0, 1]] - 20.0).abs() < 1e-10);
}
#[test]
fn test_mode_single_mode() {
let env = empty_env();
let v = eval_parse("mode([1 2 2 3 3 3 4])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 3.0).abs() < 1e-10);
}
#[test]
fn test_mode_tie_smallest_wins() {
let env = empty_env();
let v = eval_parse("mode([1 1 2 2 3])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 1.0).abs() < 1e-10);
}
#[test]
fn test_mode_scalar() {
let env = empty_env();
let v = eval_parse("mode(5)", &env).unwrap();
assert_eq!(v, Value::Scalar(5.0));
}
#[test]
fn test_histc_basic() {
let env = empty_env();
let v = eval_parse("histc([1 2 3 4 5], [1 3 5])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 3));
assert_eq!(m[[0, 0]], 2.0);
assert_eq!(m[[0, 1]], 2.0);
assert_eq!(m[[0, 2]], 1.0);
}
#[test]
fn test_histc_out_of_range_not_counted() {
let env = empty_env();
let v = eval_parse("histc([0 1 2 3 10], [1 2 3])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 1]], 1.0);
assert_eq!(m[[0, 2]], 1.0);
}
#[test]
fn test_hist_returns_void() {
let env = empty_env();
let v = eval_parse("hist([1 2 3 4 5])", &env).unwrap();
assert_eq!(v, Value::Void);
}
#[test]
fn test_hist_custom_bins_returns_void() {
let env = empty_env();
let v = eval_parse("hist([1 2 3 4 5], 5)", &env).unwrap();
assert_eq!(v, Value::Void);
}
#[test]
fn test_prctile_median() {
let env = empty_env();
let v = eval_parse("prctile([1 2 3 4 5], 50)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 3.0).abs() < 1e-10);
}
#[test]
fn test_prctile_0th_percentile() {
let env = empty_env();
let v = eval_parse("prctile([1 2 3 4 5], 0)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 1.0).abs() < 1e-10);
}
#[test]
fn test_prctile_100th_percentile() {
let env = empty_env();
let v = eval_parse("prctile([1 2 3 4 5], 100)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 5.0).abs() < 1e-10);
}
#[test]
fn test_prctile_interpolation() {
let env = empty_env();
let v = eval_parse("prctile([1 3], 50)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.0).abs() < 1e-10);
}
#[test]
fn test_prctile_vector_of_percentiles() {
let env = empty_env();
let v = eval_parse("prctile([1 2 3 4 5], [0 50 100])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 3));
assert!((m[[0, 0]] - 1.0).abs() < 1e-10);
assert!((m[[0, 1]] - 3.0).abs() < 1e-10);
assert!((m[[0, 2]] - 5.0).abs() < 1e-10);
}
#[test]
fn test_prctile_matrix_columnwise() {
let env = empty_env();
let v = eval_parse("prctile([1 10; 2 20; 3 30], 50)", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (1, 2));
assert!((m[[0, 0]] - 2.0).abs() < 1e-10);
assert!((m[[0, 1]] - 20.0).abs() < 1e-10);
}
#[test]
fn test_iqr_basic() {
let env = empty_env();
let v = eval_parse("iqr([1 2 3 4 5])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 2.0).abs() < 1e-10);
}
#[test]
fn test_iqr_scalar() {
let env = empty_env();
let v = eval_parse("iqr(5)", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_zscore_basic() {
let env = empty_env();
let v = eval_parse("zscore([2 4 6])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.len(), 3);
let vals: Vec<f64> = m.iter().copied().collect();
assert!((vals[0] - (-1.0)).abs() < 1e-10);
assert!((vals[1] - 0.0).abs() < 1e-10);
assert!((vals[2] - 1.0).abs() < 1e-10);
}
#[test]
fn test_zscore_scalar_is_zero() {
let env = empty_env();
let v = eval_parse("zscore(42)", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_zscore_constant_vector() {
let env = empty_env();
let v = eval_parse("zscore([3 3 3])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
for &x in m.iter() {
assert_eq!(x, 0.0);
}
}
#[test]
fn test_zscore_preserves_shape() {
let env = empty_env();
let v = eval_parse("zscore([1; 2; 3])", &env).unwrap();
let Value::Matrix(m) = v else {
panic!("expected matrix")
};
assert_eq!(m.dim(), (3, 1));
}
#[test]
fn test_erf_zero() {
let env = empty_env();
let v = eval_parse("erf(0)", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_erf_large_positive() {
let env = empty_env();
let v = eval_parse("erf(10)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 1.0).abs() < 1e-10);
}
#[test]
fn test_erfc_zero() {
let env = empty_env();
let v = eval_parse("erfc(0)", &env).unwrap();
assert_eq!(v, Value::Scalar(1.0));
}
#[test]
fn test_erf_erfc_sum() {
let env = empty_env();
let erf_v = eval_parse("erf(1.5)", &env).unwrap();
let erfc_v = eval_parse("erfc(1.5)", &env).unwrap();
let Value::Scalar(e) = erf_v else { panic!() };
let Value::Scalar(ec) = erfc_v else { panic!() };
assert!((e + ec - 1.0).abs() < 1e-14);
}
#[test]
fn test_normcdf_at_zero() {
let env = empty_env();
let v = eval_parse("normcdf(0)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 0.5).abs() < 1e-10);
}
#[test]
fn test_normcdf_symmetry() {
let env = empty_env();
let v1 = eval_parse("normcdf(1.5)", &env).unwrap();
let v2 = eval_parse("normcdf(-1.5)", &env).unwrap();
let Value::Scalar(x1) = v1 else { panic!() };
let Value::Scalar(x2) = v2 else { panic!() };
assert!((x1 + x2 - 1.0).abs() < 1e-14);
}
#[test]
fn test_normcdf_general() {
let env = empty_env();
let v = eval_parse("normcdf(2, 2, 1)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 0.5).abs() < 1e-10);
}
#[test]
fn test_normpdf_at_zero() {
let env = empty_env();
let v = eval_parse("normpdf(0)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
let expected = 1.0 / (2.0 * std::f64::consts::PI).sqrt();
assert!((x - expected).abs() < 1e-10);
}
#[test]
fn test_normpdf_symmetry() {
let env = empty_env();
let v1 = eval_parse("normpdf(1.5)", &env).unwrap();
let v2 = eval_parse("normpdf(-1.5)", &env).unwrap();
assert_eq!(v1, v2);
}
#[test]
fn test_normpdf_general() {
let env = empty_env();
let v = eval_parse("normpdf(3, 3, 2)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
let expected = 1.0 / (2.0 * (2.0 * std::f64::consts::PI).sqrt());
assert!((x - expected).abs() < 1e-10);
}
#[test]
fn test_skewness_symmetric_is_zero() {
let env = empty_env();
let v = eval_parse("skewness([1 2 3 4 5])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!(x.abs() < 1e-12);
}
#[test]
fn test_skewness_right_skewed_positive() {
let env = empty_env();
let v = eval_parse("skewness([1 1 2 3 10])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!(x > 0.0);
}
#[test]
fn test_skewness_scalar_is_zero() {
let env = empty_env();
let v = eval_parse("skewness(5)", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_skewness_constant_vector_is_zero() {
let env = empty_env();
let v = eval_parse("skewness([3 3 3 3])", &env).unwrap();
assert_eq!(v, Value::Scalar(0.0));
}
#[test]
fn test_kurtosis_uniform_vector() {
let env = empty_env();
let v = eval_parse("kurtosis([1 2 3 4 5])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!((x - 1.7).abs() < 1e-10);
}
#[test]
fn test_kurtosis_scalar_is_nan() {
let env = empty_env();
let v = eval_parse("kurtosis(5)", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!(x.is_nan());
}
#[test]
fn test_kurtosis_constant_vector_is_nan() {
let env = empty_env();
let v = eval_parse("kurtosis([4 4 4 4])", &env).unwrap();
let Value::Scalar(x) = v else {
panic!("expected scalar")
};
assert!(x.is_nan());
}
fn run_linalg(src: &str) -> crate::env::Env {
use crate::eval::{Base, FormatMode};
use crate::io::IoContext;
use crate::parser::parse_stmts;
crate::exec::init();
let stmts = parse_stmts(src).expect("parse_stmts failed");
let mut env = crate::env::Env::new();
env.insert("ans".to_string(), Value::Scalar(0.0));
let mut io = IoContext::new();
crate::exec::exec_stmts(
&stmts,
&mut env,
&mut io,
&FormatMode::Short,
Base::Dec,
true,
)
.expect("exec_stmts failed");
env
}
#[allow(dead_code)]
fn mat_approx_zero(env: &crate::env::Env, name: &str, tol: f64) {
match env.get(name) {
Some(Value::Matrix(m)) => {
let max = m.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
assert!(max < tol, "{name}: max element {max} >= tol {tol}");
}
Some(Value::Scalar(x)) => assert!(x.abs() < tol, "{name} = {x} >= tol {tol}"),
v => panic!("expected numeric for '{name}', got {v:?}"),
}
}
fn scalar_near(env: &crate::env::Env, name: &str, expected: f64, tol: f64) {
match env.get(name) {
Some(Value::Scalar(x)) => {
assert!(
(x - expected).abs() < tol,
"{name}: got {x}, expected {expected}"
)
}
v => panic!("expected scalar for '{name}', got {v:?}"),
}
}
#[test]
fn test_lu_pa_equals_lu() {
let env = run_linalg("A = [4 3; 6 3]; [L, U, P] = lu(A); err = norm(P*A - L*U);");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_lu_l_lower_triangular() {
let env = run_linalg("A = [4 3; 6 3]; [L, U, P] = lu(A); off = L(1,2);");
scalar_near(&env, "off", 0.0, 1e-14);
}
#[test]
fn test_qr_a_equals_qr() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; [Q, R] = qr(A); err = norm(A - Q*R);");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_qr_q_orthogonal() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; [Q, R] = qr(A); err = norm(Q'*Q - eye(3));");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_chol_rtr_equals_a() {
let env = run_linalg("A = [4 2; 2 3]; R = chol(A); err = norm(R'*R - A);");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_chol_not_posdef_errors() {
use crate::eval::Base;
use crate::eval::FormatMode;
use crate::io::IoContext;
use crate::parser::parse_stmts;
crate::exec::init();
let stmts = parse_stmts("R = chol([-1 0; 0 1])").unwrap();
let mut env = crate::env::Env::new();
let mut io = IoContext::new();
let res = crate::exec::exec_stmts(
&stmts,
&mut env,
&mut io,
&FormatMode::Short,
Base::Dec,
true,
);
assert!(
res.is_err(),
"expected error for non-positive-definite matrix"
);
}
#[test]
fn test_svd_singular_values_sorted_descending() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; s = svd(A); ok = (s(1) >= s(2));");
scalar_near(&env, "ok", 1.0, 1e-14);
}
#[test]
fn test_svd_full_reconstruction() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; [U, S, V] = svd(A); err = norm(A - U*S*V');");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_svd_u_orthogonal() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; [U, S, V] = svd(A); err = norm(U'*U - eye(3));");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_svd_v_orthogonal() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; [U, S, V] = svd(A); err = norm(V'*V - eye(2));");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_eig_symmetric_eigenvalues() {
let env = run_linalg("A = [2 1; 1 2]; d = eig(A);");
match env.get("d") {
Some(Value::Matrix(m)) => {
let mut vals: Vec<f64> = m.iter().copied().collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert!((vals[0] - 1.0).abs() < 1e-10, "smallest eig: {}", vals[0]);
assert!((vals[1] - 3.0).abs() < 1e-10, "largest eig: {}", vals[1]);
}
v => panic!("expected matrix, got {v:?}"),
}
}
#[test]
fn test_eig_multi_output() {
let env = run_linalg("A = [2 1; 1 2]; [V, D] = eig(A); err = norm(A*V - V*D);");
scalar_near(&env, "err", 0.0, 1e-10);
}
#[test]
fn test_rank_full_rank() {
let env = run_linalg("r = rank([1 2; 3 4]);");
scalar_near(&env, "r", 2.0, 1e-14);
}
#[test]
fn test_rank_rank_deficient() {
let env = run_linalg("r = rank([1 2; 2 4]);");
scalar_near(&env, "r", 1.0, 1e-14);
}
#[test]
fn test_rank_zero_matrix() {
let env = run_linalg("r = rank(zeros(3,3));");
scalar_near(&env, "r", 0.0, 1e-14);
}
#[test]
fn test_null_space() {
let env = run_linalg("A = [1 2; 2 4]; N = null(A); err = norm(A*N);");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_orth_columns_in_column_space() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; Q = orth(A); err = norm(Q'*Q - eye(2));");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_cond_identity() {
let env = run_linalg("c = cond(eye(3));");
scalar_near(&env, "c", 1.0, 1e-12);
}
#[test]
fn test_cond_singular_is_inf() {
let env = run_linalg("c = cond([1 2; 2 4]);");
match env.get("c") {
Some(Value::Scalar(x)) => assert!(x.is_infinite(), "expected Inf, got {x}"),
v => panic!("expected scalar, got {v:?}"),
}
}
#[test]
fn test_pinv_pseudoinverse() {
let env = run_linalg("A = [1 2; 3 4; 5 6]; B = pinv(A); err = norm(A*B*A - A);");
scalar_near(&env, "err", 0.0, 1e-12);
}
#[test]
fn test_norm_matrix_2norm() {
let env = run_linalg("A = [3 0; 0 1]; n = norm(A);");
scalar_near(&env, "n", 3.0, 1e-12);
}
#[test]
fn test_norm_matrix_frobenius() {
let env = run_linalg("A = [3 4; 0 0]; n = norm(A, 'fro');");
scalar_near(&env, "n", 5.0, 1e-12);
}
#[test]
fn test_norm_matrix_1norm() {
let env = run_linalg("A = [1 2; 3 4]; n = norm(A, 1);");
scalar_near(&env, "n", 6.0, 1e-12);
}
#[test]
fn test_norm_matrix_inf() {
let env = run_linalg("A = [1 2; 3 4]; n = norm(A, inf);");
scalar_near(&env, "n", 7.0, 1e-12);
}
#[test]
fn test_svd_econ() {
let env =
run_linalg("A = [1 2; 3 4; 5 6]; [U, S, V] = svd(A, 'econ'); err = norm(A - U*S*V');");
scalar_near(&env, "err", 0.0, 1e-12);
}
fn run_script_src(src: &str) -> crate::env::Env {
use crate::eval::{Base, FormatMode};
use crate::io::IoContext;
use crate::parser::parse_stmts;
crate::exec::init();
let stmts = parse_stmts(src).expect("parse_stmts failed");
let mut env = crate::env::Env::new();
env.insert("ans".to_string(), Value::Scalar(0.0));
let mut io = IoContext::new();
crate::exec::exec_stmts(
&stmts,
&mut env,
&mut io,
&FormatMode::Short,
Base::Dec,
true,
)
.expect("exec_stmts failed");
env
}
#[test]
fn test_run_does_not_abort_outer_script() {
use std::io::Write;
let dir = std::env::temp_dir();
let helper_path = dir.join("ccalc_test_run_helper.calc");
{
let mut f = std::fs::File::create(&helper_path).expect("create helper");
f.write_all(b"helper_ran = 1;\n").expect("write");
}
let path_str = helper_path.to_str().unwrap().replace('\\', "/");
let script = format!("before = 10;\nrun('{path_str}');\nafter = 20;\n");
let env = run_script_src(&script);
let _ = std::fs::remove_file(&helper_path);
assert_eq!(
env.get("before"),
Some(&Value::Scalar(10.0)),
"before should be set"
);
assert_eq!(
env.get("helper_ran"),
Some(&Value::Scalar(1.0)),
"helper script should have executed"
);
assert_eq!(
env.get("after"),
Some(&Value::Scalar(20.0)),
"statements after run() must not be skipped"
);
}
#[test]
fn test_mixed_function_script_file_runs_body() {
use std::io::Write;
let dir = std::env::temp_dir();
let file_path = dir.join("ccalc_test_mixed_fn_script.calc");
{
let mut f = std::fs::File::create(&file_path).expect("create file");
f.write_all(b"function y = double_it(x)\n y = x * 2;\nend\n\nresult = double_it(21);\n")
.expect("write");
}
let path_str = file_path.to_str().unwrap().replace('\\', "/");
let script = format!("run('{path_str}');\n");
let env = run_script_src(&script);
let _ = std::fs::remove_file(&file_path);
assert_eq!(
env.get("result"),
Some(&Value::Scalar(42.0)),
"script body must execute even when the file starts with function defs"
);
}
#[test]
fn test_assert_true_condition() {
let env = empty_env();
assert_eq!(eval_parse("assert(1)", &env).unwrap(), Value::Void);
}
#[test]
fn test_assert_false_condition() {
let env = empty_env();
assert!(eval_parse("assert(0)", &env).is_err());
}
#[test]
fn test_assert_nonzero_is_true() {
let env = empty_env();
assert_eq!(eval_parse("assert(42)", &env).unwrap(), Value::Void);
}
#[test]
fn test_assert_nan_is_false() {
let env = empty_env();
assert!(eval_parse("assert(nan)", &env).is_err());
}
#[test]
fn test_assert_equal_scalars_pass() {
let env = empty_env();
assert_eq!(eval_parse("assert(3, 3)", &env).unwrap(), Value::Void);
}
#[test]
fn test_assert_equal_scalars_fail() {
let env = empty_env();
assert!(eval_parse("assert(3, 4)", &env).is_err());
}
#[test]
fn test_assert_tol_pass() {
let env = empty_env();
assert_eq!(eval_parse("assert(1, 2, 1.5)", &env).unwrap(), Value::Void);
}
#[test]
fn test_assert_tol_fail() {
let env = empty_env();
assert!(eval_parse("assert(1, 2, 0.5)", &env).is_err());
}
#[test]
fn test_assert_exact_tol_zero() {
let env = empty_env();
assert_eq!(eval_parse("assert(5, 5, 0)", &env).unwrap(), Value::Void);
}
#[test]
fn test_suggest_similar_undefined_var() {
let mut env = empty_env();
env.insert("length".to_string(), Value::Scalar(1.0));
let err = eval(&Expr::Var("lnegth".to_string()), &env).unwrap_err();
assert!(
err.contains("did you mean"),
"expected suggestion in: {err}"
);
assert!(err.contains("length"), "expected 'length' in: {err}");
}
#[test]
fn test_suggest_similar_unknown_fn() {
let env = empty_env();
let err = eval_parse("sqrtt(4)", &env).unwrap_err();
assert!(
err.contains("did you mean"),
"expected suggestion in: {err}"
);
assert!(err.contains("sqrt"), "expected 'sqrt' in: {err}");
}
#[test]
fn test_no_suggestion_for_totally_different_name() {
let env = empty_env();
let err = eval_parse("zzzzzzz(4)", &env).unwrap_err();
assert!(
!err.contains("did you mean"),
"unexpected suggestion in: {err}"
);
}
#[test]
fn test_builtin_names_contains_expected() {
let names = builtin_names();
assert!(names.contains(&"sqrt"), "sqrt missing from builtin_names");
assert!(
names.contains(&"assert"),
"assert missing from builtin_names"
);
assert!(names.contains(&"zeros"), "zeros missing from builtin_names");
assert!(
names.contains(&"fprintf"),
"fprintf missing from builtin_names"
);
}
#[test]
fn test_builtin_names_no_duplicates() {
let names = builtin_names();
let mut seen = std::collections::HashSet::new();
for n in names {
assert!(seen.insert(n), "duplicate builtin name: {n}");
}
}
#[test]
fn test_json_builtins_in_names() {
let names = builtin_names();
assert!(
names.contains(&"jsondecode"),
"jsondecode missing from builtin_names"
);
assert!(
names.contains(&"jsonencode"),
"jsonencode missing from builtin_names"
);
}
#[cfg(feature = "json")]
mod json_tests {
use super::*;
fn decode(s: &str) -> Value {
let expr = Expr::Call(
"jsondecode".to_string(),
vec![Expr::StrLiteral(s.to_string())],
);
eval(&expr, &empty_env()).expect("jsondecode failed")
}
fn encode(v: Value) -> String {
let mut env = empty_env();
env.insert("_x".to_string(), v);
let expr = Expr::Call("jsonencode".to_string(), vec![Expr::Var("_x".to_string())]);
match eval(&expr, &env).expect("jsonencode failed") {
Value::Str(s) => s,
other => panic!("expected Str, got {other:?}"),
}
}
#[test]
fn decode_scalar_number() {
assert_eq!(decode("42"), Value::Scalar(42.0));
}
#[test]
fn decode_null_is_nan() {
match decode("null") {
Value::Scalar(x) => assert!(x.is_nan()),
other => panic!("expected Scalar(NaN), got {other:?}"),
}
}
#[test]
fn decode_bool_true() {
assert_eq!(decode("true"), Value::Scalar(1.0));
}
#[test]
fn decode_bool_false() {
assert_eq!(decode("false"), Value::Scalar(0.0));
}
#[test]
fn decode_string() {
assert_eq!(decode(r#""hello""#), Value::Str("hello".to_string()));
}
#[test]
fn decode_numeric_array() {
use ndarray::array;
match decode("[1, 2, 3]") {
Value::Matrix(m) => assert_eq!(m, array![[1.0, 2.0, 3.0]]),
other => panic!("expected Matrix, got {other:?}"),
}
}
#[test]
fn decode_empty_array() {
use ndarray::Array2;
match decode("[]") {
Value::Matrix(m) => assert_eq!(m, Array2::<f64>::zeros((1, 0))),
other => panic!("expected empty Matrix, got {other:?}"),
}
}
#[test]
fn decode_mixed_array_is_cell() {
match decode(r#"[1, "two", 3]"#) {
Value::Cell(cells) => {
assert_eq!(cells.len(), 3);
assert_eq!(cells[0], Value::Scalar(1.0));
assert_eq!(cells[1], Value::Str("two".to_string()));
assert_eq!(cells[2], Value::Scalar(3.0));
}
other => panic!("expected Cell, got {other:?}"),
}
}
#[test]
fn decode_object_is_struct() {
match decode(r#"{"x": 1, "y": 2}"#) {
Value::Struct(fields) => {
assert_eq!(fields.get("x"), Some(&Value::Scalar(1.0)));
assert_eq!(fields.get("y"), Some(&Value::Scalar(2.0)));
}
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn decode_nested_struct() {
match decode(r#"{"a": {"b": 42}}"#) {
Value::Struct(outer) => match outer.get("a") {
Some(Value::Struct(inner)) => {
assert_eq!(inner.get("b"), Some(&Value::Scalar(42.0)));
}
other => panic!("expected inner Struct, got {other:?}"),
},
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn decode_invalid_json_errors() {
let expr = Expr::Call(
"jsondecode".to_string(),
vec![Expr::StrLiteral("{bad json".to_string())],
);
assert!(eval(&expr, &empty_env()).is_err());
}
#[test]
fn decode_non_string_arg_errors() {
let expr = Expr::Call("jsondecode".to_string(), vec![Expr::Number(42.0)]);
assert!(eval(&expr, &empty_env()).is_err());
}
#[test]
fn encode_scalar() {
assert_eq!(encode(Value::Scalar(3.14)), "3.14");
}
#[test]
fn encode_scalar_integer() {
assert_eq!(encode(Value::Scalar(5.0)), "5.0");
}
#[test]
fn encode_nan_is_null() {
assert_eq!(encode(Value::Scalar(f64::NAN)), "null");
}
#[test]
fn encode_inf_errors() {
let mut env = empty_env();
env.insert("_x".to_string(), Value::Scalar(f64::INFINITY));
let expr = Expr::Call("jsonencode".to_string(), vec![Expr::Var("_x".to_string())]);
assert!(eval(&expr, &env).is_err());
}
#[test]
fn encode_string() {
assert_eq!(encode(Value::Str("hello".to_string())), r#""hello""#);
}
#[test]
fn encode_row_vector() {
use ndarray::array;
let m = Value::Matrix(array![[1.0, 2.0, 3.0]]);
assert_eq!(encode(m), "[1.0,2.0,3.0]");
}
#[test]
fn encode_matrix_2d() {
use ndarray::array;
let m = Value::Matrix(array![[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(encode(m), "[[1.0,2.0],[3.0,4.0]]");
}
#[test]
fn encode_struct() {
use indexmap::IndexMap;
let mut fields = IndexMap::new();
fields.insert("x".to_string(), Value::Scalar(1.0));
let result = encode(Value::Struct(fields));
assert_eq!(result, r#"{"x":1.0}"#);
}
#[test]
fn encode_cell() {
let cells = vec![Value::Scalar(1.0), Value::Str("a".to_string())];
let result = encode(Value::Cell(cells));
assert_eq!(result, r#"[1.0,"a"]"#);
}
#[test]
fn roundtrip_object() {
let json = r#"{"name":"Alice","score":99}"#;
let decoded = decode(json);
let reencoded = {
let mut env = empty_env();
env.insert("_x".to_string(), decoded);
let expr = Expr::Call("jsonencode".to_string(), vec![Expr::Var("_x".to_string())]);
match eval(&expr, &env).unwrap() {
Value::Str(s) => s,
other => panic!("{other:?}"),
}
};
let redecoded = decode(&reencoded);
match redecoded {
Value::Struct(fields) => {
assert_eq!(fields.get("name"), Some(&Value::Str("Alice".to_string())));
assert_eq!(fields.get("score"), Some(&Value::Scalar(99.0)));
}
other => panic!("expected Struct, got {other:?}"),
}
}
}
mod csv_tests {
use super::*;
use indexmap::IndexMap;
use ndarray::Array2;
fn tmp_csv(tag: &str, content: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("ccalc_csv_{}_{}.csv", std::process::id(), tag));
std::fs::write(&path, content).unwrap();
path
}
fn tmp_path(tag: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("ccalc_csv_{}_{}.csv", std::process::id(), tag));
path
}
fn call_rm(path: &str) -> Value {
let expr = Expr::Call(
"readmatrix".to_string(),
vec![Expr::StrLiteral(path.to_string())],
);
eval(&expr, &empty_env()).expect("readmatrix failed")
}
fn call_rt(path: &str) -> Value {
let expr = Expr::Call(
"readtable".to_string(),
vec![Expr::StrLiteral(path.to_string())],
);
eval(&expr, &empty_env()).expect("readtable failed")
}
fn call_wt(tbl: Value, path: &str) {
let mut env = empty_env();
env.insert("_t".to_string(), tbl);
let expr = Expr::Call(
"writetable".to_string(),
vec![
Expr::Var("_t".to_string()),
Expr::StrLiteral(path.to_string()),
],
);
eval(&expr, &env).expect("writetable failed");
}
#[test]
fn readmatrix_basic_numeric() {
let p = tmp_csv("rm_basic", "1,2,3\n4,5,6\n");
let ps = p.to_str().unwrap();
match call_rm(ps) {
Value::Matrix(m) => {
assert_eq!((m.nrows(), m.ncols()), (2, 3));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 2]], 6.0);
}
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readmatrix_header_skipped() {
let p = tmp_csv("rm_hdr", "x,y,z\n1,2,3\n4,5,6\n");
let ps = p.to_str().unwrap();
match call_rm(ps) {
Value::Matrix(m) => {
assert_eq!(m.nrows(), 2);
assert_eq!(m[[0, 0]], 1.0);
}
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readmatrix_numeric_first_row_not_header() {
let p = tmp_csv("rm_numfirst", "1,2,3\n4,5,6\n");
let ps = p.to_str().unwrap();
match call_rm(ps) {
Value::Matrix(m) => assert_eq!(m.nrows(), 2),
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readmatrix_explicit_tab_delim() {
let p = tmp_csv("rm_tab", "1\t2\t3\n4\t5\t6\n");
let ps = p.to_str().unwrap();
let expr = Expr::Call(
"readmatrix".to_string(),
vec![
Expr::StrLiteral(ps.to_string()),
Expr::StrLiteral("Delimiter".to_string()),
Expr::StrLiteral(r"\t".to_string()),
],
);
match eval(&expr, &empty_env()).unwrap() {
Value::Matrix(m) => {
assert_eq!((m.nrows(), m.ncols()), (2, 3));
assert_eq!(m[[1, 1]], 5.0);
}
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readmatrix_empty_cell_becomes_nan() {
let p = tmp_csv("rm_nan", "1,,3\n");
let ps = p.to_str().unwrap();
match call_rm(ps) {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 1.0);
assert!(m[[0, 1]].is_nan());
assert_eq!(m[[0, 2]], 3.0);
}
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readmatrix_empty_file() {
let p = tmp_csv("rm_empty", "");
let ps = p.to_str().unwrap();
match call_rm(ps) {
Value::Matrix(m) => assert_eq!((m.nrows(), m.ncols()), (0, 0)),
v => panic!("expected Matrix, got {v:?}"),
}
}
#[test]
fn readtable_numeric_columns() {
let p = tmp_csv("rt_num", "x,y\n1,2\n3,4\n");
let ps = p.to_str().unwrap();
match call_rt(ps) {
Value::Struct(fields) => {
assert!(fields.contains_key("x") && fields.contains_key("y"));
match &fields["x"] {
Value::Matrix(m) => {
assert_eq!((m.nrows(), m.ncols()), (2, 1));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[1, 0]], 3.0);
}
v => panic!("expected Matrix column, got {v:?}"),
}
}
v => panic!("expected Struct, got {v:?}"),
}
}
#[test]
fn readtable_mixed_columns() {
let p = tmp_csv("rt_mixed", "name,score\nAlice,95\nBob,87\n");
let ps = p.to_str().unwrap();
match call_rt(ps) {
Value::Struct(fields) => {
match &fields["name"] {
Value::Cell(c) => {
assert_eq!(c[0], Value::Str("Alice".to_string()));
assert_eq!(c[1], Value::Str("Bob".to_string()));
}
v => panic!("expected Cell column, got {v:?}"),
}
match &fields["score"] {
Value::Matrix(m) => {
assert_eq!(m[[0, 0]], 95.0);
assert_eq!(m[[1, 0]], 87.0);
}
v => panic!("expected Matrix column, got {v:?}"),
}
}
v => panic!("expected Struct, got {v:?}"),
}
}
#[test]
fn readtable_header_only() {
let p = tmp_csv("rt_hdronly", "x,y\n");
let ps = p.to_str().unwrap();
match call_rt(ps) {
Value::Struct(fields) => {
assert!(fields.contains_key("x") && fields.contains_key("y"));
match &fields["x"] {
Value::Matrix(m) => assert_eq!(m.nrows(), 0),
v => panic!("expected empty Matrix column, got {v:?}"),
}
}
v => panic!("expected Struct, got {v:?}"),
}
}
#[test]
fn readtable_quoted_field_with_comma() {
let p = tmp_csv("rt_quoted", "name,value\n\"Smith, John\",100\n");
let ps = p.to_str().unwrap();
match call_rt(ps) {
Value::Struct(fields) => match &fields["name"] {
Value::Cell(c) => {
assert_eq!(c[0], Value::Str("Smith, John".to_string()));
}
v => panic!("expected Cell, got {v:?}"),
},
v => panic!("expected Struct, got {v:?}"),
}
}
#[test]
fn readtable_empty_file() {
let p = tmp_csv("rt_empty", "");
let ps = p.to_str().unwrap();
match call_rt(ps) {
Value::Struct(fields) => assert!(fields.is_empty()),
v => panic!("expected empty Struct, got {v:?}"),
}
}
#[test]
fn writetable_basic() {
let mut fields = IndexMap::new();
fields.insert(
"x".to_string(),
Value::Matrix(Array2::from_shape_vec((2, 1), vec![1.0, 2.0]).unwrap()),
);
fields.insert(
"y".to_string(),
Value::Matrix(Array2::from_shape_vec((2, 1), vec![3.0, 4.0]).unwrap()),
);
let path = tmp_path("wt_basic");
let ps = path.to_str().unwrap();
call_wt(Value::Struct(fields), ps);
let content = std::fs::read_to_string(&path).unwrap();
let lines: Vec<&str> = content.lines().collect();
assert_eq!(lines[0], "x,y");
assert_eq!(lines[1], "1,3");
assert_eq!(lines[2], "2,4");
}
#[test]
fn writetable_quoting() {
let mut fields = IndexMap::new();
fields.insert(
"text".to_string(),
Value::Cell(vec![
Value::Str("hello, world".to_string()),
Value::Str("plain".to_string()),
]),
);
let path = tmp_path("wt_quote");
let ps = path.to_str().unwrap();
call_wt(Value::Struct(fields), ps);
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("\"hello, world\""));
assert!(content.contains("plain"));
}
#[test]
fn writetable_readtable_roundtrip() {
let p_in = tmp_csv("rt_rtrip_in", "name,score\nAlice,95\nBob,87\n");
let tbl = call_rt(p_in.to_str().unwrap());
let p_out = tmp_path("rt_rtrip_out");
call_wt(tbl, p_out.to_str().unwrap());
let content = std::fs::read_to_string(&p_out).unwrap();
let lines: Vec<&str> = content.lines().collect();
assert_eq!(lines[0], "name,score");
assert!(lines[1..].contains(&"Alice,95") || lines[1..].contains(&"Alice,95.0"));
assert!(lines[1..].contains(&"Bob,87") || lines[1..].contains(&"Bob,87.0"));
}
#[test]
fn writetable_wrong_type_errors() {
let mut fields = IndexMap::new();
fields.insert("a".to_string(), Value::Scalar(1.0));
fields.insert("b".to_string(), Value::Matrix(Array2::<f64>::zeros((2, 2))));
let path = tmp_path("wt_err");
let ps = path.to_str().unwrap();
let mut env = empty_env();
env.insert("_t".to_string(), Value::Struct(fields));
let expr = Expr::Call(
"writetable".to_string(),
vec![
Expr::Var("_t".to_string()),
Expr::StrLiteral(ps.to_string()),
],
);
assert!(eval(&expr, &env).is_err());
}
}
#[cfg(feature = "mat")]
mod mat_tests {
use super::*;
use crate::mat::mat_load;
fn tmp_mat_path(tag: &str) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("ccalc_mat_{}_{}.mat", std::process::id(), tag));
path
}
#[test]
fn test_mat_load_nonexistent() {
let result = mat_load("/nonexistent/does_not_exist.mat");
assert!(result.is_err());
let e = result.unwrap_err();
assert!(e.contains("load:"), "error should mention 'load:': {e}");
}
#[test]
fn test_load_builtin_not_mat_extension() {
let expr = Expr::Call(
"load".to_string(),
vec![Expr::StrLiteral("data.toml".to_string())],
);
let env = empty_env();
let result = eval(&expr, &env);
assert!(result.is_err());
let e = result.unwrap_err();
assert!(e.contains("non-.mat"), "error: {e}");
}
#[test]
fn test_load_builtin_bad_arg() {
let expr = Expr::Call("load".to_string(), vec![Expr::Number(42.0)]);
let env = empty_env();
let result = eval(&expr, &env);
assert!(result.is_err());
}
#[test]
fn test_load_in_builtin_names() {
assert!(
builtin_names().contains(&"load"),
"load missing from builtin_names"
);
}
#[test]
fn test_mat_load_error_prefix() {
let path = tmp_mat_path("nonexist");
let result = mat_load(path.to_str().unwrap());
assert!(result.is_err());
assert!(result.unwrap_err().starts_with("load:"));
}
#[test]
fn test_mat_roundtrip_scalar() {
use matrw::{matfile, matvar, save_matfile_v7};
let path = tmp_mat_path("scalar");
let ps = path.to_str().unwrap();
let mat = matfile!(x: matvar!(3.14),);
save_matfile_v7(ps, mat, false).expect("write .mat");
let result = mat_load(ps).unwrap();
std::fs::remove_file(&path).ok();
match result {
Value::Struct(fields) => {
assert_eq!(fields.get("x"), Some(&Value::Scalar(3.14)));
}
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn test_mat_roundtrip_vector() {
use matrw::{matfile, matvar, save_matfile_v7};
let path = tmp_mat_path("vector");
let ps = path.to_str().unwrap();
let mat = matfile!(v: matvar!([1.0, 2.0, 3.0]),);
save_matfile_v7(ps, mat, false).expect("write .mat");
let result = mat_load(ps).unwrap();
std::fs::remove_file(&path).ok();
match result {
Value::Struct(fields) => match fields.get("v").unwrap() {
Value::Matrix(m) => {
assert_eq!(m.nrows(), 1);
assert_eq!(m.ncols(), 3);
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 2]], 3.0);
}
other => panic!("expected Matrix, got {other:?}"),
},
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn test_mat_roundtrip_matrix() {
use matrw::{matfile, matvar, save_matfile_v7};
let path = tmp_mat_path("matrix");
let ps = path.to_str().unwrap();
let mat = matfile!(A: matvar!([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),);
save_matfile_v7(ps, mat, false).expect("write .mat");
let result = mat_load(ps).unwrap();
std::fs::remove_file(&path).ok();
match result {
Value::Struct(fields) => match fields.get("A").unwrap() {
Value::Matrix(m) => {
assert_eq!((m.nrows(), m.ncols()), (2, 3));
assert_eq!(m[[0, 0]], 1.0);
assert_eq!(m[[0, 2]], 3.0);
assert_eq!(m[[1, 0]], 4.0);
assert_eq!(m[[1, 2]], 6.0);
}
other => panic!("expected Matrix, got {other:?}"),
},
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn test_mat_roundtrip_string() {
use matrw::{matfile, matvar, save_matfile_v7};
let path = tmp_mat_path("string");
let ps = path.to_str().unwrap();
let mat = matfile!(label: matvar!("hello"),);
save_matfile_v7(ps, mat, false).expect("write .mat");
let result = mat_load(ps).unwrap();
std::fs::remove_file(&path).ok();
match result {
Value::Struct(fields) => match fields.get("label").unwrap() {
Value::Str(s) => assert_eq!(s, "hello"),
other => panic!("expected Str, got {other:?}"),
},
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
fn test_mat_roundtrip_struct() {
use matrw::{matfile, matvar, save_matfile_v7};
let path = tmp_mat_path("struct");
let ps = path.to_str().unwrap();
let mat = matfile!(s: matvar!({ x: 1.0, y: 2.0 }),);
save_matfile_v7(ps, mat, false).expect("write .mat");
let result = mat_load(ps).unwrap();
std::fs::remove_file(&path).ok();
match result {
Value::Struct(outer) => match outer.get("s").unwrap() {
Value::Struct(inner) => {
assert_eq!(inner.get("x"), Some(&Value::Scalar(1.0)));
assert_eq!(inner.get("y"), Some(&Value::Scalar(2.0)));
}
other => panic!("expected inner Struct, got {other:?}"),
},
other => panic!("expected Struct, got {other:?}"),
}
}
#[test]
#[ignore]
fn create_example_fixture() {
use matrw::{matfile, matvar, save_matfile_v7};
let workspace = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap();
let dir = workspace.join("examples/mat/fixtures");
std::fs::create_dir_all(&dir).unwrap();
let mat = matfile!(
score: matvar!(92.5),
readings: matvar!([23.1, 21.8, 24.3, 22.7, 25.0, 23.6]),
A: matvar!([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),
label: matvar!("experiment-1"),
sensor: matvar!({ id: 1.0, gain: 0.5 }),
);
let out = dir.join("sample.mat");
save_matfile_v7(out.to_str().unwrap(), mat, false).expect("write sample.mat");
println!("Created {}", out.display());
}
}