1use std::collections::HashMap;
6
7use super::types::{
8 MatlabAnalysisCache, MatlabBackend, MatlabClassdef, MatlabConstantFoldingHelper,
9 MatlabDepGraph, MatlabDominatorTree, MatlabExpr, MatlabFunction, MatlabLiteral,
10 MatlabLivenessInfo, MatlabParam, MatlabPassConfig, MatlabPassPhase, MatlabPassRegistry,
11 MatlabPassStats, MatlabProperty, MatlabStmt, MatlabType, MatlabWorklist, PropAccess,
12};
13
14#[cfg(test)]
15mod tests {
16 use super::*;
17 #[test]
18 pub(super) fn test_matlab_literal_emission() {
19 let backend = MatlabBackend::new();
20 assert_eq!(backend.emit_literal(&MatlabLiteral::Double(3.0)), "3");
21 assert_eq!(backend.emit_literal(&MatlabLiteral::Double(3.14)), "3.14");
22 assert_eq!(backend.emit_literal(&MatlabLiteral::Integer(42)), "42");
23 assert_eq!(backend.emit_literal(&MatlabLiteral::Logical(true)), "true");
24 assert_eq!(
25 backend.emit_literal(&MatlabLiteral::Logical(false)),
26 "false"
27 );
28 assert_eq!(
29 backend.emit_literal(&MatlabLiteral::Char("hello".to_string())),
30 "'hello'"
31 );
32 assert_eq!(
33 backend.emit_literal(&MatlabLiteral::Str("world".to_string())),
34 "\"world\""
35 );
36 assert_eq!(backend.emit_literal(&MatlabLiteral::Empty), "[]");
37 assert_eq!(backend.emit_literal(&MatlabLiteral::NaN), "NaN");
38 assert_eq!(backend.emit_literal(&MatlabLiteral::Inf(false)), "Inf");
39 assert_eq!(backend.emit_literal(&MatlabLiteral::Inf(true)), "-Inf");
40 assert_eq!(backend.emit_literal(&MatlabLiteral::Pi), "pi");
41 }
42 #[test]
43 pub(super) fn test_matlab_matrix_literal() {
44 let backend = MatlabBackend::new();
45 let mat = MatlabExpr::MatrixLit(vec![
46 vec![
47 MatlabExpr::Lit(MatlabLiteral::Integer(1)),
48 MatlabExpr::Lit(MatlabLiteral::Integer(2)),
49 ],
50 vec![
51 MatlabExpr::Lit(MatlabLiteral::Integer(3)),
52 MatlabExpr::Lit(MatlabLiteral::Integer(4)),
53 ],
54 ]);
55 assert_eq!(backend.emit_expr_pure(&mat), "[1, 2; 3, 4]");
56 let vec_row = MatlabExpr::MatrixLit(vec![vec![
57 MatlabExpr::Lit(MatlabLiteral::Integer(1)),
58 MatlabExpr::Lit(MatlabLiteral::Integer(2)),
59 MatlabExpr::Lit(MatlabLiteral::Integer(3)),
60 ]]);
61 assert_eq!(backend.emit_expr_pure(&vec_row), "[1, 2, 3]");
62 }
63 #[test]
64 pub(super) fn test_matlab_colon_range() {
65 let backend = MatlabBackend::new();
66 let simple = MatlabExpr::ColonRange {
67 start: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(1))),
68 step: None,
69 end: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(10))),
70 };
71 assert_eq!(backend.emit_expr_pure(&simple), "1:10");
72 let stepped = MatlabExpr::ColonRange {
73 start: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(0))),
74 step: Some(Box::new(MatlabExpr::Lit(MatlabLiteral::Double(0.1)))),
75 end: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(1))),
76 };
77 assert_eq!(backend.emit_expr_pure(&stepped), "0:0.1:1");
78 }
79 #[test]
80 pub(super) fn test_matlab_binary_unary_ops() {
81 let backend = MatlabBackend::new();
82 let add = MatlabExpr::BinaryOp(
83 "+".to_string(),
84 Box::new(MatlabExpr::Var("a".to_string())),
85 Box::new(MatlabExpr::Var("b".to_string())),
86 );
87 assert_eq!(backend.emit_expr_pure(&add), "a + b");
88 let elem_mul = MatlabExpr::BinaryOp(
89 ".*".to_string(),
90 Box::new(MatlabExpr::Var("A".to_string())),
91 Box::new(MatlabExpr::Var("B".to_string())),
92 );
93 assert_eq!(backend.emit_expr_pure(&elem_mul), "A .* B");
94 let transpose = MatlabExpr::UnaryOp(
95 "'".to_string(),
96 Box::new(MatlabExpr::Var("M".to_string())),
97 true,
98 );
99 assert_eq!(backend.emit_expr_pure(&transpose), "M'");
100 let neg = MatlabExpr::UnaryOp(
101 "-".to_string(),
102 Box::new(MatlabExpr::Var("x".to_string())),
103 false,
104 );
105 assert_eq!(backend.emit_expr_pure(&neg), "-x");
106 }
107 #[test]
108 pub(super) fn test_matlab_anon_func_and_index() {
109 let backend = MatlabBackend::new();
110 let anon = MatlabExpr::AnonFunc(
111 vec!["x".to_string(), "y".to_string()],
112 Box::new(MatlabExpr::BinaryOp(
113 "+".to_string(),
114 Box::new(MatlabExpr::Var("x".to_string())),
115 Box::new(MatlabExpr::Var("y".to_string())),
116 )),
117 );
118 assert_eq!(backend.emit_expr_pure(&anon), "@(x, y) x + y");
119 let index = MatlabExpr::Index {
120 obj: Box::new(MatlabExpr::Var("A".to_string())),
121 indices: vec![
122 MatlabExpr::Lit(MatlabLiteral::Integer(2)),
123 MatlabExpr::Lit(MatlabLiteral::Integer(3)),
124 ],
125 cell_index: false,
126 };
127 assert_eq!(backend.emit_expr_pure(&index), "A(2, 3)");
128 let cell_index = MatlabExpr::Index {
129 obj: Box::new(MatlabExpr::Var("C".to_string())),
130 indices: vec![MatlabExpr::Lit(MatlabLiteral::Integer(1))],
131 cell_index: true,
132 };
133 assert_eq!(backend.emit_expr_pure(&cell_index), "C{1}");
134 }
135 #[test]
136 pub(super) fn test_matlab_for_while_loops() {
137 let mut backend = MatlabBackend::new();
138 let for_loop = MatlabStmt::ForLoop {
139 var: "i".to_string(),
140 range: MatlabExpr::ColonRange {
141 start: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(1))),
142 step: None,
143 end: Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(10))),
144 },
145 body: vec![MatlabStmt::Comment("loop body".to_string())],
146 };
147 backend.emit_stmt(&for_loop);
148 let out = backend.take_output();
149 assert!(out.contains("for i = 1:10"), "missing for: {}", out);
150 assert!(out.contains("end"), "missing end: {}", out);
151 let while_loop = MatlabStmt::WhileLoop {
152 cond: MatlabExpr::Var("running".to_string()),
153 body: vec![MatlabStmt::Break],
154 };
155 backend.emit_stmt(&while_loop);
156 let out2 = backend.take_output();
157 assert!(out2.contains("while running"), "missing while: {}", out2);
158 assert!(out2.contains("break"), "missing break: {}", out2);
159 }
160 #[test]
161 pub(super) fn test_matlab_function_emit() {
162 let mut backend = MatlabBackend::new();
163 let fun = MatlabFunction::new(
164 "add_vectors",
165 vec![MatlabParam::required("a"), MatlabParam::required("b")],
166 vec!["result".to_string()],
167 vec![MatlabStmt::Assign {
168 lhs: vec!["result".to_string()],
169 rhs: MatlabExpr::BinaryOp(
170 "+".to_string(),
171 Box::new(MatlabExpr::Var("a".to_string())),
172 Box::new(MatlabExpr::Var("b".to_string())),
173 ),
174 suppress: true,
175 }],
176 );
177 backend.emit_function(&fun);
178 let out = backend.take_output();
179 assert!(
180 out.contains("function result = add_vectors(a, b)"),
181 "missing header: {}",
182 out
183 );
184 assert!(out.contains("result = a + b;"), "missing body: {}", out);
185 assert!(out.contains("end"), "missing end: {}", out);
186 }
187 #[test]
188 pub(super) fn test_matlab_if_switch() {
189 let mut backend = MatlabBackend::new();
190 let if_stmt = MatlabStmt::IfElseIf {
191 cond: MatlabExpr::BinaryOp(
192 ">".to_string(),
193 Box::new(MatlabExpr::Var("x".to_string())),
194 Box::new(MatlabExpr::Lit(MatlabLiteral::Integer(0))),
195 ),
196 then_body: vec![MatlabStmt::Return],
197 elseif_branches: vec![],
198 else_body: Some(vec![MatlabStmt::Break]),
199 };
200 backend.emit_stmt(&if_stmt);
201 let out = backend.take_output();
202 assert!(out.contains("if x > 0"), "missing if: {}", out);
203 assert!(out.contains("return;"), "missing return: {}", out);
204 assert!(out.contains("else"), "missing else: {}", out);
205 assert!(out.contains("break;"), "missing break: {}", out);
206 let switch_stmt = MatlabStmt::SwitchCase {
207 expr: MatlabExpr::Var("mode".to_string()),
208 cases: vec![
209 (
210 MatlabExpr::Lit(MatlabLiteral::Char("fast".to_string())),
211 vec![MatlabStmt::Comment("fast path".to_string())],
212 ),
213 (
214 MatlabExpr::Lit(MatlabLiteral::Char("slow".to_string())),
215 vec![MatlabStmt::Comment("slow path".to_string())],
216 ),
217 ],
218 otherwise: Some(vec![MatlabStmt::Error(
219 MatlabExpr::Lit(MatlabLiteral::Char("Unknown mode".to_string())),
220 vec![],
221 )]),
222 };
223 backend.emit_stmt(&switch_stmt);
224 let out2 = backend.take_output();
225 assert!(out2.contains("switch mode"), "missing switch: {}", out2);
226 assert!(out2.contains("case 'fast'"), "missing case: {}", out2);
227 assert!(out2.contains("otherwise"), "missing otherwise: {}", out2);
228 }
229 #[test]
230 pub(super) fn test_matlab_classdef_emit() {
231 let mut backend = MatlabBackend::new();
232 let mut cls = MatlabClassdef::new("Vehicle").inherits("handle");
233 cls.properties.push(MatlabProperty {
234 name: "speed".to_string(),
235 ty: Some(MatlabType::Double),
236 default: Some(MatlabExpr::Lit(MatlabLiteral::Double(0.0))),
237 access: PropAccess::Public,
238 is_constant: false,
239 is_dependent: false,
240 });
241 cls.methods.push(MatlabFunction::new(
242 "accelerate",
243 vec![MatlabParam::required("obj"), MatlabParam::required("delta")],
244 vec![],
245 vec![MatlabStmt::AssignField {
246 obj: "obj".to_string(),
247 field: "speed".to_string(),
248 rhs: MatlabExpr::BinaryOp(
249 "+".to_string(),
250 Box::new(MatlabExpr::FieldAccess(
251 Box::new(MatlabExpr::Var("obj".to_string())),
252 "speed".to_string(),
253 )),
254 Box::new(MatlabExpr::Var("delta".to_string())),
255 ),
256 suppress: true,
257 }],
258 ));
259 backend.emit_classdef(&cls);
260 let out = backend.take_output();
261 assert!(
262 out.contains("classdef Vehicle < handle"),
263 "missing classdef: {}",
264 out
265 );
266 assert!(out.contains("properties"), "missing properties: {}", out);
267 assert!(out.contains("methods"), "missing methods: {}", out);
268 assert!(
269 out.contains("function accelerate(obj, delta)"),
270 "missing method: {}",
271 out
272 );
273 }
274}
275#[cfg(test)]
276mod Matlab_infra_tests {
277 use super::*;
278 #[test]
279 pub(super) fn test_pass_config() {
280 let config = MatlabPassConfig::new("test_pass", MatlabPassPhase::Transformation);
281 assert!(config.enabled);
282 assert!(config.phase.is_modifying());
283 assert_eq!(config.phase.name(), "transformation");
284 }
285 #[test]
286 pub(super) fn test_pass_stats() {
287 let mut stats = MatlabPassStats::new();
288 stats.record_run(10, 100, 3);
289 stats.record_run(20, 200, 5);
290 assert_eq!(stats.total_runs, 2);
291 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
292 assert!((stats.success_rate() - 1.0).abs() < 0.01);
293 let s = stats.format_summary();
294 assert!(s.contains("Runs: 2/2"));
295 }
296 #[test]
297 pub(super) fn test_pass_registry() {
298 let mut reg = MatlabPassRegistry::new();
299 reg.register(MatlabPassConfig::new("pass_a", MatlabPassPhase::Analysis));
300 reg.register(MatlabPassConfig::new("pass_b", MatlabPassPhase::Transformation).disabled());
301 assert_eq!(reg.total_passes(), 2);
302 assert_eq!(reg.enabled_count(), 1);
303 reg.update_stats("pass_a", 5, 50, 2);
304 let stats = reg.get_stats("pass_a").expect("stats should exist");
305 assert_eq!(stats.total_changes, 5);
306 }
307 #[test]
308 pub(super) fn test_analysis_cache() {
309 let mut cache = MatlabAnalysisCache::new(10);
310 cache.insert("key1".to_string(), vec![1, 2, 3]);
311 assert!(cache.get("key1").is_some());
312 assert!(cache.get("key2").is_none());
313 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
314 cache.invalidate("key1");
315 assert!(!cache.entries["key1"].valid);
316 assert_eq!(cache.size(), 1);
317 }
318 #[test]
319 pub(super) fn test_worklist() {
320 let mut wl = MatlabWorklist::new();
321 assert!(wl.push(1));
322 assert!(wl.push(2));
323 assert!(!wl.push(1));
324 assert_eq!(wl.len(), 2);
325 assert_eq!(wl.pop(), Some(1));
326 assert!(!wl.contains(1));
327 assert!(wl.contains(2));
328 }
329 #[test]
330 pub(super) fn test_dominator_tree() {
331 let mut dt = MatlabDominatorTree::new(5);
332 dt.set_idom(1, 0);
333 dt.set_idom(2, 0);
334 dt.set_idom(3, 1);
335 assert!(dt.dominates(0, 3));
336 assert!(dt.dominates(1, 3));
337 assert!(!dt.dominates(2, 3));
338 assert!(dt.dominates(3, 3));
339 }
340 #[test]
341 pub(super) fn test_liveness() {
342 let mut liveness = MatlabLivenessInfo::new(3);
343 liveness.add_def(0, 1);
344 liveness.add_use(1, 1);
345 assert!(liveness.defs[0].contains(&1));
346 assert!(liveness.uses[1].contains(&1));
347 }
348 #[test]
349 pub(super) fn test_constant_folding() {
350 assert_eq!(MatlabConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
351 assert_eq!(MatlabConstantFoldingHelper::fold_div_i64(10, 0), None);
352 assert_eq!(MatlabConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
353 assert_eq!(
354 MatlabConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
355 0b1000
356 );
357 assert_eq!(MatlabConstantFoldingHelper::fold_bitnot_i64(0), -1);
358 }
359 #[test]
360 pub(super) fn test_dep_graph() {
361 let mut g = MatlabDepGraph::new();
362 g.add_dep(1, 2);
363 g.add_dep(2, 3);
364 g.add_dep(1, 3);
365 assert_eq!(g.dependencies_of(2), vec![1]);
366 let topo = g.topological_sort();
367 assert_eq!(topo.len(), 3);
368 assert!(!g.has_cycle());
369 let pos: std::collections::HashMap<u32, usize> =
370 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
371 assert!(pos[&1] < pos[&2]);
372 assert!(pos[&1] < pos[&3]);
373 assert!(pos[&2] < pos[&3]);
374 }
375}