pnc-engine 0.1.1

Core engine for PNC: arbitrary-precision RPN evaluation, units, and persistence
Documentation
use crate::error::PncError;
use crate::value::Value;
use std::sync::atomic::{AtomicUsize, Ordering};

static RATIONAL_DIGIT_CAP: AtomicUsize = AtomicUsize::new(10_000);

pub fn set_rational_digit_cap(cap: usize) {
    RATIONAL_DIGIT_CAP.store(cap, Ordering::Relaxed);
}

fn rational_digit_cap() -> usize {
    RATIONAL_DIGIT_CAP.load(Ordering::Relaxed)
}
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::Signed;

fn check_precision(v: &Value) -> Result<(), PncError> {
    let cap = rational_digit_cap();
    match v {
        Value::Rational(r) => {
            if r.numer().to_string().len() > cap || r.denom().to_string().len() > cap {
                return Err(PncError::PrecisionBudgetExceeded);
            }
        }
        Value::Unit { number, .. } => check_precision(number)?,
        _ => {}
    }
    Ok(())
}

pub fn add(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let b = stack.pop().unwrap();
    let a = stack.pop().unwrap();
    let result = add_values(a, b);
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

fn add_values(a: Value, b: Value) -> Value {
    match (a, b) {
        (Value::Int(x), Value::Int(y)) => Value::Int(x + y),
        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) + y),
        (Value::Rational(x), Value::Int(y)) => Value::Rational(x + BigRational::from_integer(y)),
        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x + y),
        (
            Value::Unit {
                number: an,
                unit: au,
            },
            Value::Unit {
                number: bn,
                unit: bu,
            },
        ) if au == bu => Value::Unit {
            number: Box::new(add_values(*an, *bn)),
            unit: au,
        },
        (Value::Unit { number, .. }, other) => {
            // strip unit and operate on numeric value
            add_values(*number, other)
        }
        (other, Value::Unit { number, .. }) => add_values(other, *number),
    }
}

pub fn sub(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let b = stack.pop().unwrap();
    let a = stack.pop().unwrap();
    let result = sub_values(a, b);
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

fn sub_values(a: Value, b: Value) -> Value {
    match (a, b) {
        (Value::Int(x), Value::Int(y)) => Value::Int(x - y),
        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) - y),
        (Value::Rational(x), Value::Int(y)) => Value::Rational(x - BigRational::from_integer(y)),
        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x - y),
        (
            Value::Unit {
                number: an,
                unit: au,
            },
            Value::Unit {
                number: bn,
                unit: bu,
            },
        ) if au == bu => Value::Unit {
            number: Box::new(sub_values(*an, *bn)),
            unit: au,
        },
        (Value::Unit { number, .. }, other) => sub_values(*number, other),
        (other, Value::Unit { number, .. }) => sub_values(other, *number),
    }
}

pub fn mul(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let b = stack.pop().unwrap();
    let a = stack.pop().unwrap();
    let result = mul_values(a, b);
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

fn mul_values(a: Value, b: Value) -> Value {
    match (a, b) {
        (Value::Int(x), Value::Int(y)) => Value::Int(x * y),
        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) * y),
        (Value::Rational(x), Value::Int(y)) => Value::Rational(x * BigRational::from_integer(y)),
        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x * y),
        (
            Value::Unit {
                number: an,
                unit: au,
            },
            Value::Unit {
                number: bn,
                unit: bu,
            },
        ) if au == bu => Value::Unit {
            number: Box::new(mul_values(*an, *bn)),
            unit: au,
        },
        (Value::Unit { number, .. }, other) => mul_values(*number, other),
        (other, Value::Unit { number, .. }) => mul_values(other, *number),
    }
}

fn unit_of(v: &Value) -> Option<crate::value::UnitId> {
    match v {
        Value::Unit { unit, .. } => Some(unit.clone()),
        _ => None,
    }
}

fn strip_unit(v: Value) -> Value {
    match v {
        Value::Unit { number, .. } => *number,
        other => other,
    }
}

