diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e5bca67..76a9089 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -21,6 +21,7 @@ impl Ast { initializer: Some(e), } => format!("set {name} {}", e.print_rpn()), Stmt::VarDecl { name, .. } => format!("set {name}"), + Stmt::Block(_) => "block".to_string(), }) .collect::>() .join("\n") diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index feceae4..c05636e 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -11,6 +11,7 @@ pub enum Stmt { name: String, // spanned? initializer: Option>, }, + Block(Vec>), } impl Stmt { @@ -40,6 +41,18 @@ impl Stmt { let span = var.span.join(&semi.span); Spanned::new(Self::VarDecl { name, initializer }, span) } + + pub fn block( + open_brace: Token, + inner_stmts: Vec>, + close_brace: Token, + ) -> Spanned { + debug_assert_eq!(open_brace.kind, TokenKind::LeftBrace); + debug_assert_eq!(close_brace.kind, TokenKind::RightBrace); + + let span = open_brace.span.join(&close_brace.span); + Spanned::new(Self::Block(inner_stmts), span) + } } #[cfg(test)] @@ -78,11 +91,19 @@ impl Stmt { } } + pub fn unwrap_block(&self) -> &[Spanned] { + match self { + Self::Block(inner) => inner.as_slice(), + other => panic!("expected block, found {:?}", other.as_str()), + } + } + fn as_str(&self) -> &'static str { match self { Stmt::Expr(_) => "expr", Stmt::Print(_) => "print", Stmt::VarDecl { .. } => "var decl", + Stmt::Block(_) => "block", } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 95326ad..068b32e 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -76,6 +76,7 @@ impl Interpreter { .unwrap_or(Value::Nil); self.env.define(name.clone(), val); } + Stmt::Block(_) => todo!("block execution"), } Ok(()) diff --git a/src/parser.rs b/src/parser.rs index ba8089a..08c5b6b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -62,7 +62,7 @@ impl Parser { #[tracing::instrument(name = "decl", skip_all)] fn parse_decl(&mut self) -> Result> { let Some(var) = self.pop_if_matches(MatchToken::Var) else { - return self.parse_stmt(); + return self.parse_block_or_pass(); }; let name = self @@ -87,6 +87,34 @@ impl Parser { Ok(Stmt::var_decl(var, name, initializer, semi)) } + #[tracing::instrument(name = "block", skip_all)] + fn parse_block_or_pass(&mut self) -> Result> { + let Some(open_brace) = self.pop_if_matches(MatchToken::LeftBrace) else { + return self.parse_stmt(); + }; + + let (inner_stmts, close_brace) = self.parse_inner_block_until(MatchToken::RightBrace)?; + + Ok(Stmt::block(open_brace, inner_stmts, close_brace)) + } + + fn parse_inner_block_until( + &mut self, + until: MatchToken, + ) -> Result<(Vec>, Token)> { + let mut inner_stmts = Vec::new(); + let close = loop { + if let Some(close) = self.pop_if_matches(until) { + break close; + } + + let stmt = self.parse_decl()?; + inner_stmts.push(stmt); + }; + + Ok((inner_stmts, close)) + } + #[tracing::instrument(name = "stmt", skip_all)] fn parse_stmt(&mut self) -> Result> { let print_token = self.pop_if_matches(MatchToken::Print); @@ -445,6 +473,24 @@ mod tests { assert_matches!(stmt.unwrap_var_decl_with_init(), (_, Expr::Literal { .. })) } + #[test] + fn parses_block() { + let stmt = parse_stmt("{ print 1; print 2; }"); + let inner = stmt.unwrap_block(); + assert_eq!(inner.len(), 2); + assert_matches!(inner[0].as_ref(), Stmt::Print(_)); + assert_matches!(inner[1].as_ref(), Stmt::Print(_)); + } + + #[test] + fn parses_nested_blocks() { + let stmt = parse_stmt("{ print 1; { print 2; } }"); + let inner = stmt.unwrap_block(); + assert_eq!(inner.len(), 2); + let inner_2 = inner[1].unwrap_block(); + assert_eq!(inner_2.len(), 1); + } + #[test] fn errs_on_missing_ident() { assert_matches!(