y_lang/compiler/
mod.rs

1//! Compiler for the Y programming language.
2//!
3//! This module provides capabilities for compiling type correct Y programs. Therefore, you should
4//! utilize the type checker beforehand.
5mod 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            // write the name of the string constant
70            file.write_all(format!("\t{name} db ").as_bytes())?;
71
72            // split string into lines
73            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 this is not the last line, we append a CRLF
79                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}