Skip to main content

brooks_lib/mel/c/
cg.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
18use std::fmt::Debug;
19
20use crate::common::TextLocation;
21use crate::environment::scope::Scopes;
22use crate::logging::{LogLevel, LogMsg, LogMsgs};
23
24use crate::mel::ast::{ComparisonOperator, LogicOperator, MathOperator, StringConcatOperator};
25use crate::mel::{
26    analysis::Analyzed,
27    ast::{
28        self, Argument, ArgumentList, AstVisitor, AstVisitorDriver, AstVisitorResult, BinaryExpr,
29        FunctionCall, IPAddressLiteral, Identifier, NumberLiteral, RegexLiteral, StringLiteral,
30        TernaryExpr,
31    },
32    types::Type,
33};
34
35use std::error::Error;
36
37#[derive(Debug)]
38pub enum MelCodegenError {
39    WriteFailed(Box<dyn Error>),
40    UnknownIdentifier(String),
41}
42
43#[derive(Debug)]
44pub struct MelCodegenLocatableError {
45    pub location: TextLocation,
46    pub error: MelCodegenError,
47    pub context: MelCodegenContext,
48}
49
50pub type MelCodegenResult = Result<MelCodegenContext, MelCodegenLocatableError>;
51
52#[derive(Clone, Debug)]
53pub struct LocatableString {
54    pub s: String,
55    pub l: TextLocation,
56}
57
58#[derive(Clone, Debug, Default)]
59pub struct SSA {
60    next: usize,
61    handle: String,
62}
63
64impl SSA {
65    pub fn new(handle: &str) -> Self {
66        SSA {
67            next: 0usize,
68            handle: handle.to_string(),
69        }
70    }
71
72    pub fn usse(&self) -> (String, Self) {
73        let mut next = self.clone();
74        let next_handle = format!("{}_{}", self.handle, self.next);
75        next.next += 1;
76
77        (next_handle, next)
78    }
79}
80
81#[derive(Debug, Default)]
82pub struct MelCodegenContext {
83    pub scopes: Scopes<Type>,
84    pub code: Vec<LocatableString>,
85    pub ssa_gen: SSA,
86    pub ssa: String,
87    pub log: LogMsgs,
88}
89
90impl MelCodegenContext {
91    pub fn update_log(&self, new: LogMsgs) -> Self {
92        MelCodegenContext {
93            code: self.code.clone(),
94            scopes: self.scopes.clone(),
95            ssa: self.ssa.clone(),
96            ssa_gen: self.ssa_gen.clone(),
97            log: new,
98        }
99    }
100
101    pub fn append_code(self, code: LocatableString) -> Self {
102        let mut nc = self.code.clone();
103        nc.push(code);
104        MelCodegenContext {
105            code: nc,
106            scopes: self.scopes,
107            log: self.log,
108            ssa: self.ssa,
109            ssa_gen: self.ssa_gen,
110        }
111    }
112
113    pub fn used_ssa(self, ssa: String) -> Self {
114        MelCodegenContext {
115            code: self.code,
116            scopes: self.scopes,
117            log: self.log,
118            ssa,
119            ssa_gen: self.ssa_gen,
120        }
121    }
122
123    pub fn next_ssa(self) -> (String, Self) {
124        let (next_handle, next_ssa) = self.ssa_gen.usse();
125        (
126            next_handle,
127            MelCodegenContext {
128                code: self.code,
129                scopes: self.scopes,
130                log: self.log,
131                ssa: self.ssa,
132                ssa_gen: next_ssa,
133            },
134        )
135    }
136}
137
138pub(crate) fn mel_type_to_c_type(tipe: &Type) -> String {
139    match tipe {
140        Type::Unsigned => "unsigned int".to_string(),
141        Type::Boolean => "bool".to_string(),
142        Type::Integer => "int".to_string(),
143        Type::Real => "double".to_string(),
144        Type::String => "std::string".to_string(),
145        Type::Regex => "std::regex".to_string(),
146        Type::Struct(s) => s.name.clone(),
147        Type::IPAddress => todo!(),
148        Type::Params(_) => todo!(),
149        Type::Function(_, _, _) => todo!(),
150        Type::Nil => todo!(),
151    }
152}
153
154fn mel_regex_to_c_regex(re: &str) -> String {
155    re.trim_start_matches("/").trim_end_matches("/").to_string()
156}
157
158fn mel_logic_operator_to_c_logic_operator(op: &LogicOperator, left: &str, right: &str) -> String {
159    let op = match op {
160        LogicOperator::And => "&&".to_string(),
161        LogicOperator::Or => "||".to_string(),
162    };
163
164    format!("{left} {op} {right}")
165}
166
167fn mel_comparison_operator_to_c_comparison_operator(
168    op: &ComparisonOperator,
169    left: &str,
170    right: &str,
171) -> String {
172    match op {
173        ComparisonOperator::Eq => format!("{left} == {right}"),
174        ComparisonOperator::Ne => format!("{left} != {right}"),
175        ComparisonOperator::Lt => format!("{left} < {right}"),
176        ComparisonOperator::Lte => format!("{left} <= {right}"),
177        ComparisonOperator::Gt => format!("{left} > {right}"),
178        ComparisonOperator::Gte => format!("{left} >= {right}"),
179        ComparisonOperator::Re => format!("std::regex_match({left}, {right})"),
180        ComparisonOperator::IP => todo!(),
181    }
182}
183
184fn mel_math_operator_to_c_math_operator(op: &MathOperator, left: &str, right: &str) -> String {
185    let op = match op {
186        MathOperator::Plus => "+".to_string(),
187        MathOperator::Minus => "-".to_string(),
188        MathOperator::Multiply => "*".to_string(),
189        MathOperator::Divide => "/".to_string(),
190        MathOperator::Modulo => "%".to_string(),
191    };
192    format!("{left} {op} {right}")
193}
194
195fn mel_concat_operator_to_c_concat_operator(
196    _: &StringConcatOperator,
197    left: &str,
198    right: &str,
199) -> String {
200    format!("{left} + {right}")
201}
202
203macro_rules! decl {
204    ($tpe:expr, $ssa:expr, $val:expr) => {
205        format!("{} {} = {};", mel_type_to_c_type(&$tpe), $ssa, $val)
206    };
207}
208
209macro_rules! use_log {
210    ($context:ident, $logx:expr) => {
211        match $context {
212            MelCodegenContext {
213                log,
214                scopes,
215                code,
216                ssa_gen,
217                ssa,
218            } => {
219                let logp = $logx(log);
220                MelCodegenContext {
221                    log: logp,
222                    scopes,
223                    code,
224                    ssa,
225                    ssa_gen,
226                }
227            }
228        }
229    };
230}
231
232pub struct MelCodegen {}
233
234impl AstVisitor<MelCodegenContext, Analyzed, MelCodegenLocatableError> for MelCodegen {
235    fn visit_function_call(
236        &self,
237        ast: &FunctionCall<Analyzed>,
238        context: MelCodegenContext,
239        _driver: &AstVisitorDriver,
240    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
241        let _context = use_log!(context, |log: LogMsgs| {
242            trace_with_loc!(
243                log,
244                ast.location.clone(),
245                "Generating code for function call expression"
246            )
247        });
248
249        todo!()
250    }
251
252    fn visit_identifier(
253        &self,
254        ast: &Identifier<Analyzed>,
255        context: MelCodegenContext,
256        _driver: &AstVisitorDriver,
257    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
258        let context = use_log!(context, |log: LogMsgs| {
259            trace_with_loc!(
260                log,
261                ast.location.clone(),
262                "Generating code for identifier expression"
263            )
264        });
265
266        let (ssa, context) = context.next_ssa();
267
268        let found_id = match context.scopes.lookup(&ast.identifier) {
269            Some(v) => v,
270            None => {
271                return Err(MelCodegenLocatableError {
272                    error: MelCodegenError::UnknownIdentifier(ast.identifier.clone()),
273                    location: ast.location.clone(),
274                    context,
275                });
276            }
277        };
278
279        let decl = decl!(found_id, ssa, ast.identifier);
280
281        let context = context.used_ssa(ssa);
282
283        let context = context.append_code(LocatableString {
284            s: decl,
285            l: ast.location.clone(),
286        });
287
288        Ok(context)
289    }
290
291    fn visit_argument_list(
292        &self,
293        ast: &ArgumentList<Analyzed>,
294        context: MelCodegenContext,
295        _driver: &AstVisitorDriver,
296    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
297        let _context = use_log!(context, |log: LogMsgs| {
298            trace_with_loc!(
299                log,
300                ast.location.clone(),
301                "Generating code for argument list expression"
302            )
303        });
304
305        todo!()
306    }
307
308    fn visit_argument(
309        &self,
310        ast: &Argument<Analyzed>,
311        context: MelCodegenContext,
312        _driver: &AstVisitorDriver,
313    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
314        let _context = use_log!(context, |log: LogMsgs| {
315            trace_with_loc!(
316                log,
317                ast.location.clone(),
318                "Generating code for argument list expression"
319            )
320        });
321
322        todo!()
323    }
324
325    fn visit_binary_expr(
326        &self,
327        ast: &BinaryExpr<Analyzed>,
328        context: MelCodegenContext,
329        driver: &AstVisitorDriver,
330    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
331        let context = use_log!(context, |log: LogMsgs| {
332            trace_with_loc!(
333                log,
334                ast.location.clone(),
335                "Generating code for binary expression"
336            )
337        });
338
339        let context = driver.visit(&ast.left, self, context)?;
340        let left_ssa = context.ssa.clone();
341
342        let context = driver.visit(&ast.right, self, context)?;
343        let right_ssa = context.ssa.clone();
344
345        let (ssa, mut context) = context.next_ssa();
346
347        let value = match &ast.op {
348            ast::BinaryInfixOperator::Logic(logic_operator) => {
349                mel_logic_operator_to_c_logic_operator(logic_operator, &left_ssa, &right_ssa)
350            }
351            ast::BinaryInfixOperator::Comparison(comparison_operator) => {
352                mel_comparison_operator_to_c_comparison_operator(
353                    comparison_operator,
354                    &left_ssa,
355                    &right_ssa,
356                )
357            }
358            ast::BinaryInfixOperator::Math(math_operator) => {
359                mel_math_operator_to_c_math_operator(math_operator, &left_ssa, &right_ssa)
360            }
361            ast::BinaryInfixOperator::Concat(string_concat_operator) => {
362                mel_concat_operator_to_c_concat_operator(
363                    string_concat_operator,
364                    &left_ssa,
365                    &right_ssa,
366                )
367            }
368            ast::BinaryInfixOperator::MemberAccess(_) => todo!(),
369        };
370
371        let decl = decl!(ast.aug.tipe, ssa, value);
372
373        context = context.used_ssa(ssa);
374
375        context = context.append_code(LocatableString {
376            s: decl,
377            l: ast.location.clone(),
378        });
379
380        Ok(context)
381    }
382
383    fn visit_literal(
384        &self,
385        ast: (&ast::Literal, &TextLocation, &Analyzed),
386        context: MelCodegenContext,
387        _driver: &AstVisitorDriver,
388    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
389        let context = use_log!(context, |log: LogMsgs| {
390            trace_with_loc!(log, ast.1.clone(), "Generating code for literal expression")
391        });
392
393        let (ssa, mut context) = context.next_ssa();
394
395        let lit = match ast {
396            (ast::Literal::Boolean(b), _, _) => LocatableString {
397                s: decl!(Type::Boolean, ssa, b.to_string()),
398                l: ast.1.clone(),
399            },
400            (ast::Literal::Number(NumberLiteral { literal: l }), _, _) => LocatableString {
401                s: decl!(Type::Integer, ssa, l.to_string()),
402                l: ast.1.clone(),
403            },
404            (ast::Literal::String(StringLiteral { literal: s }), _, _) => LocatableString {
405                s: decl!(Type::String, ssa, format!("\"{s}\"")),
406                l: ast.1.clone(),
407            },
408            (ast::Literal::Regex(RegexLiteral { literal: re }), _, _) => LocatableString {
409                s: decl!(
410                    Type::Regex,
411                    ssa,
412                    format!("std::regex(\"{}\")", mel_regex_to_c_regex(&re.to_string()))
413                ),
414                l: ast.1.clone(),
415            },
416            (ast::Literal::IPAddress(IPAddressLiteral { literal: _ }), _, _) => todo!(),
417        };
418
419        context = context.used_ssa(ssa);
420
421        Ok(context.append_code(lit))
422    }
423
424    fn visit_ternary_expr(
425        &self,
426        ast: &TernaryExpr<Analyzed>,
427        context: MelCodegenContext,
428        driver: &AstVisitorDriver,
429    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
430        let context = use_log!(context, |log: LogMsgs| {
431            trace_with_loc!(
432                log,
433                ast.location.clone(),
434                "Generating code for literal expression"
435            )
436        });
437
438        let context = driver.visit(&ast.condition, self, context)?;
439        let condition_ssa = context.ssa.clone();
440
441        let context = driver.visit(&ast.yes, self, context)?;
442        let yes_ssa = context.ssa.clone();
443
444        let context = driver.visit(&ast.no, self, context)?;
445        let no_ssa = context.ssa.clone();
446
447        let (ssa, mut context) = context.next_ssa();
448
449        let value = format!("{} ? {} : {}", condition_ssa, yes_ssa, no_ssa);
450        let decl = decl!(ast.aug.tipe, ssa, value);
451
452        context = context.used_ssa(ssa);
453
454        context = context.append_code(LocatableString {
455            s: decl,
456            l: ast.location.clone(),
457        });
458
459        Ok(context)
460    }
461
462    fn visit_member_access_expr(
463        &self,
464        ast: &ast::MemberAccessExpression<Analyzed>,
465        context: MelCodegenContext,
466        _driver: &AstVisitorDriver,
467    ) -> AstVisitorResult<MelCodegenContext, MelCodegenLocatableError> {
468        let _context = use_log!(context, |log: LogMsgs| {
469            trace_with_loc!(
470                log,
471                ast.location.clone(),
472                "Generating code for member access expression"
473            )
474        });
475
476        todo!()
477    }
478}