Skip to content

Commit

Permalink
[WIP] Improve expression parser errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Zollerboy1 committed May 9, 2024
1 parent 5ed10d8 commit 50651b3
Show file tree
Hide file tree
Showing 13 changed files with 532 additions and 402 deletions.
2 changes: 2 additions & 0 deletions frontend/src/ast/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ impl<'src> Dump<'src> {
for (i, (name, field)) in self.fields.iter().enumerate() {
if is_multiline {
write!(f, "\n{} ", indent_str)?;
} else if i > 0 {
write!(f, " ")?;
}

write!(f, "{} ", format!("{}:", name).fg(color))?;
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ impl<'src, M: 'src + SourceManager> BinaryOperatorSequenceExpr<'src, M> {
}
}

impl<'src, M: SourceManager> BinaryOperatorSequenceExpr<'src, M> {
impl<'src, M: 'src + SourceManager> BinaryOperatorSequenceExpr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
let mut list = Vec::with_capacity(self.rest.len() * 2 + 1);

list.push(Dump::new("ExprPart").with_field("expr", self.first.get_dump()));

list.extend(self.rest.iter().flat_map(|(op_range, expr)| {
[
Dump::new("OperatorPart").with_field("operator", *op_range),
Dump::new("OperatorPart").with_field("operator", op_range.get_str()),
Dump::new("ExprPart").with_field("expr", expr.get_dump()),
]
}));
Expand All @@ -61,10 +61,10 @@ impl<'src, M: 'src + SourceManager> BinaryOperatorExpr<'src, M> {
}
}

impl<'src, M: SourceManager> BinaryOperatorExpr<'src, M> {
impl<'src, M: 'src + SourceManager> BinaryOperatorExpr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
Dump::new("BinaryOperatorExpr")
.with_field("operator", self.op_range)
.with_field("operator", self.op_range.get_str())
.with_field("lhs", self.lhs.get_dump())
.with_field("rhs", self.rhs.get_dump())
}
Expand All @@ -87,7 +87,7 @@ impl<'src, M: 'src + SourceManager> UnaryOperatorExpr<'src, M> {
}
}

impl<'src, M: SourceManager> UnaryOperatorExpr<'src, M> {
impl<'src, M: 'src + SourceManager> UnaryOperatorExpr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
let name = if self.is_prefix {
"PrefixOperatorExpr"
Expand All @@ -96,7 +96,7 @@ impl<'src, M: SourceManager> UnaryOperatorExpr<'src, M> {
};

Dump::new(name)
.with_field("operator", self.op_range)
.with_field("operator", self.op_range.get_str())
.with_field("operand", self.operand.get_dump())
}
}
Expand All @@ -116,7 +116,7 @@ impl<'src, M: 'src + SourceManager> BorrowExpr<'src, M> {
}
}

