Skip to main content

pnc_engine/
value.rs

1use num_bigint::BigInt;
2use num_rational::BigRational;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::fmt;
5
6/// Core value type for the calculator.
7///
8/// Values are arbitrary-precision and never use floating point.
9/// Promotion rules: `Int` op `Int` → `Int` (division may promote to `Rational`);
10/// any `Rational` operand promotes the other to `Rational`.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Value {
13    /// Arbitrary-precision integer.
14    Int(BigInt),
15    /// Exact fraction (reduced, sign on numerator).
16    Rational(BigRational),
17    /// A number with an attached unit (metadata only; no dimensional analysis).
18    Unit { number: Box<Value>, unit: UnitId },
19}
20
21// UnitId is defined in unit.rs; re-export for convenience.
22pub use crate::unit::UnitId;
23
24impl Serialize for Value {
25    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26    where
27        S: Serializer,
28    {
29        use serde::ser::SerializeMap;
30        match self {
31            Value::Int(n) => {
32                let mut map = serializer.serialize_map(Some(2))?;
33                map.serialize_entry("kind", "int")?;
34                map.serialize_entry("value", &n.to_string())?;
35                map.end()
36            }
37            Value::Rational(r) => {
38                let mut map = serializer.serialize_map(Some(3))?;
39                map.serialize_entry("kind", "rational")?;
40                map.serialize_entry("numerator", &r.numer().to_string())?;
41                map.serialize_entry("denominator", &r.denom().to_string())?;
42                map.end()
43            }
44            Value::Unit { number, unit } => {
45                let (number_kind, numerator, denominator) = match number.as_ref() {
46                    Value::Int(n) => ("int", n.to_string(), "1".to_string()),
47                    Value::Rational(r) => {
48                        ("rational", r.numer().to_string(), r.denom().to_string())
49                    }
50                    Value::Unit { .. } => {
51                        return Err(serde::ser::Error::custom("unit may not wrap another unit"));
52                    }
53                };
54                let mut map = serializer.serialize_map(Some(5))?;
55                map.serialize_entry("kind", "unit")?;
56                map.serialize_entry("number_kind", number_kind)?;
57                map.serialize_entry("numerator", &numerator)?;
58                map.serialize_entry("denominator", &denominator)?;
59                map.serialize_entry("unit", &unit.to_string())?;
60                map.end()
61            }
62        }
63    }
64}
65
66impl<'de> Deserialize<'de> for Value {
67    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68    where
69        D: Deserializer<'de>,
70    {
71        #[derive(Deserialize)]
72        struct Raw {
73            kind: String,
74            value: Option<String>,
75            numerator: Option<String>,
76            denominator: Option<String>,
77            number_kind: Option<String>,
78            unit: Option<String>,
79        }
80
81        let raw = Raw::deserialize(deserializer)?;
82        match raw.kind.as_str() {
83            "int" => {
84                let value = raw
85                    .value
86                    .ok_or_else(|| serde::de::Error::missing_field("value"))?;
87                let n: BigInt = value
88                    .parse()
89                    .map_err(|_| serde::de::Error::custom("invalid integer"))?;
90                Ok(Value::Int(n))
91            }
92            "rational" => {
93                let num_str = raw
94                    .numerator
95                    .ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
96                let den_str = raw
97                    .denominator
98                    .ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
99                let n: BigInt = num_str
100                    .parse()
101                    .map_err(|_| serde::de::Error::custom("invalid numerator"))?;
102                let d: BigInt = den_str
103                    .parse()
104                    .map_err(|_| serde::de::Error::custom("invalid denominator"))?;
105                if d == BigInt::from(0) {
106                    return Err(serde::de::Error::custom("zero denominator"));
107                }
108                Ok(Value::Rational(BigRational::new(n, d)))
109            }
110            "unit" => {
111                let number_kind = raw
112                    .number_kind
113                    .ok_or_else(|| serde::de::Error::missing_field("number_kind"))?;
114                let num_str = raw
115                    .numerator
116                    .ok_or_else(|| serde::de::Error::missing_field("numerator"))?;
117                let den_str = raw
118                    .denominator
119                    .ok_or_else(|| serde::de::Error::missing_field("denominator"))?;
120                let unit_str = raw
121                    .unit
122                    .ok_or_else(|| serde::de::Error::missing_field("unit"))?;
123
124                let number = match number_kind.as_str() {
125                    "int" => {
126                        let n: BigInt = num_str
127                            .parse()
128                            .map_err(|_| serde::de::Error::custom("invalid integer"))?;
129                        Value::Int(n)
130                    }
131                    "rational" => {
132                        let n: BigInt = num_str
133                            .parse()
134                            .map_err(|_| serde::de::Error::custom("invalid numerator"))?;
135                        let d: BigInt = den_str
136                            .parse()
137                            .map_err(|_| serde::de::Error::custom("invalid denominator"))?;
138                        if d == BigInt::from(0) {
139                            return Err(serde::de::Error::custom("zero denominator"));
140                        }
141                        Value::Rational(BigRational::new(n, d))
142                    }
143                    _ => return Err(serde::de::Error::custom("unknown number_kind")),
144                };
145                let unit = UnitId::from_symbol(&unit_str)
146                    .unwrap_or_else(|| UnitId::Unknown(unit_str.clone()));
147                Ok(Value::Unit {
148                    number: Box::new(number),
149                    unit,
150                })
151            }
152            other => Err(serde::de::Error::custom(format!("unknown kind: {}", other))),
153        }
154    }
155}
156
157impl fmt::Display for Value {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match self {
160            Value::Int(n) => write!(f, "{}", n),
161            Value::Rational(r) => {
162                if r.denom() == &BigInt::from(1) {
163                    write!(f, "{}", r.numer())
164                } else {
165                    write!(f, "{}/{}", r.numer(), r.denom())
166                }
167            }
168            Value::Unit { number, unit } => write!(f, "{} {}", number, unit),
169        }
170    }
171}