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