diff options
Diffstat (limited to 'src/func')
| -rw-r--r-- | src/func/args.rs | 215 | ||||
| -rw-r--r-- | src/func/macros.rs | 22 | ||||
| -rw-r--r-- | src/func/map.rs | 59 | ||||
| -rw-r--r-- | src/func/mod.rs | 36 |
4 files changed, 95 insertions, 237 deletions
diff --git a/src/func/args.rs b/src/func/args.rs deleted file mode 100644 index d1d49b6a..00000000 --- a/src/func/args.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! Parsing, storing and deduplication of function arguments. - -use super::prelude::*; -use Expression::*; - -/// Provides a convenient interface to parse the arguments to a function. -pub struct ArgParser<'a> { - args: &'a FuncArgs, - positional_index: usize, -} - -impl<'a> ArgParser<'a> { - pub fn new(args: &'a FuncArgs) -> ArgParser<'a> { - ArgParser { - args, - positional_index: 0, - } - } - - /// Get the next positional argument of the given type. - /// - /// If there are no more arguments or the type is wrong, - /// this will return an error. - pub fn get_pos<T>(&mut self) -> ParseResult<Spanned<T::Output>> - where T: Argument<'a> { - Self::expected(self.get_pos_opt::<T>()?) - } - - /// Get the next positional argument if there is any. - /// - /// If the argument is of the wrong type, this will return an error. - pub fn get_pos_opt<T>(&mut self) -> ParseResult<Option<Spanned<T::Output>>> - where T: Argument<'a> { - let arg = self.args.positional - .get(self.positional_index) - .map(T::from_expr) - .transpose(); - - if let Ok(Some(_)) = arg { - self.positional_index += 1; - } - - arg - } - - /// Get a keyword argument with the given key and type. - pub fn get_key<T>(&mut self, key: &str) -> ParseResult<Spanned<T::Output>> - where T: Argument<'a> { - Self::expected(self.get_key_opt::<T>(key)?) - } - - /// Get a keyword argument with the given key and type if it is present. - pub fn get_key_opt<T>(&mut self, key: &str) -> ParseResult<Option<Spanned<T::Output>>> - where T: Argument<'a> { - self.args.keyword.iter() - .find(|entry| entry.val.0.val == key) - .map(|entry| T::from_expr(&entry.val.1)) - .transpose() - } - - /// Assert that there are no positional arguments left. Returns an error - /// otherwise. - pub fn done(&self) -> ParseResult<()> { - if self.positional_index == self.args.positional.len() { - Ok(()) - } else { - error!(unexpected_argument); - } - } - - /// Covert an option to a result with an error on `None`. - fn expected<T>(val: Option<Spanned<T::Output>>) -> ParseResult<Spanned<T::Output>> - where T: Argument<'a> { - val.ok_or_else(|| error!(@"expected {}", T::ERROR_MESSAGE)) - } -} - -/// A kind of argument. -pub trait Argument<'a> { - type Output; - const ERROR_MESSAGE: &'static str; - - fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>>; -} - -macro_rules! arg { - ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => ( - #[doc = $doc] - #[doc = " argument for use with the [`ArgParser`]."] - pub struct $type; - impl<'a> Argument<'a> for $type { - type Output = $output; - const ERROR_MESSAGE: &'static str = $err; - - fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>> { - #[allow(unreachable_code)] - match &expr.val { - $wanted => Ok(Spanned::new($converted, expr.span)), - _ => error!("expected {}", $err), - } - } - } - ); -} - -arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr); -arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str()); -arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str()); -arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n); -arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s); -arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b); - -/// An argument key which identifies a layouting axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum AxisKey { - Primary, - Secondary, - Vertical, - Horizontal, -} - -impl AxisKey { - /// The generic version of this axis key in the given system of axes. - pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind { - match self { - Primary => GenericAxisKind::Primary, - Secondary => GenericAxisKind::Secondary, - Vertical => axes.vertical(), - Horizontal => axes.horizontal(), - } - } - - /// The specific version of this axis key in the given system of axes. - pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind { - match self { - Primary => axes.primary(), - Secondary => axes.secondary(), - Vertical => SpecificAxisKind::Vertical, - Horizontal => SpecificAxisKind::Horizontal, - } - } -} - -/// An argument key which identifies a target alignment. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum AlignmentKey { - Left, - Top, - Right, - Bottom, - Origin, - Center, - End, -} - -impl AlignmentKey { - /// The generic axis this alignment key corresopnds to in the given system - /// of layouting axes. Falls back to `default` if the alignment is generic. - pub fn axis(&self, axes: LayoutAxes, default: GenericAxisKind) -> GenericAxisKind { - use AlignmentKey::*; - match self { - Origin | Center | End => default, - Left | Right => axes.horizontal(), - Top | Bottom => axes.vertical(), - } - } - - /// The generic version of this alignment in the given system of layouting - /// axes. Returns an error if the alignment is invalid for the given axis. - pub fn generic(&self, axes: LayoutAxes, axis: GenericAxisKind) -> LayoutResult<Alignment> { - use AlignmentKey::*; - - let horizontal = axis == axes.horizontal(); - Ok(match self { - Origin => Alignment::Origin, - Center => Alignment::Center, - End => Alignment::End, - Left if horizontal => axes.left(), - Right if horizontal => axes.right(), - Top if !horizontal => axes.top(), - Bottom if !horizontal => axes.bottom(), - _ => error!( - "invalid alignment `{}` for {} axis", - format!("{:?}", self).to_lowercase(), - format!("{:?}", axis).to_lowercase() - ) - }) - } - - /// The specific version of this alignment in the given system of layouting - /// axes. - pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey { - use AlignmentKey::*; - match (self, axis) { - (Origin, SpecificAxisKind::Horizontal) => Left, - (End, SpecificAxisKind::Horizontal) => Right, - (Origin, SpecificAxisKind::Vertical) => Top, - (End, SpecificAxisKind::Vertical) => Bottom, - _ => *self, - } - } -} - -/// An argument key which identifies a margin or padding target. -/// -/// A is the axis type used. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaddingKey<A> { - /// All four sides should have the specified padding. - All, - /// Both sides of the given axis should have the specified padding. - Axis(A), - /// Only the given side of the given axis should have the specified padding. - AxisAligned(A, AlignmentKey), -} diff --git a/src/func/macros.rs b/src/func/macros.rs index 0ffdc857..daae2769 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -10,6 +10,13 @@ macro_rules! function { function!(@meta $type | $($rest)*); }; + // Parse a tuple struct. + ($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => { + $(#[$outer])* + pub struct $type($($fields)*); + function!(@meta $type | $($rest)*); + }; + // Parse a struct with fields. ($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => { $(#[$outer])* @@ -68,14 +75,17 @@ macro_rules! function { type Meta = $meta; fn parse( - header: &FuncHeader, - $body: Option<&str>, + args: FuncArgs, + $body: Option<Spanned<&str>>, $ctx: ParseContext, $metadata: Self::Meta, ) -> ParseResult<Self> where Self: Sized { - let mut $args = $crate::func::args::ArgParser::new(&header.args); + #[allow(unused_mut)] + let mut $args = args; let val = $code; - $args.done()?; + if !$args.is_empty() { + error!(unexpected_argument); + } Ok(val) } } @@ -117,7 +127,7 @@ macro_rules! parse { (optional: $body:expr, $ctx:expr) => ( if let Some(body) = $body { - Some($crate::syntax::parse(body, $ctx)?) + Some($crate::syntax::parse(body.v, $ctx)?) } else { None } @@ -125,7 +135,7 @@ macro_rules! parse { (expected: $body:expr, $ctx:expr) => ( if let Some(body) = $body { - $crate::syntax::parse(body, $ctx)? + $crate::syntax::parse(body.v, $ctx)? } else { error!("expected body"); } diff --git a/src/func/map.rs b/src/func/map.rs new file mode 100644 index 00000000..880fe3e6 --- /dev/null +++ b/src/func/map.rs @@ -0,0 +1,59 @@ +//! 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 31e31592..7ee7d779 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -8,17 +8,12 @@ use self::prelude::*; #[macro_use] pub mod macros; -pub mod args; +pub mod map; /// Useful imports for creating your own functions. pub mod prelude { - pub use Command::*; - pub use super::args::*; - pub use super::{Scope, ParseFunc, LayoutFunc, Command, Commands}; - pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span}; - pub use crate::syntax::{parse, ParseContext, ParseResult}; - pub use crate::size::{Size, Size2D, SizeBox}; - pub use crate::style::{PageStyle, TextStyle}; + pub use super::map::ConsistentMap; + pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands}; pub use crate::layout::{ layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace, LayoutSpaces, @@ -26,16 +21,25 @@ pub mod prelude { LayoutAlignment, Alignment, SpacingKind, LayoutResult, }; + pub use crate::syntax::{ + parse, ParseContext, ParseResult, + SyntaxTree, FuncCall, FuncArgs, PosArg, KeyArg, + Expression, Ident, ExpressionKind, + Spanned, Span + }; + pub use crate::size::{Size, Size2D, SizeBox, ScaleSize, FSize, PSize}; + pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; + pub use Command::*; } /// Types representing functions that are parsed from source code. pub trait ParseFunc { - type Meta; + type Meta: Clone; /// Parse the header and body into this function given a context. fn parse( - header: &FuncHeader, - body: Option<&str>, + args: FuncArgs, + body: Option<Spanned<&str>>, ctx: ParseContext, metadata: Self::Meta, ) -> ParseResult<Self> where Self: Sized; @@ -126,8 +130,8 @@ pub struct Scope { /// A function which parses the source of a function into a function type which /// implements [`LayoutFunc`]. type Parser = dyn Fn( - &FuncHeader, - Option<&str>, + FuncArgs, + Option<Spanned<&str>>, ParseContext ) -> ParseResult<Box<dyn LayoutFunc>>; @@ -153,11 +157,11 @@ impl Scope { /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). pub fn add_with_metadata<F, T>(&mut self, name: &str, metadata: T) - where F: ParseFunc<Meta=T> + LayoutFunc + 'static, T: 'static { + where F: ParseFunc<Meta=T> + LayoutFunc + 'static, T: 'static + Clone { self.parsers.insert( name.to_owned(), - Box::new(|h, b, c| { - F::parse(h, b, c, metadata) + Box::new(move |a, b, c| { + F::parse(a, b, c, metadata.clone()) .map(|f| Box::new(f) as Box<dyn LayoutFunc>) }) ); |
