From 03fddaf3aea778057aedd74dbcb27bae971ec22f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 24 Jan 2020 12:44:04 +0100 Subject: =?UTF-8?q?Non-fatal=20argument=20parsing=20=F0=9F=8C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/syntax/func/maps.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/syntax/func/maps.rs (limited to 'src/syntax/func/maps.rs') diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs new file mode 100644 index 00000000..452c8ab1 --- /dev/null +++ b/src/syntax/func/maps.rs @@ -0,0 +1,211 @@ +//! Deduplicating maps and keys for argument parsing. + +use std::collections::HashMap; +use std::hash::Hash; +use crate::layout::{LayoutAxes, SpecificAxis, GenericAxis}; +use crate::size::{PSize, ValueBox}; +use super::*; + + +/// A deduplicating map type useful for storing possibly redundant arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct DedupMap where K: Eq { + map: Vec>, +} + +impl DedupMap where K: Eq { + pub fn new() -> DedupMap { + DedupMap { map: vec![] } + } + + pub fn from_iter(errors: &mut Errors, iter: I) -> DedupMap + where I: IntoIterator> { + let mut map = DedupMap::new(); + for Spanned { v: (key, value), span } in iter.into_iter() { + map.insert(errors, key, value, span); + } + map + } + + /// Add a key-value pair. + pub fn insert(&mut self, errors: &mut Errors, key: K, value: V, span: Span) { + if self.map.iter().any(|e| e.v.0 == key) { + errors.push(err!(span; "duplicate argument")); + } else { + self.map.push(Spanned { v: (key, value), span }); + } + } + + /// Add multiple key-value pairs. + pub fn extend(&mut self, errors: &mut Errors, items: I) + where I: IntoIterator> { + for Spanned { v: (k, v), span } in items.into_iter() { + self.insert(errors, k, v, span); + } + } + + /// Get the value corresponding to a key if it is present. + pub fn get(&self, key: K) -> Option<&V> { + self.map.iter().find(|e| e.v.0 == key).map(|e| &e.v.1) + } + + /// Get the value and its span corresponding to a key if it is present. + pub fn get_spanned(&self, key: K) -> Option> { + self.map.iter().find(|e| e.v.0 == key) + .map(|e| Spanned { v: &e.v.1, span: e.span }) + } + + /// 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.get(key) { + callback(value); + } + } + + /// Create a new 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, errors: &mut Errors, mut f: F) -> DedupMap + where F: FnMut(&K, &V) -> (K2, V2), K2: Eq { + let mut map = DedupMap::new(); + + for Spanned { v: (key, value), span } in self.map.iter() { + let (key, value) = f(key, value); + map.insert(errors, key, value, *span); + } + + map + } + + /// Iterate over the (key, value) pairs. + pub fn iter(&self) -> impl Iterator { + self.map.iter().map(|e| &e.v) + } +} + +/// A map for storing a value for two axes given by keyword arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct AxisMap(DedupMap); + +impl AxisMap { + pub fn parse, VT: Value>( + errors: &mut Errors, + object: &mut Object, + ) -> AxisMap { + let values: Vec<_> = object.get_all_spanned::(errors).collect(); + AxisMap(DedupMap::from_iter(errors, values)) + } + + /// Deduplicate from specific or generic to just specific axes. + pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap { + self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone())) + } +} + +/// A map for extracting values for two axes that are given through two +/// positional or keyword arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct PosAxisMap(DedupMap); + +impl PosAxisMap { + pub fn parse, VT: Value>( + errors: &mut Errors, + args: &mut FuncArgs, + ) -> PosAxisMap { + let mut map = DedupMap::new(); + + for &key in &[PosAxisKey::First, PosAxisKey::Second] { + if let Some(value) = args.pos.get::>(errors) { + map.insert(errors, key, value.v, value.span); + } + } + + let keywords: Vec<_> = args.key + .get_all_spanned::(errors) + .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k), v))) + .collect(); + + map.extend(errors, keywords); + + PosAxisMap(map) + } + + /// Deduplicate from positional or specific to generic axes. + pub fn dedup( + &self, + errors: &mut Errors, + axes: LayoutAxes, + mut f: F, + ) -> DedupMap where F: FnMut(&V) -> Option { + self.0.dedup(errors, |key, val| { + (match key { + PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary), + PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary), + PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), + PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, + }, val.clone()) + }) + } +} + +/// A map for extracting padding for a set of specifications given for all +/// sides, opposing sides or single sides. +#[derive(Debug, Clone, PartialEq)] +pub struct PaddingMap(DedupMap, Option>); + +impl PaddingMap { + pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap { + let mut map = DedupMap::new(); + + if let Some(psize) = args.pos.get::>>(errors) { + map.insert(errors, PaddingKey::All, psize.v, psize.span); + } + + let paddings: Vec<_> = args.key + .get_all_spanned::, Defaultable>(errors) + .collect(); + + map.extend(errors, paddings); + + PaddingMap(map) + } + + /// Apply the specified padding on a value box of optional, scalable sizes. + pub fn apply( + &self, + errors: &mut Errors, + axes: LayoutAxes, + padding: &mut ValueBox> + ) { + use PaddingKey::*; + use SpecificAxis::*; + + let map = self.0.dedup(errors, |key, &val| { + (match key { + All => All, + Both(axis) => Both(axis.to_specific(axes)), + Side(axis, alignment) => { + let axis = axis.to_specific(axes); + Side(axis, alignment.to_specific(axes, axis)) + } + }, val) + }); + + map.with(All, |&val| padding.set_all(val)); + map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); + map.with(Both(Vertical), |&val| padding.set_vertical(val)); + + for &(key, val) in map.iter() { + if let Side(_, alignment) = key { + match alignment { + AlignmentValue::Left => padding.left = val, + AlignmentValue::Right => padding.right = val, + AlignmentValue::Top => padding.top = val, + AlignmentValue::Bottom => padding.bottom = val, + _ => {}, + } + } + } + } +} -- cgit v1.2.3