diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 000000000..ed43818fc --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Error { + pub message: String, +} + +impl Error { + pub fn new(message: impl Into) -> Error { + Error { message: message.into() } + } +} diff --git a/src/func/macros.rs b/src/func/macros.rs index 90c3b11ef..1e92d7353 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -1,131 +1,76 @@ //! Helper types and macros for creating custom functions. -/// Defines function types concisely. #[macro_export] macro_rules! function { - // Parse a unit struct. - ($(#[$outer:meta])* pub struct $type:ident; $($rest:tt)*) => { - $(#[$outer])* pub struct $type; - function!(@meta $type | $($rest)*); + // Entry point. + ($(#[$outer:meta])* $v:vis $storage:ident $name:ident $($r:tt)*) => { + function!(@def($name) $(#[$outer])* $v $storage $name $($r)*); + }; + (@def($name:ident) $definition:item $($r:tt)*) => { + $definition + function!(@meta($name) $($r)*); }; - // Parse a tuple struct. - ($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => { - $(#[$outer])* pub struct $type($($fields)*); - function!(@meta $type | $($rest)*); + // Metadata. + (@meta($name:ident) type Meta = $meta:ty; $($r:tt)*) => { + function!(@parse($name, $meta) $($r)*); + }; + (@meta($name:ident) $($r:tt)*) => { + function!(@parse($name, ()) $($r)*); }; - // Parse a struct with fields. - ($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => { - $(#[$outer])* pub struct $type { $($fields)* } - function!(@meta $type | $($rest)*); + // Parse trait. + (@parse($($a:tt)*) parse(default) $($r:tt)*) => { + function!(@parse($($a)*) parse() { Default::default() } $($r)*); }; - - // Parse an enum. - ($(#[$outer:meta])* pub enum $type:ident { $($fields:tt)* } $($rest:tt)*) => { - $(#[$outer])* pub enum $type { $($fields)* } - function!(@meta $type | $($rest)*); + (@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 metadata type definition. - (@meta $type:ident | type Meta = $meta:ty; $($rest:tt)*) => { - function!(@parse $type $meta | $($rest)*); - }; - - // Set the metadata to `()` if there is no type definition. - (@meta $type:ident | $($rest:tt)*) => { - function!(@parse $type () | $($rest)*); - }; - - // Parse a `parse(default)`. - (@parse $type:ident $meta:ty | parse(default) $($rest:tt)*) => { - function!(@parse $type $meta | - parse(_args, _body, _ctx, _meta) { Default::default() } - $($rest)* - ); - }; - - // (0-arg) Parse a parse-definition without arguments. - (@parse $type:ident $meta:ty | parse() $code:block $($rest:tt)*) => { - function!(@parse $type $meta | parse(_args, _body, _ctx, _meta) $code $($rest)*); - }; - - // (1-arg) Parse a parse-definition with only the first argument. - (@parse $type:ident $meta:ty | parse($header:ident) $code:block $($rest:tt)*) => { - function!(@parse $type $meta | parse($header, _body, _ctx, _meta) $code $($rest)*); - }; - - // (2-arg) Parse a parse-definition with only the first two arguments. - (@parse $type:ident $meta:ty | - parse($header:ident, $body:pat) $code:block $($rest:tt)* - ) => { - function!(@parse $type $meta | parse($header, $body, _ctx, _meta) $code $($rest)*); - }; - - // (3-arg) Parse a parse-definition with only the first three arguments. - (@parse $type:ident $meta:ty | - parse($header:ident, $body:pat, $ctx:pat) $code:block $($rest:tt)* - ) => { - function!(@parse $type $meta | parse($header, $body, $ctx, _meta) $code $($rest)*); - }; - - // (4-arg) Parse a parse-definition with all four arguments. - (@parse $type:ident $meta:ty | - parse($header:ident, $body:pat, $ctx:pat, $metadata:pat) $code:block - $($rest:tt)* - ) => { - impl $crate::func::ParseFunc for $type { + (@parse($name:ident, $meta:ty) parse( + $header:ident, + $body:ident, + $ctx:ident, + $errors:ident, + $decos:ident, + $metadata:ident + ) $code:block $($r:tt)*) => { + impl $crate::func::Parse for $name { type Meta = $meta; fn parse( - header: $crate::syntax::FuncHeader, - $body: Option<&str>, - $ctx: $crate::syntax::ParseContext, - $metadata: Self::Meta, - ) -> $crate::syntax::ParseResult where Self: Sized { - #[allow(unused_mut)] - let mut $header = header; - let val = $code; - if !$header.args.is_empty() { - return Err($crate::TypesetError::with_message("unexpected arguments")); - } - Ok(val) + #[allow(unused)] mut $header: FuncHeader, + #[allow(unused)] $body: Option>, + #[allow(unused)] $ctx: ParseContext, + #[allow(unused)] $metadata: Self::Meta, + ) -> Parsed where Self: Sized { + #[allow(unused)] let mut $errors = vec![]; + #[allow(unused)] let mut $decos = vec![]; + let output = $code; + $crate::syntax::Parsed { output, errors: $errors, decorations: $decos } } } - function!(@layout $type | $($rest)*); + function!(@layout($name) $($r)*); }; - // (0-arg) Parse a layout-definition without arguments. - (@layout $type:ident | layout() $code:block) => { - function!(@layout $type | layout(self, _ctx) $code); - }; - - // (1-arg) Parse a layout-definition with only the first argument. - (@layout $type:ident | layout($this:ident) $code:block) => { - function!(@layout $type | layout($this, _ctx) $code); - }; - - // (2-arg) Parse a layout-definition with all arguments. - (@layout $type:ident | layout($this:ident, $ctx:pat) $code:block) => { - impl $crate::func::LayoutFunc for $type { - fn layout<'a, 'life0, 'life1, 'async_trait>( - &'a $this, - $ctx: $crate::layout::LayoutContext<'life0, 'life1> - ) -> std::pin::Pin> - > + 'async_trait - >> + (@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => { + impl $crate::syntax::Model for $name { + fn layout<'a, 'b, 'c, 't>( + #[allow(unused)] &'a $this, + #[allow(unused)] $ctx: $crate::layout::LayoutContext<'b, 'c>, + ) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>> where - 'a: 'async_trait, - 'life0: 'async_trait, - 'life1: 'async_trait, - Self: 'async_trait, + 'a: 't, + 'b: 't, + 'c: 't, + Self: 't, { - #[allow(unreachable_code)] - Box::pin(async move { Ok($code) }) + Box::pin(async move { + #[allow(unused)] let mut $errors = vec![]; + let output = $code; + $crate::layout::Layouted { output, errors: $errors } + }) } } }; @@ -137,35 +82,30 @@ macro_rules! function { /// - If the function can have a body, use `parse!(optional: body, ctx)`. /// - If the function must have a body, use `parse!(expected: body, ctx)`. #[macro_export] -macro_rules! parse { - (forbidden: $body:expr) => { - if $body.is_some() { - return Err($crate::TypesetError::with_message("unexpected body")); +macro_rules! body { + (opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({ + $body.map(|body| { + let parsed = $crate::syntax::parse(body.span.start, body.v, $ctx); + $errors.extend(parsed.errors); + $decos.extend(parsed.decorations); + parsed.output + }) + }); + + (nope: $body:expr, $errors:expr) => { + if let Some(body) = $body { + $errors.push($crate::err!(body.span, "unexpected body")); } }; - - (optional: $body:expr, $ctx:expr) => ( - if let Some(body) = $body { - Some($crate::syntax::parse(body, $ctx).0) - } else { - None - } - ); - - (expected: $body:expr, $ctx:expr) => ( - if let Some(body) = $body { - $crate::syntax::parse(body, $ctx).0 - } else { - Err($crate::TypesetError::with_message("unexpected body")) - } - ) } -/// Early-return with a formatted typesetting error or construct an error -/// expression. +/// Construct a spanned error. #[macro_export] -macro_rules! error { - (@unexpected_argument) => (error!(@"unexpected argument")); - (@$($tts:tt)*) => ($crate::TypesetError::with_message(format!($($tts)*))); - ($($tts:tt)*) => (return Err(error!(@$($tts)*));); +macro_rules! err { + ($span:expr, $($args:tt)*) => { + $crate::syntax::Spanned { + v: $crate::error::Error::new(format!($($args)*)), + span: $span, + } + }; } diff --git a/src/func/mod.rs b/src/func/mod.rs index bfc2774c6..1ca226c38 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -3,7 +3,6 @@ use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; -use async_trait::async_trait; use self::prelude::*; @@ -12,81 +11,34 @@ mod macros; /// Useful imports for creating your own functions. pub mod prelude { - pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands}; + pub use super::{Scope, Parse, Command, Commands}; pub use crate::layout::prelude::*; - pub use crate::syntax::*; + pub use crate::syntax::prelude::*; pub use crate::size::{Size, Size2D, SizeBox, ValueBox, ScaleSize, FSize, PSize}; pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; pub use Command::*; } -/// Types representing functions that are parsed from source code. -pub trait ParseFunc { +/// Parse a function from source code. +pub trait Parse { type Meta: Clone; /// Parse the header and body into this function given a context. fn parse( header: FuncHeader, - body: Option<&str>, + body: Option>, ctx: ParseContext, metadata: Self::Meta, - ) -> ParseResult where Self: Sized; + ) -> Parsed where Self: Sized; } -/// Function types which can be laid out in a layout context. -/// -/// This trait is a supertrait of `[LayoutFuncBounds]` for technical reasons. -/// The trait `[LayoutFuncBounds]` is automatically implemented for types which -/// can be used as functions, that is, all types which fulfill the bounds `Debug -/// + PartialEq + 'static`. -#[async_trait(?Send)] -pub trait LayoutFunc: LayoutFuncBounds { - /// Layout this function in a given context. - /// - /// Returns a sequence of layouting commands which describe what the - /// function is doing. - async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> LayoutResult>; -} - -impl dyn LayoutFunc { - /// Downcast a function trait object to a concrete function type. - pub fn downcast(&self) -> Option<&F> where F: LayoutFunc + 'static { - self.help_cast_as_any().downcast_ref::() - } -} - -impl PartialEq for dyn LayoutFunc { - fn eq(&self, other: &dyn LayoutFunc) -> bool { - self.help_eq(other) - } -} - -/// A helper trait that describes requirements for types that can implement -/// [`Function`]. -/// -/// Automatically implemented for all types which fulfill to the bounds `Debug + -/// PartialEq + 'static`. There should be no need to implement this manually. -pub trait LayoutFuncBounds: Debug { - /// Cast self into `Any`. - fn help_cast_as_any(&self) -> &dyn Any; - - /// Compare self with another function trait object. - fn help_eq(&self, other: &dyn LayoutFunc) -> bool; -} - -impl LayoutFuncBounds for T where T: Debug + PartialEq + 'static { - fn help_cast_as_any(&self) -> &dyn Any { - self - } - - fn help_eq(&self, other: &dyn LayoutFunc) -> bool { - if let Some(other) = other.help_cast_as_any().downcast_ref::() { - self == other - } else { - false - } - } -} +/// A function which parses the source of a function into a model type which +/// implements [`Model`]. +type Parser = dyn Fn( + FuncHeader, + Option>, + ParseContext, +) -> Parsed>; /// A sequence of layouting commands. pub type Commands<'a> = Vec>; @@ -94,7 +46,7 @@ pub type Commands<'a> = Vec>; /// Layouting commands from functions to the typesetting engine. #[derive(Debug)] pub enum Command<'a> { - LayoutTree(&'a SyntaxTree), + LayoutSyntaxModel(&'a SyntaxModel), Add(Layout), AddMultiple(MultiLayout), @@ -114,41 +66,17 @@ pub enum Command<'a> { /// A map from identifiers to function parsers. pub struct Scope { parsers: HashMap>, - debug: Option> -} - -/// A function which parses the source of a function into a function type which -/// implements [`LayoutFunc`]. -type Parser = dyn Fn( - FuncHeader, - Option<&str>, - ParseContext -) -> ParseResult>; - -fn make_parser(metadata: ::Meta) -> Box -where F: ParseFunc + LayoutFunc + 'static { - Box::new(move |a, b, c| { - F::parse(a, b, c, metadata.clone()) - .map(|f| Box::new(f) as Box) - }) + fallback: Box } impl Scope { - /// Create a new empty scope. - pub fn new() -> Scope { - Scope { - parsers: HashMap::new(), - debug: None, - } - } - - /// Create a new scope with a debug parser that is invoked if not other + /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. - pub fn with_debug() -> Scope - where F: ParseFunc + LayoutFunc + 'static { + pub fn new() -> Scope + where F: Parse + Model + 'static { Scope { parsers: HashMap::new(), - debug: Some(make_parser::(())), + fallback: parser::(()), } } @@ -159,24 +87,25 @@ impl Scope { /// Associate the given name with a type that is parseable into a function. pub fn add(&mut self, name: &str) - where F: ParseFunc + LayoutFunc + 'static { + where F: Parse + Model + 'static { self.add_with_metadata::(name, ()); } /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). - pub fn add_with_metadata(&mut self, name: &str, metadata: ::Meta) - where F: ParseFunc + LayoutFunc + 'static { + pub fn add_with_metadata(&mut self, name: &str, metadata: ::Meta) + where F: Parse + Model + 'static { self.parsers.insert( name.to_owned(), - make_parser::(metadata), + parser::(metadata), ); } /// Return the parser with the given name if there is one. - pub(crate) fn get_parser(&self, name: &str) -> Option<&Parser> { - self.parsers.get(name).map(|x| &**x) - .or(self.debug.as_ref().map(|x| &**x)) + pub(crate) fn get_parser(&self, name: &str) -> Result<&Parser, &Parser> { + self.parsers.get(name) + .map(|x| &**x) + .ok_or_else(|| &*self.fallback) } } @@ -186,3 +115,10 @@ impl Debug for Scope { write!(f, "{:?}", self.parsers.keys()) } } + +fn parser(metadata: ::Meta) -> Box where F: Parse + Model + 'static { + Box::new(move |h, b, c| { + F::parse(h, b, c, metadata.clone()) + .map(|model| Box::new(model) as Box) + }) +} diff --git a/src/layout/line.rs b/src/layout/line.rs index c4205e819..f7777ae02 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -67,18 +67,21 @@ impl LineLayouter { } /// Add a layout to the run. - pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + pub fn add(&mut self, layout: Layout) { let axes = self.ctx.axes; if let Some(alignment) = self.run.alignment { if layout.alignment.secondary != alignment.secondary { - if self.stack.is_fitting_alignment(layout.alignment) { - self.finish_line()?; + // TODO: Issue warning for non-fitting alignment in + // non-repeating context. + let fitting = self.stack.is_fitting_alignment(layout.alignment); + if !fitting && self.ctx.repeat { + self.finish_space(true); } else { - self.finish_space(true)?; + self.finish_line(); } } else if layout.alignment.primary < alignment.primary { - self.finish_line()?; + self.finish_line(); } else if layout.alignment.primary > alignment.primary { let mut rest_run = LineRun::new(); @@ -92,7 +95,7 @@ impl LineLayouter { rest_run.size.y = self.run.size.y; - self.finish_line()?; + self.finish_line(); self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard); self.run = rest_run; @@ -105,16 +108,14 @@ impl LineLayouter { let size = layout.dimensions.generalized(axes); - while !self.usable().fits(size) { + if !self.usable().fits(size) { if !self.line_is_empty() { - self.finish_line()?; - } else { - if self.stack.space_is_last() && self.stack.space_is_empty() { - error!("cannot fit box of size {} into usable size of {}", - layout.dimensions, self.usable()); - } + self.finish_line(); + } - self.finish_space(true)?; + // TODO: Issue warning about overflow if there is overflow. + if !self.usable().fits(size) { + self.stack.skip_to_fitting_space(layout.dimensions); } } @@ -124,18 +125,15 @@ impl LineLayouter { self.run.size.x += size.x; self.run.size.y.max_eq(size.y); self.run.last_spacing = LastSpacing::None; - - Ok(()) } /// Add multiple layouts to the run. /// /// This function simply calls `add` repeatedly for each layout. - pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { + pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { - self.add(layout)?; + self.add(layout); } - Ok(()) } /// The remaining usable size in the run. @@ -180,20 +178,16 @@ impl LineLayouter { } /// Finish the run and add secondary spacing to the underlying stack. - pub fn add_secondary_spacing( - &mut self, - spacing: Size, - kind: SpacingKind - ) -> LayoutResult<()> { - self.finish_line_if_not_empty()?; - Ok(self.stack.add_spacing(spacing, kind)) + pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) { + self.finish_line_if_not_empty(); + self.stack.add_spacing(spacing, kind) } /// Change the layouting axes used by this layouter. - pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> { - self.finish_line_if_not_empty()?; + pub fn set_axes(&mut self, axes: LayoutAxes) { + self.finish_line_if_not_empty(); self.ctx.axes = axes; - Ok(self.stack.set_axes(axes)) + self.stack.set_axes(axes) } /// Change the layouting spaces to use. @@ -224,19 +218,19 @@ impl LineLayouter { } /// Finish the last line and compute the final multi-layout. - pub fn finish(mut self) -> LayoutResult { - self.finish_line_if_not_empty()?; + pub fn finish(mut self) -> MultiLayout { + self.finish_line_if_not_empty(); self.stack.finish() } /// Finish the currently active space and start a new one. - pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { - self.finish_line_if_not_empty()?; + pub fn finish_space(&mut self, hard: bool) { + self.finish_line_if_not_empty(); self.stack.finish_space(hard) } /// Add the current line to the stack and start a new line. - pub fn finish_line(&mut self) -> LayoutResult<()> { + pub fn finish_line(&mut self) { let mut actions = LayoutActions::new(); let layouts = std::mem::replace(&mut self.run.layouts, vec![]); @@ -257,21 +251,17 @@ impl LineLayouter { alignment: self.run.alignment .unwrap_or(LayoutAlignment::new(Origin, Origin)), actions: actions.to_vec(), - })?; + }); self.run = LineRun::new(); - self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND); - - Ok(()) + self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); } /// Finish the current line if it is not empty. - fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> { + fn finish_line_if_not_empty(&mut self) { if !self.line_is_empty() { self.finish_line() - } else { - Ok(()) } } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 495519451..75d34409b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -4,23 +4,20 @@ use std::io::{self, Write}; use smallvec::SmallVec; use toddle::query::{SharedFontLoader, FontIndex}; +use crate::error::Error; +use crate::syntax::SpanVec; use crate::size::{Size, Size2D, SizeBox}; use crate::style::LayoutStyle; mod actions; -mod tree; +mod model; mod line; mod stack; mod text; /// Common types for layouting. pub mod prelude { - pub use super::{ - layout, LayoutResult, - MultiLayout, Layout, LayoutContext, LayoutSpaces, LayoutSpace, - LayoutExpansion, LayoutAxes, GenericAxis, SpecificAxis, Direction, - LayoutAlignment, Alignment, SpacingKind, - }; + pub use super::*; pub use GenericAxis::*; pub use SpecificAxis::*; pub use Direction::*; @@ -29,7 +26,7 @@ pub mod prelude { /// Different kinds of layouters (fully re-exported). pub mod layouters { - pub use super::tree::layout; + pub use super::model::layout; pub use super::line::{LineLayouter, LineContext}; pub use super::stack::{StackLayouter, StackContext}; pub use super::text::{layout_text, TextContext}; @@ -40,8 +37,19 @@ pub use self::layouters::*; pub use self::prelude::*; -/// The result type for layouting. -pub type LayoutResult = crate::TypesetResult; +pub struct Layouted { + pub output: T, + pub errors: SpanVec, +} + +impl Layouted { + pub fn map(self, f: F) -> Layouted where F: FnOnce(T) -> U { + Layouted { + output: f(self.output), + errors: self.errors, + } + } +} /// A collection of layouts. pub type MultiLayout = Vec; @@ -361,14 +369,16 @@ pub enum SpacingKind { Soft(u32), } -/// The standard spacing kind used for paragraph spacing. -const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1); +impl SpacingKind { + /// The standard spacing kind used for paragraph spacing. + pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1); -/// The standard spacing kind used for line spacing. -const LINE_KIND: SpacingKind = SpacingKind::Soft(2); + /// The standard spacing kind used for line spacing. + pub const LINE: SpacingKind = SpacingKind::Soft(2); -/// The standard spacing kind used for word spacing. -const WORD_KIND: SpacingKind = SpacingKind::Soft(1); + /// The standard spacing kind used for word spacing. + pub const WORD: SpacingKind = SpacingKind::Soft(1); +} /// The last appeared spacing. #[derive(Debug, Copy, Clone, PartialEq)] diff --git a/src/layout/model.rs b/src/layout/model.rs new file mode 100644 index 000000000..bcec5ceb2 --- /dev/null +++ b/src/layout/model.rs @@ -0,0 +1,193 @@ +use std::pin::Pin; +use std::future::Future; +use smallvec::smallvec; + +use crate::error::Error; +use crate::func::Command; +use crate::syntax::{Model, DynFuture, SyntaxModel, Node}; +use crate::syntax::{SpanVec, Spanned, Span, offset_spans}; +use super::*; + + +pub async fn layout( + model: &SyntaxModel, + ctx: LayoutContext<'_, '_> +) -> Layouted { + let mut layouter = ModelLayouter::new(ctx); + layouter.layout_syntax_model(model).await; + layouter.finish() +} + +#[derive(Debug, Clone)] +struct ModelLayouter<'a, 'p> { + ctx: LayoutContext<'a, 'p>, + layouter: LineLayouter, + style: LayoutStyle, + errors: SpanVec, +} + +impl<'a, 'p> ModelLayouter<'a, 'p> { + /// Create a new syntax tree layouter. + fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> { + ModelLayouter { + layouter: LineLayouter::new(LineContext { + spaces: ctx.spaces.clone(), + axes: ctx.axes, + alignment: ctx.alignment, + repeat: ctx.repeat, + debug: ctx.debug, + line_spacing: ctx.style.text.line_spacing(), + }), + style: ctx.style.clone(), + ctx, + errors: vec![], + } + } + + fn layout<'r>( + &'r mut self, + model: Spanned<&'r dyn Model> + ) -> DynFuture<'r, ()> { Box::pin(async move { + let layouted = model.v.layout(LayoutContext { + style: &self.style, + spaces: self.layouter.remaining(), + nested: true, + debug: false, + .. self.ctx + }).await; + + let commands = layouted.output; + self.errors.extend(offset_spans(layouted.errors, model.span.start)); + + for command in commands { + self.execute_command(command, model.span); + } + }) } + + fn execute_command<'r>( + &'r mut self, + command: Command<'r>, + model_span: Span, + ) -> DynFuture<'r, ()> { Box::pin(async move { + use Command::*; + + match command { + LayoutSyntaxModel(model) => self.layout_syntax_model(model).await, + + Add(layout) => self.layouter.add(layout), + AddMultiple(layouts) => self.layouter.add_multiple(layouts), + SpacingFunc(space, kind, axis) => match axis { + Primary => self.layouter.add_primary_spacing(space, kind), + Secondary => self.layouter.add_secondary_spacing(space, kind), + } + + FinishLine => self.layouter.finish_line(), + FinishSpace => self.layouter.finish_space(true), + BreakParagraph => self.layout_paragraph(), + BreakPage => { + if self.ctx.nested { + self.errors.push(Spanned::new( + Error::new( "page break cannot be issued from nested context"), + model_span, + )); + } else { + self.layouter.finish_space(true) + } + } + + SetTextStyle(style) => { + self.layouter.set_line_spacing(style.line_spacing()); + self.style.text = style; + } + SetPageStyle(style) => { + if self.ctx.nested { + self.errors.push(Spanned::new( + Error::new("page style cannot be changed from nested context"), + model_span, + )); + } else { + self.style.page = style; + + let margins = style.margins(); + self.ctx.base = style.dimensions.unpadded(margins); + self.layouter.set_spaces(smallvec![ + LayoutSpace { + dimensions: style.dimensions, + padding: margins, + expansion: LayoutExpansion::new(true, true), + } + ], true); + } + } + + SetAlignment(alignment) => self.ctx.alignment = alignment, + SetAxes(axes) => { + self.layouter.set_axes(axes); + self.ctx.axes = axes; + } + } + }) } + + fn layout_syntax_model<'r>( + &'r mut self, + model: &'r SyntaxModel + ) -> DynFuture<'r, ()> { Box::pin(async move { + use Node::*; + + for node in &model.nodes { + match &node.v { + Space => self.layout_space(), + Newline => self.layout_paragraph(), + Text(text) => self.layout_text(text).await, + + ToggleItalic => self.style.text.variant.style.toggle(), + ToggleBolder => { + let fac = if self.style.text.bolder { -1 } else { 1 }; + self.style.text.variant.weight.0 += 300 * fac; + self.style.text.bolder = !self.style.text.bolder; + } + ToggleMonospace => { + let list = &mut self.style.text.fallback.list; + match list.get(0).map(|s| s.as_str()) { + Some("monospace") => { list.remove(0); }, + _ => list.insert(0, "monospace".to_string()), + } + } + + Node::Model(model) => { + self.layout(Spanned::new(model.as_ref(), node.span)).await; + } + } + } + }) } + + async fn layout_text(&mut self, text: &str) { + self.layouter.add(layout_text(text, TextContext { + loader: &self.ctx.loader, + style: &self.style.text, + axes: self.ctx.axes, + alignment: self.ctx.alignment, + }).await) + } + + fn layout_space(&mut self) { + self.layouter.add_primary_spacing( + self.style.text.word_spacing(), + SpacingKind::WORD, + ); + } + + fn layout_paragraph(&mut self) { + self.layouter.add_secondary_spacing( + self.style.text.paragraph_spacing(), + SpacingKind::PARAGRAPH, + ); + } + + fn finish(self) -> Layouted { + Layouted { + output: self.layouter.finish(), + errors: self.errors, + } + } +} diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 80d57424f..96b44d047 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -68,10 +68,12 @@ impl StackLayouter { } /// Add a layout to the stack. - pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - // If the alignment cannot be fit in this space, finish it. - if !self.update_rulers(layout.alignment) { - self.finish_space(true)?; + pub fn add(&mut self, layout: Layout) { + // If the alignment cannot be fitted in this space, finish it. + // TODO: Issue warning for non-fitting alignment in + // non-repeating context. + if !self.update_rulers(layout.alignment) && self.ctx.repeat { + self.finish_space(true); } // Now, we add a possibly cached soft space. If the secondary alignment @@ -81,14 +83,9 @@ impl StackLayouter { self.add_spacing(spacing, SpacingKind::Hard); } - // Find the first space that fits the layout. - while !self.space.usable.fits(layout.dimensions) { - if self.space_is_last() && self.space_is_empty() { - error!("cannot fit box of size {} into usable size of {}", - layout.dimensions, self.space.usable); - } - - self.finish_space(true)?; + // TODO: Issue warning about overflow if there is overflow. + if !self.space.usable.fits(layout.dimensions) && self.ctx.repeat { + self.skip_to_fitting_space(layout.dimensions); } // Change the usable space and size of the space. @@ -98,18 +95,15 @@ impl StackLayouter { // again. self.space.layouts.push((self.ctx.axes, layout)); self.space.last_spacing = LastSpacing::None; - - Ok(()) } /// Add multiple layouts to the stack. /// /// This function simply calls `add` repeatedly for each layout. - pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { + pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { - self.add(layout)?; + self.add(layout); } - Ok(()) } /// Add secondary spacing to the stack. @@ -215,6 +209,19 @@ impl StackLayouter { } } + /// Move to the first space that can fit the given dimensions or do nothing + /// if no space is capable of that. + pub fn skip_to_fitting_space(&mut self, dimensions: Size2D) { + let start = self.next_space(); + for (index, space) in self.ctx.spaces[start..].iter().enumerate() { + if space.usable().fits(dimensions) { + self.finish_space(true); + self.start_space(start + index, true); + return; + } + } + } + /// The remaining unpadded, unexpanding spaces. If a multi-layout is laid /// out into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { @@ -251,19 +258,15 @@ impl StackLayouter { } /// Compute the finished multi-layout. - pub fn finish(mut self) -> LayoutResult { + pub fn finish(mut self) -> MultiLayout { if self.space.hard || !self.space_is_empty() { - self.finish_space(false)?; + self.finish_space(false); } - Ok(self.layouts) + self.layouts } /// Finish the current space and start a new one. - pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { - if !self.ctx.repeat && hard { - error!("cannot create new space in a non-repeating context"); - } - + pub fn finish_space(&mut self, hard: bool) { let space = self.ctx.spaces[self.space.index]; // ------------------------------------------------------------------ // @@ -376,7 +379,7 @@ impl StackLayouter { // ------------------------------------------------------------------ // // Step 5: Start the next space. - Ok(self.start_space(self.next_space(), hard)) + self.start_space(self.next_space(), hard) } /// Start a new space with the given index. diff --git a/src/layout/text.rs b/src/layout/text.rs index a66e04a23..16ae93da1 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -6,6 +6,14 @@ use crate::style::TextStyle; use super::*; +/// Layouts text into a box. +/// +/// There is no complex layout involved. The text is simply laid out left- +/// to-right using the correct font for each character. +pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout { + TextLayouter::new(text, ctx).layout().await +} + /// The context for text layouting. /// /// See [`LayoutContext`] for details about the fields. @@ -17,14 +25,6 @@ pub struct TextContext<'a, 'p> { pub alignment: LayoutAlignment, } -/// Layouts text into a box. -/// -/// There is no complex layout involved. The text is simply laid out left- -/// to-right using the correct font for each character. -pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> LayoutResult { - TextLayouter::new(text, ctx).layout().await -} - /// Layouts text into boxes. struct TextLayouter<'a, 'p> { ctx: TextContext<'a, 'p>, @@ -49,14 +49,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } /// Layout the text - async fn layout(mut self) -> LayoutResult { + async fn layout(mut self) -> Layout { if self.ctx.axes.primary.is_positive() { for c in self.text.chars() { - self.layout_char(c).await?; + self.layout_char(c).await; } } else { for c in self.text.chars().rev() { - self.layout_char(c).await?; + self.layout_char(c).await; } } @@ -64,16 +64,20 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.actions.add(LayoutAction::WriteText(self.buffer)); } - Ok(Layout { + Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size()), alignment: self.ctx.alignment, actions: self.actions.to_vec(), - }) + } } /// Layout an individual character. - async fn layout_char(&mut self, c: char) -> LayoutResult<()> { - let (index, char_width) = self.select_font(c).await?; + async fn layout_char(&mut self, c: char) { + let (index, char_width) = match self.select_font(c).await { + Some(selected) => selected, + // TODO: Issue warning about missing character. + None => return, + }; self.width += char_width; @@ -88,13 +92,11 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } self.buffer.push(c); - - Ok(()) } /// Select the best font for a character and return its index along with /// the width of the char in the font. - async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> { + async fn select_font(&mut self, c: char) -> Option<(FontIndex, Size)> { let mut loader = self.ctx.loader.borrow_mut(); let query = FontQuery { @@ -104,26 +106,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> { }; if let Some((font, index)) = loader.get(query).await { - let font_unit_ratio = 1.0 / (font.read_table::
()?.units_per_em as f32); + let header = font.read_table::
().ok()?; + let font_unit_ratio = 1.0 / (header.units_per_em as f32); let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); let glyph = font - .read_table::()? - .get(c) - .expect("select_font: font should have char"); + .read_table::() + .ok()? + .get(c)?; let glyph_width = font - .read_table::()? - .get(glyph) - .expect("select_font: font should have glyph") + .read_table::() + .ok()? + .get(glyph)? .advance_width as f32; let char_width = font_unit_to_size(glyph_width) * self.ctx.style.font_size().to_pt(); - return Ok((index, char_width)); + Some((index, char_width)) + } else { + None } - - error!("no suitable font for character `{}`", c); } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs deleted file mode 100644 index 86b00f223..000000000 --- a/src/layout/tree.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::pin::Pin; -use std::future::Future; -use smallvec::smallvec; - -use crate::func::Command; -use crate::syntax::{SyntaxTree, Node, FuncCall}; -use super::*; - - -type RecursiveResult<'a, T> = Pin> + 'a>>; - -/// Layout a syntax tree into a multibox. -pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_, '_>) -> LayoutResult { - let mut layouter = TreeLayouter::new(ctx); - layouter.layout(tree).await?; - layouter.finish() -} - -#[derive(Debug, Clone)] -struct TreeLayouter<'a, 'p> { - ctx: LayoutContext<'a, 'p>, - layouter: LineLayouter, - style: LayoutStyle, -} - -impl<'a, 'p> TreeLayouter<'a, 'p> { - /// Create a new syntax tree layouter. - fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { - TreeLayouter { - layouter: LineLayouter::new(LineContext { - spaces: ctx.spaces.clone(), - axes: ctx.axes, - alignment: ctx.alignment, - repeat: ctx.repeat, - debug: ctx.debug, - line_spacing: ctx.style.text.line_spacing(), - }), - style: ctx.style.clone(), - ctx, - } - } - - fn layout<'b>(&'b mut self, tree: &'b SyntaxTree) -> RecursiveResult<'b, ()> { - Box::pin(async move { - for node in &tree.nodes { - match &node.v { - Node::Text(text) => self.layout_text(text).await?, - - Node::Space => self.layout_space(), - Node::Newline => self.layout_paragraph()?, - - Node::ToggleItalic => self.style.text.variant.style.toggle(), - Node::ToggleBolder => { - self.style.text.variant.weight.0 += 300 * - if self.style.text.bolder { -1 } else { 1 }; - self.style.text.bolder = !self.style.text.bolder; - } - Node::ToggleMonospace => { - let list = &mut self.style.text.fallback.list; - match list.get(0).map(|s| s.as_str()) { - Some("monospace") => { list.remove(0); }, - _ => list.insert(0, "monospace".to_string()), - } - } - - Node::Func(func) => self.layout_func(func).await?, - } - } - - Ok(()) - }) - } - - async fn layout_text(&mut self, text: &str) -> LayoutResult<()> { - let layout = layout_text(text, TextContext { - loader: &self.ctx.loader, - style: &self.style.text, - axes: self.ctx.axes, - alignment: self.ctx.alignment, - }).await?; - - self.layouter.add(layout) - } - - fn layout_space(&mut self) { - self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND); - } - - fn layout_paragraph(&mut self) -> LayoutResult<()> { - self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND) - } - - fn layout_func<'b>(&'b mut self, func: &'b FuncCall) -> RecursiveResult<'b, ()> { - Box::pin(async move { - let commands = func.0.layout(LayoutContext { - style: &self.style, - spaces: self.layouter.remaining(), - nested: true, - debug: false, - .. self.ctx - }).await?; - - for command in commands { - use Command::*; - - match command { - LayoutTree(tree) => self.layout(tree).await?, - - Add(layout) => self.layouter.add(layout)?, - AddMultiple(layouts) => self.layouter.add_multiple(layouts)?, - SpacingFunc(space, kind, axis) => match axis { - Primary => self.layouter.add_primary_spacing(space, kind), - Secondary => self.layouter.add_secondary_spacing(space, kind)?, - } - - FinishLine => self.layouter.finish_line()?, - FinishSpace => self.layouter.finish_space(true)?, - BreakParagraph => self.layout_paragraph()?, - BreakPage => { - if self.ctx.nested { - error!("page break cannot be issued from nested context"); - } - - self.layouter.finish_space(true)? - } - - SetTextStyle(style) => { - self.layouter.set_line_spacing(style.line_spacing()); - self.style.text = style; - } - SetPageStyle(style) => { - if self.ctx.nested { - error!("page style cannot be altered in nested context"); - } - - self.style.page = style; - - let margins = style.margins(); - self.ctx.base = style.dimensions.unpadded(margins); - self.layouter.set_spaces(smallvec![ - LayoutSpace { - dimensions: style.dimensions, - padding: margins, - expansion: LayoutExpansion::new(true, true), - } - ], true); - } - SetAlignment(alignment) => self.ctx.alignment = alignment, - SetAxes(axes) => { - self.layouter.set_axes(axes)?; - self.ctx.axes = axes; - } - } - } - - Ok(()) - }) - } - - fn finish(self) -> LayoutResult { - self.layouter.finish() - } -} diff --git a/src/lib.rs b/src/lib.rs index 17188145e..2ca4d55a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,13 +27,14 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; use toddle::Error as FontError; use crate::func::Scope; -use crate::layout::{MultiLayout, LayoutResult}; -use crate::syntax::{parse, SyntaxTree, Colorization, ErrorMap, ParseContext, Span}; +use crate::layout::{Layouted, LayoutContext, MultiLayout}; +use crate::syntax::{parse, ParseContext, Parsed, SyntaxModel, Position}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; #[macro_use] mod macros; pub mod export; +pub mod error; #[macro_use] pub mod func; pub mod layout; @@ -51,6 +52,8 @@ pub struct Typesetter<'p> { loader: SharedFontLoader<'p>, /// The base layouting style. style: LayoutStyle, + /// The standard library scope. + scope: Scope, } impl<'p> Typesetter<'p> { @@ -59,6 +62,7 @@ impl<'p> Typesetter<'p> { Typesetter { loader: RefCell::new(FontLoader::new()), style: LayoutStyle::default(), + scope: Scope::with_std(), } } @@ -84,17 +88,16 @@ impl<'p> Typesetter<'p> { } /// Parse source code into a syntax tree. - pub fn parse(&self, src: &str) -> (SyntaxTree, Colorization, ErrorMap) { - let scope = Scope::with_std(); - parse(src, ParseContext { scope: &scope }) + pub fn parse(&self, src: &str) -> Parsed { + parse(Position::ZERO, src, ParseContext { scope: &self.scope }) } /// Layout a syntax tree and return the produced layout. - pub async fn layout(&self, tree: &SyntaxTree) -> LayoutResult { + pub async fn layout(&self, model: &SyntaxModel) -> Layouted { use crate::layout::prelude::*; let margins = self.style.page.margins(); - Ok(layout( - &tree, + layout( + &model, LayoutContext { loader: &self.loader, style: &self.style, @@ -110,42 +113,12 @@ impl<'p> Typesetter<'p> { nested: false, debug: false, }, - ).await?) + ).await } - /// Process source code directly into a layout. - pub async fn typeset(&self, src: &str) -> TypesetResult { - let tree = self.parse(src).0; - let layout = self.layout(&tree).await?; - Ok(layout) + /// 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 } } - -/// The result type for typesetting. -pub type TypesetResult = Result; - -/// The error type for typesetting. -pub struct TypesetError { - pub message: String, - pub span: Option, -} - -impl TypesetError { - /// Create a new typesetting error. - pub fn with_message(message: impl Into) -> TypesetError { - TypesetError { message: message.into(), span: None } - } -} - -error_type! { - self: TypesetError, - show: f => { - write!(f, "{}", self.message)?; - if let Some(span) = self.span { - write!(f, " at {}", span)?; - } - Ok(()) - }, - from: (err: std::io::Error, TypesetError::with_message(err.to_string())), - from: (err: FontError, TypesetError::with_message(err.to_string())), -} diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index c4feea749..74deda465 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,3 +1,4 @@ +use crate::size::ScaleSize; use super::*; @@ -126,6 +127,7 @@ impl Display for Object { } } +/// A key-value pair in an object. #[derive(Clone, PartialEq)] pub struct Pair { pub key: Spanned, @@ -144,7 +146,6 @@ debug_display!(Tuple); debug_display!(Object); debug_display!(Pair); - /// Kinds of expressions. pub trait ExpressionKind: Sized { /// The name of the expression in an `expected ` error. diff --git a/src/syntax/func.rs b/src/syntax/func.rs new file mode 100644 index 000000000..5b1ce6e88 --- /dev/null +++ b/src/syntax/func.rs @@ -0,0 +1,125 @@ +use super::*; + + +#[derive(Debug, Clone, PartialEq)] +pub struct FuncHeader { + pub name: Spanned, + pub args: FuncArgs, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArgs { + pub pos: Tuple, + pub key: Object, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Arg { + Pos(Spanned), + Key(Pair), +} + +impl Arg { + /// The span or the value or combined span of key and value. + pub fn span(&self) -> Span { + match self { + Arg::Pos(item) => item.span, + Arg::Key(Pair { key, value }) => Span::merge(key.span, value.span), + } + } +} + +impl FuncArgs { + pub fn new() -> FuncArgs { + FuncArgs { + pos: Tuple::new(), + key: Object::new(), + } + } + + /// Add an argument. + pub fn add(&mut self, arg: Arg) { + match arg { + Arg::Pos(item) => self.add_pos(item), + Arg::Key(pair) => self.add_key_pair(pair), + } + } + + /// Add a positional argument. + pub fn add_pos(&mut self, item: Spanned) { + self.pos.add(item); + } + + /// Add a keyword argument. + pub fn add_key(&mut self, key: Spanned, value: Spanned) { + self.key.add(key, value); + } + + /// Add a keyword argument from an existing pair. + pub fn add_key_pair(&mut self, pair: Pair) { + self.key.add_pair(pair); + } + + // /// Force-extract the first positional argument. + // pub fn get_pos(&mut self) -> ParseResult { + // expect(self.get_pos_opt()) + // } + + // /// Extract the first positional argument. + // pub fn get_pos_opt(&mut self) -> ParseResult> { + // Ok(if !self.positional.items.is_empty() { + // let spanned = self.positional.items.remove(0); + // Some(E::from_expr(spanned)?) + // } else { + // None + // }) + // } + + // /// Force-extract a keyword argument. + // pub fn get_key(&mut self, name: &str) -> ParseResult { + // expect(self.get_key_opt(name)) + // } + + // /// Extract a keyword argument. + // pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { + // self.keyword.pairs.iter() + // .position(|p| p.key.v.0 == name) + // .map(|index| { + // let value = self.keyword.pairs.swap_remove(index).value; + // E::from_expr(value) + // }) + // .transpose() + // } + + // /// Iterator over positional arguments. + // pub fn iter_pos(&mut self) -> std::vec::IntoIter> { + // let tuple = std::mem::replace(&mut self.positional, Tuple::new()); + // tuple.items.into_iter() + // } + + // /// Iterator over all keyword arguments. + // pub fn iter_keys(&mut self) -> std::vec::IntoIter { + // let object = std::mem::replace(&mut self.keyword, Object::new()); + // object.pairs.into_iter() + // } + + // /// Clear the argument lists. + // pub fn clear(&mut self) { + // self.positional.items.clear(); + // self.keyword.pairs.clear(); + // } + + // /// Whether both the positional and keyword argument lists are empty. + // pub fn is_empty(&self) -> bool { + // self.positional.items.is_empty() && self.keyword.pairs.is_empty() + // } +} + +// /// Extract the option expression kind from the option or return an error. +// fn expect(opt: ParseResult>) -> ParseResult { +// match opt { +// Ok(Some(spanned)) => Ok(spanned), +// Ok(None) => error!("expected {}", E::NAME), +// Err(e) => Err(e), +// } +// } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index f644f0517..75407f829 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,99 +1,125 @@ //! Tokenization and parsing of source code. -use std::fmt::{self, Display, Formatter}; -use unicode_xid::UnicodeXID; +use std::any::Any; +use std::fmt::{self, Debug, Display, Formatter}; +use std::future::Future; +use std::pin::Pin; use serde::Serialize; -use crate::func::LayoutFunc; -use crate::size::{Size, ScaleSize}; - - -pub type ParseResult = crate::TypesetResult; +use crate::error::Error; +use crate::func::{Commands, Command}; +use crate::layout::{Layouted, LayoutContext}; +use crate::size::Size; pub_use_mod!(expr); +pub_use_mod!(func); pub_use_mod!(tokens); pub_use_mod!(parsing); pub_use_mod!(span); +/// Common syntax types. +pub mod prelude { + pub use super::*; +} -/// A minimal semantic entity of source code. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Token<'s> { - /// One or more whitespace characters. The contained `usize` denotes the - /// number of newlines that were contained in the whitespace. - Whitespace(usize), - /// A line comment with inner string contents `//<&'s str>\n`. - LineComment(&'s str), - /// A block comment with inner string contents `/*<&'s str>*/`. The comment - /// can contain nested block comments. - BlockComment(&'s str), - /// An erroneous `*/` without an opening block comment. - StarSlash, +pub struct Parsed { + pub output: T, + pub errors: SpanVec, + pub decorations: SpanVec, +} - /// A left bracket: `[`. - LeftBracket, - /// A right bracket: `]`. - RightBracket, +impl Parsed { + pub fn map(self, f: F) -> Parsed where F: FnOnce(T) -> U { + Parsed { + output: f(self.output), + errors: self.errors, + decorations: self.decorations, + } + } +} - /// A left parenthesis in a function header: `(`. - LeftParen, - /// A right parenthesis in a function header: `)`. - RightParen, - /// A left brace in a function header: `{`. - LeftBrace, - /// A right brace in a function header: `}`. - RightBrace, +#[async_trait::async_trait(?Send)] +pub trait Model: Debug + ModelBounds { + async fn layout<'a>( + &'a self, + ctx: LayoutContext<'_, '_> + ) -> Layouted>; +} - /// A colon in a function header: `:`. - Colon, - /// A comma in a function header: `:`. - Comma, - /// An equals sign in a function header: `=`. - Equals, +pub type DynFuture<'a, T> = Pin + 'a>>; - /// An identifier in a function header: `center`. - ExprIdent(&'s str), - /// A quoted string in a function header: `"..."`. - ExprStr(&'s str), - /// A number in a function header: `3.14`. - ExprNumber(f64), - /// A size in a function header: `12pt`. - ExprSize(Size), - /// A boolean in a function header: `true | false`. - ExprBool(bool), +impl dyn Model { + pub fn downcast(&self) -> Option<&T> where T: Model + 'static { + self.as_any().downcast_ref::() + } +} - /// A star in body-text. - Star, - /// An underscore in body-text. - Underscore, - /// A backtick in body-text. - Backtick, +impl PartialEq for dyn Model { + fn eq(&self, other: &dyn Model) -> bool { + self.bound_eq(other) + } +} - /// Any other consecutive string. - Text(&'s str), +impl Clone for Box { + fn clone(&self) -> Self { + self.bound_clone() + } +} + +pub trait ModelBounds { + fn as_any(&self) -> &dyn Any; + fn bound_eq(&self, other: &dyn Model) -> bool; + fn bound_clone(&self) -> Box; +} + +impl ModelBounds for T where T: Model + PartialEq + Clone + 'static { + fn as_any(&self) -> &dyn Any { + self + } + + fn bound_eq(&self, other: &dyn Model) -> bool { + match other.as_any().downcast_ref::() { + Some(other) => self == other, + None => false, + } + } + + fn bound_clone(&self) -> Box { + Box::new(self.clone()) + } } /// A tree representation of source code. -#[derive(Debug, PartialEq)] -pub struct SyntaxTree { - pub nodes: Vec>, +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxModel { + pub nodes: SpanVec, } -impl SyntaxTree { - /// Create an empty syntax tree. - pub fn new() -> SyntaxTree { - SyntaxTree { nodes: vec![] } +impl SyntaxModel { + /// Create an empty syntax model. + pub fn new() -> SyntaxModel { + SyntaxModel { nodes: vec![] } } - /// Add a node to the tree. + /// Add a node to the model. pub fn add(&mut self, node: Spanned) { self.nodes.push(node); } } +#[async_trait::async_trait(?Send)] +impl Model for SyntaxModel { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted> { + Layouted { + output: vec![Command::LayoutSyntaxModel(self)], + errors: vec![], + } + } +} + /// A node in the syntax tree. -#[derive(PartialEq)] +#[derive(Debug, Clone)] pub enum Node { /// A number of whitespace characters containing less than two newlines. Space, @@ -107,215 +133,29 @@ pub enum Node { ToggleBolder, /// Monospace enabled / disabled. ToggleMonospace, - /// A function invocation. - Func(FuncCall), + /// A submodel. + Model(Box), } -impl Display for Node { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Node::Space => write!(f, "Space"), - Node::Newline => write!(f, "Newline"), - Node::Text(text) => write!(f, "{:?}", text), - Node::ToggleItalic => write!(f, "ToggleItalic"), - Node::ToggleBolder => write!(f, "ToggleBold"), - Node::ToggleMonospace => write!(f, "ToggleMonospace"), - Node::Func(func) => { - if f.alternate() { - write!(f, "{:#?}", func.0) - } else { - write!(f, "{:?}", func.0) - } - } +impl PartialEq for Node { + fn eq(&self, other: &Node) -> bool { + use Node::*; + match (self, other) { + (Space, Space) => true, + (Newline, Newline) => true, + (Text(a), Text(b)) => a == b, + (ToggleItalic, ToggleItalic) => true, + (ToggleBolder, ToggleBolder) => true, + (ToggleMonospace, ToggleMonospace) => true, + (Model(a), Model(b)) => a == b, + _ => false, } } } -debug_display!(Node); - -/// An invocation of a function. -#[derive(Debug)] -pub struct FuncCall(pub Box); - -impl PartialEq for FuncCall { - fn eq(&self, other: &FuncCall) -> bool { - &self.0 == &other.0 - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct FuncHeader { - pub name: Spanned, - pub args: FuncArgs, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct FuncArgs { - pub positional: Tuple, - pub keyword: Object, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Arg { - Pos(Spanned), - Key(Pair), -} - -impl Arg { - /// The span or the value or combined span of key and value. - pub fn span(&self) -> Span { - match self { - Arg::Pos(spanned) => spanned.span, - Arg::Key(Pair { key, value }) => Span::merge(key.span, value.span), - } - } -} - -impl FuncArgs { - pub fn new() -> FuncArgs { - FuncArgs { - positional: Tuple::new(), - keyword: Object::new(), - } - } - - /// Add a positional argument. - pub fn add_pos(&mut self, item: Spanned) { - self.positional.add(item); - } - - /// Force-extract the first positional argument. - pub fn get_pos(&mut self) -> ParseResult { - expect(self.get_pos_opt()) - } - - /// Extract the first positional argument. - pub fn get_pos_opt(&mut self) -> ParseResult> { - Ok(if !self.positional.items.is_empty() { - let spanned = self.positional.items.remove(0); - Some(E::from_expr(spanned)?) - } else { - None - }) - } - - /// Add a keyword argument. - pub fn add_key(&mut self, key: Spanned, value: Spanned) { - self.keyword.add(key, value); - } - - /// Add a keyword argument from an existing pair. - pub fn add_key_pair(&mut self, pair: Pair) { - self.keyword.add_pair(pair); - } - - /// Force-extract a keyword argument. - pub fn get_key(&mut self, name: &str) -> ParseResult { - expect(self.get_key_opt(name)) - } - - /// Extract a keyword argument. - pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { - self.keyword.pairs.iter() - .position(|p| p.key.v.0 == name) - .map(|index| { - let value = self.keyword.pairs.swap_remove(index).value; - E::from_expr(value) - }) - .transpose() - } - - /// Iterator over positional arguments. - pub fn iter_pos(&mut self) -> std::vec::IntoIter> { - let tuple = std::mem::replace(&mut self.positional, Tuple::new()); - tuple.items.into_iter() - } - - /// Iterator over all keyword arguments. - pub fn iter_keys(&mut self) -> std::vec::IntoIter { - let object = std::mem::replace(&mut self.keyword, Object::new()); - object.pairs.into_iter() - } - - /// Clear the argument lists. - pub fn clear(&mut self) { - self.positional.items.clear(); - self.keyword.pairs.clear(); - } - - /// Whether both the positional and keyword argument lists are empty. - pub fn is_empty(&self) -> bool { - self.positional.items.is_empty() && self.keyword.pairs.is_empty() - } -} - -/// Extract the option expression kind from the option or return an error. -fn expect(opt: ParseResult>) -> ParseResult { - match opt { - Ok(Some(spanned)) => Ok(spanned), - Ok(None) => error!("expected {}", E::NAME), - Err(e) => Err(e), - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize)] -pub struct Colorization { - pub tokens: Vec>, -} - -impl Colorization { - pub fn new() -> Colorization { - Colorization { tokens: vec![] } - } - - pub fn add(&mut self, token: ColorToken, span: Span) { - self.tokens.push(Spanned { v: token, span }); - } - - pub fn replace_last(&mut self, token: ColorToken) { - self.tokens.last_mut().expect("replace_last: no token").v = token; - } -} - -/// Entities which can be colored by syntax highlighting. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum ColorToken { - Comment, - Bracket, - FuncName, - Colon, - Key, - Equals, - Comma, - Paren, - Brace, - ExprIdent, - ExprStr, - ExprNumber, - ExprSize, - ExprBool, - Bold, - Italic, - Monospace, - Invalid, -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize)] -pub struct ErrorMap { - pub errors: Vec>, -} - -impl ErrorMap { - pub fn new() -> ErrorMap { - ErrorMap { errors: vec![] } - } - - pub fn add(&mut self, message: impl Into, span: Span) { - self.errors.push(Spanned { v: message.into(), span }); - } - - pub fn add_at(&mut self, message: impl Into, pos: Position) { - self.errors.push(Spanned { v: message.into(), span: Span::at(pos) }) - } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Decoration { + ValidFuncName, + InvalidFuncName, + ArgumentKey, } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index f6d0b629a..24bef7ce5 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -3,9 +3,8 @@ use super::*; use Token::*; -/// Parses source code into a syntax tree given a context. -pub fn parse(src: &str, ctx: ParseContext) -> (SyntaxTree, Colorization, ErrorMap) { - Parser::new(src, ctx).parse() +pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { + Parser::new(start, src, ctx).parse() } /// The context for parsing. @@ -18,64 +17,68 @@ pub struct ParseContext<'a> { struct Parser<'s> { src: &'s str, ctx: ParseContext<'s>, - colorization: Colorization, - error_map: ErrorMap, tokens: Tokens<'s>, peeked: Option>>>, position: Position, last_position: Position, + errors: SpanVec, + decorations: SpanVec, } impl<'s> Parser<'s> { - fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> { + fn new(start: Position, src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> { Parser { src, ctx, - error_map: ErrorMap::new(), - colorization: Colorization::new(), - tokens: Tokens::new(src), + tokens: tokenize(start, src), peeked: None, position: Position::ZERO, last_position: Position::ZERO, + errors: vec![], + decorations: vec![], } } /// The main parsing entrypoint. - fn parse(mut self) -> (SyntaxTree, Colorization, ErrorMap) { - let mut tree = SyntaxTree::new(); + fn parse(mut self) -> Parsed { + let mut model = SyntaxModel::new(); - loop { - if let Some(spanned) = self.eat() { - match spanned.v { - LineComment(_) | BlockComment(_) => {} + while let Some(token) = self.eat() { + let mut span = token.span; + let node = match token.v { + LineComment(_) | BlockComment(_) => None, + Whitespace(newlines) => Some(if newlines >= 2 { + Node::Newline + } else { + Node::Space + }), - Whitespace(newlines) => { - tree.add(spanned.map_v(if newlines >= 2 { - Node::Newline - } else { - Node::Space - })); - } + LeftBracket => self.parse_func().map(|spanned| { + span = spanned.span; + spanned.v + }), - LeftBracket => { - if let Some(func) = self.parse_func() { - tree.add(func); - } - } + Star => Some(Node::ToggleBolder), + Underscore => Some(Node::ToggleItalic), + Backtick => Some(Node::ToggleMonospace), + Text(text) => Some(Node::Text(text.to_owned())), - Star => tree.add(spanned.map_v(Node::ToggleBolder)), - Underscore => tree.add(spanned.map_v(Node::ToggleItalic)), - Backtick => tree.add(spanned.map_v(Node::ToggleMonospace)), - Text(text) => tree.add(spanned.map_v(Node::Text(text.to_owned()))), - - _ => self.unexpected(spanned), + _ => { + self.unexpected(token); + None } - } else { - break; + }; + + if let Some(v) = node { + model.add(Spanned { v, span }); } } - (tree, self.colorization, self.error_map) + Parsed { + output: model, + errors: self.errors, + decorations: self.decorations, + } } /// Parses a function including header and body with the cursor starting @@ -90,12 +93,55 @@ impl<'s> Parser<'s> { self.expected_at("closing bracket", self.pos()); } - let call = self.parse_func_call(header)?; + let body = if self.peekv() == Some(LeftBracket) { + self.eat(); + + let start_index = self.tokens.index(); + let start_position = self.tokens.pos(); + + let found = self.tokens.move_to_closing_bracket(); + + let end_index = self.tokens.index(); + let end_position = self.tokens.pos(); + + let body = &self.src[start_index .. end_index]; + + self.position = end_position; + + if found { + let next = self.eat().map(Spanned::value); + debug_assert_eq!(next, Some(RightBracket)); + } else { + self.expected_at("closing bracket", self.pos()); + } + + Some(Spanned::new(body, Span::new(start_position, end_position))) + } else { + None + }; + + let header = header?; + let (parser, decoration) = match self.ctx.scope.get_parser(header.name.v.as_str()) { + Ok(parser) => (parser, Decoration::ValidFuncName), + Err(parser) => { + let error = Error::new(format!("unknown function: `{}`", header.name.v)); + self.errors.push(Spanned::new(error, header.name.span)); + (parser, Decoration::InvalidFuncName) + } + }; + + self.decorations.push(Spanned::new(decoration, header.name.span)); + + let parsed = parser(header, body, self.ctx); + self.errors.extend(offset_spans(parsed.errors, start)); + self.decorations.extend(offset_spans(parsed.decorations, start)); + + let node = Node::Model(parsed.output); let end = self.pos(); let span = Span { start, end }; - Some(Spanned { v: Node::Func(call), span }) + Some(Spanned { v: node, span }) } /// Parses a function header including the closing bracket. @@ -125,7 +171,6 @@ impl<'s> Parser<'s> { match self.peek() { Some(Spanned { v: ExprIdent(ident), span }) => { self.eat(); - self.colorization.replace_last(ColorToken::FuncName); return Some(Spanned { v: Ident(ident.to_string()), span }); } other => self.expected_found_or_at("identifier", other, self.pos()), @@ -144,8 +189,7 @@ impl<'s> Parser<'s> { match self.peekv() { Some(RightBracket) | None => break, _ => match self.parse_arg() { - Some(Arg::Pos(item)) => args.add_pos(item), - Some(Arg::Key(pair)) => args.add_key_pair(pair), + Some(arg) => args.add(arg), None => {} } } @@ -165,11 +209,11 @@ impl<'s> Parser<'s> { let ident = Ident(ident.to_string()); if let Some(Equals) = self.peekv() { - self.colorization.replace_last(ColorToken::Key); - self.eat(); self.skip_whitespace(); + self.decorations.push(Spanned::new(Decoration::ArgumentKey, span)); + self.parse_expr().map(|value| { Arg::Key(Pair { key: Spanned { v: ident, span }, @@ -251,42 +295,6 @@ impl<'s> Parser<'s> { Spanned { v: Expression::Object(Object::new()), span } } - /// Parse the body of a function invocation. - fn parse_func_call(&mut self, header: Option) -> Option { - let body = if self.peekv() == Some(LeftBracket) { - self.eat(); - - let start = self.tokens.index(); - let found = self.tokens.move_to_closing_bracket(); - let end = self.tokens.index(); - - self.last_position = self.position; - self.position = self.tokens.pos(); - - let body = &self.src[start .. end]; - - if found { - let next = self.eat().map(Spanned::value); - debug_assert_eq!(next, Some(RightBracket)); - } else { - self.expected_at("closing bracket", self.pos()); - } - - Some(body) - } else { - None - }; - - let header = header?; - let parser = self.ctx.scope.get_parser(header.name.v.as_str()).or_else(|| { - let message = format!("unknown function: `{}`", header.name.v); - self.error_map.add(message, header.name.span); - None - })?; - - parser(header, body, self.ctx).ok().map(|f| FuncCall(f)) - } - /// Skip all whitespace/comment tokens. fn skip_whitespace(&mut self) { self.eat_until(|t| @@ -296,14 +304,16 @@ impl<'s> Parser<'s> { /// 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.error_map.add_at(format!("expected {}", thing), pos); + let error = Error::new(format!("expected {}", thing)); + self.errors.push(Spanned::new(error, Span::at(pos))); } /// 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) { let message = format!("expected {}, found {}", thing, name(found.v)); - self.error_map.add(message, found.span); + let error = Error::new(message); + self.errors.push(Spanned::new(error, found.span)); } /// Add a found-error if `found` is some and a positional error, otherwise. @@ -321,7 +331,8 @@ impl<'s> Parser<'s> { /// Add an error about an unexpected token `found`. fn unexpected(&mut self, found: Spanned) { - self.error_map.add(format!("unexpected {}", name(found.v)), found.span); + let error = Error::new(format!("unexpected {}", name(found.v))); + self.errors.push(Spanned::new(error, found.span)); } /// Consume tokens until the function returns true and only consume the last @@ -348,10 +359,6 @@ impl<'s> Parser<'s> { .unwrap_or_else(|| self.tokens.next()); if let Some(token) = token { - if let Some(color) = color(token.v) { - self.colorization.add(color, token.span); - } - self.last_position = self.position; self.position = token.span.end; } @@ -407,23 +414,3 @@ fn name(token: Token) -> &'static str { Text(_) => "invalid identifier", } } - -/// The color token corresponding to a token. -fn color(token: Token) -> Option { - Some(match token { - LineComment(_) | BlockComment(_) => ColorToken::Comment, - LeftBracket | RightBracket => ColorToken::Bracket, - LeftParen | RightParen => ColorToken::Paren, - LeftBrace | RightBrace => ColorToken::Brace, - Colon => ColorToken::Colon, - Comma => ColorToken::Comma, - Equals => ColorToken::Equals, - ExprIdent(_) => ColorToken::ExprIdent, - ExprStr(_) => ColorToken::ExprStr, - ExprNumber(_) => ColorToken::ExprNumber, - ExprSize(_) => ColorToken::ExprSize, - ExprBool(_) => ColorToken::ExprBool, - StarSlash => ColorToken::Invalid, - _ => return None, - }) -} diff --git a/src/syntax/span.rs b/src/syntax/span.rs index df9a3520e..eb39677e7 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -1,6 +1,7 @@ //! Spans map elements to the part of source code they originate from. use std::fmt::{self, Debug, Display, Formatter}; +use std::ops::{Add, AddAssign}; use serde::Serialize; @@ -100,6 +101,30 @@ impl Position { } } +impl Add for Position { + type Output = Position; + + fn add(self, rhs: Position) -> Position { + if rhs.line == 0 { + Position { + line: self.line, + column: self.column + rhs.column + } + } else { + Position { + line: self.line + rhs.line, + column: rhs.column, + } + } + } +} + +impl AddAssign for Position { + fn add_assign(&mut self, other: Position) { + *self = *self + other; + } +} + impl Display for Position { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}:{}", self.line, self.column) @@ -107,3 +132,18 @@ impl Display for Position { } debug_display!(Position); + +/// A vector of spanned things. +pub type SpanVec = Vec>; + +pub fn offset_spans( + vec: SpanVec, + start: Position, +) -> impl Iterator> { + vec.into_iter() + .map(move |mut spanned| { + spanned.span.start += start; + spanned.span.end += start; + spanned + }) +} diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 69d799654..0588bc6c9 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -1,21 +1,80 @@ use std::iter::Peekable; use std::str::Chars; +use unicode_xid::UnicodeXID; use super::*; use Token::*; use State::*; +/// A minimal semantic entity of source code. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Token<'s> { + /// One or more whitespace characters. The contained `usize` denotes the + /// number of newlines that were contained in the whitespace. + Whitespace(usize), + + /// A line comment with inner string contents `//<&'s str>\n`. + LineComment(&'s str), + /// A block comment with inner string contents `/*<&'s str>*/`. The comment + /// can contain nested block comments. + BlockComment(&'s str), + /// An erroneous `*/` without an opening block comment. + StarSlash, + + /// A left bracket: `[`. + LeftBracket, + /// A right bracket: `]`. + RightBracket, + + /// A left parenthesis in a function header: `(`. + LeftParen, + /// A right parenthesis in a function header: `)`. + RightParen, + /// A left brace in a function header: `{`. + LeftBrace, + /// A right brace in a function header: `}`. + RightBrace, + + /// A colon in a function header: `:`. + Colon, + /// A comma in a function header: `:`. + Comma, + /// An equals sign in a function header: `=`. + Equals, + + /// An identifier in a function header: `center`. + ExprIdent(&'s str), + /// A quoted string in a function header: `"..."`. + ExprStr(&'s str), + /// A number in a function header: `3.14`. + ExprNumber(f64), + /// A size in a function header: `12pt`. + ExprSize(Size), + /// A boolean in a function header: `true | false`. + ExprBool(bool), + + /// A star in body-text. + Star, + /// An underscore in body-text. + Underscore, + /// A backtick in body-text. + Backtick, + + /// Any other consecutive string. + Text(&'s str), +} + /// Decomposes text into a sequence of semantic tokens. -pub fn tokenize(src: &str) -> Tokens { - Tokens::new(src) +pub fn tokenize(start: Position, src: &str) -> Tokens { + Tokens::new(start, src) } /// An iterator over the tokens of a string of source code. pub struct Tokens<'s> { src: &'s str, state: State, - stack: Vec, + stack: Vec<(State, Position)>, iter: Peekable>, position: Position, index: usize, @@ -29,13 +88,13 @@ enum State { } impl<'s> Tokens<'s> { - pub fn new(src: &'s str) -> Tokens<'s> { + pub fn new(start: Position, src: &'s str) -> Tokens<'s> { Tokens { src, state: State::Body, stack: vec![], iter: src.chars().peekable(), - position: Position::ZERO, + position: start, index: 0, } } @@ -47,7 +106,7 @@ impl<'s> Tokens<'s> { } /// The line-colunn position in the source at which the last token ends and - /// next token will start. + /// next token will start. This position is pub fn pos(&self) -> Position { self.position } @@ -101,11 +160,13 @@ impl<'s> Iterator for Tokens<'s> { // Functions. '[' => { - if self.state == Header || self.state == Body { - self.stack.push(self.state); - self.state = Header; - } else { - self.state = Body; + match self.state { + Header | Body => { + self.stack.push((self.state, start)); + self.position = Position::new(0, '['.len_utf8()); + self.state = Header; + } + StartBody => self.state = Body, } LeftBracket @@ -114,7 +175,12 @@ impl<'s> Iterator for Tokens<'s> { if self.state == Header && self.peek() == Some('[') { self.state = StartBody; } else { - self.state = self.stack.pop().unwrap_or(Body); + if let Some((state, pos)) = self.stack.pop() { + self.state = state; + self.position = pos + self.position; + } else { + self.state = Body; + } } RightBracket diff --git a/tests/src/layouter.rs b/tests/src/layouter.rs index fa0c631a1..eeca1a1bb 100644 --- a/tests/src/layouter.rs +++ b/tests/src/layouter.rs @@ -76,10 +76,7 @@ fn test(name: &str, src: &str) -> DynResult<()> { let font_paths = provider.paths(); typesetter.add_font_provider(provider); - let layouts = match compile(&typesetter, src) { - Some(layouts) => layouts, - None => return Ok(()), - }; + let layouts = compile(&typesetter, src); // Compute the font's paths. let mut fonts = HashMap::new(); @@ -122,40 +119,32 @@ fn test(name: &str, src: &str) -> DynResult<()> { } /// Compile the source code with the typesetter. -fn compile(typesetter: &Typesetter, src: &str) -> Option { +fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout { #[cfg(not(debug_assertions))] { use std::time::Instant; // Warmup. let warmup_start = Instant::now(); - let is_ok = block_on(typesetter.typeset(&src)).is_ok(); + block_on(typesetter.typeset(&src)); let warmup_end = Instant::now(); - // Only continue if the typesetting was successful. - if is_ok { - let start = Instant::now(); - let tree = typesetter.parse(&src).unwrap(); - let mid = Instant::now(); - block_on(typesetter.layout(&tree)).unwrap(); - let end = Instant::now(); + let start = Instant::now(); + let tree = typesetter.parse(&src).output; + let mid = Instant::now(); + let layouts = block_on(typesetter.layout(&tree)).output; + let end = Instant::now(); - println!(" - cold start: {:?}", warmup_end - warmup_start); - println!(" - warmed up: {:?}", end - start); - println!(" - parsing: {:?}", mid - start); - println!(" - layouting: {:?}", end - mid); - println!(); - } - }; + println!(" - cold start: {:?}", warmup_end - warmup_start); + println!(" - warmed up: {:?}", end - start); + println!(" - parsing: {:?}", mid - start); + println!(" - layouting: {:?}", end - mid); + println!(); - match block_on(typesetter.typeset(&src)) { - Ok(layouts) => Some(layouts), - Err(err) => { - println!(" - compilation failed: {}", err); - #[cfg(not(debug_assertions))] - println!(); - None - } + layouts } + + #[cfg(debug_assertions)] + block_on(typesetter.typeset(&src)) } /// Command line options. diff --git a/tests/src/parser.rs b/tests/src/parser.rs index b2aa01da9..550090a8d 100644 --- a/tests/src/parser.rs +++ b/tests/src/parser.rs @@ -107,23 +107,22 @@ macro_rules! case { (ps $($rest:tt)*) => (case!(@parse PartialEq::eq, $($rest)*)); (@parse $cmp:expr, $src:expr, [$($e:tt)*]) => ({ - let expected = SyntaxTree { nodes: list!(nodes [$($e)*]) }; + let expected = SyntaxModel { nodes: list!(nodes [$($e)*]) }; let found = parse($src, ParseContext { scope: &scope() }).0; ($cmp(&found, &expected), expected, found) }); (c $src:expr, [$($e:tt)*]) => ({ - let expected = Colorization { tokens: list!(colors [$($e)*]) }; + let expected = Colorization { tokens: list!(decorations [$($e)*]) }; let found = parse($src, ParseContext { scope: &scope() }).1; (expected == found, expected, found) }); (e $src:expr, [$($e:tt)*]) => ({ - let errors = list!([$($e)*]).into_iter() + let expected = list!([$($e)*]).into_iter() .map(|s| s.map(|m| m.to_string())) .collect(); - let expected = ErrorMap { errors }; let found = parse($src, ParseContext { scope: &scope() }).2; (expected == found, expected, found) }); @@ -131,7 +130,7 @@ macro_rules! case { /// A scope containing the `DebugFn` as a fallback. fn scope() -> Scope { - Scope::with_debug::() + Scope::with_fallback::() } /// Parses possibly-spanned lists of token or node expressions. @@ -182,7 +181,7 @@ macro_rules! func { $(positional = list!(expr [$($p)*]);)? $(keyword = list!(expr [$($k)*]);)? - Node::Func(FuncCall(Box::new(DebugFn { + Node::Model(Box::new(DebugFn { header: FuncHeader { name: zspan(Ident($name.to_string())), args: FuncArgs { @@ -191,10 +190,10 @@ macro_rules! func { }, }, body: func!(@body $($b)*), - }))) + })) }); - (@body Some($($b:tt)*)) => (Some(SyntaxTree { nodes: list!(nodes $($b)*) })); + (@body Some($($b:tt)*)) => (Some(SyntaxModel{ nodes: list!(nodes $($b)*) })); (@body None) => (None); } @@ -270,27 +269,8 @@ mod cuts { } } - pub mod colors { - pub use typstc::syntax::ColorToken::{ - Comment as C, - Bracket as B, - FuncName as FN, - Colon as CL, - Key as K, - Equals as EQ, - Comma as CM, - Paren as P, - Brace as BR, - ExprIdent as ID, - ExprStr as STR, - ExprNumber as NUM, - ExprSize as SIZE, - ExprBool as BOOL, - Bold as BD, - Italic as IT, - Monospace as MS, - Invalid as INV, - }; + pub mod decorations { + pub use typstc::syntax::Decoration::*; } pub mod expr { diff --git a/tests/src/spanless.rs b/tests/src/spanless.rs index fde5a2edb..87d3f39d6 100644 --- a/tests/src/spanless.rs +++ b/tests/src/spanless.rs @@ -13,15 +13,17 @@ impl SpanlessEq>>> for Vec>> { } } -impl SpanlessEq for SyntaxTree { - fn spanless_eq(&self, other: &SyntaxTree) -> bool { - fn downcast(func: &FuncCall) -> &DebugFn { - func.0.downcast::().expect("not a debug fn") +impl SpanlessEq for SyntaxModel { + fn spanless_eq(&self, other: &SyntaxModel) -> bool { + fn downcast(func: &dyn Model) -> &DebugFn { + func.downcast::().expect("not a debug fn") } self.nodes.len() == other.nodes.len() && self.nodes.iter().zip(&other.nodes).all(|(x, y)| match (&x.v, &y.v) { - (Node::Func(a), Node::Func(b)) => downcast(a).spanless_eq(downcast(b)), + (Node::Model(a), Node::Model(b)) => { + downcast(a.as_ref()).spanless_eq(downcast(b.as_ref())) + } (a, b) => a == b, }) }