diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 8a562e0..01527c2 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -27,6 +27,10 @@ pub enum Stmt { body: Box>, }, Return(#[allow(dead_code)] Option>), + Class { + _name: String, + _methods: Vec>, + }, } impl Stmt { @@ -58,18 +62,12 @@ impl Stmt { Spanned::new(Self::VarDecl { name, initializer }, span) } - pub fn function( - fun_token: &Token, - name: String, - params: Vec, - body: Vec>, - closing_brace: &Token, - ) -> Spanned { + pub fn function(fun_token: &Token, function: Function, closing_brace: &Token) -> Spanned { debug_assert_eq!(fun_token.kind, TokenKind::Fun); debug_assert_eq!(closing_brace.kind, TokenKind::RightBrace); let span = fun_token.span.join(&closing_brace.span); - Spanned::new(Self::Function(Function::new(name, params, body)), span) + Spanned::new(Self::Function(function), span) } pub fn block( @@ -205,6 +203,25 @@ impl Stmt { let span = return_token.span.join(&semicolon.span); Spanned::new(Self::Return(value), span) } + + pub fn class_decl( + class_token: &Token, + name: String, + methods: Vec>, + close_brace: &Token, + ) -> Spanned { + debug_assert_matches!(&class_token.kind, TokenKind::Class); + debug_assert_matches!(&close_brace.kind, TokenKind::RightBrace); + + let span = class_token.span.join(&close_brace.span); + Spanned::new( + Self::Class { + _name: name, + _methods: methods, + }, + span, + ) + } } #[cfg(test)] @@ -308,6 +325,16 @@ impl Stmt { } } + pub fn unwrap_class_decl(&self) -> (&str, &[Spanned]) { + match self { + Self::Class { + _name: name, + _methods: functions, + } => (name.as_ref(), functions.as_slice()), + other => panic!("expected class, found {:?}", other.as_str()), + } + } + fn as_str(&self) -> &'static str { match self { Stmt::Expr(_) => "expr", @@ -321,6 +348,7 @@ impl Stmt { Stmt::If { .. } => "if", Stmt::While { .. } => "while", Stmt::Return(_) => "return", + Stmt::Class { .. } => "class", } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index ccfb668..05c65c9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -122,6 +122,7 @@ impl<'a> Interpreter<'a> { .unwrap_or(Value::Nil); return Ok(Some(value)); } + Stmt::Class { .. } => todo!("class declaration execution"), } Ok(None) diff --git a/src/parser.rs b/src/parser.rs index c9448e3..cf45a01 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use miette::SourceSpan; use paste::paste; use crate::{ - ast::{Ast, BinaryOp, Expr, Literal, LogicalOp, Lval, Stmt, UnaryOp}, + ast::{Ast, BinaryOp, Expr, Function, Literal, LogicalOp, Lval, Stmt, UnaryOp}, match_token::MatchToken, span::{Span, Spanned}, token::{Token, TokenKind}, @@ -65,6 +65,7 @@ impl Parser { match self.peek().map(|t| t.kind) { Some(TokenKind::Var) => self.parse_var_decl(), Some(TokenKind::Fun) => self.parse_fun_decl(), + Some(TokenKind::Class) => self.parse_class_decl(), _ => self.parse_stmt(), } } @@ -98,43 +99,28 @@ impl Parser { #[tracing::instrument(name = "fun", skip_all)] fn parse_fun_decl(&mut self) -> Result> { let fun_token = self.expect_token(MatchToken::Fun)?; + let (_, function, close_brace) = self.parse_fun_or_method()?; + Ok(Stmt::function(&fun_token, function, &close_brace)) + } + #[tracing::instrument(name = "class", skip_all)] + fn parse_class_decl(&mut self) -> Result> { + let class_token = self.expect_token(MatchToken::Class)?; let name = self.expect_identifier()?; - self.expect_token(MatchToken::LeftParen)?; - let params = - self.parse_comma_separated_list(MatchToken::RightParen, Self::expect_identifier)?; - self.expect_token(MatchToken::RightParen)?; - let open_brace = self.expect_token(MatchToken::LeftBrace)?; - let (body, close_brace) = self.parse_block_body().map_err(|e| match e { - ParseBlockError::ReachedEnd { last_stmt_span } => { - let end = last_stmt_span - .map(|s| s.end()) - .unwrap_or_else(|| open_brace.span.end()); - ParserError::unclosed_block(&open_brace, end) - } - ParseBlockError::ParserError(e) => e, + let (_, methods, close_brace) = self.parse_until_close_brace(|p| { + let (name_token, method, fun_close_brace) = p.parse_fun_or_method()?; + let span = name_token.span.join(&fun_close_brace.span); + Ok(Spanned::new(method, span)) })?; - Ok(Stmt::function(&fun_token, name, params, body, &close_brace)) + Ok(Stmt::class_decl(&class_token, name, methods, &close_brace)) } #[tracing::instrument(name = "block", skip_all)] fn parse_block_stmt(&mut self) -> Result> { - let open_brace = self.expect_token(MatchToken::LeftBrace)?; - - match self.parse_block_body() { - Ok((inner_stmts, close_brace)) => { - Ok(Stmt::block(&open_brace, inner_stmts, &close_brace)) - } - Err(ParseBlockError::ReachedEnd { last_stmt_span }) => { - let end = last_stmt_span - .map(|s| s.end()) - .unwrap_or_else(|| open_brace.span.end()); - Err(ParserError::unclosed_block(&open_brace, end)) - } - Err(ParseBlockError::ParserError(e)) => Err(e), - } + let (open_brace, inner_stmts, close_brace) = self.parse_block_body()?; + Ok(Stmt::block(&open_brace, inner_stmts, &close_brace)) } #[tracing::instrument(name = "if", skip_all)] @@ -218,24 +204,6 @@ impl Parser { /// /// Let the caller construct the `ParserError::UnclosedBlock`, as it has more context about /// where the block began. - fn parse_block_body(&mut self) -> Result<(Vec>, Token), ParseBlockError> { - let mut inner_stmts = Vec::new(); - let close = loop { - if let Some(close) = self.pop_if_matches(MatchToken::RightBrace) { - break close; - } - - if self.at_end() { - let last_stmt_span = inner_stmts.last().map(|s: &Spanned| s.span()); - return Err(ParseBlockError::ReachedEnd { last_stmt_span }); - } - - let stmt = self.parse_decl()?; - inner_stmts.push(stmt); - }; - - Ok((inner_stmts, close)) - } #[tracing::instrument(name = "return", skip_all)] fn parse_return_stmt(&mut self) -> Result> { @@ -265,6 +233,52 @@ impl Parser { Ok(Stmt::expr(expr, &semicolon)) } + fn parse_fun_or_method(&mut self) -> Result<(Token, Function, Token)> { + let name = self.expect_identifier()?; + let name_token = self.previous().unwrap(); + + self.expect_token(MatchToken::LeftParen)?; + let params = + self.parse_comma_separated_list(MatchToken::RightParen, Self::expect_identifier)?; + self.expect_token(MatchToken::RightParen)?; + let (_, body, close_brace) = self.parse_block_body()?; + + let function = Function::new(name, params, body); + Ok((name_token, function, close_brace)) + } + + fn parse_block_body(&mut self) -> Result<(Token, Vec>, Token)> { + self.parse_until_close_brace(Parser::parse_decl) + } + + fn parse_until_close_brace( + &mut self, + mut parse_block_item: ParseItem, + ) -> Result<(Token, Vec, Token)> + where + ParseItem: FnMut(&mut Parser) -> Result, + { + let open_brace = self.expect_token(MatchToken::LeftBrace)?; + + let mut items = Vec::new(); + loop { + if let Some(close_brace) = self.pop_if_matches(MatchToken::RightBrace) { + return Ok((open_brace, items, close_brace)); + } + + if self.at_end() { + let last_token_span = self.previous().unwrap().span; + return Err(ParserError::unclosed_block( + &open_brace, + last_token_span.end(), + )); + } + + let item = parse_block_item(self)?; + items.push(item); + } + } + /// Parse items in a comma-separated list until the next token is not a comma fn parse_comma_separated_list( &mut self, @@ -713,7 +727,10 @@ impl ParserError { fn unclosed_block(left_brace: &Token, end: usize) -> Self { let start = left_brace.span.start(); - Self::UnclosedBlock { start, end } + Self::UnclosedBlock { + start, + end: end - 1, + } } fn invalid_lval(invalid_target: &Spanned) -> Self { @@ -734,14 +751,6 @@ impl ParserError { } } -#[derive(Debug, thiserror::Error)] -enum ParseBlockError { - #[error(transparent)] - ParserError(#[from] ParserError), - #[error("Unclosed block")] - ReachedEnd { last_stmt_span: Option }, -} - #[cfg(test)] mod tests { use super::*; @@ -869,6 +878,20 @@ mod tests { assert_matches!(inner_stmts[1].as_ref(), Stmt::Expr(_)); } + #[test] + fn parses_class_decl() { + let stmt = parse_stmt("class foo {}"); + let (name, methods) = stmt.unwrap_class_decl(); + assert_eq!(name, "foo"); + assert!(methods.is_empty()); + + let stmt = parse_stmt("class foo { bar() {} }"); + let (name, methods) = stmt.unwrap_class_decl(); + assert_eq!(name, "foo"); + assert_eq!(methods.len(), 1); + assert_eq!(&methods[0].as_ref().name, "bar"); + } + #[test] fn dangling_else_attaches_to_nearest_if() { let stmt = parse_stmt("if (a) if (b) true; else false;"); diff --git a/src/resolver.rs b/src/resolver.rs index 9d6cdc1..51e5f5f 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -112,6 +112,7 @@ impl Resolver { } self.define(name.clone()); } + Stmt::Class { .. } => todo!("class declaration resolution"), Stmt::Expr(expr) => self.resolve_expr(expr)?, Stmt::Print(expr) => self.resolve_expr(expr)?,