diff --git a/src/func.rs b/src/func.rs index 098708950..57dad1034 100644 --- a/src/func.rs +++ b/src/func.rs @@ -2,25 +2,31 @@ /// Useful things for creating functions. pub mod prelude { + pub use async_trait::async_trait; pub use crate::layout::prelude::*; + pub use crate::layout::Commands; pub use crate::layout::Command::{self, *}; pub use crate::style::*; - pub use crate::syntax::prelude::*; - pub use super::{expect_no_body, parse_maybe_body, OptionExt}; + pub use crate::syntax::expr::*; + pub use crate::syntax::parsing::{ + parse, FuncArgs, FuncBody, FuncCall, FuncHeader, ParseState, + }; + pub use crate::syntax::span::{Span, SpanVec, Spanned}; + pub use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree}; + pub use crate::syntax::value::*; + pub use crate::{Pass, Feedback}; + pub use super::*; } -use crate::syntax::parsing::{parse, ParseState}; -use crate::syntax::span::{Span, Spanned}; -use crate::syntax::tree::SyntaxTree; -use crate::Feedback; +use prelude::*; /// Extra methods on `Option`s used for function argument parsing. pub trait OptionExt: Sized { - /// Calls `f` with `val` if this is `Some(val)`. + /// Call `f` with `val` if this is `Some(val)`. fn with(self, f: impl FnOnce(T)); - /// Reports an error about a missing argument with the given name and span - /// if the option is `None`. + /// Report an error about a missing argument with the given name and span if + /// the option is `None`. fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self; } @@ -39,8 +45,19 @@ impl OptionExt for Option { } } -/// Parses a function's body if there is one or returns `None` otherwise. -pub fn parse_maybe_body( +/// Generate `unexpected argument` errors for all remaining arguments. +pub fn drain_args(args: FuncArgs, f: &mut Feedback) { + for arg in args.pos.0 { + error!(@f, arg.span, "unexpected argument"); + } + + for arg in args.key.0 { + error!(@f, arg.span, "unexpected argument"); + } +} + +/// Parse a function's body if there is one or return `None` otherwise. +pub fn parse_body_maybe( body: Option>, state: &ParseState, f: &mut Feedback, @@ -52,106 +69,9 @@ pub fn parse_maybe_body( }) } -/// Generates an error if there is function body even though none was expected. +/// Generate 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` module for examples on how the -/// macro works. -#[macro_export] -macro_rules! function { - // Entry point. - ($(#[$outer:meta])* $v:vis $storage:ident $name:ident $($r:tt)*) => { - function!(@def($name) $(#[$outer])* $v $storage $name $($r)*); - }; - (@def($name:ident) $definition:item $($r:tt)*) => { - $definition - function!(@meta($name) $($r)*); - }; - - // Metadata. - (@meta($name:ident) type Meta = $meta:ty; $($r:tt)*) => { - function!(@parse($name, $meta) $($r)*); - }; - (@meta($name:ident) $($r:tt)*) => { - function!(@parse($name, ()) $($r)*); - }; - - // Parse trait. - (@parse($($a:tt)*) parse(default) $($r:tt)*) => { - function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) { Default::default() } $($r)*); - }; - (@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => { - function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*); - }; - (@parse($name:ident, $meta:ty) parse( - $header:ident, - $body:ident, - $state:ident, - $feedback:ident, - $metadata:ident - ) $code:block $($r:tt)*) => { - impl $crate::syntax::parsing::ParseCall for $name { - type Meta = $meta; - - fn parse( - #[allow(unused)] mut call: $crate::syntax::parsing::FuncCall, - #[allow(unused)] $state: &$crate::syntax::parsing::ParseState, - #[allow(unused)] $metadata: Self::Meta, - ) -> $crate::Pass - where - Self: Sized, - { - let mut feedback = $crate::Feedback::new(); - #[allow(unused)] let $header = &mut call.header; - #[allow(unused)] let $body = call.body; - #[allow(unused)] let $feedback = &mut feedback; - - let func = $code; - - for arg in call.header.args.pos.0 { - error!(@feedback, arg.span, "unexpected argument"); - } - - for arg in call.header.args.key.0 { - error!(@feedback, arg.span, "unexpected argument"); - } - - $crate::Pass::new(func, feedback) - } - } - - function!(@layout($name) $($r)*); - }; - - (@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, - #[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>, - ) -> $crate::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>> - where - 'a: 't, - 'b: 't, - Self: 't, - { - Box::pin(async move { - let mut feedback = $crate::Feedback::new(); - #[allow(unused)] let $feedback = &mut feedback; - let commands = $code; - $crate::Pass::new(commands, feedback) - }) - } - } - }; -} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 3cfb872fd..5f5a48590 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,7 +10,9 @@ mod tree; /// Basic types used across the layouting engine. pub mod prelude { pub use super::primitive::*; - pub use super::layout; + pub use super::{ + BoxLayout, layout, Layout, LayoutContext, LayoutSpace, MultiLayout, + }; pub use Dir::*; pub use GenAlign::*; pub use GenAxis::*; @@ -46,7 +48,7 @@ pub struct BoxLayout { pub elements: LayoutElements, } -/// Comamnd-based layout. +/// Command-based layouting. #[async_trait(?Send)] pub trait Layout { /// Create a sequence of layouting commands to execute. diff --git a/src/lib.rs b/src/lib.rs index fa6300bb9..38109a158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::decoration::Decorations; use crate::syntax::parsing::{parse, ParseState}; use crate::syntax::span::{Offset, Pos}; -use crate::syntax::tree::SyntaxTree; +use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree}; /// Transforms source code into typesetted layouts. /// @@ -68,7 +68,7 @@ impl Typesetter { Self { loader, style: LayoutStyle::default(), - parse_state: ParseState { scope: crate::library::std() }, + parse_state: ParseState { scope: crate::library::_std() }, } } @@ -90,7 +90,6 @@ impl Typesetter { /// Layout a syntax tree and return the produced layout. pub async fn layout(&self, tree: &SyntaxTree) -> Pass { use crate::layout::prelude::*; - use crate::layout::{LayoutContext, LayoutSpace}; let margins = self.style.page.margins(); layout( @@ -141,6 +140,11 @@ impl Pass { Self { output, feedback } } + /// Create a new pass with empty feedback. + pub fn okay(output: T) -> Self { + Self { output, feedback: Feedback::new() } + } + /// Map the output type and keep the feedback data. pub fn map(self, f: impl FnOnce(T) -> U) -> Pass { Pass { @@ -150,6 +154,13 @@ impl Pass { } } +impl Pass { + /// Create a new pass from an unboxed dynamic node and feedback data.. + pub fn node(node: T, feedback: Feedback) -> Self { + Pass::new(SyntaxNode::boxed(node), feedback) + } +} + /// Diagnostic and semantic syntax highlighting data. #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct Feedback { diff --git a/src/library/align.rs b/src/library/align.rs new file mode 100644 index 000000000..8265c0f41 --- /dev/null +++ b/src/library/align.rs @@ -0,0 +1,76 @@ +use super::*; + +/// `align`: Align content along the layouting axes. +/// +/// # Positional arguments +/// - At most two of `left`, `right`, `top`, `bottom`, `center`. +/// +/// # Keyword arguments +/// - `horizontal`: Any of `left`, `right` or `center`. +/// - `vertical`: Any of `top`, `bottom` or `center`. +/// +/// There may not be two alignment specifications for the same axis. +pub fn align(call: FuncCall, state: &ParseState) -> Pass { + let mut f = Feedback::new(); + let mut args = call.header.args; + let node = AlignNode { + body: parse_body_maybe(call.body, state, &mut f), + aligns: args.pos.all::>().collect(), + h: args.key.get::>("horizontal", &mut f), + v: args.key.get::>("vertical", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct AlignNode { + body: Option, + aligns: SpanVec, + h: Option>, + v: Option>, +} + +#[async_trait(?Send)] +impl Layout for AlignNode { + async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass> { + let mut f = Feedback::new(); + + ctx.base = ctx.spaces[0].size; + + let axes = ctx.axes; + let all = self.aligns.iter() + .map(|align| { + let spec = align.v.axis().unwrap_or(axes.primary.axis()); + (spec, align) + }) + .chain(self.h.iter().map(|align| (Horizontal, align))) + .chain(self.v.iter().map(|align| (Vertical, align))); + + let mut had = [false; 2]; + for (axis, align) in all { + if align.v.axis().map(|a| a != axis).unwrap_or(false) { + error!( + @f, align.span, + "invalid alignment {} for {} axis", align.v, axis, + ); + } else if had[axis as usize] { + error!(@f, align.span, "duplicate alignment for {} axis", axis); + } else { + had[axis as usize] = true; + let gen_axis = axis.to_generic(ctx.axes); + let gen_align = align.v.to_generic(ctx.axes); + *ctx.align.get_mut(gen_axis) = gen_align; + } + } + + Pass::new(match &self.body { + Some(body) => { + let layouted = layout(body, ctx).await; + f.extend(layouted.feedback); + vec![AddMultiple(layouted.output)] + } + None => vec![SetAlignment(ctx.align)], + }, f) + } +} diff --git a/src/library/boxed.rs b/src/library/boxed.rs new file mode 100644 index 000000000..909115d54 --- /dev/null +++ b/src/library/boxed.rs @@ -0,0 +1,53 @@ +use crate::length::ScaleLength; +use super::*; + +/// `box`: Layouts its contents into a box. +/// +/// # Keyword arguments +/// - `width`: The width of the box (length of relative to parent's width). +/// - `height`: The height of the box (length of relative to parent's height). +pub fn boxed(call: FuncCall, state: &ParseState) -> Pass { + let mut f = Feedback::new(); + let mut args = call.header.args; + let node = BoxNode { + body: parse_body_maybe(call.body, state, &mut f).unwrap_or(SyntaxTree::new()), + width: args.key.get::("width", &mut f), + height: args.key.get::("height", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct BoxNode { + body: SyntaxTree, + width: Option, + height: Option, +} + +#[async_trait(?Send)] +impl Layout for BoxNode { + async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass> { + ctx.spaces.truncate(1); + ctx.repeat = false; + + self.width.with(|v| { + let length = v.raw_scaled(ctx.base.x); + ctx.base.x = length; + ctx.spaces[0].size.x = length; + ctx.spaces[0].expansion.horizontal = true; + }); + + self.height.with(|v| { + let length = v.raw_scaled(ctx.base.y); + ctx.base.y = length; + ctx.spaces[0].size.y = length; + ctx.spaces[0].expansion.vertical = true; + }); + + layout(&self.body, ctx).await.map(|out| { + let layout = out.into_iter().next().unwrap(); + vec![Add(layout)] + }) + } +} diff --git a/src/library/font.rs b/src/library/font.rs index 6e7110212..57ee92b35 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -3,53 +3,68 @@ use fontdock::{FontStyle, FontWeight, FontWidth}; use crate::length::ScaleLength; use super::*; -function! { - /// `font`: Configure the font. - #[derive(Debug, Clone, PartialEq)] - pub struct FontFunc { - body: Option, - size: Option, - style: Option, - weight: Option, - width: Option, - list: Vec, - classes: Vec<(String, Vec)>, - } +/// `font`: Configure the font. +/// +/// # Positional arguments +/// - The font size (optional, length or relative to previous font size). +/// - A font family fallback list (optional, identifiers or strings). +/// +/// # Keyword arguments +/// - `style`: `normal`, `italic` or `oblique`. +/// - `weight`: `100` - `900` or a name like `thin`. +/// - `width`: `1` - `9` or a name like `condensed`. +/// - Any other keyword argument whose value is a tuple of strings is a class +/// fallback definition like: +/// ```typst +/// serif = ("Source Serif Pro", "Noto Serif") +/// ``` +pub fn font(call: FuncCall, state: &ParseState) -> Pass { + let mut f = Feedback::new(); + let mut args = call.header.args; - parse(header, body, state, f) { - let size = header.args.pos.get::(); - let style = header.args.key.get::("style", f); - let weight = header.args.key.get::("weight", f); - let width = header.args.key.get::("width", f); + let node = FontNode { + body: parse_body_maybe(call.body, state, &mut f), + size: args.pos.get::(), + style: args.key.get::("style", &mut f), + weight: args.key.get::("weight", &mut f), + width: args.key.get::("width", &mut f), + list: { + args.pos.all::() + .map(|s| s.0.to_lowercase()) + .collect() + }, + classes: { + args.key.all::() + .collect::>() + .into_iter() + .map(|(class, mut tuple)| { + let fallback = tuple.all::() + .map(|s| s.0.to_lowercase()) + .collect(); + (class.v.0, fallback) + }) + .collect() + }, + }; - let list = header.args.pos.all::() - .map(|s| s.0.to_lowercase()) - .collect(); + drain_args(args, &mut f); + Pass::node(node, f) +} - let classes = header.args.key - .all::() - .collect::>() - .into_iter() - .map(|(class, mut tuple)| { - let fallback = tuple.all::() - .map(|s| s.0.to_lowercase()) - .collect(); - (class.v.0, fallback) - }) - .collect(); +#[derive(Debug, Clone, PartialEq)] +struct FontNode { + body: Option, + size: Option, + style: Option, + weight: Option, + width: Option, + list: Vec, + classes: Vec<(String, Vec)>, +} - Self { - body: parse_maybe_body(body, state, f), - size, - style, - weight, - width, - list, - classes, - } - } - - layout(self, ctx, f) { +#[async_trait(?Send)] +impl Layout for FontNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass> { let mut text = ctx.style.text.clone(); self.size.with(|s| match s { @@ -76,13 +91,13 @@ function! { text.fallback.flatten(); - match &self.body { + Pass::okay(match &self.body { Some(tree) => vec![ SetTextStyle(text), LayoutSyntaxTree(tree), SetTextStyle(ctx.style.text.clone()), ], None => vec![SetTextStyle(text)], - } + }) } } diff --git a/src/library/layout.rs b/src/library/layout.rs deleted file mode 100644 index f3ddaadfd..000000000 --- a/src/library/layout.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::length::ScaleLength; -use super::*; - -function! { - /// `box`: Layouts content into a box. - #[derive(Debug, Clone, PartialEq)] - pub struct BoxFunc { - body: SyntaxTree, - width: Option, - height: Option, - } - - parse(header, body, state, f) { - Self { - 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), - } - } - - layout(self, ctx, f) { - ctx.spaces.truncate(1); - ctx.repeat = false; - - self.width.with(|v| { - let length = v.raw_scaled(ctx.base.x); - ctx.base.x = length; - ctx.spaces[0].size.x = length; - ctx.spaces[0].expansion.horizontal = true; - }); - - self.height.with(|v| { - let length = v.raw_scaled(ctx.base.y); - ctx.base.y = length; - ctx.spaces[0].size.y = length; - ctx.spaces[0].expansion.vertical = true; - }); - - let layouted = layout(&self.body, ctx).await; - let layout = layouted.output.into_iter().next().unwrap(); - f.extend(layouted.feedback); - - vec![Add(layout)] - } -} - -function! { - /// `align`: Aligns content along the layouting axes. - #[derive(Debug, Clone, PartialEq)] - pub struct AlignFunc { - body: Option, - aligns: SpanVec, - h: Option>, - v: Option>, - } - - parse(header, body, state, f) { - Self { - 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), - } - } - - layout(self, ctx, f) { - ctx.base = ctx.spaces[0].size; - - let axes = ctx.axes; - let all = self.aligns.iter() - .map(|align| { - let spec = align.v.axis().unwrap_or(axes.primary.axis()); - (spec, align) - }) - .chain(self.h.iter().map(|align| (Horizontal, align))) - .chain(self.v.iter().map(|align| (Vertical, align))); - - let mut had = [false; 2]; - for (axis, align) in all { - if align.v.axis().map(|a| a != axis).unwrap_or(false) { - error!( - @f, align.span, - "invalid alignment {} for {} axis", align.v, axis, - ); - } else if had[axis as usize] { - error!(@f, align.span, "duplicate alignment for {} axis", axis); - } else { - had[axis as usize] = true; - let gen_axis = axis.to_generic(ctx.axes); - let gen_align = align.v.to_generic(ctx.axes); - *ctx.align.get_mut(gen_axis) = gen_align; - } - } - - match &self.body { - Some(body) => { - let layouted = layout(body, ctx).await; - f.extend(layouted.feedback); - vec![AddMultiple(layouted.output)] - } - None => vec![SetAlignment(ctx.align)], - } - } -} diff --git a/src/library/mod.rs b/src/library/mod.rs index 3b09a7685..ef24d74f0 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,54 +1,34 @@ //! The standard library. +mod align; +mod boxed; mod font; -mod layout; mod page; mod spacing; +mod val; +pub use align::*; +pub use boxed::*; pub use font::*; -pub use layout::*; pub use page::*; pub use spacing::*; +pub use val::*; use crate::func::prelude::*; use crate::syntax::scope::Scope; /// Create a scope with all standard library functions. -pub fn std() -> Scope { - let mut std = Scope::new::(); +pub fn _std() -> Scope { + let mut std = Scope::new(Box::new(val)); - std.add::("val"); - std.add::("font"); - std.add::("page"); - std.add::("align"); - std.add::("box"); - std.add::("pagebreak"); - std.add_with_meta::("h", Horizontal); - std.add_with_meta::("v", Vertical); + std.insert("val", Box::new(val)); + std.insert("font", Box::new(font)); + std.insert("page", Box::new(page)); + std.insert("align", Box::new(align)); + std.insert("box", Box::new(boxed)); + std.insert("pagebreak", Box::new(pagebreak)); + std.insert("h", Box::new(h)); + std.insert("v", Box::new(v)); std } - -function! { - /// `val`: Ignores all arguments and layouts the body flatly. - /// - /// This is also the fallback function, which is used when a function name - /// could not be resolved. - #[derive(Debug, Clone, PartialEq)] - pub struct ValFunc { - body: Option, - } - - parse(header, body, state, f) { - header.args.pos.0.clear(); - header.args.key.0.clear(); - Self { body: parse_maybe_body(body, state, f), } - } - - layout(self, ctx, f) { - match &self.body { - Some(tree) => vec![LayoutSyntaxTree(tree)], - None => vec![], - } - } -} diff --git a/src/library/page.rs b/src/library/page.rs index b13f8a1ed..0a0189944 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -2,37 +2,55 @@ use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass}; use super::*; -function! { - /// `page`: Configure pages. - #[derive(Debug, Clone, PartialEq)] - pub struct PageFunc { - paper: Option, - width: Option, - height: Option, - margins: Option, - left: Option, - right: Option, - top: Option, - bottom: Option, - flip: bool, - } +/// `page`: Configure pages. +/// +/// # Positional arguments +/// - The name of a paper, e.g. `a4` (optional). +/// +/// # Keyword arguments +/// - `width`: The width of pages (length). +/// - `height`: The height of pages (length). +/// - `margins`: The margins for all sides (length or relative to side lengths). +/// - `left`: The left margin (length or relative to width). +/// - `right`: The right margin (length or relative to width). +/// - `top`: The top margin (length or relative to height). +/// - `bottom`: The bottom margin (length or relative to height). +/// - `flip`: Flips custom or paper-defined width and height (boolean). +pub fn page(call: FuncCall, _: &ParseState) -> Pass { + let mut f = Feedback::new(); + let mut args = call.header.args; + expect_no_body(call.body, &mut f); + let node = PageNode { + paper: args.pos.get::(), + width: args.key.get::("width", &mut f), + height: args.key.get::("height", &mut f), + margins: args.key.get::("margins", &mut f), + left: args.key.get::("left", &mut f), + right: args.key.get::("right", &mut f), + top: args.key.get::("top", &mut f), + bottom: args.key.get::("bottom", &mut f), + flip: args.key.get::("flip", &mut f).unwrap_or(false), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} - parse(header, body, state, f) { - expect_no_body(body, f); - Self { - paper: header.args.pos.get::(), - width: header.args.key.get::("width", f), - height: header.args.key.get::("height", f), - margins: header.args.key.get::("margins", f), - left: header.args.key.get::("left", f), - right: header.args.key.get::("right", f), - top: header.args.key.get::("top", f), - bottom: header.args.key.get::("bottom", f), - flip: header.args.key.get::("flip", f).unwrap_or(false), - } - } +#[derive(Debug, Clone, PartialEq)] +struct PageNode { + paper: Option, + width: Option, + height: Option, + margins: Option, + left: Option, + right: Option, + top: Option, + bottom: Option, + flip: bool, +} - layout(self, ctx, f) { +#[async_trait(?Send)] +impl Layout for PageNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass> { let mut style = ctx.style.page; if let Some(paper) = self.paper { @@ -54,15 +72,23 @@ function! { style.size.swap(); } - vec![SetPageStyle(style)] + Pass::okay(vec![SetPageStyle(style)]) } } -function! { - /// `pagebreak`: Ends the current page. - #[derive(Debug, Default, Clone, PartialEq)] - pub struct PageBreakFunc; - - parse(default) - layout(self, ctx, f) { vec![BreakPage] } +/// `pagebreak`: Ends the current page. +pub fn pagebreak(call: FuncCall, _: &ParseState) -> Pass { + let mut f = Feedback::new(); + drain_args(call.header.args, &mut f); + Pass::node(PageBreakNode, f) +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct PageBreakNode; + +#[async_trait(?Send)] +impl Layout for PageBreakNode { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass> { + Pass::okay(vec![BreakPage]) + } } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6503556f0..14c6135aa 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -2,31 +2,49 @@ use crate::layout::SpacingKind; use crate::length::ScaleLength; use super::*; -function! { - /// `h` and `v`: Add horizontal or vertical spacing. - #[derive(Debug, Clone, PartialEq)] - pub struct SpacingFunc { - spacing: Option<(SpecAxis, ScaleLength)>, - } +/// `h`: Add horizontal spacing. +/// +/// # Positional arguments +/// - The spacing (length or relative to font size). +pub fn h(call: FuncCall, _: &ParseState) -> Pass { + spacing(call, Horizontal) +} - type Meta = SpecAxis; +/// `v`: Add vertical spacing. +/// +/// # Positional arguments +/// - The spacing (length or relative to font size). +pub fn v(call: FuncCall, _: &ParseState) -> Pass { + spacing(call, Vertical) +} - parse(header, body, state, f, meta) { - expect_no_body(body, f); - Self { - spacing: header.args.pos.expect::(f) - .map(|s| (meta, s)) - .or_missing(header.name.span, "spacing", f), - } - } +fn spacing(call: FuncCall, axis: SpecAxis) -> Pass { + let mut f = Feedback::new(); + let mut args = call.header.args; + expect_no_body(call.body, &mut f); + let node = SpacingNode { + spacing: args.pos.expect::(&mut f) + .map(|s| (axis, s)) + .or_missing(call.header.name.span, "spacing", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} - layout(self, ctx, f) { - if let Some((axis, spacing)) = self.spacing { +#[derive(Debug, Clone, PartialEq)] +struct SpacingNode { + spacing: Option<(SpecAxis, ScaleLength)>, +} + +#[async_trait(?Send)] +impl Layout for SpacingNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass> { + Pass::okay(if let Some((axis, spacing)) = self.spacing { let axis = axis.to_generic(ctx.axes); let spacing = spacing.raw_scaled(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] - } + }) } } diff --git a/src/library/val.rs b/src/library/val.rs new file mode 100644 index 000000000..8e431049c --- /dev/null +++ b/src/library/val.rs @@ -0,0 +1,28 @@ +use super::*; + +/// `val`: Ignores all arguments and layouts its body flatly. +/// +/// This is also the fallback function, which is used when a function name +/// cannot be resolved. +pub fn val(call: FuncCall, state: &ParseState) -> Pass { + let mut f = Feedback::new(); + let node = ValNode { + body: parse_body_maybe(call.body, state, &mut f), + }; + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct ValNode { + body: Option, +} + +#[async_trait(?Send)] +impl Layout for ValNode { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass> { + Pass::okay(match &self.body { + Some(tree) => vec![LayoutSyntaxTree(tree)], + None => vec![], + }) + } +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index e1c9bbb07..e0f4e4c85 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -12,11 +12,3 @@ pub mod span; pub mod tokens; pub mod tree; pub mod value; - -/// Basic types used around the syntax side. -pub mod prelude { - pub use super::expr::*; - pub use super::span::{Span, SpanVec, Spanned}; - pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree}; - pub use super::value::*; -} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 3c8020740..bf16e1469 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -8,36 +8,16 @@ use super::expr::*; use super::scope::Scope; use super::span::{Pos, Span, Spanned}; use super::tokens::{is_newline_char, Token, TokenMode, Tokens}; -use super::tree::{DynamicNode, SyntaxNode, SyntaxTree}; +use super::tree::{SyntaxNode, SyntaxTree}; /// A function which parses a function call into a dynamic node. -pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass>; - -/// Parse a function call. -pub trait ParseCall { - /// Metadata whose value is passed to `parse`. 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. - /// - /// For example, the functions `h` and `v` are built on the same type. - type Meta: Clone; - - /// Parse the function call. - fn parse( - call: FuncCall, - state: &ParseState, - metadata: Self::Meta, - ) -> Pass - where - Self: Sized; -} +pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass; /// An invocation of a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncCall<'s> { pub header: FuncHeader, - /// The body as a raw string containing what's inside of the brackets. - pub body: Option>, + pub body: FuncBody<'s>, } /// The parsed header of a function (everything in the first set of brackets). @@ -47,6 +27,10 @@ pub struct FuncHeader { pub args: FuncArgs, } +/// The body of a function as a raw spanned string containing what's inside of +/// the brackets. +pub type FuncBody<'s> = Option>; + /// The positional and keyword arguments passed to a function. #[derive(Debug, Default, Clone, PartialEq)] pub struct FuncArgs { @@ -215,7 +199,7 @@ impl<'s> FuncParser<'s> { let call = FuncCall { header, body: self.body }; let parsed = parser(call, self.state); self.feedback.extend(parsed.feedback); - Pass::new(SyntaxNode::Dyn(parsed.output), self.feedback) + Pass::new(parsed.output, self.feedback) } fn parse_func_header(&mut self) -> Option { @@ -678,7 +662,7 @@ fn unescape_raw(raw: &str) -> Vec { mod tests { use crate::length::Length; use crate::syntax::span::SpanVec; - use crate::syntax::test::{check, DebugFn}; + use crate::syntax::test::{check, debug_func, DebugNode}; use super::*; use Decoration::*; @@ -697,11 +681,11 @@ mod tests { }; ($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => { - let mut scope = Scope::new::(); - scope.add::("f"); - scope.add::("n"); - scope.add::("box"); - scope.add::("val"); + let mut scope = Scope::new(Box::new(debug_func)); + scope.insert("f", Box::new(debug_func)); + scope.insert("n", Box::new(debug_func)); + scope.insert("box", Box::new(debug_func)); + scope.insert("val", Box::new(debug_func)); let state = ParseState { scope }; let pass = parse($source, Pos::ZERO, &state); @@ -798,13 +782,13 @@ mod tests { value: Z($value), })));)*)? )? - SyntaxNode::Dyn(Box::new(DebugFn { + SyntaxNode::boxed(DebugNode { header: FuncHeader { name: span_item!($name).map(|s| Ident(s.to_string())), args, }, body: func!(@body $($($body)*)?), - })) + }) }}; (@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) }; (@body) => { None }; diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index aac2b1b80..c17ff64d0 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 super::parsing::{CallParser, ParseCall}; -use super::tree::DynamicNode; +use super::parsing::CallParser; /// A map from identifiers to function parsers. pub struct Scope { @@ -15,31 +14,16 @@ pub struct Scope { impl Scope { /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. - pub fn new() -> Self - where - F: ParseCall + DynamicNode + 'static - { + pub fn new(fallback: Box) -> Self { Self { parsers: HashMap::new(), - fallback: make_parser::(()), + fallback, } } /// Associate the given function name with a dynamic node type. - pub fn add(&mut self, name: &str) - where - F: ParseCall + DynamicNode + 'static - { - self.add_with_meta::(name, ()); - } - - /// Add a dynamic node type with additional metadata that is passed to the - /// parser. - 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)); + pub fn insert(&mut self, name: impl Into, parser: Box) { + self.parsers.insert(name.into(), parser); } /// Return the parser with the given name if there is one. @@ -58,13 +42,3 @@ impl Debug for Scope { f.debug_set().entries(self.parsers.keys()).finish() } } - -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 9faa7f233..7dec20e3b 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::func::parse_maybe_body; +use crate::func::prelude::*; use super::decoration::Decoration; use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple}; use super::parsing::{FuncArg, FuncArgs, FuncHeader}; @@ -58,26 +58,26 @@ macro_rules! span_item { }; } -function! { - /// Most functions in the tests are parsed into the debug function for easy - /// inspection of arguments and body. - #[derive(Debug, Clone, PartialEq)] - pub struct DebugFn { - pub header: FuncHeader, - pub body: Option, - } +pub fn debug_func(call: FuncCall, state: &ParseState) -> Pass { + let mut f = Feedback::new(); + let node = DebugNode { + header: call.header, + body: parse_body_maybe(call.body, state, &mut f), + }; + Pass::node(node, f) +} - parse(header, body, state, f) { - let cloned = header.clone(); - header.args.pos.0.clear(); - header.args.key.0.clear(); - Self { - header: cloned, - body: parse_maybe_body(body, state, f), - } - } +#[derive(Debug, Clone, PartialEq)] +pub struct DebugNode { + pub header: FuncHeader, + pub body: Option, +} - layout(self, ctx, f) { vec![] } +#[async_trait(?Send)] +impl Layout for DebugNode { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass> { + unimplemented!() + } } /// Compares elements by only looking at values and ignoring spans. @@ -87,8 +87,8 @@ pub trait SpanlessEq { impl SpanlessEq for SyntaxNode { fn spanless_eq(&self, other: &Self) -> bool { - fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn { - func.downcast::().expect("not a debug fn") + fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugNode { + func.downcast::().expect("not a debug node") } match (self, other) { @@ -101,7 +101,7 @@ impl SpanlessEq for SyntaxNode { } } -impl SpanlessEq for DebugFn { +impl SpanlessEq for DebugNode { fn spanless_eq(&self, other: &Self) -> bool { self.header.spanless_eq(&other.header) && self.body.spanless_eq(&other.body) diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index 73734d28d..4ce39cd4b 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -31,6 +31,13 @@ pub enum SyntaxNode { Dyn(Box), } +impl SyntaxNode { + /// Create a `Dyn` variant from an unboxed dynamic node. + pub fn boxed(node: T) -> SyntaxNode { + SyntaxNode::Dyn(Box::new(node)) + } +} + impl PartialEq for SyntaxNode { fn eq(&self, other: &SyntaxNode) -> bool { use SyntaxNode::*; @@ -65,10 +72,7 @@ pub trait DynamicNode: Debug + Layout { impl dyn DynamicNode { /// Downcast this dynamic node to a concrete node. - pub fn downcast(&self) -> Option<&T> - where - T: DynamicNode + 'static, - { + pub fn downcast(&self) -> Option<&T> { self.as_any().downcast_ref::() } } diff --git a/src/syntax/value.rs b/src/syntax/value.rs index b7211eaf0..c523ff931 100644 --- a/src/syntax/value.rs +++ b/src/syntax/value.rs @@ -2,7 +2,7 @@ use fontdock::{FontStyle, FontWeight, FontWidth}; -use crate::layout::prelude::*; +use crate::layout::{Dir, SpecAlign}; use crate::length::{Length, ScaleLength}; use crate::paper::Paper; use crate::Feedback; @@ -103,10 +103,10 @@ macro_rules! ident_value { } ident_value!(Dir, "direction", |s| match s { - "ltr" => Some(LTR), - "rtl" => Some(RTL), - "ttb" => Some(TTB), - "btt" => Some(BTT), + "ltr" => Some(Self::LTR), + "rtl" => Some(Self::RTL), + "ttb" => Some(Self::TTB), + "btt" => Some(Self::BTT), _ => None, });