summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-06 13:26:44 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-06 13:26:44 +0100
commit1099330988da78c82c6e155fab88d81fb2f1d4c0 (patch)
tree5a13332defa0ad482ed8669ad52f59a595aa25d6 /src
parentf5b104d0da1c414fb59878d7378add316ee14798 (diff)
Finish consistent map and add two further convenience maps 🗺
Diffstat (limited to 'src')
-rw-r--r--src/func/macros.rs2
-rw-r--r--src/func/map.rs59
-rw-r--r--src/func/mod.rs4
-rw-r--r--src/library/align.rs3
-rw-r--r--src/library/boxed.rs28
-rw-r--r--src/library/keys.rs8
-rw-r--r--src/library/maps.rs178
-rw-r--r--src/library/mod.rs66
-rw-r--r--src/syntax/parsing.rs22
9 files changed, 214 insertions, 156 deletions
diff --git a/src/func/macros.rs b/src/func/macros.rs
index daae2769..17a554cf 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 880fe3e6..00000000
--- 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<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()
- }
-}
diff --git a/src/func/mod.rs b/src/func/mod.rs
index dd2a0ca9..53cfece0 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 eea25dfa..3b06fe2c 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 ef5ae24e..3ec9d001 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<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)?)]
}
}
diff --git a/src/library/keys.rs b/src/library/keys.rs
index df658027..e74027ec 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<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),
diff --git a/src/library/maps.rs b/src/library/maps.rs
new file mode 100644
index 00000000..01bde38b
--- /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<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(())
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9baae044..293f9589 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<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)]
}
}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 8245904c..9fe6c584 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<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.