interactive brainfuck repl

This commit is contained in:
nora 2021-05-03 18:06:46 +02:00
parent 5cc058267b
commit c17bea08e0
7 changed files with 168 additions and 43 deletions

View file

@ -1,13 +1,16 @@
use crate::interpreter::optimized::PrintMode;
use std::str::Chars;
pub mod simple; pub mod simple;
pub mod parsed; pub mod parsed;
pub mod optimized; pub mod optimized;
const MEM_SIZE: usize = 0xFFFF; pub const MEM_SIZE: usize = 0xFFFF;
type Memory = [u8; MEM_SIZE]; pub type Memory = [u8; MEM_SIZE];
#[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Clone)] #[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Clone)]
enum Statement { pub enum Statement {
Inc, Inc,
Dec, Dec,
R, R,
@ -20,11 +23,11 @@ enum Statement {
const ALLOWED_CHARS: [char; 8] = ['>', '<', '+', '-', '.', ',', '[', ']']; const ALLOWED_CHARS: [char; 8] = ['>', '<', '+', '-', '.', ',', '[', ']'];
fn minify(code: &str) -> String { pub fn minify(code: &str) -> String {
code.chars().filter(|c| ALLOWED_CHARS.contains(c)).collect() code.chars().filter(|c| ALLOWED_CHARS.contains(c)).collect()
} }
fn parse(chars: Vec<char>, direct_print: bool) -> Vec<Statement> { pub fn parse(chars: Chars, print_mode: PrintMode) -> Vec<Statement> {
let mut loop_stack = vec![vec![]]; let mut loop_stack = vec![vec![]];
for c in chars { for c in chars {
@ -34,10 +37,9 @@ fn parse(chars: Vec<char>, direct_print: bool) -> Vec<Statement> {
'>' => loop_stack.last_mut().unwrap().push(Statement::R), '>' => loop_stack.last_mut().unwrap().push(Statement::R),
'<' => loop_stack.last_mut().unwrap().push(Statement::L), '<' => loop_stack.last_mut().unwrap().push(Statement::L),
'.' => { '.' => {
if direct_print { match print_mode {
loop_stack.last_mut().unwrap().push(Statement::DOut) PrintMode::ToString => loop_stack.last_mut().unwrap().push(Statement::Out),
} else { PrintMode::DirectPrint => loop_stack.last_mut().unwrap().push(Statement::DOut)
loop_stack.last_mut().unwrap().push(Statement::Out)
} }
} }
',' => loop_stack.last_mut().unwrap().push(Statement::In), ',' => loop_stack.last_mut().unwrap().push(Statement::In),

View file

@ -24,7 +24,7 @@ enum ExStatement {
Loop(Vec<ExStatement>), Loop(Vec<ExStatement>),
SetNull, SetNull,
Repeat(Box<ExStatement>, usize), Repeat(Box<ExStatement>, usize),
ForLoop(usize, Box<ExStatement>), _ForLoop(usize, Box<ExStatement>),
} }
impl From<Statement> for ExStatement { impl From<Statement> for ExStatement {
@ -46,7 +46,7 @@ impl From<Statement> for ExStatement {
#[derive(Debug)] #[derive(Debug)]
pub struct BfErr { pub struct BfErr {
msg: &'static str msg: &'static str,
} }
impl BfErr { impl BfErr {
@ -64,10 +64,15 @@ impl Display for BfErr {
impl Error for BfErr {} impl Error for BfErr {}
pub fn run(pgm: &str, direct_print: bool) -> Result<String, BfErr> { pub enum PrintMode {
ToString,
DirectPrint,
}
pub fn run(pgm: &str, print_mode: PrintMode) -> Result<String, BfErr> {
let pgm = minify(pgm); let pgm = minify(pgm);
if pgm.is_empty() { return Err(BfErr::new("no program found")); }; if pgm.is_empty() { return Err(BfErr::new("no program found")); };
let pgm = parse(pgm.chars().collect(), direct_print); let pgm = parse(pgm.chars(), print_mode);
let pgm = optimize(&pgm); let pgm = optimize(&pgm);
let out = interpret(&pgm); let out = interpret(&pgm);
Ok(out) Ok(out)
@ -177,7 +182,7 @@ fn execute(statement: &ExStatement, mem: &mut Memory, pointer: &mut usize, out:
} }
} }
} }
ExStatement::ForLoop(offset, statement) => { ExStatement::_ForLoop(offset, statement) => {
*pointer += offset; *pointer += offset;
while mem[*pointer - offset] != 0 { while mem[*pointer - offset] != 0 {
execute(statement, mem, pointer, out); execute(statement, mem, pointer, out);

View file

@ -7,12 +7,12 @@ use crate::interpreter::optimized::ExStatement;
/// ///
/// Replace this: `[>>x<<-]` or `[->>x<<]` with `WhileAdd(2, x)` /// Replace this: `[>>x<<-]` or `[->>x<<]` with `WhileAdd(2, x)`
fn for_loop(to_test: ExStatement) -> ExStatement { fn _for_loop(to_test: ExStatement) -> ExStatement {
match to_test { match to_test {
ExStatement::Loop(v) => { ExStatement::Loop(v) => {
match v[..] { match v[..] {
[ExStatement::R, ExStatement::Inc, ExStatement::L, ExStatement::Dec] => { [ExStatement::R, ExStatement::Inc, ExStatement::L, ExStatement::Dec] => {
ExStatement::ForLoop(1, Box::from(ExStatement::Inc)) ExStatement::_ForLoop(1, Box::from(ExStatement::Inc))
} }
_ => ExStatement::Loop(v) _ => ExStatement::Loop(v)
} }
@ -26,18 +26,18 @@ fn for_loop(to_test: ExStatement) -> ExStatement {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::interpreter::optimized::ExStatement::{Out, Loop, Inc, R, L, Dec, ForLoop}; use crate::interpreter::optimized::ExStatement::{Out, Loop, Inc, R, L, Dec, _ForLoop};
use crate::interpreter::optimized::patterns::for_loop; use crate::interpreter::optimized::patterns::_for_loop;
#[test] #[test]
fn for_loop_false() { fn for_loop_false() {
let statement = Loop(vec![Out, Inc]); let statement = Loop(vec![Out, Inc]);
assert_eq!(statement.clone(), for_loop(statement)); assert_eq!(statement.clone(), _for_loop(statement));
} }
#[test] #[test]
fn for_loop_simplest() { fn for_loop_simplest() {
let statement = Loop(vec![R, Inc, L, Dec]); let statement = Loop(vec![R, Inc, L, Dec]);
assert_eq!(ForLoop(1, Box::from(Inc)), for_loop(statement)); assert_eq!(_ForLoop(1, Box::from(Inc)), _for_loop(statement));
} }
} }

View file

@ -8,13 +8,16 @@
use std::io::{Read, stdin, Write}; use std::io::{Read, stdin, Write};
use crate::interpreter::{MEM_SIZE, Memory, minify, parse, Statement}; use crate::interpreter::{MEM_SIZE, Memory, minify, parse, Statement};
use crate::interpreter::optimized::PrintMode;
use crate::repl::BrainfuckState;
pub fn run(pgm: &str) -> String { pub fn run(pgm: &str) -> String {
let pgm = minify(pgm); let pgm = minify(pgm);
let pgm = parse(pgm.chars().collect(), false); let pgm = parse(pgm.chars(), PrintMode::ToString);
interpret(&pgm) interpret(&pgm)
} }
fn interpret(pgm: &[Statement]) -> String { fn interpret(pgm: &[Statement]) -> String {
let mut out = String::new(); let mut out = String::new();
let mut pointer: usize = 0; let mut pointer: usize = 0;
@ -27,6 +30,12 @@ fn interpret(pgm: &[Statement]) -> String {
out out
} }
pub fn interpret_with_state(pgm: &[Statement], state: &mut BrainfuckState) {
for s in pgm {
execute(s, &mut state.memory, &mut state.pointer, &mut String::new())
}
}
fn execute(statement: &Statement, mem: &mut Memory, pointer: &mut usize, out: &mut String) { fn execute(statement: &Statement, mem: &mut Memory, pointer: &mut usize, out: &mut String) {
match statement { match statement {
Statement::R => if *pointer == MEM_SIZE - 1 { *pointer = 0 } else { *pointer += 1 }, Statement::R => if *pointer == MEM_SIZE - 1 { *pointer = 0 } else { *pointer += 1 },

View file

@ -3,37 +3,35 @@ mod repl;
use std::{env, fs}; use std::{env, fs};
use std::time::SystemTime; use std::time::SystemTime;
use crate::repl::start_repl;
use crate::interpreter::optimized::{PrintMode};
use std::error::Error;
fn main() { fn main() {
let path = env::args().nth(1); let path = env::args().nth(1);
let path = match path {
Some(p) => p, match path {
None => { Some(p) => {
println!("Please specify a path"); if let Err(why) = run_program(p) {
return; eprintln!("An error occurred in the program: {}", why)
} }
},
None => start_repl()
}; };
}
fn run_program(path: String) -> Result<(), Box<dyn Error>> {
let program = match fs::read_to_string(path) { let program = match fs::read_to_string(path) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
println!("Error reading file: {}", e); println!("Error reading file: {}", e);
return; return Err(Box::from(e));
} }
}; };
let start_time = SystemTime::now();
run(program); let out = interpreter::optimized::run(&*program, PrintMode::DirectPrint)?;
} let duration = start_time.elapsed()?;
println!("{}\nFinished execution. Took {}ms", out, duration.as_millis());
fn run(program: String) { Ok(())
/*
let start1 = SystemTime::now();
let out = interpreter::o1::run(&*program);
let end1 = start1.elapsed().unwrap();*/
let start2 = SystemTime::now();
let out2 = interpreter::optimized::run(&*program, false).unwrap();
let end2 = start2.elapsed().unwrap();
//assert_eq!(out, out2);
//println!("{}\nFinished execution. Took o1: 18008ms (for hanoi), o2: {}ms", out2/*, end1.as_millis()*/, end2.as_millis());
println!("{}\nFinished execution. Took {}ms", out2/*, end1.as_millis()*/, end2.as_millis());
} }

View file

View file

@ -0,0 +1,111 @@
use std::fmt::{Display, Formatter};
use std::fmt;
use std::io::{stdin, stdout, Write};
use crate::interpreter::{minify, parse, parsed, Memory, MEM_SIZE};
use crate::interpreter::optimized::PrintMode;
pub struct BrainfuckState {
pub memory: Memory,
pub pointer: usize,
}
impl Display for BrainfuckState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", display_state(self))
}
}
fn display_state(state: &BrainfuckState) -> String {
let start = if state.pointer < 5 {
0
} else if state.pointer > MEM_SIZE - 10 {
MEM_SIZE - 10
} else {
state.pointer - 5
};
format!("{}-\n|{}|\n{}-\n{}|\n{}|\n{}|\n{}-\n {}^^^^",
"----------".repeat(10),
{
let mut out = String::new();
let end = start + 10;
for i in start..end {
out.push_str(&*format!(" {: >5} ", i));
}
out
},
"----------".repeat(10),
"| ".repeat(10),
{
let mut out = String::new();
let end = start + 10;
for i in start..end {
out.push_str(&*format!("| {: >3} ", state.memory[i]));
}
out
},
"| ".repeat(10),
"----------".repeat(10),
" ".repeat(state.pointer - start))
}
pub fn start_repl() {
println!("Brainfuck REPL");
let mut state = BrainfuckState {
memory: [0; MEM_SIZE],
pointer: 0,
};
println!("Enter Brainfuck programs and they will be executed immediatly.");
println!("State is kept.");
println!("{}", state);
loop {
print!(">> ");
stdout().flush().unwrap();
match read_line() {
Ok(s) => {
match &*s {
":q" => break,
":?" | "help" | "?" => print_help(),
":r" => {
reset(&mut state);
println!("{}", state);
},
_ => {
print!("Output: ");
println!();
parse_input(s, &mut state);
println!("{}", state);
}
}
}
Err(why) => println!("Error reading input: {}\nPlease try again.", why)
}
}
}
fn reset(state: &mut BrainfuckState) {
state.pointer = 0;
state.memory = [0; MEM_SIZE];
}
fn print_help() {
println!("Brainfuck REPL help
:q => quit
:? => help
:r => reset state");
}
fn parse_input(pgm: String, state: &mut BrainfuckState) {
let pgm = minify(&*pgm);
let pgm = parse(pgm.chars(), PrintMode::DirectPrint);
parsed::interpret_with_state(&*pgm, state);
}
pub fn read_line() -> Result<String, std::io::Error> {
let mut buf = String::new();
stdin().read_line(&mut buf)?;
buf.pop();
Ok(buf)
}