From e63ce52ae0d929506a1fa238477f039d14d53813 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 4 Feb 2020 19:22:23 +0100 Subject: [PATCH] =?UTF-8?q?Merge=20`Parsed`=20and=20`Layouted`=20types=20i?= =?UTF-8?q?nto=20`Pass`=20with=20`Feedback`=20=F0=9F=8C=9D=F0=9F=8E=A2?= =?UTF-8?q?=F0=9F=8C=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/main.rs | 2 +- src/func.rs | 53 +++++++++++++-------------- src/layout/mod.rs | 3 +- src/layout/model.rs | 32 ++++++----------- src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++----- src/library/font.rs | 32 ++++++++--------- src/library/layout.rs | 42 +++++++++++----------- src/library/mod.rs | 4 +-- src/library/page.rs | 24 ++++++------- src/library/spacing.rs | 18 +++++----- src/syntax/mod.rs | 18 +++------- src/syntax/parsing.rs | 78 ++++++++++++---------------------------- src/syntax/scope.rs | 5 +-- src/syntax/span.rs | 5 +-- src/syntax/test.rs | 4 +-- tests/src/typeset.rs | 60 +++++++++++++++---------------- 16 files changed, 233 insertions(+), 228 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 92a83dff1..1b4903972 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -42,7 +42,7 @@ fn run() -> Result<(), Box> { let provider = DebugErrorProvider::new(fs); let typesetter = Typesetter::new((Box::new(provider), entries)); - let layouts = block_on(typesetter.typeset(&src)); + let layouts = block_on(typesetter.typeset(&src)).output; let writer = BufWriter::new(File::create(&dest)?); pdf::export(&layouts, typesetter.loader(), writer)?; diff --git a/src/func.rs b/src/func.rs index 590d9ed32..215de60f3 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,6 +1,7 @@ //! Trait and prelude for custom functions. -use crate::syntax::{ParseContext, Parsed}; +use crate::Pass; +use crate::syntax::ParseContext; use crate::syntax::func::FuncHeader; use crate::syntax::span::Spanned; @@ -36,7 +37,7 @@ pub trait ParseFunc { body: Option>, ctx: ParseContext, metadata: Self::Meta, - ) -> Parsed where Self: Sized; + ) -> Pass where Self: Sized; } /// Allows to implement a function type concisely. @@ -103,17 +104,16 @@ macro_rules! function { // Parse trait. (@parse($($a:tt)*) parse(default) $($r:tt)*) => { - function!(@parse($($a)*) parse(_h, _b, _c, _e, _d, _m) {Default::default() } $($r)*); + function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) {Default::default() } $($r)*); }; - (@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $e:ident, $d:ident) $($r:tt)* ) => { - function!(@parse($($a)*) parse($h, $b, $c, $e, $d, _metadata) $($r)*); + (@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => { + function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*); }; (@parse($name:ident, $meta:ty) parse( $header:ident, $body:ident, $ctx:ident, - $errors:ident, - $decos:ident, + $feedback:ident, $metadata:ident ) $code:block $($r:tt)*) => { impl $crate::func::ParseFunc for $name { @@ -124,42 +124,40 @@ macro_rules! function { #[allow(unused)] $body: Option<$crate::syntax::span::Spanned<&str>>, #[allow(unused)] $ctx: $crate::syntax::ParseContext, #[allow(unused)] $metadata: Self::Meta, - ) -> $crate::syntax::Parsed where Self: Sized { - let mut errors = vec![]; - let mut decorations = vec![]; + ) -> $crate::Pass where Self: Sized { + let mut feedback = $crate::Feedback::new(); #[allow(unused)] let $header = &mut header; - #[allow(unused)] let $errors = &mut errors; - #[allow(unused)] let $decos = &mut decorations; - let output = $code; + #[allow(unused)] let $feedback = &mut feedback; + + let func = $code; for arg in header.args.into_iter() { - errors.push(err!(arg.span(); "unexpected argument")); + feedback.errors.push(err!(arg.span(); "unexpected argument")); } - $crate::syntax::Parsed { output, errors, decorations } + $crate::Pass::new(func, feedback) } } function!(@layout($name) $($r)*); }; - (@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => { + (@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => { impl $crate::syntax::Model for $name { fn layout<'a, 'b, 't>( #[allow(unused)] &'a $this, #[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>, - ) -> $crate::layout::DynFuture<'t, $crate::layout::Layouted< - $crate::layout::Commands<'a>> - > where + ) -> $crate::layout::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>> + where 'a: 't, 'b: 't, Self: 't, { Box::pin(async move { - let mut errors = vec![]; - #[allow(unused)] let $errors = &mut errors; - let output = $code; - $crate::layout::Layouted { output, errors } + let mut feedback = $crate::Feedback::new(); + #[allow(unused)] let $feedback = &mut feedback; + let commands = $code; + $crate::Pass::new(commands, feedback) }) } } @@ -179,22 +177,21 @@ macro_rules! function { /// from parsing. #[macro_export] macro_rules! body { - (opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({ + (opt: $body:expr, $ctx:expr, $feedback:expr) => ({ $body.map(|body| { // Since the body span starts at the opening bracket of the body, we // need to add 1 column to find out the start position of body // content. let start = body.span.start + $crate::syntax::span::Position::new(0, 1); let parsed = $crate::syntax::parse(start, body.v, $ctx); - $errors.extend(parsed.errors); - $decos.extend(parsed.decorations); + $feedback.extend(parsed.feedback); parsed.output }) }); - (nope: $body:expr, $errors:expr) => { + (nope: $body:expr, $feedback:expr) => { if let Some(body) = $body { - $errors.push($crate::err!(body.span; "unexpected body")); + $feedback.errors.push($crate::err!(body.span; "unexpected body")); } }; } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8c120c6b6..b29d87e3a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -18,8 +18,7 @@ pub_use_mod!(model); /// Basic types used across the layouting engine. pub mod prelude { pub use super::{ - LayoutContext, layout, LayoutSpace, - Layouted, Commands, + LayoutContext, layout, LayoutSpace, Commands, LayoutAxes, LayoutAlignment, LayoutExpansion }; pub use super::GenericAxis::{self, *}; diff --git a/src/layout/model.rs b/src/layout/model.rs index d23968d8e..343205ec0 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -6,12 +6,12 @@ use std::future::Future; use std::pin::Pin; use smallvec::smallvec; +use crate::{Pass, Feedback}; use crate::GlobalFontLoader; -use crate::error::Errors; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::size::{Size, Size2D}; use crate::syntax::{Model, SyntaxModel, Node}; -use crate::syntax::span::{Spanned, Span, offset_spans}; +use crate::syntax::span::{Span, Spanned}; use super::line::{LineLayouter, LineContext}; use super::text::{layout_text, TextContext}; use super::*; @@ -23,7 +23,7 @@ pub struct ModelLayouter<'a> { ctx: LayoutContext<'a>, layouter: LineLayouter, style: LayoutStyle, - errors: Errors, + feedback: Feedback, } /// The context for layouting. @@ -51,15 +51,6 @@ pub struct LayoutContext<'a> { pub debug: bool, } -/// The result of layouting: Some layouted things and a list of errors. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Layouted { - /// The result of the layouting process. - pub output: T, - /// Errors that arose in the process of layouting. - pub errors: Errors, -} - /// A sequence of layouting commands. pub type Commands<'a> = Vec>; @@ -107,7 +98,7 @@ pub enum Command<'a> { } /// Layout a syntax model into a list of boxes. -pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_>) -> Layouted { +pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_>) -> Pass { let mut layouter = ModelLayouter::new(ctx); layouter.layout_syntax_model(model).await; layouter.finish() @@ -132,7 +123,7 @@ impl<'a> ModelLayouter<'a> { }), style: ctx.style.clone(), ctx, - errors: vec![], + feedback: Feedback::new(), } } @@ -151,7 +142,7 @@ impl<'a> ModelLayouter<'a> { }).await; // Add the errors generated by the model to the error list. - self.errors.extend(offset_spans(layouted.errors, model.span.start)); + self.feedback.extend_offset(model.span.start, layouted.feedback); for command in layouted.output { self.execute_command(command, model.span).await; @@ -195,11 +186,8 @@ impl<'a> ModelLayouter<'a> { }) } /// Compute the finished list of boxes. - pub fn finish(self) -> Layouted { - Layouted { - output: self.layouter.finish(), - errors: self.errors, - } + pub fn finish(self) -> Pass { + Pass::new(self.layouter.finish(), self.feedback) } /// Execute a command issued by a model. When the command is errorful, the @@ -225,7 +213,7 @@ impl<'a> ModelLayouter<'a> { BreakParagraph => self.layout_paragraph(), BreakPage => { if self.ctx.nested { - self.errors.push(err!(span; + self.feedback.errors.push(err!(span; "page break cannot be issued from nested context")); } else { self.layouter.finish_space(true) @@ -238,7 +226,7 @@ impl<'a> ModelLayouter<'a> { } SetPageStyle(style) => { if self.ctx.nested { - self.errors.push(err!(span; + self.feedback.errors.push(err!(span; "page style cannot be changed from nested context")); } else { self.style.page = style; diff --git a/src/lib.rs b/src/lib.rs index 2d6fa0216..b74837fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,10 +26,11 @@ use smallvec::smallvec; use toddle::{Font, OwnedData}; use toddle::query::{FontLoader, FontProvider, SharedFontLoader, FontDescriptor}; -use crate::layout::{Layouted, MultiLayout}; +use crate::error::Error; +use crate::layout::MultiLayout; use crate::style::{LayoutStyle, PageStyle, TextStyle}; -use crate::syntax::{SyntaxModel, Scope, ParseContext, Parsed, parse}; -use crate::syntax::span::Position; +use crate::syntax::{SyntaxModel, Scope, Decoration, ParseContext, parse}; +use crate::syntax::span::{Position, SpanVec, offset_spans}; /// Declare a module and reexport all its contents. @@ -100,12 +101,12 @@ impl Typesetter { } /// Parse source code into a syntax tree. - pub fn parse(&self, src: &str) -> Parsed { + pub fn parse(&self, src: &str) -> Pass { parse(Position::ZERO, src, ParseContext { scope: &self.scope }) } /// Layout a syntax tree and return the produced layout. - pub async fn layout(&self, model: &SyntaxModel) -> Layouted { + pub async fn layout(&self, model: &SyntaxModel) -> Pass { use crate::layout::prelude::*; let margins = self.style.page.margins(); @@ -130,9 +131,73 @@ impl Typesetter { } /// Process source code directly into a collection of layouts. - pub async fn typeset(&self, src: &str) -> MultiLayout { - let tree = self.parse(src).output; - self.layout(&tree).await.output + pub async fn typeset(&self, src: &str) -> Pass { + let parsed = self.parse(src); + let layouted = self.layout(&parsed.output).await; + let feedback = Feedback::merge(parsed.feedback, layouted.feedback); + Pass::new(layouted.output, feedback) + } +} + +/// The result of some pass: Some output `T` and feedback data. +#[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, +} + +impl Pass { + /// Create a new pass from output and feedback data. + pub fn new(output: T, feedback: Feedback) -> Pass { + Pass { output, feedback } + } + + /// Map the output type and keep the feedback data. + pub fn map(self, f: F) -> Pass where F: FnOnce(T) -> U { + Pass { + output: f(self.output), + feedback: self.feedback, + } + } +} + +/// User feedback data accumulated during a compilation pass. +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct Feedback { + /// Errors in the source. + pub errors: SpanVec, + /// Decorations of the source code for semantic syntax highlighting. + pub decos: SpanVec, +} + +impl Feedback { + /// Create a new feedback instance without errors and decos. + pub fn new() -> Feedback { + Feedback { + errors: vec![], + decos: vec![], + } + } + + /// Merged two feedbacks into one. + pub fn merge(mut a: Feedback, b: Feedback) -> Feedback { + a.extend(b); + a + } + + /// Add other feedback data to this feedback. + pub fn extend(&mut self, other: Feedback) { + self.errors.extend(other.errors); + self.decos.extend(other.decos); + } + + /// Add more feedback whose spans are local and need to be offset by an + /// `offset` to be correct for this feedbacks context. + pub fn extend_offset(&mut self, offset: Position, other: Feedback) { + self.errors.extend(offset_spans(offset, other.errors)); + self.decos.extend(offset_spans(offset, other.decos)); } } diff --git a/src/library/font.rs b/src/library/font.rs index 422a68f97..9c69e1ddc 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -11,10 +11,10 @@ function! { list: Vec, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { FontFamilyFunc { - body: body!(opt: body, ctx, errors, decos), - list: header.args.pos.get_all::(errors) + body: body!(opt: body, ctx, f), + list: header.args.pos.get_all::(&mut f.errors) .map(|s| s.0.to_lowercase()) .collect(), } @@ -37,11 +37,11 @@ function! { style: Option, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { FontStyleFunc { - body: body!(opt: body, ctx, errors, decos), - style: header.args.pos.get::(errors) - .or_missing(errors, header.name.span, "style"), + body: body!(opt: body, ctx, f), + style: header.args.pos.get::(&mut f.errors) + .or_missing(&mut f.errors, header.name.span, "style"), } } @@ -58,19 +58,19 @@ function! { weight: Option, } - parse(header, body, ctx, errors, decos) { - let body = body!(opt: body, ctx, errors, decos); - let weight = header.args.pos.get::>(errors) + parse(header, body, ctx, f) { + let body = body!(opt: body, ctx, f); + let weight = header.args.pos.get::>(&mut f.errors) .map(|Spanned { v: (weight, is_clamped), span }| { if is_clamped { - errors.push(err!(@Warning: span; + f.errors.push(err!(@Warning: span; "weight should be between \ 100 and 900, clamped to {}", weight.0)); } weight }) - .or_missing(errors, header.name.span, "weight"); + .or_missing(&mut f.errors, header.name.span, "weight"); FontWeightFunc { body, weight } } @@ -88,11 +88,11 @@ function! { size: Option, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { FontSizeFunc { - body: body!(opt: body, ctx, errors, decos), - size: header.args.pos.get::(errors) - .or_missing(errors, header.name.span, "size") + body: body!(opt: body, ctx, f), + size: header.args.pos.get::(&mut f.errors) + .or_missing(&mut f.errors, header.name.span, "size") } } diff --git a/src/library/layout.rs b/src/library/layout.rs index 591ea2c0c..da1652b06 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -10,17 +10,17 @@ function! { map: PosAxisMap, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { AlignFunc { - body: body!(opt: body, ctx, errors, decos), - map: PosAxisMap::parse::(errors, &mut header.args), + body: body!(opt: body, ctx, f), + map: PosAxisMap::parse::(&mut f.errors, &mut header.args), } } - layout(self, ctx, errors) { + layout(self, ctx, f) { ctx.base = ctx.spaces[0].dimensions; - let map = self.map.dedup(errors, ctx.axes, |alignment| { + let map = self.map.dedup(&mut f.errors, ctx.axes, |alignment| { alignment.axis().map(|s| s.to_generic(ctx.axes)) }); @@ -29,7 +29,7 @@ function! { if let Some(generic) = alignment.to_generic(ctx.axes, axis) { *ctx.alignment.get_mut(axis) = generic; } else { - errors.push(err!(span; + f.errors.push(err!(span; "invalid alignment `{}` for {} axis", alignment, axis)); } } @@ -38,7 +38,7 @@ function! { match &self.body { Some(body) => { let layouted = layout(body, ctx).await; - errors.extend(layouted.errors); + f.extend(layouted.feedback); vec![AddMultiple(layouted.output)] } None => vec![SetAlignment(ctx.alignment)], @@ -55,18 +55,18 @@ function! { map: PosAxisMap, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { DirectionFunc { name_span: header.name.span, - body: body!(opt: body, ctx, errors, decos), - map: PosAxisMap::parse::(errors, &mut header.args), + body: body!(opt: body, ctx, f), + map: PosAxisMap::parse::(&mut f.errors, &mut header.args), } } - layout(self, ctx, errors) { + layout(self, ctx, f) { ctx.base = ctx.spaces[0].dimensions; - let map = self.map.dedup(errors, ctx.axes, |direction| { + let map = self.map.dedup(&mut f.errors, ctx.axes, |direction| { Some(direction.axis().to_generic(ctx.axes)) }); @@ -76,7 +76,7 @@ function! { map.with(Secondary, |&dir| axes.secondary = dir); if axes.primary.axis() == axes.secondary.axis() { - errors.push(err!(self.name_span; + f.errors.push(err!(self.name_span; "invalid aligned primary and secondary axes: `{}`, `{}`", ctx.axes.primary, ctx.axes.secondary)); } else { @@ -86,7 +86,7 @@ function! { match &self.body { Some(body) => { let layouted = layout(body, ctx).await; - errors.extend(layouted.errors); + f.extend(layouted.feedback); vec![AddMultiple(layouted.output)] } None => vec![SetAxes(ctx.axes)], @@ -103,15 +103,15 @@ function! { debug: Option, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { BoxFunc { - body: body!(opt: body, ctx, errors, decos).unwrap_or(SyntaxModel::new()), - extents: AxisMap::parse::(errors, &mut header.args.key), - debug: header.args.key.get::(errors, "debug"), + body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()), + extents: AxisMap::parse::(&mut f.errors, &mut header.args.key), + debug: header.args.key.get::(&mut f.errors, "debug"), } } - layout(self, ctx, errors) { + layout(self, ctx, f) { ctx.repeat = false; ctx.spaces.truncate(1); @@ -119,7 +119,7 @@ function! { ctx.debug = debug; } - let map = self.extents.dedup(errors, ctx.axes); + let map = self.extents.dedup(&mut f.errors, ctx.axes); for &axis in &[Horizontal, Vertical] { if let Some(psize) = map.get(axis) { let size = psize.scaled(ctx.base.get(axis)); @@ -131,7 +131,7 @@ function! { let layouted = layout(&self.body, ctx).await; let layout = layouted.output.into_iter().next().unwrap(); - errors.extend(layouted.errors); + f.extend(layouted.feedback); vec![Add(layout)] } diff --git a/src/library/mod.rs b/src/library/mod.rs index f6666174a..08dffdd73 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -53,8 +53,8 @@ function! { body: Option, } - parse(header, body, ctx, errors, decos) { - ValFunc { body: body!(opt: body, ctx, errors, decos) } + parse(header, body, ctx, f) { + ValFunc { body: body!(opt: body, ctx, f) } } layout(self, ctx, errors) { diff --git a/src/library/page.rs b/src/library/page.rs index 084b34469..07e430264 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -12,16 +12,16 @@ function! { flip: bool, } - parse(header, body, ctx, errors, decos) { - body!(nope: body, errors); + parse(header, body, ctx, f) { + body!(nope: body, f); PageSizeFunc { - paper: header.args.pos.get::(errors), - extents: AxisMap::parse::(errors, &mut header.args.key), - flip: header.args.key.get::(errors, "flip").unwrap_or(false), + paper: header.args.pos.get::(&mut f.errors), + extents: AxisMap::parse::(&mut f.errors, &mut header.args.key), + flip: header.args.key.get::(&mut f.errors, "flip").unwrap_or(false), } } - layout(self, ctx, errors) { + layout(self, ctx, f) { let mut style = ctx.style.page; if let Some(paper) = self.paper { @@ -31,7 +31,7 @@ function! { style.class = PaperClass::Custom; } - let map = self.extents.dedup(errors, ctx.axes); + let map = self.extents.dedup(&mut f.errors, ctx.axes); map.with(Horizontal, |&width| style.dimensions.x = width); map.with(Vertical, |&height| style.dimensions.y = height); @@ -50,16 +50,16 @@ function! { padding: PaddingMap, } - parse(header, body, ctx, errors, decos) { - body!(nope: body, errors); + parse(header, body, ctx, f) { + body!(nope: body, f); PageMarginsFunc { - padding: PaddingMap::parse(errors, &mut header.args), + padding: PaddingMap::parse(&mut f.errors, &mut header.args), } } - layout(self, ctx, errors) { + layout(self, ctx, f) { let mut style = ctx.style.page; - self.padding.apply(errors, ctx.axes, &mut style.margins); + self.padding.apply(&mut f.errors, ctx.axes, &mut style.margins); vec![SetPageStyle(style)] } } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index d77285c5d..a6db162a8 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -46,13 +46,13 @@ function! { type Meta = ContentKind; - parse(header, body, ctx, errors, decos, meta) { + parse(header, body, ctx, f, meta) { ContentSpacingFunc { - body: body!(opt: body, ctx, errors, decos), + body: body!(opt: body, ctx, f), content: meta, - spacing: header.args.pos.get::(errors) + spacing: header.args.pos.get::(&mut f.errors) .map(|num| num as f32) - .or_missing(errors, header.name.span, "spacing"), + .or_missing(&mut f.errors, header.name.span, "spacing"), } } @@ -84,15 +84,15 @@ function! { type Meta = Option; - parse(header, body, ctx, errors, decos, meta) { - body!(nope: body, errors); + parse(header, body, ctx, f, meta) { + body!(nope: body, f); SpacingFunc { spacing: if let Some(axis) = meta { - header.args.pos.get::(errors) + header.args.pos.get::(&mut f.errors) .map(|s| (AxisKey::Specific(axis), s)) } else { - header.args.key.get_with_key::(errors) - }.or_missing(errors, header.name.span, "spacing"), + header.args.key.get_with_key::(&mut f.errors) + }.or_missing(&mut f.errors, header.name.span, "spacing"), } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index cfa4c2e5e..8596a618b 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -5,7 +5,8 @@ use std::fmt::Debug; use async_trait::async_trait; use serde::Serialize; -use crate::layout::{LayoutContext, Layouted, Commands, Command}; +use crate::{Pass, Feedback}; +use crate::layout::{LayoutContext, Commands, Command}; use self::span::{Spanned, SpanVec}; pub mod expr; @@ -25,10 +26,7 @@ mod test; pub trait Model: Debug + ModelBounds { /// Layout the model into a sequence of commands processed by a /// [`ModelLayouter`](crate::layout::ModelLayouter). - async fn layout<'a>( - &'a self, - ctx: LayoutContext<'_>, - ) -> Layouted>; + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass>; } /// A tree representation of source code. @@ -52,14 +50,8 @@ impl SyntaxModel { #[async_trait(?Send)] impl Model for SyntaxModel { - async fn layout<'a>( - &'a self, - _: LayoutContext<'_>, - ) -> Layouted> { - Layouted { - output: vec![Command::LayoutSyntaxModel(self)], - errors: vec![], - } + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass> { + Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new()) } } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 03866c2cc..1526a5cbb 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,10 +1,10 @@ //! Parsing of source code into syntax models. -use crate::error::Errors; +use crate::{Pass, Feedback}; use super::expr::*; use super::func::{FuncHeader, FuncArgs, FuncArg}; use super::scope::Scope; -use super::span::{Position, Span, Spanned, SpanVec, offset_spans}; +use super::span::{Position, Span, Spanned}; use super::tokens::{Token, Tokens, TokenizationMode}; use super::*; @@ -16,36 +16,12 @@ pub struct ParseContext<'a> { pub scope: &'a Scope, } -/// The result of parsing: Some parsed thing, errors and decorations for syntax -/// highlighting. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Parsed { - /// The result of the parsing process. - pub output: T, - /// Errors that arose in the parsing process. - pub errors: Errors, - /// Decorations for semantic syntax highlighting. - pub decorations: SpanVec, -} - -impl Parsed { - /// Map the output type and keep errors and decorations. - pub fn map(self, f: F) -> Parsed where F: FnOnce(T) -> U { - Parsed { - output: f(self.output), - errors: self.errors, - decorations: self.decorations, - } - } -} - /// Parse source code into a syntax model. /// /// All errors and decorations are offset by the `start` position. -pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { +pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Pass { let mut model = SyntaxModel::new(); - let mut errors = Vec::new(); - let mut decorations = Vec::new(); + let mut feedback = Feedback::new(); // We always start in body mode. The header tokenization mode is only used // in the `FuncParser`. @@ -64,16 +40,12 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { - let parsed: Parsed = FuncParser::new(header, body, ctx).parse(); - - // Collect the errors and decorations from the function parsing, - // but offset their spans by the start of the function since - // they are function-local. - errors.extend(offset_spans(parsed.errors, span.start)); - decorations.extend(offset_spans(parsed.decorations, span.start)); + let parsed = FuncParser::new(header, body, ctx).parse(); + feedback.extend_offset(span.start, parsed.feedback); if !terminated { - errors.push(err!(Span::at(span.end); "expected closing bracket")); + feedback.errors.push(err!(Span::at(span.end); + "expected closing bracket")); } parsed.output @@ -87,7 +59,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed continue, other => { - errors.push(err!(span; "unexpected {}", other.name())); + feedback.errors.push(err!(span; "unexpected {}", other.name())); continue; } }; @@ -95,14 +67,13 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { ctx: ParseContext<'s>, - errors: Errors, - decorations: SpanVec, + feedback: Feedback, /// ```typst /// [tokens][body] @@ -129,8 +100,7 @@ impl<'s> FuncParser<'s> { ) -> FuncParser<'s> { FuncParser { ctx, - errors: vec![], - decorations: vec![], + feedback: Feedback::new(), tokens: Tokens::new(Position::new(0, 1), header, TokenizationMode::Header), peeked: None, body, @@ -138,7 +108,7 @@ impl<'s> FuncParser<'s> { } /// Do the parsing. - fn parse(mut self) -> Parsed { + fn parse(mut self) -> Pass { let parsed = if let Some(header) = self.parse_func_header() { let name = header.name.v.as_str(); let (parser, deco) = match self.ctx.scope.get_parser(name) { @@ -147,12 +117,12 @@ impl<'s> FuncParser<'s> { // The fallback parser was returned. Invalid function. Err(parser) => { - self.errors.push(err!(header.name.span; "unknown function")); + self.feedback.errors.push(err!(header.name.span; "unknown function")); (parser, Decoration::InvalidFuncName) } }; - self.decorations.push(Spanned::new(deco, header.name.span)); + self.feedback.decos.push(Spanned::new(deco, header.name.span)); parser(header, self.body, self.ctx) } else { @@ -166,14 +136,9 @@ impl<'s> FuncParser<'s> { self.ctx.scope.get_fallback_parser()(default, self.body, self.ctx) }; - self.errors.extend(parsed.errors); - self.decorations.extend(parsed.decorations); + self.feedback.extend(parsed.feedback); - Parsed { - output: Node::Model(parsed.output), - errors: self.errors, - decorations: self.decorations, - } + Pass::new(Node::Model(parsed.output), self.feedback) } /// Parse the header tokens. @@ -235,7 +200,7 @@ impl<'s> FuncParser<'s> { self.eat(); self.skip_whitespace(); - self.decorations.push(Spanned::new(Decoration::ArgumentKey, span)); + self.feedback.decos.push(Spanned::new(Decoration::ArgumentKey, span)); self.parse_expr().map(|value| { FuncArg::Key(Pair { @@ -332,14 +297,14 @@ impl<'s> FuncParser<'s> { /// Add an error about an expected `thing` which was not found, showing /// what was found instead. fn expected_found(&mut self, thing: &str, found: Spanned) { - self.errors.push(err!(found.span; + self.feedback.errors.push(err!(found.span; "expected {}, found {}", thing, found.v.name())); } /// Add an error about an `thing` which was expected but not found at the /// given position. fn expected_at(&mut self, thing: &str, pos: Position) { - self.errors.push(err!(Span::at(pos); "expected {}", thing)); + self.feedback.errors.push(err!(Span::at(pos); "expected {}", thing)); } /// Add a expected-found-error if `found` is `Some` and an expected-error @@ -434,7 +399,8 @@ mod tests { macro_rules! e { ($s:expr => [$(($sl:tt:$sc:tt, $el:tt:$ec:tt, $e:expr)),* $(,)?]) => { let ctx = ParseContext { scope: &scope() }; - let errors = parse(Position::ZERO, $s, ctx).errors + let errors = parse(Position::ZERO, $s, ctx).feedback + .errors .into_iter() .map(|s| s.map(|e| e.message)) .collect::>(); diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index 551d06846..f7b20b9f3 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -3,9 +3,10 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use crate::Pass; use crate::func::ParseFunc; use super::func::FuncHeader; -use super::parsing::{ParseContext, Parsed}; +use super::parsing::ParseContext; use super::span::Spanned; use super::Model; @@ -75,7 +76,7 @@ type Parser = dyn Fn( FuncHeader, Option>, ParseContext, -) -> Parsed>; +) -> Pass>; fn parser(metadata: ::Meta) -> Box where F: ParseFunc + Model + 'static { diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 8973ef893..3c55daa1e 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -160,6 +160,7 @@ pub type SpanVec = Vec>; /// [Offset](Span::offset) all spans in a vector of spanned things by a start /// position. -pub fn offset_spans(vec: SpanVec, start: Position) -> impl Iterator> { - vec.into_iter().map(move |s| s.map_span(|span| span.offset(start))) +pub fn offset_spans(start: Position, vec: SpanVec) -> impl Iterator> { + vec.into_iter() + .map(move |s| s.map_span(|span| span.offset(start))) } diff --git a/src/syntax/test.rs b/src/syntax/test.rs index e37e8cf5f..df3547681 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -12,13 +12,13 @@ function! { pub body: Option, } - parse(header, body, ctx, errors, decos) { + parse(header, body, ctx, f) { let cloned = header.clone(); header.args.pos.items.clear(); header.args.key.pairs.clear(); DebugFn { header: cloned, - body: body!(opt: body, ctx, errors, decos), + body: body!(opt: body, ctx, f), } } diff --git a/tests/src/typeset.rs b/tests/src/typeset.rs index 61889e956..efeb5aee7 100644 --- a/tests/src/typeset.rs +++ b/tests/src/typeset.rs @@ -5,6 +5,7 @@ use std::fs::{File, create_dir_all, read_dir, read_to_string}; use std::io::{BufWriter, Write}; use std::panic; use std::process::Command; +use std::time::{Instant, Duration}; use futures_executor::block_on; @@ -55,8 +56,6 @@ fn main() -> DynResult<()> { }).ok(); } - println!(); - Ok(()) } @@ -121,43 +120,40 @@ fn test(name: &str, src: &str) -> DynResult<()> { /// Compile the source code with the typesetter. fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout { - #![allow(unused_variables)] - use std::time::Instant; + if cfg!(debug_assertions) { + let typeset = block_on(typesetter.typeset(src)); + let errors = typeset.feedback.errors; - // Warmup. - #[cfg(not(debug_assertions))] - let warmup = { - let warmup_start = Instant::now(); - block_on(typesetter.typeset(&src)); - Instant::now() - warmup_start - }; + if !errors.is_empty() { + for error in errors { + println!(" {:?} {:?}: {}", + error.v.severity, + error.span, + error.v.message + ); + } + } - let start = Instant::now(); - let parsed = typesetter.parse(&src); - let parse = Instant::now() - start; + typeset.output + } else { + fn measure(f: impl FnOnce() -> T) -> (T, Duration) { + let start = Instant::now(); + let output = f(); + let duration = Instant::now() - start; + (output, duration) + }; - if !parsed.errors.is_empty() { - println!("parse errors: {:#?}", parsed.errors); - } + let (_, cold) = measure(|| block_on(typesetter.typeset(src))); + let (model, parse) = measure(|| typesetter.parse(src).output); + let (layouts, layout) = measure(|| block_on(typesetter.layout(&model)).output); - let start_layout = Instant::now(); - let layouted = block_on(typesetter.layout(&parsed.output)); - let layout = Instant::now() - start_layout; - let total = Instant::now() - start; - - if !layouted.errors.is_empty() { - println!("layout errors: {:#?}", layouted.errors); - } - - #[cfg(not(debug_assertions))] { - println!(" - cold start: {:?}", warmup); - println!(" - warmed up: {:?}", total); + println!(" - cold start: {:?}", cold); + println!(" - warmed up: {:?}", parse + layout); println!(" - parsing: {:?}", parse); println!(" - layouting: {:?}", layout); - println!(); - } - layouted.output + layouts + } } /// Command line options.