diff --git a/src/error.rs b/src/error.rs index 55156038f..b35972660 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ use serde::Serialize; +use crate::syntax::SpanVec; +pub type Errors = SpanVec; + #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct Error { pub message: String, diff --git a/src/func/macros.rs b/src/func/macros.rs index 92b6c16e7..764f08361 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -39,15 +39,23 @@ macro_rules! function { type Meta = $meta; fn parse( - #[allow(unused)] mut $header: FuncHeader, - #[allow(unused)] $body: Option<(Position, &str)>, + #[allow(unused)] mut header: FuncHeader, + #[allow(unused)] $body: Option>, #[allow(unused)] $ctx: ParseContext, #[allow(unused)] $metadata: Self::Meta, ) -> Parsed where Self: Sized { - #[allow(unused)] let mut $errors = vec![]; - #[allow(unused)] let mut $decos = vec![]; + let mut errors = vec![]; + let mut decorations = vec![]; + #[allow(unused)] let $header = &mut header; + #[allow(unused)] let $errors = &mut errors; + #[allow(unused)] let $decos = &mut decorations; let output = $code; - $crate::syntax::Parsed { output, errors: $errors, decorations: $decos } + + for arg in header.args.into_iter() { + errors.push(err!(arg.span(); "unexpected argument")); + } + + $crate::syntax::Parsed { output, errors, decorations } } } @@ -58,7 +66,7 @@ macro_rules! function { impl $crate::syntax::Model for $name { fn layout<'a, 'b, 'c, 't>( #[allow(unused)] &'a $this, - #[allow(unused)] $ctx: $crate::layout::LayoutContext<'b, 'c>, + #[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b, 'c>, ) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>> where 'a: 't, @@ -67,9 +75,10 @@ macro_rules! function { Self: 't, { Box::pin(async move { - #[allow(unused)] let mut $errors = vec![]; + let mut errors = vec![]; + #[allow(unused)] let $errors = &mut errors; let output = $code; - $crate::layout::Layouted { output, errors: $errors } + $crate::layout::Layouted { output, errors } }) } } @@ -78,14 +87,17 @@ macro_rules! function { /// Parse the body of a function. /// -/// - If the function does not expect a body, use `parse!(forbidden: body)`. -/// - If the function can have a body, use `parse!(optional: body, ctx)`. -/// - If the function must have a body, use `parse!(expected: body, ctx)`. +/// - If the function does not expect a body, use `parse!(nope: body, errors)`. +/// - If the function can have a body, use `parse!(opt: body, ctx, errors, decos)`. #[macro_export] macro_rules! body { (opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({ $body.map(|body| { - let parsed = $crate::syntax::parse(body.0, body.1, $ctx); + // Since the body span starts at the opening bracket of the body, we + // need to add 1 column to find out the start position of body + // content. + let start = body.span.start + Position::new(0, 1); + let parsed = $crate::syntax::parse(start, body.v, $ctx); $errors.extend(parsed.errors); $decos.extend(parsed.decorations); parsed.output @@ -94,12 +106,19 @@ macro_rules! body { (nope: $body:expr, $errors:expr) => { if let Some(body) = $body { - $errors.push($crate::err!(body.span, "unexpected body")); + $errors.push($crate::err!(body.span; "unexpected body")); } }; } -/// Construct an error with an optional span. +/// Construct an error with optional severity and span. +/// +/// # Examples +/// ``` +/// err!(span; "the wrong {}", value); +/// err!(@Warning: span; "non-fatal!"); +/// err!("no spans here ..."); +/// ``` #[macro_export] macro_rules! err { (@$severity:ident: $span:expr; $($args:tt)*) => { diff --git a/src/func/mod.rs b/src/func/mod.rs index e33999039..b3721d912 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -26,7 +26,7 @@ pub trait Parse { /// Parse the header and body into this function given a context. fn parse( header: FuncHeader, - body: Option<(Position, &str)>, + body: Option>, ctx: ParseContext, metadata: Self::Meta, ) -> Parsed where Self: Sized; @@ -36,7 +36,7 @@ pub trait Parse { /// implements [`Model`]. type Parser = dyn Fn( FuncHeader, - Option<(Position, &str)>, + Option>, ParseContext, ) -> Parsed>; @@ -50,7 +50,7 @@ pub enum Command<'a> { Add(Layout), AddMultiple(MultiLayout), - SpacingFunc(Size, SpacingKind, GenericAxis), + AddSpacing(Size, SpacingKind, GenericAxis), FinishLine, FinishSpace, @@ -88,12 +88,12 @@ impl Scope { /// Associate the given name with a type that is parseable into a function. pub fn add(&mut self, name: &str) where F: Parse + Model + 'static { - self.add_with_metadata::(name, ()); + self.add_with_meta::(name, ()); } /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). - pub fn add_with_metadata(&mut self, name: &str, metadata: ::Meta) + pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) where F: Parse + Model + 'static { self.parsers.insert( name.to_owned(), diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 1cc16a268..209874a13 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,10 +1,11 @@ //! The core layouting engine. use std::io::{self, Write}; +use std::fmt::{self, Display, Formatter}; use smallvec::SmallVec; use toddle::query::{SharedFontLoader, FontIndex}; -use crate::error::Error; +use crate::error::Errors; use crate::syntax::{SyntaxModel, SpanVec}; use crate::size::{Size, Size2D, SizeBox}; use crate::style::LayoutStyle; @@ -93,7 +94,7 @@ pub struct LayoutContext<'a, 'p> { pub struct Layouted { pub output: T, - pub errors: SpanVec, + pub errors: Errors, } impl Layouted { @@ -231,6 +232,15 @@ impl GenericAxis { } } +impl Display for GenericAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Primary => write!(f, "primary"), + Secondary => write!(f, "secondary"), + } + } +} + /// The two specific layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum SpecificAxis { @@ -253,6 +263,15 @@ impl SpecificAxis { } } +impl Display for SpecificAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Horizontal => write!(f, "horizontal"), + Vertical => write!(f, "vertical"), + } + } +} + /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Direction { @@ -298,6 +317,17 @@ impl Direction { } } +impl Display for Direction { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + LeftToRight => write!(f, "left-to-right"), + RightToLeft => write!(f, "right-to-left"), + TopToBottom => write!(f, "top-to-bottom"), + BottomToTop => write!(f, "bottom-to-top"), + } + } +} + /// Where to align a layout in a container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlignment { diff --git a/src/layout/model.rs b/src/layout/model.rs index 73492dd90..13d380831 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -1,6 +1,6 @@ use smallvec::smallvec; -use crate::error::Error; +use crate::error::Errors; use crate::func::Command; use crate::syntax::{Model, DynFuture, SyntaxModel, Node}; use crate::syntax::{SpanVec, Spanned, Span, offset_spans}; @@ -12,7 +12,7 @@ pub struct ModelLayouter<'a, 'p> { ctx: LayoutContext<'a, 'p>, layouter: LineLayouter, style: LayoutStyle, - errors: SpanVec, + errors: Errors, } impl<'a, 'p> ModelLayouter<'a, 'p> { @@ -105,7 +105,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { Add(layout) => self.layouter.add(layout), AddMultiple(layouts) => self.layouter.add_multiple(layouts), - SpacingFunc(space, kind, axis) => match axis { + AddSpacing(space, kind, axis) => match axis { Primary => self.layouter.add_primary_spacing(space, kind), Secondary => self.layouter.add_secondary_spacing(space, kind), } diff --git a/src/library/align.rs b/src/library/align.rs deleted file mode 100644 index ca2c787b9..000000000 --- a/src/library/align.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::func::prelude::*; -use super::maps::{PosAxisMap, AlignmentKey}; - - -function! { - /// `align`: Aligns content along the layouting axes. - #[derive(Debug, PartialEq)] - pub struct AlignFunc { - body: Option, - map: PosAxisMap, - } - - parse(header, body, ctx) { - AlignFunc { - body: parse!(optional: body, ctx), - map: PosAxisMap::new(&mut header.args)?, - } - } - - layout(self, mut ctx) { - ctx.base = ctx.spaces[0].dimensions; - - let map = self.map.dedup(ctx.axes, |alignment| alignment.axis(ctx.axes))?; - for &axis in &[Primary, Secondary] { - if let Some(alignment) = map.get(axis) { - *ctx.alignment.get_mut(axis) = alignment.to_generic(ctx.axes, axis)?; - } - } - - match &self.body { - Some(body) => vec![AddMultiple(layout(&body, ctx).await?)], - None => vec![SetAlignment(ctx.alignment)], - } - } -} diff --git a/src/library/boxed.rs b/src/library/boxed.rs deleted file mode 100644 index af236da4b..000000000 --- a/src/library/boxed.rs +++ /dev/null @@ -1,57 +0,0 @@ -use smallvec::smallvec; - -use crate::func::prelude::*; -use super::maps::ExtentMap; - - -function! { - /// `box`: Layouts content into a box. - #[derive(Debug, PartialEq)] - pub struct BoxFunc { - body: SyntaxTree, - map: ExtentMap, - debug: Option, - } - - parse(header, body, ctx) { - BoxFunc { - body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()), - map: ExtentMap::new(&mut header.args, false)?, - debug: header.args.get_key_opt::("debug")?, - } - } - - layout(self, mut ctx) { - ctx.repeat = false; - - if let Some(debug) = self.debug { - ctx.debug = debug; - } - - let map = self.map.dedup(ctx.axes)?; - - // Try to layout this box in all spaces until it fits into some space. - let mut error = None; - for &(mut space) in &ctx.spaces { - let mut ctx = ctx.clone(); - - for &axis in &[Horizontal, Vertical] { - if let Some(psize) = map.get(axis) { - let size = psize.scaled(ctx.base.get(axis)); - *ctx.base.get_mut(axis) = size; - *space.dimensions.get_mut(axis) = size; - *space.expansion.get_mut(axis) = true; - } - } - - ctx.spaces = smallvec![space]; - - match layout(&self.body, ctx).await { - Ok(layouts) => return Ok(vec![AddMultiple(layouts)]), - Err(err) => error = Some(err), - } - } - - return Err(error.expect("expected at least one space")); - } -} diff --git a/src/library/direction.rs b/src/library/direction.rs deleted file mode 100644 index b7a6e212f..000000000 --- a/src/library/direction.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::func::prelude::*; -use super::maps::PosAxisMap; - - -function! { - /// `direction`: Sets the directions of the layouting axes. - #[derive(Debug, PartialEq)] - pub struct DirectionFunc { - body: Option, - map: PosAxisMap, - } - - parse(header, body, ctx) { - DirectionFunc { - body: parse!(optional: body, ctx), - map: PosAxisMap::new(&mut header.args)?, - } - } - - layout(self, mut ctx) { - ctx.base = ctx.spaces[0].dimensions; - - let map = self.map.dedup(ctx.axes, |direction| { - Some(direction.axis().to_generic(ctx.axes)) - })?; - - map.with(Primary, |&dir| ctx.axes.primary = dir); - map.with(Secondary, |&dir| ctx.axes.secondary = dir); - - if ctx.axes.primary.axis() == ctx.axes.secondary.axis() { - error!( - "invalid aligned primary and secondary axes: `{}`, `{}`", - format!("{:?}", ctx.axes.primary).to_lowercase(), - format!("{:?}", ctx.axes.secondary).to_lowercase(), - ); - } - - match &self.body { - Some(body) => vec![AddMultiple(layout(&body, ctx).await?)], - None => vec![Command::SetAxes(ctx.axes)], - } - } -} diff --git a/src/library/font.rs b/src/library/font.rs new file mode 100644 index 000000000..b4a6218af --- /dev/null +++ b/src/library/font.rs @@ -0,0 +1,104 @@ +use toddle::query::{FontWeight, FontStyle}; +use super::*; + + +function! { + /// `font.family`: Set the font family. + #[derive(Debug, Clone, PartialEq)] + pub struct FontFamilyFunc { + body: Option, + list: Vec, + } + + parse(header, body, ctx, errors, decos) { + FontFamilyFunc { + body: body!(opt: body, ctx, errors, decos), + list: header.args.pos.get_all::(errors).collect(), + } + } + + layout(self, ctx, errors) { + styled(&self.body, ctx, Some(&self.list), + |s, l| s.fallback.list = l.clone()) + } +} + +function! { + /// `font.style`: Set the font style (normal / italic). + #[derive(Debug, Clone, PartialEq)] + pub struct FontStyleFunc { + body: Option, + style: Option, + } + + parse(header, body, ctx, errors, decos) { + FontStyleFunc { + body: body!(opt: body, ctx, errors, decos), + style: header.args.pos.get::(errors) + .or_missing(errors, header.name.span, "style"), + } + } + + layout(self, ctx, errors) { + styled(&self.body, ctx, self.style, |t, s| t.variant.style = s) + } +} + +function! { + /// `font.weight`: Set text with a given weight. + #[derive(Debug, Clone, PartialEq)] + pub struct FontWeightFunc { + body: Option, + weight: Option, + } + + parse(header, body, ctx, errors, decos) { + let body = body!(opt: body, ctx, errors, decos); + let weight = header.args.pos.get::>(errors) + .map(|Spanned { v: (weight, is_clamped), span }| { + if is_clamped { + errors.push(err!(@Warning: span; + "weight should be between \ + 100 and 900, clamped to {}", weight.0)); + } + + weight + }) + .or_missing(errors, header.name.span, "weight"); + + FontWeightFunc { body, weight } + } + + layout(self, ctx, errors) { + styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w) + } +} + +function! { + /// `font.size`: Sets the font size. + #[derive(Debug, Clone, PartialEq)] + pub struct FontSizeFunc { + body: Option, + size: Option, + } + + parse(header, body, ctx, errors, decos) { + FontSizeFunc { + body: body!(opt: body, ctx, errors, decos), + size: header.args.pos.get::(errors) + .or_missing(errors, header.name.span, "size") + } + } + + layout(self, ctx, errors) { + styled(&self.body, ctx, self.size, |t, s| { + match s { + ScaleSize::Absolute(size) => { + t.base_font_size = size; + t.font_scale = 1.0; + } + ScaleSize::Scaled(scale) => t.font_scale = scale, + } + }) + } +} diff --git a/src/library/layout.rs b/src/library/layout.rs new file mode 100644 index 000000000..fb8633b11 --- /dev/null +++ b/src/library/layout.rs @@ -0,0 +1,135 @@ +use smallvec::smallvec; +use super::*; + + +function! { + /// `align`: Aligns content along the layouting axes. + #[derive(Debug, Clone, PartialEq)] + pub struct AlignFunc { + body: Option, + map: PosAxisMap, + } + + parse(header, body, ctx, errors, decos) { + AlignFunc { + body: body!(opt: body, ctx, errors, decos), + map: PosAxisMap::parse::(errors, &mut header.args), + } + } + + layout(self, ctx, errors) { + ctx.base = ctx.spaces[0].dimensions; + + let map = self.map.dedup(errors, ctx.axes, |alignment| alignment.axis(ctx.axes)); + for &axis in &[Primary, Secondary] { + if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) { + if let Some(generic) = alignment.to_generic(ctx.axes, axis) { + *ctx.alignment.get_mut(axis) = generic; + } else { + errors.push(err!(span; + "invalid alignment `{}` for {} axis", alignment, axis)); + } + } + } + + match &self.body { + Some(body) => { + let layouted = layout(body, ctx).await; + errors.extend(layouted.errors); + vec![AddMultiple(layouted.output)] + } + None => vec![SetAlignment(ctx.alignment)], + } + } +} + +function! { + /// `direction`: Sets the directions of the layouting axes. + #[derive(Debug, Clone, PartialEq)] + pub struct DirectionFunc { + name_span: Span, + body: Option, + map: PosAxisMap, + } + + parse(header, body, ctx, errors, decos) { + DirectionFunc { + name_span: header.name.span, + body: body!(opt: body, ctx, errors, decos), + map: PosAxisMap::parse::(errors, &mut header.args), + } + } + + layout(self, ctx, errors) { + ctx.base = ctx.spaces[0].dimensions; + + let map = self.map.dedup(errors, ctx.axes, |direction| { + Some(direction.axis().to_generic(ctx.axes)) + }); + + let mut axes = ctx.axes; + + map.with(Primary, |&dir| axes.primary = dir); + map.with(Secondary, |&dir| axes.secondary = dir); + + if axes.primary.axis() == axes.secondary.axis() { + errors.push(err!(self.name_span; + "invalid aligned primary and secondary axes: `{}`, `{}`", + ctx.axes.primary, ctx.axes.secondary)); + } else { + ctx.axes = axes; + } + + match &self.body { + Some(body) => { + let layouted = layout(body, ctx).await; + errors.extend(layouted.errors); + vec![AddMultiple(layouted.output)] + } + None => vec![SetAxes(ctx.axes)], + } + } +} + +function! { + /// `box`: Layouts content into a box. + #[derive(Debug, Clone, PartialEq)] + pub struct BoxFunc { + body: SyntaxModel, + extents: AxisMap, + debug: Option, + } + + parse(header, body, ctx, errors, decos) { + BoxFunc { + body: body!(opt: body, ctx, errors, decos).unwrap_or(SyntaxModel::new()), + extents: AxisMap::parse::(errors, &mut header.args.key), + debug: header.args.key.get::(errors, "debug"), + } + } + + layout(self, ctx, errors) { + ctx.repeat = false; + ctx.spaces.truncate(1); + + if let Some(debug) = self.debug { + ctx.debug = debug; + } + + let map = self.extents.dedup(errors, ctx.axes); + for &axis in &[Horizontal, Vertical] { + if let Some(psize) = map.get(axis) { + let size = psize.scaled(ctx.base.get(axis)); + *ctx.base.get_mut(axis) = size; + *ctx.spaces[0].dimensions.get_mut(axis) = size; + *ctx.spaces[0].expansion.get_mut(axis) = true; + } + } + + let layouted = layout(&self.body, ctx).await; + let layout = layouted.output.into_iter().next().unwrap(); + errors.extend(layouted.errors); + + vec![Add(layout)] + } +} diff --git a/src/library/maps/alignment.rs b/src/library/maps/alignment.rs deleted file mode 100644 index 8654e280a..000000000 --- a/src/library/maps/alignment.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::*; -use AlignmentKey::*; - - -/// An argument key which describes a target alignment. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum AlignmentKey { - Align(Alignment), - Left, - Top, - Right, - Bottom, -} - -impl AlignmentKey { - /// The generic axis this alignment key corresponds to in the given system - /// of layouting axes. `None` if the alignment is generic. - pub fn axis(self, axes: LayoutAxes) -> Option { - match self { - Left | Right => Some(Horizontal.to_generic(axes)), - Top | Bottom => Some(Vertical.to_generic(axes)), - Align(_) => None, - } - } - - /// 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 to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> LayoutResult { - let specific = axis.to_specific(axes); - let start = match axes.get(axis).is_positive() { - true => Origin, - false => End, - }; - - Ok(match (self, specific) { - (Align(alignment), _) => alignment, - (Left, Horizontal) | (Top, Vertical) => start, - (Right, Horizontal) | (Bottom, Vertical) => start.inv(), - - _ => 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 to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentKey { - let direction = axes.get_specific(axis); - if let Align(alignment) = self { - match (direction, alignment) { - (LeftToRight, Origin) | (RightToLeft, End) => Left, - (LeftToRight, End) | (RightToLeft, Origin) => Right, - (TopToBottom, Origin) | (BottomToTop, End) => Top, - (TopToBottom, End) | (BottomToTop, Origin) => Bottom, - (_, Center) => self, - } - } else { - self - } - } -} - -key!(AlignmentKey, "alignment", - "origin" => Align(Origin), - "center" => Align(Center), - "end" => Align(End), - - "left" => Left, - "top" => Top, - "right" => Right, - "bottom" => Bottom, -); diff --git a/src/library/maps/axis.rs b/src/library/maps/axis.rs deleted file mode 100644 index 3c3a8c118..000000000 --- a/src/library/maps/axis.rs +++ /dev/null @@ -1,130 +0,0 @@ -use super::*; -use AxisKey::*; - - -/// An argument key which identifies a layouting axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum AxisKey { - Generic(GenericAxis), - Specific(SpecificAxis), -} - -impl AxisKey { - /// The generic version of this axis key in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { - match self { - Generic(axis) => axis, - Specific(axis) => axis.to_generic(axes), - } - } - - /// The specific version of this axis key in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - match self { - Generic(axis) => axis.to_specific(axes), - Specific(axis) => axis, - } - } -} - -key!(AxisKey, "axis", - "horizontal" | "h" => Specific(Horizontal), - "vertical" | "v" => Specific(Vertical), - "primary" | "p" => Generic(Primary), - "secondary" | "s" => Generic(Secondary), -); - -key!(Direction, "direction", - "left-to-right" | "ltr" => LeftToRight, - "right-to-left" | "rtl" => RightToLeft, - "top-to-bottom" | "ttb" => TopToBottom, - "bottom-to-top" | "btt" => BottomToTop, -); - -/// A map for storing extents along axes. -#[derive(Debug, Clone, PartialEq)] -pub struct ExtentMap(ConsistentMap); - -impl ExtentMap { - /// Parse an extent map from the function args. - /// - /// If `all` is true other arguments will create an error, otherwise - /// they are left intact. - pub fn new(args: &mut FuncArgs, all: bool) -> ParseResult> { - let mut map = ConsistentMap::new(); - - for arg in args.iter_keys() { - let key = match arg.key.v.as_str() { - "width" | "w" => AxisKey::Specific(Horizontal), - "height" | "h" => AxisKey::Specific(Vertical), - "primary-size" | "ps" => AxisKey::Generic(Primary), - "secondary-size" | "ss" => AxisKey::Generic(Secondary), - - _ => if all { - error!("expected dimension") - } else { - args.add_key_pair(arg); - continue; - } - }; - - let e = E::from_expr(arg.value)?; - map.add(key, e)?; - } - - Ok(ExtentMap(map)) - } - - /// Deduplicate from generic to specific axes. - pub fn dedup(&self, axes: LayoutAxes) -> LayoutResult> { - self.0.dedup(|key, &val| Ok((key.to_specific(axes), val))) - } -} - -/// An argument key which identifies an axis, but allows for positional -/// arguments with unspecified axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PosAxisKey { - /// The first positional argument. - First, - /// The second positional argument. - Second, - /// An axis keyword argument. - Keyword(AxisKey), -} - -/// A map for storing some data for via keyword or positionally given axes. -#[derive(Debug, Clone, PartialEq)] -pub struct PosAxisMap(ConsistentMap); - -impl PosAxisMap { - /// Parse a positional axis map from the function args. - pub fn new(args: &mut FuncArgs) -> ParseResult> { - let mut map = ConsistentMap::new(); - - map.add_opt(PosAxisKey::First, args.get_pos_opt::()?)?; - map.add_opt(PosAxisKey::Second, args.get_pos_opt::()?)?; - - for arg in args.iter_keys() { - let axis = AxisKey::from_ident(&arg.key)?; - let value = E::from_expr(arg.value)?; - - map.add(PosAxisKey::Keyword(axis), value)?; - } - - Ok(PosAxisMap(map)) - } - - /// Deduplicate from positional or specific to generic axes. - pub fn dedup(&self, axes: LayoutAxes, f: F) -> LayoutResult> - where F: Fn(E) -> Option { - self.0.dedup(|key, &e| { - Ok((match key { - PosAxisKey::First => f(e).unwrap_or(Primary), - PosAxisKey::Second => f(e).unwrap_or(Secondary), - PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), - PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, - }, e)) - }) - } -} diff --git a/src/library/maps/mod.rs b/src/library/maps/mod.rs deleted file mode 100644 index 3538def72..000000000 --- a/src/library/maps/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Deduplicating maps and keys for argument parsing. - -use std::collections::HashMap; -use std::hash::Hash; - -use crate::func::prelude::*; - - -macro_rules! key { - ($type:ty, $name:expr, $($patterns:tt)*) => { - impl $type { - /// Parse this key from an identifier. - pub fn from_ident(ident: &Spanned) -> ParseResult { - Ok(match ident.v.as_str() { - $($patterns)* - _ => error!("expected {}", ::NAME), - }) - } - } - - impl ExpressionKind for $type { - const NAME: &'static str = $name; - - fn from_expr(expr: Spanned) -> ParseResult { - if let Expr::Ident(ident) = expr.v { - Self::from_ident(&Spanned::new(ident, expr.span)) - } else { - error!("expected {}", Self::NAME); - } - } - } - }; -} - -pub_use_mod!(axis); -pub_use_mod!(alignment); -pub_use_mod!(padding); - - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum DefaultKey { - Some(T), - None, -} - -impl Into> for DefaultKey { - fn into(self) -> Option { - match self { - DefaultKey::Some(v) => Some(v), - DefaultKey::None => None, - } - } -} - -impl ExpressionKind for DefaultKey where T: ExpressionKind { - const NAME: &'static str = T::NAME; - - fn from_expr(expr: Spanned) -> ParseResult> { - if let Expr::Ident(ident) = &expr.v { - match ident.as_str() { - "default" => return Ok(DefaultKey::None), - _ => {}, - } - } - - T::from_expr(expr).map(|v| DefaultKey::Some(v)) - } -} - -/// A deduplicating map type useful for storing possibly redundant arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct ConsistentMap where K: Hash + Eq { - map: HashMap, -} - -impl ConsistentMap where K: Hash + Eq { - pub fn new() -> ConsistentMap { - ConsistentMap { map: HashMap::new() } - } - - /// Add a key-value pair. - pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { - match self.map.insert(key, value) { - Some(_) => error!("duplicate argument"), - None => Ok(()) - } - } - - /// Add a key-value pair if the value is not `None`. - pub fn add_opt(&mut self, key: K, value: Option) -> ParseResult<()> { - Ok(if let Some(value) = value { - self.add(key, value)?; - }) - } - - /// Get the value at a key if it is present. - pub fn get(&self, key: K) -> Option<&V> { - self.map.get(&key) - } - - /// Call a function with the value if the key is present. - pub fn with(&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(&self, f: F) -> LayoutResult> - where F: Fn(&K, &V) -> ParseResult<(K2, V2)>, K2: Hash + Eq { - let mut map = ConsistentMap::new(); - - for (key, value) in self.map.iter() { - let (key, value) = f(key, value)?; - map.add(key, value)?; - } - - Ok(map) - } - - /// Iterate over the (key, value) pairs. - pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { - self.map.iter() - } -} diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs deleted file mode 100644 index e2d0ea09e..000000000 --- a/src/library/maps/padding.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::*; -use AxisKey::*; -use AlignmentKey::*; -use PaddingKey::*; - - -/// An argument key which identifies a margin or padding target. -/// -/// A is the used axis type. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaddingKey { - /// All four sides should have the specified padding. - All, - /// Both sides of the given axis should have the specified padding. - Both(A), - /// Only the given side of the given axis should have the specified padding. - Side(A, AlignmentKey), -} - -key!(PaddingKey, "axis or side", - "horizontal" | "h" => Both(Specific(Horizontal)), - "vertical" | "v" => Both(Specific(Vertical)), - "primary" | "p" => Both(Generic(Primary)), - "secondary" | "s" => Both(Generic(Secondary)), - - "left" => Side(Specific(Horizontal), Left), - "right" => Side(Specific(Horizontal), Right), - "top" => Side(Specific(Vertical), Top), - "bottom" => Side(Specific(Vertical), Bottom), - - "primary-origin" => Side(Generic(Primary), Align(Origin)), - "primary-end" => Side(Generic(Primary), Align(End)), - "secondary-origin" => Side(Generic(Secondary), Align(Origin)), - "secondary-end" => Side(Generic(Secondary), Align(End)), - "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), - "horizontal-end" => Side(Specific(Horizontal), Align(End)), - "vertical-origin" => Side(Specific(Vertical), Align(Origin)), - "vertical-end" => Side(Specific(Vertical), Align(End)), -); - -/// A map for storing padding at sides. -#[derive(Debug, Clone, PartialEq)] -pub struct PaddingMap(ConsistentMap, Option>); - -impl PaddingMap { - /// Parse a padding map from the function args. - pub fn new(args: &mut FuncArgs) -> ParseResult { - let mut map = ConsistentMap::new(); - map.add_opt(PaddingKey::All, - args.get_pos_opt::>()?.map(Into::into))?; - - for arg in args.iter_keys() { - let key = PaddingKey::from_ident(&arg.key)?; - let size = DefaultKey::::from_expr(arg.value)?.into(); - map.add(key, size)?; - } - - Ok(PaddingMap(map)) - } - - /// Apply the specified padding on the size box. - pub fn apply(&self, axes: LayoutAxes, padding: &mut ValueBox>) - -> LayoutResult<()> { - use PaddingKey::*; - - let map = self.0.dedup(|key, &val| { - Ok((match key { - All => All, - Both(axis) => Both(axis.to_specific(axes)), - Side(axis, alignment) => { - let axis = axis.to_specific(axes); - Side(axis, alignment.to_specific(axes, axis)) - } - }, val)) - })?; - - map.with(All, |&val| padding.set_all(val)); - map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); - map.with(Both(Vertical), |&val| padding.set_vertical(val)); - - for (key, &val) in map.iter() { - if let Side(_, alignment) = key { - match alignment { - AlignmentKey::Left => padding.left = val, - AlignmentKey::Right => padding.right = val, - AlignmentKey::Top => padding.top = val, - AlignmentKey::Bottom => padding.bottom = val, - _ => {}, - } - } - } - - Ok(()) - } -} diff --git a/src/library/mod.rs b/src/library/mod.rs index 8d16ccaf5..e706642f3 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,34 +1,32 @@ //! The standard library. -use toddle::query::{FontWeight, FontStyle}; - use crate::func::prelude::*; -use crate::style::{Paper, PaperClass}; -use self::maps::{ExtentMap, PaddingMap, AxisKey}; -pub mod maps; - -pub_use_mod!(align); -pub_use_mod!(boxed); -pub_use_mod!(direction); +pub_use_mod!(font); +pub_use_mod!(layout); +pub_use_mod!(page); +pub_use_mod!(spacing); /// Create a scope with all standard functions. pub fn std() -> Scope { - let mut std = Scope::new(); + let mut std = Scope::new::(); + + // Basics + std.add::("val"); // Font setup std.add::("font.family"); std.add::("font.style"); std.add::("font.weight"); std.add::("font.size"); + std.add_with_meta::("word.spacing", ContentKind::Word); // Layout + std.add_with_meta::("line.spacing", ContentKind::Line); + std.add_with_meta::("par.spacing", ContentKind::Paragraph); std.add::("align"); std.add::("direction"); - std.add_with_metadata::("par.spacing", ContentKind::Paragraph); - std.add_with_metadata::("word.spacing", ContentKind::Word); - std.add_with_metadata::("line.spacing", ContentKind::Line); std.add::("box"); // Spacing @@ -36,9 +34,9 @@ pub fn std() -> Scope { std.add::("line.break"); std.add::("par.break"); std.add::("page.break"); - std.add_with_metadata::("spacing", None); - std.add_with_metadata::("h", Some(Horizontal)); - std.add_with_metadata::("v", Some(Vertical)); + std.add_with_meta::("spacing", None); + std.add_with_meta::("h", Some(Horizontal)); + std.add_with_meta::("v", Some(Vertical)); // Page setup std.add::("page.size"); @@ -47,331 +45,45 @@ pub fn std() -> Scope { std } -// -------------------------------------------------------------------------- // -// Font setup - function! { - /// `font.family`: Set the font family. - #[derive(Debug, PartialEq)] - pub struct FontFamilyFunc { - body: Option, - list: Vec, + /// `val`: Layouts the body with no special effect. + #[derive(Debug, Clone, PartialEq)] + pub struct ValFunc { + body: Option, } - parse(header, body, ctx) { - FontFamilyFunc { - body: parse!(optional: body, ctx), - list: { - header.args.iter_pos().map(|arg| match arg.v { - Expr::Str(s) | - Expr::Ident(Ident(s)) => Ok(s.to_lowercase()), - _ => error!("expected identifier or string"), - }).collect::>>()? - } + parse(header, body, ctx, errors, decos) { + ValFunc { body: body!(opt: body, ctx, errors, decos) } + } + + layout(self, ctx, errors) { + match &self.body { + Some(model) => vec![LayoutSyntaxModel(model)], + None => vec![], } } +} - layout(self, ctx) { +/// Layout an optional body with a change of the text style. +fn styled<'a, T, F>( + body: &'a Option, + ctx: LayoutContext, + data: Option, + f: F, +) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) { + if let Some(data) = data { let mut style = ctx.style.text.clone(); - style.fallback.list = self.list.clone(); - styled(&self.body, &ctx, style) - } -} + f(&mut style, data); -function! { - /// `font.style`: Set the font style (normal / italic). - #[derive(Debug, PartialEq)] - pub struct FontStyleFunc { - body: Option, - style: FontStyle, - } - - parse(header, body, ctx) { - FontStyleFunc { - body: parse!(optional: body, ctx), - style: { - let s = header.args.get_pos::()?; - match FontStyle::from_str(&s) { - Some(style) => style, - None => error!("invalid font style: `{}`", s), - } - } + match body { + Some(model) => vec![ + SetTextStyle(style), + LayoutSyntaxModel(model), + SetTextStyle(ctx.style.text.clone()), + ], + None => vec![SetTextStyle(style)], } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - style.variant.style = self.style; - styled(&self.body, &ctx, style) - } -} - -function! { - /// `font.weight`: Set text with a given weight. - #[derive(Debug, PartialEq)] - pub struct FontWeightFunc { - body: Option, - weight: FontWeight, - } - - parse(header, body, ctx) { - FontWeightFunc { - body: parse!(optional: body, ctx), - weight: match header.args.get_pos::()? { - Expr::Number(weight) => { - let weight = weight.round() as i16; - FontWeight( - if weight < 100 { 100 } - else if weight <= 900 { weight } - else { 900 } - ) - } - Expr::Ident(Ident(s)) => { - match FontWeight::from_str(&s) { - Some(weight) => weight, - None => error!("invalid font weight: `{}`", s), - } - } - _ => error!("expected identifier or number"), - }, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - style.variant.weight = self.weight; - styled(&self.body, &ctx, style) - } -} - -function! { - /// `font.size`: Sets the font size. - #[derive(Debug, PartialEq)] - pub struct FontSizeFunc { - body: Option, - size: ScaleSize, - } - - parse(header, body, ctx) { - FontSizeFunc { - body: parse!(optional: body, ctx), - size: header.args.get_pos::()?, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - match self.size { - ScaleSize::Absolute(size) => { - style.base_font_size = size; - style.font_scale = 1.0; - } - ScaleSize::Scaled(scale) => style.font_scale = scale, - } - styled(&self.body, &ctx, style) - } -} - -// -------------------------------------------------------------------------- // -// Layout - -function! { - /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between - /// words, lines or paragraphs as a multiple of the font size. - #[derive(Debug, PartialEq)] - pub struct ContentSpacingFunc { - body: Option, - content: ContentKind, - spacing: f32, - } - - type Meta = ContentKind; - - parse(header, body, ctx, meta) { - ContentSpacingFunc { - body: parse!(optional: body, ctx), - content: meta, - spacing: header.args.get_pos::()? as f32, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - match self.content { - ContentKind::Word => style.word_spacing_scale = self.spacing, - ContentKind::Line => style.line_spacing_scale = self.spacing, - ContentKind::Paragraph => style.paragraph_spacing_scale = self.spacing, - } - styled(&self.body, &ctx, style) - } -} - -/// The different kinds of content that can be spaced. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum ContentKind { - Word, - Line, - Paragraph, -} - -// -------------------------------------------------------------------------- // -// Spacing - -function! { - /// `line.break`, `n`: Ends the current line. - #[derive(Debug, Default, PartialEq)] - pub struct LineBreakFunc; - - parse(default) - layout() { vec![FinishLine] } -} - -function! { - /// `par.break`: Ends the current paragraph. - /// - /// self has the same effect as two subsequent newlines. - #[derive(Debug, Default, PartialEq)] - pub struct ParBreakFunc; - - parse(default) - layout() { vec![BreakParagraph] } -} - -function! { - /// `page.break`: Ends the current page. - #[derive(Debug, Default, PartialEq)] - pub struct PageBreakFunc; - - parse(default) - layout() { vec![BreakPage] } -} - -function! { - /// `spacing`, `h`, `v`: Adds spacing along an axis. - #[derive(Debug, PartialEq)] - pub struct SpacingFunc { - axis: AxisKey, - spacing: FSize, - } - - type Meta = Option; - - parse(header, body, _, meta) { - parse!(forbidden: body); - - if let Some(axis) = meta { - SpacingFunc { - axis: AxisKey::Specific(axis), - spacing: FSize::from_expr( - header.args.get_pos::>()? - )?, - } - } else { - for arg in header.args.iter_keys() { - let axis = AxisKey::from_ident(&arg.key) - .map_err(|_| error!(@unexpected_argument))?; - - let spacing = FSize::from_expr(arg.value)?; - return Ok(SpacingFunc { axis, spacing }); - } - - error!("expected axis and spacing") - } - } - - layout(self, ctx) { - let axis = self.axis.to_generic(ctx.axes); - let spacing = self.spacing.scaled(ctx.style.text.font_size()); - vec![SpacingFunc(spacing, SpacingKind::Hard, axis)] - } -} - -// -------------------------------------------------------------------------- // -// Page setup - -function! { - /// `page.size`: Set the size of pages. - #[derive(Debug, PartialEq)] - pub enum PageSizeFunc { - Paper(Paper, bool), - Custom(ExtentMap), - } - - parse(header, body) { - parse!(forbidden: body); - - if let Some(name) = header.args.get_pos_opt::()? { - let flip = header.args.get_key_opt::("flip")?.unwrap_or(false); - let paper = Paper::from_name(name.as_str()) - .ok_or_else(|| error!(@"invalid paper name: `{}`", name))?; - PageSizeFunc::Paper(paper, flip) - } else { - PageSizeFunc::Custom(ExtentMap::new(&mut header.args, true)?) - } - } - - layout(self, ctx) { - let mut style = ctx.style.page; - - match self { - PageSizeFunc::Paper(paper, flip) => { - style.class = paper.class; - style.dimensions = paper.dimensions; - if *flip { - style.dimensions.swap(); - } - } - - PageSizeFunc::Custom(map) => { - style.class = PaperClass::Custom; - - let map = map.dedup(ctx.axes)?; - let dims = &mut style.dimensions; - map.with(Horizontal, |&psize| dims.x = psize.scaled(dims.x)); - map.with(Vertical, |&psize| dims.y = psize.scaled(dims.y)); - } - } - - vec![SetPageStyle(style)] - } -} - -function! { - /// `page.margins`: Sets the page margins. - #[derive(Debug, PartialEq)] - pub struct PageMarginsFunc { - map: PaddingMap, - } - - parse(header, body) { - parse!(forbidden: body); - PageMarginsFunc { - map: PaddingMap::new(&mut header.args)?, - } - } - - layout(self, ctx) { - let mut style = ctx.style.page; - self.map.apply(ctx.axes, &mut style.margins)?; - vec![SetPageStyle(style)] - } -} - -// -------------------------------------------------------------------------- // -// Helpers - -/// Layout the body with the style or update the style if there is no body. -fn styled<'a>( - body: &'a Option, - ctx: &LayoutContext, - style: TextStyle -) -> Commands<'a> { - match &body { - Some(body) => vec![ - SetTextStyle(style), - LayoutTree(body), - SetTextStyle(ctx.style.text.clone()), - ], - None => vec![SetTextStyle(style)] + } else { + vec![] } } diff --git a/src/library/page.rs b/src/library/page.rs new file mode 100644 index 000000000..25f81bc1b --- /dev/null +++ b/src/library/page.rs @@ -0,0 +1,64 @@ +use crate::style::{Paper, PaperClass}; +use super::*; + + +function! { + /// `page.size`: Set the size of pages. + #[derive(Debug, Clone, PartialEq)] + pub struct PageSizeFunc { + paper: Option, + extents: AxisMap, + flip: bool, + } + + parse(header, body, ctx, errors, decos) { + body!(nope: body, errors); + PageSizeFunc { + paper: header.args.pos.get::(errors), + extents: AxisMap::parse::(errors, &mut header.args.key), + flip: header.args.key.get::(errors, "flip").unwrap_or(false), + } + } + + layout(self, ctx, errors) { + let mut style = ctx.style.page; + + if let Some(paper) = self.paper { + style.class = paper.class; + style.dimensions = paper.dimensions; + } else { + style.class = PaperClass::Custom; + } + + let map = self.extents.dedup(errors, ctx.axes); + map.with(Horizontal, |&width| style.dimensions.x = width); + map.with(Vertical, |&height| style.dimensions.y = height); + + if self.flip { + style.dimensions.swap(); + } + + vec![SetPageStyle(style)] + } +} + +function! { + /// `page.margins`: Sets the page margins. + #[derive(Debug, Clone, PartialEq)] + pub struct PageMarginsFunc { + padding: PaddingMap, + } + + parse(header, body, ctx, errors, decos) { + body!(nope: body, errors); + PageMarginsFunc { + padding: PaddingMap::parse(errors, &mut header.args), + } + } + + layout(self, ctx, errors) { + let mut style = ctx.style.page; + self.padding.apply(errors, ctx.axes, &mut style.margins); + vec![SetPageStyle(style)] + } +} diff --git a/src/library/spacing.rs b/src/library/spacing.rs new file mode 100644 index 000000000..6c8182928 --- /dev/null +++ b/src/library/spacing.rs @@ -0,0 +1,103 @@ +use super::*; +use ContentKind::*; + + +function! { + /// `line.break`, `n`: Ends the current line. + #[derive(Debug, Default, Clone, PartialEq)] + pub struct LineBreakFunc; + + parse(default) + layout(self, ctx, errors) { vec![FinishLine] } +} + +function! { + /// `par.break`: Ends the current paragraph. + /// + /// self has the same effect as two subsequent newlines. + #[derive(Debug, Default, Clone, PartialEq)] + pub struct ParBreakFunc; + + parse(default) + layout(self, ctx, errors) { vec![BreakParagraph] } +} + +function! { + /// `page.break`: Ends the current page. + #[derive(Debug, Default, Clone, PartialEq)] + pub struct PageBreakFunc; + + parse(default) + layout(self, ctx, errors) { vec![BreakPage] } +} + +function! { + /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between + /// words, lines or paragraphs as a multiple of the font size. + #[derive(Debug, Clone, PartialEq)] + pub struct ContentSpacingFunc { + body: Option, + content: ContentKind, + spacing: Option, + } + + type Meta = ContentKind; + + parse(header, body, ctx, errors, decos, meta) { + ContentSpacingFunc { + body: body!(opt: body, ctx, errors, decos), + content: meta, + spacing: header.args.pos.get::(errors) + .map(|num| num as f32) + .or_missing(errors, header.name.span, "spacing"), + } + } + + layout(self, ctx, errors) { + styled(&self.body, ctx, self.spacing, |t, s| match self.content { + Word => t.word_spacing_scale = s, + Line => t.line_spacing_scale = s, + Paragraph => t.paragraph_spacing_scale = s, + }) + } +} + +/// The different kinds of content that can be spaced. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ContentKind { + Word, + Line, + Paragraph, +} + +function! { + /// `spacing`, `h`, `v`: Adds spacing along an axis. + #[derive(Debug, Clone, PartialEq)] + pub struct SpacingFunc { + spacing: Option<(AxisKey, FSize)>, + } + + type Meta = Option; + + parse(header, body, ctx, errors, decos, meta) { + body!(nope: body, errors); + SpacingFunc { + spacing: if let Some(axis) = meta { + header.args.pos.get::(errors) + .map(|s| (AxisKey::Specific(axis), s)) + } else { + header.args.key.get_with_key::(errors) + }.or_missing(errors, header.name.span, "spacing"), + } + } + + layout(self, ctx, errors) { + if let Some((axis, spacing)) = self.spacing { + let axis = axis.to_generic(ctx.axes); + let spacing = spacing.scaled(ctx.style.text.font_size()); + vec![AddSpacing(spacing, SpacingKind::Hard, axis)] + } else { + vec![] + } + } +} diff --git a/src/style.rs b/src/style.rs index aeff96ef6..430deaca8 100644 --- a/src/style.rs +++ b/src/style.rs @@ -156,7 +156,7 @@ pub struct Paper { impl Paper { /// The paper with the given name. - pub fn from_name(name: &str) -> Option { + pub fn from_str(name: &str) -> Option { parse_paper(name) } } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 34a1c6bf4..fe24c655b 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,4 +1,3 @@ -use crate::size::ScaleSize; use super::*; @@ -22,30 +21,13 @@ impl Expr { Str(_) => "string", Number(_) => "number", Size(_) => "size", - Bool(_) => "boolean", + Bool(_) => "bool", Tuple(_) => "tuple", Object(_) => "object", } } } -impl Display for Expr { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use Expr::*; - match self { - Ident(i) => write!(f, "{}", i), - Str(s) => write!(f, "{:?}", s), - Number(n) => write!(f, "{}", n), - Size(s) => write!(f, "{}", s), - Bool(b) => write!(f, "{}", b), - Tuple(t) => write!(f, "{}", t), - Object(o) => write!(f, "{}", o), - } - } -} - -debug_display!(Expr); - /// An identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Ident(pub String); @@ -64,17 +46,6 @@ impl Ident { } } -impl Display for Ident { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -debug_display!(Ident); - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct StringLike(pub String); - /// A sequence of expressions. #[derive(Clone, PartialEq)] pub struct Tuple { @@ -89,27 +60,31 @@ impl Tuple { pub fn add(&mut self, item: Spanned) { self.items.push(item); } -} -impl Display for Tuple { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "(")?; - - let mut first = true; - for item in &self.items { - if !first { - write!(f, ", ")?; + pub fn get(&mut self, errors: &mut Errors) -> Option { + while !self.items.is_empty() { + let expr = self.items.remove(0); + let span = expr.span; + match V::parse(expr) { + Ok(output) => return Some(output), + Err(err) => errors.push(Spanned { v: err, span }), } - write!(f, "{}", item.v)?; - first = false; } + None + } - write!(f, ")") + pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors) + -> impl Iterator + 'a { + self.items.drain(..).filter_map(move |expr| { + let span = expr.span; + match V::parse(expr) { + Ok(output) => Some(output), + Err(err) => { errors.push(Spanned { v: err, span }); None } + } + }) } } -debug_display!(Tuple); - /// A key-value collection of identifiers and associated expressions. #[derive(Clone, PartialEq)] pub struct Object { @@ -128,6 +103,108 @@ impl Object { pub fn add_pair(&mut self, pair: Pair) { self.pairs.push(pair); } + + pub fn get(&mut self, errors: &mut Errors, key: &str) -> Option { + let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?; + self.get_index::(errors, index) + } + + pub fn get_with_key( + &mut self, + errors: &mut Errors, + ) -> Option<(K::Output, V::Output)> { + for (index, pair) in self.pairs.iter().enumerate() { + let key = Spanned { v: pair.key.v.as_str(), span: pair.key.span }; + if let Some(key) = K::parse(key) { + return self.get_index::(errors, index).map(|value| (key, value)); + } + } + None + } + + pub fn get_all<'a, K: Key, V: Value>( + &'a mut self, + errors: &'a mut Errors, + ) -> impl Iterator + 'a { + let mut index = 0; + std::iter::from_fn(move || { + if index < self.pairs.len() { + let key = &self.pairs[index].key; + let key = Spanned { v: key.v.as_str(), span: key.span }; + + Some(if let Some(key) = K::parse(key) { + self.get_index::(errors, index).map(|v| (key, v)) + } else { + index += 1; + None + }) + } else { + None + } + }).filter_map(|x| x) + } + + pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( + &'a mut self, + errors: &'a mut Errors, + ) -> impl Iterator> + 'a { + self.get_all::, Spanned>(errors) + .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) + } + + fn get_index(&mut self, errors: &mut Errors, index: usize) -> Option { + let expr = self.pairs.remove(index).value; + let span = expr.span; + match V::parse(expr) { + Ok(output) => Some(output), + Err(err) => { errors.push(Spanned { v: err, span }); None } + } + } +} + +/// A key-value pair in an object. +#[derive(Clone, PartialEq)] +pub struct Pair { + pub key: Spanned, + pub value: Spanned, +} + +impl Display for Expr { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use Expr::*; + match self { + Ident(i) => write!(f, "{}", i), + Str(s) => write!(f, "{:?}", s), + Number(n) => write!(f, "{}", n), + Size(s) => write!(f, "{}", s), + Bool(b) => write!(f, "{}", b), + Tuple(t) => write!(f, "{}", t), + Object(o) => write!(f, "{}", o), + } + } +} + +impl Display for Ident { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Display for Tuple { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "(")?; + + let mut first = true; + for item in &self.items { + if !first { + write!(f, ", ")?; + } + write!(f, "{}", item.v)?; + first = false; + } + + write!(f, ")") + } } impl Display for Object { @@ -151,71 +228,14 @@ impl Display for Object { } } -debug_display!(Object); - -/// A key-value pair in an object. -#[derive(Clone, PartialEq)] -pub struct Pair { - pub key: Spanned, - pub value: Spanned, -} - impl Display for Pair { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}: {}", self.key.v, self.value.v) } } +debug_display!(Expr); +debug_display!(Ident); +debug_display!(Tuple); +debug_display!(Object); debug_display!(Pair); - -pub trait ExprKind: Sized { - /// The name of the expression in an `expected ` error. - const NAME: &'static str; - - /// Create from expression. - fn from_expr(expr: Spanned) -> Result; -} - -impl ExprKind for Spanned where T: ExprKind { - const NAME: &'static str = T::NAME; - - fn from_expr(expr: Spanned) -> Result { - let span = expr.span; - T::from_expr(expr).map(|v| Spanned { v, span }) - } -} -/// Implements the expression kind trait for a type. -macro_rules! kind { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl ExprKind for $type { - const NAME: &'static str = $name; - - fn from_expr(expr: Spanned) -> Result { - #[allow(unreachable_patterns)] - Ok(match expr.v { - $($p => $r),*, - _ => return Err( - err!("expected {}, found {}", Self::NAME, expr.v.name()) - ), - }) - } - } - }; -} - -kind!(Expr, "expression", e => e); -kind!(Ident, "identifier", Expr::Ident(i) => i); -kind!(String, "string", Expr::Str(s) => s); -kind!(f64, "number", Expr::Number(n) => n); -kind!(bool, "boolean", Expr::Bool(b) => b); -kind!(Size, "size", Expr::Size(s) => s); -kind!(Tuple, "tuple", Expr::Tuple(t) => t); -kind!(Object, "object", Expr::Object(o) => o); -kind!(ScaleSize, "number or size", - Expr::Size(size) => ScaleSize::Absolute(size), - Expr::Number(scale) => ScaleSize::Scaled(scale as f32), -); -kind!(StringLike, "identifier or string", - Expr::Ident(Ident(s)) => StringLike(s), - Expr::Str(s) => StringLike(s), -); diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs new file mode 100644 index 000000000..dff97bde2 --- /dev/null +++ b/src/syntax/func/keys.rs @@ -0,0 +1,121 @@ +use crate::layout::prelude::*; +use super::*; + +use AxisKey::*; +use PaddingKey::*; +use AlignmentValue::*; + + +pub trait Key { + type Output: Eq; + + fn parse(key: Spanned<&str>) -> Option; +} + +impl Key for Spanned { + type Output = Spanned; + + fn parse(key: Spanned<&str>) -> Option { + K::parse(key).map(|v| Spanned { v, span: key.span }) + } +} + +macro_rules! key { + ($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { + impl Key for $type { + type Output = $output; + + fn parse(key: Spanned<&str>) -> Option { + match key.v { + $($($p)|* => Some($r)),*, + other => None, + } + } + } + }; +} + +/// An argument key which identifies a layouting axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AxisKey { + Generic(GenericAxis), + Specific(SpecificAxis), +} + +impl AxisKey { + /// The generic version of this axis key in the given system of axes. + pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { + match self { + Generic(axis) => axis, + Specific(axis) => axis.to_generic(axes), + } + } + + /// The specific version of this axis key in the given system of axes. + pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { + match self { + Generic(axis) => axis.to_specific(axes), + Specific(axis) => axis, + } + } +} + +key!(AxisKey, Self, + "horizontal" | "h" => Specific(Horizontal), + "vertical" | "v" => Specific(Vertical), + "primary" | "p" => Generic(Primary), + "secondary" | "s" => Generic(Secondary), +); + +pub struct ExtentKey; + +key!(ExtentKey, AxisKey, + "width" | "w" => Specific(Horizontal), + "height" | "h" => Specific(Vertical), + "primary-size" | "ps" => Generic(Primary), + "secondary-size" | "ss" => Generic(Secondary), +); + +/// An argument key which identifies an axis, but allows for positional +/// arguments with unspecified axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PosAxisKey { + /// The first positional argument. + First, + /// The second positional argument. + Second, + /// An axis keyword argument. + Keyword(AxisKey), +} + +/// An argument key which identifies a margin or padding target. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PaddingKey { + /// All four sides should have the specified padding. + All, + /// Both sides of the given axis should have the specified padding. + Both(Axis), + /// Only the given side of the given axis should have the specified padding. + Side(Axis, AlignmentValue), +} + +key!(PaddingKey, Self, + "horizontal" | "h" => Both(Specific(Horizontal)), + "vertical" | "v" => Both(Specific(Vertical)), + "primary" | "p" => Both(Generic(Primary)), + "secondary" | "s" => Both(Generic(Secondary)), + + "left" => Side(Specific(Horizontal), Left), + "right" => Side(Specific(Horizontal), Right), + "top" => Side(Specific(Vertical), Top), + "bottom" => Side(Specific(Vertical), Bottom), + + "primary-origin" => Side(Generic(Primary), Align(Origin)), + "primary-end" => Side(Generic(Primary), Align(End)), + "secondary-origin" => Side(Generic(Secondary), Align(Origin)), + "secondary-end" => Side(Generic(Secondary), Align(End)), + "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), + "horizontal-end" => Side(Specific(Horizontal), Align(End)), + "vertical-origin" => Side(Specific(Vertical), Align(Origin)), + "vertical-end" => Side(Specific(Vertical), Align(End)), +); diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs new file mode 100644 index 000000000..452c8ab1d --- /dev/null +++ b/src/syntax/func/maps.rs @@ -0,0 +1,211 @@ +//! Deduplicating maps and keys for argument parsing. + +use std::collections::HashMap; +use std::hash::Hash; +use crate::layout::{LayoutAxes, SpecificAxis, GenericAxis}; +use crate::size::{PSize, ValueBox}; +use super::*; + + +/// A deduplicating map type useful for storing possibly redundant arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct DedupMap where K: Eq { + map: Vec>, +} + +impl DedupMap where K: Eq { + pub fn new() -> DedupMap { + DedupMap { map: vec![] } + } + + pub fn from_iter(errors: &mut Errors, iter: I) -> DedupMap + where I: IntoIterator> { + let mut map = DedupMap::new(); + for Spanned { v: (key, value), span } in iter.into_iter() { + map.insert(errors, key, value, span); + } + map + } + + /// Add a key-value pair. + pub fn insert(&mut self, errors: &mut Errors, key: K, value: V, span: Span) { + if self.map.iter().any(|e| e.v.0 == key) { + errors.push(err!(span; "duplicate argument")); + } else { + self.map.push(Spanned { v: (key, value), span }); + } + } + + /// Add multiple key-value pairs. + pub fn extend(&mut self, errors: &mut Errors, items: I) + where I: IntoIterator> { + for Spanned { v: (k, v), span } in items.into_iter() { + self.insert(errors, k, v, span); + } + } + + /// Get the value corresponding to a key if it is present. + pub fn get(&self, key: K) -> Option<&V> { + self.map.iter().find(|e| e.v.0 == key).map(|e| &e.v.1) + } + + /// Get the value and its span corresponding to a key if it is present. + pub fn get_spanned(&self, key: K) -> Option> { + self.map.iter().find(|e| e.v.0 == key) + .map(|e| Spanned { v: &e.v.1, span: e.span }) + } + + /// Call a function with the value if the key is present. + pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { + if let Some(value) = self.get(key) { + callback(value); + } + } + + /// Create a new map where keys and values are mapped to new keys and + /// values. + /// + /// Returns an error if a new key is duplicate. + pub fn dedup(&self, errors: &mut Errors, mut f: F) -> DedupMap + where F: FnMut(&K, &V) -> (K2, V2), K2: Eq { + let mut map = DedupMap::new(); + + for Spanned { v: (key, value), span } in self.map.iter() { + let (key, value) = f(key, value); + map.insert(errors, key, value, *span); + } + + map + } + + /// Iterate over the (key, value) pairs. + pub fn iter(&self) -> impl Iterator { + self.map.iter().map(|e| &e.v) + } +} + +/// A map for storing a value for two axes given by keyword arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct AxisMap(DedupMap); + +impl AxisMap { + pub fn parse, VT: Value>( + errors: &mut Errors, + object: &mut Object, + ) -> AxisMap { + let values: Vec<_> = object.get_all_spanned::(errors).collect(); + AxisMap(DedupMap::from_iter(errors, values)) + } + + /// Deduplicate from specific or generic to just specific axes. + pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap { + self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone())) + } +} + +/// A map for extracting values for two axes that are given through two +/// positional or keyword arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct PosAxisMap(DedupMap); + +impl PosAxisMap { + pub fn parse, VT: Value>( + errors: &mut Errors, + args: &mut FuncArgs, + ) -> PosAxisMap { + let mut map = DedupMap::new(); + + for &key in &[PosAxisKey::First, PosAxisKey::Second] { + if let Some(value) = args.pos.get::>(errors) { + map.insert(errors, key, value.v, value.span); + } + } + + let keywords: Vec<_> = args.key + .get_all_spanned::(errors) + .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k), v))) + .collect(); + + map.extend(errors, keywords); + + PosAxisMap(map) + } + + /// Deduplicate from positional or specific to generic axes. + pub fn dedup( + &self, + errors: &mut Errors, + axes: LayoutAxes, + mut f: F, + ) -> DedupMap where F: FnMut(&V) -> Option { + self.0.dedup(errors, |key, val| { + (match key { + PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary), + PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary), + PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), + PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, + }, val.clone()) + }) + } +} + +/// A map for extracting padding for a set of specifications given for all +/// sides, opposing sides or single sides. +#[derive(Debug, Clone, PartialEq)] +pub struct PaddingMap(DedupMap, Option>); + +impl PaddingMap { + pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap { + let mut map = DedupMap::new(); + + if let Some(psize) = args.pos.get::>>(errors) { + map.insert(errors, PaddingKey::All, psize.v, psize.span); + } + + let paddings: Vec<_> = args.key + .get_all_spanned::, Defaultable>(errors) + .collect(); + + map.extend(errors, paddings); + + PaddingMap(map) + } + + /// Apply the specified padding on a value box of optional, scalable sizes. + pub fn apply( + &self, + errors: &mut Errors, + axes: LayoutAxes, + padding: &mut ValueBox> + ) { + use PaddingKey::*; + use SpecificAxis::*; + + let map = self.0.dedup(errors, |key, &val| { + (match key { + All => All, + Both(axis) => Both(axis.to_specific(axes)), + Side(axis, alignment) => { + let axis = axis.to_specific(axes); + Side(axis, alignment.to_specific(axes, axis)) + } + }, val) + }); + + map.with(All, |&val| padding.set_all(val)); + map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); + map.with(Both(Vertical), |&val| padding.set_vertical(val)); + + for &(key, val) in map.iter() { + if let Side(_, alignment) = key { + match alignment { + AlignmentValue::Left => padding.left = val, + AlignmentValue::Right => padding.right = val, + AlignmentValue::Top => padding.top = val, + AlignmentValue::Bottom => padding.bottom = val, + _ => {}, + } + } + } + } +} diff --git a/src/syntax/func.rs b/src/syntax/func/mod.rs similarity index 85% rename from src/syntax/func.rs rename to src/syntax/func/mod.rs index abc8c4310..b6691ab5e 100644 --- a/src/syntax/func.rs +++ b/src/syntax/func/mod.rs @@ -1,5 +1,9 @@ use super::*; +pub_use_mod!(maps); +pub_use_mod!(keys); +pub_use_mod!(values); + #[derive(Debug, Clone, PartialEq)] pub struct FuncHeader { @@ -60,6 +64,11 @@ impl FuncArgs { self.key.add_pair(pair); } + pub fn into_iter(self) -> impl Iterator { + self.pos.items.into_iter().map(|item| Arg::Pos(item)) + .chain(self.key.pairs.into_iter().map(|pair| Arg::Key(pair))) + } + // /// Force-extract the first positional argument. // pub fn get_pos(&mut self) -> ParseResult { // expect(self.get_pos_opt()) @@ -123,3 +132,16 @@ impl FuncArgs { // Err(e) => Err(e), // } // } + +pub trait OptionExt: Sized { + fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self; +} + +impl OptionExt for Option { + fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self { + if self.is_none() { + errors.push(err!(span; "missing argument: {}", what)); + } + self + } +} diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs new file mode 100644 index 000000000..b29b97261 --- /dev/null +++ b/src/syntax/func/values.rs @@ -0,0 +1,223 @@ +use std::marker::PhantomData; +use toddle::query::{FontStyle, FontWeight}; + +use crate::layout::prelude::*; +use crate::size::ScaleSize; +use crate::style::Paper; +use super::*; + +use AlignmentValue::*; + + +pub trait Value { + type Output; + + fn parse(expr: Spanned) -> Result; +} + +impl Value for Spanned { + type Output = Spanned; + + fn parse(expr: Spanned) -> Result { + let span = expr.span; + V::parse(expr).map(|v| Spanned { v, span }) + } +} + +macro_rules! value { + ($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { + impl Value for $type { + type Output = $output; + + fn parse(expr: Spanned) -> Result { + #[allow(unreachable_patterns)] + match expr.v { + $($p => Ok($r)),*, + other => Err(err!("expected {}, found {}", + $name, other.name())), + } + } + } + }; +} + +value!(Expr, Self, "expression", e => e); + +value!(Ident, Self, "identifier", Expr::Ident(i) => i); +value!(String, Self, "string", Expr::Str(s) => s); +value!(f64, Self, "number", Expr::Number(n) => n); +value!(bool, Self, "bool", Expr::Bool(b) => b); +value!(Size, Self, "size", Expr::Size(s) => s); +value!(Tuple, Self, "tuple", Expr::Tuple(t) => t); +value!(Object, Self, "object", Expr::Object(o) => o); + +value!(ScaleSize, Self, "number or size", + Expr::Size(size) => ScaleSize::Absolute(size), + Expr::Number(scale) => ScaleSize::Scaled(scale as f32), +); + +pub struct StringLike; + +value!(StringLike, String, "identifier or string", + Expr::Ident(Ident(s)) => s, + Expr::Str(s) => s, +); + +pub struct Defaultable(PhantomData); + +impl Value for Defaultable { + type Output = Option; + + fn parse(expr: Spanned) -> Result { + match expr.v { + Expr::Ident(ident) if ident.as_str() == "default" => Ok(None), + _ => T::parse(expr).map(Some) + } + } +} + +impl Value for Direction { + type Output = Self; + + fn parse(expr: Spanned) -> Result { + Ok(match Ident::parse(expr)?.as_str() { + "left-to-right" | "ltr" | "LTR" => Direction::LeftToRight, + "right-to-left" | "rtl" | "RTL" => Direction::RightToLeft, + "top-to-bottom" | "ttb" | "TTB" => Direction::TopToBottom, + "bottom-to-top" | "btt" | "BTT" => Direction::BottomToTop, + other => return Err(err!("invalid direction")) + }) + } +} + +impl Value for FontStyle { + type Output = Self; + + fn parse(expr: Spanned) -> Result { + FontStyle::from_str(Ident::parse(expr)?.as_str()) + .ok_or_else(|| err!("invalid font style")) + } +} + +impl Value for FontWeight { + type Output = (Self, bool); + + fn parse(expr: Spanned) -> Result { + match expr.v { + Expr::Number(weight) => { + let weight = weight.round(); + + if weight >= 100.0 && weight <= 900.0 { + Ok((FontWeight(weight as i16), false)) + } else { + let clamped = weight.min(900.0).max(100.0) as i16; + Ok((FontWeight(clamped), true)) + } + } + Expr::Ident(id) => { + FontWeight::from_str(id.as_str()) + .ok_or_else(|| err!("invalid font weight")) + .map(|weight| (weight, false)) + } + other => Err(err!("expected identifier or number, \ + found {}", other.name())), + } + } +} + +impl Value for Paper { + type Output = Self; + + fn parse(expr: Spanned) -> Result { + Paper::from_str(Ident::parse(expr)?.as_str()) + .ok_or_else(|| err!("invalid paper type")) + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AlignmentValue { + Align(Alignment), + Left, + Top, + Right, + Bottom, +} + +impl AlignmentValue { + /// The generic axis this alignment corresponds to in the given system of + /// layouting axes. `None` if the alignment is generic. + pub fn axis(self, axes: LayoutAxes) -> Option { + match self { + Left | Right => Some(Horizontal.to_generic(axes)), + Top | Bottom => Some(Vertical.to_generic(axes)), + Align(_) => None, + } + } + + /// The generic version of this alignment in the given system of layouting + /// axes. + /// + /// Returns `None` if the alignment is invalid for the given axis. + pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option { + let specific = axis.to_specific(axes); + let start = match axes.get(axis).is_positive() { + true => Origin, + false => End, + }; + + match (self, specific) { + (Align(alignment), _) => Some(alignment), + (Left, Horizontal) | (Top, Vertical) => Some(start), + (Right, Horizontal) | (Bottom, Vertical) => Some(start.inv()), + _ => None + } + } + + /// The specific version of this alignment in the given system of layouting + /// axes. + pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentValue { + let direction = axes.get_specific(axis); + if let Align(alignment) = self { + match (direction, alignment) { + (LeftToRight, Origin) | (RightToLeft, End) => Left, + (LeftToRight, End) | (RightToLeft, Origin) => Right, + (TopToBottom, Origin) | (BottomToTop, End) => Top, + (TopToBottom, End) | (BottomToTop, Origin) => Bottom, + (_, Center) => self, + } + } else { + self + } + } +} + +impl Value for AlignmentValue { + type Output = Self; + + fn parse(expr: Spanned) -> Result { + Ok(match Ident::parse(expr)?.as_str() { + "origin" => Align(Origin), + "center" => Align(Center), + "end" => Align(End), + "left" => Left, + "top" => Top, + "right" => Right, + "bottom" => Bottom, + other => return Err(err!("invalid alignment")) + }) + } +} + +impl Display for AlignmentValue { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Align(Origin) => write!(f, "origin"), + Align(Center) => write!(f, "center"), + Align(End) => write!(f, "end"), + Left => write!(f, "left"), + Top => write!(f, "top"), + Right => write!(f, "right"), + Bottom => write!(f, "bottom"), + } + } +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index a77c764e9..356535ae4 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -6,7 +6,7 @@ use std::future::Future; use std::pin::Pin; use serde::Serialize; -use crate::error::Error; +use crate::error::{Error, Errors}; use crate::func::{Commands, Command}; use crate::layout::{Layouted, LayoutContext}; use crate::size::Size; diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index ed3430502..e726a2e01 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -12,7 +12,7 @@ pub struct ParseContext<'a> { pub struct Parsed { pub output: T, - pub errors: SpanVec, + pub errors: Errors, pub decorations: SpanVec, } @@ -77,17 +77,17 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { ctx: ParseContext<'s>, - errors: SpanVec, + errors: Errors, decorations: SpanVec, tokens: Tokens<'s>, peeked: Option>>>, - body: Option<(Position, &'s str)>, + body: Option>, } impl<'s> FuncParser<'s> { fn new( header: &'s str, - body: Option<(Position, &'s str)>, + body: Option>, ctx: ParseContext<'s> ) -> FuncParser<'s> { FuncParser { diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 6c8e736c3..d0adbf606 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -23,7 +23,7 @@ pub enum Token<'s> { /// A function invocation `[
][]`. Function { header: &'s str, - body: Option<(Position, &'s str)>, + body: Option>, terminated: bool, }, @@ -222,13 +222,16 @@ impl<'s> Tokens<'s> { return Function { header, body: None, terminated }; } + let body_start = self.pos() - start; self.eat(); - let offset = self.pos() - start; let (body, terminated) = self.read_function_part(); self.eat(); - Function { header, body: Some((offset, body)), terminated } + let body_end = self.pos(); + let span = Span::new(body_start, body_end); + + Function { header, body: Some(Spanned { v: body, span }), terminated } } fn read_function_part(&mut self) -> (&'s str, bool) {