diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-04 13:48:07 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-04 13:48:07 +0200 |
| commit | 2467cd6272c13b618ad53c5dadff5b8c8e7885bf (patch) | |
| tree | 6ad13ec06a04997564efc514b40daa3fb65233e2 /src/library | |
| parent | ed4fdcb0ada909f1cc3d7436334e253f0ec14d55 (diff) | |
Refactor function parsing ♻
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/align.rs | 76 | ||||
| -rw-r--r-- | src/library/boxed.rs | 53 | ||||
| -rw-r--r-- | src/library/font.rs | 105 | ||||
| -rw-r--r-- | src/library/layout.rs | 104 | ||||
| -rw-r--r-- | src/library/mod.rs | 54 | ||||
| -rw-r--r-- | src/library/page.rs | 98 | ||||
| -rw-r--r-- | src/library/spacing.rs | 54 | ||||
| -rw-r--r-- | src/library/val.rs | 28 |
8 files changed, 332 insertions, 240 deletions
diff --git a/src/library/align.rs b/src/library/align.rs new file mode 100644 index 00000000..8265c0f4 --- /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<SyntaxNode> { + 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::<Spanned<SpecAlign>>().collect(), + h: args.key.get::<Spanned<SpecAlign>>("horizontal", &mut f), + v: args.key.get::<Spanned<SpecAlign>>("vertical", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct AlignNode { + body: Option<SyntaxTree>, + aligns: SpanVec<SpecAlign>, + h: Option<Spanned<SpecAlign>>, + v: Option<Spanned<SpecAlign>>, +} + +#[async_trait(?Send)] +impl Layout for AlignNode { + async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> { + 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 00000000..909115d5 --- /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<SyntaxNode> { + 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::<ScaleLength>("width", &mut f), + height: args.key.get::<ScaleLength>("height", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct BoxNode { + body: SyntaxTree, + width: Option<ScaleLength>, + height: Option<ScaleLength>, +} + +#[async_trait(?Send)] +impl Layout for BoxNode { + async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> { + 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 6e711021..57ee92b3 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<SyntaxTree>, - size: Option<ScaleLength>, - style: Option<FontStyle>, - weight: Option<FontWeight>, - width: Option<FontWidth>, - list: Vec<String>, - classes: Vec<(String, Vec<String>)>, - } - - parse(header, body, state, f) { - let size = header.args.pos.get::<ScaleLength>(); - let style = header.args.key.get::<FontStyle>("style", f); - let weight = header.args.key.get::<FontWeight>("weight", f); - let width = header.args.key.get::<FontWidth>("width", f); +/// `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<SyntaxNode> { + let mut f = Feedback::new(); + let mut args = call.header.args; - let list = header.args.pos.all::<StringLike>() - .map(|s| s.0.to_lowercase()) - .collect(); + let node = FontNode { + body: parse_body_maybe(call.body, state, &mut f), + size: args.pos.get::<ScaleLength>(), + style: args.key.get::<FontStyle>("style", &mut f), + weight: args.key.get::<FontWeight>("weight", &mut f), + width: args.key.get::<FontWidth>("width", &mut f), + list: { + args.pos.all::<StringLike>() + .map(|s| s.0.to_lowercase()) + .collect() + }, + classes: { + args.key.all::<Tuple>() + .collect::<Vec<_>>() + .into_iter() + .map(|(class, mut tuple)| { + let fallback = tuple.all::<StringLike>() + .map(|s| s.0.to_lowercase()) + .collect(); + (class.v.0, fallback) + }) + .collect() + }, + }; - let classes = header.args.key - .all::<Tuple>() - .collect::<Vec<_>>() - .into_iter() - .map(|(class, mut tuple)| { - let fallback = tuple.all::<StringLike>() - .map(|s| s.0.to_lowercase()) - .collect(); - (class.v.0, fallback) - }) - .collect(); + drain_args(args, &mut f); + Pass::node(node, f) +} - Self { - body: parse_maybe_body(body, state, f), - size, - style, - weight, - width, - list, - classes, - } - } +#[derive(Debug, Clone, PartialEq)] +struct FontNode { + body: Option<SyntaxTree>, + size: Option<ScaleLength>, + style: Option<FontStyle>, + weight: Option<FontWeight>, + width: Option<FontWidth>, + list: Vec<String>, + classes: Vec<(String, Vec<String>)>, +} - layout(self, ctx, f) { +#[async_trait(?Send)] +impl Layout for FontNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> { 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 f3ddaadf..00000000 --- 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<ScaleLength>, - height: Option<ScaleLength>, - } - - parse(header, body, state, f) { - Self { - 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), - } - } - - 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<SyntaxTree>, - aligns: SpanVec<SpecAlign>, - h: Option<Spanned<SpecAlign>>, - v: Option<Spanned<SpecAlign>>, - } - - parse(header, body, state, f) { - Self { - 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), - } - } - - 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 3b09a768..ef24d74f 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::<ValFunc>(); - - std.add::<ValFunc>("val"); - std.add::<FontFunc>("font"); - std.add::<PageFunc>("page"); - std.add::<AlignFunc>("align"); - std.add::<BoxFunc>("box"); - std.add::<PageBreakFunc>("pagebreak"); - std.add_with_meta::<SpacingFunc>("h", Horizontal); - std.add_with_meta::<SpacingFunc>("v", Vertical); +pub fn _std() -> Scope { + let mut std = Scope::new(Box::new(val)); + + 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<SyntaxTree>, - } - - 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 b13f8a1e..0a018994 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<Paper>, - width: Option<Length>, - height: Option<Length>, - margins: Option<ScaleLength>, - left: Option<ScaleLength>, - right: Option<ScaleLength>, - top: Option<ScaleLength>, - bottom: Option<ScaleLength>, - 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<SyntaxNode> { + 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::<Paper>(), + width: args.key.get::<Length>("width", &mut f), + height: args.key.get::<Length>("height", &mut f), + margins: args.key.get::<ScaleLength>("margins", &mut f), + left: args.key.get::<ScaleLength>("left", &mut f), + right: args.key.get::<ScaleLength>("right", &mut f), + top: args.key.get::<ScaleLength>("top", &mut f), + bottom: args.key.get::<ScaleLength>("bottom", &mut f), + flip: args.key.get::<bool>("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::<Paper>(), - width: header.args.key.get::<Length>("width", f), - height: header.args.key.get::<Length>("height", f), - margins: header.args.key.get::<ScaleLength>("margins", f), - left: header.args.key.get::<ScaleLength>("left", f), - right: header.args.key.get::<ScaleLength>("right", f), - top: header.args.key.get::<ScaleLength>("top", f), - bottom: header.args.key.get::<ScaleLength>("bottom", f), - flip: header.args.key.get::<bool>("flip", f).unwrap_or(false), - } - } +#[derive(Debug, Clone, PartialEq)] +struct PageNode { + paper: Option<Paper>, + width: Option<Length>, + height: Option<Length>, + margins: Option<ScaleLength>, + left: Option<ScaleLength>, + right: Option<ScaleLength>, + top: Option<ScaleLength>, + bottom: Option<ScaleLength>, + flip: bool, +} - layout(self, ctx, f) { +#[async_trait(?Send)] +impl Layout for PageNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> { 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; +/// `pagebreak`: Ends the current page. +pub fn pagebreak(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> { + let mut f = Feedback::new(); + drain_args(call.header.args, &mut f); + Pass::node(PageBreakNode, f) +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct PageBreakNode; - parse(default) - layout(self, ctx, f) { vec![BreakPage] } +#[async_trait(?Send)] +impl Layout for PageBreakNode { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> { + Pass::okay(vec![BreakPage]) + } } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6503556f..14c6135a 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<SyntaxNode> { + 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<SyntaxNode> { + spacing(call, Vertical) +} - parse(header, body, state, f, meta) { - expect_no_body(body, f); - Self { - spacing: header.args.pos.expect::<ScaleLength>(f) - .map(|s| (meta, s)) - .or_missing(header.name.span, "spacing", f), - } - } +fn spacing(call: FuncCall, axis: SpecAxis) -> Pass<SyntaxNode> { + 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::<ScaleLength>(&mut f) + .map(|s| (axis, s)) + .or_missing(call.header.name.span, "spacing", &mut f), + }; + drain_args(args, &mut f); + Pass::node(node, f) +} + +#[derive(Debug, Clone, PartialEq)] +struct SpacingNode { + spacing: Option<(SpecAxis, ScaleLength)>, +} - layout(self, ctx, f) { - if let Some((axis, spacing)) = self.spacing { +#[async_trait(?Send)] +impl Layout for SpacingNode { + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> { + 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 00000000..8e431049 --- /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<SyntaxNode> { + 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<SyntaxTree>, +} + +#[async_trait(?Send)] +impl Layout for ValNode { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> { + Pass::okay(match &self.body { + Some(tree) => vec![LayoutSyntaxTree(tree)], + None => vec![], + }) + } +} |
