json_e/
builtins.rs

1use crate::fromnow::from_now;
2use crate::interpreter::Context;
3use crate::value::{Function, Value};
4use anyhow::Result;
5use lazy_static::lazy_static;
6use std::convert::TryInto;
7
8lazy_static! {
9    pub(crate) static ref BUILTINS: Context<'static> = {
10        let mut builtins = Context::new();
11        builtins.insert("abs", Value::Function(Function::new("abs", abs_builtin)));
12        builtins.insert("str", Value::Function(Function::new("str", str_builtin)));
13        builtins.insert("len", Value::Function(Function::new("len", len_builtin)));
14        builtins.insert("min", Value::Function(Function::new("min", min_builtin)));
15        builtins.insert("max", Value::Function(Function::new("max", max_builtin)));
16        builtins.insert("sqrt", Value::Function(Function::new("sqrt", sqrt_builtin)));
17        builtins.insert("ceil", Value::Function(Function::new("ceil", ceil_builtin)));
18        builtins.insert(
19            "floor",
20            Value::Function(Function::new("floor", floor_builtin)),
21        );
22        builtins.insert(
23            "lowercase",
24            Value::Function(Function::new("lowercase", lowercase_builtin)),
25        );
26        builtins.insert(
27            "uppercase",
28            Value::Function(Function::new("uppercase", uppercase_builtin)),
29        );
30        builtins.insert(
31            "number",
32            Value::Function(Function::new("number", number_builtin)),
33        );
34        builtins.insert(
35            "strip",
36            Value::Function(Function::new("strip", strip_builtin)),
37        );
38        builtins.insert("range", Value::Function(Function::new("range", range_builtin)));
39        builtins.insert(
40            "rstrip",
41            Value::Function(Function::new("rstrip", rstrip_builtin)),
42        );
43        builtins.insert(
44            "lstrip",
45            Value::Function(Function::new("lstrip", lstrip_builtin)),
46        );
47        builtins.insert("join", Value::Function(Function::new("join", join_builtin)));
48        builtins.insert(
49            "split",
50            Value::Function(Function::new("split", split_builtin)),
51        );
52        builtins.insert(
53            "fromNow",
54            Value::Function(Function::new("fromNow", from_now_builtin)),
55        );
56        builtins.insert(
57            "typeof",
58            Value::Function(Function::new("typeof", typeof_builtin)),
59        );
60        builtins.insert(
61            "defined",
62            Value::Function(Function::new("defined", defined_builtin)),
63        );
64        builtins
65    };
66}
67
68// utility functions
69
70fn array_arithmetic<F: Fn(f64, f64) -> f64>(args: &[Value], f: F) -> Result<Value> {
71    let mut res = None;
72    for arg in args {
73        let arg = *arg
74            .as_f64()
75            .ok_or_else(|| interpreter_error!("invalid arguments to builtin: min"))?;
76        if let Some(r) = res {
77            res = Some(f(arg, r));
78        } else {
79            res = Some(arg);
80        }
81    }
82    if let Some(r) = res {
83        Ok(Value::Number(r))
84    } else {
85        Err(interpreter_error!("invalid arguments to builtin: min"))
86    }
87}
88
89fn unary_arithmetic<F: Fn(f64) -> f64>(args: &[Value], op: F) -> Result<Value> {
90    if args.len() != 1 {
91        return Err(interpreter_error!("expected one argument"));
92    }
93    let v = &args[0];
94
95    match v {
96        Value::Number(n) => Ok(Value::Number(op(*n))),
97        _ => Err(interpreter_error!("invalid arguments to builtin")),
98    }
99}
100
101fn unary_string<F: Fn(&str) -> String>(args: &[Value], op: F) -> Result<Value> {
102    if args.len() != 1 {
103        return Err(interpreter_error!("expected one argument"));
104    }
105    let v = &args[0];
106
107    match v {
108        Value::String(s) => Ok(Value::String(op(s.as_ref()))),
109        _ => Err(interpreter_error!("invalid arguments to builtin")),
110    }
111}
112
113// builtin implementations
114
115fn abs_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
116    unary_arithmetic(args, f64::abs)
117}
118
119fn str_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
120    if args.len() != 1 {
121        return Err(interpreter_error!("str expects one argument"));
122    }
123    let v = &args[0];
124
125    match v {
126        Value::Null | Value::String(_) | Value::Number(_) | Value::Bool(_) => {
127            v.stringify().map(|s| Value::String(s))
128        }
129        _ => Err(interpreter_error!("invalid arguments to builtin: str")),
130    }
131}
132
133fn len_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
134    if args.len() != 1 {
135        return Err(interpreter_error!("len expects one argument"));
136    }
137    let v = &args[0];
138
139    match v {
140        Value::String(s) => Ok(Value::Number(s.chars().count() as f64)),
141        Value::Array(a) => Ok(Value::Number(a.len() as f64)),
142        _ => Err(interpreter_error!("invalid arguments to builtin: len")),
143    }
144}
145
146fn min_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
147    array_arithmetic(args, |a, b| if a < b { a } else { b })
148}
149
150fn max_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
151    array_arithmetic(args, |a, b| if a < b { b } else { a })
152}
153
154fn sqrt_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
155    unary_arithmetic(args, f64::sqrt)
156}
157
158fn ceil_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
159    unary_arithmetic(args, f64::ceil)
160}
161
162fn floor_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
163    unary_arithmetic(args, f64::floor)
164}
165
166fn lowercase_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
167    unary_string(args, str::to_lowercase)
168}
169
170fn uppercase_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
171    unary_string(args, str::to_uppercase)
172}
173
174fn number_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
175    if args.len() != 1 {
176        return Err(interpreter_error!("number expects one argument"));
177    }
178    let v = &args[0];
179    let num: f64 = match v {
180        Value::String(s) => match s.parse() {
181            Ok(num) => num,
182            Err(_) => return Err(interpreter_error!("string can't be converted to number")),
183        },
184        _ => return Err(interpreter_error!("invalid arguments to builtin: number")),
185    };
186    Ok(Value::Number(num))
187}
188
189fn strip_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
190    unary_string(args, |s| str::trim(s).to_owned())
191}
192
193fn range_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
194   if args.len() < 2 || args.len() > 3 {
195        return Err(interpreter_error!("range requires two arguments and optionally supports a third"));
196    }
197    let start = &args[0];
198    let start: i64 = match start {
199        Value::Number(n) if n.fract() == 0.0 => n.round() as i64,
200        _ => return Err(interpreter_error!("invalid arguments to builtin: range")),
201    };
202    let stop = &args[1];
203    let stop: i64 = match stop {
204        Value::Number(n) if n.fract() == 0.0 => n.round() as i64,
205        _ => return Err(interpreter_error!("invalid arguments to builtin: range")),
206    };
207    let step: i64 = match args.get(2) {
208        // If step is not provided by the user, it defaults to 1.
209        None => 1,
210        Some(val) => match val {
211            Value::Number(n) if n.fract() == 0.0 => n.round() as i64,
212            _ => return Err(interpreter_error!("invalid arguments to builtin: range")),
213        }
214    };
215
216    if step > 0 {
217        let step: usize = step.try_into()?;
218        let range = (start..stop).step_by(step).map(|i| Value::Number(i as f64)).collect();
219        Ok(Value::Array(range))
220    } else if step < 0 {
221        let step: usize = (step * -1).try_into()?;
222        let range = (stop+1..=start).rev().step_by(step).map(|i| Value::Number(i as f64)).collect();
223        Ok(Value::Array(range))
224    } else {
225        return Err(interpreter_error!("invalid argument `step` to builtin: range"));
226    }
227}
228
229fn rstrip_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
230    unary_string(args, |s| str::trim_end(s).to_owned())
231}
232
233fn lstrip_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
234    unary_string(args, |s| str::trim_start(s).to_owned())
235}
236
237fn join_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
238    if args.len() != 2 {
239        return Err(interpreter_error!("join expects two arguments"));
240    }
241    let v = &args[0];
242    let sep = &args[1];
243
244    let sep = match sep.stringify() {
245        Ok(s) => s,
246        Err(_) => return Err(interpreter_error!("invalid separator for split")),
247    };
248
249    match v {
250        Value::Array(v) => {
251            let strings: Result<Vec<String>> = v.into_iter().map(|val| val.stringify()).collect();
252            match strings {
253                Ok(s) => Ok(Value::String(s.join(&sep))),
254                Err(_) => Err(interpreter_error!(
255                    "BuiltinError: invalid arguments to builtin: join"
256                )),
257            }
258        }
259        _ => Err(interpreter_error!(
260            "BuiltinError: invalid arguments to builtin: join"
261        )),
262    }
263}
264
265fn split_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
266    if args.len() != 2 {
267        return Err(interpreter_error!("split expects two arguments"));
268    }
269    let v = &args[0];
270    let sep = &args[1];
271
272    let sep = match sep.stringify() {
273        Ok(s) => s,
274        Err(_) => return Err(interpreter_error!("invalid separator for split")),
275    };
276
277    match v {
278        Value::String(s) => {
279            if s.is_empty() {
280                return Ok(Value::Array(vec![Value::String("".to_string())]));
281            };
282            let strings = s
283                .split(&sep)
284                .filter(|v| !v.is_empty())
285                .map(|v| Value::String(v.to_string()))
286                .collect();
287            Ok(Value::Array(strings))
288        }
289        _ => Err(interpreter_error!(
290            "BuiltinError: invalid arguments to builtin: split"
291        )),
292    }
293}
294
295fn from_now_builtin(context: &Context, args: &[Value]) -> Result<Value> {
296    if args.len() != 1 && args.len() != 2 {
297        return Err(interpreter_error!("from_now expects one or two arguments"));
298    }
299
300    let v = &args[0];
301
302    let reference = if args.len() == 2 {
303        match &args[1] {
304            Value::String(s) => s.to_owned(),
305            _ => {
306                return Err(interpreter_error!(
307                    "BuiltinError: invalid arguments to builtin: fromNow"
308                ))
309            }
310        }
311    } else {
312        match context.get("now") {
313            None => unreachable!(), // this is set in render()
314            Some(Value::String(s)) => s.to_owned(),
315            _ => return Err(interpreter_error!("context value `now` must be a string")),
316        }
317    };
318
319    match v {
320        Value::String(s) => Ok(Value::String(from_now(&s, &reference)?)),
321        _ => Err(interpreter_error!(
322            "BuiltinError: invalid arguments to builtin: fromNow"
323        )),
324    }
325}
326
327fn typeof_builtin(_context: &Context, args: &[Value]) -> Result<Value> {
328    if args.len() != 1 {
329        return Err(interpreter_error!("typeof expects one argument"));
330    }
331
332    let v = &args[0];
333
334    let type_ = match v {
335        Value::String(_) => "string",
336        Value::Number(_) => "number",
337        Value::Bool(_) => "boolean",
338        Value::Array(_) => "array",
339        Value::Object(_) => "object",
340        Value::Null => "null",
341        Value::Function(_) => "function",
342        _ => {
343            return Err(interpreter_error!(
344                "BuiltinError: invalid arguments to builtin: split"
345            ))
346        }
347    };
348
349    Ok(Value::String(type_.to_string()))
350}
351
352fn defined_builtin(context: &Context, args: &[Value]) -> Result<Value> {
353    if args.len() != 1 {
354        return Err(interpreter_error!("builtin expects one argument"));
355    }
356    let v = &args[0];
357
358    match v {
359        Value::String(s) => Ok(Value::Bool(context.get(s).is_some())),
360        _ => Err(interpreter_error!(
361            "BuiltinError: invalid arguments to builtin: split"
362        )),
363    }
364}