bitcalc 0.2.0

A calculator with bit operations
mod ast;
mod eval;
mod lexer;
mod parser;

use std::fmt::{Binary, Display, LowerHex};

use num_traits::{CheckedRem, CheckedShl, CheckedShr, PrimInt, ToBytes, Zero};
use rustyline::DefaultEditor;
use rustyline::error::ReadlineError;

use crate::{ast::Expr, parser::parse};

mod colors {
    use std::fmt::Display;

    pub const RED: Seq = Seq(31);
    pub const GREEN: Seq = Seq(32);
    pub const YELLOW: Seq = Seq(33);
    pub const BLUE: Seq = Seq(34);
    pub const PURPLE: Seq = Seq(35);
    pub const RESET: Seq = Seq(0);
    pub const GRAY: Seq = Seq(2);
    pub const BOLD: Seq = Seq(1);
    pub const UNDERLINE: Seq = Seq(4);

    pub struct Seq(u8);

    impl Display for Seq {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "\x1B[{}m", self.0)
        }
    }
}

use colors::*;

enum Mode {
    U8,
    U16,
    U32,
    U64,
}

fn main() {
    let mut line_editor = DefaultEditor::new().unwrap();

    let mut mode = Mode::U64;

    println!("Welcome to bitcalc!");
    println!("Type 'help' for available operations.");

    let mut answers = Vec::new();

    loop {
        let prompt = format!("{GRAY}[{:>3}]{RESET} {BOLD}{GREEN}>{RESET} ", answers.len());
        let line = line_editor.readline(&prompt);
        match line {
            Ok(buffer) => {
                if buffer.trim().is_empty() {
                    continue;
                }
                if buffer == "q" || buffer == "quit" {
                    break;
                }
                if buffer == "h" || buffer == "help" {
                    println!("{UNDERLINE}{BOLD}Special commands{RESET}");
                    println!("  {BLUE}q{RESET}, {BLUE}quit{RESET}  quit the application");
                    println!("  {BLUE}h{RESET}, {BLUE}help{RESET}  display this help message");
                    println!(
                        "  {BLUE}bits=N{RESET}   set the number of bits to work on (8, 16, 32, 64)"
                    );
                    println!();
                    println!("{UNDERLINE}{BOLD}History values{RESET}");
                    println!("  {BLUE}_{RESET}        result of the last successful calculation");
                    println!(
                        "  {BLUE}_N{RESET}       get a value from the history with N being the index"
                    );
                    println!();
                    println!("{UNDERLINE}{BOLD}Arithmetic operators{RESET}");
                    println!("  {BLUE}+{RESET}        add");
                    println!("  {BLUE}-{RESET}        subtract");
                    println!("  {BLUE}*{RESET}        multiply");
                    println!("  {BLUE}/{RESET}        divide");
                    println!("  {BLUE}%{RESET}        remainder");
                    println!("  {BLUE}**{RESET}       exponentiation");
                    println!();
                    println!("{UNDERLINE}{BOLD}Bit manipulation operators{RESET}");
                    println!("  {BLUE}^{RESET}        bitwise exclusive or");
                    println!("  {BLUE}|{RESET}        bitwise or");
                    println!("  {BLUE}&{RESET}        bitwise and");
                    println!("  {BLUE}<<{RESET}       shift left");
                    println!("  {BLUE}>>{RESET}       shift right");
                    println!("  {BLUE}rotl{RESET}     rotate left");
                    println!("  {BLUE}rotr{RESET}     rotate right");
                    println!();
                    println!("{UNDERLINE}{BOLD}Order of operations{RESET}");
                    println!("  {BLUE}**{RESET}");
                    println!("  {BLUE}*{RESET}, {BLUE}/{RESET}, {BLUE}%{RESET}");
                    println!("  {BLUE}+{RESET}, {BLUE}-{RESET}");
                    println!(
                        "  {BLUE}<<{RESET}, {BLUE}>>{RESET}, {BLUE}rotl{RESET}, {BLUE}rotr{RESET}"
                    );
                    println!("  {BLUE}&{RESET}");
                    println!("  {BLUE}^{RESET}");
                    println!("  {BLUE}|{RESET}");
                    println!();
                    println!("Parentheses can be used to group operations.");
                    continue;
                }
                if let Some(rest) = buffer.strip_prefix("bits=") {
                    match rest.parse() {
                        Ok(b) => {
                            mode = match b {
                                8 => Mode::U8,
                                16 => Mode::U16,
                                32 => Mode::U32,
                                64 => Mode::U64,
                                _ => {
                                    println!("Error: number of bits must be 8, 16, 32 or 64.");
                                    continue;
                                }
                            };
                        }
                        Err(e) => {
                            println!("Error: {e}");
                        }
                    }
                    continue;
                }

                let parsed = parse(&buffer);
                match parsed {
                    Ok(expr) => {
                        do_eval(&mode, &mut answers, &expr);
                    }
                    Err(err) => {
                        print_error(&buffer, err);
                    }
                }
            }
            Err(ReadlineError::Interrupted | ReadlineError::Eof) => {
                break;
            }
            x => {
                println!("Event: {:?}", x);
            }
        }
    }
}

