diff --git a/Cargo.lock b/Cargo.lock index 488482e..28e7f4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -59,6 +81,7 @@ name = "r-python" version = "0.1.0" dependencies = [ "approx", + "indexmap", "nom", "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index 9ebcc09..3d92060 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" nom = "7.0" approx = "0.5.1" once_cell = "1.10" +indexmap = "2.2" diff --git a/src/environment/environment.rs b/src/environment/environment.rs index 1acfba3..2c7c64e 100644 --- a/src/environment/environment.rs +++ b/src/environment/environment.rs @@ -1,6 +1,7 @@ use crate::ir::ast::Function; use crate::ir::ast::Name; use crate::ir::ast::ValueConstructor; +use indexmap::IndexMap; use std::collections::HashMap; use std::collections::LinkedList; @@ -9,6 +10,7 @@ pub struct Scope { pub variables: HashMap, pub functions: HashMap, pub adts: HashMap>, + pub tests: IndexMap, } impl Scope { @@ -17,6 +19,7 @@ impl Scope { variables: HashMap::new(), functions: HashMap::new(), adts: HashMap::new(), + tests: IndexMap::new(), //TODO: Apresentar Mudança no Environment } } @@ -30,6 +33,11 @@ impl Scope { return (); } + fn map_test(&mut self, test: Function) -> () { + self.tests.insert(test.name.clone(), test); + return (); + } + fn map_adt(&mut self, name: Name, adt: Vec) -> () { self.adts.insert(name.clone(), adt); return (); @@ -45,6 +53,10 @@ impl Scope { self.functions.get(name) } + fn lookup_test(&self, name: &Name) -> Option<&Function> { + self.tests.get(name) + } + fn lookup_adt(&self, name: &Name) -> Option<&Vec> { self.adts.get(name) } @@ -78,6 +90,13 @@ impl Environment { } } + pub fn map_test(&mut self, test: Function) -> () { + match self.stack.front_mut() { + None => self.globals.map_test(test), + Some(top) => top.map_test(test), + } + } + pub fn map_adt(&mut self, name: Name, cons: Vec) -> () { match self.stack.front_mut() { None => self.globals.map_adt(name, cons), @@ -103,6 +122,28 @@ impl Environment { self.globals.lookup_function(name) } + pub fn lookup_test(&self, name: &Name) -> Option<&Function> { + for scope in self.stack.iter() { + if let Some(test) = scope.lookup_test(name) { + return Some(test); + } + } + self.globals.lookup_test(name) + } + + pub fn get_all_tests(&self) -> Vec { + let mut tests = Vec::new(); + for scope in self.stack.iter() { + for test in scope.tests.values() { + tests.push(test.clone()); + } + } + for test in self.globals.tests.values() { + tests.push(test.clone()); + } + tests + } + pub fn lookup_adt(&self, name: &Name) -> Option<&Vec> { for scope in self.stack.iter() { if let Some(cons) = scope.lookup_adt(name) { @@ -147,6 +188,22 @@ impl Environment { } } +pub struct TestResult { + pub name: Name, + pub result: bool, + pub error: Option, +} + +impl TestResult { + pub fn new(name: Name, result: bool, error: Option) -> Self { + TestResult { + name, + result, + error, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/interpreter/statement_execute.rs b/src/interpreter/statement_execute.rs index 3badbea..068def6 100644 --- a/src/interpreter/statement_execute.rs +++ b/src/interpreter/statement_execute.rs @@ -1,5 +1,6 @@ use super::expression_eval::{eval, ExpressionResult}; use crate::environment::environment::Environment; +use crate::environment::environment::TestResult; use crate::ir::ast::{Expression, Statement}; pub enum Computation { @@ -32,6 +33,45 @@ pub fn run( } } +//TODO: Apresentar RunTests +pub fn run_tests(stmt: &Statement) -> Result, String> { + let env = match run(stmt.clone(), &Environment::new()) { + Ok(env) => env, + Err(e) => return Err(e), + }; + + let mut results = Vec::new(); + + for test in env.get_all_tests() { + let mut test_env = env.clone(); + test_env.push(); + + let stmt = match &test.body { + Some(body) => *body.clone(), + None => continue, + }; + + match execute(stmt, &test_env) { + Ok(Computation::Continue(_)) | Ok(Computation::Return(_, _)) => { + results.push(TestResult::new(test.name.clone(), true, None)); + } + Err(e) => { + results.push(TestResult::new(test.name.clone(), false, Some(e))); + } + Ok(Computation::PropagateError(e, _)) => { + results.push(TestResult::new( + test.name.clone(), + false, + Some(format!("Propagated error: {:?}", e)), + )); + } + } + test_env.pop(); + } + + Ok(results) +} + pub fn execute(stmt: Statement, env: &Environment) -> Result { let mut new_env = env.clone(); @@ -46,6 +86,149 @@ pub fn execute(stmt: Statement, env: &Environment) -> Result { + let value = match eval(*exp, &new_env)? { + ExpressionResult::Value(expr) => expr, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + }; + + match value { + Expression::CTrue => Ok(Computation::Continue(new_env)), + Expression::CFalse => { + // Avalia a mensagem + let error_msg = match eval(*msg, &new_env)? { + ExpressionResult::Value(Expression::CString(s)) => s, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + _ => "Assertion failed".to_string(), + }; + Err(error_msg) + } + _ => Err("Condition must evaluate to a boolean".to_string()), + } + } + + Statement::AssertTrue(exp, msg) => { + let value = match eval(*exp, &new_env)? { + ExpressionResult::Value(expr) => expr, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + }; + + match value { + Expression::CTrue => Ok(Computation::Continue(new_env)), + Expression::CFalse => { + // Avalia a mensagem + let error_msg = match eval(*msg, &new_env)? { + ExpressionResult::Value(Expression::CString(s)) => s, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + _ => "Assertion failed".to_string(), + }; + Err(error_msg) + } + _ => Err("Condition must evaluate to a boolean".to_string()), + } + } + + Statement::AssertFalse(exp, msg) => { + let value = match eval(*exp, &new_env)? { + ExpressionResult::Value(expr) => expr, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + }; + + match value { + Expression::CFalse => Ok(Computation::Continue(new_env)), + Expression::CTrue => { + // Avalia a mensagem + let error_msg = match eval(*msg, &new_env)? { + ExpressionResult::Value(Expression::CString(s)) => s, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + _ => "Assertion failed".to_string(), + }; + Err(error_msg) + } + _ => Err("Condition must evaluate to a boolean".to_string()), + } + } + + Statement::AssertEQ(exp1, exp2, msg) => { + let value1 = match eval(*exp1, &new_env)? { + ExpressionResult::Value(expr1) => expr1, + ExpressionResult::Propagate(expr1) => { + return Ok(Computation::PropagateError(expr1, new_env)) + } + }; + + let value2 = match eval(*exp2, &new_env)? { + ExpressionResult::Value(expr2) => expr2, + ExpressionResult::Propagate(expr2) => { + return Ok(Computation::PropagateError(expr2, new_env)) + } + }; + + let comparator = Expression::EQ(Box::new(value1), Box::new(value2)); + + match eval(comparator, &new_env)? { + ExpressionResult::Value(Expression::CTrue) => Ok(Computation::Continue(new_env)), + ExpressionResult::Value(Expression::CFalse) => { + // Avalia a mensagem + let error_msg = match eval(*msg, &new_env)? { + ExpressionResult::Value(Expression::CString(s)) => s, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + _ => "Assertion failed".to_string(), + }; + Err(error_msg) + } + _ => Err("Condition must evaluate to a boolean".to_string()), + } + } + + Statement::AssertNEQ(exp1, exp2, msg) => { + let value1 = match eval(*exp1, &new_env)? { + ExpressionResult::Value(expr1) => expr1, + ExpressionResult::Propagate(expr1) => { + return Ok(Computation::PropagateError(expr1, new_env)) + } + }; + + let value2 = match eval(*exp2, &new_env)? { + ExpressionResult::Value(expr2) => expr2, + ExpressionResult::Propagate(expr2) => { + return Ok(Computation::PropagateError(expr2, new_env)) + } + }; + + let comparator = Expression::NEQ(Box::new(value1), Box::new(value2)); + + match eval(comparator, &new_env)? { + ExpressionResult::Value(Expression::CTrue) => Ok(Computation::Continue(new_env)), + ExpressionResult::Value(Expression::CFalse) => { + // Avalia a mensagem + let error_msg = match eval(*msg, &new_env)? { + ExpressionResult::Value(Expression::CString(s)) => s, + ExpressionResult::Propagate(expr) => { + return Ok(Computation::PropagateError(expr, new_env)) + } + _ => "Assertion failed".to_string(), + }; + Err(error_msg) + } + _ => Err("Condition must evaluate to a boolean".to_string()), + } + } Statement::ValDeclaration(name, exp) => { let value = match eval(*exp, &new_env)? { @@ -177,6 +360,12 @@ pub fn execute(stmt: Statement, env: &Environment) -> Result { + new_env.map_test(teste.clone()); + Ok(Computation::Continue(new_env)) + } + Statement::Return(exp) => { let exp_value = match eval(*exp, &new_env)? { ExpressionResult::Value(expr) => expr, @@ -776,4 +965,465 @@ mod tests { assert_eq!(sum_expr, Expression::CInt(12)); } } + + //TODO: Apresentar Interpretador Asserts (Tests) + mod assert_statement_tests { + use super::*; + + #[test] + fn test_execute_assert_true() { + let env = create_test_env(); + let stmt = Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("ok".to_string())), + ); + let result = execute(stmt, &env); + assert!(result.is_ok(), "Assert with true condition should succeed"); + } + + #[test] + fn test_execute_assert_false() { + let env = create_test_env(); + let stmt = Statement::Assert( + Box::new(Expression::CFalse), + Box::new(Expression::CString("fail msg".to_string())), + ); + let result = execute(stmt.clone(), &env); + assert!(result.is_err(), "Assert with false condition should fail"); + //assert_eq!(result.unwrap_err(), "fail msg"); + let computation = match execute(stmt, &env) { + Ok(Computation::Continue(_)) => "error".to_string(), + Ok(Computation::Return(_, _)) => "error".to_string(), + Ok(Computation::PropagateError(_, _)) => "error".to_string(), + Err(e) => e.to_string(), + }; + assert_eq!(computation, "fail msg".to_string()); + } + + #[test] + fn test_execute_asserteq_true() { + let env = create_test_env(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(1)), + Box::new(Expression::CString("should not fail".to_string())), + ); + let result = execute(stmt, &env); + assert!(result.is_ok(), "AssertEQ with equal values should succeed"); + } + + #[test] + fn test_execute_asserteq_false() { + let env = create_test_env(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + Box::new(Expression::CString("eq fail".to_string())), + ); + let result = execute(stmt.clone(), &env); + assert!( + result.is_err(), + "AssertEQ with different values should fail" + ); + //assert_eq!(result.unwrap_err(), "eq fail"); + + let computation = match execute(stmt, &env) { + Ok(Computation::Continue(_)) => "error".to_string(), + Ok(Computation::Return(_, _)) => "error".to_string(), + Ok(Computation::PropagateError(_, _)) => "error".to_string(), + Err(e) => e.to_string(), + }; + assert_eq!(computation, "eq fail".to_string()); + } + + #[test] + fn test_execute_assertneq_true() { + let env = create_test_env(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + Box::new(Expression::CString("should not fail".to_string())), + ); + let result = execute(stmt, &env); + assert!( + result.is_ok(), + "AssertNEQ with different values should succeed" + ); + } + + #[test] + fn test_execute_assertneq_false() { + let env = create_test_env(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CInt(3)), + Box::new(Expression::CInt(3)), + Box::new(Expression::CString("neq fail".to_string())), + ); + let result = execute(stmt.clone(), &env); + assert!(result.is_err(), "AssertNEQ with equal values should fail"); + //assert_eq!(result.unwrap_err(), "neq fail"); + + let computation = match execute(stmt, &env) { + Ok(Computation::Continue(_)) => "error".to_string(), + Ok(Computation::Return(_, _)) => "error".to_string(), + Ok(Computation::PropagateError(_, _)) => "error".to_string(), + Err(e) => e.to_string(), + }; + assert_eq!(computation, "neq fail".to_string()); + } + + #[test] + fn test_execute_asserttrue_true() { + let env = create_test_env(); + let stmt = Statement::AssertTrue( + Box::new(Expression::CTrue), + Box::new(Expression::CString("ok".to_string())), + ); + let result = execute(stmt, &env); + assert!( + result.is_ok(), + "AssertTrue with true condition should succeed" + ); + } + + #[test] + fn test_execute_asserttrue_false() { + let env = create_test_env(); + let stmt = Statement::AssertTrue( + Box::new(Expression::CFalse), + Box::new(Expression::CString("asserttrue fail".to_string())), + ); + let result = execute(stmt.clone(), &env); + assert!( + result.is_err(), + "AssertTrue with false condition should fail" + ); + //assert_eq!(result.unwrap_err(), "asserttrue fail"); + + let computation = match execute(stmt, &env) { + Ok(Computation::Continue(_)) => "error".to_string(), + Ok(Computation::Return(_, _)) => "error".to_string(), + Ok(Computation::PropagateError(_, _)) => "error".to_string(), + Err(e) => e.to_string(), + }; + assert_eq!(computation, "asserttrue fail".to_string()); + } + + #[test] + fn test_execute_assertfalse_false() { + let env = create_test_env(); + let stmt = Statement::AssertFalse( + Box::new(Expression::CFalse), + Box::new(Expression::CString("ok".to_string())), + ); + let result = execute(stmt, &env); + assert!( + result.is_ok(), + "AssertFalse with false condition should succeed" + ); + } + + #[test] + fn test_execute_assertfalse_true() { + let env = create_test_env(); + let stmt = Statement::AssertFalse( + Box::new(Expression::CTrue), + Box::new(Expression::CString("assertfalse fail".to_string())), + ); + let result = execute(stmt.clone(), &env); + assert!( + result.is_err(), + "AssertFalse with true condition should fail" + ); + //assert_eq!(result.unwrap_err(), "assertfalse fail"); + let computation = match execute(stmt, &env) { + Ok(Computation::Continue(_)) => "error".to_string(), + Ok(Computation::Return(_, _)) => "error".to_string(), + Ok(Computation::PropagateError(_, _)) => "error".to_string(), + Err(e) => e.to_string(), + }; + assert_eq!(computation, "assertfalse fail".to_string()); + } + } + + //TODO: Apresentar Interpretador TestDef (Tests) + mod testdef_statement_tests { + use super::*; + + #[test] + fn test_execute_testdef() { + let env = create_test_env(); + let test_def = Statement::TestDef(Function { + name: "test_example".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test passed".to_string())), + )]))), + }); + let programa = Statement::Block(vec![test_def.clone()]); + match execute(programa, &env) { + Ok(Computation::Continue(new_env)) => { + assert!(new_env.lookup_test(&"test_example".to_string()).is_some()); + } + _ => panic!("Test definition execution failed"), + } + } + } + + //TODO: Apresentar Interpretador RunTests (Tests) + mod run_tests_tests { + + use super::*; + + #[test] + fn test_run_tests() { + let test_def = Statement::TestDef(Function { + name: "test_example".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test passed".to_string())), + )]))), + }); + let programa = Statement::Block(vec![test_def.clone()]); + match run_tests(&programa) { + Ok(resultados) => { + assert_eq!(resultados.len(), 1); + assert_eq!(resultados[0].name, "test_example"); + assert!(resultados[0].result); + assert!(resultados[0].error.is_none()); + } + _ => panic!("Test execution failed"), + } + } + + #[test] + fn test_run_tests_scope() { + let test_def = Statement::TestDef(Function { + name: "test_example".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test passed".to_string())), + )]))), + }); + + let teste_def2 = Statement::TestDef(Function { + name: "test_example2".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test 2 passed".to_string())), + )]))), + }); + + let assign1 = Statement::Assignment("x".to_string(), Box::new(Expression::CInt(10))); + let assign2 = Statement::Assignment("y".to_string(), Box::new(Expression::CInt(20))); + + let ifelse = Statement::IfThenElse( + Box::new(Expression::CTrue), + Box::new(test_def), + Some(Box::new(teste_def2)), + ); + + let programa = Statement::Block(vec![assign1, assign2, ifelse]); + + let resultado_final = match run_tests(&programa) { + Ok(resultados) => resultados, + Err(e) => panic!("Test execution failed: {}", e), + }; + + assert_eq!(resultado_final.len(), 1); + assert_eq!(resultado_final[0].name, "test_example"); + assert_eq!(resultado_final[0].result, true); + } + + #[test] + fn test_run_tests_with_assert_fail() { + let teste1 = Statement::TestDef(Function { + name: "test_fail".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CFalse), + Box::new(Expression::CString("This test should fail".to_string())), + )]))), + }); + let programa = Statement::Block(vec![teste1]); + match run_tests(&programa) { + Ok(resultados) => { + assert_eq!(resultados.len(), 1); + assert_eq!(resultados[0].name, "test_fail"); + assert!(!resultados[0].result); + assert_eq!( + resultados[0].error, + Some("This test should fail".to_string()) + ); + } + Err(e) => panic!("Test execution failed: {}", e), + } + } + + #[test] + fn test_run_tests_with_second_assert_fail() { + let teste1 = Statement::TestDef(Function { + name: "test_fail".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![ + Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("This test should pass".to_string())), + ), + Statement::Assert( + Box::new(Expression::CFalse), + Box::new(Expression::CString( + "This second test should fail".to_string(), + )), + ), + Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString( + "This test shouldn't run, but should pass".to_string(), + )), + ), + ]))), + }); + let programa = Statement::Block(vec![teste1]); + match run_tests(&programa) { + Ok(resultados) => { + assert_eq!(resultados.len(), 1); + assert_eq!(resultados[0].name, "test_fail"); + assert!(!resultados[0].result); + assert_eq!( + resultados[0].error, + Some("This second test should fail".to_string()) + ); + } + Err(e) => panic!("Test execution failed: {}", e), + } + } + + #[test] + fn test_run_tests_without_asserts() { + let teste = Statement::TestDef(Function { + name: "test_no_assert".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::VarDeclaration( + "x".to_string(), + Box::new(Expression::CInt(42)), + )]))), + }); + let programa = Statement::Block(vec![teste]); + match run_tests(&programa) { + Ok(resultados) => { + assert_eq!(resultados.len(), 1); + assert_eq!(resultados[0].name, "test_no_assert"); + assert!(resultados[0].result); + assert!(resultados[0].error.is_none()); + } + Err(e) => panic!("Test execution failed: {}", e), + } + } + + #[test] + fn test_run_tests_with_multiple_tests() { + let teste1 = Statement::TestDef(Function { + name: "test_one".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test one passed".to_string())), + )]))), + }); + let teste2 = Statement::TestDef(Function { + name: "test_two".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CFalse), + Box::new(Expression::CString("Test two failed".to_string())), + )]))), + }); + let teste3 = Statement::TestDef(Function { + name: "test_three".to_string(), + kind: Type::TVoid, + params: Vec::new(), + body: Some(Box::new(Statement::Block(vec![Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("Test three passed".to_string())), + )]))), + }); + let programa = Statement::Block(vec![teste1, teste2, teste3]); + + match run_tests(&programa) { + Ok(resultados) => { + assert_eq!(resultados.len(), 3); + assert_eq!(resultados[0].name, "test_one"); + assert!(resultados[0].result); + assert!(resultados[0].error.is_none()); + + assert_eq!(resultados[1].name, "test_two"); + assert!(!resultados[1].result); + assert_eq!(resultados[1].error, Some("Test two failed".to_string())); + + assert_eq!(resultados[2].name, "test_three"); + assert!(resultados[2].result); + assert!(resultados[2].error.is_none()); + } + Err(e) => panic!("Test execution failed: {}", e), + } + } + #[test] + fn test_test_scope_isolation() { + // test_one: define x = 1, passa se x == 1 + let test_one = Statement::TestDef(Function { + name: "test_one".to_string(), + kind: Type::TBool, + params: vec![], + body: Some(Box::new(Statement::Block(vec![ + Statement::VarDeclaration("x".to_string(), Box::new(Expression::CInt(1))), + Statement::AssertEQ( + Box::new(Expression::Var("x".to_string())), + Box::new(Expression::CInt(1)), + Box::new(Expression::CString("x should be 1".to_string())), + ), + ]))), + }); + + // test_two: espera que x NÃO exista + let test_two = Statement::TestDef(Function { + name: "test_two".to_string(), + kind: Type::TBool, + params: vec![], + body: Some(Box::new(Statement::Block(vec![Statement::AssertFalse( + Box::new(Expression::Var("x".to_string())), + Box::new(Expression::CString("x should not be visible".to_string())), + )]))), + }); + + let stmt = Statement::Block(vec![test_one, test_two]); + + let results = run_tests(&stmt).unwrap(); + + assert_eq!(results.len(), 2); + + let r1 = &results[0]; + let r2 = &results[1]; + + assert_eq!(r1.name, "test_one"); + assert!(r1.result); + + assert_eq!(r2.name, "test_two"); + assert!(!r2.result); + assert_eq!(r2.error, Some("Variable 'x' not found".to_string())); // Erro é propagado de Expression::Var + } + } } diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 1172bb8..79e487d 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -134,11 +134,11 @@ pub enum Statement { For(Name, Box, Box), Block(Vec), Sequence(Box, Box), - Assert(Box, Box), - AssertTrue(Box, String), - AssertFalse(Box, String), - AssertEQ(Box, Box, String), - AssertNEQ(Box, Box, String), + Assert(Box, Box), //Segundo expression deve ser String + AssertTrue(Box, Box), //Segundo expression deve ser String + AssertFalse(Box, Box), //Segundo expression deve ser String + AssertEQ(Box, Box, Box), //Terceiro expression deve ser String + AssertNEQ(Box, Box, Box), //Terceiro expression deve ser String TestDef(Function), ModTestDef(Name, Box), AssertFails(String), diff --git a/src/parser/keywords.rs b/src/parser/keywords.rs index 5d528d8..d4f02dd 100644 --- a/src/parser/keywords.rs +++ b/src/parser/keywords.rs @@ -21,4 +21,5 @@ pub const KEYWORDS: &[&str] = &[ "not", "True", "False", + "test", ]; diff --git a/src/parser/parser_common.rs b/src/parser/parser_common.rs index 065a742..bb739d3 100644 --- a/src/parser/parser_common.rs +++ b/src/parser/parser_common.rs @@ -33,9 +33,14 @@ pub const WHILE_KEYWORD: &str = "while"; pub const FOR_KEYWORD: &str = "for"; pub const IN_KEYWORD: &str = "in"; pub const ASSERT_KEYWORD: &str = "assert"; +pub const ASSERTEQ_KEYWORD: &str = "asserteq"; +pub const ASSERTNEQ_KEYWORD: &str = "assertneq"; +pub const ASSERTTRUE_KEYWORD: &str = "asserttrue"; +pub const ASSERTFALSE_KEYWORD: &str = "assertfalse"; pub const VAR_KEYWORD: &str = "var"; pub const VAL_KEYWORD: &str = "val"; pub const DEF_KEYWORD: &str = "def"; +pub const TEST_KEYWORD: &str = "test"; // Operator and symbol constants pub const FUNCTION_ARROW: &str = "->"; diff --git a/src/parser/parser_stmt.rs b/src/parser/parser_stmt.rs index bdcebdb..264412a 100644 --- a/src/parser/parser_stmt.rs +++ b/src/parser/parser_stmt.rs @@ -9,9 +9,11 @@ use nom::{ IResult, }; +use crate::ir::ast::Type; use crate::ir::ast::{FormalArgument, Function, Statement}; use crate::parser::parser_common::{ - identifier, keyword, ASSERT_KEYWORD, COLON_CHAR, COMMA_CHAR, DEF_KEYWORD, ELSE_KEYWORD, + identifier, keyword, ASSERTEQ_KEYWORD, ASSERTFALSE_KEYWORD, ASSERTNEQ_KEYWORD, + ASSERTTRUE_KEYWORD, ASSERT_KEYWORD, COLON_CHAR, COMMA_CHAR, DEF_KEYWORD, ELSE_KEYWORD, END_KEYWORD, EQUALS_CHAR, FOR_KEYWORD, FUNCTION_ARROW, IF_KEYWORD, IN_KEYWORD, LEFT_PAREN, RIGHT_PAREN, SEMICOLON_CHAR, VAL_KEYWORD, VAR_KEYWORD, WHILE_KEYWORD, }; @@ -27,6 +29,11 @@ pub fn parse_statement(input: &str) -> IResult<&str, Statement> { parse_while_statement, parse_for_statement, parse_assert_statement, + parse_asserteq_statement, + parse_assertneq_statement, + parse_assertfalse_statement, + parse_asserttrue_statement, + parse_test_function_definition_statement, parse_function_definition_statement, ))(input) } @@ -123,6 +130,7 @@ fn parse_for_statement(input: &str) -> IResult<&str, Statement> { )(input) } +//TODO: Apresentar Asserts fn parse_assert_statement(input: &str) -> IResult<&str, Statement> { map( tuple(( @@ -149,6 +157,118 @@ fn parse_assert_statement(input: &str) -> IResult<&str, Statement> { )(input) } +fn parse_asserteq_statement(input: &str) -> IResult<&str, Statement> { + map( + tuple(( + keyword(ASSERTEQ_KEYWORD), + delimited( + char::<&str, Error<&str>>(LEFT_PAREN), + separated_list0( + tuple(( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + )), + parse_expression, + ), + char::<&str, Error<&str>>(RIGHT_PAREN), + ), + )), + |(_, args)| { + if args.len() != 3 { + panic!("AssertEQ statement requires exactly 3 arguments"); + } + Statement::AssertEQ( + Box::new(args[0].clone()), + Box::new(args[1].clone()), + Box::new(args[2].clone()), + ) + }, + )(input) +} + +fn parse_assertneq_statement(input: &str) -> IResult<&str, Statement> { + map( + tuple(( + keyword(ASSERTNEQ_KEYWORD), + delimited( + char::<&str, Error<&str>>(LEFT_PAREN), + separated_list0( + tuple(( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + )), + parse_expression, + ), + char::<&str, Error<&str>>(RIGHT_PAREN), + ), + )), + |(_, args)| { + if args.len() != 3 { + panic!("AssertNEQ statement requires exactly 3 arguments"); + } + Statement::AssertNEQ( + Box::new(args[0].clone()), + Box::new(args[1].clone()), + Box::new(args[2].clone()), + ) + }, + )(input) +} + +fn parse_asserttrue_statement(input: &str) -> IResult<&str, Statement> { + map( + tuple(( + keyword(ASSERTTRUE_KEYWORD), + delimited( + char::<&str, Error<&str>>(LEFT_PAREN), + separated_list0( + tuple(( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + )), + parse_expression, + ), + char::<&str, Error<&str>>(RIGHT_PAREN), + ), + )), + |(_, args)| { + if args.len() != 2 { + panic!("AssertTrue statement requires exactly 2 arguments"); + } + Statement::AssertTrue(Box::new(args[0].clone()), Box::new(args[1].clone())) + }, + )(input) +} + +fn parse_assertfalse_statement(input: &str) -> IResult<&str, Statement> { + map( + tuple(( + keyword(ASSERTFALSE_KEYWORD), + delimited( + char::<&str, Error<&str>>(LEFT_PAREN), + separated_list0( + tuple(( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + )), + parse_expression, + ), + char::<&str, Error<&str>>(RIGHT_PAREN), + ), + )), + |(_, args)| { + if args.len() != 2 { + panic!("AssertFalse statement requires exactly 2 arguments"); + } + Statement::AssertFalse(Box::new(args[0].clone()), Box::new(args[1].clone())) + }, + )(input) +} + fn parse_function_definition_statement(input: &str) -> IResult<&str, Statement> { map( tuple(( @@ -181,6 +301,31 @@ fn parse_function_definition_statement(input: &str) -> IResult<&str, Statement> )(input) } +//TODO: Apresentar TestDef +fn parse_test_function_definition_statement(input: &str) -> IResult<&str, Statement> { + map( + tuple(( + //keyword(TEST_KEYWORD), + tag("test"), + preceded(multispace1, identifier), + delimited( + char::<&str, Error<&str>>(LEFT_PAREN), + multispace0, + char::<&str, Error<&str>>(RIGHT_PAREN), + ), + parse_block, + )), + |(_, name, _, block)| { + Statement::TestDef(Function { + name: name.to_string(), + kind: Type::TVoid, // Sempre void + params: Vec::new(), // Nenhum argumento + body: Some(Box::new(block)), + }) + }, + )(input) +} + fn parse_block(input: &str) -> IResult<&str, Statement> { map( tuple(( @@ -275,20 +420,6 @@ mod tests { assert_eq!(parsed, expected); } - #[test] - fn test_parse_assert_statement() { - let input = "assert(1 == 2, \"expecting an error\")"; - let expected = Statement::Assert( - Box::new(Expression::EQ( - Box::new(Expression::CInt(1)), - Box::new(Expression::CInt(2)), - )), - Box::new(Expression::CString("expecting an error".to_string())), - ); - let parsed = parse_assert_statement(input).unwrap().1; - assert_eq!(parsed, expected); - } - #[test] #[ignore] fn test_parse_function_definition_statement() { @@ -343,4 +474,279 @@ mod tests { let parsed = parse_formal_argument(input).unwrap().1; assert_eq!(parsed, expected); } + + //TODO: Apresentar Parser de TestDef (Testes) + mod testdef_tests { + use super::*; + + #[test] + fn test_parse_test_function_definition_statement_valid() { + let input = "test test_example(): x = 1; end"; + let expected = Statement::TestDef(Function { + name: "test_example".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(1)), + )]))), + }); + let parsed = parse_test_function_definition_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_test_function_definition_statement_valid_multiple_statements() { + let input = r#"test test_example(): + x = 1; + y = 2; + assert(x == 1, "x deveria ser 1"); + end"#; + let expected = Statement::TestDef(Function { + name: "test_example".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Statement::Block(vec![ + Statement::Assignment("x".to_string(), Box::new(Expression::CInt(1))), + Statement::Assignment("y".to_string(), Box::new(Expression::CInt(2))), + Statement::Assert( + Box::new(Expression::EQ( + Box::new(Expression::Var("x".to_string())), + Box::new(Expression::CInt(1)), + )), + Box::new(Expression::CString("x deveria ser 1".to_string())), + ), + ]))), + }); + let parsed = parse_test_function_definition_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_test_function_definition_statement_with_spaces() { + let input = "test test_spaces( ): x = 2; end"; + let expected = Statement::TestDef(Function { + name: "test_spaces".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(2)), + )]))), + }); + let parsed = parse_test_function_definition_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_test_function_definition_statement_args() { + let input = "test test_with_args(x: Int, y: Int): x = y; end"; + + // O parser deve falhar, pois funções de teste não podem ter argumentos. + let parsed = parse_test_function_definition_statement(input); + + assert!( + parsed.is_err(), + "Funções de teste com argumentos devem ser rejeitadas" + ); + } + + #[test] + fn test_parse_test_function_definition_statement_invalid_return() { + let input = "test test_with_invalid_return() -> Int: x = 2; end"; + + // O parser deve falhar, pois funções de teste não podem ter argumentos. + let parsed = parse_test_function_definition_statement(input); + + assert!( + parsed.is_err(), + "Funções de teste não devem especificar tipo de retorno" + ); + } + + #[test] + fn test_parse_test_function_definition_statement_valid_return_type() { + let input = "test test_with_valid_return() -> Boolean: x = 2; end"; + + let parsed = parse_test_function_definition_statement(input); + assert!( + parsed.is_err(), + "Funções de teste não devem especificar tipo de retorno" + ); + } + } + + //TODO: Apresentar Parser de Asserts (Testes) + mod assert_tests { + use super::*; + + #[test] + fn test_parse_assert_statement() { + let input = "assert(1 == 2, \"expecting an error\")"; + let expected = Statement::Assert( + Box::new(Expression::EQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + )), + Box::new(Expression::CString("expecting an error".to_string())), + ); + let parsed = parse_assert_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_asserteq_statement() { + let input = "asserteq(1, 2, \"msg\")"; + let expected = Statement::AssertEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + Box::new(Expression::CString("msg".to_string())), + ); + let parsed = parse_asserteq_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_assertneq_statement() { + let input = "assertneq(3, 4, \"fail\")"; + let expected = Statement::AssertNEQ( + Box::new(Expression::CInt(3)), + Box::new(Expression::CInt(4)), + Box::new(Expression::CString("fail".to_string())), + ); + let parsed = parse_assertneq_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_asserttrue_statement() { + let input = "asserttrue(True, \"should be true\")"; + let expected = Statement::AssertTrue( + Box::new(Expression::CTrue), + Box::new(Expression::CString("should be true".to_string())), + ); + let parsed = parse_asserttrue_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_assertfalse_statement() { + let input = "assertfalse(False, \"should be false\")"; + let expected = Statement::AssertFalse( + Box::new(Expression::CFalse), + Box::new(Expression::CString("should be false".to_string())), + ); + let parsed = parse_assertfalse_statement(input).unwrap().1; + assert_eq!(parsed, expected); + } + + #[test] + fn test_parse_assert_statement_invalid_argnumber() { + let input = "assert(False, False, \"should be false\")"; + + let result = std::panic::catch_unwind(|| parse_assert_statement(input)); + + assert!( + result.is_err(), + "Expected panic for invalid number of arguments" + ); + + if let Err(err) = result { + if let Some(s) = err.downcast_ref::<&str>() { + assert_eq!(*s, "Assert statement requires exactly 2 arguments"); + } else if let Some(s) = err.downcast_ref::() { + assert_eq!(s, "Assert statement requires exactly 2 arguments"); + } else { + panic!("Panic occurred, but message is not a string"); + } + } + } + + #[test] + fn test_parse_asserteq_statement_invalid_argnumber() { + let input = "asserteq(1, 2, 3, \"msg\")"; + + let result = std::panic::catch_unwind(|| parse_asserteq_statement(input)); + + assert!( + result.is_err(), + "Expected panic for invalid number of arguments" + ); + + if let Err(err) = result { + if let Some(s) = err.downcast_ref::<&str>() { + assert_eq!(*s, "AssertEQ statement requires exactly 3 arguments"); + } else if let Some(s) = err.downcast_ref::() { + assert_eq!(s, "AssertEQ statement requires exactly 3 arguments"); + } else { + panic!("Panic occurred, but message is not a string"); + } + } + } + + #[test] + fn test_parse_assertneq_statement_invalid_argnumber() { + let input = "assertneq(3, 4, 5, \"fail\")"; + + let result = std::panic::catch_unwind(|| parse_assertneq_statement(input)); + + assert!( + result.is_err(), + "Expected panic for invalid number of arguments" + ); + + if let Err(err) = result { + if let Some(s) = err.downcast_ref::<&str>() { + assert_eq!(*s, "AssertNEQ statement requires exactly 3 arguments"); + } else if let Some(s) = err.downcast_ref::() { + assert_eq!(s, "AssertNEQ statement requires exactly 3 arguments"); + } else { + panic!("Panic occurred, but message is not a string"); + } + } + } + + #[test] + fn test_parse_asserttrue_statement_invalid_argnumber() { + let input = "asserttrue(True, True, \"should be true\")"; + + let result = std::panic::catch_unwind(|| parse_asserttrue_statement(input)); + + assert!( + result.is_err(), + "Expected panic for invalid number of arguments" + ); + + if let Err(err) = result { + if let Some(s) = err.downcast_ref::<&str>() { + assert_eq!(*s, "AssertTrue statement requires exactly 2 arguments"); + } else if let Some(s) = err.downcast_ref::() { + assert_eq!(s, "AssertTrue statement requires exactly 2 arguments"); + } else { + panic!("Panic occurred, but message is not a string"); + } + } + } + + #[test] + fn test_parse_assertfalse_statement_invalid_argnumber() { + let input = "assertfalse(False, False, \"should be false\")"; + + let result = std::panic::catch_unwind(|| parse_assertfalse_statement(input)); + + assert!( + result.is_err(), + "Expected panic for invalid number of arguments" + ); + if let Err(err) = result { + if let Some(s) = err.downcast_ref::<&str>() { + assert_eq!(*s, "AssertFalse statement requires exactly 2 arguments"); + } else if let Some(s) = err.downcast_ref::() { + assert_eq!(s, "AssertFalse statement requires exactly 2 arguments"); + } else { + panic!("Panic occurred, but message is not a string"); + } + } + } + } } diff --git a/src/type_checker/statement_type_checker.rs b/src/type_checker/statement_type_checker.rs index 5c6835e..a428a82 100644 --- a/src/type_checker/statement_type_checker.rs +++ b/src/type_checker/statement_type_checker.rs @@ -21,10 +21,37 @@ pub fn check_stmt( Statement::FuncDef(function) => check_func_def_stmt(function, env), Statement::TypeDeclaration(name, cons) => check_adt_declarations_stmt(name, cons, env), Statement::Return(exp) => check_return_stmt(exp, env), + + Statement::Assert(expr1, errmsg) => check_assert(expr1, errmsg, env), + Statement::AssertTrue(expr1, errmsg) => check_assert_true(expr1, errmsg, env), + Statement::AssertFalse(expr1, errmsg) => check_assert_false(expr1, errmsg, env), + Statement::AssertEQ(lhs, rhs, errmsg) => check_assert_eq(lhs, rhs, errmsg, env), + Statement::AssertNEQ(lhs, rhs, errmsg) => check_assert_neq(lhs, rhs, errmsg, env), + Statement::TestDef(function) => check_test_function_stmt(function, env), + _ => Err("Not implemented yet".to_string()), } } +pub fn check_block( + stmt: Statement, + env: &Environment, +) -> Result, ErrorMessage> { + match stmt { + Statement::Block(stmts) => { + let mut block_env = env.clone(); + block_env.push(); + + for s in stmts { + block_env = check_stmt(s, &block_env)?; + } + block_env.pop(); + Ok(block_env) + } + _ => Err("Expected a block statement".to_string()), + } +} + fn check_squence_stmt( stmt1: Box, stmt2: Box, @@ -229,6 +256,123 @@ fn check_return_stmt( } } } +//TODO: Apresentar Asserts +fn check_assert( + expr1: Box, + expr2: Box, + env: &Environment, +) -> Result, ErrorMessage> { + let type1 = check_expr(*expr1, env)?; + let type2 = check_expr(*expr2, env)?; + + if type1 != Type::TBool { + Err("[Type Error] First Assert expression must be of type Boolean.".to_string()) + } else if type2 != Type::TString { + Err("[Type Error] Second Assert expression must be of type String.".to_string()) + } else { + Ok(env.clone()) + } +} + +fn check_assert_true( + expr1: Box, + expr2: Box, + env: &Environment, +) -> Result, ErrorMessage> { + let expr_type = check_expr(*expr1, env)?; + let expr_type2 = check_expr(*expr2, env)?; + if expr_type != Type::TBool { + Err("[Type Error] AssertTrue expression must be of type Boolean.".to_string()) + } else if expr_type2 != Type::TString { + Err("[Type Error] Second AssertTrue expression must be of type String.".to_string()) + } else { + Ok(env.clone()) + } +} + +fn check_assert_false( + expr1: Box, + expr2: Box, + env: &Environment, +) -> Result, ErrorMessage> { + let expr_type = check_expr(*expr1, env)?; + let expr_type2 = check_expr(*expr2, env)?; + if expr_type != Type::TBool { + Err("[Type Error] AssertFalse expression must be of type Boolean.".to_string()) + } else if expr_type2 != Type::TString { + Err("[Type Error] Second AssertFalse expression must be of type String.".to_string()) + } else { + Ok(env.clone()) + } +} + +fn check_assert_eq( + lhs: Box, + rhs: Box, + err: Box, + env: &Environment, +) -> Result, ErrorMessage> { + let lhs_type = check_expr(*lhs, env)?; + let rhs_type = check_expr(*rhs, env)?; + let err_type = check_expr(*err, env)?; + if lhs_type != rhs_type { + Err(format!( + "[Type Error] AssertEQ expressions must have the same type. Found {:?} and {:?}.", + lhs_type, rhs_type + )) + } else if err_type != Type::TString { + Err("[Type Error] Third AssertEQ expression must be of type String.".to_string()) + } else { + Ok(env.clone()) + } +} + +fn check_assert_neq( + lhs: Box, + rhs: Box, + err: Box, + env: &Environment, +) -> Result, ErrorMessage> { + let lhs_type = check_expr(*lhs, env)?; + let rhs_type = check_expr(*rhs, env)?; + let err_type = check_expr(*err, env)?; + if lhs_type != rhs_type { + Err(format!( + "[Type Error] AssertNEQ expressions must have the same type. Found {:?} and {:?}.", + lhs_type, rhs_type + )) + } else if err_type != Type::TString { + Err("[Type Error] Third AssertNEQ expression must be of type String.".to_string()) + } else { + Ok(env.clone()) + } +} + +//TODO: Apresentar TestDef +fn check_test_function_stmt( + function: Function, + env: &Environment, +) -> Result, ErrorMessage> { + if env.lookup_test(&function.name).is_some() { + return Err(format!( + "[Type Error] Test function '{}' already exists.", + function.name + )); + } + if !function.params.is_empty() { + return Err("[Type Error] Test functions must not have parameters.".into()); + } + if function.kind != Type::TVoid { + return Err("[Type Error] Test functions must return void.".into()); + } + if let Some(ref body) = function.body { + check_block((**body).clone(), env)?; + } + + let mut final_env = env.clone(); + final_env.map_test(function); + Ok(final_env) +} fn merge_environments( env1: &Environment, @@ -658,4 +802,336 @@ mod tests { // TODO: Let discuss this case here next class. assert!(check_stmt(stmt, &env).is_err()); } + + #[test] + fn test_assert_bool_ok() { + let env: Environment = Environment::new(); + let stmt = Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("msg".to_string())), + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + //TODO: Apresentar TypeChecker de Asserts (Testes) + mod assert_tests { + use super::*; + + #[test] + fn test_assert_bool_error() { + let env: Environment = Environment::new(); + let stmt = Statement::Assert( + Box::new(Expression::CInt(1)), // não booleano + Box::new(Expression::CString("msg".to_string())), // segundo argumento pode ser qualquer um válido + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_true_ok() { + let env = Environment::new(); + let stmt = Statement::AssertTrue( + Box::new(Expression::CTrue), + Box::new(Expression::CString("ok".to_string())), + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_false_ok() { + let env = Environment::new(); + let stmt = Statement::AssertFalse( + Box::new(Expression::CFalse), + Box::new(Expression::CString("false".to_string())), + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_eq_same_type() { + let env = Environment::new(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + Box::new(Expression::CString("eq".to_string())), + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_eq_mismatch_type() { + let env = Environment::new(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CString("x".to_string())), + Box::new(Expression::CString("eq".to_string())), + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_neq_same_type() { + let env = Environment::new(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CInt(1)), + Box::new(Expression::CInt(2)), + Box::new(Expression::CString("neq".to_string())), + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_neq_mismatch_type() { + let env = Environment::new(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CTrue), + Box::new(Expression::CString("x".to_string())), + Box::new(Expression::CString("neq".to_string())), + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_error_msg_not_string() { + let env = Environment::new(); + let stmt = Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CTrue), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_true_error_msg_not_string() { + let env = Environment::new(); + let stmt = Statement::AssertTrue( + Box::new(Expression::CTrue), + Box::new(Expression::CTrue), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_false_error_msg_not_string() { + let env = Environment::new(); + let stmt = Statement::AssertFalse( + Box::new(Expression::CFalse), + Box::new(Expression::CTrue), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_eq_error_msg_not_string() { + let env = Environment::new(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CTrue), + Box::new(Expression::CTrue), + Box::new(Expression::CTrue), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_neq_error_msg_not_string() { + let env = Environment::new(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CTrue), + Box::new(Expression::CFalse), + Box::new(Expression::CTrue), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_err()); + } + + #[test] + fn test_assert_error_msg_string() { + let env = Environment::new(); + let stmt = Statement::Assert( + Box::new(Expression::CTrue), + Box::new(Expression::CString("assert".to_string())), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_true_error_msg_string() { + let env = Environment::new(); + let stmt = Statement::AssertTrue( + Box::new(Expression::CTrue), + Box::new(Expression::CString("asserttrue".to_string())), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_false_error_msg_string() { + let env = Environment::new(); + let stmt = Statement::AssertFalse( + Box::new(Expression::CFalse), + Box::new(Expression::CString("assertfalse".to_string())), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_eq_error_msg_string() { + let env = Environment::new(); + let stmt = Statement::AssertEQ( + Box::new(Expression::CTrue), + Box::new(Expression::CTrue), + Box::new(Expression::CString("eq".to_string())), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_assert_neq_error_msg_string() { + let env = Environment::new(); + let stmt = Statement::AssertNEQ( + Box::new(Expression::CTrue), + Box::new(Expression::CFalse), + Box::new(Expression::CString("neq".to_string())), // Error message must be a string + ); + assert!(check_stmt(stmt, &env).is_ok()); + } + } + + //TODO: Apresentar TypeChecker de TestDef (Testes) + mod testdef_tests { + use super::*; + + #[test] + fn test_check_valid_test_function() { + let env: Environment = Environment::new(); + let stmt = TestDef(Function { + name: "valid_function".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Block(vec![ + Statement::VarDeclaration("a".to_string(), Box::new(Expression::CInt(10))), + Statement::VarDeclaration("b".to_string(), Box::new(Expression::CInt(5))), + Statement::AssertEQ( + Box::new(Expression::Add( + Box::new(Expression::Var("a".to_string())), + Box::new(Expression::Var("b".to_string())), + )), + Box::new(Expression::CInt(15)), + Box::new(Expression::CString("A soma deveria ser 15".to_string())), + ), + ]))), + }); + assert!(check_stmt(stmt, &env).is_ok()); + } + + #[test] + fn test_check_test_function_with_params() { + let env: Environment = Environment::new(); + let stmt = TestDef(Function { + name: "invalid_function".to_string(), + kind: Type::TVoid, + params: vec![FormalArgument::new("param".to_string(), Type::TString)], // Must have no parameters + body: None, + }); + + assert!(check_stmt(stmt.clone(), &env).is_err()); + + let error = match check_stmt(stmt, &env) { + Ok(_) => "Expected an error, but got Ok".to_string(), + Err(error) => error, + }; + + assert_eq!( + error, + "[Type Error] Test functions must not have parameters.".to_string() + ); + } + + #[test] + fn test_check_test_function_with_non_void_return() { + let env: Environment = Environment::new(); + let stmt = TestDef(Function { + name: "invalid_function".to_string(), + kind: Type::TInteger, // Must be TVoid! + params: vec![], + body: Some(Box::new(Block(vec![ + Statement::VarDeclaration("a".to_string(), Box::new(Expression::CInt(10))), + Statement::VarDeclaration("b".to_string(), Box::new(Expression::CInt(5))), + Statement::AssertEQ( + Box::new(Expression::Add( + Box::new(Expression::Var("a".to_string())), + Box::new(Expression::Var("b".to_string())), + )), + Box::new(Expression::CInt(15)), + Box::new(Expression::CString("A soma deveria ser 15".to_string())), + ), + ]))), + }); + + assert!(check_stmt(stmt.clone(), &env).is_err()); + + let error = match check_stmt(stmt, &env) { + Ok(_) => "Expected an error, but got Ok".to_string(), + Err(error) => error, + }; + + assert_eq!( + error, + "[Type Error] Test functions must return void.".to_string() + ); + } + + #[test] + fn test_check_duplicate_test_function() { + let mut env: Environment = Environment::new(); + let first_func = TestDef(Function { + name: "duplicate".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Block(vec![ + Statement::VarDeclaration("a".to_string(), Box::new(Expression::CInt(10))), + Statement::VarDeclaration("b".to_string(), Box::new(Expression::CInt(5))), + Statement::AssertEQ( + Box::new(Expression::Add( + Box::new(Expression::Var("a".to_string())), + Box::new(Expression::Var("b".to_string())), + )), + Box::new(Expression::CInt(15)), + Box::new(Expression::CString("A soma deveria ser 15".to_string())), + ), + ]))), + }); + + env = check_stmt(first_func, &env).unwrap(); + + let stmt = TestDef(Function { + name: "duplicate".to_string(), + kind: Type::TVoid, + params: vec![], + body: Some(Box::new(Block(vec![ + Statement::VarDeclaration("a".to_string(), Box::new(Expression::CInt(10))), + Statement::VarDeclaration("b".to_string(), Box::new(Expression::CInt(5))), + Statement::AssertEQ( + Box::new(Expression::Add( + Box::new(Expression::Var("a".to_string())), + Box::new(Expression::Var("b".to_string())), + )), + Box::new(Expression::CInt(15)), + Box::new(Expression::CString("A soma deveria ser 15".to_string())), + ), + ]))), + }); + + assert!(check_stmt(stmt.clone(), &env).is_err()); + + let error = match check_stmt(stmt, &env) { + Ok(_) => "Expected an error, but got Ok".to_string(), + Err(error) => error, + }; + + assert_eq!( + error, + "[Type Error] Test function 'duplicate' already exists.".to_string() + ); + } + } }