hayro_syntax/object/
number.rs

1//! Numbers.
2
3use crate::object::macros::object;
4use crate::object::{Object, ObjectLike};
5use crate::reader::{Readable, Reader, ReaderContext, Skippable};
6use log::debug;
7use std::fmt::Debug;
8use std::str::FromStr;
9
10/// A number.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Number(pub(crate) InternalNumber);
13
14impl Number {
15    /// The number zero.
16    pub const ZERO: Number = Number::from_i32(0);
17    /// The number one.
18    pub const ONE: Number = Number::from_i32(1);
19
20    /// Returns the number as a f64.
21    pub fn as_f64(&self) -> f64 {
22        match self.0 {
23            InternalNumber::Real(r) => r as f64,
24            InternalNumber::Integer(i) => i as f64,
25        }
26    }
27
28    /// Returns the number as a f32.
29    pub fn as_f32(&self) -> f32 {
30        match self.0 {
31            InternalNumber::Real(r) => r,
32            InternalNumber::Integer(i) => {
33                let converted = i as f32;
34
35                // Double check whether conversion didn't overflow.
36                if converted as i32 != i {
37                    debug!("integer {i} was truncated to {converted}");
38                }
39
40                converted
41            }
42        }
43    }
44
45    /// Returns the number as an i32.
46    pub fn as_i32(&self) -> i32 {
47        match self.0 {
48            InternalNumber::Real(r) => {
49                let res = r as i32;
50
51                if !(r.trunc() == r) {
52                    debug!("float {r} was truncated to {res}");
53                }
54
55                res
56            }
57            InternalNumber::Integer(i) => i,
58        }
59    }
60
61    /// Create a new `Number` from an f32 number.
62    pub const fn from_f32(num: f32) -> Self {
63        Self(InternalNumber::Real(num))
64    }
65
66    /// Create a new `Number` from an i32 number.
67    pub const fn from_i32(num: i32) -> Self {
68        Self(InternalNumber::Integer(num))
69    }
70}
71
72impl Skippable for Number {
73    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
74        r.forward_if(|b| b == b'+' || b == b'-');
75
76        match r.peek_byte()? {
77            b'.' => {
78                r.read_byte()?;
79                r.forward_while_1(is_digit)?;
80            }
81            (b'0'..=b'9') => {
82                r.forward_while_1(is_digit)?;
83                if let Some(()) = r.forward_tag(b".") {
84                    r.forward_while(is_digit);
85                }
86            }
87            _ => return None,
88        }
89
90        Some(())
91    }
92}
93
94impl Readable<'_> for Number {
95    fn read(r: &mut Reader<'_>, ctx: ReaderContext) -> Option<Self> {
96        // TODO: This function is probably the biggest bottleneck in content parsing, so
97        // worth optimizing (i.e. reading the number directly from the bytes instead
98        // of first parsing it to a number).
99
100        let data = r.skip::<Number>(ctx.in_content_stream)?;
101        // We need to use f64 here, so that we can still parse a full `i32` without losing
102        // precision.
103        let num = f64::from_str(std::str::from_utf8(data).ok()?).ok()?;
104
105        if num.fract() == 0.0 {
106            Some(Number(InternalNumber::Integer(num as i32)))
107        } else {
108            Some(Number(InternalNumber::Real(num as f32)))
109        }
110    }
111}
112
113object!(Number, Number);
114
115#[derive(Clone, Copy, Debug, PartialEq)]
116pub(crate) enum InternalNumber {
117    Real(f32),
118    Integer(i32),
119}
120
121macro_rules! int_num {
122    ($i:ident) => {
123        impl Skippable for $i {
124            fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
125                r.forward_if(|b| b == b'+' || b == b'-');
126                r.forward_while_1(is_digit)?;
127
128                // We have a float instead of an integer.
129                if r.peek_byte() == Some(b'.') {
130                    return None;
131                }
132
133                Some(())
134            }
135        }
136
137        impl<'a> Readable<'a> for $i {
138            fn read(r: &mut Reader<'a>, ctx: ReaderContext<'a>) -> Option<$i> {
139                r.read::<Number>(ctx)
140                    .map(|n| n.as_i32())
141                    .and_then(|n| n.try_into().ok())
142            }
143        }
144
145        impl TryFrom<Object<'_>> for $i {
146            type Error = ();
147
148            fn try_from(value: Object<'_>) -> std::result::Result<Self, Self::Error> {
149                match value {
150                    Object::Number(n) => n.as_i32().try_into().ok().ok_or(()),
151                    _ => Err(()),
152                }
153            }
154        }
155
156        impl<'a> ObjectLike<'a> for $i {}
157    };
158}
159
160int_num!(i32);
161int_num!(u32);
162int_num!(u16);
163int_num!(usize);
164int_num!(u8);
165
166impl Skippable for f32 {
167    fn skip(r: &mut Reader<'_>, is_content_stream: bool) -> Option<()> {
168        r.skip::<Number>(is_content_stream).map(|_| {})
169    }
170}
171
172impl Readable<'_> for f32 {
173    fn read(r: &mut Reader, _: ReaderContext) -> Option<Self> {
174        r.read_without_context::<Number>().map(|n| n.as_f32())
175    }
176}
177
178impl TryFrom<Object<'_>> for f32 {
179    type Error = ();
180
181    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
182        match value {
183            Object::Number(n) => Ok(n.as_f32()),
184            _ => Err(()),
185        }
186    }
187}
188
189impl ObjectLike<'_> for f32 {}
190
191impl Skippable for f64 {
192    fn skip(r: &mut Reader<'_>, is_content_stream: bool) -> Option<()> {
193        r.skip::<Number>(is_content_stream).map(|_| {})
194    }
195}
196
197impl Readable<'_> for f64 {
198    fn read(r: &mut Reader, _: ReaderContext) -> Option<Self> {
199        r.read_without_context::<Number>().map(|n| n.as_f64())
200    }
201}
202
203impl TryFrom<Object<'_>> for f64 {
204    type Error = ();
205
206    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
207        match value {
208            Object::Number(n) => Ok(n.as_f64()),
209            _ => Err(()),
210        }
211    }
212}
213
214impl ObjectLike<'_> for f64 {}
215
216pub(crate) fn is_digit(byte: u8) -> bool {
217    byte.is_ascii_digit()
218}
219
220#[cfg(test)]
221mod tests {
222    use crate::object::Number;
223    use crate::reader::Reader;
224
225    #[test]
226    fn int_1() {
227        assert_eq!(
228            Reader::new("0".as_bytes())
229                .read_without_context::<i32>()
230                .unwrap(),
231            0
232        );
233    }
234
235    #[test]
236    fn int_3() {
237        assert_eq!(
238            Reader::new("+32".as_bytes())
239                .read_without_context::<i32>()
240                .unwrap(),
241            32
242        );
243    }
244
245    #[test]
246    fn int_4() {
247        assert_eq!(
248            Reader::new("-32".as_bytes())
249                .read_without_context::<i32>()
250                .unwrap(),
251            -32
252        );
253    }
254
255    #[test]
256    fn int_6() {
257        assert_eq!(
258            Reader::new("98349".as_bytes())
259                .read_without_context::<i32>()
260                .unwrap(),
261            98349
262        );
263    }
264
265    #[test]
266    fn int_7() {
267        assert_eq!(
268            Reader::new("003245".as_bytes())
269                .read_without_context::<i32>()
270                .unwrap(),
271            3245
272        );
273    }
274
275    #[test]
276    fn int_trailing() {
277        assert_eq!(
278            Reader::new("0abc".as_bytes())
279                .read_without_context::<i32>()
280                .unwrap(),
281            0
282        );
283    }
284
285    #[test]
286    fn real_1() {
287        assert_eq!(
288            Reader::new("3".as_bytes())
289                .read_without_context::<f32>()
290                .unwrap(),
291            3.0
292        );
293    }
294
295    #[test]
296    fn real_3() {
297        assert_eq!(
298            Reader::new("+32".as_bytes())
299                .read_without_context::<f32>()
300                .unwrap(),
301            32.0
302        );
303    }
304
305    #[test]
306    fn real_4() {
307        assert_eq!(
308            Reader::new("-32".as_bytes())
309                .read_without_context::<f32>()
310                .unwrap(),
311            -32.0
312        );
313    }
314
315    #[test]
316    fn real_5() {
317        assert_eq!(
318            Reader::new("-32.01".as_bytes())
319                .read_without_context::<f32>()
320                .unwrap(),
321            -32.01
322        );
323    }
324
325    #[test]
326    fn real_6() {
327        assert_eq!(
328            Reader::new("-.345".as_bytes())
329                .read_without_context::<f32>()
330                .unwrap(),
331            -0.345
332        );
333    }
334
335    #[test]
336    fn real_7() {
337        assert_eq!(
338            Reader::new("-.00143".as_bytes())
339                .read_without_context::<f32>()
340                .unwrap(),
341            -0.00143
342        );
343    }
344
345    #[test]
346    fn real_8() {
347        assert_eq!(
348            Reader::new("-12.0013".as_bytes())
349                .read_without_context::<f32>()
350                .unwrap(),
351            -12.0013
352        );
353    }
354
355    #[test]
356    fn real_9() {
357        assert_eq!(
358            Reader::new("98349.432534".as_bytes())
359                .read_without_context::<f32>()
360                .unwrap(),
361            98_349.43
362        );
363    }
364
365    #[test]
366    fn real_10() {
367        assert_eq!(
368            Reader::new("-34534656.34".as_bytes())
369                .read_without_context::<f32>()
370                .unwrap(),
371            -34534656.34
372        );
373    }
374
375    #[test]
376    fn real_trailing() {
377        assert_eq!(
378            Reader::new("0abc".as_bytes())
379                .read_without_context::<f32>()
380                .unwrap(),
381            0.0
382        );
383    }
384
385    #[test]
386    fn real_failing() {
387        assert!(
388            Reader::new("+abc".as_bytes())
389                .read_without_context::<f32>()
390                .is_none()
391        );
392    }
393
394    #[test]
395    fn number_1() {
396        assert_eq!(
397            Reader::new("+32".as_bytes())
398                .read_without_context::<Number>()
399                .unwrap()
400                .as_f64() as f32,
401            32.0
402        );
403    }
404
405    #[test]
406    fn number_2() {
407        assert_eq!(
408            Reader::new("-32.01".as_bytes())
409                .read_without_context::<Number>()
410                .unwrap()
411                .as_f64() as f32,
412            -32.01
413        );
414    }
415
416    #[test]
417    fn number_3() {
418        assert_eq!(
419            Reader::new("-.345".as_bytes())
420                .read_without_context::<Number>()
421                .unwrap()
422                .as_f64() as f32,
423            -0.345
424        );
425    }
426
427    #[test]
428    fn large_number() {
429        assert_eq!(
430            Reader::new("38359922".as_bytes())
431                .read_without_context::<Number>()
432                .unwrap()
433                .as_i32(),
434            38359922
435        );
436    }
437}