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