diff --git a/src/library/font.rs b/src/library/font.rs index 8e62563e8..be4d263ff 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -14,7 +14,9 @@ function! { parse(header, body, ctx, errors, decos) { FontFamilyFunc { body: body!(opt: body, ctx, errors, decos), - list: header.args.pos.get_all::(errors).collect(), + list: header.args.pos.get_all::(errors) + .map(Into::into) + .collect(), } } @@ -58,7 +60,7 @@ function! { parse(header, body, ctx, errors, decos) { let body = body!(opt: body, ctx, errors, decos); - let weight = header.args.pos.get::>(errors) + let weight = header.args.pos.get::>(errors) .map(|Spanned { v: (weight, is_clamped), span }| { if is_clamped { errors.push(err!(@Warning: span; diff --git a/src/library/layout.rs b/src/library/layout.rs index d36cd0e07..591ea2c0c 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -13,7 +13,7 @@ function! { parse(header, body, ctx, errors, decos) { AlignFunc { body: body!(opt: body, ctx, errors, decos), - map: PosAxisMap::parse::(errors, &mut header.args), + map: PosAxisMap::parse::(errors, &mut header.args), } } @@ -59,7 +59,7 @@ function! { DirectionFunc { name_span: header.name.span, body: body!(opt: body, ctx, errors, decos), - map: PosAxisMap::parse::(errors, &mut header.args), + map: PosAxisMap::parse::(errors, &mut header.args), } } @@ -106,7 +106,7 @@ function! { 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), + extents: AxisMap::parse::(errors, &mut header.args.key), debug: header.args.key.get::(errors, "debug"), } } diff --git a/src/library/page.rs b/src/library/page.rs index e2a4d687d..084b34469 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -16,7 +16,7 @@ function! { body!(nope: body, errors); PageSizeFunc { paper: header.args.pos.get::(errors), - extents: AxisMap::parse::(errors, &mut header.args.key), + extents: AxisMap::parse::(errors, &mut header.args.key), flip: header.args.key.get::(errors, "flip").unwrap_or(false), } } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index e5c9489e4..2f2eb68ea 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -119,7 +119,7 @@ impl Tuple { /// Extract (and remove) the first matching value and remove and generate /// errors for all previous items that did not match. - pub fn get(&mut self, errors: &mut Errors) -> Option { + pub fn get(&mut self, errors: &mut Errors) -> Option { while !self.items.is_empty() { let expr = self.items.remove(0); let span = expr.span; @@ -134,7 +134,7 @@ impl Tuple { /// Extract and return an iterator over all values that match and generate /// errors for all items that do not match. pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors) - -> impl Iterator + 'a { + -> impl Iterator + 'a { self.items.drain(..).filter_map(move |expr| { let span = expr.span; match V::parse(expr) { @@ -204,7 +204,7 @@ impl Object { /// /// Inserts an error if the value does not match. If the key is not /// contained, no error is inserted. - pub fn get(&mut self, errors: &mut Errors, key: &str) -> Option { + 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) } @@ -216,7 +216,7 @@ impl Object { pub fn get_with_key( &mut self, errors: &mut Errors, - ) -> Option<(K::Output, V::Output)> { + ) -> Option<(K, V)> { 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) { @@ -232,7 +232,7 @@ impl Object { pub fn get_all<'a, K: Key, V: Value>( &'a mut self, errors: &'a mut Errors, - ) -> impl Iterator + 'a { + ) -> impl Iterator + 'a { let mut index = 0; std::iter::from_fn(move || { if index < self.pairs.len() { @@ -261,14 +261,14 @@ impl Object { pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( &'a mut self, errors: &'a mut Errors, - ) -> impl Iterator> + 'a { + ) -> impl Iterator> + 'a { self.get_all::, Spanned>(errors) .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) } /// Extract the argument at the given index and insert an error if the value /// does not match. - fn get_index(&mut self, errors: &mut Errors, index: usize) -> Option { + 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) { diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs index d77447c8c..cb12ee1b5 100644 --- a/src/syntax/func/keys.rs +++ b/src/syntax/func/keys.rs @@ -16,9 +16,9 @@ use self::PaddingKey::*; /// ^^^ /// ``` /// -/// A key type has an associated output type, which is returned when parsing -/// this key from a string. Most of the time, the output type is simply the key -/// itself, as in the implementation for the [`AxisKey`]: +/// # Example implementation +/// An implementation for the `AxisKey` that identifies layouting axes might +/// look as follows: /// ``` /// # use typstc::syntax::func::Key; /// # use typstc::syntax::span::Spanned; @@ -27,9 +27,7 @@ use self::PaddingKey::*; /// # use Axis::*; /// # use AxisKey::*; /// impl Key for AxisKey { -/// type Output = Self; -/// -/// fn parse(key: Spanned<&str>) -> Option { +/// fn parse(key: Spanned<&str>) -> Option { /// match key.v { /// "horizontal" | "h" => Some(Specific(Horizontal)), /// "vertical" | "v" => Some(Specific(Vertical)), @@ -40,41 +38,28 @@ use self::PaddingKey::*; /// } /// } /// ``` -/// -/// The axis key would also be useful to identify axes when describing -/// dimensions of objects, as in `width=3cm`, because these are also properties -/// that are stored per axis. However, here the used keyword arguments are -/// actually different (`width` instead of `horizontal`)! Therefore we cannot -/// just use the axis key. -/// -/// To fix this, there is another type [`ExtentKey`] which implements `Key` and -/// has the associated output type axis key. The extent key struct itself has no -/// fields and is only used to extract the axis key. This way, we can specify -/// which argument kind we want without duplicating the type in the background. -pub trait Key { - /// The type to parse into. - type Output: Eq; +pub trait Key: Sized + Eq { + /// Parse a key string into this type if it is valid for it. + fn parse(key: Spanned<&str>) -> Option; +} - /// Parse a key string into the output type if the string is valid for this - /// key. - fn parse(key: Spanned<&str>) -> Option; +impl Key for String { + fn parse(key: Spanned<&str>) -> Option { + Some(key.v.to_string()) + } } impl Key for Spanned { - type Output = Spanned; - - fn parse(key: Spanned<&str>) -> Option { + fn parse(key: Spanned<&str>) -> Option { K::parse(key).map(|v| Spanned { v, span: key.span }) } } /// Implements [`Key`] for types that just need to match on strings. macro_rules! key { - ($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { + ($type:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { impl Key for $type { - type Output = $output; - - fn parse(key: Spanned<&str>) -> Option { + fn parse(key: Spanned<&str>) -> Option { match key.v { $($($p)|* => Some($r)),*, _ => None, @@ -110,24 +95,31 @@ impl AxisKey { } } -key!(AxisKey, Self, +key!(AxisKey, "horizontal" | "h" => Specific(Horizontal), "vertical" | "v" => Specific(Vertical), "primary" | "p" => Generic(Primary), "secondary" | "s" => Generic(Secondary), ); -/// A key which parses into an [`AxisKey`] but uses typical extent keywords +/// A key which is equivalent to a [`AxisKey`] but uses typical extent keywords /// instead of axis keywords, e.g. `width` instead of `horizontal`. -pub struct ExtentKey; +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct ExtentKey(AxisKey); -key!(ExtentKey, AxisKey, - "width" | "w" => Specific(Horizontal), - "height" | "h" => Specific(Vertical), - "primary-size" | "ps" => Generic(Primary), - "secondary-size" | "ss" => Generic(Secondary), +key!(ExtentKey, + "width" | "w" => ExtentKey(Specific(Horizontal)), + "height" | "h" => ExtentKey(Specific(Vertical)), + "primary-size" | "ps" => ExtentKey(Generic(Primary)), + "secondary-size" | "ss" => ExtentKey(Generic(Secondary)), ); +impl From for AxisKey { + fn from(key: ExtentKey) -> AxisKey { + key.0 + } +} + /// A key which identifies an axis, but alternatively allows for two positional /// arguments with unspecified axes. /// @@ -156,7 +148,7 @@ pub enum PaddingKey { Side(Axis, AlignmentValue), } -key!(PaddingKey, Self, +key!(PaddingKey, "horizontal" | "h" => Both(Specific(Horizontal)), "vertical" | "v" => Both(Specific(Vertical)), "primary" | "p" => Both(Generic(Primary)), diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs index 691b3d36a..8143d0a5c 100644 --- a/src/syntax/func/maps.rs +++ b/src/syntax/func/maps.rs @@ -95,18 +95,23 @@ impl DedupMap where K: Eq { #[derive(Debug, Clone, PartialEq)] pub struct AxisMap(DedupMap); -impl AxisMap { +impl AxisMap { /// Parse an axis map from the object. - pub fn parse, VT: Value>( + pub fn parse( errors: &mut Errors, object: &mut Object, - ) -> AxisMap { - let values: Vec<_> = object.get_all_spanned::(errors).collect(); + ) -> AxisMap where K: Key + Into { + let values: Vec<_> = object + .get_all_spanned::(errors) + .map(|s| s.map(|(k, v)| (k.into(), v))) + .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 { + pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap + where V: Clone { self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone())) } } @@ -116,23 +121,23 @@ impl AxisMap { #[derive(Debug, Clone, PartialEq)] pub struct PosAxisMap(DedupMap); -impl PosAxisMap { +impl PosAxisMap { /// Parse a positional/axis map from the function arguments. - pub fn parse, VT: Value>( + pub fn parse( errors: &mut Errors, args: &mut FuncArgs, - ) -> PosAxisMap { + ) -> PosAxisMap where K: Key + Into { let mut map = DedupMap::new(); for &key in &[PosAxisKey::First, PosAxisKey::Second] { - if let Some(Spanned { v, span }) = args.pos.get::>(errors) { + if let Some(Spanned { v, span }) = args.pos.get::>(errors) { map.insert(errors, Spanned { v: (key, v), span }) } } let keywords: Vec<_> = args.key - .get_all_spanned::(errors) - .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k), v))) + .get_all_spanned::(errors) + .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v))) .collect(); map.extend(errors, keywords); @@ -147,7 +152,11 @@ impl PosAxisMap { errors: &mut Errors, axes: LayoutAxes, mut f: F, - ) -> DedupMap where F: FnMut(&V) -> Option { + ) -> DedupMap + where + F: FnMut(&V) -> Option, + V: Clone, + { self.0.dedup(errors, |key, val| { (match key { PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary), @@ -171,11 +180,12 @@ impl PaddingMap { let all = args.pos.get::>>(errors); if let Some(Spanned { v, span }) = all { - map.insert(errors, Spanned { v: (PaddingKey::All, v), span }); + map.insert(errors, Spanned { v: (PaddingKey::All, v.into()), span }); } let paddings: Vec<_> = args.key .get_all_spanned::, Defaultable>(errors) + .map(|s| s.map(|(k, v)| (k, v.into()))) .collect(); map.extend(errors, paddings); diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs index e4a0b2ad1..18d451e3f 100644 --- a/src/syntax/func/values.rs +++ b/src/syntax/func/values.rs @@ -1,7 +1,6 @@ //! Value types for extracting function arguments. use std::fmt::{self, Display, Formatter}; -use std::marker::PhantomData; use toddle::query::{FontStyle, FontWeight}; use crate::layout::prelude::*; @@ -21,9 +20,8 @@ use self::AlignmentValue::*; /// ^^^^^ ^^^^^ /// ``` /// -/// Similarly to the [`Key`] trait, this trait has an associated output type -/// which the values are parsed into. Most of the time this is just `Self`, as -/// in the implementation for `bool`: +/// # Example implementation +/// An implementation for `bool` might look as follows: /// ``` /// # use typstc::err; /// # use typstc::error::Error; @@ -33,51 +31,24 @@ use self::AlignmentValue::*; /// # struct Bool; /* /// impl Value for bool { /// # */ impl Value for Bool { -/// # type Output = bool; /* -/// type Output = Self; -/// # */ -/// -/// fn parse(expr: Spanned) -> Result { +/// fn parse(expr: Spanned) -> Result { /// match expr.v { +/// # /* /// Expr::Bool(b) => Ok(b), +/// # */ Expr::Bool(_) => Ok(Bool), /// other => Err(err!("expected bool, found {}", other.name())), /// } /// } /// } /// ``` -/// -/// However, sometimes the `Output` type is not just `Self`. For example, there -/// is a value called `Defaultable` which acts as follows: -/// ``` -/// # use typstc::syntax::func::{FuncArgs, Defaultable}; -/// # use typstc::size::Size; -/// # let mut args = FuncArgs::new(); -/// # let mut errors = vec![]; -/// args.key.get::>(&mut errors, "size"); -/// ``` -/// This will yield. -/// ```typst -/// [func: size=2cm] => Some(Size::cm(2.0)) -/// [func: size=default] => None -/// ``` -/// -/// The type `Defaultable` has no fields and is only used for extracting the -/// option value. This prevents us from having a `Defaultable` type which is -/// essentially simply a bad [`Option`] replacement without the good utility -/// functions. -pub trait Value { - /// The type to parse into. - type Output; - +pub trait Value: Sized { /// Parse an expression into this value or return an error if the expression /// is valid for this value type. - fn parse(expr: Spanned) -> Result; + fn parse(expr: Spanned) -> Result; } impl Value for Spanned { - type Output = Spanned; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { let span = expr.span; V::parse(expr).map(|v| Spanned { v, span }) } @@ -85,11 +56,9 @@ impl Value for Spanned { /// Implements [`Value`] for types that just need to match on expressions. macro_rules! value { - ($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { + ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { impl Value for $type { - type Output = $output; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { #[allow(unreachable_patterns)] match expr.v { $($p => Ok($r)),*, @@ -101,59 +70,81 @@ macro_rules! value { }; } -value!(Expr, Self, "expression", e => e); +value!(Expr, "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!(Ident, "identifier", Expr::Ident(i) => i); +value!(String, "string", Expr::Str(s) => s); +value!(f64, "number", Expr::Number(n) => n); +value!(bool, "bool", Expr::Bool(b) => b); +value!(Size, "size", Expr::Size(s) => s); +value!(Tuple, "tuple", Expr::Tuple(t) => t); +value!(Object, "object", Expr::Object(o) => o); -value!(ScaleSize, Self, "number or size", +value!(ScaleSize, "number or size", Expr::Size(size) => ScaleSize::Absolute(size), Expr::Number(scale) => ScaleSize::Scaled(scale as f32), ); -/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and returns a -/// String. -pub struct StringLike; +/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements +/// `Into`. +pub struct StringLike(String); -value!(StringLike, String, "identifier or string", - Expr::Ident(Ident(s)) => s, - Expr::Str(s) => s, +value!(StringLike, "identifier or string", + Expr::Ident(Ident(s)) => StringLike(s), + Expr::Str(s) => StringLike(s), ); +impl From for String { + fn from(like: StringLike) -> String { + like.0 + } +} + /// A value type that matches the string `"default"` or a value type `V` and /// returns `Option::Some(V::Output)` for a value and `Option::None` for /// `"default"`. -pub struct Defaultable(PhantomData); +/// +/// # Example +/// ``` +/// # use typstc::syntax::func::{FuncArgs, Defaultable}; +/// # use typstc::size::Size; +/// # let mut args = FuncArgs::new(); +/// # let mut errors = vec![]; +/// args.key.get::>(&mut errors, "size"); +/// ``` +/// This will yield. +/// ```typst +/// [func: size=default] => None +/// [func: size=2cm] => Some(Size::cm(2.0)) +/// ``` +pub struct Defaultable(Option); impl Value for Defaultable { - type Output = Option; + fn parse(expr: Spanned) -> Result { + Ok(Defaultable(match expr.v { + Expr::Ident(ident) if ident.as_str() == "default" => None, + _ => Some(V::parse(expr)?) + })) + } +} - fn parse(expr: Spanned) -> Result { - match expr.v { - Expr::Ident(ident) if ident.as_str() == "default" => Ok(None), - _ => V::parse(expr).map(Some) - } +impl From> for Option { + fn from(defaultable: Defaultable) -> Option { + defaultable.0 } } impl Value for FontStyle { - type Output = Self; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { FontStyle::from_name(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 { +/// The additional boolean specifies whether a number was clamped into the range +/// 100 - 900 to make it a valid font weight. +impl Value for (FontWeight, bool) { + fn parse(expr: Spanned) -> Result { match expr.v { Expr::Number(weight) => { let weight = weight.round(); @@ -177,18 +168,14 @@ impl Value for FontWeight { } impl Value for Paper { - type Output = Self; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { Paper::from_name(Ident::parse(expr)?.as_str()) .ok_or_else(|| err!("invalid paper type")) } } impl Value for Direction { - type Output = Self; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { Ok(match Ident::parse(expr)?.as_str() { "left-to-right" | "ltr" | "LTR" => LeftToRight, "right-to-left" | "rtl" | "RTL" => RightToLeft, @@ -261,9 +248,7 @@ impl AlignmentValue { } impl Value for AlignmentValue { - type Output = Self; - - fn parse(expr: Spanned) -> Result { + fn parse(expr: Spanned) -> Result { Ok(match Ident::parse(expr)?.as_str() { "origin" => Align(Origin), "center" => Align(Center),