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) => {
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 } => {
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, .. } => {
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(())
}