From 146eda102a5d0241c96a808a847f3b855340765e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 11 Feb 2021 19:26:47 +0100 Subject: [PATCH] =?UTF-8?q?Move=20span=20directly=20into=20diagnostics=20?= =?UTF-8?q?=F0=9F=9A=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/diag.rs | 116 ++++++++++++++++++-------------------------- src/eval/mod.rs | 22 ++++----- src/exec/context.rs | 24 ++++----- src/lib.rs | 8 +-- src/main.rs | 19 +++----- src/parse/mod.rs | 2 +- src/parse/parser.rs | 27 +++-------- src/prelude.rs | 2 +- src/pretty.rs | 7 +-- tests/typeset.rs | 26 +++------- 10 files changed, 98 insertions(+), 155 deletions(-) diff --git a/src/diag.rs b/src/diag.rs index b1c477a55..d84331410 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -1,58 +1,64 @@ -//! Diagnostics and decorations for source code. +//! Diagnostics for source code. //! //! Errors are never fatal, the document will always compile and yield a layout. -use crate::syntax::Spanned; +use std::collections::BTreeSet; use std::fmt::{self, Display, Formatter}; -/// The result of some pass: Some output `T` and [`Feedback`] data. +use crate::syntax::Span; + +/// The result of some pass: Some output `T` and diagnostics. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Pass { /// The output of this compilation pass. pub output: T, - /// User feedback data accumulated in this pass. - pub feedback: Feedback, + /// User diagnostics accumulated in this pass. + pub diags: DiagSet, } impl Pass { - /// Create a new pass from output and feedback data. - pub fn new(output: T, feedback: Feedback) -> Self { - Self { output, feedback } + /// Create a new pass from output and diagnostics. + pub fn new(output: T, diags: DiagSet) -> Self { + Self { output, diags } } } -/// Diagnostics and semantic syntax highlighting information. -#[derive(Debug, Default, Clone, Eq, PartialEq)] -pub struct Feedback { - /// Diagnostics about the source code. - pub diags: Vec>, - /// Decorations of the source code for semantic syntax highlighting. - pub decos: Vec>, -} - -impl Feedback { - /// Create a new feedback instance without errors and decos. - pub fn new() -> Self { - Self { diags: vec![], decos: vec![] } - } - - /// Add other feedback data to this feedback. - pub fn extend(&mut self, more: Self) { - self.diags.extend(more.diags); - self.decos.extend(more.decos); - } -} +/// A set of diagnostics. +/// +/// Since this is a [`BTreeSet`], there cannot be two equal (up to span) +/// diagnostics and you can quickly iterate diagnostics in source location +/// order. +pub type DiagSet = BTreeSet; /// A diagnostic with severity level and message. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Diag { + /// The source code location. + pub span: Span, /// How severe / important the diagnostic is. pub level: Level, /// A message describing the diagnostic. pub message: String, } +impl Diag { + /// Create a new diagnostic from message and level. + pub fn new(span: impl Into, level: Level, message: impl Into) -> Self { + Self { + span: span.into(), + level, + message: message.into(), + } + } +} + +impl Display for Diag { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}: {}", self.level, self.message) + } +} + /// How severe / important a diagnostic is. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -62,13 +68,6 @@ pub enum Level { Error, } -impl Diag { - /// Create a new diagnostic from message and level. - pub fn new(level: Level, message: impl Into) -> Self { - Self { level, message: message.into() } - } -} - impl Display for Level { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { @@ -78,17 +77,6 @@ impl Display for Level { } } -/// Decorations for semantic syntax highlighting. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum Deco { - /// Strong text. - Strong, - /// Emphasized text. - Emph, -} - /// Construct a diagnostic with [`Error`](Level::Error) level. /// /// ``` @@ -96,16 +84,16 @@ pub enum Deco { /// # use typst::syntax::Span; /// # let span = Span::ZERO; /// # let name = ""; -/// // Create formatted error values. -/// let error = error!("expected {}", name); -/// -/// // Create spanned errors. -/// let spanned = error!(span, "there is an error here"); +/// let error = error!(span, "there is an error with {}", name); /// ``` #[macro_export] macro_rules! error { - ($($tts:tt)*) => { - $crate::__impl_diagnostic!($crate::diag::Level::Error; $($tts)*) + ($span:expr, $($tts:tt)*) => { + $crate::diag::Diag::new( + $span, + $crate::diag::Level::Error, + format!($($tts)*), + ) }; } @@ -115,23 +103,11 @@ macro_rules! error { /// information. #[macro_export] macro_rules! warning { - ($($tts:tt)*) => { - $crate::__impl_diagnostic!($crate::diag::Level::Warning; $($tts)*) - }; -} - -/// Backs the `error!` and `warning!` macros. -#[macro_export] -#[doc(hidden)] -macro_rules! __impl_diagnostic { - ($level:expr; $fmt:literal $($tts:tt)*) => { - $crate::diag::Diag::new($level, format!($fmt $($tts)*)) - }; - - ($level:expr; $span:expr, $fmt:literal $($tts:tt)*) => { - $crate::syntax::Spanned::new( - $crate::__impl_diagnostic!($level; $fmt $($tts)*), + ($span:expr, $($tts:tt)*) => { + $crate::diag::Diag::new( $span, + $crate::diag::Level::Warning, + format!($($tts)*), ) }; } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index ec71f6896..57a37f382 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -15,7 +15,7 @@ use std::rc::Rc; use super::*; use crate::color::Color; -use crate::diag::{Diag, Feedback}; +use crate::diag::{Diag, DiagSet}; use crate::geom::{Angle, Length, Relative}; use crate::syntax::visit::Visit; use crate::syntax::*; @@ -27,14 +27,14 @@ use crate::syntax::*; pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass { let mut ctx = EvalContext::new(env, scope); let map = tree.eval(&mut ctx); - Pass::new(map, ctx.feedback) + Pass::new(map, ctx.diags) } /// A map from expression to values to evaluated to. /// -/// The raw pointers point into the expressions contained in `tree`. Since -/// the lifetime is erased, `tree` could go out of scope while the hash map -/// still lives. While this could lead to lookup panics, it is not unsafe +/// The raw pointers point into the expressions contained in some [tree](Tree). +/// Since the lifetime is erased, the tree could go out of scope while the hash +/// map still lives. Though this could lead to lookup panics, it is not unsafe /// since the pointers are never dereferenced. pub type ExprMap = HashMap<*const Expr, Value>; @@ -45,8 +45,8 @@ pub struct EvalContext<'a> { pub env: &'a mut Env, /// The active scopes. pub scopes: Scopes<'a>, - /// The accumulated feedback. - feedback: Feedback, + /// Evaluation diagnostics. + pub diags: DiagSet, } impl<'a> EvalContext<'a> { @@ -55,13 +55,13 @@ impl<'a> EvalContext<'a> { Self { env, scopes: Scopes::with_base(scope), - feedback: Feedback::new(), + diags: DiagSet::new(), } } - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned) { - self.feedback.diags.push(diag); + /// Add a diagnostic. + pub fn diag(&mut self, diag: Diag) { + self.diags.insert(diag); } } diff --git a/src/exec/context.rs b/src/exec/context.rs index a1998f293..821a26164 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -4,8 +4,7 @@ use std::rc::Rc; use fontdock::FontStyle; use super::*; -use crate::diag::Diag; -use crate::diag::{Deco, Feedback, Pass}; +use crate::diag::{Diag, DiagSet}; use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size}; use crate::layout::{ Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree, @@ -18,8 +17,8 @@ pub struct ExecContext<'a> { pub env: &'a mut Env, /// The active execution state. pub state: State, - /// The accumulated feedback. - feedback: Feedback, + /// Execution diagnostics. + pub diags: DiagSet, /// The finished page runs. runs: Vec, /// The stack of logical groups (paragraphs and such). @@ -39,27 +38,22 @@ impl<'a> ExecContext<'a> { Self { env, state, + diags: DiagSet::new(), + runs: vec![], groups: vec![], inner: vec![], - runs: vec![], - feedback: Feedback::new(), } } /// Finish execution and return the created layout tree. pub fn finish(self) -> Pass { assert!(self.groups.is_empty(), "unfinished group"); - Pass::new(Tree { runs: self.runs }, self.feedback) + Pass::new(Tree { runs: self.runs }, self.diags) } - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned) { - self.feedback.diags.push(diag); - } - - /// Add a decoration to the feedback. - pub fn deco(&mut self, deco: Spanned) { - self.feedback.decos.push(deco); + /// Add a diagnostic. + pub fn diag(&mut self, diag: Diag) { + self.diags.insert(diag); } /// Push a layout node to the active group. diff --git a/src/lib.rs b/src/lib.rs index efdbb4cb7..c8d413176 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,9 +65,9 @@ pub fn typeset( let executed = exec::exec(env, &parsed.output, &evaluated.output, state); let frames = layout::layout(env, &executed.output); - let mut feedback = parsed.feedback; - feedback.extend(evaluated.feedback); - feedback.extend(executed.feedback); + let mut diags = parsed.diags; + diags.extend(evaluated.diags); + diags.extend(executed.diags); - Pass::new(frames, feedback) + Pass::new(frames, diags) } diff --git a/src/main.rs b/src/main.rs index d9a4f0961..c221f3b61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context}; use fontdock::fs::FsIndex; -use typst::diag::{Feedback, Pass}; +use typst::diag::Pass; use typst::env::{Env, ResourceLoader}; use typst::exec::State; use typst::export::pdf; @@ -48,26 +48,19 @@ fn main() -> anyhow::Result<()> { let scope = library::new(); let state = State::default(); - let Pass { - output: frames, - feedback: Feedback { mut diags, .. }, - } = typeset(&mut env, &src, &scope, state); - + let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state); if !diags.is_empty() { - diags.sort(); - let map = LineMap::new(&src); for diag in diags { - let span = diag.span; - let start = map.location(span.start).unwrap(); - let end = map.location(span.end).unwrap(); + let start = map.location(diag.span.start).unwrap(); + let end = map.location(diag.span.end).unwrap(); println!( "{}: {}:{}-{}: {}", - diag.v.level, + diag.level, src_path.display(), start, end, - diag.v.message, + diag.message, ); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index e7e25a1d8..8780efafa 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -22,7 +22,7 @@ use collection::{args, parenthesized}; /// Parse a string of source code. pub fn parse(src: &str) -> Pass { let mut p = Parser::new(src); - Pass::new(tree(&mut p), p.finish()) + Pass::new(tree(&mut p), p.diags) } /// Parse a syntax tree. diff --git a/src/parse/parser.rs b/src/parse/parser.rs index a64e39dd5..7c6601828 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1,12 +1,13 @@ use std::fmt::{self, Debug, Formatter}; use super::{Scanner, TokenMode, Tokens}; -use crate::diag::Diag; -use crate::diag::{Deco, Feedback}; -use crate::syntax::{Pos, Span, Spanned, Token}; +use crate::diag::{Diag, DiagSet}; +use crate::syntax::{Pos, Span, Token}; /// A convenient token-based parser. pub struct Parser<'s> { + /// Parsing diagnostics. + pub diags: DiagSet, /// An iterator over the source tokens. tokens: Tokens<'s>, /// The next token. @@ -20,8 +21,6 @@ pub struct Parser<'s> { last_end: Pos, /// The stack of open groups. groups: Vec, - /// Accumulated feedback. - feedback: Feedback, } /// A logical group of tokens, e.g. `[...]`. @@ -67,18 +66,13 @@ impl<'s> Parser<'s> { next_start: Pos::ZERO, last_end: Pos::ZERO, groups: vec![], - feedback: Feedback::new(), + diags: DiagSet::new(), } } - /// Finish parsing and return the accumulated feedback. - pub fn finish(self) -> Feedback { - self.feedback - } - - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned) { - self.feedback.diags.push(diag); + /// Add a diagnostic. + pub fn diag(&mut self, diag: Diag) { + self.diags.insert(diag); } /// Eat the next token and add a diagnostic that it is not the expected @@ -112,11 +106,6 @@ impl<'s> Parser<'s> { } } - /// Add a decoration to the feedback. - pub fn deco(&mut self, deco: Spanned) { - self.feedback.decos.push(deco); - } - /// Continue parsing in a group. /// /// When the end delimiter of the group is reached, all subsequent calls to diff --git a/src/prelude.rs b/src/prelude.rs index 5beded398..1251bd29a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,6 @@ //! A prelude for building custom functions. -pub use crate::diag::{Feedback, Pass}; +pub use crate::diag::{Diag, Pass}; #[doc(no_inline)] pub use crate::eval::{ CastResult, Eval, EvalContext, TemplateAny, TemplateNode, Value, ValueAny, ValueArgs, diff --git a/src/pretty.rs b/src/pretty.rs index a522b1256..025bc66ce 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -1,6 +1,6 @@ //! Pretty printing. -use std::fmt::{Arguments, Result, Write}; +use std::fmt::{self, Arguments, Write}; use crate::color::{Color, RgbaColor}; use crate::eval::*; @@ -70,7 +70,7 @@ impl Printer { } /// Write formatted items into the buffer. - pub fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result { + pub fn write_fmt(&mut self, fmt: Arguments<'_>) -> fmt::Result { Write::write_fmt(self, fmt) } @@ -97,7 +97,7 @@ impl Printer { } impl Write for Printer { - fn write_str(&mut self, s: &str) -> Result { + fn write_str(&mut self, s: &str) -> fmt::Result { self.push_str(s); Ok(()) } @@ -484,6 +484,7 @@ impl Pretty for Value { Value::Relative(v) => v.pretty(p), Value::Linear(v) => v.pretty(p), Value::Color(v) => v.pretty(p), + // TODO: Handle like text when directly in template. Value::Str(v) => v.pretty(p), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), diff --git a/tests/typeset.rs b/tests/typeset.rs index 4b459a938..8932c483b 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -14,7 +14,7 @@ use tiny_skia::{ use ttf_parser::OutlineBuilder; use walkdir::WalkDir; -use typst::diag::{Diag, Feedback, Level, Pass}; +use typst::diag::{Diag, DiagSet, Level, Pass}; use typst::env::{Env, ImageResource, ResourceLoader}; use typst::eval::{EvalContext, Scope, Value, ValueArgs, ValueFunc}; use typst::exec::State; @@ -25,7 +25,7 @@ use typst::layout::{Element, Expansion, Fill, Frame, Geometry, Image, Shape}; use typst::library; use typst::parse::{LineMap, Scanner}; use typst::shaping::Shaped; -use typst::syntax::{Location, Pos, Spanned}; +use typst::syntax::{Location, Pos}; use typst::typeset; const TYP_DIR: &str = "typ"; @@ -219,17 +219,11 @@ fn test_part( state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit); state.page.margins = Sides::uniform(Some(Length::pt(10.0).into())); - let Pass { - output: mut frames, - feedback: Feedback { mut diags, .. }, - } = typeset(env, &src, &scope, state); - + let Pass { output: mut frames, diags } = typeset(env, &src, &scope, state); if !compare_ref { frames.clear(); } - diags.sort_by_key(|d| d.span); - let mut ok = true; for panic in &*panics.borrow() { @@ -266,8 +260,8 @@ fn test_part( (ok, frames) } -fn parse_metadata(src: &str, map: &LineMap) -> (Option, Vec>) { - let mut diags = vec![]; +fn parse_metadata(src: &str, map: &LineMap) -> (Option, DiagSet) { + let mut diags = DiagSet::new(); let mut compare_ref = None; for (i, line) in src.lines().enumerate() { @@ -303,13 +297,9 @@ fn parse_metadata(src: &str, map: &LineMap) -> (Option, Vec> let mut s = Scanner::new(rest); let (start, _, end) = (pos(&mut s), s.eat_assert('-'), pos(&mut s)); - - let diag = Diag::new(level, s.rest().trim()); - diags.push(Spanned::new(diag, start .. end)); + diags.insert(Diag::new(start .. end, level, s.rest().trim())); } - diags.sort_by_key(|d| d.span); - (compare_ref, diags) } @@ -341,12 +331,12 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { scope.def_const("test", ValueFunc::new("test", test)); } -fn print_diag(diag: &Spanned, map: &LineMap, lines: u32) { +fn print_diag(diag: &Diag, map: &LineMap, lines: u32) { let mut start = map.location(diag.span.start).unwrap(); let mut end = map.location(diag.span.end).unwrap(); start.line += lines; end.line += lines; - println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message); + println!("{}: {}-{}: {}", diag.level, start, end, diag.message); } fn draw(frames: &[Frame], env: &Env, pixel_per_pt: f32) -> Canvas {