Move arg parser into FuncArgs
and create (incomplete) consistent map 🧭
This commit is contained in:
parent
f72b1505be
commit
72a9631b03
@ -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)),
|
||||
}
|
||||
|
215
src/func/args.rs
215
src/func/args.rs
@ -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),
|
||||
}
|
@ -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
59
src/func/map.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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>)
|
||||
})
|
||||
);
|
||||
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
@ -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<()>;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -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())),
|
||||
}
|
||||
|
@ -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)?)],
|
||||
|
@ -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
172
src/library/keys.rs
Normal 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),
|
||||
);
|
@ -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![
|
||||
|
@ -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
|
||||
}
|
||||
})*
|
||||
|
28
src/size.rs
28
src/size.rs
@ -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);
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user