Skip to main content

pnc_engine/
parse.rs

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            // check for unit-bearing number: number followed by unit symbol
51            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}