use crate::api::console::Style;
use crate::api::fs;
use crate::api::io;
use crate::api::process::ExitCode;
use alloc::vec::Vec;
use chumsky::prelude::*;
const TAPE_LEN: usize = 30_000;
fn read_byte() -> u8 {
io::stdin().read_char().unwrap_or('\0') as u8
}
#[derive(Clone, Debug, PartialEq)]
enum Instr {
Left, Right,
Incr, Decr,
Read, Write,
Loop(Vec<Self>),
}
fn parser<'a>() -> impl Parser<'a, &'a str, Vec<Instr>, extra::Err<Rich<'a, char>>> {
let comment = none_of("<>+-,.[]").ignored();
recursive(|bf| choice((
just('<').to(Instr::Left),
just('>').to(Instr::Right),
just('+').to(Instr::Incr),
just('-').to(Instr::Decr),
just(',').to(Instr::Read),
just('.').to(Instr::Write),
bf.delimited_by(just('['), just(']')).map(Instr::Loop),
)).padded_by(comment.repeated()).repeated().collect())
}
fn eval(ast: &[Instr], ptr: &mut usize, tape: &mut [u8; TAPE_LEN]) {
for sym in ast {
match sym {
Instr::Left => *ptr = (*ptr + TAPE_LEN - 1).rem_euclid(TAPE_LEN),
Instr::Right => *ptr = (*ptr + 1).rem_euclid(TAPE_LEN),
Instr::Incr => tape[*ptr] = tape[*ptr].wrapping_add(1),
Instr::Decr => tape[*ptr] = tape[*ptr].wrapping_sub(1),
Instr::Read => tape[*ptr] = read_byte(),
Instr::Write => print!("{}", tape[*ptr] as char),
Instr::Loop(ast) => {
while tape[*ptr] != 0 {
eval(ast, ptr, tape)
}
}
}
}
}
fn pos(buf: &str, i: usize) -> (usize, usize) {
let mut col = 1;
let mut row = 1;
let mut j = 0;
for line in buf.lines() {
let n = line.len();
if i < j + n {
col = i - j + 1;
break;
}
j += n + 1;
row += 1;
}
(row, col)
}
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
if args.len() != 2 {
help();
return Err(ExitCode::UsageError);
}
if args[1] == "-h" || args[1] == "--help" {
help();
return Ok(());
}
let error = Style::color("red");
let reset = Style::reset();
let path = args[1];
if let Ok(buf) = fs::read_to_string(path) {
match parser().parse(&buf).into_result() {
Ok(ast) => eval(&ast, &mut 0, &mut [0; TAPE_LEN]),
Err(errs) => errs.into_iter().for_each(|e| {
let (row, col) = pos(&buf, e.span().start);
error!("Unexpected token at {path}:{row}:{col}");
let line = buf.lines().nth(row - 1).unwrap();
let space = " ".repeat(col - 1);
let arrow = "^".repeat(e.span().end - e.span().start);
let reason = "unexpected token";
eprintln!("\n{line}\n{space}{error}{arrow} {reason}{reset}");
})
};
Ok(())
} else {
error!("Could not read '{}'", path);
Err(ExitCode::Failure)
}
}
fn help() {
let csi_option = Style::color("aqua");
let csi_title = Style::color("yellow");
let csi_reset = Style::reset();
println!(
"{}Usage:{} brainfuck {}<path>{}",
csi_title, csi_reset, csi_option, csi_reset
);
}
#[test_case]
fn test_parser() {
use alloc::vec;
let src = "+++++[-] Increment a cell five times then loop to clear it";
let ast = vec![
Instr::Incr, Instr::Incr, Instr::Incr, Instr::Incr, Instr::Incr,
Instr::Loop(vec![Instr::Decr])
];
assert_eq!(parser().parse(src).into_result(), Ok(ast));
}