diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 00cdddf..c8f66e7 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -95,6 +95,7 @@ impl Parser<'_> { Some(token) if token.type_ == TokenType::LeftBrace => self.block(), Some(token) if token.type_ == TokenType::If => self.if_statement(), Some(token) if token.type_ == TokenType::While => self.while_loop_statement(), + Some(token) if token.type_ == TokenType::For => self.for_statement(), _ => self.expression_statement(), } } @@ -188,6 +189,67 @@ impl Parser<'_> { ) } + fn for_statement(&mut self) -> Result { + let for_keyword = self.token_iterator.next().unwrap(); + self.consume_or_error( + &TokenType::LeftParen, + &for_keyword.position, + "for loop declaration", + )?; + let initializer = match self.token_iterator.peek() { + Some(t) if t.type_ == TokenType::Semicolon => { + self.consume_or_error( + &TokenType::Semicolon, + &for_keyword.position, + "for loop initializer", + ); + None + }, + Some(t) if t.type_ == TokenType::Var => Some((self.var_declaration()?).boxed()), + Some(_) => Some((self.expression_statement()?).boxed()), + None => { + return Err(LoxError { + kind: LoxErrorKind::Syntax, + message: "Expected variable declaration, expression or ';' in for loop initializer".to_owned(), + position: PositionRange::from_bounds( + &for_keyword.position, + &self.token_iterator.last_position().unwrap(), + ), + }); + }, + }; + let condition = match self.token_iterator.peek() { + Some(t) if t.type_ == TokenType::Semicolon => None, + _ => Some(self.expression()?), + }; + self.consume_or_error( + &TokenType::Semicolon, + &for_keyword.position, + "loop condition" + )?; + let increment = match self.token_iterator.peek() { + Some(t) if t.type_ == TokenType::RightParen => None, + _ => Some(self.expression()?), + }.map(|expr| { + let position = expr.position.clone(); + Statement::Expression { expr, position }.boxed() + }); + self.consume_or_error( + &TokenType::RightParen, + &for_keyword.position, + "for loop clauses", + )?; + let body = self.statement()?; + let body_position = body.position().clone(); + Ok(Statement::For { + initializer, + condition, + increment, + body: body.boxed(), + position: PositionRange::from_bounds(&for_keyword.position, &body_position), + }) + } + fn expression_statement(&mut self) -> Result { let expr = self.expression()?; let semicolon = self.consume_semicolon(&expr.position, "expression")?; diff --git a/src/ast/statement.rs b/src/ast/statement.rs index 9f934ee..3749610 100644 --- a/src/ast/statement.rs +++ b/src/ast/statement.rs @@ -31,6 +31,13 @@ pub enum Statement { condition: Expr, body: Box, position: PositionRange, + }, + For { + initializer: Option>, + condition: Option, + increment: Option>, + body: Box, + position: PositionRange, } } @@ -50,6 +57,7 @@ impl Statement { position }, Statement::While { condition: _, body: _, position } => position, + Statement::For {initializer: _, condition: _, increment: _, body: _, position} => position, } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index d9d97d1..b1ce0e5 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -216,6 +216,17 @@ impl StatementVisitor for Interpreter<'_> { self.visit_statement(body)?; } }, + ast::Statement::For { initializer, condition, increment, body, position: _ } => { + if let Some(initializer) = initializer { + self.visit_statement(&initializer); + } + while condition.is_none() || Self::is_truthy(&self.visit_expr(&condition.as_ref().unwrap())?) { + self.visit_statement(&body)?; + if let Some(increment) = increment { + self.visit_statement(&increment)?; + } + } + }, }; Ok(()) } @@ -499,6 +510,45 @@ hello\n", Ok(()), "", ), - + for_loop: ( + " + var a = 0; + var temp; + for (var b = 1; a < 3; b = temp + b) { + print a; + temp = a; + a = b; + } + ", + Ok(()), + "0 +1 +1 +2\n", + ), + for_loop_no_initializer: ( + " + var a = 0; + for (; a < 3; a = a + 1) { + print a; + } + ", + Ok(()), + "0 +1 +2\n", + ), + for_loop_no_increment: ( + " + for (var a = 0; a < 3;) { + print a; + a = a + 1; + } + ", + Ok(()), + "0 +1 +2\n", + ), ); }