1 use super::{Builtin, GlobalFunctionMap};
2 
3 #[cfg(feature = "random")]
4 use num_traits::{One, Signed, ToPrimitive, Zero};
5 #[cfg(feature = "random")]
6 use rand::Rng;
7 
8 use crate::{
9     args::CallArgs,
10     common::Op,
11     error::SassResult,
12     parse::{HigherIntermediateValue, Parser, ValueVisitor},
13     unit::Unit,
14     value::{Number, Value},
15 };
16 
percentage(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>17 pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
18     args.max_args(1)?;
19     let num = match args.get_err(0, "number")? {
20         Value::Dimension(Some(n), Unit::None, _) => Some(n * Number::from(100)),
21         Value::Dimension(None, Unit::None, _) => None,
22         v @ Value::Dimension(..) => {
23             return Err((
24                 format!(
25                     "$number: Expected {} to have no units.",
26                     v.inspect(args.span())?
27                 ),
28                 args.span(),
29             )
30                 .into())
31         }
32         v => {
33             return Err((
34                 format!("$number: {} is not a number.", v.inspect(args.span())?),
35                 args.span(),
36             )
37                 .into())
38         }
39     };
40     Ok(Value::Dimension(num, Unit::Percent, true))
41 }
42 
round(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>43 pub(crate) fn round(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
44     args.max_args(1)?;
45     match args.get_err(0, "number")? {
46         Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.round()), u, true)),
47         Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()),
48         v => Err((
49             format!("$number: {} is not a number.", v.inspect(args.span())?),
50             args.span(),
51         )
52             .into()),
53     }
54 }
55 
ceil(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>56 pub(crate) fn ceil(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
57     args.max_args(1)?;
58     match args.get_err(0, "number")? {
59         Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.ceil()), u, true)),
60         Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()),
61         v => Err((
62             format!("$number: {} is not a number.", v.inspect(args.span())?),
63             args.span(),
64         )
65             .into()),
66     }
67 }
68 
floor(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>69 pub(crate) fn floor(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
70     args.max_args(1)?;
71     match args.get_err(0, "number")? {
72         Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.floor()), u, true)),
73         Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()),
74         v => Err((
75             format!("$number: {} is not a number.", v.inspect(args.span())?),
76             args.span(),
77         )
78             .into()),
79     }
80 }
81 
abs(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>82 pub(crate) fn abs(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
83     args.max_args(1)?;
84     match args.get_err(0, "number")? {
85         Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.abs()), u, true)),
86         Value::Dimension(None, u, ..) => Ok(Value::Dimension(None, u, true)),
87         v => Err((
88             format!("$number: {} is not a number.", v.inspect(args.span())?),
89             args.span(),
90         )
91             .into()),
92     }
93 }
94 
comparable(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>95 pub(crate) fn comparable(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
96     args.max_args(2)?;
97     let unit1 = match args.get_err(0, "number1")? {
98         Value::Dimension(_, u, _) => u,
99         v => {
100             return Err((
101                 format!("$number1: {} is not a number.", v.inspect(args.span())?),
102                 args.span(),
103             )
104                 .into())
105         }
106     };
107     let unit2 = match args.get_err(1, "number2")? {
108         Value::Dimension(_, u, _) => u,
109         v => {
110             return Err((
111                 format!("$number2: {} is not a number.", v.inspect(args.span())?),
112                 args.span(),
113             )
114                 .into())
115         }
116     };
117 
118     Ok(Value::bool(unit1.comparable(&unit2)))
119 }
120 
121 // TODO: write tests for this
122 #[cfg(feature = "random")]
random(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>123 pub(crate) fn random(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
124     args.max_args(1)?;
125     let limit = match args.default_arg(0, "limit", Value::Null)? {
126         Value::Dimension(Some(n), ..) => n,
127         Value::Dimension(None, u, ..) => {
128             return Err((format!("$limit: NaN{} is not an int.", u), args.span()).into())
129         }
130         Value::Null => {
131             let mut rng = rand::thread_rng();
132             return Ok(Value::Dimension(
133                 Some(Number::from(rng.gen_range(0.0..1.0))),
134                 Unit::None,
135                 true,
136             ));
137         }
138         v => {
139             return Err((
140                 format!("$limit: {} is not a number.", v.inspect(args.span())?),
141                 args.span(),
142             )
143                 .into())
144         }
145     };
146 
147     if limit.is_one() {
148         return Ok(Value::Dimension(Some(Number::one()), Unit::None, true));
149     }
150 
151     if limit.is_decimal() {
152         return Err((
153             format!("$limit: {} is not an int.", limit.inspect()),
154             args.span(),
155         )
156             .into());
157     }
158 
159     if limit.is_zero() || limit.is_negative() {
160         return Err((
161             format!("$limit: Must be greater than 0, was {}.", limit.inspect()),
162             args.span(),
163         )
164             .into());
165     }
166 
167     let limit = match limit.to_integer().to_u32() {
168         Some(n) => n,
169         None => {
170             return Err((
171                 format!(
172                     "max must be in range 0 < max \u{2264} 2^32, was {}",
173                     limit.inspect()
174                 ),
175                 args.span(),
176             )
177                 .into())
178         }
179     };
180 
181     let mut rng = rand::thread_rng();
182     Ok(Value::Dimension(
183         Some(Number::from(rng.gen_range(0..limit) + 1)),
184         Unit::None,
185         true,
186     ))
187 }
188 
min(args: CallArgs, parser: &mut Parser) -> SassResult<Value>189 pub(crate) fn min(args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
190     args.min_args(1)?;
191     let span = args.span();
192     let mut nums = args
193         .get_variadic()?
194         .into_iter()
195         .map(|val| match val.node {
196             Value::Dimension(number, unit, _) => Ok((number, unit)),
197             v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
198         })
199         .collect::<SassResult<Vec<(Option<Number>, Unit)>>>()?
200         .into_iter();
201 
202     let mut min = match nums.next() {
203         Some((Some(n), u)) => (n, u),
204         Some((None, u)) => return Ok(Value::Dimension(None, u, true)),
205         None => unreachable!(),
206     };
207 
208     for (num, unit) in nums {
209         let num = match num {
210             Some(n) => n,
211             None => continue,
212         };
213 
214         if ValueVisitor::new(parser, span)
215             .less_than(
216                 HigherIntermediateValue::Literal(Value::Dimension(
217                     Some(num.clone()),
218                     unit.clone(),
219                     true,
220                 )),
221                 HigherIntermediateValue::Literal(Value::Dimension(
222                     Some(min.0.clone()),
223                     min.1.clone(),
224                     true,
225                 )),
226             )?
227             .is_true()
228         {
229             min = (num, unit);
230         }
231     }
232     Ok(Value::Dimension(Some(min.0), min.1, true))
233 }
234 
max(args: CallArgs, parser: &mut Parser) -> SassResult<Value>235 pub(crate) fn max(args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
236     args.min_args(1)?;
237     let span = args.span();
238     let mut nums = args
239         .get_variadic()?
240         .into_iter()
241         .map(|val| match val.node {
242             Value::Dimension(number, unit, _) => Ok((number, unit)),
243             v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()),
244         })
245         .collect::<SassResult<Vec<(Option<Number>, Unit)>>>()?
246         .into_iter();
247 
248     let mut max = match nums.next() {
249         Some((Some(n), u)) => (n, u),
250         Some((None, u)) => return Ok(Value::Dimension(None, u, true)),
251         None => unreachable!(),
252     };
253 
254     for (num, unit) in nums {
255         let num = match num {
256             Some(n) => n,
257             None => continue,
258         };
259 
260         if ValueVisitor::new(parser, span)
261             .greater_than(
262                 HigherIntermediateValue::Literal(Value::Dimension(
263                     Some(num.clone()),
264                     unit.clone(),
265                     true,
266                 )),
267                 HigherIntermediateValue::Literal(Value::Dimension(
268                     Some(max.0.clone()),
269                     max.1.clone(),
270                     true,
271                 )),
272             )?
273             .is_true()
274         {
275             max = (num, unit);
276         }
277     }
278     Ok(Value::Dimension(Some(max.0), max.1, true))
279 }
280 
divide(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>281 pub(crate) fn divide(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value> {
282     args.max_args(2)?;
283 
284     let number1 = args.get_err(0, "number1")?;
285     let number2 = args.get_err(1, "number2")?;
286 
287     ValueVisitor::new(parser, args.span()).eval(
288         HigherIntermediateValue::BinaryOp(
289             Box::new(HigherIntermediateValue::Literal(number1)),
290             Op::Div,
291             Box::new(HigherIntermediateValue::Literal(number2)),
292         ),
293         true,
294     )
295 }
296 
declare(f: &mut GlobalFunctionMap)297 pub(crate) fn declare(f: &mut GlobalFunctionMap) {
298     f.insert("percentage", Builtin::new(percentage));
299     f.insert("round", Builtin::new(round));
300     f.insert("ceil", Builtin::new(ceil));
301     f.insert("floor", Builtin::new(floor));
302     f.insert("abs", Builtin::new(abs));
303     f.insert("min", Builtin::new(min));
304     f.insert("max", Builtin::new(max));
305     f.insert("comparable", Builtin::new(comparable));
306     #[cfg(feature = "random")]
307     f.insert("random", Builtin::new(random));
308 }
309