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::AbsoluteColor;
10use crate::color::parsing::ChannelKeyword;
11use crate::derives::*;
12use crate::parser::{Parse, ParserContext};
13use crate::typed_om::{NumericBaseType, NumericType, ToTyped, TypedValue};
14use crate::values::DashedIdent;
15use crate::values::computed::{self, ToComputedValue};
16use crate::values::generics::Optional;
17use crate::values::generics::calc::{
18    self as generic, CalcNodeLeaf, CalcType, GenericAnchorFunctionFallback, GenericRandomFunction,
19    MinMaxOp, ModRemOp, ProgressClampingMode, RoundingStrategy, SimplificationResult, SortKey,
20};
21use crate::values::generics::length::GenericAnchorSizeFunction;
22use crate::values::generics::position::{
23    AnchorSideKeyword, GenericAnchorFunction, GenericAnchorSide, TreeScoped,
24};
25use crate::values::specified::length::NoCalcLength;
26use crate::values::specified::random::RandomKey;
27use crate::values::specified::{
28    NoCalcAngle, NoCalcNumber, NoCalcPercentage, NoCalcResolution, NoCalcTime, TreeCountingFunction,
29};
30use cssparser::{CowRcStr, Parser, Token, match_ignore_ascii_case};
31use debug_unreachable::debug_unreachable;
32use smallvec::SmallVec;
33use std::cmp;
34use std::convert::AsRef;
35use strum::IntoEnumIterator;
36use strum_macros::{AsRefStr, EnumIter};
37use style_traits::values::specified::AllowedNumericType;
38use style_traits::{ParseError, SpecifiedValueInfo, StyleParseErrorKind};
39use thin_vec::ThinVec;
40
41/// The name of the mathematical function that we're parsing.
42#[derive(AsRefStr, Clone, Copy, Debug, EnumIter, Parse)]
43#[strum(serialize_all = "lowercase")]
44pub enum MathFunction {
45    /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
46    Calc,
47    /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min
48    Min,
49    /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max
50    Max,
51    /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
52    Clamp,
53    /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round
54    Round,
55    /// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod
56    Mod,
57    /// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem
58    Rem,
59    /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
60    Sin,
61    /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
62    Cos,
63    /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan
64    Tan,
65    /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin
66    Asin,
67    /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos
68    Acos,
69    /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
70    Atan,
71    /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2
72    Atan2,
73    /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow
74    Pow,
75    /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt
76    Sqrt,
77    /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot
78    Hypot,
79    /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log
80    Log,
81    /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp
82    Exp,
83    /// `abs()`: https://drafts.csswg.org/css-values-4/#funcdef-abs
84    Abs,
85    /// `sign()`: https://drafts.csswg.org/css-values-4/#funcdef-sign
86    Sign,
87    /// `progress()`: https://drafts.csswg.org/css-values-5/#funcdef-progress
88    Progress,
89    /// `random()`: https://drafts.csswg.org/css-values-5/#funcdef-random
90    Random,
91    /// `sibling-count()`: https://drafts.csswg.org/css-values-5/#funcdef-sibling-count
92    #[strum(serialize = "sibling-count")]
93    SiblingCount,
94    /// `sibling-index()`: https://drafts.csswg.org/css-values-5/#funcdef-sibling-index
95    #[strum(serialize = "sibling-index")]
96    SiblingIndex,
97}
98
99impl MathFunction {
100    /// Returns an iterator for the enum variants
101    pub fn variants() -> MathFunctionIter {
102        MathFunction::iter()
103    }
104}
105
106pub use crate::values::generics::calc::CalcPercentageLeaf;
107
108/// A leaf node inside a `Calc` expression's AST.
109#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
110#[repr(u8)]
111pub enum Leaf {
112    /// `<length>`
113    Length(NoCalcLength),
114    /// `<angle>`
115    Angle(NoCalcAngle),
116    /// `<time>`
117    Time(NoCalcTime),
118    /// `<resolution>`
119    Resolution(NoCalcResolution),
120    /// A component of a color.
121    ColorComponent(ChannelKeyword),
122    /// `<percentage>`
123    Percentage(CalcPercentageLeaf),
124    /// `<number>`
125    Number(NoCalcNumber),
126    /// A tree-counting function.
127    TreeCountingFunction(TreeCountingFunction),
128    /// `<random-key>`
129    RandomKey(Box<RandomKey>),
130}
131
132impl ToTyped for Leaf {
133    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
134        // XXX Only supporting Length, Number, Percentage, Angle and Time for now
135        match *self {
136            Self::Length(ref l) => l.to_typed(dest),
137            Self::Number(n) => n.to_typed(dest),
138            Self::Percentage(ref p) => p.to_typed(dest),
139            Self::Angle(ref a) => a.to_typed(dest),
140            Self::Time(t) => t.to_typed(dest),
141            _ => Err(()),
142        }
143    }
144}
145
146impl Leaf {
147    /// Computes this leaf against the given context (if any), substituting color
148    /// channel references with the matching channel of `origin_color` when it is
149    /// provided. If no origin color is available, channel references are kept
150    /// symbolic so they can be resolved later.
151    pub fn to_computed_value(
152        &self,
153        context: Option<&computed::Context>,
154        origin_color: Option<&AbsoluteColor>,
155    ) -> Result<Self, ()> {
156        Ok(match self {
157            Self::Length(l) => {
158                let px = match context {
159                    Some(context) => l.to_computed_value(context).px(),
160                    None => l.to_computed_pixel_length_without_context()?,
161                };
162                Self::Length(NoCalcLength::from_px(px))
163            },
164            Self::TreeCountingFunction(f) => match context {
165                Some(context) => {
166                    Self::Number(NoCalcNumber::new(f.to_computed_value(context) as f32))
167                },
168                None => return Err(()),
169            },
170            Self::RandomKey(key) => match context {
171                Some(context) => Self::Number(NoCalcNumber::new(*key.to_computed_value(context))),
172                None => return Err(()),
173            },
174            Self::ColorComponent(channel_keyword) => {
175                let channel_value = origin_color
176                    .and_then(|c| c.get_component_by_channel_keyword(*channel_keyword).ok());
177                match channel_value {
178                    Some(value) => Self::Number(NoCalcNumber::new(value.unwrap_or(0.0))),
179                    // If the color needs to get resolved later (there's no absolute origin color),
180                    // or the channel is invalid, we still keep the channel keyword around.
181                    None => Self::ColorComponent(*channel_keyword),
182                }
183            },
184            // The remaining leaves are already absolute (and thus
185            // context-independent).
186            Self::Angle(..)
187            | Self::Time(..)
188            | Self::Resolution(..)
189            | Self::Percentage(..)
190            | Self::Number(..) => self.clone(),
191        })
192    }
193}
194
195/// A struct to hold a simplified calc expression and associated clamping mode.
196///
197/// In some cases, e.g. DOMMatrix, we support calc(), but reject all the
198/// relative lengths, and to_computed_pixel_length_without_context() handles
199/// this case. Therefore, if you want to add a new field, please make sure this
200/// function work properly.
201#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem, ToTyped)]
202#[allow(missing_docs)]
203pub struct CalcNumeric {
204    #[css(skip)]
205    pub clamping_mode: AllowedNumericType,
206    pub node: CalcNode,
207}
208
209impl CalcNumeric {
210    /// Returns a new CalcNumeric with the same expression but the specified clamping mode
211    pub fn with_clamping_mode(&self, clamping_mode: AllowedNumericType) -> Self {
212        Self {
213            clamping_mode,
214            node: self.node.clone(),
215        }
216    }
217
218    /// Returns a new CalcNumeric with the same clamping mode but a different leaf node
219    pub fn with_leaf_node(&self, leaf: Leaf) -> Self {
220        Self {
221            clamping_mode: self.clamping_mode,
222            node: CalcNode::Leaf(leaf),
223        }
224    }
225
226    /// Resolves this calc expression given a computed context, applying clamping.
227    pub fn resolve(
228        &self,
229        context: &computed::Context,
230        leaf_to_f32: impl FnOnce(Result<Leaf, ()>) -> f32,
231    ) -> f32 {
232        let result = self
233            .node
234            .to_computed_value(Some(context), None)
235            .and_then(|r| r.resolve());
236        self.clamping_mode.clamp(leaf_to_f32(result))
237    }
238
239    /// Gets this calc expression as a number
240    pub fn as_number(&self) -> Option<NoCalcNumber> {
241        match self.node.resolve() {
242            Ok(Leaf::Number(n)) => Some(n),
243            _ => None,
244        }
245    }
246
247    /// Gets this calc expression as a percentage
248    pub fn as_percentage(&self) -> Option<NoCalcPercentage> {
249        match self.node.resolve() {
250            Ok(Leaf::Percentage(p)) => Some(NoCalcPercentage::new(p.get())),
251            _ => None,
252        }
253    }
254
255    /// Gets this calc expression as a time
256    pub fn as_time(&self) -> Option<NoCalcTime> {
257        match self.node.resolve() {
258            Ok(Leaf::Time(t)) => Some(t),
259            _ => None,
260        }
261    }
262
263    /// Gets this calc expression as a resolution
264    pub fn as_resolution(&self) -> Option<NoCalcResolution> {
265        match self.node.resolve() {
266            Ok(Leaf::Resolution(r)) => Some(r),
267            _ => None,
268        }
269    }
270
271    /// Gets this calc expression as an angle
272    pub fn as_angle(&self) -> Option<NoCalcAngle> {
273        match self.node.resolve() {
274            Ok(Leaf::Angle(a)) => Some(a),
275            _ => None,
276        }
277    }
278}
279
280impl SpecifiedValueInfo for CalcNumeric {}
281
282/// A `calc()` expression that is known to resolve to a `<length-percentage>`.
283#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem, ToTyped)]
284pub struct CalcLengthPercentage(pub CalcNumeric);
285
286impl SpecifiedValueInfo for CalcLengthPercentage {}
287
288/// Should parsing anchor-positioning functions in `calc()` be allowed?
289#[derive(Clone, Copy, PartialEq)]
290pub enum AllowAnchorPositioningFunctions {
291    /// Don't allow any anchor positioning function.
292    No,
293    /// Allow `anchor-size()` to be parsed.
294    AllowAnchorSize,
295    /// Allow `anchor()` and `anchor-size()` to be parsed.
296    AllowAnchorAndAnchorSize,
297}
298
299bitflags! {
300    /// Additional functions within math functions that are permitted to be parsed depending on
301    /// the context of parsing (e.g. Parsing `inset` allows use of `anchor()` within `calc()`).
302    #[derive(Clone, Copy, PartialEq, Eq)]
303    pub struct AdditionalFunctions: u8 {
304        /// `anchor()` function.
305        const ANCHOR = 1 << 0;
306        /// `anchor-size()` function.
307        const ANCHOR_SIZE = 1 << 1;
308    }
309}
310
311/// Dictates whether percentages are allowed in the calculation that
312/// is parsed using this context, and whether such percentages have a
313/// known "percent hint" (the type that they will eventually resolve to).
314/// https://drafts.csswg.org/css-values-4/#calc-context
315#[derive(Copy, Clone, Debug, PartialEq)]
316pub enum PercentageContext {
317    /// Percentages are not allowed in this calculation context.
318    NotAllowed,
319    /// Percentages are allowed with the given pecent hint information.
320    Allowed(Optional<NumericBaseType>),
321}
322
323#[allow(missing_docs)]
324impl PercentageContext {
325    pub fn not_allowed() -> Self {
326        Self::NotAllowed
327    }
328
329    pub fn allowed() -> Self {
330        Self::Allowed(Optional::None)
331    }
332
333    pub fn allowed_with_hint(hint: NumericBaseType) -> Self {
334        Self::Allowed(Optional::Some(hint))
335    }
336}
337
338/// What is allowed to be parsed for math functions within in this context?
339#[derive(Clone, Copy)]
340pub struct CalcParseFlags {
341    /// Whether percentages are allowed in this context, and what numeric type they are relative
342    /// to. Used both to control parsing as well as to type check calculation trees.
343    pub percentage_context: PercentageContext,
344    /// Which relative color components, if any, are allowed.
345    pub color_components: ChannelKeyword,
346    /// Additional functions allowed to be parsed in this context.
347    pub additional_functions: AdditionalFunctions,
348    /// Whether or not in place operations should be performed. Normally, we aggressive
349    /// simplify via in-place operations, but it is disabled for generating a trace of steps.
350    pub in_place_operations: CalcNodeParseInPlaceOperations,
351}
352
353impl CalcParseFlags {
354    /// Builds parse flags with the given percentage calculation context.
355    pub fn new(percentage_context: PercentageContext) -> Self {
356        Self {
357            percentage_context,
358            ..Default::default()
359        }
360    }
361}
362
363impl Default for CalcParseFlags {
364    fn default() -> Self {
365        Self {
366            percentage_context: PercentageContext::not_allowed(),
367            color_components: ChannelKeyword::empty(),
368            additional_functions: AdditionalFunctions::empty(),
369            in_place_operations: CalcNodeParseInPlaceOperations::Yes,
370        }
371    }
372}
373
374impl generic::CalcNodeLeaf for Leaf {
375    fn numeric_type(&self) -> NumericType {
376        match self {
377            Leaf::Length(_) => NumericType::length(),
378            Leaf::Angle(_) => NumericType::angle(),
379            Leaf::Time(_) => NumericType::time(),
380            Leaf::Resolution(_) => NumericType::resolution(),
381            Leaf::Percentage(p) => p.numeric_type(),
382            Leaf::ColorComponent(_)
383            | Leaf::Number(_)
384            | Leaf::TreeCountingFunction(_)
385            | Leaf::RandomKey(_) => NumericType::number(),
386        }
387    }
388
389    fn unitless_value(&self) -> Option<f32> {
390        Some(match *self {
391            Self::Length(ref l) => l.unitless_value(),
392            Self::Percentage(ref p) => p.get(),
393            Self::Number(ref n) => n.value(),
394            Self::Resolution(ref r) => r.dppx(),
395            Self::Angle(ref a) => a.degrees(),
396            Self::Time(ref t) => t.seconds(),
397            Self::RandomKey(ref k) => {
398                return match &**k {
399                    RandomKey::Fixed(number) => number.resolve(),
400                    RandomKey::CacheKey(_) => None,
401                };
402            },
403            Self::ColorComponent(_) | Self::TreeCountingFunction(_) => return None,
404        })
405    }
406
407    fn canonical_value(&self) -> Option<f32> {
408        Some(match *self {
409            Self::Length(ref l) => l.to_px_if_absolute()?,
410            Self::Percentage(ref p) => match p.hint {
411                // Percentages that are relative to some other value (indicated by a
412                // percent hint other than "percent") cannot yet resolve to a numeric
413                // value, as the percentage's basis is not available.
414                Optional::Some(NumericBaseType::Percent) => p.get(),
415                _ => return None,
416            },
417            Self::Number(ref n) => n.value(),
418            Self::Resolution(ref r) => r.dppx(),
419            Self::Angle(ref a) => a.degrees(),
420            Self::Time(ref t) => t.seconds(),
421            Self::RandomKey(ref k) => {
422                return match &**k {
423                    RandomKey::Fixed(number) => number.resolve(),
424                    RandomKey::CacheKey(_) => None,
425                };
426            },
427            Self::ColorComponent(_) | Self::TreeCountingFunction(_) => return None,
428        })
429    }
430
431    fn is_same_unit_as(&self, other: &Self) -> bool {
432        use self::Leaf::*;
433
434        if std::mem::discriminant(self) != std::mem::discriminant(other) {
435            return false;
436        }
437
438        match (self, other) {
439            (Length(a), Length(b)) => a.length_unit() == b.length_unit(),
440            (Angle(a), Angle(b)) => a.angle_unit() == b.angle_unit(),
441            (Time(a), Time(b)) => a.time_unit() == b.time_unit(),
442            (Resolution(a), Resolution(b)) => a.resolution_unit() == b.resolution_unit(),
443            (ColorComponent(_), ColorComponent(_))
444            | (Percentage(_), Percentage(_))
445            | (Number(_), Number(_))
446            | (TreeCountingFunction(_), TreeCountingFunction(_))
447            | (RandomKey(_), RandomKey(_)) => true,
448            _ => {
449                match *other {
450                    Number(..)
451                    | Percentage(..)
452                    | Angle(..)
453                    | Time(..)
454                    | Resolution(..)
455                    | Length(..)
456                    | ColorComponent(..)
457                    | TreeCountingFunction(..)
458                    | RandomKey(..) => {},
459                }
460                unsafe {
461                    debug_unreachable!();
462                }
463            },
464        }
465    }
466
467    fn as_percentage(&self) -> Option<(f32, Optional<NumericBaseType>)> {
468        match *self {
469            Self::Percentage(p) => Some((p.get(), p.hint)),
470            _ => None,
471        }
472    }
473
474    fn as_angle_radians(&self) -> Option<f32> {
475        if let Self::Angle(ref a) = *self {
476            Some(a.radians())
477        } else {
478            None
479        }
480    }
481
482    fn new_angle_from_radians(radians: f32) -> Self {
483        Self::Angle(NoCalcAngle::from_degrees(radians.to_degrees()))
484    }
485
486    fn new_number(value: f32) -> Self {
487        Self::Number(NoCalcNumber::new(value))
488    }
489
490    fn new_from_typed_value(value: f32, numeric_type: NumericType) -> Result<Self, ()> {
491        let calc_type = numeric_type.as_calc_type()?;
492        let percent_hint = numeric_type.percent_hint();
493        Ok(match calc_type {
494            CalcType::Number => Self::new_number(value),
495            CalcType::Length => Self::Length(NoCalcLength::from_px(value)),
496            CalcType::Angle => Self::Angle(NoCalcAngle::from_degrees(value)),
497            CalcType::Time => Self::Time(NoCalcTime::from_seconds(value)),
498            CalcType::Resolution => Self::Resolution(NoCalcResolution::from_dppx(value)),
499            CalcType::Percentage => Self::Percentage(CalcPercentageLeaf::new(value, percent_hint)),
500        })
501    }
502
503    fn compare(&self, other: &Self) -> Option<cmp::Ordering> {
504        use self::Leaf::*;
505
506        if std::mem::discriminant(self) != std::mem::discriminant(other) {
507            return None;
508        }
509
510        // Percentages that resolve against some other basis value cannot be meaningfully compared.
511        if matches!(self, Percentage(p) if p.hint != Optional::Some(NumericBaseType::Percent)) {
512            return None;
513        }
514
515        let self_negative = self.is_negative().unwrap_or(false);
516        if self_negative != other.is_negative().unwrap_or(false) {
517            return Some(if self_negative {
518                cmp::Ordering::Less
519            } else {
520                cmp::Ordering::Greater
521            });
522        }
523
524        match (self, other) {
525            (Percentage(one), Percentage(other)) => one.get().partial_cmp(&other.get()),
526            (Length(one), Length(other)) => one.partial_cmp(other),
527            (Angle(one), Angle(other)) => one.degrees().partial_cmp(&other.degrees()),
528            (Time(one), Time(other)) => one.seconds().partial_cmp(&other.seconds()),
529            (Resolution(one), Resolution(other)) => one.dppx().partial_cmp(&other.dppx()),
530            (Number(one), Number(other)) => one.partial_cmp(other),
531            (ColorComponent(one), ColorComponent(other)) => one.partial_cmp(other),
532            (TreeCountingFunction(one), TreeCountingFunction(other)) => one.partial_cmp(other),
533            (RandomKey(_), RandomKey(_)) => None,
534            _ => {
535                match *self {
536                    Length(..)
537                    | Percentage(..)
538                    | Angle(..)
539                    | Time(..)
540                    | Number(..)
541                    | Resolution(..)
542                    | ColorComponent(..)
543                    | TreeCountingFunction(..)
544                    | RandomKey(..) => {},
545                }
546                unsafe {
547                    debug_unreachable!("Forgot a branch?");
548                }
549            },
550        }
551    }
552
553    fn as_number(&self) -> Option<f32> {
554        match *self {
555            Leaf::Length(_)
556            | Leaf::Angle(_)
557            | Leaf::Time(_)
558            | Leaf::Resolution(_)
559            | Leaf::Percentage(_)
560            | Leaf::ColorComponent(_)
561            | Leaf::TreeCountingFunction(_)
562            | Leaf::RandomKey(_) => None,
563            Leaf::Number(n) => Some(n.value()),
564        }
565    }
566
567    fn sort_key(&self) -> SortKey {
568        match *self {
569            Self::Number(..) => SortKey::Number,
570            Self::Percentage(..) => SortKey::Percentage,
571            Self::Time(..) => SortKey::S,
572            Self::Resolution(..) => SortKey::Dppx,
573            Self::Angle(..) => SortKey::Deg,
574            Self::Length(ref l) => l.sort_key(),
575            Self::ColorComponent(..) => SortKey::ColorComponent,
576            Self::TreeCountingFunction(..) | Self::RandomKey(..) => SortKey::Other,
577        }
578    }
579
580    fn simplify(&mut self) -> SimplificationResult {
581        match self {
582            Leaf::Length(l) => {
583                if let Some(px) = l.to_px_if_absolute() {
584                    *l = NoCalcLength::from_px(px);
585                    return SimplificationResult::Simplified;
586                }
587            },
588            Leaf::Resolution(r) => {
589                *r = NoCalcResolution::from_dppx(r.dppx());
590                return SimplificationResult::Simplified;
591            },
592            Leaf::Time(t) => {
593                *t = NoCalcTime::from_seconds(t.seconds());
594                return SimplificationResult::Simplified;
595            },
596            Leaf::Angle(a) => {
597                *a = NoCalcAngle::from_degrees(a.degrees());
598                return SimplificationResult::Simplified;
599            },
600            _ => (),
601        }
602        SimplificationResult::Unchanged
603    }
604
605    /// Tries to merge one sum to another, that is, perform `x` + `y`.
606    ///
607    /// Only handles leaf nodes, it's the caller's responsibility to simplify
608    /// them before calling this if needed.
609    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
610        use self::Leaf::*;
611
612        if std::mem::discriminant(self) != std::mem::discriminant(other) {
613            return Err(());
614        }
615
616        match (self, other) {
617            (&mut Number(ref mut one), Number(other)) => {
618                *one = NoCalcNumber::new(one.value() + other.value());
619            },
620            (&mut Percentage(ref mut one), Percentage(other)) => {
621                *one = CalcPercentageLeaf::new(one.get() + other.get(), one.combined_hint(other));
622            },
623            (&mut Angle(ref mut one), Angle(other)) => {
624                *one = NoCalcAngle::from_degrees(one.degrees() + other.degrees());
625            },
626            (&mut Time(ref mut one), Time(other)) => {
627                *one = NoCalcTime::from_seconds(one.seconds() + other.seconds());
628            },
629            (&mut Resolution(ref mut one), Resolution(other)) => {
630                *one = NoCalcResolution::from_dppx(one.dppx() + other.dppx());
631            },
632            (&mut Length(ref mut one), Length(other)) => {
633                *one = one.try_op(other, std::ops::Add::add)?;
634            },
635            (&mut ColorComponent(_), &ColorComponent(_)) => {
636                // Can not get the sum of color components, because they haven't been resolved yet.
637                return Err(());
638            },
639            (&mut TreeCountingFunction(_), &TreeCountingFunction(_)) => {
640                // Can not get the sum of tree counting functions, because they haven't been resolved yet.
641                return Err(());
642            },
643            (&mut RandomKey(_), &RandomKey(_)) => {
644                // Can not get the sum of random cache keys.
645                return Err(());
646            },
647            _ => {
648                match *other {
649                    Number(..)
650                    | Percentage(..)
651                    | Angle(..)
652                    | Time(..)
653                    | Resolution(..)
654                    | Length(..)
655                    | ColorComponent(..)
656                    | TreeCountingFunction(..)
657                    | RandomKey(..) => {},
658                }
659                unsafe {
660                    debug_unreachable!();
661                }
662            },
663        }
664
665        Ok(())
666    }
667
668    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
669        if let Self::Number(ref mut left) = *self {
670            if let Self::Number(ref right) = *other {
671                // Both sides are numbers, so we can just modify the left side.
672                *left = NoCalcNumber::new(left.value() * right.value());
673                true
674            } else {
675                // The right side is not a number, so the result should be in the units of the right
676                // side.
677                let left_val = left.value();
678                if other.map(|v| v * left_val).is_ok() {
679                    std::mem::swap(self, other);
680                    true
681                } else {
682                    false
683                }
684            }
685        } else if let Self::Number(ref right) = *other {
686            // The left side is not a number, but the right side is, so the result is the left
687            // side unit.
688            let right_val = right.value();
689            self.map(|v| v * right_val).is_ok()
690        } else {
691            // Neither side is a number, so a product is not possible.
692            false
693        }
694    }
695
696    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
697    where
698        O: Fn(f32, f32) -> f32,
699    {
700        use self::Leaf::*;
701
702        if std::mem::discriminant(self) != std::mem::discriminant(other) {
703            return Err(());
704        }
705
706        match (self, other) {
707            (&Number(one), &Number(other)) => Ok(Leaf::Number(NoCalcNumber::new(op(
708                one.value(),
709                other.value(),
710            )))),
711            (Percentage(one), Percentage(other)) => Ok(Leaf::Percentage(CalcPercentageLeaf::new(
712                op(one.get(), other.get()),
713                one.combined_hint(other),
714            ))),
715            (Angle(one), Angle(other)) => Ok(Leaf::Angle(NoCalcAngle::from_degrees(op(
716                one.degrees(),
717                other.degrees(),
718            )))),
719            (Resolution(one), Resolution(other)) => Ok(Leaf::Resolution(
720                NoCalcResolution::from_dppx(op(one.dppx(), other.dppx())),
721            )),
722            (Time(one), Time(other)) => Ok(Leaf::Time(NoCalcTime::from_seconds(op(
723                one.seconds(),
724                other.seconds(),
725            )))),
726            (Length(one), Length(other)) => Ok(Leaf::Length(one.try_op(other, op)?)),
727            (&ColorComponent(..), &ColorComponent(..)) => Err(()),
728            (&TreeCountingFunction(_), &TreeCountingFunction(_)) => Err(()),
729            (&RandomKey(_), &RandomKey(_)) => Err(()),
730            _ => {
731                match *other {
732                    Number(..)
733                    | Percentage(..)
734                    | Angle(..)
735                    | Time(..)
736                    | Length(..)
737                    | Resolution(..)
738                    | ColorComponent(..)
739                    | TreeCountingFunction(..)
740                    | RandomKey(..) => {},
741                }
742                unsafe {
743                    debug_unreachable!();
744                }
745            },
746        }
747    }
748
749    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
750        let _: () = match self {
751            Leaf::Length(one) => *one = one.map(op),
752            Leaf::Angle(one) => *one = NoCalcAngle::from_degrees(op(one.degrees())),
753            Leaf::Time(one) => *one = NoCalcTime::from_seconds(op(one.seconds())),
754            Leaf::Resolution(one) => *one = NoCalcResolution::from_dppx(op(one.dppx())),
755            Leaf::Percentage(one) => *one = CalcPercentageLeaf::new(op(one.get()), one.hint),
756            Leaf::Number(one) => *one = NoCalcNumber::new(op(one.value())),
757            Leaf::ColorComponent(..) | Leaf::TreeCountingFunction(..) | Leaf::RandomKey(..) => {
758                return Err(());
759            },
760        };
761        Ok(())
762    }
763
764    fn should_serialize_with_root_calc_wrapper(&self) -> bool {
765        match self {
766            Leaf::Length(_)
767            | Leaf::Angle(_)
768            | Leaf::Time(_)
769            | Leaf::Resolution(_)
770            | Leaf::ColorComponent(_)
771            | Leaf::Percentage(_)
772            | Leaf::Number(_) => true,
773            Leaf::TreeCountingFunction(_) | Leaf::RandomKey(_) => false,
774        }
775    }
776}
777
778impl GenericAnchorSide<Box<CalcNode>> {
779    fn parse_in_calc(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
780        if let Ok(k) = input.try_parse(|i| AnchorSideKeyword::parse(i)) {
781            return Ok(Self::Keyword(k));
782        }
783        Ok(Self::Percentage(Box::new(CalcNode::parse_argument(
784            context,
785            input,
786            CalcParseFlags::new(PercentageContext::allowed_with_hint(
787                NumericBaseType::Percent,
788            )),
789        )?)))
790    }
791}
792
793fn parse_anchor_function_fallback(
794    context: &ParserContext,
795    additional_functions: AdditionalFunctions,
796    input: &mut Parser,
797) -> Result<Box<GenericAnchorFunctionFallback<Leaf>>, ParseError> {
798    if let Ok(l) = input.try_parse(|i| -> Result<CalcNode, ParseError> {
799        Ok(CalcNode::Leaf(match *(i.next()?) {
800            Token::Number { value, .. } => {
801                if value != 0.0 {
802                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
803                }
804                Leaf::Length(NoCalcLength::from_px(0.0))
805            },
806            Token::Dimension {
807                value, ref unit, ..
808            } => Leaf::Length(
809                NoCalcLength::parse_dimension_with_context(context, value, unit)
810                    .map_err(|_| ParseError::custom(StyleParseErrorKind::UnspecifiedError))?,
811            ),
812            Token::Percentage { unit_value, .. } => Leaf::Percentage(CalcPercentageLeaf::new(
813                unit_value,
814                Optional::Some(NumericBaseType::Length),
815            )),
816            _ => return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
817        }))
818    }) {
819        return Ok(Box::new(GenericAnchorFunctionFallback::new(false, l)));
820    }
821    let node = CalcNode::parse_argument(
822        context,
823        input,
824        CalcParseFlags {
825            additional_functions,
826            percentage_context: PercentageContext::allowed_with_hint(NumericBaseType::Length),
827            ..Default::default()
828        },
829    )?
830    .into_length_or_percentage(AllowedNumericType::All)
831    .map_err(|_| ParseError::custom(StyleParseErrorKind::UnspecifiedError))?
832    .0
833    .node;
834    Ok(Box::new(GenericAnchorFunctionFallback::new(true, node)))
835}
836
837impl GenericAnchorFunction<Box<CalcNode>, Box<GenericAnchorFunctionFallback<Leaf>>> {
838    fn parse_in_calc(
839        context: &ParserContext,
840        additional_functions: AdditionalFunctions,
841        input: &mut Parser,
842    ) -> Result<Self, ParseError> {
843        if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
844            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
845        }
846        input.parse_nested_block(|i| {
847            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
848            let side = GenericAnchorSide::parse_in_calc(context, i)?;
849            let target_element = if target_element.is_none() {
850                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
851            } else {
852                target_element
853            };
854            let fallback = i
855                .try_parse(|i| {
856                    i.expect_comma()?;
857                    parse_anchor_function_fallback(context, additional_functions, i)
858                })
859                .ok();
860            Ok(Self {
861                target_element: TreeScoped::with_default_level(
862                    target_element.unwrap_or_else(DashedIdent::empty),
863                ),
864                side,
865                fallback: fallback.into(),
866            })
867        })
868    }
869}
870
871impl GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<Leaf>>> {
872    fn parse_in_calc(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
873        if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
874            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
875        }
876        GenericAnchorSizeFunction::parse_inner(context, input, |i| {
877            parse_anchor_function_fallback(context, AdditionalFunctions::ANCHOR_SIZE, i)
878        })
879    }
880}
881
882/// Specified `anchor()` function in math functions.
883pub type CalcAnchorFunction = generic::GenericCalcAnchorFunction<Leaf>;
884/// Specified `anchor-size()` function in math functions.
885pub type CalcAnchorSizeFunction = generic::GenericCalcAnchorSizeFunction<Leaf>;
886
887/// Whether in place operations should be done when parsing expressions to create CalcNode
888#[derive(Clone, Copy, PartialEq, Eq)]
889pub enum CalcNodeParseInPlaceOperations {
890    /// Avoid in place operations
891    No,
892    /// Alow in place operations
893    Yes,
894}
895
896/// A calc node representation for specified values.
897pub type SpecifiedCalcNode = generic::GenericCalcNode<Leaf>;
898pub use self::SpecifiedCalcNode as CalcNode;
899
900impl CalcNode {
901    /// Tries to parse a single element in the expression, that is, a
902    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, `<resolution>`, etc.
903    ///
904    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
905    /// expression, for example.
906    fn parse_one(
907        context: &ParserContext,
908        input: &mut Parser,
909        flags: CalcParseFlags,
910    ) -> Result<Self, ParseError> {
911        match input.next()? {
912            &Token::Number { value, .. } => {
913                Ok(CalcNode::Leaf(Leaf::Number(NoCalcNumber::new(value))))
914            },
915            &Token::Dimension {
916                value, ref unit, ..
917            } => {
918                if let Ok(l) = NoCalcLength::parse_dimension_with_context(context, value, unit) {
919                    return Ok(CalcNode::Leaf(Leaf::Length(l)));
920                }
921                if let Ok(a) = NoCalcAngle::parse_dimension(value, unit) {
922                    return Ok(CalcNode::Leaf(Leaf::Angle(a)));
923                }
924                if let Ok(t) = NoCalcTime::parse_dimension(value, unit) {
925                    return Ok(CalcNode::Leaf(Leaf::Time(t)));
926                }
927                if let Ok(t) = NoCalcResolution::parse_dimension(value, unit) {
928                    return Ok(CalcNode::Leaf(Leaf::Resolution(t)));
929                }
930                Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
931            },
932            &Token::Percentage { unit_value, .. } => {
933                let hint = match flags.percentage_context {
934                    PercentageContext::NotAllowed => {
935                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
936                    },
937                    PercentageContext::Allowed(hint) => hint,
938                };
939                Ok(CalcNode::Leaf(Leaf::Percentage(CalcPercentageLeaf::new(
940                    unit_value, hint,
941                ))))
942            },
943            &Token::ParenthesisBlock => {
944                input.parse_nested_block(|input| CalcNode::parse_argument(context, input, flags))
945            },
946            Token::Function(name)
947                if flags
948                    .additional_functions
949                    .intersects(AdditionalFunctions::ANCHOR)
950                    && name.eq_ignore_ascii_case("anchor") =>
951            {
952                let anchor_function = GenericAnchorFunction::parse_in_calc(
953                    context,
954                    flags.additional_functions,
955                    input,
956                )?;
957                Ok(CalcNode::Anchor(Box::new(anchor_function)))
958            },
959            Token::Function(name)
960                if flags
961                    .additional_functions
962                    .intersects(AdditionalFunctions::ANCHOR_SIZE)
963                    && name.eq_ignore_ascii_case("anchor-size") =>
964            {
965                let anchor_size_function =
966                    GenericAnchorSizeFunction::parse_in_calc(context, input)?;
967                Ok(CalcNode::AnchorSize(Box::new(anchor_size_function)))
968            },
969            Token::Function(name) => {
970                let function = CalcNode::math_function(context, name)?;
971                CalcNode::parse(context, input, function, flags)
972            },
973            Token::Ident(ident) => {
974                let leaf = match_ignore_ascii_case! { &**ident,
975                    "e" => Leaf::Number(NoCalcNumber::new(std::f32::consts::E)),
976                    "pi" => Leaf::Number(NoCalcNumber::new(std::f32::consts::PI)),
977                    "infinity" => Leaf::Number(NoCalcNumber::new(f32::INFINITY)),
978                    "-infinity" => Leaf::Number(NoCalcNumber::new(f32::NEG_INFINITY)),
979                    "nan" => Leaf::Number(NoCalcNumber::new(f32::NAN)),
980                    _ => {
981                        match ChannelKeyword::from_ident(ident) {
982                            Ok(channel_keyword) if flags.color_components.contains(channel_keyword) => Leaf::ColorComponent(channel_keyword),
983                            _ => return Err(ParseError::unexpected_token()),
984                        }
985                    },
986                };
987                Ok(CalcNode::Leaf(leaf))
988            },
989            _ => Err(ParseError::unexpected_token()),
990        }
991    }
992
993    /// Parse a top-level `calc` expression, with all nested sub-expressions.
994    ///
995    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
996    pub fn parse(
997        context: &ParserContext,
998        input: &mut Parser,
999        function: MathFunction,
1000        flags: CalcParseFlags,
1001    ) -> Result<Self, ParseError> {
1002        input.parse_nested_block(|input| {
1003            fn consistent_type(a: &CalcNode, b: &CalcNode) -> Result<CalcType, ()> {
1004                let a_ty = a.numeric_type()?;
1005                let b_ty = b.numeric_type()?;
1006                NumericType::add_two_types(&a_ty, &b_ty).and_then(|ty| ty.as_calc_type())
1007            }
1008
1009            fn consistent_type_multi(arguments: &[CalcNode]) -> Result<CalcType, ()> {
1010                let mut ty = arguments.first().unwrap().numeric_type()?;
1011                for arg in arguments.iter().skip(1) {
1012                    let arg_ty = arg.numeric_type()?;
1013                    ty = NumericType::add_two_types(&ty, &arg_ty)?;
1014                }
1015                ty.as_calc_type()
1016            }
1017
1018            macro_rules! require_consistent_type {
1019                ($a:expr, $b:expr) => {{
1020                    let _ = consistent_type(&$a, &$b)
1021                        .map_err(|_| ParseError::custom(StyleParseErrorKind::UnspecifiedError))?;
1022                }};
1023                ($nodes:expr) => {{
1024                    let _ = consistent_type_multi(&$nodes)
1025                        .map_err(|_| ParseError::custom(StyleParseErrorKind::UnspecifiedError))?;
1026                }};
1027            }
1028
1029            match function {
1030                MathFunction::Calc => Self::parse_argument(context, input, flags),
1031                MathFunction::Clamp => {
1032                    let min_val = if input
1033                        .try_parse(|min| min.expect_ident_matching("none"))
1034                        .ok()
1035                        .is_none()
1036                    {
1037                        Some(Self::parse_argument(context, input, flags)?)
1038                    } else {
1039                        None
1040                    };
1041
1042                    input.expect_comma()?;
1043                    let center = Self::parse_argument(context, input, flags)?;
1044                    input.expect_comma()?;
1045
1046                    let max_val = if input
1047                        .try_parse(|max| max.expect_ident_matching("none"))
1048                        .ok()
1049                        .is_none()
1050                    {
1051                        Some(Self::parse_argument(context, input, flags)?)
1052                    } else {
1053                        None
1054                    };
1055
1056                    // Specification does not state how serialization should occur for clamp
1057                    // https://github.com/w3c/csswg-drafts/issues/13535
1058                    // tentatively partially serialize to min/max
1059                    // clamp(MIN, VAL, none) is equivalent to max(MIN, VAL)
1060                    // clamp(none, VAL, MAX) is equivalent to min(VAL, MAX)
1061                    // clamp(none, VAL, none) is equivalent to just calc(VAL)
1062                    Ok(match (min_val, max_val) {
1063                        (None, None) => center,
1064                        (None, Some(max)) => {
1065                            require_consistent_type!(center, max);
1066                            Self::MinMax(vec![center, max].into(), MinMaxOp::Min)
1067                        },
1068                        (Some(min), None) => {
1069                            require_consistent_type!(min, center);
1070                            Self::MinMax(vec![min, center].into(), MinMaxOp::Max)
1071                        },
1072                        (Some(min), Some(max)) => {
1073                            require_consistent_type!(min, center);
1074                            require_consistent_type!(center, max);
1075                            require_consistent_type!(min, max);
1076                            Self::Clamp {
1077                                min: Box::new(min),
1078                                center: Box::new(center),
1079                                max: Box::new(max),
1080                            }
1081                        },
1082                    })
1083                },
1084                MathFunction::Round => {
1085                    let strategy = input.try_parse(parse_rounding_strategy);
1086
1087                    // <rounding-strategy> = nearest | up | down | to-zero
1088                    // https://drafts.csswg.org/css-values-4/#calc-syntax
1089                    fn parse_rounding_strategy(
1090                        input: &mut Parser,
1091                    ) -> Result<RoundingStrategy, ParseError> {
1092                        Ok(try_match_ident_ignore_ascii_case! { input,
1093                            "nearest" => RoundingStrategy::Nearest,
1094                            "up" => RoundingStrategy::Up,
1095                            "down" => RoundingStrategy::Down,
1096                            "to-zero" => RoundingStrategy::ToZero,
1097                        })
1098                    }
1099
1100                    if strategy.is_ok() {
1101                        input.expect_comma()?;
1102                    }
1103
1104                    let value = Self::parse_argument(context, input, flags)?;
1105
1106                    // <step> defaults to the number 1 if not provided
1107                    // https://drafts.csswg.org/css-values-4/#funcdef-round
1108                    let step = input.try_parse(|input| {
1109                        input.expect_comma()?;
1110                        Self::parse_argument(context, input, flags)
1111                    });
1112
1113                    let step = step.unwrap_or(Self::Leaf(Leaf::Number(NoCalcNumber::new(1.0))));
1114                    require_consistent_type!(value, step);
1115
1116                    Ok(Self::Round {
1117                        strategy: strategy.unwrap_or(RoundingStrategy::Nearest),
1118                        value: Box::new(value),
1119                        step: Box::new(step),
1120                    })
1121                },
1122                MathFunction::Mod | MathFunction::Rem => {
1123                    let dividend = Self::parse_argument(context, input, flags)?;
1124                    input.expect_comma()?;
1125                    let divisor = Self::parse_argument(context, input, flags)?;
1126                    require_consistent_type!(dividend, divisor);
1127
1128                    let op = match function {
1129                        MathFunction::Mod => ModRemOp::Mod,
1130                        MathFunction::Rem => ModRemOp::Rem,
1131                        _ => unreachable!(),
1132                    };
1133                    Ok(Self::ModRem {
1134                        dividend: Box::new(dividend),
1135                        divisor: Box::new(divisor),
1136                        op,
1137                    })
1138                },
1139                MathFunction::Min | MathFunction::Max => {
1140                    // TODO(emilio): The common case for parse_comma_separated
1141                    // is just one element, but for min / max is two, really...
1142                    //
1143                    // Consider adding an API to cssparser to specify the
1144                    // initial vector capacity?
1145                    let arguments = input.parse_comma_separated(|input| {
1146                        let result = Self::parse_argument(context, input, flags)?;
1147                        Ok(result)
1148                    })?;
1149                    require_consistent_type!(arguments);
1150
1151                    let op = match function {
1152                        MathFunction::Min => MinMaxOp::Min,
1153                        MathFunction::Max => MinMaxOp::Max,
1154                        _ => unreachable!(),
1155                    };
1156
1157                    Ok(Self::MinMax(arguments.into(), op))
1158                },
1159                MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => {
1160                    let node = Self::parse_argument(context, input, flags)?;
1161                    Ok(match function {
1162                        MathFunction::Sin => Self::Sin(Box::new(node)),
1163                        MathFunction::Cos => Self::Cos(Box::new(node)),
1164                        MathFunction::Tan => Self::Tan(Box::new(node)),
1165                        _ => unsafe { debug_unreachable!("We just checked!") },
1166                    })
1167                },
1168                MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => {
1169                    let node = Self::parse_argument(context, input, flags)?;
1170                    Ok(match function {
1171                        MathFunction::Asin => Self::Asin(Box::new(node)),
1172                        MathFunction::Acos => Self::Acos(Box::new(node)),
1173                        MathFunction::Atan => Self::Atan(Box::new(node)),
1174                        _ => unsafe { debug_unreachable!("We just checked!") },
1175                    })
1176                },
1177                MathFunction::Atan2 => {
1178                    let a = Self::parse_argument(context, input, flags)?;
1179                    input.expect_comma()?;
1180                    let b = Self::parse_argument(context, input, flags)?;
1181                    require_consistent_type!(a, b);
1182                    Ok(Self::Atan2(Box::new(a), Box::new(b)))
1183                },
1184                MathFunction::Pow => {
1185                    let a = Self::parse_argument(context, input, flags)?;
1186                    input.expect_comma()?;
1187                    let b = Self::parse_argument(context, input, flags)?;
1188                    Ok(Self::Pow(Box::new(a), Box::new(b)))
1189                },
1190                MathFunction::Sqrt => {
1191                    let a = Self::parse_argument(context, input, flags)?;
1192                    Ok(Self::Sqrt(Box::new(a)))
1193                },
1194                MathFunction::Hypot => {
1195                    let arguments = input.parse_comma_separated(|input| {
1196                        let result = Self::parse_argument(context, input, flags)?;
1197                        Ok(result)
1198                    })?;
1199                    require_consistent_type!(arguments);
1200                    Ok(Self::Hypot(arguments.into()))
1201                },
1202                MathFunction::Log => {
1203                    let a = Self::parse_argument(context, input, flags)?;
1204                    let b = input
1205                        .try_parse(|input| {
1206                            input.expect_comma()?;
1207                            Self::parse_argument(context, input, flags)
1208                        })
1209                        .ok();
1210                    Ok(Self::Log(Box::new(a), b.map(Box::new).into()))
1211                },
1212                MathFunction::Exp => {
1213                    let a = Self::parse_argument(context, input, flags)?;
1214                    Ok(Self::Exp(Box::new(a)))
1215                },
1216                MathFunction::Abs => {
1217                    let node = Self::parse_argument(context, input, flags)?;
1218                    Ok(Self::Abs(Box::new(node)))
1219                },
1220                MathFunction::Sign => {
1221                    let node = Self::parse_argument(context, input, flags)?;
1222                    Ok(Self::Sign(Box::new(node)))
1223                },
1224                MathFunction::Progress => {
1225                    if !crate::pref!("layout.css.progress-function.enabled") {
1226                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1227                    }
1228
1229                    let clamping_mode = input
1230                        .try_parse(|i| ProgressClampingMode::parse(i))
1231                        .unwrap_or(ProgressClampingMode::Clamp);
1232
1233                    let value = Self::parse_argument(context, input, flags)?;
1234                    input.expect_comma()?;
1235                    let start = Self::parse_argument(context, input, flags)?;
1236                    input.expect_comma()?;
1237                    let end = Self::parse_argument(context, input, flags)?;
1238
1239                    require_consistent_type!(value, start);
1240                    require_consistent_type!(value, end);
1241                    require_consistent_type!(start, end);
1242
1243                    Ok(Self::Progress {
1244                        clamping_mode,
1245                        value: Box::new(value),
1246                        start: Box::new(start),
1247                        end: Box::new(end),
1248                    })
1249                },
1250                MathFunction::Random => {
1251                    if !crate::pref!("layout.css.random.enabled") {
1252                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1253                    }
1254
1255                    // Increment the random() function count so that any `property-index-scoped`
1256                    // random keys construct the correct random cache name when parsed.
1257                    context
1258                        .property_declaration_context
1259                        .increment_random_count();
1260
1261                    let key = match input.try_parse(|input| RandomKey::parse(context, input)) {
1262                        Ok(key) => {
1263                            input.expect_comma()?;
1264                            key
1265                        },
1266                        // The <random-key> is optional, and behaves as `auto`.
1267                        Err(_) => RandomKey::auto(context)?,
1268                    };
1269                    let min = Self::parse_argument(context, input, flags)?;
1270                    input.expect_comma()?;
1271                    let max = Self::parse_argument(context, input, flags)?;
1272                    let step = input.try_parse(|input| {
1273                        input.expect_comma()?;
1274                        Self::parse_argument(context, input, flags)
1275                    });
1276
1277                    Ok(Self::Random(Box::new(GenericRandomFunction {
1278                        key: Self::Leaf(Leaf::RandomKey(Box::new(key))),
1279                        min,
1280                        max,
1281                        step: step.ok().into(),
1282                    })))
1283                },
1284                MathFunction::SiblingCount | MathFunction::SiblingIndex => {
1285                    if !crate::pref!("layout.css.tree-counting-functions.enabled") {
1286                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1287                    }
1288
1289                    if !context.has_element_context() {
1290                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1291                    }
1292
1293                    // Tree-counting functions have no arguments
1294                    input.expect_exhausted()?;
1295
1296                    Ok(Self::Leaf(Leaf::TreeCountingFunction(match function {
1297                        MathFunction::SiblingCount => TreeCountingFunction::SiblingCount,
1298                        MathFunction::SiblingIndex => TreeCountingFunction::SiblingIndex,
1299                        _ => unsafe { debug_unreachable!("We just checked!") },
1300                    })))
1301                },
1302            }
1303        })
1304    }
1305
1306    fn parse_argument(
1307        context: &ParserContext,
1308        input: &mut Parser,
1309        flags: CalcParseFlags,
1310    ) -> Result<Self, ParseError> {
1311        let mut sum = SmallVec::<[CalcNode; 1]>::new();
1312        let first = Self::parse_product(context, input, flags)?;
1313        sum.push(first);
1314        loop {
1315            let start = input.state();
1316            match input.next_including_whitespace() {
1317                Ok(&Token::WhiteSpace(_)) => {
1318                    if input.is_exhausted() {
1319                        break; // allow trailing whitespace
1320                    }
1321                    match *input.next()? {
1322                        Token::Delim('+') => {
1323                            let rhs = Self::parse_product(context, input, flags)?;
1324                            if flags.in_place_operations == CalcNodeParseInPlaceOperations::No
1325                                || sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err()
1326                            {
1327                                sum.push(rhs);
1328                            }
1329                        },
1330                        Token::Delim('-') => {
1331                            let mut rhs = Self::parse_product(context, input, flags)?;
1332                            rhs.negate();
1333                            if flags.in_place_operations == CalcNodeParseInPlaceOperations::No
1334                                || sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err()
1335                            {
1336                                sum.push(rhs);
1337                            }
1338                        },
1339                        _ => {
1340                            input.reset(&start);
1341                            break;
1342                        },
1343                    }
1344                },
1345                _ => {
1346                    input.reset(&start);
1347                    break;
1348                },
1349            }
1350        }
1351
1352        Ok(if sum.len() == 1 {
1353            sum.drain(..).next().unwrap()
1354        } else {
1355            Self::Sum(sum.into_boxed_slice().into())
1356        })
1357    }
1358
1359    /// Parse a top-level `calc` expression, and all the products that may
1360    /// follow, and stop as soon as a non-product expression is found.
1361    ///
1362    /// This should parse correctly:
1363    ///
1364    /// * `2`
1365    /// * `2 * 2`
1366    /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
1367    ///
1368    fn parse_product(
1369        context: &ParserContext,
1370        input: &mut Parser,
1371        flags: CalcParseFlags,
1372    ) -> Result<Self, ParseError> {
1373        let mut product = SmallVec::<[CalcNode; 1]>::new();
1374        let first = Self::parse_one(context, input, flags)?;
1375        product.push(first);
1376
1377        loop {
1378            let start = input.state();
1379            match input.next() {
1380                Ok(&Token::Delim('*')) => {
1381                    let mut rhs = Self::parse_one(context, input, flags)?;
1382
1383                    // We can unwrap here, because we start the function by adding a node to
1384                    // the list.
1385                    if flags.in_place_operations == CalcNodeParseInPlaceOperations::No
1386                        || !product.last_mut().unwrap().try_product_in_place(&mut rhs)
1387                    {
1388                        product.push(rhs);
1389                    }
1390                },
1391                Ok(&Token::Delim('/')) => {
1392                    let rhs = Self::parse_one(context, input, flags)?;
1393
1394                    enum InPlaceDivisionResult {
1395                        /// The right was merged into the left.
1396                        Merged,
1397                        /// The right is not a number or could not be resolved, so the left is
1398                        /// unchanged.
1399                        Unchanged,
1400                        /// The division should have been applied in-place, but could not due
1401                        /// to an error, making the calculation invalid.
1402                        Invalid,
1403                    }
1404
1405                    fn try_division_in_place(
1406                        left: &mut CalcNode,
1407                        right: &CalcNode,
1408                        in_place_operations: CalcNodeParseInPlaceOperations,
1409                    ) -> InPlaceDivisionResult {
1410                        if in_place_operations == CalcNodeParseInPlaceOperations::No {
1411                            return InPlaceDivisionResult::Unchanged;
1412                        }
1413
1414                        if let Ok(resolved) = right.resolve()
1415                            && let Some(number) = resolved.as_number()
1416                            && number != 1.0
1417                            && left.is_product_distributive()
1418                        {
1419                            if left.map(|l| l / number).is_err() {
1420                                return InPlaceDivisionResult::Invalid;
1421                            }
1422                            return InPlaceDivisionResult::Merged;
1423                        }
1424                        InPlaceDivisionResult::Unchanged
1425                    }
1426
1427                    // If the left-hand side supported in-place division and the right-hand
1428                    // side was a resolved number, then the division was already applied
1429                    // and merged, so no further work is required. Otherwise, the right-hand
1430                    // side is emitted as an Invert node.
1431                    match try_division_in_place(
1432                        product.last_mut().unwrap(),
1433                        &rhs,
1434                        flags.in_place_operations,
1435                    ) {
1436                        InPlaceDivisionResult::Merged => {},
1437                        InPlaceDivisionResult::Unchanged => {
1438                            product.push(Self::Invert(Box::new(rhs)))
1439                        },
1440                        InPlaceDivisionResult::Invalid => {
1441                            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1442                        },
1443                    }
1444                },
1445                _ => {
1446                    input.reset(&start);
1447                    break;
1448                },
1449            }
1450        }
1451
1452        Ok(if product.len() == 1 {
1453            product.drain(..).next().unwrap()
1454        } else {
1455            Self::Product(product.into_boxed_slice().into())
1456        })
1457    }
1458
1459    /// Computes this calc tree against the given context (if any), resolving
1460    /// context-dependent leaves (e.g. lengths) and substituting color channel
1461    /// references against `origin_color` when provided.
1462    pub fn to_computed_value(
1463        &self,
1464        context: Option<&computed::Context>,
1465        origin_color: Option<&AbsoluteColor>,
1466    ) -> Result<Self, ()> {
1467        let mut failed = false;
1468        let result = self.map_leaves(|leaf| {
1469            match leaf.to_computed_value(context, origin_color) {
1470                Ok(c) => c,
1471                Err(()) => {
1472                    // XXX a bit hacky way of propagating the error...
1473                    failed = true;
1474                    leaf.clone()
1475                },
1476            }
1477        });
1478        if failed {
1479            return Err(());
1480        }
1481        return Ok(result);
1482    }
1483
1484    /// Tries to simplify this expression into a `<length>` value. Used for properties that
1485    /// accept `<length>` but not `<length-percentage>`.
1486    pub fn into_length(
1487        mut self,
1488        clamping_mode: AllowedNumericType,
1489    ) -> Result<CalcLengthPercentage, ()> {
1490        self.simplify_and_sort();
1491
1492        if self.numeric_type_as_calc_type()? != CalcType::Length {
1493            return Err(());
1494        }
1495
1496        Ok(CalcLengthPercentage(CalcNumeric {
1497            clamping_mode,
1498            node: self,
1499        }))
1500    }
1501
1502    /// Tries to simplify this expression into a `<length>` or `<percentage>`
1503    /// value.
1504    pub fn into_length_or_percentage(
1505        mut self,
1506        clamping_mode: AllowedNumericType,
1507    ) -> Result<CalcLengthPercentage, ()> {
1508        self.simplify_and_sort();
1509
1510        let ty = self.numeric_type_as_calc_type()?;
1511        if ty != CalcType::Length && ty != CalcType::Percentage {
1512            return Err(());
1513        }
1514
1515        Ok(CalcLengthPercentage(CalcNumeric {
1516            clamping_mode,
1517            node: self,
1518        }))
1519    }
1520
1521    /// Tries to simplify this expression into a `<time>` value.
1522    fn into_time(mut self, clamping_mode: AllowedNumericType) -> Result<CalcNumeric, ()> {
1523        self.simplify_and_sort();
1524
1525        if self.numeric_type_as_calc_type()? != CalcType::Time {
1526            return Err(());
1527        }
1528
1529        Ok(CalcNumeric {
1530            clamping_mode,
1531            node: self,
1532        })
1533    }
1534
1535    /// Tries to simplify this expression into a `<resolution>` value.
1536    fn into_resolution(mut self) -> Result<CalcNumeric, ()> {
1537        self.simplify_and_sort();
1538
1539        if self.numeric_type_as_calc_type()? != CalcType::Resolution {
1540            return Err(());
1541        }
1542
1543        Ok(CalcNumeric {
1544            clamping_mode: AllowedNumericType::NonNegative,
1545            node: self,
1546        })
1547    }
1548
1549    /// Tries to simplify this expression into a `CalcNumeric` value.
1550    fn into_angle(mut self, clamping_mode: AllowedNumericType) -> Result<CalcNumeric, ()> {
1551        self.simplify_and_sort();
1552
1553        if self.numeric_type_as_calc_type()? != CalcType::Angle {
1554            return Err(());
1555        }
1556
1557        Ok(CalcNumeric {
1558            clamping_mode,
1559            node: self,
1560        })
1561    }
1562
1563    /// Tries to convert this expression into a `CalcNumeric`, keeping the
1564    /// AST for later evaluation at computed-value time.
1565    fn into_number(mut self, clamping_mode: AllowedNumericType) -> Result<CalcNumeric, ()> {
1566        self.simplify_and_sort();
1567
1568        if self.numeric_type_as_calc_type()? != CalcType::Number {
1569            return Err(());
1570        }
1571
1572        Ok(CalcNumeric {
1573            clamping_mode,
1574            node: self,
1575        })
1576    }
1577
1578    /// Tries to convert this expression into a `CalcNumeric`, keeping the
1579    /// AST for later evaluation at computed-value time.
1580    fn into_percentage(mut self, clamping_mode: AllowedNumericType) -> Result<CalcNumeric, ()> {
1581        self.simplify_and_sort();
1582
1583        if self.numeric_type_as_calc_type()? != CalcType::Percentage {
1584            return Err(());
1585        }
1586
1587        Ok(CalcNumeric {
1588            clamping_mode,
1589            node: self,
1590        })
1591    }
1592
1593    /// Given a function name, and the location from where the token came from,
1594    /// return a mathematical function corresponding to that name or an error.
1595    #[inline]
1596    pub fn math_function<'i>(
1597        _: &ParserContext,
1598        name: &CowRcStr<'i>,
1599    ) -> Result<MathFunction, ParseError> {
1600        let function = match MathFunction::from_ident(name) {
1601            Ok(f) => f,
1602            Err(()) => return Err(ParseError::unexpected_token()),
1603        };
1604
1605        Ok(function)
1606    }
1607
1608    /// Convenience parsing function for `<length> | <percentage>`, and, optionally, `anchor()`.
1609    pub fn parse_length_or_percentage(
1610        context: &ParserContext,
1611        input: &mut Parser,
1612        clamping_mode: AllowedNumericType,
1613        function: MathFunction,
1614        allow_anchor: AllowAnchorPositioningFunctions,
1615    ) -> Result<CalcLengthPercentage, ParseError> {
1616        let percentage_context = PercentageContext::allowed_with_hint(NumericBaseType::Length);
1617        let additional_functions = match allow_anchor {
1618            AllowAnchorPositioningFunctions::No => AdditionalFunctions::empty(),
1619            AllowAnchorPositioningFunctions::AllowAnchorSize => AdditionalFunctions::ANCHOR_SIZE,
1620            AllowAnchorPositioningFunctions::AllowAnchorAndAnchorSize => {
1621                AdditionalFunctions::ANCHOR | AdditionalFunctions::ANCHOR_SIZE
1622            },
1623        };
1624        let flags = CalcParseFlags {
1625            additional_functions,
1626            percentage_context,
1627            ..Default::default()
1628        };
1629        Self::parse(context, input, function, flags)?
1630            .into_length_or_percentage(clamping_mode)
1631            .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1632    }
1633
1634    /// Convenience parsing function for percentages.
1635    pub fn parse_percentage(
1636        context: &ParserContext,
1637        input: &mut Parser,
1638        clamping_mode: AllowedNumericType,
1639        function: MathFunction,
1640    ) -> Result<CalcNumeric, ParseError> {
1641        Self::parse(
1642            context,
1643            input,
1644            function,
1645            CalcParseFlags::new(PercentageContext::allowed_with_hint(
1646                NumericBaseType::Percent,
1647            )),
1648        )?
1649        .into_percentage(clamping_mode)
1650        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1651    }
1652
1653    /// Convenience parsing function for `<length>`.
1654    pub fn parse_length(
1655        context: &ParserContext,
1656        input: &mut Parser,
1657        clamping_mode: AllowedNumericType,
1658        function: MathFunction,
1659        percentage_context: PercentageContext,
1660    ) -> Result<CalcLengthPercentage, ParseError> {
1661        Self::parse(
1662            context,
1663            input,
1664            function,
1665            CalcParseFlags::new(percentage_context),
1666        )?
1667        .into_length(clamping_mode)
1668        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1669    }
1670
1671    /// Convenience parsing function for `<number>`.
1672    pub fn parse_number(
1673        context: &ParserContext,
1674        input: &mut Parser,
1675        clamping_mode: AllowedNumericType,
1676        function: MathFunction,
1677        percentage_context: PercentageContext,
1678    ) -> Result<CalcNumeric, ParseError> {
1679        Self::parse(
1680            context,
1681            input,
1682            function,
1683            CalcParseFlags::new(percentage_context),
1684        )?
1685        .into_number(clamping_mode)
1686        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1687    }
1688
1689    /// Convenience parsing function for `<angle>`.
1690    pub fn parse_angle(
1691        context: &ParserContext,
1692        input: &mut Parser,
1693        function: MathFunction,
1694        percentage_context: PercentageContext,
1695    ) -> Result<CalcNumeric, ParseError> {
1696        Self::parse(
1697            context,
1698            input,
1699            function,
1700            CalcParseFlags::new(percentage_context),
1701        )?
1702        .into_angle(AllowedNumericType::All)
1703        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1704    }
1705
1706    /// Convenience parsing function for `<time>`.
1707    pub fn parse_time(
1708        context: &ParserContext,
1709        input: &mut Parser,
1710        clamping_mode: AllowedNumericType,
1711        function: MathFunction,
1712        percentage_context: PercentageContext,
1713    ) -> Result<CalcNumeric, ParseError> {
1714        Self::parse(
1715            context,
1716            input,
1717            function,
1718            CalcParseFlags::new(percentage_context),
1719        )?
1720        .into_time(clamping_mode)
1721        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1722    }
1723
1724    /// Convenience parsing function for `<resolution>`.
1725    pub fn parse_resolution(
1726        context: &ParserContext,
1727        input: &mut Parser,
1728        function: MathFunction,
1729        percentage_context: PercentageContext,
1730    ) -> Result<CalcNumeric, ParseError> {
1731        Self::parse(
1732            context,
1733            input,
1734            function,
1735            CalcParseFlags::new(percentage_context),
1736        )?
1737        .into_resolution()
1738        .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1739    }
1740}