~tmpod/brainfucc

2b3b2e8c6ca041f794de2cbd965b30bb728e5fd9 — tmpod 1 year, 5 months ago
Implement initial prototype
7 files changed, 213 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.lock
A Cargo.toml
A src/cli.rs
A src/compiler.rs
A src/interpreter.rs
A src/main.rs
A  => .gitignore +4 -0
@@ 1,4 @@
/target
.idea
.nvimsesh
.lite_workspace.lua

A  => Cargo.lock +5 -0
@@ 1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "brainfucc"
version = "0.1.0"

A  => Cargo.toml +9 -0
@@ 1,9 @@
[package]
name = "brainfucc"
version = "0.1.0"
authors = ["Tmpod <tmpod@protonmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

A  => src/cli.rs +1 -0
@@ 1,1 @@


A  => src/compiler.rs +86 -0
@@ 1,86 @@
use std::collections::HashMap;

pub type Word = u8;

// Using 3 letter codes cus it has some resemblance with generic assembly words
// Should probably change it tho
#[derive(Debug)]
pub enum BrainfuckInstruction {
    RGT,
    LFT,
    INC,
    DEC,
    OUT,
    INP,
    JMP,
    RET,
}

#[derive(Debug)]
pub struct CompiledBrainfuck {
    pub name: Option<String>,
    pub instructions: Vec<BrainfuckInstruction>,
    pub jumps: HashMap<usize, usize>,
    pub comments: String,
}

pub fn compile_into_tuple(
    source: &str,
) -> (Vec<BrainfuckInstruction>, HashMap<usize, usize>, String) {
    let mut instructions = Vec::new();
    let mut jumps = HashMap::new();
    let mut jump_stack = Vec::new();
    let mut comments = String::new();

    for c in source.chars() {
        let inst = match c {
            '<' => BrainfuckInstruction::RGT,
            '>' => BrainfuckInstruction::LFT,
            '+' => BrainfuckInstruction::INC,
            '-' => BrainfuckInstruction::DEC,
            '.' => BrainfuckInstruction::OUT,
            ',' => BrainfuckInstruction::INP,
            '[' => {
                jump_stack.push(instructions.len());
                BrainfuckInstruction::JMP
            }
            ']' => {
                let open = jump_stack.pop().unwrap();
                jumps.insert(open, instructions.len());
                jumps.insert(instructions.len(), open);
                BrainfuckInstruction::RET
            }
            _ => {
                comments.push(c);
                continue;
            }
        };
        instructions.push(inst);
    }

    (instructions, jumps, comments)
}

pub fn compile(source: &str) -> CompiledBrainfuck {
    let compiled = compile_into_tuple(source);
    CompiledBrainfuck {
        name: Option::None,
        instructions: compiled.0,
        jumps: compiled.1,
        comments: compiled.2,
    }
}

pub fn compile_from_string(source: String) -> CompiledBrainfuck {
    compile(source.as_str())
}

pub fn compile_named(source: &str, name: &str) -> CompiledBrainfuck {
    let compiled = compile_into_tuple(source.into());
    CompiledBrainfuck {
        name: Option::Some(name.into()),
        instructions: compiled.0,
        jumps: compiled.1,
        comments: compiled.2,
    }
}

A  => src/interpreter.rs +88 -0
@@ 1,88 @@
use std::io::Read;

use crate::compiler::Word;
use crate::compiler::{BrainfuckInstruction, CompiledBrainfuck};

const DEFAULT_TAPE_SIZE: usize = 30_000;

type InputHook = fn() -> Word;
type OutputHook = fn(Word) -> ();

pub fn execute(
    compiled: CompiledBrainfuck,
    tape_size: usize,
    input_hook: InputHook,
    output_hook: OutputHook,
) {
    let mut tape: Vec<Word> = vec![0; tape_size];
    let mut mem_ptr = 0_usize;
    let mut inst_ptr = 0_usize;

    while inst_ptr < compiled.instructions.len() {
        eprint!("{} ", inst_ptr);
        let inst = &compiled.instructions[inst_ptr];
        match inst {
            BrainfuckInstruction::RGT => {
                // Wrap around
                mem_ptr += 1;
                if mem_ptr == tape_size {
                    mem_ptr = 0;
                }
            }
            BrainfuckInstruction::LFT => {
                if mem_ptr == 0 {
                    mem_ptr = tape_size - 1;
                } else {
                    mem_ptr -= 1;
                }
            }
            BrainfuckInstruction::INC => {
                tape[mem_ptr] = tape[mem_ptr].wrapping_add(1);
            }
            BrainfuckInstruction::DEC => {
                tape[mem_ptr] = tape[mem_ptr].wrapping_sub(1);
            }
            BrainfuckInstruction::OUT => {
                output_hook(tape[mem_ptr]);
            }
            BrainfuckInstruction::INP => {
                tape[mem_ptr] = input_hook();
            }
            BrainfuckInstruction::JMP => {
                if tape[mem_ptr] == 0 {
                    eprint!("(j) ");
                    inst_ptr = compiled.jumps[&inst_ptr];
                }
            }
            BrainfuckInstruction::RET => {
                if tape[mem_ptr] != 0 {
                    eprint!("(r) ");
                    inst_ptr = compiled.jumps[&inst_ptr]
                }
            }
        }

        inst_ptr += 1;
    }
}

pub fn default_input_hook() -> u8 {
    std::io::stdin()
        .bytes()
        .next()
        .expect("Could not read from stdin!")
        .expect("Could not read a byte!")
}

pub fn default_output_hook(out: u8) {
    print!("{}", out as char);
}

pub fn execute_default(compiled: CompiledBrainfuck) {
    execute(
        compiled,
        DEFAULT_TAPE_SIZE,
        default_input_hook,
        default_output_hook,
    )
}

A  => src/main.rs +20 -0
@@ 1,20 @@
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::Read;

mod cli;
mod compiler;
mod interpreter;

fn main() -> Result<(), Box<dyn Error>> {
    let mut src_file = File::open(env::args().skip(1).next().expect("Missing source filename"))?;
    let mut src = String::new();
    src_file.read_to_string(&mut src).unwrap();

    let compiled = crate::compiler::compile_from_string(src);
    eprintln!("Compiled: {:?}", compiled);
    crate::interpreter::execute_default(compiled);

    Ok(())
}