Finish consistent map and add two further convenience maps 🗺
This commit is contained in:
parent
f5b104d0da
commit
1099330988
@ -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)*);
|
||||
};
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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<AxisKey, Size>,
|
||||
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)?)]
|
||||
}
|
||||
}
|
||||
|
@ -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<A> {
|
||||
/// All four sides should have the specified padding.
|
||||
@ -150,7 +152,7 @@ pub enum PaddingKey<A> {
|
||||
AxisAligned(A, AlignmentKey),
|
||||
}
|
||||
|
||||
kind!(PaddingKey<AxisKey>, "axis or anchor",
|
||||
kind!(PaddingKey<AxisKey>, "axis or side",
|
||||
"horizontal" => PaddingKey::Axis(AxisKey::Horizontal),
|
||||
"vertical" => PaddingKey::Axis(AxisKey::Vertical),
|
||||
"primary" => PaddingKey::Axis(AxisKey::Primary),
|
||||
|
178
src/library/maps.rs
Normal file
178
src/library/maps.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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<Size>,
|
||||
height: Option<Size>,
|
||||
map: ExtentMap,
|
||||
}
|
||||
|
||||
parse(args, body) {
|
||||
parse!(forbidden: body);
|
||||
PageSize {
|
||||
width: args.get_key_opt::<Size>("width")?.map(|s| s.v),
|
||||
height: args.get_key_opt::<Size>("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<PaddingKey<AxisKey>, Size>,
|
||||
map: PaddingMap,
|
||||
}
|
||||
|
||||
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);
|
||||
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)]
|
||||
}
|
||||
}
|
||||
|
@ -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<FuncArgs> {
|
||||
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<Ident>, args: FuncArgs)
|
||||
-> ParseResult<Box<dyn LayoutFunc>> {
|
||||
fn parse_func_call(&mut self, name: Spanned<Ident>, args: FuncArgs) -> ParseResult<FuncCall> {
|
||||
// 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.
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user