<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! [Calc expressions][calc].
//!
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation

use app_units::Au;
use cssparser::{Parser, Token};
use parser::ParserContext;
use std::ascii::AsciiExt;
use std::fmt;
use style_traits::ToCss;
use values::{CSSInteger, CSSFloat, HasViewportPercentage};
use values::specified::{Angle, Time};
use values::specified::length::{FontRelativeLength, NoCalcLength, ViewportPercentageLength};

/// A node inside a `Calc` expression's AST.
#[derive(Clone, Debug)]
pub enum CalcNode {
    /// `<length>`
    Length(NoCalcLength),
    /// `<angle>`
    Angle(Angle),
    /// `<time>`
    Time(Time),
    /// `<percentage>`
    Percentage(CSSFloat),
    /// `<number>`
    Number(CSSFloat),
    /// An expression of the form `x + y`
    Sum(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x - y`
    Sub(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x * y`
    Mul(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x / y`
    Div(Box<CalcNode>, Box<CalcNode>),
}

/// An expected unit we intend to parse within a `calc()` expression.
///
/// This is used as a hint for the parser to fast-reject invalid expressions.
#[derive(Clone, Copy, PartialEq)]
pub enum CalcUnit {
    /// `<number>`
    Number,
    /// `<integer>`
    Integer,
    /// `<length>`
    Length,
    /// `<length> | <percentage>`
    LengthOrPercentage,
    /// `<angle>`
    Angle,
    /// `<time>`
    Time,
}

/// A struct to hold a simplified `<length>` or `<percentage>` expression.
#[derive(Clone, PartialEq, Copy, Debug, Default)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub struct CalcLengthOrPercentage {
    pub absolute: Option<Au>,
    pub vw: Option<CSSFloat>,
    pub vh: Option<CSSFloat>,
    pub vmin: Option<CSSFloat>,
    pub vmax: Option<CSSFloat>,
    pub em: Option<CSSFloat>,
    pub ex: Option<CSSFloat>,
    pub ch: Option<CSSFloat>,
    pub rem: Option<CSSFloat>,
    pub percentage: Option<CSSFloat>,
    #[cfg(feature = "gecko")]
    pub mozmm: Option<CSSFloat>,
}

impl HasViewportPercentage for CalcLengthOrPercentage {
    fn has_viewport_percentage(&self) -> bool {
        self.vw.is_some() || self.vh.is_some() ||
        self.vmin.is_some() || self.vmax.is_some()
    }
}

impl ToCss for CalcLengthOrPercentage {
    #[allow(unused_assignments)]
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        let mut first_value = true;
        macro_rules! first_value_check {
            () => {
                if !first_value {
                    try!(dest.write_str(" + "));
                } else {
                    first_value = false;
                }
            };
        }

        macro_rules! serialize {
            ( $( $val:ident ),* ) => {
                $(
                    if let Some(val) = self.$val {
                        first_value_check!();
                        try!(val.to_css(dest));
                        try!(dest.write_str(stringify!($val)));
                    }
                )*
            };
        }

        try!(dest.write_str("calc("));

        serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);

        #[cfg(feature = "gecko")]
        {
            serialize!(mozmm);
        }

        if let Some(val) = self.absolute {
            first_value_check!();
            try!(val.to_css(dest));
        }

        if let Some(val) = self.percentage {
            first_value_check!();
            try!(write!(dest, "{}%", val * 100.));
        }

        write!(dest, ")")
    }
}

impl CalcNode {
    /// Tries to parse a single element in the expression, that is, a
    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
    /// `expected_unit`.
    ///
    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
    /// expression, for example.
    fn parse_one(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        match (try!(input.next()), expected_unit) {
            (Token::Number(ref value), _) => Ok(CalcNode::Number(value.value)),
            (Token::Dimension(ref value, ref unit), CalcUnit::Length) |
            (Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
                NoCalcLength::parse_dimension(context, value.value, unit)
                    .map(CalcNode::Length)
            }
            (Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
                Angle::parse_dimension(value.value,
                                       unit,
                                       /* from_calc = */ true)
                    .map(CalcNode::Angle)
            }
            (Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
                Time::parse_dimension(value.value,
                                      unit,
                                      /* from_calc = */ true)
                    .map(CalcNode::Time)
            }
            (Token::Percentage(ref value), CalcUnit::LengthOrPercentage) => {
                Ok(CalcNode::Percentage(value.unit_value))
            }
            (Token::ParenthesisBlock, _) => {
                input.parse_nested_block(|i| {
                    CalcNode::parse(context, i, expected_unit)
                })
            }
            (Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
                input.parse_nested_block(|i| {
                    CalcNode::parse(context, i, expected_unit)
                })
            }
            _ => Err(())
        }
    }

    /// Parse a top-level `calc` expression, with all nested sub-expressions.
    ///
    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
    fn parse(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        let mut root = Self::parse_product(context, input, expected_unit)?;

        loop {
            let position = input.position();
            match input.next_including_whitespace() {
                Ok(Token::WhiteSpace(_)) => {
                    if input.is_exhausted() {
                        break; // allow trailing whitespace
                    }
                    match input.next()? {
                        Token::Delim('+') => {
                            let rhs =
                                Self::parse_product(context, input, expected_unit)?;
                            let new_root =
                                CalcNode::Sum(Box::new(root), Box::new(rhs));
                            root = new_root;
                        }
                        Token::Delim('-') => {
                            let rhs =
                                Self::parse_product(context, input, expected_unit)?;
                            let new_root =
                                CalcNode::Sub(Box::new(root), Box::new(rhs));
                            root = new_root;
                        }
                        _ => return Err(()),
                    }
                }
                _ => {
                    input.reset(position);
                    break
                }
            }
        }

        Ok(root)
    }

    /// Parse a top-level `calc` expression, and all the products that may
    /// follow, and stop as soon as a non-product expression is found.
    ///
    /// This should parse correctly:
    ///
    ///     * `2`
    ///     * `2 * 2`
    ///     * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
    ///
    fn parse_product(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        let mut root = Self::parse_one(context, input, expected_unit)?;<commit_msg>Fix markdown warning in parse_product doc comment<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! [Calc expressions][calc].
//!
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation

use app_units::Au;
use cssparser::{Parser, Token};
use parser::ParserContext;
use std::ascii::AsciiExt;
use std::fmt;
use style_traits::ToCss;
use values::{CSSInteger, CSSFloat, HasViewportPercentage};
use values::specified::{Angle, Time};
use values::specified::length::{FontRelativeLength, NoCalcLength, ViewportPercentageLength};

/// A node inside a `Calc` expression's AST.
#[derive(Clone, Debug)]
pub enum CalcNode {
    /// `<length>`
    Length(NoCalcLength),
    /// `<angle>`
    Angle(Angle),
    /// `<time>`
    Time(Time),
    /// `<percentage>`
    Percentage(CSSFloat),
    /// `<number>`
    Number(CSSFloat),
    /// An expression of the form `x + y`
    Sum(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x - y`
    Sub(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x * y`
    Mul(Box<CalcNode>, Box<CalcNode>),
    /// An expression of the form `x / y`
    Div(Box<CalcNode>, Box<CalcNode>),
}

/// An expected unit we intend to parse within a `calc()` expression.
///
/// This is used as a hint for the parser to fast-reject invalid expressions.
#[derive(Clone, Copy, PartialEq)]
pub enum CalcUnit {
    /// `<number>`
    Number,
    /// `<integer>`
    Integer,
    /// `<length>`
    Length,
    /// `<length> | <percentage>`
    LengthOrPercentage,
    /// `<angle>`
    Angle,
    /// `<time>`
    Time,
}

/// A struct to hold a simplified `<length>` or `<percentage>` expression.
#[derive(Clone, PartialEq, Copy, Debug, Default)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub struct CalcLengthOrPercentage {
    pub absolute: Option<Au>,
    pub vw: Option<CSSFloat>,
    pub vh: Option<CSSFloat>,
    pub vmin: Option<CSSFloat>,
    pub vmax: Option<CSSFloat>,
    pub em: Option<CSSFloat>,
    pub ex: Option<CSSFloat>,
    pub ch: Option<CSSFloat>,
    pub rem: Option<CSSFloat>,
    pub percentage: Option<CSSFloat>,
    #[cfg(feature = "gecko")]
    pub mozmm: Option<CSSFloat>,
}

impl HasViewportPercentage for CalcLengthOrPercentage {
    fn has_viewport_percentage(&self) -> bool {
        self.vw.is_some() || self.vh.is_some() ||
        self.vmin.is_some() || self.vmax.is_some()
    }
}

impl ToCss for CalcLengthOrPercentage {
    #[allow(unused_assignments)]
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        let mut first_value = true;
        macro_rules! first_value_check {
            () => {
                if !first_value {
                    try!(dest.write_str(" + "));
                } else {
                    first_value = false;
                }
            };
        }

        macro_rules! serialize {
            ( $( $val:ident ),* ) => {
                $(
                    if let Some(val) = self.$val {
                        first_value_check!();
                        try!(val.to_css(dest));
                        try!(dest.write_str(stringify!($val)));
                    }
                )*
            };
        }

        try!(dest.write_str("calc("));

        serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);

        #[cfg(feature = "gecko")]
        {
            serialize!(mozmm);
        }

        if let Some(val) = self.absolute {
            first_value_check!();
            try!(val.to_css(dest));
        }

        if let Some(val) = self.percentage {
            first_value_check!();
            try!(write!(dest, "{}%", val * 100.));
        }

        write!(dest, ")")
    }
}

impl CalcNode {
    /// Tries to parse a single element in the expression, that is, a
    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
    /// `expected_unit`.
    ///
    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
    /// expression, for example.
    fn parse_one(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        match (try!(input.next()), expected_unit) {
            (Token::Number(ref value), _) => Ok(CalcNode::Number(value.value)),
            (Token::Dimension(ref value, ref unit), CalcUnit::Length) |
            (Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
                NoCalcLength::parse_dimension(context, value.value, unit)
                    .map(CalcNode::Length)
            }
            (Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
                Angle::parse_dimension(value.value,
                                       unit,
                                       /* from_calc = */ true)
                    .map(CalcNode::Angle)
            }
            (Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
                Time::parse_dimension(value.value,
                                      unit,
                                      /* from_calc = */ true)
                    .map(CalcNode::Time)
            }
            (Token::Percentage(ref value), CalcUnit::LengthOrPercentage) => {
                Ok(CalcNode::Percentage(value.unit_value))
            }
            (Token::ParenthesisBlock, _) => {
                input.parse_nested_block(|i| {
                    CalcNode::parse(context, i, expected_unit)
                })
            }
            (Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
                input.parse_nested_block(|i| {
                    CalcNode::parse(context, i, expected_unit)
                })
            }
            _ => Err(())
        }
    }

    /// Parse a top-level `calc` expression, with all nested sub-expressions.
    ///
    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
    fn parse(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        let mut root = Self::parse_product(context, input, expected_unit)?;

        loop {
            let position = input.position();
            match input.next_including_whitespace() {
                Ok(Token::WhiteSpace(_)) => {
                    if input.is_exhausted() {
                        break; // allow trailing whitespace
                    }
                    match input.next()? {
                        Token::Delim('+') => {
                            let rhs =
                                Self::parse_product(context, input, expected_unit)?;
                            let new_root =
                                CalcNode::Sum(Box::new(root), Box::new(rhs));
                            root = new_root;
                        }
                        Token::Delim('-') => {
                            let rhs =
                                Self::parse_product(context, input, expected_unit)?;
                            let new_root =
                                CalcNode::Sub(Box::new(root), Box::new(rhs));
                            root = new_root;
                        }
                        _ => return Err(()),
                    }
                }
                _ => {
                    input.reset(position);
                    break
                }
            }
        }

        Ok(root)
    }

    /// Parse a top-level `calc` expression, and all the products that may
    /// follow, and stop as soon as a non-product expression is found.
    ///
    /// This should parse correctly:
    ///
    /// * `2`
    /// * `2 * 2`
    /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
    ///
    fn parse_product(
        context: &ParserContext,
        input: &mut Parser,
        expected_unit: CalcUnit)
        -> Result<Self, ()>
    {
        let mut root = Self::parse_one(context, input, expected_unit)?;
{'fraction_non_alphanumeric': 0.14036606480336383, 'fraction_numerical': 0.0023497402918624785, 'mean_word_length': 1.6005788712011577, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 125, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6803119', 'n_tokens_mistral': 4813, 'n_tokens_neox': 4639, 'n_words': 1069}