Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
];

shellHook = ''
export PS1="MiniC ❄️ > "
export PS1="MiniC ❄️ \[\033[01;34m\]\w\[\033[00m\] > "
'';
};
}
Expand Down
109 changes: 109 additions & 0 deletions openspec/specs/pointers/spec.md
Original file line number Diff line number Diff line change
@@ -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`
4 changes: 4 additions & 0 deletions src/interpreter/eval_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ pub fn eval_expr(expr: &CheckedExpr, env: &mut Environment<Value>) -> Result<Val
args.iter().map(|a| eval_expr(a, env)).collect();
eval_call(name, arg_vals?, env)
}

Expr::AddrOf(_) | Expr::Deref(_) => Err(RuntimeError::new(
"address-of and dereference are not implemented in the interpreter yet",
)),
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub enum Type {
Str,
Array(Box<Type>),
Fun(Vec<Type>, Box<Type>),
Pointer(Box<Type>),
/// Matches any type. Only used as a parameter type in native stdlib registrations.
Any,
}
Expand Down Expand Up @@ -110,6 +111,9 @@ pub enum Expr<Ty> {
base: Box<ExprD<Ty>>,
index: Box<ExprD<Ty>>,
},
/// Pointer operations
AddrOf(Box<ExprD<Ty>>),
Deref(Box<ExprD<Ty>>),
}

/// Statement with type decoration.
Expand Down
6 changes: 6 additions & 0 deletions src/parser/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 4 additions & 0 deletions src/parser/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
23 changes: 17 additions & 6 deletions src/parser/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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('[')),
Expand Down Expand Up @@ -206,4 +217,4 @@ pub fn assignment(input: &str) -> IResult<&str, UncheckedStmt> {
})
},
)(input)
}
}
6 changes: 6 additions & 0 deletions src/semantic/type_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)),
}
}

Expand Down Expand Up @@ -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",
)),
}
}

Expand Down
10 changes: 10 additions & 0 deletions tests/fixtures/pointer_feature.minic
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
void increment(int* p) {
*p = *p + 1;
}

void main() {
int x = 10;
int* y = &x;
increment(&x);
print(x);
}
14 changes: 14 additions & 0 deletions tests/fixtures/pointer_function.minic
Original file line number Diff line number Diff line change
@@ -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);
}
11 changes: 11 additions & 0 deletions tests/fixtures/pointer_init.minic
Original file line number Diff line number Diff line change
@@ -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;
}
84 changes: 84 additions & 0 deletions tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use mini_c::parser::{
},
statement,
};
use mini_c::parser::functions::type_name;

// --- Literals ---

Expand Down Expand Up @@ -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)))
]
);
}
Loading