summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/func/helpers.rs129
-rw-r--r--src/func/mod.rs (renamed from src/func.rs)15
-rw-r--r--src/library/align.rs49
-rw-r--r--src/library/boxed.rs51
-rw-r--r--src/library/breaks.rs47
-rw-r--r--src/library/mod.rs65
-rw-r--r--src/library/spacing.rs76
-rw-r--r--src/library/structure.rs148
-rw-r--r--src/library/style.rs39
-rw-r--r--src/library/styles.rs65
10 files changed, 346 insertions, 338 deletions
diff --git a/src/func/helpers.rs b/src/func/helpers.rs
new file mode 100644
index 00000000..d562284f
--- /dev/null
+++ b/src/func/helpers.rs
@@ -0,0 +1,129 @@
+use super::prelude::*;
+use std::iter::Peekable;
+use std::slice::Iter;
+
+/// Implement the function trait more concisely.
+#[macro_export]
+macro_rules! function {
+ (data: $ident:ident, $($tts:tt)*) => {
+ impl Function for $ident {
+ function!(@parse $ident, $($tts)*);
+ }
+ };
+
+ (@parse $ident:ident, parse: plain, $($tts:tt)*) => {
+ fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext)
+ -> ParseResult<Self> where Self: Sized
+ {
+ Arguments::new(header).done()?;
+ if body.is_some() {
+ err!("expected no body");
+ }
+ Ok($ident)
+ }
+ function!(@layout $($tts)*);
+ };
+
+ (
+ @parse $ident:ident,
+ parse($args:ident, $body:ident, $ctx:ident)
+ $block:block
+ $($tts:tt)*
+ ) => {
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
+ -> ParseResult<Self> where Self: Sized
+ {
+ #[allow(unused_mut)] let mut $args = Arguments::new(header);
+ let $body = body;
+ let $ctx = ctx;
+ $block
+ }
+ function!(@layout $($tts)*);
+ };
+
+ (@layout layout($this:pat, $ctx:pat) $block:block) => {
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
+ let $ctx = ctx;
+ let $this = self;
+ $block
+ }
+ };
+}
+
+/// Parse the body of a function.
+/// - If the function does not expect a body, use `forbidden`.
+/// - If the function can have a body, use `optional`.
+/// - If the function must have a body, use `required`.
+#[macro_export]
+macro_rules! parse {
+ (forbidden: $body:expr) => {
+ if $body.is_some() {
+ err!("unexpected body");
+ }
+ };
+
+ (optional: $body:expr, $ctx:expr) => {
+ if let Some(body) = $body {
+ Some($crate::parsing::parse(body, $ctx)?)
+ } else {
+ None
+ }
+ };
+
+ (required: $body:expr, $ctx:expr) => {
+ if let Some(body) = $body {
+ $crate::parsing::parse(body, $ctx)?
+ } else {
+ err!("expected body");
+ }
+ }
+}
+
+/// Return a formatted parsing error.
+#[macro_export]
+macro_rules! err {
+ ($($tts:tt)*) => {
+ return Err(ParseError::new(format!($($tts)*)));
+ };
+}
+
+/// Convenient interface for parsing function arguments.
+pub struct Arguments<'a> {
+ args: Peekable<Iter<'a, Expression>>,
+}
+
+impl<'a> Arguments<'a> {
+ pub fn new(header: &'a FuncHeader) -> Arguments<'a> {
+ Arguments {
+ args: header.args.iter().peekable()
+ }
+ }
+
+ pub fn get_expr(&mut self) -> ParseResult<&'a Expression> {
+ self.args.next()
+ .ok_or_else(|| ParseError::new("expected expression"))
+ }
+
+ pub fn get_ident(&mut self) -> ParseResult<&'a str> {
+ match self.get_expr()? {
+ Expression::Ident(s) => Ok(s.as_str()),
+ _ => Err(ParseError::new("expected identifier")),
+ }
+ }
+
+ pub fn get_ident_if_present(&mut self) -> ParseResult<Option<&'a str>> {
+ if self.args.peek().is_some() {
+ self.get_ident().map(|s| Some(s))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn done(&mut self) -> ParseResult<()> {
+ if self.args.peek().is_none() {
+ Ok(())
+ } else {
+ Err(ParseError::new("unexpected argument"))
+ }
+ }
+}
diff --git a/src/func.rs b/src/func/mod.rs
index 05bbe83d..9a6fcbd1 100644
--- a/src/func.rs
+++ b/src/func/mod.rs
@@ -9,6 +9,21 @@ use crate::parsing::{ParseContext, ParseResult};
use crate::style::TextStyle;
use crate::syntax::{FuncHeader, SyntaxTree};
+#[macro_use]
+mod helpers;
+pub use helpers::Arguments;
+
+/// Useful imports for creating your own functions.
+pub mod prelude {
+ pub use crate::func::{Command, CommandList, Function};
+ pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
+ pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
+ pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
+ pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
+ pub use crate::size::{Size, Size2D, SizeBox};
+ pub use super::helpers::*;
+}
+
/// Typesetting function types.
///
/// These types have to be able to parse tokens into themselves and store the
diff --git a/src/library/align.rs b/src/library/align.rs
deleted file mode 100644
index cc41f295..00000000
--- a/src/library/align.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use super::prelude::*;
-use crate::layout::Alignment;
-
-/// Allows to align content in different ways.
-#[derive(Debug, PartialEq)]
-pub struct AlignFunc {
- alignment: Alignment,
- body: Option<SyntaxTree>,
-}
-
-impl Function for AlignFunc {
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
- where Self: Sized {
- if header.args.len() != 1 || !header.kwargs.is_empty() {
- return err("align: expected exactly one positional argument");
- }
-
- let alignment = if let Expression::Ident(ident) = &header.args[0] {
- match ident.as_str() {
- "left" => Alignment::Left,
- "right" => Alignment::Right,
- "center" => Alignment::Center,
- s => return err(format!("invalid alignment specifier: '{}'", s)),
- }
- } else {
- return err(format!(
- "expected alignment specifier, found: '{}'",
- header.args[0]
- ));
- };
-
- let body = parse_maybe_body(body, ctx)?;
-
- Ok(AlignFunc { alignment, body })
- }
-
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
- if let Some(body) = &self.body {
- let layouts = layout_tree(body, LayoutContext {
- alignment: self.alignment,
- .. ctx
- })?;
-
- Ok(commands![Command::AddMany(layouts)])
- } else {
- Ok(commands![Command::SetAlignment(self.alignment)])
- }
- }
-}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
deleted file mode 100644
index 71a184a6..00000000
--- a/src/library/boxed.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use super::prelude::*;
-use crate::layout::Flow;
-
-/// Wraps content into a box.
-#[derive(Debug, PartialEq)]
-pub struct BoxFunc {
- body: SyntaxTree,
- flow: Flow,
-}
-
-impl Function for BoxFunc {
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
- where Self: Sized {
- let flow = if header.args.is_empty() {
- Flow::Vertical
- } else if header.args.len() == 1 {
- if let Expression::Ident(ident) = &header.args[0] {
- match ident.as_str() {
- "vertical" => Flow::Vertical,
- "horizontal" => Flow::Horizontal,
- f => return err(format!("invalid flow specifier: '{}'", f)),
- }
- } else {
- return err(format!(
- "expected alignment specifier, found: '{}'",
- header.args[0]
- ));
- }
- } else {
- return err("box: expected flow specifier or no arguments");
- };
-
- if let Some(body) = body {
- Ok(BoxFunc {
- body: parse(body, ctx)?,
- flow,
- })
- } else {
- err("box: expected body")
- }
- }
-
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
- let layout = layout_tree(&self.body, LayoutContext {
- flow: self.flow,
- .. ctx
- })?;
-
- Ok(commands![Command::AddMany(layout)])
- }
-}
diff --git a/src/library/breaks.rs b/src/library/breaks.rs
deleted file mode 100644
index a622350f..00000000
--- a/src/library/breaks.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use super::prelude::*;
-
-/// Ends the current line.
-#[derive(Debug, PartialEq)]
-pub struct LinebreakFunc;
-
-impl Function for LinebreakFunc {
- fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
- where Self: Sized {
- if has_arguments(header) {
- return err("linebreak: expected no arguments");
- }
-
- if body.is_some() {
- return err("linebreak: expected no body");
- }
-
- Ok(LinebreakFunc)
- }
-
- fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
- Ok(commands![Command::FinishFlexRun])
- }
-}
-
-/// Ends the current page.
-#[derive(Debug, PartialEq)]
-pub struct PagebreakFunc;
-
-impl Function for PagebreakFunc {
- fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
- where Self: Sized {
- if has_arguments(header) {
- return err("pagebreak: expected no arguments");
- }
-
- if body.is_some() {
- return err("pagebreak: expected no body");
- }
-
- Ok(PagebreakFunc)
- }
-
- fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
- Ok(commands![Command::FinishLayout])
- }
-}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 784ef204..74f77204 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -2,64 +2,29 @@
use crate::func::Scope;
-mod align;
-mod boxed;
-mod breaks;
-mod spacing;
-mod styles;
+mod structure;
+mod style;
-/// Useful imports for creating your own functions.
-pub mod prelude {
- pub use crate::func::{Command, CommandList, Function};
- pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
- pub use crate::layout::{LayoutError, LayoutResult};
- pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
- pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
- pub use super::helpers::*;
-}
-
-pub use align::AlignFunc;
-pub use boxed::BoxFunc;
-pub use breaks::{LinebreakFunc, PagebreakFunc};
-pub use spacing::{HorizontalSpaceFunc, VerticalSpaceFunc};
-pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
+pub use structure::*;
+pub use style::*;
/// Create a scope with all standard functions.
pub fn std() -> Scope {
let mut std = Scope::new();
- std.add::<AlignFunc>("align");
- std.add::<BoxFunc>("box");
- std.add::<LinebreakFunc>("line.break");
- std.add::<LinebreakFunc>("n");
- std.add::<PagebreakFunc>("page.break");
+ std.add::<Align>("align");
+ std.add::<Boxed>("box");
- std.add::<HorizontalSpaceFunc>("h");
- std.add::<VerticalSpaceFunc>("v");
+ std.add::<Linebreak>("line.break");
+ std.add::<Linebreak>("n");
+ std.add::<Pagebreak>("page.break");
- std.add::<BoldFunc>("bold");
- std.add::<ItalicFunc>("italic");
- std.add::<MonospaceFunc>("mono");
- std
-}
+ std.add::<HorizontalSpace>("h");
+ std.add::<VerticalSpace>("v");
-/// Helpers for writing custom functions.
-pub mod helpers {
- use super::prelude::*;
+ std.add::<Bold>("bold");
+ std.add::<Italic>("italic");
+ std.add::<Monospace>("mono");
- pub fn has_arguments(header: &FuncHeader) -> bool {
- !header.args.is_empty() || !header.kwargs.is_empty()
- }
-
- pub fn parse_maybe_body(body: Option<&str>, ctx: ParseContext) -> ParseResult<Option<SyntaxTree>> {
- if let Some(body) = body {
- Ok(Some(parse(body, ctx)?))
- } else {
- Ok(None)
- }
- }
-
- pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
- Err(ParseError::new(message))
- }
+ std
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
deleted file mode 100644
index 91288edc..00000000
--- a/src/library/spacing.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use std::marker::PhantomData;
-use super::prelude::*;
-use crate::size::Size;
-
-/// Adds vertical space.
-pub type VerticalSpaceFunc = SpaceFunc<SpaceVertical>;
-
-/// Adds horizontal space.
-pub type HorizontalSpaceFunc = SpaceFunc<SpaceHorizontal>;
-
-/// Adds generic space.
-#[derive(Debug, PartialEq)]
-pub struct SpaceFunc<F: SpaceFlow> {
- spacing: Spacing,
- _phantom: PhantomData<F>,
-}
-
-/// Absolute or font-relative spacing.
-#[derive(Debug, PartialEq)]
-enum Spacing {
- Absolute(Size),
- Relative(f32),
-}
-
-impl<F: SpaceFlow> Function for SpaceFunc<F> {
- fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
- where Self: Sized {
- if header.args.len() != 1 || !header.kwargs.is_empty() {
- return err("align: expected exactly one positional argument");
- }
-
- let spacing = match header.args[0] {
- Expression::Size(s) => Spacing::Absolute(s),
- Expression::Number(f) => Spacing::Relative(f as f32),
- _ => return err("space: expected size or number"),
- };
-
- if body.is_some() {
- return err("space: expected no body");
- }
-
- Ok(SpaceFunc {
- spacing,
- _phantom: PhantomData,
- })
- }
-
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
- let space = match self.spacing {
- Spacing::Absolute(s) => s,
- Spacing::Relative(f) => Size::pt(f * ctx.style.font_size),
- };
-
- Ok(commands![F::cmd(space)])
- }
-}
-
-pub trait SpaceFlow: std::fmt::Debug + PartialEq + 'static {
- fn cmd(space: Size) -> Command<'static>;
-}
-
-#[derive(Debug, PartialEq)]
-pub struct SpaceVertical;
-impl SpaceFlow for SpaceVertical {
- fn cmd(space: Size) -> Command<'static> {
- Command::Add(Layout::empty(Size::zero(), space))
- }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct SpaceHorizontal;
-impl SpaceFlow for SpaceHorizontal {
- fn cmd(space: Size) -> Command<'static> {
- Command::AddFlex(Layout::empty(space, Size::zero()))
- }
-}
diff --git a/src/library/structure.rs b/src/library/structure.rs
new file mode 100644
index 00000000..ae05a12b
--- /dev/null
+++ b/src/library/structure.rs
@@ -0,0 +1,148 @@
+use crate::func::prelude::*;
+
+/// Ends the current page.
+#[derive(Debug, PartialEq)]
+pub struct Pagebreak;
+
+function! {
+ data: Pagebreak,
+ parse: plain,
+
+ layout(_, _) {
+ Ok(commands![Command::FinishLayout])
+ }
+}
+
+/// Ends the current line.
+#[derive(Debug, PartialEq)]
+pub struct Linebreak;
+
+function! {
+ data: Linebreak,
+ parse: plain,
+
+ layout(_, _) {
+ Ok(commands![Command::FinishFlexRun])
+ }
+}
+
+/// Aligns content in different ways.
+#[derive(Debug, PartialEq)]
+pub struct Align {
+ body: Option<SyntaxTree>,
+ alignment: Alignment,
+}
+
+function! {
+ data: Align,
+
+ parse(args, body, ctx) {
+ let body = parse!(optional: body, ctx);
+ let alignment = match args.get_ident()? {
+ "left" => Alignment::Left,
+ "right" => Alignment::Right,
+ "center" => Alignment::Center,
+ s => err!("invalid alignment specifier: {}", s),
+ };
+ args.done()?;
+
+ Ok(Align {
+ body,
+ alignment,
+ })
+ }
+
+ layout(this, ctx) {
+ Ok(commands![match &this.body {
+ Some(body) => {
+ Command::AddMany(layout_tree(body, LayoutContext {
+ alignment: this.alignment,
+ .. ctx
+ })?)
+ }
+ None => Command::SetAlignment(this.alignment)
+ }])
+ }
+}
+
+/// Layouts content into a box.
+#[derive(Debug, PartialEq)]
+pub struct Boxed {
+ body: SyntaxTree,
+ flow: Flow,
+}
+
+function! {
+ data: Boxed,
+
+ parse(args, body, ctx) {
+ let body = parse!(required: body, ctx);
+
+ let mut flow = Flow::Vertical;
+ if let Some(ident) = args.get_ident_if_present()? {
+ flow = match ident {
+ "vertical" => Flow::Vertical,
+ "horizontal" => Flow::Horizontal,
+ f => err!("invalid flow specifier: {}", f),
+ };
+ }
+ args.done()?;
+
+ Ok(Boxed {
+ body,
+ flow,
+ })
+ }
+
+ layout(this, ctx) {
+ Ok(commands![
+ Command::AddMany(layout_tree(&this.body, LayoutContext {
+ flow: this.flow,
+ .. ctx
+ })?)
+ ])
+ }
+}
+
+macro_rules! spacefunc {
+ ($ident:ident, $name:expr, $var:ident => $command:expr) => {
+ /// Adds whitespace.
+ #[derive(Debug, PartialEq)]
+ pub struct $ident(Spacing);
+
+ function! {
+ data: $ident,
+
+ parse(args, body, _ctx) {
+ parse!(forbidden: body);
+
+ let spacing = match args.get_expr()? {
+ Expression::Size(s) => Spacing::Absolute(*s),
+ Expression::Number(f) => Spacing::Relative(*f as f32),
+ _ => err!("invalid spacing, expected size or number"),
+ };
+
+ Ok($ident(spacing))
+ }
+
+ layout(this, ctx) {
+ let $var = match this.0 {
+ Spacing::Absolute(s) => s,
+ Spacing::Relative(f) => Size::pt(f * ctx.style.font_size),
+ };
+
+ Ok(commands![$command])
+ }
+ }
+ };
+}
+
+/// Absolute or font-relative spacing.
+#[derive(Debug, PartialEq)]
+enum Spacing {
+ Absolute(Size),
+ Relative(f32),
+}
+
+spacefunc!(HorizontalSpace, "h", space => Command::AddFlex(Layout::empty(space, Size::zero())));
+spacefunc!(VerticalSpace, "v", space => Command::Add(Layout::empty(Size::zero(), space)));
diff --git a/src/library/style.rs b/src/library/style.rs
new file mode 100644
index 00000000..397375a4
--- /dev/null
+++ b/src/library/style.rs
@@ -0,0 +1,39 @@
+use crate::func::prelude::*;
+use toddle::query::FontClass;
+
+macro_rules! stylefunc {
+ ($ident:ident) => {
+ /// Styles text.
+ #[derive(Debug, PartialEq)]
+ pub struct $ident {
+ body: Option<SyntaxTree>
+ }
+
+ function! {
+ data: $ident,
+
+ parse(args, body, ctx) {
+ args.done()?;
+ Ok($ident { body: parse!(optional: body, ctx) })
+ }
+
+ layout(this, ctx) {
+ let mut new_style = ctx.style.clone();
+ new_style.toggle_class(FontClass::$ident);
+
+ Ok(match &this.body {
+ Some(body) => commands![
+ Command::SetStyle(new_style),
+ Command::Layout(body),
+ Command::SetStyle(ctx.style.clone()),
+ ],
+ None => commands![Command::SetStyle(new_style)]
+ })
+ }
+ }
+ };
+}
+
+stylefunc!(Italic);
+stylefunc!(Bold);
+stylefunc!(Monospace);
diff --git a/src/library/styles.rs b/src/library/styles.rs
deleted file mode 100644
index bc84ac3b..00000000
--- a/src/library/styles.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use toddle::query::FontClass;
-
-use super::prelude::*;
-
-macro_rules! style_func {
- (
- $(#[$outer:meta])*
- pub struct $struct:ident { $name:expr },
- $style:ident => $class:ident
- ) => {
- $(#[$outer])*
- #[derive(Debug, PartialEq)]
- pub struct $struct {
- body: Option<SyntaxTree>
- }
-
- impl Function for $struct {
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
- -> ParseResult<Self> where Self: Sized {
- // Accept only invocations without arguments and with body.
- if has_arguments(header) {
- return err(format!("{}: expected no arguments", $name));
- }
-
- let body = parse_maybe_body(body, ctx)?;
-
- Ok($struct { body })
- }
-
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
- let mut new_style = ctx.style.clone();
- new_style.toggle_class(FontClass::$class);
-
- if let Some(body) = &self.body {
- let saved_style = ctx.style.clone();
- Ok(commands![
- Command::SetStyle(new_style),
- Command::Layout(body),
- Command::SetStyle(saved_style),
- ])
- } else {
- Ok(commands![Command::SetStyle(new_style)])
- }
- }
- }
- };
-}
-
-style_func! {
- /// Typesets text in bold.
- pub struct BoldFunc { "bold" },
- style => Bold
-}
-
-style_func! {
- /// Typesets text in italics.
- pub struct ItalicFunc { "italic" },
- style => Italic
-}
-
-style_func! {
- /// Typesets text in monospace.
- pub struct MonospaceFunc { "mono" },
- style => Monospace
-}