mod test_helpers;
use runmat_parser::parse;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::thread;
use test_helpers::lower;
use test_helpers::{execute, interpret};
#[test]
fn nargin_nargout_in_user_functions() {
let program = r#"
function y = f(a,b)
y = nargin() + nargout(); % 2 + 1 = 3
end
r = f(10, 20);
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-3.0).abs()<1e-9)));
let program2 = r#"
function [x,y] = g(a)
x = nargin(); % 1
y = nargout(); % 2
end
[u,v] = g(7);
"#;
let hir2 = lower(&parse(program2).unwrap()).unwrap();
let vars2 = execute(&hir2).unwrap();
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-1.0).abs()<1e-9)));
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-2.0).abs()<1e-9)));
}
#[test]
fn not_enough_and_too_many_inputs_fixed_arity() {
let program = r#"
function y = f(a,b)
y = a + b;
end
r = f(1);
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let err = execute(&hir).err().unwrap();
assert_eq!(err.identifier(), Some("RunMat:NotEnoughInputs"));
let program2 = r#"
function y = f(a,b)
y = a + b;
end
r = f(1,2,3);
"#;
let hir2 = lower(&parse(program2).unwrap()).unwrap();
let err2 = execute(&hir2).err().unwrap();
assert_eq!(err2.identifier(), Some("RunMat:TooManyInputs"));
}
#[test]
fn inputs_with_varargin_minimum_only() {
let program_err = r#"
function y = f(a,b,varargin)
y = a + b;
end
r = f(1);
"#;
let hir_e = lower(&parse(program_err).unwrap()).unwrap();
let err = execute(&hir_e).err().unwrap();
assert_eq!(err.identifier(), Some("RunMat:NotEnoughInputs"));
let program_ok = r#"
function y = f(a,b,varargin)
% Keep it simple to avoid runtime cat/sum dependencies
y = a + b + varargin{1} + varargin{2};
end
r = f(1,2,3,4);
"#;
let hir_ok = lower(&parse(program_ok).unwrap()).unwrap();
let vars_ok = execute(&hir_ok).unwrap();
assert!(vars_ok
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-10.0).abs()<1e-9)));
}
#[test]
fn too_many_outputs_and_varargout_mismatch() {
let program_tmo = r#"
function y = f(a)
y = a + 1;
end
[x1,x2] = f(3);
"#;
let hir_tmo = lower(&parse(program_tmo).unwrap()).unwrap();
let err_tmo = execute(&hir_tmo).err().unwrap();
assert_eq!(err_tmo.identifier(), Some("RunMat:TooManyOutputs"));
let program_mis = r#"
function varargout = h(a)
varargout = {a+1, a+2};
end
[x1,x2,x3] = h(5);
"#;
let hir_mis = lower(&parse(program_mis).unwrap()).unwrap();
let err_mis = execute(&hir_mis).err().unwrap();
assert_eq!(err_mis.identifier(), Some("RunMat:VarargoutMismatch"));
}
#[allow(dead_code)]
fn function_definition_and_calls() {
let ast = parse("function y = f(x); y = x + 1; end; a = f(2);").unwrap();
let hir = lower(&ast).unwrap();
let result = execute(&hir);
assert!(result.is_ok());
}
#[test]
fn nested_function_calls() {
let program = "function y = add(a, b); y = a + b; end; function y = multiply_and_add(x); y = add(x * 2, x * 3); end; result = multiply_and_add(4);";
let handle = thread::Builder::new()
.stack_size(8 * 1024 * 1024)
.spawn(move || {
let ast = parse(program).unwrap();
let hir = lower(&ast).unwrap();
let result = execute(&hir);
assert!(result.is_ok());
})
.expect("spawn nested_function_calls thread");
handle.join().expect("nested_function_calls thread failed");
}
#[test]
fn shared_input_output_name_updates_in_place() {
let program = r#"
function x = bump(x)
x = x + 1;
end
r = bump(4);
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs() < 1e-9)));
}
#[test]
fn shared_input_output_name_multi_output_reads_original_input() {
let program = r#"
function [x, y] = bump_and_copy(x)
y = x;
x = x + 1;
end
[a, b] = bump_and_copy(4);
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs() < 1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 4.0).abs() < 1e-9)));
}
#[test]
fn fprintf_inline_cast_argument_does_not_stack_underflow() {
let program = r#"
x = single(3.14);
fprintf("Value: %.4f\n", double(x));
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let result = execute(&hir);
assert!(
result.is_ok(),
"inline cast in fprintf should execute without stack underflow: {result:?}"
);
}
#[test]
fn sprintf_inline_cast_argument_formats_value() {
let program = r#"
x = single(3.14);
s = sprintf("Value: %.4f", double(x));
"#;
let hir = lower(&parse(program).unwrap()).unwrap();
let vars = execute(&hir).expect("sprintf with inline cast should succeed");
assert!(vars.iter().any(|value| {
if let runmat_builtins::Value::CharArray(chars) = value {
let rendered: String = chars.data.iter().collect();
rendered == "Value: 3.1400"
} else {
false
}
}));
}
#[test]
fn member_get_set_and_method_call_skeleton() {
let input = "obj = new_object('Point'); obj = setfield(obj, 'x', 3); ax = getfield(obj, 'x');";
let ast = parse(input).unwrap();
let hir = lower(&ast).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 3.0).abs() < f64::EPSILON)));
let input2 = "obj = new_object('Point'); obj = setfield(obj,'x',5); obj = setfield(obj,'y',7); obj = call_method(obj, 'move', 1, 2); rx = getfield(obj,'x'); ry = getfield(obj,'y');";
let ast2 = parse(input2).unwrap();
let hir2 = lower(&ast2).unwrap();
let vars2 = execute(&hir2).unwrap();
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 6.0).abs() < f64::EPSILON)));
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 9.0).abs() < f64::EPSILON)));
}
#[test]
fn implicit_struct_creation_for_root_variable_assignment() {
let input = "s.x = 10; s.y = 20; v = getfield(s, 'x') + getfield(s, 'y');";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("implicit struct creation should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 30.0).abs() < 1e-9)));
}
#[test]
fn implicit_struct_creation_for_function_output_variable() {
let input = r#"
function r = make_result()
r.x = 10;
r.y = 20;
end
s = make_result();
x = getfield(s, 'x');
y = getfield(s, 'y');
"#;
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("function output struct materialization should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs() < 1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 20.0).abs() < 1e-9)));
}
#[test]
fn nested_member_assignment_materializes_missing_intermediate_structs() {
let input = "s = struct(); s.a.b = 1; v = getfield(getfield(s, 'a'), 'b');";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("nested member assignment should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 1.0).abs() < 1e-9)));
}
#[test]
fn struct_field_indexing_read_path_uses_member_then_index_semantics() {
let input =
"s = struct(); s.arr = [10 20 30]; x = s.arr(2); y = s.arr(1:2); y1 = y(1); y2 = y(2);";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("struct field indexing reads should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 20.0).abs() < 1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs() < 1e-9)));
}
#[test]
fn dotted_invoke_preserves_object_method_dispatch() {
let input = "obj = new_object('Point'); obj = setfield(obj,'x',5); obj = setfield(obj,'y',7); obj = obj.move(1,2); rx = getfield(obj,'x'); ry = getfield(obj,'y');";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("object method dispatch should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 6.0).abs() < 1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 9.0).abs() < 1e-9)));
}
#[test]
fn dotted_invoke_runtime_struct_dispatch_when_base_type_unknown() {
let input = r#"
function y = pick(s)
y = s.arr(2);
end
s = struct();
s.arr = [10 20 30];
z = pick(s);
"#;
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("runtime dotted invoke struct dispatch should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 20.0).abs() < 1e-9)));
}
#[test]
fn nested_dynamic_member_assignment_materializes_and_writes_back() {
let input =
"s = struct(); f1 = 'a'; f2 = 'b'; s.(f1).(f2) = 3; v = getfield(getfield(s, 'a'), 'b');";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("nested dynamic member assignment should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 3.0).abs() < 1e-9)));
}
#[test]
fn mixed_member_cell_and_index_read_chain() {
let input = "s = struct(); s.arr = {[10 20], [30 40]}; v = s.arr{2}(1);";
let hir = lower(&parse(input).unwrap()).unwrap();
let vars = execute(&hir).expect("mixed member/cell/index chain should succeed");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 30.0).abs() < 1e-9)));
}
#[test]
fn function_handle_anon_round_trip() {
let input = "h = make_handle('sin'); g = make_anon('x', 'x+1');";
let ast = parse(input).unwrap();
let hir = lower(&ast).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::FunctionHandle(_))));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::String(s) if s.starts_with("@anon"))));
}
#[test]
fn cellfun_upper_function_handle_round_trip() {
let input =
"names = {'Ada', 'Linus', 'Katherine'}; upper = cellfun(@upper, names, 'UniformOutput', false);";
let ast = parse(input).unwrap();
let hir = lower(&ast).unwrap();
let vars = execute(&hir).unwrap();
let mut found = false;
for value in vars {
if let runmat_builtins::Value::Cell(ca) = value {
if ca.data.len() != 3 {
continue;
}
let texts: Vec<String> = ca
.data
.iter()
.map(|ptr| String::try_from(&**ptr))
.collect::<Result<_, _>>()
.unwrap_or_default();
if texts == vec!["ADA", "LINUS", "KATHERINE"] {
found = true;
break;
}
}
}
assert!(found);
}
#[test]
fn classes_static_and_inheritance() {
let ast = parse("__register_test_classes();").unwrap();
let hir = lower(&ast).unwrap();
assert!(execute(&hir).is_ok());
let ast2 = parse("__register_test_classes(); p = new_object('Point'); ax = getfield(p,'x'); ns = new_object('pkg.PointNS'); nsx = getfield(ns,'x');").unwrap();
let hir2 = lower(&ast2).unwrap();
let vars2 = execute(&hir2).unwrap();
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 0.0).abs()<f64::EPSILON)));
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 1.0).abs()<f64::EPSILON)));
let ast3 = parse("__register_test_classes(); o = classref('Point').origin(); sv = classref('Point').staticValue;").unwrap();
let hir3 = lower(&ast3).unwrap();
let vars3 = execute(&hir3).unwrap();
assert!(vars3
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
assert!(vars3
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 42.0).abs()<f64::EPSILON)));
let ast4 = parse("__register_test_classes(); c = new_object('Circle'); c = setfield(c,'r', 2); a = feval(getmethod(c,'area'));").unwrap();
let hir4 = lower(&ast4).unwrap();
let vars4 = execute(&hir4).unwrap();
assert!(vars4.iter().any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - std::f64::consts::PI*4.0).abs() < 1e-9)));
let ast5 =
parse("__register_test_classes(); p = new_object('Point'); s = getfield(p,'secret');")
.unwrap();
let hir5 = lower(&ast5).unwrap();
assert!(execute(&hir5).is_err());
}
#[test]
fn static_method_via_classref_uses_namespaced_builtin_without_class_registry() {
let ast = parse("P = classref(\"Point\").origin(); point_class = class(P);").unwrap();
let hir = lower(&ast).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::String(s) if s == "Point")));
}
#[cfg(any(feature = "test-classes", test))]
#[test]
fn classes_constructor_and_overloaded_indexing() {
let ast = parse("__register_test_classes();").unwrap();
let hir = lower(&ast).unwrap();
assert!(execute(&hir).is_ok());
let program = "__register_test_classes(); c = Ctor(7); o = new_object('OverIdx'); o = call_method(o,'subsasgn','.', 'k', 5); t = call_method(o,'subsref','.', 'k');";
let hir2 = lower(&parse(program).unwrap()).unwrap();
let vars = execute(&hir2).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs() < 1e-9)));
}
#[cfg(any(feature = "test-classes", test))]
#[test]
fn classes_property_access_attributes() {
let _ = execute(&lower(&parse("__register_test_classes();").unwrap()).unwrap());
let ast =
parse("__register_test_classes(); p = new_object('Point'); p = setfield(p,'secret', 7);")
.unwrap();
let hir = lower(&ast).unwrap();
assert!(execute(&hir).is_err());
}
#[test]
#[ignore]
fn import_builtin_resolution_for_static_method() {
let program = "__register_test_classes(); import Point.*; o = origin();";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
}
#[test]
#[ignore]
fn import_specific_resolution_for_builtin() {
let program =
"import pkg.missing.*; import Point.origin; __register_test_classes(); o = origin();";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
}
#[test]
#[ignore]
fn import_ambiguity_specific_conflict_errors() {
let program = "import Point.origin; import Circle.area; r = origin();";
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn import_static_method_via_specific_class_import() {
let program = "__register_test_classes(); import Point.*; p = origin(); v = classref('Point').staticValue;";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(_))));
}
#[test]
fn import_precedence_specific_over_wildcard_and_locals() {
let program = r#"
__register_test_classes();
import PkgF.foo; % specific import of PkgF.foo
import PkgG.*; % wildcard import with also foo
a = foo(); % should resolve to PkgF.foo (10)
foo = @() 42; % local function handle shadowing imports
b = feval(foo); % should be 42 (local shadow)
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 42.0).abs()<1e-9)));
}
#[test]
fn import_ambiguity_between_specifics_errors() {
let program = r#"
import PkgF.foo;
import PkgG.foo;
y = foo();
"#;
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn import_ambiguity_between_wildcards_errors() {
let program = r#"
import PkgF.*;
import PkgG.*;
y = foo();
"#;
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn import_specific_conflict_with_user_function_prefers_local() {
let program = r#"
import PkgF.foo; % specific import exists
function y = foo()
y = 33;
end
a = foo(); % should call local function => 33
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 33.0).abs()<1e-9)));
}
#[test]
fn import_static_property_shadowed_by_local_variable() {
let program = r#"
__register_test_classes();
import Point.*;
staticValue = 7; % shadows classref('Point').staticValue (42)
v = staticValue;
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 7.0).abs()<1e-9)));
}
#[test]
fn import_specific_beats_wildcard_for_function_resolution() {
let program = r#"
import PkgF.foo; % specific import
import PkgG.*; % wildcard that also provides foo
y = foo(); % should resolve to PkgF.foo => 10
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs()<1e-9)));
}
#[test]
fn import_wildcard_static_method_ambiguity_errors() {
let program = r#"
import PkgF.*;
import PkgG.*;
z = foo(); % ambiguous via wildcard imports
"#;
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn local_function_shadows_class_static_method_under_class_star() {
let program = r#"
__register_test_classes();
import Point.*;
function y = origin()
y = 123;
end
v = origin(); % should call local (123), not Point.origin
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 123.0).abs()<1e-9)));
}
#[test]
fn multi_segment_import_builtin_vs_user_function_specific_prefers_user_function() {
let program = r#"
import PkgF.foo; % provides builtin PkgF.foo
function y = foo()
y = 77;
end
a = foo(); % should call the user function -> 77
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 77.0).abs()<1e-9)));
}
#[test]
fn unqualified_static_property_without_imports_errors() {
let program = r#"
__register_test_classes();
v = staticValue;
"#;
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn unqualified_static_property_shadowed_by_local_variable() {
let program = r#"
__register_test_classes();
import Point.*;
staticValue = 9;
v = staticValue; % picks local, not class static 42
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 9.0).abs()<1e-9)));
}
#[test]
fn import_nested_package_class_static_method_resolution() {
let program = r#"
__register_test_classes();
import pkg.PointNS.*; % nested path Simulation (pkg.PointNS has no methods; ensures no accidental resolution)
function z = origin()
z = 5;
end
v = origin(); % local function shadows any static method named origin
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs()<1e-9)));
}
#[test]
fn class_property_attribute_conflicts_error() {
let program = r#"
classdef Bad
properties(Constant, Dependent)
x
end
end
"#;
let ast = runmat_parser::parse(program).unwrap();
let res = lower(&ast);
assert!(res.is_err());
}
#[test]
fn class_method_attribute_conflicts_error() {
let program = r#"
classdef Bad
methods(Abstract, Sealed)
function y = f(obj)
y = 1;
end
end
end
"#;
let ast = runmat_parser::parse(program).unwrap();
let res = lower(&ast);
assert!(res.is_err());
}
#[test]
#[ignore]
fn metaclass_context_with_imports() {
let program = "import pkg.*; ?pkg.Class; x=1;";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let ok_or_exc = vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-1.0).abs()<1e-9))
|| vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::MException(_)));
assert!(ok_or_exc);
}
#[test]
fn metaclass_postfix_member_and_method() {
let program = "__register_test_classes(); v = ?Point.staticValue; o = ?Point.origin();";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 42.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn import_ambiguity_wildcard_conflict_errors() {
let program = "import PkgA.*; import PkgB.*; x = 1;";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(!vars.is_empty());
}
#[test]
#[ignore]
fn classdef_with_attributes_enforced() {
let src = "classdef A\n properties(GetAccess=private, SetAccess=public)\n p\n end\nend\n a = new_object('A'); a = setfield(a,'p',5); try; v = getfield(a,'p'); catch e; ok=1; end";
let ast = runmat_parser::parse(src).unwrap();
let hir = lower(&ast).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 1.0).abs() < 1e-9)));
}
#[test]
fn builtin_call_with_expanded_middle_argument() {
let program = "C = deal(10, 20); r = max(5, C{1});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs() < 1e-9)));
}
#[test]
fn builtin_call_with_two_expanded_args() {
let program = "C = deal(3, 4); D = deal(5, 6); r = max(C{1}, D{1});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs() < 1e-9)));
}
#[test]
fn user_function_with_two_expanded_args() {
let program = "function y = sum2(a,b); y = a + b; end; C = deal(7,8); D = deal(11,12); r = sum2(C{2}, D{1});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 19.0).abs() < 1e-9)));
}
#[test]
fn expansion_on_non_cell_errors() {
let program = "r = max(5, 10{1});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
assert!(execute(&hir).is_err());
}
#[cfg(any(feature = "test-classes", test))]
#[test]
fn object_cell_expansion_via_subsref() {
let program = "__register_test_classes(); o = new_object('OverIdx'); o = call_method(o,'subsasgn','{}', {1}, 42); r = max(o{1}, 5);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 42.0).abs() < 1e-9)));
}
#[test]
fn expand_all_elements_in_args() {
let program = "C = deal(1,2); a = max(C{:});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(_))));
}
#[test]
fn builtin_vector_index_expansion() {
let program = "C = deal(9, 2); r = max(C{[1 2]});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 9.0).abs() < 1e-9)));
}
#[test]
fn user_function_vector_index_expansion() {
let program = "function y = sum2(a,b); y = a + b; end; C = deal(3,4); r = sum2(C{[1 2]});";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 7.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn end_minus_one_1d_slice_collect() {
let program = "A = [1 2 3]; B = A(1:1:end-1); s = sum(B);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 3.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn end_minus_one_1d_slice_assign_broadcast() {
let program = "A = [1 2 3 4]; A(2:1:end-1) = 9; r = sum(A);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 23.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn multidim_range_end_assign() {
let program = "A = [1 2 3; 4 5 6]; A(:,2:2:end-1) = 9; s = sum(A);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 32.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn multidim_range_end_assign_non_scalar_rhs_broadcast() {
let program = "A = [1 2 3; 4 5 6]; B = [7;8]; A(:,2:2:end-1) = B; s = sum(A);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 29.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn mixed_range_end_assign_vector_broadcast() {
let program =
"A = [1 2 3 4; 5 6 7 8; 9 10 11 12]; B = [100;200]; A(2:end, 1:2:end-1) = B; s = sum(A);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 646.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn mixed_range_end_assign_matrix_rhs_exact_shape() {
let program =
"A = [1 2 3 4; 5 6 7 8; 9 10 11 12]; B = [1 3; 2 4]; A(2:end, 1:2:end-1) = B; s = sum(A);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 56.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn mixed_range_end_assign_shape_mismatch_error() {
let program = "A = [1 2 3 4; 5 6 7 8; 9 10 11 12]; B = [1;2;3]; A(2:end, 1:2:end-1) = B;";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let res = execute(&hir);
assert!(res.is_err());
}
#[test]
#[ignore]
fn broadcasting_roundtrip_property_like() {
let program = "A = zeros(3,4); v = [7;8;9]; A(:, 1:2:end-1) = v; x = A(:,1); y = A(:,3);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let mut count = 0;
for v in vars {
if let runmat_builtins::Value::Tensor(t) = v {
if t.shape == vec![3, 1]
&& (t.data[0] - 7.0).abs() < 1e-9
&& (t.data[1] - 8.0).abs() < 1e-9
&& (t.data[2] - 9.0).abs() < 1e-9
{
count += 1;
}
}
}
assert!(count >= 1);
}
#[test]
fn logical_mask_write_scalar_and_vector() {
let program = "A = [1 2 3 4 5 6]; m = [true false true false true false]; A(m) = 9; s1 = sum(A);\nB = [1 2 3 4 5 6]; idx = [1 3 5]; B(idx) = [7 8 9]; s2 = sum(B);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-39.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-36.0).abs()<1e-9)));
}
#[test]
fn gather_scatter_roundtrip_nd() {
let program = "A = [1 2 3; 4 5 6; 7 8 9]; S = A(2:3, 1:2); A(2:3, 1:2) = S; t = sum(A(:));";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-45.0).abs()<1e-9)));
}
#[test]
fn shape_broadcasting_laws() {
let program = "A = zeros(3,4); v = [1;2;3]; A(:, 2:2:4) = v; x = sum(A(:));\nC = zeros(2,3,2); w = [5;6]; C(:,2,:) = w; y = sum(C(:));";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-12.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-22.0).abs()<1e-9)));
}
#[test]
fn column_major_rhs_mapping() {
let program = "A = zeros(3,3); R = [10 13 16; 11 14 17; 12 15 18]; A(:, [1 3]) = R(:, [1 3]); s = sum(A(:));";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-84.0).abs()<1e-9)));
}
#[test]
fn builtin_call_with_function_return_propagation() {
let program = "function [a,b] = g(); a=9; b=4; end; r = max(g());";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 9.0).abs() < 1e-9)));
}
#[test]
fn function_call_base_expand_all() {
let program = r#"
function y = sum2(a,b); y = a + b; end; r = sum2(deal(5,6){:});
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 11.0).abs() < 1e-9)));
}
#[test]
fn function_return_propagation_in_args() {
let program = "function [a,b] = g(); a=2; b=3; end; function y = f(x1,x2); y = x1 + x2; end; [u,v] = g(); r = f(u,v);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 5.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn varargin_pack_and_forward() {
let program = r#"
function y = f(varargin)
y = sum(cat(2, varargin{:}));
end
function out = g(varargin)
out = feval(@f, varargin{:});
end
r1 = f(1,2,3);
r2 = g(4,5,6,7);
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-6.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-22.0).abs()<1e-9)));
}
#[test]
#[ignore]
fn varargout_expand_into_outer_call() {
let program = r#"
function varargout = h(x)
varargout = {x+1, x+2, x+3};
end
r = max(h(10));
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-12.0).abs()<1e-9)));
}
#[test]
#[ignore]
fn user_function_consumes_varargout_exact_needed() {
let program = r#"
function y = f(a,b,c)
y = a + 10*b + 100*c;
end
function varargout = g()
varargout = {1,2,3};
end
r = f(g());
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-321.0).abs()<1e-9)));
}
#[test]
fn operator_overloading_plus_times_lt_eq() {
let setup = "__register_test_classes();";
let program = format!(
"{setup} o = new_object('OverIdx'); o = call_method(o,'subsasgn','.', 'k', 5); r1 = o + 3;"
);
let hir = lower(&runmat_parser::parse(&program).unwrap()).unwrap();
let _ = execute(&hir).unwrap();
}
#[test]
fn operator_overloading_elementwise_vs_mtimes_and_mixed() {
let setup = "__register_test_classes();";
let program =
format!("{setup} o = new_object('OverIdx'); a = o .* 2; b = o * 2; c = 2 .* o; d = 2 * o;");
let hir = lower(&runmat_parser::parse(&program).unwrap()).unwrap();
let _ = execute(&hir).unwrap();
}
#[test]
fn operator_overloading_relational_lt_eq() {
let setup = "__register_test_classes();";
let program = format!(
"{setup} o = new_object('OverIdx'); t1 = (o < 10); t2 = (10 < o); t3 = (o == 0); t4 = (0 == o);"
);
let hir = lower(&runmat_parser::parse(&program).unwrap()).unwrap();
let _ = execute(&hir).unwrap();
}
#[test]
fn operator_overloading_full_grid_basic() {
let setup = "__register_test_classes(); o = new_object('OverIdx'); o = call_method(o,'subsasgn','.', 'k', 5);";
let statements = [
"a1 = (o ~= 10);",
"a2 = (10 ~= o);",
"b1 = (o >= 5);",
"b2 = (o <= 5);",
"% use numeric power to avoid object exponent when not provided",
"c1 = ([2 3] .^ 2);",
"c2 = (2 .^ [2 3]);",
"d1 = (o ./ 2);",
"d2 = (2 ./ o);",
"e1 = (o .\\ 2);",
"e2 = (2 .\\ o);",
"f1 = ([1 0 1] & [1 1 0]);",
"f2 = ([1 0 1] | [0 1 1]);",
];
let mut program = String::from(setup);
program.push('\n');
for (idx, stmt) in statements.iter().enumerate() {
program.push_str(stmt);
program.push('\n');
let ast = runmat_parser::parse(&program).unwrap();
let hir = lower(&ast).unwrap();
execute(&hir).unwrap_or_else(|err| {
let bc = runmat_ignition::compile(&hir, &HashMap::new()).unwrap();
panic!(
"operator overload script failed after stmt #{idx} `{stmt}`: {err}\nbytecode={:?}",
bc.instructions
);
});
}
}
#[test]
fn import_precedence_and_class_static_shadowing() {
let program = r#"
function y = f(); y = 123; end
__register_test_classes();
import Point.origin; % specific import
import Pkg.*; % wildcard import (non-existent)
origin = 7; % local shadows import
a = origin; % uses local variable, not static
b = f(); % user function resolves before imports
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-7.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-123.0).abs()<1e-9)));
}
#[test]
fn static_method_resolution_under_wildcard_import() {
let program = r#"
__register_test_classes();
import Point.*;
r = origin(); % static method
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let _ = execute(&hir).unwrap();
}
#[test]
fn operator_overloading_numeric_results_and_bitwise_arrays() {
let program = "__register_test_classes(); o = new_object('OverIdx'); o = call_method(o,'subsasgn','.', 'k', 5); r1 = o + 3; r2 = o .* 2; r3 = o * 4; a = (o < 10); b = (o == 5);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 8.0).abs() < 1e-9))); assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs() < 1e-9))); assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 20.0).abs() < 1e-9))); assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 1.0).abs() < 1e-9))); assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 1.0).abs() < 1e-9)));
let program2 = "A = [1 0 1; 0 1 0]; B = [1 1 0; 0 0 1]; C = A & B; D = A | B; Sc = sum(C(:)); Sd = sum(D(:));";
let hir2 = lower(&runmat_parser::parse(program2).unwrap()).unwrap();
let vars2 = execute(&hir2).unwrap();
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-1.0).abs()<1e-9)));
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-5.0).abs()<1e-9)));
}
#[test]
fn operator_overloading_left_division_variants() {
let setup = "__register_test_classes(); o = new_object('OverIdx'); o = call_method(o,'subsasgn','.', 'k', 5);";
let program = format!("{setup} a = (o .\\ 2); b = (2 .\\ o); c = (o ./ 2); d = (2 ./ o);",);
let hir = lower(&runmat_parser::parse(&program).unwrap()).unwrap();
let _ = execute(&hir).unwrap();
}
#[test]
fn bitwise_or_row_vectors() {
let program = "f = ([1 0 1] | [0 1 1]);";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let bc = runmat_ignition::compile(&hir, &HashMap::new()).unwrap();
dbg!(&bc.instructions);
let _ = interpret(&bc).unwrap();
}
#[test]
#[ignore]
fn function_return_propagation_partial_fill() {
let program = "function [a,b,c] = g(); a=1; b=2; c=3; end; function y = h(x1,x2); y = x1*10 + x2; end; r = h(g());";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 12.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn nested_function_return_propagation_mixed_with_fixed() {
let program = "function [a,b] = g(); a=4; b=5; end; function y = f(x,p,z); y = x + p + z; end; r = f(1, g());";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 10.0).abs() < 1e-9)));
}
#[test]
#[ignore]
fn nested_try_catch_rethrow_unified_exception_ids() {
let program = r#"
function y = inner()
error('RunMat:inner:bad', 'inner fail')
end
function z = middle()
try
z = inner();
catch e
rethrow(e);
end
end
try
r = middle();
catch e
id = getfield(e, 'identifier');
msg = getfield(e, 'message');
% Surface identifier and message as outputs
out_id = id; out_msg = msg; out_exc = e;
end
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let has_exc = vars.iter().any(|v| match v {
runmat_builtins::Value::MException(me) => {
me.identifier == "RunMat:inner:bad" && me.message == "inner fail"
}
_ => false,
});
let has_id = vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::String(s) if s=="RunMat:inner:bad"));
let has_msg = vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::String(s) if s=="inner fail"));
assert!(has_exc || has_id || has_msg);
}
#[test]
#[ignore]
fn globals_basic_and_shadowing() {
let prog = "global G; G = 5; function y = f(x); global G; y = G + x; end; a = f(3);";
let ast = runmat_parser::parse(prog).unwrap();
let hir = lower(&ast).unwrap();
let res = execute(&hir);
if let Ok(vars) = res {
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 8.0).abs() < 1e-12)));
}
}
#[test]
#[ignore]
fn persistents_init_once_across_calls() {
let prog = "function y = counter(); persistent C; if C==0; C = 0; end; C = C + 1; y = C; end; a = counter(); b = counter(); c = counter();";
let ast = runmat_parser::parse(prog).unwrap();
let hir = lower(&ast).unwrap();
let res = execute(&hir);
if let Ok(vars) = res {
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 3.0).abs() < 1e-12)));
}
}
#[test]
#[ignore]
fn import_precedence_and_shadowing() {
let program = "function y = f(); y = 123; end; __register_test_classes(); import Point.origin; import Pkg.*; origin = 7; a = origin; b = f();";
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-7.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-123.0).abs()<1e-9)));
}
#[test]
fn class_dependent_property_get_set() {
let program = r#"
classdef D
properties(Dependent)
p
end
end
d = new_object('D');
d = setfield(d, 'p', 7); % should route to set.p, writing p_backing
b = getfield(d, 'p_backing');
% get.p should return p_backing
v = getfield(d, 'p');
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-7.0).abs()<1e-9)));
}
#[test]
fn struct_isfield_multi_and_fieldnames() {
let program = r#"
s = struct(); s = setfield(s, 'a', 1); s = setfield(s, 'b', 2);
c = {'a','x';'b','a'}; r = isfield(s, c); f = fieldnames(s);
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let mut found_r_ok = false;
let mut found_f_ok = false;
for v in &vars {
match v {
runmat_builtins::Value::LogicalArray(arr) => {
if arr.shape == vec![2, 2] && arr.data == vec![1, 1, 0, 1] {
found_r_ok = true;
}
}
runmat_builtins::Value::Cell(ca) => {
if ca.cols == 1 && ca.rows >= 2 {
found_f_ok = true;
}
}
_ => {}
}
}
assert!(found_r_ok && found_f_ok);
}
#[test]
fn struct_isfield_string_array_placeholder() {
let program = r#"
s = struct(); s = setfield(s, 'a', 1);
% In real MATLAB, this would be ["a" "b"; "x" "a"]. We signal via comment as parser lacks string arrays.
% For now, ensure isfield(s, 'a') works and returns true.
r = isfield(s, 'a');
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
println!("vars: {vars:?}");
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Bool(true))));
}
#[test]
fn oop_negative_undefined_property_and_missing_subsref() {
let prog = r#"
classdef NoRef
properties
p
end
end
o = new_object('NoRef');
try
v = o.noSuchProp;
catch e
ok = 1;
end
"#;
let hir = lower(&runmat_parser::parse(prog).unwrap()).unwrap();
let res = execute(&hir);
if let Ok(vars) = res {
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-1.0).abs()<1e-9)));
}
let prog2 = r#"
classdef NoRef
properties
p
end
end
o = new_object('NoRef');
try
x = o(1);
catch e
ok=2;
end
"#;
let hir2 = lower(&runmat_parser::parse(prog2).unwrap()).unwrap();
let res2 = execute(&hir2);
if let Ok(vars2) = res2 {
assert!(vars2
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-2.0).abs()<1e-9)));
}
}
#[test]
fn containers_map_parenthesis_indexing() {
let program = r#"
fruit = containers.Map({'apple'}, {99});
energy = fruit('apple');
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n - 99.0).abs() < 1e-9)));
}
#[test]
fn containers_map_dot_properties() {
let program = r#"
m = containers.Map();
key_type = m.KeyType;
value_type = m.ValueType;
count = m.Count;
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars.iter().any(|v| match v {
runmat_builtins::Value::CharArray(ca) => ca.data.iter().collect::<String>() == "char",
_ => false,
}));
assert!(vars.iter().any(|v| match v {
runmat_builtins::Value::CharArray(ca) => ca.data.iter().collect::<String>() == "any",
_ => false,
}));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n).abs() < 1e-9)));
}
#[test]
fn string_array_literal_concat_index_and_compare() {
let program = r#"
A = ["a" "bb"; "ccc" "d"]; % 2x2 string array
x = A(2,1); % "ccc"
B = [A, ["e"; "e"]]; % hcat with 2x1 string-array -> 2x3
C = [A; ["z" "y"]]; % vcat -> 3x2
e1 = (A == "a"); % logical mask 2x2
e2 = (A ~= "bb"); % logical mask 2x2
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let mut saw_a = false;
let mut saw_x = false;
let mut saw_b = false;
let mut saw_c = false;
let mut saw_e1 = false;
let mut saw_e2 = false;
for v in vars {
match v {
runmat_builtins::Value::StringArray(sa) => {
println!("SA shape={:?} data={:?}", sa.shape, sa.data);
if sa.shape == vec![2, 2] {
saw_a = true;
}
if sa.shape == vec![2, 3] {
saw_b = true;
}
if sa.shape == vec![3, 2] {
saw_c = true;
}
if sa.data.iter().any(|s| s == "ccc") {
saw_x = true;
}
}
runmat_builtins::Value::String(s) => {
println!("S {s}");
if s == "ccc" {
saw_x = true;
}
}
runmat_builtins::Value::CharArray(ca) => {
let s: String = ca.data.iter().collect();
if s == "ccc" {
saw_x = true;
}
}
runmat_builtins::Value::Tensor(t) => {
println!("T shape={:?} data={:?}", t.shape, t.data);
if t.shape == vec![2, 2] {
saw_e1 = true;
saw_e2 = true;
}
}
_ => {}
}
}
assert!(saw_a && saw_x && saw_b && saw_c && saw_e1 && saw_e2);
}
#[test]
fn string_literal_and_num2str_horzcat_promotes_and_runs() {
let program = r#"
wn = 6;
label = ["wn = ", num2str(wn), " rad/s"];
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let mut saw_label = false;
for value in vars {
if let runmat_builtins::Value::StringArray(sa) = value {
if sa.shape == vec![1, 3] && sa.data == vec!["wn = ", "6", " rad/s"] {
saw_label = true;
}
}
}
assert!(saw_label, "expected label string-array concat to succeed");
}
#[test]
fn computed_integer_indices_work_for_column_slice_read_and_assign() {
let program = r#"
A = [1, 2, 3; 4, 5, 6];
j = length(1:2);
c = A(:, j);
B = zeros(2, 3);
B(:, j) = [7; 9];
d = B(:, j);
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
let mut saw_read = false;
let mut saw_assign = false;
for value in vars {
if let runmat_builtins::Value::Tensor(t) = value {
if t.shape == vec![2, 1] && t.data == vec![2.0, 5.0] {
saw_read = true;
}
if t.shape == vec![2, 1] && t.data == vec![7.0, 9.0] {
saw_assign = true;
}
}
}
assert!(saw_read, "expected computed integer slice read to succeed");
assert!(
saw_assign,
"expected computed integer slice assign to succeed"
);
}
#[test]
fn import_deep_multiseg_package_specific_vs_wildcard() {
let program = r#"
__register_test_classes();
import PkgF.foo; % specific import of PkgF.foo (builtin -> 10)
import pkg.PointNS.*; % wildcard unrelated (should not affect foo)
a = foo(); % resolves to PkgF.foo => 10
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-10.0).abs()<1e-9)));
}
#[test]
fn import_shadowing_matrix_locals_user_specific_wildcard_classstar() {
let program = r#"
__register_test_classes();
import Point.*; % Class.* provides origin (static)
import PkgF.foo; % specific provides foo()=10
import PkgG.*; % wildcard also provides foo()=20
function y = bar(); y = 33; end % user function (distinct name)
foo = @() 77; % local variable (handle)
a = feval(foo); % 77 (local)
b = bar(); % 33 (user function)
c = origin(); % static method via Class.* (no shadowing by foo)
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-77.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-33.0).abs()<1e-9)));
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Object(_))));
}
#[test]
fn import_wildcard_vs_classstar_ambiguity_for_static_method() {
let program = r#"
__register_test_classes();
import Point.*;
import Point.*; % duplicate
r = origin();
"#;
let ast = runmat_parser::parse(program).unwrap();
let hir = lower(&ast).unwrap();
let res = runmat_ignition::compile(&hir, &HashMap::new());
assert!(res.is_err());
}
#[test]
fn import_specific_vs_wildcard_same_name_prefers_specific_under_nesting() {
let program = r#"
import PkgF.foo; % specific import
import PkgG.*; % wildcard also has foo
y = foo(); % should call specific -> 10
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars
.iter()
.any(|v| matches!(v, runmat_builtins::Value::Num(n) if (*n-10.0).abs()<1e-9)));
}
#[test]
fn type_class_static_method_zeros() {
let program = r#"
A = double.zeros(2, 3);
s = size(A);
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars.iter().any(|v| {
if let runmat_builtins::Value::Tensor(t) = v {
t.shape == vec![2, 3] && t.data.iter().all(|&x| x == 0.0)
} else {
false
}
}));
}
#[test]
fn type_class_static_method_logical_zeros() {
let program = r#"
A = logical.zeros(2, 2);
"#;
let hir = lower(&runmat_parser::parse(program).unwrap()).unwrap();
let vars = execute(&hir).unwrap();
assert!(vars.iter().any(|v| {
if let runmat_builtins::Value::LogicalArray(l) = v {
l.shape == vec![2, 2] && l.data.iter().all(|&x| x == 0)
} else {
false
}
}));
}