diff --git a/src/lib.rs b/src/lib.rs index 0259ef4..bd76d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,9 +89,7 @@ pub fn parse( tracing::info!("Parsing the source code at path: {}", path.display()); let mut parser = Parser::new(&tokens); - let program = parser - .parse_program(handler) - .ok_or_else(|| Error::other("An error occurred while parsing the source code."))?; + let program = parser.parse_program(handler)?; if handler.has_received() { return Err(Error::other( diff --git a/src/syntax/error.rs b/src/syntax/error.rs index f811ef5..4f543ec 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -3,14 +3,31 @@ use std::fmt::Display; use crate::{ - base::log::{Message, Severity, SourceCodeDisplay}, + base::{ + log::{Message, Severity, SourceCodeDisplay}, + source_file::Span, + }, lexical::token::{KeywordKind, Token}, }; +/// Result type for parsing operations. +pub type ParseResult = Result; + +/// An enumeration containing all kinds of syntactic errors that can occur while parsing the +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + UnexpectedSyntax(#[from] UnexpectedSyntax), + #[error(transparent)] + InvalidArgument(#[from] InvalidArgument), +} + /// Enumeration containing all kinds of syntax that can be failed to parse. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(missing_docs)] pub enum SyntaxKind { + Either(&'static [SyntaxKind]), Punctuation(char), Keyword(KeywordKind), Identifier, @@ -24,6 +41,43 @@ pub enum SyntaxKind { ExecuteBlockTail, } +impl SyntaxKind { + fn expected_binding_str(&self) -> String { + match self { + Self::Either(variants) => { + if variants.is_empty() { + "end of file".to_string() + } else if variants.len() == 1 { + variants[0].expected_binding_str() + } else { + let comma_range = ..variants.len() - 2; + let comma_elements = variants[comma_range] + .iter() + .map(Self::expected_binding_str) + .collect::>() + .join(", "); + format!( + "{}, or {}", + comma_elements, + variants.last().unwrap().expected_binding_str() + ) + } + } + Self::Identifier => "an identifier token".to_string(), + Self::Punctuation(char) => format!("a punctuation token `{char}`"), + Self::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()), + Self::Declaration => "a declaration token".to_string(), + Self::Numeric => "a numeric token".to_string(), + Self::StringLiteral => "a string literal".to_string(), + Self::Statement => "a statement syntax".to_string(), + Self::Expression => "an expression syntax".to_string(), + Self::Type => "a type syntax".to_string(), + Self::ExecuteBlock => "an execute block syntax".to_string(), + Self::ExecuteBlockTail => "an execute block tail syntax".to_string(), + } + } +} + /// A syntax/token is expected but found an other invalid token. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UnexpectedSyntax { @@ -36,19 +90,7 @@ pub struct UnexpectedSyntax { impl Display for UnexpectedSyntax { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let expected_binding = match self.expected { - SyntaxKind::Identifier => "an identifier token".to_string(), - SyntaxKind::Punctuation(char) => format!("a punctuation token `{char}`"), - SyntaxKind::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()), - SyntaxKind::Declaration => "a declaration token".to_string(), - SyntaxKind::Numeric => "a numeric token".to_string(), - SyntaxKind::StringLiteral => "a string literal".to_string(), - SyntaxKind::Statement => "a statement syntax".to_string(), - SyntaxKind::Expression => "an expression syntax".to_string(), - SyntaxKind::Type => "a type syntax".to_string(), - SyntaxKind::ExecuteBlock => "an execute block syntax".to_string(), - SyntaxKind::ExecuteBlockTail => "an execute block tail syntax".to_string(), - }; + let expected_binding = self.expected.expected_binding_str(); let found_binding = match self.found.clone() { Some(Token::Comment(..)) => "a comment token".to_string(), Some(Token::DocComment(..)) => "a doc comment token".to_string(), @@ -83,10 +125,24 @@ impl Display for UnexpectedSyntax { impl std::error::Error for UnexpectedSyntax {} -/// An enumeration containing all kinds of syntactic errors that can occur while parsing the -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error { - #[error(transparent)] - UnexpectedSyntax(#[from] UnexpectedSyntax), +/// An error that occurred due to an invalid argument. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InvalidArgument { + /// The error message. + pub message: String, + /// The span of the invalid argument. + pub span: Span, } + +impl Display for InvalidArgument { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", Message::new(Severity::Error, &self.message))?; + write!( + f, + "\n{}", + SourceCodeDisplay::new(&self.span, Option::::None) + ) + } +} + +impl std::error::Error for InvalidArgument {} diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 0264b38..b2e14b4 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -11,7 +11,7 @@ use crate::{ }, }; -use super::error::{Error, SyntaxKind, UnexpectedSyntax}; +use super::error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax}; /// Represents a parser that reads a token stream and constructs an abstract syntax tree. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)] @@ -38,12 +38,15 @@ impl<'a> Parser<'a> { /// Steps into the [`Delimited`] token stream and parses the content within the delimiters. /// /// The parser's position must be at the delimited token stream. + /// + /// # Errors + /// - If the parser's position is not at the delimited token stream. pub fn step_into( &mut self, delimiter: Delimiter, - f: impl FnOnce(&mut Self) -> Option, + f: impl FnOnce(&mut Self) -> ParseResult, handler: &impl Handler, - ) -> Option> { + ) -> ParseResult> { self.current_frame.stop_at_significant(); let raw_token_tree = self .current_frame @@ -62,7 +65,7 @@ impl<'a> Parser<'a> { delimited_tree } found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Punctuation(expected), found: Some(match found { TokenTree::Token(token) => token.clone(), @@ -70,18 +73,20 @@ impl<'a> Parser<'a> { Token::Punctuation(delimited_tree.open.clone()) } }), - })); + }); + handler.receive(err.clone()); - return None; + return Err(err); } } } else { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Punctuation(expected), found: self.get_reading(None).into_token(), - })); + }); + handler.receive(err.clone()); - return None; + return Err(err); }; // creates a new frame @@ -99,7 +104,10 @@ impl<'a> Parser<'a> { let tree = f(self); // pops the current frame off the stack - let new_frame = self.stack.pop()?; + let new_frame = self + .stack + .pop() + .expect("frame has been pushed on the stack before"); // the current frame must be at the end if !self.current_frame.is_exhausted() { @@ -111,10 +119,12 @@ impl<'a> Parser<'a> { .delimiter .closing_char(); - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Punctuation(expected), found: self.peek().into_token(), - })); + }); + handler.receive(err.clone()); + return Err(err); } let close_punctuation = self @@ -128,7 +138,7 @@ impl<'a> Parser<'a> { // replaces the current frame with the popped one self.current_frame = new_frame; - Some(DelimitedTree { + Ok(DelimitedTree { open, tree, close: close_punctuation, @@ -137,12 +147,15 @@ impl<'a> Parser<'a> { /// Tries to parse the given function, and if it fails, resets the current index to the /// `current_index` before the function call. - pub fn try_parse(&mut self, f: impl FnOnce(&mut Self) -> Option) -> Option { + /// + /// # Errors + /// - If the given function returns an error. + pub fn try_parse(&mut self, f: impl FnOnce(&mut Self) -> ParseResult) -> ParseResult { let current_index = self.current_frame.current_index; let result = f(self); - if result.is_none() { + if result.is_err() { self.current_frame.current_index = current_index; } @@ -157,7 +170,7 @@ pub struct DelimitedTree { pub open: Punctuation, /// The tree inside the delimiter. - pub tree: Option, + pub tree: ParseResult, /// The closing delimiter. pub close: Punctuation, @@ -363,15 +376,19 @@ impl<'a> Frame<'a> { /// /// # Errors /// If the next [`Token`] is not an [`Identifier`]. - pub fn parse_identifier(&mut self, handler: &impl Handler) -> Option { + pub fn parse_identifier( + &mut self, + handler: &impl Handler, + ) -> ParseResult { match self.next_significant_token() { - Reading::Atomic(Token::Identifier(ident)) => Some(ident), + Reading::Atomic(Token::Identifier(ident)) => Ok(ident), found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Identifier, found: found.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -380,15 +397,16 @@ impl<'a> Frame<'a> { /// /// # Errors /// If the next [`Token`] is not an [`Identifier`]. - pub fn parse_numeric(&mut self, handler: &impl Handler) -> Option { + pub fn parse_numeric(&mut self, handler: &impl Handler) -> ParseResult { match self.next_significant_token() { - Reading::Atomic(Token::Numeric(ident)) => Some(ident), + Reading::Atomic(Token::Numeric(ident)) => Ok(ident), found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Numeric, found: found.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -400,15 +418,16 @@ impl<'a> Frame<'a> { pub fn parse_string_literal( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.next_significant_token() { - Reading::Atomic(Token::StringLiteral(literal)) => Some(literal), + Reading::Atomic(Token::StringLiteral(literal)) => Ok(literal), found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::StringLiteral, found: found.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -421,17 +440,18 @@ impl<'a> Frame<'a> { &mut self, expected: KeywordKind, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.next_significant_token() { Reading::Atomic(Token::Keyword(keyword_token)) if keyword_token.keyword == expected => { - Some(keyword_token) + Ok(keyword_token) } found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Keyword(expected), found: found.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -445,7 +465,7 @@ impl<'a> Frame<'a> { expected: char, skip_insignificant: bool, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match if skip_insignificant { self.next_significant_token() } else { @@ -454,14 +474,15 @@ impl<'a> Frame<'a> { Reading::Atomic(Token::Punctuation(punctuation_token)) if punctuation_token.punctuation == expected => { - Some(punctuation_token) + Ok(punctuation_token) } found => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Punctuation(expected), found: found.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } diff --git a/src/syntax/syntax_tree/condition.rs b/src/syntax/syntax_tree/condition.rs index cadaca4..49d51ad 100644 --- a/src/syntax/syntax_tree/condition.rs +++ b/src/syntax/syntax_tree/condition.rs @@ -1,5 +1,7 @@ //! Syntax tree nodes for conditions. +#![allow(clippy::missing_errors_doc)] + use std::{cmp::Ordering, collections::VecDeque}; use enum_as_inner::EnumAsInner; @@ -9,14 +11,14 @@ use crate::{ base::{ self, source_file::{SourceElement, Span}, - VoidHandler, Handler, + Handler, VoidHandler, }, lexical::{ token::{Punctuation, StringLiteral, Token}, token_stream::Delimiter, }, syntax::{ - error::{Error, SyntaxKind, UnexpectedSyntax}, + error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax}, parser::{Parser, Reading}, }, }; @@ -241,12 +243,20 @@ impl SourceElement for Condition { impl<'a> Parser<'a> { /// Parses a [`Condition`]. - pub fn parse_condition(&mut self, handler: &impl Handler) -> Option { + /// + /// # Precedence of the operators + /// 1. `!` + /// 2. `&&` + /// 3. `||` + pub fn parse_condition( + &mut self, + handler: &impl Handler, + ) -> ParseResult { let mut lhs = Condition::Primary(self.parse_primary_condition(handler)?); let mut expressions = VecDeque::new(); // Parses a list of binary operators and expressions - while let Some(binary_operator) = self.try_parse_conditional_binary_operator() { + while let Ok(binary_operator) = self.try_parse_conditional_binary_operator() { expressions.push_back(( binary_operator, Some(Condition::Primary(self.parse_primary_condition(handler)?)), @@ -300,14 +310,14 @@ impl<'a> Parser<'a> { } } - Some(lhs) + Ok(lhs) } /// Parses a [`PrimaryCondition`]. pub fn parse_primary_condition( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.stop_at_significant() { // prefixed expression Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '!' => { @@ -321,7 +331,7 @@ impl<'a> Parser<'a> { let operand = Box::new(self.parse_primary_condition(handler)?); - Some(PrimaryCondition::Prefix(ConditionalPrefix { + Ok(PrimaryCondition::Prefix(ConditionalPrefix { operator, operand, })) @@ -330,7 +340,7 @@ impl<'a> Parser<'a> { // string literal Reading::Atomic(Token::StringLiteral(literal)) => { self.forward(); - Some(PrimaryCondition::StringLiteral(literal)) + Ok(PrimaryCondition::StringLiteral(literal)) } // parenthesized condition @@ -342,12 +352,17 @@ impl<'a> Parser<'a> { // make progress self.forward(); - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: SyntaxKind::Expression, + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Either(&[ + SyntaxKind::Punctuation('!'), + SyntaxKind::StringLiteral, + SyntaxKind::Punctuation('('), + ]), found: unexpected.into_token(), - })); + }); + handler.receive(err.clone()); - None + Err(err) } } } @@ -356,38 +371,59 @@ impl<'a> Parser<'a> { pub fn parse_parenthesized_condition( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { let token_tree = self.step_into( Delimiter::Parenthesis, |parser| { let cond = parser.parse_condition(handler)?; parser.stop_at_significant(); - Some(cond) + Ok(cond) }, handler, )?; - Some(ParenthesizedCondition { + Ok(ParenthesizedCondition { open_paren: token_tree.open, condition: Box::new(token_tree.tree?), close_paren: token_tree.close, }) } - fn try_parse_conditional_binary_operator(&mut self) -> Option { + fn try_parse_conditional_binary_operator(&mut self) -> ParseResult { self.try_parse(|parser| match parser.next_significant_token() { - Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation { - '&' => { - let b = parser.parse_punctuation('&', false, &VoidHandler)?; - Some(ConditionalBinaryOperator::LogicalAnd(punc, b)) - } - '|' => { - let b = parser.parse_punctuation('|', false, &VoidHandler)?; - Some(ConditionalBinaryOperator::LogicalOr(punc, b)) - } - _ => None, + Reading::Atomic(token) => match token.clone() { + Token::Punctuation(punc) => match punc.punctuation { + '&' => { + let b = parser.parse_punctuation('&', false, &VoidHandler)?; + Ok(ConditionalBinaryOperator::LogicalAnd(punc, b)) + } + '|' => { + let b = parser.parse_punctuation('|', false, &VoidHandler)?; + Ok(ConditionalBinaryOperator::LogicalOr(punc, b)) + } + _ => Err(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Either(&[ + SyntaxKind::Punctuation('&'), + SyntaxKind::Punctuation('|'), + ]), + found: Some(token), + })), + }, + unexpected => Err(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Either(&[ + SyntaxKind::Punctuation('&'), + SyntaxKind::Punctuation('|'), + ]), + found: Some(unexpected), + })), }, - _ => None, + unexpected => Err(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Either(&[ + SyntaxKind::Punctuation('&'), + SyntaxKind::Punctuation('|'), + ]), + found: unexpected.into_token(), + })), }) } } diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index 0e66178..431eff7 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -15,7 +15,7 @@ use crate::{ token_stream::Delimiter, }, syntax::{ - error::{Error, SyntaxKind, UnexpectedSyntax}, + error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax}, parser::{Parser, Reading}, }, }; @@ -227,7 +227,15 @@ impl SourceElement for Import { } impl<'a> Parser<'a> { - pub fn parse_annotation(&mut self, handler: &impl Handler) -> Option { + /// Parses an annotation. + /// + /// # Errors + /// - if the parser position is not at an annotation. + /// - if the parsing of the annotation fails + pub fn parse_annotation( + &mut self, + handler: &impl Handler, + ) -> ParseResult { match self.stop_at_significant() { Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => { // eat the pound sign @@ -239,36 +247,29 @@ impl<'a> Parser<'a> { |parser| { let identifier = parser.parse_identifier(handler)?; - let value = if let Reading::Atomic(Token::Punctuation(punctuation)) = - parser.stop_at_significant() - { - if punctuation.punctuation == '=' { + let value = match parser.stop_at_significant() { + Reading::Atomic(Token::Punctuation(punc)) + if punc.punctuation == '=' => + { // eat the equals sign parser.forward(); // parse the string literal - let string_literal = parser - .next_significant_token() - .into_token()? - .into_string_literal() - .ok()?; + let string_literal = parser.parse_string_literal(handler)?; - Some((punctuation, string_literal)) - } else { - None + Some((punc, string_literal)) } - } else { - None + _ => None, }; - Some((identifier, value)) + Ok((identifier, value)) }, handler, )?; let (identifier, value) = content.tree?; - Some(Annotation { + Ok(Annotation { pound_sign: punctuation, open_bracket: content.open, identifier, @@ -276,7 +277,14 @@ impl<'a> Parser<'a> { close_bracket: content.close, }) } - _ => None, + unexpected => { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Punctuation('#'), + found: unexpected.into_token(), + }); + handler.receive(err.clone()); + Err(err) + } } } @@ -284,7 +292,7 @@ impl<'a> Parser<'a> { pub fn parse_declaration( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.stop_at_significant() { Reading::Atomic(Token::Keyword(function_keyword)) if function_keyword.keyword == KeywordKind::Function => @@ -293,22 +301,17 @@ impl<'a> Parser<'a> { tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); - Some(Declaration::Function(function)) + Ok(Declaration::Function(function)) } Reading::Atomic(Token::Keyword(pub_keyword)) if pub_keyword.keyword == KeywordKind::Pub => { - // eat the public keyword - self.forward(); - - // parse the function keyword let function = self.parse_function(handler)?; - Some(Declaration::Function(Function { - public_keyword: Some(pub_keyword), - ..function - })) + tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); + + Ok(Declaration::Function(function)) } // parse annotations @@ -316,23 +319,15 @@ impl<'a> Parser<'a> { // parse the annotation let mut annotations = Vec::new(); - while let Some(annotation) = - self.try_parse(|parser| parser.parse_annotation(handler)) + while let Ok(annotation) = + self.try_parse(|parser| parser.parse_annotation(&VoidHandler)) { annotations.push(annotation); } - self.parse_declaration(handler).and_then(|declaration| { - if let Declaration::Function(mut function) = declaration { - function.annotations.extend(annotations); - Some(Declaration::Function(function)) - } else { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: SyntaxKind::Keyword(KeywordKind::Function), - found: None, - })); - None - } + self.parse_function(handler).map(|mut function| { + function.annotations.extend(annotations); + Declaration::Function(function) }) } @@ -366,12 +361,12 @@ impl<'a> Parser<'a> { // } ; - if let Some(items) = items { + if let Ok(items) = items { let semicolon = self.parse_punctuation(';', true, handler)?; tracing::trace!("Parsed import from '{:?}'", module.str_content()); - Some(Declaration::Import(Import { + Ok(Declaration::Import(Import { from_keyword, module, import_keyword, @@ -379,12 +374,13 @@ impl<'a> Parser<'a> { semicolon, })) } else { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: SyntaxKind::Identifier, + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Punctuation('*'), found: self.stop_at_significant().into_token(), - })); + }); + handler.receive(err.clone()); - None + Err(err) } } @@ -392,49 +388,64 @@ impl<'a> Parser<'a> { // make progress self.forward(); - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Declaration, found: unexpected.into_token(), - })); + }); + handler.receive(err.clone()); - None + Err(err) } } } - pub fn parse_function(&mut self, handler: &impl Handler) -> Option { - if let Reading::Atomic(Token::Keyword(function_keyword)) = self.stop_at_significant() { - // eat the function keyword - self.forward(); + /// Parses a function. + /// + /// # Errors + /// - if the parser is not at a function (not at annotation). + /// - if the parsing of the function fails. + pub fn parse_function(&mut self, handler: &impl Handler) -> ParseResult { + let pub_keyword = + self.try_parse(|parser| parser.parse_keyword(KeywordKind::Pub, &VoidHandler)); - // parse the identifier - let identifier = self.parse_identifier(handler)?; - let delimited_tree = self.parse_enclosed_list( - Delimiter::Parenthesis, - ',', - |parser: &mut Parser<'_>| parser.parse_identifier(handler), - handler, - )?; + match self.stop_at_significant() { + Reading::Atomic(Token::Keyword(function_keyword)) + if function_keyword.keyword == KeywordKind::Function => + { + // eat the function keyword + self.forward(); - // parse the block - let block = self.parse_block(handler)?; + // parse the identifier + let identifier = self.parse_identifier(handler)?; + let delimited_tree = self.parse_enclosed_list( + Delimiter::Parenthesis, + ',', + |parser: &mut Parser<'_>| parser.parse_identifier(handler), + handler, + )?; - Some(Function { - public_keyword: None, - annotations: Vec::new(), - function_keyword, - identifier, - open_paren: delimited_tree.open, - parameters: delimited_tree.list, - close_paren: delimited_tree.close, - block, - }) - } else { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: SyntaxKind::Keyword(KeywordKind::Function), - found: self.peek().into_token(), - })); - None + // parse the block + let block = self.parse_block(handler)?; + + Ok(Function { + public_keyword: pub_keyword.ok(), + annotations: Vec::new(), + function_keyword, + identifier, + open_paren: delimited_tree.open, + parameters: delimited_tree.list, + close_paren: delimited_tree.close, + block, + }) + } + unexpected => { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Keyword(KeywordKind::Function), + found: unexpected.into_token(), + }); + handler.receive(err.clone()); + Err(err) + } } } } diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index 3eb9e81..1db61bf 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -14,7 +14,8 @@ use crate::{ token_stream::Delimiter, }, syntax::{ - error::{Error, UnexpectedSyntax}, + self, + error::{Error, ParseResult, UnexpectedSyntax}, parser::{Parser, Reading}, }, }; @@ -156,12 +157,22 @@ impl LuaCode { impl<'a> Parser<'a> { /// Parses an [`Expression`] - pub fn parse_expression(&mut self, handler: &impl Handler) -> Option { - Some(Expression::Primary(self.parse_primary(handler)?)) + /// + /// # Errors + /// - If the parser is not at a primary expression. + pub fn parse_expression( + &mut self, + handler: &impl Handler, + ) -> ParseResult { + self.parse_primary(handler).map(Expression::Primary) } /// Parses an [`Primary`] - pub fn parse_primary(&mut self, handler: &impl Handler) -> Option { + /// + /// # Errors + /// - If the parser is not at a primary expression. + /// - If the parser is not at a valid primary expression. + pub fn parse_primary(&mut self, handler: &impl Handler) -> ParseResult { match self.stop_at_significant() { // identifier expression Reading::Atomic(Token::Identifier(identifier)) => { @@ -169,24 +180,31 @@ impl<'a> Parser<'a> { self.forward(); // function call - if matches!(self.stop_at_significant(), Reading::IntoDelimited(punc) if punc.punctuation == '(') - { - let token_tree = self.parse_enclosed_list( - Delimiter::Parenthesis, - ',', - |parser| parser.parse_expression(handler).map(Box::new), - handler, - )?; + match self.stop_at_significant() { + Reading::IntoDelimited(punc) if punc.punctuation == '(' => { + let token_tree = self.parse_enclosed_list( + Delimiter::Parenthesis, + ',', + |parser| parser.parse_expression(handler).map(Box::new), + handler, + )?; - Some(Primary::FunctionCall(FunctionCall { - identifier, - left_parenthesis: token_tree.open, - right_parenthesis: token_tree.close, - arguments: token_tree.list, - })) - } else { - // insert parser for regular identifier here - None + Ok(Primary::FunctionCall(FunctionCall { + identifier, + left_parenthesis: token_tree.open, + right_parenthesis: token_tree.close, + arguments: token_tree.list, + })) + } + unexpected => { + // insert parser for regular identifier here + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: syntax::error::SyntaxKind::Punctuation('('), + found: unexpected.into_token(), + }); + handler.receive(err.clone()); + Err(err) + } } } @@ -195,7 +213,7 @@ impl<'a> Parser<'a> { // eat the string literal self.forward(); - Some(Primary::StringLiteral(literal)) + Ok(Primary::StringLiteral(literal)) } // lua code expression @@ -212,9 +230,16 @@ impl<'a> Parser<'a> { |parser| match parser.next_significant_token() { Reading::Atomic(Token::Identifier(identifier)) => { parser.forward(); - Some(identifier) + Ok(identifier) + } + unexpected => { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: syntax::error::SyntaxKind::Identifier, + found: unexpected.into_token(), + }); + handler.receive(err.clone()); + Err(err) } - _ => None, }, handler, )?; @@ -241,12 +266,12 @@ impl<'a> Parser<'a> { }) .expect("Invalid lua code span"); - Some(combined.str().trim().to_owned()) + Ok(combined.str().trim().to_owned()) }, handler, )?; - Some(Primary::Lua(Box::new(LuaCode { + Ok(Primary::Lua(Box::new(LuaCode { lua_keyword, left_parenthesis: variables.open, variables: variables.list, @@ -261,12 +286,13 @@ impl<'a> Parser<'a> { // make progress self.forward(); - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: crate::syntax::error::SyntaxKind::Expression, + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: syntax::error::SyntaxKind::Expression, found: unexpected.into_token(), - })); + }); + handler.receive(err.clone()); - None + Err(err) } } } diff --git a/src/syntax/syntax_tree/mod.rs b/src/syntax/syntax_tree/mod.rs index 399e657..7d892e5 100644 --- a/src/syntax/syntax_tree/mod.rs +++ b/src/syntax/syntax_tree/mod.rs @@ -15,7 +15,7 @@ use crate::{ syntax::parser::Reading, }; -use super::parser::Parser; +use super::{error::ParseResult, parser::Parser}; pub mod condition; pub mod declaration; @@ -76,9 +76,9 @@ impl<'a> Parser<'a> { &mut self, delimiter: Delimiter, separator: char, - mut f: impl FnMut(&mut Self) -> Option, + mut f: impl FnMut(&mut Self) -> ParseResult, handler: &impl Handler, - ) -> Option> { + ) -> ParseResult> { fn skip_to_next_separator(this: &mut Parser, separator: char) -> Option { if let Reading::Atomic(Token::Punctuation(punc)) = this.stop_at(|token| { matches!( @@ -101,7 +101,7 @@ impl<'a> Parser<'a> { let mut trailing_separator: Option = None; while !parser.is_exhausted() { - let Some(element) = f(parser) else { + let Ok(element) = f(parser) else { skip_to_next_separator(parser, separator); continue; }; @@ -122,7 +122,7 @@ impl<'a> Parser<'a> { // expect separator if not exhausted if !parser.is_exhausted() { - let Some(separator) = parser.parse_punctuation(separator, true, handler) + let Ok(separator) = parser.parse_punctuation(separator, true, handler) else { if let Some(punctuation) = skip_to_next_separator(parser, separator) { trailing_separator = Some(punctuation); @@ -135,7 +135,7 @@ impl<'a> Parser<'a> { } } - Some(first.map(|first| ConnectedList { + Ok(first.map(|first| ConnectedList { first, rest, trailing_separator, @@ -144,7 +144,7 @@ impl<'a> Parser<'a> { handler, )?; - Some(DelimitedList { + Ok(DelimitedList { open: delimited_tree.open, list: delimited_tree.tree.unwrap(), close: delimited_tree.close, @@ -162,20 +162,20 @@ impl<'a> Parser<'a> { pub fn parse_connected_list( &mut self, seperator: char, - mut f: impl FnMut(&mut Self) -> Option, + mut f: impl FnMut(&mut Self) -> ParseResult, _handler: &impl Handler, - ) -> Option> { + ) -> ParseResult> { let first = f(self)?; let mut rest = Vec::new(); - while let Some(sep) = + while let Ok(sep) = self.try_parse(|parser| parser.parse_punctuation(seperator, true, &VoidHandler)) { - if let Some(element) = self.try_parse(&mut f) { + if let Ok(element) = self.try_parse(&mut f) { rest.push((sep, element)); } else { - return Some(ConnectedList { + return Ok(ConnectedList { first, rest, trailing_separator: Some(sep), @@ -183,7 +183,7 @@ impl<'a> Parser<'a> { } } - Some(ConnectedList { + Ok(ConnectedList { first, rest, trailing_separator: None, diff --git a/src/syntax/syntax_tree/program.rs b/src/syntax/syntax_tree/program.rs index 8740f7f..e487d2a 100644 --- a/src/syntax/syntax_tree/program.rs +++ b/src/syntax/syntax_tree/program.rs @@ -1,6 +1,7 @@ //! The program node of the syntax tree. use getset::Getters; +use itertools::Itertools; use crate::{ base::{ @@ -11,7 +12,7 @@ use crate::{ lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token}, syntax::{ self, - error::{SyntaxKind, UnexpectedSyntax}, + error::{InvalidArgument, ParseResult, SyntaxKind, UnexpectedSyntax}, parser::{Parser, Reading}, }, }; @@ -70,18 +71,34 @@ impl Namespace { } /// Validates a namespace string. - #[must_use] - pub fn validate_str(namespace: &str) -> bool { + /// + /// # Errors + /// - If the namespace contains invalid characters. + pub fn validate_str(namespace: &str) -> Result<(), String> { const VALID_CHARS: &str = "0123456789abcdefghijklmnopqrstuvwxyz_-."; - namespace.chars().all(|c| VALID_CHARS.contains(c)) + let invalid_chars = namespace + .chars() + .filter(|c| !VALID_CHARS.contains(*c)) + .sorted() + .unique() + .collect::(); + + if invalid_chars.is_empty() { + Ok(()) + } else { + Err(invalid_chars) + } } } impl<'a> Parser<'a> { /// Parses a [`ProgramFile`]. #[tracing::instrument(level = "debug", skip_all)] - pub fn parse_program(&mut self, handler: &impl Handler) -> Option { + pub fn parse_program( + &mut self, + handler: &impl Handler, + ) -> ParseResult { tracing::debug!("Parsing program"); let namespace = match self.stop_at_significant() { @@ -92,23 +109,37 @@ impl<'a> Parser<'a> { self.forward(); let namespace_name = self.parse_string_literal(handler).and_then(|name| { - Namespace::validate_str(name.str_content().as_ref()).then_some(name) + Namespace::validate_str(name.str_content().as_ref()) + .map(|()| name.clone()) + .map_err(|invalid| { + let err = syntax::error::Error::InvalidArgument(InvalidArgument { + message: format!( + "Invalid characters in namespace '{}'. The following characters are not allowed in namespace definitions: '{}'", + name.str_content(), + invalid + ), + span: name.span(), + }); + handler.receive(err.clone()); + err + }) })?; let semicolon = self.parse_punctuation(';', true, handler)?; - Some(Namespace { + Ok(Namespace { namespace_keyword, namespace_name, semicolon, }) } unexpected => { - handler.receive(syntax::error::Error::from(UnexpectedSyntax { + let err = syntax::error::Error::from(UnexpectedSyntax { expected: SyntaxKind::Keyword(KeywordKind::Namespace), found: unexpected.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } }?; @@ -123,7 +154,7 @@ impl<'a> Parser<'a> { let result = self.parse_declaration(handler); #[allow(clippy::option_if_let_else)] - if let Some(x) = result { + if let Ok(x) = result { declarations.push(x); } else { self.stop_at(|reading| { @@ -137,7 +168,7 @@ impl<'a> Parser<'a> { } } - Some(ProgramFile { + Ok(ProgramFile { namespace, declarations, }) diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index 435a51d..412fd40 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -15,7 +15,10 @@ use crate::{ token::{CommandLiteral, DocComment, Keyword, KeywordKind, Punctuation, Token}, token_stream::Delimiter, }, - syntax::parser::{Parser, Reading}, + syntax::{ + error::ParseResult, + parser::{Parser, Reading}, + }, }; use self::execute_block::ExecuteBlock; @@ -209,7 +212,10 @@ impl Semicolon { impl<'a> Parser<'a> { /// Parses a [`Block`]. - pub fn parse_block(&mut self, handler: &impl Handler) -> Option { + /// + /// # Errors + /// - if the parser is not at a block. + pub fn parse_block(&mut self, handler: &impl Handler) -> ParseResult { let token_tree = self.step_into( Delimiter::Brace, |parser| { @@ -217,7 +223,7 @@ impl<'a> Parser<'a> { while !parser.is_exhausted() { parser.parse_statement(handler).map_or_else( - || { + |_| { // error recovery parser.stop_at(|reading| matches!( reading, @@ -234,12 +240,12 @@ impl<'a> Parser<'a> { ); } - Some(statements) + Ok(statements) }, handler, )?; - Some(Block { + Ok(Block { open_brace: token_tree.open, statements: token_tree.tree?, close_brace: token_tree.close, @@ -248,19 +254,22 @@ impl<'a> Parser<'a> { /// Parses a [`Statement`]. #[tracing::instrument(level = "trace", skip_all)] - pub fn parse_statement(&mut self, handler: &impl Handler) -> Option { + pub fn parse_statement( + &mut self, + handler: &impl Handler, + ) -> ParseResult { match self.stop_at_significant() { // variable declaration Reading::Atomic(Token::CommandLiteral(command)) => { self.forward(); tracing::trace!("Parsed literal command '{}'", command.clean_command()); - Some(Statement::LiteralCommand(command)) + Ok(Statement::LiteralCommand(command)) } // block statement Reading::IntoDelimited(open_brace) if open_brace.punctuation == '{' => { let block = self.parse_block(handler)?; - Some(Statement::Block(block)) + Ok(Statement::Block(block)) } // execute block @@ -274,7 +283,7 @@ impl<'a> Parser<'a> { // doc comment Reading::Atomic(Token::DocComment(doc_comment)) => { self.forward(); - Some(Statement::DocComment(doc_comment)) + Ok(Statement::DocComment(doc_comment)) } // grouping statement @@ -288,7 +297,7 @@ impl<'a> Parser<'a> { tracing::trace!("Parsed group command"); - Some(Statement::Grouping(Grouping { + Ok(Statement::Grouping(Grouping { group_keyword, block, })) @@ -306,7 +315,7 @@ impl<'a> Parser<'a> { tracing::trace!("Parsed run statement: {:?}", expression); - Some(Statement::Run(Run { + Ok(Statement::Run(Run { run_keyword, expression, semicolon, @@ -320,7 +329,7 @@ impl<'a> Parser<'a> { tracing::trace!("Parsed semicolon statement: {:?}", expression); - Some(Statement::Semicolon(Semicolon { + Ok(Statement::Semicolon(Semicolon { expression, semicolon, })) diff --git a/src/syntax/syntax_tree/statement/execute_block.rs b/src/syntax/syntax_tree/statement/execute_block.rs index 2bc4bfa..d579526 100644 --- a/src/syntax/syntax_tree/statement/execute_block.rs +++ b/src/syntax/syntax_tree/statement/execute_block.rs @@ -8,15 +8,14 @@ use crate::{ base::{ self, source_file::{SourceElement, Span}, - VoidHandler, Handler, + Handler, VoidHandler, }, lexical::{ token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token}, token_stream::Delimiter, }, syntax::{ - self, - error::{SyntaxKind, UnexpectedSyntax}, + error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax}, parser::{DelimitedTree, Parser, Reading}, syntax_tree::condition::ParenthesizedCondition, }, @@ -711,10 +710,14 @@ impl Summon { impl<'a> Parser<'a> { /// Parses an [`ExecuteBlock`]. + /// + /// # Errors + /// - if not at the start of an execute block statement. + /// - if the parsing of the execute block statement fails. pub fn parse_execute_block_statement( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.stop_at_significant() { Reading::Atomic(Token::Keyword(if_keyword)) if if_keyword.keyword == KeywordKind::If => @@ -739,14 +742,17 @@ impl<'a> Parser<'a> { // eat the else keyword parser.forward(); - let else_block = parser.parse_block(handler)?; + let else_block = parser.parse_block(&VoidHandler)?; - Some((else_keyword, else_block)) + Ok((else_keyword, else_block)) } - _ => None, + unexpected => Err(UnexpectedSyntax { + expected: SyntaxKind::Keyword(KeywordKind::Else), + found: unexpected.into_token(), + }), }?; - Some(( + Ok(( block, Else { else_keyword, @@ -755,11 +761,11 @@ impl<'a> Parser<'a> { )) }); - if let Some((block, else_tail)) = else_tail { - Some(ExecuteBlock::IfElse(conditional, block, else_tail)) + if let Ok((block, else_tail)) = else_tail { + Ok(ExecuteBlock::IfElse(conditional, block, else_tail)) } else { let tail = self.parse_execute_block_tail(handler)?; - Some(ExecuteBlock::HeadTail( + Ok(ExecuteBlock::HeadTail( ExecuteBlockHead::Conditional(conditional), tail, )) @@ -777,11 +783,12 @@ impl<'a> Parser<'a> { handler, ), unexpected => { - handler.receive(syntax::error::Error::from(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::Punctuation('('), found: unexpected.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } }?; @@ -789,16 +796,17 @@ impl<'a> Parser<'a> { let head = head_from_keyword(keyword, argument)?; - Some(ExecuteBlock::HeadTail(head, tail)) + Ok(ExecuteBlock::HeadTail(head, tail)) } // unexpected unexpected => { - handler.receive(syntax::error::Error::from(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::ExecuteBlock, found: unexpected.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -806,7 +814,7 @@ impl<'a> Parser<'a> { fn parse_execute_block_tail( &mut self, handler: &impl Handler, - ) -> Option { + ) -> ParseResult { match self.stop_at_significant() { // nested execute block Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == ',' => { @@ -815,7 +823,7 @@ impl<'a> Parser<'a> { let execute_block = self.parse_execute_block_statement(handler)?; - Some(ExecuteBlockTail::ExecuteBlock( + Ok(ExecuteBlockTail::ExecuteBlock( punc, Box::new(execute_block), )) @@ -825,15 +833,16 @@ impl<'a> Parser<'a> { Reading::IntoDelimited(punc) if punc.punctuation == '{' => { let block = self.parse_block(handler)?; - Some(ExecuteBlockTail::Block(block)) + Ok(ExecuteBlockTail::Block(block)) } unexpected => { - handler.receive(syntax::error::Error::from(UnexpectedSyntax { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { expected: SyntaxKind::ExecuteBlockTail, found: unexpected.into_token(), - })); - None + }); + handler.receive(err.clone()); + Err(err) } } } @@ -842,8 +851,8 @@ impl<'a> Parser<'a> { fn head_from_keyword( keyword: Keyword, argument: DelimitedTree, -) -> Option { - Some(match keyword.keyword { +) -> ParseResult { + Ok(match keyword.keyword { KeywordKind::Align => Align { align_keyword: keyword, open_paren: argument.open,