commit 0f2094ede3dad9bee6d5c7cdbc9cc2aa4aaa0f9e Author: CrispyPin Date: Sun Dec 3 00:59:22 2023 +0100 init; implement bf interpreter with basic controls diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e2fc507 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "brainfuck" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a1564a8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "brainfuck" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e90abe3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,239 @@ +use std::{env, fs, io::stdin, process::exit}; + +#[derive(Debug)] +struct StateMachine { + memory: Vec, + mem_ptr: usize, + program: Vec, + program_ptr: usize, + output: Vec, + input: Vec, + input_ptr: usize, + state: State, +} + +#[derive(Debug, Default, PartialEq)] +enum State { + #[default] + Running, + TooFarLeft, + EndOfProgram, +} + +#[derive(Debug)] +struct DebugCommand { + command: Command, + line_number: usize, + column: usize, +} + +// #[derive(Debug)] +// struct FastCommand { +// command: Command, +// count: u8, +// } + +#[derive(Debug, Clone, Copy)] +enum Command { + Inc, + Dec, + Right, + Left, + Read, + Write, + BeginLoop(usize), + EndLoop(usize), +} + +fn main() { + let args: Vec<_> = env::args().collect(); + if args.len() <= 1 { + println!("usage: brainfuck source_file input_file"); + exit(0); + } + let filename = &args[1]; + let source = fs::read_to_string(filename).unwrap_or_else(|err| { + println!("Error reading file: {err}"); + exit(1); + }); + let input_data = args + .get(2) + .map(|path| { + fs::read(path).unwrap_or_else(|err| { + println!("Error reading file: {err}"); + exit(1); + }) + }) + .unwrap_or_default(); + + let program = parse(&source); + + for c in &program { + print!("{}", c.command.char()); + } + println!(); + // dbg!(&code_dbg); + let mut state_machine = StateMachine::new(program, input_data); + loop { + println!("{:?}", state_machine.memory); + println!("{:?}", state_machine.state); + println!("output: {}", String::from_utf8_lossy(&state_machine.output)); + let mut action = String::new(); + stdin().read_line(&mut action).unwrap(); + action = action.trim().to_owned(); + if action.starts_with("step ") { + if let Ok(num) = action[5..].trim().parse() { + state_machine.step(num); + } + } + match action.as_str() { + "step" => state_machine.step_once(), + "run" => state_machine.run(), + "exit" | "quit" => break, + _ => (), + } + } +} + +impl StateMachine { + fn new(program: Vec, input: Vec) -> Self { + Self { + memory: vec![0], + mem_ptr: 0, + program, + program_ptr: 0, + output: Vec::new(), + input, + input_ptr: 0, + state: State::Running, + } + } + + fn step(&mut self, num: usize) { + for _ in 0..num { + self.step_once(); + if self.state != State::Running { + break; + } + } + } + + fn run(&mut self) { + while self.state == State::Running { + self.step_once(); + } + } + + fn step_once(&mut self) { + if self.program_ptr >= self.program.len() { + self.state = State::EndOfProgram; + } + if self.state != State::Running { + return; + } + let command = self.program[self.program_ptr].command; + match command { + Command::Inc => self.memory[self.mem_ptr] = self.memory[self.mem_ptr].wrapping_add(1), + Command::Dec => self.memory[self.mem_ptr] = self.memory[self.mem_ptr].wrapping_sub(1), + Command::Right => { + self.mem_ptr += 1; + if self.mem_ptr >= self.memory.len() { + self.memory.push(0); + } + } + Command::Left => { + if self.mem_ptr == 0 { + self.state = State::TooFarLeft; + } else { + self.mem_ptr -= 1; + } + } + Command::Read => { + if self.input_ptr < self.input.len() { + self.memory[self.mem_ptr] = self.input[self.input_ptr]; + self.input_ptr += 1; + } else { + self.memory[self.mem_ptr] = 0; + } + } + Command::Write => self.output.push(self.memory[self.mem_ptr]), + Command::BeginLoop(end_of_loop) => { + if self.memory[self.mem_ptr] == 0 { + self.program_ptr = end_of_loop; + } + } + Command::EndLoop(start_of_loop) => { + if self.memory[self.mem_ptr] != 0 { + self.program_ptr = start_of_loop; + } + } + } + + self.program_ptr += 1; + } +} + +fn parse(source_text: &str) -> Vec { + let mut out: Vec = Vec::new(); + let mut loop_starts = Vec::new(); + for (line_number, line) in source_text + .lines() + .enumerate() + .map(|(num, line)| (num + 1, line)) + { + for (column, char) in line.chars().enumerate() { + let cmd = match char { + '+' => Command::Inc, + '-' => Command::Dec, + '>' => Command::Right, + '<' => Command::Left, + ',' => Command::Read, + '.' => Command::Write, + '[' => { + loop_starts.push(out.len()); + Command::BeginLoop(usize::MAX) + } + ']' => { + if loop_starts.is_empty() { + println!("Parser error: no opening bracket for closing bracket at {line_number}:{column}"); + exit(1); + } + let last_loop_start = loop_starts.pop().unwrap(); + out[last_loop_start].command = Command::BeginLoop(out.len()); + + Command::EndLoop(last_loop_start) + } + _ => continue, + }; + out.push(DebugCommand { + command: cmd, + line_number, + column, + }); + } + } + if let Some(loop_start_index) = loop_starts.pop() { + let loop_start = &out[loop_start_index]; + println!( + "Parser error: no matching closing bracket for open bracket at {}:{}", + loop_start.line_number, loop_start.column + ); + exit(1); + } + out +} + +impl Command { + fn char(&self) -> char { + match self { + Command::Inc => '+', + Command::Dec => '-', + Command::Right => '>', + Command::Left => '<', + Command::Read => ',', + Command::Write => '.', + Command::BeginLoop(_) => '[', + Command::EndLoop(_) => ']', + } + } +}