grass_compiler/evaluate/
bin_op.rs

1use std::cmp::Ordering;
2
3use codemap::Span;
4
5use crate::{
6    common::{BinaryOp, QuoteKind},
7    error::SassResult,
8    unit::Unit,
9    value::{SassNumber, Value},
10    Options,
11};
12
13pub(crate) fn add(left: Value, right: Value, options: &Options, span: Span) -> SassResult<Value> {
14    Ok(match left {
15        Value::Calculation(..) => match right {
16            Value::String(s, quotes) => Value::String(
17                format!(
18                    "{}{}",
19                    left.to_css_string(span, options.is_compressed())?,
20                    s
21                ),
22                quotes,
23            ),
24            _ => {
25                return Err((
26                    format!(
27                        "Undefined operation \"{} + {}\".",
28                        left.inspect(span)?,
29                        right.inspect(span)?
30                    ),
31                    span,
32                )
33                    .into())
34            }
35        },
36        Value::Map(..) | Value::FunctionRef(..) => {
37            return Err((
38                format!("{} isn't a valid CSS value.", left.inspect(span)?),
39                span,
40            )
41                .into())
42        }
43        Value::True | Value::False => match right {
44            Value::String(s, QuoteKind::Quoted) => Value::String(
45                format!(
46                    "{}{}",
47                    left.to_css_string(span, options.is_compressed())?,
48                    s
49                ),
50                QuoteKind::Quoted,
51            ),
52            _ => Value::String(
53                format!(
54                    "{}{}",
55                    left.to_css_string(span, options.is_compressed())?,
56                    right.to_css_string(span, options.is_compressed())?
57                ),
58                QuoteKind::None,
59            ),
60        },
61        Value::Null => match right {
62            Value::Null => Value::Null,
63            _ => Value::String(
64                right.to_css_string(span, options.is_compressed())?,
65                QuoteKind::None,
66            ),
67        },
68        Value::Dimension(SassNumber {
69            num,
70            unit,
71            as_slash: _,
72        }) => match right {
73            Value::Dimension(SassNumber {
74                num: num2,
75                unit: unit2,
76                as_slash: _,
77            }) => {
78                if !unit.comparable(&unit2) {
79                    return Err(
80                        (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
81                    );
82                }
83                if unit == unit2 {
84                    Value::Dimension(SassNumber {
85                        num: num + num2,
86                        unit,
87                        as_slash: None,
88                    })
89                } else if unit == Unit::None {
90                    Value::Dimension(SassNumber {
91                        num: num + num2,
92                        unit: unit2,
93                        as_slash: None,
94                    })
95                } else if unit2 == Unit::None {
96                    Value::Dimension(SassNumber {
97                        num: num + num2,
98                        unit,
99                        as_slash: None,
100                    })
101                } else {
102                    Value::Dimension(SassNumber {
103                        num: num + num2.convert(&unit2, &unit),
104                        unit,
105                        as_slash: None,
106                    })
107                }
108            }
109            Value::String(s, q) => Value::String(
110                format!("{}{}{}", num.to_string(options.is_compressed()), unit, s),
111                q,
112            ),
113            Value::Null => Value::String(
114                format!("{}{}", num.to_string(options.is_compressed()), unit),
115                QuoteKind::None,
116            ),
117            Value::True | Value::False | Value::List(..) | Value::ArgList(..) => Value::String(
118                format!(
119                    "{}{}{}",
120                    num.to_string(options.is_compressed()),
121                    unit,
122                    right.to_css_string(span, options.is_compressed())?
123                ),
124                QuoteKind::None,
125            ),
126            Value::Map(..) | Value::FunctionRef(..) => {
127                return Err((
128                    format!("{} isn't a valid CSS value.", right.inspect(span)?),
129                    span,
130                )
131                    .into())
132            }
133            Value::Color(..) | Value::Calculation(..) => {
134                return Err((
135                    format!(
136                        "Undefined operation \"{}{} + {}\".",
137                        num.inspect(),
138                        unit,
139                        right.inspect(span)?
140                    ),
141                    span,
142                )
143                    .into())
144            }
145        },
146        c @ Value::Color(..) => match right {
147            // todo: we really can't add to any other types?
148            Value::String(..) | Value::Null | Value::List(..) => Value::String(
149                format!(
150                    "{}{}",
151                    c.to_css_string(span, options.is_compressed())?,
152                    right.to_css_string(span, options.is_compressed())?,
153                ),
154                QuoteKind::None,
155            ),
156            _ => {
157                return Err((
158                    format!(
159                        "Undefined operation \"{} + {}\".",
160                        c.inspect(span)?,
161                        right.inspect(span)?
162                    ),
163                    span,
164                )
165                    .into())
166            }
167        },
168        Value::String(text, quotes) => match right {
169            Value::String(text2, ..) => Value::String(text + &text2, quotes),
170            _ => Value::String(
171                text + &right.to_css_string(span, options.is_compressed())?,
172                quotes,
173            ),
174        },
175        Value::List(..) | Value::ArgList(..) => match right {
176            Value::String(s, q) => Value::String(
177                format!(
178                    "{}{}",
179                    left.to_css_string(span, options.is_compressed())?,
180                    s
181                ),
182                q,
183            ),
184            _ => Value::String(
185                format!(
186                    "{}{}",
187                    left.to_css_string(span, options.is_compressed())?,
188                    right.to_css_string(span, options.is_compressed())?
189                ),
190                QuoteKind::None,
191            ),
192        },
193    })
194}
195
196pub(crate) fn sub(left: Value, right: Value, options: &Options, span: Span) -> SassResult<Value> {
197    Ok(match left {
198        Value::Calculation(..) => {
199            return Err((
200                format!(
201                    "Undefined operation \"{} - {}\".",
202                    left.inspect(span)?,
203                    right.inspect(span)?
204                ),
205                span,
206            )
207                .into())
208        }
209        Value::Null => Value::String(
210            format!("-{}", right.to_css_string(span, options.is_compressed())?),
211            QuoteKind::None,
212        ),
213        Value::Dimension(SassNumber {
214            num,
215            unit,
216            as_slash: _,
217        }) => match right {
218            Value::Dimension(SassNumber {
219                num: num2,
220                unit: unit2,
221                as_slash: _,
222            }) => {
223                if !unit.comparable(&unit2) {
224                    return Err(
225                        (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
226                    );
227                }
228                if unit == unit2 {
229                    Value::Dimension(SassNumber {
230                        num: num - num2,
231                        unit,
232                        as_slash: None,
233                    })
234                } else if unit == Unit::None {
235                    Value::Dimension(SassNumber {
236                        num: num - num2,
237                        unit: unit2,
238                        as_slash: None,
239                    })
240                } else if unit2 == Unit::None {
241                    Value::Dimension(SassNumber {
242                        num: num - num2,
243                        unit,
244                        as_slash: None,
245                    })
246                } else {
247                    Value::Dimension(SassNumber {
248                        num: num - num2.convert(&unit2, &unit),
249                        unit,
250                        as_slash: None,
251                    })
252                }
253            }
254            Value::List(..)
255            | Value::String(..)
256            | Value::True
257            | Value::False
258            | Value::ArgList(..) => Value::String(
259                format!(
260                    "{}{}-{}",
261                    num.to_string(options.is_compressed()),
262                    unit,
263                    right.to_css_string(span, options.is_compressed())?
264                ),
265                QuoteKind::None,
266            ),
267            Value::Map(..) | Value::FunctionRef(..) => {
268                return Err((
269                    format!("{} isn't a valid CSS value.", right.inspect(span)?),
270                    span,
271                )
272                    .into())
273            }
274            Value::Color(..) | Value::Calculation(..) => {
275                return Err((
276                    format!(
277                        "Undefined operation \"{}{} - {}\".",
278                        num.inspect(),
279                        unit,
280                        right.inspect(span)?
281                    ),
282                    span,
283                )
284                    .into())
285            }
286            Value::Null => Value::String(
287                format!("{}{}-", num.to_string(options.is_compressed()), unit),
288                QuoteKind::None,
289            ),
290        },
291        c @ Value::Color(..) => match right {
292            Value::Dimension(SassNumber { .. }) | Value::Color(..) => {
293                return Err((
294                    format!(
295                        "Undefined operation \"{} - {}\".",
296                        c.inspect(span)?,
297                        right.inspect(span)?
298                    ),
299                    span,
300                )
301                    .into())
302            }
303            _ => Value::String(
304                format!(
305                    "{}-{}",
306                    c.to_css_string(span, options.is_compressed())?,
307                    right.to_css_string(span, options.is_compressed())?
308                ),
309                QuoteKind::None,
310            ),
311        },
312        Value::String(..) => Value::String(
313            format!(
314                "{}-{}",
315                left.to_css_string(span, options.is_compressed())?,
316                right.to_css_string(span, options.is_compressed())?
317            ),
318            QuoteKind::None,
319        ),
320        // todo: can be greatly simplified
321        _ => match right {
322            Value::String(s, q) => Value::String(
323                format!(
324                    "{}-{}{}{}",
325                    left.to_css_string(span, options.is_compressed())?,
326                    q,
327                    s,
328                    q
329                ),
330                QuoteKind::None,
331            ),
332            Value::Null => Value::String(
333                format!("{}-", left.to_css_string(span, options.is_compressed())?),
334                QuoteKind::None,
335            ),
336            _ => Value::String(
337                format!(
338                    "{}-{}",
339                    left.to_css_string(span, options.is_compressed())?,
340                    right.to_css_string(span, options.is_compressed())?
341                ),
342                QuoteKind::None,
343            ),
344        },
345    })
346}
347
348pub(crate) fn mul(left: Value, right: Value, _: &Options, span: Span) -> SassResult<Value> {
349    Ok(match left {
350        Value::Dimension(SassNumber {
351            num,
352            unit,
353            as_slash: _,
354        }) => match right {
355            Value::Dimension(SassNumber {
356                num: num2,
357                unit: unit2,
358                as_slash: _,
359            }) => {
360                if unit2 == Unit::None {
361                    return Ok(Value::Dimension(SassNumber {
362                        num: num * num2,
363                        unit,
364                        as_slash: None,
365                    }));
366                }
367
368                let n = SassNumber {
369                    num,
370                    unit,
371                    as_slash: None,
372                } * SassNumber {
373                    num: num2,
374                    unit: unit2,
375                    as_slash: None,
376                };
377
378                Value::Dimension(n)
379            }
380            _ => {
381                return Err((
382                    format!(
383                        "Undefined operation \"{}{} * {}\".",
384                        num.inspect(),
385                        unit,
386                        right.inspect(span)?
387                    ),
388                    span,
389                )
390                    .into())
391            }
392        },
393        _ => {
394            return Err((
395                format!(
396                    "Undefined operation \"{} * {}\".",
397                    left.inspect(span)?,
398                    right.inspect(span)?
399                ),
400                span,
401            )
402                .into())
403        }
404    })
405}
406
407pub(crate) fn cmp(
408    left: &Value,
409    right: &Value,
410    _: &Options,
411    span: Span,
412    op: BinaryOp,
413) -> SassResult<Value> {
414    let ordering = match left.cmp(right, span, op)? {
415        Some(ord) => ord,
416        None => return Ok(Value::False),
417    };
418
419    Ok(match op {
420        BinaryOp::GreaterThan => match ordering {
421            Ordering::Greater => Value::True,
422            Ordering::Less | Ordering::Equal => Value::False,
423        },
424        BinaryOp::GreaterThanEqual => match ordering {
425            Ordering::Greater | Ordering::Equal => Value::True,
426            Ordering::Less => Value::False,
427        },
428        BinaryOp::LessThan => match ordering {
429            Ordering::Less => Value::True,
430            Ordering::Greater | Ordering::Equal => Value::False,
431        },
432        BinaryOp::LessThanEqual => match ordering {
433            Ordering::Less | Ordering::Equal => Value::True,
434            Ordering::Greater => Value::False,
435        },
436        _ => unreachable!(),
437    })
438}
439
440pub(crate) fn single_eq(
441    left: &Value,
442    right: &Value,
443    options: &Options,
444    span: Span,
445) -> SassResult<Value> {
446    Ok(Value::String(
447        format!(
448            "{}={}",
449            left.to_css_string(span, options.is_compressed())?,
450            right.to_css_string(span, options.is_compressed())?
451        ),
452        QuoteKind::None,
453    ))
454}
455
456pub(crate) fn div(left: Value, right: Value, options: &Options, span: Span) -> SassResult<Value> {
457    Ok(match (left, right) {
458        (Value::Dimension(num1), Value::Dimension(num2)) => {
459            if num2.unit == Unit::None {
460                return Ok(Value::Dimension(SassNumber {
461                    num: num1.num / num2.num,
462                    unit: num1.unit,
463                    as_slash: None,
464                }));
465            }
466
467            let n = SassNumber {
468                num: num1.num,
469                unit: num1.unit,
470                as_slash: None,
471            } / SassNumber {
472                num: num2.num,
473                unit: num2.unit,
474                as_slash: None,
475            };
476
477            Value::Dimension(n)
478        }
479        (
480            left @ Value::Color(..),
481            right @ (Value::Dimension(SassNumber { .. }) | Value::Color(..)),
482        ) => {
483            return Err((
484                format!(
485                    "Undefined operation \"{} / {}\".",
486                    left.inspect(span)?,
487                    right.inspect(span)?
488                ),
489                span,
490            )
491                .into())
492        }
493        (left, right) => Value::String(
494            format!(
495                "{}/{}",
496                left.to_css_string(span, options.is_compressed())?,
497                right.to_css_string(span, options.is_compressed())?
498            ),
499            QuoteKind::None,
500        ),
501    })
502}
503
504pub(crate) fn rem(left: Value, right: Value, _: &Options, span: Span) -> SassResult<Value> {
505    Ok(match (left, right) {
506        (Value::Dimension(num1), Value::Dimension(num2)) => {
507            if !num1.unit.comparable(&num2.unit) {
508                return Err((
509                    format!("Incompatible units {} and {}.", num1.unit, num2.unit),
510                    span,
511                )
512                    .into());
513            }
514
515            let new_num = num1.num % num2.num.convert(&num2.unit, &num1.unit);
516            let new_unit = if num1.unit == num2.unit {
517                num1.unit
518            } else if num1.unit == Unit::None {
519                num2.unit
520            } else {
521                num1.unit
522            };
523            Value::Dimension(SassNumber {
524                num: new_num,
525                unit: new_unit,
526                as_slash: None,
527            })
528        }
529        (left, right) => {
530            return Err((
531                format!(
532                    "Undefined operation \"{} % {}\".",
533                    left.inspect(span)?,
534                    right.inspect(span)?
535                ),
536                span,
537            )
538                .into())
539        }
540    })
541}