diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-10-23 00:14:43 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-10-23 00:21:40 +0200 |
| commit | ecf0ff4d054f11c79ec0ddbbdf45f3dfcf9fc8d7 (patch) | |
| tree | efdc76915e5c7f43629aa3aa5d5aa3998d7f5367 /src/func | |
| parent | cff325b520727ac0eec46a3757831afaa0916cc1 (diff) | |
Introduce a set of macros for writing functions more concisely 🎁
Diffstat (limited to 'src/func')
| -rw-r--r-- | src/func/helpers.rs | 129 | ||||
| -rw-r--r-- | src/func/mod.rs | 187 |
2 files changed, 316 insertions, 0 deletions
diff --git a/src/func/helpers.rs b/src/func/helpers.rs new file mode 100644 index 00000000..d562284f --- /dev/null +++ b/src/func/helpers.rs @@ -0,0 +1,129 @@ +use super::prelude::*; +use std::iter::Peekable; +use std::slice::Iter; + +/// Implement the function trait more concisely. +#[macro_export] +macro_rules! function { + (data: $ident:ident, $($tts:tt)*) => { + impl Function for $ident { + function!(@parse $ident, $($tts)*); + } + }; + + (@parse $ident:ident, parse: plain, $($tts:tt)*) => { + fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) + -> ParseResult<Self> where Self: Sized + { + Arguments::new(header).done()?; + if body.is_some() { + err!("expected no body"); + } + Ok($ident) + } + function!(@layout $($tts)*); + }; + + ( + @parse $ident:ident, + parse($args:ident, $body:ident, $ctx:ident) + $block:block + $($tts:tt)* + ) => { + fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) + -> ParseResult<Self> where Self: Sized + { + #[allow(unused_mut)] let mut $args = Arguments::new(header); + let $body = body; + let $ctx = ctx; + $block + } + function!(@layout $($tts)*); + }; + + (@layout layout($this:pat, $ctx:pat) $block:block) => { + fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> { + let $ctx = ctx; + let $this = self; + $block + } + }; +} + +/// Parse the body of a function. +/// - If the function does not expect a body, use `forbidden`. +/// - If the function can have a body, use `optional`. +/// - If the function must have a body, use `required`. +#[macro_export] +macro_rules! parse { + (forbidden: $body:expr) => { + if $body.is_some() { + err!("unexpected body"); + } + }; + + (optional: $body:expr, $ctx:expr) => { + if let Some(body) = $body { + Some($crate::parsing::parse(body, $ctx)?) + } else { + None + } + }; + + (required: $body:expr, $ctx:expr) => { + if let Some(body) = $body { + $crate::parsing::parse(body, $ctx)? + } else { + err!("expected body"); + } + } +} + +/// Return a formatted parsing error. +#[macro_export] +macro_rules! err { + ($($tts:tt)*) => { + return Err(ParseError::new(format!($($tts)*))); + }; +} + +/// Convenient interface for parsing function arguments. +pub struct Arguments<'a> { + args: Peekable<Iter<'a, Expression>>, +} + +impl<'a> Arguments<'a> { + pub fn new(header: &'a FuncHeader) -> Arguments<'a> { + Arguments { + args: header.args.iter().peekable() + } + } + + pub fn get_expr(&mut self) -> ParseResult<&'a Expression> { + self.args.next() + .ok_or_else(|| ParseError::new("expected expression")) + } + + pub fn get_ident(&mut self) -> ParseResult<&'a str> { + match self.get_expr()? { + Expression::Ident(s) => Ok(s.as_str()), + _ => Err(ParseError::new("expected identifier")), + } + } + + pub fn get_ident_if_present(&mut self) -> ParseResult<Option<&'a str>> { + if self.args.peek().is_some() { + self.get_ident().map(|s| Some(s)) + } else { + Ok(None) + } + } + + pub fn done(&mut self) -> ParseResult<()> { + if self.args.peek().is_none() { + Ok(()) + } else { + Err(ParseError::new("unexpected argument")) + } + } +} diff --git a/src/func/mod.rs b/src/func/mod.rs new file mode 100644 index 00000000..9a6fcbd1 --- /dev/null +++ b/src/func/mod.rs @@ -0,0 +1,187 @@ +//! Dynamic typesetting functions. + +use std::any::Any; +use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; + +use crate::layout::{Layout, LayoutContext, Alignment, LayoutResult, MultiLayout}; +use crate::parsing::{ParseContext, ParseResult}; +use crate::style::TextStyle; +use crate::syntax::{FuncHeader, SyntaxTree}; + +#[macro_use] +mod helpers; +pub use helpers::Arguments; + +/// Useful imports for creating your own functions. +pub mod prelude { + pub use crate::func::{Command, CommandList, Function}; + pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout}; + pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult}; + pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult}; + pub use crate::syntax::{Expression, FuncHeader, SyntaxTree}; + pub use crate::size::{Size, Size2D, SizeBox}; + pub use super::helpers::*; +} + +/// Typesetting function types. +/// +/// These types have to be able to parse tokens into themselves and store the +/// relevant information from the parsing to do their role in typesetting later. +/// +/// The trait `FunctionBounds` is automatically implemented for types which can +/// be used as functions, that is they fulfill the bounds `Debug + PartialEq + +/// 'static`. +pub trait Function: FunctionBounds { + /// Parse the header and body into this function given a context. + fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self> + where Self: Sized; + + /// Layout this function given a context. + /// + /// Returns optionally the resulting layout and a new context if changes to + /// the context should be made. + fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList>; +} + +impl PartialEq for dyn Function { + fn eq(&self, other: &dyn Function) -> bool { + self.help_eq(other) + } +} + +/// A helper trait that describes requirements for types that can implement +/// [`Function`]. +/// +/// Automatically implemented for all types which fulfill to the bounds `Debug + +/// PartialEq + 'static`. There should be no need to implement this manually. +pub trait FunctionBounds: Debug { + /// Cast self into `Any`. + fn help_cast_as_any(&self) -> &dyn Any; + + /// Compare self with another function. + fn help_eq(&self, other: &dyn Function) -> bool; +} + +impl<T> FunctionBounds for T +where T: Debug + PartialEq + 'static +{ + fn help_cast_as_any(&self) -> &dyn Any { + self + } + + fn help_eq(&self, other: &dyn Function) -> bool { + if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() { + self == other + } else { + false + } + } +} + +/// A sequence of commands requested for execution by a function. +#[derive(Debug)] +pub struct CommandList<'a> { + pub commands: Vec<Command<'a>>, +} + +impl<'a> CommandList<'a> { + /// Create an empty command list. + pub fn new() -> CommandList<'a> { + CommandList { commands: vec![] } + } + + /// Create a command list with commands from a vector. + pub fn from_vec(commands: Vec<Command<'a>>) -> CommandList<'a> { + CommandList { commands } + } + + /// Add a command to the sequence. + pub fn add(&mut self, command: Command<'a>) { + self.commands.push(command); + } + + /// Whether there are any commands in this sequence. + pub fn is_empty(&self) -> bool { + self.commands.is_empty() + } +} + +impl<'a> IntoIterator for CommandList<'a> { + type Item = Command<'a>; + type IntoIter = std::vec::IntoIter<Command<'a>>; + + fn into_iter(self) -> Self::IntoIter { + self.commands.into_iter() + } +} + +impl<'a> IntoIterator for &'a CommandList<'a> { + type Item = &'a Command<'a>; + type IntoIter = std::slice::Iter<'a, Command<'a>>; + + fn into_iter(self) -> Self::IntoIter { + self.commands.iter() + } +} + +/// Commands requested for execution by functions. +#[derive(Debug)] +pub enum Command<'a> { + Layout(&'a SyntaxTree), + Add(Layout), + AddMany(MultiLayout), + AddFlex(Layout), + SetAlignment(Alignment), + SetStyle(TextStyle), + FinishLayout, + FinishFlexRun, +} + +macro_rules! commands { + ($($x:expr),*$(,)*) => ({ + $crate::func::CommandList::from_vec(vec![$($x,)*]) + }); +} + +/// A map from identifiers to functions. +pub struct Scope { + parsers: HashMap<String, Box<ParseFunc>>, +} + +/// A function which parses a function invocation into a function type. +type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext) -> ParseResult<Box<dyn Function>>; + +impl Scope { + /// Create a new empty scope. + pub fn new() -> Scope { + Scope { + parsers: HashMap::new(), + } + } + + /// Create a new scope with the standard functions contained. + pub fn with_std() -> Scope { + crate::library::std() + } + + /// Add a function type to the scope giving it a name. + pub fn add<F: Function + 'static>(&mut self, name: &str) { + self.parsers.insert( + name.to_owned(), + Box::new(|h, b, c| F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)), + ); + } + + /// Return the parser with the given name if there is one. + pub(crate) fn get_parser(&self, name: &str) -> Option<&ParseFunc> { + self.parsers.get(name).map(|x| &**x) + } +} + +impl Debug for Scope { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Scope ")?; + write!(f, "{:?}", self.parsers.keys()) + } +} |
