Finish consistent map and add two further convenience maps 🗺

This commit is contained in:
Laurenz 2019-12-06 13:26:44 +01:00
parent f5b104d0da
commit 1099330988
10 changed files with 225 additions and 157 deletions

View File

@ -29,7 +29,7 @@ macro_rules! function {
function!(@parse $type $meta | $($rest)*); 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)*) => { (@meta $type:ident | $($rest:tt)*) => {
function!(@parse $type () | $($rest)*); function!(@parse $type () | $($rest)*);
}; };

View File

@ -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<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
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<V>) -> 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<Spanned<V>>) -> 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<F>(&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<F, K2, V2>(&self, _f: F) -> ParseResult<ConsistentMap<K2, V2>>
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()
}
}

View File

@ -8,13 +8,9 @@ use self::prelude::*;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod map;
pub use map::ConsistentMap;
/// Useful imports for creating your own functions. /// Useful imports for creating your own functions.
pub mod prelude { pub mod prelude {
pub use super::map::ConsistentMap;
pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands}; pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands};
pub use crate::layout::{ pub use crate::layout::{
layout_tree, Layout, MultiLayout, layout_tree, Layout, MultiLayout,

View File

@ -1,5 +1,6 @@
use crate::func::prelude::*; use crate::func::prelude::*;
use super::keys::*; use super::maps::ConsistentMap;
use super::keys::{AxisKey, AlignmentKey};
function! { function! {
/// `align`: Aligns content along the layouting axes. /// `align`: Aligns content along the layouting axes.

View File

@ -1,43 +1,23 @@
use crate::func::prelude::*; use crate::func::prelude::*;
use super::keys::*; use super::maps::ExtentMap;
function! { function! {
/// `box`: Layouts content into a box. /// `box`: Layouts content into a box.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Boxed { pub struct Boxed {
body: SyntaxTree, body: SyntaxTree,
map: ConsistentMap<AxisKey, Size>, map: ExtentMap,
} }
parse(args, body, ctx) { 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 { Boxed {
body: parse!(expected: body, ctx), body: parse!(expected: body, ctx),
map, map: ExtentMap::new(&mut args, false)?,
} }
} }
layout(self, mut ctx) { layout(self, mut ctx) {
let map = self.map.dedup(|key, val| Ok((key.specific(ctx.axes), val)))?; self.map.apply(ctx.axes, &mut ctx.spaces[0].dimensions)?;
let dimensions = &mut ctx.spaces[0].dimensions;
map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val);
map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val);
vec![AddMultiple(layout_tree(&self.body, ctx)?)] vec![AddMultiple(layout_tree(&self.body, ctx)?)]
} }
} }

View File

@ -1,4 +1,6 @@
use crate::func::prelude::*; //! Keys for the consistent maps.
use super::*;
macro_rules! kind { macro_rules! kind {
($type:ty, $name:expr, $($patterns:tt)*) => { ($type:ty, $name:expr, $($patterns:tt)*) => {
@ -139,7 +141,7 @@ kind!(AlignmentKey, "alignment",
/// An argument key which identifies a margin or padding target. /// 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaddingKey<A> { pub enum PaddingKey<A> {
/// All four sides should have the specified padding. /// All four sides should have the specified padding.
@ -150,7 +152,7 @@ pub enum PaddingKey<A> {
AxisAligned(A, AlignmentKey), AxisAligned(A, AlignmentKey),
} }
kind!(PaddingKey<AxisKey>, "axis or anchor", kind!(PaddingKey<AxisKey>, "axis or side",
"horizontal" => PaddingKey::Axis(AxisKey::Horizontal), "horizontal" => PaddingKey::Axis(AxisKey::Horizontal),
"vertical" => PaddingKey::Axis(AxisKey::Vertical), "vertical" => PaddingKey::Axis(AxisKey::Vertical),
"primary" => PaddingKey::Axis(AxisKey::Primary), "primary" => PaddingKey::Axis(AxisKey::Primary),

178
src/library/maps.rs Normal file
View File

@ -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<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
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<V>) -> 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<Spanned<V>>) -> 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<F>(&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<F, K2, V2>(&self, f: F) -> LayoutResult<ConsistentMap<K2, V2>>
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<AxisKey, Size>);
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<ExtentMap> {
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<PaddingKey<AxisKey>, 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<PaddingMap> {
let mut map = ConsistentMap::new();
map.add_opt_span(PaddingKey::All, args.get_pos_opt::<Size>()?)?;
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(())
}
}

View File

@ -3,11 +3,15 @@
use crate::func::prelude::*; use crate::func::prelude::*;
use toddle::query::FontClass; use toddle::query::FontClass;
use keys::*;
use maps::*;
pub_use_mod!(align); pub_use_mod!(align);
pub_use_mod!(boxed); pub_use_mod!(boxed);
mod keys; pub mod maps;
use keys::*; pub mod keys;
/// Create a scope with all standard functions. /// Create a scope with all standard functions.
pub fn std() -> Scope { pub fn std() -> Scope {
@ -74,22 +78,19 @@ function! {
/// `page.size`: Set the size of pages. /// `page.size`: Set the size of pages.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct PageSize { pub struct PageSize {
width: Option<Size>, map: ExtentMap,
height: Option<Size>,
} }
parse(args, body) { parse(args, body) {
parse!(forbidden: body); parse!(forbidden: body);
PageSize { PageSize {
width: args.get_key_opt::<Size>("width")?.map(|s| s.v), map: ExtentMap::new(&mut args, true)?,
height: args.get_key_opt::<Size>("height")?.map(|s| s.v),
} }
} }
layout(self, ctx) { layout(self, ctx) {
let mut style = ctx.style.page; let mut style = ctx.style.page;
if let Some(width) = self.width { style.dimensions.x = width; } self.map.apply(ctx.axes, &mut style.dimensions)?;
if let Some(height) = self.height { style.dimensions.y = height; }
vec![SetPageStyle(style)] vec![SetPageStyle(style)]
} }
} }
@ -98,58 +99,19 @@ function! {
/// `page.margins`: Set the margins of pages. /// `page.margins`: Set the margins of pages.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct PageMargins { pub struct PageMargins {
map: ConsistentMap<PaddingKey<AxisKey>, Size>, map: PaddingMap,
} }
parse(args, body) { parse(args, body) {
let mut map = ConsistentMap::new();
map.add_opt_span(PaddingKey::All, args.get_pos_opt::<Size>()?)?;
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); parse!(forbidden: body);
PageMargins { map } PageMargins {
map: PaddingMap::new(&mut args, true)?,
}
} }
layout(self, ctx) { 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 mut style = ctx.style.page;
let padding = &mut style.margins; self.map.apply(ctx.axes, &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,
_ => {},
}
}
}
vec![SetPageStyle(style)] vec![SetPageStyle(style)]
} }
} }

