summaryrefslogtreecommitdiff
path: root/src/syntax/func/values.rs
blob: 3269f8e946a2a81eba2d928a94343e10580f6ade (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//! Value types for extracting function arguments.

use std::fmt::{self, Display, Formatter};
use toddle::query::{FontStyle, FontWeight};

use crate::layout::prelude::*;
use crate::length::{Length, ScaleLength};
use crate::paper::Paper;
use super::*;

use self::AlignmentValue::*;

/// Value types are used to extract the values of positional and keyword
/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and
/// [`Objects`](crate::syntax::expr::Object). They represent the value part of
/// an argument.
/// ```typst
/// [func: value, key=value]
///        ^^^^^      ^^^^^
/// ```
///
/// # Example implementation
/// An implementation for `bool` might look as follows:
/// ```
/// # use typstc::error;
/// # use typstc::diagnostic::Diagnostic;
/// # use typstc::syntax::expr::Expr;
/// # use typstc::syntax::func::Value;
/// # use typstc::syntax::span::Spanned;
/// # struct Bool; /*
/// impl Value for bool {
/// # */ impl Value for Bool {
///     fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
///         match expr.v {
///             # /*
///             Expr::Bool(b) => Ok(b),
///             # */ Expr::Bool(_) => Ok(Bool),
///             other => Err(error!("expected bool, found {}", other.name())),
///         }
///     }
/// }
/// ```
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, Diagnostic>;
}

impl<V: Value> Value for Spanned<V> {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        let span = expr.span;
        V::parse(expr).map(|v| Spanned { v, span })
    }
}

/// Implements [`Value`] for types that just need to match on expressions.
macro_rules! value {
    ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
        impl Value for $type {
            fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
                #[allow(unreachable_patterns)]
                match expr.v {
                    $($p => Ok($r)),*,
                    other => Err(
                        error!("expected {}, found {}", $name, other.name())
                    ),
                }
            }
        }
    };
}

value!(Expr,   "expression", e => e);

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!(Length, "length",     Expr::Length(s)   => s);
value!(Tuple,  "tuple",      Expr::Tuple(t)  => t);
value!(Object, "object",     Expr::Object(o) => o);

value!(ScaleLength, "number or length",
    Expr::Length(length)    => ScaleLength::Absolute(length),
    Expr::Number(scale) => ScaleLength::Scaled(scale as f64),
);

/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
/// `Into<String>`.
pub struct StringLike(pub String);

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 identifier `default` or a value type `V` and
/// implements `Into<Option>` yielding `Option::Some(V)` for a value and
/// `Option::None` for `default`.
///
/// # Example
/// ```
/// # use typstc::syntax::func::{FuncArgs, Defaultable};
/// # use typstc::length::Length;
/// # let mut args = FuncArgs::new();
/// # let mut errors = vec![];
/// args.key.get::<Defaultable<Length>>(&mut errors, "length");
/// ```
/// This will yield.
/// ```typst
/// [func: length=default] => None
/// [func: length=2cm]     => Some(Length::cm(2.0))
/// ```
pub struct Defaultable<V>(pub Option<V>);

impl<V: Value> Value for Defaultable<V> {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        Ok(Defaultable(match expr.v {
            Expr::Ident(ident) if ident.as_str() == "default" => None,
            _ => Some(V::parse(expr)?)
        }))
    }
}

impl<V> From<Defaultable<V>> for Option<V> {
    fn from(defaultable: Defaultable<V>) -> Option<V> {
        defaultable.0
    }
}

impl Value for FontStyle {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        FontStyle::from_name(Ident::parse(expr)?.as_str())
            .ok_or_else(|| error!("invalid font style"))
    }
}

/// 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, Diagnostic> {
        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_name(id.as_str())
                    .ok_or_else(|| error!("invalid font weight"))
                    .map(|weight| (weight, false))
            }
            other => Err(
                error!("expected identifier or number, found {}", other.name())
            ),
        }
    }
}

impl Value for Paper {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        Paper::from_name(Ident::parse(expr)?.as_str())
            .ok_or_else(|| error!("invalid paper type"))
    }
}

impl Value for Direction {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        Ok(match Ident::parse(expr)?.as_str() {
            "left-to-right" | "ltr" | "LTR" => LeftToRight,
            "right-to-left" | "rtl" | "RTL" => RightToLeft,
            "top-to-bottom" | "ttb" | "TTB" => TopToBottom,
            "bottom-to-top" | "btt" | "BTT" => BottomToTop,
            _ => return Err(error!("invalid direction"))
        })
    }
}

/// A value type that matches identifiers that are valid alignments like
/// `origin` or `right`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum AlignmentValue {
    /// A generic alignment.
    Align(Alignment),
    Left,
    Top,
    Right,
    Bottom,
}

impl AlignmentValue {
    /// The specific axis this alignment corresponds to. `None` if the alignment
    /// is generic.
    pub fn axis(self) -> Option<SpecificAxis> {
        match self {
            Left | Right => Some(Horizontal),
            Top | Bottom => Some(Vertical),
            Align(_) => None,
        }
    }

    /// The generic version of this alignment on the given axis 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<Alignment> {
        let specific = axis.to_specific(axes);
        let positive = axes.get(axis).is_positive();

        // The alignment matching the origin of the positive coordinate direction.
        let start = if positive { Origin } else { 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 on the given axis in the given
    /// system of layouting axes.
    pub fn to_specific(self, axes: LayoutAxes, axis: GenericAxis) -> AlignmentValue {
        let direction = axes.get(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 {
    fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
        Ok(match Ident::parse(expr)?.as_str() {
            "origin" => Align(Origin),
            "center" => Align(Center),
            "end"    => Align(End),
            "left"   => Left,
            "top"    => Top,
            "right"  => Right,
            "bottom" => Bottom,
            _ => return Err(error!("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"),
        }
    }
}