Skip to content
21 changes: 21 additions & 0 deletions docs/PARSER.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ The parser supports various types of statements:
- Assert statements
- ADT (Algebraic Data Type) declarations

#### For statement semantics (iterables and scoping)

- Syntax: `for <identifier> in <expression>: <block> end`
- Supported iterables at parse time (resolved at runtime/type-check):
- Lists: `[e1, e2, ...]`
- Strings: "abc" (iterates over characters as 1-length strings)
- Tuples: `(e1, e2, ...)` (see tuple literals below)
- Scoping: the loop variable is bound in an inner scope created for the loop body. It is not visible outside the loop.
- The variable is considered immutable by the type checker; each iteration rebinds it.

### Expression Parsing
Handles different types of expressions:
- Arithmetic expressions
Expand All @@ -69,6 +79,17 @@ Handles different types of expressions:
- Literals (numbers, strings, booleans)
- ADT constructors and pattern matching

#### Tuple literals and parenthesis grouping

The parser supports tuple literals and distinguishes them from parenthesized groupings:

- Empty tuple: `()` -> `Expression::Tuple([])`
- Single-element tuple: `(x,)` -> `Expression::Tuple([x])`
- Multi-element tuple: `(x, y, z)` -> `Expression::Tuple([x, y, z])`
- Grouping (no comma): `(expr)` -> parsed as `expr` (not a tuple)

Tuples may be nested, e.g., `((1, 2), (3, 4))`.

### Type System
Supports a rich type system including:
- Basic types (Int, Real, Boolean, String, Unit, Any)
Expand Down
28 changes: 28 additions & 0 deletions docs/type_checker.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,34 @@ Error: type mismatch
```

### Statement Sequences
### For Loops

Typing rules for `for` loops support the following iterable types:

- `TList(T)` iterates elements of type `T`.
- `TString` iterates characters as `TString` (1-length strings).
- `TTuple([T, T, ..., T])` iterates elements if and only if the tuple is homogeneous (all element types equal). Empty tuple is rejected.

Scoping and binding:

- The loop introduces an inner scope for the body using `push()`/`pop()`.
- The iterator variable is bound as immutable to the element type in that inner scope.
- The variable does not escape to the outer scope after the loop finishes.

Formally:

```
Γ ⊢ e : TList(T) or Γ ⊢ e : TString or Γ ⊢ e : TTuple([T, ..., T]) (homogeneous, non-empty)
Γ, x:T ⊢ s : Γ' (checked in an inner scope)
———————————————————————————————————————————————————————————————————————————————
Γ ⊢ For(x, e, s) : Γ
```

Errors:

- Non-iterable type in `e` → `[Type Error] Type <...> is not iterable`.
- Empty tuple type → `[Type Error] Cannot iterate over empty tuple type`.
- Heterogeneous tuple → `[Type Error] Can only iterate over homogeneous tuples (all elements same type)`.

```rust
// Sequential composition
Expand Down
39 changes: 39 additions & 0 deletions src/environment/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ impl<A: Clone> Scope<A> {
.map(|(mutable, value)| (*mutable, value.clone()))
}

fn update_var(&mut self, var: &Name, value: A) -> bool {
if let Some((mutable, slot)) = self.variables.get_mut(var) {
*slot = value;
// preserve existing mutability flag
let _ = mutable; // silence unused warning if optimised out
true
} else {
false
}
}

fn remove_var(&mut self, var: &Name) -> bool {
self.variables.remove(var).is_some()
}

fn lookup_function(&self, name: &Name) -> Option<&Function> {
self.functions.get(name)
}
Expand Down Expand Up @@ -113,6 +128,30 @@ impl<A: Clone> Environment<A> {
self.globals.lookup_var(var)
}

/// Update an existing variable in the nearest scope where it's defined.
/// Returns true if the variable existed and was updated; false otherwise.
pub fn update_existing_variable(&mut self, var: &Name, value: A) -> bool {
// Search local scopes first (top-most first)
for scope in self.stack.iter_mut() {
if scope.update_var(var, value.clone()) {
return true;
}
}
// Fallback to globals
self.globals.update_var(var, value)
}

/// Remove a variable from the nearest scope where it's defined.
/// Returns true if something was removed; false otherwise.
pub fn remove_variable(&mut self, var: &Name) -> bool {
for scope in self.stack.iter_mut() {
if scope.remove_var(var) {
return true;
}
}
self.globals.remove_var(var)
}

