Expand functionality of function! macro 🛰

This commit is contained in:
Laurenz 2019-12-04 19:34:29 +01:00
parent ace57c3420
commit 9fb31defd0
20 changed files with 1039 additions and 767 deletions

View File

@ -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),

215
src/func/args.rs Normal file
View File

@ -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),
}

View File

@ -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);

149
src/func/macros.rs Normal file
View File

@ -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)*)););
}

View File

@ -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;
/// Layout this function given a 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>;
fn parse(
header: &FuncHeader,
body: Option<&str>,
ctx: ParseContext,
metadata: Self::Meta,
) -> ParseResult<Self> where Self: Sized;
}
impl dyn Function {
/// Downcast a dynamic function to a concrete function type.
pub fn downcast<F>(&self) -> Option<&F> where F: Function + 'static {
/// 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 a sequence of layouting commands which describe what the
/// function is doing.
fn layout(&self, ctx: LayoutContext) -> LayoutResult<Commands>;
}
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)
}
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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 map = ArgMap::new();
map.put(Key::First, args.get_pos_opt::<ArgIdent>()?)?;
map.put(Key::Second, args.get_pos_opt::<ArgIdent>()?)?;
let mut align = Align {
body,
positional_1: None,
positional_2: None,
primary: None,
secondary: None,
horizontal: None,
vertical: None,
};
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"),
};
if let Some(arg) = args.get_pos_opt::<ArgIdent>()? {
align.positional_1 = Some(parse_align_specifier(arg)?);
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();
layout(self, mut ctx) {
let axes = ctx.axes;
let basic = axes.primary.is_horizontal();
let mut primary = false;
let mut secondary = false;
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 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"),
};
let alignment = val.generic(axes, axis)?;
Ok((key, alignment))
})?;
if *was_set {
panic!("duplicate alignment for {} axis", name);
}
map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val);
map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val);
*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)?;
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),
}

View File

@ -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 mut map = ArgMap::new();
let body = parse!(required: body, ctx);
Ok(Boxed {
body,
width,
height,
})
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"),
};
let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?;
map.add(key, size);
}
Boxed {
body: parse!(expected: body, ctx),
map,
}
}
layout(this, mut ctx) {
if let Some(width) = this.width {
ctx.spaces[0].dimensions.x = width;
}
if let Some(height) = this.height {
ctx.spaces[0].dimensions.y = height;
}
layout(self, mut ctx) {
let map = self.map.dedup(|key, val| {
Ok((key.specific(ctx.axes), val))
});
Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)])
let mut dimensions = &mut ctx.spaces[0].dimensions;
map.with(AxisKey::Horizontal, |val| dimensions.x = val);
map.with(AxisKey::Vertical, |val| dimensions.y = val);
vec![AddMultiple(layout_tree(&self.body, ctx)?)]
}
}

View File

@ -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::<LineBreak>("n");
std.add::<LineBreak>("line.break");
std.add::<ParagraphBreak>("paragraph.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::<Boxed>("box");
std.add::<PageSize>("page.size");
std.add::<PageMargins>("page.margins");
std.add::<LineBreak>("n");
std.add::<LineBreak>("line.break");
std.add::<ParBreak>("par.break");
std.add::<PageBreak>("page.break");
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));
}
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)]
}
}
}

View File

@ -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)])
}
}

View File

@ -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));

View File

@ -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`.");

View File

@ -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

View File

@ -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);

View File

@ -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) { Ok(TreeFn(parse!(required: body, ctx))) }
layout(_, _) { Ok(vec![]) }
parse(args, body, ctx) {
args.clear();
TreeFn {
tree: parse!(expected: body, ctx)
}
}
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,
/// A testing function without a body.
#[derive(Debug, Default, PartialEq)]
pub struct BodylessFn;
parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) }
layout(_, _) { Ok(vec![]) }
}
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));

72
src/syntax/span.rs Normal file
View File

@ -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);