From 8023c54df897a0e169fc2fca740b23786ee5b830 Mon Sep 17 00:00:00 2001 From: SapoSopa Date: Mon, 13 Apr 2026 21:47:03 -0300 Subject: [PATCH 01/19] =?UTF-8?q?ast:=20adiciona=20lambda,=20call=20por=20?= =?UTF-8?q?express=C3=A3o=20e=20decl=20sem=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ir/ast.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 5f57b24..1d2d464 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,7 @@ pub enum Statement { Decl { name: String, ty: Type, - init: Box>, + init: Option>>, /// Adicionado para suportar algo como 'fn(int) -> int f;' }, Assign { target: Box>, From 26d45ea02065e033d518ae2942ef233e2d322757 Mon Sep 17 00:00:00 2001 From: SapoSopa Date: Mon, 13 Apr 2026 22:23:36 -0300 Subject: [PATCH 02/19] ast: comentario ajustado --- src/ir/ast.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 1d2d464..86e0978 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -145,7 +145,8 @@ pub enum Statement { Decl { name: String, ty: Type, - init: Option>>, /// Adicionado para suportar algo como 'fn(int) -> int f;' + /// Adicionado para suportar algo como 'fn(int) -> int f;' + init: Option>>, }, Assign { target: Box>, From 346b0259227c1087405b3c43da27e5983f1698c2 Mon Sep 17 00:00:00 2001 From: SapoSopa Date: Tue, 14 Apr 2026 09:40:16 -0300 Subject: [PATCH 03/19] =?UTF-8?q?feat(project5):=20fun=C3=A7=C3=B5es=20de?= =?UTF-8?q?=20primeira=20classe=20(tipos=20fn,=20lambdas,=20call=20por=20e?= =?UTF-8?q?xpress=C3=A3o)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/eval_expr.rs | 75 +++++++++++++++++++++++++++++++++ src/interpreter/exec_stmt.rs | 6 +++ src/interpreter/value.rs | 8 ++++ src/parser/expressions.rs | 61 ++++++++++++++++++++++++++- src/parser/functions.rs | 14 +++++++ src/parser/identifiers.rs | 2 +- src/parser/statements.rs | 2 +- src/semantic/type_checker.rs | 80 +++++++++++++++++++++++++++++++++--- tests/parser.rs | 1 + tests/type_checker.rs | 1 + 10 files changed, 241 insertions(+), 9 deletions(-) diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 49fcbef..8d65acf 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -147,6 +147,24 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { + 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 +198,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/parser/expressions.rs b/src/parser/expressions.rs index 8cfcab4..429ca3d 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::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, 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..9baf679 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,19 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { )(input) } +/// Parser auxiliar +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(",")), type_name), + preceded(multispace0, tag(")")), + )(rest)?; + let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + let (rest, ret) = preceded(multispace0, type_name)(rest)?; + 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( 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..83ea225 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -96,7 +96,7 @@ fn decl_statement(input: &str) -> IResult<&str, UncheckedStmt> { wrap(Statement::Decl { name: name.to_string(), ty, - init: Box::new(init), + init: Some(Box::new(init)), }) }, )(input) diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index 46681cf..cbf6a4c 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")); diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..e5bb606 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))); } diff --git a/tests/type_checker.rs b/tests/type_checker.rs index 3357161..25e9c27 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"); From 0862d5acd47c0b87f5e4caf419586f976f33b237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20de=20Sousa=20Rodrigues?= <141956872+vsr87@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:54:55 +0000 Subject: [PATCH 04/19] =?UTF-8?q?feat(project5):=20implementa=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20function=20type=20parsing=20com=20valida=C3=A7=C3=A3o?= =?UTF-8?q?=20e=20lambda=20disambiguation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/functions.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/parser/functions.rs b/src/parser/functions.rs index 9baf679..e5aa659 100644 --- a/src/parser/functions.rs +++ b/src/parser/functions.rs @@ -60,14 +60,40 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { /// Parser auxiliar fn fun_type(input: &str) -> IResult<&str, Type> { + // tenta reconhecer a palavra-chave "fn" ignorando espacos antes dela let (rest, _) = preceded(multispace0, tag("fn"))(input)?; + + // faz o parsing da lista de parametros dentro dos parênteses let (rest, params) = delimited( preceded(multispace0, tag("(")), - separated_list0(preceded(multispace0, tag(",")), type_name), + separated_list0( + preceded(multispace0, tag(",")), + preceded(multispace0, type_name), + ), preceded(multispace0, tag(")")), )(rest)?; + + // detectar vírgula inválida logo após '(' → fn(,) + let check_params = rest.trim_start(); + if check_params.starts_with(",") { + return Err(nom::Err::Error( + nom::error::Error::new(rest, nom::error::ErrorKind::Tag), + )); + } + + // consome o símbolo "->", que separa parametros do tipo de retorno let (rest, _) = preceded(multispace0, tag("->"))(rest)?; + // faz o parsing do tipo de retorno da funcao let (rest, ret) = preceded(multispace0, type_name)(rest)?; + + // evitar confundir com lambda + let check = rest.trim_start(); + if check.starts_with("{") { + return Err(nom::Err::Error( + nom::error::Error::new(rest, nom::error::ErrorKind::Tag), + )); + } + // se tudo deu certo, retorna o tipo de funcao construido Ok((rest, Type::Fun(params, Box::new(ret)))) } From e75c651e7a4567a6ad893938baebf061d6c3ee1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20de=20Sousa=20Rodrigues?= <141956872+vsr87@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:57:36 +0000 Subject: [PATCH 05/19] test(parser/project5): adicionando testes para function type parsing --- tests/type_function_tests.rs | 200 +++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 tests/type_function_tests.rs 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 From 17f297d14ccb46df6ee578a7cdebc84dc2d1e33e Mon Sep 17 00:00:00 2001 From: AlvaroNegromonte Date: Wed, 15 Apr 2026 11:01:15 -0300 Subject: [PATCH 06/19] feat(parser): add function type support to declarations and function params --- src/parser/functions.rs | 34 +++++++++++++--------------------- src/parser/statements.rs | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/parser/functions.rs b/src/parser/functions.rs index e5aa659..3d0cf0e 100644 --- a/src/parser/functions.rs +++ b/src/parser/functions.rs @@ -58,12 +58,13 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { )(input) } -/// Parser auxiliar +/// 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> { - // tenta reconhecer a palavra-chave "fn" ignorando espacos antes dela let (rest, _) = preceded(multispace0, tag("fn"))(input)?; - // faz o parsing da lista de parametros dentro dos parênteses let (rest, params) = delimited( preceded(multispace0, tag("(")), separated_list0( @@ -73,27 +74,17 @@ fn fun_type(input: &str) -> IResult<&str, Type> { preceded(multispace0, tag(")")), )(rest)?; - // detectar vírgula inválida logo após '(' → fn(,) - let check_params = rest.trim_start(); - if check_params.starts_with(",") { - return Err(nom::Err::Error( - nom::error::Error::new(rest, nom::error::ErrorKind::Tag), - )); - } - - // consome o símbolo "->", que separa parametros do tipo de retorno let (rest, _) = preceded(multispace0, tag("->"))(rest)?; - // faz o parsing do tipo de retorno da funcao let (rest, ret) = preceded(multispace0, type_name)(rest)?; - // evitar confundir com lambda - let check = rest.trim_start(); - if check.starts_with("{") { - return Err(nom::Err::Error( - nom::error::Error::new(rest, nom::error::ErrorKind::Tag), - )); + // 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, + ))); } - // se tudo deu certo, retorna o tipo de funcao construido + Ok((rest, Type::Fun(params, Box::new(ret)))) } @@ -119,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 { @@ -128,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/statements.rs b/src/parser/statements.rs index 83ea225..307f211 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: Some(Box::new(init)), + init: init.map(Box::new), }) }, )(input) @@ -206,4 +212,4 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> { }) }, )(input) -} +} \ No newline at end of file From 59bb4688b8f18e42f5f61e761d4acfc1079d5c30 Mon Sep 17 00:00:00 2001 From: AlvaroNegromonte Date: Wed, 15 Apr 2026 11:12:29 -0300 Subject: [PATCH 07/19] test(parser): add integration tests for function types --- tests/type_function_integration_tests.rs | 139 +++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tests/type_function_integration_tests.rs 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 From a1ac64a54e29410ca2bf7227ddc7f02e25809f94 Mon Sep 17 00:00:00 2001 From: jvnl Date: Fri, 17 Apr 2026 08:00:22 -0300 Subject: [PATCH 08/19] [fix](parser/expressions, parser/statements): enforce block-only body for lambda expressions --- src/parser/expressions.rs | 4 ++-- src/parser/statements.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 429ca3d..e514472 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -38,7 +38,7 @@ 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::statement; +use crate::parser::statements::block_statement; use nom::{ branch::alt, bytes::complete::tag, @@ -87,7 +87,7 @@ fn lambda_expr(input: &str) -> IResult<&str, UncheckedExpr> { )(rest)?; let (rest, _) = preceded(multispace0, tag("->"))(rest)?; let (rest, return_tipo) = preceded(multispace0, type_name)(rest)?; - let (rest, crp) = preceded(multispace0, statement)(rest)?; + let (rest, crp) = preceded(multispace0, block_statement)(rest)?; Ok(( rest, wrap(Expr::Lambda { diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 307f211..888ca5e 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -110,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('{')), From 3922e0827e160b412bb0db453fe30b5988164b16 Mon Sep 17 00:00:00 2001 From: GabrielVMayerhofer Date: Fri, 17 Apr 2026 22:17:38 -0300 Subject: [PATCH 09/19] test: add integration tests for lambda functions --- tests/interpreter.rs | 57 +++++++++++++++++++++++++++++++++++++++++++ tests/parser.rs | 2 +- tests/type_checker.rs | 23 +++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/tests/interpreter.rs b/tests/interpreter.rs index 51696c9..f026fda 100644 --- a/tests/interpreter.rs +++ b/tests/interpreter.rs @@ -256,3 +256,60 @@ 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)); +// } +// "#; +// assert!(run_full(src).is_ok(), "Falha na execução da lambda básica."); +// } + +// #[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 e5bb606..5583172 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -659,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 25e9c27..3b6d8dd 100644 --- a/tests/type_checker.rs +++ b/tests/type_checker.rs @@ -203,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 From f55bfcdc416ccfea5309488b7f08c742290adb4a Mon Sep 17 00:00:00 2001 From: jambis-prg Date: Sun, 19 Apr 2026 00:24:27 -0300 Subject: [PATCH 10/19] [fix](interpreter/eval_expr, semantic/type_checker): support lambda calls and correct function type checking --- src/interpreter/eval_expr.rs | 7 ++++++- src/semantic/type_checker.rs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 8d65acf..5fa895b 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -145,7 +145,12 @@ 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 } => { diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index cbf6a4c..e929a8f 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -650,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, } } From b54d83139c812f599133a9a27b853ee54a3b90e3 Mon Sep 17 00:00:00 2001 From: jambis-prg Date: Sun, 19 Apr 2026 00:25:41 -0300 Subject: [PATCH 11/19] [test](interpreter/lambda): interpreter lambda tests are now passing --- tests/interpreter.rs | 56 +++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/tests/interpreter.rs b/tests/interpreter.rs index f026fda..cfbf22d 100644 --- a/tests/interpreter.rs +++ b/tests/interpreter.rs @@ -272,35 +272,37 @@ fn run_full(src: &str) -> Result<(), String> { 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)); -// } -// "#; -// assert!(run_full(src).is_ok(), "Falha na execução da lambda básica."); -// } +#[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)); -// } -// "#; +#[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() -// ); -// } + 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() { From dfb0a5dd004b515517b9f174478e729b4b57b80f Mon Sep 17 00:00:00 2001 From: Sapo Sopa Date: Mon, 1 Jun 2026 14:45:09 -0300 Subject: [PATCH 12/19] Definition of the second milestone. --- docs/09-projects.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/09-projects.md b/docs/09-projects.md index 53fb56f..9e1cebf 100644 --- a/docs/09-projects.md +++ b/docs/09-projects.md @@ -13,19 +13,14 @@ The allocation of projects to groups was performed randomly, using a reproducibl | Project | Group | |-------------|--------| -| Projeto 10 | Daniel Silvestre de França e Souza
João Pedro Barbosa Marins
Marcelo Arcoverde Neves Britto de Rezende
Rafael Paz Fernandes
Vinícius Lima Sá de Melo | -| Projeto 7 | Enzo Gurgel Bissoli
Shellyda de Fatima Silva Barbosa
Rodrigo Santos Batista
Juliana Serafim da Silva
Anderson Vitor Leoncio de Lima | -| Projeto 8 | Alberto Guevara de Araujo Franca
Davi Gonzaga Guerreiro Barboza
Fábio Pereira de Miranda
Felipe Torres de Macedo | +| Projeto 1 | Valter Sanches, Artur Vinicius
Victor Silva, Miguel Gomes, Matheus Ayres| | Projeto 2 | Ian Medeiros Melo
Victor Mendonça Aguiar
Rafael Alves de Azevedo Silva
João Victor Fellows Rabelo
Guilherme Montenegro de Albuquerque | -| Projeto 5 | Álvaro Cavalcante Negromonte
Gabriel Valença Mayerhofer
Henrique César Higino Holanda Cordeiro
João Victor Nascimento Lima
Vinicius de Souza Rodrigues | +| Projeto 3 | Joana D'Arc, Juliana Silva
Leandro Luiz, Paulo Vitor, Thiago Henrique| | Projeto 4 | Bruno Antonio dos Santos Bezerra
Luan de Oliveira Romancini Leite
Leônidas Dantas de Castro Netto
Pedro Gabriel Alves da Silva | - -The following projects were not assigned. - - * Project 1 - * Project 3 - * Project 6 - * Project 9 +| Projeto 5 | Álvaro Cavalcante Negromonte
Gabriel Valença Mayerhofer
Henrique César Higino Holanda Cordeiro
João Victor Nascimento Lima
Vinicius de Souza Rodrigues | +| Projeto 7 | Enzo Gurgel Bissoli
Shellyda de Fatima Silva Barbosa
Rodrigo Santos Batista
Juliana Serafim da Silva
Anderson Vitor Leoncio de Lima | +| Projeto 8 | Alberto Guevara de Araujo Franca
Davi Gonzaga Guerreiro Barboza
Fábio Pereira de Miranda
Felipe Torres de Macedo | +| Projeto 10 | Daniel Silvestre de França e Souza
João Pedro Barbosa Marins
Marcelo Arcoverde Neves Britto de Rezende
Rafael Paz Fernandes
Vinícius Lima Sá de Melo | --- @@ -391,7 +386,7 @@ A good starting point for any of them is: ### Second Milestone: * Review the implementation of the type checker and interpreter - * Deadline: 11/05 + * Deadline: 07/06 ### Third Milestone: From 2146f10fd3610e85b51aa3ec55198a026216617f Mon Sep 17 00:00:00 2001 From: Sapo Sopa Date: Wed, 3 Jun 2026 09:31:23 -0300 Subject: [PATCH 13/19] =?UTF-8?q?docs=20+=20tests:=20formaliza=20sem=C3=A2?= =?UTF-8?q?ntica=20de=20fun=C3=A7=C3=B5es=20de=201=C2=AA=20classe=20e=20ad?= =?UTF-8?q?iciona=20testes-esqueleto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/semantics-first-class-fn.md | 77 ++++++++++++++++++++++++++++++++ tests/interpreter_closure.rs | 20 +++++++++ tests/interpreter_fn_value.rs | 18 ++++++++ tests/type_fn_assign.rs | 28 ++++++++++++ tests/type_lambda_call.rs | 29 ++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 docs/semantics-first-class-fn.md create mode 100644 tests/interpreter_closure.rs create mode 100644 tests/interpreter_fn_value.rs create mode 100644 tests/type_fn_assign.rs create mode 100644 tests/type_lambda_call.rs diff --git a/docs/semantics-first-class-fn.md b/docs/semantics-first-class-fn.md new file mode 100644 index 0000000..87de355 --- /dev/null +++ b/docs/semantics-first-class-fn.md @@ -0,0 +1,77 @@ +# Semântica: Funções de Primeira Classe e Closures (Milestone 2) + +Objetivo curto +- Formalizar como funções passam a ser valores em MiniC, qual a representação em runtime e a semântica de captura (closures). + +Decisão principal +- Captura por snapshot no momento da criação: uma lambda carrega uma cópia do *ambiente de execução* (snapshot do `Environment`) quando é criada. Ao chamar a closure, o intérprete restaura esse ambiente capturado, vincula os parâmetros e executa o corpo. + +Representação sugerida (estrutura em Rust-like, acho que era assim que o professor fez) +``` +// representação conceitual +struct ClosureValue { + params: Vec, // nomes + tipos + return_type: Type, + body: Statement, // AST do corpo + captured_env: Environment, // snapshot por criação +} + +// em runtime stored as +// Value::Fn(FnValue::Closure { decl, captured }) +``` + +O que é capturado +- Capturamos um *snapshot completo* do `Environment` (mapa nome → `Value`) no momento da criação da lambda. Isso significa: + - variáveis livres ficam com o valor que tinham na criação; + - a closure não observa mudanças subsequentes em bindings fora do seu escopo local. + +Semântica da chamada (ideia em pseudocódigo) +``` +fn call_closure(closure, args, env) { + // 1. snapshot do caller + caller_snapshot = env.snapshot() + + // 2. entra no ambiente capturado (lexical scope) + env.restore(closure.captured_env.clone()) + + // 3. bind dos parâmetros com os argumentos (sobrepondo nomes capturados) + for (param, arg) in zip(closure.params, args) { + env.declare(param.name.clone(), arg) + } + + // 4. executar o corpo + result = exec_stmt(&closure.body, env) + + // 5. restaurar ambiente do caller + env.restore(caller_snapshot) + + return result.unwrap_or(Value::Void) +} +``` + +Decisão sobre declarações sem inicializador +- Não declarar uma variável de tipo função sem init (a não ser que queiram mais trabalho, ai só avisar no grupo pra gente rever isso). + +Invariantes de tipo +- Uma `Expr::Lambda` tem tipo `Type::Fun(param_types, Box::new(return_type))` no type checker. +- `types_compatible` deve comparar aridade e compatibilidade por posição de `Type::Fun`. + +Mensagens de erro (sugeridas) +- Chamar algo não-função: `RuntimeError: 'X' is not a function` ou `attempting to call a non-function value`. +- Número de argumentos errado: `function 'f' expects N arguments, got M`. +- Atribuição de tipo inválida: `assignment to f: expected fn(...)->..., got ...`. + +Testes obrigatórios (mínimos) — mapeados +- Pessoa 2 (Sistema de Tipos): + - `fn(int) -> int f;` — válido + - `fn(float) -> int f; f = fn(int x) -> int { return x; }` — inválido (TypeError) + +- Pessoa 3 (Lambdas / Chamadas — type-checker): + - `fn(int x) -> int { return x * 2; }` — type-check OK + - `f(true)` com `f: fn(int)->int` — inválido (TypeError) + +- Pessoa 4 (Runtime: função como valor): + - declarar `f = fn(int x) -> int { return x * 2 }; print(f(21));` → saída `42` + +- Pessoa 5 (Closures): + - `int y = 10; fn(int)->int f = fn(int x)->int { return x + y; }; y = 20; print(f(1));` → saída `11` (captura por snapshot) diff --git a/tests/interpreter_closure.rs b/tests/interpreter_closure.rs new file mode 100644 index 0000000..89d16bb --- /dev/null +++ b/tests/interpreter_closure.rs @@ -0,0 +1,20 @@ +use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; + +fn run(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +#[test] +fn test_closure_snapshot() { + let src = r#" + void main() { + int y = 10; + fn(int) -> int f = fn(int x) -> int { return x + y; }; + y = 20; + print(f(1)); + } + "#; + assert!(run(src).is_ok(), "closure should capture creation-time value"); +} diff --git a/tests/interpreter_fn_value.rs b/tests/interpreter_fn_value.rs new file mode 100644 index 0000000..6e56e6d --- /dev/null +++ b/tests/interpreter_fn_value.rs @@ -0,0 +1,18 @@ +use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; + +fn run(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +#[test] +fn test_fn_value_runtime() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x * 2; }; + print(f(21)); + } + "#; + assert!(run(src).is_ok(), "expected runtime to succeed"); +} diff --git a/tests/type_fn_assign.rs b/tests/type_fn_assign.rs new file mode 100644 index 0000000..d3b880d --- /dev/null +++ b/tests/type_fn_assign.rs @@ -0,0 +1,28 @@ +use mini_c::{parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) +} + +#[test] +fn test_assign_compatible_fn_type() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x; }; + int r = f(5); + } + "#; + assert!(type_check_src(src).is_ok(), "expected type-check to succeed"); +} + +#[test] +fn test_assign_incompatible_fn_type() { + let src = r#" + void main() { + fn(float) -> int f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "expected type error for incompatible function assignment"); +} diff --git a/tests/type_lambda_call.rs b/tests/type_lambda_call.rs new file mode 100644 index 0000000..2e9d50f --- /dev/null +++ b/tests/type_lambda_call.rs @@ -0,0 +1,29 @@ +use mini_c::{parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) +} + +#[test] +fn test_lambda_type_check_ok() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x * 2; }; + int r = f(3); + } + "#; + assert!(type_check_src(src).is_ok(), "lambda should type-check"); +} + +#[test] +fn test_lambda_call_wrong_arg() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x; }; + f(true); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "expected type error when calling with wrong arg type"); +} From c9dd8c316a7e777e52961b770cd48d3d5e36fe9b Mon Sep 17 00:00:00 2001 From: GabrielVMayerhofer Date: Thu, 4 Jun 2026 15:47:49 -0300 Subject: [PATCH 14/19] feat(interpreter: implement runtime de funoes e lambdas --- src/interpreter/eval_expr.rs | 4 ++-- src/interpreter/value.rs | 2 +- tests/interpreter_fn_value.rs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 5fa895b..cc81025 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -162,7 +162,7 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { let captured = env.snapshot(); - let decl = crate::ir::ast::FunDecl { + let decl = crate::ir::ast::CheckedFunDecl { name: "".to_string(), params: params.clone(), return_type: return_tipo.clone(), @@ -317,4 +317,4 @@ fn values_equal(a: &Value, b: &Value) -> bool { (Value::Str(x), Value::Str(y)) => x == y, _ => false, } -} +} \ No newline at end of file diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index c4148cb..e03c2cf 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -156,4 +156,4 @@ impl fmt::Display for RuntimeError { } } -impl std::error::Error for RuntimeError {} +impl std::error::Error for RuntimeError {} \ No newline at end of file diff --git a/tests/interpreter_fn_value.rs b/tests/interpreter_fn_value.rs index 6e56e6d..d5e2881 100644 --- a/tests/interpreter_fn_value.rs +++ b/tests/interpreter_fn_value.rs @@ -1,4 +1,6 @@ -use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; +use mini_c::interpreter::interpret; +use mini_c::parser::program; +use mini_c::semantic::type_check; fn run(src: &str) -> Result<(), String> { let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; @@ -16,3 +18,15 @@ fn test_fn_value_runtime() { "#; assert!(run(src).is_ok(), "expected runtime to succeed"); } + +#[test] +fn test_fn_value_call_through_variable() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x * 2; }; + fn(int) -> int g = f; + print(g(21)); + } + "#; + assert!(run(src).is_ok(), "expected function call through variable to succeed"); +} \ No newline at end of file From 2f7fc52ce63570b697549ca657e61eae5067eccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Sousa?= Date: Sat, 6 Jun 2026 11:57:05 -0300 Subject: [PATCH 15/19] feat: implement type compatibility and uninitialized declarations for functions (Milestone 2) --- src/interpreter/exec_stmt.rs | 12 ++-- src/semantic/type_checker.rs | 37 +++++----- tests/type_function_compat.rs | 131 ++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 23 deletions(-) create mode 100644 tests/type_function_compat.rs diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs index 7bf7e66..608d465 100644 --- a/src/interpreter/exec_stmt.rs +++ b/src/interpreter/exec_stmt.rs @@ -47,13 +47,11 @@ 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)?; + let val = if let Some(init_expr) = init { + eval_expr(init_expr, env)? + } else { + Value::Void + }; env.declare(name.clone(), val); Ok(None) } diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index e929a8f..41b8053 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -152,24 +152,29 @@ 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!( - "declaration of {}: expected {:?}, got {:?}", - name, ty, init_checked.ty - ))); - } + let init_checked = if let Some(init_expr) = init { + let checked = type_check_expr_to_typed(init_expr, env)?; + if !types_compatible(&checked.ty, ty) { + return Err(TypeError::new(format!( + "declaration of {}: expected {:?}, got {:?}", + name, ty, checked.ty + ))); + } + Some(Box::new(checked)) + } else { + if !matches!(ty, Type::Fun(_, _)) { + return Err(TypeError::new(format!( + "variable '{}' must be initialized", + name + ))); + } + None + }; env.declare(name.clone(), ty.clone()); Statement::Decl { name: name.clone(), ty: ty.clone(), - init: Some(Box::new(init_checked)), + init: init_checked, } } Statement::Assign { target, value } => { @@ -656,12 +661,12 @@ fn types_compatible(a: &Type, b: &Type) -> bool { } for (a, b) in params_a.iter().zip(params_b.iter()) { - if !types_compatible(a, b) { + if a != b { return false; } } - types_compatible(ret_a, ret_b) + ret_a == ret_b }, _ => false, } diff --git a/tests/type_function_compat.rs b/tests/type_function_compat.rs new file mode 100644 index 0000000..e2997b0 --- /dev/null +++ b/tests/type_function_compat.rs @@ -0,0 +1,131 @@ +use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; + +fn type_check_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) +} + +fn run_src(src: &str) -> Result<(), String> { + let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; + let checked = type_check(&unchecked).map_err(|e| format!("type error: {}", e.message))?; + interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) +} + +// --------------------------------------------------------------------------- +// ✅ CASOS VÁLIDOS +// --------------------------------------------------------------------------- + +#[test] +fn test_valid_uninitialized_fn_decl() { + let src = r#" + void main() { + fn(int) -> int f; + } + "#; + assert!(type_check_src(src).is_ok(), "Expected valid uninitialized fn declaration to pass type checker"); +} + +#[test] +fn test_valid_fn_assign_and_call() { + let src = r#" + void main() { + fn(int) -> int f; + f = fn(int x) -> int { return x * 3; }; + int r = f(4); + print(r); + } + "#; + assert!(run_src(src).is_ok(), "Expected valid function assignment and call to succeed at runtime"); +} + +// --------------------------------------------------------------------------- +// ❌ CASOS INVÁLIDOS +// --------------------------------------------------------------------------- + +#[test] +fn test_invalid_uninitialized_int_decl() { + let src = r#" + void main() { + int x; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject uninitialized int variable"); + assert!(res.unwrap_err().contains("must be initialized")); +} + +#[test] +fn test_incompatible_fn_type_assignment() { + let src = r#" + void main() { + fn(float) -> int f; + f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject incompatible function type assignment"); + assert!(res.unwrap_err().contains("assignment to f")); +} + +#[test] +fn test_incompatible_fn_type_declaration() { + let src = r#" + void main() { + fn(float) -> int f = fn(int x) -> int { return x; }; + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject incompatible function type initialization"); + assert!(res.unwrap_err().contains("declaration of f")); +} + +#[test] +fn test_call_non_function() { + let src = r#" + void main() { + int x = 42; + x(5); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling a non-function value"); + assert!(res.unwrap_err().contains("is not a function")); +} + +#[test] +fn test_call_non_function_literal() { + let src = r#" + void main() { + int y = 1(5); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling a non-function literal"); + assert!(res.unwrap_err().contains("attempting to call a non-function value")); +} + +#[test] +fn test_wrong_argument_count() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { return x; }; + f(1, 2); + } + "#; + let res = type_check_src(src); + assert!(res.is_err(), "Expected type checker to reject calling function with wrong number of arguments"); + assert!(res.unwrap_err().contains("expects 1 arguments, got 2")); +} + +#[test] +fn test_uninitialized_fn_call_runtime_error() { + let src = r#" + void main() { + fn(int) -> int f; + f(5); + } + "#; + let res = run_src(src); + assert!(res.is_err(), "Expected calling an uninitialized function variable to fail at runtime"); + assert!(res.unwrap_err().contains("is not a function")); +} From 3926948999a110f5521b01b23572dd7a8938aa75 Mon Sep 17 00:00:00 2001 From: AlvaroNegromonte Date: Sat, 6 Jun 2026 11:59:22 -0300 Subject: [PATCH 16/19] [TypeChecker] Check lambda and function-value calls --- src/semantic/type_checker.rs | 227 ++++++++++++++++++++--------------- tests/type_lambda_call.rs | 90 +++++++++++++- 2 files changed, 212 insertions(+), 105 deletions(-) diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index e929a8f..3efc2bc 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -44,7 +44,7 @@ //! Centralising compatibility logic here means all callers (declaration, //! assignment, call-argument checking) share one consistent definition. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::environment::Environment; use crate::ir::ast::{ @@ -274,32 +274,16 @@ fn check_call( args: &[CheckedExpr], env: &Environment, ) -> Result<(), TypeError> { - match env.get(name) { - Some(Type::Fun(param_tys, _)) => { - if args.len() != param_tys.len() { - return Err(TypeError::new(format!( - "function '{}' expects {} arguments, got {}", - name, - param_tys.len(), - args.len() - ))); - } - for (i, (arg, param_ty)) in args.iter().zip(param_tys.iter()).enumerate() { - if !types_compatible(&arg.ty, param_ty) { - return Err(TypeError::new(format!( - "argument {} to {}: expected {:?}, got {:?}", - i + 1, - name, - param_ty, - arg.ty - ))); - } - } - Ok(()) - } - Some(_) => Err(TypeError::new(format!("'{}' is not a function", name))), - None => Err(TypeError::new(format!("undefined function: {}", name))), - } + let callee_ty = env + .get(name) + .ok_or_else(|| TypeError::new(format!("undefined function: {}", name)))?; + + type_of_function_call( + callee_ty, + args, + &format!("function '{}'", name), + ) + .map(|_| ()) } fn type_check_assign_target( @@ -339,6 +323,76 @@ fn type_check_assign_target( } } +fn type_of_function_call( + callee_ty: &Type, + args: &[CheckedExpr], + label: &str, +) -> Result { + match callee_ty { + Type::Fun(param_tys, return_ty) => { + if args.len() != param_tys.len() { + return Err(TypeError::new(format!( + "{} expects {} arguments, got {}", + label, + param_tys.len(), + args.len() + ))); + } + + for (i, (arg, param_ty)) in args.iter().zip(param_tys.iter()).enumerate() { + if !types_compatible(&arg.ty, param_ty) { + return Err(TypeError::new(format!( + "argument {} to {}: expected {:?}, got {:?}", + i + 1, + label, + param_ty, + arg.ty + ))); + } + } + + Ok((**return_ty).clone()) + } + + other => Err(TypeError::new(format!( + "{} is not a function, got {:?}", + label, other + ))), + } +} + +fn type_check_lambda_body( + params: &[(String, Type)], + return_tipo: &Type, + crp: &UncheckedStmt, + env: &Environment, +) -> Result { + let mut lambda_env = Environment::::new(); + lambda_env.restore(env.snapshot()); + + let mut seen_params = HashSet::new(); + + for (name, ty) in params.iter() { + if ty == &Type::Unit { + return Err(TypeError::new(format!( + "lambda parameter '{}' cannot have type void", + name + ))); + } + + if !seen_params.insert(name.clone()) { + return Err(TypeError::new(format!( + "duplicate lambda parameter: {}", + name + ))); + } + + lambda_env.declare(name.clone(), ty.clone()); + } + + type_check_stmt(crp, &mut lambda_env, return_tipo) +} + fn type_check_expr_to_typed( e: &UncheckedExpr, env: &Environment, @@ -407,30 +461,40 @@ fn type_check_expr_inner( )), Expr::Call { name, args } => { let args_checked: Result, _> = - args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + + let args_checked = args_checked?; + + check_call(name, &args_checked, env)?; + Ok(Expr::Call { name: name.clone(), - args: args_checked?, + 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(); + + let args_checked = args_checked?; + + type_of_function_call(&chmd_checked.ty, &args_checked, "function value")?; + Ok(Expr::CallExpr { chmd: Box::new(chmd_checked), - args: args_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)?; + Expr::Lambda { + params, + return_tipo, + crp, + } => { + let body_checked = type_check_lambda_body(params, return_tipo, crp, env)?; Ok(Expr::Lambda { params: params.clone(), @@ -438,6 +502,7 @@ fn type_check_expr_inner( crp: Box::new(body_checked), }) } + Expr::ArrayLit(elems) => { let elems_checked: Result, _> = elems.iter().map(|e| type_check_expr_to_typed(e, env)).collect(); @@ -515,79 +580,43 @@ fn type_check_expr( Expr::Call { name, args } => { let args_checked: Result, _> = args.iter().map(|a| type_check_expr_to_typed(a, env)).collect(); + let args_checked = args_checked?; - match env.get(name) { - Some(Type::Fun(param_tys, return_ty)) => { - if args_checked.len() != param_tys.len() { - return Err(TypeError::new(format!( - "function '{}' expects {} arguments, got {}", - name, - 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 {}: expected {:?}, got {:?}", - i + 1, - name, - param_ty, - arg.ty - ))); - } - } - Ok((**return_ty).clone()) - } - Some(_) => Err(TypeError::new(format!("'{}' is not a function", name))), - None => Err(TypeError::new(format!("undefined function: {}", name))), - } + + let callee_ty = env + .get(name) + .ok_or_else(|| TypeError::new(format!("undefined function: {}", name)))?; + + type_of_function_call( + callee_ty, + &args_checked, + &format!("function '{}'", name), + ) } + Expr::CallExpr { chmd, args } => { - let chmd_ty = type_check_expr(chmd, env)?; + 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(); + 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_of_function_call(&chmd_checked.ty, &args_checked, "function value") + } - type_check_stmt(crp, &mut lambda_env, return_tipo)?; + Expr::Lambda { + params, + return_tipo, + crp, + } => { + type_check_lambda_body(params, return_tipo, crp, env)?; 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")); diff --git a/tests/type_lambda_call.rs b/tests/type_lambda_call.rs index 2e9d50f..845f1bd 100644 --- a/tests/type_lambda_call.rs +++ b/tests/type_lambda_call.rs @@ -1,29 +1,107 @@ use mini_c::{parser::program, semantic::type_check}; fn type_check_src(src: &str) -> Result<(), String> { - let (_, unchecked) = program(src).map_err(|e| format!("parse error: {:?}", e))?; - type_check(&unchecked).map(|_| ()).map_err(|e| format!("type error: {}", e.message)) + let (rest, unchecked) = program(src) + .map_err(|e| format!("parse error: {:?}", e))?; + + if !rest.trim().is_empty() { + return Err(format!("unparsed input: {:?}", rest)); + } + + type_check(&unchecked) + .map(|_| ()) + .map_err(|e| format!("type error: {}", e.message)) } #[test] fn test_lambda_type_check_ok() { let src = r#" void main() { - fn(int) -> int f = fn(int x) -> int { return x * 2; }; + fn(int) -> int f = fn(int x) -> int { + return x * 2; + }; + int r = f(3); } "#; - assert!(type_check_src(src).is_ok(), "lambda should type-check"); + + assert!( + type_check_src(src).is_ok(), + "lambda should type-check" + ); } #[test] fn test_lambda_call_wrong_arg() { let src = r#" void main() { - fn(int) -> int f = fn(int x) -> int { return x; }; + fn(int) -> int f = fn(int x) -> int { + return x; + }; + f(true); } "#; + let res = type_check_src(src); - assert!(res.is_err(), "expected type error when calling with wrong arg type"); + + assert!( + res.is_err(), + "expected type error when calling with wrong arg type" + ); } + +#[test] +fn test_lambda_wrong_return_type() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return true; + }; + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when lambda returns bool instead of int" + ); +} + +#[test] +fn test_lambda_call_wrong_number_of_args() { + let src = r#" + void main() { + fn(int) -> int f = fn(int x) -> int { + return x; + }; + + f(); + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when calling function with wrong number of args" + ); +} + +#[test] +fn test_call_non_function_value() { + let src = r#" + void main() { + int x = 10; + x(1); + } + "#; + + let res = type_check_src(src); + + assert!( + res.is_err(), + "expected type error when calling a non-function value" + ); +} \ No newline at end of file From c532ad4c908dc95727d29c2b3f4cc48bd47c9966 Mon Sep 17 00:00:00 2001 From: AlvaroNegromonte Date: Sat, 6 Jun 2026 12:10:15 -0300 Subject: [PATCH 17/19] fix:[TypeChecker] Fixes non-function call error message --- src/semantic/type_checker.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index be5b2d4..f5409c7 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -359,10 +359,17 @@ fn type_of_function_call( Ok((**return_ty).clone()) } - other => Err(TypeError::new(format!( - "{} is not a function, got {:?}", - label, other - ))), + other => { + if label == "function value" { + Err(TypeError::new("attempting to call a non-function value")) + } + else { + Err(TypeError::new(format!( + "{} is not a function, got {:?}", + label, other + ))) + } + } } } From c76527f70fb2ed55ff85fc9251905741767cafa7 Mon Sep 17 00:00:00 2001 From: jambis-prg Date: Sat, 6 Jun 2026 15:24:50 -0300 Subject: [PATCH 18/19] [feat](interpreter/closures): implement lexical closures with snapshot capture and add interpreter tests --- src/interpreter/eval_expr.rs | 53 ++++++++++----- tests/interpreter_closure.rs | 123 ++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 19 deletions(-) diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index cc81025..03f0179 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -45,7 +45,6 @@ use crate::ir::ast::{CheckedExpr, Expr, Literal}; use super::exec_stmt::exec_stmt; use super::value::{FnValue, RuntimeError, Value}; -/// Evaluate a checked expression to a runtime value. pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result { match &expr.exp { Expr::Literal(lit) => Ok(eval_literal(lit)), @@ -146,18 +145,20 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result, RuntimeError> = args.iter().map(|a| eval_expr(a, env)).collect(); - let callee = env.get(name) + let callee = env + .get(name) .cloned() .ok_or_else(|| RuntimeError::new(format!("undefined function '{}'", name)))?; - eval_call_value(callee, arg_vals?, env) + eval_call_value(callee, arg_vals?, env, Some(name)) } 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) + + eval_call_value(callee_val, arg_vals?, env, None) } Expr::Lambda { params, return_tipo, crp } => { @@ -173,7 +174,6 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result, @@ -194,10 +194,16 @@ pub fn eval_call( 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)?; + + let result = exec_stmt(&decl.body, env); env.restore(snapshot); - Ok(result.unwrap_or(Value::Void)) + + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } } + Some(_) => Err(RuntimeError::new(format!("'{}' is not a function", name))), None => Err(RuntimeError::new(format!("undefined function '{}'", name))), } @@ -207,6 +213,7 @@ fn eval_call_value( callee: Value, args: Vec, env: &mut Environment, + callee_name: Option<&str>, ) -> Result { match callee { Value::Fn(FnValue::Native(f)) => (f)(args), @@ -220,19 +227,26 @@ fn eval_call_value( 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)?; + + let result = exec_stmt(&decl.body, env); env.restore(snapshot); - Ok(result.unwrap_or(Value::Void)) + + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } } Value::Fn(FnValue::Closure { decl, captured }) => { if args.len() != decl.params.len() { return Err(RuntimeError::new(format!( - "function expects {} arguments, got {}", + "function '{}' expects {} arguments, got {}", + decl.name, decl.params.len(), args.len() ))); @@ -240,23 +254,28 @@ fn eval_call_value( 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 + let result = exec_stmt(&decl.body, env); env.restore(caller_snapshot); - Ok(result.unwrap_or(Value::Void)) + match result { + Ok(result) => Ok(result.unwrap_or(Value::Void)), + Err(err) => Err(err), + } } - other => Err(RuntimeError::new(format!("'{}' is not a function", other))), + other => { + if let Some(name) = callee_name { + Err(RuntimeError::new(format!("'{}' is not a function", name))) + } else { + Err(RuntimeError::new(format!("value is not callable: {}", other))) + } + } } } diff --git a/tests/interpreter_closure.rs b/tests/interpreter_closure.rs index 89d16bb..ed642cc 100644 --- a/tests/interpreter_closure.rs +++ b/tests/interpreter_closure.rs @@ -7,7 +7,7 @@ fn run(src: &str) -> Result<(), String> { } #[test] -fn test_closure_snapshot() { +fn closure_snapshot_captures_creation_time_value() { let src = r#" void main() { int y = 10; @@ -16,5 +16,124 @@ fn test_closure_snapshot() { print(f(1)); } "#; - assert!(run(src).is_ok(), "closure should capture creation-time value"); + + assert!(run(src).is_ok()); +} + +#[test] +fn closures_capture_different_snapshots() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = + fn(int x) -> int { + return x + y; + }; + + y = 20; + + fn(int) -> int g = + fn(int x) -> int { + return x + y; + }; + + print(f(1)); + print(g(1)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closure_snapshot_captures_multiple_variables() { + let src = r#" + void main() { + int a = 2; + int b = 3; + + fn(int) -> int f = fn(int x) -> int { + return x + a + b; + }; + + a = 100; + b = 200; + print(f(1)); + } + "#; + + assert!(run(src).is_ok()); } + +#[test] +fn closure_called_through_variable_is_accepted() { + let src = r#" + void main() { + fn(int) -> int inc = fn(int x) -> int { return x + 1; }; + print(inc(41)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn closure_can_be_called_multiple_times() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = fn(int x) -> int { + return x + y; + }; + + print(f(1)); + print(f(2)); + print(f(3)); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn caller_environment_is_restored_after_closure_call() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int f = fn(int x) -> int { + return x + y; + }; + + print(f(1)); + y = 50; + print(y); + } + "#; + + assert!(run(src).is_ok()); +} + +#[test] +fn nested_closure_still_uses_captured_snapshot() { + let src = r#" + void main() { + int y = 10; + + fn(int) -> int make = fn(int x) -> int { + return x + y; + }; + + fn(int) -> int call = fn(int z) -> int { + return make(z); + }; + + y = 20; + print(call(1)); + } + "#; + + assert!(run(src).is_ok()); +} \ No newline at end of file From 27aaf7d9d8f21b4a99cade9dbe26a5af083c2c51 Mon Sep 17 00:00:00 2001 From: Sapo Sopa Date: Sun, 7 Jun 2026 21:51:57 -0300 Subject: [PATCH 19/19] Definition of the second milestone. --- docs/09-projects.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/09-projects.md b/docs/09-projects.md index 9e1cebf..f9f8f37 100644 --- a/docs/09-projects.md +++ b/docs/09-projects.md @@ -386,11 +386,11 @@ A good starting point for any of them is: ### Second Milestone: * Review the implementation of the type checker and interpreter - * Deadline: 07/06 + * Deadline: 14/06 ### Third Milestone: * Review the implementation of the type code generator * Deadline: 30/06 -The outcomes of the projects must be submitted via pull-requests. +The outcomes of the projects must be submitted via pull-requests. \ No newline at end of file