mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
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::Feedback;
|
||||||
use crate::syntax::parsing::{FuncCall, ParseState};
|
use crate::syntax::span::{Span, Spanned};
|
||||||
use crate::syntax::span::Span;
|
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 mod prelude {
|
||||||
pub use crate::{function, body, error, warning};
|
|
||||||
pub use crate::layout::prelude::*;
|
pub use crate::layout::prelude::*;
|
||||||
pub use crate::layout::Command::{self, *};
|
pub use crate::layout::Command::{self, *};
|
||||||
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
pub use crate::syntax::prelude::*;
|
||||||
pub use crate::syntax::expr::*;
|
pub use crate::style::*;
|
||||||
pub use crate::syntax::tree::SyntaxTree;
|
pub use super::{OptionExt, parse_maybe_body, expect_no_body};
|
||||||
pub use crate::syntax::span::{Span, Spanned};
|
|
||||||
pub use crate::syntax::value::*;
|
|
||||||
pub use super::OptionExt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a function from source code.
|
/// Extra methods on [`Options`](Option) used for function argument parsing.
|
||||||
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.
|
|
||||||
pub trait OptionExt<T>: Sized {
|
pub trait OptionExt<T>: Sized {
|
||||||
/// Calls `f` with `val` if this is `Some(val)`.
|
/// Calls `f` with `val` if this is `Some(val)`.
|
||||||
fn with(self, f: impl FnOnce(T));
|
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
|
/// # 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.
|
/// examples on how the macro works.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! function {
|
macro_rules! function {
|
||||||
@ -101,7 +97,7 @@ macro_rules! function {
|
|||||||
$feedback:ident,
|
$feedback:ident,
|
||||||
$metadata:ident
|
$metadata:ident
|
||||||
) $code:block $($r:tt)*) => {
|
) $code:block $($r:tt)*) => {
|
||||||
impl $crate::func::ParseFunc for $name {
|
impl $crate::syntax::parsing::ParseCall for $name {
|
||||||
type Meta = $meta;
|
type Meta = $meta;
|
||||||
|
|
||||||
fn parse(
|
fn parse(
|
||||||
@ -111,7 +107,7 @@ macro_rules! function {
|
|||||||
) -> $crate::Pass<Self> where Self: Sized {
|
) -> $crate::Pass<Self> where Self: Sized {
|
||||||
let mut feedback = $crate::Feedback::new();
|
let mut feedback = $crate::Feedback::new();
|
||||||
#[allow(unused)] let $header = &mut call.header;
|
#[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;
|
#[allow(unused)] let $feedback = &mut feedback;
|
||||||
|
|
||||||
let func = $code;
|
let func = $code;
|
||||||
@ -131,7 +127,11 @@ macro_rules! function {
|
|||||||
function!(@layout($name) $($r)*);
|
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 {
|
impl $crate::layout::Layout for $name {
|
||||||
fn layout<'a, 'b, 't>(
|
fn layout<'a, 'b, 't>(
|
||||||
#[allow(unused)] &'a $this,
|
#[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>)>,
|
classes: Vec<(String, Vec<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
parse(header, body, state, f) {
|
||||||
let size = header.args.pos.get::<ScaleLength>();
|
let size = header.args.pos.get::<ScaleLength>();
|
||||||
|
|
||||||
let style = header.args.key.get::<FontStyle>("style", f);
|
let style = header.args.key.get::<FontStyle>("style", f);
|
||||||
@ -41,7 +41,7 @@ function! {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
FontFunc {
|
FontFunc {
|
||||||
body: body!(opt: body, ctx, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
size,
|
size,
|
||||||
style,
|
style,
|
||||||
weight,
|
weight,
|
||||||
|
@ -12,9 +12,9 @@ function! {
|
|||||||
height: Option<ScaleLength>,
|
height: Option<ScaleLength>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
parse(header, body, state, f) {
|
||||||
BoxFunc {
|
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),
|
width: header.args.key.get::<ScaleLength>("width", f),
|
||||||
height: header.args.key.get::<ScaleLength>("height", f),
|
height: header.args.key.get::<ScaleLength>("height", f),
|
||||||
}
|
}
|
||||||
@ -56,9 +56,9 @@ function! {
|
|||||||
v: Option<Spanned<SpecAlign>>,
|
v: Option<Spanned<SpecAlign>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
parse(header, body, state, f) {
|
||||||
AlignFunc {
|
AlignFunc {
|
||||||
body: body!(opt: body, ctx, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
|
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
|
||||||
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
|
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
|
||||||
v: header.args.key.get::<Spanned<SpecAlign>>("vertical", 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::func::prelude::*;
|
||||||
use crate::layout::{LayoutContext, Commands};
|
use crate::layout::{LayoutContext, Commands};
|
||||||
@ -37,7 +37,7 @@ function! {
|
|||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
header.args.pos.0.clear();
|
header.args.pos.0.clear();
|
||||||
header.args.key.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) {
|
layout(self, ctx, f) {
|
||||||
|
@ -20,7 +20,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
body!(nope: body, f);
|
expect_no_body(body, f);
|
||||||
PageFunc {
|
PageFunc {
|
||||||
paper: header.args.pos.get::<Paper>(),
|
paper: header.args.pos.get::<Paper>(),
|
||||||
width: header.args.key.get::<Length>("width", f),
|
width: header.args.key.get::<Length>("width", f),
|
||||||
|
@ -34,7 +34,7 @@ function! {
|
|||||||
type Meta = SpecAxis;
|
type Meta = SpecAxis;
|
||||||
|
|
||||||
parse(header, body, state, f, meta) {
|
parse(header, body, state, f, meta) {
|
||||||
body!(nope: body, f);
|
expect_no_body(body, f);
|
||||||
SpacingFunc {
|
SpacingFunc {
|
||||||
spacing: header.args.pos.expect::<ScaleLength>(f)
|
spacing: header.args.pos.expect::<ScaleLength>(f)
|
||||||
.map(|s| (meta, s))
|
.map(|s| (meta, s))
|
||||||
|
@ -4,6 +4,14 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test;
|
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 decoration;
|
||||||
pub mod expr;
|
pub mod expr;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
@ -13,6 +13,27 @@ use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
|||||||
/// A function which parses a function call into a tree.
|
/// A function which parses a function call into a tree.
|
||||||
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
|
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.
|
/// An invocation of a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncCall<'s> {
|
pub struct FuncCall<'s> {
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use crate::func::ParseFunc;
|
use super::parsing::{CallParser, ParseCall};
|
||||||
use super::parsing::CallParser;
|
|
||||||
use super::tree::DynamicNode;
|
use super::tree::DynamicNode;
|
||||||
|
|
||||||
/// A map from identifiers to function parsers.
|
/// 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
|
/// Create a new empty scope with a fallback parser that is invoked when no
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn new<F>() -> Scope
|
pub fn new<F>() -> Scope
|
||||||
where F: ParseFunc<Meta=()> + DynamicNode + 'static {
|
where F: ParseCall<Meta=()> + DynamicNode + 'static {
|
||||||
Scope {
|
Scope {
|
||||||
parsers: HashMap::new(),
|
parsers: HashMap::new(),
|
||||||
fallback: make_parser::<F>(()),
|
fallback: make_parser::<F>(()),
|
||||||
@ -31,14 +30,14 @@ impl Scope {
|
|||||||
|
|
||||||
/// Associate the given name with a type that is parseable into a function.
|
/// Associate the given name with a type that is parseable into a function.
|
||||||
pub fn add<F>(&mut self, name: &str)
|
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, ());
|
self.add_with_meta::<F>(name, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a parseable type with additional metadata that is given to the
|
/// Add a parseable type with additional metadata that is given to the
|
||||||
/// parser (other than the default of `()`).
|
/// parser (other than the default of `()`).
|
||||||
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
|
||||||
where F: ParseFunc + DynamicNode + 'static {
|
where F: ParseCall + DynamicNode + 'static {
|
||||||
self.parsers.insert(
|
self.parsers.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
make_parser::<F>(metadata),
|
make_parser::<F>(metadata),
|
||||||
@ -64,8 +63,8 @@ impl Debug for Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
|
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
|
||||||
where F: ParseFunc + DynamicNode + 'static {
|
where F: ParseCall + DynamicNode + 'static {
|
||||||
Box::new(move |f, s| {
|
Box::new(move |f, s| {
|
||||||
F::parse(f, s, metadata.clone())
|
F::parse(f, s, metadata.clone())
|
||||||
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::func::parse_maybe_body;
|
||||||
use super::decoration::Decoration;
|
use super::decoration::Decoration;
|
||||||
use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
|
use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
|
||||||
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
||||||
@ -71,7 +72,7 @@ function! {
|
|||||||
header.args.key.0.clear();
|
header.args.key.0.clear();
|
||||||
DebugFn {
|
DebugFn {
|
||||||
header: cloned,
|
header: cloned,
|
||||||
body: body!(opt: body, state, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user