From ccbe16b3cb22ac29015d169830621e60300bbc07 Mon Sep 17 00:00:00 2001 From: Alex van de Sandt Date: Sun, 30 Jun 2024 13:38:56 -0400 Subject: [PATCH] Implement while loops --- src/ast/expr.rs | 8 ++++++-- src/ast/mod.rs | 1 + src/ast/stmt.rs | 29 +++++++++++++++++++++++++++++ src/interpreter.rs | 14 ++++++++++++++ src/parser.rs | 35 ++++++++++++++++++++++++++++++++--- 5 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 5815c6c..08460e7 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -90,7 +90,11 @@ impl Expr { Spanned::new(e, span) } - pub fn logical(lhs: Spanned, op: Spanned, rhs: Spanned) -> Spanned { + pub fn logical( + lhs: Spanned, + op: Spanned, + rhs: Spanned, + ) -> Spanned { let span = lhs.span().join(&rhs.span()); let e = Self::Logical { left: Box::new(lhs), @@ -275,7 +279,7 @@ pub enum Lval { impl Lval { pub fn from_expr(target: Spanned) -> Result, Spanned> { - let span = dbg!(target.span()); + let span = target.span(); match target.as_ref() { Expr::Var { name } => { let lval = Lval::Ident { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b0a2e55..cf27254 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -23,6 +23,7 @@ impl Ast { Stmt::VarDecl { name, .. } => format!("set {name}"), Stmt::Block(_) => "block".to_string(), Stmt::If { .. } => "if".to_string(), + Stmt::While { .. } => "while".to_string(), }) .collect::>() .join("\n") diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 86d2a5f..a1f655c 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -18,6 +18,10 @@ pub enum Stmt { then: Box>, otherwise: Option>>, }, + While { + condition: Spanned, + body: Box>, + }, } impl Stmt { @@ -83,6 +87,23 @@ impl Stmt { span, ) } + + pub fn while_stmt( + while_token: &Token, + condition: Spanned, + body: Spanned, + ) -> Spanned { + debug_assert_eq!(while_token.kind, TokenKind::While); + + let span = while_token.span.join(&body.span()); + Spanned::new( + Self::While { + condition, + body: Box::new(body), + }, + span, + ) + } } #[cfg(test)] @@ -156,6 +177,13 @@ impl Stmt { } } + pub fn unwrap_while(&self) -> (&Expr, &Stmt) { + match self { + Self::While { condition, body } => (condition, body), + other => panic!("expected while, found {:?}", other.as_str()), + } + } + fn as_str(&self) -> &'static str { match self { Stmt::Expr(_) => "expr", @@ -166,6 +194,7 @@ impl Stmt { otherwise: Some(_), .. } => "if-else", Stmt::If { .. } => "if", + Stmt::While { .. } => "while", } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index bb1bd25..8b178b0 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -94,6 +94,13 @@ impl Interpreter { Self::execute_stmt(otherwise, output, env)?; } } + Stmt::While { condition, body } => { + let mut condition_val = Self::interpret_expr(condition, env)?; + while condition_val.is_truthy() { + Self::execute_stmt(body, output, env)?; + condition_val = Self::interpret_expr(condition, env)?; + } + } } Ok(()) @@ -417,6 +424,13 @@ mod test { assert_eq!(output, "false"); } + #[test] + fn executes_while() { + let (_, output) = + execute_stmts("var foo = true; while (foo) { print \"body\"; foo = false; }"); + assert_eq!(output, "body"); + } + #[test] fn child_env_shadows_parent() { let (e, out) = execute_stmts("var foo = 1; { print foo; } { var foo = 2; }"); diff --git a/src/parser.rs b/src/parser.rs index a116c91..6d6c930 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -111,7 +111,7 @@ impl Parser { #[tracing::instrument(name = "if", skip_all)] fn parse_if_or_pass(&mut self) -> Result> { let Some(if_token) = self.pop_if_matches(MatchToken::If) else { - return self.parse_stmt(); + return self.parse_while_or_pass(); }; self.pop_if_matches(MatchToken::LeftParen).ok_or_else(|| { @@ -134,6 +134,27 @@ impl Parser { Ok(Stmt::if_then(&if_token, condition, then, otherwise)) } + #[tracing::instrument(name = "while", skip_all)] + fn parse_while_or_pass(&mut self) -> Result> { + let Some(while_token) = self.pop_if_matches(MatchToken::While) else { + return self.parse_print_or_expr_stmt(); + }; + + self.pop_if_matches(MatchToken::LeftParen).ok_or_else(|| { + ParserError::expected_token(TokenKind::LeftParen, self.peek().as_ref()) + })?; + + let condition = self.parse_expr()?; + + self.pop_if_matches(MatchToken::RightParen).ok_or_else(|| { + ParserError::expected_token(TokenKind::RightParen, self.peek().as_ref()) + })?; + + let body = self.parse_block_or_pass()?; + + Ok(Stmt::while_stmt(&while_token, condition, body)) + } + /// Parse the body of a block into a group of statements. This may be a bare block statement or /// part of another piece of syntax, such as a while statement. /// @@ -159,7 +180,7 @@ impl Parser { } #[tracing::instrument(name = "stmt", skip_all)] - fn parse_stmt(&mut self) -> Result> { + fn parse_print_or_expr_stmt(&mut self) -> Result> { let print_token = self.pop_if_matches(MatchToken::Print); let expr = self.parse_expr()?; let semi = self @@ -620,6 +641,14 @@ mod tests { assert_matches!(otherwise, Stmt::Expr { .. }); } + #[test] + fn parses_while() { + let stmt = parse_stmt("while (true) print 1;"); + let (condition, body) = stmt.unwrap_while(); + assert_matches!(condition, Expr::Literal { .. }); + assert_matches!(body, Stmt::Print { .. }); + } + #[test] fn dangling_else_attches_to_nearest_if() { let stmt = parse_stmt("if (a) if (b) true; else false;"); @@ -830,7 +859,7 @@ mod tests { mod helper { use crate::{ - ast::{Ast, BinaryOp, Expr, Stmt, LogicalOp}, + ast::{Ast, BinaryOp, Expr, LogicalOp, Stmt}, parser::{parse, parse_expr, ParserError}, scanner::scan, span::Spanned, -- 2.51.2