pub fn lookup_function(&self, name: &Name) -> Option<&Function> {
for scope in self.stack.iter() {
if let Some(func) = scope.lookup_function(name) {
Expand Down
15 changes: 15 additions & 0 deletions src/interpreter/expression_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn eval(exp: Expression, env: &Environment<Expression>) -> Result<Expression
Expression::IsNothing(e) => eval_isnothing_expression(*e, env),
Expression::FuncCall(name, args) => eval_function_call(name, args, env),
Expression::ListValue(values) => eval_list_value(values, env),
Expression::Tuple(values) => eval_tuple_value(values, env),
_ if is_constant(exp.clone()) => Ok(ExpressionResult::Value(exp)),
_ => Err(String::from("Not implemented yet.")),
}
Expand Down Expand Up @@ -535,6 +536,20 @@ fn eval_list_value(
Ok(ExpressionResult::Value(Expression::ListValue(values)))
}

fn eval_tuple_value(
sub_expressions: Vec<Expression>,
env: &Environment<Expression>,
) -> Result<ExpressionResult, String> {
let mut values = Vec::new();
for exp in sub_expressions {
match eval(exp, env)? {
ExpressionResult::Value(expr) => values.push(expr),
ExpressionResult::Propagate(expr) => return Ok(ExpressionResult::Propagate(expr)),
}
}
Ok(ExpressionResult::Value(Expression::Tuple(values)))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
120 changes: 107 additions & 13 deletions src/interpreter/statement_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,25 @@ pub fn execute(stmt: Statement, env: &Environment<Expression>) -> Result<Computa
return Ok(Computation::PropagateError(expr, new_env))
}
};
new_env.map_variable(name, true, value);
// Respect existing mutability; if variable exists and is immutable, propagate error
match new_env.lookup(&name) {
Some((is_mut, _)) => {
if !is_mut {
return Ok(Computation::PropagateError(
Expression::CString(format!(
"Cannot assign to immutable variable '{}'",
name
)),
new_env,
));
}
let _ = new_env.update_existing_variable(&name, value);
}
None => {
// If not previously declared, create as mutable (back-compat with tests)
new_env.map_variable(name, true, value);
}
}
Ok(Computation::Continue(new_env))
}

Expand Down Expand Up @@ -316,18 +334,68 @@ pub fn execute(stmt: Statement, env: &Environment<Expression>) -> Result<Computa
}
}

