summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-04 19:34:29 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-04 19:35:28 +0100
commit9fb31defd037a90bf8f9e38fa33acae23a70b269 (patch)
treee0fd887792a59cbb3262a5d3157d0c786df56d60 /src
parentace57c34206a13b4bc3885b944cc51e274f30b0f (diff)
Expand functionality of function! macro 🛰
Diffstat (limited to 'src')
-rw-r--r--src/export/pdf.rs2
-rw-r--r--src/func/args.rs215
-rw-r--r--src/func/helpers.rs203
-rw-r--r--src/func/macros.rs149
-rw-r--r--src/func/mod.rs121
-rw-r--r--src/layout/flex.rs18
-rw-r--r--src/layout/mod.rs116
-rw-r--r--src/layout/stack.rs2
-rw-r--r--src/layout/text.rs2
-rw-r--r--src/layout/tree.rs6
-rw-r--r--src/library/align.rs193
-rw-r--r--src/library/boxed.rs59
-rw-r--r--src/library/mod.rs270
-rw-r--r--src/library/page.rs79
-rw-r--r--src/library/spacing.rs71
-rw-r--r--src/library/style.rs39
-rw-r--r--src/size.rs38
-rw-r--r--src/syntax/mod.rs87
-rw-r--r--src/syntax/parsing.rs64
-rw-r--r--src/syntax/span.rs72
20 files changed, 1039 insertions, 767 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 76fc59b8..8370f454 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -80,7 +80,7 @@ impl<'d, W: Write> ExportProcess<'d, W> {
) -> PdfResult<ExportProcess<'d, W>>
{
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
- let offsets = Self::calculate_offsets(layouts.count(), fonts.len());
+ let offsets = Self::calculate_offsets(layouts.len(), fonts.len());
Ok(ExportProcess {
writer: PdfWriter::new(target),
diff --git a/src/func/args.rs b/src/func/args.rs
new file mode 100644
index 00000000..2e9e80cc
--- /dev/null
+++ b/src/func/args.rs
@@ -0,0 +1,215 @@
+//! Parsing, storing and deduplication of function arguments.
+
+use super::prelude::*;
+use Expression::*;
+
+/// Provides a convenient interface to parse the arguments to a function.
+pub struct ArgParser<'a> {
+ args: &'a FuncArgs,
+ positional_index: usize,
+}
+
+impl<'a> ArgParser<'a> {
+ pub fn new(args: &'a FuncArgs) -> ArgParser<'a> {
+ ArgParser {
+ args,
+ positional_index: 0,
+ }
+ }
+
+ /// Get the next positional argument of the given type.
+ ///
+ /// If there are no more arguments or the type is wrong,
+ /// this will return an error.
+ pub fn get_pos<T>(&mut self) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ Self::expected(self.get_pos_opt::<T>()?)
+ }
+
+ /// Get the next positional argument if there is any.
+ ///
+ /// If the argument is of the wrong type, this will return an error.
+ pub fn get_pos_opt<T>(&mut self) -> ParseResult<Option<Spanned<T::Output>>>
+ where T: Argument<'a> {
+ let arg = self.args.positional
+ .get(self.positional_index)
+ .map(T::from_expr)
+ .transpose();
+
+ if let Ok(Some(_)) = arg {
+ self.positional_index += 1;
+ }
+
+ arg
+ }
+
+ /// Get a keyword argument with the given key and type.
+ pub fn get_key<T>(&mut self, key: &str) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ Self::expected(self.get_key_opt::<T>(key)?)
+ }
+
+ /// Get a keyword argument with the given key and type if it is present.
+ pub fn get_key_opt<T>(&mut self, key: &str) -> ParseResult<Option<Spanned<T::Output>>>
+ where T: Argument<'a> {
+ self.args.keyword.iter()
+ .find(|entry| entry.val.0.val == key)
+ .map(|entry| T::from_expr(&entry.val.1))
+ .transpose()
+ }
+
+ /// Assert that there are no positional arguments left. Returns an error
+ /// otherwise.
+ pub fn done(&self) -> ParseResult<()> {
+ if self.positional_index == self.args.positional.len() {
+ Ok(())
+ } else {
+ pr!("unexpected argument");
+ }
+ }
+
+ /// Covert an option to a result with an error on `None`.
+ fn expected<T>(val: Option<Spanned<T::Output>>) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ val.ok_or_else(|| pr!(@"expected {}", T::ERROR_MESSAGE))
+ }
+}
+
+/// A kind of argument.
+pub trait Argument<'a> {
+ type Output;
+ const ERROR_MESSAGE: &'static str;
+
+ fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>>;
+}
+
+macro_rules! arg {
+ ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => (
+ #[doc = $doc]
+ #[doc = " argument for use with the [`ArgParser`]."]
+ pub struct $type;
+ impl<'a> Argument<'a> for $type {
+ type Output = $output;
+ const ERROR_MESSAGE: &'static str = $err;
+
+ fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>> {
+ #[allow(unreachable_patterns)]
+ match &expr.val {
+ $wanted => Ok(Spanned::new($converted, expr.span)),
+ _ => pr!("expected {}", $err),
+ }
+ }
+ }
+ );
+}
+
+arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr);
+arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str());
+arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str());
+arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n);
+arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s);
+arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b);
+
+/// An argument key which identifies a layouting axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AxisKey {
+ Primary,
+ Secondary,
+ Vertical,
+ Horizontal,
+}
+
+impl AxisKey {
+ /// The generic version of this axis key in the given system of axes.
+ pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind {
+ match self {
+ Primary => GenericAxisKind::Primary,
+ Secondary => GenericAxisKind::Secondary,
+ Vertical => axes.vertical(),
+ Horizontal => axes.horizontal(),
+ }
+ }
+
+ /// The specific version of this axis key in the given system of axes.
+ pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind {
+ match self {
+ Primary => axes.primary(),
+ Secondary => axes.secondary(),
+ Vertical => SpecificAxisKind::Vertical,
+ Horizontal => SpecificAxisKind::Horizontal,
+ }
+ }
+}
+
+/// An argument key which identifies a target alignment.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AlignmentKey {
+ Left,
+ Top,
+ Right,
+ Bottom,
+ Origin,
+ Center,
+ End,
+}
+
+impl AlignmentKey {
+ /// The generic axis this alignment key corresopnds to in the given system
+ /// of layouting axes. Falls back to `default` if the alignment is generic.
+ pub fn axis(&self, axes: LayoutAxes, default: GenericAxisKind) -> GenericAxisKind {
+ use AlignmentKey::*;
+ match self {
+ Origin | Center | End => default,
+ Left | Right => axes.horizontal(),
+ Top | Bottom => axes.vertical(),
+ }
+ }
+
+ /// The generic version of this alignment in the given system of layouting
+ /// axes. Returns an error if the alignment is invalid for the given axis.
+ pub fn generic(&self, axes: LayoutAxes, axis: GenericAxisKind) -> LayoutResult<Alignment> {
+ use AlignmentKey::*;
+
+ let horizontal = axis == axes.horizontal();
+ Ok(match self {
+ Origin => Alignment::Origin,
+ Center => Alignment::Center,
+ End => Alignment::End,
+ Left if horizontal => axes.left(),
+ Right if horizontal => axes.right(),
+ Top if !horizontal => axes.top(),
+ Bottom if !horizontal => axes.bottom(),
+ _ => lr!(
+ "invalid alignment `{}` for {} axis",
+ format!("{:?}", self).to_lowercase(),
+ format!("{:?}", axis).to_lowercase()
+ )
+ })
+ }
+
+ /// The specific version of this alignment in the given system of layouting
+ /// axes.
+ pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey {
+ use AlignmentKey::*;
+ match (self, axis) {
+ (Origin, SpecificAxisKind::Horizontal) => Left,
+ (End, SpecificAxisKind::Horizontal) => Right,
+ (Origin, SpecificAxisKind::Vertical) => Top,
+ (End, SpecificAxisKind::Vertical) => Bottom,
+ _ => *self,
+ }
+ }
+}
+
+/// An argument key which identifies a margin or padding target.
+///
+/// A is the axis type used.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PaddingKey<A> {
+ /// All four sides should have the specified padding.
+ All,
+ /// Both sides of the given axis should have the specified padding.
+ Axis(A),
+ /// Only the given side of the given axis should have the specified padding.
+ AxisAligned(A, AlignmentKey),
+}
diff --git a/src/func/helpers.rs b/src/func/helpers.rs
deleted file mode 100644
index c82a90e1..00000000
--- a/src/func/helpers.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-//! Helper types and macros for creating custom functions.
-
-use super::prelude::*;
-use Expression::*;
-
-/// Lets you implement the function trait more concisely.
-#[macro_export]
-macro_rules! function {
- (data: $ident:ident, $($tts:tt)*) => (
- #[allow(unused_imports)]
- use $crate::func::prelude::*;
-
- 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 {
- ArgParser::new(&header.args).done()?;
- if body.is_some() {
- perr!("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 = ArgParser::new(&header.args);
- 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() {
- perr!("unexpected body");
- }
- };
-
- (optional: $body:expr, $ctx:expr) => (
- if let Some(body) = $body {
- Some($crate::syntax::parse(body, $ctx)?)
- } else {
- None
- }
- );
-
- (required: $body:expr, $ctx:expr) => (
- if let Some(body) = $body {
- $crate::syntax::parse(body, $ctx)?
- } else {
- perr!("expected body");
- }
- )
-}
-
-/// Early-return with a formatted parsing error or yield
-/// an error expression without returning when prefixed with `@`.
-#[macro_export]
-macro_rules! perr {
- (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*)));
- ($($tts:tt)*) => (return Err(perr!(@$($tts)*)););
-}
-
-/// Early-return with a formatted layouting error or yield
-/// an error expression without returning when prefixed with `@`.
-#[macro_export]
-macro_rules! lerr {
- (@$($tts:tt)*) => ($crate::layout::LayoutError::new(format!($($tts)*)));
- ($($tts:tt)*) => (return Err(lerr!(@$($tts)*)););
-}
-
-
-/// Easy parsing of function arguments.
-pub struct ArgParser<'a> {
- args: &'a FuncArgs,
- positional_index: usize,
-}
-
-impl<'a> ArgParser<'a> {
- pub fn new(args: &'a FuncArgs) -> ArgParser<'a> {
- ArgParser {
- args,
- positional_index: 0,
- }
- }
-
- /// Get the next positional argument of the given type.
- ///
- /// If there are no more arguments or the type is wrong,
- /// this will return an error.
- pub fn get_pos<T>(&mut self) -> ParseResult<Spanned<T::Output>> where T: Argument<'a> {
- self.get_pos_opt::<T>()?
- .ok_or_else(|| perr!(@"expected {}", T::ERROR_MESSAGE))
- }
-
- /// Get the next positional argument if there is any.
- ///
- /// If the argument is of the wrong type, this will return an error.
- pub fn get_pos_opt<T>(&mut self) -> ParseResult<Option<Spanned<T::Output>>>
- where T: Argument<'a> {
- let arg = self.args.positional
- .get(self.positional_index)
- .map(T::from_expr)
- .transpose();
-
- if let Ok(Some(_)) = arg {
- self.positional_index += 1;
- }
-
- arg
- }
-
- /// Get a keyword argument with the given key and type.
- pub fn get_key<T>(&mut self, key: &str) -> ParseResult<Spanned<T::Output>>
- where T: Argument<'a> {
- self.get_key_opt::<T>(key)?
- .ok_or_else(|| perr!(@"expected {}", T::ERROR_MESSAGE))
- }
-
- /// Get a keyword argument with the given key and type if it is present.
- pub fn get_key_opt<T>(&mut self, key: &str) -> ParseResult<Option<Spanned<T::Output>>>
- where T: Argument<'a> {
- self.args.keyword.iter()
- .find(|entry| entry.val.0.val == key)
- .map(|entry| T::from_expr(&entry.val.1))
- .transpose()
- }
-
- /// Assert that there are no positional arguments left. Returns an error, otherwise.
- pub fn done(&self) -> ParseResult<()> {
- if self.positional_index == self.args.positional.len() {
- Ok(())
- } else {
- perr!("unexpected argument");
- }
- }
-}
-
-/// A kind of argument.
-pub trait Argument<'a> {
- type Output;
- const ERROR_MESSAGE: &'static str;
-
- fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>>;
-}
-
-macro_rules! arg {
- ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => (
- #[doc = $doc]
- #[doc = " argument for use with the [`ArgParser`]."]
- pub struct $type;
- impl<'a> Argument<'a> for $type {
- type Output = $output;
- const ERROR_MESSAGE: &'static str = $err;
-
- fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>> {
- #[allow(unreachable_patterns)]
- match &expr.val {
- $wanted => Ok(Spanned::new($converted, expr.span)),
- _ => perr!("expected {}", $err),
- }
- }
- }
- );
-}
-
-arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr);
-arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str());
-arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str());
-arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n);
-arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s);
-arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b);
diff --git a/src/func/macros.rs b/src/func/macros.rs
new file mode 100644
index 00000000..78cf1f56
--- /dev/null
+++ b/src/func/macros.rs
@@ -0,0 +1,149 @@
+//! Helper types and macros for creating custom functions.
+
+/// Defines function types concisely.
+#[macro_export]
+macro_rules! function {
+ // Parse a unit struct.
+ ($(#[$outer:meta])* pub struct $type:ident; $($rest:tt)*) => {
+ $(#[$outer])*
+ pub struct $type;
+ function!(@meta $type | $($rest)*);
+ };
+
+ // Parse a struct with fields.
+ ($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => {
+ $(#[$outer])*
+ pub struct $type { $($fields)* }
+ function!(@meta $type | $($rest)*);
+ };
+
+ // Parse a metadata type definition.
+ (@meta $type:ident | type Meta = $meta:ty; $($rest:tt)*) => {
+ function!(@parse $type $meta | $($rest)*);
+ };
+
+ // Set the metadata to `()` if there is not type definition.
+ (@meta $type:ident | $($rest:tt)*) => {
+ function!(@parse $type () | $($rest)*);
+ };
+
+ // Parse a `parse(default)`.
+ (@parse $type:ident $meta:ty | parse(default) $($rest:tt)*) => {
+ function!(@parse $type $meta |
+ parse(_args, _body, _ctx, _meta) { Default::default() }
+ $($rest)*
+ );
+ };
+
+ // (0-arg) Parse a parse-definition without arguments.
+ (@parse $type:ident $meta:ty | parse() $code:block $($rest:tt)*) => {
+ function!(@parse $type $meta | parse(_args, _body, _ctx, _meta) $code $($rest)*);
+ };
+
+ // (1-arg) Parse a parse-definition with only the first argument.
+ (@parse $type:ident $meta:ty | parse($args:ident) $code:block $($rest:tt)*) => {
+ function!(@parse $type $meta | parse($args, _body, _ctx, _meta) $code $($rest)*);
+ };
+
+ // (2-arg) Parse a parse-definition with only the first two arguments.
+ (@parse $type:ident $meta:ty |
+ parse($args:ident, $body:pat) $code:block $($rest:tt)*
+ ) => {
+ function!(@parse $type $meta | parse($args, $body, _ctx, _meta) $code $($rest)*);
+ };
+
+ // (3-arg) Parse a parse-definition with only the first three arguments.
+ (@parse $type:ident $meta:ty |
+ parse($args:ident, $body:pat, $ctx:pat) $code:block $($rest:tt)*
+ ) => {
+ function!(@parse $type $meta | parse($args, $body, $ctx, _meta) $code $($rest)*);
+ };
+
+ // (4-arg) Parse a parse-definition with all four arguments.
+ (@parse $type:ident $meta:ty |
+ parse($args:ident, $body:pat, $ctx:pat, $metadata:pat) $code:block
+ $($rest:tt)*
+ ) => {
+ impl $crate::func::ParseFunc for $type {
+ type Meta = $meta;
+
+ fn parse(
+ header: &FuncHeader,
+ $body: Option<&str>,
+ $ctx: ParseContext,
+ $metadata: Self::Meta,
+ ) -> ParseResult<Self> where Self: Sized {
+ let mut $args = $crate::func::args::ArgParser::new(&header.args);
+ let val = $code;
+ $args.done()?;
+ Ok(val)
+ }
+ }
+
+ function!(@layout $type | $($rest)*);
+ };
+
+ // (0-arg) Parse a layout-definition without arguments.
+ (@layout $type:ident | layout() $code:block) => {
+ function!(@layout $type | layout(self, _ctx) $code);
+ };
+
+ // (1-arg) Parse a layout-definition with only the first argument.
+ (@layout $type:ident | layout($this:ident) $code:block) => {
+ function!(@layout $type | layout($this, _ctx) $code);
+ };
+
+ // (2-arg) Parse a layout-definition with all arguments.
+ (@layout $type:ident | layout($this:ident, $ctx:pat) $code:block) => {
+ impl $crate::func::LayoutFunc for $type {
+ fn layout(&$this, $ctx: LayoutContext) -> LayoutResult<Commands> {
+ Ok($code)
+ }
+ }
+ };
+}
+
+/// Parse the body of a function.
+/// - If the function does not expect a body, use `parse!(forbidden: body)`.
+/// - If the function can have a body, use `parse!(optional: body, ctx)`.
+/// - If the function must have a body, use `parse!(expected: body, ctx)`.
+#[macro_export]
+macro_rules! parse {
+ (forbidden: $body:expr) => {
+ if $body.is_some() {
+ pr!("unexpected body");
+ }
+ };
+
+ (optional: $body:expr, $ctx:expr) => (
+ if let Some(body) = $body {
+ Some($crate::syntax::parse(body, $ctx)?)
+ } else {
+ None
+ }
+ );
+
+ (expected: $body:expr, $ctx:expr) => (
+ if let Some(body) = $body {
+ $crate::syntax::parse(body, $ctx)?
+ } else {
+ pr!("expected body");
+ }
+ )
+}
+
+/// Early-return with a formatted parsing error or yield
+/// an error expression without returning when prefixed with `@`.
+#[macro_export]
+macro_rules! pr {
+ (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*)));
+ ($($tts:tt)*) => (return Err(pr!(@$($tts)*)););
+}
+
+/// Early-return with a formatted layouting error or yield
+/// an error expression without returning when prefixed with `@`.
+#[macro_export]
+macro_rules! lr {
+ (@$($tts:tt)*) => ($crate::layout::LayoutError::new(format!($($tts)*)));
+ ($($tts:tt)*) => (return Err(lr!(@$($tts)*)););
+}
diff --git a/src/func/mod.rs b/src/func/mod.rs
index 33a6c756..b16eecb8 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -7,53 +7,64 @@ use std::fmt::{self, Debug, Formatter};
use self::prelude::*;
#[macro_use]
-pub mod helpers;
+pub mod macros;
+pub mod args;
/// Useful imports for creating your own functions.
pub mod prelude {
- pub use crate::func::{Command, CommandList, Function};
- pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext};
- pub use crate::layout::{LayoutSpace, LayoutSpaces, SpacingKind};
- pub use crate::layout::{LayoutAxes, Axis, AxisKind, LayoutAlignment, Alignment};
- pub use crate::layout::{LayoutError, LayoutResult};
+ pub use Command::*;
+ pub use super::args::*;
+ pub use super::{Scope, ParseFunc, LayoutFunc, Command, Commands};
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
pub use crate::size::{Size, Size2D, SizeBox};
pub use crate::style::{PageStyle, TextStyle};
- pub use super::helpers::*;
- pub use Command::*;
+ pub use crate::layout::{
+ layout_tree, Layout, MultiLayout,
+ LayoutContext, LayoutSpace, LayoutSpaces,
+ LayoutAxes, Axis, GenericAxisKind, SpecificAxisKind,
+ LayoutAlignment, Alignment,
+ SpacingKind,
+ LayoutError, LayoutResult,
+ };
}
-/// Typesetting function types.
-///
-/// These types have to be able to parse themselves from a string and build
-/// a list of layouting commands corresponding to the parsed source.
-///
-/// This trait is a supertrait of `FunctionBounds` for technical reasons. The
-/// trait `FunctionBounds` is automatically implemented for types which can
-/// be used as functions, that is, all types which fulfill the bounds `Debug + PartialEq +
-/// 'static`.
-pub trait Function: FunctionBounds {
+/// Types representing functions that are parsed from source code.
+pub trait ParseFunc {
+ type Meta;
+
/// Parse the header and body into this function given a context.
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
- where Self: Sized;
+ fn parse(
+ header: &FuncHeader,
+ body: Option<&str>,
+ ctx: ParseContext,
+ metadata: Self::Meta,
+ ) -> ParseResult<Self> where Self: Sized;
+}
- /// Layout this function given a context.
+/// Types representing functions which can be laid out in a layout context.
+///
+/// This trait is a supertrait of `[LayoutFuncBounds]` for technical reasons.
+/// The trait `[LayoutFuncBounds]` is automatically implemented for types which
+/// can be used as functions, that is, all types which fulfill the bounds `Debug
+/// + PartialEq + 'static`.
+pub trait LayoutFunc: LayoutFuncBounds {
+ /// Layout this function in a given context.
///
- /// Returns optionally the resulting layout and a new context if changes to
- /// the context should be made.
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList>;
+ /// Returns a sequence of layouting commands which describe what the
+ /// function is doing.
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<Commands>;
}
-impl dyn Function {
- /// Downcast a dynamic function to a concrete function type.
- pub fn downcast<F>(&self) -> Option<&F> where F: Function + 'static {
+impl dyn LayoutFunc {
+ /// Downcast a function trait object to a concrete function type.
+ pub fn downcast<F>(&self) -> Option<&F> where F: LayoutFunc + 'static {
self.help_cast_as_any().downcast_ref::<F>()
}
}
-impl PartialEq for dyn Function {
- fn eq(&self, other: &dyn Function) -> bool {
+impl PartialEq for dyn LayoutFunc {
+ fn eq(&self, other: &dyn LayoutFunc) -> bool {
self.help_eq(other)
}
}
@@ -63,22 +74,20 @@ impl PartialEq for dyn Function {
///
/// Automatically implemented for all types which fulfill to the bounds `Debug +
/// PartialEq + 'static`. There should be no need to implement this manually.
-pub trait FunctionBounds: Debug {
+pub trait LayoutFuncBounds: Debug {
/// Cast self into `Any`.
fn help_cast_as_any(&self) -> &dyn Any;
- /// Compare self with another function.
- fn help_eq(&self, other: &dyn Function) -> bool;
+ /// Compare self with another function trait object.
+ fn help_eq(&self, other: &dyn LayoutFunc) -> bool;
}
-impl<T> FunctionBounds for T
-where T: Debug + PartialEq + 'static
-{
+impl<T> LayoutFuncBounds for T where T: Debug + PartialEq + 'static {
fn help_cast_as_any(&self) -> &dyn Any {
self
}
- fn help_eq(&self, other: &dyn Function) -> bool {
+ fn help_eq(&self, other: &dyn LayoutFunc) -> bool {
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
self == other
} else {
@@ -87,17 +96,17 @@ where T: Debug + PartialEq + 'static
}
}
-/// A sequence of commands requested for execution by a function.
-pub type CommandList<'a> = Vec<Command<'a>>;
+/// A sequence of layouting commands.
+pub type Commands<'a> = Vec<Command<'a>>;
-/// Commands requested for execution by functions.
+/// Layouting commands from functions to the typesetting engine.
#[derive(Debug)]
pub enum Command<'a> {
LayoutTree(&'a SyntaxTree),
Add(Layout),
AddMultiple(MultiLayout),
- AddSpacing(Size, SpacingKind, AxisKind),
+ AddSpacing(Size, SpacingKind, GenericAxisKind),
FinishLine,
FinishRun,
@@ -110,13 +119,18 @@ pub enum Command<'a> {
SetAxes(LayoutAxes),
}
-/// A map from identifiers to functions.
+/// A map from identifiers to function parsers.
pub struct Scope {
- parsers: HashMap<String, Box<ParseFunc>>,
+ parsers: HashMap<String, Box<Parser>>,
}
-/// A function which parses a function invocation into a function type.
-type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext) -> ParseResult<Box<dyn Function>>;
+/// A function which parses the source of a function into a function type which
+/// implements [`LayoutFunc`].
+type Parser = dyn Fn(
+ &FuncHeader,
+ Option<&str>,
+ ParseContext
+) -> ParseResult<Box<dyn LayoutFunc>>;
impl Scope {
/// Create a new empty scope.
@@ -131,16 +145,27 @@ impl Scope {
crate::library::std()
}
- /// Add a function type to the scope giving it a name.
- pub fn add<F: Function + 'static>(&mut self, name: &str) {
+ /// Associate the given name with a type that is parseable into a function.
+ pub fn add<F>(&mut self, name: &str)
+ where F: ParseFunc<Meta=()> + LayoutFunc + 'static {
+ self.add_with_metadata::<F, ()>(name, ());
+ }
+
+ /// Add a parseable type with additional metadata that is given to the
+ /// parser (other than the default of `()`).
+ pub fn add_with_metadata<F, T>(&mut self, name: &str, metadata: T)
+ where F: ParseFunc<Meta=T> + LayoutFunc + 'static, T: 'static {
self.parsers.insert(
name.to_owned(),
- Box::new(|h, b, c| F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)),
+ Box::new(|h, b, c| {
+ F::parse(h, b, c, metadata)
+ .map(|f| Box::new(f) as Box<dyn LayoutFunc>)
+ })
);
}
/// Return the parser with the given name if there is one.
- pub(crate) fn get_parser(&self, name: &str) -> Option<&ParseFunc> {
+ pub(crate) fn get_parser(&self, name: &str) -> Option<&Parser> {
self.parsers.get(name).map(|x| &**x)
}
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index afca23ec..3e8a64e1 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -14,7 +14,7 @@ pub struct FlexLayouter {
#[derive(Debug, Clone)]
enum FlexUnit {
Boxed(Layout),
- Space(Size, SpaceKind),
+ Space(Size, SpacingKind),
SetAxes(LayoutAxes),
Break,
}
@@ -102,11 +102,11 @@ impl FlexLayouter {
self.units.push(FlexUnit::Break);
}
- pub fn add_primary_space(&mut self, space: Size, kind: SpaceKind) {
+ pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) {
self.units.push(FlexUnit::Space(space, kind))
}
- pub fn add_secondary_space(&mut self, space: Size, kind: SpaceKind) -> LayoutResult<()> {
+ pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> {
if !self.run_is_empty() {
self.finish_run()?;
}
@@ -179,7 +179,7 @@ impl FlexLayouter {
debug_render: false,
})?;
- self.stack.add_spacing(self.flex_spacing, SpaceKind::Independent);
+ self.stack.add_spacing(self.flex_spacing, SpacingKind::Independent);
let remaining = self.axes.specialize(Size2D {
x: self.part.usable
@@ -230,7 +230,7 @@ impl FlexLayouter {
while size.x > self.line.usable {
if self.stack.space_is_last() {
- lerr!("box does not fit into line");
+ lr!("box does not fit into line");
}
self.stack.finish_space(true);
@@ -238,7 +238,7 @@ impl FlexLayouter {
}
if let LastSpacing::Soft(space) = self.part.space {
- self.layout_space(space, SpaceKind::Hard);
+ self.layout_space(space, SpacingKind::Hard);
}
let offset = self.part.dimensions.x;
@@ -251,8 +251,8 @@ impl FlexLayouter {
Ok(())
}
- fn layout_space(&mut self, space: Size, kind: SpaceKind) {
- if kind == SpaceKind::Soft {
+ fn layout_space(&mut self, space: Size, kind: SpacingKind) {
+ if kind == SpacingKind::Soft {
if self.part.space != LastSpacing::Forbidden {
self.part.space = LastSpacing::Soft(space);
}
@@ -263,7 +263,7 @@ impl FlexLayouter {
self.part.dimensions.x += space;
}
- if kind == SpaceKind::Hard {
+ if kind == SpacingKind::Hard {
self.part.space = LastSpacing::Forbidden;
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index f45ed10a..4304e46e 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -102,7 +102,7 @@ impl LayoutSpace {
}
/// The axes along which the content is laid out.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAxes {
pub primary: Axis,
pub secondary: Axis,
@@ -136,17 +136,66 @@ impl LayoutAxes {
// at the call site, we still have this second function.
self.generalize(size)
}
-}
-/// The two kinds of axes.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum AxisKind {
- Primary,
- Secondary,
+ /// Returns the generic axis kind which is the horizontal axis.
+ pub fn horizontal(&self) -> GenericAxisKind {
+ match self.primary.is_horizontal() {
+ true => GenericAxisKind::Primary,
+ false => GenericAxisKind::Secondary,
+ }
+ }
+
+ /// Returns the generic axis kind which is the vertical axis.
+ pub fn vertical(&self) -> GenericAxisKind {
+ self.horizontal().inv()
+ }
+
+ /// Returns the specific axis kind which is the primary axis.
+ pub fn primary(&self) -> SpecificAxisKind {
+ match self.primary.is_horizontal() {
+ true => SpecificAxisKind::Horizontal,
+ false => SpecificAxisKind::Vertical,
+ }
+ }
+
+ /// Returns the specific axis kind which is the secondary axis.
+ pub fn secondary(&self) -> SpecificAxisKind {
+ self.primary().inv()
+ }
+
+ /// Returns the generic alignment corresponding to left-alignment.
+ pub fn left(&self) -> Alignment {
+ let positive = match self.primary.is_horizontal() {
+ true => self.primary.is_positive(),
+ false => self.secondary.is_positive(),
+ };
+
+ if positive { Alignment::Origin } else { Alignment::End }
+ }
+
+ /// Returns the generic alignment corresponding to right-alignment.
+ pub fn right(&self) -> Alignment {
+ self.left().inv()
+ }
+
+ /// Returns the generic alignment corresponding to top-alignment.
+ pub fn top(&self) -> Alignment {
+ let positive = match self.primary.is_horizontal() {
+ true => self.secondary.is_positive(),
+ false => self.primary.is_positive(),
+ };
+
+ if positive { Alignment::Origin } else { Alignment::End }
+ }
+
+ /// Returns the generic alignment corresponding to bottom-alignment.
+ pub fn bottom(&self) -> Alignment {
+ self.top().inv()
+ }
}
/// Directions along which content is laid out.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Axis {
LeftToRight,
RightToLeft,
@@ -180,8 +229,42 @@ impl Axis {
}
}
+/// The two generic kinds of layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum GenericAxisKind {
+ Primary,
+ Secondary,
+}
+
+impl GenericAxisKind {
+ /// The other axis.
+ pub fn inv(&self) -> GenericAxisKind {
+ match self {
+ GenericAxisKind::Primary => GenericAxisKind::Secondary,
+ GenericAxisKind::Secondary => GenericAxisKind::Primary,
+ }
+ }
+}
+
+/// The two specific kinds of layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum SpecificAxisKind {
+ Horizontal,
+ Vertical,
+}
+
+impl SpecificAxisKind {
+ /// The other axis.
+ pub fn inv(&self) -> SpecificAxisKind {
+ match self {
+ SpecificAxisKind::Horizontal => SpecificAxisKind::Vertical,
+ SpecificAxisKind::Vertical => SpecificAxisKind::Horizontal,
+ }
+ }
+}
+
/// The place to put a layout in a container.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAlignment {
pub primary: Alignment,
pub secondary: Alignment,
@@ -194,13 +277,24 @@ impl LayoutAlignment {
}
/// Where to align content.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Alignment {
Origin,
Center,
End,
}
+impl Alignment {
+ /// The inverse alignment.
+ pub fn inv(&self) -> Alignment {
+ match self {
+ Alignment::Origin => Alignment::End,
+ Alignment::Center => Alignment::Center,
+ Alignment::End => Alignment::Origin,
+ }
+ }
+}
+
/// The specialized anchor position for an item with the given alignment in a
/// container with a given size along the given axis.
pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size {
@@ -213,7 +307,7 @@ pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size {
}
/// Whitespace between boxes with different interaction properties.
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SpacingKind {
/// A hard space consumes surrounding soft spaces and is always layouted.
Hard,
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 793c2044..64823b67 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -108,7 +108,7 @@ impl StackLayouter {
// Find the first (sub-)space that fits the layout.
while !self.sub.usable.fits(new_size) {
if self.space_is_last() && self.space_is_empty() {
- lerr!("box does not fit into stack");
+ lr!("box does not fit into stack");
}
self.finish_space(true);
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 343127e3..514bfe92 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -116,6 +116,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
self.classes.pop();
}
- lerr!("no suitable font for character `{}`", c);
+ lr!("no suitable font for character `{}`", c);
}
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index efa0c7b7..5fe3b6fd 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -99,8 +99,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Add(layout) => self.flex.add(layout),
AddMultiple(layouts) => self.flex.add_multiple(layouts),
AddSpacing(space, kind, axis) => match axis {
- AxisKind::Primary => self.flex.add_primary_space(space, kind),
- AxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
+ GenericAxisKind::Primary => self.flex.add_primary_space(space, kind),
+ GenericAxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
}
FinishLine => self.flex.add_break(),
@@ -111,7 +111,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
SetTextStyle(style) => self.style.text = style,
SetPageStyle(style) => {
if !self.ctx.top_level {
- lerr!("page style cannot only be altered in the top-level context");
+ lr!("page style cannot only be altered in the top-level context");
}
self.style.page = style;
diff --git a/src/library/align.rs b/src/library/align.rs
index 530120e7..14e329e3 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -1,155 +1,68 @@
use crate::func::prelude::*;
-/// 📐 `align`: Aligns content in different ways.
-#[derive(Debug, PartialEq)]
-pub struct Align {
- body: Option<SyntaxTree>,
- positional_1: Option<AlignSpecifier>,
- positional_2: Option<AlignSpecifier>,
- primary: Option<AlignSpecifier>,
- secondary: Option<AlignSpecifier>,
- horizontal: Option<AlignSpecifier>,
- vertical: Option<AlignSpecifier>,
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum AlignSpecifier {
- Origin,
- Center,
- End,
- Left,
- Right,
- Top,
- Bottom,
-}
-
function! {
- data: Align,
+ /// `align`: Aligns content along the layouting axes.
+ #[derive(Debug, PartialEq)]
+ pub struct Align {
+ body: Option<SyntaxTree>,
+ map: ArgMap<Key, AlignmentKey>,
+ }
parse(args, body, ctx) {
- let body = parse!(optional: body, ctx);
-
- let mut align = Align {
- body,
- positional_1: None,
- positional_2: None,
- primary: None,
- secondary: None,
- horizontal: None,
- vertical: None,
- };
-
- if let Some(arg) = args.get_pos_opt::<ArgIdent>()? {
- align.positional_1 = Some(parse_align_specifier(arg)?);
+ let mut map = ArgMap::new();
+ map.put(Key::First, args.get_pos_opt::<ArgIdent>()?)?;
+ map.put(Key::Second, args.get_pos_opt::<ArgIdent>()?)?;
+
+ for arg in args.keys() {
+ let key = match arg.val.0.val {
+ "horizontal" => Key::Axis(AxisKey::Horizontal),
+ "vertical" => Key::Axis(AxisKey::Vertical),
+ "primary" => Key::Axis(AxisKey::Primary),
+ "secondary" => Key::Axis(AxisKey::Secondary),
+ _ => pr!("unexpected argument"),
+ };
+
+ let value = AlignmentKey::parse(arg.val.1.val)?;
+ map.add(key, value);
}
- if let Some(arg) = args.get_pos_opt::<ArgIdent>()? {
- align.positional_2 = Some(parse_align_specifier(arg)?);
+ Align {
+ body: parse!(optional: body, ctx),
+ map,
}
-
- let mut parse_arg = |axis, target: &mut Option<AlignSpecifier>| {
- Ok(if let Some(arg) = args.get_key_opt::<ArgIdent>(axis)? {
- if target.is_none() {
- *target = Some(parse_align_specifier(arg)?);
- } else {
- perr!("duplicate alignment specification for {} axis", axis);
- }
- })
- };
-
- parse_arg("primary", &mut align.primary)?;
- parse_arg("secondary", &mut align.secondary)?;
- parse_arg("horizontal", &mut align.horizontal)?;
- parse_arg("vertical", &mut align.vertical)?;
-
- args.done()?;
-
- Ok(align)
}
- layout(this, ctx) {
- let mut axes = ctx.axes;
- let primary_horizontal = axes.primary.is_horizontal();
-
- let mut primary = false;
- let mut secondary = false;
-
- let mut set_axis = |is_primary: bool, spec: Option<AlignSpecifier>| -> LayoutResult<()> {
- if let Some(spec) = spec {
- let (axis, was_set, name) = match is_primary {
- true => (&mut axes.primary, &mut primary, "primary"),
- false => (&mut axes.secondary, &mut secondary, "secondary"),
- };
-
- if *was_set {
- panic!("duplicate alignment for {} axis", name);
- }
-
- *was_set = true;
-
- let horizontal = axis.is_horizontal();
- let alignment = generic_alignment(spec, horizontal)?;
-
- if axis.alignment == Alignment::End && alignment == Alignment::Origin {
- axis.expand = true;
- }
-
- axis.alignment = alignment;
- }
-
- Ok(())
- };
-
- if let Some(spec) = this.positional_1 {
- let positional = generic_alignment(spec, primary_horizontal).is_ok();
- set_axis(positional, this.positional_1)?;
+ layout(self, mut ctx) {
+ let axes = ctx.axes;
+ let basic = axes.primary.is_horizontal();
+
+ let map = self.map.dedup(|key, val| {
+ let axis = match key {
+ Key::First => val.axis(axes, GenericAxisKind::Primary),
+ Key::Second => val.axis(axes, GenericAxisKind::Secondary),
+ Key::Axis(AxisKey::Primary) => GenericAxisKind::Primary,
+ Key::Axis(AxisKey::Secondary) => GenericAxisKind::Secondary,
+ Key::Axis(AxisKey::Horizontal) => axes.horizontal(),
+ Key::Axis(AxisKey::Vertical) => axes.vertical(),
+ };
+
+ let alignment = val.generic(axes, axis)?;
+ Ok((key, alignment))
+ })?;
+
+ map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val);
+ map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val);
+
+ match &self.body {
+ Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)],
+ None => vec![Command::SetAlignment(ctx.alignment)],
}
-
- if let Some(spec) = this.positional_2 {
- let positional = generic_alignment(spec, primary_horizontal).is_ok();
- set_axis(positional, this.positional_2)?;
- }
-
- set_axis(true, this.primary)?;
- set_axis(false, this.secondary)?;
- set_axis(primary_horizontal, this.horizontal)?;
- set_axis(!primary_horizontal, this.vertical)?;
-
- Ok(match &this.body {
- Some(body) => vec![AddMultiple(
- layout_tree(body, LayoutContext {
- axes,
- .. ctx.clone()
- })?
- )],
- None => vec![Command::SetAxes(axes)]
- })
}
}
-fn parse_align_specifier(arg: Spanned<&str>) -> ParseResult<AlignSpecifier> {
- Ok(match arg.val {
- "origin" => AlignSpecifier::Origin,
- "center" => AlignSpecifier::Center,
- "end" => AlignSpecifier::End,
- "left" => AlignSpecifier::Left,
- "right" => AlignSpecifier::Right,
- "top" => AlignSpecifier::Top,
- "bottom" => AlignSpecifier::Bottom,
- s => perr!("invalid alignment specifier: {}", s),
- })
-}
-
-fn generic_alignment(spec: AlignSpecifier, horizontal: bool) -> LayoutResult<Alignment> {
- use AlignSpecifier::*;
- Ok(match (spec, horizontal) {
- (Origin, _) | (Left, true) | (Top, false) => Alignment::Origin,
- (Center, _) => Alignment::Center,
- (End, _) | (Right, true) | (Bottom, false) => Alignment::End,
- _ => lerr!(
- "invalid alignment specifier `{}` for {} axis",
- format!("{:?}", spec).to_lowercase(),
- if horizontal { "horizontal" } else { "vertical" },
- ),
- })
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+enum Key {
+ First,
+ Second,
+ Axis(AxisKey),
}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index 8984417c..d4e39450 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -1,37 +1,44 @@
use crate::func::prelude::*;
-/// `box`: Layouts content into a box.
-#[derive(Debug, PartialEq)]
-pub struct Boxed {
- body: SyntaxTree,
- width: Option<Size>,
- height: Option<Size>,
-}
-
function! {
- data: Boxed,
+ /// `box`: Layouts content into a box.
+ #[derive(Debug, PartialEq)]
+ pub struct Boxed {
+ body: SyntaxTree,
+ map: ArgMap<AxisKey, Size>,
+ }
parse(args, body, ctx) {
- let width = args.get_key_opt::<ArgSize>("width")?.map(|a| a.val);
- let height = args.get_key_opt::<ArgSize>("height")?.map(|a| a.val);
- args.done()?;
-
- let body = parse!(required: body, ctx);
- Ok(Boxed {
- body,
- width,
- height,
- })
- }
+ let mut map = ArgMap::new();
+
+ for arg in args.keys() {
+ let key = match arg.val.0.val {
+ "width" | "w" => AxisKey::Horizontal,
+ "height" | "h" => AxisKey::Vertical,
+ "primary-size" => AxisKey::Primary,
+ "secondary-size" => AxisKey::Secondary,
+ _ => pr!("unexpected argument"),
+ };
- layout(this, mut ctx) {
- if let Some(width) = this.width {
- ctx.spaces[0].dimensions.x = width;
+ let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?;
+ map.add(key, size);
}
- if let Some(height) = this.height {
- ctx.spaces[0].dimensions.y = height;
+
+ Boxed {
+ body: parse!(expected: body, ctx),
+ map,
}
+ }
+
+ layout(self, mut ctx) {
+ let map = self.map.dedup(|key, val| {
+ Ok((key.specific(ctx.axes), val))
+ });
+
+ let mut dimensions = &mut ctx.spaces[0].dimensions;
+ map.with(AxisKey::Horizontal, |val| dimensions.x = val);
+ map.with(AxisKey::Vertical, |val| dimensions.y = val);
- Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)])
+ vec![AddMultiple(layout_tree(&self.body, ctx)?)]
}
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index adcb974b..b09303bb 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -1,34 +1,274 @@
//! The standard library for the _Typst_ language.
-use crate::func::Scope;
+use crate::func::prelude::*;
+use toddle::query::FontClass;
-pub_use_mod!(boxed);
pub_use_mod!(align);
-pub_use_mod!(spacing);
-pub_use_mod!(style);
-pub_use_mod!(page);
+pub_use_mod!(boxed);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
let mut std = Scope::new();
- std.add::<Boxed>("box");
-
std.add::<Align>("align");
+ std.add::<Boxed>("box");
+ std.add::<PageSize>("page.size");
+ std.add::<PageMargins>("page.margins");
std.add::<LineBreak>("n");
std.add::<LineBreak>("line.break");
- std.add::<ParagraphBreak>("paragraph.break");
+ std.add::<ParBreak>("par.break");
std.add::<PageBreak>("page.break");
- std.add::<HorizontalSpace>("h");
- std.add::<VerticalSpace>("v");
- std.add::<Bold>("bold");
- std.add::<Italic>("italic");
- std.add::<Monospace>("mono");
+ std.add_with_metadata::<Spacing, Option<AxisKey>>("spacing", None);
+ for (name, key) in &[
+ ("h", AxisKey::Horizontal),
+ ("v", AxisKey::Vertical),
+ ] {
+ std.add_with_metadata::<Spacing, Option<AxisKey>>(name, Some(*key));
+ }
- std.add::<PageSize>("page.size");
- std.add::<PageMargins>("page.margins");
+ for (name, class) in &[
+ ("bold", FontClass::Bold),
+ ("italic", FontClass::Italic),
+ ("mono", FontClass::Monospace),
+ ] {
+ std.add_with_metadata::<StyleChange, FontClass>(name, *class);
+ }
std
}
+
+function! {
+ /// `line.break`, `n`: Ends the current line.
+ #[derive(Debug, Default, PartialEq)]
+ pub struct LineBreak;
+
+ parse(default)
+ layout() { vec![FinishLine] }
+}
+
+function! {
+ /// `par.break`: Ends the current paragraph.
+ ///
+ /// self has the same effect as two subsequent newlines.
+ #[derive(Debug, Default, PartialEq)]
+ pub struct ParBreak;
+
+ parse(default)
+ layout() { vec![BreakParagraph] }
+}
+
+
+function! {
+ /// `page.break`: Ends the current page.
+ #[derive(Debug, Default, PartialEq)]
+ pub struct PageBreak;
+
+ parse(default)
+ layout() { vec![FinishSpace] }
+}
+
+function! {
+ /// `page.size`: Set the size of pages.
+ #[derive(Debug, PartialEq)]
+ pub struct PageSize {
+ width: Option<Size>,
+ height: Option<Size>,
+ }
+
+ parse(args, body) {
+ parse!(forbidden: body);
+ PageSize {
+ width: args.get_key_opt::<ArgSize>("width")?.map(|a| a.val),
+ height: args.get_key_opt::<ArgSize>("height")?.map(|a| a.val),
+ }
+ }
+
+ layout(self, ctx) {
+ let mut style = ctx.style.page;
+ if let Some(width) = self.width { style.dimensions.x = width; }
+ if let Some(height) = self.height { style.dimensions.y = height; }
+ vec![SetPageStyle(style)]
+ }
+}
+
+function! {
+ /// `page.margins`: Set the margins of pages.
+ #[derive(Debug, PartialEq)]
+ pub struct PageMargins {
+ map: ArgMap<PaddingKey, Size>,
+ }
+
+ parse(args, body) {
+ use PaddingKey::*;
+ use AlignmentKey::*;
+
+ let mut map = ArgMap::new();
+ map.add_opt(All, args.get_pos_opt::<ArgSize>()?);
+
+ for arg in args.keys() {
+ let key = match arg.val.0.val {
+ "horizontal" => Axis(AxisKey::Horizontal),
+ "vertical" => Axis(AxisKey::Vertical),
+ "primary" => Axis(AxisKey::Primary),
+ "secondary" => Axis(AxisKey::Secondary),
+
+ "left" => AxisAligned(AxisKey::Horizontal, Left),
+ "right" => AxisAligned(AxisKey::Horizontal, Right),
+ "top" => AxisAligned(AxisKey::Vertical, Top),
+ "bottom" => AxisAligned(AxisKey::Vertical, Bottom),
+
+ "primary-origin" => AxisAligned(AxisKey::Primary, Origin),
+ "primary-end" => AxisAligned(AxisKey::Primary, End),
+ "secondary-origin" => AxisAligned(AxisKey::Secondary, Origin),
+ "secondary-end" => AxisAligned(AxisKey::Secondary, End),
+ "horizontal-origin" => AxisAligned(AxisKey::Horizontal, Origin),
+ "horizontal-end" => AxisAligned(AxisKey::Horizontal, End),
+ "vertical-origin" => AxisAligned(AxisKey::Vertical, Origin),
+ "vertical-end" => AxisAligned(AxisKey::Vertical, End),
+
+ _ => pr!("unexpected argument"),
+ };
+
+ let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?;
+ map.add(key, size);
+ }
+
+ parse!(forbidden: body);
+ PageMargins { map }
+ }
+
+ layout(self, ctx) {
+ use PaddingKey::*;
+
+ let axes = ctx.axes;
+ let map = self.map.dedup(|key, val| {
+ match key {
+ All => All,
+ Axis(axis) => Axis(axis.specific(axes)),
+ AxisAligned(axis, alignment) => {
+ let axis = axis.specific(axes);
+ AxisAligned(axis, alignment.specific(axes, axis))
+ }
+ }
+ });
+
+ let style = ctx.style.page;
+ let padding = &mut style.margins;
+
+ map.with(All, |val| padding.set_all(val));
+ map.with(Axis(AxisKey::Horizontal), |val| padding.set_horizontal(val));
+ map.with(Axis(AxisKey::Vertical), |val| padding.set_vertical(val));
+
+ for (key, val) in map.iter() {
+ if let AxisAligned(axis, alignment) = key {
+ match alignment {
+ AlignmentKey::Left => padding.left = val,
+ AlignmentKey::Right => padding.right = val,
+ AlignmentKey::Top => padding.top = val,
+ AlignmentKey::Bottom => padding.bottom = val,
+ _ => {},
+ }
+ }
+ }
+
+ vec![SetPageStyle(style)]
+ }
+}
+
+function! {
+ /// `spacing`, `h`, `v`: Add spacing along an axis.
+ #[derive(Debug, PartialEq)]
+ pub struct Spacing {
+ axis: AxisKey,
+ spacing: SpacingValue,
+ }
+
+ type Meta = Option<AxisKey>;
+
+ parse(args, body, _, meta) {
+ let spacing = if let Some(axis) = meta {
+ Spacing {
+ axis,
+ spacing: SpacingValue::from_expr(args.get_pos::<ArgExpr>()?)?,
+ }
+ } else {
+ if let Some(arg) = args.get_key_next() {
+ let axis = match arg.val.0.val {
+ "horizontal" => AxisKey::Horizontal,
+ "vertical" => AxisKey::Vertical,
+ "primary" => AxisKey::Primary,
+ "secondary" => AxisKey::Secondary,
+ _ => pr!("unexpected argument"),
+ };
+
+ let spacing = SpacingValue::from_expr(arg.val.1.val)?;
+ Spacing { axis, spacing }
+ } else {
+ pr!("expected axis and expression")
+ }
+ };
+
+ parse!(forbidden: body);
+ spacing
+ }
+
+ layout(self, ctx) {
+ let axis = self.axis.generic(ctx.axes);
+ let spacing = match self.spacing {
+ SpacingValue::Absolute(s) => s,
+ SpacingValue::Relative(f) => f * ctx.style.text.font_size,
+ };
+
+ vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
+ }
+}
+
+#[derive(Debug, PartialEq)]
+enum SpacingValue {
+ Absolute(Size),
+ Relative(f32),
+}
+
+impl SpacingValue {
+ fn from_expr(expr: Spanned<&Expression>) -> ParseResult<SpacingValue> {
+ Ok(match expr.val {
+ Expression::Size(s) => SpacingValue::Absolute(*s),
+ Expression::Num(f) => SpacingValue::Relative(*f as f32),
+ _ => pr!("invalid spacing: expected size or number"),
+ })
+ }
+}
+
+function! {
+ /// Sets text with a different style.
+ #[derive(Debug, PartialEq)]
+ pub struct StyleChange {
+ body: Option<SyntaxTree>,
+ class: FontClass,
+ }
+
+ type Meta = FontClass;
+
+ parse(args, body, ctx, meta) {
+ StyleChange {
+ body: parse!(optional: body, ctx),
+ class: meta,
+ }
+ }
+
+ layout(self, ctx) {
+ let mut style = ctx.style.text.clone();
+ style.toggle_class(self.class);
+
+ match &self.body {
+ Some(body) => vec![
+ SetTextStyle(style),
+ LayoutTree(body),
+ SetTextStyle(ctx.style.text.clone()),
+ ],
+ None => vec![SetTextStyle(style)]
+ }
+ }
+}
diff --git a/src/library/page.rs b/src/library/page.rs
deleted file mode 100644
index e8a80870..00000000
--- a/src/library/page.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-use crate::func::prelude::*;
-
-/// `page.break`: Ends the current page.
-#[derive(Debug, PartialEq)]
-pub struct PageBreak;
-
-function! {
- data: PageBreak,
- parse: plain,
- layout(_, _) { Ok(vec![FinishSpace]) }
-}
-
-/// `page.size`: Set the size of pages.
-#[derive(Debug, PartialEq)]
-pub struct PageSize {
- width: Option<Size>,
- height: Option<Size>,
-}
-
-function! {
- data: PageSize,
-
- parse(args, body, _ctx) {
- parse!(forbidden: body);
- Ok(PageSize {
- width: args.get_key_opt::<ArgSize>("width")?.map(|a| a.val),
- height: args.get_key_opt::<ArgSize>("height")?.map(|a| a.val),
- })
- }
-
- layout(this, ctx) {
- let mut style = ctx.style.page;
-
- if let Some(width) = this.width { style.dimensions.x = width; }
- if let Some(height) = this.height { style.dimensions.y = height; }
-
- Ok(vec![SetPageStyle(style)])
- }
-}
-
-/// `page.margins`: Set the margins of pages.
-#[derive(Debug, PartialEq)]
-pub struct PageMargins {
- left: Option<Size>,
- top: Option<Size>,
- right: Option<Size>,
- bottom: Option<Size>,
-}
-
-function! {
- data: PageMargins,
-
- parse(args, body, _ctx) {
- parse!(forbidden: body);
- let default = args.get_pos_opt::<ArgSize>()?;
- let mut get = |which| {
- args.get_key_opt::<ArgSize>(which)
- .map(|size| size.or(default).map(|a| a.val))
- };
-
- Ok(PageMargins {
- left: get("left")?,
- top: get("top")?,
- right: get("right")?,
- bottom: get("bottom")?,
- })
- }
-
- layout(this, ctx) {
- let mut style = ctx.style.page;
-
- if let Some(left) = this.left { style.margins.left = left; }
- if let Some(top) = this.top { style.margins.top = top; }
- if let Some(right) = this.right { style.margins.right = right; }
- if let Some(bottom) = this.bottom { style.margins.bottom = bottom; }
-
- Ok(vec![SetPageStyle(style)])
- }
-}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
deleted file mode 100644
index 47fe9fff..00000000
--- a/src/library/spacing.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use crate::func::prelude::*;
-
-/// `line.break`, `n`: Ends the current line.
-#[derive(Debug, PartialEq)]
-pub struct LineBreak;
-
-function! {
- data: LineBreak,
- parse: plain,
- layout(_, _) { Ok(vec![FinishLine]) }
-}
-
-/// `paragraph.break`: Ends the current paragraph.
-///
-/// This has the same effect as two subsequent newlines.
-#[derive(Debug, PartialEq)]
-pub struct ParagraphBreak;
-
-function! {
- data: ParagraphBreak,
- parse: plain,
- layout(_, _) { Ok(vec![BreakParagraph]) }
-}
-
-macro_rules! space_func {
- ($ident:ident, $doc:expr, $var:ident => $command:expr) => (
- #[doc = $doc]
- #[derive(Debug, PartialEq)]
- pub struct $ident(Spacing);
-
- function! {
- data: $ident,
-
- parse(args, body, _ctx) {
- parse!(forbidden: body);
-
- let arg = args.get_pos::<ArgExpr>()?;
- let spacing = match arg.val {
- Expression::Size(s) => Spacing::Absolute(*s),
- Expression::Num(f) => Spacing::Relative(*f as f32),
- _ => perr!("invalid spacing, expected size or number"),
- };
-
- Ok($ident(spacing))
- }
-
- layout(this, ctx) {
- let $var = match this.0 {
- Spacing::Absolute(s) => s,
- Spacing::Relative(f) => f * ctx.style.text.font_size,
- };
-
- Ok(vec![$command])
- }
- }
- );
-}
-
-/// Absolute or font-relative spacing.
-#[derive(Debug, PartialEq)]
-enum Spacing {
- Absolute(Size),
- Relative(f32),
-}
-
-// FIXME: h != primary and v != secondary.
-space_func!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.",
- space => AddSpacing(space, SpacingKind::Hard, AxisKind::Primary));
-
-space_func!(VerticalSpace, "📑 `v`: Adds vertical whitespace.",
- space => AddSpacing(space, SpacingKind::Hard, AxisKind::Secondary));
diff --git a/src/library/style.rs b/src/library/style.rs
deleted file mode 100644
index b2de19bb..00000000
--- a/src/library/style.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use crate::func::prelude::*;
-use toddle::query::FontClass;
-
-macro_rules! stylefunc {
- ($ident:ident, $doc:expr) => (
- #[doc = $doc]
- #[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 style = ctx.style.text.clone();
- style.toggle_class(FontClass::$ident);
-
- Ok(match &this.body {
- Some(body) => vec![
- SetTextStyle(style),
- LayoutTree(body),
- SetTextStyle(ctx.style.text.clone()),
- ],
- None => vec![SetTextStyle(style)]
- })
- }
- }
- );
-}
-
-stylefunc!(Italic, "`italic`: Sets text in _italics_.");
-stylefunc!(Bold, "`bold`: Sets text in **bold**.");
-stylefunc!(Monospace, "`mono`: Sets text in `monospace`.");
diff --git a/src/size.rs b/src/size.rs
index 50b3ace5..a9c3198c 100644
--- a/src/size.rs
+++ b/src/size.rs
@@ -51,25 +51,19 @@ impl Size {
/// Create a size from an amount of millimeters.
#[inline]
pub fn mm(mm: f32) -> Size {
- Size {
- points: 2.83465 * mm,
- }
+ Size { points: 2.83465 * mm }
}
/// Create a size from an amount of centimeters.
#[inline]
pub fn cm(cm: f32) -> Size {
- Size {
- points: 28.3465 * cm,
- }
+ Size { points: 28.3465 * cm }
}
/// Create a size from an amount of inches.
#[inline]
pub fn inches(inches: f32) -> Size {
- Size {
- points: 72.0 * inches,
- }
+ Size { points: 72.0 * inches }
}
/// Convert this size into points.
@@ -188,12 +182,33 @@ impl SizeBox {
/// Create a box with all four fields set to the same value `s`.
#[inline]
- pub fn with_all(s: Size) -> SizeBox {
- SizeBox { left: s, top: s, right: s, bottom: s }
+ pub fn with_all(value: Size) -> SizeBox {
+ SizeBox { left: value, top: value, right: value, bottom: value }
+ }
+
+ /// Set the `left` and `right` values.
+ #[inline]
+ pub fn set_all(&mut self, value: Size) {
+ *self = SizeBox::with_all(value);
+ }
+
+ /// Set the `left` and `right` values.
+ #[inline]
+ pub fn set_horizontal(&mut self, value: Size) {
+ self.left = value;
+ self.right = value;
+ }
+
+ /// Set the `top` and `bottom` values.
+ #[inline]
+ pub fn set_vertical(&mut self, value: Size) {
+ self.top = value;
+ self.bottom = value;
}
}
/// The maximum of two sizes.
+#[inline]
pub fn max(a: Size, b: Size) -> Size {
if a >= b {
a
@@ -203,6 +218,7 @@ pub fn max(a: Size, b: Size) -> Size {
}
/// The minimum of two sizes.
+#[inline]
pub fn min(a: Size, b: Size) -> Size {
if a <= b {
a
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 9406fdf4..33413317 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -2,13 +2,15 @@
use std::fmt::{self, Display, Formatter};
-use crate::func::Function;
+use crate::func::LayoutFunc;
use crate::size::Size;
mod tokens;
#[macro_use]
mod parsing;
+mod span;
+pub use span::{Span, Spanned};
pub use tokens::{tokenize, Tokens};
pub use parsing::{parse, ParseContext, ParseError, ParseResult};
@@ -90,7 +92,13 @@ pub enum Node {
#[derive(Debug)]
pub struct FuncCall {
pub header: Spanned<FuncHeader>,
- pub body: Spanned<Box<dyn Function>>,
+ pub body: Spanned<Box<dyn LayoutFunc>>,
+}
+
+impl PartialEq for FuncCall {
+ fn eq(&self, other: &FuncCall) -> bool {
+ (self.header == other.header) && (&self.body == &other.body)
+ }
}
/// Contains header information of a function invocation.
@@ -134,12 +142,6 @@ pub enum Expression {
Bool(bool),
}
-impl PartialEq for FuncCall {
- fn eq(&self, other: &FuncCall) -> bool {
- (self.header == other.header) && (&self.body == &other.body)
- }
-}
-
impl Display for Expression {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Expression::*;
@@ -154,72 +156,3 @@ impl Display for Expression {
}
debug_display!(Expression);
-
-/// Annotates a value with the part of the source code it corresponds to.
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub struct Spanned<T> {
- pub val: T,
- pub span: Span,
-}
-
-impl<T> Spanned<T> {
- pub fn new(val: T, span: Span) -> Spanned<T> {
- Spanned { val, span }
- }
-
- pub fn value(self) -> T {
- self.val
- }
-
- pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
- Spanned::new(f(self.val), self.span)
- }
-}
-
-impl<T> Display for Spanned<T> where T: std::fmt::Debug {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "({:?}:{})", self.val, self.span)
- }
-}
-
-debug_display!(Spanned; T where T: std::fmt::Debug);
-
-/// Describes a slice of source code.
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub struct Span {
- pub start: usize,
- pub end: usize,
-}
-
-impl Span {
- pub fn new(start: usize, end: usize) -> Span {
- Span { start, end }
- }
-
- pub fn merge(a: Span, b: Span) -> Span {
- Span {
- start: a.start.min(b.start),
- end: a.end.max(b.end),
- }
- }
-
- pub fn at(index: usize) -> Span {
- Span { start: index, end: index + 1 }
- }
-
- pub fn pair(&self) -> (usize, usize) {
- (self.start, self.end)
- }
-
- pub fn expand(&mut self, other: Span) {
- *self = Span::merge(*self, other)
- }
-}
-
-impl Display for Span {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "[{}, {}]", self.start, self.end)
- }
-}
-
-debug_display!(Span);
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 77697161..b56094e1 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -2,7 +2,7 @@
use unicode_xid::UnicodeXID;
-use crate::func::{Function, Scope};
+use crate::func::{LayoutFunc, Scope};
use crate::size::Size;
use super::*;
@@ -120,10 +120,10 @@ impl<'s> Parser<'s> {
if is_identifier(word) {
Ok(Spanned::new(word.to_owned(), span))
} else {
- perr!("invalid identifier: '{}'", word);
+ pr!("invalid identifier: '{}'", word);
}
}
- _ => perr!("expected identifier"),
+ _ => pr!("expected identifier"),
}?;
self.skip_white();
@@ -132,7 +132,7 @@ impl<'s> Parser<'s> {
let args = match self.tokens.next().map(Spanned::value) {
Some(Token::RightBracket) => FuncArgs::new(),
Some(Token::Colon) => self.parse_func_args()?,
- _ => perr!("expected arguments or closing bracket"),
+ _ => pr!("expected arguments or closing bracket"),
};
let end = self.tokens.string_index();
@@ -158,7 +158,7 @@ impl<'s> Parser<'s> {
match self.tokens.next().map(Spanned::value) {
Some(Token::Comma) => {},
Some(Token::RightBracket) => break,
- _ => perr!("expected comma or closing bracket"),
+ _ => pr!("expected comma or closing bracket"),
}
}
@@ -183,7 +183,7 @@ impl<'s> Parser<'s> {
self.skip_white();
let name = token.span_map(|_| name.to_string());
- let next = self.tokens.next().ok_or_else(|| perr!(@"expected value"))?;
+ let next = self.tokens.next().ok_or_else(|| pr!(@"expected value"))?;
let val = Self::parse_expression(next)?;
let span = Span::merge(name.span, val.span);
@@ -219,18 +219,19 @@ impl<'s> Parser<'s> {
}
}
- _ => perr!("expected expression"),
+ _ => pr!("expected expression"),
}, token.span))
}
/// Parse the body of a function.
- fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult<Spanned<Box<dyn Function>>> {
+ fn parse_func_body(&mut self, header: &FuncHeader)
+ -> ParseResult<Spanned<Box<dyn LayoutFunc>>> {
// Now we want to parse this function dynamically.
let parser = self
.ctx
.scope
.get_parser(&header.name.val)
- .ok_or_else(|| perr!(@"unknown function: '{}'", &header.name.val))?;
+ .ok_or_else(|| pr!(@"unknown function: '{}'", &header.name.val))?;
let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket);
@@ -298,7 +299,7 @@ impl<'s> Parser<'s> {
state = NewlineState::Zero;
match token.val {
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
- Token::StarSlash => perr!("unexpected end of block comment"),
+ Token::StarSlash => pr!("unexpected end of block comment"),
_ => break,
}
}
@@ -454,7 +455,7 @@ mod tests {
#![allow(non_snake_case)]
use super::*;
- use crate::func::{CommandList, Function, Scope};
+ use crate::func::{Commands, Scope};
use crate::layout::{LayoutContext, LayoutResult};
use funcs::*;
use Node::{Func as F, Newline as N, Space as S};
@@ -464,37 +465,36 @@ mod tests {
mod funcs {
use super::*;
- /// A testing function which just parses it's body into a syntax tree.
- #[derive(Debug)]
- pub struct TreeFn(pub SyntaxTree);
-
function! {
- data: TreeFn,
+ /// A testing function which just parses it's body into a syntax
+ /// tree.
+ #[derive(Debug)]
+ pub struct TreeFn { pub tree: SyntaxTree }
+
+ parse(args, body, ctx) {
+ args.clear();
+ TreeFn {
+ tree: parse!(expected: body, ctx)
+ }
+ }
- parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) }
- layout(_, _) { Ok(vec![]) }
+ layout() { vec![] }
}
impl PartialEq for TreeFn {
fn eq(&self, other: &TreeFn) -> bool {
- assert_tree_equal(&self.0, &other.0);
+ assert_tree_equal(&self.tree, &other.tree);
true
}
}
- /// A testing function without a body.
- #[derive(Debug)]
- pub struct BodylessFn;
-
function! {
- data: BodylessFn,
-
- parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) }
- layout(_, _) { Ok(vec![]) }
- }
+ /// A testing function without a body.
+ #[derive(Debug, Default, PartialEq)]
+ pub struct BodylessFn;
- impl PartialEq for BodylessFn {
- fn eq(&self, _: &BodylessFn) -> bool { true }
+ parse(default)
+ layout() { vec![] }
}
}
@@ -583,7 +583,7 @@ mod tests {
func!(@$name, Box::new(BodylessFn), FuncArgs::new())
);
(name => $name:expr, body => $tree:expr $(,)*) => (
- func!(@$name, Box::new(TreeFn($tree)), FuncArgs::new())
+ func!(@$name, Box::new(TreeFn { tree: $tree }), FuncArgs::new())
);
(@$name:expr, $body:expr, $args:expr) => (
FuncCall {
@@ -789,7 +789,7 @@ mod tests {
assert_eq!(func.header.val.args.positional[0].span.pair(), (13, 16));
assert_eq!(func.header.val.args.positional[1].span.pair(), (18, 23));
- let body = &func.body.val.downcast::<TreeFn>().unwrap().0.nodes;
+ let body = &func.body.val.downcast::<TreeFn>().unwrap().tree.nodes;
assert_eq!(func.body.span.pair(), (24, 37));
assert_eq!(body[0].span.pair(), (0, 4));
assert_eq!(body[1].span.pair(), (4, 5));
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
new file mode 100644
index 00000000..aa494224
--- /dev/null
+++ b/src/syntax/span.rs
@@ -0,0 +1,72 @@
+//! Spans map elements to the part of source code they originate from.
+
+use std::fmt::{self, Display, Formatter};
+
+/// Annotates a value with the part of the source code it corresponds to.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Spanned<T> {
+ pub val: T,
+ pub span: Span,
+}
+
+impl<T> Spanned<T> {
+ pub fn new(val: T, span: Span) -> Spanned<T> {
+ Spanned { val, span }
+ }
+
+ pub fn value(self) -> T {
+ self.val
+ }
+
+ pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
+ Spanned::new(f(self.val), self.span)
+ }
+}
+
+impl<T> Display for Spanned<T> where T: std::fmt::Debug {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "({:?}:{})", self.val, self.span)
+ }
+}
+
+debug_display!(Spanned; T where T: std::fmt::Debug);
+
+/// Describes a slice of source code.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Span {
+ pub start: usize,
+ pub end: usize,
+}
+
+impl Span {
+ pub fn new(start: usize, end: usize) -> Span {
+ Span { start, end }
+ }
+
+ pub fn merge(a: Span, b: Span) -> Span {
+ Span {
+ start: a.start.min(b.start),
+ end: a.end.max(b.end),
+ }
+ }
+
+ pub fn at(index: usize) -> Span {
+ Span { start: index, end: index + 1 }
+ }
+
+ pub fn pair(&self) -> (usize, usize) {
+ (self.start, self.end)
+ }
+
+ pub fn expand(&mut self, other: Span) {
+ *self = Span::merge(*self, other)
+ }
+}
+
+impl Display for Span {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "[{}, {}]", self.start, self.end)
+ }
+}
+
+debug_display!(Span);