From 9fb31defd037a90bf8f9e38fa33acae23a70b269 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 4 Dec 2019 19:34:29 +0100 Subject: [PATCH] =?UTF-8?q?Expand=20functionality=20of=20function!=20macro?= =?UTF-8?q?=20=F0=9F=9B=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/export/pdf.rs | 2 +- src/func/args.rs | 215 +++++++++++++++++++++++++++++++ src/func/helpers.rs | 203 ------------------------------ src/func/macros.rs | 149 ++++++++++++++++++++++ src/func/mod.rs | 125 ++++++++++-------- src/layout/flex.rs | 18 +-- src/layout/mod.rs | 116 +++++++++++++++-- src/layout/stack.rs | 2 +- src/layout/text.rs | 2 +- src/layout/tree.rs | 6 +- src/library/align.rs | 181 +++++++-------------------- src/library/boxed.rs | 59 +++++---- src/library/mod.rs | 278 ++++++++++++++++++++++++++++++++++++++--- src/library/page.rs | 79 ------------ src/library/spacing.rs | 71 ----------- src/library/style.rs | 39 ------ src/size.rs | 38 ++++-- src/syntax/mod.rs | 87 ++----------- src/syntax/parsing.rs | 64 +++++----- src/syntax/span.rs | 72 +++++++++++ 20 files changed, 1039 insertions(+), 767 deletions(-) create mode 100644 src/func/args.rs delete mode 100644 src/func/helpers.rs create mode 100644 src/func/macros.rs delete mode 100644 src/library/page.rs delete mode 100644 src/library/spacing.rs delete mode 100644 src/library/style.rs create mode 100644 src/syntax/span.rs diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 76fc59b89..8370f4543 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -80,7 +80,7 @@ impl<'d, W: Write> ExportProcess<'d, W> { ) -> PdfResult> { let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?; - let offsets = Self::calculate_offsets(layouts.count(), fonts.len()); + let offsets = Self::calculate_offsets(layouts.len(), fonts.len()); Ok(ExportProcess { writer: PdfWriter::new(target), diff --git a/src/func/args.rs b/src/func/args.rs new file mode 100644 index 000000000..2e9e80cc1 --- /dev/null +++ b/src/func/args.rs @@ -0,0 +1,215 @@ +//! Parsing, storing and deduplication of function arguments. + +use super::prelude::*; +use Expression::*; + +/// Provides a convenient interface to parse the arguments to a function. +pub struct ArgParser<'a> { + args: &'a FuncArgs, + positional_index: usize, +} + +impl<'a> ArgParser<'a> { + pub fn new(args: &'a FuncArgs) -> ArgParser<'a> { + ArgParser { + args, + positional_index: 0, + } + } + + /// Get the next positional argument of the given type. + /// + /// If there are no more arguments or the type is wrong, + /// this will return an error. + pub fn get_pos(&mut self) -> ParseResult> + where T: Argument<'a> { + Self::expected(self.get_pos_opt::()?) + } + + /// Get the next positional argument if there is any. + /// + /// If the argument is of the wrong type, this will return an error. + pub fn get_pos_opt(&mut self) -> ParseResult>> + where T: Argument<'a> { + let arg = self.args.positional + .get(self.positional_index) + .map(T::from_expr) + .transpose(); + + if let Ok(Some(_)) = arg { + self.positional_index += 1; + } + + arg + } + + /// Get a keyword argument with the given key and type. + pub fn get_key(&mut self, key: &str) -> ParseResult> + where T: Argument<'a> { + Self::expected(self.get_key_opt::(key)?) + } + + /// Get a keyword argument with the given key and type if it is present. + pub fn get_key_opt(&mut self, key: &str) -> ParseResult>> + where T: Argument<'a> { + self.args.keyword.iter() + .find(|entry| entry.val.0.val == key) + .map(|entry| T::from_expr(&entry.val.1)) + .transpose() + } + + /// Assert that there are no positional arguments left. Returns an error + /// otherwise. + pub fn done(&self) -> ParseResult<()> { + if self.positional_index == self.args.positional.len() { + Ok(()) + } else { + pr!("unexpected argument"); + } + } + + /// Covert an option to a result with an error on `None`. + fn expected(val: Option>) -> ParseResult> + where T: Argument<'a> { + val.ok_or_else(|| pr!(@"expected {}", T::ERROR_MESSAGE)) + } +} + +/// A kind of argument. +pub trait Argument<'a> { + type Output; + const ERROR_MESSAGE: &'static str; + + fn from_expr(expr: &'a Spanned) -> ParseResult>; +} + +macro_rules! arg { + ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => ( + #[doc = $doc] + #[doc = " argument for use with the [`ArgParser`]."] + pub struct $type; + impl<'a> Argument<'a> for $type { + type Output = $output; + const ERROR_MESSAGE: &'static str = $err; + + fn from_expr(expr: &'a Spanned) -> ParseResult> { + #[allow(unreachable_patterns)] + match &expr.val { + $wanted => Ok(Spanned::new($converted, expr.span)), + _ => pr!("expected {}", $err), + } + } + } + ); +} + +arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr); +arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str()); +arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str()); +arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n); +arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s); +arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b); + +/// An argument key which identifies a layouting axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AxisKey { + Primary, + Secondary, + Vertical, + Horizontal, +} + +impl AxisKey { + /// The generic version of this axis key in the given system of axes. + pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind { + match self { + Primary => GenericAxisKind::Primary, + Secondary => GenericAxisKind::Secondary, + Vertical => axes.vertical(), + Horizontal => axes.horizontal(), + } + } + + /// The specific version of this axis key in the given system of axes. + pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind { + match self { + Primary => axes.primary(), + Secondary => axes.secondary(), + Vertical => SpecificAxisKind::Vertical, + Horizontal => SpecificAxisKind::Horizontal, + } + } +} + +/// An argument key which identifies a target alignment. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AlignmentKey { + Left, + Top, + Right, + Bottom, + Origin, + Center, + End, +} + +impl AlignmentKey { + /// The generic axis this alignment key corresopnds to in the given system + /// of layouting axes. Falls back to `default` if the alignment is generic. + pub fn axis(&self, axes: LayoutAxes, default: GenericAxisKind) -> GenericAxisKind { + use AlignmentKey::*; + match self { + Origin | Center | End => default, + Left | Right => axes.horizontal(), + Top | Bottom => axes.vertical(), + } + } + + /// The generic version of this alignment in the given system of layouting + /// axes. Returns an error if the alignment is invalid for the given axis. + pub fn generic(&self, axes: LayoutAxes, axis: GenericAxisKind) -> LayoutResult { + use AlignmentKey::*; + + let horizontal = axis == axes.horizontal(); + Ok(match self { + Origin => Alignment::Origin, + Center => Alignment::Center, + End => Alignment::End, + Left if horizontal => axes.left(), + Right if horizontal => axes.right(), + Top if !horizontal => axes.top(), + Bottom if !horizontal => axes.bottom(), + _ => lr!( + "invalid alignment `{}` for {} axis", + format!("{:?}", self).to_lowercase(), + format!("{:?}", axis).to_lowercase() + ) + }) + } + + /// The specific version of this alignment in the given system of layouting + /// axes. + pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey { + use AlignmentKey::*; + match (self, axis) { + (Origin, SpecificAxisKind::Horizontal) => Left, + (End, SpecificAxisKind::Horizontal) => Right, + (Origin, SpecificAxisKind::Vertical) => Top, + (End, SpecificAxisKind::Vertical) => Bottom, + _ => *self, + } + } +} + +/// An argument key which identifies a margin or padding target. +/// +/// A is the axis type used. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PaddingKey { + /// All four sides should have the specified padding. + All, + /// Both sides of the given axis should have the specified padding. + Axis(A), + /// Only the given side of the given axis should have the specified padding. + AxisAligned(A, AlignmentKey), +} diff --git a/src/func/helpers.rs b/src/func/helpers.rs deleted file mode 100644 index c82a90e1f..000000000 --- a/src/func/helpers.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Helper types and macros for creating custom functions. - -use super::prelude::*; -use Expression::*; - -/// Lets you implement the function trait more concisely. -#[macro_export] -macro_rules! function { - (data: $ident:ident, $($tts:tt)*) => ( - #[allow(unused_imports)] - use $crate::func::prelude::*; - - impl Function for $ident { - function!(@parse $ident, $($tts)*); - } - ); - - (@parse $ident:ident, parse: plain, $($tts:tt)*) => ( - fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult - where Self: Sized { - ArgParser::new(&header.args).done()?; - if body.is_some() { - perr!("expected no body"); - } - Ok($ident) - } - - function!(@layout $($tts)*); - ); - - ( - @parse $ident:ident, - parse($args:ident, $body:ident, $ctx:ident) - $block:block - $($tts:tt)* - ) => ( - fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult - where Self: Sized { - #[allow(unused_mut)] - let mut $args = ArgParser::new(&header.args); - let $body = body; - let $ctx = ctx; - $block - } - - function!(@layout $($tts)*); - ); - - (@layout layout($this:pat, $ctx:pat) $block:block) => ( - fn layout(&self, ctx: LayoutContext) -> LayoutResult { - let $ctx = ctx; - let $this = self; - $block - } - ); -} - -/// Parse the body of a function. -/// - If the function does not expect a body, use `forbidden`. -/// - If the function can have a body, use `optional`. -/// - If the function must have a body, use `required`. -#[macro_export] -macro_rules! parse { - (forbidden: $body:expr) => { - if $body.is_some() { - perr!("unexpected body"); - } - }; - - (optional: $body:expr, $ctx:expr) => ( - if let Some(body) = $body { - Some($crate::syntax::parse(body, $ctx)?) - } else { - None - } - ); - - (required: $body:expr, $ctx:expr) => ( - if let Some(body) = $body { - $crate::syntax::parse(body, $ctx)? - } else { - perr!("expected body"); - } - ) -} - -/// Early-return with a formatted parsing error or yield -/// an error expression without returning when prefixed with `@`. -#[macro_export] -macro_rules! perr { - (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*))); - ($($tts:tt)*) => (return Err(perr!(@$($tts)*));); -} - -/// Early-return with a formatted layouting error or yield -/// an error expression without returning when prefixed with `@`. -#[macro_export] -macro_rules! lerr { - (@$($tts:tt)*) => ($crate::layout::LayoutError::new(format!($($tts)*))); - ($($tts:tt)*) => (return Err(lerr!(@$($tts)*));); -} - - -/// Easy parsing of function arguments. -pub struct ArgParser<'a> { - args: &'a FuncArgs, - positional_index: usize, -} - -impl<'a> ArgParser<'a> { - pub fn new(args: &'a FuncArgs) -> ArgParser<'a> { - ArgParser { - args, - positional_index: 0, - } - } - - /// Get the next positional argument of the given type. - /// - /// If there are no more arguments or the type is wrong, - /// this will return an error. - pub fn get_pos(&mut self) -> ParseResult> where T: Argument<'a> { - self.get_pos_opt::()? - .ok_or_else(|| perr!(@"expected {}", T::ERROR_MESSAGE)) - } - - /// Get the next positional argument if there is any. - /// - /// If the argument is of the wrong type, this will return an error. - pub fn get_pos_opt(&mut self) -> ParseResult>> - where T: Argument<'a> { - let arg = self.args.positional - .get(self.positional_index) - .map(T::from_expr) - .transpose(); - - if let Ok(Some(_)) = arg { - self.positional_index += 1; - } - - arg - } - - /// Get a keyword argument with the given key and type. - pub fn get_key(&mut self, key: &str) -> ParseResult> - where T: Argument<'a> { - self.get_key_opt::(key)? - .ok_or_else(|| perr!(@"expected {}", T::ERROR_MESSAGE)) - } - - /// Get a keyword argument with the given key and type if it is present. - pub fn get_key_opt(&mut self, key: &str) -> ParseResult>> - where T: Argument<'a> { - self.args.keyword.iter() - .find(|entry| entry.val.0.val == key) - .map(|entry| T::from_expr(&entry.val.1)) - .transpose() - } - - /// Assert that there are no positional arguments left. Returns an error, otherwise. - pub fn done(&self) -> ParseResult<()> { - if self.positional_index == self.args.positional.len() { - Ok(()) - } else { - perr!("unexpected argument"); - } - } -} - -/// A kind of argument. -pub trait Argument<'a> { - type Output; - const ERROR_MESSAGE: &'static str; - - fn from_expr(expr: &'a Spanned) -> ParseResult>; -} - -macro_rules! arg { - ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => ( - #[doc = $doc] - #[doc = " argument for use with the [`ArgParser`]."] - pub struct $type; - impl<'a> Argument<'a> for $type { - type Output = $output; - const ERROR_MESSAGE: &'static str = $err; - - fn from_expr(expr: &'a Spanned) -> ParseResult> { - #[allow(unreachable_patterns)] - match &expr.val { - $wanted => Ok(Spanned::new($converted, expr.span)), - _ => perr!("expected {}", $err), - } - } - } - ); -} - -arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr); -arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str()); -arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str()); -arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n); -arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s); -arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b); diff --git a/src/func/macros.rs b/src/func/macros.rs new file mode 100644 index 000000000..78cf1f56f --- /dev/null +++ b/src/func/macros.rs @@ -0,0 +1,149 @@ +//! 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)*); + }; + + // 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 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 not 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($args:ident) $code:block $($rest:tt)*) => { + function!(@parse $type $meta | parse($args, _body, _ctx, _meta) $code $($rest)*); + }; + + // (2-arg) Parse a parse-definition with only the first two arguments. + (@parse $type:ident $meta:ty | + parse($args:ident, $body:pat) $code:block $($rest:tt)* + ) => { + function!(@parse $type $meta | parse($args, $body, _ctx, _meta) $code $($rest)*); + }; + + // (3-arg) Parse a parse-definition with only the first three arguments. + (@parse $type:ident $meta:ty | + parse($args:ident, $body:pat, $ctx:pat) $code:block $($rest:tt)* + ) => { + function!(@parse $type $meta | parse($args, $body, $ctx, _meta) $code $($rest)*); + }; + + // (4-arg) Parse a parse-definition with all four arguments. + (@parse $type:ident $meta:ty | + parse($args:ident, $body:pat, $ctx:pat, $metadata:pat) $code:block + $($rest:tt)* + ) => { + impl $crate::func::ParseFunc for $type { + type Meta = $meta; + + fn parse( + header: &FuncHeader, + $body: Option<&str>, + $ctx: ParseContext, + $metadata: Self::Meta, + ) -> ParseResult where Self: Sized { + let mut $args = $crate::func::args::ArgParser::new(&header.args); + let val = $code; + $args.done()?; + Ok(val) + } + } + + function!(@layout $type | $($rest)*); + }; + + // (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(&$this, $ctx: LayoutContext) -> LayoutResult { + Ok($code) + } + } + }; +} + +/// Parse the body of a function. +/// - If the function does not expect a body, use `parse!(forbidden: body)`. +/// - 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() { + pr!("unexpected body"); + } + }; + + (optional: $body:expr, $ctx:expr) => ( + if let Some(body) = $body { + Some($crate::syntax::parse(body, $ctx)?) + } else { + None + } + ); + + (expected: $body:expr, $ctx:expr) => ( + if let Some(body) = $body { + $crate::syntax::parse(body, $ctx)? + } else { + pr!("expected body"); + } + ) +} + +/// Early-return with a formatted parsing error or yield +/// an error expression without returning when prefixed with `@`. +#[macro_export] +macro_rules! pr { + (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*))); + ($($tts:tt)*) => (return Err(pr!(@$($tts)*));); +} + +/// Early-return with a formatted layouting error or yield +/// an error expression without returning when prefixed with `@`. +#[macro_export] +macro_rules! lr { + (@$($tts:tt)*) => ($crate::layout::LayoutError::new(format!($($tts)*))); + ($($tts:tt)*) => (return Err(lr!(@$($tts)*));); +} diff --git a/src/func/mod.rs b/src/func/mod.rs index 33a6c7565..b16eecb81 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -7,53 +7,64 @@ use std::fmt::{self, Debug, Formatter}; use self::prelude::*; #[macro_use] -pub mod helpers; +pub mod macros; +pub mod args; /// Useful imports for creating your own functions. pub mod prelude { - pub use crate::func::{Command, CommandList, Function}; - pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext}; - pub use crate::layout::{LayoutSpace, LayoutSpaces, SpacingKind}; - pub use crate::layout::{LayoutAxes, Axis, AxisKind, LayoutAlignment, Alignment}; - pub use crate::layout::{LayoutError, LayoutResult}; + pub use Command::*; + pub use super::args::*; + pub use super::{Scope, ParseFunc, LayoutFunc, Command, Commands}; pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span}; pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult}; pub use crate::size::{Size, Size2D, SizeBox}; pub use crate::style::{PageStyle, TextStyle}; - pub use super::helpers::*; - pub use Command::*; + pub use crate::layout::{ + layout_tree, Layout, MultiLayout, + LayoutContext, LayoutSpace, LayoutSpaces, + LayoutAxes, Axis, GenericAxisKind, SpecificAxisKind, + LayoutAlignment, Alignment, + SpacingKind, + LayoutError, LayoutResult, + }; } -/// Typesetting function types. -/// -/// These types have to be able to parse themselves from a string and build -/// a list of layouting commands corresponding to the parsed source. -/// -/// This trait is a supertrait of `FunctionBounds` for technical reasons. The -/// trait `FunctionBounds` is automatically implemented for types which can -/// be used as functions, that is, all types which fulfill the bounds `Debug + PartialEq + -/// 'static`. -pub trait Function: FunctionBounds { +/// Types representing functions that are parsed from source code. +pub trait ParseFunc { + type Meta; + /// Parse the header and body into this function given a context. - fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult - where Self: Sized; - - /// Layout this function given a context. - /// - /// Returns optionally the resulting layout and a new context if changes to - /// the context should be made. - fn layout(&self, ctx: LayoutContext) -> LayoutResult; + fn parse( + header: &FuncHeader, + body: Option<&str>, + ctx: ParseContext, + metadata: Self::Meta, + ) -> ParseResult where Self: Sized; } -impl dyn Function { - /// Downcast a dynamic function to a concrete function type. - pub fn downcast(&self) -> Option<&F> where F: Function + 'static { +/// Types representing functions 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`. +pub trait LayoutFunc: LayoutFuncBounds { + /// Layout this function in a given context. + /// + /// Returns a sequence of layouting commands which describe what the + /// function is doing. + fn layout(&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 Function { - fn eq(&self, other: &dyn Function) -> bool { +impl PartialEq for dyn LayoutFunc { + fn eq(&self, other: &dyn LayoutFunc) -> bool { self.help_eq(other) } } @@ -63,22 +74,20 @@ impl PartialEq for dyn 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 FunctionBounds: Debug { +pub trait LayoutFuncBounds: Debug { /// Cast self into `Any`. fn help_cast_as_any(&self) -> &dyn Any; - /// Compare self with another function. - fn help_eq(&self, other: &dyn Function) -> bool; + /// Compare self with another function trait object. + fn help_eq(&self, other: &dyn LayoutFunc) -> bool; } -impl FunctionBounds for T -where T: Debug + PartialEq + 'static -{ +impl LayoutFuncBounds for T where T: Debug + PartialEq + 'static { fn help_cast_as_any(&self) -> &dyn Any { self } - fn help_eq(&self, other: &dyn Function) -> bool { + fn help_eq(&self, other: &dyn LayoutFunc) -> bool { if let Some(other) = other.help_cast_as_any().downcast_ref::() { self == other } else { @@ -87,17 +96,17 @@ where T: Debug + PartialEq + 'static } } -/// A sequence of commands requested for execution by a function. -pub type CommandList<'a> = Vec>; +/// A sequence of layouting commands. +pub type Commands<'a> = Vec>; -/// Commands requested for execution by functions. +/// Layouting commands from functions to the typesetting engine. #[derive(Debug)] pub enum Command<'a> { LayoutTree(&'a SyntaxTree), Add(Layout), AddMultiple(MultiLayout), - AddSpacing(Size, SpacingKind, AxisKind), + AddSpacing(Size, SpacingKind, GenericAxisKind), FinishLine, FinishRun, @@ -110,13 +119,18 @@ pub enum Command<'a> { SetAxes(LayoutAxes), } -/// A map from identifiers to functions. +/// A map from identifiers to function parsers. pub struct Scope { - parsers: HashMap>, + parsers: HashMap>, } -/// A function which parses a function invocation into a function type. -type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext) -> ParseResult>; +/// 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>; impl Scope { /// Create a new empty scope. @@ -131,16 +145,27 @@ impl Scope { crate::library::std() } - /// Add a function type to the scope giving it a name. - pub fn add(&mut self, name: &str) { + /// 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 { + 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: T) + where F: ParseFunc + LayoutFunc + 'static, T: 'static { self.parsers.insert( name.to_owned(), - Box::new(|h, b, c| F::parse(h, b, c).map(|func| Box::new(func) as Box)), + Box::new(|h, b, c| { + F::parse(h, b, c, metadata) + .map(|f| Box::new(f) as Box) + }) ); } /// Return the parser with the given name if there is one. - pub(crate) fn get_parser(&self, name: &str) -> Option<&ParseFunc> { + pub(crate) fn get_parser(&self, name: &str) -> Option<&Parser> { self.parsers.get(name).map(|x| &**x) } } diff --git a/src/layout/flex.rs b/src/layout/flex.rs index afca23ec8..3e8a64e16 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -14,7 +14,7 @@ pub struct FlexLayouter { #[derive(Debug, Clone)] enum FlexUnit { Boxed(Layout), - Space(Size, SpaceKind), + Space(Size, SpacingKind), SetAxes(LayoutAxes), Break, } @@ -102,11 +102,11 @@ impl FlexLayouter { self.units.push(FlexUnit::Break); } - pub fn add_primary_space(&mut self, space: Size, kind: SpaceKind) { + pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) { self.units.push(FlexUnit::Space(space, kind)) } - pub fn add_secondary_space(&mut self, space: Size, kind: SpaceKind) -> LayoutResult<()> { + pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> { if !self.run_is_empty() { self.finish_run()?; } @@ -179,7 +179,7 @@ impl FlexLayouter { debug_render: false, })?; - self.stack.add_spacing(self.flex_spacing, SpaceKind::Independent); + self.stack.add_spacing(self.flex_spacing, SpacingKind::Independent); let remaining = self.axes.specialize(Size2D { x: self.part.usable @@ -230,7 +230,7 @@ impl FlexLayouter { while size.x > self.line.usable { if self.stack.space_is_last() { - lerr!("box does not fit into line"); + lr!("box does not fit into line"); } self.stack.finish_space(true); @@ -238,7 +238,7 @@ impl FlexLayouter { } if let LastSpacing::Soft(space) = self.part.space { - self.layout_space(space, SpaceKind::Hard); + self.layout_space(space, SpacingKind::Hard); } let offset = self.part.dimensions.x; @@ -251,8 +251,8 @@ impl FlexLayouter { Ok(()) } - fn layout_space(&mut self, space: Size, kind: SpaceKind) { - if kind == SpaceKind::Soft { + fn layout_space(&mut self, space: Size, kind: SpacingKind) { + if kind == SpacingKind::Soft { if self.part.space != LastSpacing::Forbidden { self.part.space = LastSpacing::Soft(space); } @@ -263,7 +263,7 @@ impl FlexLayouter { self.part.dimensions.x += space; } - if kind == SpaceKind::Hard { + if kind == SpacingKind::Hard { self.part.space = LastSpacing::Forbidden; } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f45ed10af..4304e46e2 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -102,7 +102,7 @@ impl LayoutSpace { } /// The axes along which the content is laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { pub primary: Axis, pub secondary: Axis, @@ -136,17 +136,66 @@ impl LayoutAxes { // at the call site, we still have this second function. self.generalize(size) } -} -/// The two kinds of axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum AxisKind { - Primary, - Secondary, + /// Returns the generic axis kind which is the horizontal axis. + pub fn horizontal(&self) -> GenericAxisKind { + match self.primary.is_horizontal() { + true => GenericAxisKind::Primary, + false => GenericAxisKind::Secondary, + } + } + + /// Returns the generic axis kind which is the vertical axis. + pub fn vertical(&self) -> GenericAxisKind { + self.horizontal().inv() + } + + /// Returns the specific axis kind which is the primary axis. + pub fn primary(&self) -> SpecificAxisKind { + match self.primary.is_horizontal() { + true => SpecificAxisKind::Horizontal, + false => SpecificAxisKind::Vertical, + } + } + + /// Returns the specific axis kind which is the secondary axis. + pub fn secondary(&self) -> SpecificAxisKind { + self.primary().inv() + } + + /// Returns the generic alignment corresponding to left-alignment. + pub fn left(&self) -> Alignment { + let positive = match self.primary.is_horizontal() { + true => self.primary.is_positive(), + false => self.secondary.is_positive(), + }; + + if positive { Alignment::Origin } else { Alignment::End } + } + + /// Returns the generic alignment corresponding to right-alignment. + pub fn right(&self) -> Alignment { + self.left().inv() + } + + /// Returns the generic alignment corresponding to top-alignment. + pub fn top(&self) -> Alignment { + let positive = match self.primary.is_horizontal() { + true => self.secondary.is_positive(), + false => self.primary.is_positive(), + }; + + if positive { Alignment::Origin } else { Alignment::End } + } + + /// Returns the generic alignment corresponding to bottom-alignment. + pub fn bottom(&self) -> Alignment { + self.top().inv() + } } /// Directions along which content is laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Axis { LeftToRight, RightToLeft, @@ -180,8 +229,42 @@ impl Axis { } } +/// The two generic kinds of layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum GenericAxisKind { + Primary, + Secondary, +} + +impl GenericAxisKind { + /// The other axis. + pub fn inv(&self) -> GenericAxisKind { + match self { + GenericAxisKind::Primary => GenericAxisKind::Secondary, + GenericAxisKind::Secondary => GenericAxisKind::Primary, + } + } +} + +/// The two specific kinds of layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum SpecificAxisKind { + Horizontal, + Vertical, +} + +impl SpecificAxisKind { + /// The other axis. + pub fn inv(&self) -> SpecificAxisKind { + match self { + SpecificAxisKind::Horizontal => SpecificAxisKind::Vertical, + SpecificAxisKind::Vertical => SpecificAxisKind::Horizontal, + } + } +} + /// The place to put a layout in a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlignment { pub primary: Alignment, pub secondary: Alignment, @@ -194,13 +277,24 @@ impl LayoutAlignment { } /// Where to align content. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Alignment { Origin, Center, End, } +impl Alignment { + /// The inverse alignment. + pub fn inv(&self) -> Alignment { + match self { + Alignment::Origin => Alignment::End, + Alignment::Center => Alignment::Center, + Alignment::End => Alignment::Origin, + } + } +} + /// The specialized anchor position for an item with the given alignment in a /// container with a given size along the given axis. pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size { @@ -213,7 +307,7 @@ pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size { } /// Whitespace between boxes with different interaction properties. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SpacingKind { /// A hard space consumes surrounding soft spaces and is always layouted. Hard, diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 793c2044d..64823b677 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -108,7 +108,7 @@ impl StackLayouter { // Find the first (sub-)space that fits the layout. while !self.sub.usable.fits(new_size) { if self.space_is_last() && self.space_is_empty() { - lerr!("box does not fit into stack"); + lr!("box does not fit into stack"); } self.finish_space(true); diff --git a/src/layout/text.rs b/src/layout/text.rs index 343127e37..514bfe92a 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -116,6 +116,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.classes.pop(); } - lerr!("no suitable font for character `{}`", c); + lr!("no suitable font for character `{}`", c); } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index efa0c7b74..5fe3b6fd5 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -99,8 +99,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Add(layout) => self.flex.add(layout), AddMultiple(layouts) => self.flex.add_multiple(layouts), AddSpacing(space, kind, axis) => match axis { - AxisKind::Primary => self.flex.add_primary_space(space, kind), - AxisKind::Secondary => self.flex.add_secondary_space(space, kind)?, + GenericAxisKind::Primary => self.flex.add_primary_space(space, kind), + GenericAxisKind::Secondary => self.flex.add_secondary_space(space, kind)?, } FinishLine => self.flex.add_break(), @@ -111,7 +111,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { SetTextStyle(style) => self.style.text = style, SetPageStyle(style) => { if !self.ctx.top_level { - lerr!("page style cannot only be altered in the top-level context"); + lr!("page style cannot only be altered in the top-level context"); } self.style.page = style; diff --git a/src/library/align.rs b/src/library/align.rs index 530120e79..14e329e32 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,155 +1,68 @@ use crate::func::prelude::*; -/// 📐 `align`: Aligns content in different ways. -#[derive(Debug, PartialEq)] -pub struct Align { - body: Option, - positional_1: Option, - positional_2: Option, - primary: Option, - secondary: Option, - horizontal: Option, - vertical: Option, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum AlignSpecifier { - Origin, - Center, - End, - Left, - Right, - Top, - Bottom, -} - function! { - data: Align, + /// `align`: Aligns content along the layouting axes. + #[derive(Debug, PartialEq)] + pub struct Align { + body: Option, + map: ArgMap, + } parse(args, body, ctx) { - let body = parse!(optional: body, ctx); + let mut map = ArgMap::new(); + map.put(Key::First, args.get_pos_opt::()?)?; + map.put(Key::Second, args.get_pos_opt::()?)?; - let mut align = Align { - body, - positional_1: None, - positional_2: None, - primary: None, - secondary: None, - horizontal: None, - vertical: None, - }; + for arg in args.keys() { + let key = match arg.val.0.val { + "horizontal" => Key::Axis(AxisKey::Horizontal), + "vertical" => Key::Axis(AxisKey::Vertical), + "primary" => Key::Axis(AxisKey::Primary), + "secondary" => Key::Axis(AxisKey::Secondary), + _ => pr!("unexpected argument"), + }; - if let Some(arg) = args.get_pos_opt::()? { - align.positional_1 = Some(parse_align_specifier(arg)?); + let value = AlignmentKey::parse(arg.val.1.val)?; + map.add(key, value); } - if let Some(arg) = args.get_pos_opt::()? { - align.positional_2 = Some(parse_align_specifier(arg)?); + Align { + body: parse!(optional: body, ctx), + map, } - - let mut parse_arg = |axis, target: &mut Option| { - Ok(if let Some(arg) = args.get_key_opt::(axis)? { - if target.is_none() { - *target = Some(parse_align_specifier(arg)?); - } else { - perr!("duplicate alignment specification for {} axis", axis); - } - }) - }; - - parse_arg("primary", &mut align.primary)?; - parse_arg("secondary", &mut align.secondary)?; - parse_arg("horizontal", &mut align.horizontal)?; - parse_arg("vertical", &mut align.vertical)?; - - args.done()?; - - Ok(align) } - layout(this, ctx) { - let mut axes = ctx.axes; - let primary_horizontal = axes.primary.is_horizontal(); + layout(self, mut ctx) { + let axes = ctx.axes; + let basic = axes.primary.is_horizontal(); - let mut primary = false; - let mut secondary = false; + let map = self.map.dedup(|key, val| { + let axis = match key { + Key::First => val.axis(axes, GenericAxisKind::Primary), + Key::Second => val.axis(axes, GenericAxisKind::Secondary), + Key::Axis(AxisKey::Primary) => GenericAxisKind::Primary, + Key::Axis(AxisKey::Secondary) => GenericAxisKind::Secondary, + Key::Axis(AxisKey::Horizontal) => axes.horizontal(), + Key::Axis(AxisKey::Vertical) => axes.vertical(), + }; - let mut set_axis = |is_primary: bool, spec: Option| -> LayoutResult<()> { - if let Some(spec) = spec { - let (axis, was_set, name) = match is_primary { - true => (&mut axes.primary, &mut primary, "primary"), - false => (&mut axes.secondary, &mut secondary, "secondary"), - }; + let alignment = val.generic(axes, axis)?; + Ok((key, alignment)) + })?; - if *was_set { - panic!("duplicate alignment for {} axis", name); - } + map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val); + map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val); - *was_set = true; - - let horizontal = axis.is_horizontal(); - let alignment = generic_alignment(spec, horizontal)?; - - if axis.alignment == Alignment::End && alignment == Alignment::Origin { - axis.expand = true; - } - - axis.alignment = alignment; - } - - Ok(()) - }; - - if let Some(spec) = this.positional_1 { - let positional = generic_alignment(spec, primary_horizontal).is_ok(); - set_axis(positional, this.positional_1)?; + match &self.body { + Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)], + None => vec![Command::SetAlignment(ctx.alignment)], } - - if let Some(spec) = this.positional_2 { - let positional = generic_alignment(spec, primary_horizontal).is_ok(); - set_axis(positional, this.positional_2)?; - } - - set_axis(true, this.primary)?; - set_axis(false, this.secondary)?; - set_axis(primary_horizontal, this.horizontal)?; - set_axis(!primary_horizontal, this.vertical)?; - - Ok(match &this.body { - Some(body) => vec![AddMultiple( - layout_tree(body, LayoutContext { - axes, - .. ctx.clone() - })? - )], - None => vec![Command::SetAxes(axes)] - }) } } -fn parse_align_specifier(arg: Spanned<&str>) -> ParseResult { - Ok(match arg.val { - "origin" => AlignSpecifier::Origin, - "center" => AlignSpecifier::Center, - "end" => AlignSpecifier::End, - "left" => AlignSpecifier::Left, - "right" => AlignSpecifier::Right, - "top" => AlignSpecifier::Top, - "bottom" => AlignSpecifier::Bottom, - s => perr!("invalid alignment specifier: {}", s), - }) -} - -fn generic_alignment(spec: AlignSpecifier, horizontal: bool) -> LayoutResult { - use AlignSpecifier::*; - Ok(match (spec, horizontal) { - (Origin, _) | (Left, true) | (Top, false) => Alignment::Origin, - (Center, _) => Alignment::Center, - (End, _) | (Right, true) | (Bottom, false) => Alignment::End, - _ => lerr!( - "invalid alignment specifier `{}` for {} axis", - format!("{:?}", spec).to_lowercase(), - if horizontal { "horizontal" } else { "vertical" }, - ), - }) +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Key { + First, + Second, + Axis(AxisKey), } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 8984417cc..d4e394501 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,37 +1,44 @@ use crate::func::prelude::*; -/// `box`: Layouts content into a box. -#[derive(Debug, PartialEq)] -pub struct Boxed { - body: SyntaxTree, - width: Option, - height: Option, -} - function! { - data: Boxed, + /// `box`: Layouts content into a box. + #[derive(Debug, PartialEq)] + pub struct Boxed { + body: SyntaxTree, + map: ArgMap, + } parse(args, body, ctx) { - let width = args.get_key_opt::("width")?.map(|a| a.val); - let height = args.get_key_opt::("height")?.map(|a| a.val); - args.done()?; + let mut map = ArgMap::new(); - let body = parse!(required: body, ctx); - Ok(Boxed { - body, - width, - height, - }) + for arg in args.keys() { + let key = match arg.val.0.val { + "width" | "w" => AxisKey::Horizontal, + "height" | "h" => AxisKey::Vertical, + "primary-size" => AxisKey::Primary, + "secondary-size" => AxisKey::Secondary, + _ => pr!("unexpected argument"), + }; + + let size = ArgParser::convert::(arg.val.1.val)?; + map.add(key, size); + } + + Boxed { + body: parse!(expected: body, ctx), + map, + } } - layout(this, mut ctx) { - if let Some(width) = this.width { - ctx.spaces[0].dimensions.x = width; - } - if let Some(height) = this.height { - ctx.spaces[0].dimensions.y = height; - } + layout(self, mut ctx) { + let map = self.map.dedup(|key, val| { + Ok((key.specific(ctx.axes), val)) + }); - Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)]) + let mut dimensions = &mut ctx.spaces[0].dimensions; + map.with(AxisKey::Horizontal, |val| dimensions.x = val); + map.with(AxisKey::Vertical, |val| dimensions.y = val); + + vec![AddMultiple(layout_tree(&self.body, ctx)?)] } } diff --git a/src/library/mod.rs b/src/library/mod.rs index adcb974b1..b09303bba 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,34 +1,274 @@ //! The standard library for the _Typst_ language. -use crate::func::Scope; +use crate::func::prelude::*; +use toddle::query::FontClass; -pub_use_mod!(boxed); pub_use_mod!(align); -pub_use_mod!(spacing); -pub_use_mod!(style); -pub_use_mod!(page); +pub_use_mod!(boxed); /// Create a scope with all standard functions. pub fn std() -> Scope { let mut std = Scope::new(); - std.add::("box"); - std.add::("align"); - - std.add::("n"); - std.add::("line.break"); - std.add::("paragraph.break"); - std.add::("page.break"); - std.add::("h"); - std.add::("v"); - - std.add::("bold"); - std.add::("italic"); - std.add::("mono"); - + std.add::("box"); std.add::("page.size"); std.add::("page.margins"); + std.add::("n"); + std.add::("line.break"); + std.add::("par.break"); + std.add::("page.break"); + + std.add_with_metadata::>("spacing", None); + for (name, key) in &[ + ("h", AxisKey::Horizontal), + ("v", AxisKey::Vertical), + ] { + std.add_with_metadata::>(name, Some(*key)); + } + + for (name, class) in &[ + ("bold", FontClass::Bold), + ("italic", FontClass::Italic), + ("mono", FontClass::Monospace), + ] { + std.add_with_metadata::(name, *class); + } + std } + +function! { + /// `line.break`, `n`: Ends the current line. + #[derive(Debug, Default, PartialEq)] + pub struct LineBreak; + + parse(default) + layout() { vec![FinishLine] } +} + +function! { + /// `par.break`: Ends the current paragraph. + /// + /// self has the same effect as two subsequent newlines. + #[derive(Debug, Default, PartialEq)] + pub struct ParBreak; + + parse(default) + layout() { vec![BreakParagraph] } +} + + +function! { + /// `page.break`: Ends the current page. + #[derive(Debug, Default, PartialEq)] + pub struct PageBreak; + + parse(default) + layout() { vec![FinishSpace] } +} + +function! { + /// `page.size`: Set the size of pages. + #[derive(Debug, PartialEq)] + pub struct PageSize { + width: Option, + height: Option, + } + + parse(args, body) { + parse!(forbidden: body); + PageSize { + width: args.get_key_opt::("width")?.map(|a| a.val), + height: args.get_key_opt::("height")?.map(|a| a.val), + } + } + + layout(self, ctx) { + let mut style = ctx.style.page; + if let Some(width) = self.width { style.dimensions.x = width; } + if let Some(height) = self.height { style.dimensions.y = height; } + vec![SetPageStyle(style)] + } +} + +function! { + /// `page.margins`: Set the margins of pages. + #[derive(Debug, PartialEq)] + pub struct PageMargins { + map: ArgMap, + } + + parse(args, body) { + use PaddingKey::*; + use AlignmentKey::*; + + let mut map = ArgMap::new(); + map.add_opt(All, args.get_pos_opt::()?); + + for arg in args.keys() { + let key = match arg.val.0.val { + "horizontal" => Axis(AxisKey::Horizontal), + "vertical" => Axis(AxisKey::Vertical), + "primary" => Axis(AxisKey::Primary), + "secondary" => Axis(AxisKey::Secondary), + + "left" => AxisAligned(AxisKey::Horizontal, Left), + "right" => AxisAligned(AxisKey::Horizontal, Right), + "top" => AxisAligned(AxisKey::Vertical, Top), + "bottom" => AxisAligned(AxisKey::Vertical, Bottom), + + "primary-origin" => AxisAligned(AxisKey::Primary, Origin), + "primary-end" => AxisAligned(AxisKey::Primary, End), + "secondary-origin" => AxisAligned(AxisKey::Secondary, Origin), + "secondary-end" => AxisAligned(AxisKey::Secondary, End), + "horizontal-origin" => AxisAligned(AxisKey::Horizontal, Origin), + "horizontal-end" => AxisAligned(AxisKey::Horizontal, End), + "vertical-origin" => AxisAligned(AxisKey::Vertical, Origin), + "vertical-end" => AxisAligned(AxisKey::Vertical, End), + + _ => pr!("unexpected argument"), + }; + + let size = ArgParser::convert::(arg.val.1.val)?; + map.add(key, size); + } + + parse!(forbidden: body); + PageMargins { map } + } + + layout(self, ctx) { + use PaddingKey::*; + + let axes = ctx.axes; + let map = self.map.dedup(|key, val| { + match key { + All => All, + Axis(axis) => Axis(axis.specific(axes)), + AxisAligned(axis, alignment) => { + let axis = axis.specific(axes); + AxisAligned(axis, alignment.specific(axes, axis)) + } + } + }); + + let style = ctx.style.page; + let padding = &mut style.margins; + + map.with(All, |val| padding.set_all(val)); + map.with(Axis(AxisKey::Horizontal), |val| padding.set_horizontal(val)); + map.with(Axis(AxisKey::Vertical), |val| padding.set_vertical(val)); + + for (key, val) in map.iter() { + if let AxisAligned(axis, alignment) = key { + match alignment { + AlignmentKey::Left => padding.left = val, + AlignmentKey::Right => padding.right = val, + AlignmentKey::Top => padding.top = val, + AlignmentKey::Bottom => padding.bottom = val, + _ => {}, + } + } + } + + vec![SetPageStyle(style)] + } +} + +function! { + /// `spacing`, `h`, `v`: Add spacing along an axis. + #[derive(Debug, PartialEq)] + pub struct Spacing { + axis: AxisKey, + spacing: SpacingValue, + } + + type Meta = Option; + + parse(args, body, _, meta) { + let spacing = if let Some(axis) = meta { + Spacing { + axis, + spacing: SpacingValue::from_expr(args.get_pos::()?)?, + } + } else { + if let Some(arg) = args.get_key_next() { + let axis = match arg.val.0.val { + "horizontal" => AxisKey::Horizontal, + "vertical" => AxisKey::Vertical, + "primary" => AxisKey::Primary, + "secondary" => AxisKey::Secondary, + _ => pr!("unexpected argument"), + }; + + let spacing = SpacingValue::from_expr(arg.val.1.val)?; + Spacing { axis, spacing } + } else { + pr!("expected axis and expression") + } + }; + + parse!(forbidden: body); + spacing + } + + layout(self, ctx) { + let axis = self.axis.generic(ctx.axes); + let spacing = match self.spacing { + SpacingValue::Absolute(s) => s, + SpacingValue::Relative(f) => f * ctx.style.text.font_size, + }; + + vec![AddSpacing(spacing, SpacingKind::Hard, axis)] + } +} + +#[derive(Debug, PartialEq)] +enum SpacingValue { + Absolute(Size), + Relative(f32), +} + +impl SpacingValue { + fn from_expr(expr: Spanned<&Expression>) -> ParseResult { + Ok(match expr.val { + Expression::Size(s) => SpacingValue::Absolute(*s), + Expression::Num(f) => SpacingValue::Relative(*f as f32), + _ => pr!("invalid spacing: expected size or number"), + }) + } +} + +function! { + /// Sets text with a different style. + #[derive(Debug, PartialEq)] + pub struct StyleChange { + body: Option, + class: FontClass, + } + + type Meta = FontClass; + + parse(args, body, ctx, meta) { + StyleChange { + body: parse!(optional: body, ctx), + class: meta, + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + style.toggle_class(self.class); + + match &self.body { + Some(body) => vec![ + SetTextStyle(style), + LayoutTree(body), + SetTextStyle(ctx.style.text.clone()), + ], + None => vec![SetTextStyle(style)] + } + } +} diff --git a/src/library/page.rs b/src/library/page.rs deleted file mode 100644 index e8a808701..000000000 --- a/src/library/page.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::func::prelude::*; - -/// `page.break`: Ends the current page. -#[derive(Debug, PartialEq)] -pub struct PageBreak; - -function! { - data: PageBreak, - parse: plain, - layout(_, _) { Ok(vec![FinishSpace]) } -} - -/// `page.size`: Set the size of pages. -#[derive(Debug, PartialEq)] -pub struct PageSize { - width: Option, - height: Option, -} - -function! { - data: PageSize, - - parse(args, body, _ctx) { - parse!(forbidden: body); - Ok(PageSize { - width: args.get_key_opt::("width")?.map(|a| a.val), - height: args.get_key_opt::("height")?.map(|a| a.val), - }) - } - - layout(this, ctx) { - let mut style = ctx.style.page; - - if let Some(width) = this.width { style.dimensions.x = width; } - if let Some(height) = this.height { style.dimensions.y = height; } - - Ok(vec![SetPageStyle(style)]) - } -} - -/// `page.margins`: Set the margins of pages. -#[derive(Debug, PartialEq)] -pub struct PageMargins { - left: Option, - top: Option, - right: Option, - bottom: Option, -} - -function! { - data: PageMargins, - - parse(args, body, _ctx) { - parse!(forbidden: body); - let default = args.get_pos_opt::()?; - let mut get = |which| { - args.get_key_opt::(which) - .map(|size| size.or(default).map(|a| a.val)) - }; - - Ok(PageMargins { - left: get("left")?, - top: get("top")?, - right: get("right")?, - bottom: get("bottom")?, - }) - } - - layout(this, ctx) { - let mut style = ctx.style.page; - - if let Some(left) = this.left { style.margins.left = left; } - if let Some(top) = this.top { style.margins.top = top; } - if let Some(right) = this.right { style.margins.right = right; } - if let Some(bottom) = this.bottom { style.margins.bottom = bottom; } - - Ok(vec![SetPageStyle(style)]) - } -} diff --git a/src/library/spacing.rs b/src/library/spacing.rs deleted file mode 100644 index 47fe9fff6..000000000 --- a/src/library/spacing.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::func::prelude::*; - -/// `line.break`, `n`: Ends the current line. -#[derive(Debug, PartialEq)] -pub struct LineBreak; - -function! { - data: LineBreak, - parse: plain, - layout(_, _) { Ok(vec![FinishLine]) } -} - -/// `paragraph.break`: Ends the current paragraph. -/// -/// This has the same effect as two subsequent newlines. -#[derive(Debug, PartialEq)] -pub struct ParagraphBreak; - -function! { - data: ParagraphBreak, - parse: plain, - layout(_, _) { Ok(vec![BreakParagraph]) } -} - -macro_rules! space_func { - ($ident:ident, $doc:expr, $var:ident => $command:expr) => ( - #[doc = $doc] - #[derive(Debug, PartialEq)] - pub struct $ident(Spacing); - - function! { - data: $ident, - - parse(args, body, _ctx) { - parse!(forbidden: body); - - let arg = args.get_pos::()?; - let spacing = match arg.val { - Expression::Size(s) => Spacing::Absolute(*s), - Expression::Num(f) => Spacing::Relative(*f as f32), - _ => perr!("invalid spacing, expected size or number"), - }; - - Ok($ident(spacing)) - } - - layout(this, ctx) { - let $var = match this.0 { - Spacing::Absolute(s) => s, - Spacing::Relative(f) => f * ctx.style.text.font_size, - }; - - Ok(vec![$command]) - } - } - ); -} - -/// Absolute or font-relative spacing. -#[derive(Debug, PartialEq)] -enum Spacing { - Absolute(Size), - Relative(f32), -} - -// FIXME: h != primary and v != secondary. -space_func!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.", - space => AddSpacing(space, SpacingKind::Hard, AxisKind::Primary)); - -space_func!(VerticalSpace, "📑 `v`: Adds vertical whitespace.", - space => AddSpacing(space, SpacingKind::Hard, AxisKind::Secondary)); diff --git a/src/library/style.rs b/src/library/style.rs deleted file mode 100644 index b2de19bba..000000000 --- a/src/library/style.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::func::prelude::*; -use toddle::query::FontClass; - -macro_rules! stylefunc { - ($ident:ident, $doc:expr) => ( - #[doc = $doc] - #[derive(Debug, PartialEq)] - pub struct $ident { - body: Option - } - - function! { - data: $ident, - - parse(args, body, ctx) { - args.done()?; - Ok($ident { body: parse!(optional: body, ctx) }) - } - - layout(this, ctx) { - let mut style = ctx.style.text.clone(); - style.toggle_class(FontClass::$ident); - - Ok(match &this.body { - Some(body) => vec![ - SetTextStyle(style), - LayoutTree(body), - SetTextStyle(ctx.style.text.clone()), - ], - None => vec![SetTextStyle(style)] - }) - } - } - ); -} - -stylefunc!(Italic, "`italic`: Sets text in _italics_."); -stylefunc!(Bold, "`bold`: Sets text in **bold**."); -stylefunc!(Monospace, "`mono`: Sets text in `monospace`."); diff --git a/src/size.rs b/src/size.rs index 50b3ace56..a9c3198cf 100644 --- a/src/size.rs +++ b/src/size.rs @@ -51,25 +51,19 @@ impl Size { /// Create a size from an amount of millimeters. #[inline] pub fn mm(mm: f32) -> Size { - Size { - points: 2.83465 * mm, - } + Size { points: 2.83465 * mm } } /// Create a size from an amount of centimeters. #[inline] pub fn cm(cm: f32) -> Size { - Size { - points: 28.3465 * cm, - } + Size { points: 28.3465 * cm } } /// Create a size from an amount of inches. #[inline] pub fn inches(inches: f32) -> Size { - Size { - points: 72.0 * inches, - } + Size { points: 72.0 * inches } } /// Convert this size into points. @@ -188,12 +182,33 @@ impl SizeBox { /// Create a box with all four fields set to the same value `s`. #[inline] - pub fn with_all(s: Size) -> SizeBox { - SizeBox { left: s, top: s, right: s, bottom: s } + pub fn with_all(value: Size) -> SizeBox { + SizeBox { left: value, top: value, right: value, bottom: value } + } + + /// Set the `left` and `right` values. + #[inline] + pub fn set_all(&mut self, value: Size) { + *self = SizeBox::with_all(value); + } + + /// Set the `left` and `right` values. + #[inline] + pub fn set_horizontal(&mut self, value: Size) { + self.left = value; + self.right = value; + } + + /// Set the `top` and `bottom` values. + #[inline] + pub fn set_vertical(&mut self, value: Size) { + self.top = value; + self.bottom = value; } } /// The maximum of two sizes. +#[inline] pub fn max(a: Size, b: Size) -> Size { if a >= b { a @@ -203,6 +218,7 @@ pub fn max(a: Size, b: Size) -> Size { } /// The minimum of two sizes. +#[inline] pub fn min(a: Size, b: Size) -> Size { if a <= b { a diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 9406fdf40..334133171 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -2,13 +2,15 @@ use std::fmt::{self, Display, Formatter}; -use crate::func::Function; +use crate::func::LayoutFunc; use crate::size::Size; mod tokens; #[macro_use] mod parsing; +mod span; +pub use span::{Span, Spanned}; pub use tokens::{tokenize, Tokens}; pub use parsing::{parse, ParseContext, ParseError, ParseResult}; @@ -90,7 +92,13 @@ pub enum Node { #[derive(Debug)] pub struct FuncCall { pub header: Spanned, - pub body: Spanned>, + pub body: Spanned>, +} + +impl PartialEq for FuncCall { + fn eq(&self, other: &FuncCall) -> bool { + (self.header == other.header) && (&self.body == &other.body) + } } /// Contains header information of a function invocation. @@ -134,12 +142,6 @@ pub enum Expression { Bool(bool), } -impl PartialEq for FuncCall { - fn eq(&self, other: &FuncCall) -> bool { - (self.header == other.header) && (&self.body == &other.body) - } -} - impl Display for Expression { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use Expression::*; @@ -154,72 +156,3 @@ impl Display for Expression { } debug_display!(Expression); - -/// Annotates a value with the part of the source code it corresponds to. -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct Spanned { - pub val: T, - pub span: Span, -} - -impl Spanned { - pub fn new(val: T, span: Span) -> Spanned { - Spanned { val, span } - } - - pub fn value(self) -> T { - self.val - } - - pub fn span_map(self, f: F) -> Spanned where F: FnOnce(T) -> U { - Spanned::new(f(self.val), self.span) - } -} - -impl Display for Spanned where T: std::fmt::Debug { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "({:?}:{})", self.val, self.span) - } -} - -debug_display!(Spanned; T where T: std::fmt::Debug); - -/// Describes a slice of source code. -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct Span { - pub start: usize, - pub end: usize, -} - -impl Span { - pub fn new(start: usize, end: usize) -> 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(index: usize) -> Span { - Span { start: index, end: index + 1 } - } - - pub fn pair(&self) -> (usize, usize) { - (self.start, self.end) - } - - pub fn expand(&mut self, other: Span) { - *self = Span::merge(*self, other) - } -} - -impl Display for Span { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[{}, {}]", self.start, self.end) - } -} - -debug_display!(Span); diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 77697161e..b56094e1a 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -2,7 +2,7 @@ use unicode_xid::UnicodeXID; -use crate::func::{Function, Scope}; +use crate::func::{LayoutFunc, Scope}; use crate::size::Size; use super::*; @@ -120,10 +120,10 @@ impl<'s> Parser<'s> { if is_identifier(word) { Ok(Spanned::new(word.to_owned(), span)) } else { - perr!("invalid identifier: '{}'", word); + pr!("invalid identifier: '{}'", word); } } - _ => perr!("expected identifier"), + _ => pr!("expected identifier"), }?; self.skip_white(); @@ -132,7 +132,7 @@ impl<'s> Parser<'s> { let args = match self.tokens.next().map(Spanned::value) { Some(Token::RightBracket) => FuncArgs::new(), Some(Token::Colon) => self.parse_func_args()?, - _ => perr!("expected arguments or closing bracket"), + _ => pr!("expected arguments or closing bracket"), }; let end = self.tokens.string_index(); @@ -158,7 +158,7 @@ impl<'s> Parser<'s> { match self.tokens.next().map(Spanned::value) { Some(Token::Comma) => {}, Some(Token::RightBracket) => break, - _ => perr!("expected comma or closing bracket"), + _ => pr!("expected comma or closing bracket"), } } @@ -183,7 +183,7 @@ impl<'s> Parser<'s> { self.skip_white(); let name = token.span_map(|_| name.to_string()); - let next = self.tokens.next().ok_or_else(|| perr!(@"expected value"))?; + let next = self.tokens.next().ok_or_else(|| pr!(@"expected value"))?; let val = Self::parse_expression(next)?; let span = Span::merge(name.span, val.span); @@ -219,18 +219,19 @@ impl<'s> Parser<'s> { } } - _ => perr!("expected expression"), + _ => pr!("expected expression"), }, token.span)) } /// Parse the body of a function. - fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult>> { + fn parse_func_body(&mut self, header: &FuncHeader) + -> ParseResult>> { // Now we want to parse this function dynamically. let parser = self .ctx .scope .get_parser(&header.name.val) - .ok_or_else(|| perr!(@"unknown function: '{}'", &header.name.val))?; + .ok_or_else(|| pr!(@"unknown function: '{}'", &header.name.val))?; let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket); @@ -298,7 +299,7 @@ impl<'s> Parser<'s> { state = NewlineState::Zero; match token.val { Token::LineComment(_) | Token::BlockComment(_) => self.advance(), - Token::StarSlash => perr!("unexpected end of block comment"), + Token::StarSlash => pr!("unexpected end of block comment"), _ => break, } } @@ -454,7 +455,7 @@ mod tests { #![allow(non_snake_case)] use super::*; - use crate::func::{CommandList, Function, Scope}; + use crate::func::{Commands, Scope}; use crate::layout::{LayoutContext, LayoutResult}; use funcs::*; use Node::{Func as F, Newline as N, Space as S}; @@ -464,37 +465,36 @@ mod tests { mod funcs { use super::*; - /// A testing function which just parses it's body into a syntax tree. - #[derive(Debug)] - pub struct TreeFn(pub SyntaxTree); - function! { - data: TreeFn, + /// A testing function which just parses it's body into a syntax + /// tree. + #[derive(Debug)] + pub struct TreeFn { pub tree: SyntaxTree } - parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) } - layout(_, _) { Ok(vec![]) } + parse(args, body, ctx) { + args.clear(); + TreeFn { + tree: parse!(expected: body, ctx) + } + } + + layout() { vec![] } } impl PartialEq for TreeFn { fn eq(&self, other: &TreeFn) -> bool { - assert_tree_equal(&self.0, &other.0); + assert_tree_equal(&self.tree, &other.tree); true } } - /// A testing function without a body. - #[derive(Debug)] - pub struct BodylessFn; - function! { - data: BodylessFn, + /// A testing function without a body. + #[derive(Debug, Default, PartialEq)] + pub struct BodylessFn; - parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) } - layout(_, _) { Ok(vec![]) } - } - - impl PartialEq for BodylessFn { - fn eq(&self, _: &BodylessFn) -> bool { true } + parse(default) + layout() { vec![] } } } @@ -583,7 +583,7 @@ mod tests { func!(@$name, Box::new(BodylessFn), FuncArgs::new()) ); (name => $name:expr, body => $tree:expr $(,)*) => ( - func!(@$name, Box::new(TreeFn($tree)), FuncArgs::new()) + func!(@$name, Box::new(TreeFn { tree: $tree }), FuncArgs::new()) ); (@$name:expr, $body:expr, $args:expr) => ( FuncCall { @@ -789,7 +789,7 @@ mod tests { assert_eq!(func.header.val.args.positional[0].span.pair(), (13, 16)); assert_eq!(func.header.val.args.positional[1].span.pair(), (18, 23)); - let body = &func.body.val.downcast::().unwrap().0.nodes; + let body = &func.body.val.downcast::().unwrap().tree.nodes; assert_eq!(func.body.span.pair(), (24, 37)); assert_eq!(body[0].span.pair(), (0, 4)); assert_eq!(body[1].span.pair(), (4, 5)); diff --git a/src/syntax/span.rs b/src/syntax/span.rs new file mode 100644 index 000000000..aa4942240 --- /dev/null +++ b/src/syntax/span.rs @@ -0,0 +1,72 @@ +//! Spans map elements to the part of source code they originate from. + +use std::fmt::{self, Display, Formatter}; + +/// Annotates a value with the part of the source code it corresponds to. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Spanned { + pub val: T, + pub span: Span, +} + +impl Spanned { + pub fn new(val: T, span: Span) -> Spanned { + Spanned { val, span } + } + + pub fn value(self) -> T { + self.val + } + + pub fn span_map(self, f: F) -> Spanned where F: FnOnce(T) -> U { + Spanned::new(f(self.val), self.span) + } +} + +impl Display for Spanned where T: std::fmt::Debug { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({:?}:{})", self.val, self.span) + } +} + +debug_display!(Spanned; T where T: std::fmt::Debug); + +/// Describes a slice of source code. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +impl Span { + pub fn new(start: usize, end: usize) -> 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(index: usize) -> Span { + Span { start: index, end: index + 1 } + } + + pub fn pair(&self) -> (usize, usize) { + (self.start, self.end) + } + + pub fn expand(&mut self, other: Span) { + *self = Span::merge(*self, other) + } +} + +impl Display for Span { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}, {}]", self.start, self.end) + } +} + +debug_display!(Span);