Skip to main content

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