Skip to main content

style/values/specified/
calc.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! [Calc expressions][calc].
6//!
7//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
8
9use crate::color::parsing::ChannelKeyword;
10use crate::derives::*;
11use crate::parser::{Parse, ParserContext};
12use crate::values::generics::calc::{
13    self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis,
14    RoundingStrategy, SortKey,
15};
16use crate::values::generics::length::GenericAnchorSizeFunction;
17use crate::values::generics::position::{
18    AnchorSideKeyword, GenericAnchorFunction, GenericAnchorSide, TreeScoped,
19};
20use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
21use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength};
22use crate::values::specified::{self, Angle, Resolution, Time};
23use crate::values::{serialize_number, serialize_percentage, CSSFloat, DashedIdent};
24use cssparser::{match_ignore_ascii_case, CowRcStr, Parser, Token};
25use debug_unreachable::debug_unreachable;
26use smallvec::SmallVec;
27use std::cmp;
28use std::fmt::{self, Write};
29use style_traits::values::specified::AllowedNumericType;
30use style_traits::{
31    CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, ToTyped, TypedValue,
32};
33
34/// The name of the mathematical function that we're parsing.
35#[derive(Clone, Copy, Debug, Parse)]
36pub enum MathFunction {
37    /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
38    Calc,
39    /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min
40    Min,
41    /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max
42    Max,
43    /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
44    Clamp,
45    /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round
46    Round,
47    /// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod
48    Mod,
49    /// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem
50    Rem,
51    /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
52    Sin,
53    /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
54    Cos,
55    /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan
56    Tan,
57    /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin
58    Asin,
59    /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos
60    Acos,
61    /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
62    Atan,
63    /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2
64    Atan2,
65    /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow
66    Pow,
67    /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt
68    Sqrt,
69    /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot
70    Hypot,
71    /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log
72    Log,
73    /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp
74    Exp,
75    /// `abs()`: https://drafts.csswg.org/css-values-4/#funcdef-abs
76    Abs,
77    /// `sign()`: https://drafts.csswg.org/css-values-4/#funcdef-sign
78    Sign,
79}
80
81/// A leaf node inside a `Calc` expression's AST.
82#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
83#[repr(u8)]
84pub enum Leaf {
85    /// `<length>`
86    Length(NoCalcLength),
87    /// `<angle>`
88    Angle(Angle),
89    /// `<time>`
90    Time(Time),
91    /// `<resolution>`
92    Resolution(Resolution),
93    /// A component of a color.
94    ColorComponent(ChannelKeyword),
95    /// `<percentage>`
96    Percentage(CSSFloat),
97    /// `<number>`
98    Number(CSSFloat),
99}
100
101impl Leaf {
102    fn as_length(&self) -> Option<&NoCalcLength> {
103        match *self {
104            Self::Length(ref l) => Some(l),
105            _ => None,
106        }
107    }
108}
109
110impl ToCss for Leaf {
111    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
112    where
113        W: Write,
114    {
115        match *self {
116            Self::Length(ref l) => l.to_css(dest),
117            Self::Number(n) => serialize_number(n, /* was_calc = */ false, dest),
118            Self::Resolution(ref r) => r.to_css(dest),
119            Self::Percentage(p) => serialize_percentage(p, dest),
120            Self::Angle(ref a) => a.to_css(dest),
121            Self::Time(ref t) => t.to_css(dest),
122            Self::ColorComponent(ref s) => s.to_css(dest),
123        }
124    }
125}
126
127impl ToTyped for Leaf {
128    fn to_typed(&self) -> Option<TypedValue> {
129        // XXX Only supporting Length for now
130        match *self {
131            Self::Length(ref l) => l.to_typed(),
132            _ => None,
133        }
134    }
135}
136
137/// A struct to hold a simplified `<length>` or `<percentage>` expression.
138///
139/// In some cases, e.g. DOMMatrix, we support calc(), but reject all the
140/// relative lengths, and to_computed_pixel_length_without_context() handles
141/// this case. Therefore, if you want to add a new field, please make sure this
142/// function work properly.
143#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem, ToTyped)]
144#[allow(missing_docs)]
145#[typed_value(derive_fields)]
146pub struct CalcLengthPercentage {
147    #[css(skip)]
148    pub clamping_mode: AllowedNumericType,
149    pub node: CalcNode,
150}
151
152impl CalcLengthPercentage {
153    fn same_unit_length_as(a: &Self, b: &Self) -> Option<(CSSFloat, CSSFloat)> {
154        debug_assert_eq!(a.clamping_mode, b.clamping_mode);
155        debug_assert_eq!(a.clamping_mode, AllowedNumericType::All);
156
157        let a = a.node.as_leaf()?;
158        let b = b.node.as_leaf()?;
159
160        if a.sort_key() != b.sort_key() {
161            return None;
162        }
163
164        let a = a.as_length()?.unitless_value();
165        let b = b.as_length()?.unitless_value();
166        return Some((a, b));
167    }
168}
169
170impl SpecifiedValueInfo for CalcLengthPercentage {}
171
172/// Should parsing anchor-positioning functions in `calc()` be allowed?
173#[derive(Clone, Copy, PartialEq)]
174pub enum AllowAnchorPositioningFunctions {
175    /// Don't allow any anchor positioning function.
176    No,
177    /// Allow `anchor-size()` to be parsed.
178    AllowAnchorSize,
179    /// Allow `anchor()` and `anchor-size()` to be parsed.
180    AllowAnchorAndAnchorSize,
181}
182
183bitflags! {
184    /// Additional functions within math functions that are permitted to be parsed depending on
185    /// the context of parsing (e.g. Parsing `inset` allows use of `anchor()` within `calc()`).
186    #[derive(Clone, Copy, PartialEq, Eq)]
187    struct AdditionalFunctions: u8 {
188        /// `anchor()` function.
189        const ANCHOR = 1 << 0;
190        /// `anchor-size()` function.
191        const ANCHOR_SIZE = 1 << 1;
192    }
193}
194
195/// What is allowed to be parsed for math functions within in this context?
196#[derive(Clone, Copy)]
197pub struct AllowParse {
198    /// Units allowed to be parsed.
199    units: CalcUnits,
200    /// Additional functions allowed to be parsed in this context.
201    additional_functions: AdditionalFunctions,
202}
203
204impl AllowParse {
205    /// Allow only specified units to be parsed, without any additional functions.
206    pub fn new(units: CalcUnits) -> Self {
207        Self {
208            units,
209            additional_functions: AdditionalFunctions::empty(),
210        }
211    }
212
213    /// Add new units to the allowed units to be parsed.
214    fn new_including(mut self, units: CalcUnits) -> Self {
215        self.units |= units;
216        self
217    }
218
219    /// Should given unit be allowed to parse?
220    fn includes(&self, unit: CalcUnits) -> bool {
221        self.units.intersects(unit)
222    }
223}
224
225impl generic::CalcNodeLeaf for Leaf {
226    fn unit(&self) -> CalcUnits {
227        match self {
228            Leaf::Length(_) => CalcUnits::LENGTH,
229            Leaf::Angle(_) => CalcUnits::ANGLE,
230            Leaf::Time(_) => CalcUnits::TIME,
231            Leaf::Resolution(_) => CalcUnits::RESOLUTION,
232            Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT,
233            Leaf::Percentage(_) => CalcUnits::PERCENTAGE,
234            Leaf::Number(_) => CalcUnits::empty(),
235        }
236    }
237
238    fn unitless_value(&self) -> Option<f32> {
239        Some(match *self {
240            Self::Length(ref l) => l.unitless_value(),
241            Self::Percentage(n) | Self::Number(n) => n,
242            Self::Resolution(ref r) => r.dppx(),
243            Self::Angle(ref a) => a.degrees(),
244            Self::Time(ref t) => t.seconds(),
245            Self::ColorComponent(_) => return None,
246        })
247    }
248
249    fn new_number(value: f32) -> Self {
250        Self::Number(value)
251    }
252
253    fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<cmp::Ordering> {
254        use self::Leaf::*;
255
256        if std::mem::discriminant(self) != std::mem::discriminant(other) {
257            return None;
258        }
259
260        if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
261            return None;
262        }
263
264        let self_negative = self.is_negative().unwrap_or(false);
265        if self_negative != other.is_negative().unwrap_or(false) {
266            return Some(if self_negative {
267                cmp::Ordering::Less
268            } else {
269                cmp::Ordering::Greater
270            });
271        }
272
273        match (self, other) {
274            (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
275            (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
276            (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
277            (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
278            (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()),
279            (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
280            (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other),
281            _ => {
282                match *self {
283                    Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..)
284                    | Resolution(..) | ColorComponent(..) => {},
285                }
286                unsafe {
287                    debug_unreachable!("Forgot a branch?");
288                }
289            },
290        }
291    }
292
293    fn as_number(&self) -> Option<f32> {
294        match *self {
295            Leaf::Length(_)
296            | Leaf::Angle(_)
297            | Leaf::Time(_)
298            | Leaf::Resolution(_)
299            | Leaf::Percentage(_)
300            | Leaf::ColorComponent(_) => None,
301            Leaf::Number(value) => Some(value),
302        }
303    }
304
305    fn sort_key(&self) -> SortKey {
306        match *self {
307            Self::Number(..) => SortKey::Number,
308            Self::Percentage(..) => SortKey::Percentage,
309            Self::Time(..) => SortKey::S,
310            Self::Resolution(..) => SortKey::Dppx,
311            Self::Angle(..) => SortKey::Deg,
312            Self::Length(ref l) => match *l {
313                NoCalcLength::Absolute(..) => SortKey::Px,
314                NoCalcLength::FontRelative(ref relative) => match *relative {
315                    FontRelativeLength::Em(..) => SortKey::Em,
316                    FontRelativeLength::Ex(..) => SortKey::Ex,
317                    FontRelativeLength::Rex(..) => SortKey::Rex,
318                    FontRelativeLength::Ch(..) => SortKey::Ch,
319                    FontRelativeLength::Rch(..) => SortKey::Rch,
320                    FontRelativeLength::Cap(..) => SortKey::Cap,
321                    FontRelativeLength::Rcap(..) => SortKey::Rcap,
322                    FontRelativeLength::Ic(..) => SortKey::Ic,
323                    FontRelativeLength::Ric(..) => SortKey::Ric,
324                    FontRelativeLength::Rem(..) => SortKey::Rem,
325                    FontRelativeLength::Lh(..) => SortKey::Lh,
326                    FontRelativeLength::Rlh(..) => SortKey::Rlh,
327                },
328                NoCalcLength::ViewportPercentage(ref vp) => match *vp {
329                    ViewportPercentageLength::Vh(..) => SortKey::Vh,
330                    ViewportPercentageLength::Svh(..) => SortKey::Svh,
331                    ViewportPercentageLength::Lvh(..) => SortKey::Lvh,
332                    ViewportPercentageLength::Dvh(..) => SortKey::Dvh,
333                    ViewportPercentageLength::Vw(..) => SortKey::Vw,
334                    ViewportPercentageLength::Svw(..) => SortKey::Svw,
335                    ViewportPercentageLength::Lvw(..) => SortKey::Lvw,
336                    ViewportPercentageLength::Dvw(..) => SortKey::Dvw,
337                    ViewportPercentageLength::Vmax(..) => SortKey::Vmax,
338                    ViewportPercentageLength::Svmax(..) => SortKey::Svmax,
339                    ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax,
340                    ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax,
341                    ViewportPercentageLength::Vmin(..) => SortKey::Vmin,
342                    ViewportPercentageLength::Svmin(..) => SortKey::Svmin,
343                    ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin,
344                    ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin,
345                    ViewportPercentageLength::Vb(..) => SortKey::Vb,
346                    ViewportPercentageLength::Svb(..) => SortKey::Svb,
347                    ViewportPercentageLength::Lvb(..) => SortKey::Lvb,
348                    ViewportPercentageLength::Dvb(..) => SortKey::Dvb,
349                    ViewportPercentageLength::Vi(..) => SortKey::Vi,
350                    ViewportPercentageLength::Svi(..) => SortKey::Svi,
351                    ViewportPercentageLength::Lvi(..) => SortKey::Lvi,
352                    ViewportPercentageLength::Dvi(..) => SortKey::Dvi,
353                },
354                NoCalcLength::ContainerRelative(ref cq) => match *cq {
355                    ContainerRelativeLength::Cqw(..) => SortKey::Cqw,
356                    ContainerRelativeLength::Cqh(..) => SortKey::Cqh,
357                    ContainerRelativeLength::Cqi(..) => SortKey::Cqi,
358                    ContainerRelativeLength::Cqb(..) => SortKey::Cqb,
359                    ContainerRelativeLength::Cqmin(..) => SortKey::Cqmin,
360                    ContainerRelativeLength::Cqmax(..) => SortKey::Cqmax,
361                },
362                NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
363            },
364            Self::ColorComponent(..) => SortKey::ColorComponent,
365        }
366    }
367
368    fn simplify(&mut self) {
369        if let Self::Length(NoCalcLength::Absolute(ref mut abs)) = *self {
370            *abs = AbsoluteLength::Px(abs.to_px());
371        }
372    }
373
374    /// Tries to merge one sum to another, that is, perform `x` + `y`.
375    ///
376    /// Only handles leaf nodes, it's the caller's responsibility to simplify
377    /// them before calling this if needed.
378    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
379        use self::Leaf::*;
380
381        if std::mem::discriminant(self) != std::mem::discriminant(other) {
382            return Err(());
383        }
384
385        match (self, other) {
386            (&mut Number(ref mut one), &Number(ref other))
387            | (&mut Percentage(ref mut one), &Percentage(ref other)) => {
388                *one += *other;
389            },
390            (&mut Angle(ref mut one), &Angle(ref other)) => {
391                *one = specified::Angle::from_calc(one.degrees() + other.degrees());
392            },
393            (&mut Time(ref mut one), &Time(ref other)) => {
394                *one = specified::Time::from_seconds(one.seconds() + other.seconds());
395            },
396            (&mut Resolution(ref mut one), &Resolution(ref other)) => {
397                *one = specified::Resolution::from_dppx(one.dppx() + other.dppx());
398            },
399            (&mut Length(ref mut one), &Length(ref other)) => {
400                *one = one.try_op(other, std::ops::Add::add)?;
401            },
402            (&mut ColorComponent(_), &ColorComponent(_)) => {
403                // Can not get the sum of color components, because they haven't been resolved yet.
404                return Err(());
405            },
406            _ => {
407                match *other {
408                    Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..)
409                    | Length(..) | ColorComponent(..) => {},
410                }
411                unsafe {
412                    debug_unreachable!();
413                }
414            },
415        }
416
417        Ok(())
418    }
419
420    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
421        if let Self::Number(ref mut left) = *self {
422            if let Self::Number(ref right) = *other {
423                // Both sides are numbers, so we can just modify the left side.
424                *left *= *right;
425                true
426            } else {
427                // The right side is not a number, so the result should be in the units of the right
428                // side.
429                if other.map(|v| v * *left).is_ok() {
430                    std::mem::swap(self, other);
431                    true
432                } else {
433                    false
434                }
435            }
436        } else if let Self::Number(ref right) = *other {
437            // The left side is not a number, but the right side is, so the result is the left
438            // side unit.
439            self.map(|v| v * *right).is_ok()
440        } else {
441            // Neither side is a number, so a product is not possible.
442            false
443        }
444    }
445
446    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
447    where
448        O: Fn(f32, f32) -> f32,
449    {
450        use self::Leaf::*;
451
452        if std::mem::discriminant(self) != std::mem::discriminant(other) {
453            return Err(());
454        }
455
456        match (self, other) {
457            (&Number(one), &Number(other)) => {
458                return Ok(Leaf::Number(op(one, other)));
459            },
460            (&Percentage(one), &Percentage(other)) => {
461                return Ok(Leaf::Percentage(op(one, other)));
462            },
463            (&Angle(ref one), &Angle(ref other)) => {
464                return Ok(Leaf::Angle(specified::Angle::from_calc(op(
465                    one.degrees(),
466                    other.degrees(),
467                ))));
468            },
469            (&Resolution(ref one), &Resolution(ref other)) => {
470                return Ok(Leaf::Resolution(specified::Resolution::from_dppx(op(
471                    one.dppx(),
472                    other.dppx(),
473                ))));
474            },
475            (&Time(ref one), &Time(ref other)) => {
476                return Ok(Leaf::Time(specified::Time::from_seconds(op(
477                    one.seconds(),
478                    other.seconds(),
479                ))));
480            },
481            (&Length(ref one), &Length(ref other)) => {
482                return Ok(Leaf::Length(one.try_op(other, op)?));
483            },
484            _ => {
485                match *other {
486                    Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..)
487                    | Resolution(..) | ColorComponent(..) => {},
488                }
489                unsafe {
490                    debug_unreachable!();
491                }
492            },
493        }
494    }
495
496    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
497        Ok(match self {
498            Leaf::Length(one) => *one = one.map(op),
499            Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())),
500            Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())),
501            Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())),
502            Leaf::Percentage(one) => *one = op(*one),
503            Leaf::Number(one) => *one = op(*one),
504            Leaf::ColorComponent(..) => return Err(()),
505        })
506    }
507}
508
509impl GenericAnchorSide<Box<CalcNode>> {
510    fn parse_in_calc<'i, 't>(
511        context: &ParserContext,
512        input: &mut Parser<'i, 't>,
513    ) -> Result<Self, ParseError<'i>> {
514        if let Ok(k) = input.try_parse(|i| AnchorSideKeyword::parse(i)) {
515            return Ok(Self::Keyword(k));
516        }
517        Ok(Self::Percentage(Box::new(CalcNode::parse_argument(
518            context,
519            input,
520            AllowParse::new(CalcUnits::PERCENTAGE),
521        )?)))
522    }
523}
524
525impl GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>> {
526    fn parse_in_calc<'i, 't>(
527        context: &ParserContext,
528        additional_functions: AdditionalFunctions,
529        input: &mut Parser<'i, 't>,
530    ) -> Result<Self, ParseError<'i>> {
531        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
532            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
533        }
534        input.parse_nested_block(|i| {
535            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
536            let side = GenericAnchorSide::parse_in_calc(context, i)?;
537            let target_element = if target_element.is_none() {
538                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
539            } else {
540                target_element
541            };
542            let fallback = i
543                .try_parse(|i| {
544                    i.expect_comma()?;
545                    Ok::<Box<CalcNode>, ParseError<'i>>(Box::new(
546                        CalcNode::parse_argument(
547                            context,
548                            i,
549                            AllowParse {
550                                units: CalcUnits::LENGTH_PERCENTAGE,
551                                additional_functions,
552                            },
553                        )?
554                        .into_length_or_percentage(AllowedNumericType::All)
555                        .map_err(|_| i.new_custom_error(StyleParseErrorKind::UnspecifiedError))?
556                        .node,
557                    ))
558                })
559                .ok();
560            Ok(Self {
561                target_element: TreeScoped::with_default_level(
562                    target_element.unwrap_or_else(DashedIdent::empty),
563                ),
564                side,
565                fallback: fallback.into(),
566            })
567        })
568    }
569}
570
571impl GenericAnchorSizeFunction<Box<CalcNode>> {
572    fn parse_in_calc<'i, 't>(
573        context: &ParserContext,
574        input: &mut Parser<'i, 't>,
575    ) -> Result<Self, ParseError<'i>> {
576        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
577            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
578        }
579        GenericAnchorSizeFunction::parse_inner(context, input, |i| {
580            Ok(Box::new(
581                CalcNode::parse_argument(
582                    context,
583                    i,
584                    AllowParse::new(CalcUnits::LENGTH_PERCENTAGE),
585                )?
586                .into_length_or_percentage(AllowedNumericType::All)
587                .map_err(|_| i.new_custom_error(StyleParseErrorKind::UnspecifiedError))?
588                .node,
589            ))
590        })
591    }
592}
593
594/// Specified `anchor()` function in math functions.
595pub type CalcAnchorFunction = generic::GenericCalcAnchorFunction<Leaf>;
596/// Specified `anchor-size()` function in math functions.
597pub type CalcAnchorSizeFunction = generic::GenericCalcAnchorSizeFunction<Leaf>;
598
599/// A calc node representation for specified values.
600pub type CalcNode = generic::GenericCalcNode<Leaf>;
601impl CalcNode {
602    /// Tries to parse a single element in the expression, that is, a
603    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, `<resolution>`, etc.
604    ///
605    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
606    /// expression, for example.
607    fn parse_one<'i, 't>(
608        context: &ParserContext,
609        input: &mut Parser<'i, 't>,
610        allowed: AllowParse,
611    ) -> Result<Self, ParseError<'i>> {
612        let location = input.current_source_location();
613        match input.next()? {
614            &Token::Number { value, .. } => Ok(CalcNode::Leaf(Leaf::Number(value))),
615            &Token::Dimension {
616                value, ref unit, ..
617            } => {
618                if allowed.includes(CalcUnits::LENGTH) {
619                    if let Ok(l) = NoCalcLength::parse_dimension(context, value, unit) {
620                        return Ok(CalcNode::Leaf(Leaf::Length(l)));
621                    }
622                }
623                if allowed.includes(CalcUnits::ANGLE) {
624                    if let Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) {
625                        return Ok(CalcNode::Leaf(Leaf::Angle(a)));
626                    }
627                }
628                if allowed.includes(CalcUnits::TIME) {
629                    if let Ok(t) = Time::parse_dimension(value, unit) {
630                        return Ok(CalcNode::Leaf(Leaf::Time(t)));
631                    }
632                }
633                if allowed.includes(CalcUnits::RESOLUTION) {
634                    if let Ok(t) = Resolution::parse_dimension(value, unit) {
635                        return Ok(CalcNode::Leaf(Leaf::Resolution(t)));
636                    }
637                }
638                return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
639            },
640            &Token::Percentage { unit_value, .. } if allowed.includes(CalcUnits::PERCENTAGE) => {
641                Ok(CalcNode::Leaf(Leaf::Percentage(unit_value)))
642            },
643            &Token::ParenthesisBlock => {
644                input.parse_nested_block(|input| CalcNode::parse_argument(context, input, allowed))
645            },
646            &Token::Function(ref name)
647                if allowed
648                    .additional_functions
649                    .intersects(AdditionalFunctions::ANCHOR)
650                    && name.eq_ignore_ascii_case("anchor") =>
651            {
652                let anchor_function = GenericAnchorFunction::parse_in_calc(
653                    context,
654                    allowed.additional_functions,
655                    input,
656                )?;
657                Ok(CalcNode::Anchor(Box::new(anchor_function)))
658            },
659            &Token::Function(ref name)
660                if allowed
661                    .additional_functions
662                    .intersects(AdditionalFunctions::ANCHOR_SIZE)
663                    && name.eq_ignore_ascii_case("anchor-size") =>
664            {
665                let anchor_size_function =
666                    GenericAnchorSizeFunction::parse_in_calc(context, input)?;
667                Ok(CalcNode::AnchorSize(Box::new(anchor_size_function)))
668            },
669            &Token::Function(ref name) => {
670                let function = CalcNode::math_function(context, name, location)?;
671                CalcNode::parse(context, input, function, allowed)
672            },
673            &Token::Ident(ref ident) => {
674                let leaf = match_ignore_ascii_case! { &**ident,
675                    "e" => Leaf::Number(std::f32::consts::E),
676                    "pi" => Leaf::Number(std::f32::consts::PI),
677                    "infinity" => Leaf::Number(f32::INFINITY),
678                    "-infinity" => Leaf::Number(f32::NEG_INFINITY),
679                    "nan" => Leaf::Number(f32::NAN),
680                    _ => {
681                        if crate::color::parsing::rcs_enabled() &&
682                            allowed.includes(CalcUnits::COLOR_COMPONENT)
683                        {
684                            if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) {
685                                Leaf::ColorComponent(channel_keyword)
686                            } else {
687                                return Err(location
688                                    .new_unexpected_token_error(Token::Ident(ident.clone())));
689                            }
690                        } else {
691                            return Err(
692                                location.new_unexpected_token_error(Token::Ident(ident.clone()))
693                            );
694                        }
695                    },
696                };
697                Ok(CalcNode::Leaf(leaf))
698            },
699            t => Err(location.new_unexpected_token_error(t.clone())),
700        }
701    }
702
703    /// Parse a top-level `calc` expression, with all nested sub-expressions.
704    ///
705    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
706    pub fn parse<'i, 't>(
707        context: &ParserContext,
708        input: &mut Parser<'i, 't>,
709        function: MathFunction,
710        allowed: AllowParse,
711    ) -> Result<Self, ParseError<'i>> {
712        input.parse_nested_block(|input| {
713            match function {
714                MathFunction::Calc => Self::parse_argument(context, input, allowed),
715                MathFunction::Clamp => {
716                    let min = Self::parse_argument(context, input, allowed)?;
717                    input.expect_comma()?;
718                    let center = Self::parse_argument(context, input, allowed)?;
719                    input.expect_comma()?;
720                    let max = Self::parse_argument(context, input, allowed)?;
721                    Ok(Self::Clamp {
722                        min: Box::new(min),
723                        center: Box::new(center),
724                        max: Box::new(max),
725                    })
726                },
727                MathFunction::Round => {
728                    let strategy = input.try_parse(parse_rounding_strategy);
729
730                    // <rounding-strategy> = nearest | up | down | to-zero
731                    // https://drafts.csswg.org/css-values-4/#calc-syntax
732                    fn parse_rounding_strategy<'i, 't>(
733                        input: &mut Parser<'i, 't>,
734                    ) -> Result<RoundingStrategy, ParseError<'i>> {
735                        Ok(try_match_ident_ignore_ascii_case! { input,
736                            "nearest" => RoundingStrategy::Nearest,
737                            "up" => RoundingStrategy::Up,
738                            "down" => RoundingStrategy::Down,
739                            "to-zero" => RoundingStrategy::ToZero,
740                        })
741                    }
742
743                    if strategy.is_ok() {
744                        input.expect_comma()?;
745                    }
746
747                    let value = Self::parse_argument(context, input, allowed)?;
748
749                    // <step> defaults to the number 1 if not provided
750                    // https://drafts.csswg.org/css-values-4/#funcdef-round
751                    let step = input.try_parse(|input| {
752                        input.expect_comma()?;
753                        Self::parse_argument(context, input, allowed)
754                    });
755
756                    let step = step.unwrap_or(Self::Leaf(Leaf::Number(1.0)));
757
758                    Ok(Self::Round {
759                        strategy: strategy.unwrap_or(RoundingStrategy::Nearest),
760                        value: Box::new(value),
761                        step: Box::new(step),
762                    })
763                },
764                MathFunction::Mod | MathFunction::Rem => {
765                    let dividend = Self::parse_argument(context, input, allowed)?;
766                    input.expect_comma()?;
767                    let divisor = Self::parse_argument(context, input, allowed)?;
768
769                    let op = match function {
770                        MathFunction::Mod => ModRemOp::Mod,
771                        MathFunction::Rem => ModRemOp::Rem,
772                        _ => unreachable!(),
773                    };
774                    Ok(Self::ModRem {
775                        dividend: Box::new(dividend),
776                        divisor: Box::new(divisor),
777                        op,
778                    })
779                },
780                MathFunction::Min | MathFunction::Max => {
781                    // TODO(emilio): The common case for parse_comma_separated
782                    // is just one element, but for min / max is two, really...
783                    //
784                    // Consider adding an API to cssparser to specify the
785                    // initial vector capacity?
786                    let arguments = input.parse_comma_separated(|input| {
787                        let result = Self::parse_argument(context, input, allowed)?;
788                        Ok(result)
789                    })?;
790
791                    let op = match function {
792                        MathFunction::Min => MinMaxOp::Min,
793                        MathFunction::Max => MinMaxOp::Max,
794                        _ => unreachable!(),
795                    };
796
797                    Ok(Self::MinMax(arguments.into(), op))
798                },
799                MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => {
800                    let a = Self::parse_angle_argument(context, input)?;
801
802                    let number = match function {
803                        MathFunction::Sin => a.sin(),
804                        MathFunction::Cos => a.cos(),
805                        MathFunction::Tan => a.tan(),
806                        _ => unsafe {
807                            debug_unreachable!("We just checked!");
808                        },
809                    };
810
811                    Ok(Self::Leaf(Leaf::Number(number)))
812                },
813                MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => {
814                    let a = Self::parse_number_argument(context, input)?;
815
816                    let radians = match function {
817                        MathFunction::Asin => a.asin(),
818                        MathFunction::Acos => a.acos(),
819                        MathFunction::Atan => a.atan(),
820                        _ => unsafe {
821                            debug_unreachable!("We just checked!");
822                        },
823                    };
824
825                    Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
826                },
827                MathFunction::Atan2 => {
828                    let allow_all = allowed.new_including(CalcUnits::ALL);
829                    let a = Self::parse_argument(context, input, allow_all)?;
830                    input.expect_comma()?;
831                    let b = Self::parse_argument(context, input, allow_all)?;
832
833                    let radians = Self::try_resolve(input, || {
834                        if let Ok(a) = a.to_number() {
835                            let b = b.to_number()?;
836                            return Ok(a.atan2(b));
837                        }
838
839                        if let Ok(a) = a.to_percentage() {
840                            let b = b.to_percentage()?;
841                            return Ok(a.atan2(b));
842                        }
843
844                        if let Ok(a) = a.to_time(None) {
845                            let b = b.to_time(None)?;
846                            return Ok(a.seconds().atan2(b.seconds()));
847                        }
848
849                        if let Ok(a) = a.to_angle() {
850                            let b = b.to_angle()?;
851                            return Ok(a.radians().atan2(b.radians()));
852                        }
853
854                        if let Ok(a) = a.to_resolution() {
855                            let b = b.to_resolution()?;
856                            return Ok(a.dppx().atan2(b.dppx()));
857                        }
858
859                        let a = a.into_length_or_percentage(AllowedNumericType::All)?;
860                        let b = b.into_length_or_percentage(AllowedNumericType::All)?;
861                        let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?;
862
863                        Ok(a.atan2(b))
864                    })?;
865
866                    Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
867                },
868                MathFunction::Pow => {
869                    let a = Self::parse_number_argument(context, input)?;
870                    input.expect_comma()?;
871                    let b = Self::parse_number_argument(context, input)?;
872
873                    let number = a.powf(b);
874
875                    Ok(Self::Leaf(Leaf::Number(number)))
876                },
877                MathFunction::Sqrt => {
878                    let a = Self::parse_number_argument(context, input)?;
879
880                    let number = a.sqrt();
881
882                    Ok(Self::Leaf(Leaf::Number(number)))
883                },
884                MathFunction::Hypot => {
885                    let arguments = input.parse_comma_separated(|input| {
886                        let result = Self::parse_argument(context, input, allowed)?;
887                        Ok(result)
888                    })?;
889
890                    Ok(Self::Hypot(arguments.into()))
891                },
892                MathFunction::Log => {
893                    let a = Self::parse_number_argument(context, input)?;
894                    let b = input
895                        .try_parse(|input| {
896                            input.expect_comma()?;
897                            Self::parse_number_argument(context, input)
898                        })
899                        .ok();
900
901                    let number = match b {
902                        Some(b) => a.log(b),
903                        None => a.ln(),
904                    };
905
906                    Ok(Self::Leaf(Leaf::Number(number)))
907                },
908                MathFunction::Exp => {
909                    let a = Self::parse_number_argument(context, input)?;
910                    let number = a.exp();
911                    Ok(Self::Leaf(Leaf::Number(number)))
912                },
913                MathFunction::Abs => {
914                    let node = Self::parse_argument(context, input, allowed)?;
915                    Ok(Self::Abs(Box::new(node)))
916                },
917                MathFunction::Sign => {
918                    // The sign of a percentage is dependent on the percentage basis, so if
919                    // percentages aren't allowed (so there's no basis) we shouldn't allow them in
920                    // sign(). The rest of the units are safe tho.
921                    let node = Self::parse_argument(
922                        context,
923                        input,
924                        allowed.new_including(CalcUnits::ALL - CalcUnits::PERCENTAGE),
925                    )?;
926                    Ok(Self::Sign(Box::new(node)))
927                },
928            }
929        })
930    }
931
932    fn parse_angle_argument<'i, 't>(
933        context: &ParserContext,
934        input: &mut Parser<'i, 't>,
935    ) -> Result<CSSFloat, ParseError<'i>> {
936        let argument = Self::parse_argument(context, input, AllowParse::new(CalcUnits::ANGLE))?;
937        argument
938            .to_number()
939            .or_else(|()| Ok(argument.to_angle()?.radians()))
940            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
941    }
942
943    fn parse_number_argument<'i, 't>(
944        context: &ParserContext,
945        input: &mut Parser<'i, 't>,
946    ) -> Result<CSSFloat, ParseError<'i>> {
947        Self::parse_argument(context, input, AllowParse::new(CalcUnits::empty()))?
948            .to_number()
949            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
950    }
951
952    fn parse_argument<'i, 't>(
953        context: &ParserContext,
954        input: &mut Parser<'i, 't>,
955        allowed: AllowParse,
956    ) -> Result<Self, ParseError<'i>> {
957        let mut sum = SmallVec::<[CalcNode; 1]>::new();
958        let first = Self::parse_product(context, input, allowed)?;
959        sum.push(first);
960        loop {
961            let start = input.state();
962            match input.next_including_whitespace() {
963                Ok(&Token::WhiteSpace(_)) => {
964                    if input.is_exhausted() {
965                        break; // allow trailing whitespace
966                    }
967                    match *input.next()? {
968                        Token::Delim('+') => {
969                            let rhs = Self::parse_product(context, input, allowed)?;
970                            if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() {
971                                sum.push(rhs);
972                            }
973                        },
974                        Token::Delim('-') => {
975                            let mut rhs = Self::parse_product(context, input, allowed)?;
976                            rhs.negate();
977                            if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() {
978                                sum.push(rhs);
979                            }
980                        },
981                        _ => {
982                            input.reset(&start);
983                            break;
984                        },
985                    }
986                },
987                _ => {
988                    input.reset(&start);
989                    break;
990                },
991            }
992        }
993
994        Ok(if sum.len() == 1 {
995            sum.drain(..).next().unwrap()
996        } else {
997            Self::Sum(sum.into_boxed_slice().into())
998        })
999    }
1000
1001    /// Parse a top-level `calc` expression, and all the products that may
1002    /// follow, and stop as soon as a non-product expression is found.
1003    ///
1004    /// This should parse correctly:
1005    ///
1006    /// * `2`
1007    /// * `2 * 2`
1008    /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
1009    ///
1010    fn parse_product<'i, 't>(
1011        context: &ParserContext,
1012        input: &mut Parser<'i, 't>,
1013        allowed: AllowParse,
1014    ) -> Result<Self, ParseError<'i>> {
1015        let mut product = SmallVec::<[CalcNode; 1]>::new();
1016        let first = Self::parse_one(context, input, allowed)?;
1017        product.push(first);
1018
1019        loop {
1020            let start = input.state();
1021            match input.next() {
1022                Ok(&Token::Delim('*')) => {
1023                    let mut rhs = Self::parse_one(context, input, allowed)?;
1024
1025                    // We can unwrap here, becuase we start the function by adding a node to
1026                    // the list.
1027                    if !product.last_mut().unwrap().try_product_in_place(&mut rhs) {
1028                        product.push(rhs);
1029                    }
1030                },
1031                Ok(&Token::Delim('/')) => {
1032                    let rhs = Self::parse_one(context, input, allowed)?;
1033
1034                    enum InPlaceDivisionResult {
1035                        /// The right was merged into the left.
1036                        Merged,
1037                        /// The right is not a number or could not be resolved, so the left is
1038                        /// unchanged.
1039                        Unchanged,
1040                        /// The right was resolved, but was not a number, so the calculation is
1041                        /// invalid.
1042                        Invalid,
1043                    }
1044
1045                    fn try_division_in_place(
1046                        left: &mut CalcNode,
1047                        right: &CalcNode,
1048                    ) -> InPlaceDivisionResult {
1049                        if let Ok(resolved) = right.resolve() {
1050                            if let Some(number) = resolved.as_number() {
1051                                if number != 1.0 && left.is_product_distributive() {
1052                                    if left.map(|l| l / number).is_err() {
1053                                        return InPlaceDivisionResult::Invalid;
1054                                    }
1055                                    return InPlaceDivisionResult::Merged;
1056                                }
1057                            } else {
1058                                // Color components are valid denominators, but they can't resolve
1059                                // at parse time.
1060                                return if resolved.unit().contains(CalcUnits::COLOR_COMPONENT) {
1061                                    InPlaceDivisionResult::Unchanged
1062                                } else {
1063                                    InPlaceDivisionResult::Invalid
1064                                };
1065                            }
1066                        }
1067                        InPlaceDivisionResult::Unchanged
1068                    }
1069
1070                    // The right hand side of a division *must* be a number, so if we can
1071                    // already resolve it, then merge it with the last node on the product list.
1072                    // We can unwrap here, becuase we start the function by adding a node to
1073                    // the list.
1074                    match try_division_in_place(&mut product.last_mut().unwrap(), &rhs) {
1075                        InPlaceDivisionResult::Merged => {},
1076                        InPlaceDivisionResult::Unchanged => {
1077                            product.push(Self::Invert(Box::new(rhs)))
1078                        },
1079                        InPlaceDivisionResult::Invalid => {
1080                            return Err(
1081                                input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
1082                            )
1083                        },
1084                    }
1085                },
1086                _ => {
1087                    input.reset(&start);
1088                    break;
1089                },
1090            }
1091        }
1092
1093        Ok(if product.len() == 1 {
1094            product.drain(..).next().unwrap()
1095        } else {
1096            Self::Product(product.into_boxed_slice().into())
1097        })
1098    }
1099
1100    fn try_resolve<'i, 't, F>(
1101        input: &Parser<'i, 't>,
1102        closure: F,
1103    ) -> Result<CSSFloat, ParseError<'i>>
1104    where
1105        F: FnOnce() -> Result<CSSFloat, ()>,
1106    {
1107        closure().map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1108    }
1109
1110    /// Tries to simplify this expression into a `<length>` or `<percentage>`
1111    /// value.
1112    pub fn into_length_or_percentage(
1113        mut self,
1114        clamping_mode: AllowedNumericType,
1115    ) -> Result<CalcLengthPercentage, ()> {
1116        self.simplify_and_sort();
1117
1118        // Although we allow numbers inside CalcLengthPercentage, calculations that resolve to a
1119        // number result is still not allowed.
1120        let unit = self.unit()?;
1121        if !CalcUnits::LENGTH_PERCENTAGE.intersects(unit) {
1122            Err(())
1123        } else {
1124            Ok(CalcLengthPercentage {
1125                clamping_mode,
1126                node: self,
1127            })
1128        }
1129    }
1130
1131    /// Tries to simplify this expression into a `<time>` value.
1132    fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> {
1133        let seconds = if let Leaf::Time(time) = self.resolve()? {
1134            time.seconds()
1135        } else {
1136            return Err(());
1137        };
1138
1139        Ok(Time::from_seconds_with_calc_clamping_mode(
1140            seconds,
1141            clamping_mode,
1142        ))
1143    }
1144
1145    /// Tries to simplify the expression into a `<resolution>` value.
1146    fn to_resolution(&self) -> Result<Resolution, ()> {
1147        let dppx = if let Leaf::Resolution(resolution) = self.resolve()? {
1148            resolution.dppx()
1149        } else {
1150            return Err(());
1151        };
1152
1153        Ok(Resolution::from_dppx_calc(dppx))
1154    }
1155
1156    /// Tries to simplify this expression into an `Angle` value.
1157    fn to_angle(&self) -> Result<Angle, ()> {
1158        let degrees = if let Leaf::Angle(angle) = self.resolve()? {
1159            angle.degrees()
1160        } else {
1161            return Err(());
1162        };
1163
1164        let result = Angle::from_calc(degrees);
1165        Ok(result)
1166    }
1167
1168    /// Tries to simplify this expression into a `<number>` value.
1169    fn to_number(&self) -> Result<CSSFloat, ()> {
1170        let number = if let Leaf::Number(number) = self.resolve()? {
1171            number
1172        } else {
1173            return Err(());
1174        };
1175
1176        let result = number;
1177
1178        Ok(result)
1179    }
1180
1181    /// Tries to simplify this expression into a `<percentage>` value.
1182    fn to_percentage(&self) -> Result<CSSFloat, ()> {
1183        if let Leaf::Percentage(percentage) = self.resolve()? {
1184            Ok(percentage)
1185        } else {
1186            Err(())
1187        }
1188    }
1189
1190    /// Given a function name, and the location from where the token came from,
1191    /// return a mathematical function corresponding to that name or an error.
1192    #[inline]
1193    pub fn math_function<'i>(
1194        _: &ParserContext,
1195        name: &CowRcStr<'i>,
1196        location: cssparser::SourceLocation,
1197    ) -> Result<MathFunction, ParseError<'i>> {
1198        let function = match MathFunction::from_ident(&*name) {
1199            Ok(f) => f,
1200            Err(()) => {
1201                return Err(location.new_unexpected_token_error(Token::Function(name.clone())))
1202            },
1203        };
1204
1205        Ok(function)
1206    }
1207
1208    /// Convenience parsing function for `<length> | <percentage>`, and, optionally, `anchor()`.
1209    pub fn parse_length_or_percentage<'i, 't>(
1210        context: &ParserContext,
1211        input: &mut Parser<'i, 't>,
1212        clamping_mode: AllowedNumericType,
1213        function: MathFunction,
1214        allow_anchor: AllowAnchorPositioningFunctions,
1215    ) -> Result<CalcLengthPercentage, ParseError<'i>> {
1216        let allowed = if allow_anchor == AllowAnchorPositioningFunctions::No {
1217            AllowParse::new(CalcUnits::LENGTH_PERCENTAGE)
1218        } else {
1219            AllowParse {
1220                units: CalcUnits::LENGTH_PERCENTAGE,
1221                additional_functions: match allow_anchor {
1222                    AllowAnchorPositioningFunctions::No => unreachable!(),
1223                    AllowAnchorPositioningFunctions::AllowAnchorSize => {
1224                        AdditionalFunctions::ANCHOR_SIZE
1225                    },
1226                    AllowAnchorPositioningFunctions::AllowAnchorAndAnchorSize => {
1227                        AdditionalFunctions::ANCHOR | AdditionalFunctions::ANCHOR_SIZE
1228                    },
1229                },
1230            }
1231        };
1232        Self::parse(context, input, function, allowed)?
1233            .into_length_or_percentage(clamping_mode)
1234            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1235    }
1236
1237    /// Convenience parsing function for percentages.
1238    pub fn parse_percentage<'i, 't>(
1239        context: &ParserContext,
1240        input: &mut Parser<'i, 't>,
1241        function: MathFunction,
1242    ) -> Result<CSSFloat, ParseError<'i>> {
1243        Self::parse(
1244            context,
1245            input,
1246            function,
1247            AllowParse::new(CalcUnits::PERCENTAGE),
1248        )?
1249        .to_percentage()
1250        .map(crate::values::normalize)
1251        .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1252    }
1253
1254    /// Convenience parsing function for `<length>`.
1255    pub fn parse_length<'i, 't>(
1256        context: &ParserContext,
1257        input: &mut Parser<'i, 't>,
1258        clamping_mode: AllowedNumericType,
1259        function: MathFunction,
1260    ) -> Result<CalcLengthPercentage, ParseError<'i>> {
1261        Self::parse(context, input, function, AllowParse::new(CalcUnits::LENGTH))?
1262            .into_length_or_percentage(clamping_mode)
1263            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1264    }
1265
1266    /// Convenience parsing function for `<number>`.
1267    pub fn parse_number<'i, 't>(
1268        context: &ParserContext,
1269        input: &mut Parser<'i, 't>,
1270        function: MathFunction,
1271    ) -> Result<CSSFloat, ParseError<'i>> {
1272        Self::parse(
1273            context,
1274            input,
1275            function,
1276            AllowParse::new(CalcUnits::empty()),
1277        )?
1278        .to_number()
1279        .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1280    }
1281
1282    /// Convenience parsing function for `<angle>`.
1283    pub fn parse_angle<'i, 't>(
1284        context: &ParserContext,
1285        input: &mut Parser<'i, 't>,
1286        function: MathFunction,
1287    ) -> Result<Angle, ParseError<'i>> {
1288        Self::parse(context, input, function, AllowParse::new(CalcUnits::ANGLE))?
1289            .to_angle()
1290            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1291    }
1292
1293    /// Convenience parsing function for `<time>`.
1294    pub fn parse_time<'i, 't>(
1295        context: &ParserContext,
1296        input: &mut Parser<'i, 't>,
1297        clamping_mode: AllowedNumericType,
1298        function: MathFunction,
1299    ) -> Result<Time, ParseError<'i>> {
1300        Self::parse(context, input, function, AllowParse::new(CalcUnits::TIME))?
1301            .to_time(Some(clamping_mode))
1302            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1303    }
1304
1305    /// Convenience parsing function for `<resolution>`.
1306    pub fn parse_resolution<'i, 't>(
1307        context: &ParserContext,
1308        input: &mut Parser<'i, 't>,
1309        function: MathFunction,
1310    ) -> Result<Resolution, ParseError<'i>> {
1311        Self::parse(
1312            context,
1313            input,
1314            function,
1315            AllowParse::new(CalcUnits::RESOLUTION),
1316        )?
1317        .to_resolution()
1318        .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1319    }
1320}