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
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(())
+}