Replace body! macro with functions 🧰

This commit is contained in:
Laurenz 2020-08-02 22:21:58 +02:00
parent 266d457292
commit 5a8f2fb73d
10 changed files with 87 additions and 87 deletions

View File

@ -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<Self> 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<T>: Sized {
/// Calls `f` with `val` if this is `Some(val)`.
fn with(self, f: impl FnOnce(T));
@ -63,10 +39,30 @@ impl<T> OptionExt<T> for Option<T> {
}
}
/// 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<Spanned<&str>>,
state: &ParseState,
f: &mut Feedback,
) -> Option<SyntaxTree> {
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<Spanned<&str>>, 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<Self> 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<Spanned<&str>>`.
/// - 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");
}
};
}

View File

@ -17,7 +17,7 @@ function! {
classes: Vec<(String, Vec<String>)>,
}
parse(header, body, ctx, f) {
parse(header, body, state, f) {
let size = header.args.pos.get::<ScaleLength>();
let style = header.args.key.get::<FontStyle>("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,

View File

@ -12,9 +12,9 @@ function! {
height: Option<ScaleLength>,
}
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::<ScaleLength>("width", f),
height: header.args.key.get::<ScaleLength>("height", f),
}
@ -56,9 +56,9 @@ function! {
v: Option<Spanned<SpecAlign>>,
}
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::<Spanned<SpecAlign>>().collect(),
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
v: header.args.key.get::<Spanned<SpecAlign>>("vertical", f),

View File

@ -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) {

View File

@ -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::<Paper>(),
width: header.args.key.get::<Length>("width", f),

View File

@ -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::<ScaleLength>(f)
.map(|s| (meta, s))

View File

@ -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;

View File

@ -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<Box<dyn DynamicNode>>;
/// 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<Self> where Self: Sized;
}
/// An invocation of a function.
#[derive(Debug, Clone, PartialEq)]
pub struct FuncCall<'s> {

View File

@ -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<F>() -> Scope
where F: ParseFunc<Meta=()> + DynamicNode + 'static {
where F: ParseCall<Meta=()> + DynamicNode + 'static {
Scope {
parsers: HashMap::new(),
fallback: make_parser::<F>(()),
@ -31,14 +30,14 @@ impl Scope {
/// Associate the given name with a type that is parseable into a function.
pub fn add<F>(&mut self, name: &str)
where F: ParseFunc<Meta=()> + DynamicNode + 'static {
where F: ParseCall<Meta=()> + DynamicNode + 'static {
self.add_with_meta::<F>(name, ());
}
/// Add a parseable type with additional metadata that is given to the
/// parser (other than the default of `()`).
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
where F: ParseFunc + DynamicNode + 'static {
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
where F: ParseCall + DynamicNode + 'static {
self.parsers.insert(
name.to_string(),
make_parser::<F>(metadata),
@ -64,8 +63,8 @@ impl Debug for Scope {
}
}
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
where F: ParseFunc + DynamicNode + 'static {
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
where F: ParseCall + DynamicNode + 'static {
Box::new(move |f, s| {
F::parse(f, s, metadata.clone())
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)

View File

@ -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),
}
}