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