From 5a8f2fb73ddafba9fdbe952385ae2676126183ae Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 2 Aug 2020 22:21:58 +0200 Subject: [PATCH] =?UTF-8?q?Replace=20body!=20macro=20with=20functions=20?= =?UTF-8?q?=F0=9F=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/func.rs | 107 +++++++++++++++-------------------------- src/library/font.rs | 4 +- src/library/layout.rs | 8 +-- src/library/mod.rs | 4 +- src/library/page.rs | 2 +- src/library/spacing.rs | 2 +- src/syntax/mod.rs | 8 +++ src/syntax/parsing.rs | 21 ++++++++ src/syntax/scope.rs | 15 +++--- src/syntax/test.rs | 3 +- 10 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/func.rs b/src/func.rs index afc796ae3..bb2862d28 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,44 +1,20 @@ -//! Trait and prelude for custom functions. +//! Tools for building custom functions. -use crate::{Pass, Feedback}; -use crate::syntax::parsing::{FuncCall, ParseState}; -use crate::syntax::span::Span; +use crate::Feedback; +use crate::syntax::span::{Span, Spanned}; +use crate::syntax::parsing::{parse, ParseState}; +use crate::syntax::tree::SyntaxTree; -/// Types that are useful for creating your own functions. +/// Useful things for creating functions. pub mod prelude { - pub use crate::{function, body, error, warning}; pub use crate::layout::prelude::*; pub use crate::layout::Command::{self, *}; - pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; - pub use crate::syntax::expr::*; - pub use crate::syntax::tree::SyntaxTree; - pub use crate::syntax::span::{Span, Spanned}; - pub use crate::syntax::value::*; - pub use super::OptionExt; + pub use crate::syntax::prelude::*; + pub use crate::style::*; + pub use super::{OptionExt, parse_maybe_body, expect_no_body}; } -/// Parse a function from source code. -pub trait ParseFunc { - /// A metadata type whose value is passed into the function parser. This - /// allows a single function to do different things depending on the value - /// that needs to be given when inserting the function into a - /// [scope](crate::syntax::Scope). - /// - /// For example, the functions `word.spacing`, `line.spacing` and - /// `par.spacing` are actually all the same function - /// [`ContentSpacingFunc`](crate::library::ContentSpacingFunc) with the - /// metadata specifiy which content should be spaced. - type Meta: Clone; - - /// Parse the header and body into this function given a context. - fn parse( - header: FuncCall, - state: &ParseState, - metadata: Self::Meta, - ) -> Pass where Self: Sized; -} - -/// Extra methods on [`Options`](Option) used for argument parsing. +/// Extra methods on [`Options`](Option) used for function argument parsing. pub trait OptionExt: Sized { /// Calls `f` with `val` if this is `Some(val)`. fn with(self, f: impl FnOnce(T)); @@ -63,10 +39,30 @@ impl OptionExt for Option { } } -/// Allows to implement a function type concisely. +/// Parses a function's body if there is one or returns `None` otherwise. +pub fn parse_maybe_body( + body: Option>, + state: &ParseState, + f: &mut Feedback, +) -> Option { + body.map(|body| { + let parsed = parse(body.v, body.span.start, state); + f.extend(parsed.feedback); + parsed.output + }) +} + +/// Generates an error if there is function body even though none was expected. +pub fn expect_no_body(body: Option>, f: &mut Feedback) { + if let Some(body) = body { + error!(@f, body.span, "unexpected body"); + } +} + +/// Implement a custom function concisely. /// /// # Examples -/// Look at the source code of the [`library`](crate::library) module for more +/// Look at the source code of the [`library`](crate::library) module for /// examples on how the macro works. #[macro_export] macro_rules! function { @@ -101,7 +97,7 @@ macro_rules! function { $feedback:ident, $metadata:ident ) $code:block $($r:tt)*) => { - impl $crate::func::ParseFunc for $name { + impl $crate::syntax::parsing::ParseCall for $name { type Meta = $meta; fn parse( @@ -111,7 +107,7 @@ macro_rules! function { ) -> $crate::Pass where Self: Sized { let mut feedback = $crate::Feedback::new(); #[allow(unused)] let $header = &mut call.header; - #[allow(unused)] let $body = &mut call.body; + #[allow(unused)] let $body = call.body; #[allow(unused)] let $feedback = &mut feedback; let func = $code; @@ -131,7 +127,11 @@ macro_rules! function { function!(@layout($name) $($r)*); }; - (@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => { + (@layout($name:ident) layout( + $this:ident, + $ctx:ident, + $feedback:ident + ) $code:block) => { impl $crate::layout::Layout for $name { fn layout<'a, 'b, 't>( #[allow(unused)] &'a $this, @@ -152,32 +152,3 @@ macro_rules! function { } }; } - -/// Parse the body of a function. -/// -/// - If the function does not expect a body, use `body!(nope: body, feedback)`. -/// - If the function can have a body, use `body!(opt: body, state, feedback, -/// decos)`. -/// -/// # Arguments -/// - The `$body` should be of type `Option>`. -/// - The `$state` is the parse state to use. -/// - The `$feedback` should be a mutable references to a -/// [`Feedback`](crate::Feedback) struct which is filled with the feedback -/// information arising from parsing. -#[macro_export] -macro_rules! body { - (opt: $body:expr, $state:expr, $feedback:expr) => ({ - $body.map(|body| { - let parsed = $crate::syntax::parsing::parse(body.v, body.span.start, $state); - $feedback.extend(parsed.feedback); - parsed.output - }) - }); - - (nope: $body:expr, $feedback:expr) => { - if let Some(body) = $body { - error!(@$feedback, body.span, "unexpected body"); - } - }; -} diff --git a/src/library/font.rs b/src/library/font.rs index efcbb86fa..21ac14c75 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -17,7 +17,7 @@ function! { classes: Vec<(String, Vec)>, } - parse(header, body, ctx, f) { + parse(header, body, state, f) { let size = header.args.pos.get::(); let style = header.args.key.get::("style", f); @@ -41,7 +41,7 @@ function! { .collect(); FontFunc { - body: body!(opt: body, ctx, f), + body: parse_maybe_body(body, state, f), size, style, weight, diff --git a/src/library/layout.rs b/src/library/layout.rs index d46265a46..d6d024363 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -12,9 +12,9 @@ function! { height: Option, } - parse(header, body, ctx, f) { + parse(header, body, state, f) { BoxFunc { - body: body!(opt: body, ctx, f).unwrap_or(SyntaxTree::new()), + body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()), width: header.args.key.get::("width", f), height: header.args.key.get::("height", f), } @@ -56,9 +56,9 @@ function! { v: Option>, } - parse(header, body, ctx, f) { + parse(header, body, state, f) { AlignFunc { - body: body!(opt: body, ctx, f), + body: parse_maybe_body(body, state, f), aligns: header.args.pos.all::>().collect(), h: header.args.key.get::>("horizontal", f), v: header.args.key.get::>("vertical", f), diff --git a/src/library/mod.rs b/src/library/mod.rs index 7a664257a..7240e42bb 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,4 +1,4 @@ -//! The _Typst_ standard library. +//! The standard library. use crate::func::prelude::*; use crate::layout::{LayoutContext, Commands}; @@ -37,7 +37,7 @@ function! { parse(header, body, state, f) { header.args.pos.0.clear(); header.args.key.0.clear(); - ValFunc { body: body!(opt: body, state, f) } + ValFunc { body: parse_maybe_body(body, state, f), } } layout(self, ctx, f) { diff --git a/src/library/page.rs b/src/library/page.rs index d1964fd28..faf08ee04 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -20,7 +20,7 @@ function! { } parse(header, body, state, f) { - body!(nope: body, f); + expect_no_body(body, f); PageFunc { paper: header.args.pos.get::(), width: header.args.key.get::("width", f), diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 22c4669e3..d8263694a 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -34,7 +34,7 @@ function! { type Meta = SpecAxis; parse(header, body, state, f, meta) { - body!(nope: body, f); + expect_no_body(body, f); SpacingFunc { spacing: header.args.pos.expect::(f) .map(|s| (meta, s)) diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 86c2fd248..6b1f3d087 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -4,6 +4,14 @@ #[macro_use] mod test; +/// Basic types used around the syntax side. +pub mod prelude { + pub use super::expr::*; + pub use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; + pub use super::span::{SpanVec, Span, Spanned}; + pub use super::value::*; +} + pub mod decoration; pub mod expr; pub mod tree; diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 7594c14da..bcbcb8d45 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -13,6 +13,27 @@ use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; /// A function which parses a function call into a tree. pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass>; +/// Parse a function call. +pub trait ParseCall { + /// A metadata type whose value is passed into the function parser. This + /// allows a single function to do different things depending on the value + /// that needs to be given when inserting the function into a + /// [scope](crate::syntax::Scope). + /// + /// For example, the functions `word.spacing`, `line.spacing` and + /// `par.spacing` are actually all the same function + /// [`ContentSpacingFunc`](crate::library::ContentSpacingFunc) with the + /// metadata specifiy which content should be spaced. + type Meta: Clone; + + /// Parse the header and body into this function given a context. + fn parse( + header: FuncCall, + state: &ParseState, + metadata: Self::Meta, + ) -> Pass where Self: Sized; +} + /// An invocation of a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncCall<'s> { diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index d30929442..8fdad6a01 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; -use crate::func::ParseFunc; -use super::parsing::CallParser; +use super::parsing::{CallParser, ParseCall}; use super::tree::DynamicNode; /// A map from identifiers to function parsers. @@ -17,7 +16,7 @@ impl Scope { /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. pub fn new() -> Scope - where F: ParseFunc + DynamicNode + 'static { + where F: ParseCall + DynamicNode + 'static { Scope { parsers: HashMap::new(), fallback: make_parser::(()), @@ -31,14 +30,14 @@ impl Scope { /// Associate the given name with a type that is parseable into a function. pub fn add(&mut self, name: &str) - where F: ParseFunc + DynamicNode + 'static { + where F: ParseCall + DynamicNode + 'static { self.add_with_meta::(name, ()); } /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). - pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) - where F: ParseFunc + DynamicNode + 'static { + pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) + where F: ParseCall + DynamicNode + 'static { self.parsers.insert( name.to_string(), make_parser::(metadata), @@ -64,8 +63,8 @@ impl Debug for Scope { } } -fn make_parser(metadata: ::Meta) -> Box -where F: ParseFunc + DynamicNode + 'static { +fn make_parser(metadata: ::Meta) -> Box +where F: ParseCall + DynamicNode + 'static { Box::new(move |f, s| { F::parse(f, s, metadata.clone()) .map(|tree| Box::new(tree) as Box) diff --git a/src/syntax/test.rs b/src/syntax/test.rs index b701e5777..504bc3340 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; +use crate::func::parse_maybe_body; use super::decoration::Decoration; use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair}; use super::parsing::{FuncHeader, FuncArgs, FuncArg}; @@ -71,7 +72,7 @@ function! { header.args.key.0.clear(); DebugFn { header: cloned, - body: body!(opt: body, state, f), + body: parse_maybe_body(body, state, f), } }