diff --git a/src/func/helpers.rs b/src/func/helpers.rs index a7c270c26..6e6896c9f 100644 --- a/src/func/helpers.rs +++ b/src/func/helpers.rs @@ -1,8 +1,9 @@ -use std::iter::Peekable; -use std::slice::Iter; -use super::prelude::*; +//! Helper types and macros for creating custom functions. -/// Implement the function trait more concisely. +use super::prelude::*; +use Expression::*; + +/// Lets you implement the function trait more concisely. #[macro_export] macro_rules! function { (data: $ident:ident, $($tts:tt)*) => ( @@ -15,15 +16,15 @@ macro_rules! function { ); (@parse $ident:ident, parse: plain, $($tts:tt)*) => ( - fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) - -> ParseResult where Self: Sized - { - Arguments::new(header).done()?; + fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult + where Self: Sized { + ArgParser::new(&header.args).done()?; if body.is_some() { err!("expected no body"); } Ok($ident) } + function!(@layout $($tts)*); ); @@ -33,14 +34,15 @@ macro_rules! function { $block:block $($tts:tt)* ) => ( - fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) - -> ParseResult where Self: Sized - { - #[allow(unused_mut)] let mut $args = Arguments::new(header); + fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult + where Self: Sized { + #[allow(unused_mut)] + let mut $args = ArgParser::new(&header.args); let $body = body; let $ctx = ctx; $block } + function!(@layout $($tts)*); ); @@ -82,51 +84,110 @@ macro_rules! parse { ) } -/// Return a formatted parsing error. +/// Early-return with a formatted parsing error or yield +/// an error expression without returning when prefixed with `@`. #[macro_export] macro_rules! err { (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*))); ($($tts:tt)*) => (return Err(err!(@$($tts)*));); } -/// Convenient interface for parsing function arguments. -pub struct Arguments<'a> { - args: Peekable>>, +/// Easy parsing of function arguments. +pub struct ArgParser<'a> { + args: &'a FuncArgs, + positional_index: usize, } -impl<'a> Arguments<'a> { - pub fn new(header: &'a FuncHeader) -> Arguments<'a> { - Arguments { - args: header.args.positional.iter().peekable() +impl<'a> ArgParser<'a> { + pub fn new(args: &'a FuncArgs) -> ArgParser<'a> { + ArgParser { + args, + positional_index: 0, } } - pub fn get_expr(&mut self) -> ParseResult<&'a Spanned> { - self.args.next() - .ok_or_else(|| ParseError::new("expected expression")) + /// 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(&mut self) -> ParseResult> where T: Argument<'a> { + self.get_pos_opt::()? + .ok_or_else(|| err!(@"expected {}", T::ERROR_MESSAGE)) } - pub fn get_ident(&mut self) -> ParseResult> { - let expr = self.get_expr()?; - match &expr.val { - Expression::Ident(s) => Ok(Spanned::new(s.as_str(), expr.span)), - _ => err!("expected identifier"), + /// 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(&mut self) -> ParseResult>> + 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 } - pub fn get_ident_if_present(&mut self) -> ParseResult>> { - if self.args.peek().is_some() { - self.get_ident().map(|s| Some(s)) - } else { - Ok(None) - } + /// Get a keyword argument with the given key and type. + pub fn get_key(&mut self, key: &str) -> ParseResult> + where T: Argument<'a> { + self.get_key_opt::(key)? + .ok_or_else(|| err!(@"expected {}", T::ERROR_MESSAGE)) } - pub fn done(&mut self) -> ParseResult<()> { - if self.args.peek().is_none() { + /// Get a keyword argument with the given key and type if it is present. + pub fn get_key_opt(&mut self, key: &str) -> ParseResult>> + 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 { - Err(ParseError::new("unexpected argument")) + err!("unexpected argument"); } } } + +/// A kind of argument. +pub trait Argument<'a> { + type Output; + const ERROR_MESSAGE: &'static str; + + fn from_expr(expr: &'a Spanned) -> ParseResult>; +} + +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) -> ParseResult> { + match &expr.val { + $wanted => Ok(Spanned::new($converted, expr.span)), + #[allow(unreachable_patterns)] _ => err!("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); diff --git a/src/func/mod.rs b/src/func/mod.rs index 402f0111f..bd61204ec 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -7,8 +7,7 @@ use std::fmt::{self, Debug, Formatter}; use self::prelude::*; #[macro_use] -mod helpers; -pub use helpers::Arguments; +pub mod helpers; /// Useful imports for creating your own functions. pub mod prelude { @@ -24,11 +23,12 @@ pub mod prelude { /// 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. +/// These types have to be able to parse themselves from a string and build +/// a list of layouting commands corresponding to the parsed source. /// -/// The trait `FunctionBounds` is automatically implemented for types which can -/// be used as functions, that is they fulfill the bounds `Debug + PartialEq + +/// This trait is a supertrait of `FunctionBounds` for technical reasons. The +/// trait `FunctionBounds` is automatically implemented for types which can +/// be used as functions, that is, all types which fulfill the bounds `Debug + PartialEq + /// 'static`. pub trait Function: FunctionBounds { /// Parse the header and body into this function given a context. @@ -84,6 +84,19 @@ where T: Debug + PartialEq + 'static } } +/// Commands requested for execution by functions. +#[derive(Debug)] +pub enum Command<'a> { + LayoutTree(&'a SyntaxTree), + Add(Layout), + AddMany(MultiLayout), + AddFlex(Layout), + SetAlignment(Alignment), + SetStyle(TextStyle), + FinishLayout, + FinishFlexRun, +} + /// A sequence of commands requested for execution by a function. #[derive(Debug)] pub struct CommandList<'a> { @@ -130,19 +143,8 @@ impl<'a> IntoIterator for &'a CommandList<'a> { } } -/// 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, -} - +/// Create a list of commands. +#[macro_export] macro_rules! commands { ($($x:expr),*$(,)*) => ( $crate::func::CommandList::from_vec(vec![$($x,)*]) diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 67ad48b41..707d6113e 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -62,7 +62,7 @@ debug_display!(LayoutAction); /// content is written. /// /// Furthermore, the action list can translate absolute position into a coordinate system -/// with a different. This is realized in the `add_box` method, which allows a layout to +/// with a different origin. This is realized in the `add_box` method, which allows a layout to /// be added at a position, effectively translating all movement actions inside the layout /// by the position. #[derive(Debug, Clone)] diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index 7521967b3..379579f50 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -2,7 +2,7 @@ use super::*; /// Layouts boxes stack-like. /// -/// The boxes are arranged vertically, each layout gettings it's own "line". +/// The boxes are arranged along an axis, each layout gettings it's own "line". #[derive(Debug, Clone)] pub struct StackLayouter { ctx: StackContext, diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 131570df5..1d88751fb 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -108,7 +108,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { for command in commands { match command { - Command::Layout(tree) => self.layout(tree)?, + Command::LayoutTree(tree) => self.layout(tree)?, Command::Add(layout) => { self.finish_flex()?; diff --git a/src/library/structure.rs b/src/library/structure.rs index 977d7499b..e6d242a00 100644 --- a/src/library/structure.rs +++ b/src/library/structure.rs @@ -1,6 +1,7 @@ use crate::func::prelude::*; +use Command::*; -/// Ends the current page. +/// 📜 `page.break`: Ends the current page. #[derive(Debug, PartialEq)] pub struct Pagebreak; @@ -9,11 +10,11 @@ function! { parse: plain, layout(_, _) { - Ok(commands![Command::FinishLayout]) + Ok(commands![FinishLayout]) } } -/// Ends the current line. +/// 🔙 `line.break`, `n`: Ends the current line. #[derive(Debug, PartialEq)] pub struct Linebreak; @@ -22,11 +23,14 @@ function! { parse: plain, layout(_, _) { - Ok(commands![Command::FinishFlexRun]) + Ok(commands![FinishFlexRun]) } } -/// Aligns content in different ways. +/// 📐 `align`: Aligns content in different ways. +/// +/// **Positional arguments:** +/// - `left`, `right` or `center` _(required)_. #[derive(Debug, PartialEq)] pub struct Align { body: Option, @@ -38,7 +42,7 @@ function! { parse(args, body, ctx) { let body = parse!(optional: body, ctx); - let arg = args.get_ident()?; + let arg = args.get_pos::()?; let alignment = match arg.val { "left" => Alignment::Left, "right" => Alignment::Right, @@ -56,17 +60,22 @@ function! { layout(this, ctx) { Ok(commands![match &this.body { Some(body) => { - Command::AddMany(layout_tree(body, LayoutContext { + AddMany(layout_tree(body, LayoutContext { alignment: this.alignment, .. ctx })?) } - None => Command::SetAlignment(this.alignment) + None => SetAlignment(this.alignment) }]) } } -/// Layouts content into a box. +/// 📦 `box`: Layouts content into a box. +/// +/// **Positional arguments:** None. +/// +/// **Keyword arguments:** +/// - flow: either `horizontal` or `vertical` _(optional)_. #[derive(Debug, PartialEq)] pub struct Boxed { body: SyntaxTree, @@ -80,7 +89,7 @@ function! { let body = parse!(required: body, ctx); let mut flow = Flow::Vertical; - if let Some(ident) = args.get_ident_if_present()? { + if let Some(ident) = args.get_key_opt::("flow")? { flow = match ident.val { "vertical" => Flow::Vertical, "horizontal" => Flow::Horizontal, @@ -97,7 +106,7 @@ function! { layout(this, ctx) { Ok(commands![ - Command::AddMany(layout_tree(&this.body, LayoutContext { + AddMany(layout_tree(&this.body, LayoutContext { flow: this.flow, .. ctx })?) @@ -106,8 +115,12 @@ function! { } macro_rules! spacefunc { - ($ident:ident, $name:expr, $var:ident => $command:expr) => ( - /// Adds whitespace. + ($ident:ident, $doc:expr, $var:ident => $command:expr) => ( + #[doc = $doc] + /// + /// **Positional arguments:** + /// - Spacing as a size or number, which is interpreted as a multiple + /// of the font size _(required)_. #[derive(Debug, PartialEq)] pub struct $ident(Spacing); @@ -117,10 +130,10 @@ macro_rules! spacefunc { parse(args, body, _ctx) { parse!(forbidden: body); - let arg = args.get_expr()?; + let arg = args.get_pos::()?; let spacing = match arg.val { - Expression::Size(s) => Spacing::Absolute(s), - Expression::Number(f) => Spacing::Relative(f as f32), + Expression::Size(s) => Spacing::Absolute(*s), + Expression::Num(f) => Spacing::Relative(*f as f32), _ => err!("invalid spacing, expected size or number"), }; @@ -146,5 +159,8 @@ enum Spacing { Relative(f32), } -spacefunc!(HorizontalSpace, "h", space => Command::AddFlex(Layout::empty(space, Size::zero()))); -spacefunc!(VerticalSpace, "v", space => Command::Add(Layout::empty(Size::zero(), space))); +spacefunc!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.", + space => AddFlex(Layout::empty(space, Size::zero()))); + +spacefunc!(VerticalSpace, "📑 `v`: Adds vertical whitespace.", + space => Add(Layout::empty(Size::zero(), space))); diff --git a/src/library/style.rs b/src/library/style.rs index 48f4d4db2..9bcdcccd7 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -2,8 +2,8 @@ use crate::func::prelude::*; use toddle::query::FontClass; macro_rules! stylefunc { - ($ident:ident) => ( - /// Styles text. + ($ident:ident, $doc:expr) => ( + #[doc = $doc] #[derive(Debug, PartialEq)] pub struct $ident { body: Option @@ -24,7 +24,7 @@ macro_rules! stylefunc { Ok(match &this.body { Some(body) => commands![ Command::SetStyle(new_style), - Command::Layout(body), + Command::LayoutTree(body), Command::SetStyle(ctx.style.clone()), ], None => commands![Command::SetStyle(new_style)] @@ -34,6 +34,6 @@ macro_rules! stylefunc { ); } -stylefunc!(Italic); -stylefunc!(Bold); -stylefunc!(Monospace); +stylefunc!(Italic, "💡 `italic`: Sets text in _italics_."); +stylefunc!(Bold, "🧱 `bold`: Sets text in **bold**."); +stylefunc!(Monospace, "👩‍💻 `mono`: Sets text in `monospace`."); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 286bb2c77..09a5bdc39 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -128,7 +128,7 @@ pub enum FuncArg { pub enum Expression { Ident(String), Str(String), - Number(f64), + Num(f64), Size(Size), Bool(bool), } @@ -145,7 +145,7 @@ impl Display for Expression { match self { Ident(s) => write!(f, "{}", s), Str(s) => write!(f, "{:?}", s), - Number(n) => write!(f, "{}", n), + Num(n) => write!(f, "{}", n), Size(s) => write!(f, "{}", s), Bool(b) => write!(f, "{}", b), } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index f952f5e6a..e4cda6a59 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -211,7 +211,7 @@ impl<'s> Parser<'s> { if let Ok(b) = text.parse::() { Expression::Bool(b) } else if let Ok(num) = text.parse::() { - Expression::Number(num) + Expression::Num(num) } else if let Ok(size) = text.parse::() { Expression::Size(size) } else { @@ -499,7 +499,7 @@ mod tests { mod args { use super::Expression; - pub use Expression::{Number as N, Size as Z, Bool as B}; + pub use Expression::{Num as N, Size as Z, Bool as B}; pub fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) } pub fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) } diff --git a/tests/layouts/coma.typ b/tests/layouts/coma.typ index b56299eca..83900e03c 100644 --- a/tests/layouts/coma.typ +++ b/tests/layouts/coma.typ @@ -1,6 +1,6 @@ {size:420pt*300pt} -[box: horizontal][ +[box: flow=horizontal][ *Technical University Berlin* [n] *Faculty II, Institute for Mathematics* [n] Secretary Example [n] @@ -17,7 +17,7 @@ *Alle Antworten sind zu beweisen.* ] -[box: horizontal][ +[box: flow=horizontal][ *1. Aufgabe* [align: right][(1 + 1 + 2 Punkte)] ]