rott/rottlib/src/diagnostics/parse_error_diagnostics/mod.rs

155 lines
6.0 KiB
Rust

//! Conversion from parser errors to user-facing diagnostics.
//!
//! This module maps [`ParseError`] values produced by the parser to structured
//! [`Diagnostic`] values suitable for rendering.
//!
//! It owns the top-level dispatch by [`crate::parser::ParseErrorKind`] and
//! keeps small shared utilities used by parse-error diagnostic constructors.
//!
//! Concrete diagnostic constructors are grouped into submodules that mirror
//! parser areas or grammar families.
use super::{Diagnostic, DiagnosticBuilder};
use crate::diagnostics::parse_error_diagnostics::block_items::{
diagnostic_block_expected_item, diagnostic_block_missing_closing_brace,
diagnostic_block_missing_semicolon_after_expression,
};
use crate::lexer::{TokenPosition, TokenSpan, TokenizedFile};
use crate::parser::{ParseError, ParseErrorKind};
mod block_items;
mod control_flow_expressions;
mod primary_expressions;
#[derive(Clone, Copy)]
enum FoundAt<'src> {
Token(&'src str),
EndOfFile,
Unknown,
}
fn found_at<'src>(file: &TokenizedFile<'src>, position: TokenPosition) -> FoundAt<'src> {
if let Some(token_text) = file.token_text(position) {
FoundAt::Token(token_text)
} else if file.is_eof(&position) {
FoundAt::EndOfFile
} else {
FoundAt::Unknown
}
}
fn collapse_span_to_end_on_same_line(file: &TokenizedFile<'_>, mut span: TokenSpan) -> TokenSpan {
if file.same_line(span.start, span.end) {
span.start = span.end;
}
span
}
fn should_show_context_label<'src>(
file: &TokenizedFile<'src>,
context_span: TokenSpan,
blame_span: TokenSpan,
) -> bool {
!file.same_line(context_span.start, blame_span.end)
}
fn primary_span_with_optional_multiline_context<'src>(
file: &TokenizedFile<'src>,
context_span: Option<TokenSpan>,
blame_span: TokenSpan,
) -> TokenSpan {
if let Some(context_span) = context_span
&& should_show_context_label(file, context_span, blame_span)
{
TokenSpan {
start: context_span.start,
end: blame_span.end,
}
} else {
collapse_span_to_end_on_same_line(file, blame_span)
}
}
pub(crate) fn diagnostic_from_parse_error<'src>(
error: ParseError,
file: &TokenizedFile<'src>,
) -> Diagnostic {
use control_flow_expressions::*;
use primary_expressions::*;
match error.kind {
// primary_expressions.rs
ParseErrorKind::ParenthesizedExpressionInvalidStart => {
diagnostic_parenthesized_expression_invalid_start(error, file)
}
ParseErrorKind::ExpressionExpected => diagnostic_expression_expected(error, file),
ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis => {
diagnostic_parenthesized_expression_missing_closing_parenthesis(error, file)
}
ParseErrorKind::ClassTypeMissingTypeArgument => {
diagnostic_class_type_missing_type_argument(error, file)
}
ParseErrorKind::ClassTypeExpectedQualifiedTypeName => {
diagnostic_class_type_expected_qualified_type_name(error, file)
}
ParseErrorKind::ClassTypeInvalidStart => diagnostic_class_type_invalid_start(error, file),
ParseErrorKind::ClassTypeMissingClosingAngleBracket => {
diagnostic_class_type_missing_closing_angle_bracket(error, file)
}
ParseErrorKind::NewMissingClassSpecifier => {
diagnostic_new_missing_class_specifier(error, file)
}
ParseErrorKind::NewTooManyArguments => diagnostic_new_too_many_arguments(error, file),
ParseErrorKind::NewMissingClosingParenthesis => {
diagnostic_new_missing_closing_parenthesis(error, file)
}
ParseErrorKind::NewArgumentMissingComma => {
diagnostic_new_argument_missing_comma(error, file)
}
// control_flow_expressions.rs
ParseErrorKind::ConditionExpected => diagnostic_condition_expected(error, file),
ParseErrorKind::ControlFlowBodyExpected => {
diagnostic_control_flow_body_expected(error, file)
}
ParseErrorKind::DoMissingUntil => diagnostic_do_missing_until(error, file),
ParseErrorKind::ForEachIteratorExpressionExpected => {
diagnostic_for_each_iterator_expression_expected(error, file)
}
ParseErrorKind::ForLoopHeaderInitializerInvalidStart => {
diagnostic_for_loop_header_initializer_invalid_start(error, file)
}
ParseErrorKind::ForLoopHeaderMissingSemicolonAfterInitializer => {
diagnostic_for_loop_header_missing_semicolon_after_initializer(error, file)
}
ParseErrorKind::ForLoopHeaderConditionInvalidStart => {
diagnostic_for_loop_header_condition_invalid_start(error, file)
}
ParseErrorKind::ForLoopHeaderMissingSemicolonAfterCondition => {
diagnostic_for_loop_header_missing_semicolon_after_condition(error, file)
}
ParseErrorKind::ForLoopHeaderStepInvalidStart => {
diagnostic_for_loop_header_step_invalid_start(error, file)
}
ParseErrorKind::ForLoopHeaderMissingClosingParenthesis => {
diagnostic_for_loop_header_missing_closing_parenthesis(error, file)
}
ParseErrorKind::ReturnValueInvalidStart => {
diagnostic_return_value_invalid_start(error, file)
}
ParseErrorKind::BreakValueInvalidStart => diagnostic_break_value_invalid_start(error, file),
ParseErrorKind::GotoMissingLabel => diagnostic_goto_missing_label(error, file),
// block_items.rs
ParseErrorKind::BlockMissingSemicolonAfterExpression => {
diagnostic_block_missing_semicolon_after_expression(error, file)
}
ParseErrorKind::BlockMissingClosingBrace => {
diagnostic_block_missing_closing_brace(error, file)
}
ParseErrorKind::BlockExpectedItem => diagnostic_block_expected_item(error, file),
_ => DiagnosticBuilder::error(format!("error {:?} while parsing", error.kind))
.primary_label(error.covered_span, "happened here")
.build(),
}
}