reqlang_expr/
builtins.rs

1use core::fmt;
2use std::fmt::Display;
3
4use crate::{errors::ExprResult, types::Type, value::Value};
5
6#[derive(Clone)]
7pub struct FnArg {
8    pub name: &'static str,
9    pub ty: Type,
10    pub variadic: bool,
11}
12
13impl FnArg {
14    pub fn new(name: &'static str, ty: Type) -> Self {
15        Self {
16            name,
17            ty,
18            variadic: false,
19        }
20    }
21
22    pub fn new_varadic(name: &'static str, ty: Type) -> Self {
23        Self {
24            name,
25            ty,
26            variadic: true,
27        }
28    }
29}
30
31#[derive(Clone)]
32/// Builtin function used in expressions
33pub struct BuiltinFn<'a> {
34    /// Needs to follow identifier naming rules
35    pub name: &'static str,
36    /// Arguments the function expects
37    pub args: &'a [FnArg],
38    /// Type returned by the function
39    pub return_type: Type,
40    /// Function used at runtime
41    pub func: fn(Vec<Value>) -> ExprResult<Value>,
42}
43
44impl<'a> BuiltinFn<'a> {
45    pub fn arity(&self) -> u8 {
46        let len = self.args.len() as u8;
47
48        if self.is_variadic() { len - 1 } else { len }
49    }
50
51    pub fn is_variadic(&self) -> bool {
52        self.args.last().map(|arg| arg.variadic).unwrap_or(false)
53    }
54
55    pub fn arity_matches(&self, arity: u8) -> bool {
56        if self.is_variadic() {
57            self.arity() <= arity
58        } else {
59            self.arity() == arity
60        }
61    }
62
63    /// The default set of builtin functions
64    ///
65    /// This also defines the lookup index for builtins during compilation
66    pub const DEFAULT_BUILTINS: [BuiltinFn<'a>; 17] = [
67        BuiltinFn::ID,
68        BuiltinFn::NOOP,
69        BuiltinFn::IS_EMPTY,
70        BuiltinFn::AND,
71        BuiltinFn::OR,
72        BuiltinFn::COND,
73        BuiltinFn::TO_STR,
74        BuiltinFn::CONCAT,
75        BuiltinFn::CONTAINS,
76        BuiltinFn::TRIM,
77        BuiltinFn::TRIM_START,
78        BuiltinFn::TRIM_END,
79        BuiltinFn::LOWERCASE,
80        BuiltinFn::UPPERCASE,
81        BuiltinFn::TYPE,
82        BuiltinFn::EQ,
83        BuiltinFn::NOT,
84    ];
85
86    // Builtin Definitions
87
88    /// Return [`Value`] passed in
89    ///
90    /// `(id :variable)`
91    pub const ID: BuiltinFn<'static> = BuiltinFn {
92        name: "id",
93        args: &[FnArg {
94            name: "value",
95            ty: Type::Value,
96            variadic: false,
97        }],
98        return_type: Type::Value,
99        func: Self::id,
100    };
101
102    fn id(args: Vec<Value>) -> ExprResult<Value> {
103        let arg = args.first().unwrap();
104
105        Ok(arg.clone())
106    }
107
108    /// Return [`Value::String`] of `` `noop` ``
109    ///
110    /// `(noop)`
111    pub const NOOP: BuiltinFn<'static> = BuiltinFn {
112        name: "noop",
113        args: &[],
114        return_type: Type::String,
115        func: Self::noop,
116    };
117
118    fn noop(_: Vec<Value>) -> ExprResult<Value> {
119        Ok(Value::String(String::from("noop")))
120    }
121
122    /// Return [`Type::Bool`] if [`Value::String`] is empty
123    ///
124    /// `` (is_empty `...`) ``
125    pub const IS_EMPTY: BuiltinFn<'static> = BuiltinFn {
126        name: "is_empty",
127        args: &[FnArg {
128            name: "value",
129            ty: Type::String,
130            variadic: false,
131        }],
132        return_type: Type::String,
133        func: Self::is_empty,
134    };
135
136    fn is_empty(args: Vec<Value>) -> ExprResult<Value> {
137        let string_arg = args
138            .first()
139            .expect("should have string expression passed")
140            .get_string()?;
141
142        Ok(Value::Bool(string_arg.is_empty()))
143    }
144
145    /// Return [`Type::Bool`] if args [`Value::Bool`] are both `true`
146    ///
147    /// `(and true true)`
148    pub const AND: BuiltinFn<'static> = BuiltinFn {
149        name: "and",
150        args: &[
151            FnArg {
152                name: "a",
153                ty: Type::Bool,
154                variadic: false,
155            },
156            FnArg {
157                name: "b",
158                ty: Type::Bool,
159                variadic: false,
160            },
161        ],
162        return_type: Type::Bool,
163        func: Self::and,
164    };
165
166    fn and(args: Vec<Value>) -> ExprResult<Value> {
167        let a_arg = args
168            .first()
169            .expect("should have first expression passed")
170            .get_bool()?;
171        let b_arg = args
172            .get(1)
173            .expect("should have second expression passed")
174            .get_bool()?;
175
176        Ok(Value::Bool(a_arg && b_arg))
177    }
178
179    /// Return [`Type::Bool`] if at least one [`Value::Bool`] is `true`
180    ///
181    /// `(or false true)`
182    pub const OR: BuiltinFn<'static> = BuiltinFn {
183        name: "or",
184        args: &[
185            FnArg {
186                name: "a",
187                ty: Type::Bool,
188                variadic: false,
189            },
190            FnArg {
191                name: "b",
192                ty: Type::Bool,
193                variadic: false,
194            },
195        ],
196        return_type: Type::Bool,
197        func: Self::or,
198    };
199
200    fn or(args: Vec<Value>) -> ExprResult<Value> {
201        let a_arg = args
202            .first()
203            .expect("should have first expression passed")
204            .get_bool()?;
205        let b_arg = args
206            .get(1)
207            .expect("should have second expression passed")
208            .get_bool()?;
209
210        Ok(Value::Bool(a_arg || b_arg))
211    }
212
213    /// Return conditional [`Value`] based on if conditional [`Value::Bool`] is true
214    ///
215    /// `` (cond true `foo` `bar`) ``
216    pub const COND: BuiltinFn<'static> = BuiltinFn {
217        name: "cond",
218        args: &[
219            FnArg {
220                name: "cond",
221                ty: Type::Bool,
222                variadic: false,
223            },
224            FnArg {
225                name: "then",
226                ty: Type::Value,
227                variadic: false,
228            },
229            FnArg {
230                name: "else",
231                ty: Type::Value,
232                variadic: false,
233            },
234        ],
235        return_type: Type::Bool,
236        func: Self::cond,
237    };
238
239    fn cond(args: Vec<Value>) -> ExprResult<Value> {
240        let cond_arg = args
241            .first()
242            .expect("should have cond expression passed")
243            .get_bool()?;
244        let then_arg = args
245            .get(1)
246            .cloned()
247            .expect("should have then expression passed");
248        let else_arg = args
249            .get(2)
250            .cloned()
251            .expect("should have else expression passed");
252
253        if cond_arg { Ok(then_arg) } else { Ok(else_arg) }
254    }
255
256    /// Return [`Value::String`] for the given [`Value`]
257    ///
258    /// `(to_str true)`
259    pub const TO_STR: BuiltinFn<'static> = BuiltinFn {
260        name: "to_str",
261        args: &[FnArg {
262            name: "value",
263            ty: Type::Value,
264            variadic: false,
265        }],
266        return_type: Type::String,
267        func: Self::to_str,
268    };
269
270    fn to_str(args: Vec<Value>) -> ExprResult<Value> {
271        let value_arg = args.first().expect("should have string expression passed");
272
273        Ok(match value_arg {
274            Value::String(_) => value_arg.clone(),
275            _ => Value::String(value_arg.to_string()),
276        })
277    }
278
279    /// Return [`Value::String`] concatenation of the given [`Value`] arguments
280    ///
281    /// `` (concat `Hello` `, ` `World!`) ``
282    pub const CONCAT: BuiltinFn<'static> = BuiltinFn {
283        name: "concat",
284        args: &[
285            FnArg {
286                name: "a",
287                ty: Type::Value,
288                variadic: false,
289            },
290            FnArg {
291                name: "b",
292                ty: Type::Value,
293                variadic: false,
294            },
295            FnArg {
296                name: "rest",
297                ty: Type::Value,
298                variadic: true,
299            },
300        ],
301        return_type: Type::String,
302        func: Self::concat,
303    };
304
305    fn concat(args: Vec<Value>) -> ExprResult<Value> {
306        let mut result = String::new();
307
308        for arg in args {
309            let value = match arg {
310                Value::String(string) => string,
311                _ => arg.to_string(),
312            };
313
314            result.push_str(value.as_str());
315        }
316
317        Ok(Value::String(result))
318    }
319
320    /// Returns [`Value::Bool`] if `needle` [`Value::String`] is in `haystack` [`Value::String`]
321    ///
322    /// `` (contains `Hello` `Hello World`) ``
323    pub const CONTAINS: BuiltinFn<'static> = BuiltinFn {
324        name: "contains",
325        args: &[
326            FnArg {
327                name: "needle",
328                ty: Type::String,
329                variadic: false,
330            },
331            FnArg {
332                name: "haystack",
333                ty: Type::String,
334                variadic: false,
335            },
336        ],
337        return_type: Type::Bool,
338        func: Self::contains,
339    };
340
341    fn contains(args: Vec<Value>) -> ExprResult<Value> {
342        let needle_arg = args
343            .first()
344            .expect("should have first expression passed")
345            .get_string()?;
346        let haystack_arg = args
347            .get(1)
348            .expect("should have second expression passed")
349            .get_string()?;
350
351        Ok(Value::Bool(haystack_arg.contains(needle_arg)))
352    }
353
354    /// Returns [`Value::String`] with whitespace trimmed from both sides of [`Value::String`]
355    ///
356    /// `` (trim ` Hello `) ``
357    pub const TRIM: BuiltinFn<'static> = BuiltinFn {
358        name: "trim",
359        args: &[FnArg {
360            name: "value",
361            ty: Type::String,
362            variadic: false,
363        }],
364        return_type: Type::String,
365        func: Self::trim,
366    };
367
368    fn trim(args: Vec<Value>) -> ExprResult<Value> {
369        let string_arg = args
370            .first()
371            .expect("should have string expression passed")
372            .get_string()?;
373
374        Ok(Value::String(string_arg.trim().to_string()))
375    }
376
377    /// Returns [`Value::String`] with whitespace trimmed from start of [`Value::String`]
378    ///
379    /// `` (trim_start ` Hello`) ``
380    pub const TRIM_START: BuiltinFn<'static> = BuiltinFn {
381        name: "trim_start",
382        args: &[FnArg {
383            name: "value",
384            ty: Type::String,
385            variadic: false,
386        }],
387        return_type: Type::String,
388        func: Self::trim_start,
389    };
390
391    fn trim_start(args: Vec<Value>) -> ExprResult<Value> {
392        let string_arg = args
393            .first()
394            .expect("should have string expression passed")
395            .get_string()?;
396
397        Ok(Value::String(string_arg.trim_start().to_string()))
398    }
399
400    /// Returns [`Value::String`] with whitespace trimmed from end of [`Value::String`]
401    ///
402    /// `` (trim_end `Hello `) ``
403    pub const TRIM_END: BuiltinFn<'static> = BuiltinFn {
404        name: "trim_end",
405        args: &[FnArg {
406            name: "value",
407            ty: Type::String,
408            variadic: false,
409        }],
410        return_type: Type::String,
411        func: Self::trim_end,
412    };
413
414    fn trim_end(args: Vec<Value>) -> ExprResult<Value> {
415        let string_arg = args
416            .first()
417            .expect("should have string expression passed")
418            .get_string()?;
419
420        Ok(Value::String(string_arg.trim_end().to_string()))
421    }
422
423    /// Returns [`Value::String`] lowercased
424    ///
425    /// `` (lowercase ` HELLO`) ``
426    pub const LOWERCASE: BuiltinFn<'static> = BuiltinFn {
427        name: "lowercase",
428        args: &[{
429            FnArg {
430                name: "value",
431                ty: Type::String,
432                variadic: false,
433            }
434        }],
435        return_type: Type::String,
436        func: Self::lowercase,
437    };
438
439    fn lowercase(args: Vec<Value>) -> ExprResult<Value> {
440        let string_arg = args
441            .first()
442            .expect("should have string expression passed")
443            .get_string()?;
444
445        Ok(Value::String(string_arg.to_lowercase().to_string()))
446    }
447
448    /// Returns [`Value::String`] uppercased
449    ///
450    /// `` (uppercase ` HELLO`) ``
451    pub const UPPERCASE: BuiltinFn<'static> = BuiltinFn {
452        name: "uppercase",
453        args: &[FnArg {
454            name: "value",
455            ty: Type::String,
456            variadic: false,
457        }],
458        return_type: Type::String,
459        func: Self::uppercase,
460    };
461
462    fn uppercase(args: Vec<Value>) -> ExprResult<Value> {
463        let string_arg = args
464            .first()
465            .expect("should have string expression passed")
466            .get_string()?;
467
468        Ok(Value::String(string_arg.to_uppercase().to_string()))
469    }
470
471    /// Returns [`Value::Type`] of [`Value`]
472    ///
473    /// (type true)
474    pub const TYPE: BuiltinFn<'static> = BuiltinFn {
475        name: "type",
476        args: &[FnArg {
477            name: "value",
478            ty: Type::Value,
479            variadic: false,
480        }],
481        return_type: Type::String,
482        func: Self::get_type,
483    };
484
485    fn get_type(args: Vec<Value>) -> ExprResult<Value> {
486        let value_arg = args.first().expect("should have first expression passed");
487
488        Ok(Value::Type(Type::Type(value_arg.get_type().into()).into()))
489    }
490
491    /// Returns [`Value::Bool`] if two [`Value`] are equal
492    ///
493    /// (eq true true)
494    pub const EQ: BuiltinFn<'static> = BuiltinFn {
495        name: "eq",
496        args: &[
497            {
498                FnArg {
499                    name: "a",
500                    ty: Type::Value,
501                    variadic: false,
502                }
503            },
504            {
505                FnArg {
506                    name: "b",
507                    ty: Type::Value,
508                    variadic: false,
509                }
510            },
511        ],
512        return_type: Type::Bool,
513        func: Self::eq,
514    };
515
516    fn eq(args: Vec<Value>) -> ExprResult<Value> {
517        let first_arg = args.first().expect("should have first expression passed");
518        let second_arg = args.get(1).expect("should have second expression passed");
519
520        let equals = first_arg == second_arg;
521
522        Ok(equals.into())
523    }
524
525    /// Returns [`Value::Bool`] negated
526    ///
527    /// (not true)
528    pub const NOT: BuiltinFn<'static> = BuiltinFn {
529        name: "not",
530        args: &[{
531            let ty = Type::Bool;
532            FnArg {
533                name: "value",
534                ty,
535                variadic: false,
536            }
537        }],
538        return_type: Type::Bool,
539        func: Self::not,
540    };
541
542    fn not(args: Vec<Value>) -> ExprResult<Value> {
543        let value_arg = args.first().expect("should have first expression passed");
544
545        let value = &value_arg.get_bool()?;
546
547        Ok(Value::Bool(!value))
548    }
549}
550
551impl<'a> PartialEq for BuiltinFn<'a> {
552    fn eq(&self, other: &Self) -> bool {
553        self.name == other.name
554    }
555}
556
557impl<'a> Display for BuiltinFn<'a> {
558    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559        let name = &self.name;
560        let args: Vec<String> = self
561            .args
562            .iter()
563            .map(|arg| {
564                let prefix: &str = if arg.variadic { "..." } else { "" };
565
566                format!("{prefix}{}: {}", arg.name, arg.ty.name())
567            })
568            .collect();
569
570        let args: String = args.join(", ");
571
572        let return_type: String = self.return_type.name().to_string();
573
574        write!(f, "{name}({args}) -> {return_type}")
575    }
576}
577
578impl<'a> fmt::Debug for BuiltinFn<'a> {
579    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
580        let name = &self.name;
581        let args: Vec<String> = self
582            .args
583            .iter()
584            .map(|arg| {
585                let prefix: &str = if arg.variadic { "..." } else { "" };
586
587                format!("{prefix}{}: {}", arg.name, arg.ty.name())
588            })
589            .collect();
590
591        let args: String = args.join(", ");
592
593        let return_type: String = self.return_type.name().to_string();
594
595        write!(f, "{name}({args}) -> {return_type}")
596    }
597}
598
599#[derive(Debug, PartialEq)]
600pub enum FnArity {
601    N(u8),
602    Variadic { n: u8 },
603}
604
605#[cfg(test)]
606mod value_tests {
607    use super::*;
608
609    fn example_builtin(_args: Vec<Value>) -> ExprResult<Value> {
610        Ok(Value::String("".to_string()))
611    }
612
613    #[test]
614    fn test_builtins_display_var_arity() {
615        let f = BuiltinFn {
616            name: "test_builtin",
617            args: &[FnArg::new_varadic("rest", Type::String)],
618            return_type: Type::String,
619            func: example_builtin,
620        };
621        assert_eq!("test_builtin(...rest: String) -> String", format!("{}", f))
622    }
623
624    #[test]
625    fn test_builtins_display_0_arity() {
626        assert_eq!(
627            "test_builtin() -> String",
628            format!(
629                "{}",
630                BuiltinFn {
631                    name: "test_builtin",
632                    args: &[],
633                    return_type: Type::String,
634                    func: example_builtin
635                }
636            )
637        )
638    }
639
640    #[test]
641    fn test_builtins_debug_0_arity() {
642        assert_eq!(
643            "test_builtin() -> String",
644            format!(
645                "{:#?}",
646                BuiltinFn {
647                    name: "test_builtin",
648                    args: &[],
649                    return_type: Type::String,
650                    func: example_builtin
651                }
652            )
653        )
654    }
655
656    #[test]
657    fn test_builtins_display_1_arity() {
658        assert_eq!(
659            "test_builtin(value: String) -> String",
660            format!(
661                "{}",
662                BuiltinFn {
663                    name: "test_builtin",
664                    args: &[FnArg::new("value", Type::String)],
665                    return_type: Type::String,
666                    func: example_builtin
667                }
668            )
669        )
670    }
671
672    #[test]
673    fn test_builtins_debug_1_arity() {
674        assert_eq!(
675            "test_builtin(value: String) -> String",
676            format!(
677                "{:#?}",
678                BuiltinFn {
679                    name: "test_builtin",
680                    args: &[FnArg::new("value", Type::String)],
681                    return_type: Type::String,
682                    func: example_builtin
683                }
684            )
685        )
686    }
687
688    #[test]
689    fn test_builtins_display_2_arity() {
690        assert_eq!(
691            "test_builtin(a: String, b: String) -> String",
692            format!(
693                "{}",
694                BuiltinFn {
695                    name: "test_builtin",
696                    args: &[FnArg::new("a", Type::String), FnArg::new("b", Type::String)],
697                    return_type: Type::String,
698                    func: example_builtin
699                }
700            )
701        )
702    }
703
704    #[test]
705    fn test_builtins_debug_2_arity() {
706        assert_eq!(
707            "test_builtin(a: String, b: String) -> String",
708            format!(
709                "{:#?}",
710                BuiltinFn {
711                    name: "test_builtin",
712                    args: &[FnArg::new("a", Type::String), FnArg::new("b", Type::String)],
713                    return_type: Type::String,
714                    func: example_builtin
715                }
716            )
717        )
718    }
719}