bfc_rs/
compiler.rs

1#[cfg(test)]
2mod tests;
3use super::BrainfuckInstr;
4/// Instructions and directives found at the beginning of every program.
5const ALLOCATE_AND_START: &str = include_str!("compiler/alloc_start.asm");
6/// Ditto for the end.
7const EXIT_SYSCALL: &str = include_str!("compiler/exit.asm");
8
9/// Transforms abstract Brainfuck code into assembly.
10pub fn compile(code: &[BrainfuckInstr]) -> String {
11    let mut output = String::new();
12    output.push_str(ALLOCATE_AND_START);
13    let mut total_loops = 0;
14    let mut loop_no_stack = Vec::new();
15    for instruction in code {
16        // Generate the rest of the damn code.
17        translate_instruction(
18            instruction,
19            &mut total_loops,
20            &mut loop_no_stack,
21            &mut output
22        );
23    }
24    output.push_str(EXIT_SYSCALL);
25    output
26}
27/// Transforms an individual Brainfuck instruction into an x86 one and stores it in the output buffer.
28fn translate_instruction(instruction: &BrainfuckInstr, total_loops: &mut u32, loop_no_stack: &mut Vec<u32>, output: &mut String) {
29    use BrainfuckInstr::*;
30    output.push_str(match instruction {
31        PointerDec => "dec rsi",
32        PointerInc => "inc rsi",
33        DataDec => "dec byte [rsi]",
34        DataInc => "inc byte [rsi]",
35        GetByte => "mov rdx,1\nmov rdi,0\nmov rax,0\nsyscall",
36        PutByte => "mov rdx,1\nmov rdi,1\nmov rax,1\nsyscall",
37        WhileNonzero => {
38            *total_loops += 1;
39            loop_no_stack.push(*total_loops);
40            let asm = format!(
41                "cmp byte [rsi],0\nje .end_{x}\n.while_{x}:\n",
42                /* In match arms like this one, our ASM instruction strings have \n at the end
43                because we're not going to reach past the end of our match block. */
44                x = total_loops
45            );
46            output.push_str(&asm);
47            return // early return so this block doesn't have to evaluate to an expression
48            // (clarification: Rust doesn't like references to Strings that are about to go out of scope and die)
49        },
50        EndWhile => {
51            let asm = format!(
52                "cmp byte [rsi],0\njne .while_{x}\n.end_{x}:\n",
53                x = loop_no_stack.pop().expect("There should be a loop number on the stack at this point.")
54            );
55            output.push_str(&asm);
56            return // ditto
57        },
58        /*We'll be writing out the full names of our enum's variants from now on
59        to get proper type hints to show in our IDE, like (x: &u16).
60        Rust-analyzer doesn't deal well with imported enum variants yet,
61        unless we're talking about Options and Results - those are built-in. */
62        BrainfuckInstr::PointerSub(x) => {
63            let asm = format!("sub rsi,{x}\n", x = x);
64            output.push_str(&asm);
65            return
66        },
67        BrainfuckInstr::PointerAdd(x) => {
68            let asm = format!("add rsi,{x}\n", x = x);
69            output.push_str(&asm);
70            return
71        },
72        BrainfuckInstr::DataSub(x) => {
73            let asm = format!("sub byte [rsi],{x}\n", x = x);
74            output.push_str(&asm);
75            return
76        },
77        BrainfuckInstr::DataAdd(x) => {
78            let asm = format!("add byte [rsi],{x}\n", x = x);
79            output.push_str(&asm);
80            return
81        },
82        BrainfuckInstr::Print(x) => {
83            // Copypasted from the PutByte arm, except rdx is now set dynamically
84            let asm = format!("mov rdx,{x}\nmov rdi,1\nmov rax,1\nsyscall\n", x = x);
85            output.push_str(&asm);
86            return
87        }
88    });
89    output.push('\n');
90}