From dc6880417b161ef7c0641b3fac9d8d3276422f84 Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Thu, 16 Apr 2026 20:49:07 -0300 Subject: [PATCH 01/11] add examples --- examples/hello.minic | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 examples/hello.minic diff --git a/examples/hello.minic b/examples/hello.minic new file mode 100644 index 0000000..456ac27 --- /dev/null +++ b/examples/hello.minic @@ -0,0 +1,4 @@ +void main() { + str name = "Alice"; + print(name); +} \ No newline at end of file From 9b98fc03d4f0ee8af7179fb2e62efcd35f1e444c Mon Sep 17 00:00:00 2001 From: Marcelo Arcoverde Date: Thu, 16 Apr 2026 21:02:26 -0300 Subject: [PATCH 02/11] add relative path to nix shell --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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\] > " ''; }; } From fa116a95b9d3cd2c25fbe99202417d30a3d1f45b Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Sun, 19 Apr 2026 15:13:45 -0300 Subject: [PATCH 03/11] feat: add T* support in AST and parser --- src/ir/ast.rs | 1 + src/parser/functions.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 5f57b24..2022884 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, } 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), From f3ecbbe2a27323c6d7aa76497f5a1b8f4fca1ed1 Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Sun, 19 Apr 2026 15:55:06 -0300 Subject: [PATCH 04/11] feat: add & and * support --- src/ir/ast.rs | 3 +++ src/parser/expressions.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 2022884..197298d 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -111,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) } From b48b0327b646cad6c447d68c1847db01499a2cd0 Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Sun, 19 Apr 2026 15:55:48 -0300 Subject: [PATCH 05/11] adress and deref todo in next phases --- src/interpreter/eval_expr.rs | 4 ++++ src/semantic/type_checker.rs | 6 ++++++ 2 files changed, 10 insertions(+) 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/semantic/type_checker.rs b/src/semantic/type_checker.rs index 46681cf..7ac3169 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -416,6 +416,9 @@ 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(_) | Expr::Deref(_) => Err(TypeError::new( + "address-of and dereference are not implemented in the type checker yet", + )), } } @@ -543,6 +546,9 @@ fn type_check_expr( Err(TypeError::new("indexed expression must be array")) } } + Expr::AddrOf(_) | Expr::Deref(_) => Err(TypeError::new( + "address-of and dereference are not implemented in the type checker yet", + )), } } From 74b492bff0985c07c16d69981c07935a7b078ef1 Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Sun, 19 Apr 2026 15:56:30 -0300 Subject: [PATCH 06/11] tests: add tests for T*, &n and *p --- tests/parser.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..2c26d0f 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,55 @@ 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"))); +} \ No newline at end of file From 792792a0e0f94111d90a3a01214df97e9b6d1101 Mon Sep 17 00:00:00 2001 From: vlsmcin Date: Mon, 20 Apr 2026 00:43:24 -0300 Subject: [PATCH 07/11] refactor: Deleted unused folder --- examples/hello.minic | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 examples/hello.minic diff --git a/examples/hello.minic b/examples/hello.minic deleted file mode 100644 index 456ac27..0000000 --- a/examples/hello.minic +++ /dev/null @@ -1,4 +0,0 @@ -void main() { - str name = "Alice"; - print(name); -} \ No newline at end of file From 076d52e60d7ebd4bfd1072cf7a65f274ff22dea1 Mon Sep 17 00:00:00 2001 From: vlsmcin Date: Mon, 20 Apr 2026 00:44:46 -0300 Subject: [PATCH 08/11] feat: Create statement pointer tratative --- src/parser/statements.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) 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 From 3205cc9ee29e56730448a7ed7f970230857d7246 Mon Sep 17 00:00:00 2001 From: vlsmcin Date: Mon, 20 Apr 2026 00:45:11 -0300 Subject: [PATCH 09/11] tests: Create pointer statements unit tests --- tests/parser.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/parser.rs b/tests/parser.rs index 2c26d0f..9869bbb 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -724,4 +724,35 @@ fn test_pointer_address_of() { 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 From 9533d5c0d2db4a0070046f0e8dd2017a0679f793 Mon Sep 17 00:00:00 2001 From: vlsmcin Date: Mon, 20 Apr 2026 00:45:34 -0300 Subject: [PATCH 10/11] tests: Create integration tests --- tests/fixtures/pointer_feature.minic | 10 ++++ tests/fixtures/pointer_function.minic | 14 +++++ tests/fixtures/pointer_init.minic | 11 ++++ tests/program.rs | 74 +++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 tests/fixtures/pointer_feature.minic create mode 100644 tests/fixtures/pointer_function.minic create mode 100644 tests/fixtures/pointer_init.minic 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/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 From 23f7fe0f6e37fca22c2b8ca8bebbb177ceb308ee Mon Sep 17 00:00:00 2001 From: rafael-pf Date: Mon, 20 Apr 2026 20:02:24 -0300 Subject: [PATCH 11/11] docs: add pointer feature spec --- openspec/specs/pointers/spec.md | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 openspec/specs/pointers/spec.md 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