Skip to main content

brooks_lib/mel/
analysis.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//! Analyse a MEL expression
19
20use std::{
21    fmt::{Debug, Display},
22    net::IpAddr,
23    sync::Arc,
24};
25
26use crate::{
27    common::TextLocation,
28    environment::scope::Scopes,
29    mel::{
30        error::{AnalysisAssertions, AnalysisError, AnalysisLocatableError, AnalysisPreconditions},
31        types::{ArgumentTypeList, ParamsTypeCheckerError, ParamsTypeCheckerGenerator},
32    },
33};
34
35use crate::mel::{
36    analysis::AnalysisAssertions::{
37        ContextMissingExpr, ContextMissingParams, ContextWrongExprType,
38    },
39    ast::{
40        self, Argument, ArgumentList, AstVisitor, AstVisitorDriver, AstVisitorResult, BinaryExpr,
41        BinaryInfixOperator, BooleanLiteral,
42        ComparisonOperator::{IP, Re},
43        Expr, FunctionCall, IPAddressLiteral, Identifier, MemberAccessExpression, NumberLiteral,
44        StringLiteral, TernaryExpr,
45    },
46    error::AnalysisError::{AssertionFailure, InvalidType, Mismatch, UnknownIdentifier},
47    types::{
48        self,
49        Type::{self, Struct},
50    },
51};
52
53#[derive(Debug, Clone)]
54pub enum CompiledConstant {
55    Integer(i64),
56    String(String),
57    Boolean(bool),
58    IPAddress(IpAddr),
59}
60
61#[derive(Debug, Clone)]
62pub struct Analyzed {
63    pub tipe: Type,
64    pub constant: Option<CompiledConstant>,
65}
66
67impl Expr<Analyzed> {
68    pub fn tipe(&self) -> Type {
69        match self {
70            Expr::FunctionCall(function_call) => function_call.aug.tipe.clone(),
71            Expr::BinaryExpr(binary_expr) => binary_expr.aug.tipe.clone(),
72            Expr::TernaryExpr(ternary_expr) => ternary_expr.aug.tipe.clone(),
73            Expr::Identifier(identifier) => identifier.aug.tipe.clone(),
74            Expr::ArgumentList(argument_list) => argument_list.aug.tipe.clone(),
75            Expr::Argument(argument) => argument.aug.tipe.clone(),
76            Expr::MemberAccess(member) => member.aug.tipe.clone(),
77            Expr::Literal(_, _, aug) => aug.tipe.clone(),
78        }
79    }
80
81    pub fn constant(&self) -> Option<CompiledConstant> {
82        let analyzed = match self {
83            Expr::FunctionCall(function_call) => &function_call.aug,
84            Expr::BinaryExpr(binary_expr) => &binary_expr.aug,
85            Expr::TernaryExpr(ternary_expr) => &ternary_expr.aug,
86            Expr::Identifier(identifier) => &identifier.aug,
87            Expr::ArgumentList(argument_list) => &argument_list.aug,
88            Expr::Argument(argument) => &argument.aug,
89            Expr::MemberAccess(member) => &member.aug,
90            Expr::Literal(_, _, aug) => aug,
91        };
92        analyzed.constant.clone()
93    }
94}
95
96impl<A: Clone + Debug> Display for Expr<A> {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self {
99            Expr::FunctionCall(_) => write!(f, "FunctionCall"),
100            Expr::BinaryExpr(_) => write!(f, "BinaryExpr"),
101            Expr::TernaryExpr(_) => write!(f, "TernaryExpr"),
102            Expr::Identifier(_) => write!(f, "Identifier"),
103            Expr::ArgumentList(_) => write!(f, "ArgumentList"),
104            Expr::Argument(_) => write!(f, "Argument"),
105            Expr::MemberAccess(_) => write!(f, "MemberAccess"),
106            Expr::Literal(_, _, _) => write!(f, "Literal"),
107        }
108    }
109}
110
111#[derive(Debug, Clone, Default)]
112pub struct MelAnalysisContext {
113    pub expr: Option<Expr<Analyzed>>,
114    pub ptcg: Option<ParamsTypeCheckerGenerator>,
115    pub scopes: Scopes<Type>,
116}
117
118impl MelAnalysisContext {
119    pub fn update_expr(&self, new: Expr<Analyzed>) -> Self {
120        MelAnalysisContext {
121            expr: Some(new),
122            ptcg: self.ptcg,
123            scopes: self.scopes.clone(),
124        }
125    }
126    pub fn update_scopes(&self, new: &Scopes<Type>) -> Self {
127        MelAnalysisContext {
128            expr: self.expr.clone(),
129            ptcg: self.ptcg,
130            scopes: new.clone(),
131        }
132    }
133    pub fn update_param_type_checker_generator(&self, ptcg: ParamsTypeCheckerGenerator) -> Self {
134        MelAnalysisContext {
135            expr: self.expr.clone(),
136            ptcg: Some(ptcg),
137            scopes: self.scopes.clone(),
138        }
139    }
140}
141
142impl From<Vec<Argument<Analyzed>>> for ArgumentTypeList {
143    fn from(value: Vec<Argument<Analyzed>>) -> Self {
144        ArgumentTypeList {
145            args: value.iter().map(|a| a.expr.tipe()).collect(),
146        }
147    }
148}
149
150pub struct MelTypeChecker {}
151
152impl AstVisitor<MelAnalysisContext, (), AnalysisLocatableError> for MelTypeChecker {
153    fn visit_function_call(
154        &self,
155        ast: &FunctionCall<()>,
156        context: MelAnalysisContext,
157        driver: &AstVisitorDriver,
158    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
159        let callee = driver.visit(&ast.callee, self, context.clone())?;
160
161        let callee = if let Some(callee) = callee.expr {
162            callee
163        } else {
164            return Err(AnalysisLocatableError {
165                error: AssertionFailure(AnalysisAssertions::ContextMissingExpr(
166                    "visit_function_call".to_string(),
167                ))
168                .into(),
169                location: ast.location.clone(),
170            });
171        };
172
173        let fn_params = match callee.tipe() {
174            Type::Function(_, return_type_generator, param_type_checker_generator) => {
175                let return_type = return_type_generator();
176                (return_type, param_type_checker_generator)
177            }
178            _ => unreachable!(), // TODO: Check whether this scenario is _really_ unreachable.
179        };
180
181        let context_with_params = context.update_param_type_checker_generator(fn_params.1);
182        let args = self.visit_argument_list(&ast.arguments, context_with_params, driver)?;
183        let args = match args.expr.unwrap() {
184            Expr::ArgumentList(argument_list) => Ok(argument_list),
185            e => Err(AssertionFailure(ContextWrongExprType(
186                "ArgumentList".to_string(),
187                e.to_string(),
188                "visit_function_call".to_string(),
189            ))
190            .into()),
191        }
192        .map_err(|e| AnalysisLocatableError {
193            error: e,
194            location: ast.location.clone(),
195        })?;
196
197        Ok(
198            context.update_expr(Expr::FunctionCall(Arc::new(FunctionCall {
199                callee: callee.clone(),
200                location: ast.location.clone(),
201                arguments: (*args).clone(),
202                aug: Analyzed {
203                    tipe: fn_params.0.clone(),
204                    constant: None,
205                },
206            }))),
207        )
208    }
209
210    fn visit_identifier(
211        &self,
212        ast: &Identifier<()>,
213        context: MelAnalysisContext,
214        _driver: &AstVisitorDriver,
215    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
216        let found_id = context
217            .scopes
218            .lookup(&ast.identifier)
219            .ok_or(AnalysisLocatableError {
220                error: UnknownIdentifier(ast.identifier.clone()).into(),
221                location: ast.location.clone(),
222            })?;
223
224        Ok(context.update_expr(Expr::Identifier(Arc::new(Identifier {
225            identifier: ast.identifier.clone(),
226            location: ast.location.clone(),
227            aug: Analyzed {
228                tipe: found_id,
229                constant: None,
230            },
231        }))))
232    }
233
234    fn visit_argument_list(
235        &self,
236        ast: &ArgumentList<()>,
237        context: MelAnalysisContext,
238        driver: &AstVisitorDriver,
239    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
240        let ptcg = context.ptcg.ok_or(AnalysisLocatableError {
241            error: AssertionFailure(ContextMissingParams).into(),
242            location: ast.location.clone(),
243        })?;
244
245        let ptc = ptcg();
246
247        let mut arg_types: Vec<Argument<Analyzed>> = vec![];
248        for arg in &ast.arguments {
249            let arg = self
250                .visit_argument(arg, context.clone(), driver)?
251                .expr
252                .ok_or(AnalysisLocatableError {
253                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
254                        "visit_argument_list".into(),
255                    ))
256                    .into(),
257                    location: arg.location.clone(),
258                })?;
259            let arg = match arg {
260                Expr::Argument(argument) => Ok(argument),
261                e => Err(AssertionFailure(ContextWrongExprType(
262                    "Argument".to_string(),
263                    e.to_string(),
264                    "visit_argument_list".to_string(),
265                ))),
266            }
267            .map_err(|e| AnalysisLocatableError {
268                error: e.into(),
269                location: ast.location.clone(),
270            })?;
271            arg_types.push((*arg).clone());
272        }
273
274        ptc.check(arg_types.clone().into()).map_err(|e| {
275            let error = match e {
276                ParamsTypeCheckerError::Miscount(expected, actual) => {
277                    AnalysisError::Miscount(expected, actual)
278                }
279                ParamsTypeCheckerError::Mismatch(expected, actual) => {
280                    AnalysisError::Mismatch(expected, actual)
281                }
282            };
283            AnalysisLocatableError {
284                error: error.into(),
285                location: ast.location.clone(),
286            }
287        })?;
288
289        Ok(
290            context.update_expr(Expr::ArgumentList(Arc::new(ArgumentList {
291                arguments: arg_types,
292                location: ast.location.clone(),
293                aug: Analyzed {
294                    tipe: Type::Nil,
295                    constant: None,
296                },
297            }))),
298        )
299    }
300
301    fn visit_argument(
302        &self,
303        ast: &Argument<()>,
304        context: MelAnalysisContext,
305        driver: &AstVisitorDriver,
306    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
307        let arg =
308            driver
309                .visit(&ast.expr, self, context.clone())?
310                .expr
311                .ok_or(AnalysisLocatableError {
312                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
313                        "visit_argument".into(),
314                    ))
315                    .into(),
316                    location: ast.location.clone(),
317                })?;
318
319        let arg_type = arg.tipe();
320        Ok(context.update_expr(Expr::Argument(Arc::new(Argument {
321            expr: arg,
322            location: ast.location.clone(),
323            aug: Analyzed {
324                tipe: arg_type,
325                constant: None,
326            },
327        }))))
328    }
329
330    fn visit_binary_expr(
331        &self,
332        ast: &BinaryExpr<()>,
333        context: MelAnalysisContext,
334        driver: &AstVisitorDriver,
335    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
336        let left =
337            driver
338                .visit(&ast.left, self, context.clone())?
339                .expr
340                .ok_or(AnalysisLocatableError {
341                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
342                        "visit_binary_expr".into(),
343                    ))
344                    .into(),
345                    location: ast.left.location(),
346                })?;
347        let right = driver
348            .visit(&ast.right, self, context.clone())?
349            .expr
350            .ok_or(AnalysisLocatableError {
351                error: AnalysisError::AssertionFailure(ContextMissingExpr(
352                    "visit_binary_expr".into(),
353                ))
354                .into(),
355                location: ast.right.location(),
356            })?;
357
358        let left_type = left.tipe();
359        let right_type = right.tipe();
360
361        let (valid_types, result_type) = match &ast.op {
362            BinaryInfixOperator::Logic(_) => (vec![Type::Boolean], Type::Boolean),
363            BinaryInfixOperator::Comparison(Re) => (vec![Type::Regex, Type::String], Type::Boolean),
364            BinaryInfixOperator::Comparison(IP) => (vec![Type::IPAddress], Type::Boolean),
365            BinaryInfixOperator::Comparison(_) => (
366                vec![Type::Boolean, Type::String, Type::Integer],
367                Type::Boolean,
368            ),
369            BinaryInfixOperator::Math(_) => (vec![Type::Integer], Type::Integer),
370            BinaryInfixOperator::Concat(_) => (vec![Type::String], Type::String),
371            BinaryInfixOperator::MemberAccess(e) => {
372                return Err(AnalysisLocatableError {
373                    error: AnalysisError::AssertionFailure(AnalysisAssertions::InvalidOperator(
374                        e.to_string(),
375                        "visit_binary_expr".to_string(),
376                    ))
377                    .into(),
378                    location: ast.right.location(),
379                });
380            }
381        };
382
383        if !valid_types.contains(&left_type) {
384            return Err(AnalysisLocatableError {
385                error: InvalidType(valid_types, left_type).into(),
386                location: left.location(),
387            });
388        }
389        if !valid_types.contains(&right_type) {
390            return Err(AnalysisLocatableError {
391                error: InvalidType(valid_types, right_type).into(),
392                location: right.location(),
393            });
394        }
395
396        // If the operator is the regex operator, then there is special handling.
397        if let BinaryInfixOperator::Comparison(Re) = ast.op {
398            if left_type != Type::String {
399                return Err(AnalysisLocatableError {
400                    error: Mismatch(Type::String, left_type).into(),
401                    location: left.location(),
402                });
403            }
404            if right_type != Type::Regex {
405                return Err(AnalysisLocatableError {
406                    error: Mismatch(Type::Regex, right_type).into(),
407                    location: right.location(),
408                });
409            }
410            return Ok(context.update_expr(Expr::BinaryExpr(Arc::new(BinaryExpr {
411                left,
412                right,
413                op: ast.op.clone(),
414                location: ast.location.clone(),
415                aug: Analyzed {
416                    tipe: result_type,
417                    constant: None,
418                },
419            }))));
420        }
421
422        // Otherwise, the types just need to be equal!
423        if left_type != right_type {
424            return Err(AnalysisLocatableError {
425                error: Mismatch(left_type, right_type).into(),
426                location: right.location(),
427            });
428        }
429
430        Ok(context.update_expr(Expr::BinaryExpr(Arc::new(BinaryExpr {
431            left,
432            right,
433            op: ast.op.clone(),
434            location: ast.location.clone(),
435            aug: Analyzed {
436                tipe: result_type,
437                constant: None,
438            },
439        }))))
440    }
441
442    fn visit_literal(
443        &self,
444        ast: (&ast::Literal, &TextLocation, &()),
445        context: MelAnalysisContext,
446        _driver: &AstVisitorDriver,
447    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
448        match ast {
449            (b @ ast::Literal::Boolean(_), location, _) => Ok(context.update_expr(Expr::Literal(
450                b.clone(),
451                location.clone(),
452                Analyzed {
453                    tipe: Type::Boolean,
454                    constant: None,
455                },
456            ))),
457            (n @ ast::Literal::Number(_), location, _) => Ok(context.update_expr(Expr::Literal(
458                n.clone(),
459                location.clone(),
460                Analyzed {
461                    tipe: Type::Integer,
462                    constant: None,
463                },
464            ))),
465            (s @ ast::Literal::String(_), location, _) => Ok(context.update_expr(Expr::Literal(
466                s.clone(),
467                location.clone(),
468                Analyzed {
469                    tipe: Type::String,
470                    constant: None,
471                },
472            ))),
473            (r @ ast::Literal::Regex(_), location, _) => Ok(context.update_expr(Expr::Literal(
474                r.clone(),
475                location.clone(),
476                Analyzed {
477                    tipe: Type::Regex,
478                    constant: None,
479                },
480            ))),
481            (ip @ ast::Literal::IPAddress(_), location, _) => {
482                Ok(context.update_expr(Expr::Literal(
483                    ip.clone(),
484                    location.clone(),
485                    Analyzed {
486                        tipe: Type::IPAddress,
487                        constant: None,
488                    },
489                )))
490            }
491        }
492    }
493
494    fn visit_ternary_expr(
495        &self,
496        ast: &TernaryExpr<()>,
497        context: MelAnalysisContext,
498        driver: &AstVisitorDriver,
499    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
500        let condition = driver
501            .visit(&ast.condition, self, context.clone())?
502            .expr
503            .ok_or(AnalysisLocatableError {
504                error: AnalysisError::AssertionFailure(ContextMissingExpr(
505                    "visit_ternary_expr".into(),
506                ))
507                .into(),
508                location: ast.condition.location(),
509            })?;
510        let yes =
511            driver
512                .visit(&ast.yes, self, context.clone())?
513                .expr
514                .ok_or(AnalysisLocatableError {
515                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
516                        "visit_ternary_expr".into(),
517                    ))
518                    .into(),
519                    location: ast.yes.location(),
520                })?;
521
522        let no =
523            driver
524                .visit(&ast.no, self, context.clone())?
525                .expr
526                .ok_or(AnalysisLocatableError {
527                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
528                        "visit_ternary_expr".into(),
529                    ))
530                    .into(),
531                    location: ast.no.location(),
532                })?;
533
534        let condition_type = condition.tipe();
535        let yes_type = yes.tipe();
536        let no_type = no.tipe();
537
538        if condition_type != Type::Boolean {
539            return Err(AnalysisLocatableError {
540                error: Mismatch(Type::Boolean, condition_type).into(),
541                location: ast.location.clone(),
542            });
543        }
544
545        if yes_type != no_type {
546            return Err(AnalysisLocatableError {
547                error: Mismatch(yes_type, no_type).into(),
548                location: ast.location.clone(),
549            });
550        }
551
552        Ok(context.update_expr(Expr::TernaryExpr(Arc::new(TernaryExpr {
553            condition,
554            yes,
555            no,
556            location: ast.location.clone(),
557            aug: Analyzed {
558                tipe: yes_type,
559                constant: None,
560            },
561        }))))
562    }
563
564    fn visit_member_access_expr(
565        &self,
566        ast: &ast::MemberAccessExpression<()>,
567        context: MelAnalysisContext,
568        driver: &AstVisitorDriver,
569    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
570        let base =
571            driver
572                .visit(&ast.base, self, context.clone())?
573                .expr
574                .ok_or(AnalysisLocatableError {
575                    error: AnalysisError::AssertionFailure(ContextMissingExpr(
576                        "visit_member_access_expr".into(),
577                    ))
578                    .into(),
579                    location: ast.base.location(),
580                })?;
581
582        let struct_type = match base.tipe() {
583            Type::Struct(struct_type) => struct_type,
584            t => {
585                return Err(AnalysisLocatableError {
586                    error: AnalysisError::Mismatch(Struct(types::Struct::new("TODO")), t).into(),
587                    location: ast.base.location(),
588                });
589            }
590        };
591
592        let member_type =
593            struct_type
594                .type_for_field(&ast.member.identifier)
595                .ok_or(AnalysisLocatableError {
596                    error: AnalysisError::UnknownField(
597                        struct_type.name,
598                        ast.member.identifier.clone(),
599                    )
600                    .into(),
601                    location: ast.base.location(),
602                })?;
603
604        Ok(
605            context.update_expr(Expr::MemberAccess(Arc::new(MemberAccessExpression {
606                base,
607                oper: ast.oper.clone(),
608                member: Identifier {
609                    identifier: ast.member.identifier.clone(),
610                    aug: Analyzed {
611                        tipe: types::Type::Nil,
612                        constant: None,
613                    },
614                    location: ast.location.clone(),
615                },
616                location: ast.location.clone(),
617                aug: Analyzed {
618                    tipe: member_type,
619                    constant: None,
620                },
621            }))),
622        )
623    }
624}
625
626#[cfg(test)]
627mod type_check_tests {
628    use crate::common::TextLocation;
629    use crate::mel::MEL_USER_SET_VARIABLE_STRUCT_NAME;
630    use crate::mel::types::Type::IPAddress;
631    use crate::mel::types::{
632        BuiltinFunctionType, IntegerBuiltin, RealBuiltin, SimpleParamTypeChecker, StringBuiltin,
633        minimal_core_variable_types,
634    };
635    use crate::mel::{
636        analysis::{AnalysisError, Analyzed, MelAnalysisContext, MelTypeChecker},
637        ast::{AstVisitorDriver, BinaryExpr, Expr, FunctionCall, Identifier},
638        compiler::compile,
639        types::{
640            self,
641            Type::{self, Boolean, Function, Integer},
642        },
643    };
644    use std::assert_matches;
645
646    #[test]
647    fn test_type_check_literal() {
648        let expr = "5";
649
650        let compile_result = compile(expr);
651        let ast = compile_result.expect("Compilation error");
652
653        let driver = AstVisitorDriver {};
654        let visitor = MelTypeChecker {};
655        let context = MelAnalysisContext::default();
656        let result = driver
657            .visit(&ast, &visitor, context)
658            .expect("Could not analyze");
659
660        let result = result.expr.expect("Could not get the analyzed expression");
661        let aug = match result {
662            Expr::Literal(_, _, c) => c,
663            _ => todo!(),
664        };
665
666        assert_matches!(
667            aug,
668            Analyzed {
669                tipe: Type::Integer,
670                constant: None,
671            }
672        )
673    }
674
675    #[test]
676    fn test_type_check_invalid_regex_expr() {
677        let expr = "\"testing\" ~= /[tsting/";
678
679        let compile_result = compile(expr);
680        let _ =
681            compile_result.expect_err("Could compile expression with invalid regular expression.");
682    }
683
684    #[test]
685    fn test_type_check_binary_regex_expr() {
686        let expr = "\"testing\" ~= /testing/";
687
688        let compile_result = compile(expr);
689        let ast = compile_result.expect("Compilation error");
690
691        let driver = AstVisitorDriver {};
692        let visitor = MelTypeChecker {};
693        let context = MelAnalysisContext::default();
694        let result = driver
695            .visit(&ast, &visitor, context)
696            .expect("Could not analyze");
697
698        let result = result.expr.expect("Could not get the analyzed expression");
699        let binary_expr = match result {
700            Expr::BinaryExpr(binary_expr) => binary_expr,
701            _ => todo!(),
702        };
703
704        assert_matches!(
705            *binary_expr,
706            BinaryExpr::<Analyzed> {
707                left: _,
708                right: _,
709                op: _,
710                location: _,
711                aug: Analyzed {
712                    tipe: Type::Boolean,
713                    constant: _
714                }
715            }
716        )
717    }
718
719    #[test]
720    fn test_type_check_binary_regex_expr_error() {
721        let expr = "5 ~= /testing/";
722
723        let compile_result = compile(expr);
724        let ast = compile_result.expect("Compilation error");
725
726        let driver = AstVisitorDriver {};
727        let visitor = MelTypeChecker {};
728        let context = MelAnalysisContext::default();
729        let result = driver
730            .visit(&ast, &visitor, context)
731            .expect_err("Could analyze expression with type error");
732
733        assert_eq!(
734            result.location,
735            TextLocation {
736                start: 0,
737                extent: 1
738            }
739        );
740        assert_matches!(
741            *result.error,
742            AnalysisError::InvalidType(i, Integer)
743            if i == vec![Type::Regex, Type::String]
744        )
745    }
746
747    #[test]
748    fn test_type_check_binary_regex_expr_error_reverse() {
749        let expr = "\"/testing/\" ~= false";
750
751        let compile_result = compile(expr);
752        let ast = compile_result.expect("Compilation error");
753
754        let driver = AstVisitorDriver {};
755        let visitor = MelTypeChecker {};
756        let context = MelAnalysisContext::default();
757        let result = driver
758            .visit(&ast, &visitor, context)
759            .expect_err("Could analyze expression with type error");
760
761        assert_eq!(
762            result.location,
763            TextLocation {
764                start: 15,
765                extent: 5
766            }
767        );
768        assert_matches!(*result.error,
769            AnalysisError::InvalidType(i, Boolean)
770            if i == vec![Type::Regex, Type::String]
771        )
772    }
773
774    #[test]
775    fn test_type_check_binary_regex_expr_error_two_res() {
776        let expr = "/testing/ ~= /[\\W]/";
777
778        let compile_result = compile(expr);
779        let ast = compile_result.expect("Compilation error");
780
781        let driver = AstVisitorDriver {};
782        let visitor = MelTypeChecker {};
783        let context = MelAnalysisContext::default();
784        let result = driver
785            .visit(&ast, &visitor, context)
786            .expect_err("Could analyze expression with type error");
787        assert_eq!(
788            result.location,
789            TextLocation {
790                start: 0,
791                extent: 9
792            }
793        );
794        assert_matches!(
795            *result.error,
796            AnalysisError::Mismatch(Type::String, Type::Regex)
797        )
798    }
799
800    #[test]
801    fn test_type_check_binary_regex_expr_error_two_strings() {
802        let expr = "\"testing\" ~= \"hello\"";
803
804        let compile_result = compile(expr);
805        let ast = compile_result.expect("Compilation error");
806
807        let driver = AstVisitorDriver {};
808        let visitor = MelTypeChecker {};
809        let context = MelAnalysisContext::default();
810        let result = driver
811            .visit(&ast, &visitor, context)
812            .expect_err("Could analyze expression with type error");
813
814        assert_eq!(
815            result.location,
816            TextLocation {
817                start: 13,
818                extent: 7
819            }
820        );
821        assert_matches!(
822            *result.error,
823            AnalysisError::Mismatch(Type::Regex, Type::String)
824        )
825    }
826
827    #[test]
828    fn test_type_check_binary_expr() {
829        let expr = "5 + 3";
830
831        let compile_result = compile(expr);
832        let ast = compile_result.expect("Compilation error");
833
834        let driver = AstVisitorDriver {};
835        let visitor = MelTypeChecker {};
836        let context = MelAnalysisContext::default();
837        let result = driver
838            .visit(&ast, &visitor, context)
839            .expect("Could not analyze");
840
841        let result = result.expr.expect("Could not get the analyzed expression");
842        let binary_expr = match result {
843            Expr::BinaryExpr(binary_expr) => binary_expr,
844            _ => todo!(),
845        };
846
847        assert_matches!(
848            *binary_expr,
849            BinaryExpr::<Analyzed> {
850                left: _,
851                right: _,
852                op: _,
853                location: _,
854                aug: Analyzed {
855                    tipe: Type::Integer,
856                    constant: _
857                }
858            }
859        )
860    }
861
862    #[test]
863    fn test_type_check_binary_expr_nested() {
864        let expr = "5 + (3 * 8)";
865
866        let compile_result = compile(expr);
867        let ast = compile_result.expect("Compilation error");
868
869        let driver = AstVisitorDriver {};
870        let visitor = MelTypeChecker {};
871        let context = MelAnalysisContext::default();
872        let result = driver
873            .visit(&ast, &visitor, context)
874            .expect("Could not analyze");
875
876        let result = result.expr.expect("Could not get the analyzed expression");
877        let binary_expr = match result {
878            Expr::BinaryExpr(binary_expr) => binary_expr,
879            _ => todo!(),
880        };
881
882        assert_matches!(
883            *binary_expr,
884            BinaryExpr::<Analyzed> {
885                left: _,
886                right: _,
887                op: _,
888                location: _,
889                aug: Analyzed {
890                    tipe: Type::Integer,
891                    constant: _
892                }
893            }
894        );
895    }
896
897    #[test]
898    fn test_type_check_binary_expr_error() {
899        let expr = "5 + \"testing\"";
900
901        let compile_result = compile(expr);
902        let ast = compile_result.expect("Compilation error");
903
904        let driver = AstVisitorDriver {};
905        let visitor = MelTypeChecker {};
906        let context = MelAnalysisContext::default();
907        let result = driver
908            .visit(&ast, &visitor, context)
909            .expect_err("Could analyze expression with invalid types");
910
911        assert_eq!(
912            result.location,
913            TextLocation {
914                start: 4,
915                extent: 9
916            }
917        );
918        assert_matches!(
919            *result.error,
920                AnalysisError::InvalidType(x, types::Type::String)
921            if x == vec![Integer]
922        )
923    }
924
925    #[test]
926    fn test_type_check_identifier_missing() {
927        let expr = "a";
928
929        let compile_result = compile(expr);
930        let ast = compile_result.expect("Compilation error");
931
932        let driver = AstVisitorDriver {};
933        let visitor = MelTypeChecker {};
934        let context = MelAnalysisContext::default();
935        let result = driver
936            .visit(&ast, &visitor, context)
937            .expect_err("Could analyze expression with missing identifier");
938
939        assert_eq!(
940            result.location,
941            TextLocation {
942                start: 0,
943                extent: 1
944            }
945        );
946        assert_matches!(
947            *result.error,
948            AnalysisError::UnknownIdentifier(a)
949            if a == "a"
950        )
951    }
952
953    #[test]
954    fn test_type_check_identifier_in_expression() {
955        let expr = "5 + a";
956
957        let compile_result = compile(expr);
958        let ast = compile_result.expect("Compilation error");
959
960        let driver = AstVisitorDriver {};
961        let visitor = MelTypeChecker {};
962        let mut context = MelAnalysisContext::default();
963
964        context = context.update_scopes(&context.scopes.insert("a", Integer));
965
966        let result = driver
967            .visit(&ast, &visitor, context)
968            .expect("Could not analyze expression with missing identifier");
969
970        let result = result.expr.expect("Could not get the analyzed expression");
971        let binary_expr = match result {
972            Expr::BinaryExpr(binary_expr) => binary_expr,
973            _ => todo!(),
974        };
975
976        assert_matches!(
977            *binary_expr,
978            BinaryExpr::<Analyzed> {
979                left: _,
980                right: _,
981                op: _,
982                location: _,
983                aug: Analyzed {
984                    tipe: Type::Integer,
985                    constant: _
986                }
987            }
988        );
989    }
990
991    #[test]
992    fn test_type_check_bool_to_math_operator() {
993        let expr = "false + true";
994
995        let compile_result = compile(expr);
996        let ast = compile_result.expect("Compilation error");
997
998        let driver = AstVisitorDriver {};
999        let visitor = MelTypeChecker {};
1000        let mut context = MelAnalysisContext::default();
1001
1002        context = context.update_scopes(&context.scopes.insert("a", Integer));
1003
1004        let result = driver
1005            .visit(&ast, &visitor, context)
1006            .expect_err("Could analyze expression with type error");
1007
1008        assert_eq!(
1009            result.location,
1010            TextLocation {
1011                start: 0,
1012                extent: 5
1013            }
1014        );
1015        assert_matches!(
1016            *result.error,
1017            AnalysisError::InvalidType(a, Boolean)
1018        if a == vec![Integer]
1019        )
1020    }
1021
1022    #[test]
1023    fn test_type_check_bool_to_math_operator2() {
1024        let expr = "5 + true";
1025
1026        let compile_result = compile(expr);
1027        let ast = compile_result.expect("Compilation error");
1028
1029        let driver = AstVisitorDriver {};
1030        let visitor = MelTypeChecker {};
1031        let mut context = MelAnalysisContext::default();
1032
1033        context = context.update_scopes(&context.scopes.insert("a", Integer));
1034
1035        let result = driver
1036            .visit(&ast, &visitor, context)
1037            .expect_err("Could analyze expression with type error");
1038
1039        assert_eq!(
1040            result.location,
1041            TextLocation {
1042                start: 4,
1043                extent: 4
1044            }
1045        );
1046        assert_matches!(
1047            *result.error,
1048            AnalysisError::InvalidType(a, Boolean)
1049        if a == vec![Integer]
1050        )
1051    }
1052
1053    #[test]
1054    fn test_type_check_identifier() {
1055        let expr = "a";
1056
1057        let compile_result = compile(expr);
1058        let ast = compile_result.expect("Compilation error");
1059
1060        let driver = AstVisitorDriver {};
1061        let visitor = MelTypeChecker {};
1062        let mut context = MelAnalysisContext::default();
1063
1064        context = context.update_scopes(&context.scopes.insert("a", Integer));
1065
1066        let result = driver
1067            .visit(&ast, &visitor, context)
1068            .expect("Could not analyze expression with missing identifier");
1069
1070        let result = result.expr.expect("Could not get the analyzed expression");
1071        let binary_expr = match result {
1072            Expr::Identifier(id) => id,
1073            _ => todo!(),
1074        };
1075
1076        assert_matches!(
1077            *binary_expr,
1078            Identifier::<Analyzed> {
1079                identifier: _,
1080                location: _,
1081                aug: Analyzed {
1082                    tipe: Type::Integer,
1083                    constant: _
1084                }
1085            }
1086        );
1087    }
1088
1089    #[test]
1090    fn test_type_check_function_call_integer_error() {
1091        let expr = "integer(0, 1)";
1092
1093        let compile_result = compile(expr);
1094        let ast = compile_result.expect("Compilation error");
1095
1096        let driver = AstVisitorDriver {};
1097        let visitor = MelTypeChecker {};
1098        let mut context = MelAnalysisContext::default();
1099
1100        let b = IntegerBuiltin {};
1101
1102        context = context.update_scopes(&context.scopes.insert(
1103            &b.name(),
1104            Function(
1105                b.name(),
1106                b.return_type_calculator(),
1107                b.params_type_checker(),
1108            ),
1109        ));
1110
1111        let result = driver.visit(&ast, &visitor, context).expect_err(
1112            "Could analyze call of builtin function with incorrect number of parameters",
1113        );
1114
1115        assert_eq!(
1116            result.location,
1117            TextLocation {
1118                start: 7,
1119                extent: 6
1120            }
1121        );
1122        assert_matches!(*result.error, AnalysisError::Miscount(1, 2))
1123    }
1124
1125    #[test]
1126    fn test_type_check_function_call_real_error() {
1127        let expr = "real(0, 1)";
1128
1129        let compile_result = compile(expr);
1130        let ast = compile_result.expect("Compilation error");
1131
1132        let driver = AstVisitorDriver {};
1133        let visitor = MelTypeChecker {};
1134        let mut context = MelAnalysisContext::default();
1135
1136        let b = RealBuiltin {};
1137
1138        context = context.update_scopes(&context.scopes.insert(
1139            &b.name(),
1140            Function(
1141                b.name(),
1142                b.return_type_calculator(),
1143                b.params_type_checker(),
1144            ),
1145        ));
1146
1147        let result = driver.visit(&ast, &visitor, context).expect_err(
1148            "Could analyze call of builtin function with incorrect number of parameters",
1149        );
1150
1151        assert_eq!(
1152            result.location,
1153            TextLocation {
1154                start: 4,
1155                extent: 6
1156            }
1157        );
1158        assert_matches!(*result.error, AnalysisError::Miscount(1, 2))
1159    }
1160
1161    #[test]
1162    fn test_type_check_function_call_string_error() {
1163        let expr = "string(0, 1)";
1164
1165        let compile_result = compile(expr);
1166        let ast = compile_result.expect("Compilation error");
1167
1168        let driver = AstVisitorDriver {};
1169        let visitor = MelTypeChecker {};
1170        let mut context = MelAnalysisContext::default();
1171
1172        let b = StringBuiltin {};
1173
1174        context = context.update_scopes(&context.scopes.insert(
1175            &b.name(),
1176            Function(
1177                b.name(),
1178                b.return_type_calculator(),
1179                b.params_type_checker(),
1180            ),
1181        ));
1182
1183        let result = driver.visit(&ast, &visitor, context).expect_err(
1184            "Could analyze call of builtin function with incorrect number of parameters",
1185        );
1186
1187        assert_eq!(
1188            result.location,
1189            TextLocation {
1190                start: 6,
1191                extent: 6
1192            }
1193        );
1194        assert_matches!(*result.error, AnalysisError::Miscount(1, 2))
1195    }
1196
1197    #[test]
1198    fn test_type_check_function_call() {
1199        let expr = "testing(5)";
1200
1201        let compile_result = compile(expr);
1202        let ast = compile_result.expect("Compilation error");
1203
1204        let driver = AstVisitorDriver {};
1205        let visitor = MelTypeChecker {};
1206        let mut context = MelAnalysisContext::default();
1207
1208        context = context.update_scopes(&context.scopes.insert(
1209            "testing",
1210            Function(
1211                "testing".to_string(),
1212                || Type::Integer,
1213                || {
1214                    Box::new(SimpleParamTypeChecker {
1215                        p: types::ParameterTypesList {
1216                            params: vec![Type::Integer],
1217                        },
1218                    })
1219                },
1220            ),
1221        ));
1222
1223        let result = driver
1224            .visit(&ast, &visitor, context)
1225            .expect("Could not analyze expression with missing identifier");
1226
1227        let result = result.expr.expect("Could not get the analyzed expression");
1228        let binary_expr = match result {
1229            Expr::FunctionCall(id) => id,
1230            _ => todo!(),
1231        };
1232
1233        assert_matches!(
1234            *binary_expr,
1235            FunctionCall::<Analyzed> {
1236                callee: _,
1237                location: _,
1238                arguments: _,
1239                aug: Analyzed {
1240                    tipe: Integer,
1241                    constant: _
1242                }
1243            }
1244        );
1245    }
1246
1247    #[test]
1248    fn test_type_check_function_call_error() {
1249        let expr = "use_me(5)";
1250
1251        let compile_result = compile(expr);
1252        let ast = compile_result.expect("Compilation error");
1253
1254        let driver = AstVisitorDriver {};
1255        let visitor = MelTypeChecker {};
1256        let mut context = MelAnalysisContext::default();
1257
1258        context = context.update_scopes(&context.scopes.insert(
1259            "use",
1260            Function(
1261                "testing".to_string(),
1262                || Type::Integer,
1263                || {
1264                    Box::new(SimpleParamTypeChecker {
1265                        p: types::ParameterTypesList {
1266                            params: vec![Type::Integer],
1267                        },
1268                    })
1269                },
1270            ),
1271        ));
1272
1273        let result = driver
1274            .visit(&ast, &visitor, context)
1275            .expect_err("Could analyze expression with missing identifier");
1276
1277        assert_matches!(
1278            *result.error, AnalysisError::UnknownIdentifier(a)
1279            if a == "use_me"
1280        )
1281    }
1282
1283    #[test]
1284    fn test_type_check_function_call_error_wrong_argument_count() {
1285        let expr = "use(5)";
1286
1287        let compile_result = compile(expr);
1288        let ast = compile_result.expect("Compilation error");
1289
1290        let driver = AstVisitorDriver {};
1291        let visitor = MelTypeChecker {};
1292        let mut context = MelAnalysisContext::default();
1293
1294        context = context.update_scopes(&context.scopes.insert(
1295            "use",
1296            Function(
1297                "use".to_string(),
1298                || Type::Integer,
1299                || {
1300                    Box::new(SimpleParamTypeChecker {
1301                        p: types::ParameterTypesList {
1302                            params: vec![Type::Integer, Type::String],
1303                        },
1304                    })
1305                },
1306            ),
1307        ));
1308
1309        let result = driver
1310            .visit(&ast, &visitor, context)
1311            .expect_err("Could analyze expression with miscounted params/args");
1312
1313        assert_matches!(*result.error, AnalysisError::Miscount(2, 1))
1314    }
1315
1316    #[test]
1317    fn test_type_check_function_call_in_expression() {
1318        let expr = "use_me(5)";
1319
1320        let compile_result = compile(expr);
1321        let ast = compile_result.expect("Compilation error");
1322
1323        let driver = AstVisitorDriver {};
1324        let visitor = MelTypeChecker {};
1325        let mut context = MelAnalysisContext::default();
1326
1327        context = context.update_scopes(&context.scopes.insert(
1328            "use_me",
1329            Function(
1330                "use_me".to_string(),
1331                || Type::Integer,
1332                || {
1333                    Box::new(SimpleParamTypeChecker {
1334                        p: types::ParameterTypesList {
1335                            params: vec![Type::Integer],
1336                        },
1337                    })
1338                },
1339            ),
1340        ));
1341
1342        let result = driver
1343            .visit(&ast, &visitor, context)
1344            .expect("Could not analyze expression");
1345
1346        let result = result.expr.expect("Could not get the analyzed expression");
1347        let binary_expr = match result {
1348            Expr::FunctionCall(id) => id,
1349            _ => todo!(),
1350        };
1351
1352        assert_matches!(
1353            *binary_expr,
1354            FunctionCall::<Analyzed> {
1355                callee: _,
1356                arguments: _,
1357                location: _,
1358                aug: Analyzed {
1359                    tipe: Integer,
1360                    constant: _
1361                }
1362            }
1363        );
1364    }
1365
1366    #[test]
1367    fn test_type_check_function_call_in_expression2() {
1368        let expr = "use_me(5, \"testing\")";
1369
1370        let compile_result = compile(expr);
1371        let ast = compile_result.expect("Compilation error");
1372
1373        let driver = AstVisitorDriver {};
1374        let visitor = MelTypeChecker {};
1375        let mut context = MelAnalysisContext::default();
1376
1377        context = context.update_scopes(&context.scopes.insert(
1378            "use_me",
1379            Function(
1380                "use_me".to_string(),
1381                || Type::Integer,
1382                || {
1383                    Box::new(SimpleParamTypeChecker {
1384                        p: types::ParameterTypesList {
1385                            params: vec![Type::Integer, Type::String],
1386                        },
1387                    })
1388                },
1389            ),
1390        ));
1391
1392        let result = driver
1393            .visit(&ast, &visitor, context)
1394            .expect("Could not analyze expression");
1395
1396        let result = result.expr.expect("Could not get the analyzed expression");
1397        let binary_expr = match result {
1398            Expr::FunctionCall(id) => id,
1399            _ => todo!(),
1400        };
1401
1402        assert_matches!(
1403            *binary_expr,
1404            FunctionCall::<Analyzed> {
1405                callee: _,
1406                arguments: _,
1407                location: _,
1408                aug: Analyzed {
1409                    tipe: Integer,
1410                    constant: _
1411                }
1412            }
1413        );
1414    }
1415
1416    #[test]
1417    fn test_type_check_function_call_in_expression3() {
1418        let expr = "use_me(5, \"testing\")";
1419
1420        let compile_result = compile(expr);
1421        let ast = compile_result.expect("Compilation error");
1422
1423        let driver = AstVisitorDriver {};
1424        let visitor = MelTypeChecker {};
1425        let mut context = MelAnalysisContext::default();
1426
1427        context = context.update_scopes(&context.scopes.insert(
1428            "use_me",
1429            Function(
1430                "use_me".to_string(),
1431                || Type::Integer,
1432                || {
1433                    Box::new(SimpleParamTypeChecker {
1434                        p: types::ParameterTypesList {
1435                            params: vec![Type::Integer, Type::Integer],
1436                        },
1437                    })
1438                },
1439            ),
1440        ));
1441
1442        let result = driver
1443            .visit(&ast, &visitor, context)
1444            .expect_err("Could analyze expression with type error");
1445
1446        assert_eq!(
1447            result.location,
1448            TextLocation {
1449                start: 6,
1450                extent: 14
1451            }
1452        );
1453        assert_matches!(
1454            *result.error,
1455            AnalysisError::Mismatch(Integer, Type::String),
1456        )
1457    }
1458
1459    #[test]
1460    fn test_type_check_function_call_in_binary_expression() {
1461        let expr = "use_me(5) + 10";
1462
1463        let compile_result = compile(expr);
1464        let ast = compile_result.expect("Compilation error");
1465
1466        let driver = AstVisitorDriver {};
1467        let visitor = MelTypeChecker {};
1468        let mut context = MelAnalysisContext::default();
1469
1470        context = context.update_scopes(&context.scopes.insert(
1471            "use_me",
1472            Function(
1473                "use_me".to_string(),
1474                || Type::Integer,
1475                || {
1476                    Box::new(SimpleParamTypeChecker {
1477                        p: types::ParameterTypesList {
1478                            params: vec![Type::Integer],
1479                        },
1480                    })
1481                },
1482            ),
1483        ));
1484
1485        let result = driver
1486            .visit(&ast, &visitor, context)
1487            .expect("Could not analyze expression");
1488
1489        let result = result.expr.expect("Could not get the analyzed expression");
1490        let binary_expr = match result {
1491            Expr::BinaryExpr(bin) => bin,
1492            _ => todo!(),
1493        };
1494
1495        assert_matches!(
1496            *binary_expr,
1497            BinaryExpr::<Analyzed> {
1498                left: _,
1499                right: _,
1500                op: _,
1501                location: _,
1502
1503                aug: Analyzed {
1504                    tipe: Integer,
1505                    constant: _
1506                }
1507            }
1508        );
1509    }
1510
1511    #[test]
1512    fn test_type_check_function_call_in_expression_mismatch() {
1513        let expr = "5 + (use_me(5) + 10)";
1514
1515        let compile_result = compile(expr);
1516        let ast = compile_result.expect("Compilation error");
1517
1518        let driver = AstVisitorDriver {};
1519        let visitor = MelTypeChecker {};
1520        let mut context = MelAnalysisContext::default();
1521
1522        context = context.update_scopes(&context.scopes.insert(
1523            "use_me",
1524            Function(
1525                "use_me".to_string(),
1526                || Type::Boolean,
1527                || {
1528                    Box::new(SimpleParamTypeChecker {
1529                        p: types::ParameterTypesList {
1530                            params: vec![Type::Integer],
1531                        },
1532                    })
1533                },
1534            ),
1535        ));
1536
1537        let result = driver
1538            .visit(&ast, &visitor, context)
1539            .expect_err("Could analyze expression with type error");
1540
1541        assert_eq!(
1542            result.location,
1543            TextLocation {
1544                start: 5,
1545                extent: 9
1546            }
1547        );
1548        assert_matches!(
1549            *result.error, AnalysisError::InvalidType(i, Boolean)
1550            if i == vec![Integer]
1551        )
1552    }
1553
1554    #[test]
1555    fn test_type_check_comparison_expr() {
1556        let expr = "5 < 4";
1557
1558        let compile_result = compile(expr);
1559        let ast = compile_result.expect("Compilation error");
1560
1561        let driver = AstVisitorDriver {};
1562        let visitor = MelTypeChecker {};
1563        let context = MelAnalysisContext::default();
1564        let result = driver
1565            .visit(&ast, &visitor, context)
1566            .expect("Could not analyze");
1567
1568        let result = result.expr.expect("Could not get the analyzed expression");
1569        let binary_expr = match result {
1570            Expr::BinaryExpr(id) => id,
1571            _ => todo!(),
1572        };
1573
1574        assert_matches!(
1575            *binary_expr,
1576            BinaryExpr::<Analyzed> {
1577                left: _,
1578                right: _,
1579                op: _,
1580                location: _,
1581                aug: Analyzed {
1582                    tipe: Type::Boolean,
1583                    constant: None,
1584                }
1585            }
1586        );
1587    }
1588
1589    #[test]
1590    fn test_type_check_comparison_expr_mismatch() {
1591        let expr = "(5 < 4) + 5";
1592
1593        let compile_result = compile(expr);
1594        let ast = compile_result.expect("Compilation error");
1595
1596        let driver = AstVisitorDriver {};
1597        let visitor = MelTypeChecker {};
1598        let context = MelAnalysisContext::default();
1599
1600        let result = driver
1601            .visit(&ast, &visitor, context)
1602            .expect_err("Could analyze expression with type error");
1603
1604        assert_eq!(
1605            result.location,
1606            TextLocation {
1607                start: 1,
1608                extent: 5
1609            }
1610        );
1611        assert_matches!(
1612            *result.error,
1613            AnalysisError::InvalidType(i, Boolean) if i == vec![Integer]
1614        )
1615    }
1616
1617    #[test]
1618    fn test_type_check_comparison_expr_ipliteral() {
1619        let expr = "8.8.8.8 ipmatch 10.1.9.2";
1620
1621        let compile_result = compile(expr);
1622        let ast = compile_result.expect("Compilation error");
1623
1624        let driver = AstVisitorDriver {};
1625        let visitor = MelTypeChecker {};
1626        let context = MelAnalysisContext::default();
1627
1628        let result = driver
1629            .visit(&ast, &visitor, context)
1630            .expect("Could not analyze");
1631
1632        let result = result.expr.expect("Could not get the analyzed expression");
1633        let binary_expr = match result {
1634            Expr::BinaryExpr(id) => id,
1635            _ => todo!(),
1636        };
1637
1638        assert_matches!(
1639            *binary_expr,
1640            BinaryExpr::<Analyzed> {
1641                left: _,
1642                right: _,
1643                op: _,
1644                location: _,
1645                aug: Analyzed {
1646                    tipe: Type::Boolean,
1647                    constant: None,
1648                }
1649            }
1650        );
1651    }
1652
1653    #[test]
1654    fn test_type_check_comparison_expr_ipliteral2() {
1655        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch 2001:0db8:85a3:0000:0000:8a2e:0370:7334";
1656
1657        let compile_result = compile(expr);
1658        let ast = compile_result.expect("Compilation error");
1659
1660        let driver = AstVisitorDriver {};
1661        let visitor = MelTypeChecker {};
1662        let context = MelAnalysisContext::default();
1663
1664        let result = driver
1665            .visit(&ast, &visitor, context)
1666            .expect("Could not analyze");
1667
1668        let result = result.expr.expect("Could not get the analyzed expression");
1669        let binary_expr = match result {
1670            Expr::BinaryExpr(id) => id,
1671            _ => todo!(),
1672        };
1673
1674        assert_matches!(
1675            *binary_expr,
1676            BinaryExpr::<Analyzed> {
1677                left: _,
1678                right: _,
1679                op: _,
1680                location: _,
1681                aug: Analyzed {
1682                    tipe: Type::Boolean,
1683                    constant: None,
1684                }
1685            }
1686        );
1687    }
1688
1689    #[test]
1690    fn test_type_check_comparison_expr_ipliteral3() {
1691        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch 8.8.8.8";
1692
1693        let compile_result = compile(expr);
1694        let ast = compile_result.expect("Compilation error");
1695
1696        let driver = AstVisitorDriver {};
1697        let visitor = MelTypeChecker {};
1698        let context = MelAnalysisContext::default();
1699
1700        let result = driver
1701            .visit(&ast, &visitor, context)
1702            .expect("Could not analyze");
1703
1704        let result = result.expr.expect("Could not get the analyzed expression");
1705        let binary_expr = match result {
1706            Expr::BinaryExpr(id) => id,
1707            _ => todo!(),
1708        };
1709
1710        assert_matches!(
1711            *binary_expr,
1712            BinaryExpr::<Analyzed> {
1713                left: _,
1714                right: _,
1715                op: _,
1716                location: _,
1717                aug: Analyzed {
1718                    tipe: Type::Boolean,
1719                    constant: None,
1720                }
1721            }
1722        );
1723    }
1724
1725    #[test]
1726    fn test_type_check_comparison_expr_ipliteral4() {
1727        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch a";
1728
1729        let compile_result = compile(expr);
1730        let ast = compile_result.expect("Compilation error");
1731
1732        let driver = AstVisitorDriver {};
1733        let visitor = MelTypeChecker {};
1734        let mut context = MelAnalysisContext::default();
1735
1736        context = context.update_scopes(&context.scopes.insert("a", IPAddress));
1737
1738        let result = driver
1739            .visit(&ast, &visitor, context)
1740            .expect("Could not analyze");
1741
1742        let result = result.expr.expect("Could not get the analyzed expression");
1743        let binary_expr = match result {
1744            Expr::BinaryExpr(id) => id,
1745            _ => todo!(),
1746        };
1747
1748        assert_matches!(
1749            *binary_expr,
1750            BinaryExpr::<Analyzed> {
1751                left: _,
1752                right: _,
1753                op: _,
1754                location: _,
1755                aug: Analyzed {
1756                    tipe: Type::Boolean,
1757                    constant: None,
1758                }
1759            }
1760        );
1761    }
1762
1763    #[test]
1764    fn test_type_check_comparison_expr_ip() {
1765        let expr = "a ipmatch b";
1766
1767        let compile_result = compile(expr);
1768        let ast = compile_result.expect("Compilation error");
1769
1770        let driver = AstVisitorDriver {};
1771        let visitor = MelTypeChecker {};
1772        let mut context = MelAnalysisContext::default();
1773
1774        context = context.update_scopes(&context.scopes.insert("a", IPAddress));
1775
1776        context = context.update_scopes(&context.scopes.insert("b", IPAddress));
1777
1778        let result = driver
1779            .visit(&ast, &visitor, context)
1780            .expect("Could not analyze");
1781
1782        let result = result.expr.expect("Could not get the analyzed expression");
1783        let binary_expr = match result {
1784            Expr::BinaryExpr(id) => id,
1785            _ => todo!(),
1786        };
1787
1788        assert_matches!(
1789            *binary_expr,
1790            BinaryExpr::<Analyzed> {
1791                left: _,
1792                right: _,
1793                op: _,
1794                location: _,
1795                aug: Analyzed {
1796                    tipe: Type::Boolean,
1797                    constant: None,
1798                }
1799            }
1800        );
1801    }
1802
1803    #[test]
1804    fn test_type_check_comparison_expr_ipliteral_mismatch() {
1805        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch 8";
1806
1807        let compile_result = compile(expr);
1808        let ast = compile_result.expect("Compilation error");
1809
1810        let driver = AstVisitorDriver {};
1811        let visitor = MelTypeChecker {};
1812        let context = MelAnalysisContext::default();
1813
1814        let result = driver
1815            .visit(&ast, &visitor, context)
1816            .expect_err("Could analyze expression with type error");
1817
1818        assert_eq!(
1819            result.location,
1820            TextLocation {
1821                start: 48,
1822                extent: 1
1823            }
1824        );
1825        assert_matches!(
1826            *result.error,
1827                AnalysisError::InvalidType(i, Integer)
1828            if i == vec![IPAddress]
1829        )
1830    }
1831
1832    #[test]
1833    fn test_type_check_comparison_expr_ipliteral_mismatch2() {
1834        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch a";
1835
1836        let compile_result = compile(expr);
1837        let ast = compile_result.expect("Compilation error");
1838
1839        let driver = AstVisitorDriver {};
1840        let visitor = MelTypeChecker {};
1841        let mut context = MelAnalysisContext::default();
1842
1843        context = context.update_scopes(&context.scopes.insert("a", Type::String));
1844
1845        let result = driver
1846            .visit(&ast, &visitor, context)
1847            .expect_err("Could analyze expression with type error");
1848
1849        assert_eq!(
1850            result.location,
1851            TextLocation {
1852                start: 48,
1853                extent: 1
1854            }
1855        );
1856        assert_matches!(
1857            *result.error,
1858                AnalysisError::InvalidType(i, Type::String)
1859            if i == vec![IPAddress]
1860        )
1861    }
1862
1863    #[test]
1864    fn test_type_check_comparison_expr_ip_mismatch() {
1865        let expr = "a ipmatch b";
1866
1867        let compile_result = compile(expr);
1868        let ast = compile_result.expect("Compilation error");
1869
1870        let driver = AstVisitorDriver {};
1871        let visitor = MelTypeChecker {};
1872        let mut context = MelAnalysisContext::default();
1873
1874        context = context.update_scopes(&context.scopes.insert("a", IPAddress));
1875
1876        context = context.update_scopes(&context.scopes.insert("b", Type::String));
1877
1878        let result = driver
1879            .visit(&ast, &visitor, context)
1880            .expect_err("Could analyze expression with type error");
1881
1882        assert_eq!(
1883            result.location,
1884            TextLocation {
1885                start: 10,
1886                extent: 1
1887            }
1888        );
1889        assert_matches!(
1890            *result.error,
1891                AnalysisError::InvalidType(i, Type::String)
1892            if i == vec![IPAddress]
1893        )
1894    }
1895
1896    #[test]
1897    fn test_basic_scope_of_types_for_var_struct() {
1898        let sc = minimal_core_variable_types();
1899        let var = sc
1900            .lookup(MEL_USER_SET_VARIABLE_STRUCT_NAME)
1901            .expect("Could not get type of var from scope that should contain it.");
1902        assert_matches!(var, Type::Struct(s) if s.fields().count() == 0);
1903    }
1904
1905    #[test]
1906    fn test_basic_scope_of_types_for_var_struct_updated() {
1907        let sc = minimal_core_variable_types();
1908        let var = sc
1909            .lookup(MEL_USER_SET_VARIABLE_STRUCT_NAME)
1910            .expect("Could not get type of var from scope that should contain it.");
1911
1912        let new_var = match var {
1913            Type::Struct(mut s) => {
1914                s.insert_field("new_user_variable", Type::String);
1915                s
1916            }
1917            _ => panic!(),
1918        };
1919
1920        let sc = sc.insert(MEL_USER_SET_VARIABLE_STRUCT_NAME, Type::Struct(new_var));
1921        let var = sc
1922            .lookup(MEL_USER_SET_VARIABLE_STRUCT_NAME)
1923            .expect("Could not get type of var from scope that should contain it.");
1924
1925        assert_matches!(var, Type::Struct(s) if s.fields().count() == 1);
1926    }
1927
1928    #[test]
1929    fn test_basic_scope_of_types_for_req_struct() {
1930        let sc = minimal_core_variable_types();
1931        let req = sc
1932            .lookup("req")
1933            .expect("Could not get type of req from scope that should contain it.");
1934        assert_matches!(req, Type::Struct(_));
1935    }
1936}
1937
1938pub struct ConstEvaluator {}
1939
1940impl ConstEvaluator {
1941    pub fn const_evaluatable(op: &ast::BinaryInfixOperator) -> bool {
1942        matches!(op, ast::BinaryInfixOperator::Comparison(IP))
1943            || !matches!(op, ast::BinaryInfixOperator::Comparison(_))
1944    }
1945
1946    fn evaluate_binary_expr(
1947        left: &CompiledConstant,
1948        left_type: Type,
1949        right: &CompiledConstant,
1950        right_type: Type,
1951        op: ast::BinaryInfixOperator,
1952    ) -> Result<CompiledConstant, Box<AnalysisError>> {
1953        match op {
1954            ast::BinaryInfixOperator::Comparison(IP) => {
1955                let left = if let CompiledConstant::IPAddress(l) = left {
1956                    l
1957                } else {
1958                    return Err(AnalysisError::Mismatch(Type::IPAddress, left_type).into());
1959                };
1960                let right = if let CompiledConstant::IPAddress(r) = right {
1961                    r
1962                } else {
1963                    return Err(AnalysisError::Mismatch(Type::IPAddress, right_type).into());
1964                };
1965                Ok(CompiledConstant::Boolean(left == right))
1966            }
1967            ast::BinaryInfixOperator::Comparison(_) => {
1968                Err(AnalysisError::OptimizationNotSupported("Comparison operator".into()).into())
1969            }
1970            ast::BinaryInfixOperator::Logic(logic_operator) => {
1971                let left = if let CompiledConstant::Boolean(l) = left {
1972                    l
1973                } else {
1974                    return Err(AnalysisError::Mismatch(Type::Boolean, left_type).into());
1975                };
1976                let right = if let CompiledConstant::Boolean(r) = right {
1977                    r
1978                } else {
1979                    return Err(AnalysisError::Mismatch(Type::Boolean, right_type).into());
1980                };
1981                match logic_operator {
1982                    ast::LogicOperator::And => Ok(CompiledConstant::Boolean(*left && *right)),
1983                    ast::LogicOperator::Or => Ok(CompiledConstant::Boolean(*left || *right)),
1984                }
1985            }
1986            ast::BinaryInfixOperator::Math(math_operator) => {
1987                let left = if let CompiledConstant::Integer(l) = left {
1988                    l
1989                } else {
1990                    return Err(AnalysisError::Mismatch(Type::Integer, left_type).into());
1991                };
1992                let right = if let CompiledConstant::Integer(r) = right {
1993                    r
1994                } else {
1995                    return Err(AnalysisError::Mismatch(Type::Integer, right_type).into());
1996                };
1997                match math_operator {
1998                    ast::MathOperator::Plus => Ok(CompiledConstant::Integer(left + right)),
1999                    ast::MathOperator::Minus => Ok(CompiledConstant::Integer(left - right)),
2000                    ast::MathOperator::Multiply => Ok(CompiledConstant::Integer(left * right)),
2001                    ast::MathOperator::Divide => Ok(CompiledConstant::Integer(left / right)),
2002                    ast::MathOperator::Modulo => Ok(CompiledConstant::Integer(left % right)),
2003                }
2004            }
2005            ast::BinaryInfixOperator::Concat(_) => {
2006                let left = if let CompiledConstant::String(l) = left {
2007                    l
2008                } else {
2009                    return Err(AnalysisError::Mismatch(Type::String, left_type).into());
2010                };
2011                let right = if let CompiledConstant::String(r) = right {
2012                    r
2013                } else {
2014                    return Err(AnalysisError::Mismatch(Type::String, right_type).into());
2015                };
2016                Ok(CompiledConstant::String(left.to_owned() + right))
2017            }
2018            ast::BinaryInfixOperator::MemberAccess(c) => Err(AnalysisError::AssertionFailure(
2019                AnalysisAssertions::InvalidOperator(
2020                    c.to_string(),
2021                    "evaluate_binary_expr".to_string(),
2022                ),
2023            )
2024            .into()),
2025        }
2026    }
2027}
2028pub struct MelOptimizer {}
2029
2030impl AstVisitor<MelAnalysisContext, (), AnalysisLocatableError> for MelOptimizer {
2031    fn visit_function_call(
2032        &self,
2033        _ast: &FunctionCall<()>,
2034        context: MelAnalysisContext,
2035        _driver: &AstVisitorDriver,
2036    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2037        Ok(context.clone())
2038    }
2039
2040    fn visit_identifier(
2041        &self,
2042        _ast: &Identifier<()>,
2043        context: MelAnalysisContext,
2044        _driver: &AstVisitorDriver,
2045    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2046        Ok(context.clone())
2047    }
2048
2049    fn visit_argument_list(
2050        &self,
2051        _ast: &ArgumentList<()>,
2052        context: MelAnalysisContext,
2053        _driver: &AstVisitorDriver,
2054    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2055        Ok(context.clone())
2056    }
2057
2058    fn visit_argument(
2059        &self,
2060        _: &Argument<()>,
2061        context: MelAnalysisContext,
2062        _: &AstVisitorDriver,
2063    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2064        Ok(context.clone())
2065    }
2066
2067    fn visit_binary_expr(
2068        &self,
2069        ast: &BinaryExpr<()>,
2070        context: MelAnalysisContext,
2071        driver: &AstVisitorDriver,
2072    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2073        let expr = context.expr.as_ref().ok_or(AnalysisLocatableError {
2074            error: AnalysisError::PreconditionFailure(AnalysisPreconditions::ContextMissingExpr(
2075                "visit_binary_expr".into(),
2076            ))
2077            .into(),
2078            location: ast.location.clone(),
2079        })?;
2080
2081        let analyzed_binary_expr = match expr {
2082            Expr::BinaryExpr(binary_expr) => (**binary_expr).clone(),
2083            _ => todo!(),
2084        };
2085
2086        let left = driver
2087            .visit(
2088                &ast.left,
2089                self,
2090                context.update_expr(analyzed_binary_expr.left),
2091            )?
2092            .expr
2093            .ok_or(AnalysisLocatableError {
2094                error: AnalysisError::AssertionFailure(ContextMissingExpr(
2095                    "visit_binary_expr".into(),
2096                ))
2097                .into(),
2098                location: ast.location.clone(),
2099            })?;
2100
2101        let right = driver
2102            .visit(
2103                &ast.right,
2104                self,
2105                context.update_expr(analyzed_binary_expr.right),
2106            )?
2107            .expr
2108            .ok_or(AnalysisLocatableError {
2109                error: AnalysisError::AssertionFailure(ContextMissingExpr(
2110                    "visit_binary_expr".into(),
2111                ))
2112                .into(),
2113                location: ast.location.clone(),
2114            })?;
2115
2116        let constant = match (left.constant(), right.constant()) {
2117            (Some(cl), Some(cr)) => {
2118                if ConstEvaluator::const_evaluatable(&ast.op) {
2119                    Some(
2120                        ConstEvaluator::evaluate_binary_expr(
2121                            &cl,
2122                            left.tipe(),
2123                            &cr,
2124                            right.tipe(),
2125                            ast.op.clone(),
2126                        )
2127                        .map_err(|e| AnalysisLocatableError {
2128                            error: e,
2129                            location: ast.location.clone(),
2130                        })?,
2131                    )
2132                } else {
2133                    None
2134                }
2135            }
2136            _ => None,
2137        };
2138
2139        Ok(
2140            context.update_expr(Expr::BinaryExpr(Arc::new(ast::BinaryExpr {
2141                left,
2142                right,
2143                op: ast.op.clone(),
2144                location: ast.location.clone(),
2145                aug: Analyzed {
2146                    tipe: expr.tipe(),
2147                    constant,
2148                },
2149            }))),
2150        )
2151    }
2152
2153    fn visit_literal(
2154        &self,
2155        _ast: (&ast::Literal, &TextLocation, &()),
2156        context: MelAnalysisContext,
2157        _driver: &AstVisitorDriver,
2158    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2159        match _ast {
2160            (b @ ast::Literal::Boolean(v), location, _) => Ok(context.update_expr(Expr::Literal(
2161                b.clone(),
2162                location.clone(),
2163                Analyzed {
2164                    tipe: Type::Boolean,
2165                    constant: Some(CompiledConstant::Boolean(*v == BooleanLiteral::True)),
2166                },
2167            ))),
2168            (n @ ast::Literal::Number(NumberLiteral { literal: v }), location, _) => Ok(context
2169                .update_expr(Expr::Literal(
2170                    n.clone(),
2171                    location.clone(),
2172                    Analyzed {
2173                        tipe: Type::Integer,
2174                        constant: Some(CompiledConstant::Integer(*v as i64)),
2175                    },
2176                ))),
2177            (s @ ast::Literal::String(StringLiteral { literal: v }), location, _) => Ok(context
2178                .update_expr(Expr::Literal(
2179                    s.clone(),
2180                    location.clone(),
2181                    Analyzed {
2182                        tipe: Type::String,
2183                        constant: Some(CompiledConstant::String(v.clone())),
2184                    },
2185                ))),
2186            (r @ ast::Literal::Regex(_), location, _) => Ok(context.update_expr(Expr::Literal(
2187                r.clone(),
2188                location.clone(),
2189                Analyzed {
2190                    tipe: Type::Regex,
2191                    constant: None,
2192                },
2193            ))),
2194            (ip @ ast::Literal::IPAddress(IPAddressLiteral { literal: ipl }), location, _) => {
2195                Ok(context.update_expr(Expr::Literal(
2196                    ip.clone(),
2197                    location.clone(),
2198                    Analyzed {
2199                        tipe: Type::IPAddress,
2200                        constant: Some(CompiledConstant::IPAddress(*ipl)),
2201                    },
2202                )))
2203            }
2204        }
2205    }
2206
2207    fn visit_ternary_expr(
2208        &self,
2209        ast: &ast::TernaryExpr<()>,
2210        context: MelAnalysisContext,
2211        driver: &AstVisitorDriver,
2212    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2213        let expr = context.expr.as_ref().ok_or(AnalysisLocatableError {
2214            error: AnalysisError::PreconditionFailure(AnalysisPreconditions::ContextMissingExpr(
2215                "visit_ternary_expr".into(),
2216            ))
2217            .into(),
2218            location: ast.location.clone(),
2219        })?;
2220
2221        let analyzed_binary_expr = match expr {
2222            Expr::TernaryExpr(ternary_expr) => (**ternary_expr).clone(),
2223            _ => todo!(),
2224        };
2225
2226        let condition = driver
2227            .visit(
2228                &ast.condition,
2229                self,
2230                context.update_expr(analyzed_binary_expr.condition),
2231            )?
2232            .expr
2233            .ok_or(AnalysisLocatableError {
2234                error: AnalysisError::AssertionFailure(ContextMissingExpr(
2235                    "visit_ternary_expr".into(),
2236                ))
2237                .into(),
2238                location: ast.location.clone(),
2239            })?;
2240        let yes = driver
2241            .visit(
2242                &ast.yes,
2243                self,
2244                context.update_expr(analyzed_binary_expr.yes),
2245            )?
2246            .expr
2247            .ok_or(AnalysisLocatableError {
2248                error: AnalysisError::AssertionFailure(ContextMissingExpr(
2249                    "visit_ternary_expr".into(),
2250                ))
2251                .into(),
2252                location: ast.location.clone(),
2253            })?;
2254
2255        let no = driver
2256            .visit(&ast.no, self, context.update_expr(analyzed_binary_expr.no))?
2257            .expr
2258            .ok_or(AnalysisLocatableError {
2259                error: AnalysisError::AssertionFailure(ContextMissingExpr(
2260                    "visit_ternary_expr".into(),
2261                ))
2262                .into(),
2263                location: ast.location.clone(),
2264            })?;
2265
2266        let condition_c = match condition.constant() {
2267            Some(condition_c) => condition_c,
2268            None => {
2269                return Ok(
2270                    context.update_expr(Expr::TernaryExpr(Arc::new(ast::TernaryExpr {
2271                        condition,
2272                        yes,
2273                        no,
2274                        location: ast.location.clone(),
2275                        aug: Analyzed {
2276                            tipe: expr.tipe(),
2277                            constant: None,
2278                        },
2279                    }))),
2280                );
2281            }
2282        };
2283
2284        let condition_c = match condition_c {
2285            CompiledConstant::Boolean(b) => b,
2286            e => {
2287                return Err(AnalysisLocatableError {
2288                    error: AnalysisError::AssertionFailure(
2289                        AnalysisAssertions::ContextWrongExprType(
2290                            "Boolean".to_string(),
2291                            e.to_string(),
2292                            "visit_ternary_expr".to_string(),
2293                        ),
2294                    )
2295                    .into(),
2296                    location: ast.location.clone(),
2297                });
2298            }
2299        };
2300
2301        let constant_result = if condition_c { &yes } else { &no }.constant();
2302
2303        Ok(
2304            context.update_expr(Expr::TernaryExpr(Arc::new(ast::TernaryExpr {
2305                condition,
2306                yes,
2307                no,
2308                location: ast.location.clone(),
2309                aug: Analyzed {
2310                    tipe: expr.tipe(),
2311                    constant: constant_result,
2312                },
2313            }))),
2314        )
2315    }
2316
2317    fn visit_member_access_expr(
2318        &self,
2319        _ast: &ast::MemberAccessExpression<()>,
2320        context: MelAnalysisContext,
2321        _driver: &AstVisitorDriver,
2322    ) -> AstVisitorResult<MelAnalysisContext, AnalysisLocatableError> {
2323        Ok(context.clone())
2324    }
2325}
2326
2327#[cfg(test)]
2328mod optimizer_tests {
2329    use crate::mel::{
2330        analysis::{
2331            Analyzed, CompiledConstant, MelAnalysisContext, MelOptimizer, MelTypeChecker, ast::Expr,
2332        },
2333        ast::{AstVisitorDriver, BinaryExpr},
2334        compiler::compile,
2335        types::Type,
2336    };
2337    use std::assert_matches;
2338
2339    #[test]
2340    fn test_optimize_binary_expr() {
2341        let expr = "5 + 3";
2342
2343        let compile_result = compile(expr);
2344        let ast = compile_result.expect("Compilation error");
2345
2346        let driver = AstVisitorDriver {};
2347        let visitor = MelTypeChecker {};
2348        let context = MelAnalysisContext::default();
2349        let result = driver
2350            .visit(&ast, &visitor, context)
2351            .expect("Could not analyze");
2352
2353        let driver = AstVisitorDriver {};
2354        let visitor = MelOptimizer {};
2355        let result = driver
2356            .visit(&ast, &visitor, result)
2357            .expect("Could not analyze");
2358
2359        let result = result.expr.expect("Could not get the analyzed expression");
2360        let binary_expr = match result {
2361            Expr::BinaryExpr(id) => id,
2362            _ => todo!(),
2363        };
2364
2365        assert_matches!(
2366            *binary_expr,
2367            BinaryExpr::<Analyzed> {
2368                left: _,
2369                right: _,
2370                op: _,
2371                location: _,
2372                aug: Analyzed {
2373                    tipe: Type::Integer,
2374                    constant: Some(CompiledConstant::Integer(8)),
2375                }
2376            }
2377        );
2378    }
2379
2380    #[test]
2381    fn test_optimize_nested_binary_expr() {
2382        let expr = "5 + 3 + (8 * 11)";
2383
2384        let compile_result = compile(expr);
2385        let ast = compile_result.expect("Compilation error");
2386
2387        let driver = AstVisitorDriver {};
2388        let visitor = MelTypeChecker {};
2389        let context = MelAnalysisContext::default();
2390        let result = driver
2391            .visit(&ast, &visitor, context)
2392            .expect("Could not analyze");
2393
2394        let driver = AstVisitorDriver {};
2395        let visitor = MelOptimizer {};
2396        let result = driver
2397            .visit(&ast, &visitor, result)
2398            .expect("Could not analyze");
2399
2400        let result = result.expr.expect("Could not get the analyzed expression");
2401        let binary_expr = match result {
2402            Expr::BinaryExpr(id) => id,
2403            _ => todo!(),
2404        };
2405
2406        assert_matches!(
2407            *binary_expr,
2408            BinaryExpr::<Analyzed> {
2409                left: _,
2410                right: _,
2411                op: _,
2412                location: _,
2413                aug: Analyzed {
2414                    tipe: Type::Integer,
2415                    constant: Some(CompiledConstant::Integer(96)),
2416                }
2417            }
2418        );
2419    }
2420
2421    #[test]
2422    fn test_optimize_nested_binary_expr2() {
2423        let expr = "(8 * 11) / 8";
2424
2425        let compile_result = compile(expr);
2426        let ast = compile_result.expect("Compilation error");
2427
2428        let driver = AstVisitorDriver {};
2429        let visitor = MelTypeChecker {};
2430        let context = MelAnalysisContext::default();
2431        let result = driver
2432            .visit(&ast, &visitor, context)
2433            .expect("Could not analyze");
2434
2435        let driver = AstVisitorDriver {};
2436        let visitor = MelOptimizer {};
2437        let result = driver
2438            .visit(&ast, &visitor, result)
2439            .expect("Could not analyze");
2440
2441        let result = result.expr.expect("Could not get the analyzed expression");
2442        let binary_expr = match result {
2443            Expr::BinaryExpr(id) => id,
2444            _ => todo!(),
2445        };
2446
2447        assert_matches!(
2448            *binary_expr,
2449            BinaryExpr::<Analyzed> {
2450                left: _,
2451                right: _,
2452                op: _,
2453                location: _,
2454                aug: Analyzed {
2455                    tipe: Type::Integer,
2456                    constant: Some(CompiledConstant::Integer(11)),
2457                }
2458            }
2459        );
2460    }
2461
2462    #[test]
2463    fn test_optimize_nested_binary_expr3() {
2464        let expr = "(8 * 11) % 2";
2465
2466        let compile_result = compile(expr);
2467        let ast = compile_result.expect("Compilation error");
2468
2469        let driver = AstVisitorDriver {};
2470        let visitor = MelTypeChecker {};
2471        let context = MelAnalysisContext::default();
2472        let result = driver
2473            .visit(&ast, &visitor, context)
2474            .expect("Could not analyze");
2475
2476        let driver = AstVisitorDriver {};
2477        let visitor = MelOptimizer {};
2478        let result = driver
2479            .visit(&ast, &visitor, result)
2480            .expect("Could not analyze");
2481
2482        let result = result.expr.expect("Could not get the analyzed expression");
2483        let binary_expr = match result {
2484            Expr::BinaryExpr(id) => id,
2485            _ => todo!(),
2486        };
2487
2488        assert_matches!(
2489            *binary_expr,
2490            BinaryExpr::<Analyzed> {
2491                left: _,
2492                right: _,
2493                op: _,
2494                location: _,
2495                aug: Analyzed {
2496                    tipe: Type::Integer,
2497                    constant: Some(CompiledConstant::Integer(0)),
2498                }
2499            }
2500        );
2501    }
2502
2503    #[test]
2504    fn test_optimize_nested_binary_expr4() {
2505        let expr = "\"testing\" . \"one\"";
2506
2507        let compile_result = compile(expr);
2508        let ast = compile_result.expect("Compilation error");
2509
2510        let driver = AstVisitorDriver {};
2511        let visitor = MelTypeChecker {};
2512        let context = MelAnalysisContext::default();
2513        let result = driver
2514            .visit(&ast, &visitor, context)
2515            .expect("Could not analyze");
2516
2517        let driver = AstVisitorDriver {};
2518        let visitor = MelOptimizer {};
2519        let result = driver
2520            .visit(&ast, &visitor, result)
2521            .expect("Could not analyze");
2522
2523        let result = result.expr.expect("Could not get the analyzed expression");
2524        let binary_expr = match result {
2525            Expr::BinaryExpr(id) => id,
2526            _ => todo!(),
2527        };
2528
2529        assert_matches!(
2530            &*binary_expr,
2531            BinaryExpr::<Analyzed> {
2532                left: _,
2533                right: _,
2534                op: _,
2535                location: _,
2536                aug: Analyzed {
2537                    tipe: Type::String,
2538                    constant: Some(CompiledConstant::String(a)),
2539                }
2540            } if a == "testingone"
2541        );
2542    }
2543
2544    #[test]
2545    fn test_optimize_nested_binary_expr5() {
2546        let expr = "(\"testing\" . \"one\") . \"two\"";
2547
2548        let compile_result = compile(expr);
2549        let ast = compile_result.expect("Compilation error");
2550
2551        let driver = AstVisitorDriver {};
2552        let visitor = MelTypeChecker {};
2553        let context = MelAnalysisContext::default();
2554        let result = driver
2555            .visit(&ast, &visitor, context)
2556            .expect("Could not analyze");
2557
2558        let driver = AstVisitorDriver {};
2559        let visitor = MelOptimizer {};
2560        let result = driver
2561            .visit(&ast, &visitor, result)
2562            .expect("Could not analyze");
2563
2564        let result = result.expr.expect("Could not get the analyzed expression");
2565        let binary_expr = match result {
2566            Expr::BinaryExpr(id) => id,
2567            _ => todo!(),
2568        };
2569
2570        assert_matches!(
2571            &*binary_expr,
2572            BinaryExpr::<Analyzed> {
2573                left: _,
2574                right: _,
2575                op: _,
2576                location: _,
2577                aug: Analyzed {
2578                    tipe: Type::String,
2579                    constant: Some(CompiledConstant::String(a)),
2580                }
2581            } if a == "testingonetwo"
2582        );
2583    }
2584
2585    #[test]
2586    fn test_optimize_logical_binary_expr() {
2587        let expr = "true or false";
2588
2589        let compile_result = compile(expr);
2590        let ast = compile_result.expect("Compilation error");
2591
2592        let driver = AstVisitorDriver {};
2593        let visitor = MelTypeChecker {};
2594        let context = MelAnalysisContext::default();
2595        let result = driver
2596            .visit(&ast, &visitor, context)
2597            .expect("Could not analyze");
2598
2599        let driver = AstVisitorDriver {};
2600        let visitor = MelOptimizer {};
2601        let result = driver
2602            .visit(&ast, &visitor, result)
2603            .expect("Could not analyze");
2604
2605        let result = result.expr.expect("Could not get the analyzed expression");
2606        let binary_expr = match result {
2607            Expr::BinaryExpr(id) => id,
2608            _ => todo!(),
2609        };
2610
2611        assert_matches!(
2612            *binary_expr,
2613            BinaryExpr::<Analyzed> {
2614                left: _,
2615                right: _,
2616                op: _,
2617                location: _,
2618                aug: Analyzed {
2619                    tipe: Type::Boolean,
2620                    constant: Some(CompiledConstant::Boolean(true)),
2621                }
2622            }
2623        );
2624    }
2625
2626    #[test]
2627    fn test_optimize_logical_binary_expr2() {
2628        let expr = "true and false";
2629
2630        let compile_result = compile(expr);
2631        let ast = compile_result.expect("Compilation error");
2632
2633        let driver = AstVisitorDriver {};
2634        let visitor = MelTypeChecker {};
2635        let context = MelAnalysisContext::default();
2636        let result = driver
2637            .visit(&ast, &visitor, context)
2638            .expect("Could not analyze");
2639
2640        let driver = AstVisitorDriver {};
2641        let visitor = MelOptimizer {};
2642        let result = driver
2643            .visit(&ast, &visitor, result)
2644            .expect("Could not analyze");
2645
2646        let result = result.expr.expect("Could not get the analyzed expression");
2647        let binary_expr = match result {
2648            Expr::BinaryExpr(id) => id,
2649            _ => todo!(),
2650        };
2651
2652        assert_matches!(
2653            *binary_expr,
2654            BinaryExpr::<Analyzed> {
2655                left: _,
2656                right: _,
2657                op: _,
2658                location: _,
2659                aug: Analyzed {
2660                    tipe: Type::Boolean,
2661                    constant: Some(CompiledConstant::Boolean(false)),
2662                }
2663            }
2664        );
2665    }
2666
2667    #[test]
2668    fn test_optimize_comparison_expr() {
2669        let expr = "5 < 4";
2670
2671        let compile_result = compile(expr);
2672        let ast = compile_result.expect("Compilation error");
2673
2674        let driver = AstVisitorDriver {};
2675        let visitor = MelTypeChecker {};
2676        let context = MelAnalysisContext::default();
2677        let result = driver
2678            .visit(&ast, &visitor, context)
2679            .expect("Could not analyze");
2680
2681        let driver = AstVisitorDriver {};
2682        let visitor = MelOptimizer {};
2683        let result = driver
2684            .visit(&ast, &visitor, result)
2685            .expect("Could not analyze");
2686
2687        let result = result.expr.expect("Could not get the analyzed expression");
2688        let binary_expr = match result {
2689            Expr::BinaryExpr(id) => id,
2690            _ => todo!(),
2691        };
2692
2693        assert_matches!(
2694            *binary_expr,
2695            BinaryExpr::<Analyzed> {
2696                left: _,
2697                right: _,
2698                op: _,
2699                location: _,
2700                aug: Analyzed {
2701                    tipe: Type::Boolean,
2702                    constant: None,
2703                }
2704            }
2705        );
2706    }
2707
2708    #[test]
2709    fn test_optimize_ipmatch_comparison_expr() {
2710        let expr = "8.8.8.8 ipmatch 5.5.5.5";
2711
2712        let compile_result = compile(expr);
2713        let ast = compile_result.expect("Compilation error");
2714
2715        let driver = AstVisitorDriver {};
2716        let visitor = MelTypeChecker {};
2717        let context = MelAnalysisContext::default();
2718        let result = driver
2719            .visit(&ast, &visitor, context)
2720            .expect("Could not analyze");
2721
2722        let driver = AstVisitorDriver {};
2723        let visitor = MelOptimizer {};
2724        let result = driver
2725            .visit(&ast, &visitor, result)
2726            .expect("Could not analyze");
2727
2728        let result = result.expr.expect("Could not get the analyzed expression");
2729        let binary_expr = match result {
2730            Expr::BinaryExpr(id) => id,
2731            _ => todo!(),
2732        };
2733
2734        assert_matches!(
2735            *binary_expr,
2736            BinaryExpr::<Analyzed> {
2737                left: _,
2738                right: _,
2739                op: _,
2740                location: _,
2741                aug: Analyzed {
2742                    tipe: Type::Boolean,
2743                    constant: Some(CompiledConstant::Boolean(false)),
2744                }
2745            }
2746        );
2747    }
2748
2749    #[test]
2750    fn test_optimize_ipmatch_comparison_expr2() {
2751        let expr = "5.5.5.5 ipmatch 5.5.5.5";
2752
2753        let compile_result = compile(expr);
2754        let ast = compile_result.expect("Compilation error");
2755
2756        let driver = AstVisitorDriver {};
2757        let visitor = MelTypeChecker {};
2758        let context = MelAnalysisContext::default();
2759        let result = driver
2760            .visit(&ast, &visitor, context)
2761            .expect("Could not analyze");
2762
2763        let driver = AstVisitorDriver {};
2764        let visitor = MelOptimizer {};
2765        let result = driver
2766            .visit(&ast, &visitor, result)
2767            .expect("Could not analyze");
2768
2769        let result = result.expr.expect("Could not get the analyzed expression");
2770        let binary_expr = match result {
2771            Expr::BinaryExpr(id) => id,
2772            _ => todo!(),
2773        };
2774
2775        assert_matches!(
2776            *binary_expr,
2777            BinaryExpr::<Analyzed> {
2778                left: _,
2779                right: _,
2780                op: _,
2781                location: _,
2782                aug: Analyzed {
2783                    tipe: Type::Boolean,
2784                    constant: Some(CompiledConstant::Boolean(true)),
2785                }
2786            }
2787        );
2788    }
2789
2790    #[test]
2791    fn test_optimize_ipmatch_comparison_expr3() {
2792        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch 5.5.5.5";
2793
2794        let compile_result = compile(expr);
2795        let ast = compile_result.expect("Compilation error");
2796
2797        let driver = AstVisitorDriver {};
2798        let visitor = MelTypeChecker {};
2799        let context = MelAnalysisContext::default();
2800        let result = driver
2801            .visit(&ast, &visitor, context)
2802            .expect("Could not analyze");
2803
2804        let driver = AstVisitorDriver {};
2805        let visitor = MelOptimizer {};
2806        let result = driver
2807            .visit(&ast, &visitor, result)
2808            .expect("Could not analyze");
2809
2810        let result = result.expr.expect("Could not get the analyzed expression");
2811        let binary_expr = match result {
2812            Expr::BinaryExpr(id) => id,
2813            _ => todo!(),
2814        };
2815
2816        assert_matches!(
2817            *binary_expr,
2818            BinaryExpr::<Analyzed> {
2819                left: _,
2820                right: _,
2821                op: _,
2822                location: _,
2823                aug: Analyzed {
2824                    tipe: Type::Boolean,
2825                    constant: Some(CompiledConstant::Boolean(false)),
2826                }
2827            }
2828        );
2829    }
2830
2831    #[test]
2832    fn test_optimize_ipmatch_comparison_expr4() {
2833        let expr = "2001:0db8:85a3:0000:0000:8a2e:0370:7334 ipmatch 2001:0db8:85a3:0000:0000:8a2e:0370:7334";
2834
2835        let compile_result = compile(expr);
2836        let ast = compile_result.expect("Compilation error");
2837
2838        let driver = AstVisitorDriver {};
2839        let visitor = MelTypeChecker {};
2840        let context = MelAnalysisContext::default();
2841        let result = driver
2842            .visit(&ast, &visitor, context)
2843            .expect("Could not analyze");
2844
2845        let driver = AstVisitorDriver {};
2846        let visitor = MelOptimizer {};
2847        let result = driver
2848            .visit(&ast, &visitor, result)
2849            .expect("Could not analyze");
2850
2851        let result = result.expr.expect("Could not get the analyzed expression");
2852        let binary_expr = match result {
2853            Expr::BinaryExpr(id) => id,
2854            _ => todo!(),
2855        };
2856
2857        assert_matches!(
2858            *binary_expr,
2859            BinaryExpr::<Analyzed> {
2860                left: _,
2861                right: _,
2862                op: _,
2863                location: _,
2864                aug: Analyzed {
2865                    tipe: Type::Boolean,
2866                    constant: Some(CompiledConstant::Boolean(true)),
2867                }
2868            }
2869        );
2870    }
2871}
2872
2873#[cfg(test)]
2874mod analysis_error_tests {
2875    use crate::mel::{
2876        analysis::{AnalysisError, AnalysisPreconditions, MelAnalysisContext, MelOptimizer},
2877        ast::AstVisitorDriver,
2878        compiler::compile,
2879    };
2880    use std::assert_matches;
2881
2882    #[test]
2883    fn test_invalid_context_error_binary_expr() {
2884        let expr = "5 < 4";
2885
2886        let compile_result = compile(expr);
2887        let ast = compile_result.expect("Compilation error");
2888
2889        let context = MelAnalysisContext::default();
2890        let driver = AstVisitorDriver {};
2891        let visitor = MelOptimizer {};
2892
2893        let result = driver
2894            .visit(&ast, &visitor, context)
2895            .expect_err("Could analyze expression with no context");
2896        assert_matches!(
2897            *result.error,
2898            AnalysisError::PreconditionFailure(AnalysisPreconditions::ContextMissingExpr(_))
2899        );
2900    }
2901}
2902
2903pub type MelAnalysisResult = Result<Expr<Analyzed>, Box<AnalysisLocatableError>>;
2904
2905/// Analyze the source code of a MEL expression.
2906pub fn analyze(expr: &Expr<()>, scopes: &Scopes<Type>) -> MelAnalysisResult {
2907    let driver = AstVisitorDriver {};
2908    let visitor = MelTypeChecker {};
2909    let mut context = MelAnalysisContext::default();
2910
2911    context = context.update_scopes(scopes);
2912    let result = driver.visit(expr, &visitor, context)?;
2913
2914    let driver = AstVisitorDriver {};
2915    let visitor = MelOptimizer {};
2916    let result = driver.visit(expr, &visitor, result)?;
2917
2918    let result = result.expr.ok_or(AnalysisLocatableError {
2919        error: AnalysisError::Incalculable.into(),
2920        location: TextLocation {
2921            start: 0,
2922            extent: 0,
2923        },
2924    })?;
2925
2926    Ok(result)
2927}