pub fn div(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let b = stack.pop().unwrap();
    let a = stack.pop().unwrap();
    let result_unit = if unit_of(&a).is_some() && unit_of(&a) == unit_of(&b) {
        unit_of(&a)
    } else {
        None
    };
    let a = strip_unit(a);
    let b = strip_unit(b);

    let result = match (a, b) {
        (Value::Int(x), Value::Int(y)) => {
            if y == BigInt::from(0) {
                return Err(PncError::DivisionByZero);
            }
            if x.clone() % y.clone() == BigInt::from(0) {
                Value::Int(x / y)
            } else {
                Value::Rational(BigRational::new(x, y))
            }
        }
        (Value::Int(x), Value::Rational(y)) => {
            if y == BigRational::from_integer(BigInt::from(0)) {
                return Err(PncError::DivisionByZero);
            }
            Value::Rational(BigRational::from_integer(x) / y)
        }
        (Value::Rational(x), Value::Int(y)) => {
            if y == BigInt::from(0) {
                return Err(PncError::DivisionByZero);
            }
            Value::Rational(x / BigRational::from_integer(y))
        }
        (Value::Rational(x), Value::Rational(y)) => {
            if y == BigRational::from_integer(BigInt::from(0)) {
                return Err(PncError::DivisionByZero);
            }
            Value::Rational(x / y)
        }
        _ => return Err(PncError::Parse("unsupported operand types".to_string())),
    };

    let result = match result_unit {
        Some(u) => Value::Unit {
            number: Box::new(result),
            unit: u,
        },
        None => result,
    };
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

pub fn clear(stack: &mut Vec<Value>) {
    stack.clear();
}

pub fn remove_at(stack: &mut Vec<Value>, index: usize) -> Result<(), PncError> {
    if index >= stack.len() {
        return Err(PncError::StackUnderflow);
    }
    stack.remove(index);
    Ok(())
}

pub fn swap_with_top(stack: &mut [Value], index: usize) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    if index >= stack.len() {
        return Err(PncError::StackUnderflow);
    }
    let last = stack.len() - 1;
    if index == last {
        return Ok(());
    }
    stack.swap(index, last);
    Ok(())
}

pub fn neg(stack: &mut Vec<Value>) -> Result<(), PncError> {
    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
    let result = neg_value(v);
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

fn neg_value(v: Value) -> Value {
    match v {
        Value::Int(n) => Value::Int(-n),
        Value::Rational(r) => Value::Rational(-r),
        Value::Unit { number, unit } => Value::Unit {
            number: Box::new(neg_value(*number)),
            unit,
        },
    }
}

pub fn abs(stack: &mut Vec<Value>) -> Result<(), PncError> {
    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
    let result = abs_value(v);
    check_precision(&result)?;
    stack.push(result);
    Ok(())
}

fn abs_value(v: Value) -> Value {
    match v {
        Value::Int(n) => Value::Int(n.abs()),
        Value::Rational(r) => Value::Rational(r.abs()),
        Value::Unit { number, unit } => Value::Unit {
            number: Box::new(abs_value(*number)),
            unit,
        },
    }
}

pub fn rmod(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let b = stack.pop().unwrap();
    let a = stack.pop().unwrap();
    match (a, b) {
        (Value::Int(x), Value::Int(y)) => {
            if y == BigInt::from(0) {
                return Err(PncError::DivisionByZero);
            }
            stack.push(Value::Int(x % y));
        }
        _ => return Err(PncError::Parse("unsupported".to_string())),
    }
    Ok(())
}

pub fn over(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 2 {
        return Err(PncError::StackUnderflow);
    }
    let v = stack[stack.len() - 2].clone();
    stack.push(v);
    Ok(())
}

pub fn rot(stack: &mut Vec<Value>) -> Result<(), PncError> {
    if stack.len() < 3 {
        return Err(PncError::StackUnderflow);
    }
    let n = stack.len();
    let v = stack.remove(n - 3);
    stack.push(v);
    Ok(())
}

pub fn to_rational(stack: &mut Vec<Value>) -> Result<(), PncError> {
    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
    let r = match v {
        Value::Int(n) => BigRational::from_integer(n),
        Value::Rational(r) => r,
        Value::Unit { number, unit } => {
            // strip unit and convert inner
            let inner = to_rational_value(*number)?;
            stack.push(Value::Unit {
                number: Box::new(Value::Rational(inner)),
                unit,
            });
            return Ok(());
        }
    };
    stack.push(Value::Rational(r));
    Ok(())
}

fn to_rational_value(v: Value) -> Result<BigRational, PncError> {
    match v {
        Value::Int(n) => Ok(BigRational::from_integer(n)),
        Value::Rational(r) => Ok(r),
        _ => Err(PncError::Parse("unsupported".to_string())),
    }
}

pub fn to_int(stack: &mut Vec<Value>) -> Result<(), PncError> {
    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
    let n = match v {
        Value::Int(n) => n,
        Value::Rational(r) => r.to_integer(),
        Value::Unit { number, .. } => {
            // strip unit
            match *number {
                Value::Int(n) => n,
                Value::Rational(r) => r.to_integer(),
                _ => return Err(PncError::Parse("unsupported".to_string())),
            }
        }
    };
    stack.push(Value::Int(n));
    Ok(())
}