summaryrefslogtreecommitdiff
path: root/src/syntax/func
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-24 12:44:04 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-24 12:44:04 +0100
commit03fddaf3aea778057aedd74dbcb27bae971ec22f (patch)
tree37e3136e29e0e5d69ec8f56e43d156739d2931ab /src/syntax/func
parent78da2bdd5d77d1b8572e5e9da119bfa68127a3fa (diff)
Non-fatal argument parsing 🌋
Diffstat (limited to 'src/syntax/func')
-rw-r--r--src/syntax/func/keys.rs121
-rw-r--r--src/syntax/func/maps.rs211
-rw-r--r--src/syntax/func/mod.rs147
-rw-r--r--src/syntax/func/values.rs223
4 files changed, 702 insertions, 0 deletions
diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs
new file mode 100644
index 00000000..dff97bde
--- /dev/null
+++ b/src/syntax/func/keys.rs
@@ -0,0 +1,121 @@
+use crate::layout::prelude::*;
+use super::*;
+
+use AxisKey::*;
+use PaddingKey::*;
+use AlignmentValue::*;
+
+
+pub trait Key {
+ type Output: Eq;
+
+ fn parse(key: Spanned<&str>) -> Option<Self::Output>;
+}
+
+impl<K: Key> Key for Spanned<K> {
+ type Output = Spanned<K::Output>;
+
+ fn parse(key: Spanned<&str>) -> Option<Self::Output> {
+ K::parse(key).map(|v| Spanned { v, span: key.span })
+ }
+}
+
+macro_rules! key {
+ ($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => {
+ impl Key for $type {
+ type Output = $output;
+
+ fn parse(key: Spanned<&str>) -> Option<Self::Output> {
+ match key.v {
+ $($($p)|* => Some($r)),*,
+ other => None,
+ }
+ }
+ }
+ };
+}
+
+/// An argument key which identifies a layouting axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AxisKey {
+ Generic(GenericAxis),
+ Specific(SpecificAxis),
+}
+
+impl AxisKey {
+ /// The generic version of this axis key in the given system of axes.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis {
+ match self {
+ Generic(axis) => axis,
+ Specific(axis) => axis.to_generic(axes),
+ }
+ }
+
+ /// The specific version of this axis key in the given system of axes.
+ pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis {
+ match self {
+ Generic(axis) => axis.to_specific(axes),
+ Specific(axis) => axis,
+ }
+ }
+}
+
+key!(AxisKey, Self,
+ "horizontal" | "h" => Specific(Horizontal),
+ "vertical" | "v" => Specific(Vertical),
+ "primary" | "p" => Generic(Primary),
+ "secondary" | "s" => Generic(Secondary),
+);
+
+pub struct ExtentKey;
+
+key!(ExtentKey, AxisKey,
+ "width" | "w" => Specific(Horizontal),
+ "height" | "h" => Specific(Vertical),
+ "primary-size" | "ps" => Generic(Primary),
+ "secondary-size" | "ss" => Generic(Secondary),
+);
+
+/// An argument key which identifies an axis, but allows for positional
+/// arguments with unspecified axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PosAxisKey {
+ /// The first positional argument.
+ First,
+ /// The second positional argument.
+ Second,
+ /// An axis keyword argument.
+ Keyword(AxisKey),
+}
+
+/// An argument key which identifies a margin or padding target.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PaddingKey<Axis> {
+ /// All four sides should have the specified padding.
+ All,
+ /// Both sides of the given axis should have the specified padding.
+ Both(Axis),
+ /// Only the given side of the given axis should have the specified padding.
+ Side(Axis, AlignmentValue),
+}
+
+key!(PaddingKey<AxisKey>, Self,
+ "horizontal" | "h" => Both(Specific(Horizontal)),
+ "vertical" | "v" => Both(Specific(Vertical)),
+ "primary" | "p" => Both(Generic(Primary)),
+ "secondary" | "s" => Both(Generic(Secondary)),
+
+ "left" => Side(Specific(Horizontal), Left),
+ "right" => Side(Specific(Horizontal), Right),
+ "top" => Side(Specific(Vertical), Top),
+ "bottom" => Side(Specific(Vertical), Bottom),
+
+ "primary-origin" => Side(Generic(Primary), Align(Origin)),
+ "primary-end" => Side(Generic(Primary), Align(End)),
+ "secondary-origin" => Side(Generic(Secondary), Align(Origin)),
+ "secondary-end" => Side(Generic(Secondary), Align(End)),
+ "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)),
+ "horizontal-end" => Side(Specific(Horizontal), Align(End)),
+ "vertical-origin" => Side(Specific(Vertical), Align(Origin)),
+ "vertical-end" => Side(Specific(Vertical), Align(End)),
+);
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<K, V> where K: Eq {
+ map: Vec<Spanned<(K, V)>>,
+}
+
+impl<K, V> DedupMap<K, V> where K: Eq {
+ pub fn new() -> DedupMap<K, V> {
+ DedupMap { map: vec![] }
+ }
+
+ pub fn from_iter<I>(errors: &mut Errors, iter: I) -> DedupMap<K, V>
+ where I: IntoIterator<Item=Spanned<(K, V)>> {
+ 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<I>(&mut self, errors: &mut Errors, items: I)
+ where I: IntoIterator<Item=Spanned<(K, V)>> {
+ 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<Spanned<&V>> {
+ 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<F>(&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<F, K2, V2>(&self, errors: &mut Errors, mut f: F) -> DedupMap<K2, V2>
+ 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<Item=&(K, V)> {
+ 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<V>(DedupMap<AxisKey, V>);
+
+impl<V: Clone> AxisMap<V> {
+ pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
+ errors: &mut Errors,
+ object: &mut Object,
+ ) -> AxisMap<V> {
+ let values: Vec<_> = object.get_all_spanned::<KT, VT>(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<SpecificAxis, V> {
+ 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<V>(DedupMap<PosAxisKey, V>);
+
+impl<V: Clone> PosAxisMap<V> {
+ pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
+ errors: &mut Errors,
+ args: &mut FuncArgs,
+ ) -> PosAxisMap<V> {
+ let mut map = DedupMap::new();
+
+ for &key in &[PosAxisKey::First, PosAxisKey::Second] {
+ if let Some(value) = args.pos.get::<Spanned<VT>>(errors) {
+ map.insert(errors, key, value.v, value.span);
+ }
+ }
+
+ let keywords: Vec<_> = args.key
+ .get_all_spanned::<KT, VT>(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<F>(
+ &self,
+ errors: &mut Errors,
+ axes: LayoutAxes,
+ mut f: F,
+ ) -> DedupMap<GenericAxis, V> where F: FnMut(&V) -> Option<GenericAxis> {
+ 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<PaddingKey<AxisKey>, Option<PSize>>);
+
+impl PaddingMap {
+ pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap {
+ let mut map = DedupMap::new();
+
+ if let Some(psize) = args.pos.get::<Spanned<Defaultable<PSize>>>(errors) {
+ map.insert(errors, PaddingKey::All, psize.v, psize.span);
+ }
+
+ let paddings: Vec<_> = args.key
+ .get_all_spanned::<PaddingKey<AxisKey>, Defaultable<PSize>>(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<Option<PSize>>
+ ) {
+ 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,
+ _ => {},
+ }
+ }
+ }
+ }
+}
diff --git a/src/syntax/func/mod.rs b/src/syntax/func/mod.rs
new file mode 100644
index 00000000..b6691ab5
--- /dev/null
+++ b/src/syntax/func/mod.rs
@@ -0,0 +1,147 @@
+use super::*;
+
+pub_use_mod!(maps);
+pub_use_mod!(keys);
+pub_use_mod!(values);
+
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct FuncHeader {
+ pub name: Spanned<Ident>,
+ pub args: FuncArgs,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct FuncArgs {
+ pub pos: Tuple,
+ pub key: Object,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Arg {
+ Pos(Spanned<Expr>),
+ Key(Pair),
+}
+
+impl Arg {
+ /// The span or the value or combined span of key and value.
+ pub fn span(&self) -> Span {
+ match self {
+ Arg::Pos(item) => item.span,
+ Arg::Key(Pair { key, value }) => Span::merge(key.span, value.span),
+ }
+ }
+}
+
+impl FuncArgs {
+ pub fn new() -> FuncArgs {
+ FuncArgs {
+ pos: Tuple::new(),
+ key: Object::new(),
+ }
+ }
+
+ /// Add an argument.
+ pub fn add(&mut self, arg: Arg) {
+ match arg {
+ Arg::Pos(item) => self.add_pos(item),
+ Arg::Key(pair) => self.add_key_pair(pair),
+ }
+ }
+
+ /// Add a positional argument.
+ pub fn add_pos(&mut self, item: Spanned<Expr>) {
+ self.pos.add(item);
+ }
+
+ /// Add a keyword argument.
+ pub fn add_key(&mut self, key: Spanned<Ident>, value: Spanned<Expr>) {
+ self.key.add(key, value);
+ }
+
+ /// Add a keyword argument from an existing pair.
+ pub fn add_key_pair(&mut self, pair: Pair) {
+ self.key.add_pair(pair);
+ }
+
+ pub fn into_iter(self) -> impl Iterator<Item=Arg> {
+ self.pos.items.into_iter().map(|item| Arg::Pos(item))
+ .chain(self.key.pairs.into_iter().map(|pair| Arg::Key(pair)))
+ }
+
+ // /// Force-extract the first positional argument.
+ // pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
+ // expect(self.get_pos_opt())
+ // }
+
+ // /// Extract the first positional argument.
+ // pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<E>> {
+ // Ok(if !self.positional.items.is_empty() {
+ // let spanned = self.positional.items.remove(0);
+ // Some(E::from_expr(spanned)?)
+ // } else {
+ // None
+ // })
+ // }
+
+ // /// Force-extract a keyword argument.
+ // pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<E> {
+ // expect(self.get_key_opt(name))
+ // }
+
+ // /// Extract a keyword argument.
+ // pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<E>> {
+ // self.keyword.pairs.iter()
+ // .position(|p| p.key.v.0 == name)
+ // .map(|index| {
+ // let value = self.keyword.pairs.swap_remove(index).value;
+ // E::from_expr(value)
+ // })
+ // .transpose()
+ // }
+
+ // /// Iterator over positional arguments.
+ // pub fn iter_pos(&mut self) -> std::vec::IntoIter<Spanned<Expr>> {
+ // let tuple = std::mem::replace(&mut self.positional, Tuple::new());
+ // tuple.items.into_iter()
+ // }
+
+ // /// Iterator over all keyword arguments.
+ // pub fn iter_keys(&mut self) -> std::vec::IntoIter<Pair> {
+ // let object = std::mem::replace(&mut self.keyword, Object::new());
+ // object.pairs.into_iter()
+ // }
+
+ // /// Clear the argument lists.
+ // pub fn clear(&mut self) {
+ // self.positional.items.clear();
+ // self.keyword.pairs.clear();
+ // }
+
+ // /// Whether both the positional and keyword argument lists are empty.
+ // pub fn is_empty(&self) -> bool {
+ // self.positional.items.is_empty() && self.keyword.pairs.is_empty()
+ // }
+}
+
+// /// Extract the option expression kind from the option or return an error.
+// fn expect<E: ExpressionKind>(opt: ParseResult<Option<E>>) -> ParseResult<E> {
+// match opt {
+// Ok(Some(spanned)) => Ok(spanned),
+// Ok(None) => error!("expected {}", E::NAME),
+// Err(e) => Err(e),
+// }
+// }
+
+pub trait OptionExt: Sized {
+ fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self;
+}
+
+impl<T> OptionExt for Option<T> {
+ fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self {
+ if self.is_none() {
+ errors.push(err!(span; "missing argument: {}", what));
+ }
+ self
+ }
+}
diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs
new file mode 100644
index 00000000..b29b9726
--- /dev/null
+++ b/src/syntax/func/values.rs
@@ -0,0 +1,223 @@
+use std::marker::PhantomData;
+use toddle::query::{FontStyle, FontWeight};
+
+use crate::layout::prelude::*;
+use crate::size::ScaleSize;
+use crate::style::Paper;
+use super::*;
+
+use AlignmentValue::*;
+
+
+pub trait Value {
+ type Output;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error>;
+}
+
+impl<V: Value> Value for Spanned<V> {
+ type Output = Spanned<V::Output>;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ let span = expr.span;
+ V::parse(expr).map(|v| Spanned { v, span })
+ }
+}
+
+macro_rules! value {
+ ($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
+ impl Value for $type {
+ type Output = $output;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ #[allow(unreachable_patterns)]
+ match expr.v {
+ $($p => Ok($r)),*,
+ other => Err(err!("expected {}, found {}",
+ $name, other.name())),
+ }
+ }
+ }
+ };
+}
+
+value!(Expr, Self, "expression", e => e);
+
+value!(Ident, Self, "identifier", Expr::Ident(i) => i);
+value!(String, Self, "string", Expr::Str(s) => s);
+value!(f64, Self, "number", Expr::Number(n) => n);
+value!(bool, Self, "bool", Expr::Bool(b) => b);
+value!(Size, Self, "size", Expr::Size(s) => s);
+value!(Tuple, Self, "tuple", Expr::Tuple(t) => t);
+value!(Object, Self, "object", Expr::Object(o) => o);
+
+value!(ScaleSize, Self, "number or size",
+ Expr::Size(size) => ScaleSize::Absolute(size),
+ Expr::Number(scale) => ScaleSize::Scaled(scale as f32),
+);
+
+pub struct StringLike;
+
+value!(StringLike, String, "identifier or string",
+ Expr::Ident(Ident(s)) => s,
+ Expr::Str(s) => s,
+);
+
+pub struct Defaultable<T>(PhantomData<T>);
+
+impl<T: Value> Value for Defaultable<T> {
+ type Output = Option<T::Output>;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ match expr.v {
+ Expr::Ident(ident) if ident.as_str() == "default" => Ok(None),
+ _ => T::parse(expr).map(Some)
+ }
+ }
+}
+
+impl Value for Direction {
+ type Output = Self;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ Ok(match Ident::parse(expr)?.as_str() {
+ "left-to-right" | "ltr" | "LTR" => Direction::LeftToRight,
+ "right-to-left" | "rtl" | "RTL" => Direction::RightToLeft,
+ "top-to-bottom" | "ttb" | "TTB" => Direction::TopToBottom,
+ "bottom-to-top" | "btt" | "BTT" => Direction::BottomToTop,
+ other => return Err(err!("invalid direction"))
+ })
+ }
+}
+
+impl Value for FontStyle {
+ type Output = Self;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ FontStyle::from_str(Ident::parse(expr)?.as_str())
+ .ok_or_else(|| err!("invalid font style"))
+ }
+}
+
+impl Value for FontWeight {
+ type Output = (Self, bool);
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ match expr.v {
+ Expr::Number(weight) => {
+ let weight = weight.round();
+
+ if weight >= 100.0 && weight <= 900.0 {
+ Ok((FontWeight(weight as i16), false))
+ } else {
+ let clamped = weight.min(900.0).max(100.0) as i16;
+ Ok((FontWeight(clamped), true))
+ }
+ }
+ Expr::Ident(id) => {
+ FontWeight::from_str(id.as_str())
+ .ok_or_else(|| err!("invalid font weight"))
+ .map(|weight| (weight, false))
+ }
+ other => Err(err!("expected identifier or number, \
+ found {}", other.name())),
+ }
+ }
+}
+
+impl Value for Paper {
+ type Output = Self;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ Paper::from_str(Ident::parse(expr)?.as_str())
+ .ok_or_else(|| err!("invalid paper type"))
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AlignmentValue {
+ Align(Alignment),
+ Left,
+ Top,
+ Right,
+ Bottom,
+}
+
+impl AlignmentValue {
+ /// The generic axis this alignment corresponds to in the given system of
+ /// layouting axes. `None` if the alignment is generic.
+ pub fn axis(self, axes: LayoutAxes) -> Option<GenericAxis> {
+ match self {
+ Left | Right => Some(Horizontal.to_generic(axes)),
+ Top | Bottom => Some(Vertical.to_generic(axes)),
+ Align(_) => None,
+ }
+ }
+
+ /// The generic version of this alignment in the given system of layouting
+ /// axes.
+ ///
+ /// Returns `None` if the alignment is invalid for the given axis.
+ pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option<Alignment> {
+ let specific = axis.to_specific(axes);
+ let start = match axes.get(axis).is_positive() {
+ true => Origin,
+ false => End,
+ };
+
+ match (self, specific) {
+ (Align(alignment), _) => Some(alignment),
+ (Left, Horizontal) | (Top, Vertical) => Some(start),
+ (Right, Horizontal) | (Bottom, Vertical) => Some(start.inv()),
+ _ => None
+ }
+ }
+
+ /// The specific version of this alignment in the given system of layouting
+ /// axes.
+ pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentValue {
+ let direction = axes.get_specific(axis);
+ if let Align(alignment) = self {
+ match (direction, alignment) {
+ (LeftToRight, Origin) | (RightToLeft, End) => Left,
+ (LeftToRight, End) | (RightToLeft, Origin) => Right,
+ (TopToBottom, Origin) | (BottomToTop, End) => Top,
+ (TopToBottom, End) | (BottomToTop, Origin) => Bottom,
+ (_, Center) => self,
+ }
+ } else {
+ self
+ }
+ }
+}
+
+impl Value for AlignmentValue {
+ type Output = Self;
+
+ fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
+ Ok(match Ident::parse(expr)?.as_str() {
+ "origin" => Align(Origin),
+ "center" => Align(Center),
+ "end" => Align(End),
+ "left" => Left,
+ "top" => Top,
+ "right" => Right,
+ "bottom" => Bottom,
+ other => return Err(err!("invalid alignment"))
+ })
+ }
+}
+
+impl Display for AlignmentValue {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Align(Origin) => write!(f, "origin"),
+ Align(Center) => write!(f, "center"),
+ Align(End) => write!(f, "end"),
+ Left => write!(f, "left"),
+ Top => write!(f, "top"),
+ Right => write!(f, "right"),
+ Bottom => write!(f, "bottom"),
+ }
+ }
+}