diff --git a/tamer/src/diagnose.rs b/tamer/src/diagnose.rs
index 90b0d56b..4c078db0 100644
--- a/tamer/src/diagnose.rs
+++ b/tamer/src/diagnose.rs
@@ -22,6 +22,58 @@
//! This system is heavily motivated by Rust's.
//! While the data structures and organization may differ,
//! the diagnostic output is visually similar.
+//!
+//! Visual Report
+//! -------------
+//! The primary output of the system is a [`Report`](report::Report).
+//! A report consists not only of the diagnostic information provided by the
+//! system
+//! (such as error messages),
+//! but also annotated source code.
+//! Reports attempt to create a narrative that walks the user through why
+//! and how problems arose,
+//! and hopefully provides information on how to resolve the problem.
+//!
+//! Here is an example report:
+//!
+//! ```text
+//! error: expected closing tag for `classify`
+//! --> /home/user/program/foo.xml:16:5
+//! |
+//! 16 |
+//! | --------- note: element `classify` is opened here
+//!
+//! --> /home/user/program/foo.xml:24:5
+//! |
+//! 24 |
+//! | ^^^^^^^^ error: expected ``
+//! ```
+//!
+//! A single report is produced for each error
+//! (or other suitable diagnostic event).
+//! The system may produce multiple reports at a time if it discovers
+//! multiple issues and is able to recover enough to continue to discover
+//! others.
+//!
+//! Each report is separated into a number of sections,
+//! with each section delimited by a header.
+//! Each section describes one or more spans related to a range of related
+//! source lines.
+//! Those source lines are annotated using the [`Span`]s associated with the
+//! emitted diagnostic data,
+//! such as an error.
+//!
+//! Each section has a _gutter_ containing the line number of source lines.
+//! The area below the section header and to the right of the gutter is
+//! called the _body_ of the section.
+//! The gutter will expand as needed to fit the line number.
+//!
+//! _Warning: Reports do not yet strip terminal escape sequences,
+//! which may interfere with the diagnostic output.
+//! This may represent a security risk depending on your threat model,
+//! but does require access to the source code being compiled._
+//!
+//! See the [`report`] module for more information.
mod report;
mod resolver;
diff --git a/tamer/src/diagnose/report.rs b/tamer/src/diagnose/report.rs
index b479f9ec..183e7918 100644
--- a/tamer/src/diagnose/report.rs
+++ b/tamer/src/diagnose/report.rs
@@ -17,7 +17,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-//! Rendering of diagnostic information.
+//! Rendering of diagnostic report.
+//!
+//! This module is responsible for [resolving](super::resolver) and
+//! rendering spans into a formatted [`Report`],
+//! which in turn can be rendered into a string with [`Display::fmt`].
+//!
+//! See the [parent module](super) for more summary information.
// NB: `write!` together with `\n` is preferred to `writeln!` so that there
// is only a single sequence of characters to search for while tracking
@@ -38,6 +44,10 @@ use std::{
ops::Add,
};
+/// Render a [`Report`] with detailed diagnostic information.
+///
+/// Rendering of reports is layered---this
+/// report can be further rendered into a string using [`Display::fmt`].
pub trait Reporter {
/// Render diagnostic report.
///
@@ -199,15 +209,49 @@ impl<'d, S: ResolvedSpanData> MaybeResolvedSpan<'d, S> {
}
}
+/// Diagnostic report.
+///
+/// This contains the raw data that,
+/// when rendered with [`Display::fmt`],
+/// will produce a robust report to help guide the user through diagnosing
+/// and resolving problems.
#[derive(Debug)]
pub struct Report<'d, D: Diagnostic> {
+ /// Summary message of the contents of the report.
+ ///
+ /// This message should be suitable on its own,
+ /// e.g. a typical one-line error message.
msg: Message<'d, D>,
- secs: Vec>,
+
+ /// The largest severity level found within all of the [`Section`]s of
+ /// the report.
+ ///
+ /// This level should be used alongside the summary message to describe
+ /// how severe of a problem this report represents.
level: Level,
+
+ /// Sections of the report.
+ ///
+ /// A section contains a header describing a location in the source
+ /// code (line and column numbers),
+ /// followed by annotated source code and descriptive labels.
+ secs: Vec>,
+
+ /// The maximum line number encountered in each of the [`Section`]s of
+ /// the report.
+ ///
+ /// This number is used to determine the gutter width,
+ /// which contains the line numbers of the annotated source lines.
+ /// It can be propagated to all [`Section`]s using
+ /// [`normalize_gutters`](Report::normalize_gutters).
line_max: NonZeroU32,
}
impl<'d, D: Diagnostic> Report<'d, D> {
+ /// Create an empty report.
+ ///
+ /// To add to the body of the report,
+ /// use [`Extend::extend`].
fn empty(msg: Message<'d, D>) -> Self {
Self {
msg,
@@ -229,6 +273,12 @@ impl<'d, D: Diagnostic> Report<'d, D> {
}
impl<'d, D: Diagnostic> Extend> for Report<'d, D> {
+ /// Extend the body of the report.
+ ///
+ /// This tracks the most severe [`Level`] and highest line number seen.
+ /// Further,
+ /// adjacent sections may be squashed if they meet certain criteria
+ /// (see [`Section::maybe_squash_into`]).
fn extend>>(&mut self, secs: T) {
for sec in secs {
self.level = self.level.min(sec.level());
@@ -258,6 +308,7 @@ impl<'d, D: Diagnostic> Display for Report<'d, D> {
}
}
+/// Summary diagnostic message.
#[derive(Debug)]
struct Message<'d, D: Diagnostic>(&'d D);
@@ -281,10 +332,27 @@ impl<'d, D: Diagnostic> Display for Message<'d, D> {
/// messages quickly.
#[derive(Debug, PartialEq, Eq)]
struct Section<'d> {
+ /// Heading that delimits the beginning of each section.
+ ///
+ /// The heading describes the location of its principal [`Span`].
heading: SpanHeading,
+
+ /// The most severe [`Level`] encountered in this section body.
level: Level,
+
+ /// The principal [`Span`] that this section describes.
+ ///
+ /// If a section contains information about multiple spans,
+ /// this represents the one that the user should focus on.
span: Span,
+
+ /// Annotated source lines and labels.
body: Vec>,
+
+ /// The largest line number encountered in this section.
+ ///
+ /// This is used to determine how wide to render the gutter,
+ /// which contain the line numbers for source lines.
line_max: NonZeroU32,
}
@@ -598,9 +666,25 @@ impl Display for HeadingColNum {
/// Line of output in a [`Section`] body.
#[derive(Debug, PartialEq, Eq)]
enum SectionLine<'d> {
+ /// Padding for a possibly annotated source line.
+ ///
+ /// A padding line is intended to add extra space above or below a
+ /// source line to make it easier to read.
+ /// Padding lines contain a gutter,
+ /// but no line number.
SourceLinePadding,
+
+ /// A line of source code.
SourceLine(SectionSourceLine),
+
+ /// Source line annotations
+ /// (marks and labels).
SourceLineMark(LineMark<'d>),
+
+ /// A label that is not rendered as a line annotation.
+ ///
+ /// Footnotes are intended too appear at the end of a [`Section`] and
+ /// contain supplemental information.
Footnote(Level, Label<'d>),
}
@@ -637,6 +721,10 @@ impl<'d> SectionLine<'d> {
}
}
+ /// Attempt to convert a line into a footnote.
+ ///
+ /// If there is no [`Level`] and [`Label`] available,
+ /// [`None`] is returned.
fn into_footnote(self) -> Option {
match self {
Self::SourceLinePadding => None,
@@ -661,6 +749,7 @@ impl<'d> Display for SectionLine<'d> {
}
}
+/// A [`SourceLine`] displayed within a [`Section`].
#[derive(Debug, PartialEq, Eq)]
struct SectionSourceLine(SourceLine);
@@ -684,6 +773,12 @@ impl Display for SectionSourceLine {
/// A type of line annotation that marks columns and provides labels,
/// if available.
+///
+/// Marks are displayed below a [`SectionSourceLine`] and are intended to
+/// visually display a [`Span`].
+/// Column resolution
+/// (see [`super::resolver`])
+/// exists primarily for mark rendering.
#[derive(Debug, PartialEq, Eq)]
struct LineMark<'d> {
level: Level,
@@ -715,13 +810,14 @@ impl<'d> Display for LineMark<'d> {
}
}
+/// Mark styling.
trait MarkChar {
+ /// Character used to underline the columns applicable to a given span
+ /// underneath a source line.
fn mark_char(&self) -> char;
}
impl MarkChar for Level {
- /// Character used to underline the columns applicable to a given span
- /// underneath a source line.
fn mark_char(&self) -> char {
match self {
Level::InternalError => '!',