Skip to main content

float_pigment_css/parser/property_value/
calc.rs

1use alloc::string::ToString;
2use core::{f32::consts::PI, marker::PhantomData};
3
4use super::*;
5
6impl CalcExpr {
7    /// Check if the expression is a number.
8    pub fn is_number(&self) -> bool {
9        if let CalcExpr::Number(_) = self {
10            return true;
11        }
12        false
13    }
14
15    /// Get the number value if the expression is a number.
16    pub fn get_number(&self) -> Option<&Number> {
17        match self {
18            CalcExpr::Number(num) => Some(num.as_ref()),
19            _ => None,
20        }
21    }
22
23    /// Check if the expression is a static number `0`.
24    pub fn is_zero(&self) -> bool {
25        match self {
26            CalcExpr::Number(v) => match *v.as_ref() {
27                Number::F32(f) => f == 0.,
28                Number::I32(i) => i == 0,
29                _ => false,
30            },
31            _ => unreachable!(),
32        }
33    }
34
35    /// Check if the expression is a literal value, a.k.a. does not contain any operator.
36    pub fn is_specified_value(&self) -> bool {
37        match self {
38            CalcExpr::Angle(angle) => !matches!(angle.as_ref(), Angle::Calc(_)),
39            CalcExpr::Number(num) => !matches!(num.as_ref(), Number::Calc(_)),
40            CalcExpr::Length(length) => !matches!(
41                length.as_ref(),
42                Length::Expr(_) | Length::Auto | Length::Undefined
43            ),
44            _ => false,
45        }
46    }
47
48    /// Multiply `rhs` if `mul` is true; divide `rhs` otherwise.
49    pub fn mul_div(&self, rhs: f32, mul: bool) -> Self {
50        match self {
51            CalcExpr::Angle(angle) => {
52                let v = if mul {
53                    angle.to_f32() * rhs
54                } else {
55                    angle.to_f32() / rhs
56                };
57                let ret = match angle.as_ref() {
58                    Angle::Deg(_) => Angle::Deg(v),
59                    Angle::Grad(_) => Angle::Grad(v),
60                    Angle::Rad(_) => Angle::Rad(v),
61                    Angle::Turn(_) => Angle::Turn(v),
62                    Angle::Calc(_) => unreachable!(),
63                };
64                CalcExpr::Angle(Box::new(ret))
65            }
66            CalcExpr::Length(length) => {
67                let v = if mul {
68                    length.to_f32() * rhs
69                } else {
70                    length.to_f32() / rhs
71                };
72                let ret = match length.as_ref() {
73                    Length::Px(_) => Length::Px(v),
74                    Length::Em(_) => Length::Em(v),
75                    Length::Rpx(_) => Length::Rpx(v),
76                    Length::Ratio(_) => Length::Ratio(v),
77                    Length::Rem(_) => Length::Rem(v),
78                    Length::Vh(_) => Length::Vh(v),
79                    Length::Vw(_) => Length::Vw(v),
80                    Length::Vmax(_) => Length::Vmax(v),
81                    Length::Vmin(_) => Length::Vmin(v),
82                    _ => unreachable!(),
83                };
84                CalcExpr::Length(Box::new(ret))
85            }
86            CalcExpr::Number(num) => {
87                let ret = if mul {
88                    num.to_f32() * rhs
89                } else {
90                    num.to_f32() / rhs
91                };
92                CalcExpr::Number(Box::new(Number::F32(ret)))
93            }
94            _ => unreachable!(),
95        }
96    }
97}
98
99impl Angle {
100    /// Convert to `rad` form.
101    ///
102    /// Panics if it is an expression.
103    pub fn to_rad(&self) -> Angle {
104        match self {
105            Angle::Rad(rad) => Angle::Rad(*rad),
106            Angle::Deg(deg) => Angle::Rad(deg * PI / 180.),
107            Angle::Grad(grad) => Angle::Rad(grad * PI / 200.),
108            Angle::Turn(turn) => Angle::Rad(turn * 2. * PI),
109            _ => panic!("not a literal value"),
110        }
111    }
112
113    /// Create a new value from a turn-based value.
114    pub fn from_ratio(turn: f32) -> Angle {
115        Angle::Rad(turn * 2. * PI)
116    }
117
118    /// Erase the unit and leave the `f32` value.
119    ///
120    /// Panics if it is an expression.
121    pub fn to_f32(&self) -> f32 {
122        match self {
123            Angle::Calc(_) => panic!("not a literal value"),
124            Angle::Rad(v) => *v,
125            Angle::Deg(v) => *v,
126            Angle::Grad(v) => *v,
127            Angle::Turn(v) => *v,
128        }
129    }
130}
131
132impl Length {
133    /// Erase the unit and leave the `f32` value.
134    ///
135    /// Panics if it is an expression or it does not contain a unit.
136    pub fn to_f32(&self) -> f32 {
137        match self {
138            Length::Px(v) => *v,
139            Length::Em(v) => *v,
140            Length::Rpx(v) => *v,
141            Length::Ratio(v) => *v,
142            Length::Rem(v) => *v,
143            Length::Vh(v) => *v,
144            Length::Vw(v) => *v,
145            Length::Vmax(v) => *v,
146            Length::Vmin(v) => *v,
147            Length::Expr(_) | Length::Auto | Length::Undefined => panic!("not a literal value"),
148        }
149    }
150}
151
152pub(crate) struct ComputeCalcExpr<T> {
153    _mark: PhantomData<*const T>,
154}
155
156impl ComputeCalcExpr<Angle> {
157    pub fn try_compute(expr: &CalcExpr) -> Option<Angle> {
158        match expr {
159            CalcExpr::Angle(angle) => Some(angle.as_ref().clone().to_rad()),
160            CalcExpr::Plus(l, r) | CalcExpr::Sub(l, r) => {
161                let l = Self::try_compute(l)?;
162
163                let r = Self::try_compute(r)?;
164                match expr {
165                    CalcExpr::Plus(_, _) => Some(Angle::Rad(l.to_f32() + r.to_f32())),
166                    CalcExpr::Sub(_, _) => Some(Angle::Rad(l.to_f32() - r.to_f32())),
167                    _ => None,
168                }
169            }
170            CalcExpr::Mul(l, r) | CalcExpr::Div(l, r) => {
171                let l = Self::try_compute(l)?;
172                let r = ComputeCalcExpr::<Number>::try_compute(r)?;
173                match expr {
174                    CalcExpr::Mul(_, _) => Some(Angle::Rad(l.to_f32() * r.to_f32())),
175                    CalcExpr::Div(_, _) => Some(Angle::Rad(l.to_f32() / r.to_f32())),
176                    _ => None,
177                }
178            }
179            CalcExpr::Length(l) => match l.as_ref() {
180                Length::Ratio(ratio) => Some(Angle::from_ratio(*ratio)),
181                _ => None,
182            },
183            _ => None,
184        }
185    }
186}
187
188impl ComputeCalcExpr<Number> {
189    pub fn try_compute(expr: &CalcExpr) -> Option<Number> {
190        match expr {
191            CalcExpr::Number(num) => Some(*num.clone()),
192            CalcExpr::Plus(l, r)
193            | CalcExpr::Sub(l, r)
194            | CalcExpr::Mul(l, r)
195            | CalcExpr::Div(l, r) => {
196                let l = Self::try_compute(l)?;
197                let r = Self::try_compute(r)?;
198                match expr {
199                    CalcExpr::Plus(_, _) => Some(Number::F32(l.to_f32() + r.to_f32())),
200                    CalcExpr::Sub(_, _) => Some(Number::F32(l.to_f32() - r.to_f32())),
201                    CalcExpr::Mul(_, _) => Some(Number::F32(l.to_f32() * r.to_f32())),
202                    CalcExpr::Div(_, _) => Some(Number::F32(l.to_f32() / r.to_f32())),
203                    _ => None,
204                }
205            }
206            _ => None,
207        }
208    }
209}
210
211#[derive(Copy, Clone, PartialEq, Eq, Debug)]
212pub(crate) enum LengthUnit {
213    Px,
214    Vw,
215    Vh,
216    Rem,
217    Rpx,
218    Em,
219    Ratio,
220    Vmin,
221    Vmax,
222
223    Undefined,
224    Expr,
225    Auto,
226}
227
228impl LengthUnit {
229    pub(crate) fn is_specified_unit(&self) -> bool {
230        !matches!(self, Self::Undefined | Self::Expr | Self::Auto)
231    }
232    pub(crate) fn to_length(unit: LengthUnit, value: f32) -> Length {
233        match unit {
234            LengthUnit::Auto => Length::Auto,
235            LengthUnit::Undefined => Length::Undefined,
236            LengthUnit::Expr => todo!(),
237            LengthUnit::Px => Length::Px(value),
238            LengthUnit::Vw => Length::Vw(value),
239            LengthUnit::Vh => Length::Vh(value),
240            LengthUnit::Rem => Length::Rem(value),
241            LengthUnit::Em => Length::Em(value),
242            LengthUnit::Rpx => Length::Rpx(value),
243            LengthUnit::Ratio => Length::Ratio(value),
244            LengthUnit::Vmin => Length::Vmin(value),
245            LengthUnit::Vmax => Length::Vmax(value),
246        }
247    }
248}
249
250impl ComputeCalcExpr<Length> {
251    pub(crate) fn try_compute(expr: &CalcExpr) -> Option<Length> {
252        match expr {
253            CalcExpr::Length(l) => Some(*l.clone()),
254            CalcExpr::Angle(_) | CalcExpr::Number(_) => None,
255            CalcExpr::Plus(l, r) | CalcExpr::Sub(l, r) => {
256                let l = Self::try_compute(l)?;
257                let r = Self::try_compute(r)?;
258                let ((l_unit, l_v), (r_unit, r_v)) =
259                    (Self::length_unit_value(&l), Self::length_unit_value(&r));
260                // TODO
261                // merge same unit
262                if (l_unit == r_unit) && l_unit.is_specified_unit() {
263                    match expr {
264                        CalcExpr::Plus(_, _) => {
265                            return Some(LengthUnit::to_length(l_unit, l_v + r_v));
266                        }
267                        CalcExpr::Sub(_, _) => {
268                            return Some(LengthUnit::to_length(l_unit, l_v - r_v));
269                        }
270                        _ => unreachable!(),
271                    }
272                }
273                //
274                None
275            }
276            _ => None,
277        }
278    }
279    pub(crate) fn length_unit_value(length: &Length) -> (LengthUnit, f32) {
280        match length {
281            Length::Px(v) => (LengthUnit::Px, *v),
282            Length::Em(v) => (LengthUnit::Em, *v),
283            Length::Ratio(v) => (LengthUnit::Ratio, *v),
284            Length::Rem(v) => (LengthUnit::Rem, *v),
285            Length::Rpx(v) => (LengthUnit::Rpx, *v),
286            Length::Vh(v) => (LengthUnit::Vh, *v),
287            Length::Vw(v) => (LengthUnit::Vw, *v),
288            Length::Vmax(v) => (LengthUnit::Vmax, *v),
289            Length::Vmin(v) => (LengthUnit::Vmin, *v),
290            Length::Undefined => (LengthUnit::Undefined, f32::NAN),
291            Length::Auto => (LengthUnit::Auto, f32::NAN),
292            Length::Expr(_) => (LengthUnit::Expr, f32::NAN),
293        }
294    }
295}
296
297#[inline(never)]
298fn next_operator<'a, 't: 'a, 'i: 't>(parser: &'a mut Parser<'i, 't>) -> Option<Operator> {
299    let mut ret = None;
300    let _ = parser.try_parse::<_, (), ParseError<'_, CustomError>>(|parser| {
301        let token = parser.next()?.clone();
302        ret = match token {
303            Token::Delim(c) => match c {
304                '+' => Some(Operator::Plus),
305                '-' => Some(Operator::Sub),
306                '*' => Some(Operator::Mul),
307                '/' => Some(Operator::Div),
308                _ => None,
309            },
310            _ => None,
311        };
312        Err(parser.new_custom_error(CustomError::Unsupported))
313    });
314    ret
315}
316
317#[derive(Debug, PartialEq, Eq)]
318enum Operator {
319    Plus,
320    Sub,
321    Mul,
322    Div,
323}
324
325#[derive(Debug, Copy, Clone, Eq, PartialEq)]
326pub(crate) enum ExpectValueType {
327    Number,
328    NumberAndLength,
329    NumberAndAngle,
330    AngleAndLength,
331}
332
333#[inline(never)]
334fn combine_calc_expr(operator: Operator, lhs: CalcExpr, rhs: CalcExpr) -> CalcExpr {
335    // Unwrap nested `Length::Expr(Calc(...))` to flatten the expression tree.
336    let final_lhs = match lhs {
337        CalcExpr::Length(length_expr) => match *length_expr {
338            Length::Expr(LengthExpr::Calc(calc_expr)) => calc_expr,
339            other => Box::new(CalcExpr::Length(Box::new(other))),
340        },
341        other => Box::new(other),
342    };
343    let final_rhs = match rhs {
344        CalcExpr::Length(length_expr) => match *length_expr {
345            Length::Expr(LengthExpr::Calc(calc_expr)) => calc_expr,
346            other => Box::new(CalcExpr::Length(Box::new(other))),
347        },
348        other => Box::new(other),
349    };
350    match operator {
351        Operator::Plus => CalcExpr::Plus(final_lhs, final_rhs),
352        Operator::Sub => CalcExpr::Sub(final_lhs, final_rhs),
353        Operator::Mul => CalcExpr::Mul(final_lhs, final_rhs),
354        Operator::Div => CalcExpr::Div(final_lhs, final_rhs),
355    }
356}
357
358#[inline(never)]
359pub(crate) fn parse_calc_inner<'a, 't: 'a, 'i: 't>(
360    parser: &'a mut Parser<'i, 't>,
361    properties: &mut Vec<PropertyMeta>,
362    st: &mut ParseState,
363    expect_type: ExpectValueType,
364) -> Result<CalcExpr, ParseError<'i, CustomError>> {
365    parser.parse_nested_block(|parser| {
366        let ret = parse_calc_sum_expr(parser, properties, st, expect_type)?;
367        Ok(ret)
368    })
369}
370
371#[inline(never)]
372fn parse_calc_sum_expr<'a, 't: 'a, 'i: 't>(
373    parser: &'a mut Parser<'i, 't>,
374    properties: &mut Vec<PropertyMeta>,
375    st: &mut ParseState,
376    expect_type: ExpectValueType,
377) -> Result<CalcExpr, ParseError<'i, CustomError>> {
378    let mut expr = parse_calc_product_expr(parser, properties, st, expect_type)?;
379    loop {
380        let op = next_operator(parser);
381        if !(op.is_some() && (op == Some(Operator::Plus) || op == Some(Operator::Sub))) {
382            return Ok(expr);
383        }
384        /*
385         * The + and - operators must be surrounded by whitespace.
386         */
387        parser.next_including_whitespace()?;
388        parser.next()?;
389        parser.next_including_whitespace()?;
390        let rhs = parse_calc_product_expr(parser, properties, st, expect_type)?;
391        if let Some(op) = op {
392            match op {
393                Operator::Plus | Operator::Sub => {
394                    expr = combine_calc_expr(op, expr, rhs);
395                }
396                _ => unreachable!(),
397            }
398        } else {
399            return Err(parser.new_custom_error(CustomError::Unsupported));
400        }
401    }
402}
403
404#[inline(never)]
405fn parse_calc_product_expr<'a, 't: 'a, 'i: 't>(
406    parser: &'a mut Parser<'i, 't>,
407    properties: &mut Vec<PropertyMeta>,
408    st: &mut ParseState,
409    expect_type: ExpectValueType,
410) -> Result<CalcExpr, ParseError<'i, CustomError>> {
411    let mut expr = parse_calc_parenthesis_expr(parser, properties, st, expect_type)?;
412    loop {
413        let op = next_operator(parser);
414        if !(op.is_some() && (op == Some(Operator::Mul) || op == Some(Operator::Div))) {
415            return Ok(expr);
416        }
417        /*
418         * The * and / operators do not require whitespace, but adding it for consistency is recommended.
419         */
420        parser.next()?;
421        let rhs = parse_calc_parenthesis_expr(parser, properties, st, expect_type)?;
422        match op.unwrap() {
423            Operator::Mul => {
424                if expr.is_number() && rhs.is_number() {
425                    let l_v = expr.get_number().unwrap();
426                    let r_v = rhs.get_number().unwrap();
427                    expr = CalcExpr::Number(Box::new(Number::F32(l_v.to_f32() * r_v.to_f32())));
428                } else if expr.is_number() || rhs.is_number() {
429                    if expr.is_number() {
430                        if rhs.is_specified_value() {
431                            expr = rhs.mul_div(expr.get_number().unwrap().to_f32(), true);
432                        } else {
433                            expr = combine_calc_expr(Operator::Mul, rhs, expr);
434                        }
435                    } else if expr.is_specified_value() {
436                        expr = expr.mul_div(rhs.get_number().unwrap().to_f32(), true);
437                    } else {
438                        expr = combine_calc_expr(Operator::Mul, expr, rhs);
439                    }
440                } else {
441                    return Err(parser.new_custom_error(CustomError::Unsupported));
442                }
443            }
444            Operator::Div => {
445                // NAN & zero
446                if !rhs.is_number() || rhs.is_zero() {
447                    return Err(
448                        parser.new_custom_error(CustomError::Reason("divided by zero".to_string()))
449                    );
450                }
451                if expr.is_number() && rhs.is_number() {
452                    let l_v = expr.get_number().unwrap();
453                    let r_v = rhs.get_number().unwrap();
454                    expr = CalcExpr::Number(Box::new(Number::F32(l_v.to_f32() / r_v.to_f32())));
455                } else if expr.is_specified_value() && rhs.is_number() {
456                    expr = expr.mul_div(rhs.get_number().unwrap().to_f32(), false)
457                } else {
458                    expr = combine_calc_expr(Operator::Div, expr, rhs);
459                }
460            }
461            _ => unreachable!(),
462        }
463    }
464}
465
466#[inline(never)]
467fn parse_calc_parenthesis_expr<'a, 't: 'a, 'i: 't>(
468    parser: &'a mut Parser<'i, 't>,
469    properties: &mut Vec<PropertyMeta>,
470    st: &mut ParseState,
471    expect_type: ExpectValueType,
472) -> Result<CalcExpr, ParseError<'i, CustomError>> {
473    let value = parse_calc_value(parser, properties, st, expect_type);
474    if value.is_ok() {
475        return value;
476    }
477    parser.try_parse::<_, CalcExpr, ParseError<'_, CustomError>>(|parser| {
478        parser.expect_parenthesis_block()?;
479        parser.parse_nested_block(|parser| parse_calc_sum_expr(parser, properties, st, expect_type))
480    })
481}
482
483#[inline(never)]
484fn parse_calc_value<'a, 't: 'a, 'i: 't>(
485    parser: &'a mut Parser<'i, 't>,
486    properties: &mut Vec<PropertyMeta>,
487    st: &mut ParseState,
488    expect_type: ExpectValueType,
489) -> Result<CalcExpr, ParseError<'i, CustomError>> {
490    // match number
491    let num =
492        parser.try_parse::<_, Number, ParseError<'_, _>>(|parser| number(parser, properties, st));
493    if let Ok(num) = num {
494        return Ok(CalcExpr::Number(Box::new(num)));
495    }
496    // match length
497    let length = parser.try_parse::<_, Length, ParseError<'_, _>>(|parser| {
498        length_percentage_auto(parser, properties, st)
499    });
500    if let Ok(length) = length {
501        if expect_type == ExpectValueType::NumberAndLength
502            || expect_type == ExpectValueType::AngleAndLength
503        {
504            return Ok(CalcExpr::Length(Box::new(length)));
505        }
506    }
507    // match angle
508    let angle =
509        parser.try_parse::<_, Angle, ParseError<'_, _>>(|parser| angle(parser, properties, st));
510    if let Ok(angle) = angle {
511        if expect_type == ExpectValueType::NumberAndAngle
512            || expect_type == ExpectValueType::AngleAndLength
513        {
514            return Ok(CalcExpr::Angle(Box::new(angle)));
515        }
516    }
517    Err(parser.new_custom_error(CustomError::Unmatched))
518}