lewp_css/domain/expressions/
calc_expression.rs

1// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
3
4use {
5    super::{CalculablePropertyValue, Expression},
6    crate::{
7        domain::units::{
8            conversions::{
9                AttributeConversion,
10                CssVariableConversion,
11                FontRelativeLengthConversion,
12                PercentageConversion,
13                ViewportPercentageLengthConversion,
14            },
15            Unit,
16        },
17        parsers::ParserContext,
18        CustomParseError,
19    },
20    cssparser::{ParseError, Parser, ToCss},
21    either::{Either, Right},
22    std::fmt,
23};
24
25#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
26pub enum CalcExpression<U: Unit> {
27    CalculablePropertyValue(CalculablePropertyValue<U>),
28
29    Number(U::Number),
30
31    Parentheses(Box<CalcExpression<U>>),
32
33    Addition(Box<CalcExpression<U>>, Box<CalcExpression<U>>),
34
35    Subtraction(Box<CalcExpression<U>>, Box<CalcExpression<U>>),
36
37    Multiplication(Box<CalcExpression<U>>, Box<CalcExpression<U>>),
38
39    Division(Box<CalcExpression<U>>, Box<CalcExpression<U>>),
40}
41
42impl<U: Unit> Default for CalcExpression<U> {
43    #[inline(always)]
44    fn default() -> Self {
45        CalcExpression::CalculablePropertyValue(
46            CalculablePropertyValue::default(),
47        )
48    }
49}
50
51impl<U: Unit> ToCss for CalcExpression<U> {
52    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
53        use self::CalcExpression::*;
54
55        match *self {
56            CalculablePropertyValue(ref calculable) => calculable.to_css(dest),
57
58            Number(ref number) => number.to_css(dest),
59
60            Parentheses(ref calcFunctionBody) => {
61                dest.write_char('(')?;
62                calcFunctionBody.to_css(dest)?;
63                dest.write_char(')')
64            }
65
66            Addition(ref lhs, ref rhs) => {
67                lhs.to_css(dest)?;
68                // Whitespace should not be needed if the lhs ends in a ')' or the rhs begins with '(' but the spec does not permit this (https://www.w3.org/TR/css3-values/#calc-notation)
69                dest.write_str(" + ")?;
70                rhs.to_css(dest)
71            }
72
73            Subtraction(ref lhs, ref rhs) => {
74                lhs.to_css(dest)?;
75                // Whitespace should not be needed if the lhs ends in a ')' or the rhs begins with '(' but the spec does not permit this (https://www.w3.org/TR/css3-values/#calc-notation)
76                dest.write_str(" - ")?;
77                rhs.to_css(dest)
78            }
79
80            Multiplication(ref lhs, ref rhs) => {
81                lhs.to_css(dest)?;
82                dest.write_char('*')?;
83                rhs.to_css(dest)
84            }
85
86            Division(ref lhs, ref rhs) => {
87                lhs.to_css(dest)?;
88                dest.write_char('/')?;
89                rhs.to_css(dest)
90            }
91        }
92    }
93}
94
95impl<U: Unit> Expression<U> for CalcExpression<U> {
96    /// Evaluate the calc() expression, returning the numeric value of the canonical dimension
97    /// Division by zero is handled by returning the maximum possible f32 value
98    /// Subtractions for UnsignedCssNumber that are negative are handled by returning 0.0
99    /// Note: We are quite lenient with calculations of unit-less and unit-having quantities, eg 100px * 100px is evaluated to 10,000px, not 10,000px^2, and 50 + 100px is evaluated to 150px
100    #[inline(always)]
101    fn evaluate<
102        Conversion: FontRelativeLengthConversion<U::Number>
103            + ViewportPercentageLengthConversion<U::Number>
104            + PercentageConversion<U::Number>
105            + AttributeConversion<U>
106            + CssVariableConversion,
107    >(
108        &self,
109        conversion: &Conversion,
110    ) -> Option<U::Number> {
111        use self::CalcExpression::*;
112
113        match *self {
114            CalculablePropertyValue(ref calculable) => {
115                calculable.evaluate(conversion)
116            }
117
118            Number(number) => Some(number),
119
120            Parentheses(ref subExpression) => {
121                subExpression.evaluate(conversion)
122            }
123
124            Addition(ref lhsSubExpression, ref rhsSubExpression) => match (
125                lhsSubExpression.evaluate(conversion),
126                rhsSubExpression.evaluate(conversion),
127            ) {
128                (Some(lhs), Some(rhs)) => Some(lhs + rhs),
129                _ => None,
130            },
131
132            Subtraction(ref lhsSubExpression, ref rhsSubExpression) => match (
133                lhsSubExpression.evaluate(conversion),
134                rhsSubExpression.evaluate(conversion),
135            ) {
136                (Some(lhs), Some(rhs)) => Some(lhs - rhs),
137                _ => None,
138            },
139
140            Multiplication(ref lhsSubExpression, ref rhsSubExpression) => {
141                match (
142                    lhsSubExpression.evaluate(conversion),
143                    rhsSubExpression.evaluate(conversion),
144                ) {
145                    (Some(lhs), Some(rhs)) => Some(lhs * rhs),
146                    _ => None,
147                }
148            }
149
150            Division(ref lhsSubExpression, ref rhsSubExpression) => match (
151                lhsSubExpression.evaluate(conversion),
152                rhsSubExpression.evaluate(conversion),
153            ) {
154                (Some(lhs), Some(rhs)) => Some(lhs / rhs),
155                _ => None,
156            },
157        }
158    }
159}
160
161impl<U: Unit> CalcExpression<U> {
162    /// Parse a top-level `calc` expression, with all nested sub-expressions.
163    /// DOES NOT simplify expressions. This is because simplification is harder than it ought to be:-
164    /// * Percentages can be treated as multiples of 'x', eg 50% => 0.5x, BUT
165    /// * Zero percentages have to be preserved, so detecting 'divide by zero' at parse time isn't easy
166    /// * Calc expressions additionally have other unknown quantities when dealing with units:-
167    ///   * 4 kinds of font relative units
168    ///   * 4 kinds of viewport proportion units
169    ///   * Any number of `var()` and `attr()` sub-expressions (although the latter SHOULD have a known type)
170    /// * It would be possible to simplify some terms, even then, but the coding cost of doing so (with 9 unknown quantities) seems a bit painful
171    ///    * Absolute units could be converted to their canonical unit (eg pixels for AbsoluteLength)
172    ///    * Single-term entries in parentheses and expressions could be eliminated
173    ///    * nested expressions() could be converted to just ()
174    ///
175    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
176    #[inline(always)]
177    pub(crate) fn parse<'i, 't>(
178        context: &ParserContext,
179        input: &mut Parser<'i, 't>,
180    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
181        input.parse_nested_block(|input| Self::parse_sum(context, input))
182    }
183
184    /// Parse a `calc` expression, and all the sum that may follow, and stop as soon as a non-sum expression is found.
185    ///
186    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
187    fn parse_sum<'i, 't>(
188        context: &ParserContext,
189        input: &mut Parser<'i, 't>,
190    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
191        use {self::CalcExpression::*, cssparser::Token::*};
192
193        let mut currentSum = Self::parse_product(context, input)?;
194
195        loop {
196            let stateToResetParseToIfNotSum = input.state();
197            match *input.next_including_whitespace()? {
198                WhiteSpace(_) => {
199                    // a trailing whitespace
200                    if input.is_exhausted() {
201                        break;
202                    }
203                }
204
205                _ => {
206                    input.reset(&stateToResetParseToIfNotSum);
207                    break;
208                }
209            }
210
211            let isAddition = match *input.next()? {
212                Delim('+') => true,
213
214                Delim('-') => false,
215
216                ref unexpectedToken => {
217                    return CustomParseError::unexpectedToken(unexpectedToken)
218                }
219            };
220
221            currentSum = if isAddition {
222                Addition(
223                    Box::new(currentSum),
224                    Box::new(Self::parse_product(context, input)?),
225                )
226            } else {
227                Subtraction(
228                    Box::new(currentSum),
229                    Box::new(Self::parse_product(context, input)?),
230                )
231            }
232        }
233
234        Ok(currentSum)
235    }
236
237    /// Parse a `calc` expression, and all the products that may follow, and stop as soon as a non-product expression is found.
238    ///
239    /// This should parse correctly:-
240    ///
241    /// * `2`
242    /// * `2 * 2`
243    /// * `2 * 2 + 2` (will leave the `+ 2` unparsed).
244    /// * `2 / 2 * 2 + 2` (will leave the `+ 2` unparsed).
245    fn parse_product<'i, 't>(
246        context: &ParserContext,
247        input: &mut Parser<'i, 't>,
248    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
249        use {self::CalcExpression::*, cssparser::Token::*};
250
251        let mut currentProduct = Self::parse_one(context, input)?;
252
253        loop {
254            let stateToResetParseToIfNotProduct = input.state();
255            match *input.next()? {
256                Delim('*') => {
257                    currentProduct = Multiplication(
258                        Box::new(currentProduct),
259                        Box::new(Self::parse_one(context, input)?),
260                    );
261                }
262
263                Delim('/') => {
264                    currentProduct = Division(
265                        Box::new(currentProduct),
266                        Box::new(Self::parse_one(context, input)?),
267                    );
268                }
269
270                _ => {
271                    input.reset(&stateToResetParseToIfNotProduct);
272                    break;
273                }
274            }
275        }
276
277        Ok(currentProduct)
278    }
279
280    fn parse_one<'i, 't>(
281        context: &ParserContext,
282        input: &mut Parser<'i, 't>,
283    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
284        let either = U::parse_one_inside_calc_function(context, input)?;
285        if either.is_left() {
286            Ok(CalcExpression::CalculablePropertyValue(
287                either.left().unwrap(),
288            ))
289        } else {
290            Ok(either.right().unwrap())
291        }
292    }
293
294    #[inline(always)]
295    pub(crate) fn parse_parentheses<'i, 't>(
296        context: &ParserContext,
297        input: &mut Parser<'i, 't>,
298    ) -> Result<
299        Either<CalculablePropertyValue<U>, CalcExpression<U>>,
300        ParseError<'i, CustomParseError<'i>>,
301    > {
302        Ok(Right(CalcExpression::Parentheses(Box::new(
303            CalcExpression::parse(context, input)?,
304        ))))
305    }
306}