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/interpreter/eval_expr.rs b/src/interpreter/eval_expr.rs index 49fcbef..0592c8e 100644 --- a/src/interpreter/eval_expr.rs +++ b/src/interpreter/eval_expr.rs @@ -147,6 +147,10 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment) -> Result Err(RuntimeError::new( + "address-of and dereference are not implemented in the interpreter yet", + )), } } diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 5f57b24..197298d 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, } @@ -110,6 +111,9 @@ pub enum Expr { base: Box>, index: Box>, }, + /// Pointer operations + AddrOf(Box>), + Deref(Box>), } /// Statement with type decoration. 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 46681cf..ceaee0f 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -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/parser.rs b/tests/parser.rs index eca6640..9869bbb 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 --- @@ -672,3 +673,86 @@ 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.params, + vec![ + ("x".to_string(), Type::Pointer(Box::new(Type::Int))), + ("y".to_string(), Type::Pointer(Box::new(Type::Int))) + ] + ); +} \ No newline at end of file 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