use codespan_reporting::{diagnostic, files}; use simplexpr::dynval; use diagnostic::*; use crate::{ config::{attributes::AttrError, validate::ValidationError}, error::{get_parse_error_span, AstError}, }; use super::parser::parse_error; use eww_shared_util::{AttrName, Span, Spanned, VarName}; fn span_to_primary_label(span: Span) -> Label { Label::primary(span.2, span.0..span.1) } fn span_to_secondary_label(span: Span) -> Label { Label::secondary(span.2, span.0..span.1) } #[macro_export] macro_rules! gen_diagnostic { ( $(msg = $msg:expr)? $(, label = $span:expr $(=> $label:expr)?)? $(, note = $note:expr)? $(,)? ) => { ::codespan_reporting::diagnostic::Diagnostic::error() $(.with_message($msg.to_string()))? $(.with_labels(vec![ ::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1) $(.with_message($label))? ]))? $(.with_notes(vec![$note.to_string()]))? }; ($msg:expr $(, $span:expr $(,)?)?) => {{ ::codespan_reporting::diagnostic::Diagnostic::error() .with_message($msg.to_string()) $(.with_labels(vec![::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1)]))? }}; } pub trait DiagnosticExt: Sized { fn with_label(self, label: Label) -> Self; } impl DiagnosticExt for Diagnostic { fn with_label(self, label: Label) -> Self { self.with_labels(vec![label]) } } pub trait ToDiagnostic: std::fmt::Debug { fn to_diagnostic(&self) -> Diagnostic; fn to_message(&self) -> String { self.to_diagnostic().message } } impl ToDiagnostic for Diagnostic { fn to_diagnostic(&self) -> Diagnostic { self.clone() } } impl ToDiagnostic for AstError { fn to_diagnostic(&self) -> Diagnostic { match self { AstError::UnknownToplevel(span, name) => gen_diagnostic!(self, span), AstError::MissingNode(span) => gen_diagnostic! { msg = "Expected another element", label = span => "Expected another element here", }, AstError::WrongExprType(span, expected, actual) => gen_diagnostic! { msg = "Wrong type of expression", label = span => format!("Expected a `{}` here", expected), note = format!("Expected: {}\nGot: {}", expected, actual), }, AstError::NotAValue(span, actual) => gen_diagnostic! { msg = format!("Expected value, but got `{}`", actual), label = span => "Expected some value here", note = format!("Got: {}", actual), }, AstError::ParseError { file_id, source } => lalrpop_error_to_diagnostic(source, *file_id), AstError::MismatchedElementName(span, expected, got) => gen_diagnostic! { msg = format!("Expected element `{}`, but found `{}`", expected, got), label = span => format!("Expected `{}` here", expected), note = format!("Expected: {}\nGot: {}", expected, got), }, AstError::ErrorContext { label_span, context, main_err } => { main_err.to_diagnostic().with_label(span_to_secondary_label(*label_span).with_message(context)) } AstError::ConversionError(source) => source.to_diagnostic(), AstError::Other(span, source) => gen_diagnostic!(source, span), AstError::AttrError(source) => source.to_diagnostic(), AstError::IncludedFileNotFound(include) => gen_diagnostic!( msg = format!("Included file `{}` not found", include.path), label = include.path_span => "Included here", ), AstError::TooManyNodes(extra_nodes_span, expected) => gen_diagnostic! { msg = self, label = extra_nodes_span => "these elements must not be here", note = "Consider wrapping the elements in some container element", }, AstError::ErrorNote(note, source) => source.to_diagnostic().with_notes(vec![note.to_string()]), AstError::ValidationError(source) => source.to_diagnostic(), } } } impl ToDiagnostic for parse_error::ParseError { fn to_diagnostic(&self) -> Diagnostic { match self { parse_error::ParseError::SimplExpr(error) => error.to_diagnostic(), parse_error::ParseError::LexicalError(span) => generate_lexical_error_diagnostic(*span), } } } impl ToDiagnostic for AttrError { fn to_diagnostic(&self) -> Diagnostic { match self { AttrError::MissingRequiredAttr(span, attr_name) => { gen_diagnostic!(format!("Missing attribute `{}`", attr_name), span) } AttrError::EvaluationError(span, source) => source.to_diagnostic(), AttrError::Other(span, source) => gen_diagnostic!(source, span), } } } impl ToDiagnostic for ValidationError { fn to_diagnostic(&self) -> Diagnostic { match self { ValidationError::UnknownWidget(span, name) => gen_diagnostic! { msg = self, label = span => "Used here", }, ValidationError::MissingAttr { widget_name, arg_name, arg_list_span, use_span } => { let mut diag = gen_diagnostic!(self).with_label(span_to_secondary_label(*use_span).with_message("Argument missing here")); if let Some(arg_list_span) = arg_list_span { diag = diag.with_label(span_to_secondary_label(*arg_list_span).with_message("But is required here")); } diag } } } } fn lalrpop_error_to_diagnostic( error: &lalrpop_util::ParseError, file_id: usize, ) -> Diagnostic { use lalrpop_util::ParseError::*; match error { InvalidToken { location } => gen_diagnostic!("Invalid token", Span::point(*location, file_id)), UnrecognizedEOF { location, expected } => gen_diagnostic! { msg = "Input ended unexpectedly. Check if you have any unclosed delimiters", label = Span::point(*location, file_id), }, UnrecognizedToken { token, expected } => gen_diagnostic! { msg = format!("Unexpected token `{}` encountered", token.1), label = Span(token.0, token.2, file_id) => "Token unexpected", }, ExtraToken { token } => gen_diagnostic!(format!("Extra token encountered: `{}`", token.1)), User { error } => error.to_diagnostic(), } } impl ToDiagnostic for simplexpr::error::Error { fn to_diagnostic(&self) -> Diagnostic { use simplexpr::error::Error::*; match self { ParseError { source, file_id } => lalrpop_error_to_diagnostic(source, *file_id), ConversionError(error) => error.to_diagnostic(), Eval(error) => error.to_diagnostic(), Other(error) => gen_diagnostic!(error), Spanned(span, error) => gen_diagnostic!(error, span), } } } impl ToDiagnostic for simplexpr::parser::lexer::LexicalError { fn to_diagnostic(&self) -> Diagnostic { generate_lexical_error_diagnostic(self.span()) } } impl ToDiagnostic for simplexpr::eval::EvalError { fn to_diagnostic(&self) -> Diagnostic { use simplexpr::eval::EvalError::*; match self { UnresolvedVariable(name) | UnknownVariable(name) | NoVariablesAllowed(name) => gen_diagnostic! { msg = self, note = format!("If you meant to use the literal value \"{}\", surround the value in quotes", name) }, Spanned(span, error) => error.as_ref().to_diagnostic().with_label(span_to_primary_label(*span)), _ => gen_diagnostic!(self, self.span()), } } } impl ToDiagnostic for dynval::ConversionError { fn to_diagnostic(&self) -> Diagnostic { let diag = gen_diagnostic! { msg = self, label = self.value.span() => format!("`{}` is not of type `{}`", self.value, self.target_type), }; diag.with_notes(self.source.as_ref().map(|x| vec![format!("{}", x)]).unwrap_or_default()) } } fn generate_lexical_error_diagnostic(span: Span) -> Diagnostic { gen_diagnostic! { msg = "Invalid token", label = span => "Invalid token" } }