Skip to main content

ephemeris_documents/
scalar.rs

1//! Leaf values of an annotated tree.
2
3use std::fmt;
4
5use facet_value::VNumber;
6
7use crate::annotation::StringStyle;
8use crate::error::{Feature, ValueError};
9use crate::format::Format;
10
11/// A finite floating point number.
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub(crate) struct Finite(f64);
14
15impl Finite {
16    /// Wrap `value`.
17    ///
18    /// # Errors
19    /// Returns [`ValueError::NonFinite`] for NaN and infinities.
20    pub(crate) const fn new(value: f64) -> Result<Self, ValueError> {
21        if value.is_finite() {
22            Ok(Self(value))
23        } else {
24            Err(ValueError::NonFinite)
25        }
26    }
27
28    pub(crate) const fn get(self) -> f64 {
29        self.0
30    }
31}
32
33impl fmt::Display for Finite {
34    /// Whole numbers get a `.0`, as Rust prints them without a dot and they
35    /// would read back as integers.
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        let text = self.0.to_string();
38        if text.contains('.') {
39            f.write_str(&text)
40        } else {
41            write!(f, "{text}.0")
42        }
43    }
44}
45
46/// A null, boolean, number, or string leaf.
47#[derive(Debug, Clone, PartialEq)]
48pub(crate) enum Scalar {
49    /// The null value.
50    Null,
51    /// A boolean.
52    Bool(bool),
53    /// An integer that fits `i64`.
54    Integer(i64),
55    /// An integer that does not fit `i64` but fits `u64`.
56    Unsigned(u64),
57    /// A finite floating point number.
58    Float(Finite),
59    /// A string and the style it is rendered in.
60    String {
61        /// The string content.
62        text: String,
63        /// The requested rendering style.
64        style: StringStyle,
65    },
66}
67
68impl Scalar {
69    /// Check that `format` can write this scalar. A format that supports
70    /// literal strings can still lack the text of one.
71    ///
72    /// # Errors
73    /// Returns the unsupported feature error for `format`.
74    pub(crate) fn check(&self, format: Format) -> Result<(), ValueError> {
75        match self {
76            Self::Null => format.require(Feature::Null),
77            Self::Unsigned(_) => format.require(Feature::LargeInteger),
78            Self::String {
79                text,
80                style: StringStyle::Literal,
81            } => {
82                format.require(Feature::LiteralString)?;
83                if format.reproduces_literal(text) {
84                    Ok(())
85                } else {
86                    Err(format.reject(Feature::LiteralString))
87                }
88            }
89            Self::Bool(_)
90            | Self::Integer(_)
91            | Self::Float(_)
92            | Self::String {
93                text: _,
94                style: StringStyle::Plain,
95            } => Ok(()),
96        }
97    }
98
99    /// Decode a number, keeping floats and integers apart.
100    ///
101    /// # Errors
102    /// Returns [`ValueError::NonFinite`] for a non-finite float, and
103    /// [`ValueError::IntegerOutOfRange`] for an integer outside the `i64` and
104    /// `u64` ranges.
105    pub(crate) fn number(number: &VNumber) -> Result<Self, ValueError> {
106        if number.is_float() {
107            let value = number.to_f64().ok_or(ValueError::UnsupportedKind)?;
108            return Finite::new(value).map(Self::Float);
109        }
110        if let Some(value) = number.to_i64() {
111            return Ok(Self::Integer(value));
112        }
113        number
114            .to_u64()
115            .map(Self::Unsigned)
116            .ok_or(ValueError::IntegerOutOfRange)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn whole_floats_stay_floats() {
126        let two = Scalar::number(&VNumber::from_f64(2.0));
127        assert_eq!(two, Ok(Scalar::Float(Finite::new(2.0).unwrap())));
128        let big = 2.0f64.powi(63);
129        let two_63 = Scalar::number(&VNumber::from_f64(big));
130        assert_eq!(two_63, Ok(Scalar::Float(Finite::new(big).unwrap())));
131    }
132
133    #[test]
134    fn integers_above_u64_are_rejected() {
135        let huge = VNumber::from_u128(u128::from(u64::MAX) + 1);
136        assert_eq!(Scalar::number(&huge), Err(ValueError::IntegerOutOfRange));
137    }
138}