hayro_syntax/object/
number.rs1use 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#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Number(pub(crate) InternalNumber);
13
14impl Number {
15 pub const ZERO: Number = Number::from_i32(0);
17 pub const ONE: Number = Number::from_i32(1);
19
20 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 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 if converted as i32 != i {
37 debug!("integer {i} was truncated to {converted}");
38 }
39
40 converted
41 }
42 }
43 }
44
45 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 pub const fn from_f32(num: f32) -> Self {
63 Self(InternalNumber::Real(num))
64 }
65
66 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 let data = r.skip::<Number>(ctx.in_content_stream)?;
101 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 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}