ft: restructure with tests

This commit is contained in:
2025-04-22 09:47:45 +09:00
parent 638c2a0e96
commit 694083a446
3 changed files with 107 additions and 69 deletions

1
example/hello_world.bf Normal file
View File

@@ -0,0 +1 @@
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

91
src/interpret.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::io::{self, Read};
pub fn interpret(code: &str) -> Vec<u8> {
let mut memory = [0 as u8; 10_000];
let mut head = 0 as usize;
let mut pc = 0 as usize;
let mut loop_stack = Vec::new();
let mut out = Vec::new();
let code_bytes = code.as_bytes();
while pc < code.len() {
match code_bytes[pc] as char {
'>' => head += 1,
'<' => head -= 1,
'+' => memory[head] = memory[head].wrapping_add(1),
'-' => memory[head] = memory[head].wrapping_sub(1),
'.' => {
print!("{}", memory[head] as char);
out.push(memory[head]);
}
',' => {
let mut input_buf = [0u8; 1];
if io::stdin().read_exact(&mut input_buf).is_ok() {
memory[head] = input_buf[0];
}
}
'[' => {
// loop start
if memory[head] == 0 {
let mut count_brackets = 1;
// find nested brackets
while count_brackets > 0 {
pc += 1;
if pc >= code_bytes.len() {
panic!("Missing closing loop!");
}
match code_bytes[pc] as char {
// closing current loop
']' => count_brackets -= 1,
// nested bracket
'[' => count_brackets += 1,
// ignore rest
_ => {}
}
}
} else {
// save pc to jump back to
loop_stack.push(pc);
}
}
']' => {
// loop terminator
if memory[head] != 0 {
// jump back to loop beginning
if let Some(start_pc) = loop_stack.last() {
pc = *start_pc;
} else {
panic!("Loop stack empty, but still called!")
}
} else {
// loop done, continue normally
loop_stack.pop();
}
}
_ => panic!("Invalid instruction"),
}
pc += 1;
}
print!("\n");
return out;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_h() {
let code = "+++++++++[>++++++++<-]>.";
let out = interpret(code);
assert_eq!(std::str::from_utf8(&out).unwrap(), "H");
}
#[test]
fn test_hello() {
let code = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
let out = interpret(code);
assert_eq!(std::str::from_utf8(&out).unwrap(), "Hello World!\n");
}
}

View File

@@ -1,73 +1,19 @@
use std::io::{self, Read};
use crate::interpret::interpret;
use std::{env, fs, process};
fn interpret(code: &str) {
let mut memory = [0 as u8; 10_000];
let mut head = 0 as usize;
let mut pc = 0 as usize;
let mut loop_stack = Vec::new();
let code_bytes = code.as_bytes();
while pc < code.len() {
match code_bytes[pc] as char {
'>' => head += 1,
'<' => head -= 1,
'+' => memory[head] = memory[head].wrapping_add(1),
'-' => memory[head] = memory[head].wrapping_sub(1),
'.' => print!("{}", memory[head] as char),
',' => {
let mut input_buf = [0u8; 1];
if io::stdin().read_exact(&mut input_buf).is_ok() {
memory[head] = input_buf[0];
}
}
'[' => {
// loop start
if memory[head] == 0 {
let mut count_brackets = 1;
// find nested brackets
while count_brackets > 0 {
pc += 1;
if pc >= code_bytes.len() {
panic!("Missing closing loop!");
}
match code_bytes[pc] as char {
// closing current loop
']' => count_brackets -= 1,
// nested bracket
'[' => count_brackets += 1,
// ignore rest
_ => {}
}
}
} else {
// save pc to jump back to
loop_stack.push(pc);
}
}
']' => {
// loop terminator
if memory[head] != 0 {
// jump back to loop beginning
if let Some(start_pc) = loop_stack.last() {
pc = *start_pc;
} else {
panic!("Loop stack empty, but still called!")
}
} else {
// loop done, continue normally
loop_stack.pop();
}
}
_ => panic!("Invalid instruction"),
}
pc += 1;
}
print!("\n");
}
mod interpret;
fn main() {
// let code = "+++++++++[>++++++++<-]>.";
let code = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
interpret(code);
let args: Vec<_> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <filename>", args[0]);
process::exit(1);
}
let filename = &args[1];
let code = fs::read_to_string(filename).unwrap_or_else(|err| {
panic!("Error reading file {}: {}", filename, err);
});
interpret(&code);
}