Skip to content

Instantly share code, notes, and snippets.

@mediocregopher
Created June 16, 2016 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mediocregopher/69a569c998d2b2147dfadc795415eff2 to your computer and use it in GitHub Desktop.
Save mediocregopher/69a569c998d2b2147dfadc795415eff2 to your computer and use it in GitHub Desktop.
Simple implementation of a brainfuck interpreter in rust
/*
[dependencies]
clap = "2"
*/
extern crate clap;
use clap::{Arg, App};
use std::num::Wrapping;
use std::io::Read;
use std::fs::File;
const TAPE_SIZE: usize = 64;
struct Tape(Vec<Option<Box<[u8;TAPE_SIZE]>>>);
/// Implements an infinite tape of values, each being one byte in size. Data is allocated sparsely
/// on the heap
impl Tape {
fn new() -> Tape {
Tape(Vec::new())
}
fn indices(&self, i: u64) -> (usize, usize) {
let vec_i = (i / (TAPE_SIZE as u64)) as usize;
let inner_i = (i % (TAPE_SIZE as u64)) as usize;
(vec_i, inner_i)
}
fn get(&self, i: u64) -> u8 {
let (vec_i, inner_i) = self.indices(i);
match self.0.get(vec_i) {
None => 0,
Some(&None) => 0,
Some(&Some(ref res)) => res[inner_i],
}
}
fn map<F>(&mut self, i: u64, f: F)
where F : Fn(u8) -> u8 {
let (vec_i, inner_i) = self.indices(i);
while self.0.len() <= vec_i {
self.0.push(None)
}
let opt = &mut self.0[vec_i];
if opt.is_none() {
*opt = Some(Box::new([0;TAPE_SIZE]));
}
if let Some(ref mut p) = *opt {
p[inner_i] = f(p[inner_i]);
}
}
}
// TODO these could be made into one generic function
// followup: fuck it
fn prev_matching_bracket(src: &Vec<u8>, mut ir: usize) -> usize {
let mut bc = 1;
loop {
ir = ir - 1;
// don't bother comparing for 0, it'll panic due to underflow
if src[ir] == '[' as u8 {
bc = bc - 1;
} else if src[ir] == ']' as u8 {
bc = bc + 1;
}
if bc == 0 {
return ir;
}
}
}
fn next_matching_bracket(src: &Vec<u8>, mut ir: usize) -> usize {
let mut bc = 1;
loop {
ir = ir + 1;
if ir >= src.len() { panic!("couldn't find next matching ']'") };
if src[ir] == '[' as u8 {
bc = bc + 1;
} else if src[ir] == ']' as u8 {
bc = bc - 1;
}
if bc == 0 {
return ir;
}
}
}
fn main() {
let matches = App::new("brainrust")
.arg(Arg::with_name("INPUT")
.help("Sets the brainfuck source file to use")
.required(true)
.index(1))
.arg(Arg::with_name("v")
.short("v")
.multiple(true)
.help("Sets the level of verbosity"))
.get_matches();
let input = matches.value_of("INPUT").unwrap();
let mut src = Vec::<u8>::new();
File::open(input).expect("can't open file")
.read_to_end(&mut src).expect("couldn't read source file");
let src = src; // src is immutable once read in
let verbosity = matches.occurrences_of("v");
let mut t = Tape::new();
let mut ir: usize = 0; // instruction pointer
let mut p: u64 = 0; // tape pointer
let mut maxp: u64 = 0; // max value p ever reaches, for final debug output
loop {
let c = src[ir] as char;
match c {
// we wrap these so they wrap around if out-of-bounds
'+' => t.map(p, |i: u8| (Wrapping(i)+Wrapping(1)).0),
'-' => t.map(p, |i: u8| (Wrapping(i)-Wrapping(1)).0),
'>' => p = p + 1,
'<' => p = p - 1,
'.' => print!("{}",t.get(p) as char),
',' => {
let mut buf = [0; 1];
// TODO this just blocks if there's no stdin, should read a 0
match std::io::stdin().read_exact(&mut buf) {
Ok(_) => t.map(p, |_: u8| buf[0]),
Err(_) => {},
}
},
'[' => if t.get(p) == 0 { ir = next_matching_bracket(&src, ir) },
']' => if t.get(p) != 0 { ir = prev_matching_bracket(&src, ir) },
_ => {},
}
if verbosity >= 2 { println!("ir:{} c:{} p:{} t[p]:{}", ir, c, p, t.get(p)); }
if p > maxp {
maxp = p;
}
ir = ir + 1;
if ir >= src.len() {
break
}
}
if verbosity >= 1 {
println!("\nFinal tape:");
for i in 0..maxp+1 {
println!("t[{}]: {}", i, t.get(i))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment