diff --git a/src/func/macros.rs b/src/func/macros.rs index daae27693..17a554cf0 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -29,7 +29,7 @@ macro_rules! function { function!(@parse $type $meta | $($rest)*); }; - // Set the metadata to `()` if there is not type definition. + // Set the metadata to `()` if there is no type definition. (@meta $type:ident | $($rest:tt)*) => { function!(@parse $type () | $($rest)*); }; diff --git a/src/func/map.rs b/src/func/map.rs deleted file mode 100644 index 880fe3e61..000000000 --- a/src/func/map.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! A deduplicating map. - -use std::collections::HashMap; -use std::hash::Hash; - -use crate::syntax::{Spanned, ParseResult}; - -/// A deduplicating map type useful for storing possibly redundant arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct ConsistentMap where K: Hash + Eq { - map: HashMap, -} - -impl ConsistentMap where K: Hash + Eq { - pub fn new() -> ConsistentMap { - ConsistentMap { map: HashMap::new() } - } - - /// Add a key-value pair. - pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { - self.map.insert(key, value); - // TODO - Ok(()) - } - - /// Add a key-value pair if the value is not `None`. - pub fn add_opt(&mut self, key: K, value: Option) -> ParseResult<()> { - Ok(if let Some(value) = value { - self.add(key, value)?; - }) - } - - /// Add a key-spanned-value pair the value is not `None`. - pub fn add_opt_span(&mut self, key: K, value: Option>) -> ParseResult<()> { - Ok(if let Some(spanned) = value { - self.add(key, spanned.v)?; - }) - } - - /// Call a function with the value if the key is present. - pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { - if let Some(value) = self.map.get(&key) { - callback(value); - } - } - - /// Create a new consistent map where keys and values are mapped to new - /// keys and values. Returns an error if a new key is duplicate. - pub fn dedup(&self, _f: F) -> ParseResult> - where F: FnOnce(K, V) -> ParseResult<(K2, V2)>, K2: Hash + Eq { - // TODO - Ok(ConsistentMap::new()) - } - - /// Iterate over the (key, value) pairs. - pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { - self.map.iter() - } -} diff --git a/src/func/mod.rs b/src/func/mod.rs index dd2a0ca90..53cfece0c 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -8,13 +8,9 @@ use self::prelude::*; #[macro_use] mod macros; -mod map; - -pub use map::ConsistentMap; /// Useful imports for creating your own functions. pub mod prelude { - pub use super::map::ConsistentMap; pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands}; pub use crate::layout::{ layout_tree, Layout, MultiLayout, diff --git a/src/library/align.rs b/src/library/align.rs index eea25dfab..3b06fe2c3 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,5 +1,6 @@ use crate::func::prelude::*; -use super::keys::*; +use super::maps::ConsistentMap; +use super::keys::{AxisKey, AlignmentKey}; function! { /// `align`: Aligns content along the layouting axes. diff --git a/src/library/boxed.rs b/src/library/boxed.rs index ef5ae24e1..3ec9d0016 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -1,43 +1,23 @@ use crate::func::prelude::*; -use super::keys::*; +use super::maps::ExtentMap; function! { /// `box`: Layouts content into a box. #[derive(Debug, PartialEq)] pub struct Boxed { body: SyntaxTree, - map: ConsistentMap, + map: ExtentMap, } parse(args, body, ctx) { - let mut map = ConsistentMap::new(); - - for arg in args.keys() { - let key = match arg.v.key.v.0.as_str() { - "width" | "w" => AxisKey::Horizontal, - "height" | "h" => AxisKey::Vertical, - "primary-size" => AxisKey::Primary, - "secondary-size" => AxisKey::Secondary, - _ => error!(unexpected_argument), - }; - - let size = Size::from_expr(arg.v.value)?; - map.add(key, size)?; - } - Boxed { body: parse!(expected: body, ctx), - map, + map: ExtentMap::new(&mut args, false)?, } } layout(self, mut ctx) { - let map = self.map.dedup(|key, val| Ok((key.specific(ctx.axes), val)))?; - - let dimensions = &mut ctx.spaces[0].dimensions; - map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val); - map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val); - + self.map.apply(ctx.axes, &mut ctx.spaces[0].dimensions)?; vec![AddMultiple(layout_tree(&self.body, ctx)?)] } } diff --git a/src/library/keys.rs b/src/library/keys.rs index df6580279..e74027ec5 100644 --- a/src/library/keys.rs +++ b/src/library/keys.rs @@ -1,4 +1,6 @@ -use crate::func::prelude::*; +//! Keys for the consistent maps. + +use super::*; macro_rules! kind { ($type:ty, $name:expr, $($patterns:tt)*) => { @@ -139,7 +141,7 @@ kind!(AlignmentKey, "alignment", /// An argument key which identifies a margin or padding target. /// -/// A is the axis type used. +/// A is the used axis type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PaddingKey { /// All four sides should have the specified padding. @@ -150,7 +152,7 @@ pub enum PaddingKey { AxisAligned(A, AlignmentKey), } -kind!(PaddingKey, "axis or anchor", +kind!(PaddingKey, "axis or side", "horizontal" => PaddingKey::Axis(AxisKey::Horizontal), "vertical" => PaddingKey::Axis(AxisKey::Vertical), "primary" => PaddingKey::Axis(AxisKey::Primary), diff --git a/src/library/maps.rs b/src/library/maps.rs new file mode 100644 index 000000000..01bde38b4 --- /dev/null +++ b/src/library/maps.rs @@ -0,0 +1,178 @@ +//! Deduplicating maps for argument parsing. + +use std::collections::HashMap; +use std::hash::Hash; + +use super::*; + +/// A deduplicating map type useful for storing possibly redundant arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct ConsistentMap where K: Hash + Eq { + map: HashMap, +} + +impl ConsistentMap where K: Hash + Eq { + pub fn new() -> ConsistentMap { + ConsistentMap { map: HashMap::new() } + } + + /// Add a key-value pair. + pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { + match self.map.insert(key, value) { + Some(_) => error!("duplicate arguments"), + None => Ok(()) + } + } + + /// Add a key-value pair if the value is not `None`. + pub fn add_opt(&mut self, key: K, value: Option) -> ParseResult<()> { + Ok(if let Some(value) = value { + self.add(key, value)?; + }) + } + + /// Add a key-spanned-value pair the value is not `None`. + pub fn add_opt_span(&mut self, key: K, value: Option>) -> ParseResult<()> { + Ok(if let Some(spanned) = value { + self.add(key, spanned.v)?; + }) + } + + /// Call a function with the value if the key is present. + pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { + if let Some(value) = self.map.get(&key) { + callback(value); + } + } + + /// Create a new consistent map where keys and values are mapped to new keys + /// and values. + /// + /// Returns an error if a new key is duplicate. + pub fn dedup(&self, f: F) -> LayoutResult> + where + F: Fn(&K, &V) -> ParseResult<(K2, V2)>, + K2: Hash + Eq + { + let mut map = ConsistentMap::new(); + + for (key, value) in self.map.iter() { + let (key, value) = f(key, value)?; + map.add(key, value)?; + } + + Ok(map) + } + + /// Iterate over the (key, value) pairs. + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { + self.map.iter() + } +} + +/// A map for storing extents along axes. +#[derive(Debug, Clone, PartialEq)] +pub struct ExtentMap(ConsistentMap); + +impl ExtentMap { + /// Parse an extent map from the function args. + /// + /// If `enforce` is true other arguments will create an error, otherwise + /// they are left intact. + pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult { + let mut map = ConsistentMap::new(); + + for arg in args.keys() { + let key = match arg.v.key.v.0.as_str() { + "width" | "w" => AxisKey::Horizontal, + "height" | "h" => AxisKey::Vertical, + "primary-size" => AxisKey::Primary, + "secondary-size" => AxisKey::Secondary, + _ => if enforce { + error!("expected dimension") + } else { + args.add_key(arg); + continue; + } + }; + + let size = Size::from_expr(arg.v.value)?; + map.add(key, size)?; + } + + Ok(ExtentMap(map)) + } + + /// Map from any axis key to the specific axis kind. + pub fn apply(&self, axes: LayoutAxes, dimensions: &mut Size2D) -> LayoutResult<()> { + let map = self.0.dedup(|key, &val| Ok((key.specific(axes), val)))?; + + map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val); + map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val); + + Ok(()) + } +} + +/// A map for storing padding at sides. +#[derive(Debug, Clone, PartialEq)] +pub struct PaddingMap(ConsistentMap, Size>); + +impl PaddingMap { + /// Parse an extent map from the function args. + /// + /// If `enforce` is true other arguments will create an error, otherwise + /// they are left intact. + pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult { + let mut map = ConsistentMap::new(); + + map.add_opt_span(PaddingKey::All, args.get_pos_opt::()?)?; + + for arg in args.keys() { + let key = match PaddingKey::from_ident(&arg.v.key) { + Ok(key) => key, + e => if enforce { e? } else { args.add_key(arg); continue; } + }; + + let size = Size::from_expr(arg.v.value)?; + + map.add(key, size)?; + } + + Ok(PaddingMap(map)) + } + + /// Map from any axis key to the specific axis kind. + pub fn apply(&self, axes: LayoutAxes, padding: &mut SizeBox) -> LayoutResult<()> { + use PaddingKey::*; + + let map = self.0.dedup(|key, &val| { + Ok((match key { + All => All, + Axis(axis) => Axis(axis.specific(axes)), + AxisAligned(axis, alignment) => { + let axis = axis.specific(axes); + AxisAligned(axis, alignment.specific(axes, axis)) + } + }, val)) + })?; + + map.with(All, |&val| padding.set_all(val)); + map.with(Axis(SpecificAxisKind::Horizontal), |&val| padding.set_horizontal(val)); + map.with(Axis(SpecificAxisKind::Vertical), |&val| padding.set_vertical(val)); + + for (key, &val) in map.iter() { + if let AxisAligned(_, alignment) = key { + match alignment { + AlignmentKey::Left => padding.left = val, + AlignmentKey::Right => padding.right = val, + AlignmentKey::Top => padding.top = val, + AlignmentKey::Bottom => padding.bottom = val, + _ => {}, + } + } + } + + Ok(()) + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 9baae044e..293f95891 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -3,11 +3,15 @@ use crate::func::prelude::*; use toddle::query::FontClass; +use keys::*; +use maps::*; + pub_use_mod!(align); pub_use_mod!(boxed); -mod keys; -use keys::*; +pub mod maps; +pub mod keys; + /// Create a scope with all standard functions. pub fn std() -> Scope { @@ -74,22 +78,19 @@ function! { /// `page.size`: Set the size of pages. #[derive(Debug, PartialEq)] pub struct PageSize { - width: Option, - height: Option, + map: ExtentMap, } parse(args, body) { parse!(forbidden: body); PageSize { - width: args.get_key_opt::("width")?.map(|s| s.v), - height: args.get_key_opt::("height")?.map(|s| s.v), + map: ExtentMap::new(&mut args, true)?, } } 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; } + self.map.apply(ctx.axes, &mut style.dimensions)?; vec![SetPageStyle(style)] } } @@ -98,58 +99,19 @@ function! { /// `page.margins`: Set the margins of pages. #[derive(Debug, PartialEq)] pub struct PageMargins { - map: ConsistentMap, Size>, + map: PaddingMap, } parse(args, body) { - let mut map = ConsistentMap::new(); - map.add_opt_span(PaddingKey::All, args.get_pos_opt::()?)?; - - for arg in args.keys() { - let key = PaddingKey::from_ident(&arg.v.key)?; - let size = Size::from_expr(arg.v.value)?; - - map.add(key, size)?; - } - parse!(forbidden: body); - PageMargins { map } + PageMargins { + map: PaddingMap::new(&mut args, true)?, + } } layout(self, ctx) { - use PaddingKey::*; - - let axes = ctx.axes; - let map = self.map.dedup(|key, val| { - Ok((match key { - All => All, - Axis(axis) => Axis(axis.specific(axes)), - AxisAligned(axis, alignment) => { - let axis = axis.specific(axes); - AxisAligned(axis, alignment.specific(axes, axis)) - } - }, val)) - })?; - let mut style = ctx.style.page; - let padding = &mut style.margins; - - map.with(All, |&val| padding.set_all(val)); - map.with(Axis(SpecificAxisKind::Horizontal), |&val| padding.set_horizontal(val)); - map.with(Axis(SpecificAxisKind::Vertical), |&val| padding.set_vertical(val)); - - for (key, &val) in map.iter() { - if let AxisAligned(_, alignment) = key { - match alignment { - AlignmentKey::Left => padding.left = val, - AlignmentKey::Right => padding.right = val, - AlignmentKey::Top => padding.top = val, - AlignmentKey::Bottom => padding.bottom = val, - _ => {}, - } - } - } - + self.map.apply(ctx.axes, &mut style.margins)?; vec![SetPageStyle(style)] } } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 8245904c7..9fe6c5846 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,7 +1,7 @@ //! Parsing of token streams into syntax trees. use crate::TypesetResult; -use crate::func::{LayoutFunc, Scope}; +use crate::func::Scope; use crate::size::Size; use super::*; @@ -104,7 +104,7 @@ impl<'s> Parser<'s> { _ => error!("expected arguments or closing bracket"), }; - let func = FuncCall(self.parse_func_call(name, args)?); + let func = self.parse_func_call(name, args)?; span.end = self.tokens.string_index(); // Finally this function is parsed to the end. @@ -132,16 +132,15 @@ impl<'s> Parser<'s> { /// Parse the arguments to a function. fn parse_func_args(&mut self) -> ParseResult { - let mut pos = Vec::new(); - let mut key = Vec::new(); + let mut args = FuncArgs::new(); loop { self.skip_white(); match self.parse_func_arg()? { - Some(DynArg::Pos(arg)) => pos.push(arg), - Some(DynArg::Key(arg)) => key.push(arg), - _ => {}, + Some(DynArg::Pos(arg)) => args.add_pos(arg), + Some(DynArg::Key(arg)) => args.add_key(arg), + None => {}, } match self.tokens.next().map(Spanned::value) { @@ -151,7 +150,7 @@ impl<'s> Parser<'s> { } } - Ok(FuncArgs { pos, key }) + Ok(args) } /// Parse one argument to a function. @@ -198,8 +197,7 @@ impl<'s> Parser<'s> { } /// Parse a function call. - fn parse_func_call(&mut self, name: Spanned, args: FuncArgs) - -> ParseResult> { + fn parse_func_call(&mut self, name: Spanned, args: FuncArgs) -> ParseResult { // Now we want to parse this function dynamically. let parser = self .ctx @@ -210,7 +208,7 @@ impl<'s> Parser<'s> { let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket); // Do the parsing dependent on whether the function has a body. - Ok(if has_body { + Ok(FuncCall(if has_body { self.advance(); // Find out the string which makes the body of this function. @@ -235,7 +233,7 @@ impl<'s> Parser<'s> { body } else { parser(args, None, self.ctx)? - }) + })) } /// Parse an expression. diff --git a/tests/layouting.rs b/tests/layouting.rs index 7f6014e8d..001ff45a6 100644 --- a/tests/layouting.rs +++ b/tests/layouting.rs @@ -29,6 +29,8 @@ fn main() { fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap(); fs::create_dir_all(format!("{}/pdf", CACHE_DIR)).unwrap(); + let mut failed = 0; + for entry in fs::read_dir("tests/layouts/").unwrap() { let path = entry.unwrap().path(); @@ -49,9 +51,17 @@ fn main() { let mut src = String::new(); file.read_to_string(&mut src).unwrap(); - test(name, &src); + if std::panic::catch_unwind(|| test(name, &src)).is_err() { + failed += 1; + println!(); + } } } + + if failed > 0 { + println!("{} tests failed.", failed); + std::process::exit(-1); + } } /// Create a _PDF_ with a name from the source code.