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/expr.rs | 230 +++++++++++++++++++++++++--------------------- src/syntax/func.rs | 125 ------------------------- src/syntax/func/keys.rs | 121 ++++++++++++++++++++++++ src/syntax/func/maps.rs | 211 ++++++++++++++++++++++++++++++++++++++++++ src/syntax/func/mod.rs | 147 +++++++++++++++++++++++++++++ src/syntax/func/values.rs | 223 ++++++++++++++++++++++++++++++++++++++++++++ src/syntax/mod.rs | 2 +- src/syntax/parsing.rs | 8 +- src/syntax/tokens.rs | 9 +- 9 files changed, 838 insertions(+), 238 deletions(-) delete mode 100644 src/syntax/func.rs create mode 100644 src/syntax/func/keys.rs create mode 100644 src/syntax/func/maps.rs create mode 100644 src/syntax/func/mod.rs create mode 100644 src/syntax/func/values.rs (limited to 'src/syntax') diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 34a1c6bf..fe24c655 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,4 +1,3 @@ -use crate::size::ScaleSize; use super::*; @@ -22,30 +21,13 @@ impl Expr { Str(_) => "string", Number(_) => "number", Size(_) => "size", - Bool(_) => "boolean", + Bool(_) => "bool", Tuple(_) => "tuple", Object(_) => "object", } } } -impl Display for Expr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use Expr::*; - match self { - Ident(i) => write!(f, "{}", i), - Str(s) => write!(f, "{:?}", s), - Number(n) => write!(f, "{}", n), - Size(s) => write!(f, "{}", s), - Bool(b) => write!(f, "{}", b), - Tuple(t) => write!(f, "{}", t), - Object(o) => write!(f, "{}", o), - } - } -} - -debug_display!(Expr); - /// An identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Ident(pub String); @@ -64,17 +46,6 @@ impl Ident { } } -impl Display for Ident { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -debug_display!(Ident); - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct StringLike(pub String); - /// A sequence of expressions. #[derive(Clone, PartialEq)] pub struct Tuple { @@ -89,27 +60,31 @@ impl Tuple { pub fn add(&mut self, item: Spanned) { self.items.push(item); } -} - -impl Display for Tuple { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "(")?; - let mut first = true; - for item in &self.items { - if !first { - write!(f, ", ")?; + pub fn get(&mut self, errors: &mut Errors) -> Option { + while !self.items.is_empty() { + let expr = self.items.remove(0); + let span = expr.span; + match V::parse(expr) { + Ok(output) => return Some(output), + Err(err) => errors.push(Spanned { v: err, span }), } - write!(f, "{}", item.v)?; - first = false; } + None + } - write!(f, ")") + pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors) + -> impl Iterator + 'a { + self.items.drain(..).filter_map(move |expr| { + let span = expr.span; + match V::parse(expr) { + Ok(output) => Some(output), + Err(err) => { errors.push(Spanned { v: err, span }); None } + } + }) } } -debug_display!(Tuple); - /// A key-value collection of identifiers and associated expressions. #[derive(Clone, PartialEq)] pub struct Object { @@ -128,6 +103,108 @@ impl Object { pub fn add_pair(&mut self, pair: Pair) { self.pairs.push(pair); } + + pub fn get(&mut self, errors: &mut Errors, key: &str) -> Option { + let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?; + self.get_index::(errors, index) + } + + pub fn get_with_key( + &mut self, + errors: &mut Errors, + ) -> Option<(K::Output, V::Output)> { + for (index, pair) in self.pairs.iter().enumerate() { + let key = Spanned { v: pair.key.v.as_str(), span: pair.key.span }; + if let Some(key) = K::parse(key) { + return self.get_index::(errors, index).map(|value| (key, value)); + } + } + None + } + + pub fn get_all<'a, K: Key, V: Value>( + &'a mut self, + errors: &'a mut Errors, + ) -> impl Iterator + 'a { + let mut index = 0; + std::iter::from_fn(move || { + if index < self.pairs.len() { + let key = &self.pairs[index].key; + let key = Spanned { v: key.v.as_str(), span: key.span }; + + Some(if let Some(key) = K::parse(key) { + self.get_index::(errors, index).map(|v| (key, v)) + } else { + index += 1; + None + }) + } else { + None + } + }).filter_map(|x| x) + } + + pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( + &'a mut self, + errors: &'a mut Errors, + ) -> impl Iterator> + 'a { + self.get_all::, Spanned>(errors) + .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) + } + + fn get_index(&mut self, errors: &mut Errors, index: usize) -> Option { + let expr = self.pairs.remove(index).value; + let span = expr.span; + match V::parse(expr) { + Ok(output) => Some(output), + Err(err) => { errors.push(Spanned { v: err, span }); None } + } + } +} + +/// A key-value pair in an object. +#[derive(Clone, PartialEq)] +pub struct Pair { + pub key: Spanned, + pub value: Spanned, +} + +impl Display for Expr { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use Expr::*; + match self { + Ident(i) => write!(f, "{}", i), + Str(s) => write!(f, "{:?}", s), + Number(n) => write!(f, "{}", n), + Size(s) => write!(f, "{}", s), + Bool(b) => write!(f, "{}", b), + Tuple(t) => write!(f, "{}", t), + Object(o) => write!(f, "{}", o), + } + } +} + +impl Display for Ident { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Display for Tuple { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "(")?; + + let mut first = true; + for item in &self.items { + if !first { + write!(f, ", ")?; + } + write!(f, "{}", item.v)?; + first = false; + } + + write!(f, ")") + } } impl Display for Object { @@ -151,71 +228,14 @@ impl Display for Object { } } -debug_display!(Object); - -/// A key-value pair in an object. -#[derive(Clone, PartialEq)] -pub struct Pair { - pub key: Spanned, - pub value: Spanned, -} - impl Display for Pair { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}: {}", self.key.v, self.value.v) } } +debug_display!(Expr); +debug_display!(Ident); +debug_display!(Tuple); +debug_display!(Object); debug_display!(Pair); - -pub trait ExprKind: Sized { - /// The name of the expression in an `expected ` error. - const NAME: &'static str; - - /// Create from expression. - fn from_expr(expr: Spanned) -> Result; -} - -impl ExprKind for Spanned where T: ExprKind { - const NAME: &'static str = T::NAME; - - fn from_expr(expr: Spanned) -> Result { - let span = expr.span; - T::from_expr(expr).map(|v| Spanned { v, span }) - } -} -/// Implements the expression kind trait for a type. -macro_rules! kind { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl ExprKind for $type { - const NAME: &'static str = $name; - - fn from_expr(expr: Spanned) -> Result { - #[allow(unreachable_patterns)] - Ok(match expr.v { - $($p => $r),*, - _ => return Err( - err!("expected {}, found {}", Self::NAME, expr.v.name()) - ), - }) - } - } - }; -} - -kind!(Expr, "expression", e => e); -kind!(Ident, "identifier", Expr::Ident(i) => i); -kind!(String, "string", Expr::Str(s) => s); -kind!(f64, "number", Expr::Number(n) => n); -kind!(bool, "boolean", Expr::Bool(b) => b); -kind!(Size, "size", Expr::Size(s) => s); -kind!(Tuple, "tuple", Expr::Tuple(t) => t); -kind!(Object, "object", Expr::Object(o) => o); -kind!(ScaleSize, "number or size", - Expr::Size(size) => ScaleSize::Absolute(size), - Expr::Number(scale) => ScaleSize::Scaled(scale as f32), -); -kind!(StringLike, "identifier or string", - Expr::Ident(Ident(s)) => StringLike(s), - Expr::Str(s) => StringLike(s), -); diff --git a/src/syntax/func.rs b/src/syntax/func.rs deleted file mode 100644 index abc8c431..00000000 --- a/src/syntax/func.rs +++ /dev/null @@ -1,125 +0,0 @@ -use super::*; - - -#[derive(Debug, Clone, PartialEq)] -pub struct FuncHeader { - pub name: Spanned, - 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), - 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) { - self.pos.add(item); - } - - /// Add a keyword argument. - pub fn add_key(&mut self, key: Spanned, value: Spanned) { - 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); - } - - // /// Force-extract the first positional argument. - // pub fn get_pos(&mut self) -> ParseResult { - // expect(self.get_pos_opt()) - // } - - // /// Extract the first positional argument. - // pub fn get_pos_opt(&mut self) -> ParseResult> { - // 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(&mut self, name: &str) -> ParseResult { - // expect(self.get_key_opt(name)) - // } - - // /// Extract a keyword argument. - // pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { - // 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> { - // 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 { - // 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(opt: ParseResult>) -> ParseResult { -// match opt { -// Ok(Some(spanned)) => Ok(spanned), -// Ok(None) => error!("expected {}", E::NAME), -// Err(e) => Err(e), -// } -// } 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; +} + +impl Key for Spanned { + type Output = Spanned; + + fn parse(key: Spanned<&str>) -> Option { + 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 { + 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 { + /// 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, 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 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, + _ => {}, + } + } + } + } +} 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, + 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), + 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) { + self.pos.add(item); + } + + /// Add a keyword argument. + pub fn add_key(&mut self, key: Spanned, value: Spanned) { + 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 { + 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(&mut self) -> ParseResult { + // expect(self.get_pos_opt()) + // } + + // /// Extract the first positional argument. + // pub fn get_pos_opt(&mut self) -> ParseResult> { + // 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(&mut self, name: &str) -> ParseResult { + // expect(self.get_key_opt(name)) + // } + + // /// Extract a keyword argument. + // pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { + // 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> { + // 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 { + // 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(opt: ParseResult>) -> ParseResult { +// 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 OptionExt for Option { + 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) -> Result; +} + +impl Value for Spanned { + type Output = Spanned; + + fn parse(expr: Spanned) -> Result { + 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) -> Result { + #[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(PhantomData); + +impl Value for Defaultable { + type Output = Option; + + fn parse(expr: Spanned) -> Result { + 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) -> Result { + 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) -> Result { + 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) -> Result { + 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) -> Result { + 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 { + 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 { + 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) -> Result { + 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"), + } + } +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index a77c764e..356535ae 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -6,7 +6,7 @@ use std::future::Future; use std::pin::Pin; use serde::Serialize; -use crate::error::Error; +use crate::error::{Error, Errors}; use crate::func::{Commands, Command}; use crate::layout::{Layouted, LayoutContext}; use crate::size::Size; diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index ed343050..e726a2e0 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -12,7 +12,7 @@ pub struct ParseContext<'a> { pub struct Parsed { pub output: T, - pub errors: SpanVec, + pub errors: Errors, pub decorations: SpanVec, } @@ -77,17 +77,17 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { ctx: ParseContext<'s>, - errors: SpanVec, + errors: Errors, decorations: SpanVec, tokens: Tokens<'s>, peeked: Option>>>, - body: Option<(Position, &'s str)>, + body: Option>, } impl<'s> FuncParser<'s> { fn new( header: &'s str, - body: Option<(Position, &'s str)>, + body: Option>, ctx: ParseContext<'s> ) -> FuncParser<'s> { FuncParser { diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 6c8e736c..d0adbf60 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -23,7 +23,7 @@ pub enum Token<'s> { /// A function invocation `[
][]`. Function { header: &'s str, - body: Option<(Position, &'s str)>, + body: Option>, terminated: bool, }, @@ -222,13 +222,16 @@ impl<'s> Tokens<'s> { return Function { header, body: None, terminated }; } + let body_start = self.pos() - start; self.eat(); - let offset = self.pos() - start; let (body, terminated) = self.read_function_part(); self.eat(); - Function { header, body: Some((offset, body)), terminated } + let body_end = self.pos(); + let span = Span::new(body_start, body_end); + + Function { header, body: Some(Spanned { v: body, span }), terminated } } fn read_function_part(&mut self) -> (&'s str, bool) { -- cgit v1.2.3