diff --git a/docs/PARSER.md b/docs/PARSER.md index fbb4517..fb81bd6 100644 --- a/docs/PARSER.md +++ b/docs/PARSER.md @@ -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 in : 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 @@ -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) diff --git a/docs/type_checker.md b/docs/type_checker.md index 2706be5..dc6e586 100644 --- a/docs/type_checker.md +++ b/docs/type_checker.md @@ -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 diff --git a/src/environment/environment.rs b/src/environment/environment.rs index 2c7c64e..0b00674 100644 --- a/src/environment/environment.rs +++ b/src/environment/environment.rs @@ -49,6 +49,21 @@ impl Scope { .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) } @@ -113,6 +128,30 @@ impl Environment { 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) { diff --git a/src/interpreter/expression_eval.rs b/src/interpreter/expression_eval.rs index add00f7..71e5edd 100644 --- a/src/interpreter/expression_eval.rs +++ b/src/interpreter/expression_eval.rs @@ -33,6 +33,7 @@ pub fn eval(exp: Expression, env: &Environment) -> Result 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.")), } @@ -535,6 +536,20 @@ fn eval_list_value( Ok(ExpressionResult::Value(Expression::ListValue(values))) } +fn eval_tuple_value( + sub_expressions: Vec, + env: &Environment, +) -> Result { + 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::*; diff --git a/src/interpreter/statement_execute.rs b/src/interpreter/statement_execute.rs index 068def6..7bf5b8b 100644 --- a/src/interpreter/statement_execute.rs +++ b/src/interpreter/statement_execute.rs @@ -248,7 +248,25 @@ pub fn execute(stmt: Statement, env: &Environment) -> Result { + 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)) } @@ -316,18 +334,68 @@ pub fn execute(stmt: Statement, env: &Environment) -> Result { - 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) => { @@ -337,10 +405,38 @@ pub fn execute(stmt: Statement, env: &Environment) -> Result 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")), } } @@ -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] diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 79e487d..f4c6bfd 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -119,6 +119,9 @@ pub enum Expression { // List value ListValue(Vec), + // Tuple value + Tuple(Vec), + // Constructor Constructor(Name, Vec>), } @@ -130,6 +133,10 @@ pub enum Statement { ValDeclaration(Name, Box), Assignment(Name, Box), IfThenElse(Box, Box, Option>), + IfChain { + branches: Vec<(Box, Box)>, + else_branch: Option>, + }, While(Box, Box), For(Name, Box, Box), Block(Vec), diff --git a/src/parser/keywords.rs b/src/parser/keywords.rs index d4f02dd..d7be3f6 100644 --- a/src/parser/keywords.rs +++ b/src/parser/keywords.rs @@ -2,6 +2,7 @@ pub const KEYWORDS: &[&str] = &[ "if", "in", "else", + "elif", "def", "while", "for", diff --git a/src/parser/parser_common.rs b/src/parser/parser_common.rs index bb739d3..6a7121a 100644 --- a/src/parser/parser_common.rs +++ b/src/parser/parser_common.rs @@ -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"; @@ -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, ) } diff --git a/src/parser/parser_expr.rs b/src/parser/parser_expr.rs index 35207c8..bc7e3ab 100644 --- a/src/parser/parser_expr.rs +++ b/src/parser/parser_expr.rs @@ -117,14 +117,62 @@ fn parse_factor(input: &str) -> IResult<&str, Expression> { parse_list, parse_function_call, parse_var, - delimited( - char::<&str, Error<&str>>(LEFT_PAREN), - parse_expression, - char::<&str, Error<&str>>(RIGHT_PAREN), - ), + parse_paren_or_tuple, ))(input) } +// Parses either a parenthesized expression or a tuple literal. +// Cases: +// - () => Tuple([]) +// - (x,) => Tuple([x]) +// - (x, y, z) => Tuple([...]) +// - (expr) => expr (grouping) +fn parse_paren_or_tuple(input: &str) -> IResult<&str, Expression> { + let (input, _) = char::<&str, Error<&str>>(LEFT_PAREN)(input)?; + let (input, _) = multispace0(input)?; + + // Try to parse an optional first expression + let (input, first_opt) = opt(parse_expression)(input)?; + let (input, _) = multispace0(input)?; + + // Try to consume a comma to decide if it's a tuple + let (input_after_comma_try, comma_opt) = opt(delimited( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + ))(input)?; + + let (input, result_expr) = match (first_opt, comma_opt) { + (None, _) => { + // No expression inside: must be () => empty tuple + (input, Expression::Tuple(vec![])) + } + (Some(first), Some(_)) => { + // There was a comma: it's a tuple. Parse remaining elements (possibly zero) + let (input, rest) = separated_list0( + tuple(( + multispace0, + char::<&str, Error<&str>>(COMMA_CHAR), + multispace0, + )), + parse_expression, + )(input_after_comma_try)?; + let mut elements = Vec::with_capacity(1 + rest.len()); + elements.push(first); + elements.extend(rest); + (input, Expression::Tuple(elements)) + } + (Some(expr), None) => { + // Single expression and no comma: grouping + (input, expr) + } + }; + + let (input, _) = multispace0(input)?; + let (input, _) = char::<&str, Error<&str>>(RIGHT_PAREN)(input)?; + Ok((input, result_expr)) +} + fn parse_bool(input: &str) -> IResult<&str, Expression> { alt(( value(Expression::CTrue, keyword("True")), diff --git a/src/parser/parser_stmt.rs b/src/parser/parser_stmt.rs index 264412a..61f7651 100644 --- a/src/parser/parser_stmt.rs +++ b/src/parser/parser_stmt.rs @@ -13,18 +13,19 @@ use crate::ir::ast::Type; use crate::ir::ast::{FormalArgument, Function, Statement}; use crate::parser::parser_common::{ identifier, keyword, ASSERTEQ_KEYWORD, ASSERTFALSE_KEYWORD, ASSERTNEQ_KEYWORD, - ASSERTTRUE_KEYWORD, ASSERT_KEYWORD, COLON_CHAR, COMMA_CHAR, DEF_KEYWORD, ELSE_KEYWORD, - END_KEYWORD, EQUALS_CHAR, FOR_KEYWORD, FUNCTION_ARROW, IF_KEYWORD, IN_KEYWORD, LEFT_PAREN, - RIGHT_PAREN, SEMICOLON_CHAR, VAL_KEYWORD, VAR_KEYWORD, WHILE_KEYWORD, + ASSERTTRUE_KEYWORD, ASSERT_KEYWORD, COLON_CHAR, COMMA_CHAR, DEF_KEYWORD, ELIF_KEYWORD, + ELSE_KEYWORD, END_KEYWORD, EQUALS_CHAR, FOR_KEYWORD, FUNCTION_ARROW, IF_KEYWORD, IN_KEYWORD, + LEFT_PAREN, RIGHT_PAREN, SEMICOLON_CHAR, VAL_KEYWORD, VAR_KEYWORD, WHILE_KEYWORD, }; use crate::parser::parser_expr::parse_expression; use crate::parser::parser_type::parse_type; pub fn parse_statement(input: &str) -> IResult<&str, Statement> { alt(( + // Prefer keyword-led statements first to avoid consuming keywords as identifiers in assignments parse_var_declaration_statement, parse_val_declaration_statement, - parse_assignment_statement, + parse_if_chain_statement, parse_if_else_statement, parse_while_statement, parse_for_statement, @@ -35,6 +36,8 @@ pub fn parse_statement(input: &str) -> IResult<&str, Statement> { parse_asserttrue_statement, parse_test_function_definition_statement, parse_function_definition_statement, + // Fallback: generic assignment should be tried last + parse_assignment_statement, ))(input) } @@ -106,6 +109,34 @@ fn parse_if_else_statement(input: &str) -> IResult<&str, Statement> { )(input) } +pub fn parse_if_chain_statement(input: &str) -> IResult<&str, Statement> { + let (input_after_if, _) = keyword(IF_KEYWORD)(input)?; + let (input_after_expr, cond_if) = parse_expression(input_after_if)?; + let (input_after_block, block_if) = parse_block(input_after_expr)?; + + let mut branches = vec![(Box::new(cond_if), Box::new(block_if))]; + let mut current_input = input_after_block; + + loop { + let result = tuple((keyword(ELIF_KEYWORD), parse_expression, parse_block))(current_input); + match result { + Ok((next_input, (_, cond_elif, block_elif))) => { + branches.push((Box::new(cond_elif), Box::new(block_elif))); + current_input = next_input; + } + Err(_) => break, + } + } + let (input, else_branch) = opt(preceded(keyword(ELSE_KEYWORD), parse_block))(current_input)?; + Ok(( + input, + Statement::IfChain { + branches, + else_branch: else_branch.map(Box::new), + }, + )) +} + fn parse_while_statement(input: &str) -> IResult<&str, Statement> { map( tuple(( @@ -121,9 +152,11 @@ fn parse_for_statement(input: &str) -> IResult<&str, Statement> { map( tuple(( keyword(FOR_KEYWORD), - preceded(multispace1, identifier), + // keyword() already consumes trailing spaces; allow zero or more here + preceded(multispace0, identifier), preceded(multispace0, keyword(IN_KEYWORD)), - preceded(multispace1, parse_expression), + // Likewise, allow optional spaces before the iterable expression + preceded(multispace0, parse_expression), parse_block, )), |(_, var, _, expr, block)| Statement::For(var.to_string(), Box::new(expr), Box::new(block)), @@ -275,6 +308,7 @@ fn parse_function_definition_statement(input: &str) -> IResult<&str, Statement> keyword(DEF_KEYWORD), preceded(multispace1, identifier), delimited( + // Corrigido: Removido o comentário que quebrava a sintaxe char::<&str, Error<&str>>(LEFT_PAREN), separated_list0( tuple(( @@ -290,6 +324,7 @@ fn parse_function_definition_statement(input: &str) -> IResult<&str, Statement> preceded(multispace0, parse_type), parse_block, )), + // Corrigido: O nome da variável 'args' agora está correto |(_, name, args, _, t, block)| { Statement::FuncDef(Function { name: name.to_string(), @@ -475,6 +510,99 @@ mod tests { assert_eq!(parsed, expected); } + #[test] + fn test_parse_if_chain_statement() { + // Cenário 1: Apenas um "if", sem "elif" ou "else". + let input_if_only = "if True: x = 1; end"; + let expected_if_only = Statement::IfChain { + branches: vec![( + Box::new(Expression::CTrue), + Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(1)), + )])), + )], + else_branch: None, + }; + let (_, parsed_if_only) = parse_if_chain_statement(input_if_only).unwrap(); + assert_eq!(parsed_if_only, expected_if_only); + + // Cenário 2: Um "if" com "else", mas sem "elif". + let input_if_else = "if False: x = 1; end else: y = 2; end"; + let expected_if_else = Statement::IfChain { + branches: vec![( + Box::new(Expression::CFalse), + Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(1)), + )])), + )], + else_branch: Some(Box::new(Statement::Block(vec![Statement::Assignment( + "y".to_string(), + Box::new(Expression::CInt(2)), + )]))), + }; + let (_, parsed_if_else) = parse_if_chain_statement(input_if_else).unwrap(); + assert_eq!(parsed_if_else, expected_if_else); + + // Cenário 3: "if", um "elif", e um "else". + let input_if_elif_else = "if a: x = 1; end elif b: y = 2; end else: z = 3; end"; + let expected_if_elif_else = Statement::IfChain { + branches: vec![ + ( + Box::new(Expression::Var("a".to_string())), + Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(1)), + )])), + ), + ( + Box::new(Expression::Var("b".to_string())), + Box::new(Statement::Block(vec![Statement::Assignment( + "y".to_string(), + Box::new(Expression::CInt(2)), + )])), + ), + ], + else_branch: Some(Box::new(Statement::Block(vec![Statement::Assignment( + "z".to_string(), + Box::new(Expression::CInt(3)), + )]))), + }; + let (_, parsed_if_elif_else) = parse_if_chain_statement(input_if_elif_else).unwrap(); + assert_eq!(parsed_if_elif_else, expected_if_elif_else); + + // Cenário 4: "if" com múltiplos "elif" e sem "else". + let input_multi_elif = "if a: x=1; end elif b: y=2; end elif c: z=3; end"; + let expected_multi_elif = Statement::IfChain { + branches: vec![ + ( + Box::new(Expression::Var("a".to_string())), + Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::CInt(1)), + )])), + ), + ( + Box::new(Expression::Var("b".to_string())), + Box::new(Statement::Block(vec![Statement::Assignment( + "y".to_string(), + Box::new(Expression::CInt(2)), + )])), + ), + ( + Box::new(Expression::Var("c".to_string())), + Box::new(Statement::Block(vec![Statement::Assignment( + "z".to_string(), + Box::new(Expression::CInt(3)), + )])), + ), + ], + else_branch: None, + }; + let (_, parsed_multi_elif) = parse_if_chain_statement(input_multi_elif).unwrap(); + assert_eq!(parsed_multi_elif, expected_multi_elif); + } //TODO: Apresentar Parser de TestDef (Testes) mod testdef_tests { use super::*; diff --git a/src/type_checker/statement_type_checker.rs b/src/type_checker/statement_type_checker.rs index a428a82..8af49e4 100644 --- a/src/type_checker/statement_type_checker.rs +++ b/src/type_checker/statement_type_checker.rs @@ -168,6 +168,28 @@ fn check_while_stmt( Ok(new_env) } +// Função auxiliar para determinar o tipo do elemento iterável +fn get_iterable_element_type(expr_type: &Type) -> Result { + match expr_type { + Type::TList(base_type) => Ok((**base_type).clone()), + Type::TString => Ok(Type::TString), // Caracteres como strings + Type::TTuple(types) => { + if types.is_empty() { + return Err("[Type Error] Cannot iterate over empty tuple type".to_string()); + } + + // Verificar se todos os tipos são iguais (tupla homogênea) + let first_type = &types[0]; + if types.iter().all(|t| t == first_type) { + Ok(first_type.clone()) + } else { + Err("[Type Error] Can only iterate over homogeneous tuples (all elements same type)".to_string()) + } + } + _ => Err(format!("[Type Error] Type {:?} is not iterable", expr_type)), + } +} + fn check_for_stmt( var: Name, expr: Box, @@ -175,32 +197,31 @@ fn check_for_stmt( env: &Environment, ) -> Result, ErrorMessage> { let mut new_env = env.clone(); - let _var_type = env.lookup(&var); + // Avaliar o tipo da expressão iterável let expr_type = check_expr(*expr, &new_env)?; - match expr_type { - Type::TList(base_type) => { - if let Some((_, t)) = env.lookup(&var) { - if t == *base_type || *base_type == Type::TAny { - new_env = check_stmt(*stmt, &new_env)?; - return Ok(new_env); - } else { - return Err(format!( - "[TypeError] Type mismatch between {:?} and {:?}", - t, base_type - )); - } - } else { - new_env.map_variable(var.clone(), false, *base_type); - new_env = check_stmt(*stmt, &new_env)?; - return Ok(new_env); - } - } - _ => { - return Err(format!( - "[TypeError] Expecting a List type, but found a {:?}", - expr_type + + // Determinar o tipo do elemento + let element_type = get_iterable_element_type(&expr_type)?; + + // Regra híbrida: + // - Se a variável já existe, não cria novo escopo e só checa o corpo. + // - Se não existe, mapeia a variável no escopo atual (imutável) e checa o corpo. + if let Some((_mutable, existing_type)) = new_env.lookup(&var) { + // Permitir TAny e igualdade de tipos + if existing_type == element_type + || element_type == Type::TAny + || existing_type == Type::TAny + { + check_stmt(*stmt, &new_env) + } else { + Err(format!( + "[Type Error] Type mismatch for iterator '{:?}': expected {:?}, found {:?}", + var, existing_type, element_type )) } + } else { + new_env.map_variable(var.clone(), false, element_type); + check_stmt(*stmt, &new_env) } } diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs index 937e3c2..f71d5f6 100644 --- a/tests/parser_tests.rs +++ b/tests/parser_tests.rs @@ -109,6 +109,42 @@ mod expression_tests { assert_eq!(rest, ""); assert_eq!(result, expected); } + + #[test] + fn test_tuple_literals() { + let cases = vec![ + ( + "(1, 2, 3)", + Expression::Tuple(vec![ + Expression::CInt(1), + Expression::CInt(2), + Expression::CInt(3), + ]), + ), + ( + "(\"hello\", True)", + Expression::Tuple(vec![ + Expression::CString("hello".to_string()), + Expression::CTrue, + ]), + ), + ("()", Expression::Tuple(vec![])), + ("(42,)", Expression::Tuple(vec![Expression::CInt(42)])), + ( + "((1, 2), (3, 4))", + Expression::Tuple(vec![ + Expression::Tuple(vec![Expression::CInt(1), Expression::CInt(2)]), + Expression::Tuple(vec![Expression::CInt(3), Expression::CInt(4)]), + ]), + ), + ]; + + for (input, expected) in cases { + let (rest, result) = parse_expression(input).unwrap(); + assert_eq!(rest, ""); + assert_eq!(result, expected); + } + } } // Statement Tests @@ -204,6 +240,75 @@ mod statement_tests { assert_eq!(result, expected); } + #[test] + fn test_for_over_tuple() { + let cases = vec![ + ( + "for x in (1, 2, 3): x = x + 1; end", + Statement::For( + "x".to_string(), + Box::new(Expression::Tuple(vec![ + Expression::CInt(1), + Expression::CInt(2), + Expression::CInt(3), + ])), + Box::new(Statement::Block(vec![Statement::Assignment( + "x".to_string(), + Box::new(Expression::Add( + Box::new(Expression::Var("x".to_string())), + Box::new(Expression::CInt(1)), + )), + )])), + ), + ), + ( + "for item in (\"a\", \"b\"): val x = item; end", + Statement::For( + "item".to_string(), + Box::new(Expression::Tuple(vec![ + Expression::CString("a".to_string()), + Expression::CString("b".to_string()), + ])), + Box::new(Statement::Block(vec![Statement::ValDeclaration( + "x".to_string(), + Box::new(Expression::Var("item".to_string())), + )])), + ), + ), + ( + "for x in (): val y = 1; end", + Statement::For( + "x".to_string(), + Box::new(Expression::Tuple(vec![])), + Box::new(Statement::Block(vec![Statement::ValDeclaration( + "y".to_string(), + Box::new(Expression::CInt(1)), + )])), + ), + ), + ( + "for t in ((1,2), (3,4)): val x = t; end", + Statement::For( + "t".to_string(), + Box::new(Expression::Tuple(vec![ + Expression::Tuple(vec![Expression::CInt(1), Expression::CInt(2)]), + Expression::Tuple(vec![Expression::CInt(3), Expression::CInt(4)]), + ])), + Box::new(Statement::Block(vec![Statement::ValDeclaration( + "x".to_string(), + Box::new(Expression::Var("t".to_string())), + )])), + ), + ), + ]; + + for (input, expected) in cases { + let (rest, result) = parse_statement(input).unwrap(); + assert_eq!(rest, ""); + assert_eq!(result, expected); + } + } + #[test] #[ignore] fn test_function_definitions() {