bitcalc 0.1.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 reedline::{DefaultPrompt, Reedline, Signal};

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

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

fn main() {
    let mut line_editor = Reedline::create();
    let prompt = DefaultPrompt::new(
        reedline::DefaultPromptSegment::Empty,
        reedline::DefaultPromptSegment::Empty,
    );

    let mut mode = Mode::U64;

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

    loop {
        let sig = line_editor.read_line(&prompt);
        match sig {
            Ok(Signal::Success(buffer)) => {
                if buffer == "h" || buffer == "help" {
                    println!("Special commands:");
                    println!("  h, help  display this help message");
                    println!("  bits=N   set the number of bits to work on (8, 16, 32, 64)");
                    println!();
                    println!("Arithmetic operators:");
                    println!("  +        add");
                    println!("  -        subtract");
                    println!("  *        multiply");
                    println!("  /        divide");
                    println!("  %        remainder");
                    println!("  **       exponentiation");
                    println!();
                    println!("Bit manipulation operators:");
                    println!("  ^        bitwise exclusive or");
                    println!("  |        bitwise or");
                    println!("  &        bitwise and");
                    println!("  <<       shift left");
                    println!("  >>       shift right");
                    println!("  rotl     rotate left");
                    println!("  rotr     rotate right");
                    println!();
                    println!("Order of operations:");
                    println!("  **");
                    println!("  *, /, %");
                    println!("  +, -");
                    println!("  <<, >>, rotl, rotr");
                    println!("  &");
                    println!("  ^");
                    println!("  |");
                    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, &expr);
                    }
                    Err(err) => {
                        print_error(&buffer, err);
                    }
                }
            }
            Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
                break;
            }
            x => {
                println!("Event: {:?}", x);
            }
        }
    }
}

fn do_eval(mode: &Mode, expr: &Expr) {
    println!("Parsed:");
    println!("  {expr}");
    println!();
    match mode {
        Mode::U8 => eval_and_print::<u8>(expr),
        Mode::U16 => eval_and_print::<u16>(expr),
        Mode::U32 => eval_and_print::<u32>(expr),
        Mode::U64 => eval_and_print::<u64>(expr),
    }
}

fn eval_and_print<
    I: Zero
        + PrimInt
        + TryFrom<u64>
        + TryInto<u32>
        + TryInto<usize>
        + CheckedRem
        + CheckedShl
        + CheckedShr
        + ToBytes
        + Display
        + LowerHex
        + Binary,
>(
    expr: &Expr,
) {
    let res = eval::eval::<I>(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 printed_bin: Vec<_> = x_bytes
                .as_ref()
                .iter()
                .map(|x| format!("{x:08b}"))
                .collect();
            let printed_bin = printed_bin.join("_");

            println!("Result:");
            println!("  Dec: {x}");
            println!("  Hex: 0x{printed_hex}");
            println!("  Bin: 0b{printed_bin}");
        }
        Err(e) => {
            println!("Error: {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();
}