1use crate::{CodeGenerator, Error, Lowerer, Result};
6use oxur_lang::CoreForm;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9
10pub struct Compiler {
12 codegen: CodeGenerator,
13 output_dir: PathBuf,
14}
15
16impl Compiler {
17 pub fn new(output_dir: PathBuf) -> Self {
18 Self { codegen: CodeGenerator::new(), output_dir }
19 }
20
21 pub fn compile(
32 &mut self,
33 forms: Vec<CoreForm>,
34 source_map: oxur_smap::SourceMap,
35 output: &Path,
36 ) -> Result<oxur_smap::SourceMap> {
37 let mut lowerer = Lowerer::new(source_map);
39 let (ast, source_map) = lowerer.lower(forms)?;
40
41 let source = self.codegen.generate(&ast)?;
43
44 let rs_file = self.output_dir.join("generated.rs");
46 std::fs::write(&rs_file, source)?;
47
48 self.compile_with_rustc(&rs_file, output, &source_map)?;
50
51 Ok(source_map)
52 }
53
54 fn compile_with_rustc(
55 &self,
56 source: &Path,
57 output: &Path,
58 source_map: &oxur_smap::SourceMap,
59 ) -> Result<()> {
60 let output_result = Command::new("rustc")
61 .arg(source)
62 .arg("-o")
63 .arg(output)
64 .arg("--error-format=json")
65 .output()?;
66
67 if !output_result.status.success() {
68 let stderr = String::from_utf8_lossy(&output_result.stderr);
70 let diagnostics =
71 crate::RustcDiagnostic::from_json_lines(&stderr).unwrap_or_else(|_| vec![]);
72
73 let translator = crate::ErrorTranslator::new(source_map.clone());
75 let translated = translator.translate_diagnostics(&diagnostics);
76
77 let error_msg = format!(
78 "rustc failed with exit code: {:?}\n\n{}",
79 output_result.status.code(),
80 translated
81 );
82
83 return Err(Error::Compile(error_msg));
84 }
85
86 Ok(())
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_compiler_creation() {
96 let compiler = Compiler::new(PathBuf::from("/tmp"));
97 assert_eq!(compiler.output_dir, PathBuf::from("/tmp"));
98 }
99
100 #[test]
101 fn test_compiler_has_codegen() {
102 let compiler = Compiler::new(PathBuf::from("/tmp"));
103 assert_eq!(compiler.output_dir, PathBuf::from("/tmp"));
105 }
106
107 #[test]
108 fn test_compiler_output_dir() {
109 let test_dir = PathBuf::from("/var/tmp/test");
110 let compiler = Compiler::new(test_dir.clone());
111 assert_eq!(compiler.output_dir, test_dir);
112 }
113
114 #[test]
115 fn test_compile_with_empty_forms() {
116 use tempfile::TempDir;
117
118 let temp_dir = TempDir::new().unwrap();
119 let output_dir = temp_dir.path().join("build");
120 std::fs::create_dir_all(&output_dir).unwrap();
121
122 let mut compiler = Compiler::new(output_dir.clone());
123 let output_path = temp_dir.path().join("test_output");
124 let source_map = oxur_smap::SourceMap::new();
125
126 let result = compiler.compile(vec![], source_map, &output_path);
129 assert!(result.is_ok() || result.is_err());
132 }
133
134 #[test]
135 fn test_compile_hello_world() {
136 use oxur_lang::{Expander, Parser};
137 use tempfile::TempDir;
138
139 let source = r#"(deffn main ()
141 (println! "Hello, world!"))"#;
142
143 let mut parser = Parser::new(source.to_string());
144 let surface_forms = parser.parse().unwrap();
145
146 let mut expander = Expander::new();
147 let core_forms = expander.expand(surface_forms).unwrap();
148 let source_map = expander.source_map().clone();
149
150 let temp_dir = TempDir::new().unwrap();
152 let output_dir = temp_dir.path().join("build");
153 std::fs::create_dir_all(&output_dir).unwrap();
154
155 let mut compiler = Compiler::new(output_dir);
156 let binary_path = temp_dir.path().join("hello_world");
157
158 let result = compiler.compile(core_forms, source_map, &binary_path);
159 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
160
161 assert!(binary_path.exists(), "Binary was not created");
163
164 let output = Command::new(&binary_path).output().unwrap();
166 let stdout = String::from_utf8_lossy(&output.stdout);
167
168 eprintln!("Binary output:\n{}", stdout);
169
170 assert!(output.status.success(), "Binary execution failed");
171 assert!(stdout.contains("Hello, world!"), "Output doesn't contain expected text");
172 }
173
174 #[test]
175 fn test_error_translation_format() {
176 use oxur_lang::{Expander, Parser};
177 use tempfile::TempDir;
178
179 let source = r#"(deffn main ()
181 (println! x))"#; let mut parser = Parser::new(source.to_string());
184 let surface_forms = parser.parse().unwrap();
185
186 let mut expander = Expander::new();
187 let core_forms = expander.expand(surface_forms).unwrap();
188 let source_map = expander.source_map().clone();
189
190 let temp_dir = TempDir::new().unwrap();
192 let output_dir = temp_dir.path().join("build");
193 std::fs::create_dir_all(&output_dir).unwrap();
194
195 let mut compiler = Compiler::new(output_dir);
196 let binary_path = temp_dir.path().join("test_error");
197
198 let result = compiler.compile(core_forms, source_map, &binary_path);
199
200 assert!(result.is_err(), "Should fail due to undefined variable");
202
203 if let Err(crate::Error::Compile(msg)) = result {
204 eprintln!("Error message:\n{}", msg);
206
207 assert!(!msg.is_empty(), "Should have error message");
210 }
211 }
212}