yabfi 1.0.1

Yet another brainfuck interpreter.
Documentation
use std::{
    env, fs,
    io::{self, Read},
    process,
};

use yabfi;

fn main() {
    let mut args = env::args();

    _ = args.next().expect("Receiving program name");

    let mode = match args.next() {
        Some(s) => s,
        None => err_with_help(1, "Missing mode".to_string()),
    };

    let input_mode = match args.next() {
        Some(s) => s,
        None => err_with_help(1, "Missing input mode".to_string()),
    };

    let code = match input_mode.as_str() {
        "file" => {
            let input = match args.next() {
                Some(s) => s,
                None => err_with_help(3, "Missing input".to_string()),
            };
            let bytes = match fs::read(input) {
                Ok(bytes) => bytes,
                Err(e) => err_no_help(4, format!("Failed to read file: {e:#?}")),
            };
            match String::from_utf8(bytes) {
                Ok(s) => s,
                Err(e) => err_no_help(6, format!("Code is not utf-8: {e:#?}")),
            }
        }
        "args" => match args.next() {
            Some(s) => s,
            None => err_with_help(3, "Missing input".to_string()),
        },
        "stdi" => read_stdin(),
        _ => {
            err_with_help(2, format!("Invalid input mode: {input_mode}"));
        }
    };

    match mode.as_str() {
        "run" => match yabfi::run(&code) {
            Ok(_) => {}
            Err(e) => println!("{e}"),
        },
        "parse" => match yabfi::parse(&code) {
            Ok(tree) => println!("{tree:#?}"),
            Err(e) => println!("{e}"),
        },
        _ => err_with_help(2, format!("Invalid mode: {mode}")),
    }
}

fn read_stdin() -> String {
    let mut stdin = io::stdin();

    let mut bytes = Vec::new();
    match stdin.read_to_end(&mut bytes) {
        Ok(_) => {}
        Err(e) => err_no_help(5, format!("Failed to read stdin: {e:#?}")),
    };
    match String::from_utf8(bytes) {
        Ok(s) => s,
        Err(e) => err_no_help(6, format!("Code is not utf-8: {e:#?}")),
    }
}

fn err_with_help(code: i32, info: String) -> ! {
    println!("ERR: {info}");
    print_sep();
    print_help();
    process::exit(code);
}

fn err_no_help(code: i32, info: String) -> ! {
    println!("ERR: {info}");
    process::exit(code);
}

fn print_help() {
    let mut args = env::args();

    let program_name = args.next().expect("Receiving program name");

    println!("Format: {program_name} <mode> <in_mode> <input>");
    print_sep();
    println!("<mode> has these possible values:");
    println!("  run: Runs brainfuck code.");
    println!("  parse: Parses brainfuck code and prettyprints syntax tree.");
    print_sep();
    println!("<in_mode> has these possible values:");
    println!("  file: Reads <input> as a file path and interprets its content as bf code.");
    println!("        Incompatible with 't' and 'i'.");
    println!("  args: Reads <input> as brainfuck code and interprets it.");
    println!("        Incompatible with 'f' and 'i'.");
    println!("  stdi: Ignores <input> and interprets stdin as brainfuck code up to EOF.");
    println!("        Incompatible with 't' and 'f'.");
    println!("        This will always throw an IoErr if the program was piped in and has");
    println!("        read (',') instructions.");
}

fn print_sep() {
    println!("==========");
}