pnc-engine 0.1.1

Core engine for PNC: arbitrary-precision RPN evaluation, units, and persistence
Documentation
use num_bigint::BigInt;
use num_rational::BigRational;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
    Int(BigInt),
    Rational(BigRational),
    Unit { number: Box<Value>, unit: UnitId },
}

// UnitId is defined in unit.rs; re-export for convenience.
pub use crate::unit::UnitId;

impl Serialize for Value {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::SerializeMap;
        match self {
            Value::Int(n) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("kind", "int")?;
                map.serialize_entry("value", &n.to_string())?;
                map.end()
            }
            Value::Rational(r) => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("kind", "rational")?;
                map.serialize_entry("numerator", &r.numer().to_string())?;
                map.serialize_entry("denominator", &r.denom().to_string())?;
                map.end()
            }
            Value::Unit { number, unit } => {
                let (number_kind, numerator, denominator) = match number.as_ref() {
                    Value::Int(n) => ("int", n.to_string(), "1".to_string()),
                    Value::Rational(r) => {
                        ("rational", r.numer().to_string(), r.denom().to_string())
                    }
                    Value::Unit { .. } => {
                        return Err(serde::ser::Error::custom("unit may not wrap another unit"))
                    }
                };
                let mut map = serializer.serialize_map(Some(5))?;
                map.serialize_entry("kind", "unit")?;
                map.serialize_entry("number_kind", number_kind)?;
                map.serialize_entry("numerator", &numerator)?;
                map.serialize_entry("denominator", &denominator)?;
                map.serialize_entry("unit", &unit.to_string())?;
                map.end()
            }
        }
    }
}

impl<'de> Deserialize<'de> for Value {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Raw {
            kind: String,
            value: Option<String>,
            numerator: Option<String>,
            denominator: Option<String>,
            number_kind: Option<String>,
            unit: Option<String>,
        }

        let raw = Raw::deserialize(deserializer)?;
        match raw.kind.as_str() {
            "int" => {
                let value = raw
                    .value
                    .ok_or_else(|| serde::de::Error::missing_field("value"))?;
                let n: BigInt = value
                    .parse()
                    .map_err(|_| serde::de::Error::custom("invalid integer"))?;
                Ok(Value::Int(n))
            }
            "rational" => {
                let num_str = raw
                    .numerator
                    .ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
                let den_str = raw
                    .denominator
                    .ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
                let n: BigInt = num_str
                    .parse()
                    .map_err(|_| serde::de::Error::custom("invalid numerator"))?;
                let d: BigInt = den_str
                    .parse()
                    .map_err(|_| serde::de::Error::custom("invalid denominator"))?;
                if d == BigInt::from(0) {
                    return Err(serde::de::Error::custom("zero denominator"));
                }
                Ok(Value::Rational(BigRational::new(n, d)))
            }
            "unit" => {
                let number_kind = raw
                    .number_kind
                    .ok_or_else(|| serde::de::Error::missing_field("number_kind"))?;
                let num_str = raw
                    .numerator
                    .ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
                let den_str = raw
                    .denominator
                    .ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
                let unit_str = raw
                    .unit
                    .ok_or_else(|| serde::de::Error::missing_field("unit"))?;

                let number = match number_kind.as_str() {
                    "int" => {
                        let n: BigInt = num_str
                            .parse()
                            .map_err(|_| serde::de::Error::custom("invalid integer"))?;
                        Value::Int(n)
                    }
                    "rational" => {
                        let n: BigInt = num_str
                            .parse()
                            .map_err(|_| serde::de::Error::custom("invalid numerator"))?;
                        let d: BigInt = den_str
                            .parse()
                            .map_err(|_| serde::de::Error::custom("invalid denominator"))?;
                        if d == BigInt::from(0) {
                            return Err(serde::de::Error::custom("zero denominator"));
                        }
                        Value::Rational(BigRational::new(n, d))
                    }
                    _ => return Err(serde::de::Error::custom("unknown number_kind")),
                };
                let unit = match unit_str.as_str() {
                    "c" => UnitId::Celsius,
                    "f" => UnitId::Fahrenheit,
                    "k" => UnitId::Kelvin,
                    other => UnitId::Unknown(other.to_string()),
                };
                Ok(Value::Unit {
                    number: Box::new(number),
                    unit,
                })
            }
            other => Err(serde::de::Error::custom(format!("unknown kind: {}", other))),
        }
    }
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::Int(n) => write!(f, "{}", n),
            Value::Rational(r) => {
                if r.denom() == &BigInt::from(1) {
                    write!(f, "{}", r.numer())
                } else {
                    write!(f, "{}/{}", r.numer(), r.denom())
                }
            }
            Value::Unit { number, unit } => write!(f, "{} {}", number, unit),
        }
    }
}