summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-07 19:02:13 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-07 19:07:11 +0100
commit1ece263579afcf94ca44acc8e157bce01c3226b8 (patch)
tree6da8a7b03bf2138efb728d20f506aec3557afe5c /src
parent271af7ed0308c9eca7da5dce93d52d38be84889f (diff)
Create easy-to-use argument parser 💎
Diffstat (limited to 'src')
-rw-r--r--src/func/helpers.rs133
-rw-r--r--src/func/mod.rs40
-rw-r--r--src/layout/actions.rs2
-rw-r--r--src/layout/stacked.rs2
-rw-r--r--src/layout/tree.rs2
-rw-r--r--src/library/structure.rs52
-rw-r--r--src/library/style.rs12
-rw-r--r--src/syntax/mod.rs4
-rw-r--r--src/syntax/parsing.rs4
9 files changed, 165 insertions, 86 deletions
diff --git a/src/func/helpers.rs b/src/func/helpers.rs
index a7c270c2..6e6896c9 100644
--- a/src/func/helpers.rs
+++ b/src/func/helpers.rs
@@ -1,8 +1,9 @@
-use std::iter::Peekable;
-use std::slice::Iter;
+//! Helper types and macros for creating custom functions.
+
use super::prelude::*;
+use Expression::*;
-/// Implement the function trait more concisely.
+/// 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<Self> where Self: Sized
- {
- Arguments::new(header).done()?;
+ fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
+ 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<Self> where Self: Sized
- {
- #[allow(unused_mut)] let mut $args = Arguments::new(header);
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
+ 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<Iter<'a, Spanned<Expression>>>,
+/// 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<Expression>> {
- 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<T>(&mut self) -> ParseResult<Spanned<T::Output>> where T: Argument<'a> {
+ self.get_pos_opt::<T>()?
+ .ok_or_else(|| err!(@"expected {}", T::ERROR_MESSAGE))
}
- pub fn get_ident(&mut self) -> ParseResult<Spanned<&'a str>> {
- 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<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
}
- pub fn get_ident_if_present(&mut self) -> ParseResult<Option<Spanned<&'a str>>> {
- 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<T>(&mut self, key: &str) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ self.get_key_opt::<T>(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<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 {
- 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<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>> {
+ 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 402f0111..bd61204e 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 67ad48b4..707d6113 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 7521967b..379579f5 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 131570df..1d88751f 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 977d7499..e6d242a0 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<SyntaxTree>,
@@ -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::<ArgIdent>()?;
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::<ArgIdent>("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::<ArgExpr>()?;
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 48f4d4db..9bcdcccd 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<SyntaxTree>
@@ -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 286bb2c7..09a5bdc3 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 f952f5e6..e4cda6a5 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::<bool>() {
Expression::Bool(b)
} else if let Ok(num) = text.parse::<f64>() {
- Expression::Number(num)
+ Expression::Num(num)
} else if let Ok(size) = text.parse::<Size>() {
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()) }