impl<'src, M: SourceManager> BorrowExpr<'src, M> {
impl<'src, M: 'src + SourceManager> BorrowExpr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
Dump::new("BorrowExpr")
.with_field("is_mutable", self.is_mutable)
Expand Down Expand Up @@ -146,7 +146,7 @@ pub enum LiteralExpr<'src, M: 'src + SourceManager> {
StringInterpolation(Arc<[InterpolationExprPart<'src, M>]>),
}

impl<'src, M: SourceManager> LiteralExpr<'src, M> {
impl<'src, M: 'src + SourceManager> LiteralExpr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
match self {
Self::Bool(v) => Dump::new("BoolExpr").with_field("value", *v),
Expand Down Expand Up @@ -186,7 +186,7 @@ pub enum ExprKind<'src, M: 'src + SourceManager> {
Error,
}

impl<'src, M: SourceManager> ExprKind<'src, M> {
impl<'src, M: 'src + SourceManager> ExprKind<'src, M> {
fn get_dump(&self) -> Dump<'src> {
match self {
ExprKind::BinaryOperatorSequence(expr) => expr.get_dump(),
Expand Down Expand Up @@ -234,7 +234,9 @@ impl<'src, M: 'src + SourceManager> Expr<'src, M> {

Self::new(kind, source_range)
}
}

impl<'src, M: 'src + SourceManager> Expr<'src, M> {
fn get_dump(&self) -> Dump<'src> {
self.kind.get_dump()
}
Expand Down
17 changes: 12 additions & 5 deletions frontend/src/diag/arg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::{Display, Formatter, Result as FmtResult};

use ariadne::Color;
use ariadne::{Color, Fmt as _};
use itertools::Itertools as _;
use juice_core::diag::DiagnosticArg;

Expand Down Expand Up @@ -65,8 +65,8 @@ impl Display for TokenKindArg {
}

impl DiagnosticArg for TokenKindArg {
fn with_color(self, _color: impl Into<Option<Color>>) -> impl Display {
self
fn with_color(self, color: impl Into<Option<Color>>) -> impl Display {
self.fg(color)
}
}

Expand Down Expand Up @@ -95,7 +95,14 @@ impl Display for TokenKindListArg {
}

impl DiagnosticArg for TokenKindListArg {
fn with_color(self, _color: impl Into<Option<Color>>) -> impl Display {
self
fn with_color(self, color: impl Into<Option<Color>>) -> impl Display {
let color = color.into();

format!(
"[{}]",
self.token_kinds
.into_iter()
.format_with(", ", |arg, f| f(&arg.with_color(color)))
)
}
}
1 change: 1 addition & 0 deletions frontend/src/diag/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ diagnostic!(
[error] NewlineInInterpolation => "Newline in string interpolation",
[error] MultilineStringInInterpolation => "Multiline string literal in string interpolation",
[error] UnexpectedParserError => "Unexpected parser error",
[error] ExpectedExpression(reason: &'static str) => "Expected expression {}",
}

#[derive(Debug, Clone)]
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/diag/diagnostic_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ diagnostic_note!(
EscapeSequenceLocation => "Escape sequence is here",
UnicodeEscapeLocation => "Unicode escape is here",
UnexpectedParserErrorLocation => "Parser error generated here",
ExpectedExpressionLocation => "Expression expected here",
BinaryOperatorLocation => "Binary operator is here",
}
);

Expand All @@ -43,6 +45,6 @@ diagnostic_note!(
InsufficientIndentation =>
"The indentation of the last line gets stripped from all other lines in multiline string literals",
UnexpectedParserError(expected: into TokenKindListArg, found: into TokenKindArg) =>
"Expected one of the following tokens: {}, but found `{}`",
"Expected one of the following tokens: {}, but found {}",
}
);
144 changes: 144 additions & 0 deletions frontend/src/parser/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use chumsky::{error::Error as ChumskyError, util::MaybeRef};
use derive_where::derive_where;

use super::lexer::TokenKind;
use crate::{
diag::{Diagnostic, DiagnosticConsumer, DiagnosticContextNote, DiagnosticEngine, DiagnosticNote},
source_loc::{SourceLoc, SourceRange},
source_manager::SourceManager,
};

#[derive_where(Debug, Clone)]
pub struct Error<'src, M: 'src + SourceManager> {
pub source_loc: SourceLoc<'src, M>,
pub diagnostic: Diagnostic<'src>,
pub context_notes: Vec<(SourceRange<'src, M>, DiagnosticContextNote<'src>)>,
pub note: Option<DiagnosticNote<'src>>,
}

impl<'src, M: 'src + SourceManager> Error<'src, M> {
pub fn new(
source_loc: SourceLoc<'src, M>,
diagnostic: Diagnostic<'src>,
context_notes: Vec<(SourceRange<'src, M>, DiagnosticContextNote<'src>)>,
note: Option<DiagnosticNote<'src>>,
) -> Self {
Self {
source_loc,
diagnostic,
context_notes,
note,
}
}

pub fn diagnose<C: DiagnosticConsumer<'src, M>>(
self,
diagnostics: &DiagnosticEngine<'src, M, C>,
) -> Result<(), C::Error> {
let mut report = diagnostics.report(self.source_loc, self.diagnostic);

for (source_range, context_note) in self.context_notes {
report = report.with_context_note(source_range, context_note);
}

if let Some(note) = self.note {
report = report.with_note(note);
}

report.diagnose()
}
}

#[derive_where(Debug, Clone)]
pub enum ParserErrorKind<'src, M: 'src + SourceManager> {
ExpectedFound {
source_range: SourceRange<'src, M>,
expected: Vec<Option<TokenKind<'src, M>>>,
found: Option<TokenKind<'src, M>>,
},
Other(Error<'src, M>),
}

pub struct ParserError<'src, M: 'src + SourceManager> {
kind: ParserErrorKind<'src, M>,
context_notes: Vec<(SourceRange<'src, M>, DiagnosticContextNote<'src>)>,
}

impl<'src, M: 'src + SourceManager> ParserError<'src, M> {
pub fn new(kind: ParserErrorKind<'src, M>) -> Self {
Self {
kind,
context_notes: Vec::new(),
}
}

pub fn with_context_note(
mut self,
source_range: SourceRange<'src, M>,
context_note: DiagnosticContextNote<'src>,
) -> Self {
self.context_notes.push((source_range, context_note));
self
}

pub fn diagnose<C: DiagnosticConsumer<'src, M>>(
mut self,
diagnostics: &DiagnosticEngine<'src, M, C>,
) -> Result<(), C::Error> {
let mut error = match self.kind {
ParserErrorKind::ExpectedFound {
source_range,
expected,
found,
} => Error::new(
source_range.start_loc(),
Diagnostic::unexpected_parser_error(),
vec![(source_range, DiagnosticContextNote::unexpected_parser_error_location())],
Some(DiagnosticNote::unexpected_parser_error(expected, found)),
),
ParserErrorKind::Other(error) => error,
};

error.context_notes.append(&mut self.context_notes);

error.diagnose(diagnostics)
}
}

impl<'src, M: 'src + SourceManager> From<Error<'src, M>> for ParserError<'src, M> {
fn from(err: Error<'src, M>) -> Self {
Self::new(ParserErrorKind::Other(err))
}
}

impl<'src, 'lex, M: 'src + SourceManager> ChumskyError<'lex, super::ParserInput<'src, 'lex, M>> for ParserError<'src, M>
where
'src: 'lex,
{
fn expected_found<Iter: IntoIterator<Item = Option<MaybeRef<'lex, TokenKind<'src, M>>>>>(
expected: Iter,
found: Option<MaybeRef<'lex, TokenKind<'src, M>>>,
span: SourceRange<'src, M>,
) -> Self {
Self::new(ParserErrorKind::ExpectedFound {
source_range: span,
expected: expected.into_iter().map(|e| e.as_deref().cloned()).collect(),
found: found.as_deref().cloned(),
})
}

fn merge(mut self, mut other: Self) -> Self {
if let (
ParserErrorKind::ExpectedFound { expected, .. },
ParserErrorKind::ExpectedFound {
expected: other_expected,
..
},
) = (&mut self.kind, &mut other.kind)
{
expected.append(other_expected);
}

self
}
}
Loading

0 comments on commit 50651b3

Please sign in to comment.