Skip to main content

brooks_lib/verifying/mel/
verify.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//! MEL Verification
19
20use crate::{
21    cdni::{
22        gmd::spec::TypedGenericMetadata,
23        mel::spec::{SetVariable, TypedSetVariable},
24    },
25    common::TextLocation,
26    mel::{
27        self, MEL_USER_SET_VARIABLE_STRUCT_NAME,
28        analysis::{self, Analyzed},
29        ast::Expr,
30        compiler::{self, error::CompilerLocatableError},
31        types::{
32            self,
33            Type::{self, Struct},
34        },
35    },
36    verifying::verify::{
37        AnalysisResult, CdniVerificationKey, MetadataAnalysisContext, MetadataVerificationError,
38        Verifier,
39    },
40};
41use std::fmt::{Debug, Display};
42
43#[derive(Debug, Clone, Default)]
44pub struct SetVariableVerificationKey {
45    pub value: Option<()>,
46    pub expression: Option<Expr<Analyzed>>,
47}
48
49#[derive(Debug, Clone)]
50pub enum MelVerificationError {
51    CompilationError(Box<CompilerLocatableError>),
52    AnalysisError(Box<mel::error::AnalysisLocatableError>),
53}
54
55impl Display for MelVerificationError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            MelVerificationError::CompilationError(mel_compiler_locatable_error) => {
59                write!(f, "Compilation error: {mel_compiler_locatable_error}")
60            }
61            MelVerificationError::AnalysisError(mel_analysis_locatable_error) => {
62                write!(f, "Analysis error: {mel_analysis_locatable_error}")
63            }
64        }
65    }
66}
67
68#[derive(Debug)]
69pub struct SetVariableMetadataAnalyzer {}
70
71impl Verifier<MetadataAnalysisContext, MetadataVerificationError> for SetVariableMetadataAnalyzer {
72    fn analyze(
73        &self,
74        v: &TypedGenericMetadata<()>,
75        mut input: MetadataAnalysisContext,
76    ) -> AnalysisResult<MetadataAnalysisContext, MetadataVerificationError> {
77        let typed_set_variable = serde_json::from_value::<TypedSetVariable<()>>(
78            serde_json::to_value(v)
79                .map_err(|e| MetadataVerificationError::JsonError(e.to_string()))?,
80        )
81        .map_err(|e| MetadataVerificationError::JsonError(e.to_string()))?;
82
83        let mut verification_key = SetVariableVerificationKey::default();
84
85        let expression = if let Some(value_is_expression) =
86            typed_set_variable.value.value_is_expression
87            && value_is_expression
88        {
89            let parsed =
90                compiler::compile(&typed_set_variable.value.variable_value).map_err(|e| {
91                    MetadataVerificationError::MelVerificationError(
92                        MelVerificationError::CompilationError(e.into()).into(),
93                    )
94                })?;
95
96            analysis::analyze(&parsed, &input.scopes).map_err(|e| {
97                MetadataVerificationError::MelVerificationError(
98                    MelVerificationError::AnalysisError(e).into(),
99                )
100            })?
101        } else {
102            todo!()
103        };
104
105        verification_key.expression = Some(expression.clone());
106
107        let res = TypedSetVariable {
108            tpe: TypedSetVariable::<()>::typed_cdni_metadata_name(),
109            value: SetVariable {
110                aug: verification_key.clone(),
111                variable_name: typed_set_variable.value.variable_name.clone(),
112                variable_value: typed_set_variable.value.variable_value,
113                value_is_expression: typed_set_variable.value.value_is_expression,
114            },
115        };
116
117        // Now, modify the fields of the var to include this field!
118        let var = input
119            .scopes
120            .lookup(MEL_USER_SET_VARIABLE_STRUCT_NAME)
121            .ok_or(MetadataVerificationError::MelVerificationError(
122                MelVerificationError::AnalysisError(
123                    mel::error::AnalysisLocatableError {
124                        error: mel::error::AnalysisError::UnknownIdentifier(
125                            MEL_USER_SET_VARIABLE_STRUCT_NAME.to_string(),
126                        )
127                        .into(),
128                        location: TextLocation {
129                            start: 0,
130                            extent: 0,
131                        },
132                    }
133                    .into(),
134                )
135                .into(),
136            ))?;
137
138        let mut var_type = match var {
139            Type::Struct(s) => s,
140            it => {
141                return Err(MetadataVerificationError::MelVerificationError(
142                    MelVerificationError::AnalysisError(
143                        mel::error::AnalysisLocatableError {
144                            error: mel::error::AnalysisError::InvalidType(
145                                vec![Struct(types::Struct::new(
146                                    MEL_USER_SET_VARIABLE_STRUCT_NAME,
147                                ))],
148                                it,
149                            )
150                            .into(),
151                            location: TextLocation {
152                                start: 0,
153                                extent: 0,
154                            },
155                        }
156                        .into(),
157                    )
158                    .into(),
159                )
160                .into());
161            }
162        };
163
164        var_type.insert_field(&typed_set_variable.value.variable_name, expression.tipe());
165
166        input.scopes = input
167            .scopes
168            .insert(MEL_USER_SET_VARIABLE_STRUCT_NAME, Type::Struct(var_type));
169
170        Ok((input, CdniVerificationKey::SetVariable(Box::new(res))))
171    }
172
173    fn type_name(&self, candidate: &str) -> bool {
174        candidate == TypedSetVariable::<()>::typed_cdni_metadata_name()
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use std::assert_matches;
181
182    use crate::{
183        cdni::{
184            mel::spec::TypedSetVariable,
185            tests::test_helpers::{generic_metadata, set_variable},
186        },
187        mel::types::{builtin_function_types, minimal_core_variable_types},
188        verifying::{
189            mel::verify::SetVariableMetadataAnalyzer,
190            verify::{CdniVerificationKey, MetadataAnalysisContext, Verifier},
191        },
192    };
193
194    #[test]
195    fn verify_test() {
196        let variable = set_variable("is_bad".to_string(), "true".to_string(), Some(true));
197
198        let json = generic_metadata(
199            &TypedSetVariable::<()>::typed_cdni_metadata_name(),
200            Some(serde_json::to_value(variable.value).expect("TODO")),
201        );
202
203        let svma = SetVariableMetadataAnalyzer {};
204
205        let result = svma.analyze(
206            &json,
207            MetadataAnalysisContext {
208                scopes: (&builtin_function_types() + &minimal_core_variable_types()).into(),
209            },
210        );
211
212        assert_matches!(result, Ok((_, CdniVerificationKey::SetVariable(e))) if e.value.aug.expression.is_some());
213    }
214}