Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
use flake

# Add scripts directory to PATH for convenient access to minic wrapper
PATH_add "$(pwd)/scripts"
2 changes: 2 additions & 0 deletions scripts/minic
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
exec "$(dirname "$0")/../target/debug/mini_c" "$@"
38 changes: 38 additions & 0 deletions src/codegen/tac_code_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,44 @@ pub fn translate_statement(statement: CheckedStmt, env: &mut Environment) -> Vec
instructions.push(Instruction::Label(label_end_if));
instructions
},
Statement::Switch { target, cases, default } => {
let target_ty = target.ty.clone();
let (target_addr, mut instructions) = translate_expression(*target, env);
let label_default = env.new_label();
let label_end = env.new_label();

let case_labels: Vec<String> = (0..cases.len())
.map(|_| env.new_label())
.collect();

for (i, (lit, _)) in cases.iter().enumerate() {
let lit_addr = Address::Constant(lit.clone(), target_ty.clone());
instructions.push(Instruction::ConditionalJMPRelational(
Operator::EQ,
target_addr.clone(),
lit_addr,
case_labels[i].clone(),
));
}

instructions.push(Instruction::JMP(label_default.clone()));

for (i, (_, body)) in cases.into_iter().enumerate() {
instructions.push(Instruction::Label(case_labels[i].clone()));
for stmt in body {
instructions.extend(translate_statement(stmt, env));
}
instructions.push(Instruction::JMP(label_end.clone()));
}

instructions.push(Instruction::Label(label_default));
for stmt in default {
instructions.extend(translate_statement(stmt, env));
}

instructions.push(Instruction::Label(label_end));
instructions
},
_ => todo!()
}
}
Expand Down
49 changes: 48 additions & 1 deletion src/interpreter/exec_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
//! This gives MiniC correct lexical block scoping without a scope stack.

use crate::environment::Environment;
use crate::ir::ast::{CheckedExpr, CheckedStmt, Expr, Statement};
use crate::ir::ast::{CheckedExpr, CheckedStmt, Expr, Literal, Statement};

use super::eval_expr::{eval_call, eval_expr};
use super::value::{RuntimeError, Value};
Expand Down Expand Up @@ -111,6 +111,53 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment<Value>) -> ExecResult
}
}
},

// --- Switch ---
Statement::Switch { target, cases, default } => {
// Detecta labels duplicadas como erro
let mut seen_cases = Vec::new();
for (lit, _) in cases {
if seen_cases.contains(lit) {
return Err(RuntimeError::new(format!(
"duplicate case label in switch: {:?}",
lit
)));
}
seen_cases.push(lit.clone());
}

// Avalia o valor da expressão alvo
let target_val = eval_expr(target, env)?;

// Determina qual bloco de comandos executar (Padrão: default)
let mut stmts_to_exec = default;

for (lit, stmts) in cases {
let case_matches = match (lit, &target_val) {
(Literal::Int(i), Value::Int(v)) => *i == *v,
(Literal::Bool(b1), Value::Bool(b2)) => *b1 == *b2,
_ => false,
};

if case_matches {
stmts_to_exec = stmts; // Substitui o bloco pelo case correspondente
break; // Sai do loop após encontrar o case correspondente
}
}

// Executa o bloco escolhido isolando o escopo do switch
let outer_keys = env.names();

for stmt in stmts_to_exec {
if let Some(ret) = exec_stmt(stmt, env)? {
env.remove_new(&outer_keys); // Limpa o escopo se houver um 'return' no meio
return Ok(Some(ret));
}
}

env.remove_new(&outer_keys); // Limpa o escopo no final da execução normal
Ok(None)
},

// --- Return ---
Statement::Return(Some(expr)) => {
Expand Down
7 changes: 6 additions & 1 deletion src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! * [`Literal`] — a constant value written directly in source code.
//! * [`Expr`] / [`ExprD`] — expressions (arithmetic, comparisons, calls, …).
//! * [`Statement`] / [`StatementD`] — statements (declarations, assignments,
//! `if`, `while`, `return`, blocks).
//! `if`, `while`, `switch`, `return`, blocks).
//! * [`FunDecl`] — a single function declaration with its body.
//! * [`Program`] — the top-level container: a list of function declarations.
//!
Expand Down Expand Up @@ -151,6 +151,11 @@ pub enum Statement<Ty> {
cond: Box<ExprD<Ty>>,
body: Box<StatementD<Ty>>,
},
Switch {
target: Box<ExprD<Ty>>,
cases: Vec<(Literal, Vec<StatementD<Ty>>)>,
default: Vec<StatementD<Ty>>,
},
/// Return statement: `return [expr]`.
Return(Option<Box<ExprD<Ty>>>),
}
Expand Down
58 changes: 52 additions & 6 deletions src/parser/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
//! Exposes two public functions:
//!
//! * [`statement`] — the top-level entry point; tries each statement form in
//! order: `return`, `if`, `while`, call-statement, block, declaration,
//! order: `return`, `if`, `while`, `switch`, call-statement, block, declaration,
//! assignment.
//! * [`assignment`] — parses `lvalue = expression ;`; exported separately
//! because the test suite uses it directly.
//!
//! # Grammar
//!
//! ```text
//! statement := block | if_stmt | while_stmt | simple ';'
//! statement := block | if_stmt | while_stmt | switch_stmt | simple ';'
//! block := '{' statement* '}'
//! if_stmt := 'if' expr block ['else' block]
//! while_stmt := 'while' expr block
//! switch (expr) { case literal: statement+; … default: statement+ }
//! simple := return | decl | call | assign
//! ```
//!
//! Every simple statement is terminated by `;`.
//! Compound statements (`if`, `while`, block) end with `}` and need no `;`.
//! Compound statements (`if`, `while`, `switch`, block) end with `}` and need no `;`.
//!
//! # Design Decisions
//!
Expand All @@ -40,16 +41,17 @@
//! suffixes in a loop using the same pattern as the `primary` parser in
//! `expressions.rs`, producing a left-associative `Index` chain.

