diff --git a/flake.nix b/flake.nix index fd81e57..755cce4 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ ]; shellHook = '' - export PS1="MiniC ❄️ > " + export PS1="MiniC ❄️ \[\033[01;34m\]\w\[\033[00m\] > " ''; }; } diff --git a/openspec/specs/pointers/spec.md b/openspec/specs/pointers/spec.md new file mode 100644 index 0000000..2768c58 --- /dev/null +++ b/openspec/specs/pointers/spec.md @@ -0,0 +1,109 @@ +# Pointers + +## Purpose + +Document MiniC **pointer syntax** and **intended usage**: pointer types (`T*`), +address-of (`&expr`), dereference (`*expr`), assignment through a dereference +(`*expr = …`), and pointer-typed parameters and return values. + +## Requirements + +### Requirement: Pointer type notation + +The parser SHALL recognise pointer types written as a scalar type name +immediately followed by `*`, with no space between them: `int*`, `float*`, +`bool*`, `str*`. Function parameters, return types, and local declarations +SHALL use this same spelling (as in the three fixture programs). + +#### Scenario: Declaration with pointer type + +- **WHEN** the source contains `int* y = &x` or `float* a_ref = &a` +- **THEN** the parser SHALL succeed and associate the declared name with + `Type::Pointer` to the corresponding scalar type + +#### Scenario: Function signature with pointers + +- **WHEN** the source contains `void increment(int* p)` or + `int* changeRef(int* x, int* y)` +- **THEN** the parser SHALL succeed with each pointer parameter typed as + `Pointer` to the inner scalar type + +--- + +### Requirement: Address-of expression + +The parser SHALL recognise the unary prefix `&` applied to an expression, +producing `Expr::AddrOf`. The fixtures use `&` only on simple identifiers +(`&x`, `&a`, …), which is the intended teaching subset. + +#### Scenario: Initialisation from address of variable + +- **WHEN** the input is `int* y = &x` or `bool* d_ref = &d` +- **THEN** the parser SHALL succeed with the initialiser as `AddrOf` wrapping + the identifier expression + +#### Scenario: Address passed to a function + +- **WHEN** the input is `increment(&x)` as in `pointer_feature.minic` +- **THEN** the parser SHALL succeed with the call argument as `AddrOf` + +--- + +### Requirement: Dereference expression + +The parser SHALL recognise the unary prefix `*` as dereference (same lexical +token as multiplication, resolved in the unary layer), producing `Expr::Deref`. + +#### Scenario: Dereference on the right-hand side + +- **WHEN** the input is `*p + 1` as in `pointer_feature.minic` +- **THEN** the parser SHALL succeed with `Deref` applied to the operand of `+` + as appropriate for unary-before-additive precedence + +#### Scenario: Nested dereference in assignment target + +- **WHEN** the input is `*p = *p + 1` +- **THEN** the parser SHALL succeed with assignment whose target expression is + `Deref` and whose value expression uses `Deref` on the same pointer + +--- + +### Requirement: Assignment to a dereference + +The parser SHALL accept assignment statements whose target is a dereference +expression `*expr`, as in `*p = *p + 1`. + +#### Scenario: Mutate through pointer parameter + +- **WHEN** the statement is `*p = *p + 1` inside `increment` in + `pointer_feature.minic` +- **THEN** the parser SHALL produce `Stmt::Assign` with a `Deref` target + +--- + +### Requirement: Return type and return value as pointer + +The parser SHALL allow a function to declare a pointer return type and +`return` an expression of pointer type, as in `changeRef` in +`pointer_function.minic`. + +#### Scenario: Return pointer from function + +- **WHEN** the function is `int* changeRef(int* x, int* y) { … return x; }` +- **THEN** the parser SHALL succeed with return type `Pointer(Int)` and a + return statement carrying the pointer expression + +--- + +### Requirement: Assignment between pointer variables + +The parser SHALL allow assignment where both sides are pointer-typed +expressions (e.g. `x = y` when `x` and `y` are `int*`), as in the body of +`changeRef` in `pointer_function.minic`. + +#### Scenario: Rebind pointer parameter + +- **WHEN** the statement is `x = y` with `x` and `y` declared as `int*` + parameters +- **THEN** the parser SHALL succeed with `Stmt::Assign` and identifier + expressions for `x` and `y` \ No newline at end of file diff --git a/src/codegen/tac_code_gen.rs b/src/codegen/tac_code_gen.rs index 2f5e6ee..a7b1171 100644 --- a/src/codegen/tac_code_gen.rs +++ b/src/codegen/tac_code_gen.rs @@ -27,6 +27,7 @@ impl Environment { } } +#[allow(dead_code)] fn translate_program(program: CheckedProgram, env: &mut Environment) -> TACProgram { let main_fn = program.main_function(); match main_fn { @@ -36,6 +37,7 @@ fn translate_program(program: CheckedProgram, env: &mut Environment) -> TACProgr } +#[allow(dead_code)] fn translate_function(function: CheckedFunDecl, env: &mut Environment) -> TACProgram { let mut instructions = if let Statement::Block { seq : stmts } = function.body.stmt { @@ -55,16 +57,24 @@ pub fn translate_statement(statement: CheckedStmt, env: &mut Environment) -> Vec seq.into_iter().flat_map(|s| translate_statement(s, env)).collect::>() }, Statement::Assign { target, value } => { - if let Expr::Ident(name) = &target.exp { - let var_type = target.ty.clone(); - let var_address = Address::Variable(name.to_string(), var_type); - let (expression_address, instructions) = translate_expression(*value, env); - res.extend(instructions); - res.push(Instruction::CopyAssignment(var_address, expression_address)); - res - } - else { - todo!() + match target.exp { + Expr::Ident(name) => { + let var_addr = Address::Variable(name.to_string(), target.ty); + let (val_addr, instrs) = translate_expression(*value, env); + res.extend(instrs); + res.push(Instruction::CopyAssignment(var_addr, val_addr)); + res + }, + Expr::Deref(inner) => { + let (ptr_addr, ptr_instrs) = translate_expression(*inner, env); + let (val_addr, val_instrs) = translate_expression(*value, env); + res.extend(ptr_instrs); + res.extend(val_instrs); + res.push(Instruction::DerefWrite(ptr_addr, val_addr)); + res + }, + + _ => todo!() } }, Statement::Call{name, args} => { @@ -164,7 +174,19 @@ fn translate_expression(expression: CheckedExpr, env: &mut Environment) -> (Addr let temp = Address::Temporary(env.new_temporary(), expression.ty); instructions.push(Instruction::BinaryAssignment(Operator::Add, temp.clone(), l_addr, r_addr)); (temp, instructions) - } + }, + Expr::AddrOf(inner) => { + let (inner_addr, mut instructions) = translate_expression(*inner, env); + let temp = Address::Temporary(env.new_temporary(), expression.ty); + instructions.push(Instruction::AddressOf(temp.clone(), inner_addr)); + (temp, instructions) + }, + Expr::Deref(inner) => { + let (inner_addr, mut instructions) = translate_expression(*inner, env); + let temp = Address::Temporary(env.new_temporary(), expression.ty); + instructions.push(Instruction::DerefRead(temp.clone(), inner_addr)); + (temp, instructions) + }, _ => todo!() } } diff --git a/src/environment/env.rs b/src/environment/env.rs index dd83ba1..efbd104 100644 --- a/src/environment/env.rs +++ b/src/environment/env.rs @@ -53,59 +53,77 @@ //! language. The cost — cloning the entire map on each function call — is //! acceptable at MiniC's scale. -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap}; -/// Unified parametric environment: maps names to values of type `V`. -/// Both variable bindings and function bindings are stored in the same map. +/// Unified parametric environment representing program state. +/// It separates lexical bindings (`scopes`) from physical memory (`store`). pub struct Environment { - bindings: HashMap, + scopes: HashMap, + store: HashMap, + next_address: usize, } impl Environment { pub fn new() -> Self { Self { - bindings: HashMap::new(), + scopes: HashMap::new(), + store: HashMap::new(), + next_address: 1, } } - /// Bind `name` to `value`, overwriting any existing binding. + /// Allocates a new address in the store for `value`, and binds `name` to this address. pub fn declare(&mut self, name: impl Into, value: V) { - self.bindings.insert(name.into(), value); + let addr = self.next_address; + self.next_address += 1; + self.store.insert(addr, value); + self.scopes.insert(name.into(), addr); } - /// Look up a binding by name. + /// Looks up the value currently bound to `name` by resolving its physical address. pub fn get(&self, name: &str) -> Option<&V> { - self.bindings.get(name) + let addr = self.scopes.get(name)?; + self.store.get(addr) } - /// Update an existing binding. Returns `false` if the name is not bound. + /// Updates the value at the address currently bound to `name`. Returns `false` if the name is not found. pub fn set(&mut self, name: &str, value: V) -> bool { - if self.bindings.contains_key(name) { - self.bindings.insert(name.to_string(), value); + if let Some(&addr) = self.scopes.get(name) { + self.store.insert(addr, value); true } else { false } } - /// Capture a full clone of the current bindings (for function call scoping). - pub fn snapshot(&self) -> HashMap { - self.bindings.clone() + /// Returns the physical address (usize) bound to `name`. + pub fn get_address(&self, name: &str) -> Option { + self.scopes.get(name).copied() } - /// Replace all bindings with the given snapshot (for function call scoping). - pub fn restore(&mut self, snapshot: HashMap) { - self.bindings = snapshot; + /// Reads a value directly from the memory store using its physical address. + pub fn read_store(&self, addr: usize) -> Option<&V> { + self.store.get(&addr) } - /// Return the set of currently bound names (for block-entry capture). - pub fn names(&self) -> HashSet { - self.bindings.keys().cloned().collect() + /// Writes a value directly to the memory store at the given physical address. + pub fn write_store(&mut self, addr: usize, value: V) -> bool { + if self.store.contains_key(&addr) { + self.store.insert(addr, value); + true + } else { + false + } + } + + /// Captures a clone of the current lexical scope (names to addresses). + pub fn snapshot(&self) -> HashMap { + self.scopes.clone() } - /// Remove any binding whose name is not in `outer` (for block-exit cleanup). - pub fn remove_new(&mut self, outer: &HashSet) { - self.bindings.retain(|k, _| outer.contains(k)); + /// Replaces the current lexical scope with a previously saved snapshot. + pub fn restore(&mut self, snapshot: HashMap) { + self.scopes = snapshot; } } diff --git a/src/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 49fcbef..627576c 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -147,6 +147,32 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result eval_addr_of(elem, env), + Expr::Deref(elem) => eval_deref(elem, env), + } +} + +fn eval_addr_of(elem: &CheckedExpr, env: &Environment) -> Result { + match &elem.exp { + Expr::Ident(name) => { + if let Some(addr) = env.get_address(name) { + Ok(Value::Ptr(addr)) + } else { + Err(RuntimeError::new(format!("undefined variable '{}'", name))) + } + } + _ => Err(RuntimeError::new("can only take address of variables")), + } +} + +fn eval_deref(elem: &CheckedExpr, env: &mut Environment) -> Result { + let ptr_val = eval_expr(elem, env)?; + if let Value::Ptr(addr) = ptr_val { + env.read_store(addr).cloned().ok_or_else(|| { + RuntimeError::new(format!("dereference of invalid address '{}'", addr)) + }) + } else { + Err(RuntimeError::new("cannot dereference non-pointer value")) } } @@ -167,12 +193,17 @@ pub fn eval_call( 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)) } Some(_) => Err(RuntimeError::new(format!("'{}' is not a function", name))), @@ -235,6 +266,7 @@ fn values_equal(a: &Value, b: &Value) -> bool { (Value::Float(x), Value::Int(y)) => *x == (*y as f64), (Value::Bool(x), Value::Bool(y)) => x == y, (Value::Str(x), Value::Str(y)) => x == y, + (Value::Ptr(x), Value::Ptr(y)) => x == y, _ => false, } } diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs index ceeda2c..62932eb 100644 --- a/src/interpreter/exec_stmt.rs +++ b/src/interpreter/exec_stmt.rs @@ -63,14 +63,16 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment) -> ExecResult // Only remove variables declared inside the block on exit. // Assignments to outer-scope variables must persist (e.g., loop counters). Statement::Block { seq } => { - let outer_keys = env.names(); - for s in seq { - if let Some(ret) = exec_stmt(s, env)? { - env.remove_new(&outer_keys); - return Ok(Some(ret)); + let snapshot = env.snapshot(); + + for stmt in seq { + if let Some(val) = exec_stmt(stmt, env)? { + env.restore(snapshot.clone()); + return Ok(Some(val)); } } - env.remove_new(&outer_keys); + + env.restore(snapshot); Ok(None) } @@ -158,6 +160,18 @@ fn assign_lvalue( }; assign_index(base, idx, val, env) } + Expr::Deref(inner) => { + let ptr_val = eval_expr(inner, env)?; + if let Value::Ptr(addr) = ptr_val { + if env.write_store(addr, val) { + Ok(()) + } else { + Err(RuntimeError::new(format!("invalid memory address '{}'", addr))) + } + } else { + Err(RuntimeError::new("cannot assign to dereference of non-pointer")) + } + } _ => Err(RuntimeError::new("invalid assignment target".to_string())), } } diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 2abdc6e..5dcda9d 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -28,6 +28,7 @@ //! Value::Array([...]) — a list of Values //! Value::Void — no value (returned by void functions) //! Value::Fn(FnValue::...) — a callable function +//! Value::Ptr("x") — pointer to variable `x` (name-based indirection) //! ``` //! //! This is the idiomatic Rust approach to *tagged unions* — a value that can @@ -103,6 +104,8 @@ pub enum Value { Array(Vec), Void, Fn(FnValue), + /// Pointer: holds the name of the target variable in the environment. + Ptr(usize), } impl fmt::Display for Value { @@ -124,6 +127,7 @@ impl fmt::Display for Value { write!(f, "]") } Value::Fn(_) => write!(f, ""), + Value::Ptr(target) => write!(f, "&{}", target), } } } diff --git a/src/ir/ast.rs b/src/ir/ast.rs index fc400dd..602c6a3 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -58,6 +58,7 @@ pub enum Type { Str, Array(Box), Fun(Vec, Box), + Pointer(Box), /// Matches any type. Only used as a parameter type in native stdlib registrations. Any, } @@ -112,6 +113,9 @@ pub enum Expr { base: Box>, index: Box>, }, + /// Pointer operations + AddrOf(Box>), + Deref(Box>), } /// Statement with type decoration. diff --git a/src/ir/tac.rs b/src/ir/tac.rs index 92e9623..908586c 100644 --- a/src/ir/tac.rs +++ b/src/ir/tac.rs @@ -26,6 +26,9 @@ pub enum Instruction { Store(Address, Address, Address), // x[i] = y Load(Address, Address, Address), // x = y[i] Return(Option
), + AddressOf(Address, Address), // x = &y + DerefRead(Address, Address), // x = *y + DerefWrite(Address, Address), // *x = y } #[derive(Debug, Clone, PartialEq)] diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 8cfcab4..b6f8b1f 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -119,6 +119,12 @@ fn unary(input: &str) -> IResult<&str, UncheckedExpr> { map(pair(preceded(multispace0, tag("-")), unary), |(_, e)| { wrap(Expr::Neg(Box::new(e))) }), + map(pair(preceded(multispace0, tag("&")), unary), |(_, e)| { + wrap(Expr::AddrOf(Box::new(e))) + }), + map(pair(preceded(multispace0, tag("*")), unary), |(_, e)| { + wrap(Expr::Deref(Box::new(e))) + }), primary, ))(input) } diff --git a/src/parser/functions.rs b/src/parser/functions.rs index 845a59c..235c6f0 100644 --- a/src/parser/functions.rs +++ b/src/parser/functions.rs @@ -48,6 +48,10 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { map(tag("float[]"), |_| Type::Array(Box::new(Type::Float))), map(tag("bool[]"), |_| Type::Array(Box::new(Type::Bool))), map(tag("str[]"), |_| Type::Array(Box::new(Type::Str))), + map(tag("int*"), |_| Type::Pointer(Box::new(Type::Int))), + map(tag("float*"), |_| Type::Pointer(Box::new(Type::Float))), + map(tag("bool*"), |_| Type::Pointer(Box::new(Type::Bool))), + map(tag("str*"), |_| Type::Pointer(Box::new(Type::Str))), map(tag("int"), |_| Type::Int), map(tag("float"), |_| Type::Float), map(tag("bool"), |_| Type::Bool), diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 9dcfef5..5a3c228 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -162,11 +162,22 @@ fn while_statement(input: &str) -> IResult<&str, UncheckedStmt> { /// 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)?; - let mut acc = ExprD { - exp: Expr::Ident(id.to_string()), - ty: (), - }; + let (mut rest, mut acc) = preceded( + multispace0, + alt(( + map(preceded(tag("*"), identifier), |id| ExprD { + exp: Expr::Deref(Box::new(ExprD { + exp: Expr::Ident(id.to_string()), + ty: (), + })), + ty: (), + }), + map(identifier, |id| ExprD { + exp: Expr::Ident(id.to_string()), + ty: (), + }), + )), + )(input)?; loop { let index_parse = delimited( preceded(multispace0, char('[')), @@ -206,4 +217,4 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> { }) }, )(input) -} +} \ No newline at end of file diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index e17c4b3..6380ce7 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -123,7 +123,7 @@ pub fn type_check(program: &UncheckedProgram) -> Result, - fn_snapshot: &HashMap, + fn_snapshot: &HashMap, ) -> Result { // Restore to clean function-only state, then add parameters. env.restore(fn_snapshot.clone()); @@ -314,6 +314,43 @@ fn type_check_assign_target( } Ok(()) } + Expr::Deref(inner) => { + let inner_ty = type_check_expr(inner, env)?; + match inner_ty { + Type::Pointer(base_ty) => { + if !types_compatible(value_ty, &base_ty) { + return Err(TypeError::new(format!( + "assignment through pointer: expected {:?}, got {:?}", + base_ty, + value_ty + ))); + } + Ok(()) + } + other => Err(TypeError::new(format!( + "cannot dereference non-pointer type: {:?}", + other + ))), + } + }, + Expr::AddrOf(inner) => { + let inner_ty = type_check_expr(inner, env)?; + match &inner.exp { + Expr::Ident(_) => { + if !types_compatible(&Type::Pointer(Box::new(inner_ty.clone())), value_ty) { + return Err(TypeError::new(format!( + "assignment to address-of: expected {:?}, got {:?}", + Type::Pointer(Box::new(inner_ty)), + value_ty + ))); + } + Ok(()) + } + _ => Err(TypeError::new( + "can only take address of variables" + )), + } + }, Expr::Index { base, index } => { let index_ty = type_check_expr(index, env)?; if index_ty != Type::Int { @@ -416,6 +453,8 @@ fn type_check_expr_inner( base: Box::new(type_check_expr_to_typed(base, env)?), index: Box::new(type_check_expr_to_typed(index, env)?), }), + Expr::AddrOf(elem) => Ok(Expr::AddrOf(Box::new(type_check_expr_to_typed(elem, env)?))), + Expr::Deref(elem) => Ok(Expr::Deref(Box::new(type_check_expr_to_typed(elem, env)?))), } } @@ -543,6 +582,27 @@ fn type_check_expr( Err(TypeError::new("indexed expression must be array")) } } + Expr::AddrOf(elem) => { + let inner_ty = type_check_expr(elem, env)?; + match &elem.exp { + Expr::Ident(_) => { + Ok(Type::Pointer(Box::new(inner_ty))) + } + _ => Err(TypeError::new( + "can only take address of variables" + )), + } + }, + Expr::Deref(elem) => { + let inner_ty = type_check_expr(elem, env)?; + match inner_ty { + Type::Pointer(base_ty) => Ok(*base_ty), + other => Err(TypeError::new(format!( + "cannot dereference non-pointer type: {:?}", + other + ))), + } + }, } } @@ -579,6 +639,7 @@ fn types_compatible(a: &Type, b: &Type) -> bool { | (Type::Str, Type::Str) | (Type::Unit, Type::Unit) => true, (Type::Int, Type::Float) | (Type::Float, Type::Int) => true, + (Type::Pointer(a), Type::Pointer(b)) => {types_compatible(a, b)}, (Type::Array(a), Type::Array(b)) => types_compatible(a, b), _ => false, } diff --git a/tests/fixtures/pointer_feature.minic b/tests/fixtures/pointer_feature.minic new file mode 100644 index 0000000..579146a --- /dev/null +++ b/tests/fixtures/pointer_feature.minic @@ -0,0 +1,10 @@ +void increment(int* p) { + *p = *p + 1; +} + +void main() { + int x = 10; + int* y = &x; + increment(&x); + print(x); +} \ No newline at end of file diff --git a/tests/fixtures/pointer_function.minic b/tests/fixtures/pointer_function.minic new file mode 100644 index 0000000..89b5387 --- /dev/null +++ b/tests/fixtures/pointer_function.minic @@ -0,0 +1,14 @@ +int* changeRef (int* x, int* y){ + x = y; + return x; +} + +void main() { + int x = 10; + int y = 5; + + int* x_ref = &x; + int* y_ref = &y; + + changeRef(x_ref, y_ref); +} \ No newline at end of file diff --git a/tests/fixtures/pointer_init.minic b/tests/fixtures/pointer_init.minic new file mode 100644 index 0000000..7c8032d --- /dev/null +++ b/tests/fixtures/pointer_init.minic @@ -0,0 +1,11 @@ +void main() { + float a = 10.0; + int b = 5; + str c = "teste"; + bool d = true; + + float* a_ref = &a; + int* b_ref = &b; + str* c_ref = &c; + bool* d_ref = &d; +} \ No newline at end of file diff --git a/tests/interpreter.rs b/tests/interpreter.rs index 51696c9..ee9bc6d 100644 --- a/tests/interpreter.rs +++ b/tests/interpreter.rs @@ -1,4 +1,16 @@ -use mini_c::{interpreter::interpret, parser::program, semantic::type_check}; +use mini_c::{ + environment::Environment, + interpreter::{ + eval_expr::eval_call, + exec_stmt::exec_stmt, + value::{FnValue, Value}, + }, + ir::ast::{CheckedProgram, CheckedStmt, Statement}, + parser::program, + semantic::type_check, + stdlib::NativeRegistry, +}; +use mini_c::interpreter::interpret; /// Parse, type-check, and interpret a MiniC source string. fn run(src: &str) -> Result<(), String> { @@ -9,6 +21,52 @@ fn run(src: &str) -> Result<(), String> { interpret(&checked).map_err(|e| format!("runtime error: {}", e.message)) } +fn checked_program(src: &str) -> Result { + let unchecked = program(src) + .map_err(|e| format!("parse error: {:?}", e)) + .map(|(_, p)| p)?; + type_check(&unchecked).map_err(|e| format!("type error: {}", e.message)) +} + +fn exec_stmt_sequence(seq: &[CheckedStmt], env: &mut Environment) -> Result<(), String> { + for stmt in seq { + match exec_stmt(stmt, env).map_err(|e| format!("runtime error: {}", e.message))? { + Some(Value::Void) => continue, + Some(v) => return Err(format!("unexpected return value: {:?}", v)), + None => continue, + } + } + Ok(()) +} + +fn main_body_sequence(program: &CheckedProgram) -> Result<&[CheckedStmt], String> { + let main = program + .main_function() + .ok_or_else(|| "missing main function".to_string())?; + match &main.body.stmt { + Statement::Block { seq } => Ok(seq), + _ => Err("main body is not a block".to_string()), + } +} + +fn build_program_env(program: &CheckedProgram) -> Environment { + let mut env = Environment::new(); + + // Register stdlib native functions like the interpreter does. + let registry = NativeRegistry::default(); + for (name, entry) in registry.iter() { + env.declare(name.clone(), Value::Fn(FnValue::Native(entry.func))); + } + + for fun in &program.functions { + env.declare( + fun.name.clone(), + Value::Fn(FnValue::UserDefined(fun.clone())), + ); + } + env +} + // --------------------------------------------------------------------------- // 7.2 Empty main // --------------------------------------------------------------------------- @@ -256,3 +314,211 @@ fn test_stdlib_pow_float_args() { "#; assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); } + +// --------------------------------------------------------------------------- +// Pointers (Value::Ptr in a single bindings map) +// --------------------------------------------------------------------------- +#[test] +fn test_pointer_addr_of_and_deref_read() { + let src = r#" + void main() { + int x = 10; + int* p = &x; + int y = *p; + } + "#; + assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); +} + +#[test] +fn test_pointer_deref_assign() { + let src = r#" + void increment(int* p) { + *p = *p + 1; + } + void main() { + int x = 10; + increment(&x); + } + "#; + assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); +} + +#[test] +fn test_pointer_rebind() { + let src = r#" + void main() { + int x = 10; + int y = 5; + int* x_ref = &x; + int* y_ref = &y; + x_ref = y_ref; + } + "#; + assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); +} + +#[test] +fn test_pointer_return() { + let src = r#" + int* pick(int* a, int* b) { return a; } + void main() { + int x = 1; + int y = 2; + int* p = pick(&x, &y); + } + "#; + assert!(run(src).is_ok(), "{}", run(src).unwrap_err()); +} + +#[test] +fn test_pointer_deref_returns_original_value() { + let program = checked_program( + r#" + void main() { + int x = 10; + int* p = &x; + int y = *p; + } + "#, + ) + .unwrap(); + + let mut env = Environment::new(); + exec_stmt_sequence(main_body_sequence(&program).unwrap(), &mut env).unwrap(); + + assert_eq!(env.get("y"), Some(&Value::Int(10))); +} + +#[test] +fn test_pointer_deref_assign_updates_target() { + let program = checked_program( + r#" + void main() { + int x = 10; + int* p = &x; + *p = *p + 1; + } + "#, + ) + .unwrap(); + + let mut env = Environment::new(); + exec_stmt_sequence(main_body_sequence(&program).unwrap(), &mut env).unwrap(); + + assert_eq!(env.get("x"), Some(&Value::Int(11))); +} + +#[test] +fn test_pointer_rebind_and_deref_new_target() { + let program = checked_program( + r#" + void main() { + int x = 10; + int y = 5; + int* p = &x; + p = &y; + *p = 99; + } + "#, + ) + .unwrap(); + + let mut env = Environment::new(); + exec_stmt_sequence(main_body_sequence(&program).unwrap(), &mut env).unwrap(); + + assert_eq!(env.get("x"), Some(&Value::Int(10))); + assert_eq!(env.get("y"), Some(&Value::Int(99))); +} + +#[test] +fn test_pointer_function_return_and_deref() { + let program = checked_program( + r#" + int* pick(int* a, int* b) { return b; } + void main() { + int x = 1; + int y = 2; + int* p = pick(&x, &y); + int z = *p; + } + "#, + ) + .unwrap(); + + let mut env = build_program_env(&program); + exec_stmt_sequence(main_body_sequence(&program).unwrap(), &mut env).unwrap(); + + assert_eq!(env.get("z"), Some(&Value::Int(2))); +} + +#[test] +fn test_pointer_parameter_aliasing_updates_caller_variable() { + let program = checked_program( + r#" + void increment(int* p) { + *p = *p + 1; + } + void main() { + int x = 10; + increment(&x); + } + "#, + ) + .unwrap(); + + let mut env = build_program_env(&program); + env.declare("x".to_string(), Value::Int(10)); + + let addr_x = env.get_address("x").expect("x deveria ter um endereço"); + eval_call("increment", vec![Value::Ptr(addr_x)], &mut env) + .expect("pointer function call failed"); + + assert_eq!(env.get("x"), Some(&Value::Int(11))); +} + +#[test] +fn test_pointer_escaping_local_scope_survives_in_store() { + let program = checked_program( + r#" + int* leak(int x) { + return &x; + } + void main() { + int* p = leak(10); + int y = *p; + } + "#, + ) + .unwrap(); + + let mut env = build_program_env(&program); + exec_stmt_sequence(main_body_sequence(&program).unwrap(), &mut env).unwrap(); + + assert_eq!(env.get("y"), Some(&Value::Int(10))); +} + +#[test] +fn test_traditional_stack_and_heap_separation() { + let mut env = Environment::new(); + + env.declare("x".to_string(), Value::Int(10)); + let addr_x = env.get_address("x").unwrap(); + + let snapshot = env.snapshot(); + + env.declare("p".to_string(), Value::Ptr(addr_x)); + + if let Some(&Value::Ptr(target_addr)) = env.get("p") { + env.write_store(target_addr, Value::Int(11)); + } + + env.restore(snapshot); + + assert!(env.get("p").is_none(), "O isolamento de escopo falhou, 'p' vazou."); + assert_eq!( + env.get("x"), + Some(&Value::Int(11)), + "A mutação de memória foi perdida! A Store foi indevidamente revertida." + ); +} diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..82eba6b 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -9,6 +9,7 @@ use mini_c::parser::{ }, statement, }; +use mini_c::parser::functions::type_name; // --- Literals --- @@ -658,7 +659,7 @@ fn test_multidimensional_indexed_assignment() { #[test] fn test_nested_index() { let result = expression("arr[i][j]").unwrap().1; - assert!(matches!(result.exp, Expr::Index { ref base, ref index } + assert!(matches!(result.exp, Expr::Index { base: _, ref index } if matches!(index.exp, Expr::Ident(ref s) if s == "j"))); if let Expr::Index { ref base, .. } = result.exp { assert!(matches!(base.exp, Expr::Index { ref base, ref index } @@ -672,3 +673,92 @@ fn test_array_in_expression() { assert!(matches!(result.exp, Expr::Index { ref base, ref index } if matches!(base.exp, Expr::ArrayLit(_)) && index.exp == Expr::Literal(Literal::Int(0)))); } + +// --- Pointers (`Type::Pointer` + `type_name`: int*, float*, …) --- + +#[test] +fn test_pointer_type_name() { + assert_eq!( + type_name("int*"), + Ok(("", Type::Pointer(Box::new(Type::Int)))) + ); + assert_eq!( + type_name("float*"), + Ok(("", Type::Pointer(Box::new(Type::Float)))) + ); + assert_eq!( + type_name("bool*"), + Ok(("", Type::Pointer(Box::new(Type::Bool)))) + ); + assert_eq!( + type_name("str*"), + Ok(("", Type::Pointer(Box::new(Type::Str)))) + ); + // `int*` deve ser reconhecido antes de `int` (caso contrário vira só Int). + assert_eq!(type_name("int"), Ok(("", Type::Int))); +} + +#[test] +fn test_pointer_variable_declaration() { + let result = statement("int* ptr = q;").unwrap().1; + assert!(matches!( + result.stmt, + Statement::Decl { + ref name, + ref ty, + .. + } if name == "ptr" && ty == &Type::Pointer(Box::new(Type::Int)) + )); + if let Statement::Decl { ref init, .. } = result.stmt { + assert!(matches!(init.exp, Expr::Ident(ref s) if s == "q")); + } +} + +#[test] +fn test_pointer_address_of() { + let result = expression("&x").unwrap().1; + assert!(matches!(result.exp, Expr::AddrOf(ref target) if matches!(target.exp, Expr::Ident(ref s) if s == "x"))); +} + +#[test] +fn test_pointer_dereference() { + let result = expression("*p").unwrap().1; + assert!(matches!(result.exp, Expr::Deref(ref target) if matches!(target.exp, Expr::Ident(ref s) if s == "p"))); +} + +#[test] +fn test_pointer_params_function() { + let result = fun_decl("void foo(int* p) { *p = 42; }").unwrap().1; + assert_eq!(result.name, "foo"); + assert_eq!( + result.params, + vec![("p".to_string(), Type::Pointer(Box::new(Type::Int)))] + ); + assert!(matches!(result.body.stmt, Statement::Block { ref seq } if seq.len() == 1)); + if let Statement::Block { ref seq } = &result.body.stmt { + assert!(matches!(seq[0].stmt, Statement::Assign { ref target, ref value } + if matches!(target.exp, Expr::Deref(ref t) if matches!(t.exp, Expr::Ident(ref s) if s == "p")) + && value.exp == Expr::Literal(Literal::Int(42)))); + } +} + +#[test] +fn test_pointer_type_function() { + let result = fun_decl("int* changeRef(int* x, int* y) { x = y; return x; }") + .unwrap() + .1; + assert_eq!(result.name, "changeRef"); + + assert_eq!( + result.return_type, + Type::Pointer(Box::new(Type::Int)) + ); + + assert_eq!( + result.params, + vec![ + ("x".to_string(), Type::Pointer(Box::new(Type::Int))), + ("y".to_string(), Type::Pointer(Box::new(Type::Int))) + ] + ); +} diff --git a/tests/program.rs b/tests/program.rs index dd8160b..800b858 100644 --- a/tests/program.rs +++ b/tests/program.rs @@ -91,3 +91,77 @@ fn test_parse_top_level_statements_fail() { let result = parse_program_file("top_level_statements.minic"); assert!(result.is_err(), "top-level statements without def should fail to parse"); } + +#[test] +fn test_parse_pointer_feature_program() { + let prog = parse_program_file("pointer_feature.minic").expect("pointer feature should parse"); + assert_eq!(prog.functions.len(), 2); + assert_eq!(prog.functions[0].name, "increment"); + assert_eq!(prog.functions[1].name, "main"); + assert_eq!(prog.functions[0].params, vec![("p".to_string(), Type::Pointer(Box::new(Type::Int)))]); + + if let Statement::Block { ref seq } = prog.functions[1].body.stmt { + assert_eq!(seq.len(), 4); + assert!(matches!(seq[0].stmt, Statement::Decl { ref name, .. } if name == "x")); + assert!(matches!(seq[1].stmt, Statement::Decl { ref name, .. } if name == "y")); + assert!(matches!(seq[2].stmt, Statement::Call { ref name, .. } if name == "increment")); + assert!(matches!(seq[3].stmt, Statement::Call { ref name, .. } if name == "print")); + } else { + panic!("expected main to have block body"); + } +} + +#[test] +fn test_parse_pointer_function_program() { + let prog = parse_program_file("pointer_function.minic").expect("pointer function should parse"); + assert_eq!(prog.functions.len(), 2); + assert_eq!(prog.functions[0].name, "changeRef"); + assert_eq!(prog.functions[1].name, "main"); + assert_eq!( + prog.functions[0].params, + vec![ + ("x".to_string(), Type::Pointer(Box::new(Type::Int))), + ("y".to_string(), Type::Pointer(Box::new(Type::Int))) + ] + ); + + if let Statement::Block { ref seq } = prog.functions[0].body.stmt { + assert_eq!(seq.len(), 2); + assert!(matches!(seq[0].stmt, Statement::Assign { .. })); + assert!(matches!(seq[1].stmt, Statement::Return(_))); + } else { + panic!("expected changeRef to have block body"); + } + + if let Statement::Block { ref seq } = prog.functions[1].body.stmt { + assert_eq!(seq.len(), 5); + assert!(matches!(seq[0].stmt, Statement::Decl { ref name, .. } if name == "x")); + assert!(matches!(seq[1].stmt, Statement::Decl { ref name, .. } if name == "y")); + assert!(matches!(seq[2].stmt, Statement::Decl { ref name, .. } if name == "x_ref")); + assert!(matches!(seq[3].stmt, Statement::Decl { ref name, .. } if name == "y_ref")); + assert!(matches!(seq[4].stmt, Statement::Call { ref name, .. } if name == "changeRef")); + } else { + panic!("expected main to have block body"); + } +} + +#[test] +fn test_parse_pointer_init_program() { + let prog = parse_program_file("pointer_init.minic").expect("pointer init should parse"); + assert_eq!(prog.functions.len(), 1); + assert_eq!(prog.functions[0].name, "main"); + + if let Statement::Block { ref seq } = prog.functions[0].body.stmt { + assert_eq!(seq.len(), 8); + assert!(matches!(seq[0].stmt, Statement::Decl { ref name, .. } if name == "a")); + assert!(matches!(seq[1].stmt, Statement::Decl { ref name, .. } if name == "b")); + assert!(matches!(seq[2].stmt, Statement::Decl { ref name, .. } if name == "c")); + assert!(matches!(seq[3].stmt, Statement::Decl { ref name, .. } if name == "d")); + assert!(matches!(seq[4].stmt, Statement::Decl { ref name, .. } if name == "a_ref")); + assert!(matches!(seq[5].stmt, Statement::Decl { ref name, .. } if name == "b_ref")); + assert!(matches!(seq[6].stmt, Statement::Decl { ref name, .. } if name == "c_ref")); + assert!(matches!(seq[7].stmt, Statement::Decl { ref name, .. } if name == "d_ref")); + } else { + panic!("expected main to have block body"); + } +} \ No newline at end of file diff --git a/tests/tac_gen.rs b/tests/tac_gen.rs index f602e15..f726e94 100644 --- a/tests/tac_gen.rs +++ b/tests/tac_gen.rs @@ -58,12 +58,89 @@ fn test_if_else_with_relational_condition() { let temp = Address::Temporary("temp1".to_string(), Type::Int); assert_eq!(instructions, vec![ - Instruction::ConditionalJMPRelational(Operator::GTE, x.clone(), y.clone(), "Label1:".to_string()), - Instruction::BinaryAssignment(Operator::Add, temp.clone(), x.clone(), y.clone()), - Instruction::CopyAssignment(z.clone(), temp), + Instruction::ConditionalJMPRelational(Operator::LT, x.clone(), y.clone(), "Label1:".to_string()), Instruction::JMP("Label2:".to_string()), Instruction::Label("Label1:".to_string()), - Instruction::CopyAssignment(z, x), + Instruction::BinaryAssignment(Operator::Add, temp.clone(), x.clone(), y.clone()), + Instruction::CopyAssignment(z.clone(), temp), + Instruction::JMP("Label3:".to_string()), Instruction::Label("Label2:".to_string()), + Instruction::CopyAssignment(z, x), + Instruction::Label("Label3:".to_string()), ]); } + +#[test] +fn test_pointer_assignment() { + + let p_deref = ExprD { + exp: Expr::Deref(Box::new(ExprD { exp: Expr::Ident("p".to_string()), ty: Type::Pointer(Box::new(Type::Int)) })), + ty: Type::Int, + }; + + let one = ExprD { + exp: Expr::Literal(Literal::Int(1)), + ty: Type::Int, + }; + + let add_expr = ExprD { + exp: Expr::Add(Box::new(p_deref.clone()), Box::new(one)), + ty: Type::Int, + }; + + let stmt = StatementD { + stmt: Statement::Assign { + target: Box::new(p_deref), + value: Box::new(add_expr), + }, + ty: Type::Unit, + }; + + let mut env = Environment::new(); + let instructions = translate_statement(stmt, &mut env); + + let p = Address::Variable("p".to_string(), Type::Pointer(Box::new(Type::Int))); + let temp1 = Address::Temporary("temp1".to_string(), Type::Int); + let temp2 = Address::Temporary("temp2".to_string(), Type::Int); + + assert_eq!(instructions, vec![ + Instruction::DerefRead(temp1.clone(), p.clone()), + Instruction::BinaryAssignment(Operator::Add, temp2.clone(), temp1, Address::Constant(Literal::Int(1), Type::Int)), + Instruction::DerefWrite(p, temp2), + ]); +} + +#[test] +fn test_pointer_address_of() { + + let addr_of_expr = ExprD { + exp: Expr::AddrOf(Box::new(ExprD { + exp: Expr::Ident("x".to_string()), + ty: Type::Int + })), + ty: Type::Pointer(Box::new(Type::Int)), + }; + + let stmt = StatementD { + stmt: Statement::Assign { + target: Box::new(ExprD { + exp: Expr::Ident("y".to_string()), + ty: Type::Pointer(Box::new(Type::Int)) + }), + value: Box::new(addr_of_expr), + }, + ty: Type::Unit, + }; + + let mut env = Environment::new(); + let instructions = translate_statement(stmt, &mut env); + + let x = Address::Variable("x".to_string(), Type::Int); + let y = Address::Variable("y".to_string(), Type::Pointer(Box::new(Type::Int))); + let temp1 = Address::Temporary("temp1".to_string(), Type::Pointer(Box::new(Type::Int))); + + assert_eq!(instructions, vec![ + Instruction::AddressOf(temp1.clone(), x), + Instruction::CopyAssignment(y, temp1), + ]); +} \ No newline at end of file diff --git a/tests/type_checker.rs b/tests/type_checker.rs index 3357161..f323b18 100644 --- a/tests/type_checker.rs +++ b/tests/type_checker.rs @@ -1,7 +1,9 @@ //! Integration tests for the MiniC type checker. +use std::path::Path; + use nom::combinator::all_consuming; -use mini_c::ir::ast::{CheckedProgram, Type}; +use mini_c::ir::ast::{CheckedProgram, Expr, Statement, Type}; use mini_c::parser::program; use mini_c::semantic::type_check; @@ -14,6 +16,14 @@ fn parse_and_type_check(src: &str) -> Result Result { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures") + .join(name); + let src = std::fs::read_to_string(&path).expect("fixture file should exist"); + parse_and_type_check(src.trim()) +} + #[test] fn test_type_check_simple_assign() { let result = parse_and_type_check("void main() int x = 1;"); @@ -202,3 +212,145 @@ 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)"); } + +// --------------------------------------------------------------------------- +// Pointers +// --------------------------------------------------------------------------- +#[test] +fn test_type_check_pointer_init_fixture() { + assert!( + parse_and_type_check_fixture("pointer_init.minic").is_ok(), + "{}", + parse_and_type_check_fixture("pointer_init.minic") + .unwrap_err() + .message + ); +} + +#[test] +fn test_type_check_pointer_feature_fixture() { + assert!(parse_and_type_check_fixture("pointer_feature.minic").is_ok()); +} + +#[test] +fn test_type_check_pointer_function_fixture() { + assert!(parse_and_type_check_fixture("pointer_function.minic").is_ok()); +} + +#[test] +fn test_type_check_addr_of_and_deref_types() { + let prog = parse_and_type_check( + "void main() { int x = 1; int* p = &x; int y = *p; }", + ) + .expect("pointer decl and deref should type-check"); + + let main_fn = prog.functions.iter().find(|f| f.name == "main").unwrap(); + let Statement::Block { seq } = &main_fn.body.stmt else { + panic!("expected block body"); + }; + + let p_init = match &seq[1].stmt { + Statement::Decl { init, .. } => init, + _ => panic!("expected int* p = &x decl"), + }; + assert_eq!(p_init.ty, Type::Pointer(Box::new(Type::Int))); + assert!(matches!(p_init.exp, Expr::AddrOf(_))); + + let y_init = match &seq[2].stmt { + Statement::Decl { init, .. } => init, + _ => panic!("expected int y = *p decl"), + }; + assert_eq!(y_init.ty, Type::Int); + assert!(matches!(y_init.exp, Expr::Deref(_))); +} + +#[test] +fn test_type_check_pointer_param_and_deref_assign() { + let prog = parse_and_type_check( + "void bump(int* p) { *p = *p + 1; }\nvoid main() { int x = 0; bump(&x); }", + ) + .expect("pointer param and assignment through deref"); + + let bump = prog.functions.iter().find(|f| f.name == "bump").unwrap(); + assert_eq!( + bump.params, + vec![("p".to_string(), Type::Pointer(Box::new(Type::Int)))] + ); +} + +#[test] +fn test_type_check_pointer_return_type() { + let prog = parse_and_type_check( + "int* id(int* p) { return p; }\nvoid main() { int x = 1; int* q = id(&x); }", + ) + .expect("pointer return"); + + let id_fn = prog.functions.iter().find(|f| f.name == "id").unwrap(); + assert_eq!(id_fn.return_type, Type::Pointer(Box::new(Type::Int))); +} + +#[test] +fn test_type_check_pointer_rebind() { + assert!(parse_and_type_check( + "void main() { int x = 1; int y = 2; int* a = &x; int* b = &y; a = b; }", + ) + .is_ok()); +} + +#[test] +fn test_type_check_deref_non_pointer() { + let result = parse_and_type_check("void main() { int x = 1; int y = *x; }"); + assert!(result.is_err()); + assert!( + result.unwrap_err().message.contains("cannot dereference non-pointer") + ); +} + +#[test] +fn test_type_check_addr_of_non_variable() { + let result = parse_and_type_check("void main() { int x = 1; int* p = &(x + 1); }"); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .message + .contains("can only take address of variables") + ); +} + +#[test] +fn test_type_check_deref_assign_type_mismatch() { + let result = parse_and_type_check( + "void main() { int x = 1; int* p = &x; *p = true; }", + ); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .message + .contains("assignment through pointer") + ); +} + +#[test] +fn test_type_check_call_expects_pointer_arg() { + let result = parse_and_type_check( + "void take(int* p) { *p = 1; }\nvoid main() { int x = 0; take(x); }", + ); + assert!(result.is_err()); + assert!(result.unwrap_err().message.contains("argument")); +} + +#[test] +fn test_type_check_assign_int_from_pointer() { + let result = parse_and_type_check( + "void main() { int x = 1; int* p = &x; int y = p; }", + ); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.message.contains("declaration"), + "unexpected error: {}", + err.message + ); +}