summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-04 13:48:07 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-04 13:48:07 +0200
commit2467cd6272c13b618ad53c5dadff5b8c8e7885bf (patch)
tree6ad13ec06a04997564efc514b40daa3fb65233e2 /src/library
parented4fdcb0ada909f1cc3d7436334e253f0ec14d55 (diff)
Refactor function parsing ♻
Diffstat (limited to 'src/library')
-rw-r--r--src/library/align.rs76
-rw-r--r--src/library/boxed.rs53
-rw-r--r--src/library/font.rs105
-rw-r--r--src/library/layout.rs104
-rw-r--r--src/library/mod.rs54
-rw-r--r--src/library/page.rs98
-rw-r--r--src/library/spacing.rs54
-rw-r--r--src/library/val.rs28
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![],
+ })
+ }
+}