use meval::{Context, eval_str, eval_str_with_context};
use rustyline::DefaultEditor;
mod fprice_standalone;
use fprice_standalone::fprice;
fn format_binary_64bit(value: i64) -> String {
let bits = format!("{:064b}", value);
let upper = &bits[0..32];
let lower = &bits[32..64];
let upper_formatted: String = upper
.chars()
.collect::<Vec<_>>()
.chunks(4)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<_>>()
.join(" ");
let lower_formatted: String = lower
.chars()
.collect::<Vec<_>>()
.chunks(4)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<_>>()
.join(" ");
format!(
"{}\n63 47 32\n\n{}\n31 15 0",
upper_formatted, lower_formatted
)
}
fn evaluate_command(
input: &str,
context: &Context,
last_result: Option<f64>,
) -> Result<(String, Option<f64>), String> {
let lower = input.to_lowercase();
let expr = if let Some(ans) = last_result {
lower.replace("ans", &ans.to_string())
} else {
lower.clone()
};
if let Some((expr_part, format_part)) = extract_conversion(&expr) {
let processed_expr = preprocess_operators(&expr_part)?;
let result: i64 = eval_str(&processed_expr)
.map_err(|e| format!("Failed to evaluate expression: {}", e))?
as i64;
return convert_result(result, &format_part).map(|s| (s, Some(result as f64)));
}
let expr = preprocess_operators(&expr)?;
eval_expr_with_context(&expr, context)
}
fn extract_conversion(input: &str) -> Option<(String, String)> {
if let Some(pos) = input.find(" to ") {
let expr_part = input[..pos].trim().to_string();
let format_part = input[pos + 4..].trim().to_string();
return Some((expr_part, format_part));
}
None
}
fn convert_result(value: i64, format: &str) -> Result<String, String> {
match format {
"hex" | "hexadecimal" => Ok(format!("0x{:X}", value)),
"binary" | "bin" => {
let binary_str = format!("{:b}", value);
let padding = (4 - binary_str.len() % 4) % 4;
let padded = format!("{}{}", "0".repeat(padding), binary_str);
let spaced: String = padded
.chars()
.rev()
.collect::<Vec<_>>()
.chunks(4)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<_>>()
.join(" ")
.chars()
.rev()
.collect();
Ok(format!("0b{}", spaced))
}
"bin4" => {
Ok(format!("{:04b}", value as u8 & 0xF))
}
"bin8" => {
Ok(format!(
"{:04b} {:04b}",
(value as u8 >> 4) & 0xF,
value as u8 & 0xF
))
}
"octal" | "oct" => Ok(format!("0o{:o}", value)),
_ => Err(format!("Unknown conversion target: {}", format)),
}
}
fn convert_hex_literals(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
let mut pos = 0;
while pos < result.len() {
if let Some(px_start) = result[pos..].find("0x") {
let abs_px_start = pos + px_start;
let hex_start = abs_px_start + 2;
let mut hex_end = hex_start;
let chars: Vec<char> = result.chars().collect();
let mut has_valid_digit = false;
while hex_end < chars.len() {
let c = chars[hex_end];
if c.is_ascii_hexdigit() {
has_valid_digit = true;
hex_end += 1;
} else if c == ' ' {
hex_end += 1;
} else {
break;
}
}
if has_valid_digit {
let hex_str: String = result[hex_start..hex_end]
.chars()
.filter(|c| *c != ' ')
.collect();
if let Ok(value) = i64::from_str_radix(&hex_str, 16) {
result.replace_range(abs_px_start..hex_end, &value.to_string());
pos = abs_px_start + value.to_string().len();
continue;
}
}
}
pos += 1;
}
Ok(result)
}
fn convert_octal_literals(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
let mut pos = 0;
while pos < result.len() {
if let Some(po_start) = result[pos..].find("0o") {
let abs_po_start = pos + po_start;
let octal_start = abs_po_start + 2;
let mut octal_end = octal_start;
let chars: Vec<char> = result.chars().collect();
let mut has_valid_digit = false;
while octal_end < chars.len() {
let c = chars[octal_end];
if c >= '0' && c <= '7' {
has_valid_digit = true;
octal_end += 1;
} else if c == ' ' {
octal_end += 1;
} else {
break;
}
}
if has_valid_digit {
let octal_str: String = result[octal_start..octal_end]
.chars()
.filter(|c| *c != ' ')
.collect();
if let Ok(value) = i64::from_str_radix(&octal_str, 8) {
result.replace_range(abs_po_start..octal_end, &value.to_string());
pos = abs_po_start + value.to_string().len();
continue;
}
}
}
pos += 1;
}
Ok(result)
}
fn convert_binary_literals(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
let mut pos = 0;
while pos < result.len() {
if let Some(pb_start) = result[pos..].find("0b") {
let abs_pb_start = pos + pb_start;
let binary_start = abs_pb_start + 2;
let mut binary_end = binary_start;
let chars: Vec<char> = result.chars().collect();
let mut has_valid_digit = false;
while binary_end < chars.len() {
let c = chars[binary_end];
if c == '0' || c == '1' {
has_valid_digit = true;
binary_end += 1;
} else if c == ' ' {
binary_end += 1;
} else {
break;
}
}
if has_valid_digit {
let binary_str: String = result[binary_start..binary_end]
.chars()
.filter(|c| *c != ' ')
.collect();
if let Ok(value) = i64::from_str_radix(&binary_str, 2) {
result.replace_range(abs_pb_start..binary_end, &value.to_string());
pos = abs_pb_start + value.to_string().len();
continue;
}
}
}
pos += 1;
}
Ok(result)
}
fn process_power_operator(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
loop {
let mut power_pos = None;
let chars: Vec<char> = result.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '^' {
if i + 1 < chars.len() && chars[i + 1] == '^' {
i += 2; continue;
}
power_pos = Some(i);
break;
}
i += 1;
}
let pos = match power_pos {
Some(p) => p,
None => break,
};
let left_end = pos;
let left_start = find_operand_start(&result, left_end);
let left_expr = result[left_start..left_end].trim();
let right_start = pos + 1;
let right_end = find_operand_end(&result, right_start);
let right_expr = result[right_start..right_end].trim();
if left_expr.is_empty() || right_expr.is_empty() {
break;
}
let left_val: f64 = eval_str(left_expr)
.map_err(|e| format!("Failed to evaluate left operand '{}': {}", left_expr, e))?;
let right_val: f64 = eval_str(right_expr)
.map_err(|e| format!("Failed to evaluate right operand '{}': {}", right_expr, e))?;
let power_result = left_val.powf(right_val);
result.replace_range(left_start..right_end, &power_result.to_string());
}
Ok(result)
}
fn preprocess_operators(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
result = result.replace('¬', "~");
result = result.replace('∨', "|");
result = result.replace('∧', "&");
result = result.replace('⊻', "^");
result = result.replace("xor", "^^");
result = result.replace("^^", "^^");
result = result.replace("**", "^");
result = convert_hex_literals(&result)?;
result = convert_octal_literals(&result)?;
result = convert_binary_literals(&result)?;
result = process_power_operator(&result)?;
result = process_not_operator(&result)?;
result = process_binary_operator("&", &result, |a, b| a & b)?;
result = process_binary_operator("^^", &result, |a, b| a ^ b)?;
result = process_binary_operator("|", &result, |a, b| a | b)?;
result = preprocess_shift_operators(&result)?;
Ok(result)
}
fn process_not_operator(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
loop {
let not_pos = result.find('~');
if not_pos.is_none() {
break;
}
let pos = not_pos.unwrap();
let right_start = pos + 1; let right_end = find_operand_end(&result, right_start);
let right_expr = result[right_start..right_end].trim();
if right_expr.is_empty() {
break;
}
let right_val: i64 = eval_str(right_expr)
.map_err(|e| format!("Failed to evaluate operand '{}': {}", right_expr, e))?
as i64;
let not_result = !right_val;
result.replace_range(pos..right_end, ¬_result.to_string());
}
Ok(result)
}
fn process_binary_operator<F>(op: &str, expr: &str, op_func: F) -> Result<String, String>
where
F: Fn(i64, i64) -> i64,
{
let mut result = expr.to_string();
loop {
let op_pos = result.find(op);
if op_pos.is_none() {
break;
}
let pos = op_pos.unwrap();
let left_end = pos;
let left_start = find_operand_start(&result, left_end);
let left_expr = result[left_start..left_end].trim();
let right_start = pos + op.len();
let right_end = find_operand_end(&result, right_start);
let right_expr = result[right_start..right_end].trim();
if left_expr.is_empty() || right_expr.is_empty() {
break;
}
let left_val: i64 = eval_str(left_expr)
.map_err(|e| format!("Failed to evaluate left operand '{}': {}", left_expr, e))?
as i64;
let right_val: i64 = eval_str(right_expr)
.map_err(|e| format!("Failed to evaluate right operand '{}': {}", right_expr, e))?
as i64;
let op_result = op_func(left_val, right_val);
result.replace_range(left_start..right_end, &op_result.to_string());
}
Ok(result)
}
fn preprocess_shift_operators(expr: &str) -> Result<String, String> {
let mut result = expr.to_string();
loop {
let left_shift_pos = result.find("<<");
let right_shift_pos = result.find(">>");
if left_shift_pos.is_none() && right_shift_pos.is_none() {
break;
}
let (pos, is_left_shift) = match (left_shift_pos, right_shift_pos) {
(Some(l), Some(r)) if l < r => (l, true),
(Some(_l), Some(r)) => (r, false),
(Some(l), None) => (l, true),
(None, Some(r)) => (r, false),
_ => break,
};
let left_end = pos;
let left_start = find_operand_start(&result, left_end);
let left_expr = &result[left_start..left_end];
let right_start = pos + 2; let right_end = find_operand_end(&result, right_start);
let right_expr = &result[right_start..right_end];
let left_val: i64 = eval_str(left_expr)
.map_err(|e| format!("Failed to evaluate left operand '{}': {}", left_expr, e))?
as i64;
let right_val: i32 = eval_str(right_expr)
.map_err(|e| format!("Failed to evaluate right operand '{}': {}", right_expr, e))?
as i32;
let shift_result = if is_left_shift {
left_val << right_val
} else {
left_val >> right_val
};
result.replace_range(left_start..right_end, &shift_result.to_string());
}
Ok(result)
}
fn find_operand_start(s: &str, operand_end: usize) -> usize {
let chars: Vec<char> = s.chars().collect();
let mut pos = if operand_end > 0 { operand_end - 1 } else { 0 };
let mut paren_depth = 0;
let mut found_non_space = false;
while pos > 0 {
match chars[pos] {
')' => paren_depth += 1,
'(' => {
if paren_depth == 0 {
return pos;
}
paren_depth -= 1;
}
' ' | '\t' if !found_non_space && paren_depth == 0 => {
pos -= 1;
continue;
}
c if is_operator_char(c) && paren_depth == 0 && found_non_space => return pos + 1,
' ' | '\t' if found_non_space && paren_depth == 0 => return pos + 1,
_ => {
found_non_space = true;
}
}
pos -= 1;
}
0
}
fn find_operand_end(s: &str, op_start: usize) -> usize {
let chars: Vec<char> = s.chars().collect();
let mut pos = op_start;
let mut paren_depth = 0;
let mut found_non_space = false;
while pos < chars.len() {
match chars[pos] {
'(' => paren_depth += 1,
')' => {
if paren_depth == 0 {
return pos;
}
paren_depth -= 1;
}
' ' | '\t' if !found_non_space && paren_depth == 0 => {
pos += 1;
continue;
}
c if is_operator_char(c) && paren_depth == 0 && found_non_space => return pos,
' ' | '\t' if found_non_space && paren_depth == 0 => return pos,
_ => {
found_non_space = true;
}
}
pos += 1;
}
chars.len()
}
fn is_operator_char(c: char) -> bool {
matches!(
c,
'+' | '-' | '*' | '/' | '%' | '^' | '<' | '>' | '=' | '!' | '~' | '|' | '&'
)
}
fn eval_expr_with_context(expr: &str, context: &Context) -> Result<(String, Option<f64>), String> {
match eval_str_with_context(expr, context) {
Ok(result) => {
let formatted = format_result(result)?;
Ok((formatted, Some(result)))
}
Err(_) => {
match eval_str(expr) {
Ok(result) => {
let formatted = format_result(result)?;
Ok((formatted, Some(result)))
}
Err(e) => Err(format!("{}", e)),
}
}
}
}
fn format_result(result: f64) -> Result<String, String> {
if result.is_nan() {
Ok("NaN".to_string())
} else if result.is_infinite() {
Ok(if result.is_sign_positive() {
"Infinity"
} else {
"-Infinity"
}
.to_string())
} else if result.fract() == 0.0 && result.abs() < 1e15 {
Ok(fprice(result as i64))
} else {
Ok(format!("{}", result))
}
}
fn parse_result_number(result: &str) -> Option<f64> {
if result == "NaN" || result == "Infinity" || result == "-Infinity" {
return None;
}
let num_str = result
.strip_prefix("0x")
.or_else(|| result.strip_prefix("0b"))
.or_else(|| result.strip_prefix("0o"))
.unwrap_or(result);
num_str.parse().ok()
}
fn print_val(x: Result<String, String>) -> String {
x.unwrap_or_else(|e| e)
}
fn main() -> rustyline::Result<()> {
let is_interactive = atty::is(atty::Stream::Stdin);
if is_interactive {
println!("Qalculate CLI - Interactive Calculator");
println!("Type 'exit' or 'quit' to exit\n");
println!("Supported: sqrt(72), 2^3 + 5, sin(pi), 133 to hex, etc.\n");
}
let mut context = Context::new();
let mut last_result: Option<f64> = None;
let mut rl = DefaultEditor::new()?;
context.var("pi", std::f64::consts::PI);
context.var("e", std::f64::consts::E);
loop {
let input = rl.readline("> ");
let input = match input {
Ok(line) => line,
Err(_) => break,
};
let input = input.trim();
if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") {
println!("Goodbye!");
break;
}
if input.is_empty() {
continue;
}
match evaluate_command(input, &context, last_result) {
Ok((result, num_value)) => {
println!("\t\t{}", result);
if let Some(num) = num_value {
println!(
"\t ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nHEX : {:?}\nDEC : {:?}\nOCT : {:?}\nBIN : {:?}\n{}\n\n",
print_val(convert_result(num as i64, "hex")),
result,
print_val(convert_result(num as i64, "oct")),
print_val(convert_result(num as i64, "bin")),
format_binary_64bit(num as i64)
);
last_result = Some(num);
context.var("ans", num);
}
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
if !input.is_empty() {
let _ = rl.add_history_entry(input);
}
}
Ok(())
}