View File

@ -1,7 +1,7 @@
//! Parsing of token streams into syntax trees. //! Parsing of token streams into syntax trees.
use crate::TypesetResult; use crate::TypesetResult;
use crate::func::{LayoutFunc, Scope}; use crate::func::Scope;
use crate::size::Size; use crate::size::Size;
use super::*; use super::*;
@ -104,7 +104,7 @@ impl<'s> Parser<'s> {
_ => error!("expected arguments or closing bracket"), _ => 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(); span.end = self.tokens.string_index();
// Finally this function is parsed to the end. // Finally this function is parsed to the end.
@ -132,16 +132,15 @@ impl<'s> Parser<'s> {
/// Parse the arguments to a function. /// Parse the arguments to a function.
fn parse_func_args(&mut self) -> ParseResult<FuncArgs> { fn parse_func_args(&mut self) -> ParseResult<FuncArgs> {
let mut pos = Vec::new(); let mut args = FuncArgs::new();
let mut key = Vec::new();
loop { loop {
self.skip_white(); self.skip_white();
match self.parse_func_arg()? { match self.parse_func_arg()? {
Some(DynArg::Pos(arg)) => pos.push(arg), Some(DynArg::Pos(arg)) => args.add_pos(arg),
Some(DynArg::Key(arg)) => key.push(arg), Some(DynArg::Key(arg)) => args.add_key(arg),
_ => {}, None => {},
} }
match self.tokens.next().map(Spanned::value) { 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. /// Parse one argument to a function.
@ -198,8 +197,7 @@ impl<'s> Parser<'s> {
} }
/// Parse a function call. /// Parse a function call.
fn parse_func_call(&mut self, name: Spanned<Ident>, args: FuncArgs) fn parse_func_call(&mut self, name: Spanned<Ident>, args: FuncArgs) -> ParseResult<FuncCall> {
-> ParseResult<Box<dyn LayoutFunc>> {
// Now we want to parse this function dynamically. // Now we want to parse this function dynamically.
let parser = self let parser = self
.ctx .ctx
@ -210,7 +208,7 @@ impl<'s> Parser<'s> {
let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket); let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket);
// Do the parsing dependent on whether the function has a body. // Do the parsing dependent on whether the function has a body.
Ok(if has_body { Ok(FuncCall(if has_body {
self.advance(); self.advance();
// Find out the string which makes the body of this function. // Find out the string which makes the body of this function.
@ -235,7 +233,7 @@ impl<'s> Parser<'s> {
body body
} else { } else {
parser(args, None, self.ctx)? parser(args, None, self.ctx)?
}) }))
} }
/// Parse an expression. /// Parse an expression.

View File

@ -29,6 +29,8 @@ fn main() {
fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap(); fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap();
fs::create_dir_all(format!("{}/pdf", 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() { for entry in fs::read_dir("tests/layouts/").unwrap() {
let path = entry.unwrap().path(); let path = entry.unwrap().path();
@ -49,9 +51,17 @@ fn main() {
let mut src = String::new(); let mut src = String::new();
file.read_to_string(&mut src).unwrap(); 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. /// Create a _PDF_ with a name from the source code.