pnc-engine 0.1.1

Core engine for PNC: arbitrary-precision RPN evaluation, units, and persistence
Documentation
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) {
            // check for unit-bearing number: number followed by unit symbol
            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(())
}