Skip to main content

brooks_lib/mel/interpreter/
builtins.rs

1// brooks, Copyright 2026, Will Hawkins
2//
3// This file is part of brooks.
4
5// This file is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Interpreter implementation for MEL builtins
19
20use std::{
21    collections::HashMap,
22    fmt::{Debug, Display},
23    sync::Arc,
24};
25
26use brooks_macros::builtin_function_interpreter;
27
28use crate::{
29    environment::scope::Scope,
30    mel::{
31        interpreter::{
32            error::BuiltinInterpError,
33            interpret::BuiltinFunction,
34            values::{TypedValue, Value},
35        },
36        types::{
37            Add_Query_MultiBuiltin, Add_QueryBuiltin, BooleanBuiltin, BuiltinFunctionType,
38            IntegerBuiltin, Keep_Query_MultiBuiltin, LowerBuiltin, Match_ReplaceBuiltin,
39            MatchBuiltin, Path_ElementBuiltin, Path_ElementsBuiltin, RealBuiltin,
40            Remove_Query_MultiBuiltin, Remove_QueryBuiltin, StringBuiltin, Type, UnsignedBuiltin,
41            UpperBuiltin,
42        },
43    },
44};
45
46type BuiltinInterpResult = Result<TypedValue, Box<BuiltinInterpError>>;
47
48pub trait BuiltinFunctionInterpreter: Debug {
49    fn interpw(&self, args: Value) -> BuiltinInterpResult;
50}
51
52#[builtin_function_interpreter(Type::String, Type::String, Type::Integer)]
53impl Path_ElementBuiltin {
54    fn interp(&self, path: &str, element: &i64) -> BuiltinInterpResult {
55        let parts = path.split("/");
56
57        if (*element as usize) < parts.clone().count() {
58            let part =
59                parts
60                    .clone()
61                    .nth((*element) as usize)
62                    .ok_or(BuiltinInterpError::RuntimeError(format!(
63                        "Index {} is out of bounds (max {})",
64                        element,
65                        parts.count()
66                    )))?;
67            Ok(TypedValue {
68                value: Value::String(part.to_string()),
69                tpe: Type::String,
70            })
71        } else {
72            Err(BuiltinInterpError::RuntimeError(format!(
73                "Index {} is out of bounds (max {})",
74                element,
75                parts.count()
76            ))
77            .into())
78        }
79    }
80}
81
82#[builtin_function_interpreter(Type::String, Type::String, Type::Integer, Type::Integer)]
83impl Path_ElementsBuiltin {
84    fn interp(&self, path: &str, element_n: &i64, element_m: &i64) -> BuiltinInterpResult {
85        let parts = path.split("/");
86
87        let element_n = *element_n as usize;
88        let element_m = *element_m as usize;
89
90        if element_m < element_n {
91            return Err(BuiltinInterpError::RuntimeError(format!(
92                "Cannot access elements from {element_n} to {element_m} -- out of order",
93            ))
94            .into());
95        }
96
97        let result = parts.skip(element_n).take(element_m - element_n + 1); // + 1 for inclusive.
98
99        Ok(TypedValue {
100            value: Value::String(result.collect::<Vec<_>>().join("/")),
101            tpe: Type::String,
102        })
103    }
104}
105
106#[builtin_function_interpreter(Type::String, Type::String, Type::String)]
107impl MatchBuiltin {
108    fn interp(&self, input: &str, mtch: &str) -> BuiltinInterpResult {
109        let re = regex::RegexBuilder::new(mtch).build().map_err(|_| {
110            // TODO: Figure out how to use regex error messages nicely.
111            BuiltinInterpError::RuntimeError(format!("{mtch} is not a valid regular expression"))
112        })?;
113
114        let result = match re.find(input) {
115            Some(found) => found.as_str().to_string(),
116            None => "".to_string(),
117        };
118        Ok(TypedValue {
119            value: Value::String(result),
120            tpe: Type::String,
121        })
122    }
123}
124
125#[builtin_function_interpreter(Type::String, Type::String, Type::String, Type::String)]
126impl Match_ReplaceBuiltin {
127    fn interp(&self, input: &str, mtch: &str, replace: &str) -> BuiltinInterpResult {
128        let re = regex::RegexBuilder::new(mtch).build().map_err(|_| {
129            // TODO: Figure out how to use regex error messages nicely.
130            BuiltinInterpError::RuntimeError(format!("{mtch} is not a valid regular expression"))
131        })?;
132
133        let result = re.replace(input, replace);
134
135        Ok(TypedValue {
136            value: Value::String(result.to_string()),
137            tpe: Type::String,
138        })
139    }
140}
141
142#[derive(Debug, Clone, Default)]
143struct ParsedQuery {
144    elems: HashMap<String, String>,
145    order: Vec<String>,
146}
147
148impl TryFrom<&str> for ParsedQuery {
149    type Error = BuiltinInterpError;
150
151    fn try_from(value: &str) -> Result<Self, Self::Error> {
152        // Based on https://url.spec.whatwg.org/#urlencoded-parsing
153        let sequences = value.split('&');
154        let mut elems: HashMap<String, String> = HashMap::new();
155        let mut order: Vec<String> = vec![];
156        for bytes in sequences {
157            if bytes.is_empty() {
158                continue;
159            }
160
161            let (name, value) = match bytes.split_once('=') {
162                None => (bytes.to_string(), "".to_string()),
163                Some((n, v)) => (n.to_string(), v.to_string()),
164            };
165
166            elems.insert(name.clone(), value);
167            order.push(name);
168        }
169
170        Ok(ParsedQuery { elems, order })
171    }
172}
173
174impl Display for ParsedQuery {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        let res: Vec<_> = self
177            .order
178            .iter()
179            .map(|e| {
180                if let Some((k, v)) = self.elems.get_key_value(e) {
181                    let rest = if !v.is_empty() {
182                        format!("={v}")
183                    } else {
184                        "".to_string()
185                    };
186                    k.to_string() + &rest
187                } else {
188                    "".to_string()
189                }
190            })
191            .filter(|e| !e.is_empty())
192            .collect();
193
194        write!(f, "{}", res.join("&"))
195    }
196}
197
198#[builtin_function_interpreter(Type::String, Type::String, Type::String, Type::String)]
199impl Add_QueryBuiltin {
200    fn interp(&self, existing: &str, newq: &str, newv: &str) -> BuiltinInterpResult {
201        let mut pq = ParsedQuery::try_from(existing)?;
202
203        if pq
204            .elems
205            .insert(newq.to_string(), newv.to_string())
206            .is_none()
207        {
208            pq.order.push(newq.to_string())
209        }
210
211        Ok(TypedValue {
212            value: Value::String(pq.to_string()),
213            tpe: Type::String,
214        })
215    }
216}
217
218fn parse_multi_kv(i: &str) -> Vec<(String, Option<String>)> {
219    parse_multi_k(i)
220        .into_iter()
221        .map(|newi| {
222            let newi = newi.trim();
223            if let Some((n, v)) = newi.split_once('=') {
224                let n = n.trim();
225                let v = v.trim();
226                Some((
227                    n.to_owned(),
228                    if v.is_empty() {
229                        None
230                    } else {
231                        Some(v.to_owned())
232                    },
233                ))
234            } else {
235                None
236            }
237        })
238        .filter(|f| f.is_some())
239        .flatten()
240        .collect()
241}
242
243fn parse_multi_k(i: &str) -> Vec<String> {
244    i.split(',')
245        .map(|newi| {
246            let newi = newi.trim();
247            if !newi.is_empty() {
248                Some(newi.to_owned())
249            } else {
250                None
251            }
252        })
253        .filter(|f| f.is_some())
254        .flatten()
255        .collect()
256}
257
258#[builtin_function_interpreter(Type::String, Type::String, Type::String)]
259impl Add_Query_MultiBuiltin {
260    fn interp(&self, existing: &str, news: &str) -> BuiltinInterpResult {
261        let mut pq = ParsedQuery::try_from(existing)?;
262
263        for (n, v) in parse_multi_kv(news) {
264            if let Some(v) = v
265                && pq.elems.insert(n.to_string(), v).is_none()
266            {
267                pq.order.push(n.to_string())
268            };
269        }
270
271        Ok(TypedValue {
272            value: Value::String(pq.to_string()),
273            tpe: Type::String,
274        })
275    }
276}
277
278#[builtin_function_interpreter(Type::String, Type::String, Type::String)]
279impl Remove_QueryBuiltin {
280    fn interp(&self, existing: &str, oldq: &str) -> BuiltinInterpResult {
281        let mut pq = ParsedQuery::try_from(existing)?;
282
283        pq.elems.remove(oldq);
284
285        Ok(TypedValue {
286            value: Value::String(pq.to_string()),
287            tpe: Type::String,
288        })
289    }
290}
291
292#[builtin_function_interpreter(Type::String, Type::String, Type::String)]
293impl Remove_Query_MultiBuiltin {
294    fn interp(&self, existing: &str, olds: &str) -> BuiltinInterpResult {
295        let mut pq = ParsedQuery::try_from(existing)?;
296
297        for oldi in parse_multi_k(olds) {
298            pq.elems.remove(&oldi);
299        }
300
301        Ok(TypedValue {
302            value: Value::String(pq.to_string()),
303            tpe: Type::String,
304        })
305    }
306}
307
308#[builtin_function_interpreter(Type::String, Type::String, Type::String)]
309impl Keep_Query_MultiBuiltin {
310    fn interp(&self, existing: &str, keeps: &str) -> BuiltinInterpResult {
311        let mut pq = ParsedQuery::try_from(existing)?;
312
313        // TODO: This could be so much nicer.
314        let keep: Vec<_> = parse_multi_k(keeps);
315        let keys: Vec<_> = pq.elems.keys().cloned().collect();
316
317        for k in keys {
318            if !keep.contains(&k) {
319                pq.elems.remove(&k);
320            }
321        }
322
323        Ok(TypedValue {
324            value: Value::String(pq.to_string()),
325            tpe: Type::String,
326        })
327    }
328}
329
330#[builtin_function_interpreter(Type::String, Type::String)]
331impl UpperBuiltin {
332    fn interp(&self, s: &str) -> BuiltinInterpResult {
333        Ok(TypedValue {
334            value: Value::String(s.to_uppercase()),
335            tpe: Type::String,
336        })
337    }
338}
339
340#[builtin_function_interpreter(Type::String, Type::String)]
341impl LowerBuiltin {
342    fn interp(&self, s: &str) -> BuiltinInterpResult {
343        Ok(TypedValue {
344            value: Value::String(s.to_lowercase()),
345            tpe: Type::String,
346        })
347    }
348}
349
350impl BuiltinFunctionInterpreter for BooleanBuiltin {
351    fn interpw(&self, args: Value) -> BuiltinInterpResult {
352        let args = match args {
353            Value::ArgumentList(args) => args,
354            _ => return Err(BuiltinInterpError::ArgumentsInvalid.into()),
355        };
356
357        if args.len() != 1 {
358            return Err(BuiltinInterpError::ArgumentMiscount(1, args.len()).into());
359        }
360
361        let arg = &args[0];
362
363        let result = match arg {
364            TypedValue {
365                value: Value::Nil,
366                tpe: Type::Nil,
367            } => TypedValue {
368                value: Value::Boolean(false),
369                tpe: Type::Boolean,
370            },
371            TypedValue {
372                value: Value::String(s),
373                tpe: Type::String,
374            } => TypedValue {
375                value: Value::Boolean(!s.is_empty()),
376                tpe: Type::Boolean,
377            },
378            TypedValue {
379                value: Value::Integer(i),
380                tpe: Type::Integer,
381            } => TypedValue {
382                value: Value::Boolean(*i != 0),
383                tpe: Type::Boolean,
384            },
385            TypedValue {
386                value: Value::Real(r),
387                tpe: Type::Real,
388            } => TypedValue {
389                value: Value::Boolean(*r != 0.0),
390                tpe: Type::Boolean,
391            },
392            b @ TypedValue {
393                value: Value::Boolean(_),
394                tpe: Type::Boolean,
395            } => b.clone(),
396            TypedValue { value: _, tpe: t } => {
397                return Err(BuiltinInterpError::RuntimeError(format!(
398                    "Semantics of calling boolean on an argument with type {t} are undefined"
399                ))
400                .into());
401            }
402        };
403        Ok(result)
404    }
405}
406
407impl BuiltinFunctionInterpreter for IntegerBuiltin {
408    fn interpw(&self, args: Value) -> BuiltinInterpResult {
409        let args = match args {
410            Value::ArgumentList(args) => args,
411            _ => return Err(BuiltinInterpError::ArgumentsInvalid.into()),
412        };
413
414        if args.len() != 1 {
415            return Err(BuiltinInterpError::ArgumentMiscount(1, args.len()).into());
416        }
417
418        // Now, interpret!
419
420        let arg = &args[0];
421
422        let result = match arg {
423            TypedValue {
424                value: Value::Nil,
425                tpe: Type::Nil,
426            } => TypedValue {
427                value: Value::Integer(0),
428                tpe: Type::Integer,
429            },
430            TypedValue {
431                value: Value::String(s),
432                tpe: Type::String,
433            } => s.parse().map_or(
434                TypedValue {
435                    value: Value::Nil,
436                    tpe: Type::Nil,
437                },
438                |v| TypedValue {
439                    value: Value::Integer(v),
440                    tpe: Type::Integer,
441                },
442            ),
443            v @ TypedValue {
444                value: Value::Integer(_),
445                tpe: Type::Integer,
446            } => v.clone(),
447            TypedValue {
448                value: Value::Real(r),
449                tpe: Type::Real,
450            } => TypedValue {
451                value: Value::Integer(*r as i64),
452                tpe: Type::Integer,
453            },
454            TypedValue {
455                value: Value::Boolean(b),
456                tpe: Type::Boolean,
457            } => TypedValue {
458                value: Value::Integer(if *b { 1 } else { 0 }),
459                tpe: Type::Integer,
460            },
461            TypedValue { value: _, tpe: t } => {
462                return Err(BuiltinInterpError::RuntimeError(format!(
463                    "Semantics of calling integer on an argument with type {t} are undefined"
464                ))
465                .into());
466            }
467        };
468        Ok(result)
469    }
470}
471
472impl BuiltinFunctionInterpreter for UnsignedBuiltin {
473    fn interpw(&self, args: Value) -> BuiltinInterpResult {
474        let args = match args {
475            Value::ArgumentList(args) => args,
476            _ => return Err(BuiltinInterpError::ArgumentsInvalid.into()),
477        };
478
479        if args.len() != 1 {
480            return Err(BuiltinInterpError::ArgumentMiscount(1, args.len()).into());
481        }
482
483        let arg = &args[0];
484
485        let result = match arg {
486            TypedValue {
487                value: Value::Nil,
488                tpe: Type::Nil,
489            } => TypedValue {
490                value: Value::Unsigned(0),
491                tpe: Type::Unsigned,
492            },
493            TypedValue {
494                value: Value::String(s),
495                tpe: Type::String,
496            } => {
497                if s.starts_with("0x") {
498                    u64::from_str_radix(s.trim_start_matches("0x"), 16).map_or(
499                        TypedValue {
500                            value: Value::Nil,
501                            tpe: Type::Nil,
502                        },
503                        |v| TypedValue {
504                            value: Value::Unsigned(v),
505                            tpe: Type::Unsigned,
506                        },
507                    )
508                } else {
509                    s.parse().map_or(
510                        TypedValue {
511                            value: Value::Nil,
512                            tpe: Type::Nil,
513                        },
514                        |v| TypedValue {
515                            value: Value::Unsigned(v),
516                            tpe: Type::Unsigned,
517                        },
518                    )
519                }
520            }
521            TypedValue {
522                value: Value::Integer(value),
523                tpe: Type::Integer,
524            } => {
525                if *value >= 0 {
526                    TypedValue {
527                        value: Value::Unsigned(*value as u64),
528                        tpe: Type::Unsigned,
529                    }
530                } else {
531                    TypedValue {
532                        value: Value::Unsigned(0),
533                        tpe: Type::Unsigned,
534                    }
535                }
536            }
537            TypedValue {
538                value: Value::Real(r),
539                tpe: Type::Real,
540            } => {
541                if *r >= 0.0 {
542                    TypedValue {
543                        value: Value::Unsigned(*r as u64),
544                        tpe: Type::Unsigned,
545                    }
546                } else {
547                    TypedValue {
548                        value: Value::Unsigned(0),
549                        tpe: Type::Unsigned,
550                    }
551                }
552            }
553            TypedValue {
554                value: Value::Boolean(b),
555                tpe: Type::Boolean,
556            } => TypedValue {
557                value: Value::Unsigned(if *b { 1 } else { 0 }),
558                tpe: Type::Unsigned,
559            },
560            TypedValue { value: _, tpe: t } => {
561                return Err(BuiltinInterpError::RuntimeError(format!(
562                    "Semantics of calling integer on an argument with type {t} are undefined"
563                ))
564                .into());
565            }
566        };
567        Ok(result)
568    }
569}
570
571impl BuiltinFunctionInterpreter for RealBuiltin {
572    fn interpw(&self, args: Value) -> BuiltinInterpResult {
573        let args = match args {
574            Value::ArgumentList(args) => args,
575            _ => return Err(BuiltinInterpError::ArgumentsInvalid.into()),
576        };
577
578        if args.len() != 1 {
579            return Err(BuiltinInterpError::ArgumentMiscount(1, args.len()).into());
580        }
581
582        let arg = &args[0];
583
584        let result = match arg {
585            TypedValue {
586                value: Value::Nil,
587                tpe: Type::Nil,
588            } => TypedValue {
589                value: Value::Real(0.0),
590                tpe: Type::Real,
591            },
592            TypedValue {
593                value: Value::String(s),
594                tpe: Type::String,
595            } => s.parse().map_or(
596                TypedValue {
597                    value: Value::Nil,
598                    tpe: Type::Nil,
599                },
600                |v| TypedValue {
601                    value: Value::Real(v),
602                    tpe: Type::Real,
603                },
604            ),
605            TypedValue {
606                value: Value::Integer(value),
607                tpe: Type::Integer,
608            } => TypedValue {
609                value: Value::Real(*value as f64),
610                tpe: Type::Real,
611            },
612            r @ TypedValue {
613                value: Value::Real(_),
614                tpe: Type::Real,
615            } => r.clone(),
616            TypedValue {
617                value: Value::Boolean(b),
618                tpe: Type::Boolean,
619            } => TypedValue {
620                value: Value::Real(if *b { 1.0 } else { 0.0 }),
621                tpe: Type::Real,
622            },
623            TypedValue { value: _, tpe: t } => {
624                return Err(BuiltinInterpError::RuntimeError(format!(
625                    "Semantics of calling integer on an argument with type {t} are undefined"
626                ))
627                .into());
628            }
629        };
630        Ok(result)
631    }
632}
633
634impl BuiltinFunctionInterpreter for StringBuiltin {
635    fn interpw(&self, args: Value) -> BuiltinInterpResult {
636        let args = match args {
637            Value::ArgumentList(args) => args,
638            _ => return Err(BuiltinInterpError::ArgumentsInvalid.into()),
639        };
640
641        if args.len() != 1 {
642            return Err(BuiltinInterpError::ArgumentMiscount(1, args.len()).into());
643        }
644
645        Ok(TypedValue {
646            value: Value::String(args[0].to_string()),
647            tpe: Type::String,
648        })
649    }
650}
651
652macro_rules! add_builtin_function_interpreter_to_scope {
653    ($scope:ident, $builtin:ident) => {
654        $scope.insert(
655            &$builtin.name(),
656            TypedValue {
657                value: Value::Function(Arc::new($builtin.clone())),
658                tpe: Type::Function(
659                    $builtin.name(),
660                    $builtin.return_type_calculator(),
661                    $builtin.params_type_checker(),
662                ),
663            },
664        )
665    };
666}
667
668/// Create a [`Scope<TypedValue>`] containing all the MEL builtin interpreters.
669pub fn builtin_builtin_function_interpreters() -> Scope<TypedValue> {
670    let path_element = Path_ElementBuiltin {};
671    let path_elements = Path_ElementsBuiltin {};
672    let mtch = MatchBuiltin {};
673    let match_replace = Match_ReplaceBuiltin {};
674    let add_query = Add_QueryBuiltin {};
675    let add_query_multi = Add_Query_MultiBuiltin {};
676    let remove_query = Remove_QueryBuiltin {};
677    let remove_query_multi = Remove_Query_MultiBuiltin {};
678    let keep_query_multi = Keep_Query_MultiBuiltin {};
679    let lower = LowerBuiltin {};
680    let upper = UpperBuiltin {};
681
682    // Conversion functions.
683    let boolean = BooleanBuiltin {};
684    let integer = IntegerBuiltin {};
685    let unsigned = UnsignedBuiltin {};
686    let string = StringBuiltin {};
687    let real = RealBuiltin {};
688
689    let mut scopes = Scope::<TypedValue>::default();
690
691    scopes = add_builtin_function_interpreter_to_scope!(scopes, path_element);
692    scopes = add_builtin_function_interpreter_to_scope!(scopes, path_elements);
693    scopes = add_builtin_function_interpreter_to_scope!(scopes, mtch);
694    scopes = add_builtin_function_interpreter_to_scope!(scopes, match_replace);
695    scopes = add_builtin_function_interpreter_to_scope!(scopes, add_query);
696    scopes = add_builtin_function_interpreter_to_scope!(scopes, add_query_multi);
697    scopes = add_builtin_function_interpreter_to_scope!(scopes, remove_query);
698    scopes = add_builtin_function_interpreter_to_scope!(scopes, remove_query_multi);
699    scopes = add_builtin_function_interpreter_to_scope!(scopes, keep_query_multi);
700    scopes = add_builtin_function_interpreter_to_scope!(scopes, lower);
701    scopes = add_builtin_function_interpreter_to_scope!(scopes, upper);
702
703    scopes = add_builtin_function_interpreter_to_scope!(scopes, boolean);
704    scopes = add_builtin_function_interpreter_to_scope!(scopes, integer);
705    scopes = add_builtin_function_interpreter_to_scope!(scopes, unsigned);
706    scopes = add_builtin_function_interpreter_to_scope!(scopes, string);
707    scopes = add_builtin_function_interpreter_to_scope!(scopes, real);
708    scopes
709}