Move span directly into diagnostics 🚚

This commit is contained in:
Laurenz 2021-02-11 19:26:47 +01:00
parent 1711b67877
commit 146eda102a
10 changed files with 98 additions and 155 deletions

View File

@ -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<T> {
/// 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<T> Pass<T> {
/// 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<Spanned<Diag>>,
/// Decorations of the source code for semantic syntax highlighting.
pub decos: Vec<Spanned<Deco>>,
}
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<Diag>;
/// 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<Span>, level: Level, message: impl Into<String>) -> 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<String>) -> 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)*),
)
};
}

View File

@ -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<ExprMap> {
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<Diag>) {
self.feedback.diags.push(diag);
/// Add a diagnostic.
pub fn diag(&mut self, diag: Diag) {
self.diags.insert(diag);
}
}

View File

@ -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<NodePages>,
/// 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<Tree> {
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<Diag>) {
self.feedback.diags.push(diag);
}
/// Add a decoration to the feedback.
pub fn deco(&mut self, deco: Spanned<Deco>) {
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.

View File

@ -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)
}

View File

@ -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,
);
}
}

View File

@ -22,7 +22,7 @@ use collection::{args, parenthesized};
/// Parse a string of source code.
pub fn parse(src: &str) -> Pass<Tree> {
let mut p = Parser::new(src);
Pass::new(tree(&mut p), p.finish())
Pass::new(tree(&mut p), p.diags)
}
/// Parse a syntax tree.

View File

@ -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<GroupEntry>,
/// 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<Diag>) {
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<Deco>) {
self.feedback.decos.push(deco);
}
/// Continue parsing in a group.
///
/// When the end delimiter of the group is reached, all subsequent calls to

View File

@ -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,

View File

@ -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),

View File

@ -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<bool>, Vec<Spanned<Diag>>) {
let mut diags = vec![];
fn parse_metadata(src: &str, map: &LineMap) -> (Option<bool>, 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<bool>, Vec<Spanned<Diag>>
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<RefCell<Vec<Panic>>>) {
scope.def_const("test", ValueFunc::new("test", test));
}
fn print_diag(diag: &Spanned<Diag>, 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 {