use crate::error::PncError;
use crate::ops;
use crate::unit::{self, UnitId};
use crate::value::Value;
use num_bigint::BigInt;
use num_traits::Num;
fn parse_unit_symbol(s: &str) -> Option<UnitId> {
UnitId::from_symbol(s)
}
fn has_prefix(s: &str) -> bool {
let body = s.strip_prefix('-').unwrap_or(s);
body.starts_with("0b")
|| body.starts_with("0B")
|| body.starts_with("0o")
|| body.starts_with("0O")
|| body.starts_with("0x")
|| body.starts_with("0X")
}
fn parse_integer(tok: &str) -> Option<Value> {
let (negative, body) = if let Some(rest) = tok.strip_prefix('-') {
(true, rest)
} else {
(false, tok)
};
if body.starts_with('+') {
return None;
}
let (base, digits) =
if let Some(rest) = body.strip_prefix("0b").or_else(|| body.strip_prefix("0B")) {
(2u32, rest)
} else if let Some(rest) = body.strip_prefix("0o").or_else(|| body.strip_prefix("0O")) {
(8u32, rest)
} else if let Some(rest) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
(16u32, rest)
} else {
(10u32, body)
};
if digits.is_empty() {
return None;
}
if digits.contains('_') || digits.starts_with('+') {
return None;
}
let n = if base == 10 {
digits.parse::<BigInt>().ok()?
} else {
if base == 16 {
BigInt::from_str_radix(&digits.to_lowercase(), base).ok()?
} else {
BigInt::from_str_radix(digits, base).ok()?
}
};
if negative {
Some(Value::Int(-n))
} else {
Some(Value::Int(n))
}
}
fn parse_number(tok: &str) -> Option<Value> {
if let Some((a, b)) = tok.split_once('/') {
let a = a.trim();
let b = b.trim();
if has_prefix(a) || has_prefix(b) {
return None;
}
let (neg_n, n_body) = if let Some(rest) = a.strip_prefix('-') {
(true, rest)
} else {
(false, a)
};
if n_body.starts_with('+') || n_body.contains('_') {
return None;
}
let n: BigInt = n_body.parse().ok()?;
if b.starts_with('-') || b.starts_with('+') || b.contains('_') {
return None;
}
let d: BigInt = b.parse().ok()?;
if d == BigInt::from(0) {
return None;
}
if d < BigInt::from(0) {
return None;
}
let n = if neg_n { -n } else { n };
Some(Value::Rational(num_rational::BigRational::new(n, d)))
} else {
parse_integer(tok)
}
}
pub fn eval_line(stack: &mut Vec<Value>, line: &str) -> Result<(), PncError> {
let tokens: Vec<&str> = line.split_whitespace().collect();
let mut i = 0;
while i < tokens.len() {
let tok = tokens[i];
if tok == "to" {
if i + 1 >= tokens.len() {
return Err(PncError::Parse("missing unit after 'to'".to_string()));
}
let target = parse_unit_symbol(tokens[i + 1])
.ok_or_else(|| PncError::Parse(tokens[i + 1].to_string()))?;
let top = stack.last().cloned().ok_or(PncError::StackUnderflow)?;
let converted =
unit::convert(&top, target).map_err(|_| PncError::InvalidUnitConversion)?;
stack.pop();
stack.push(converted);
i += 2;
continue;
}
if let Some(num) = parse_number(tok) {
if i + 1 < tokens.len()
&& let Some(u) = parse_unit_symbol(tokens[i + 1])
{
stack.push(Value::Unit {
number: Box::new(num),
unit: u,
});
i += 2;
continue;
}
stack.push(num);
i += 1;
continue;
}
match tok {
"+" => ops::add(stack)?,
"-" => ops::sub(stack)?,
"*" => ops::mul(stack)?,
"/" => ops::div(stack)?,
"neg" => ops::neg(stack)?,
"abs" => ops::abs(stack)?,
"mod" => ops::rmod(stack)?,
"over" => ops::over(stack)?,
"rot" => ops::rot(stack)?,
"clear" => ops::clear(stack),
"to_rational" => ops::to_rational(stack)?,
"to_int" => ops::to_int(stack)?,
_ => {
if tok.chars().all(char::is_alphabetic) {
return Err(PncError::UnknownCommand(tok.to_string()));
}
return Err(PncError::Parse(tok.to_string()));
}
}
i += 1;
}
Ok(())
}
pub fn is_number_token(tok: &str) -> bool {
parse_number(tok).is_some()
}