fn do_eval(mode: &Mode, answers: &mut Vec<u64>, expr: &Expr) {
    match mode {
        Mode::U8 => eval_and_print::<u8>(answers, expr),
        Mode::U16 => eval_and_print::<u16>(answers, expr),
        Mode::U32 => eval_and_print::<u32>(answers, expr),
        Mode::U64 => eval_and_print::<u64>(answers, expr),
    }
}

fn eval_and_print<
    I: Zero
        + PrimInt
        + TryFrom<u64>
        + TryInto<u64>
        + TryInto<u32>
        + TryInto<usize>
        + CheckedRem
        + CheckedShl
        + CheckedShr
        + ToBytes
        + Display
        + LowerHex
        + Binary,
>(
    answers: &mut Vec<u64>,
    expr: &Expr,
) {
    let res = eval::eval::<I>(answers, expr);
    match res {
        Ok(x) => {
            let x_bytes = x.to_be_bytes();

            let printed_hex: Vec<_> = x_bytes
                .as_ref()
                .iter()
                .map(|x| format!("{:_>8}", format!("{x:02x}")))
                .collect();
            let printed_hex = printed_hex.join("_");

            let (hex_dim, hex_bright) = match printed_hex.find(|c| c != '_' && c != '0') {
                Some(x) => printed_hex.split_at(x),
                None => (printed_hex.as_ref(), ""),
            };

            let printed_bin: Vec<_> = x_bytes
                .as_ref()
                .iter()
                .map(|x| format!("{x:08b}"))
                .collect();
            let printed_bin = printed_bin.join("_");

            let (bin_dim, bin_bright) = match printed_bin.find(|c| c != '_' && c != '0') {
                Some(x) => printed_bin.split_at(x),
                None => (printed_bin.as_ref(), ""),
            };

            println!();
            println!("  {GRAY}{expr}{RESET} = {BLUE}{x}{RESET}");
            println!();
            println!("  Hex: {GRAY}0x{hex_dim}{RESET}{BLUE}{hex_bright}{RESET}");
            println!("  Bin: {GRAY}0b{bin_dim}{RESET}{BLUE}{bin_bright}{RESET}");
            println!();

            answers.push(x.try_into().ok().unwrap());
        }
        Err(e) => {
            println!("{RED}Error:{RESET} {e}");
        }
    }
}

fn print_error(input: &str, err: parser::ParseError) {
    use ariadne::{ColorGenerator, Label, Report, ReportKind, Source};

    let mut colors = ColorGenerator::new();

    // Generate & choose some colours for each of our elements
    let a = colors.next();

    let mut builder =
        Report::build(ReportKind::Error, ("input", 0..0)).with_message(err.kind().to_string());

    if let Some(span) = err.span() {
        builder = builder.with_label(
            Label::new(("input", span.clone()))
                .with_message(err.kind().to_string())
                .with_color(a),
        );
    }

    builder
        .finish()
        .print(("input", Source::from(input)))
        .unwrap();
}