use super::*;
fn test_io() -> IoContext {
IoContext::new()
}
#[test]
fn test_who_columns_empty() {
assert!(who_format_columns(&[], 80).is_empty());
}
#[test]
fn test_who_columns_single_entry() {
let entries = vec!["x = 3.14".to_string()];
assert_eq!(who_format_columns(&entries, 80), vec!["x = 3.14"]);
}
#[test]
fn test_who_columns_fits_one_row() {
let entries = vec![
"a = 1".to_string(),
"b = 2".to_string(),
"c = 3".to_string(),
];
let lines = who_format_columns(&entries, 80);
assert_eq!(lines.len(), 1);
assert!(lines[0].contains("a = 1"));
assert!(lines[0].contains("b = 2"));
assert!(lines[0].contains("c = 3"));
}
#[test]
fn test_who_columns_wraps_to_two_columns() {
let entries: Vec<String> = (1..=6).map(|i| format!("v{} = {}", i, i)).collect();
let lines = who_format_columns(&entries, 18);
assert_eq!(lines.len(), 3);
assert!(lines[0].starts_with("v1 = 1"));
assert!(lines[0].contains("v4 = 4"));
assert!(lines[1].starts_with("v2 = 2"));
assert!(lines[2].starts_with("v3 = 3"));
}
#[test]
fn test_who_columns_narrow_terminal_one_col() {
let entries = vec!["longname = 123".to_string(), "other = 456".to_string()];
let lines = who_format_columns(&entries, 5);
assert_eq!(lines.len(), 2);
}
#[test]
fn test_split_stmts_single_no_semi() {
assert_eq!(split_stmts("a = 1"), vec![("a = 1", false)]);
}
#[test]
fn test_split_stmts_single_trailing_semi() {
assert_eq!(split_stmts("a = 1;"), vec![("a = 1", true)]);
}
#[test]
fn test_split_stmts_two_stmts_last_no_semi() {
assert_eq!(
split_stmts("a = 1; b = 2"),
vec![("a = 1", true), ("b = 2", false)]
);
}
#[test]
fn test_split_stmts_all_silent() {
assert_eq!(
split_stmts("a = 1; b = 2; c = 3;"),
vec![("a = 1", true), ("b = 2", true), ("c = 3", true)]
);
}
#[test]
fn test_split_stmts_comment_stripped() {
assert_eq!(split_stmts("10; % comment"), vec![("10", true)]);
assert_eq!(split_stmts("10 % comment"), vec![("10", false)]);
}
#[test]
fn test_split_stmts_empty_input() {
assert_eq!(split_stmts(""), Vec::<(&str, bool)>::new());
assert_eq!(split_stmts(" % only comment"), Vec::<(&str, bool)>::new());
}
#[test]
fn test_split_stmts_whitespace_segments_skipped() {
assert_eq!(
split_stmts("a = 1;; b = 2"),
vec![("a = 1", true), ("b = 2", false)]
);
}
#[test]
fn test_split_stmts_semi_in_string_not_split() {
assert_eq!(
split_stmts("fprintf('hello; world')"),
vec![("fprintf('hello; world')", false)]
);
}
#[test]
fn test_split_stmts_semi_in_matrix_not_split() {
assert_eq!(split_stmts("[1 2; 3 4]"), vec![("[1 2; 3 4]", false)]);
}
#[test]
fn test_split_stmts_transpose_semi_splits() {
assert_eq!(split_stmts("p = R' * q;"), vec![("p = R' * q", true)]);
}
#[test]
fn test_split_stmts_transpose_multi_stmt() {
assert_eq!(split_stmts("A'; B"), vec![("A'", true), ("B", false)]);
}
#[test]
fn test_split_stmts_matrix_then_semi() {
assert_eq!(
split_stmts("A = [1 2; 3 4]; B = 5"),
vec![("A = [1 2; 3 4]", true), ("B = 5", false)]
);
}
#[test]
fn test_extract_base_suffix_hex() {
let (expr, suffix) = extract_base_suffix("255 hex");
assert_eq!(expr, "255");
assert_eq!(suffix, Some(BaseSuffix::Switch(Base::Hex)));
}
#[test]
fn test_extract_base_suffix_bin() {
let (expr, suffix) = extract_base_suffix("10 bin");
assert_eq!(expr, "10");
assert_eq!(suffix, Some(BaseSuffix::Switch(Base::Bin)));
}
#[test]
fn test_extract_base_suffix_oct() {
let (expr, suffix) = extract_base_suffix("8 oct");
assert_eq!(expr, "8");
assert_eq!(suffix, Some(BaseSuffix::Switch(Base::Oct)));
}
#[test]
fn test_extract_base_suffix_dec() {
let (expr, suffix) = extract_base_suffix("255 dec");
assert_eq!(expr, "255");
assert_eq!(suffix, Some(BaseSuffix::Switch(Base::Dec)));
}
#[test]
fn test_extract_base_suffix_show_all() {
let (expr, suffix) = extract_base_suffix("10 base");
assert_eq!(expr, "10");
assert_eq!(suffix, Some(BaseSuffix::ShowAll));
}
#[test]
fn test_extract_base_suffix_none() {
let (expr, suffix) = extract_base_suffix("255 + 10");
assert_eq!(expr, "255 + 10");
assert!(suffix.is_none());
}
#[test]
fn test_extract_base_suffix_complex() {
let (expr, suffix) = extract_base_suffix("0xFF + 0b1010 hex");
assert_eq!(expr, "0xFF + 0b1010");
assert_eq!(suffix, Some(BaseSuffix::Switch(Base::Hex)));
}
#[test]
fn test_extract_base_suffix_no_space() {
let (expr, suffix) = extract_base_suffix("hex");
assert_eq!(expr, "hex");
assert!(suffix.is_none());
}
#[test]
fn test_format_for_base_hex() {
assert_eq!(format_for_base(10.0, Base::Hex), "0xA");
assert_eq!(format_for_base(255.0, Base::Hex), "0xFF");
assert_eq!(format_for_base(0.0, Base::Hex), "0x0");
}
#[test]
fn test_format_for_base_bin() {
assert_eq!(format_for_base(10.0, Base::Bin), "0b1010");
assert_eq!(format_for_base(1.0, Base::Bin), "0b1");
}
#[test]
fn test_format_for_base_oct() {
assert_eq!(format_for_base(8.0, Base::Oct), "0o10");
assert_eq!(format_for_base(255.0, Base::Oct), "0o377");
}
#[allow(clippy::approx_constant)]
#[test]
fn test_format_for_base_dec() {
assert_eq!(format_for_base(42.0, Base::Dec), "42");
assert_eq!(format_for_base(3.14, Base::Dec), "3.14");
}
#[test]
fn test_format_expr_hex_converts_bin_and_dec() {
assert_eq!(
format_expr_for_display("0xFF + 0b1010 + 10", Base::Hex),
Some("0xFF + 0xA + 0xA".to_string())
);
}
#[test]
fn test_format_expr_hex_keeps_hex_literals() {
assert_eq!(
format_expr_for_display("0xFF + 0b1010", Base::Hex),
Some("0xFF + 0xA".to_string())
);
}
#[test]
fn test_format_expr_dec_converts_hex() {
assert_eq!(
format_expr_for_display("0xFF + 10", Base::Dec),
Some("255 + 10".to_string())
);
}
#[test]
fn test_format_expr_no_change_when_all_match() {
assert_eq!(format_expr_for_display("10 + 5", Base::Dec), None);
assert_eq!(format_expr_for_display("0xFF + 0xA", Base::Hex), None);
}
#[test]
fn test_format_expr_preserves_identifiers() {
assert_eq!(
format_expr_for_display("sin(pi) + 0b1010", Base::Hex),
Some("sin(pi) + 0xA".to_string())
);
}
#[test]
fn test_format_expr_bin_accumulator_mixed_bases() {
assert_eq!(
format_expr_for_display("2 + 0b110 + 0xa", Base::Bin),
Some("0b10 + 0b110 + 0b1010".to_string())
);
}
#[test]
fn test_format_expr_hex_accumulator_bin_literals() {
assert_eq!(
format_expr_for_display("0b11 + 0b11", Base::Hex),
Some("0x3 + 0x3".to_string())
);
}
#[test]
fn test_parse_disp_cmd_simple() {
assert_eq!(parse_disp_cmd("disp(42)"), Some("42"));
assert_eq!(parse_disp_cmd("disp(x + 1)"), Some("x + 1"));
assert_eq!(parse_disp_cmd("disp(sin(pi/2))"), Some("sin(pi/2)"));
}
#[test]
fn test_parse_disp_cmd_not_matched() {
assert!(parse_disp_cmd("display(42)").is_none());
assert!(parse_disp_cmd("disp()").is_none());
assert!(parse_disp_cmd("disp 42").is_none());
}
#[test]
fn test_expand_vars_no_vars() {
let env = new_env();
assert_eq!(expand_vars_for_display("2 + 3", &env, Base::Dec), None);
}
#[test]
fn test_expand_vars_single() {
let mut env = new_env();
env.insert("x".to_string(), Value::Scalar(10.0));
assert_eq!(
expand_vars_for_display("x + 5", &env, Base::Dec),
Some("10 + 5".to_string())
);
}
#[test]
fn test_expand_vars_multiple() {
let mut env = new_env();
env.insert("ans".to_string(), Value::Scalar(13.0));
env.insert("x".to_string(), Value::Scalar(10.0));
env.insert("y".to_string(), Value::Scalar(20.0));
assert_eq!(
expand_vars_for_display("ans + x + y", &env, Base::Dec),
Some("13 + 10 + 20".to_string())
);
}
#[test]
fn test_expand_vars_unknown_ident_preserved() {
let mut env = new_env();
env.insert("x".to_string(), Value::Scalar(5.0));
assert_eq!(
expand_vars_for_display("sqrt(x)", &env, Base::Dec),
Some("sqrt(5)".to_string())
);
}
#[test]
fn test_expand_vars_in_hex_base() {
let mut env = new_env();
env.insert("x".to_string(), Value::Scalar(255.0));
assert_eq!(
expand_vars_for_display("x + 1", &env, Base::Hex),
Some("0xFF + 1".to_string())
);
}
#[test]
fn test_expand_vars_matrix_not_expanded() {
let mut env = new_env();
evaluate("[1 2; 3 4]", &mut env, &mut test_io()).unwrap();
let mat_val = env.get("ans").unwrap().clone();
env.insert("m".to_string(), mat_val);
assert_eq!(expand_vars_for_display("m + 1", &env, Base::Dec), None);
}
#[test]
fn test_evaluate_simple() {
let mut env = Env::new();
let result = evaluate("3 * 4", &mut env, &mut test_io()).unwrap();
assert!(matches!(result, EvalResult::Value(Value::Scalar(12.0))));
assert_eq!(ans(&env), 12.0);
}
#[test]
fn test_evaluate_partial_adds_to_ans() {
let mut env = Env::new();
env.insert("ans".to_string(), Value::Scalar(10.0));
let result = evaluate("+ 5", &mut env, &mut test_io()).unwrap();
assert!(matches!(result, EvalResult::Value(Value::Scalar(15.0))));
assert_eq!(ans(&env), 15.0);
}
#[test]
fn test_evaluate_assignment() {
let mut env = Env::new();
let result = evaluate("x = 7", &mut env, &mut test_io()).unwrap();
assert!(matches!(&result, EvalResult::Assigned(n, Value::Scalar(v)) if n == "x" && *v == 7.0));
assert_eq!(env.get("x"), Some(&Value::Scalar(7.0)));
}
#[test]
fn test_evaluate_expression_always_updates_ans() {
let mut env = new_env();
let result = evaluate("3 * 4", &mut env, &mut test_io()).unwrap();
assert!(matches!(result, EvalResult::Value(Value::Scalar(12.0))));
assert_eq!(ans(&env), 12.0);
}
#[test]
fn test_evaluate_assignment_does_not_update_ans() {
let mut env = new_env();
let result = evaluate("x = 7", &mut env, &mut test_io()).unwrap();
assert!(matches!(&result, EvalResult::Assigned(n, Value::Scalar(v)) if n == "x" && *v == 7.0));
assert_eq!(env.get("x"), Some(&Value::Scalar(7.0)));
assert_eq!(ans(&env), 0.0);
}
#[test]
fn test_evaluate_sets_base_via_suffix() {
let mut env = Env::new();
let mut base = Base::Dec;
let (to_eval, suffix) = extract_base_suffix("255 hex");
if let Some(BaseSuffix::Switch(b)) = suffix {
base = b;
}
evaluate(to_eval, &mut env, &mut test_io()).unwrap();
assert_eq!(base, Base::Hex);
assert_eq!(ans(&env), 255.0);
}
fn pipe_output(input: &str) -> Vec<String> {
use ccalc_engine::eval::{FormatMode, eval};
use ccalc_engine::parser::{Stmt, parse};
use std::io::Cursor;
let mut output = Vec::new();
let mut env = new_env();
let fmt = FormatMode::default();
let mut base = Base::Dec;
let reader = Cursor::new(input);
'lines: for line in reader.lines() {
let line = line.unwrap();
let trimmed = line.trim();
for (stmt, silent) in split_stmts(trimmed) {
match stmt {
"exit" | "quit" => break 'lines,
"clear" => {
env.clear();
continue;
}
"cls" | "who" => continue,
"hex" => {
base = Base::Hex;
continue;
}
"dec" => {
base = Base::Dec;
continue;
}
"bin" => {
base = Base::Bin;
continue;
}
"oct" => {
base = Base::Oct;
continue;
}
_ => {}
}
if let Some(name) = stmt.strip_prefix("clear ").map(str::trim) {
if !name.is_empty() {
env.remove(name);
}
continue;
}
if let Some(cmd) = try_parse_save_load(stmt, &env) {
use ccalc_engine::env::{load_workspace, save_workspace, save_workspace_vars};
match cmd {
SaveLoadCmd::Save { path, vars } => {
let _ = match (&path, vars.is_empty()) {
(None, _) => save_workspace_default(&env),
(Some(p), true) => save_workspace(&env, std::path::Path::new(p)),
(Some(p), false) => {
let var_refs: Vec<&str> = vars.iter().map(String::as_str).collect();
save_workspace_vars(&env, std::path::Path::new(p), &var_refs)
}
};
}
SaveLoadCmd::Load { path } => {
let result = match path {
None => load_workspace_default(),
Some(p) => load_workspace(std::path::Path::new(&p)),
};
if let Ok(loaded) = result {
env = loaded;
}
}
}
continue;
}
if let Some(arg) = parse_disp_cmd(stmt) {
let result = parse(arg.trim()).and_then(|stmt| {
let expr = match stmt {
Stmt::Expr(e) | Stmt::Assign(_, e) => e,
_ => return Err("Block statements not valid in disp()".to_string()),
};
eval(&expr, &env)
});
match result {
Ok(v) => match &v {
Value::Void => {}
Value::Matrix(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push(full);
}
}
Value::Scalar(n) => output.push(format_scalar(*n, base, &fmt)),
Value::Complex(re, im) => output.push(format_complex(*re, *im, &fmt)),
Value::Str(s) | Value::StringObj(s) => output.push(s.clone()),
Value::Lambda(_) => output.push("@<lambda>".to_string()),
Value::Function { .. } => output.push("@function".to_string()),
Value::Tuple(_) => {}
Value::Cell(_) | Value::Struct(_) | Value::StructArray(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push(full);
}
}
},
Err(e) => output.push(format!("Error: {e}")),
}
continue;
}
let (to_eval, base_suffix) = extract_base_suffix(stmt);
let show_all = matches!(base_suffix, Some(BaseSuffix::ShowAll));
if let Some(BaseSuffix::Switch(b)) = base_suffix {
base = b;
}
match evaluate(to_eval, &mut env, &mut test_io()) {
Ok(result) => {
if !silent {
match result {
EvalResult::Assigned(name, v) => match &v {
Value::Void => {}
Value::Matrix(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push(format!("{name} ="));
output.push(full);
}
}
Value::Scalar(n) => {
output.push(format!(
"{} = {}",
name,
format_scalar(*n, base, &fmt)
));
}
Value::Complex(re, im) => {
output.push(format!(
"{} = {}",
name,
format_complex(*re, *im, &fmt)
));
}
Value::Str(s) => output.push(format!("{name} = {s}")),
Value::StringObj(s) => output.push(format!("{name} = {s}")),
Value::Lambda(_) => output.push(format!("{name} = @<lambda>")),
Value::Function { .. } => {
output.push(format!("{name} = @function"))
}
Value::Tuple(_) => {}
Value::Cell(_) | Value::Struct(_) | Value::StructArray(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push(format!("{name} ="));
output.push(full);
}
}
},
EvalResult::Value(v) => match &v {
Value::Void => {}
Value::Matrix(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push("ans =".to_string());
output.push(full);
}
}
Value::Scalar(n) => {
if show_all {
let i = n.round() as i64;
let u = i.unsigned_abs();
let sign = if i < 0 { "-" } else { "" };
output.push(format!("2 - {}0b{:b}", sign, u));
output.push(format!("8 - {}0o{:o}", sign, u));
output.push(format!(
"10 - {}",
format_scalar(*n, Base::Dec, &fmt)
));
output.push(format!("16 - {}0x{:X}", sign, u));
} else {
output.push(format_scalar(*n, base, &fmt));
}
}
Value::Complex(re, im) => {
output.push(format_complex(*re, *im, &fmt));
}
Value::Str(s) | Value::StringObj(s) => output.push(s.clone()),
Value::Lambda(_) => output.push("@<lambda>".to_string()),
Value::Function { .. } => output.push("@function".to_string()),
Value::Tuple(_) => {}
Value::Cell(_) | Value::Struct(_) | Value::StructArray(_) => {
if let Some(full) = format_value_full(&v, &fmt) {
output.push("ans =".to_string());
output.push(full);
}
}
},
}
}
}
Err(e) => output.push(format!("Error: {e}")),
}
}
}
output
}
#[test]
fn test_pipe_simple_expression() {
assert_eq!(pipe_output("2 + 2"), vec!["4"]);
}
#[test]
fn test_pipe_power() {
assert_eq!(pipe_output("2 ^ 32"), vec!["4294967296"]);
}
#[test]
fn test_pipe_sqrt() {
assert_eq!(pipe_output("sqrt(2)"), vec!["1.4142135624"]);
}
#[test]
fn test_pipe_multi_line_accumulates() {
let lines = "10\n+ 5\n* 2";
assert_eq!(pipe_output(lines), vec!["10", "15", "30"]);
}
#[test]
fn test_pipe_quit_with_exit() {
let lines = "1\n2\nexit\n3";
assert_eq!(pipe_output(lines), vec!["1", "2"]);
}
#[test]
fn test_pipe_quit_with_quit() {
let lines = "1\n2\nquit\n3";
assert_eq!(pipe_output(lines), vec!["1", "2"]);
}
#[test]
fn test_pipe_empty_lines_skipped() {
let lines = "1\n\n\n2";
assert_eq!(pipe_output(lines), vec!["1", "2"]);
}
#[test]
fn test_pipe_comments_skipped() {
let lines = "% header comment\n1\n% inline comment\n+ 2\n% trailing comment";
assert_eq!(pipe_output(lines), vec!["1", "3"]);
}
#[test]
fn test_pipe_inline_comments_stripped() {
let lines = "10 % first value\n+ 5 % add five";
assert_eq!(pipe_output(lines), vec!["10", "15"]);
}
#[test]
fn test_pipe_error_reported() {
let out = pipe_output("1 / 0");
assert!(out[0].starts_with("Error:"));
}
#[test]
fn test_pipe_variable_assignment() {
let lines = "x = 7\nx + 3";
assert_eq!(pipe_output(lines), vec!["x = 7", "10"]);
}
#[test]
fn test_pipe_hex_literals() {
assert_eq!(pipe_output("0xFF"), vec!["255"]);
assert_eq!(pipe_output("0xFF + 0b1010"), vec!["265"]);
}
#[test]
fn test_pipe_hex_base_suffix_changes_display() {
let lines = "0xFF + 0b1010 hex";
assert_eq!(pipe_output(lines), vec!["0x109"]);
}
#[test]
fn test_pipe_base_persists() {
let lines = "0xFF + 0b1010 hex\n+ 0b10";
assert_eq!(pipe_output(lines), vec!["0x109", "0x10B"]);
}
#[test]
fn test_pipe_base_switch_dec() {
let lines = "255 hex\ndec";
let out = pipe_output(lines);
assert_eq!(out, vec!["0xFF"]);
}
#[test]
fn test_pipe_bin_literals() {
assert_eq!(pipe_output("0b1010"), vec!["10"]);
}
#[test]
fn test_pipe_oct_literals() {
assert_eq!(pipe_output("0o17"), vec!["15"]);
}
#[test]
fn test_pipe_base_suffix_shows_all() {
let out = pipe_output("10 base");
assert_eq!(out, vec!["2 - 0b1010", "8 - 0o12", "10 - 10", "16 - 0xA"]);
}
#[test]
fn test_pipe_base_suffix_evaluates_expression() {
let out = pipe_output("0xFF + 0b1010 base");
assert_eq!(
out,
vec!["2 - 0b100001001", "8 - 0o411", "10 - 265", "16 - 0x109"]
);
}
#[test]
fn test_pipe_base_suffix_accumulator_set() {
let out = pipe_output("10 base\n+ 5");
assert_eq!(out[4], "15");
}
#[test]
fn test_pipe_sci_partial_expression() {
let out = pipe_output("1e-12\n* 1000");
assert_eq!(out[0], "1e-12");
assert_eq!(out[1], "0.000000001");
let out2 = pipe_output("1e-12\n* 1000\n* 1000");
assert_eq!(out2[2], "0.000001");
let out3 = pipe_output("1e-12\n* 10");
assert_eq!(out3[1], "1e-11");
}
#[test]
fn test_pipe_semicolon_suppresses_output() {
let out = pipe_output("10;\n+ 5");
assert_eq!(out, vec!["15"]);
}
#[test]
fn test_pipe_semicolon_still_updates_ans() {
let out = pipe_output("10;\ndisp(ans)");
assert_eq!(out, vec!["10"]);
}
#[test]
fn test_pipe_semicolon_with_comment() {
let out = pipe_output("10; % intermediate\ndisp(ans)");
assert_eq!(out, vec!["10"]);
}
#[test]
fn test_pipe_semicolon_variable_store() {
let out = pipe_output("7;\nx = ans;\nx + 3");
assert_eq!(out, vec!["10"]);
}
#[test]
fn test_pipe_multi_stmt_last_shown() {
let out = pipe_output("a = 1; b = 2");
assert_eq!(out, vec!["b = 2"]);
}
#[test]
fn test_pipe_multi_stmt_all_silent() {
let out = pipe_output("a = 1; b = 2;");
assert_eq!(out, Vec::<String>::new());
}
#[test]
fn test_pipe_multi_stmt_ans_not_updated_by_assignment() {
let out = pipe_output("a = 5; b = 10\ndisp(ans)");
assert_eq!(out, vec!["b = 10", "0"]);
}
#[test]
fn test_pipe_multi_stmt_expr_last() {
let out = pipe_output("x = 3; x * 2");
assert_eq!(out, vec!["6"]);
}
#[test]
fn test_pipe_disp_simple() {
let out = pipe_output("disp(42)");
assert_eq!(out, vec!["42"]);
}
#[test]
fn test_pipe_disp_expression() {
let out = pipe_output("disp(sqrt(16))");
assert_eq!(out, vec!["4"]);
}
#[test]
fn test_pipe_disp_does_not_change_ans() {
let out = pipe_output("10\ndisp(42)\n+ 5");
assert_eq!(out, vec!["10", "42", "15"]);
}
#[test]
fn test_pipe_disp_variable() {
let out = pipe_output("x = 7;\ndisp(x)");
assert_eq!(out, vec!["7"]);
}
#[test]
fn test_pipe_fprintf_returns_void_no_captured_output() {
let out = pipe_output("fprintf('hello\\n')");
assert!(out.is_empty());
}
#[test]
fn test_pipe_fprintf_double_quotes_void() {
let out = pipe_output("fprintf(\"hi\\n\")");
assert!(out.is_empty());
}
#[test]
fn test_pipe_fprintf_with_arg_void() {
let out = pipe_output("fprintf('%d\\n', 42)");
assert!(out.is_empty());
}
#[test]
fn test_pipe_sprintf_single_quotes() {
let out = pipe_output("sprintf('hello\\n')");
assert_eq!(out, vec!["hello\n"]);
}
#[test]
fn test_pipe_sprintf_format_arg() {
let out = pipe_output("sprintf('%d items', 5)");
assert_eq!(out, vec!["5 items"]);
}
#[test]
fn test_pipe_base_suffix_accumulator_set_uses_ans() {
let out = pipe_output("ans\n+ 5");
assert_eq!(out, vec!["0", "5"]);
}
#[test]
fn test_pipe_matrix_assignment() {
let out = pipe_output("A = [1 2; 3 4]");
assert_eq!(out[0], "A =");
assert!(out[1].contains("1"));
assert!(out[1].contains("2"));
}
#[test]
fn test_pipe_matrix_literal() {
let out = pipe_output("[1 2 3]");
assert_eq!(out[0], "ans =");
assert!(out[1].contains("1"));
}
#[test]
fn test_pipe_matrix_semicolon_not_split() {
let out = pipe_output("[1 2; 3 4]");
assert_eq!(out[0], "ans =");
assert_eq!(out[1].lines().count(), 2);
}
#[test]
fn test_try_parse_save_bare() {
let env = ccalc_engine::env::Env::new();
let cmd = try_parse_save_load("save", &env);
assert!(matches!(cmd, Some(SaveLoadCmd::Save { path: None, .. })));
}
#[test]
fn test_try_parse_load_bare() {
let env = ccalc_engine::env::Env::new();
let cmd = try_parse_save_load("load", &env);
assert!(matches!(cmd, Some(SaveLoadCmd::Load { path: None })));
}
#[test]
fn test_try_parse_save_with_path() {
let env = ccalc_engine::env::Env::new();
let cmd = try_parse_save_load("save('session.mat')", &env);
match cmd {
Some(SaveLoadCmd::Save {
path: Some(p),
vars,
}) => {
assert_eq!(p, "session.mat");
assert!(vars.is_empty());
}
_ => panic!("expected Save with path"),
}
}
#[test]
fn test_try_parse_save_with_vars() {
let env = ccalc_engine::env::Env::new();
let cmd = try_parse_save_load("save('out.mat', 'x', 'y')", &env);
match cmd {
Some(SaveLoadCmd::Save {
path: Some(p),
vars,
}) => {
assert_eq!(p, "out.mat");
assert_eq!(vars, vec!["x", "y"]);
}
_ => panic!("expected Save with path and vars"),
}
}
#[test]
fn test_try_parse_load_with_path() {
let env = ccalc_engine::env::Env::new();
let cmd = try_parse_save_load("load('session.mat')", &env);
match cmd {
Some(SaveLoadCmd::Load { path: Some(p) }) => assert_eq!(p, "session.mat"),
_ => panic!("expected Load with path"),
}
}
#[test]
fn test_try_parse_unrelated_returns_none() {
let env = ccalc_engine::env::Env::new();
assert!(try_parse_save_load("x = 3", &env).is_none());
assert!(try_parse_save_load("fprintf('hi')", &env).is_none());
}
#[allow(clippy::approx_constant)]
#[test]
fn test_pipe_save_load_roundtrip() {
use ccalc_engine::env::load_workspace;
let tmp = std::env::temp_dir().join("ccalc_test_pipe_saveload.mat");
let path = tmp.to_string_lossy().to_string();
let save_script = format!("x = 42\ny = 3.14\nsave('{path}')");
pipe_output(&save_script);
let loaded = load_workspace(std::path::Path::new(&path)).unwrap();
assert_eq!(loaded.get("x"), Some(&Value::Scalar(42.0)));
assert_eq!(loaded.get("y"), Some(&Value::Scalar(3.14)));
std::fs::remove_file(&tmp).ok();
}
#[test]
fn test_pipe_save_selective_vars() {
use ccalc_engine::env::load_workspace;
let tmp = std::env::temp_dir().join("ccalc_test_pipe_save_selective.mat");
let path = tmp.to_string_lossy().to_string();
let save_script = format!("x = 1\ny = 2\nz = 3\nsave('{path}', 'x', 'z')");
pipe_output(&save_script);
let loaded = load_workspace(std::path::Path::new(&path)).unwrap();
assert_eq!(loaded.get("x"), Some(&Value::Scalar(1.0)));
assert_eq!(loaded.get("z"), Some(&Value::Scalar(3.0)));
assert!(!loaded.contains_key("y"));
std::fs::remove_file(&tmp).ok();
}