use std::fmt;
#[cfg(debug_assertions)]
macro_rules! debug_log {
($verbose:expr, $($arg:tt)*) => {
if $verbose {
eprintln!($($arg)*);
}
}
}
#[cfg(not(debug_assertions))]
macro_rules! debug_log {
($verbose:expr, $($arg:tt)*) => {};
}
use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::Value;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArbDecimal {
sign: i8, int_part: String, frac_part: String, }
impl ArbDecimal {
pub fn parse(s: &str, _verbose: bool) -> Result<ArbDecimal, String> {
debug_log!(_verbose, "DEBUG parse => raw input='{}'", s);
let trimmed = s.trim();
if trimmed.is_empty() {
debug_log!(_verbose, "DEBUG parse => empty => error");
return Err("Cannot parse empty decimal".to_string());
}
let mut sign = 1i8;
let mut idx = 0;
if let Some(ch) = trimmed.chars().next() {
if ch == '-' {
sign = -1;
idx = 1;
} else if ch == '+' {
idx = 1;
}
}
let slice = &trimmed[idx..];
let parts = slice.split('.').collect::<Vec<_>>();
if parts.len() > 2 {
debug_log!(_verbose, "DEBUG parse => found multiple dots => error => '{}'", s);
return Err(format!("Invalid decimal: '{}'", s));
}
let int_str = parts[0];
let frac_str = if parts.len() == 2 { parts[1] } else { "" };
if !int_str.chars().all(|c| c.is_ascii_digit()) && !int_str.is_empty() {
debug_log!(_verbose, "DEBUG parse => invalid int_part => '{}'", int_str);
return Err(format!("Invalid int part: '{}'", int_str));
}
if !frac_str.chars().all(|c| c.is_ascii_digit()) {
debug_log!(_verbose, "DEBUG parse => invalid frac_part => '{}'", frac_str);
return Err(format!("Invalid frac part: '{}'", frac_str));
}
let mut i2 = int_str.to_string();
if i2.is_empty() {
i2 = "0".to_string();
}
let result = ArbDecimal {
sign,
int_part: i2,
frac_part: frac_str.to_string(),
};
debug_log!(_verbose, "DEBUG parse => final => {:?}", result);
Ok(result)
}
fn as_whole_digits(&self, _verbose: bool) -> (String, usize) {
let frac_len = self.frac_part.len();
let mut combined = self.int_part.clone();
if frac_len > 0 {
combined.push_str(&self.frac_part);
}
let trimmed = strip_leading_zeros(&combined);
let final_str = if trimmed.is_empty() { "0".to_string() } else { trimmed };
debug_log!(_verbose,
"DEBUG as_whole_digits => self={:?}, combined='{}', final_str='{}', frac_len={}",
self, combined, final_str, frac_len);
(final_str, frac_len)
}
fn from_whole_digits(s: &str, frac_len: usize, sign: i8, _verbose: bool) -> ArbDecimal {
debug_log!(_verbose, "DEBUG from_whole_digits => s='{}', frac_len={}, sign={}", s, frac_len, sign);
if s == "0" {
debug_log!(_verbose, "DEBUG from_whole_digits => s='0' => returning zero decimal");
return ArbDecimal {
sign,
int_part: "0".to_string(),
frac_part: "".to_string(),
};
}
let trimmed = strip_leading_zeros(s);
if trimmed == "0" {
debug_log!(_verbose, "DEBUG from_whole_digits => all zeros => returning zero decimal");
return ArbDecimal {
sign,
int_part: "0".to_string(),
frac_part: "".to_string(),
};
}
let total_len = trimmed.len();
if frac_len >= total_len {
let leading_zeros = frac_len - total_len;
let frac_p = format!("{}{}", "0".repeat(leading_zeros), trimmed);
debug_log!(_verbose,
"DEBUG from_whole_digits => frac_len >= total_len => leading_zeros={} => frac_p='{}'",
leading_zeros, frac_p);
ArbDecimal {
sign,
int_part: "0".to_string(),
frac_part: frac_p,
}
} else {
let split_pos = total_len - frac_len;
let int_part_raw = &trimmed[..split_pos];
let frac_part_raw = &trimmed[split_pos..];
debug_log!(_verbose,
"DEBUG from_whole_digits => int_part_raw='{}', frac_part_raw='{}'",
int_part_raw, frac_part_raw);
let final_int = if int_part_raw.is_empty() {
"0".to_string()
} else {
strip_leading_zeros(int_part_raw)
};
ArbDecimal {
sign,
int_part: final_int,
frac_part: frac_part_raw.to_string(),
}
}
}
fn is_zero(&self) -> bool {
self.int_part == "0" && (self.frac_part.is_empty() || self.frac_part == "0")
}
pub fn add(&self, other: &ArbDecimal, verbose: bool) -> ArbDecimal {
debug_log!(verbose, "DEBUG add => self={:?}, other={:?}", self, other);
if self.sign != other.sign {
debug_log!(verbose, "DEBUG add => different sign => call sub(neg other)");
let neg = ArbDecimal {
sign: -other.sign,
int_part: other.int_part.clone(),
frac_part: other.frac_part.clone(),
};
return self.sub(&neg, verbose);
}
let sign = self.sign;
let (ld, lf) = self.as_whole_digits(verbose);
let (rd, rf) = other.as_whole_digits(verbose);
let maxf = lf.max(rf);
debug_log!(verbose, "DEBUG add => lf={}, rf={}, maxf={}", lf, rf, maxf);
let pad_left = if maxf > lf { maxf - lf } else { 0 };
debug_log!(verbose, "DEBUG add => pad_left={}", pad_left);
let ld2 = format!("{}{}", ld, "0".repeat(pad_left));
let pad_right = if maxf > rf { maxf - rf } else { 0 };
debug_log!(verbose, "DEBUG add => pad_right={}", pad_right);
let rd2 = format!("{}{}", rd, "0".repeat(pad_right));
debug_log!(verbose, "DEBUG add => ld2='{}', rd2='{}'", ld2, rd2);
let sum_str = add_strings(&ld2, &rd2, verbose);
let result = ArbDecimal::from_whole_digits(&sum_str, maxf, sign, verbose);
debug_log!(verbose, "DEBUG add => final result={:?}", result);
result
}
pub fn sub(&self, other: &ArbDecimal, verbose: bool) -> ArbDecimal {
debug_log!(verbose, "DEBUG sub => self={:?}, other={:?}", self, other);
if self.sign != other.sign {
debug_log!(verbose, "DEBUG sub => different sign => call add(neg other)");
let neg = ArbDecimal {
sign: -other.sign,
int_part: other.int_part.clone(),
frac_part: other.frac_part.clone(),
};
return self.add(&neg, verbose);
}
let sign = self.sign;
let (ld, lf) = self.as_whole_digits(verbose);
let (rd, rf) = other.as_whole_digits(verbose);
let maxf = lf.max(rf);
debug_log!(verbose, "DEBUG sub => lf={}, rf={}, maxf={}", lf, rf, maxf);
let pad_left = if maxf > lf { maxf - lf } else { 0 };
debug_log!(verbose, "DEBUG sub => pad_left={}", pad_left);
let ld2 = format!("{}{}", ld, "0".repeat(pad_left));
let pad_right = if maxf > rf { maxf - rf } else { 0 };
debug_log!(verbose, "DEBUG sub => pad_right={}", pad_right);
let rd2 = format!("{}{}", rd, "0".repeat(pad_right));
debug_log!(verbose, "DEBUG sub => ld2='{}', rd2='{}'", ld2, rd2);
let cmp = compare_strings(&ld2, &rd2);
if cmp == 0 {
debug_log!(verbose, "DEBUG sub => they are equal => zero result");
return ArbDecimal {
sign: 1,
int_part: "0".to_string(),
frac_part: "".to_string(),
};
}
if cmp > 0 {
let diff_str = sub_strings(&ld2, &rd2, verbose);
let result = ArbDecimal::from_whole_digits(&diff_str, maxf, sign, verbose);
debug_log!(verbose, "DEBUG sub => final result={:?}", result);
result
} else {
let diff_str = sub_strings(&rd2, &ld2, verbose);
let result = ArbDecimal::from_whole_digits(&diff_str, maxf, -sign, verbose);
debug_log!(verbose, "DEBUG sub => final result={:?}", result);
result
}
}
pub fn mul(&self, other: &ArbDecimal, verbose: bool) -> ArbDecimal {
debug_log!(verbose, "DEBUG mul => self={:?}, other={:?}", self, other);
let sign = if self.sign == other.sign { 1 } else { -1 };
let (ld, lf) = self.as_whole_digits(verbose);
let (rd, rf) = other.as_whole_digits(verbose);
let total_f = lf + rf;
debug_log!(verbose, "DEBUG mul => lf={}, rf={}, total_f={}", lf, rf, total_f);
if ld == "0" || rd == "0" {
debug_log!(verbose, "DEBUG mul => at least one is '0' => returning zero");
return ArbDecimal {
sign: 1,
int_part: "0".to_string(),
frac_part: "".to_string(),
};
}
debug_log!(verbose, "DEBUG mul => ld='{}', rd='{}'", ld, rd);
let prod_str = mul_strings(&ld, &rd, verbose);
let result = ArbDecimal::from_whole_digits(&prod_str, total_f, sign, verbose);
debug_log!(verbose, "DEBUG mul => final result={:?}", result);
result
}
pub fn div(&self, other: &ArbDecimal, precision: usize, verbose: bool) -> Result<ArbDecimal, String> {
debug_log!(verbose, "DEBUG div => self={:?}, other={:?}, precision={}", self, other, precision);
if other.is_zero() {
debug_log!(verbose, "DEBUG div => error => division by zero");
return Err("Division by zero".to_string());
}
let sign = if self.sign == other.sign { 1 } else { -1 };
let (ld, lf) = self.as_whole_digits(verbose);
let (rd, rf) = other.as_whole_digits(verbose);
let shift = (precision + rf).saturating_sub(lf);
debug_log!(verbose, "DEBUG div => ld='{}', lf={}, rd='{}', rf={}, shift={}", ld, lf, rd, rf, shift);
let scaled_num = format!("{}{}", ld, "0".repeat(shift));
debug_log!(verbose, "DEBUG div => scaled_num='{}'", scaled_num);
let (q, _rem) = div_strings(&scaled_num, &rd, verbose);
let dec = ArbDecimal::from_whole_digits(&q, precision, sign, verbose);
debug_log!(verbose, "DEBUG div => final result={:?}", dec);
Ok(dec)
}
}
fn compare_strings(a: &str, b: &str) -> i32 {
let aa = strip_leading_zeros(a);
let bb = strip_leading_zeros(b);
if aa.len() > bb.len() {
1
} else if aa.len() < bb.len() {
-1
} else {
aa.cmp(&bb) as i32
}
}
fn strip_leading_zeros(s: &str) -> String {
let t = s.trim_start_matches('0');
if t.is_empty() {
"0".to_string()
} else {
t.to_string()
}
}
#[cfg(debug_assertions)]
fn add_strings(a: &str, b: &str, verbose: bool) -> String {
debug_log!(verbose, "DEBUG add_strings => a='{}', b='{}'", a, b);
let r = add_strings_inner(a, b);
debug_log!(verbose, " => result='{}'", r);
r
}
#[cfg(not(debug_assertions))]
fn add_strings(a: &str, b: &str, _verbose: bool) -> String {
add_strings_inner(a, b)
}
fn add_strings_inner(a: &str, b: &str) -> String {
let mut carry = 0;
let mut res = String::new();
let mut i = a.len();
let mut j = b.len();
while i > 0 || j > 0 || carry > 0 {
let x = if i > 0 { (a.as_bytes()[i - 1] - b'0') as i32 } else { 0 };
let y = if j > 0 { (b.as_bytes()[j - 1] - b'0') as i32 } else { 0 };
let sum = x + y + carry;
carry = sum / 10;
let digit = (sum % 10) as u8 + b'0';
res.push(digit as char);
if i > 0 { i -= 1; }
if j > 0 { j -= 1; }
}
res.chars().rev().collect()
}
#[cfg(debug_assertions)]
fn sub_strings(a: &str, b: &str, verbose: bool) -> String {
debug_log!(verbose, "DEBUG sub_strings => a='{}', b='{}'", a, b);
let r = sub_strings_inner(a, b);
debug_log!(verbose, " => result='{}'", r);
r
}
#[cfg(not(debug_assertions))]
fn sub_strings(a: &str, b: &str, _verbose: bool) -> String {
sub_strings_inner(a, b)
}
fn sub_strings_inner(a: &str, b: &str) -> String {
let mut res = String::new();
let mut borrow = 0;
let mut i = a.len();
let mut j = b.len();
while i > 0 || j > 0 {
let mut x = if i > 0 { (a.as_bytes()[i - 1] - b'0') as i32 } else { 0 };
let y = if j > 0 { (b.as_bytes()[j - 1] - b'0') as i32 } else { 0 };
x -= borrow;
if x < y {
x += 10;
borrow = 1;
} else {
borrow = 0;
}
let diff = x - y;
res.push((diff as u8 + b'0') as char);
if i > 0 { i -= 1; }
if j > 0 { j -= 1; }
}
let rr = res.chars().rev().collect::<String>();
strip_leading_zeros(&rr)
}
#[cfg(debug_assertions)]
fn mul_strings(a: &str, b: &str, verbose: bool) -> String {
debug_log!(verbose, "DEBUG mul_strings => a='{}', b='{}'", a, b);
let r = mul_strings_inner(a, b);
debug_log!(verbose, " => result='{}'", r);
r
}
#[cfg(not(debug_assertions))]
fn mul_strings(a: &str, b: &str, _verbose: bool) -> String {
mul_strings_inner(a, b)
}
fn mul_strings_inner(a: &str, b: &str) -> String {
if a == "0" || b == "0" {
println!("DEBUG mul_strings => one is '0' => returning '0'");
return "0".to_string();
}
let la = a.len();
let lb = b.len();
let mut arr = vec![0i32; la + lb];
for i in (0..la).rev() {
let av = (a.as_bytes()[i] - b'0') as i32;
for j in (0..lb).rev() {
let bv = (b.as_bytes()[j] - b'0') as i32;
let sum = av * bv + arr[i + j + 1];
arr[i + j + 1] = sum % 10;
arr[i + j] += sum / 10;
}
}
let mut s = String::new();
for d in arr {
s.push((d as u8 + b'0') as char);
}
strip_leading_zeros(&s)
}
#[cfg(debug_assertions)]
fn div_strings(a: &str, b: &str, verbose: bool) -> (String, String) {
debug_log!(verbose, "DEBUG div_strings => a='{}', b='{}'", a, b);
let (q, r) = div_strings_inner(a, b);
debug_log!(verbose, " => quotient='{}', remainder='{}'", q, r);
(q, r)
}
#[cfg(not(debug_assertions))]
fn div_strings(a: &str, b: &str, _verbose: bool) -> (String, String) {
div_strings_inner(a, b)
}
fn div_strings_inner(a: &str, b: &str) -> (String, String) {
if b == "0" {
println!("DEBUG div_strings => b='0' => returning (\"0\",\"0\")");
return ("0".to_string(), "0".to_string());
}
if a == "0" {
println!("DEBUG div_strings => a='0' => returning (\"0\",\"0\")");
return ("0".to_string(), "0".to_string());
}
let bb = strip_leading_zeros(b);
let mut remainder = String::new();
let mut quotient = String::new();
for ch in a.chars() {
remainder.push(ch);
remainder = strip_leading_zeros(&remainder);
let mut count = 0;
while compare_strings(&remainder, &bb) >= 0 {
remainder = sub_strings_inner(&remainder, &bb);
count += 1;
}
quotient.push((count as u8 + b'0') as char);
}
let qf = strip_leading_zeros("ient);
let rf = strip_leading_zeros(&remainder);
println!("DEBUG div_strings => final => quotient='{}', remainder='{}'", qf, rf);
(qf, rf)
}
#[derive(Debug, Clone)]
enum Token {
LParen,
RParen,
Plus,
Minus,
Star,
Slash,
Number(ArbDecimal),
}
#[derive(Debug, Clone)]
pub enum Expr {
Number(ArbDecimal),
Infix(Op, Box<Expr>, Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
Add,
Sub,
Mul,
Div,
}
pub fn eval_arb_expression(input: &str, verbose: bool) -> Result<String, String> {
debug_log!(verbose, "DEBUG eval_arb_expression => input='{}'", input);
let tokens = tokenize(input, verbose)?;
debug_log!(verbose, "DEBUG => tokens={:?}", tokens);
let mut parser = PrattParser::new(tokens, verbose);
let expr = parser.parse_expr(0)?;
if !parser.is_done() {
return Err("Unexpected tokens after parse".to_string());
}
debug_log!(verbose, "DEBUG => final AST => {:?}", expr);
let result = eval_expr(&expr, verbose)?;
debug_log!(verbose, "DEBUG => final decimal => {:?}", result);
Ok(result.to_string())
}
fn tokenize(input: &str, verbose: bool) -> Result<Vec<Token>, String> {
debug_log!(verbose, "[tokenize] => '{}'", input);
let mut chars = input.chars().peekable();
let mut tokens = Vec::new();
while let Some(&ch) = chars.peek() {
match ch {
' ' | '\t' | '\n' => {
chars.next();
}
'(' => {
tokens.push(Token::LParen);
chars.next();
}
')' => {
tokens.push(Token::RParen);
chars.next();
}
'+' => {
tokens.push(Token::Plus);
chars.next();
}
'-' => {
tokens.push(Token::Minus);
chars.next();
}
'*' => {
tokens.push(Token::Star);
chars.next();
}
'/' => {
tokens.push(Token::Slash);
chars.next();
}
'0'..='9' | '.' => {
let mut s = String::new();
while let Some(&c2) = chars.peek() {
if c2.is_ascii_digit() || c2 == '.' {
s.push(c2);
chars.next();
} else {
break;
}
}
debug_log!(verbose, "[tokenize] => number chunk='{}'", s);
let dec = ArbDecimal::parse(&s, verbose)
.map_err(|e| format!("Decimal parse error: {}", e))?;
tokens.push(Token::Number(dec));
}
_ => {
return Err(format!("Invalid character '{}'", ch));
}
}
}
if tokens.is_empty() {
return Err("No tokens found".to_string());
}
debug_log!(verbose, "[tokenize] => tokens={:?}", tokens);
Ok(tokens)
}
struct PrattParser {
tokens: Vec<Token>,
pos: usize,
verbose: bool,
}
impl PrattParser {
fn new(tokens: Vec<Token>, verbose: bool) -> Self {
PrattParser { tokens, pos: 0, verbose }
}
fn is_done(&self) -> bool {
self.pos >= self.tokens.len()
}
fn current(&self) -> Option<&Token> {
self.tokens.get(self.pos)
}
fn advance(&mut self) {
if self.pos < self.tokens.len() {
self.pos += 1;
}
}
fn parse_expr(&mut self, min_bp: u8) -> Result<Expr, String> {
let mut lhs = self.parse_prefix()?;
loop {
let op = match self.current() {
Some(Token::Plus) => Op::Add,
Some(Token::Minus) => Op::Sub,
Some(Token::Star) => Op::Mul,
Some(Token::Slash) => Op::Div,
_ => break,
};
let (left_bp, right_bp) = infix_binding_power(op);
if left_bp < min_bp {
break;
}
self.advance();
let rhs = self.parse_expr(right_bp)?;
lhs = Expr::Infix(op, Box::new(lhs), Box::new(rhs));
}
Ok(lhs)
}
fn parse_prefix(&mut self) -> Result<Expr, String> {
let tok = self.current().ok_or("Unexpected EOF in parse_prefix")?;
match tok {
Token::Number(d) => {
let dec = d.clone();
self.advance();
Ok(Expr::Number(dec))
}
Token::Minus => {
self.advance();
let factor = self.parse_expr(255)?;
match factor {
Expr::Number(dv) => {
let neg = ArbDecimal {
sign: -dv.sign,
int_part: dv.int_part,
frac_part: dv.frac_part,
};
Ok(Expr::Number(neg))
}
Expr::Infix(_, _, _) => {
let zero = ArbDecimal::parse("0", self.verbose).unwrap();
Ok(Expr::Infix(Op::Sub, Box::new(Expr::Number(zero)), Box::new(factor)))
}
}
}
Token::LParen => {
self.advance();
let sub = self.parse_expr(0)?;
match self.current() {
Some(Token::RParen) => {
self.advance();
Ok(sub)
}
_ => Err("Expected ')'".to_string()),
}
}
_ => Err(format!("Unexpected token in prefix: {:?}", tok)),
}
}
}
fn infix_binding_power(op: Op) -> (u8, u8) {
match op {
Op::Add | Op::Sub => (40, 41),
Op::Mul | Op::Div => (50, 51),
}
}
fn eval_expr(expr: &Expr, verbose: bool) -> Result<ArbDecimal, String> {
match expr {
Expr::Number(d) => Ok(d.clone()),
Expr::Infix(op, lhs, rhs) => {
let lv = eval_expr(lhs, verbose)?;
let rv = eval_expr(rhs, verbose)?;
match op {
Op::Add => Ok(lv.add(&rv, verbose)),
Op::Sub => Ok(lv.sub(&rv, verbose)),
Op::Mul => Ok(lv.mul(&rv, verbose)),
Op::Div => lv.div(&rv, 20, verbose).map_err(|e| e.to_string()),
}
}
}
}
impl fmt::Display for ArbDecimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
pub fn math_arb_bridge(
interp: &mut Interpreter,
args: Vec<Value>
) -> Result<Value, String> {
if args.len() != 1 {
return Err(format!("math:arb => expected 1 argument, got {}", args.len()));
}
let raw_expr = match &args[0] {
Value::SingleString(s) => s.clone(),
Value::StrArray(ss) if ss.len() == 1 => ss[0].clone(),
_ => return Err("math:arb => argument must be a single string".to_string()),
};
let verbose = interp.is_verbose();
let result_str = match eval_arb_expression(&raw_expr, verbose) {
Ok(s) => s,
Err(e) => {
return Err(format!(
"math:arb => cannot parse '{}': {}",
raw_expr, e
));
}
};
Ok(Value::SingleString(result_str))
}