Feat: add structured diagnostics with yansi colors
Introduces fluxc/src/diagnostics.rs with Level (Critical, Error, Warning, Note), Label (Primary/Secondary with optional message), and Diagnostic types. Diagnostic::render(src, filename) produces rustc-style output: colored header, --> file:line:col pointer, source line with gutter, and ^ / - underlines aligned to the offending span. Replaces the flat ParseError struct in the parser; all five error sites now emit Diagnostic values with source-pointing labels.
This commit is contained in:
@@ -1,29 +1,14 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
ast::{
|
||||
BinaryOp, Block, CompoundAssignOp, ElseBranch, Expr, ExprKind, FieldDef, FuncDef, Param,
|
||||
Program, Stmt, StmtKind, StructDef, StructField, TopLevelDef, TopLevelDefKind, Type,
|
||||
UnaryOp,
|
||||
},
|
||||
diagnostics::{Diagnostic, Label},
|
||||
lexer::Lexer,
|
||||
token::{Span, Token, TokenKind},
|
||||
};
|
||||
|
||||
// ── Parse error ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseError {
|
||||
pub span: Span,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "error at {}: {}", self.span, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Binding powers ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns `(left_bp, right_bp)` for infix operators.
|
||||
@@ -139,7 +124,7 @@ fn token_to_binary_op(kind: TokenKind) -> BinaryOp {
|
||||
pub struct Parser<'src> {
|
||||
tokens: Vec<Token<'src>>,
|
||||
pos: usize,
|
||||
pub errors: Vec<ParseError>,
|
||||
pub errors: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl<'src> Parser<'src> {
|
||||
@@ -175,11 +160,12 @@ impl<'src> Parser<'src> {
|
||||
if tok.kind == kind {
|
||||
self.advance()
|
||||
} else {
|
||||
self.errors.push(
|
||||
Diagnostic::error(format!("expected {kind}, found {}", tok.kind)).with_label(
|
||||
Label::primary(tok.span).with_message(format!("expected {kind} here")),
|
||||
),
|
||||
);
|
||||
let span = Span::new(tok.span.start, tok.span.start);
|
||||
self.errors.push(ParseError {
|
||||
span,
|
||||
message: format!("expected {}, found {}", kind, tok.kind),
|
||||
});
|
||||
Token::new(kind, span, "")
|
||||
}
|
||||
}
|
||||
@@ -261,10 +247,10 @@ impl<'src> Parser<'src> {
|
||||
|
||||
// Error — insert recovery placeholder
|
||||
_ => {
|
||||
self.errors.push(ParseError {
|
||||
span: tok.span,
|
||||
message: format!("expected type, found {}", tok.kind),
|
||||
});
|
||||
self.errors.push(
|
||||
Diagnostic::error(format!("expected type, found {}", tok.kind))
|
||||
.with_label(Label::primary(tok.span).with_message("expected a type here")),
|
||||
);
|
||||
Type::Error
|
||||
}
|
||||
}
|
||||
@@ -339,10 +325,10 @@ impl<'src> Parser<'src> {
|
||||
| TokenKind::Fn
|
||||
| TokenKind::Struct
|
||||
| TokenKind::Eof => {
|
||||
self.errors.push(ParseError {
|
||||
span: tok.span,
|
||||
message: format!("unexpected {} in statement position", tok.kind),
|
||||
});
|
||||
self.errors.push(
|
||||
Diagnostic::error(format!("unexpected {} in statement position", tok.kind))
|
||||
.with_label(Label::primary(tok.span)),
|
||||
);
|
||||
self.synchronize();
|
||||
Stmt {
|
||||
kind: StmtKind::Error,
|
||||
@@ -553,10 +539,10 @@ impl<'src> Parser<'src> {
|
||||
|
||||
// Error recovery
|
||||
_ => {
|
||||
self.errors.push(ParseError {
|
||||
span: tok.span,
|
||||
message: format!("unexpected token {} in expression", tok.kind),
|
||||
});
|
||||
self.errors.push(
|
||||
Diagnostic::error(format!("unexpected token {} in expression", tok.kind))
|
||||
.with_label(Label::primary(tok.span)),
|
||||
);
|
||||
Expr::new(ExprKind::Error, tok.span)
|
||||
}
|
||||
}
|
||||
@@ -730,10 +716,12 @@ impl<'src> Parser<'src> {
|
||||
TokenKind::Fn => self.parse_func_def(),
|
||||
TokenKind::Struct => self.parse_struct_def(),
|
||||
_ => {
|
||||
self.errors.push(ParseError {
|
||||
span: tok.span,
|
||||
message: format!("expected `fn` or `struct`, found {}", tok.kind),
|
||||
});
|
||||
self.errors.push(
|
||||
Diagnostic::error(format!("expected `fn` or `struct`, found {}", tok.kind))
|
||||
.with_label(
|
||||
Label::primary(tok.span).with_message("expected `fn` or `struct`"),
|
||||
),
|
||||
);
|
||||
self.synchronize_top_level();
|
||||
TopLevelDef {
|
||||
kind: TopLevelDefKind::Error,
|
||||
|
||||
Reference in New Issue
Block a user