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 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 _ => 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}