Initial commit

This commit is contained in:
viridian 2024-05-09 18:55:17 +02:00
commit 620db0e5d1
Signed by: viridian
GPG key ID: DCD4DF95CE23FE8C
3 changed files with 681 additions and 0 deletions

160
Cargo.lock generated Normal file
View file

@ -0,0 +1,160 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "brainfuck"
version = "0.1.0"
dependencies = [
"indicatif",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "indicatif"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "unicode-width"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[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]
indicatif = "*"

512
src/main.rs Normal file
View file

@ -0,0 +1,512 @@
use std::{
ops::{Add, Sub},
process::exit,
usize,
};
use indicatif::ProgressStyle;
fn main() {
let args: Vec<String> = std::env::args().collect();
let program = std::fs::read_to_string(args.get(1).unwrap()).unwrap();
let mut machine = Machine::new(program);
if let Some(_) = args.get(2) {
loop {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
for s in input.trim().chars() {
match s {
's' => {
let state = machine.step();
if state == MachineState::EOI {
println!("Reached end of program printing end state");
machine.instruction_pointer -= 1;
machine.dbg();
machine.mem();
machine.code();
exit(0);
}
}
'd' => {
machine.dbg();
}
'm' => {
machine.mem();
}
'i' => {
machine.code();
}
'w' => {
machine.write();
}
'e' => {
machine.execute();
machine.instruction_pointer -= 1;
}
'r' => machine.run_for(),
_ => {
println!("Option unrecognized")
}
}
}
}
} else {
machine.execute();
}
}
#[derive(Debug)]
struct Machine {
instruction_pointer: usize,
data_pointer: usize,
data: Vec<Cell>,
instructions: Vec<Instruction>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
struct Cell {
value: u8,
}
impl Machine {
fn new(program: String) -> Self {
Machine {
instruction_pointer: 0,
data_pointer: 0,
data: vec![Cell { value: 0 }],
instructions: parse(program),
}
}
fn execute(&mut self) {
loop {
match self.step() {
MachineState::Ok => continue,
MachineState::EOI => break,
}
}
}
fn step(&mut self) -> MachineState {
let instruction = self.instructions.get(self.instruction_pointer).unwrap();
match instruction {
Instruction::BINC => self.binc(),
Instruction::BDEC => self.bdec(),
Instruction::PINC => self.pinc(),
Instruction::PDEC => self.pdec(),
Instruction::JIZ(target) => self.jiz(*target),
Instruction::JINZ(target) => self.jinz(*target),
Instruction::OUT => self.out(),
Instruction::IN => self.inp(),
Instruction::DBG => self.dbg(),
Instruction::MEM => self.mem(),
}
self.instruction_pointer += 1;
if self.instruction_pointer + 1 > self.instructions.len() {
MachineState::EOI
} else {
MachineState::Ok
}
}
fn binc(&mut self) {
let cell = *self
.data
.get_mut(self.data_pointer)
.unwrap_or(&mut Cell { value: 0 })
+ Cell { value: 1 };
self.data[self.data_pointer] = cell;
}
fn bdec(&mut self) {
let cell = *self
.data
.get_mut(self.data_pointer)
.unwrap_or(&mut Cell { value: 0 })
- Cell { value: 1 };
self.data[self.data_pointer] = cell;
}
fn pinc(&mut self) {
self.data_pointer += 1;
if self.data.get(self.data_pointer).is_none() {
self.data.push(Cell { value: 0 });
}
}
fn pdec(&mut self) {
if self.data_pointer != 0 {
self.data_pointer -= 1;
}
}
fn jiz(&mut self, target: usize) {
if self
.data
.get(self.data_pointer)
.unwrap_or(&Cell { value: 0 })
.value
== 0
{
self.instruction_pointer = target;
}
}
fn jinz(&mut self, target: usize) {
if self
.data
.get(self.data_pointer)
.unwrap_or(&Cell { value: 0 })
.value
!= 0
{
self.instruction_pointer = target;
}
}
fn out(&self) {
let value = self
.data
.get(self.data_pointer)
.unwrap_or(&Cell { value: 0 })
.value;
let c: char = (value as u8).into();
print!("{c}");
}
fn inp(&mut self) {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let value = *input.into_bytes().first().unwrap() as u8;
let cell = Cell { value };
self.data[self.data_pointer] = cell;
}
fn dbg(&self) {
println!(
"Instruction pointer: {} \nCurrent Instruction: {:?}\nData pointer: {}",
self.instruction_pointer,
self.instructions[self.instruction_pointer],
self.data_pointer
);
}
fn mem(&self) {
println!("Memory: {:?}", self.data);
}
fn code(&self) {
println!("Instructions: {:?}", self.instructions);
}
fn write(&mut self) {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let addres: usize = input.trim().parse().unwrap();
input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let value: u8 = input.trim().parse().unwrap();
let cell = Cell { value };
if self.data.len() <= self.data_pointer {
self.data.insert(addres, cell);
} else {
self.data[addres] = cell;
}
println!("Wrote {value} to {addres}");
}
fn run_for(&mut self) {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let repetitions: usize = input.trim().parse().unwrap();
let mut bar = indicatif::ProgressBar::new(repetitions as u64);
bar.set_style(
ProgressStyle::with_template(
"{bar:40} | {human_pos}/{human_len} | {eta_precise} | {elapsed_precise}",
)
.unwrap(),
);
for _ in 0..repetitions {
bar.inc(1);
match self.step() {
MachineState::Ok => continue,
MachineState::EOI => {
self.instruction_pointer -= 1;
break;
}
}
}
bar.finish();
}
}
impl Add for Cell {
type Output = Cell;
fn add(self, rhs: Self) -> Self::Output {
Cell {
value: self.value + rhs.value,
}
}
}
impl Sub for Cell {
type Output = Cell;
fn sub(self, rhs: Self) -> Self::Output {
if self.value == 0 {
Cell { value: 0 }
} else {
Cell {
value: self.value - rhs.value,
}
}
}
}
#[allow(clippy::upper_case_acronyms)]
#[derive(PartialEq, Eq, Debug)]
enum Instruction {
PINC, // >
PDEC, // <
BINC, // +
BDEC, // -
OUT, // .
IN, // ,
JIZ(usize), // [
JINZ(usize), // ]
DBG, // ? (Special instruction not standart)
MEM, // ! (Special instruction not standart)
}
#[derive(Debug)]
enum ParseError {
NoTargetForJIZ(usize),
NoTargetForJINZ(usize),
}
#[derive(Debug, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
enum MachineState {
Ok,
EOI, // End of Instructions
}
fn parse(program: String) -> Vec<Instruction> {
let mut instruction_vec: Vec<Instruction> = vec![];
for (i, c) in program.chars().enumerate() {
if let Some(instruction) =
match_single_char_populate(&c, &program, i, instruction_vec.len())
{
instruction_vec.push(instruction);
}
}
instruction_vec
}
fn match_single_char_populate(
c: &char,
program: &String,
index: usize,
instruction_index: usize,
) -> Option<Instruction> {
match c {
'>' => Some(Instruction::PINC),
'<' => Some(Instruction::PDEC),
'+' => Some(Instruction::BINC),
'-' => Some(Instruction::BDEC),
'.' => Some(Instruction::OUT),
',' => Some(Instruction::IN),
'[' => Some(Instruction::JIZ(
locate_jmp_target_jiz(program, index, instruction_index).unwrap(),
)),
']' => Some(Instruction::JINZ(
locate_jmp_target_jinz(program, index, instruction_index).unwrap(),
)),
'?' => Some(Instruction::DBG),
'!' => Some(Instruction::MEM),
_ => None,
}
}
fn match_single_char(c: &char) -> Option<Instruction> {
match c {
'>' => Some(Instruction::PINC),
'<' => Some(Instruction::PDEC),
'+' => Some(Instruction::BINC),
'-' => Some(Instruction::BDEC),
'.' => Some(Instruction::OUT),
',' => Some(Instruction::IN),
'[' => Some(Instruction::JIZ(0)),
']' => Some(Instruction::JINZ(0)),
'?' => Some(Instruction::DBG),
'!' => Some(Instruction::MEM),
_ => None,
}
}
fn locate_jmp_target_jiz(
program: &String,
index: usize,
instruction_index: usize,
) -> Result<usize, ParseError> {
let program_vector: Vec<char> = program.chars().collect();
let mut data_pointer = index + 1;
let mut searched_jinz = 1;
let mut instruction_index = instruction_index;
if instruction_index != 0 {
instruction_index -= 1;
}
while searched_jinz != 0 {
if data_pointer == program_vector.len() {
return Err(ParseError::NoTargetForJIZ(index));
}
if let Some(instruction) = match_single_char(program_vector.get(data_pointer).unwrap()) {
match instruction {
Instruction::JIZ(_) => searched_jinz += 1,
Instruction::JINZ(_) => searched_jinz -= 1,
_ => (),
}
instruction_index += 1;
}
data_pointer += 1;
}
Ok(instruction_index)
}
fn locate_jmp_target_jinz(
program: &String,
index: usize,
instruction_index: usize,
) -> Result<usize, ParseError> {
let program_vector: Vec<char> = program.chars().collect();
let mut data_pointer = index - 1;
let mut searched_jiz = 1;
let mut instruction_index = instruction_index - 1;
while searched_jiz != 0 {
if let Some(instruction) = match_single_char(program_vector.get(data_pointer).unwrap()) {
match instruction {
Instruction::JINZ(_) => searched_jiz += 1,
Instruction::JIZ(_) => searched_jiz -= 1,
_ => (),
}
instruction_index -= 1;
} else if data_pointer == 0 {
return Err(ParseError::NoTargetForJINZ(index));
}
data_pointer -= 1;
}
Ok(instruction_index + 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locate_jmp_target_for_jiz_single_jmp() {
let program = String::from("[++>]-");
let index = 0;
let return_value = locate_jmp_target_jiz(&program, index, index).unwrap();
assert_eq!(return_value, 4);
}
#[test]
fn locate_jmp_target_for_jiz_multi_jmp() {
let program = String::from("+[>+[<]-]+");
let index = 1;
let return_value = locate_jmp_target_jiz(&program, index, index).unwrap();
assert_eq!(return_value, 7);
}
#[test]
fn locate_jmp_target_for_jinz_single_jmp() {
let program = String::from("+[>>+]");
let index = 5;
let return_value = locate_jmp_target_jinz(&program, index, index).unwrap();
assert_eq!(return_value, 1);
}
#[test]
fn locate_jmp_target_for_jinz_multi_jmp() {
let program = String::from("+[>[<]>+]");
let index = 8;
let return_value = locate_jmp_target_jinz(&program, index, index).unwrap();
assert_eq!(return_value, 1);
}
#[test]
fn parse_program_no_jmp() {
let program = String::from("+><-.>,");
let program_vec = parse(program);
let correct_output = vec![
Instruction::BINC,
Instruction::PINC,
Instruction::PDEC,
Instruction::BDEC,
Instruction::OUT,
Instruction::PINC,
Instruction::IN,
];
for (index, instruction) in program_vec.iter().enumerate() {
assert_eq!(instruction, correct_output.get(index).unwrap());
}
}
#[test]
fn parse_program_single_jmp() {
let program = String::from("+>[<]-.>,");
let program_vec = parse(program);
let correct_output = vec![
Instruction::BINC,
Instruction::PINC,
Instruction::JIZ(3),
Instruction::PDEC,
Instruction::JINZ(2),
Instruction::BDEC,
Instruction::OUT,
Instruction::PINC,
Instruction::IN,
];
for (index, instruction) in program_vec.iter().enumerate() {
dbg!(&index);
assert_eq!(instruction, correct_output.get(index).unwrap());
}
}
#[test]
fn add_values() {
let program = String::from("+++>++++[<+>-]");
let mut machine = Machine::new(program);
machine.execute();
let cell0 = Cell { value: 7 };
assert_eq!(machine.data[0], cell0);
}
}