mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
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>>
|
) -> PdfResult<ExportProcess<'d, W>>
|
||||||
{
|
{
|
||||||
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
|
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 {
|
Ok(ExportProcess {
|
||||||
writer: PdfWriter::new(target),
|
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::*;
|
use self::prelude::*;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod helpers;
|
pub mod macros;
|
||||||
|
pub mod args;
|
||||||
|
|
||||||
/// Useful imports for creating your own functions.
|
/// Useful imports for creating your own functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::func::{Command, CommandList, Function};
|
pub use Command::*;
|
||||||
pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext};
|
pub use super::args::*;
|
||||||
pub use crate::layout::{LayoutSpace, LayoutSpaces, SpacingKind};
|
pub use super::{Scope, ParseFunc, LayoutFunc, Command, Commands};
|
||||||
pub use crate::layout::{LayoutAxes, Axis, AxisKind, LayoutAlignment, Alignment};
|
|
||||||
pub use crate::layout::{LayoutError, LayoutResult};
|
|
||||||
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
|
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
|
||||||
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
||||||
pub use crate::size::{Size, Size2D, SizeBox};
|
pub use crate::size::{Size, Size2D, SizeBox};
|
||||||
pub use crate::style::{PageStyle, TextStyle};
|
pub use crate::style::{PageStyle, TextStyle};
|
||||||
pub use super::helpers::*;
|
pub use crate::layout::{
|
||||||
pub use Command::*;
|
layout_tree, Layout, MultiLayout,
|
||||||
|
LayoutContext, LayoutSpace, LayoutSpaces,
|
||||||
|
LayoutAxes, Axis, GenericAxisKind, SpecificAxisKind,
|
||||||
|
LayoutAlignment, Alignment,
|
||||||
|
SpacingKind,
|
||||||
|
LayoutError, LayoutResult,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Typesetting function types.
|
/// Types representing functions that are parsed from source code.
|
||||||
///
|
pub trait ParseFunc {
|
||||||
/// These types have to be able to parse themselves from a string and build
|
type Meta;
|
||||||
/// 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 {
|
|
||||||
/// Parse the header and body into this function given a context.
|
/// Parse the header and body into this function given a context.
|
||||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
fn parse(
|
||||||
where Self: Sized;
|
header: &FuncHeader,
|
||||||
|
body: Option<&str>,
|
||||||
/// Layout this function given a context.
|
ctx: ParseContext,
|
||||||
///
|
metadata: Self::Meta,
|
||||||
/// Returns optionally the resulting layout and a new context if changes to
|
) -> ParseResult<Self> where Self: Sized;
|
||||||
/// the context should be made.
|
|
||||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn Function {
|
/// Types representing functions which can be laid out in a layout context.
|
||||||
/// Downcast a dynamic function to a concrete function type.
|
///
|
||||||
pub fn downcast<F>(&self) -> Option<&F> where F: Function + 'static {
|
/// 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>()
|
self.help_cast_as_any().downcast_ref::<F>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for dyn Function {
|
impl PartialEq for dyn LayoutFunc {
|
||||||
fn eq(&self, other: &dyn Function) -> bool {
|
fn eq(&self, other: &dyn LayoutFunc) -> bool {
|
||||||
self.help_eq(other)
|
self.help_eq(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,22 +74,20 @@ impl PartialEq for dyn Function {
|
|||||||
///
|
///
|
||||||
/// Automatically implemented for all types which fulfill to the bounds `Debug +
|
/// Automatically implemented for all types which fulfill to the bounds `Debug +
|
||||||
/// PartialEq + 'static`. There should be no need to implement this manually.
|
/// PartialEq + 'static`. There should be no need to implement this manually.
|
||||||
pub trait FunctionBounds: Debug {
|
pub trait LayoutFuncBounds: Debug {
|
||||||
/// Cast self into `Any`.
|
/// Cast self into `Any`.
|
||||||
fn help_cast_as_any(&self) -> &dyn Any;
|
fn help_cast_as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
/// Compare self with another function.
|
/// Compare self with another function trait object.
|
||||||
fn help_eq(&self, other: &dyn Function) -> bool;
|
fn help_eq(&self, other: &dyn LayoutFunc) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FunctionBounds for T
|
impl<T> LayoutFuncBounds for T where T: Debug + PartialEq + 'static {
|
||||||
where T: Debug + PartialEq + 'static
|
|
||||||
{
|
|
||||||
fn help_cast_as_any(&self) -> &dyn Any {
|
fn help_cast_as_any(&self) -> &dyn Any {
|
||||||
self
|
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>() {
|
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
|
||||||
self == other
|
self == other
|
||||||
} else {
|
} else {
|
||||||
@ -87,17 +96,17 @@ where T: Debug + PartialEq + 'static
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of commands requested for execution by a function.
|
/// A sequence of layouting commands.
|
||||||
pub type CommandList<'a> = Vec<Command<'a>>;
|
pub type Commands<'a> = Vec<Command<'a>>;
|
||||||
|
|
||||||
/// Commands requested for execution by functions.
|
/// Layouting commands from functions to the typesetting engine.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
LayoutTree(&'a SyntaxTree),
|
LayoutTree(&'a SyntaxTree),
|
||||||
|
|
||||||
Add(Layout),
|
Add(Layout),
|
||||||
AddMultiple(MultiLayout),
|
AddMultiple(MultiLayout),
|
||||||
AddSpacing(Size, SpacingKind, AxisKind),
|
AddSpacing(Size, SpacingKind, GenericAxisKind),
|
||||||
|
|
||||||
FinishLine,
|
FinishLine,
|
||||||
FinishRun,
|
FinishRun,
|
||||||
@ -110,13 +119,18 @@ pub enum Command<'a> {
|
|||||||
SetAxes(LayoutAxes),
|
SetAxes(LayoutAxes),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from identifiers to functions.
|
/// A map from identifiers to function parsers.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
parsers: HashMap<String, Box<ParseFunc>>,
|
parsers: HashMap<String, Box<Parser>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function which parses a function invocation into a function type.
|
/// A function which parses the source of a function into a function type which
|
||||||
type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext) -> ParseResult<Box<dyn Function>>;
|
/// implements [`LayoutFunc`].
|
||||||
|
type Parser = dyn Fn(
|
||||||
|
&FuncHeader,
|
||||||
|
Option<&str>,
|
||||||
|
ParseContext
|
||||||
|
) -> ParseResult<Box<dyn LayoutFunc>>;
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope.
|
/// Create a new empty scope.
|
||||||
@ -131,16 +145,27 @@ impl Scope {
|
|||||||
crate::library::std()
|
crate::library::std()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a function type to the scope giving it a name.
|
/// Associate the given name with a type that is parseable into a function.
|
||||||
pub fn add<F: Function + 'static>(&mut self, name: &str) {
|
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(
|
self.parsers.insert(
|
||||||
name.to_owned(),
|
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.
|
/// 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)
|
self.parsers.get(name).map(|x| &**x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ pub struct FlexLayouter {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum FlexUnit {
|
enum FlexUnit {
|
||||||
Boxed(Layout),
|
Boxed(Layout),
|
||||||
Space(Size, SpaceKind),
|
Space(Size, SpacingKind),
|
||||||
SetAxes(LayoutAxes),
|
SetAxes(LayoutAxes),
|
||||||
Break,
|
Break,
|
||||||
}
|
}
|
||||||
@ -102,11 +102,11 @@ impl FlexLayouter {
|
|||||||
self.units.push(FlexUnit::Break);
|
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))
|
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() {
|
if !self.run_is_empty() {
|
||||||
self.finish_run()?;
|
self.finish_run()?;
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ impl FlexLayouter {
|
|||||||
debug_render: false,
|
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 {
|
let remaining = self.axes.specialize(Size2D {
|
||||||
x: self.part.usable
|
x: self.part.usable
|
||||||
@ -230,7 +230,7 @@ impl FlexLayouter {
|
|||||||
|
|
||||||
while size.x > self.line.usable {
|
while size.x > self.line.usable {
|
||||||
if self.stack.space_is_last() {
|
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);
|
self.stack.finish_space(true);
|
||||||
@ -238,7 +238,7 @@ impl FlexLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let LastSpacing::Soft(space) = self.part.space {
|
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;
|
let offset = self.part.dimensions.x;
|
||||||
@ -251,8 +251,8 @@ impl FlexLayouter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_space(&mut self, space: Size, kind: SpaceKind) {
|
fn layout_space(&mut self, space: Size, kind: SpacingKind) {
|
||||||
if kind == SpaceKind::Soft {
|
if kind == SpacingKind::Soft {
|
||||||
if self.part.space != LastSpacing::Forbidden {
|
if self.part.space != LastSpacing::Forbidden {
|
||||||
self.part.space = LastSpacing::Soft(space);
|
self.part.space = LastSpacing::Soft(space);
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ impl FlexLayouter {
|
|||||||
self.part.dimensions.x += space;
|
self.part.dimensions.x += space;
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == SpaceKind::Hard {
|
if kind == SpacingKind::Hard {
|
||||||
self.part.space = LastSpacing::Forbidden;
|
self.part.space = LastSpacing::Forbidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ impl LayoutSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The axes along which the content is laid out.
|
/// 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 struct LayoutAxes {
|
||||||
pub primary: Axis,
|
pub primary: Axis,
|
||||||
pub secondary: Axis,
|
pub secondary: Axis,
|
||||||
@ -136,17 +136,66 @@ impl LayoutAxes {
|
|||||||
// at the call site, we still have this second function.
|
// at the call site, we still have this second function.
|
||||||
self.generalize(size)
|
self.generalize(size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// The two kinds of axes.
|
/// Returns the generic axis kind which is the horizontal axis.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
pub fn horizontal(&self) -> GenericAxisKind {
|
||||||
pub enum AxisKind {
|
match self.primary.is_horizontal() {
|
||||||
Primary,
|
true => GenericAxisKind::Primary,
|
||||||
Secondary,
|
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.
|
/// Directions along which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
LeftToRight,
|
LeftToRight,
|
||||||
RightToLeft,
|
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.
|
/// 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 struct LayoutAlignment {
|
||||||
pub primary: Alignment,
|
pub primary: Alignment,
|
||||||
pub secondary: Alignment,
|
pub secondary: Alignment,
|
||||||
@ -194,13 +277,24 @@ impl LayoutAlignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Where to align content.
|
/// Where to align content.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
Origin,
|
Origin,
|
||||||
Center,
|
Center,
|
||||||
End,
|
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
|
/// The specialized anchor position for an item with the given alignment in a
|
||||||
/// container with a given size along the given axis.
|
/// container with a given size along the given axis.
|
||||||
pub fn anchor(axis: Axis, size: Size, alignment: Alignment) -> Size {
|
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.
|
/// Whitespace between boxes with different interaction properties.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum SpacingKind {
|
pub enum SpacingKind {
|
||||||
/// A hard space consumes surrounding soft spaces and is always layouted.
|
/// A hard space consumes surrounding soft spaces and is always layouted.
|
||||||
Hard,
|
Hard,
|
||||||
|
@ -108,7 +108,7 @@ impl StackLayouter {
|
|||||||
// Find the first (sub-)space that fits the layout.
|
// Find the first (sub-)space that fits the layout.
|
||||||
while !self.sub.usable.fits(new_size) {
|
while !self.sub.usable.fits(new_size) {
|
||||||
if self.space_is_last() && self.space_is_empty() {
|
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);
|
self.finish_space(true);
|
||||||
|
@ -116,6 +116,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
self.classes.pop();
|
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),
|
Add(layout) => self.flex.add(layout),
|
||||||
AddMultiple(layouts) => self.flex.add_multiple(layouts),
|
AddMultiple(layouts) => self.flex.add_multiple(layouts),
|
||||||
AddSpacing(space, kind, axis) => match axis {
|
AddSpacing(space, kind, axis) => match axis {
|
||||||
AxisKind::Primary => self.flex.add_primary_space(space, kind),
|
GenericAxisKind::Primary => self.flex.add_primary_space(space, kind),
|
||||||
AxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
|
GenericAxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
FinishLine => self.flex.add_break(),
|
FinishLine => self.flex.add_break(),
|
||||||
@ -111,7 +111,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
SetTextStyle(style) => self.style.text = style,
|
SetTextStyle(style) => self.style.text = style,
|
||||||
SetPageStyle(style) => {
|
SetPageStyle(style) => {
|
||||||
if !self.ctx.top_level {
|
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;
|
self.style.page = style;
|
||||||
|
@ -1,155 +1,68 @@
|
|||||||
use crate::func::prelude::*;
|
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! {
|
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) {
|
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 {
|
for arg in args.keys() {
|
||||||
body,
|
let key = match arg.val.0.val {
|
||||||
positional_1: None,
|
"horizontal" => Key::Axis(AxisKey::Horizontal),
|
||||||
positional_2: None,
|
"vertical" => Key::Axis(AxisKey::Vertical),
|
||||||
primary: None,
|
"primary" => Key::Axis(AxisKey::Primary),
|
||||||
secondary: None,
|
"secondary" => Key::Axis(AxisKey::Secondary),
|
||||||
horizontal: None,
|
_ => pr!("unexpected argument"),
|
||||||
vertical: None,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(arg) = args.get_pos_opt::<ArgIdent>()? {
|
let value = AlignmentKey::parse(arg.val.1.val)?;
|
||||||
align.positional_1 = Some(parse_align_specifier(arg)?);
|
map.add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(arg) = args.get_pos_opt::<ArgIdent>()? {
|
Align {
|
||||||
align.positional_2 = Some(parse_align_specifier(arg)?);
|
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) {
|
layout(self, mut ctx) {
|
||||||
let mut axes = ctx.axes;
|
let axes = ctx.axes;
|
||||||
let primary_horizontal = axes.primary.is_horizontal();
|
let basic = axes.primary.is_horizontal();
|
||||||
|
|
||||||
let mut primary = false;
|
let map = self.map.dedup(|key, val| {
|
||||||
let mut secondary = false;
|
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<()> {
|
let alignment = val.generic(axes, axis)?;
|
||||||
if let Some(spec) = spec {
|
Ok((key, alignment))
|
||||||
let (axis, was_set, name) = match is_primary {
|
})?;
|
||||||
true => (&mut axes.primary, &mut primary, "primary"),
|
|
||||||
false => (&mut axes.secondary, &mut secondary, "secondary"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if *was_set {
|
map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val);
|
||||||
panic!("duplicate alignment for {} axis", name);
|
map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val);
|
||||||
}
|
|
||||||
|
|
||||||
*was_set = true;
|
match &self.body {
|
||||||
|
Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)],
|
||||||
let horizontal = axis.is_horizontal();
|
None => vec![Command::SetAlignment(ctx.alignment)],
|
||||||
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)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
Ok(match arg.val {
|
enum Key {
|
||||||
"origin" => AlignSpecifier::Origin,
|
First,
|
||||||
"center" => AlignSpecifier::Center,
|
Second,
|
||||||
"end" => AlignSpecifier::End,
|
Axis(AxisKey),
|
||||||
"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" },
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,44 @@
|
|||||||
use crate::func::prelude::*;
|
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! {
|
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) {
|
parse(args, body, ctx) {
|
||||||
let width = args.get_key_opt::<ArgSize>("width")?.map(|a| a.val);
|
let mut map = ArgMap::new();
|
||||||
let height = args.get_key_opt::<ArgSize>("height")?.map(|a| a.val);
|
|
||||||
args.done()?;
|
|
||||||
|
|
||||||
let body = parse!(required: body, ctx);
|
for arg in args.keys() {
|
||||||
Ok(Boxed {
|
let key = match arg.val.0.val {
|
||||||
body,
|
"width" | "w" => AxisKey::Horizontal,
|
||||||
width,
|
"height" | "h" => AxisKey::Vertical,
|
||||||
height,
|
"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) {
|
layout(self, mut ctx) {
|
||||||
if let Some(width) = this.width {
|
let map = self.map.dedup(|key, val| {
|
||||||
ctx.spaces[0].dimensions.x = width;
|
Ok((key.specific(ctx.axes), val))
|
||||||
}
|
});
|
||||||
if let Some(height) = this.height {
|
|
||||||
ctx.spaces[0].dimensions.y = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
//! 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!(align);
|
||||||
pub_use_mod!(spacing);
|
pub_use_mod!(boxed);
|
||||||
pub_use_mod!(style);
|
|
||||||
pub_use_mod!(page);
|
|
||||||
|
|
||||||
/// Create a scope with all standard functions.
|
/// Create a scope with all standard functions.
|
||||||
pub fn std() -> Scope {
|
pub fn std() -> Scope {
|
||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
|
|
||||||
std.add::<Boxed>("box");
|
|
||||||
|
|
||||||
std.add::<Align>("align");
|
std.add::<Align>("align");
|
||||||
|
std.add::<Boxed>("box");
|
||||||
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::<PageSize>("page.size");
|
std.add::<PageSize>("page.size");
|
||||||
std.add::<PageMargins>("page.margins");
|
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
|
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.
|
/// Create a size from an amount of millimeters.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mm(mm: f32) -> Size {
|
pub fn mm(mm: f32) -> Size {
|
||||||
Size {
|
Size { points: 2.83465 * mm }
|
||||||
points: 2.83465 * mm,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a size from an amount of centimeters.
|
/// Create a size from an amount of centimeters.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cm(cm: f32) -> Size {
|
pub fn cm(cm: f32) -> Size {
|
||||||
Size {
|
Size { points: 28.3465 * cm }
|
||||||
points: 28.3465 * cm,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a size from an amount of inches.
|
/// Create a size from an amount of inches.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn inches(inches: f32) -> Size {
|
pub fn inches(inches: f32) -> Size {
|
||||||
Size {
|
Size { points: 72.0 * inches }
|
||||||
points: 72.0 * inches,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this size into points.
|
/// Convert this size into points.
|
||||||
@ -188,12 +182,33 @@ impl SizeBox {
|
|||||||
|
|
||||||
/// Create a box with all four fields set to the same value `s`.
|
/// Create a box with all four fields set to the same value `s`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_all(s: Size) -> SizeBox {
|
pub fn with_all(value: Size) -> SizeBox {
|
||||||
SizeBox { left: s, top: s, right: s, bottom: s }
|
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.
|
/// The maximum of two sizes.
|
||||||
|
#[inline]
|
||||||
pub fn max(a: Size, b: Size) -> Size {
|
pub fn max(a: Size, b: Size) -> Size {
|
||||||
if a >= b {
|
if a >= b {
|
||||||
a
|
a
|
||||||
@ -203,6 +218,7 @@ pub fn max(a: Size, b: Size) -> Size {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum of two sizes.
|
/// The minimum of two sizes.
|
||||||
|
#[inline]
|
||||||
pub fn min(a: Size, b: Size) -> Size {
|
pub fn min(a: Size, b: Size) -> Size {
|
||||||
if a <= b {
|
if a <= b {
|
||||||
a
|
a
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
use crate::func::Function;
|
use crate::func::LayoutFunc;
|
||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
|
|
||||||
mod tokens;
|
mod tokens;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
mod span;
|
||||||
|
|
||||||
|
pub use span::{Span, Spanned};
|
||||||
pub use tokens::{tokenize, Tokens};
|
pub use tokens::{tokenize, Tokens};
|
||||||
pub use parsing::{parse, ParseContext, ParseError, ParseResult};
|
pub use parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||||
|
|
||||||
@ -90,7 +92,13 @@ pub enum Node {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuncCall {
|
pub struct FuncCall {
|
||||||
pub header: Spanned<FuncHeader>,
|
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.
|
/// Contains header information of a function invocation.
|
||||||
@ -134,12 +142,6 @@ pub enum Expression {
|
|||||||
Bool(bool),
|
Bool(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for FuncCall {
|
|
||||||
fn eq(&self, other: &FuncCall) -> bool {
|
|
||||||
(self.header == other.header) && (&self.body == &other.body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Expression {
|
impl Display for Expression {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
use Expression::*;
|
use Expression::*;
|
||||||
@ -154,72 +156,3 @@ impl Display for Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug_display!(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 unicode_xid::UnicodeXID;
|
||||||
|
|
||||||
use crate::func::{Function, Scope};
|
use crate::func::{LayoutFunc, Scope};
|
||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -120,10 +120,10 @@ impl<'s> Parser<'s> {
|
|||||||
if is_identifier(word) {
|
if is_identifier(word) {
|
||||||
Ok(Spanned::new(word.to_owned(), span))
|
Ok(Spanned::new(word.to_owned(), span))
|
||||||
} else {
|
} else {
|
||||||
perr!("invalid identifier: '{}'", word);
|
pr!("invalid identifier: '{}'", word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => perr!("expected identifier"),
|
_ => pr!("expected identifier"),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
self.skip_white();
|
self.skip_white();
|
||||||
@ -132,7 +132,7 @@ impl<'s> Parser<'s> {
|
|||||||
let args = match self.tokens.next().map(Spanned::value) {
|
let args = match self.tokens.next().map(Spanned::value) {
|
||||||
Some(Token::RightBracket) => FuncArgs::new(),
|
Some(Token::RightBracket) => FuncArgs::new(),
|
||||||
Some(Token::Colon) => self.parse_func_args()?,
|
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();
|
let end = self.tokens.string_index();
|
||||||
@ -158,7 +158,7 @@ impl<'s> Parser<'s> {
|
|||||||
match self.tokens.next().map(Spanned::value) {
|
match self.tokens.next().map(Spanned::value) {
|
||||||
Some(Token::Comma) => {},
|
Some(Token::Comma) => {},
|
||||||
Some(Token::RightBracket) => break,
|
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();
|
self.skip_white();
|
||||||
|
|
||||||
let name = token.span_map(|_| name.to_string());
|
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 val = Self::parse_expression(next)?;
|
||||||
let span = Span::merge(name.span, val.span);
|
let span = Span::merge(name.span, val.span);
|
||||||
|
|
||||||
@ -219,18 +219,19 @@ impl<'s> Parser<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => perr!("expected expression"),
|
_ => pr!("expected expression"),
|
||||||
}, token.span))
|
}, token.span))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the body of a function.
|
/// 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.
|
// Now we want to parse this function dynamically.
|
||||||
let parser = self
|
let parser = self
|
||||||
.ctx
|
.ctx
|
||||||
.scope
|
.scope
|
||||||
.get_parser(&header.name.val)
|
.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);
|
let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket);
|
||||||
|
|
||||||
@ -298,7 +299,7 @@ impl<'s> Parser<'s> {
|
|||||||
state = NewlineState::Zero;
|
state = NewlineState::Zero;
|
||||||
match token.val {
|
match token.val {
|
||||||
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
|
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
|
||||||
Token::StarSlash => perr!("unexpected end of block comment"),
|
Token::StarSlash => pr!("unexpected end of block comment"),
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -454,7 +455,7 @@ mod tests {
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::func::{CommandList, Function, Scope};
|
use crate::func::{Commands, Scope};
|
||||||
use crate::layout::{LayoutContext, LayoutResult};
|
use crate::layout::{LayoutContext, LayoutResult};
|
||||||
use funcs::*;
|
use funcs::*;
|
||||||
use Node::{Func as F, Newline as N, Space as S};
|
use Node::{Func as F, Newline as N, Space as S};
|
||||||
@ -464,37 +465,36 @@ mod tests {
|
|||||||
mod funcs {
|
mod funcs {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A testing function which just parses it's body into a syntax tree.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TreeFn(pub SyntaxTree);
|
|
||||||
|
|
||||||
function! {
|
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))) }
|
parse(args, body, ctx) {
|
||||||
layout(_, _) { Ok(vec![]) }
|
args.clear();
|
||||||
|
TreeFn {
|
||||||
|
tree: parse!(expected: body, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout() { vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for TreeFn {
|
impl PartialEq for TreeFn {
|
||||||
fn eq(&self, other: &TreeFn) -> bool {
|
fn eq(&self, other: &TreeFn) -> bool {
|
||||||
assert_tree_equal(&self.0, &other.0);
|
assert_tree_equal(&self.tree, &other.tree);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A testing function without a body.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BodylessFn;
|
|
||||||
|
|
||||||
function! {
|
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) }
|
parse(default)
|
||||||
layout(_, _) { Ok(vec![]) }
|
layout() { vec![] }
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for BodylessFn {
|
|
||||||
fn eq(&self, _: &BodylessFn) -> bool { true }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,7 +583,7 @@ mod tests {
|
|||||||
func!(@$name, Box::new(BodylessFn), FuncArgs::new())
|
func!(@$name, Box::new(BodylessFn), FuncArgs::new())
|
||||||
);
|
);
|
||||||
(name => $name:expr, body => $tree:expr $(,)*) => (
|
(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) => (
|
(@$name:expr, $body:expr, $args:expr) => (
|
||||||
FuncCall {
|
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[0].span.pair(), (13, 16));
|
||||||
assert_eq!(func.header.val.args.positional[1].span.pair(), (18, 23));
|
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!(func.body.span.pair(), (24, 37));
|
||||||
assert_eq!(body[0].span.pair(), (0, 4));
|
assert_eq!(body[0].span.pair(), (0, 4));
|
||||||
assert_eq!(body[1].span.pair(), (4, 5));
|
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…
x
Reference in New Issue
Block a user