postscript/compact1/
number.rs

1use crate::Result;
2
3macro_rules! reject(() => (raise!("found a malformed number")));
4
5/// A number.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum Number {
8    /// An integer number.
9    Integer(i32),
10    /// A real number.
11    Real(f32),
12}
13
14impl From<f32> for Number {
15    #[inline]
16    fn from(value: f32) -> Self {
17        Number::Real(value)
18    }
19}
20
21impl From<i32> for Number {
22    #[inline]
23    fn from(value: i32) -> Self {
24        Number::Integer(value)
25    }
26}
27
28impl crate::value::Read for Number {
29    fn read<T: crate::tape::Read>(tape: &mut T) -> Result<Self> {
30        let first = tape.take::<u8>()?;
31        Ok(match first {
32            0x20..=0xf6 => Number::Integer(first as i32 - 139),
33            0xf7..=0xfa => {
34                Number::Integer((first as i32 - 247) * 256 + tape.take::<u8>()? as i32 + 108)
35            }
36            0xfb..=0xfe => {
37                Number::Integer(-(first as i32 - 251) * 256 - tape.take::<u8>()? as i32 - 108)
38            }
39            0x1c => Number::Integer(tape.take::<u16>()? as i16 as i32),
40            0x1d => Number::Integer(tape.take::<u32>()? as i32),
41            0x1e => Number::Real(parse(tape)?),
42            _ => reject!(),
43        })
44    }
45}
46
47fn parse<T: crate::tape::Read>(tape: &mut T) -> Result<f32> {
48    let mut buffer = String::new();
49    let mut byte = 0;
50    let mut high = true;
51    loop {
52        let nibble = match high {
53            true => {
54                byte = tape.take::<u8>()?;
55                byte >> 4
56            }
57            false => byte & 0x0f,
58        };
59        high = !high;
60        match nibble {
61            0..=9 => buffer.push((b'0' + nibble) as char),
62            0x0a => buffer.push('.'),
63            0x0b => buffer.push('e'),
64            0x0c => buffer.push_str("e-"),
65            0x0e => buffer.push('-'),
66            0x0f => break,
67            _ => reject!(),
68        }
69    }
70    match buffer.parse() {
71        Ok(value) => Ok(value),
72        _ => reject!(),
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use std::io::Cursor;
79
80    use super::Number;
81    use crate::tape::Read;
82
83    #[test]
84    fn integer() {
85        macro_rules! read(
86            ($tape:expr) => (
87                match $tape.take().unwrap() {
88                    Number::Integer(value) => value,
89                    _ => unreachable!(),
90                }
91            )
92        );
93
94        let mut tape = Cursor::new(vec![0x8b]);
95        assert_eq!(read!(tape), 0);
96
97        let mut tape = Cursor::new(vec![0xef]);
98        assert_eq!(read!(tape), 100);
99
100        let mut tape = Cursor::new(vec![0x27]);
101        assert_eq!(read!(tape), -100);
102
103        let mut tape = Cursor::new(vec![0xfa, 0x7c]);
104        assert_eq!(read!(tape), 1000);
105
106        let mut tape = Cursor::new(vec![0xfe, 0x7c]);
107        assert_eq!(read!(tape), -1000);
108
109        let mut tape = Cursor::new(vec![0x1c, 0x27, 0x10]);
110        assert_eq!(read!(tape), 10000);
111
112        let mut tape = Cursor::new(vec![0x1c, 0xd8, 0xf0]);
113        assert_eq!(read!(tape), -10000);
114
115        let mut tape = Cursor::new(vec![0x1d, 0x00, 0x01, 0x86, 0xa0]);
116        assert_eq!(read!(tape), 100000);
117
118        let mut tape = Cursor::new(vec![0x1d, 0xff, 0xfe, 0x79, 0x60]);
119        assert_eq!(read!(tape), -100000);
120    }
121
122    #[test]
123    fn real() {
124        macro_rules! read(
125            ($tape:expr) => (
126                match $tape.take().unwrap() {
127                    Number::Real(value) => value,
128                    _ => unreachable!(),
129                }
130            )
131        );
132
133        let mut tape = Cursor::new(vec![0x1e, 0xe2, 0xa2, 0x5f, 0x0f]);
134        assert!((read!(tape) + 2.25).abs() < 1e-14);
135
136        let mut tape = Cursor::new(vec![0x1e, 0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff, 0x0f]);
137        assert!((read!(tape) - 0.140541e-3).abs() < 1e-14);
138    }
139}