summaryrefslogtreecommitdiff
path: root/src/func
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/func
parent271af7ed0308c9eca7da5dce93d52d38be84889f (diff)
Create easy-to-use argument parser 💎
Diffstat (limited to 'src/func')
-rw-r--r--src/func/helpers.rs133
-rw-r--r--src/func/mod.rs40
2 files changed, 118 insertions, 55 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,)*])