commit 060e00daff6f600431229456c2c9a576b8a58307 Author: Geoff Date: Wed Aug 17 21:53:23 2022 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42fa8bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +*.bf +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e0d955b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[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] +clap = { version = "3.2.17", features = ["derive"] } +atty = "0.2.14" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f0170a0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,149 @@ +use clap::CommandFactory; +use clap::Parser; +use std::io::Write; +use std::{path::PathBuf, io::{self, Read, BufReader, BufRead}}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(value_parser, value_name = "FILE")] + file: Option, + + #[clap(short, long, action)] + interactive: bool, + + #[clap(short, long, action)] + verbose: bool, +} + +fn main() -> Result<(), io::Error> { + //clap magic + let args = Args::parse(); + let mut cmd = Args::command(); + + let mut reader: Box = match &args.file { + None => { + if atty::is(atty::Stream::Stdin) && !args.interactive { + //special case when there is no piped file + //and --interactive is not specified + //display the help and exit + cmd.print_help()?; + return Ok(()); + } + Box::new(BufReader::new(io::stdin())) + }, + Some(filepath) => Box::new(BufReader::new(std::fs::File::open(filepath)?)) + }; + + //init + let actions = std::collections::HashSet::from(['>', '<', '+', '-', '.', ',', '[', ']']); + let mut memory = [0_u8; 30000]; + let mut pointer = 0; + let mut instructions_str: String = "".into(); + let mut i = 0; //instructions index + + if args.interactive && args.file.is_some() { + reader.read_to_string(&mut instructions_str)?; + reader = Box::new(BufReader::new(io::stdin())); + } else if !args.interactive { + reader.read_to_string(&mut instructions_str)?; + } + + loop { + //filtering instructions to only contain brainfuck code (optimization) + let instructions = instructions_str.chars() + .filter(|c| actions.contains(c)) + .collect::>(); + + 'reading_instructions: while i < instructions.len() { + match instructions[i] { + '>' => pointer += 1, + '<' => pointer -= 1, + '+' => memory[pointer] += 1, + '-' => memory[pointer] -= 1, + '.' => print!("{}", memory[pointer] as char), + ',' => { + let mut input = "".into(); + print!("Enter input (one byte only): "); + io::stdout().flush()?; + let mut handle = io::stdin().lock(); + handle.read_line(&mut input)?; + + if input.len() > 0 { + memory[pointer] = input.chars().nth(0).unwrap() as u8; + } + }, + '[' => + if memory[pointer] == 0 { + let mut pile = 1; + while i < instructions.len() && pile != 0 { + i += 1; + + match instructions[i] { + '[' => pile += 1, + ']' => pile -= 1, + _ => () + } + } + } + ']' => + if memory[pointer] != 0 { + let mut pile = 1; + while i > 0 { + match instructions[i-1] { + ']' => pile += 1, + '[' => pile -= 1, + _ => () + } + + if pile == 0 { break; } + i -= 1; + } + continue 'reading_instructions; + } + + _ => () + } + + i += 1; + } + + if args.verbose || args.interactive { + print_mem(&memory); + println!("pointer: {} (0x{:02x})", &pointer, &memory[pointer]); + println!(); + } + + if !args.interactive { break; } + + print!("~ "); + io::stdout().flush()?; + reader.read_line(&mut instructions_str)?; + } + + Ok(()) +} + +fn print_mem(memory: &[u8]) { + let mut i = 0; + let mut s = String::new(); + let mut z_counter = 0; + let z_limit = 3; + + while i < memory.len() { + if memory[i] == 0 { + z_counter += 1; + } else { + z_counter = 0; + } + if z_counter == z_limit { + s += ".. "; + + } else if z_counter < z_limit { + s += &format!("{:02x} ", memory[i]); + } + i += 1; + } + println!(); + println!("-- memory (hex) --\n{}", s); +}