svgtypes/
number.rs
1use std::str::FromStr;
5
6use crate::{ByteExt, Error, Stream};
7
8#[derive(Clone, Copy, PartialEq, Debug)]
10pub struct Number(pub f64);
11
12impl std::str::FromStr for Number {
13 type Err = Error;
14
15 fn from_str(text: &str) -> Result<Self, Self::Err> {
16 let mut s = Stream::from(text);
17 let n = s.parse_number()?;
18 s.skip_spaces();
19 if !s.at_end() {
20 return Err(Error::UnexpectedData(s.calc_char_pos()));
21 }
22
23 Ok(Self(n))
24 }
25}
26
27impl Stream<'_> {
28 pub fn parse_number(&mut self) -> Result<f64, Error> {
39 self.skip_spaces();
41
42 let start = self.pos();
43
44 if self.at_end() {
45 return Err(Error::InvalidNumber(self.calc_char_pos_at(start)));
46 }
47
48 self.parse_number_impl()
49 .map_err(|_| Error::InvalidNumber(self.calc_char_pos_at(start)))
50 }
51
52 fn parse_number_impl(&mut self) -> Result<f64, Error> {
53 let start = self.pos();
54
55 let mut c = self.curr_byte()?;
56
57 if c.is_sign() {
59 self.advance(1);
60 c = self.curr_byte()?;
61 }
62
63 match c {
65 b'0'..=b'9' => self.skip_digits(),
66 b'.' => {}
67 _ => return Err(Error::InvalidNumber(0)),
68 }
69
70 if let Ok(b'.') = self.curr_byte() {
72 self.advance(1);
73 self.skip_digits();
74 }
75
76 if let Ok(c) = self.curr_byte() {
77 if matches!(c, b'e' | b'E') {
78 let c2 = self.next_byte()?;
79 if c2 != b'm' && c2 != b'x' {
81 self.advance(1);
82
83 match self.curr_byte()? {
84 b'+' | b'-' => {
85 self.advance(1);
86 self.skip_digits();
87 }
88 b'0'..=b'9' => self.skip_digits(),
89 _ => {
90 return Err(Error::InvalidNumber(0));
91 }
92 }
93 }
94 }
95 }
96
97 let s = self.slice_back(start);
98
99 if let Ok(n) = f64::from_str(s) {
101 if n.is_finite() {
103 return Ok(n);
104 }
105 }
106
107 Err(Error::InvalidNumber(0))
108 }
109
110 pub fn parse_list_number(&mut self) -> Result<f64, Error> {
112 if self.at_end() {
113 return Err(Error::UnexpectedEndOfStream);
114 }
115
116 let n = self.parse_number()?;
117 self.skip_spaces();
118 self.parse_list_separator();
119 Ok(n)
120 }
121}
122
123#[derive(Clone, Copy, PartialEq, Eq, Debug)]
139pub struct NumberListParser<'a>(Stream<'a>);
140
141impl<'a> From<&'a str> for NumberListParser<'a> {
142 #[inline]
143 fn from(v: &'a str) -> Self {
144 NumberListParser(Stream::from(v))
145 }
146}
147
148impl Iterator for NumberListParser<'_> {
149 type Item = Result<f64, Error>;
150
151 fn next(&mut self) -> Option<Self::Item> {
152 if self.0.at_end() {
153 None
154 } else {
155 let v = self.0.parse_list_number();
156 if v.is_err() {
157 self.0.jump_to_end();
158 }
159
160 Some(v)
161 }
162 }
163}
164
165#[rustfmt::skip]
166#[cfg(test)]
167mod tests {
168 use crate::Stream;
169
170 macro_rules! test_p {
171 ($name:ident, $text:expr, $result:expr) => (
172 #[test]
173 fn $name() {
174 let mut s = Stream::from($text);
175 assert_eq!(s.parse_number().unwrap(), $result);
176 }
177 )
178 }
179
180 test_p!(parse_1, "0", 0.0);
181 test_p!(parse_2, "1", 1.0);
182 test_p!(parse_3, "-1", -1.0);
183 test_p!(parse_4, " -1 ", -1.0);
184 test_p!(parse_5, " 1 ", 1.0);
185 test_p!(parse_6, ".4", 0.4);
186 test_p!(parse_7, "-.4", -0.4);
187 test_p!(parse_8, "-.4text", -0.4);
188 test_p!(parse_9, "-.01 text", -0.01);
189 test_p!(parse_10, "-.01 4", -0.01);
190 test_p!(parse_11, ".0000000000008", 0.0000000000008);
191 test_p!(parse_12, "1000000000000", 1000000000000.0);
192 test_p!(parse_13, "123456.123456", 123456.123456);
193 test_p!(parse_14, "+10", 10.0);
194 test_p!(parse_15, "1e2", 100.0);
195 test_p!(parse_16, "1e+2", 100.0);
196 test_p!(parse_17, "1E2", 100.0);
197 test_p!(parse_18, "1e-2", 0.01);
198 test_p!(parse_19, "1ex", 1.0);
199 test_p!(parse_20, "1em", 1.0);
200 test_p!(parse_21, "12345678901234567890", 12345678901234567000.0);
201 test_p!(parse_22, "0.", 0.0);
202 test_p!(parse_23, "1.3e-2", 0.013);
203 macro_rules! test_p_err {
206 ($name:ident, $text:expr) => (
207 #[test]
208 fn $name() {
209 let mut s = Stream::from($text);
210 assert_eq!(s.parse_number().unwrap_err().to_string(),
211 "invalid number at position 1");
212 }
213 )
214 }
215
216 test_p_err!(parse_err_1, "q");
217 test_p_err!(parse_err_2, "");
218 test_p_err!(parse_err_3, "-");
219 test_p_err!(parse_err_4, "+");
220 test_p_err!(parse_err_5, "-q");
221 test_p_err!(parse_err_6, ".");
222 test_p_err!(parse_err_7, "99999999e99999999");
223 test_p_err!(parse_err_8, "-99999999e99999999");
224}