1use crate::error::PncError;
2use crate::ops;
3use crate::unit::{self, UnitId};
4use crate::value::Value;
5use num_bigint::BigInt;
6
7fn parse_unit_symbol(s: &str) -> Option<UnitId> {
8 match s.to_lowercase().as_str() {
9 "c" => Some(UnitId::Celsius),
10 "f" => Some(UnitId::Fahrenheit),
11 "k" => Some(UnitId::Kelvin),
12 _ => None,
13 }
14}
15
16fn parse_number(tok: &str) -> Option<Value> {
17 if let Some((a, b)) = tok.split_once('/') {
18 let n: BigInt = a.parse().ok()?;
19 let d: BigInt = b.parse().ok()?;
20 if d == BigInt::from(0) {
21 return None;
22 }
23 Some(Value::Rational(num_rational::BigRational::new(n, d)))
24 } else {
25 let n: BigInt = tok.parse().ok()?;
26 Some(Value::Int(n))
27 }
28}
29
30pub fn eval_line(stack: &mut Vec<Value>, line: &str) -> Result<(), PncError> {
31 let tokens: Vec<&str> = line.split_whitespace().collect();
32 let mut i = 0;
33 while i < tokens.len() {
34 let tok = tokens[i];
35 if tok == "to" {
36 if i + 1 >= tokens.len() {
37 return Err(PncError::Parse("missing unit after 'to'".to_string()));
38 }
39 let target = parse_unit_symbol(tokens[i + 1])
40 .ok_or_else(|| PncError::Parse(tokens[i + 1].to_string()))?;
41 let top = stack.last().cloned().ok_or(PncError::StackUnderflow)?;
42 let converted =
43 unit::convert(&top, target).map_err(|_| PncError::InvalidUnitConversion)?;
44 stack.pop();
45 stack.push(converted);
46 i += 2;
47 continue;
48 }
49 if let Some(num) = parse_number(tok) {
50 if i + 1 < tokens.len() {
52 if let Some(u) = parse_unit_symbol(tokens[i + 1]) {
53 stack.push(Value::Unit {
54 number: Box::new(num),
55 unit: u,
56 });
57 i += 2;
58 continue;
59 }
60 }
61 stack.push(num);
62 i += 1;
63 continue;
64 }
65 match tok {
66 "+" => ops::add(stack)?,
67 "-" => ops::sub(stack)?,
68 "*" => ops::mul(stack)?,
69 "/" => ops::div(stack)?,
70 "neg" => ops::neg(stack)?,
71 "abs" => ops::abs(stack)?,
72 "mod" => ops::rmod(stack)?,
73 "over" => ops::over(stack)?,
74 "rot" => ops::rot(stack)?,
75 "clear" => ops::clear(stack),
76 "to_rational" => ops::to_rational(stack)?,
77 "to_int" => ops::to_int(stack)?,
78 _ => {
79 if tok.chars().all(char::is_alphabetic) {
80 return Err(PncError::UnknownCommand(tok.to_string()));
81 }
82 return Err(PncError::Parse(tok.to_string()));
83 }
84 }
85 i += 1;
86 }
87 Ok(())
88}