summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-05 19:48:37 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-05 19:48:37 +0100
commit72a9631b038d1a60e4e4a78e92cd69e6f8ce4316 (patch)
tree17614efc2e21dd0b8caa24beaaaee7c40c150281 /src
parentf72b1505bebf8d2fe1a60d386a3a3c3b67d4f903 (diff)
Move arg parser into `FuncArgs` and create (incomplete) consistent map 🧭
Diffstat (limited to 'src')
-rw-r--r--src/export/pdf.rs10
-rw-r--r--src/func/args.rs215
-rw-r--r--src/func/macros.rs22
-rw-r--r--src/func/map.rs59
-rw-r--r--src/func/mod.rs36
-rw-r--r--src/layout/flex.rs127
-rw-r--r--src/layout/mod.rs34
-rw-r--r--src/layout/stack.rs57
-rw-r--r--src/layout/tree.rs6
-rw-r--r--src/lib.rs10
-rw-r--r--src/library/align.rs37
-rw-r--r--src/library/boxed.rs21
-rw-r--r--src/library/keys.rs172
-rw-r--r--src/library/mod.rs104
-rw-r--r--src/macros.rs24
-rw-r--r--src/size.rs28
-rw-r--r--src/syntax/mod.rs204
-rw-r--r--src/syntax/parsing.rs349
-rw-r--r--src/syntax/span.rs14
-rw-r--r--src/syntax/tokens.rs2
20 files changed, 753 insertions, 778 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 8370f454..8d6393c9 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -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)),
}
diff --git a/src/func/args.rs b/src/func/args.rs
deleted file mode 100644
index d1d49b6a..00000000
--- a/src/func/args.rs
+++ /dev/null
@@ -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),
-}
diff --git a/src/func/macros.rs b/src/func/macros.rs
index 0ffdc857..daae2769 100644
--- a/src/func/macros.rs
+++ b/src/func/macros.rs
@@ -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");
}
diff --git a/src/func/map.rs b/src/func/map.rs
new file mode 100644
index 00000000..880fe3e6
--- /dev/null
+++ b/src/func/map.rs
@@ -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()
+ }
+}
diff --git a/src/func/mod.rs b/src/func/mod.rs
index 31e31592..7ee7d779 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -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>)
})
);
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 46d66951..13901968 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -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!()
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 690e91b7..e31d2c17 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -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<()>;
}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 176aa261..3f9af350 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -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 {
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index c9d40e93..4370ceab 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -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,
diff --git a/src/lib.rs b/src/lib.rs
index 3369d01c..e63ec936 100644
--- a/src/lib.rs
+++ b/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())),
}
diff --git a/src/library/align.rs b/src/library/align.rs
index 417d8f07..eea25dfa 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -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)?)],
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index a2df45e3..ef5ae24e 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -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)?)]
}
diff --git a/src/library/keys.rs b/src/library/keys.rs
new file mode 100644
index 00000000..df658027
--- /dev/null
+++ b/src/library/keys.rs
@@ -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),
+);
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 02af0d83..f25c6397 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -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),
-
- "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);
+ let key = PaddingKey::from_ident(&arg.v.key)?;
+ let size = Size::from_expr(arg.v.value)?;
+
+ 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 spacing = SpacingValue::from_expr(arg.val.1.val)?;
+ let axis = AxisKey::from_ident(&arg.v.key)
+ .map_err(|_| error!(@unexpected_argument))?;
+
+ 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![
diff --git a/src/macros.rs b/src/macros.rs
index b6f069b7..ebe1cad7 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -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
}
})*
diff --git a/src/size.rs b/src/size.rs
index a9c3198c..d415be3f 100644
--- a/src/size.rs
+++ b/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);
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 1b55fb4e..21088b83 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -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)
+);
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 2d76b6cf..3527e6b1 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -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,64 +93,55 @@ 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 name = self.parse_func_name()?;
- let header = self.parse_func_header()?;
- let body = self.parse_func_body(&header.val)?;
+ // Check for arguments
+ let args = match self.tokens.next().map(Spanned::value) {
+ Some(Token::RightBracket) => FuncArgs::new(),
+ Some(Token::Colon) => self.parse_func_args()?,
+ _ => error!("expected arguments or closing bracket"),
+ };
+ let call = self.parse_func_call(name, args)?;
span.end = self.tokens.string_index();
// Finally this function is parsed to the end.
- self.append(Node::Func(FuncCall { header, body }), span);
+ self.append(Node::Func(FuncCall { call }), span);
Ok(())
}
/// Parse a function header.
- fn parse_func_header(&mut self) -> ParseResult<Spanned<FuncHeader>> {
- let start = self.tokens.string_index() - 1;
-
+ fn parse_func_name(&mut self) -> ParseResult<Spanned<Ident>> {
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);
- }
+ Some(Spanned { v: Token::Text(word), span }) => {
+ let ident = Ident::new(word.to_string())?;
+ Spanned::new(ident, span)
}
_ => error!("expected identifier"),
- }?;
-
- self.skip_white();
-
- // Check for arguments
- let args = match self.tokens.next().map(Spanned::value) {
- Some(Token::RightBracket) => FuncArgs::new(),
- Some(Token::Colon) => self.parse_func_args()?,
- _ => error!("expected arguments or closing bracket"),
};
- let end = self.tokens.string_index();
+ self.skip_white();
- // Store the header information of the function invocation.
- Ok(Spanned::new(FuncHeader { name, args }, Span::new(start, end)))
+ 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);
+
+ 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 };
- FuncArg::Keyword(Spanned::new((name, val), span))
+ 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)
- }
+ 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
- }
+ 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;
+ function! {
+ /// A testing function without a body.
+ #[derive(Debug, Default, PartialEq)]
+ pub struct BodylessFn(Vec<Expression>, Vec<(Ident, Expression)>);
- parse(default)
- layout() { vec![] }
+ 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");
}
}
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index aa494224..9e018437 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -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)
}
}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 752a0b29..f5609b59 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -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);
}