Expand functionality of function! macro 🛰
This commit is contained in:
parent
ace57c3420
commit
9fb31defd0
@ -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
215
src/func/args.rs
Normal 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),
|
||||
}
|
@ -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
149
src/func/macros.rs
Normal 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)*)););
|
||||
}
|
125
src/func/mod.rs
125
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;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)?)]
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)])
|
||||
}
|
||||
}
|
@ -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));
|
@ -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`.");
|
38
src/size.rs
38
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
|
||||
|
@ -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);
|
||||
|
@ -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
72
src/syntax/span.rs
Normal 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);
|
Loading…
Reference in New Issue
Block a user