diff --git a/src/error.rs b/src/error.rs index b35972660..cd1077410 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ use serde::Serialize; -use crate::syntax::SpanVec; +use crate::syntax::span::SpanVec; pub type Errors = SpanVec; @@ -10,14 +10,14 @@ pub struct Error { pub severity: Severity, } -impl Error { - pub fn new(message: impl Into, severity: Severity) -> Error { - Error { message: message.into(), severity } - } -} - #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize)] pub enum Severity { Warning, Error, } + +impl Error { + pub fn new(message: impl Into, severity: Severity) -> Error { + Error { message: message.into(), severity } + } +} diff --git a/src/export/pdf.rs b/src/export/pdf.rs index c4020e101..421ffb199 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -8,7 +8,7 @@ use tide::content::Content; use tide::doc::{Catalog, Page, PageTree, Resource, Text}; use tide::font::{ CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags, Type0Font, - CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord + CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord, }; use toddle::Error as FontError; @@ -16,7 +16,7 @@ use toddle::font::OwnedFont; use toddle::query::{SharedFontLoader, FontIndex}; use toddle::tables::{ CharMap, Header, HorizontalMetrics, MacStyleFlags, - Name, NameEntry, Post, OS2 + Name, NameEntry, Post, OS2, }; use crate::layout::{MultiLayout, Layout, LayoutAction}; diff --git a/src/func/macros.rs b/src/func.rs similarity index 71% rename from src/func/macros.rs rename to src/func.rs index 764f08361..a7cdfe2fb 100644 --- a/src/func/macros.rs +++ b/src/func.rs @@ -1,5 +1,35 @@ //! Helper types and macros for creating custom functions. +use crate::syntax::{ParseContext, Parsed}; +use crate::syntax::func::FuncHeader; +use crate::syntax::span::Spanned; + +pub mod prelude { + pub use crate::layout::prelude::*; + pub use crate::layout::{LayoutContext, Commands, layout}; + pub use crate::layout::Command::{self, *}; + pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; + pub use crate::syntax::SyntaxModel; + pub use crate::syntax::expr::*; + pub use crate::syntax::func::*; + pub use crate::syntax::func::keys::*; + pub use crate::syntax::func::values::*; + pub use crate::syntax::span::{Span, Spanned}; +} + + +/// Parse a function from source code. +pub trait ParseFunc { + type Meta: Clone; + + /// Parse the header and body into this function given a context. + fn parse( + header: FuncHeader, + body: Option>, + ctx: ParseContext, + metadata: Self::Meta, + ) -> Parsed where Self: Sized; +} #[macro_export] macro_rules! function { @@ -35,15 +65,15 @@ macro_rules! function { $decos:ident, $metadata:ident ) $code:block $($r:tt)*) => { - impl $crate::func::Parse for $name { + impl $crate::func::ParseFunc for $name { type Meta = $meta; fn parse( - #[allow(unused)] mut header: FuncHeader, - #[allow(unused)] $body: Option>, - #[allow(unused)] $ctx: ParseContext, + #[allow(unused)] mut header: $crate::syntax::func::FuncHeader, + #[allow(unused)] $body: Option<$crate::syntax::span::Spanned<&str>>, + #[allow(unused)] $ctx: $crate::syntax::ParseContext, #[allow(unused)] $metadata: Self::Meta, - ) -> Parsed where Self: Sized { + ) -> $crate::syntax::Parsed where Self: Sized { let mut errors = vec![]; let mut decorations = vec![]; #[allow(unused)] let $header = &mut header; @@ -67,8 +97,9 @@ macro_rules! function { fn layout<'a, 'b, 'c, 't>( #[allow(unused)] &'a $this, #[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b, 'c>, - ) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>> - where + ) -> $crate::layout::DynFuture<'t, $crate::layout::Layouted< + $crate::layout::Commands<'a>> + > where 'a: 't, 'b: 't, 'c: 't, @@ -96,7 +127,7 @@ macro_rules! 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 + Position::new(0, 1); + 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); @@ -122,7 +153,7 @@ macro_rules! body { #[macro_export] macro_rules! err { (@$severity:ident: $span:expr; $($args:tt)*) => { - $crate::syntax::Spanned { v: err!(@Error: $($args)*), span: $span } + $crate::syntax::span::Spanned { v: err!(@Error: $($args)*), span: $span } }; (@$severity:ident: $($args:tt)*) => { diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 1bf5d6841..710d92b4c 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -1,10 +1,12 @@ //! Drawing and cofiguration actions composing layouts. +use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; use toddle::query::FontIndex; -use super::*; -use LayoutAction::*; +use crate::size::{Size, Size2D}; +use super::{Layout, Serialize}; +use self::LayoutAction::*; /// A layouting action. diff --git a/src/layout/line.rs b/src/layout/line.rs index f7777ae02..5e0839b1a 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,3 +1,4 @@ +use super::stack::{StackLayouter, StackContext}; use super::*; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 209874a13..f8074524a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -3,40 +3,26 @@ use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; use smallvec::SmallVec; -use toddle::query::{SharedFontLoader, FontIndex}; +use toddle::query::FontIndex; -use crate::error::Errors; -use crate::syntax::{SyntaxModel, SpanVec}; use crate::size::{Size, Size2D, SizeBox}; -use crate::style::LayoutStyle; +use self::{GenericAxis::*, SpecificAxis::*, Direction::*, Alignment::*}; -mod actions; -mod model; -mod line; -mod stack; -mod text; +pub mod line; +pub mod stack; +pub mod text; + +pub_use_mod!(actions); +pub_use_mod!(model); -/// Common types for layouting. pub mod prelude { - pub use super::*; - pub use GenericAxis::*; - pub use SpecificAxis::*; - pub use Direction::*; - pub use Alignment::*; + pub use super::{LayoutSpace, LayoutExpansion, LayoutAxes, LayoutAlignment}; + pub use super::GenericAxis::{self, *}; + pub use super::SpecificAxis::{self, *}; + pub use super::Direction::{self, *}; + pub use super::Alignment::{self, *}; } -/// Different kinds of layouters (fully re-exported). -pub mod layouters { - pub use super::model::ModelLayouter; - pub use super::line::{LineLayouter, LineContext}; - pub use super::stack::{StackLayouter, StackContext}; - pub use super::text::{layout_text, TextContext}; -} - -pub use self::actions::{LayoutAction, LayoutActions}; -pub use self::layouters::*; -pub use self::prelude::*; - /// A collection of layouts. pub type MultiLayout = Vec; @@ -67,49 +53,32 @@ impl Layout { } } -/// The general context for layouting. -#[derive(Debug, Clone)] -pub struct LayoutContext<'a, 'p> { - /// The font loader to retrieve fonts from when typesetting text - /// using [`layout_text`]. - pub loader: &'a SharedFontLoader<'p>, - /// The style for pages and text. - pub style: &'a LayoutStyle, - /// The base unpadded dimensions of this container (for relative sizing). - pub base: Size2D, - /// The spaces to layout in. - pub spaces: LayoutSpaces, - /// Whether to have repeated spaces or to use only the first and only once. - pub repeat: bool, - /// The initial axes along which content is laid out. - pub axes: LayoutAxes, - /// The alignment of the finished layout. - pub alignment: LayoutAlignment, - /// Whether the layout that is to be created will be nested in a parent - /// container. - pub nested: bool, - /// Whether to debug render a box around the layout. - pub debug: bool, +/// Layout components that can be serialized. +pub trait Serialize { + /// Serialize the data structure into an output writable. + fn serialize(&self, f: &mut W) -> io::Result<()>; } -pub struct Layouted { - pub output: T, - pub errors: Errors, -} - -impl Layouted { - pub fn map(self, f: F) -> Layouted where F: FnOnce(T) -> U { - Layouted { - output: f(self.output), - errors: self.errors, +impl Serialize for Layout { + fn serialize(&self, f: &mut W) -> io::Result<()> { + writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?; + writeln!(f, "{}", self.actions.len())?; + for action in &self.actions { + action.serialize(f)?; + writeln!(f)?; } + Ok(()) } } -pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted { - let mut layouter = ModelLayouter::new(ctx); - layouter.layout_syntax_model(model).await; - layouter.finish() +impl Serialize for MultiLayout { + fn serialize(&self, f: &mut W) -> io::Result<()> { + writeln!(f, "{}", self.len())?; + for layout in self { + layout.serialize(f)?; + } + Ok(()) + } } /// A possibly stack-allocated vector of layout spaces. @@ -405,31 +374,3 @@ impl LastSpacing { } } } - -/// Layout components that can be serialized. -pub trait Serialize { - /// Serialize the data structure into an output writable. - fn serialize(&self, f: &mut W) -> io::Result<()>; -} - -impl Serialize for Layout { - fn serialize(&self, f: &mut W) -> io::Result<()> { - writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?; - writeln!(f, "{}", self.actions.len())?; - for action in &self.actions { - action.serialize(f)?; - writeln!(f)?; - } - Ok(()) - } -} - -impl Serialize for MultiLayout { - fn serialize(&self, f: &mut W) -> io::Result<()> { - writeln!(f, "{}", self.len())?; - for layout in self { - layout.serialize(f)?; - } - Ok(()) - } -} diff --git a/src/layout/model.rs b/src/layout/model.rs index 13d380831..2e61b4536 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -1,9 +1,15 @@ +use std::future::Future; +use std::pin::Pin; use smallvec::smallvec; +use toddle::query::SharedFontLoader; use crate::error::Errors; -use crate::func::Command; -use crate::syntax::{Model, DynFuture, SyntaxModel, Node}; -use crate::syntax::{SpanVec, Spanned, Span, offset_spans}; +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 super::line::{LineLayouter, LineContext}; +use super::text::{layout_text, TextContext}; use super::*; @@ -15,6 +21,76 @@ pub struct ModelLayouter<'a, 'p> { errors: Errors, } +/// The general context for layouting. +#[derive(Debug, Clone)] +pub struct LayoutContext<'a, 'p> { + /// The font loader to retrieve fonts from when typesetting text + /// using [`layout_text`]. + pub loader: &'a SharedFontLoader<'p>, + /// The style for pages and text. + pub style: &'a LayoutStyle, + /// The base unpadded dimensions of this container (for relative sizing). + pub base: Size2D, + /// The spaces to layout in. + pub spaces: LayoutSpaces, + /// Whether to have repeated spaces or to use only the first and only once. + pub repeat: bool, + /// The initial axes along which content is laid out. + pub axes: LayoutAxes, + /// The alignment of the finished layout. + pub alignment: LayoutAlignment, + /// Whether the layout that is to be created will be nested in a parent + /// container. + pub nested: bool, + /// Whether to debug render a box around the layout. + pub debug: bool, +} + +pub struct Layouted { + pub output: T, + pub errors: Errors, +} + +impl Layouted { + pub fn map(self, f: F) -> Layouted where F: FnOnce(T) -> U { + Layouted { + output: f(self.output), + errors: self.errors, + } + } +} + +/// A sequence of layouting commands. +pub type Commands<'a> = Vec>; + +/// Layouting commands from functions to the typesetting engine. +#[derive(Debug)] +pub enum Command<'a> { + LayoutSyntaxModel(&'a SyntaxModel), + + Add(Layout), + AddMultiple(MultiLayout), + AddSpacing(Size, SpacingKind, GenericAxis), + + FinishLine, + FinishSpace, + BreakParagraph, + BreakPage, + + SetTextStyle(TextStyle), + SetPageStyle(PageStyle), + SetAlignment(LayoutAlignment), + SetAxes(LayoutAxes), +} + +pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted { + let mut layouter = ModelLayouter::new(ctx); + layouter.layout_syntax_model(model).await; + layouter.finish() +} + +pub type DynFuture<'a, T> = Pin + 'a>>; + impl<'a, 'p> ModelLayouter<'a, 'p> { /// Create a new syntax tree layouter. pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> { diff --git a/src/layout/text.rs b/src/layout/text.rs index 16ae93da1..eb598e2fd 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -6,12 +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 +/// Layouts text into boxes. +struct TextLayouter<'a, 'p> { + ctx: TextContext<'a, 'p>, + text: &'a str, + actions: LayoutActions, + buffer: String, + active_font: FontIndex, + width: Size, } /// The context for text layouting. @@ -25,14 +27,12 @@ pub struct TextContext<'a, 'p> { pub alignment: LayoutAlignment, } -/// Layouts text into boxes. -struct TextLayouter<'a, 'p> { - ctx: TextContext<'a, 'p>, - text: &'a str, - actions: LayoutActions, - buffer: String, - active_font: FontIndex, - width: Size, +/// 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 } impl<'a, 'p> TextLayouter<'a, 'p> { diff --git a/src/lib.rs b/src/lib.rs index 3ebe34c2c..946e16d20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,22 +25,24 @@ use smallvec::smallvec; use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; -use crate::func::Scope; -use crate::layout::{Layouted, MultiLayout}; -use crate::syntax::{parse, ParseContext, Parsed, SyntaxModel, Position}; +use crate::layout::MultiLayout; +use crate::layout::prelude::*; +use crate::layout::{LayoutContext, Layouted, layout}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; +use crate::syntax::{SyntaxModel, Scope, ParseContext, Parsed, parse}; +use crate::syntax::span::Position; #[macro_use] mod macros; -pub mod export; pub mod error; +pub mod export; #[macro_use] pub mod func; pub mod layout; pub mod library; -pub mod syntax; pub mod size; pub mod style; +pub mod syntax; /// Transforms source code into typesetted layouts. @@ -93,7 +95,6 @@ impl<'p> Typesetter<'p> { /// Layout a syntax tree and return the produced layout. pub async fn layout(&self, model: &SyntaxModel) -> Layouted { - use crate::layout::prelude::*; let margins = self.style.page.margins(); layout( &model, diff --git a/src/library/font.rs b/src/library/font.rs index b4a6218af..07707e907 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -1,4 +1,5 @@ use toddle::query::{FontWeight, FontStyle}; +use crate::size::FSize; use super::*; @@ -79,7 +80,7 @@ function! { #[derive(Debug, Clone, PartialEq)] pub struct FontSizeFunc { body: Option, - size: Option, + size: Option, } parse(header, body, ctx, errors, decos) { @@ -93,11 +94,11 @@ function! { layout(self, ctx, errors) { styled(&self.body, ctx, self.size, |t, s| { match s { - ScaleSize::Absolute(size) => { + FSize::Absolute(size) => { t.base_font_size = size; t.font_scale = 1.0; } - ScaleSize::Scaled(scale) => t.font_scale = scale, + FSize::Scaled(scale) => t.font_scale = scale, } }) } diff --git a/src/library/layout.rs b/src/library/layout.rs index fb8633b11..87e9c3578 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,4 +1,5 @@ -use smallvec::smallvec; +use crate::size::PSize; +use crate::syntax::func::maps::{AxisMap, PosAxisMap}; use super::*; diff --git a/src/library/mod.rs b/src/library/mod.rs index e706642f3..b570c48b9 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,5 +1,6 @@ //! The standard library. +use crate::syntax::Scope; use crate::func::prelude::*; pub_use_mod!(font); diff --git a/src/library/page.rs b/src/library/page.rs index 25f81bc1b..7e135f597 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,4 +1,6 @@ +use crate::size::Size; use crate::style::{Paper, PaperClass}; +use crate::syntax::func::maps::{AxisMap, PaddingMap}; use super::*; diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6c8182928..907d5f9bd 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -1,5 +1,8 @@ +use crate::size::FSize; +use crate::layout::SpacingKind; + use super::*; -use ContentKind::*; +use self::ContentKind::*; function! { diff --git a/src/size.rs b/src/size.rs index a5bc5d7ff..9dc74dd68 100644 --- a/src/size.rs +++ b/src/size.rs @@ -129,7 +129,7 @@ pub type PSize = ScaleSize; /// A value in two dimensions. #[derive(Default, Copy, Clone, PartialEq)] -pub struct Value2D { +pub struct Value2D { /// The horizontal component. pub x: T, /// The vertical component. @@ -226,13 +226,13 @@ impl Value2D { } } -impl Display for Value2D where T: Display { +impl Display for Value2D where T: Display { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[{}, {}]", self.x, self.y) } } -impl Debug for Value2D where T: Debug { +impl Debug for Value2D where T: Debug { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[{:?}, {:?}]", self.x, self.y) } @@ -293,7 +293,7 @@ impl Neg for Size2D { /// A value that is stretchable in an interval from a minimal through an optimal /// to a maximal value. -pub struct StretchValue { +pub struct StretchValue { /// The minimum this value can be stretched to. pub min: T, /// The optimum for this value. @@ -302,20 +302,20 @@ pub struct StretchValue { pub max: T, } -impl StretchValue { +impl StretchValue { /// Create a new stretch size from minimum, optimal and maximum values. pub fn new(min: T, opt: T, max: T) -> StretchValue { StretchValue { min, opt, max } } } -impl Display for StretchValue where T: Display { +impl Display for StretchValue where T: Display { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "({}, {}, {})", self.min, self.opt, self.max) } } -impl Debug for StretchValue where T: Debug { +impl Debug for StretchValue where T: Debug { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "({:?}, {:?}, {:?})", self.min, self.opt, self.max) } @@ -326,7 +326,7 @@ pub type StretchSize = StretchValue; /// A value in four dimensions. #[derive(Default, Copy, Clone, PartialEq)] -pub struct ValueBox { +pub struct ValueBox { /// The left extent. pub left: T, /// The top extent. @@ -337,7 +337,7 @@ pub struct ValueBox { pub bottom: T, } -impl ValueBox { +impl ValueBox { /// Create a new box from four sizes. pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox { ValueBox { left, top, right, bottom } @@ -345,7 +345,12 @@ impl ValueBox { /// Create a box with all four fields set to the same value `s`. pub fn with_all(value: T) -> ValueBox { - ValueBox { left: value, top: value, right: value, bottom: value } + ValueBox { + left: value.clone(), + top: value.clone(), + right: value.clone(), + bottom: value + } } /// Get a mutable reference to the value for the specified direction at the @@ -372,25 +377,25 @@ impl ValueBox { /// Set the `left` and `right` values. pub fn set_horizontal(&mut self, value: T) { - self.left = value; + self.left = value.clone(); self.right = value; } /// Set the `top` and `bottom` values. pub fn set_vertical(&mut self, value: T) { - self.top = value; + self.top = value.clone(); self.bottom = value; } } -impl Display for ValueBox where T: Display { +impl Display for ValueBox where T: Display { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[left: {}, top: {}, right: {}, bottom: {}]", self.left, self.top, self.right, self.bottom) } } -impl Debug for ValueBox where T: Debug { +impl Debug for ValueBox where T: Debug { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]", self.left, self.top, self.right, self.bottom) diff --git a/src/style.rs b/src/style.rs index 430deaca8..22ed4d2d9 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,6 @@ //! Styles for text and pages. use toddle::query::{FontFallbackTree, FontVariant, FontStyle, FontWeight}; - use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize}; diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index fe24c655b..b4c0dfaa3 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,4 +1,10 @@ -use super::*; +use std::fmt::{self, Display, Formatter}; + +use crate::error::Errors; +use crate::size::Size; +use super::func::{keys::Key, values::Value}; +use super::span::{Span, Spanned}; +use super::tokens::is_identifier; /// An argument or return value. @@ -91,6 +97,13 @@ pub struct Object { pub pairs: Vec, } +/// A key-value pair in an object. +#[derive(Clone, PartialEq)] +pub struct Pair { + pub key: Spanned, + pub value: Spanned, +} + impl Object { pub fn new() -> Object { Object { pairs: vec![] } @@ -162,13 +175,6 @@ impl Object { } } -/// A key-value pair in an object. -#[derive(Clone, PartialEq)] -pub struct Pair { - pub key: Spanned, - pub value: Spanned, -} - impl Display for Expr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use Expr::*; diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs index dff97bde2..116cd4e6d 100644 --- a/src/syntax/func/keys.rs +++ b/src/syntax/func/keys.rs @@ -1,9 +1,10 @@ use crate::layout::prelude::*; +use super::values::AlignmentValue::{self, *}; use super::*; -use AxisKey::*; -use PaddingKey::*; -use AlignmentValue::*; +use self::AxisKey::*; +use self::PaddingKey::*; + pub trait Key { @@ -28,7 +29,7 @@ macro_rules! key { fn parse(key: Spanned<&str>) -> Option { match key.v { $($($p)|* => Some($r)),*, - other => None, + _ => None, } } } diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs index 452c8ab1d..8941024e5 100644 --- a/src/syntax/func/maps.rs +++ b/src/syntax/func/maps.rs @@ -1,9 +1,11 @@ //! Deduplicating maps and keys for argument parsing. -use std::collections::HashMap; -use std::hash::Hash; -use crate::layout::{LayoutAxes, SpecificAxis, GenericAxis}; +use crate::error::Errors; +use crate::layout::prelude::*; use crate::size::{PSize, ValueBox}; +use crate::syntax::span::Spanned; +use super::keys::*; +use super::values::*; use super::*; @@ -179,7 +181,6 @@ impl PaddingMap { padding: &mut ValueBox> ) { use PaddingKey::*; - use SpecificAxis::*; let map = self.0.dedup(errors, |key, &val| { (match key { diff --git a/src/syntax/func/mod.rs b/src/syntax/func/mod.rs index b6691ab5e..e66b4d6af 100644 --- a/src/syntax/func/mod.rs +++ b/src/syntax/func/mod.rs @@ -1,8 +1,10 @@ -use super::*; +use crate::error::{Error, Errors}; +use super::expr::{Expr, Ident, Tuple, Object, Pair}; +use super::span::{Span, Spanned}; -pub_use_mod!(maps); -pub_use_mod!(keys); -pub_use_mod!(values); +pub mod maps; +pub mod keys; +pub mod values; #[derive(Debug, Clone, PartialEq)] @@ -17,22 +19,6 @@ pub struct FuncArgs { 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 { @@ -42,10 +28,10 @@ impl FuncArgs { } /// Add an argument. - pub fn add(&mut self, arg: Arg) { + pub fn add(&mut self, arg: FuncArg) { match arg { - Arg::Pos(item) => self.add_pos(item), - Arg::Key(pair) => self.add_key_pair(pair), + FuncArg::Pos(item) => self.add_pos(item), + FuncArg::Key(pair) => self.add_key_pair(pair), } } @@ -64,74 +50,27 @@ impl FuncArgs { self.key.add_pair(pair); } - pub fn into_iter(self) -> impl Iterator { - self.pos.items.into_iter().map(|item| Arg::Pos(item)) - .chain(self.key.pairs.into_iter().map(|pair| Arg::Key(pair))) + pub fn into_iter(self) -> impl Iterator { + self.pos.items.into_iter().map(|item| FuncArg::Pos(item)) + .chain(self.key.pairs.into_iter().map(|pair| FuncArg::Key(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), -// } -// } +#[derive(Debug, Clone, PartialEq)] +pub enum FuncArg { + Pos(Spanned), + Key(Pair), +} + +impl FuncArg { + /// The span or the value or combined span of key and value. + pub fn span(&self) -> Span { + match self { + FuncArg::Pos(item) => item.span, + FuncArg::Key(Pair { key, value }) => Span::merge(key.span, value.span), + } + } +} pub trait OptionExt: Sized { fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self; diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs index b29b97261..a767aef65 100644 --- a/src/syntax/func/values.rs +++ b/src/syntax/func/values.rs @@ -1,12 +1,13 @@ +use std::fmt::{self, Display, Formatter}; use std::marker::PhantomData; use toddle::query::{FontStyle, FontWeight}; use crate::layout::prelude::*; -use crate::size::ScaleSize; +use crate::size::{Size, ScaleSize}; use crate::style::Paper; use super::*; -use AlignmentValue::*; +use self::AlignmentValue::*; pub trait Value { @@ -76,20 +77,6 @@ impl Value for Defaultable { } } -impl Value for Direction { - type Output = Self; - - fn parse(expr: Spanned) -> Result { - Ok(match Ident::parse(expr)?.as_str() { - "left-to-right" | "ltr" | "LTR" => Direction::LeftToRight, - "right-to-left" | "rtl" | "RTL" => Direction::RightToLeft, - "top-to-bottom" | "ttb" | "TTB" => Direction::TopToBottom, - "bottom-to-top" | "btt" | "BTT" => Direction::BottomToTop, - other => return Err(err!("invalid direction")) - }) - } -} - impl Value for FontStyle { type Output = Self; @@ -134,6 +121,20 @@ impl Value for Paper { } } +impl Value for Direction { + type Output = Self; + + fn parse(expr: Spanned) -> Result { + Ok(match Ident::parse(expr)?.as_str() { + "left-to-right" | "ltr" | "LTR" => LeftToRight, + "right-to-left" | "rtl" | "RTL" => RightToLeft, + "top-to-bottom" | "ttb" | "TTB" => TopToBottom, + "bottom-to-top" | "btt" | "BTT" => BottomToTop, + _ => return Err(err!("invalid direction")) + }) + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum AlignmentValue { Align(Alignment), @@ -203,7 +204,7 @@ impl Value for AlignmentValue { "top" => Top, "right" => Right, "bottom" => Bottom, - other => return Err(err!("invalid alignment")) + _ => return Err(err!("invalid alignment")) }) } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 356535ae4..d5afbca66 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,77 +1,25 @@ //! Tokenization and parsing of source code. use std::any::Any; -use std::fmt::{self, Debug, Display, Formatter}; -use std::future::Future; -use std::pin::Pin; +use std::fmt::Debug; +use async_trait::async_trait; use serde::Serialize; -use crate::error::{Error, Errors}; -use crate::func::{Commands, Command}; -use crate::layout::{Layouted, LayoutContext}; -use crate::size::Size; +use crate::layout::{LayoutContext, Layouted, Commands, Command}; +use self::span::{Spanned, SpanVec}; -pub_use_mod!(expr); -pub_use_mod!(func); -pub_use_mod!(tokens); +pub mod expr; +pub mod func; +pub mod span; + +pub_use_mod!(scope); pub_use_mod!(parsing); -pub_use_mod!(span); - -/// Common syntax types. -pub mod prelude { - pub use super::*; -} +pub_use_mod!(tokens); -#[async_trait::async_trait(?Send)] +#[async_trait(?Send)] pub trait Model: Debug + ModelBounds { - async fn layout<'a>( - &'a self, - ctx: LayoutContext<'_, '_> - ) -> Layouted>; -} - -pub type DynFuture<'a, T> = Pin + 'a>>; - -impl dyn Model { - pub fn downcast(&self) -> Option<&T> where T: Model + 'static { - self.as_any().downcast_ref::() - } -} - -impl PartialEq for dyn Model { - fn eq(&self, other: &dyn Model) -> bool { - self.bound_eq(other) - } -} - -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()) - } + async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted>; } /// A tree representation of source code. @@ -92,7 +40,7 @@ impl SyntaxModel { } } -#[async_trait::async_trait(?Send)] +#[async_trait(?Send)] impl Model for SyntaxModel { async fn layout<'a>(&'a self, _: LayoutContext<'_, '_>) -> Layouted> { Layouted { @@ -144,3 +92,44 @@ pub enum Decoration { InvalidFuncName, ArgumentKey, } + +impl dyn Model { + pub fn downcast(&self) -> Option<&T> where T: Model + 'static { + self.as_any().downcast_ref::() + } +} + +impl PartialEq for dyn Model { + fn eq(&self, other: &dyn Model) -> bool { + self.bound_eq(other) + } +} + +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()) + } +} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index e726a2e01..3e3e827f2 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,6 +1,10 @@ -use crate::func::Scope; +use crate::error::Errors; +use super::expr::*; +use super::func::{FuncHeader, FuncArgs, FuncArg}; +use super::scope::Scope; +use super::span::{Position, Span, Spanned, SpanVec, offset_spans}; +use super::tokens::{Token, Tokens, TokenizationMode}; use super::*; -use Token::*; /// The context for parsing. @@ -37,13 +41,13 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed if newlines >= 2 { + Token::Space(newlines) => if newlines >= 2 { Node::Newline } else { Node::Space }, - Function { header, body, terminated } => { + Token::Function { header, body, terminated } => { let parsed: Parsed = FuncParser::new(header, body, ctx).parse(); errors.extend(offset_spans(parsed.errors, span.start)); @@ -56,15 +60,15 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed Node::ToggleBolder, - Underscore => Node::ToggleItalic, - Backtick => Node::ToggleMonospace, - Text(text) => Node::Text(text.to_owned()), + Token::Star => Node::ToggleBolder, + Token::Underscore => Node::ToggleItalic, + Token::Backtick => Node::ToggleMonospace, + Token::Text(text) => Node::Text(text.to_owned()), - LineComment(_) | BlockComment(_) => continue, + Token::LineComment(_) | Token::BlockComment(_) => continue, other => { - errors.push(err!(span; "unexpected {}", name(other))); + errors.push(err!(span; "unexpected {}", other.name())); continue; } }; @@ -140,7 +144,7 @@ impl<'s> FuncParser<'s> { self.skip_whitespace(); let name = match self.eat() { - Some(Spanned { v: ExprIdent(ident), span }) => { + Some(Spanned { v: Token::ExprIdent(ident), span }) => { Spanned { v: Ident(ident.to_string()), span } } other => { @@ -151,7 +155,7 @@ impl<'s> FuncParser<'s> { self.skip_whitespace(); let args = match self.eat().map(Spanned::value) { - Some(Colon) => self.parse_func_args(), + Some(Token::Colon) => self.parse_func_args(), Some(_) => { self.expected_at("colon", name.span.end); FuncArgs::new() @@ -179,38 +183,38 @@ impl<'s> FuncParser<'s> { } /// Parse a positional or keyword argument. - fn parse_arg(&mut self) -> Option { + fn parse_arg(&mut self) -> Option { let first = self.peek()?; let span = first.span; - let arg = if let ExprIdent(ident) = first.v { + let arg = if let Token::ExprIdent(ident) = first.v { self.eat(); self.skip_whitespace(); let ident = Ident(ident.to_string()); - if let Some(Equals) = self.peekv() { + if let Some(Token::Equals) = self.peekv() { self.eat(); self.skip_whitespace(); self.decorations.push(Spanned::new(Decoration::ArgumentKey, span)); self.parse_expr().map(|value| { - Arg::Key(Pair { + FuncArg::Key(Pair { key: Spanned { v: ident, span }, value, }) }) } else { - Some(Arg::Pos(Spanned::new(Expr::Ident(ident), span))) + Some(FuncArg::Pos(Spanned::new(Expr::Ident(ident), span))) } } else { - self.parse_expr().map(|expr| Arg::Pos(expr)) + self.parse_expr().map(|expr| FuncArg::Pos(expr)) }; if let Some(arg) = &arg { self.skip_whitespace(); match self.peekv() { - Some(Comma) => { self.eat(); } + Some(Token::Comma) => { self.eat(); } Some(_) => self.expected_at("comma", arg.span().end), _ => {} } @@ -228,11 +232,11 @@ impl<'s> FuncParser<'s> { let spanned = |v| Spanned { v, span: first.span }; Some(match first.v { - ExprIdent(i) => { + Token::ExprIdent(i) => { self.eat(); spanned(Expr::Ident(Ident(i.to_string()))) } - ExprStr { string, terminated } => { + Token::ExprStr { string, terminated } => { if !terminated { self.expected_at("quote", first.span.end); } @@ -240,12 +244,13 @@ impl<'s> FuncParser<'s> { self.eat(); spanned(Expr::Str(string.to_string())) } - ExprNumber(n) => { self.eat(); spanned(Expr::Number(n)) } - ExprSize(s) => { self.eat(); spanned(Expr::Size(s)) } - ExprBool(b) => { self.eat(); spanned(Expr::Bool(b)) } + Token::ExprNumber(n) => { self.eat(); spanned(Expr::Number(n)) } + Token::ExprSize(s) => { self.eat(); spanned(Expr::Size(s)) } + Token::ExprBool(b) => { self.eat(); spanned(Expr::Bool(b)) } + + Token::LeftParen => self.parse_tuple(), + Token::LeftBrace => self.parse_object(), - LeftParen => self.parse_tuple(), - LeftBrace => self.parse_object(), _ => return None, }) } @@ -255,7 +260,7 @@ impl<'s> FuncParser<'s> { let start = self.pos(); // TODO: Do the thing. - self.eat_until(|t| t == RightParen, true); + self.eat_until(|t| t == Token::RightParen, true); let end = self.pos(); let span = Span { start, end }; @@ -268,7 +273,7 @@ impl<'s> FuncParser<'s> { let start = self.pos(); // TODO: Do the thing. - self.eat_until(|t| t == RightBrace, true); + self.eat_until(|t| t == Token::RightBrace, true); let end = self.pos(); let span = Span { start, end }; @@ -278,15 +283,16 @@ impl<'s> FuncParser<'s> { /// Skip all whitespace/comment tokens. fn skip_whitespace(&mut self) { - self.eat_until(|t| - !matches!(t, Space(_) | LineComment(_) | BlockComment(_)), false) + self.eat_until(|t| !matches!(t, + Token::Space(_) | Token::LineComment(_) | + Token::BlockComment(_)), false) } /// 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; - "expected {}, found {}", thing, name(found.v))); + "expected {}, found {}", thing, found.v.name())); } /// Add an error about an `thing` which was expected but not found at the @@ -348,31 +354,3 @@ impl<'s> FuncParser<'s> { .unwrap_or_else(|| self.tokens.pos()) } } - -/// The name of a token in an `(un)expected <...>` error. -fn name(token: Token) -> &'static str { - match token { - Space(_) => "space", - LineComment(_) | BlockComment(_) => "comment", - Function { .. } => "function", - LeftParen => "opening paren", - RightParen => "closing paren", - LeftBrace => "opening brace", - RightBrace => "closing brace", - Colon => "colon", - Comma => "comma", - Equals => "equals sign", - ExprIdent(_) => "identifier", - ExprStr { .. } => "string", - ExprNumber(_) => "number", - ExprSize(_) => "size", - ExprBool(_) => "boolean", - Star => "star", - Underscore => "underscore", - Backtick => "backtick", - Text(_) => "invalid identifier", - Invalid("]") => "closing bracket", - Invalid("*/") => "end of block comment", - Invalid(_) => "invalid token", - } -} diff --git a/src/func/mod.rs b/src/syntax/scope.rs similarity index 55% rename from src/func/mod.rs rename to src/syntax/scope.rs index b3721d912..2aae331d9 100644 --- a/src/func/mod.rs +++ b/src/syntax/scope.rs @@ -1,67 +1,12 @@ -//! Dynamic typesetting functions. - use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; -use self::prelude::*; +use crate::func::ParseFunc; +use super::func::FuncHeader; +use super::parsing::{ParseContext, Parsed}; +use super::span::Spanned; +use super::Model; -#[macro_use] -mod macros; - -/// Useful imports for creating your own functions. -pub mod prelude { - pub use super::{Scope, Parse, Command, Commands}; - pub use crate::error::Error; - pub use crate::layout::prelude::*; - 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::*; -} - -/// 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>, - ctx: ParseContext, - metadata: Self::Meta, - ) -> Parsed where Self: Sized; -} - -/// 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>; - -/// Layouting commands from functions to the typesetting engine. -#[derive(Debug)] -pub enum Command<'a> { - LayoutSyntaxModel(&'a SyntaxModel), - - Add(Layout), - AddMultiple(MultiLayout), - AddSpacing(Size, SpacingKind, GenericAxis), - - FinishLine, - FinishSpace, - BreakParagraph, - BreakPage, - - SetTextStyle(TextStyle), - SetPageStyle(PageStyle), - SetAlignment(LayoutAlignment), - SetAxes(LayoutAxes), -} /// A map from identifiers to function parsers. pub struct Scope { @@ -73,7 +18,7 @@ impl Scope { /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. pub fn new() -> Scope - where F: Parse + Model + 'static { + where F: ParseFunc + Model + 'static { Scope { parsers: HashMap::new(), fallback: parser::(()), @@ -87,14 +32,14 @@ impl Scope { /// Associate the given name with a type that is parseable into a function. pub fn add(&mut self, name: &str) - where F: Parse + Model + 'static { + where F: ParseFunc + Model + 'static { self.add_with_meta::(name, ()); } /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). - pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) - where F: Parse + Model + 'static { + pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) + where F: ParseFunc + Model + 'static { self.parsers.insert( name.to_owned(), parser::(metadata), @@ -121,7 +66,16 @@ impl Debug for Scope { } } -fn parser(metadata: ::Meta) -> Box where F: Parse + Model + 'static { +/// A function which parses the source of a function into a model type which +/// implements [`Model`]. +type Parser = dyn Fn( + FuncHeader, + Option>, + ParseContext, +) -> Parsed>; + +fn parser(metadata: ::Meta) -> Box +where F: ParseFunc + 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/syntax/span.rs b/src/syntax/span.rs index e049861fe..f5bd4caf8 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -5,93 +5,6 @@ use std::ops::{Add, Sub}; use serde::Serialize; -/// Annotates a value with the part of the source code it corresponds to. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] -pub struct Spanned { - pub v: T, - pub span: Span, -} - -impl Spanned { - pub fn new(v: T, span: Span) -> Spanned { - Spanned { v, span } - } - - pub fn value(self) -> T { - self.v - } - - pub fn map(self, f: F) -> Spanned where F: FnOnce(T) -> V { - Spanned { v: f(self.v), span: self.span } - } - - pub fn map_span(mut self, f: F) -> Spanned where F: FnOnce(Span) -> Span { - self.span = f(self.span); - self - } -} - -impl Display for Spanned where T: std::fmt::Display { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "({}, {}, ", self.span.start, self.span.end)?; - self.v.fmt(f)?; - write!(f, ")") - } -} - -impl Debug for Spanned where T: std::fmt::Debug { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "({}, {}, ", self.span.start, self.span.end)?; - self.v.fmt(f)?; - write!(f, ")") - } -} - -/// Describes a slice of source code. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] -pub struct Span { - pub start: Position, - pub end: Position, -} - -impl Span { - pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO }; - - pub fn new(start: Position, end: Position) -> Span { - Span { start, end } - } - - pub fn merge(a: Span, b: Span) -> Span { - Span { - start: a.start.min(b.start), - end: a.end.max(b.end), - } - } - - pub fn at(pos: Position) -> Span { - Span { start: pos, end: pos } - } - - pub fn expand(&mut self, other: Span) { - *self = Span::merge(*self, other) - } - - pub fn offset(self, start: Position) -> Span { - Span { - start: start + self.start, - end: start + self.end, - } - } -} - -impl Display for Span { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "({}, {})", self.start, self.end) - } -} - -debug_display!(Span); - /// A line-column position in source code. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] pub struct Position { @@ -145,13 +58,68 @@ impl Sub for Position { } } -impl Display for Position { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}:{}", self.line, self.column) +/// Describes a slice of source code. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct Span { + pub start: Position, + pub end: Position, +} + +impl Span { + pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO }; + + pub fn new(start: Position, end: Position) -> Span { + Span { start, end } + } + + pub fn merge(a: Span, b: Span) -> Span { + Span { + start: a.start.min(b.start), + end: a.end.max(b.end), + } + } + + pub fn at(pos: Position) -> Span { + Span { start: pos, end: pos } + } + + pub fn expand(&mut self, other: Span) { + *self = Span::merge(*self, other) + } + + pub fn offset(self, start: Position) -> Span { + Span { + start: start + self.start, + end: start + self.end, + } } } -debug_display!(Position); +/// Annotates a value with the part of the source code it corresponds to. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct Spanned { + pub v: T, + pub span: Span, +} + +impl Spanned { + pub fn new(v: T, span: Span) -> Spanned { + Spanned { v, span } + } + + pub fn value(self) -> T { + self.v + } + + pub fn map(self, f: F) -> Spanned where F: FnOnce(T) -> V { + Spanned { v: f(self.v), span: self.span } + } + + pub fn map_span(mut self, f: F) -> Spanned where F: FnOnce(Span) -> Span { + self.span = f(self.span); + self + } +} /// A vector of spanned things. pub type SpanVec = Vec>; @@ -159,3 +127,34 @@ pub type SpanVec = Vec>; pub fn offset_spans(vec: SpanVec, start: Position) -> impl Iterator> { vec.into_iter().map(move |s| s.map_span(|span| span.offset(start))) } + +impl Display for Position { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} + +impl Display for Span { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {})", self.start, self.end) + } +} + +impl Display for Spanned where T: std::fmt::Display { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {}, ", self.span.start, self.span.end)?; + self.v.fmt(f)?; + write!(f, ")") + } +} + +impl Debug for Spanned where T: std::fmt::Debug { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {}, ", self.span.start, self.span.end)?; + self.v.fmt(f)?; + write!(f, ")") + } +} + +debug_display!(Position); +debug_display!(Span); diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index d0adbf606..0a8e2f178 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -2,9 +2,11 @@ use std::iter::Peekable; use std::str::Chars; use unicode_xid::UnicodeXID; -use super::*; -use Token::*; -use TokenizationMode::*; +use crate::size::Size; +use super::span::{Position, Span, Spanned}; + +use self::Token::*; +use self::TokenizationMode::*; /// A minimal semantic entity of source code. @@ -68,6 +70,37 @@ pub enum Token<'s> { Invalid(&'s str), } +impl<'s> Token<'s> { + /// The natural-language name for this token for use in error messages. + pub fn name(self) -> &'static str { + match self { + Space(_) => "space", + LineComment(_) => "line comment", + BlockComment(_) => "block comment", + Function { .. } => "function", + LeftParen => "opening paren", + RightParen => "closing paren", + LeftBrace => "opening brace", + RightBrace => "closing brace", + Colon => "colon", + Comma => "comma", + Equals => "equals sign", + ExprIdent(_) => "identifier", + ExprStr { .. } => "string", + ExprNumber(_) => "number", + ExprSize(_) => "size", + ExprBool(_) => "boolean", + Star => "star", + Underscore => "underscore", + Backtick => "backtick", + Text(_) => "invalid identifier", + Invalid("]") => "closing bracket", + Invalid("*/") => "end of block comment", + Invalid(_) => "invalid token", + } + } +} + /// An iterator over the tokens of a string of source code. pub struct Tokens<'s> { src: &'s str,