change return type of parse_* functions from Option to Result

This commit is contained in:
Moritz Hölting 2024-09-19 20:54:39 +02:00
parent 2bc8281f19
commit 0cccee936e
10 changed files with 462 additions and 265 deletions

View File

@ -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(

View File

@ -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<T> = Result<T, Error>;
/// 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::<Vec<_>>()
.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::<u8>::None)
)
}
}
impl std::error::Error for InvalidArgument {}

View File

@ -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<T>(
&mut self,
delimiter: Delimiter,
f: impl FnOnce(&mut Self) -> Option<T>,
f: impl FnOnce(&mut Self) -> ParseResult<T>,
handler: &impl Handler<base::Error>,
) -> Option<DelimitedTree<T>> {
) -> ParseResult<DelimitedTree<T>> {
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<T>(&mut self, f: impl FnOnce(&mut Self) -> Option<T>) -> Option<T> {
///
/// # Errors
/// - If the given function returns an error.
pub fn try_parse<T>(&mut self, f: impl FnOnce(&mut Self) -> ParseResult<T>) -> ParseResult<T> {
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<T> {
pub open: Punctuation,
/// The tree inside the delimiter.
pub tree: Option<T>,
pub tree: ParseResult<T>,
/// 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<base::Error>) -> Option<Identifier> {
pub fn parse_identifier(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<Identifier> {
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<Error>) -> Option<Numeric> {
pub fn parse_numeric(&mut self, handler: &impl Handler<Error>) -> ParseResult<Numeric> {
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<base::Error>,
) -> Option<StringLiteral> {
) -> ParseResult<StringLiteral> {
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<base::Error>,
) -> Option<Keyword> {
) -> ParseResult<Keyword> {
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<base::Error>,
) -> Option<Punctuation> {
) -> ParseResult<Punctuation> {
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)
}
}
}

View File

@ -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<base::Error>) -> Option<Condition> {
///
/// # Precedence of the operators
/// 1. `!`
/// 2. `&&`
/// 3. `||`
pub fn parse_condition(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<Condition> {
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<base::Error>,
) -> Option<PrimaryCondition> {
) -> ParseResult<PrimaryCondition> {
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<base::Error>,
) -> Option<ParenthesizedCondition> {
) -> ParseResult<ParenthesizedCondition> {
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<ConditionalBinaryOperator> {
fn try_parse_conditional_binary_operator(&mut self) -> ParseResult<ConditionalBinaryOperator> {
self.try_parse(|parser| match parser.next_significant_token() {
Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation {
Reading::Atomic(token) => match token.clone() {
Token::Punctuation(punc) => match punc.punctuation {
'&' => {
let b = parser.parse_punctuation('&', false, &VoidHandler)?;
Some(ConditionalBinaryOperator::LogicalAnd(punc, b))
Ok(ConditionalBinaryOperator::LogicalAnd(punc, b))
}
'|' => {
let b = parser.parse_punctuation('|', false, &VoidHandler)?;
Some(ConditionalBinaryOperator::LogicalOr(punc, b))
Ok(ConditionalBinaryOperator::LogicalOr(punc, b))
}
_ => None,
_ => Err(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Either(&[
SyntaxKind::Punctuation('&'),
SyntaxKind::Punctuation('|'),
]),
found: Some(token),
})),
},
_ => None,
unexpected => Err(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Either(&[
SyntaxKind::Punctuation('&'),
SyntaxKind::Punctuation('|'),
]),
found: Some(unexpected),
})),
},
unexpected => Err(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Either(&[
SyntaxKind::Punctuation('&'),
SyntaxKind::Punctuation('|'),
]),
found: unexpected.into_token(),
})),
})
}
}

View File

