docs: Add and improve documentation for every module.
This commit is contained in:
@@ -1,14 +1,37 @@
|
||||
//! Compiler diagnostic reporting with source-location context.
|
||||
//!
|
||||
//! This module provides [`Diagnostic`], a structured error/warning message that
|
||||
//! can optionally include a source span and one or more labelled secondary
|
||||
//! spans. Diagnostics are rendered to `stderr` in a rustc-inspired format:
|
||||
//!
|
||||
//! ```text
|
||||
//! Error: undeclared variable `x`
|
||||
//! --> src/main.bky:3:5
|
||||
//! |
|
||||
//! 3 | let y = x + 1;
|
||||
//! | ^ undeclared variable
|
||||
//! |
|
||||
//! ```
|
||||
use std::{fmt::Display, path::Path, process::exit};
|
||||
|
||||
use yansi::Paint;
|
||||
|
||||
use crate::token::Span;
|
||||
|
||||
/// The importance level of a [`Diagnostic`].
|
||||
///
|
||||
/// Variants are ordered from least to most severe so that `<` / `>` comparisons
|
||||
/// work intuitively (e.g. `Severity::Warning < Severity::Error`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Severity {
|
||||
/// Purely informational; never causes the compiler to stop.
|
||||
Note,
|
||||
/// Something suspicious that may or may not be a problem.
|
||||
Warning,
|
||||
/// A recoverable problem that prevents successful compilation.
|
||||
Error,
|
||||
/// An unrecoverable problem; the process will exit immediately after
|
||||
/// reporting this diagnostic.
|
||||
Critical,
|
||||
}
|
||||
|
||||
@@ -23,14 +46,29 @@ impl Display for Severity {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single compiler message with optional source-location information.
|
||||
///
|
||||
/// Build a diagnostic with [`Diagnostic::new`], optionally attach a primary
|
||||
/// source location via [`with_span`](Diagnostic::with_span), attach labelled
|
||||
/// secondary locations via [`add_label`](Diagnostic::add_label), then call
|
||||
/// [`report`](Diagnostic::report) to print it.
|
||||
///
|
||||
/// If the severity is [`Severity::Critical`], `report` will call
|
||||
/// [`process::exit`](std::process::exit) after printing.
|
||||
pub struct Diagnostic {
|
||||
pub severity: Severity,
|
||||
/// Primary source location, if any.
|
||||
pub span: Option<Span>,
|
||||
pub message: String,
|
||||
/// Secondary labelled spans rendered below the primary snippet.
|
||||
pub labels: Vec<(Span, String)>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Create a new diagnostic with the given severity and message.
|
||||
///
|
||||
/// No source location is attached; use [`with_span`](Self::with_span) to
|
||||
/// add one.
|
||||
pub fn new(severity: Severity, message: impl ToString) -> Self {
|
||||
Self {
|
||||
severity,
|
||||
@@ -40,16 +78,29 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a primary source span to this diagnostic.
|
||||
pub fn with_span(mut self, span: Span) -> Self {
|
||||
self.span = Some(span);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach a labelled secondary span.
|
||||
///
|
||||
/// Labels whose span matches the primary span exactly are merged into the
|
||||
/// primary underline as inline text. All other labels are rendered as
|
||||
/// separate snippets below the primary one.
|
||||
pub fn add_label(mut self, span: Span, message: impl ToString) -> Self {
|
||||
self.labels.push((span, message.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Print this diagnostic to `stderr` and, if the severity is
|
||||
/// [`Severity::Critical`], terminate the process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `file_name` – path shown in the `-->` location line.
|
||||
/// * `source` – full source text of the file, used to extract line/col
|
||||
/// information and to display the relevant source snippet.
|
||||
pub fn report(self, file_name: &Path, source: &str) {
|
||||
eprintln!("{}: {}", self.severity, self.message.bold());
|
||||
|
||||
@@ -165,6 +216,7 @@ fn render_snippet(
|
||||
eprintln!("{pad} {bar} {spaces}{colored_carets}{label_text}");
|
||||
}
|
||||
|
||||
/// Apply severity-appropriate ANSI colour to a string.
|
||||
fn paint_severity(s: &str, severity: Severity) -> String {
|
||||
match severity {
|
||||
Severity::Note => format!("{}", s.bold().bright_cyan()),
|
||||
@@ -173,6 +225,7 @@ fn paint_severity(s: &str, severity: Severity) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of decimal digits in `n` (minimum 1).
|
||||
fn count_digits(n: usize) -> usize {
|
||||
format!("{n}").len()
|
||||
}
|
||||
@@ -187,6 +240,10 @@ fn get_line_content(source: &str, position: u32) -> (usize, &str) {
|
||||
(line_start, &rest[..line_len])
|
||||
}
|
||||
|
||||
/// Returns the 1-based `(line, column)` for a byte `position` within `source`.
|
||||
///
|
||||
/// Both line and column are counted from 1. The column is measured in Unicode
|
||||
/// scalar values (characters), not bytes.
|
||||
fn get_line_col(source: &str, position: u32) -> (usize, usize) {
|
||||
let prefix = &source[..position as usize];
|
||||
let line = prefix.bytes().filter(|&b| b == b'\n').count() + 1;
|
||||
|
||||
Reference in New Issue
Block a user