init; implement bf interpreter with basic controls
This commit is contained in:
commit
0f2094ede3
5 changed files with 256 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -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"
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
@ -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]
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hard_tabs = true
|
239
src/main.rs
Normal file
239
src/main.rs
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
use std::{env, fs, io::stdin, process::exit};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct StateMachine {
|
||||||
|
memory: Vec<u8>,
|
||||||
|
mem_ptr: usize,
|
||||||
|
program: Vec<DebugCommand>,
|
||||||
|
program_ptr: usize,
|
||||||
|
output: Vec<u8>,
|
||||||
|
input: Vec<u8>,
|
||||||
|
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<DebugCommand>, input: Vec<u8>) -> 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<DebugCommand> {
|
||||||
|
let mut out: Vec<DebugCommand> = 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(_) => ']',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue