diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 49fcbef..5fa895b 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -145,7 +145,30 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { let arg_vals: Result, RuntimeError> = args.iter().map(|a| eval_expr(a, env)).collect(); - eval_call(name, arg_vals?, env) + + let callee = env.get(name) + .cloned() + .ok_or_else(|| RuntimeError::new(format!("undefined function '{}'", name)))?; + + eval_call_value(callee, arg_vals?, env) + } + + Expr::CallExpr { chmd, args } => { + let callee_val = eval_expr(chmd, env)?; + let arg_vals: Result, RuntimeError> = + args.iter().map(|a| eval_expr(a, env)).collect(); + eval_call_value(callee_val, arg_vals?, env) + } + + Expr::Lambda { params, return_tipo, crp } => { + let captured = env.snapshot(); + let decl = crate::ir::ast::FunDecl { + name: "".to_string(), + params: params.clone(), + return_type: return_tipo.clone(), + body: crp.clone(), + }; + Ok(Value::Fn(FnValue::Closure { decl, captured })) } } } @@ -180,6 +203,63 @@ pub fn eval_call( } } +fn eval_call_value( + callee: Value, + args: Vec, + env: &mut Environment, +) -> Result { + match callee { + Value::Fn(FnValue::Native(f)) => (f)(args), + + Value::Fn(FnValue::UserDefined(decl)) => { + if args.len() != decl.params.len() { + return Err(RuntimeError::new(format!( + "function '{}' expects {} arguments, got {}", + decl.name, + decl.params.len(), + args.len() + ))); + } + let snapshot = env.snapshot(); + for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) { + env.declare(param_name.clone(), val); + } + let result = exec_stmt(&decl.body, env)?; + env.restore(snapshot); + Ok(result.unwrap_or(Value::Void)) + } + + Value::Fn(FnValue::Closure { decl, captured }) => { + if args.len() != decl.params.len() { + return Err(RuntimeError::new(format!( + "function expects {} arguments, got {}", + decl.params.len(), + args.len() + ))); + } + + let caller_snapshot = env.snapshot(); + + // entra no ambiente capturado (lexical scoping) + env.restore(captured); + + // bind dos params “por cima” do capturado + for ((param_name, _), val) in decl.params.iter().zip(args.into_iter()) { + env.declare(param_name.clone(), val); + } + + let result = exec_stmt(&decl.body, env)?; + + // volta para o caller + env.restore(caller_snapshot); + + Ok(result.unwrap_or(Value::Void)) + } + + other => Err(RuntimeError::new(format!("'{}' is not a function", other))), + } +} + // --- Helpers --- fn eval_literal(lit: &Literal) -> Value { diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs index ceeda2c..7bf7e66 100644 --- a/src/interpreter/exec_stmt.rs +++ b/src/interpreter/exec_stmt.rs @@ -47,6 +47,12 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment) -> ExecResult match &stmt.stmt { // --- Variable declaration --- Statement::Decl { name, init, .. } => { + let init = init.as_ref().ok_or_else(|| { + RuntimeError::new(format!( + "variable '{}' declared without initializer", + name + )) + })?; let val = eval_expr(init, env)?; env.declare(name.clone(), val); Ok(None) diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 2abdc6e..c4148cb 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -61,6 +61,7 @@ //! needs `Value`, and `Value` needs to reference the callable type. use std::fmt; +use std::collections::HashMap; use crate::ir::ast::CheckedFunDecl; @@ -72,6 +73,11 @@ pub type NativeFn = fn(Vec) -> Result; pub enum FnValue { UserDefined(CheckedFunDecl), Native(NativeFn), + + Closure { + decl: CheckedFunDecl, + captured: HashMap, + }, } impl PartialEq for FnValue { @@ -79,6 +85,7 @@ impl PartialEq for FnValue { match (self, other) { (FnValue::UserDefined(a), FnValue::UserDefined(b)) => a == b, (FnValue::Native(a), FnValue::Native(b)) => (*a as usize) == (*b as usize), + (FnValue::Closure { decl: da, .. }, FnValue::Closure { decl: db, .. }) => da == db, _ => false, } } @@ -89,6 +96,7 @@ impl fmt::Debug for FnValue { match self { FnValue::UserDefined(decl) => write!(f, "UserDefined({})", decl.name), FnValue::Native(_) => write!(f, "Native()"), + FnValue::Closure { decl, .. } => write!(f, "Closure({})", decl.name), } } } diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 5f57b24..86e0978 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -103,6 +103,25 @@ pub enum Expr { name: String, args: Vec>, }, + + /// Chamada de função por expressão: chmd(args) + /// apenas para não mexer em Call, mas depois podemos mesclar os dois (Call pode ser um caso especial de CallExpr onde callee é um Ident). + /// Ex.: 'f(42)', '(funçãolambda)(42)', etc.) + CallExpr { + chmd: Box>, + args: Vec>, + }, + + /// Função Lambda: `fn(params) -> return_tipo { crp }` + /// regra pra não ficar ambiguo: + /// 'fn(...) -> ...' é tipo, ou seja 'Type::Fun' + /// 'fn(...) -> ... { ... }' é expressão, ou seja 'Expr::Lambda' + Lambda { + params: Vec, + return_tipo: Type, + crp: Box>, + }, + /// Array literal: [ expr, expr, ... ] ArrayLit(Vec>), /// Index expression: `base[index]` @@ -126,7 +145,8 @@ pub enum Statement { Decl { name: String, ty: Type, - init: Box>, + /// Adicionado para suportar algo como 'fn(int) -> int f;' + init: Option>>, }, Assign { target: Box>, diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 8cfcab4..e514472 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -34,9 +34,11 @@ //! recursing on the right-hand side, which would accidentally produce //! right-associative trees. -use crate::ir::ast::{Expr, ExprD, UncheckedExpr}; +use crate::ir::ast::{Expr, ExprD, Param, UncheckedExpr}; use crate::parser::identifiers::identifier; +use crate::parser::functions::type_name; use crate::parser::literals::literal; +use crate::parser::statements::block_statement; use nom::{ branch::alt, bytes::complete::tag, @@ -65,11 +67,51 @@ pub fn parse_call(input: &str) -> IResult<&str, (String, Vec)> { Ok((rest, (name.to_string(), args))) } +/// Parser de Lambda +fn lambda_expr(input: &str) -> IResult<&str, UncheckedExpr> { + // fn ( Type name, ... ) -> Type { ... } + let (rest, _) = preceded(multispace0, tag("fn"))(input)?; + let (rest, params) = delimited( + preceded(multispace0, tag("(")), + separated_list0( + preceded(multispace0, tag(",")), + map( + tuple(( + preceded(multispace0, type_name), + preceded(nom::character::complete::multispace1, identifier), + )), + |(ty, name)| -> Param { (name.to_string(), ty) }, + ), + ), + preceded(multispace0, tag(")")), + )(rest)?; + let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + let (rest, return_tipo) = preceded(multispace0, type_name)(rest)?; + let (rest, crp) = preceded(multispace0, block_statement)(rest)?; + Ok(( + rest, + wrap(Expr::Lambda { + params, + return_tipo, + crp: Box::new(crp), + }), + )) +} + +/// Parser de lista de args +fn arg_list(input: &str) -> IResult<&str, Vec> { + delimited( + preceded(multispace0, tag("(")), + separated_list0(preceded(multispace0, tag(",")), preceded(multispace0, expression)), + preceded(multispace0, tag(")")), + )(input) +} + /// Atom: literal, call, array literal, identifier, or parenthesized expression. fn atom(input: &str) -> IResult<&str, UncheckedExpr> { alt(( + lambda_expr, map(literal, |l| wrap(Expr::Literal(l.into()))), - map(parse_call, |(name, args)| wrap(Expr::Call { name, args })), map( delimited( preceded(multispace0, char('[')), @@ -94,6 +136,21 @@ fn atom(input: &str) -> IResult<&str, UncheckedExpr> { fn primary(input: &str) -> IResult<&str, UncheckedExpr> { let (mut rest, mut acc) = atom(input)?; loop { + if let Ok((r, args)) = arg_list(rest) { + acc = if let Expr::Ident(name) = &acc.exp { + wrap(Expr::Call { + name: name.clone(), + args, + }) + } else { + wrap(Expr::CallExpr { + chmd: Box::new(acc), + args, + }) + }; + rest = r; + continue; + } let index_parse = delimited( preceded(multispace0, char('[')), preceded(multispace0, expression), diff --git a/src/parser/functions.rs b/src/parser/functions.rs index 845a59c..3d0cf0e 100644 --- a/src/parser/functions.rs +++ b/src/parser/functions.rs @@ -39,6 +39,7 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { preceded( multispace0, alt(( + fun_type, // 2D arrays must be tried before 1D (longer prefix first) map(tag("int[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Int))))), map(tag("float[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Float))))), @@ -57,6 +58,36 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { )(input) } +/// Parse a function type: `fn(T1, T2, ...) -> Ret`. +/// +/// This parser must reject lambdas like: +/// `fn(int x) -> int { return x; }` +fn fun_type(input: &str) -> IResult<&str, Type> { + let (rest, _) = preceded(multispace0, tag("fn"))(input)?; + + let (rest, params) = delimited( + preceded(multispace0, tag("(")), + separated_list0( + preceded(multispace0, tag(",")), + preceded(multispace0, type_name), + ), + preceded(multispace0, tag(")")), + )(rest)?; + + let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + let (rest, ret) = preceded(multispace0, type_name)(rest)?; + + // avoid confusing function types with lambdas + if rest.trim_start().starts_with("{") { + return Err(nom::Err::Error(nom::error::Error::new( + rest, + nom::error::ErrorKind::Tag, + ))); + } + + Ok((rest, Type::Fun(params, Box::new(ret)))) +} + /// Parse a typed parameter (C-style): `Type name`. fn param(input: &str) -> IResult<&str, (String, Type)> { map( @@ -79,6 +110,7 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { preceded(multispace0, tag(")")), )(rest)?; let (rest, body) = preceded(multispace0, statement)(rest)?; + Ok(( rest, FunDecl { @@ -88,4 +120,4 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { body: Box::new(body), }, )) -} +} \ No newline at end of file diff --git a/src/parser/identifiers.rs b/src/parser/identifiers.rs index f6bdecb..5691461 100644 --- a/src/parser/identifiers.rs +++ b/src/parser/identifiers.rs @@ -28,7 +28,7 @@ use nom::{ }; /// Reserved words: boolean literals and type names. -const RESERVED: &[&str] = &["true", "false", "int", "float", "bool", "str", "void", "return"]; +const RESERVED: &[&str] = &["true", "false", "int", "float", "bool", "str", "void", "return", "fn"]; /// Parse an identifier (variable name). /// Must start with letter or underscore; subsequent chars may be letter, digit, or underscore. diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 9dcfef5..888ca5e 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -82,21 +82,27 @@ fn return_statement(input: &str) -> IResult<&str, UncheckedStmt> { Ok((rest, wrap(Statement::Return(expr.map(Box::new))))) } -/// Parse a variable declaration: `Type ident = expr ;`. Must come before assignment. +/// Parse a variable declaration: `Type ident [= expr] ;`. +/// Must come before assignment. fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> { map( tuple(( type_name, preceded(nom::character::complete::multispace1, identifier), - preceded(multispace0, nom::bytes::complete::tag("=")), - preceded(multispace0, expression), + opt(preceded( + multispace0, + preceded( + nom::bytes::complete::tag("="), + preceded(multispace0, expression), + ), + )), preceded(multispace0, char(';')), )), - |(ty, name, _, init, _)| { + |(ty, name, init, _)| { wrap(Statement::Decl { name: name.to_string(), ty, - init: Box::new(init), + init: init.map(Box::new), }) }, )(input) @@ -104,7 +110,7 @@ fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> { /// Parse a block statement: `{ stmt* }`. /// Each statement inside the block carries its own terminator (`;` or `}`). -fn block_statement(input: &str) -> IResult<&str, UncheckedStmt> { +pub(crate) fn block_statement(input: &str) -> IResult<&str, UncheckedStmt> { map( delimited( preceded(multispace0, char('{')), @@ -206,4 +212,4 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> { }) }, )(input) -} +} \ No newline at end of file diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index 46681cf..e929a8f 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -152,6 +152,12 @@ fn type_check_stmt( if env.get(name).is_some() { return Err(TypeError::new(format!("redeclaration of variable: {}", name))); } + let init = init.as_ref().ok_or_else(|| { + TypeError::new(format!( + "variable '{}' must be initialized", + name + )) + })?; let init_checked = type_check_expr_to_typed(init, env)?; if !types_compatible(&init_checked.ty, ty) { return Err(TypeError::new(format!( @@ -163,7 +169,7 @@ fn type_check_stmt( Statement::Decl { name: name.clone(), ty: ty.clone(), - init: Box::new(init_checked), + init: Some(Box::new(init_checked)), } } Statement::Assign { target, value } => { @@ -407,6 +413,31 @@ fn type_check_expr_inner( args: args_checked?, }) } + Expr::CallExpr { chmd, args } => { + let chmd_checked = type_check_expr_to_typed(chmd, env)?; + let args_checked: Result, _> = + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + Ok(Expr::CallExpr { + chmd: Box::new(chmd_checked), + args: args_checked?, + }) + } + Expr::Lambda { params, return_tipo, crp } => { + let mut lambda_env = Environment::::new(); + lambda_env.restore(env.snapshot()); + + for (name, ty) in params.iter() { + lambda_env.declare(name.clone(), ty.clone()); + } + + let body_checked = type_check_stmt(crp, &mut lambda_env, return_tipo)?; + + Ok(Expr::Lambda { + params: params.clone(), + return_tipo: return_tipo.clone(), + crp: Box::new(body_checked), + }) + } Expr::ArrayLit(elems) => { let elems_checked: Result, _> = elems.iter().map(|e| type_check_expr_to_typed(e, env)).collect(); @@ -426,10 +457,6 @@ fn type_check_expr( match &e.exp { Expr::Literal(l) => Ok(literal_type(l)), Expr::Ident(name) => match env.get(name) { - Some(Type::Fun(_, _)) => Err(TypeError::new(format!( - "cannot use function '{}' as a value", - name - ))), Some(ty) => Ok(ty.clone()), None => Err(TypeError::new(format!("undeclared variable: {}", name))), }, @@ -518,6 +545,49 @@ fn type_check_expr( None => Err(TypeError::new(format!("undefined function: {}", name))), } } + Expr::CallExpr { chmd, args } => { + let chmd_ty = type_check_expr(chmd, env)?; + let args_checked: Result, _> = + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + let args_checked = args_checked?; + if let Type::Fun(param_tys, return_ty) = chmd_ty { + if args_checked.len() != param_tys.len() { + return Err(TypeError::new(format!( + "function value expects {} arguments, got {}", + param_tys.len(), + args_checked.len() + ))); + } + for (i, (arg, param_ty)) in + args_checked.iter().zip(param_tys.iter()).enumerate() + { + if !types_compatible(&arg.ty, param_ty) { + return Err(TypeError::new(format!( + "argument {} to function value: expected {:?}, got {:?}", + i + 1, + param_ty, + arg.ty + ))); + } + } + Ok(*return_ty) + } else { + Err(TypeError::new("attempting to call a non-function value")) + } + } + Expr::Lambda { params, return_tipo, crp } => { + let mut lambda_env = Environment::::new(); + lambda_env.restore(env.snapshot()); + + for (name, ty) in params.iter() { + lambda_env.declare(name.clone(), ty.clone()); + } + + type_check_stmt(crp, &mut lambda_env, return_tipo)?; + + let param_tys = params.iter().map(|(_, ty)| ty.clone()).collect(); + Ok(Type::Fun(param_tys, Box::new(return_tipo.clone()))) + } Expr::ArrayLit(elems) => { if elems.is_empty() { return Err(TypeError::new("empty array literal needs type annotation")); @@ -580,6 +650,19 @@ fn types_compatible(a: &Type, b: &Type) -> bool { | (Type::Unit, Type::Unit) => true, (Type::Int, Type::Float) | (Type::Float, Type::Int) => true, (Type::Array(a), Type::Array(b)) => types_compatible(a, b), + (Type::Fun(params_a, ret_a), Type::Fun(params_b, ret_b)) => { + if params_a.len() != params_b.len() { + return false; + } + + for (a, b) in params_a.iter().zip(params_b.iter()) { + if !types_compatible(a, b) { + return false; + } + } + + types_compatible(ret_a, ret_b) + }, _ => false, } } diff --git a/tests/interpreter.rs b/tests/interpreter.rs index 51696c9..cfbf22d 100644 --- a/tests/interpreter.rs +++ b/tests/interpreter.rs @@ -256,3 +256,62 @@ fn test_stdlib_pow_float_args() { "#; assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); } + +// LAMBDA + +/// Executa o pipeline completo: Parser -> Type Check -> Interpret +fn run_full(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("Parser Error: {:?}", e))?; + + // Passando &unchecked (Referência) + let checked = type_check(&unchecked).map_err(|e| format!("Type Error: {}", e.message))?; + + // Execução + interpret(&checked).map_err(|e| format!("Runtime Error: {}", e.message))?; + + Ok(()) +} + +#[test] +fn test_exec_lambda_simple_math() { + let src = r#" + void main() { + fn(int) -> int dobrar = fn(int n) -> int { return n * 2; }; + print(dobrar(10)); + } + "#; + + let result = run_full(src); + assert!(result.is_ok(), "Falha na execução da lambda básica. Erro: {:?}", result.err()); +} + +#[test] +fn test_exec_closure_capture_success() { + // Este teste valida se o seu interpretador injeta o HashMap 'captured' no ambiente + let src = r#" + void main() { + int base = 100; + fn(int) -> int somar = fn(int n) -> int { return n + base; }; + print(somar(50)); + } + "#; + + let result = run_full(src); + assert!( + result.is_ok(), + "A Closure falhou! Provavelmente a variável 'base' não foi injetada no ambiente da lambda. Erro: {:?}", + result.err() + ); +} + +#[test] +fn test_exec_immediate_lambda_call() { + // Testa (fn(x)->x{...})(val) + let src = r#" + void main() { + int x = (fn(int a, int b) -> int { return a + b; })(5, 5); + print(x); + } + "#; + assert!(run_full(src).is_ok()); +} \ No newline at end of file diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..5583172 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -340,6 +340,7 @@ fn test_decl_statement() { assert!(matches!(result.stmt, Statement::Decl { ref name, ref ty, .. } if name == "x" && ty == &Type::Int)); if let Statement::Decl { ref init, .. } = result.stmt { + let init = init.as_ref().expect("Decl init should be present"); assert_eq!(init.exp, Expr::Literal(Literal::Int(42))); } @@ -658,7 +659,7 @@ fn test_multidimensional_indexed_assignment() { #[test] fn test_nested_index() { let result = expression("arr[i][j]").unwrap().1; - assert!(matches!(result.exp, Expr::Index { ref base, ref index } + assert!(matches!(result.exp, Expr::Index { base: _, ref index } if matches!(index.exp, Expr::Ident(ref s) if s == "j"))); if let Expr::Index { ref base, .. } = result.exp { assert!(matches!(base.exp, Expr::Index { ref base, ref index } diff --git a/tests/type_checker.rs b/tests/type_checker.rs index 3357161..3b6d8dd 100644 --- a/tests/type_checker.rs +++ b/tests/type_checker.rs @@ -27,6 +27,7 @@ fn test_type_check_int_float_coercion() { let prog = result.unwrap(); let main_fn = prog.functions.iter().find(|f| f.name == "main").unwrap(); if let mini_c::ir::ast::Statement::Decl { ref init, .. } = main_fn.body.stmt { + let init = init.as_ref().expect("Decl init should be present"); assert_eq!(init.ty, Type::Float); } else { panic!("expected Decl"); @@ -202,3 +203,26 @@ fn test_type_check_print_wrong_arity() { let result = parse_and_type_check("void main() { print(1, 2); }"); assert!(result.is_err(), "expected arity error for print(1, 2)"); } + +// LAMBDA + +#[test] +fn test_type_check_lambda_capture() { + let src = r#" + void main() { + int externo = 10; + fn(int) -> int f = fn(int x) -> int { return x + externo; }; + } + "#; + let (_, unchecked) = program(src).expect("Falha no parser"); + // Usando &unchecked para evitar erro de mismatched types (E0308) + let result = type_check(&unchecked); + assert!(result.is_ok(), "Type Checker não encontrou a variável capturada 'externo'"); +} + +#[test] +fn test_type_check_void_lambda() { + let src = "void main() { fn() -> void f = fn() -> void { return; }; }"; + let (_, unchecked) = program(src).expect("Falha no parser"); + assert!(type_check(&unchecked).is_ok()); +} \ No newline at end of file diff --git a/tests/type_function_integration_tests.rs b/tests/type_function_integration_tests.rs new file mode 100644 index 0000000..83050fd --- /dev/null +++ b/tests/type_function_integration_tests.rs @@ -0,0 +1,139 @@ +use mini_c::parser::functions::{fun_decl, type_name}; +use mini_c::parser::statements::statement; + +// ========================= +// ✅ DECLARAÇÕES COM TYPE::FUN +// ========================= + +#[test] +fn test_statement_decl_with_function_type_no_init() { + let input = "fn(int) -> int f;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_nested_function_type_no_init() { + let input = "fn(fn(int) -> int) -> int g;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_function_type_and_init() { + let input = "fn(int) -> int f = foo;"; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ FUNÇÕES COM PARÂMETROS TYPE::FUN +// ========================= + +#[test] +fn test_fun_decl_param_with_function_type() { + let input = "int apply(fn(int) -> int f, int x) { return x; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_fun_decl_nested_function_type_in_param() { + let input = "int h(fn(fn(int) -> int) -> int f) { return 0; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ FUNÇÕES COM RETORNO TYPE::FUN +// ========================= + +#[test] +fn test_fun_decl_returning_function_type() { + let input = "fn(int) -> int make() { return foo; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_fun_decl_returning_nested_function_type() { + let input = "fn(int) -> fn(int) -> int make() { return foo; }"; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ✅ ESPAÇOS / ROBUSTEZ +// ========================= + +#[test] +fn test_fun_decl_with_many_spaces() { + let input = " + int apply( + fn(int) -> int f, + int x + ) + { + return x; + } + "; + + let result = fun_decl(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +#[test] +fn test_statement_decl_with_many_spaces() { + let input = " + fn( int , float ) -> bool g ; + "; + + let result = statement(input); + assert!(result.is_ok()); + + let (rest, _) = result.unwrap(); + assert_eq!(rest.trim(), ""); +} + +// ========================= +// ❌ NÃO CONFUNDIR COM LAMBDA +// ========================= + +#[test] +fn test_type_name_still_rejects_lambda() { + let input = "fn(int x) -> int { return x; }"; + + let result = type_name(input); + assert!(result.is_err()); +} \ No newline at end of file diff --git a/tests/type_function_tests.rs b/tests/type_function_tests.rs new file mode 100644 index 0000000..1bc3ef3 --- /dev/null +++ b/tests/type_function_tests.rs @@ -0,0 +1,200 @@ +use mini_c::parser::functions::type_name; +use mini_c::ir::ast::Type; + +// ========================= +// ✅ CASOS VÁLIDOS +// ========================= + +#[test] +fn test_fun_type_simple() { + let input = "fn(int) -> int"; + + let result = type_name(input); + assert!(result.is_ok()); + + let (rest, ty): (&str, Type) = result.unwrap(); + assert_eq!(rest.trim(), ""); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 1); + assert_eq!(params[0], Type::Int); + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_multiple_params() { + let input = "fn(int, float) -> bool"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 2); + assert_eq!(params[0], Type::Int); + assert_eq!(params[1], Type::Float); + assert_eq!(*ret, Type::Bool); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_no_params() { + let input = "fn() -> int"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 0); + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_nested() { + let input = "fn(fn(int) -> int) -> int"; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 1); + + match ¶ms[0] { + Type::Fun(inner_params, inner_ret) => { + assert_eq!(inner_params.len(), 1); + assert_eq!(inner_params[0], Type::Int); + assert_eq!(**inner_ret, Type::Int); + } + _ => panic!("Expected nested function type"), + } + + assert_eq!(*ret, Type::Int); + } + _ => panic!("Expected function type"), + } +} + +#[test] +fn test_fun_type_spaces_everywhere() { + let input = " fn( int , float ) -> bool "; + + let (_, ty) = type_name(input).unwrap(); + + match ty { + Type::Fun(params, ret) => { + assert_eq!(params.len(), 2); + assert_eq!(params[0], Type::Int); + assert_eq!(params[1], Type::Float); + assert_eq!(*ret, Type::Bool); + } + _ => panic!("Expected function type"), + } +} + +// ========================= +// ❌ CASOS INVÁLIDOS +// ========================= + +#[test] +fn test_lambda_not_type() { + let input = "fn(int x) -> int { return x; }"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_invalid_comma() { + let input = "fn(,) -> int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_missing_arrow() { + let input = "fn(int) int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_unclosed_paren() { + let input = "fn(int -> int"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_missing_return_type() { + let input = "fn(int) ->"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +#[test] +fn test_random_garbage() { + let input = "fn(abc) => ???"; + + let result = type_name(input); + + assert!(result.is_err()); +} + +// ========================= +// 🔥 EDGE CASES +// ========================= + +#[test] +fn test_deeply_nested_functions() { + let input = "fn(fn(fn(int) -> int) -> int) -> int"; + + let result = type_name(input); + + assert!(result.is_ok()); +} + +#[test] +fn test_trailing_input() { + let input = "fn(int) -> int extra"; + + let (rest, _): (&str, Type) = type_name(input).unwrap(); + + assert!(rest.trim().starts_with("extra")); +} + +// ========================= +// 💡 ROBUSTEZ +// ========================= + +#[test] +fn test_many_spaces_and_newlines() { + let input = " + fn( + int, + float + ) + -> + bool + "; + + let result = type_name(input); + + assert!(result.is_ok()); +} \ No newline at end of file