use crate::ir::ast::{Expr, ExprD, Statement, StatementD, UncheckedExpr, UncheckedStmt};
use crate::ir::ast::{Literal, Expr, ExprD, Statement, StatementD, UncheckedExpr, UncheckedStmt};
use crate::parser::expressions::{expression, parse_call};
use crate::parser::functions::type_name;
use crate::parser::identifiers::identifier;
use crate::parser::literals::{integer_literal, boolean_literal};
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{char, multispace0},
combinator::{map, opt},
multi::many0,
multi::{many0, many1},
sequence::{delimited, preceded, tuple},
IResult,
};
Expand All @@ -58,14 +60,15 @@ fn wrap(s: Statement<()>) -> UncheckedStmt {
StatementD { stmt: s, ty: () }
}

/// Parse any statement: block | if | while | return | decl | call | assignment.
/// Parse any statement: block | if | while | switch | return | decl | call | assignment.
pub fn statement(input: &str) -> IResult<&str, UncheckedStmt> {
preceded(
multispace0,
alt((
block_statement,
if_statement,
while_statement,
switch_statement,
return_statement,
decl_statement,
call_statement,
Expand Down Expand Up @@ -160,6 +163,49 @@ fn while_statement(input: &str) -> IResult<&str, UncheckedStmt> {
))
}

/// Parse a switch statement: `switch (expr) { case literal: statement+; … default: statement+ }`.
fn switch_statement(input: &str) -> IResult<&str, UncheckedStmt> {
let (rest, _) = preceded(multispace0, tag("switch"))(input)?;
let (rest, target) = preceded(multispace0, expression)(rest)?;
let (rest, _) = preceded(multispace0, char('{'))(rest)?;

let (rest, cases) = many1(map(
tuple((
preceded(multispace0, tag("case")),
preceded(
multispace0,
alt((
map(integer_literal, |i| Literal::Int(i)),
map(boolean_literal, |b| Literal::Bool(b))
))
),
preceded(multispace0, char(':')),
many1(preceded(multispace0, statement)),
)),
|(_, literal, _, statements)| (literal, statements),
))(rest)?;

let (rest, default) = preceded(multispace0, map(
tuple((
preceded(multispace0, tag("default")),
preceded(multispace0, char(':')),
many1(preceded(multispace0, statement)),
)),
|(_, _, statements)| statements,
))(rest)?;

let (rest, _) = preceded(multispace0, char('}'))(rest)?;

Ok((
rest,
wrap(Statement::Switch {
target: Box::new(target),
cases,
default,
}),
))
}

/// Parse an lvalue: identifier followed by zero or more `[ expr ]` suffixes.
fn lvalue(input: &str) -> IResult<&str, UncheckedExpr> {
let (mut rest, id) = preceded(multispace0, identifier)(input)?;
Expand Down
71 changes: 71 additions & 0 deletions src/semantic/type_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,77 @@ fn type_check_stmt(
body: Box::new(body_checked),
}
}
Statement::Switch { target, cases, default } => {
// Verifica o tipo da expressão alvo (target)
let target_checked = type_check_expr_to_typed(target, env)?;

match target_checked.ty {
Type::Int | Type::Bool => {}
_ => {
return Err(TypeError::new(format!(
"switch target must be Int or Bool, got {:?}",
target_checked.ty
)));
}
}

// Verifica cada um dos cases
let mut checked_cases = Vec::new();
let mut seen_cases = Vec::new();
for (lit, stmts) in cases {
if seen_cases.contains(lit) {
return Err(TypeError::new(format!(
"duplicate case label in switch: {:?}",
lit
)));
}
seen_cases.push(lit.clone());

// Descobre o tipo do literal do case atual
let lit_ty = match lit {
Literal::Int(_) => Type::Int,
Literal::Bool(_) => Type::Bool,
_ => {
return Err(TypeError::new(format!(
"switch case literal type not supported: {:?}",
lit
)));
}
};

// Garante que o tipo do literal é compatível com o tipo do target
if !types_compatible(&target_checked.ty, &lit_ty) {
return Err(TypeError::new(format!(
"switch case literal type mismatch: expected {:?}, got {:?}",
target_checked.ty, lit_ty
)));
}

// Cria um novo escopo para as instruções deste case
let snapshot = env.snapshot();
let mut checked_stmts = Vec::new();
for stmt in stmts {
checked_stmts.push(type_check_stmt(stmt, env, expected_return)?);
}
env.restore(snapshot);

checked_cases.push((lit.clone(), checked_stmts));
}

// Verifica o ramo padrão (default)
let snapshot = env.snapshot();
let mut checked_default = Vec::new();
for stmt in default {
checked_default.push(type_check_stmt(stmt, env, expected_return)?);
}
env.restore(snapshot);

Statement::Switch {
target: Box::new(target_checked),
cases: checked_cases,
default: checked_default,
}
}
Statement::Return(expr) => match expr {
None => {
if *expected_return != Type::Unit {
Expand Down
Loading