Streamline Key + Value traits 🌊
This commit is contained in:
parent
ec60795575
commit
f655656fb8
@ -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::<StringLike>(errors).collect(),
|
||||
list: header.args.pos.get_all::<StringLike>(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::<Spanned<FontWeight>>(errors)
|
||||
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(errors)
|
||||
.map(|Spanned { v: (weight, is_clamped), span }| {
|
||||
if is_clamped {
|
||||
errors.push(err!(@Warning: span;
|
||||
|
@ -13,7 +13,7 @@ function! {
|
||||
parse(header, body, ctx, errors, decos) {
|
||||
AlignFunc {
|
||||
body: body!(opt: body, ctx, errors, decos),
|
||||
map: PosAxisMap::parse::<AxisKey, AlignmentValue>(errors, &mut header.args),
|
||||
map: PosAxisMap::parse::<AxisKey>(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::<AxisKey, Direction>(errors, &mut header.args),
|
||||
map: PosAxisMap::parse::<AxisKey>(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::<ExtentKey, PSize>(errors, &mut header.args.key),
|
||||
extents: AxisMap::parse::<ExtentKey>(errors, &mut header.args.key),
|
||||
debug: header.args.key.get::<bool>(errors, "debug"),
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ function! {
|
||||
body!(nope: body, errors);
|
||||
PageSizeFunc {
|
||||
paper: header.args.pos.get::<Paper>(errors),
|
||||
extents: AxisMap::parse::<ExtentKey, Size>(errors, &mut header.args.key),
|
||||
extents: AxisMap::parse::<ExtentKey>(errors, &mut header.args.key),
|
||||
flip: header.args.key.get::<bool>(errors, "flip").unwrap_or(false),
|
||||
}
|
||||
}
|
||||
|
@ -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<V: Value>(&mut self, errors: &mut Errors) -> Option<V::Output> {
|
||||
pub fn get<V: Value>(&mut self, errors: &mut Errors) -> Option<V> {
|
||||
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<Item=V::Output> + 'a {
|
||||
-> impl Iterator<Item=V> + '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<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V::Output> {
|
||||
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
|
||||
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
|
||||
self.get_index::<V>(errors, index)
|
||||
}
|
||||
@ -216,7 +216,7 @@ impl Object {
|
||||
pub fn get_with_key<K: Key, V: Value>(
|
||||
&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<Item=(K::Output, V::Output)> + 'a {
|
||||
) -> impl Iterator<Item=(K, V)> + '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<Item=Spanned<(K::Output, V::Output)>> + 'a {
|
||||
) -> impl Iterator<Item=Spanned<(K, V)>> + 'a {
|
||||
self.get_all::<Spanned<K>, Spanned<V>>(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<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V::Output> {
|
||||
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
|
||||
let expr = self.pairs.remove(index).value;
|
||||
let span = expr.span;
|
||||
match V::parse(expr) {
|
||||
|
@ -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<Self::Output> {
|
||||
/// fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
/// 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<Self>;
|
||||
}
|
||||
|
||||
/// Parse a key string into the output type if the string is valid for this
|
||||
/// key.
|
||||
fn parse(key: Spanned<&str>) -> Option<Self::Output>;
|
||||
impl Key for String {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
Some(key.v.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Key> Key for Spanned<K> {
|
||||
type Output = Spanned<K::Output>;
|
||||
|
||||
fn parse(key: Spanned<&str>) -> Option<Self::Output> {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
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<Self::Output> {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
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<ExtentKey> 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<Axis> {
|
||||
Side(Axis, AlignmentValue),
|
||||
}
|
||||
|
||||
key!(PaddingKey<AxisKey>, Self,
|
||||
key!(PaddingKey<AxisKey>,
|
||||
"horizontal" | "h" => Both(Specific(Horizontal)),
|
||||
"vertical" | "v" => Both(Specific(Vertical)),
|
||||
"primary" | "p" => Both(Generic(Primary)),
|
||||
|
@ -95,18 +95,23 @@ impl<K, V> DedupMap<K, V> where K: Eq {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AxisMap<V>(DedupMap<AxisKey, V>);
|
||||
|
||||
impl<V: Clone> AxisMap<V> {
|
||||
impl<V: Value> AxisMap<V> {
|
||||
/// Parse an axis map from the object.
|
||||
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
|
||||
pub fn parse<K>(
|
||||
errors: &mut Errors,
|
||||
object: &mut Object,
|
||||
) -> AxisMap<V> {
|
||||
let values: Vec<_> = object.get_all_spanned::<KT, VT>(errors).collect();
|
||||
) -> AxisMap<V> where K: Key + Into<AxisKey> {
|
||||
let values: Vec<_> = object
|
||||
.get_all_spanned::<K, V>(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<SpecificAxis, V> {
|
||||
pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
|
||||
where V: Clone {
|
||||
self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone()))
|
||||
}
|
||||
}
|
||||
@ -116,23 +121,23 @@ impl<V: Clone> AxisMap<V> {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
|
||||
|
||||
impl<V: Clone> PosAxisMap<V> {
|
||||
impl<V: Value> PosAxisMap<V> {
|
||||
/// Parse a positional/axis map from the function arguments.
|
||||
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
|
||||
pub fn parse<K>(
|
||||
errors: &mut Errors,
|
||||
args: &mut FuncArgs,
|
||||
) -> PosAxisMap<V> {
|
||||
) -> PosAxisMap<V> where K: Key + Into<AxisKey> {
|
||||
let mut map = DedupMap::new();
|
||||
|
||||
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
|
||||
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<VT>>(errors) {
|
||||
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(errors) {
|
||||
map.insert(errors, Spanned { v: (key, v), span })
|
||||
}
|
||||
}
|
||||
|
||||
let keywords: Vec<_> = args.key
|
||||
.get_all_spanned::<KT, VT>(errors)
|
||||
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k), v)))
|
||||
.get_all_spanned::<K, V>(errors)
|
||||
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v)))
|
||||
.collect();
|
||||
|
||||
map.extend(errors, keywords);
|
||||
@ -147,7 +152,11 @@ impl<V: Clone> PosAxisMap<V> {
|
||||
errors: &mut Errors,
|
||||
axes: LayoutAxes,
|
||||
mut f: F,
|
||||
) -> DedupMap<GenericAxis, V> where F: FnMut(&V) -> Option<GenericAxis> {
|
||||
) -> DedupMap<GenericAxis, V>
|
||||
where
|
||||
F: FnMut(&V) -> Option<GenericAxis>,
|
||||
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::<Spanned<Defaultable<PSize>>>(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::<PaddingKey<AxisKey>, Defaultable<PSize>>(errors)
|
||||
.map(|s| s.map(|(k, v)| (k, v.into())))
|
||||
.collect();
|
||||
|
||||
map.extend(errors, paddings);
|
||||
|
@ -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<Expr>) -> Result<Self::Output, Error> {
|
||||
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
/// 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<V>` 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::<Defaultable<Size>>(&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<V>` 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<Expr>) -> Result<Self::Output, Error>;
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
impl<V: Value> Value for Spanned<V> {
|
||||
type Output = Spanned<V::Output>;
|
||||
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
let span = expr.span;
|
||||
V::parse(expr).map(|v| Spanned { v, span })
|
||||
}
|
||||
@ -85,11 +56,9 @@ impl<V: Value> Value for Spanned<V> {
|
||||
|
||||
/// 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<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
#[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<String>`.
|
||||
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<StringLike> 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<V>(PhantomData<V>);
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use typstc::syntax::func::{FuncArgs, Defaultable};
|
||||
/// # use typstc::size::Size;
|
||||
/// # let mut args = FuncArgs::new();
|
||||
/// # let mut errors = vec![];
|
||||
/// args.key.get::<Defaultable<Size>>(&mut errors, "size");
|
||||
/// ```
|
||||
/// This will yield.
|
||||
/// ```typst
|
||||
/// [func: size=default] => None
|
||||
/// [func: size=2cm] => Some(Size::cm(2.0))
|
||||
/// ```
|
||||
pub struct Defaultable<V>(Option<V>);
|
||||
|
||||
impl<V: Value> Value for Defaultable<V> {
|
||||
type Output = Option<V::Output>;
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
Ok(Defaultable(match expr.v {
|
||||
Expr::Ident(ident) if ident.as_str() == "default" => None,
|
||||
_ => Some(V::parse(expr)?)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
||||
match expr.v {
|
||||
Expr::Ident(ident) if ident.as_str() == "default" => Ok(None),
|
||||
_ => V::parse(expr).map(Some)
|
||||
}
|
||||
impl<V> From<Defaultable<V>> for Option<V> {
|
||||
fn from(defaultable: Defaultable<V>) -> Option<V> {
|
||||
defaultable.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for FontStyle {
|
||||
type Output = Self;
|
||||
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
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<Expr>) -> Result<Self::Output, Error> {
|
||||
/// 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<Expr>) -> Result<Self, Error> {
|
||||
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<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
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<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
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<Expr>) -> Result<Self::Output, Error> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
|
||||
Ok(match Ident::parse(expr)?.as_str() {
|
||||
"origin" => Align(Origin),
|
||||
"center" => Align(Center),
|
||||
|
Loading…
x
Reference in New Issue
Block a user