Replace body! macro with functions 🧰
This commit is contained in:
parent
266d457292
commit
5a8f2fb73d
107
src/func.rs
107
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<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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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> {
|
||||
|
@ -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>)
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user