Skip to main content

brooks_lib/mel/c/
mod.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
18pub mod cg;
19
20use std::{
21    fs::OpenOptions,
22    io::{BufWriter, Read, Write},
23    path::{Path, PathBuf},
24};
25
26use crate::mel::{
27    analysis::Analyzed,
28    ast::{AstVisitorDriver, Expr},
29    c::cg::{
30        LocatableString, MelCodegen, MelCodegenContext, MelCodegenError, MelCodegenLocatableError,
31        mel_type_to_c_type,
32    },
33};
34
35#[cfg(test)]
36mod tests;
37
38#[allow(clippy::result_large_err)]
39pub fn codegen<A: Write>(
40    expr: &Expr<Analyzed>,
41    context: MelCodegenContext,
42    mut output: A,
43) -> Result<A, MelCodegenLocatableError> {
44    let driver = AstVisitorDriver {};
45    let visitor = MelCodegen {};
46
47    let result = driver.visit(expr, &visitor, context)?;
48
49    for c in &result.code {
50        if let Err(e) = write!(output, "// {}\n{}\n", c.l, c.s) {
51            return Err(MelCodegenLocatableError {
52                location: expr.location().clone(),
53                error: MelCodegenError::WriteFailed(Box::new(e)),
54                context: result,
55            });
56        }
57    }
58
59    Ok(output)
60}
61
62#[allow(clippy::result_large_err)]
63pub fn codegen_function<A: Write>(
64    expr: &Expr<Analyzed>,
65    context: MelCodegenContext,
66    function_name: &str,
67    params: Vec<(String, String)>,
68    mut output: A,
69) -> Result<A, MelCodegenLocatableError> {
70    let driver = AstVisitorDriver {};
71    let visitor = MelCodegen {};
72
73    if let Err(e) = writeln!(
74        output,
75        "{} {}({}) {{",
76        mel_type_to_c_type(&expr.tipe()),
77        function_name,
78        params
79            .iter()
80            .map(|(tpe, name)| { format!("{} {}", tpe, name) })
81            .collect::<Vec<_>>()
82            .join(",")
83    ) {
84        return Err(MelCodegenLocatableError {
85            location: expr.location().clone(),
86            error: MelCodegenError::WriteFailed(Box::new(e)),
87            context,
88        });
89    };
90
91    let mut context = driver.visit(expr, &visitor, context)?;
92
93    // Add a return statement!
94    let ret = format!("return {};", context.ssa);
95
96    context = context.append_code(LocatableString {
97        s: ret,
98        l: expr.location().clone(),
99    });
100
101    for c in &context.code {
102        if let Err(e) = write!(output, "// {}\n{}\n", c.l, c.s) {
103            return Err(MelCodegenLocatableError {
104                location: expr.location().clone(),
105                error: MelCodegenError::WriteFailed(Box::new(e)),
106                context,
107            });
108        }
109    }
110
111    writeln!(output, "}}").map_err(|e| MelCodegenLocatableError {
112        location: expr.location().clone(),
113        error: MelCodegenError::WriteFailed(Box::new(e)),
114        context,
115    })?;
116    Ok(output)
117}
118
119#[allow(clippy::result_large_err)]
120pub fn codegen_project(
121    expr: &Expr<Analyzed>,
122    context: MelCodegenContext,
123    function_name: &str,
124    params: Vec<(String, String)>,
125    output_path: &Path,
126    materials_path: &Path,
127) -> Result<(), MelCodegenLocatableError> {
128    let driver = AstVisitorDriver {};
129    let visitor = MelCodegen {};
130
131    let mut skeleton_contents_path = materials_path.to_path_buf();
132    skeleton_contents_path.push("mel.cpp");
133
134    let mut skeleton_contents: Vec<u8> = vec![];
135    OpenOptions::new()
136        .read(true)
137        .open(skeleton_contents_path)
138        .expect("Could not open skeleton file.")
139        .read_to_end(&mut skeleton_contents)
140        .expect("Could not read string from skeleton file");
141
142    let skeleton_contents =
143        String::from_utf8(skeleton_contents).expect("Could not convert source to string");
144
145    let function_data: Vec<u8> = vec![];
146    let mut function_buffer = BufWriter::new(function_data);
147
148    if let Err(e) = writeln!(
149        function_buffer,
150        "{} {}({}) {{",
151        mel_type_to_c_type(&expr.tipe()),
152        function_name,
153        params
154            .iter()
155            .map(|(tpe, name)| { format!("{} {}", tpe, name) })
156            .collect::<Vec<_>>()
157            .join(",")
158    ) {
159        return Err(MelCodegenLocatableError {
160            location: expr.location().clone(),
161            error: MelCodegenError::WriteFailed(Box::new(e)),
162            context,
163        });
164    };
165
166    let mut context = driver.visit(expr, &visitor, context)?;
167
168    // Add a return statement!
169    let ret = format!("return {};", context.ssa);
170
171    context = context.append_code(LocatableString {
172        s: ret,
173        l: expr.location().clone(),
174    });
175
176    for c in &context.code {
177        if let Err(e) = write!(function_buffer, "\t// {}\n\t{}\n", c.l, c.s) {
178            return Err(MelCodegenLocatableError {
179                location: expr.location().clone(),
180                error: MelCodegenError::WriteFailed(Box::new(e)),
181                context,
182            });
183        }
184    }
185
186    writeln!(function_buffer, "}}").map_err(|e| MelCodegenLocatableError {
187        location: expr.location().clone(),
188        error: MelCodegenError::WriteFailed(Box::new(e)),
189        context,
190    })?;
191
192    let function_code = String::from_utf8_lossy(function_buffer.buffer());
193
194    let skeleton_contents = skeleton_contents.replace("INTERPRET_FUNCTION", &function_code);
195
196    let mut skeleton_output_path = output_path.to_path_buf();
197    skeleton_output_path.push(PathBuf::from("mel.cpp"));
198
199    let mut output = OpenOptions::new()
200        .read(true)
201        .write(true)
202        .create(true)
203        .truncate(true)
204        .open(skeleton_output_path)
205        .expect("Could not open output skeleton file");
206
207    output
208        .write_all(skeleton_contents.as_bytes())
209        .expect("Could not write to output file");
210
211    let mut cmake_path = materials_path.to_path_buf();
212    cmake_path.push("CMakeLists.txt");
213    let mut cmake_output_path = output_path.to_path_buf();
214    cmake_output_path.push("CMakeLists.txt");
215    std::fs::copy(cmake_path, cmake_output_path).expect("Could not copy CMakeLists.txt");
216
217    Ok(())
218}