summaryrefslogtreecommitdiff
path: root/src/func
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-10-23 00:14:43 +0200
committerLaurenz <laurmaedje@gmail.com>2019-10-23 00:21:40 +0200
commitecf0ff4d054f11c79ec0ddbbdf45f3dfcf9fc8d7 (patch)
treeefdc76915e5c7f43629aa3aa5d5aa3998d7f5367 /src/func
parentcff325b520727ac0eec46a3757831afaa0916cc1 (diff)
Introduce a set of macros for writing functions more concisely 🎁
Diffstat (limited to 'src/func')
-rw-r--r--src/func/helpers.rs129
-rw-r--r--src/func/mod.rs187
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())
+ }
+}