use crate::error::PncError;
use crate::ops;
use crate::unit::{self, UnitId};
use crate::value::Value;
use num_bigint::BigInt;
fn parse_unit_symbol(s: &str) -> Option<UnitId> {
match s.to_lowercase().as_str() {
"c" => Some(UnitId::Celsius),
"f" => Some(UnitId::Fahrenheit),
"k" => Some(UnitId::Kelvin),
_ => None,
}
}
fn parse_number(tok: &str) -> Option<Value> {
if let Some((a, b)) = tok.split_once('/') {
let n: BigInt = a.parse().ok()?;
let d: BigInt = b.parse().ok()?;
if d == BigInt::from(0) {
return None;
}
Some(Value::Rational(num_rational::BigRational::new(n, d)))
} else {
let n: BigInt = tok.parse().ok()?;
Some(Value::Int(n))
}
}
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() {
if 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(())
}