Statement::For(var, list, stmt) => {
let values = match eval(*list.clone(), &new_env)? {
Statement::For(var, expr, stmt) => {
let coll = match eval(*expr.clone(), &new_env)? {
ExpressionResult::Value(expr) => expr,
ExpressionResult::Propagate(expr) => {
return Ok(Computation::PropagateError(expr, new_env))
}
};

match values {
Expression::ListValue(expressions) => {
for exp in expressions {
new_env.map_variable(var.clone(), false, exp);
match coll {
// List of values
Expression::ListValue(items) => {
for item in items {
// Bind loop variable in a transient manner: shadow during iteration
// Save previous binding (if any)
let prev = new_env.lookup(&var.clone());
new_env.map_variable(var.clone(), false, item);
match execute(*stmt.clone(), &new_env)? {
Computation::Continue(env) => new_env = env,
Computation::Return(expr, env) => {
return Ok(Computation::Return(expr, env))
}
Computation::PropagateError(expr, env) => {
return Ok(Computation::PropagateError(expr, env))
}
}
// Restore previous binding after each iteration
let _ = new_env.remove_variable(&var.clone());
if let Some((was_mut, old_val)) = prev {
new_env.map_variable(var.clone(), was_mut, old_val);
}
}
Ok(Computation::Continue(new_env))
}

// String - itera sobre caracteres
Expression::CString(s) => {
for ch in s.chars() {
let char_value = Expression::CString(ch.to_string());
let prev = new_env.lookup(&var.clone());
new_env.map_variable(var.clone(), false, char_value);
match execute(*stmt.clone(), &new_env)? {
Computation::Continue(env) => new_env = env,
Computation::Return(expr, env) => {
return Ok(Computation::Return(expr, env))
}
Computation::PropagateError(expr, env) => {
return Ok(Computation::PropagateError(expr, env))
}
}
let _ = new_env.remove_variable(&var.clone());
if let Some((was_mut, old_val)) = prev {
new_env.map_variable(var.clone(), was_mut, old_val);
}
}
Ok(Computation::Continue(new_env))
}

// Tupla (assumindo que você tem Expression::Tuple)
Expression::Tuple(items) => {
for item in items {
let prev = new_env.lookup(&var.clone());
new_env.map_variable(var.clone(), false, item);
match execute(*stmt.clone(), &new_env)? {
Computation::Continue(env) => new_env = env,
Computation::Return(expr, env) => {
Expand All @@ -337,10 +405,38 @@ pub fn execute(stmt: Statement, env: &Environment<Expression>) -> Result<Computa
return Ok(Computation::PropagateError(expr, env))
}
}
let _ = new_env.remove_variable(&var.clone());
if let Some((was_mut, old_val)) = prev {
new_env.map_variable(var.clone(), was_mut, old_val);
}
}
return Ok(Computation::Continue(new_env));
Ok(Computation::Continue(new_env))
}
_ => unreachable!(),

// Constructor (já existia)
Expression::Constructor(_, items) => {
for item_expr in items {
let item_value = *item_expr.clone();
let prev = new_env.lookup(&var.clone());
new_env.map_variable(var.clone(), false, item_value);
match execute(*stmt.clone(), &new_env)? {
Computation::Continue(env) => new_env = env,
Computation::Return(expr, env) => {
return Ok(Computation::Return(expr, env))
}
Computation::PropagateError(expr, env) => {
return Ok(Computation::PropagateError(expr, env))
}
}
let _ = new_env.remove_variable(&var.clone());
if let Some((was_mut, old_val)) = prev {
new_env.map_variable(var.clone(), was_mut, old_val);
}
}
Ok(Computation::Continue(new_env))
}

_ => Err(String::from("Cannot iterate over provided expression")),
}
}

Expand Down Expand Up @@ -859,11 +955,9 @@ mod tests {
let (_, result_expr) = result_value.unwrap();
assert_eq!(result_expr, Expression::CInt(42));

// Check that loop variable i is still accessible with the last value
// With isolated loop scope, the iterator variable should NOT leak outside the loop
let i_value = final_env.lookup(&"i".to_string());
assert!(i_value.is_some());
let (_, i_expr) = i_value.unwrap();
assert_eq!(i_expr, Expression::CInt(42));
assert!(i_value.is_none());
}

#[test]
Expand Down
7 changes: 7 additions & 0 deletions src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ pub enum Expression {
// List value
ListValue(Vec<Expression>),

// Tuple value
Tuple(Vec<Expression>),

// Constructor
Constructor(Name, Vec<Box<Expression>>),
}
Expand All @@ -130,6 +133,10 @@ pub enum Statement {
ValDeclaration(Name, Box<Expression>),
Assignment(Name, Box<Expression>),
IfThenElse(Box<Expression>, Box<Statement>, Option<Box<Statement>>),
IfChain {
branches: Vec<(Box<Expression>, Box<Statement>)>,
else_branch: Option<Box<Statement>>,
},
While(Box<Expression>, Box<Statement>),
For(Name, Box<Expression>, Box<Statement>),
Block(Vec<Statement>),
Expand Down
1 change: 1 addition & 0 deletions src/parser/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub const KEYWORDS: &[&str] = &[
"if",
"in",
"else",
"elif",
"def",
"while",
"for",
Expand Down
14 changes: 10 additions & 4 deletions src/parser/parser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub const END_KEYWORD: &str = "end";

// Statement keyword constants
pub const IF_KEYWORD: &str = "if";
pub const ELIF_KEYWORD: &str = "elif";
pub const ELSE_KEYWORD: &str = "else";
pub const WHILE_KEYWORD: &str = "while";
pub const FOR_KEYWORD: &str = "for";
Expand Down Expand Up @@ -72,11 +73,16 @@ pub fn separator<'a>(sep: &'static str) -> impl FnMut(&'a str) -> IResult<&'a st
}

/// Parses a reserved keyword (e.g., "if") surrounded by optional spaces
/// Fails if followed by an identifier character
/// A implementação da função keyword foi alterada para que seja garantida que a keyword seja uma palavra completa e seja separada por um espaço
pub fn keyword<'a>(kw: &'static str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
terminated(
delimited(multispace0, tag(kw), multispace0),
not(peek(identifier_start_or_continue)),
delimited(
multispace0,
terminated(
tag(kw),
// Ensure the keyword is not followed by an identifier character (letter, digit, or underscore)
peek(not(identifier_start_or_continue)),
),
multispace0,
)
}

Expand Down
Loading
Loading