Move arg parser into FuncArgs and create (incomplete) consistent map 🧭

This commit is contained in:
Laurenz 2019-12-05 19:48:37 +01:00
parent f72b1505be
commit 72a9631b03
22 changed files with 765 additions and 790 deletions

View File

@ -399,16 +399,16 @@ pub enum PdfExportError {
}
error_type! {
err: PdfExportError,
self: PdfExportError,
res: PdfResult,
show: f => match err {
show: f => match self {
PdfExportError::Font(err) => write!(f, "font error: {}", err),
PdfExportError::Io(err) => write!(f, "io error: {}", err),
},
source: match err {
source: match self {
PdfExportError::Font(err) => Some(err),
PdfExportError::Io(err) => Some(err),
},
from: (io::Error, PdfExportError::Io(err)),
from: (FontError, PdfExportError::Font(err)),
from: (err: io::Error, PdfExportError::Io(err)),
from: (err: FontError, PdfExportError::Font(err)),
}

View File

@ -1,215 +0,0 @@
//! 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 {
error!(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(|| error!(@"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_code)]
match &expr.val {
$wanted => Ok(Spanned::new($converted, expr.span)),
_ => error!("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(),
_ => error!(
"invalid alignment `{}` for {} axis",
format!("{:?}", self).to_lowercase(),
format!("{:?}", axis).to_lowercase()
)
})
}
/// The specific version of this alignment in the given system of layouting
/// axes.
pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey {
use AlignmentKey::*;
match (self, axis) {
(Origin, SpecificAxisKind::Horizontal) => Left,
(End, SpecificAxisKind::Horizontal) => Right,
(Origin, SpecificAxisKind::Vertical) => Top,
(End, SpecificAxisKind::Vertical) => Bottom,
_ => *self,
}
}
}
/// An argument key which identifies a margin or padding target.
///
/// A is the axis type used.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaddingKey<A> {
/// All four sides should have the specified padding.
All,
/// Both sides of the given axis should have the specified padding.
Axis(A),
/// Only the given side of the given axis should have the specified padding.
AxisAligned(A, AlignmentKey),
}

View File

@ -10,6 +10,13 @@ macro_rules! function {
function!(@meta $type | $($rest)*);
};
// Parse a tuple struct.
($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => {
$(#[$outer])*
pub struct $type($($fields)*);
function!(@meta $type | $($rest)*);
};
// Parse a struct with fields.
($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => {
$(#[$outer])*
@ -68,14 +75,17 @@ macro_rules! function {
type Meta = $meta;
fn parse(
header: &FuncHeader,
$body: Option<&str>,
args: FuncArgs,
$body: Option<Spanned<&str>>,
$ctx: ParseContext,
$metadata: Self::Meta,
) -> ParseResult<Self> where Self: Sized {
let mut $args = $crate::func::args::ArgParser::new(&header.args);
#[allow(unused_mut)]
let mut $args = args;
let val = $code;
$args.done()?;
if !$args.is_empty() {
error!(unexpected_argument);
}
Ok(val)
}
}
@ -117,7 +127,7 @@ macro_rules! parse {
(optional: $body:expr, $ctx:expr) => (
if let Some(body) = $body {
Some($crate::syntax::parse(body, $ctx)?)
Some($crate::syntax::parse(body.v, $ctx)?)
} else {
None
}
@ -125,7 +135,7 @@ macro_rules! parse {
(expected: $body:expr, $ctx:expr) => (
if let Some(body) = $body {
$crate::syntax::parse(body, $ctx)?
$crate::syntax::parse(body.v, $ctx)?
} else {
error!("expected body");
}

59
src/func/map.rs Normal file
View File

@ -0,0 +1,59 @@
//! A deduplicating map.
use std::collections::HashMap;
use std::hash::Hash;
use crate::syntax::{Spanned, ParseResult};
/// A deduplicating map type useful for storing possibly redundant arguments.
#[derive(Debug, Clone, PartialEq)]
pub struct ConsistentMap<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
ConsistentMap { map: HashMap::new() }
}
/// Add a key-value pair.
pub fn add(&mut self, key: K, value: V) -> ParseResult<()> {
self.map.insert(key, value);
// TODO
Ok(())
}
/// Add a key-value pair if the value is not `None`.
pub fn add_opt(&mut self, key: K, value: Option<V>) -> ParseResult<()> {
Ok(if let Some(value) = value {
self.add(key, value)?;
})
}
/// Add a key-spanned-value pair the value is not `None`.
pub fn add_opt_span(&mut self, key: K, value: Option<Spanned<V>>) -> ParseResult<()> {
Ok(if let Some(spanned) = value {
self.add(key, spanned.v)?;
})
}
/// Call a function with the value if the key is present.
pub fn with<F>(&self, key: K, callback: F) where F: FnOnce(&V) {
if let Some(value) = self.map.get(&key) {
callback(value);
}
}
/// Create a new consistent map where keys and values are mapped to new
/// keys and values. Returns an error if a new key is duplicate.
pub fn dedup<F, K2, V2>(&self, _f: F) -> ParseResult<ConsistentMap<K2, V2>>
where F: FnOnce(K, V) -> ParseResult<(K2, V2)>, K2: Hash + Eq {
// TODO
Ok(ConsistentMap::new())
}
/// Iterate over the (key, value) pairs.
pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> {
self.map.iter()
}
}

View File

@ -8,17 +8,12 @@ use self::prelude::*;
#[macro_use]
pub mod macros;
pub mod args;
pub mod map;
/// Useful imports for creating your own functions.
pub mod prelude {
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, ParseResult};
pub use crate::size::{Size, Size2D, SizeBox};
pub use crate::style::{PageStyle, TextStyle};
pub use super::map::ConsistentMap;
pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands};
pub use crate::layout::{
layout_tree, Layout, MultiLayout,
LayoutContext, LayoutSpace, LayoutSpaces,
@ -26,16 +21,25 @@ pub mod prelude {
LayoutAlignment, Alignment,
SpacingKind, LayoutResult,
};
pub use crate::syntax::{
parse, ParseContext, ParseResult,
SyntaxTree, FuncCall, FuncArgs, PosArg, KeyArg,
Expression, Ident, ExpressionKind,
Spanned, Span
};
pub use crate::size::{Size, Size2D, SizeBox, ScaleSize, FSize, PSize};
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
pub use Command::*;
}
/// Types representing functions that are parsed from source code.
pub trait ParseFunc {
type Meta;
type Meta: Clone;
/// Parse the header and body into this function given a context.
fn parse(
header: &FuncHeader,
body: Option<&str>,
args: FuncArgs,
body: Option<Spanned<&str>>,
ctx: ParseContext,
metadata: Self::Meta,
) -> ParseResult<Self> where Self: Sized;
@ -126,8 +130,8 @@ pub struct Scope {
/// A function which parses the source of a function into a function type which
/// implements [`LayoutFunc`].
type Parser = dyn Fn(
&FuncHeader,
Option<&str>,
FuncArgs,
Option<Spanned<&str>>,
ParseContext
) -> ParseResult<Box<dyn LayoutFunc>>;
@ -153,11 +157,11 @@ impl Scope {
/// 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 {
where F: ParseFunc<Meta=T> + LayoutFunc + 'static, T: 'static + Clone {
self.parsers.insert(
name.to_owned(),
Box::new(|h, b, c| {
F::parse(h, b, c, metadata)
Box::new(move |a, b, c| {
F::parse(a, b, c, metadata.clone())
.map(|f| Box::new(f) as Box<dyn LayoutFunc>)
})
);

View File

@ -50,7 +50,7 @@ impl PartialLine {
usable,
content: vec![],
dimensions: Size2D::zero(),
space: LastSpacing::Forbidden,
space: LastSpacing::Hard,
}
}
}
@ -72,7 +72,7 @@ impl FlexLayouter {
let stack = StackLayouter::new(StackContext {
spaces: ctx.spaces,
axes: ctx.axes,
expand: ctx.expand,
alignment: ctx.alignment,
});
let usable = stack.primary_usable();
@ -167,130 +167,27 @@ impl FlexLayouter {
}
fn finish_line(&mut self) -> LayoutResult<Size2D> {
self.finish_partial_line();
if self.axes.primary.needs_expansion() {
self.line.combined_dimensions.x = self.line.usable;
}
self.stack.add(Layout {
dimensions: self.axes.specialize(self.line.combined_dimensions),
actions: self.line.actions.to_vec(),
debug_render: false,
})?;
self.stack.add_spacing(self.flex_spacing, SpacingKind::Independent);
let remaining = self.axes.specialize(Size2D {
x: self.part.usable
- self.part.dimensions.x
- self.part.space.soft_or_zero(),
y: self.line.combined_dimensions.y,
});
self.start_line();
Ok(remaining)
unimplemented!()
}
fn start_line(&mut self) {
let usable = self.stack.primary_usable();
self.line = FlexLine::new(usable);
self.part = PartialLine::new(usable);
unimplemented!()
}
#[allow(dead_code)]
fn finish_partial_line(&mut self) {
let factor = self.axes.primary.axis.factor();
let anchor =
self.axes.primary.anchor(self.line.usable)
- self.axes.primary.anchor(self.part.dimensions.x);
for (offset, layout) in self.part.content.drain(..) {
let pos = self.axes.specialize(Size2D::with_x(anchor + factor * offset));
self.line.actions.add_layout(pos, layout);
}
self.line.combined_dimensions.x = match self.axes.primary.alignment {
Alignment::Origin => self.part.dimensions.x,
Alignment::Center => self.part.usable / 2 + self.part.dimensions.x / 2,
Alignment::End => self.part.usable,
};
self.line.combined_dimensions.y.max_eq(self.part.dimensions.y);
unimplemented!()
}
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
let size = self.axes.generalize(boxed.dimensions);
let new_dimension = self.part.dimensions.x
+ size.x
+ self.part.space.soft_or_zero();
if new_dimension > self.part.usable {
self.finish_line()?;
while size.x > self.line.usable {
if self.stack.space_is_last() {
error!("box of size {} does not fit into line of size {}",
size.x, self.line.usable);
}
self.stack.finish_space(true);
}
}
if let LastSpacing::Soft(space) = self.part.space {
self.layout_space(space, SpacingKind::Hard);
}
let offset = self.part.dimensions.x;
self.part.content.push((offset, boxed));
self.part.dimensions.x += size.x;
self.part.dimensions.y.max_eq(size.y);
self.part.space = LastSpacing::Allowed;
Ok(())
fn layout_box(&mut self, _boxed: Layout) -> LayoutResult<()> {
unimplemented!()
}
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);
}
} else {
if self.part.dimensions.x + space > self.part.usable {
self.part.dimensions.x = self.part.usable;
} else {
self.part.dimensions.x += space;
}
if kind == SpacingKind::Hard {
self.part.space = LastSpacing::Forbidden;
}
}
fn layout_space(&mut self, _space: Size, _kind: SpacingKind) {
unimplemented!()
}
fn layout_set_axes(&mut self, axes: LayoutAxes) {
if axes.primary != self.axes.primary {
self.finish_partial_line();
let extent = self.line.combined_dimensions.x;
let usable = self.line.usable;
let new_usable = match axes.primary.alignment {
Alignment::Origin if extent == Size::zero() => usable,
Alignment::Center if extent < usable / 2 => usable - 2 * extent,
Alignment::End => usable - extent,
_ => Size::zero(),
};
self.part = PartialLine::new(new_usable);
}
if axes.secondary != self.axes.secondary {
self.stack.set_axes(axes);
}
self.axes = axes;
fn layout_set_axes(&mut self, _axes: LayoutAxes) {
unimplemented!()
}
}

View File

@ -10,7 +10,7 @@ use crate::TypesetResult;
use crate::func::Command;
use crate::size::{Size, Size2D, SizeBox};
use crate::style::{LayoutStyle, TextStyle};
use crate::syntax::{FuncCall, Node, SyntaxTree};
use crate::syntax::{Node, SyntaxTree, FuncCall};
mod actions;
mod tree;
@ -137,6 +137,19 @@ impl LayoutAxes {
self.generalize(size)
}
/// Return the specified generic axis.
pub fn get_generic(&self, axis: GenericAxisKind) -> Axis {
match axis {
GenericAxisKind::Primary => self.primary,
GenericAxisKind::Secondary => self.secondary,
}
}
/// Return the specified specific axis.
pub fn get_specific(&self, axis: SpecificAxisKind) -> Axis {
self.get_generic(axis.generic(*self))
}
/// Returns the generic axis kind which is the horizontal axis.
pub fn horizontal(&self) -> GenericAxisKind {
match self.primary.is_horizontal() {
@ -237,6 +250,14 @@ pub enum GenericAxisKind {
}
impl GenericAxisKind {
/// The specific version of this axis in the given system of axes.
pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind {
match self {
GenericAxisKind::Primary => axes.primary(),
GenericAxisKind::Secondary => axes.secondary(),
}
}
/// The other axis.
pub fn inv(&self) -> GenericAxisKind {
match self {
@ -254,6 +275,14 @@ pub enum SpecificAxisKind {
}
impl SpecificAxisKind {
/// The generic version of this axis in the given system of axes.
pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind {
match self {
SpecificAxisKind::Horizontal => axes.horizontal(),
SpecificAxisKind::Vertical => axes.vertical(),
}
}
/// The other axis.
pub fn inv(&self) -> SpecificAxisKind {
match self {
@ -330,6 +359,7 @@ enum LastSpacing {
}
impl LastSpacing {
#[allow(dead_code)]
fn soft_or_zero(&self) -> Size {
match self {
LastSpacing::Soft(space, _) => *space,
@ -339,7 +369,7 @@ impl LastSpacing {
}
/// Layout components that can be serialized.
trait Serialize {
pub trait Serialize {
/// Serialize the data structure into an output writable.
fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()>;
}

View File

@ -244,51 +244,12 @@ impl StackLayouter {
self.layouts
}
pub fn finish_space(&mut self, hard: bool) {
self.finish_subspace();
let space = self.ctx.spaces[self.space.index];
self.layouts.push(Layout {
dimensions: match self.ctx.expand {
true => space.dimensions,
false => self.space.combined_dimensions.padded(space.padding),
},
baseline: None,
alignment: self.ctx.alignment,
actions: actions.to_vec(),
});
self.start_space(self.next_space(), hard);
pub fn finish_space(&mut self, _hard: bool) {
unimplemented!()
}
fn finish_subspace(&mut self) {
let factor = self.ctx.axes.secondary.axis.factor();
let anchor =
self.ctx.axes.anchor(self.sub.usable)
- self.ctx.axes.anchor(Size2D::with_y(self.sub.dimensions.y));
for (offset, layout_anchor, layout) in self.sub.boxes.drain(..) {
let pos = self.sub.origin
+ self.ctx.axes.specialize(
anchor + Size2D::new(-layout_anchor, factor * offset)
);
self.space.actions.add_layout(pos, layout);
}
if self.ctx.axes.primary.needs_expansion() {
self.sub.dimensions.x = self.sub.usable.x;
}
if self.ctx.axes.secondary.needs_expansion() {
self.sub.dimensions.y = self.sub.usable.y;
}
let space = self.ctx.spaces[self.space.index];
let origin = self.sub.origin;
let dimensions = self.ctx.axes.specialize(self.sub.dimensions);
self.space.combined_dimensions.max_eq(origin - space.start() + dimensions);
unimplemented!()
}
/// Start a new space with the given index.
@ -304,17 +265,7 @@ impl StackLayouter {
/// The remaining sub
fn remaining_subspace(&self) -> (Size2D, Size2D) {
let new_origin = self.sub.origin + match self.ctx.axes.secondary.axis.is_positive() {
true => self.ctx.axes.specialize(Size2D::with_y(self.sub.dimensions.y)),
false => Size2D::zero(),
};
let new_usable = self.ctx.axes.specialize(Size2D {
x: self.sub.usable.x,
y: self.sub.usable.y - self.sub.dimensions.y - self.sub.space.soft_or_zero(),
});
(new_origin, new_usable)
unimplemented!()
}
fn next_space(&self) -> usize {

View File

@ -1,5 +1,5 @@
use super::*;
use smallvec::smallvec;
use super::*;
pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
@ -31,7 +31,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
for node in &tree.nodes {
match &node.val {
match &node.v {
Node::Text(text) => self.layout_text(text)?,
Node::Space => self.layout_space(),
@ -75,7 +75,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let spaces = self.flex.remaining();
let commands = func.body.val.layout(LayoutContext {
let commands = func.call.layout(LayoutContext {
loader: self.ctx.loader,
style: &self.style,
top_level: false,

View File

@ -135,14 +135,14 @@ impl TypesetError {
}
error_type! {
err: TypesetError,
self: TypesetError,
show: f => {
write!(f, "{}", err.message)?;
if let Some(span) = err.span {
write!(f, "{}", self.message)?;
if let Some(span) = self.span {
write!(f, " at {}", span)?;
}
Ok(())
},
from: (std::io::Error, TypesetError::with_message(err.to_string())),
from: (FontError, TypesetError::with_message(err.to_string())),
from: (err: std::io::Error, TypesetError::with_message(err.to_string())),
from: (err: FontError, TypesetError::with_message(err.to_string())),
}

View File

@ -1,29 +1,25 @@
use crate::func::prelude::*;
use super::keys::*;
function! {
/// `align`: Aligns content along the layouting axes.
#[derive(Debug, PartialEq)]
pub struct Align {
body: Option<SyntaxTree>,
map: ArgMap<Key, AlignmentKey>,
map: ConsistentMap<Key, AlignmentKey>,
}
parse(args, 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 map = ConsistentMap::new();
map.add_opt_span(Key::First, args.get_pos_opt::<AlignmentKey>()?)?;
map.add_opt_span(Key::Second, args.get_pos_opt::<AlignmentKey>()?)?;
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),
_ => error!(unexpected_argument),
};
let axis = AxisKey::from_ident(&arg.v.key)?;
let value = AlignmentKey::from_expr(arg.v.value)?;
let value = AlignmentKey::parse(arg.val.1.val)?;
map.add(key, value);
map.add(Key::Axis(axis), value)?;
}
Align {
@ -34,24 +30,23 @@ function! {
layout(self, mut ctx) {
let axes = ctx.axes;
let basic = axes.primary.is_horizontal();
let map = self.map.dedup(|key, val| {
let map = self.map.dedup(|key, alignment| {
let axis = match key {
Key::First => val.axis(axes, GenericAxisKind::Primary),
Key::Second => val.axis(axes, GenericAxisKind::Secondary),
Key::First => alignment.axis(axes, GenericAxisKind::Primary),
Key::Second => alignment.axis(axes, GenericAxisKind::Secondary),
Key::Axis(AxisKey::Primary) => GenericAxisKind::Primary,
Key::Axis(AxisKey::Secondary) => GenericAxisKind::Secondary,
Key::Axis(AxisKey::Horizontal) => axes.horizontal(),
Key::Axis(AxisKey::Vertical) => axes.vertical(),
};
let alignment = val.generic(axes, axis)?;
Ok((key, alignment))
let alignment = alignment.generic(axes, axis)?;
Ok((axis, alignment))
})?;
map.with(GenericAxisKind::Primary, |val| ctx.alignment.primary = val);
map.with(GenericAxisKind::Secondary, |val| ctx.alignment.secondary = val);
map.with(GenericAxisKind::Primary, |&val| ctx.alignment.primary = val);
map.with(GenericAxisKind::Secondary, |&val| ctx.alignment.secondary = val);
match &self.body {
Some(body) => vec![AddMultiple(layout_tree(&body, ctx)?)],

View File

@ -1,18 +1,19 @@
use crate::func::prelude::*;
use super::keys::*;
function! {
/// `box`: Layouts content into a box.
#[derive(Debug, PartialEq)]
pub struct Boxed {
body: SyntaxTree,
map: ArgMap<AxisKey, Size>,
map: ConsistentMap<AxisKey, Size>,
}
parse(args, body, ctx) {
let mut map = ArgMap::new();
let mut map = ConsistentMap::new();
for arg in args.keys() {
let key = match arg.val.0.val {
let key = match arg.v.key.v.0.as_str() {
"width" | "w" => AxisKey::Horizontal,
"height" | "h" => AxisKey::Vertical,
"primary-size" => AxisKey::Primary,
@ -20,8 +21,8 @@ function! {
_ => error!(unexpected_argument),
};
let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?;
map.add(key, size);
let size = Size::from_expr(arg.v.value)?;
map.add(key, size)?;
}
Boxed {
@ -31,13 +32,11 @@ function! {
}
layout(self, mut ctx) {
let map = self.map.dedup(|key, val| {
Ok((key.specific(ctx.axes), val))
});
let map = self.map.dedup(|key, val| Ok((key.specific(ctx.axes), val)))?;
let mut dimensions = &mut ctx.spaces[0].dimensions;
map.with(AxisKey::Horizontal, |val| dimensions.x = val);
map.with(AxisKey::Vertical, |val| dimensions.y = val);
let dimensions = &mut ctx.spaces[0].dimensions;
map.with(SpecificAxisKind::Horizontal, |&val| dimensions.x = val);
map.with(SpecificAxisKind::Vertical, |&val| dimensions.y = val);
vec![AddMultiple(layout_tree(&self.body, ctx)?)]
}

172
src/library/keys.rs Normal file
View File

@ -0,0 +1,172 @@
use crate::func::prelude::*;
macro_rules! kind {
($type:ty, $name:expr, $($patterns:tt)*) => {
impl $type {
/// Parse this key from an identifier.
pub fn from_ident(ident: &Spanned<Ident>) -> ParseResult<Self> {
Ok(match ident.v.0.as_str() {
$($patterns)*
_ => error!("expected {}", <Self as ExpressionKind>::NAME),
})
}
}
impl ExpressionKind for $type {
const NAME: &'static str = $name;
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> {
if let Expression::Ident(ident) = expr.v {
Self::from_ident(&Spanned::new(ident, expr.span))
} else {
error!("expected {}", Self::NAME);
}
}
}
};
}
/// 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 {
AxisKey::Primary => GenericAxisKind::Primary,
AxisKey::Secondary => GenericAxisKind::Secondary,
AxisKey::Vertical => axes.vertical(),
AxisKey::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 {
AxisKey::Primary => axes.primary(),
AxisKey::Secondary => axes.secondary(),
AxisKey::Vertical => SpecificAxisKind::Vertical,
AxisKey::Horizontal => SpecificAxisKind::Horizontal,
}
}
}
kind!(AxisKey, "axis",
"horizontal" => AxisKey::Horizontal,
"vertical" => AxisKey::Vertical,
"primary" => AxisKey::Primary,
"secondary" => AxisKey::Secondary,
);
/// 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(),
_ => error!(
"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::*;
use SpecificAxisKind::*;
let positive = axes.get_specific(axis).is_positive();
match (self, axis, positive) {
(Origin, Horizontal, true) | (End, Horizontal, false) => Left,
(End, Horizontal, true) | (Origin, Horizontal, false) => Right,
(Origin, Vertical, true) | (End, Vertical, false) => Top,
(End, Vertical, true) | (Origin, Vertical, false) => Bottom,
_ => *self,
}
}
}
kind!(AlignmentKey, "alignment",
"left" => AlignmentKey::Left,
"top" => AlignmentKey::Top,
"right" => AlignmentKey::Right,
"bottom" => AlignmentKey::Bottom,
"origin" => AlignmentKey::Origin,
"center" => AlignmentKey::Center,
"end" => AlignmentKey::End,
);
/// 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),
}
kind!(PaddingKey<AxisKey>, "axis or anchor",
"horizontal" => PaddingKey::Axis(AxisKey::Horizontal),
"vertical" => PaddingKey::Axis(AxisKey::Vertical),
"primary" => PaddingKey::Axis(AxisKey::Primary),
"secondary" => PaddingKey::Axis(AxisKey::Secondary),
"left" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Left),
"right" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Right),
"top" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Top),
"bottom" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Bottom),
"primary-origin" => PaddingKey::AxisAligned(AxisKey::Primary, AlignmentKey::Origin),
"primary-end" => PaddingKey::AxisAligned(AxisKey::Primary, AlignmentKey::End),
"secondary-origin" => PaddingKey::AxisAligned(AxisKey::Secondary, AlignmentKey::Origin),
"secondary-end" => PaddingKey::AxisAligned(AxisKey::Secondary, AlignmentKey::End),
"horizontal-origin" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::Origin),
"horizontal-end" => PaddingKey::AxisAligned(AxisKey::Horizontal, AlignmentKey::End),
"vertical-origin" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::Origin),
"vertical-end" => PaddingKey::AxisAligned(AxisKey::Vertical, AlignmentKey::End),
);

View File

@ -6,6 +6,9 @@ use toddle::query::FontClass;
pub_use_mod!(align);
pub_use_mod!(boxed);
mod keys;
use keys::*;
/// Create a scope with all standard functions.
pub fn std() -> Scope {
let mut std = Scope::new();
@ -33,7 +36,7 @@ pub fn std() -> Scope {
("italic", FontClass::Italic),
("mono", FontClass::Monospace),
] {
std.add_with_metadata::<StyleChange, FontClass>(name, *class);
std.add_with_metadata::<StyleChange, FontClass>(name, class.clone());
}
std
@ -80,8 +83,8 @@ function! {
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),
width: args.get_key_opt::<Size>("width")?.map(|s| s.v),
height: args.get_key_opt::<Size>("height")?.map(|s| s.v),
}
}
@ -97,42 +100,18 @@ function! {
/// `page.margins`: Set the margins of pages.
#[derive(Debug, PartialEq)]
pub struct PageMargins {
map: ArgMap<PaddingKey, Size>,
map: ConsistentMap<PaddingKey<AxisKey>, Size>,
}
parse(args, body) {
use PaddingKey::*;
use AlignmentKey::*;
let mut map = ArgMap::new();
map.add_opt(All, args.get_pos_opt::<ArgSize>()?);
let mut map = ConsistentMap::new();
map.add_opt_span(PaddingKey::All, args.get_pos_opt::<Size>()?)?;
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),
let key = PaddingKey::from_ident(&arg.v.key)?;
let size = Size::from_expr(arg.v.value)?;
"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),
_ => error!(unexpected_argument),
};
let size = ArgParser::convert::<ArgSize>(arg.val.1.val)?;
map.add(key, size);
map.add(key, size)?;
}
parse!(forbidden: body);
@ -144,25 +123,25 @@ function! {
let axes = ctx.axes;
let map = self.map.dedup(|key, val| {
match key {
Ok((match key {
All => All,
Axis(axis) => Axis(axis.specific(axes)),
AxisAligned(axis, alignment) => {
let axis = axis.specific(axes);
AxisAligned(axis, alignment.specific(axes, axis))
}
}
});
}, val))
})?;
let style = ctx.style.page;
let mut 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));
map.with(All, |&val| padding.set_all(val));
map.with(Axis(SpecificAxisKind::Horizontal), |&val| padding.set_horizontal(val));
map.with(Axis(SpecificAxisKind::Vertical), |&val| padding.set_vertical(val));
for (key, val) in map.iter() {
if let AxisAligned(axis, alignment) = key {
for (key, &val) in map.iter() {
if let AxisAligned(_, alignment) = key {
match alignment {
AlignmentKey::Left => padding.left = val,
AlignmentKey::Right => padding.right = val,
@ -182,7 +161,7 @@ function! {
#[derive(Debug, PartialEq)]
pub struct Spacing {
axis: AxisKey,
spacing: SpacingValue,
spacing: FSize,
}
type Meta = Option<AxisKey>;
@ -191,19 +170,14 @@ function! {
let spacing = if let Some(axis) = meta {
Spacing {
axis,
spacing: SpacingValue::from_expr(args.get_pos::<ArgExpr>()?)?,
spacing: FSize::from_expr(args.get_pos::<Expression>()?)?,
}
} 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,
_ => error!(unexpected_argument),
};
let axis = AxisKey::from_ident(&arg.v.key)
.map_err(|_| error!(@unexpected_argument))?;
let spacing = SpacingValue::from_expr(arg.val.1.val)?;
let spacing = FSize::from_expr(arg.v.value)?;
Spacing { axis, spacing }
} else {
error!("expected axis and expression")
@ -217,30 +191,14 @@ function! {
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,
FSize::Absolute(size) => size,
FSize::Scaled(scale) => scale * 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),
_ => error!("invalid spacing: expected size or number"),
})
}
}
function! {
/// Sets text with a different style.
#[derive(Debug, PartialEq)]
@ -260,7 +218,7 @@ function! {
layout(self, ctx) {
let mut style = ctx.style.text.clone();
style.toggle_class(self.class);
style.toggle_class(self.class.clone());
match &self.body {
Some(body) => vec![

View File

@ -3,37 +3,33 @@
/// Create trait implementations for an error type.
macro_rules! error_type {
(
$var:ident: $err:ident,
$this:ident: $type:ident,
$(res: $res:ident,)*
show: $f:ident => $show:expr,
$(source: $source:expr,)*
$(from: ($from:path, $conv:expr),)*
$(from: ($err:ident: $from:path, $conv:expr),)*
) => {
// Possibly create a result type.
$(type $res<T> = std::result::Result<T, $err>;)*
$(type $res<T> = std::result::Result<T, $type>;)*
impl std::fmt::Display for $err {
fn fmt(&self, $f: &mut std::fmt::Formatter) -> std::fmt::Result {
#[allow(unused)]
let $var = self;
impl std::fmt::Display for $type {
fn fmt(&$this, $f: &mut std::fmt::Formatter) -> std::fmt::Result {
$show
}
}
debug_display!($err);
debug_display!($type);
impl std::error::Error for $err {
impl std::error::Error for $type {
// The source method is only generated if an implementation was given.
$(fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
#[allow(unused)]
let $var = self;
$(fn source(&$this) -> Option<&(dyn std::error::Error + 'static)> {
$source
})*
}
// Create any number of from implementations.
$(impl From<$from> for $err {
fn from($var: $from) -> $err {
$(impl From<$from> for $type {
fn from($err: $from) -> $type {
$conv
}
})*

View File

@ -35,6 +35,19 @@ pub struct SizeBox {
pub bottom: Size,
}
/// A size or scale.
#[derive(Copy, Clone, PartialEq)]
pub enum ScaleSize {
Absolute(Size),
Scaled(f32),
}
/// A size that is possibly scaled by the font size.
pub type FSize = ScaleSize;
/// A size that is possibly scaled by the size of the padded parent container.
pub type PSize = ScaleSize;
impl Size {
/// Create a zeroed size.
#[inline]
@ -241,7 +254,7 @@ debug_display!(Size);
pub struct ParseSizeError;
error_type! {
err: ParseSizeError,
self: ParseSizeError,
show: f => write!(f, "failed to parse size"),
}
@ -469,3 +482,16 @@ impl Display for SizeBox {
}
debug_display!(SizeBox);
//------------------------------------------------------------------------------------------------//
impl Display for ScaleSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ScaleSize::Absolute(size) => write!(f, "{}", size),
ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
}
}
}
debug_display!(ScaleSize);

View File

@ -1,9 +1,10 @@
//! Tokenization and parsing of source code.
use std::fmt::{self, Display, Formatter};
use unicode_xid::UnicodeXID;
use crate::func::LayoutFunc;
use crate::size::Size;
use crate::size::{Size, ScaleSize};
mod tokens;
#[macro_use]
@ -88,54 +89,133 @@ pub enum Node {
Func(FuncCall),
}
/// A function invocation, consisting of header and a dynamically parsed body.
/// An invocation of a function.
#[derive(Debug)]
pub struct FuncCall {
pub header: Spanned<FuncHeader>,
pub body: Spanned<Box<dyn LayoutFunc>>,
pub call: Box<dyn LayoutFunc>,
}
impl PartialEq for FuncCall {
fn eq(&self, other: &FuncCall) -> bool {
(self.header == other.header) && (&self.body == &other.body)
&self.call == &other.call
}
}
/// Contains header information of a function invocation.
#[derive(Debug, Clone, PartialEq)]
pub struct FuncHeader {
pub name: Spanned<String>,
pub args: FuncArgs,
}
/// The arguments passed to a function.
#[derive(Debug, Clone, PartialEq)]
pub struct FuncArgs {
pub positional: Vec<Spanned<Expression>>,
pub keyword: Vec<Spanned<(Spanned<String>, Spanned<Expression>)>>
pub pos: Vec<Spanned<PosArg>>,
pub key: Vec<Spanned<KeyArg>>,
}
impl FuncArgs {
/// Create an empty collection of arguments.
fn new() -> FuncArgs {
pub fn new() -> FuncArgs {
FuncArgs {
positional: vec![],
keyword: vec![],
pos: vec![],
key: vec![],
}
}
/// Add a positional argument.
pub fn add_pos(&mut self, arg: Spanned<PosArg>) {
self.pos.push(arg);
}
/// Add a keyword argument.
pub fn add_key(&mut self, arg: Spanned<KeyArg>) {
self.key.push(arg);
}
/// Force-extract the first positional argument.
pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<Spanned<E>> {
expect(self.get_pos_opt())
}
/// Extract the first positional argument.
pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<Spanned<E>>> {
Ok(if !self.pos.is_empty() {
let spanned = self.pos.remove(0);
let span = spanned.span;
Some(Spanned::new(E::from_expr(spanned)?, span))
} else {
None
})
}
/// Iterator over positional arguments.
pub fn pos(&mut self) -> std::vec::IntoIter<Spanned<PosArg>> {
let vec = std::mem::replace(&mut self.pos, vec![]);
vec.into_iter()
}
/// Force-extract a keyword argument.
pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Spanned<E>> {
expect(self.get_key_opt(name))
}
/// Extract a keyword argument.
pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<Spanned<E>>> {
Ok(if let Some(index) = self.key.iter().position(|arg| arg.v.key.v.0 == name) {
let Spanned { v, span } = self.key.swap_remove(index);
Some(Spanned::new(E::from_expr(v.value)?, span))
} else {
None
})
}
/// Extract any keyword argument.
pub fn get_key_next(&mut self) -> Option<Spanned<KeyArg>> {
self.key.pop()
}
/// Iterator over all keyword arguments.
pub fn keys(&mut self) -> std::vec::IntoIter<Spanned<KeyArg>> {
let vec = std::mem::replace(&mut self.key, vec![]);
vec.into_iter()
}
/// Clear the argument lists.
pub fn clear(&mut self) {
self.pos.clear();
self.key.clear();
}
/// Whether both the positional and keyword argument lists are empty.
pub fn is_empty(&self) -> bool {
self.pos.is_empty() && self.key.is_empty()
}
}
/// One argument passed to a function.
fn expect<E: ExpressionKind>(opt: ParseResult<Option<Spanned<E>>>) -> ParseResult<Spanned<E>> {
match opt {
Ok(Some(spanned)) => Ok(spanned),
Ok(None) => error!("expected {}", E::NAME),
Err(e) => Err(e),
}
}
/// A positional argument passed to a function.
pub type PosArg = Expression;
/// A keyword argument passed to a function.
#[derive(Debug, Clone, PartialEq)]
pub enum FuncArg {
Positional(Spanned<Expression>),
Keyword(Spanned<(Spanned<String>, Spanned<Expression>)>),
pub struct KeyArg {
pub key: Spanned<Ident>,
pub value: Spanned<Expression>,
}
/// Either a positional or keyword argument.
#[derive(Debug, Clone, PartialEq)]
pub enum DynArg {
Pos(Spanned<PosArg>),
Key(Spanned<KeyArg>),
}
/// An argument or return value.
#[derive(Clone, PartialEq)]
pub enum Expression {
Ident(String),
Ident(Ident),
Str(String),
Num(f64),
Size(Size),
@ -146,7 +226,7 @@ impl Display for Expression {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Expression::*;
match self {
Ident(s) => write!(f, "{}", s),
Ident(i) => write!(f, "{}", i),
Str(s) => write!(f, "{:?}", s),
Num(n) => write!(f, "{}", n),
Size(s) => write!(f, "{}", s),
@ -156,3 +236,81 @@ impl Display for Expression {
}
debug_display!(Expression);
/// An identifier.
#[derive(Clone, PartialEq)]
pub struct Ident(pub String);
impl Ident {
fn new(string: String) -> ParseResult<Ident> {
if is_identifier(&string) {
Ok(Ident(string))
} else {
error!("invalid identifier: `{}`", string);
}
}
}
impl Display for Ident {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
debug_display!(Ident);
/// Whether this word is a valid unicode identifier.
fn is_identifier(string: &str) -> bool {
let mut chars = string.chars();
match chars.next() {
Some('-') => (),
Some(c) if UnicodeXID::is_xid_start(c) => (),
_ => return false,
}
while let Some(c) = chars.next() {
match c {
'.' | '-' => (),
c if UnicodeXID::is_xid_continue(c) => (),
_ => return false,
}
}
true
}
/// Kinds of expressions.
pub trait ExpressionKind: Sized {
const NAME: &'static str;
/// Create from expression.
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self>;
}
macro_rules! kind {
($type:ty, $name:expr, $($patterns:tt)*) => {
impl ExpressionKind for $type {
const NAME: &'static str = $name;
fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> {
#[allow(unreachable_patterns)]
Ok(match expr.v {
$($patterns)*,
_ => error!("expected {}", Self::NAME),
})
}
}
};
}
kind!(Expression, "expression", e => e);
kind!(Ident, "identifier", Expression::Ident(ident) => ident);
kind!(String, "string", Expression::Str(string) => string);
kind!(f64, "number", Expression::Num(num) => num);
kind!(bool, "boolean", Expression::Bool(boolean) => boolean);
kind!(Size, "size", Expression::Size(size) => size);
kind!(ScaleSize, "number or size",
Expression::Size(size) => ScaleSize::Absolute(size),
Expression::Num(scale) => ScaleSize::Scaled(scale as f32)
);

View File

@ -1,7 +1,5 @@
//! Parsing of token streams into syntax trees.
use unicode_xid::UnicodeXID;
use crate::TypesetResult;
use crate::func::{LayoutFunc, Scope};
use crate::size::Size;
@ -67,7 +65,7 @@ impl<'s> Parser<'s> {
use Token::*;
if let Some(token) = self.tokens.peek() {
match token.val {
match token.v {
// Functions.
LeftBracket => self.parse_func()?,
RightBracket => error!("unexpected closing bracket"),
@ -80,8 +78,8 @@ impl<'s> Parser<'s> {
// Normal text.
Text(word) => self.append_consumed(Node::Text(word.to_owned()), token.span),
// The rest is handled elsewhere or should not happen, because `Tokens` does not
// yield these in a body.
// The rest is handled elsewhere or should not happen, because
// the tokenizer does not yield these in a body.
Space | Newline | LineComment(_) | BlockComment(_) |
Colon | Equals | Comma | Quoted(_) | StarSlash
=> panic!("parse_body_part: unexpected token: {:?}", token),
@ -95,39 +93,10 @@ impl<'s> Parser<'s> {
fn parse_func(&mut self) -> ParseResult<()> {
// This should only be called if a left bracket was seen.
let token = self.tokens.next().expect("parse_func: expected token");
assert!(token.val == Token::LeftBracket);
assert!(token.v == Token::LeftBracket);
let mut span = token.span;
let header = self.parse_func_header()?;
let body = self.parse_func_body(&header.val)?;
span.end = self.tokens.string_index();
// Finally this function is parsed to the end.
self.append(Node::Func(FuncCall { header, body }), span);
Ok(())
}
/// Parse a function header.
fn parse_func_header(&mut self) -> ParseResult<Spanned<FuncHeader>> {
let start = self.tokens.string_index() - 1;
self.skip_white();
let name = match self.tokens.next() {
Some(Spanned { val: Token::Text(word), span }) => {
if is_identifier(word) {
Ok(Spanned::new(word.to_owned(), span))
} else {
error!("invalid identifier: `{}`", word);
}
}
_ => error!("expected identifier"),
}?;
self.skip_white();
let name = self.parse_func_name()?;
// Check for arguments
let args = match self.tokens.next().map(Spanned::value) {
@ -136,23 +105,43 @@ impl<'s> Parser<'s> {
_ => error!("expected arguments or closing bracket"),
};
let end = self.tokens.string_index();
let call = self.parse_func_call(name, args)?;
span.end = self.tokens.string_index();
// Store the header information of the function invocation.
Ok(Spanned::new(FuncHeader { name, args }, Span::new(start, end)))
// Finally this function is parsed to the end.
self.append(Node::Func(FuncCall { call }), span);
Ok(())
}
/// Parse a function header.
fn parse_func_name(&mut self) -> ParseResult<Spanned<Ident>> {
self.skip_white();
let name = match self.tokens.next() {
Some(Spanned { v: Token::Text(word), span }) => {
let ident = Ident::new(word.to_string())?;
Spanned::new(ident, span)
}
_ => error!("expected identifier"),
};
self.skip_white();
Ok(name)
}
/// Parse the arguments to a function.
fn parse_func_args(&mut self) -> ParseResult<FuncArgs> {
let mut positional = Vec::new();
let mut keyword = Vec::new();
let mut pos = Vec::new();
let mut key = Vec::new();
loop {
self.skip_white();
match self.parse_func_arg()? {
Some(FuncArg::Positional(arg)) => positional.push(arg),
Some(FuncArg::Keyword(arg)) => keyword.push(arg),
Some(DynArg::Pos(arg)) => pos.push(arg),
Some(DynArg::Key(arg)) => key.push(arg),
_ => {},
}
@ -163,17 +152,17 @@ impl<'s> Parser<'s> {
}
}
Ok(FuncArgs { positional, keyword })
Ok(FuncArgs { pos, key })
}
/// Parse one argument to a function.
fn parse_func_arg(&mut self) -> ParseResult<Option<FuncArg>> {
fn parse_func_arg(&mut self) -> ParseResult<Option<DynArg>> {
let token = match self.tokens.peek() {
Some(token) => token,
None => return Ok(None),
};
Ok(match token.val {
Ok(match token.v {
Token::Text(name) => {
self.advance();
self.skip_white();
@ -183,55 +172,41 @@ impl<'s> Parser<'s> {
self.advance();
self.skip_white();
let name = token.span_map(|_| name.to_string());
let next = self.tokens.next().ok_or_else(|| error!(@"expected expression"))?;
let val = Self::parse_expression(next)?;
let span = Span::merge(name.span, val.span);
let name = Ident::new(name.to_string())?;
let key = Spanned::new(name, token.span);
FuncArg::Keyword(Spanned::new((name, val), span))
let next = self.tokens.next()
.ok_or_else(|| error!(@"expected expression"))?;
let value = Self::parse_expression(next)?;
let span = Span::merge(key.span, value.span);
let arg = KeyArg { key, value };
DynArg::Key(Spanned::new(arg, span))
}
_ => FuncArg::Positional(Self::parse_expression(token)?),
_ => DynArg::Pos(Self::parse_expression(token)?),
})
}
Token::Quoted(_) => {
self.advance();
Some(FuncArg::Positional(Self::parse_expression(token)?))
Some(DynArg::Pos(Self::parse_expression(token)?))
}
_ => None,
})
}
/// Parse an expression.
fn parse_expression(token: Spanned<Token>) -> ParseResult<Spanned<Expression>> {
Ok(Spanned::new(match token.val {
Token::Quoted(text) => Expression::Str(text.to_owned()),
Token::Text(text) => {
if let Ok(b) = text.parse::<bool>() {
Expression::Bool(b)
} else if let Ok(num) = text.parse::<f64>() {
Expression::Num(num)
} else if let Ok(size) = text.parse::<Size>() {
Expression::Size(size)
} else {
Expression::Ident(text.to_owned())
}
}
_ => error!("expected expression"),
}, token.span))
}
/// Parse the body of a function.
fn parse_func_body(&mut self, header: &FuncHeader)
-> ParseResult<Spanned<Box<dyn LayoutFunc>>> {
/// Parse a function call.
fn parse_func_call(&mut self, name: Spanned<Ident>, args: FuncArgs)
-> ParseResult<Box<dyn LayoutFunc>> {
// Now we want to parse this function dynamically.
let parser = self
.ctx
.scope
.get_parser(&header.name.val)
.ok_or_else(|| error!(@"unknown function: `{}`", &header.name.val))?;
.get_parser(&name.v.0)
.ok_or_else(|| error!(@"unknown function: `{}`", &name.v))?;
let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket);
@ -245,30 +220,50 @@ impl<'s> Parser<'s> {
.map(|end| start + end)
.ok_or_else(|| error!(@"expected closing bracket"))?;
let span = Span::new(start - 1, end + 1);
// Parse the body.
let body_string = &self.src[start..end];
let body = parser(&header, Some(body_string), self.ctx)?;
let body = parser(args, Some(Spanned::new(body_string, span)), self.ctx)?;
// Skip to the end of the function in the token stream.
self.tokens.set_string_index(end);
// Now the body should be closed.
let token = self.tokens.next().expect("parse_func_body: expected token");
assert!(token.val == Token::RightBracket);
assert!(token.v == Token::RightBracket);
Spanned::new(body, Span::new(start - 1, end + 1))
body
} else {
let body = parser(&header, None, self.ctx)?;
Spanned::new(body, Span::new(0, 0))
parser(args, None, self.ctx)?
})
}
/// Parse an expression.
fn parse_expression(token: Spanned<Token>) -> ParseResult<Spanned<Expression>> {
Ok(Spanned::new(match token.v {
Token::Quoted(text) => Expression::Str(text.to_owned()),
Token::Text(text) => {
if let Ok(b) = text.parse::<bool>() {
Expression::Bool(b)
} else if let Ok(num) = text.parse::<f64>() {
Expression::Num(num)
} else if let Ok(size) = text.parse::<Size>() {
Expression::Size(size)
} else {
Expression::Ident(Ident::new(text.to_string())?)
}
}
_ => error!("expected expression"),
}, token.span))
}
/// Parse whitespace (as long as there is any) and skip over comments.
fn parse_white(&mut self) -> ParseResult<()> {
let mut state = NewlineState::Zero;
while let Some(token) = self.tokens.peek() {
match token.val {
match token.v {
Token::Space => {
self.advance();
match state {
@ -297,7 +292,7 @@ impl<'s> Parser<'s> {
}
state = NewlineState::Zero;
match token.val {
match token.v {
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
Token::StarSlash => error!("unexpected end of block comment"),
_ => break,
@ -312,7 +307,7 @@ impl<'s> Parser<'s> {
/// Skip over whitespace and comments.
fn skip_white(&mut self) {
while let Some(token) = self.tokens.peek() {
match token.val {
match token.v {
Token::Space | Token::Newline |
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
_ => break,
@ -333,7 +328,7 @@ impl<'s> Parser<'s> {
/// Append a space, merging with a previous space if there is one.
fn append_space(&mut self, span: Span) {
match self.tree.nodes.last_mut() {
Some(ref mut node) if node.val == Node::Space => node.span.expand(span),
Some(ref mut node) if node.v == Node::Space => node.span.expand(span),
_ => self.append(Node::Space, span),
}
}
@ -412,102 +407,73 @@ impl<'s> Iterator for PeekableTokens<'s> {
}
}
/// Whether this word is a valid unicode identifier.
fn is_identifier(string: &str) -> bool {
let mut chars = string.chars();
match chars.next() {
Some(c) if c != '.' && !UnicodeXID::is_xid_start(c) => return false,
None => return false,
_ => (),
}
while let Some(c) = chars.next() {
if c != '.' && !UnicodeXID::is_xid_continue(c) {
return false;
}
}
true
}
/// The result type for parsing.
pub type ParseResult<T> = TypesetResult<T>;
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
#![allow(non_snake_case)]
use super::*;
use crate::func::{Commands, Scope};
use crate::layout::{LayoutContext, LayoutResult};
use funcs::*;
use crate::syntax::*;
use Node::{Func as F, Newline as N, Space as S};
/// Two test functions, one which parses it's body as another syntax tree
/// and another one which does not expect a body.
mod funcs {
use super::*;
function! {
/// A testing function which just parses it's body into a syntax
/// tree.
#[derive(Debug)]
pub struct TreeFn { pub tree: SyntaxTree }
function! {
/// A testing function which just parses it's body into a syntax
/// tree.
#[derive(Debug)]
pub struct TreeFn { pub tree: SyntaxTree }
parse(args, body, ctx) {
args.clear();
TreeFn {
tree: parse!(expected: body, ctx)
}
}
layout() { vec![] }
}
impl PartialEq for TreeFn {
fn eq(&self, other: &TreeFn) -> bool {
assert_tree_equal(&self.tree, &other.tree);
true
parse(args, body, ctx) {
args.clear();
TreeFn {
tree: parse!(expected: body, ctx)
}
}
function! {
/// A testing function without a body.
#[derive(Debug, Default, PartialEq)]
pub struct BodylessFn;
layout() { vec![] }
}
parse(default)
layout() { vec![] }
impl PartialEq for TreeFn {
fn eq(&self, other: &TreeFn) -> bool {
assert_tree_equal(&self.tree, &other.tree);
true
}
}
function! {
/// A testing function without a body.
#[derive(Debug, Default, PartialEq)]
pub struct BodylessFn(Vec<Expression>, Vec<(Ident, Expression)>);
parse(args, body) {
parse!(forbidden: body);
BodylessFn(
args.pos().map(Spanned::value).collect(),
args.keys().map(|arg| (arg.v.key.v, arg.v.value.v)).collect(),
)
}
layout() { vec![] }
}
mod args {
use super::*;
use super::Expression;
pub use Expression::{Num as N, Size as Z, Bool as B};
pub fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) }
pub fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) }
pub fn I(string: &str) -> Expression {
Expression::Ident(Ident::new(string.to_owned()).unwrap())
}
}
/// Asserts that two syntax trees are equal except for all spans inside them.
fn assert_tree_equal(a: &SyntaxTree, b: &SyntaxTree) {
for (x, y) in a.nodes.iter().zip(&b.nodes) {
let equal = match (x, y) {
(Spanned { val: F(x), .. }, Spanned { val: F(y), .. }) => {
x.header.val.name.val == y.header.val.name.val
&& x.header.val.args.positional.iter().map(|span| &span.val)
.eq(y.header.val.args.positional.iter().map(|span| &span.val))
&& x.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val))
.eq(y.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val)))
&& &x.body.val == &y.body.val
}
_ => x.val == y.val
};
if !equal {
panic!("assert_tree_equal: ({:#?}) != ({:#?})", x.val, y.val);
if x.v != y.v {
panic!("trees are not equal: ({:#?}) != ({:#?})", x.v, y.v);
}
}
}
@ -564,21 +530,15 @@ mod tests {
/// Shortcut macro to create a function.
macro_rules! func {
(name => $name:expr) => (
func!(@$name, Box::new(BodylessFn), FuncArgs::new())
() => (
FuncCall { call: Box::new(BodylessFn(vec![], vec![])) }
);
(name => $name:expr, body => $tree:expr $(,)*) => (
func!(@$name, Box::new(TreeFn { tree: $tree }), FuncArgs::new())
(body: $tree:expr $(,)*) => (
FuncCall { call: Box::new(TreeFn { tree: $tree }) }
);
(args: $pos:expr, $key:expr) => (
FuncCall { call: Box::new(BodylessFn($pos, $key)) }
);
(@$name:expr, $body:expr, $args:expr) => (
FuncCall {
header: zerospan(FuncHeader {
name: zerospan($name.to_string()),
args: $args,
}),
body: zerospan($body),
}
)
}
/// Parse the basic cases.
@ -613,25 +573,21 @@ mod tests {
scope.add::<TreeFn>("modifier");
scope.add::<TreeFn>("func");
test_scoped(&scope,"[test]", tree! [ F(func! { name => "test" }) ]);
test_scoped(&scope,"[ test]", tree! [ F(func! { name => "test" }) ]);
test_scoped(&scope,"[test]", tree! [ F(func! {}) ]);
test_scoped(&scope,"[ test]", tree! [ F(func! {}) ]);
test_scoped(&scope, "This is an [modifier][example] of a function invocation.", tree! [
T("This"), S, T("is"), S, T("an"), S,
F(func! { name => "modifier", body => tree! [ T("example") ] }), S,
F(func! { body: tree! [ T("example") ] }), S,
T("of"), S, T("a"), S, T("function"), S, T("invocation.")
]);
test_scoped(&scope, "[func][Hello][modifier][Here][end]", tree! [
F(func! { name => "func", body => tree! [ T("Hello") ] }),
F(func! { name => "modifier", body => tree! [ T("Here") ] }),
F(func! { name => "end" }),
F(func! { body: tree! [ T("Hello") ] }),
F(func! { body: tree! [ T("Here") ] }),
F(func! {}),
]);
test_scoped(&scope, "[func][]", tree! [ F(func! { name => "func", body => tree! [] }) ]);
test_scoped(&scope, "[func][]", tree! [ F(func! { body: tree! [] }) ]);
test_scoped(&scope, "[modifier][[func][call]] outside", tree! [
F(func! {
name => "modifier",
body => tree! [ F(func! { name => "func", body => tree! [ T("call") ] }) ],
}),
S, T("outside")
F(func! { body: tree! [ F(func! { body: tree! [ T("call") ] }) ] }), S, T("outside")
]);
}
@ -643,16 +599,14 @@ mod tests {
use args::*;
fn func(
positional: Vec<Expression>,
keyword: Vec<(&str, Expression)>,
pos: Vec<Expression>,
key: Vec<(&str, Expression)>,
) -> SyntaxTree {
let args = FuncArgs {
positional: positional.into_iter().map(zerospan).collect(),
keyword: keyword.into_iter()
.map(|(s, e)| zerospan((zerospan(s.to_string()), zerospan(e))))
.collect()
};
tree! [ F(func!(@"align", Box::new(BodylessFn), args)) ]
let key = key.into_iter()
.map(|s| (Ident::new(s.0.to_string()).unwrap(), s.1))
.collect();
tree! [ F(func!(args: pos, key)) ]
}
let mut scope = Scope::new();
@ -689,9 +643,9 @@ mod tests {
test_scoped(&scope, "Text\n// Comment\n More text",
tree! [ T("Text"), S, T("More"), S, T("text") ]);
test_scoped(&scope, "[test/*world*/]",
tree! [ F(func! { name => "test" }) ]);
tree! [ F(func! {}) ]);
test_scoped(&scope, "[test/*]*/]",
tree! [ F(func! { name => "test" }) ]);
tree! [ F(func! {}) ]);
}
/// Test if escaped, but unbalanced parens are correctly parsed.
@ -702,21 +656,14 @@ mod tests {
scope.add::<TreeFn>("code");
test_scoped(&scope, r"My [code][Close \]] end", tree! [
T("My"), S, F(func! {
name => "code",
body => tree! [ T("Close"), S, T("]") ]
}), S, T("end")
T("My"), S, F(func! { body: tree! [ T("Close"), S, T("]") ] }), S, T("end")
]);
test_scoped(&scope, r"My [code][\[ Open] end", tree! [
T("My"), S, F(func! {
name => "code",
body => tree! [ T("["), S, T("Open") ]
}), S, T("end")
T("My"), S, F(func! { body: tree! [ T("["), S, T("Open") ] }), S, T("end")
]);
test_scoped(&scope, r"My [code][Open \] and \[ close]end", tree! [
T("My"), S, F(func! {
name => "code",
body => tree! [ T("Open"), S, T("]"), S, T("and"), S, T("["), S, T("close") ]
T("My"), S, F(func! { body:
tree! [ T("Open"), S, T("]"), S, T("and"), S, T("["), S, T("close") ]
}), T("end")
]);
}
@ -729,15 +676,9 @@ mod tests {
scope.add::<BodylessFn>("func");
scope.add::<TreeFn>("bold");
test_scoped(&scope, "[func] ⺐.", tree! [
F(func! { name => "func" }),
S, T("⺐.")
]);
test_scoped(&scope, "[func] ⺐.", tree! [ F(func! {}), S, T("⺐.") ]);
test_scoped(&scope, "[bold][Hello 🌍!]", tree! [
F(func! {
name => "bold",
body => tree! [ T("Hello"), S, T("🌍!") ],
})
F(func! { body: tree! [ T("Hello"), S, T("🌍!") ] })
]);
}
@ -768,14 +709,8 @@ mod tests {
assert_eq!(tree[1].span.pair(), (4, 5));
assert_eq!(tree[2].span.pair(), (5, 37));
let func = if let Node::Func(f) = &tree[2].val { f } else { panic!() };
assert_eq!(func.header.span.pair(), (5, 24));
assert_eq!(func.header.val.name.span.pair(), (6, 11));
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().tree.nodes;
assert_eq!(func.body.span.pair(), (24, 37));
let func = if let Node::Func(f) = &tree[2].v { f } else { panic!() };
let body = &func.call.downcast::<TreeFn>().unwrap().tree.nodes;
assert_eq!(body[0].span.pair(), (0, 4));
assert_eq!(body[1].span.pair(), (4, 5));
assert_eq!(body[2].span.pair(), (5, 6));
@ -793,7 +728,7 @@ mod tests {
test_err("No functions here]", "unexpected closing bracket");
test_err_scoped(&scope, "[hello][world", "expected closing bracket");
test_err("[hello world", "expected arguments or closing bracket");
test_err("[ no-name][Why?]", "invalid identifier: 'no-name'");
test_err("[ no^name][Why?]", "invalid identifier: `no^name`");
test_err("Hello */", "unexpected end of block comment");
}
}

View File

@ -5,27 +5,27 @@ 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 v: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub fn new(val: T, span: Span) -> Spanned<T> {
Spanned { val, span }
pub fn new(v: T, span: Span) -> Spanned<T> {
Spanned { v, span }
}
pub fn value(self) -> T {
self.val
self.v
}
pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
Spanned::new(f(self.val), self.span)
pub fn map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
Spanned::new(f(self.v), 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)
write!(f, "({:?}:{})", self.v, self.span)
}
}

View File

@ -354,7 +354,7 @@ mod tests {
/// Test if the source code tokenizes to the tokens.
fn test(src: &str, tokens: Vec<Token>) {
assert_eq!(Tokens::new(src)
.map(|token| token.val)
.map(|token| token.v)
.collect::<Vec<_>>(), tokens);
}

View File

@ -3,10 +3,10 @@ use std::io::{BufWriter, Read, Write};
use std::process::Command;
use typst::export::pdf::PdfExporter;
use typst::layout::LayoutAction;
use typst::toddle::query::FileSystemFontProvider;
use typst::layout::{LayoutAction, Serialize};
use typst::size::{Size, Size2D, SizeBox};
use typst::style::PageStyle;
use typst::toddle::query::FileSystemFontProvider;
use typst::Typesetter;
const CACHE_DIR: &str = "tests/cache";

View File

@ -3,7 +3,7 @@
A short sentence. [n] [align: right][words.]
A short sentence. [paragraph.break] [align: right][words.]
A short sentence. [par.break] [align: right][words.]
[align: bottom]
A longer sentence with a few more words.