@ -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<base::Error>) -> Option<Annotation> {
/// 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<base::Error>,
) -> ParseResult<Annotation> {
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()
let value = match parser.stop_at_significant() {
Reading::Atomic(Token::Punctuation(punc))
if punc.punctuation == '=' =>
{
if punctuation.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<base::Error>,
) -> Option<Declaration> {
) -> ParseResult<Declaration> {
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 {
self.parse_function(handler).map(|mut function| {
function.annotations.extend(annotations);
Some(Declaration::Function(function))
} else {
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Keyword(KeywordKind::Function),
found: None,
}));
None
}
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,18 +388,30 @@ 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<base::Error>) -> Option<Function> {
if let Reading::Atomic(Token::Keyword(function_keyword)) = self.stop_at_significant() {
/// 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<base::Error>) -> ParseResult<Function> {
let pub_keyword =
self.try_parse(|parser| parser.parse_keyword(KeywordKind::Pub, &VoidHandler));
match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(function_keyword))
if function_keyword.keyword == KeywordKind::Function =>
{
// eat the function keyword
self.forward();
@ -419,8 +427,8 @@ impl<'a> Parser<'a> {
// parse the block
let block = self.parse_block(handler)?;
Some(Function {
public_keyword: None,
Ok(Function {
public_keyword: pub_keyword.ok(),
annotations: Vec::new(),
function_keyword,
identifier,
@ -429,12 +437,15 @@ impl<'a> Parser<'a> {
close_paren: delimited_tree.close,
block,
})
} else {
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
}
unexpected => {
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Keyword(KeywordKind::Function),
found: self.peek().into_token(),
}));
None
found: unexpected.into_token(),
});
handler.receive(err.clone());
Err(err)
}
}
}
}

View File

@ -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<base::Error>) -> Option<Expression> {
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<base::Error>,
) -> ParseResult<Expression> {
self.parse_primary(handler).map(Expression::Primary)
}
/// Parses an [`Primary`]
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> Option<Primary> {
///
/// # 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<base::Error>) -> ParseResult<Primary> {
match self.stop_at_significant() {
// identifier expression
Reading::Atomic(Token::Identifier(identifier)) => {
@ -169,8 +180,8 @@ impl<'a> Parser<'a> {
self.forward();
// function call
if matches!(self.stop_at_significant(), Reading::IntoDelimited(punc) if punc.punctuation == '(')
{
match self.stop_at_significant() {
Reading::IntoDelimited(punc) if punc.punctuation == '(' => {
let token_tree = self.parse_enclosed_list(
Delimiter::Parenthesis,
',',
@ -178,15 +189,22 @@ impl<'a> Parser<'a> {
handler,
)?;
Some(Primary::FunctionCall(FunctionCall {
Ok(Primary::FunctionCall(FunctionCall {
identifier,
left_parenthesis: token_tree.open,
right_parenthesis: token_tree.close,
arguments: token_tree.list,
}))
} else {
}
unexpected => {
// insert parser for regular identifier here
None
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)
}
}
}

View File

@ -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<T>,
mut f: impl FnMut(&mut Self) -> ParseResult<T>,
handler: &impl Handler<base::Error>,
) -> Option<DelimitedList<T>> {
) -> ParseResult<DelimitedList<T>> {
fn skip_to_next_separator(this: &mut Parser, separator: char) -> Option<Punctuation> {
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<Punctuation> = 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<T>(
&mut self,
seperator: char,
mut f: impl FnMut(&mut Self) -> Option<T>,
mut f: impl FnMut(&mut Self) -> ParseResult<T>,
_handler: &impl Handler<base::Error>,
) -> Option<ConnectedList<T, Punctuation>> {
) -> ParseResult<ConnectedList<T, Punctuation>> {
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,

View File

@ -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::<String>();
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<base::Error>) -> Option<ProgramFile> {
pub fn parse_program(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<ProgramFile> {
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,
})

View File

@ -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<base::Error>) -> Option<Block> {
///
/// # Errors
/// - if the parser is not at a block.
pub fn parse_block(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Block> {
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<base::Error>) -> Option<Statement> {
pub fn parse_statement(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<Statement> {
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,
}))

View File

@ -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<base::Error>,
) -> Option<ExecuteBlock> {
) -> ParseResult<ExecuteBlock> {
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<base::Error>,
) -> Option<ExecuteBlockTail> {
) -> ParseResult<ExecuteBlockTail> {
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<StringLiteral>,
) -> Option<ExecuteBlockHead> {
Some(match keyword.keyword {
) -> ParseResult<ExecuteBlockHead> {
Ok(match keyword.keyword {
KeywordKind::Align => Align {
align_keyword: keyword,
open_paren: argument.open,