1mod scope;
6mod ystd;
7
8use std::{error::Error, fs::File, io::prelude::*, path::PathBuf, process::Command};
9
10use Instruction::*;
11use InstructionOperand::*;
12use InstructionSize::*;
13use Reg::*;
14
15use log::{error, info};
16
17use crate::{
18 asm::{Instruction, InstructionOperand, InstructionSize, Reg, EXIT_SYSCALL},
19 ast::Ast,
20 loader::{Module, Modules},
21 typechecker::TypeInfo,
22};
23
24use self::{
25 scope::{Constant, Scope},
26 ystd::INT_TO_STR,
27};
28pub struct Compiler {
29 scope: Scope,
30 modules: Modules<TypeInfo>,
31}
32
33impl Compiler {
34 pub fn from_ast(ast: Ast<TypeInfo>, modules: Modules<TypeInfo>) -> Self {
35 Self {
36 scope: Scope::from_statements(ast.nodes(), 0, true, Option::None),
37 modules,
38 }
39 }
40
41 fn prelude() -> Vec<Instruction> {
42 vec![
43 Label("str_len".to_owned()),
44 Xor(Register(Rax), Register(Rax)),
45 Label(".str_len_loop".to_owned()),
46 Cmp(Memory(Byte, format!("{Rdi}+{Rax}")), Immediate(0)),
47 Je(".str_len_end".to_owned()),
48 Inc(Rax),
49 Jmp(".str_len_loop".to_owned()),
50 Label(".str_len_end".to_owned()),
51 Ret,
52 Literal(INT_TO_STR.to_owned()),
53 ]
54 }
55
56 fn write_data_from_standard_library(&mut self, file: &mut File) -> Result<(), Box<dyn Error>> {
57 file.write_all("\tint_to_str_val: times 64 db 0\n\n".as_bytes())?;
58
59 Ok(())
60 }
61
62 fn write_data_from_scope(
63 &mut self,
64 file: &mut File,
65 scope: &Scope,
66 ) -> Result<(), Box<dyn Error>> {
67 file.write_all("section .data\n".as_bytes())?;
68 for Constant { value, name } in scope.constants.values() {
69 file.write_all(format!("\t{name} db ").as_bytes())?;
71
72 let string = &value;
74 let mut parts = string.split('\n').peekable();
75
76 while let Some(part) = parts.next() {
77 file.write_all(format!("\"{part}\", ").as_bytes())?;
78 if parts.peek().is_some() {
80 file.write_all("0xa, 0xd, ".as_bytes())?;
81 }
82 }
83 file.write_all("0\n".as_bytes())?;
84 }
85
86 Ok(())
87 }
88
89 fn write_data_section(&mut self, file: &mut File) -> Result<(), Box<dyn Error>> {
90 self.write_data_from_scope(file, &self.scope.clone())?;
91 self.write_data_from_standard_library(file)?;
92 Ok(())
93 }
94
95 fn write_global_entry(&self, file: &mut File) -> Result<(), Box<dyn Error>> {
96 #[cfg(target_os = "macos")]
97 file.write_all("\tglobal _main\n".as_bytes())?;
98
99 #[cfg(target_os = "linux")]
100 file.write_all("\tglobal main\n".as_bytes())?;
101
102 file.write_all("\tglobal str_len\n".as_bytes())?;
103 file.write_all("\tglobal int_to_str\n".as_bytes())?;
104
105 Ok(())
106 }
107
108 fn write_external_symbols(
109 &mut self,
110 file: &mut File,
111 scope: &Scope,
112 ) -> Result<(), Box<dyn Error>> {
113 for external in &scope.externals {
114 file.write_all(format!("extern {external}\n").as_bytes())?;
115 }
116
117 Ok(())
118 }
119
120 fn write_functions(&mut self, file: &mut File, scope: &Scope) -> Result<(), Box<dyn Error>> {
121 file.write_all("\nsection .text\n".as_bytes())?;
122
123 for (identifier, function) in &scope.functions {
124 file.write_all(format!("{}", Label(identifier.to_owned())).as_bytes())?;
125
126 for instruction in &function.instructions {
127 file.write_all(format!("{instruction}\n").as_bytes())?;
128 }
129 }
130
131 Ok(())
132 }
133
134 fn write_prelude(&mut self, file: &mut File) -> Result<(), Box<dyn Error>> {
135 let prelude = Self::prelude();
136 for instruction in &prelude {
137 file.write_all(format!("{instruction}\n").as_bytes())?;
138 }
139
140 Ok(())
141 }
142
143 fn write_text_section(&mut self, file: &mut File, scope: &Scope) -> Result<(), Box<dyn Error>> {
144 self.write_global_entry(file)?;
145
146 self.write_external_symbols(file, scope)?;
147
148 self.write_functions(file, scope)?;
149 self.write_prelude(file)?;
150
151 #[cfg(target_os = "macos")]
152 let mut instructions = vec![Label("_main".to_owned())];
153
154 #[cfg(target_os = "linux")]
155 let mut instructions = vec![Label("main".to_owned())];
156
157 instructions.append(&mut self.scope.instructions.clone());
158
159 for instruction in &instructions {
160 file.write_all(format!("{instruction}\n").as_bytes())?;
161 }
162
163 Ok(())
164 }
165
166 fn write_exit(&self, file: &mut File) -> Result<(), Box<dyn Error>> {
167 file.write_all(format!("{}\n", Label("exit".to_owned())).as_bytes())?;
168 file.write_all(format!("{}\n", Mov(Register(Rax), EXIT_SYSCALL)).as_bytes())?;
169 file.write_all(format!("{}\n", Mov(Register(Rdi), Immediate(0))).as_bytes())?;
170 file.write_all(format!("{Syscall}\n").as_bytes())?;
171
172 Ok(())
173 }
174
175 fn write_code(&mut self, target: PathBuf) -> Result<(), Box<dyn Error>> {
176 let mut file = File::create(format!("{}.asm", target.to_string_lossy()))?;
177
178 file.write_all("default rel\n\n".as_bytes())?;
179
180 self.write_data_section(&mut file)?;
181 self.write_text_section(&mut file, &self.scope.clone())?;
182
183 self.write_exit(&mut file)?;
184 Ok(())
185 }
186
187 fn compile_nasm(&mut self, target: PathBuf) -> Result<(), Box<dyn Error>> {
188 info!("Compiling '{}.asm'...", target.to_string_lossy());
189
190 #[cfg(target_os = "macos")]
191 let output = Command::new("nasm")
192 .args([
193 "-f",
194 "macho64",
195 &format!("{}.asm", target.to_string_lossy()),
196 ])
197 .output()?;
198
199 #[cfg(target_os = "linux")]
200 let output = Command::new("nasm")
201 .args(["-f", "elf64", &format!("{}.asm", target.to_string_lossy())])
202 .output()?;
203
204 let stderr = std::str::from_utf8(&output.stderr)?;
205
206 if !stderr.is_empty() {
207 error!("{stderr}");
208 }
209
210 Ok(())
211 }
212
213 fn link_program(&mut self, target: PathBuf, files: Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
214 info!("Linking program...");
215
216 let mut args = Vec::<String>::new();
217
218 #[cfg(target_os = "macos")]
219 {
220 args.extend(["-arch", "x86_64"].map(|s| s.to_string()));
221 }
222
223 args.push("-o".to_string());
224
225 let target = target.to_string_lossy();
226 args.push(target.to_string());
227
228 let target = format!("{target}.o");
229 args.push(target);
230
231 let mut files = files
232 .iter()
233 .map(|file| format!("{}.o", file.to_string_lossy().as_ref()))
234 .collect::<Vec<_>>();
235
236 args.append(&mut files);
237
238 let output = Command::new("cc").args(args.as_slice()).output()?;
239
240 let stderr = std::str::from_utf8(&output.stderr)?;
241
242 if !stderr.is_empty() {
243 error!("{stderr}");
244 }
245
246 Ok(())
247 }
248
249 fn compile_module(
250 &mut self,
251 module: &Module<TypeInfo>,
252 folder: PathBuf,
253 ) -> Result<PathBuf, Box<dyn Error>> {
254 let mut scope = Scope::from_statements(module.ast.nodes(), 0, true, Some(module.clone()));
255 scope.compile();
256
257 let mut output = folder;
258 output.push(module.name.clone());
259
260 let mut file = File::create(format!("{}.asm", output.to_string_lossy()))?;
261
262 file.write_all("default rel\n\n".as_bytes())?;
263
264 for export in module.exports.flatten().keys() {
265 file.write_all(format!("global {}\n", module.resolve(export)).as_bytes())?;
266 }
267
268 self.write_external_symbols(&mut file, &scope)?;
269
270 self.write_data_from_scope(&mut file, &scope)?;
271 self.write_functions(&mut file, &scope)?;
272
273 self.compile_nasm(output.clone())?;
274
275 Ok(output)
276 }
277
278 pub fn compile_program(&mut self, target: PathBuf) -> Result<(), Box<dyn Error>> {
279 info!("Generating code...");
280
281 self.scope.compile();
282
283 let mut folder = target.clone();
284 folder.pop();
285
286 let modules = self.modules.clone();
287
288 let mut others = vec![];
289
290 for module in modules.values() {
291 others.push(self.compile_module(module, folder.clone())?);
292 }
293
294 self.write_code(target.clone())?;
295 self.compile_nasm(target.clone())?;
296 self.link_program(target, others)?;
297
298 Ok(())
299 }
300}