Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8023c54
ast: adiciona lambda, call por expressão e decl sem init
SapoSopa Apr 14, 2026
26d45ea
ast: comentario ajustado
SapoSopa Apr 14, 2026
346b025
feat(project5): funções de primeira classe (tipos fn, lambdas, call p…
SapoSopa Apr 14, 2026
0862d5a
feat(project5): implementação de function type parsing com validação …
vsr87 Apr 14, 2026
e75c651
test(parser/project5): adicionando testes para function type parsing
vsr87 Apr 14, 2026
17f297d
feat(parser): add function type support to declarations and function …
AlvaroNegromonte Apr 15, 2026
59bb468
test(parser): add integration tests for function types
AlvaroNegromonte Apr 15, 2026
a1ac64a
[fix](parser/expressions, parser/statements): enforce block-only body…
Apr 17, 2026
3922e08
test: add integration tests for lambda functions
GabrielVMayerhofer Apr 18, 2026
f55bfcd
[fix](interpreter/eval_expr, semantic/type_checker): support lambda c…
jambis-prg Apr 19, 2026
b54d831
[test](interpreter/lambda): interpreter lambda tests are now passing
jambis-prg Apr 19, 2026
dfb0a5d
Definition of the second milestone.
SapoSopa Jun 1, 2026
2146f10
docs + tests: formaliza semântica de funções de 1ª classe e adiciona …
SapoSopa Jun 3, 2026
c9dd8c3
feat(interpreter: implement runtime de funoes e lambdas
GabrielVMayerhofer Jun 4, 2026
2f7fc52
feat: implement type compatibility and uninitialized declarations for…
vsr87 Jun 6, 2026
3926948
[TypeChecker] Check lambda and function-value calls
AlvaroNegromonte Jun 6, 2026
b460eaf
Merge pull request #2 from SapoSopa/feat/type-lambda-and-call
AlvaroNegromonte Jun 6, 2026
c532ad4
fix:[TypeChecker] Fixes non-function call error message
AlvaroNegromonte Jun 6, 2026
c76527f
[feat](interpreter/closures): implement lexical closures with snapsho…
jambis-prg Jun 6, 2026
85bcac0
Merge pull request #3 from SapoSopa/feat/closure_environments
SapoSopa Jun 8, 2026
27aaf7d
Definition of the second milestone.
SapoSopa Jun 8, 2026
d767550
Merge remote-tracking branch 'upstream/main' into milestone-3-TAC
SapoSopa Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/09-projects01.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,4 @@ A good starting point for any of them is:
* 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.
77 changes: 77 additions & 0 deletions docs/semantics-first-class-fn.md
Original file line number Diff line number Diff line change
@@ -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<Param>, // nomes + tipos
return_type: Type,
body: Statement, // AST do corpo
captured_env: Environment<Value>, // 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)
111 changes: 105 additions & 6 deletions src/interpreter/eval_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value>) -> Result<Value, RuntimeError> {
match &expr.exp {
Expr::Literal(lit) => Ok(eval_literal(lit)),
Expand Down Expand Up @@ -145,12 +144,36 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment<Value>) -> Result<Val
Expr::Call { name, args } => {
let arg_vals: Result<Vec<Value>, 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, Some(name))
}

Expr::CallExpr { chmd, args } => {
let callee_val = eval_expr(chmd, env)?;
let arg_vals: Result<Vec<Value>, RuntimeError> =
args.iter().map(|a| eval_expr(a, env)).collect();

eval_call_value(callee_val, arg_vals?, env, None)
}

Expr::Lambda { params, return_tipo, crp } => {
let captured = env.snapshot();
let decl = crate::ir::ast::CheckedFunDecl {
name: "<lambda>".to_string(),
params: params.clone(),
return_type: return_tipo.clone(),
body: crp.clone(),
};
Ok(Value::Fn(FnValue::Closure { decl, captured }))
}
}
}

/// Dispatch a function call via the unified environment.
pub fn eval_call(
name: &str,
args: Vec<Value>,
Expand All @@ -171,15 +194,91 @@ 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))),
}
}

fn eval_call_value(
callee: Value,
args: Vec<Value>,
env: &mut Environment<Value>,
callee_name: Option<&str>,
) -> Result<Value, RuntimeError> {
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);

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 {}",
decl.name,
decl.params.len(),
args.len()
)));
}

let caller_snapshot = env.snapshot();

env.restore(captured);

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(caller_snapshot);

match result {
Ok(result) => Ok(result.unwrap_or(Value::Void)),
Err(err) => Err(err),
}
}

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)))
}
}
}
}

// --- Helpers ---

fn eval_literal(lit: &Literal) -> Value {
Expand Down Expand Up @@ -237,4 +336,4 @@ fn values_equal(a: &Value, b: &Value) -> bool {
(Value::Str(x), Value::Str(y)) => x == y,
_ => false,
}
}
}
6 changes: 5 additions & 1 deletion src/interpreter/exec_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment<Value>) -> ExecResult
match &stmt.stmt {
// --- Variable declaration ---
Statement::Decl { name, init, .. } => {
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)
}
Expand Down
10 changes: 9 additions & 1 deletion src/interpreter/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -72,13 +73,19 @@ pub type NativeFn = fn(Vec<Value>) -> Result<Value, RuntimeError>;
pub enum FnValue {
UserDefined(CheckedFunDecl),
Native(NativeFn),

Closure {
decl: CheckedFunDecl,
captured: HashMap<String, Value>,
},
}

impl PartialEq for FnValue {
fn eq(&self, other: &Self) -> bool {
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,
}
}
Expand All @@ -89,6 +96,7 @@ impl fmt::Debug for FnValue {
match self {
FnValue::UserDefined(decl) => write!(f, "UserDefined({})", decl.name),
FnValue::Native(_) => write!(f, "Native(<fn ptr>)"),
FnValue::Closure { decl, .. } => write!(f, "Closure({})", decl.name),
}
}
}
Expand Down Expand Up @@ -148,4 +156,4 @@ impl fmt::Display for RuntimeError {
}
}

impl std::error::Error for RuntimeError {}
impl std::error::Error for RuntimeError {}
22 changes: 21 additions & 1 deletion src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@ pub enum Expr<Ty> {
name: String,
args: Vec<ExprD<Ty>>,
},

/// 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<ExprD<Ty>>,
args: Vec<ExprD<Ty>>,
},

/// 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<Param>,
return_tipo: Type,
crp: Box<StatementD<Ty>>,
},

/// Array literal: [ expr, expr, ... ]
ArrayLit(Vec<ExprD<Ty>>),
/// Index expression: `base[index]`
Expand All @@ -128,7 +147,8 @@ pub enum Statement<Ty> {
Decl {
name: String,
ty: Type,
init: Box<ExprD<Ty>>,
/// Adicionado para suportar algo como 'fn(int) -> int f;'
init: Option<Box<ExprD<Ty>>>,
},
Assign {
target: Box<ExprD<Ty>>,
Expand Down
Loading