summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/func.rs138
-rw-r--r--src/layout/mod.rs6
-rw-r--r--src/lib.rs17
-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
-rw-r--r--src/syntax/mod.rs8
-rw-r--r--src/syntax/parsing.rs48
-rw-r--r--src/syntax/scope.rs36
-rw-r--r--src/syntax/test.rs44
-rw-r--r--src/syntax/tree.rs12
-rw-r--r--src/syntax/value.rs10
17 files changed, 435 insertions, 456 deletions
diff --git a/src/func.rs b/src/func.rs
index 09870895..57dad103 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<T>: 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<T> OptionExt<T> for Option<T> {
}
}
-/// 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<Spanned<&str>>,
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<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` 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<Self>
- 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 3cfb872f..5f5a4859 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 fa6300bb..38109a15 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<MultiLayout> {
use crate::layout::prelude::*;
- use crate::layout::{LayoutContext, LayoutSpace};
let margins = self.style.page.margins();
layout(
@@ -141,6 +140,11 @@ impl<T> Pass<T> {
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<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
Pass {
@@ -150,6 +154,13 @@ impl<T> Pass<T> {
}
}
+impl Pass<SyntaxNode> {
+ /// Create a new pass from an unboxed dynamic node and feedback data..
+ pub fn node<T: DynamicNode + 'static>(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 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![],
+ })
+ }
+}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index e1c9bbb0..e0f4e4c8 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 3c802074..bf16e146 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<Box<dyn DynamicNode>>;
-
-/// 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<Self>
- where
- Self: Sized;
-}
+pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<SyntaxNode>;
/// 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<Spanned<&'s str>>,
+ 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<Spanned<&'s str>>;
+
/// 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<FuncHeader> {
@@ -678,7 +662,7 @@ fn unescape_raw(raw: &str) -> Vec<String> {
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::<DebugFn>();
- scope.add::<DebugFn>("f");
- scope.add::<DebugFn>("n");
- scope.add::<DebugFn>("box");
- scope.add::<DebugFn>("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 aac2b1b8..c17ff64d 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<F>() -> Self
- where
- F: ParseCall<Meta = ()> + DynamicNode + 'static
- {
+ pub fn new(fallback: Box<CallParser>) -> Self {
Self {
parsers: HashMap::new(),
- fallback: make_parser::<F>(()),
+ fallback,
}
}
/// Associate the given function name with a dynamic node type.
- pub fn add<F>(&mut self, name: &str)
- where
- F: ParseCall<Meta = ()> + DynamicNode + 'static
- {
- self.add_with_meta::<F>(name, ());
- }
-
- /// Add a dynamic node type with additional metadata that is passed to the
- /// parser.
- 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));
+ pub fn insert(&mut self, name: impl Into<String>, parser: Box<CallParser>) {
+ 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<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>)
- })
-}
diff --git a/src/syntax/test.rs b/src/syntax/test.rs
index 9faa7f23..7dec20e3 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<SyntaxTree>,
- }
+pub fn debug_func(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
+ 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<SyntaxTree>,
+}
- layout(self, ctx, f) { vec![] }
+#[async_trait(?Send)]
+impl Layout for DebugNode {
+ async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
+ unimplemented!()
+ }
}
/// Compares elements by only looking at values and ignoring spans.
@@ -87,8 +87,8 @@ pub trait SpanlessEq<Rhs = Self> {
impl SpanlessEq for SyntaxNode {
fn spanless_eq(&self, other: &Self) -> bool {
- fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn {
- func.downcast::<DebugFn>().expect("not a debug fn")
+ fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugNode {
+ func.downcast::<DebugNode>().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 73734d28..4ce39cd4 100644
--- a/src/syntax/tree.rs
+++ b/src/syntax/tree.rs
@@ -31,6 +31,13 @@ pub enum SyntaxNode {
Dyn(Box<dyn DynamicNode>),
}
+impl SyntaxNode {
+ /// Create a `Dyn` variant from an unboxed dynamic node.
+ pub fn boxed<T: DynamicNode + 'static>(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<T>(&self) -> Option<&T>
- where
- T: DynamicNode + 'static,
- {
+ pub fn downcast<T: DynamicNode + 'static>(&self) -> Option<&T> {
self.as_any().downcast_ref::<T>()
}
}
diff --git a/src/syntax/value.rs b/src/syntax/value.rs
index b7211eaf..c523ff93 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,
});