ferroui_base/rendering/composition/expressions/
token_parser.rs1#[inline]
7fn is_white_space(ch: char) -> bool {
8 ch.is_whitespace() || ('\u{1c}'..='\u{1f}').contains(&ch)
9}
10
11#[inline]
12fn is_alpha_numeric(ch: u8) -> bool {
13 ch.is_ascii_alphanumeric()
14}
15
16#[inline]
19fn to_lower_invariant(ch: char) -> char {
20 if ch.is_ascii() {
21 return ch.to_ascii_lowercase();
22 }
23 let mut lower = ch.to_lowercase();
24 match (lower.next(), lower.next()) {
25 (Some(single), None) => single,
26 _ => ch,
27 }
28}
29
30#[derive(Clone, Copy, Debug)]
36pub struct TokenParser<'a> {
37 s: &'a str,
38 position: i32,
39}
40
41impl<'a> TokenParser<'a> {
42 pub fn new(s: &'a str) -> Self {
44 Self { s, position: 0 }
45 }
46
47 pub fn position(&self) -> i32 {
49 self.position
50 }
51
52 #[inline]
54 fn advance_raw(&mut self, bytes: usize, units: usize) {
55 self.s = &self.s[bytes..];
56 self.position += units as i32;
57 }
58
59 #[inline]
60 fn first_byte(&self) -> Option<u8> {
61 self.s.as_bytes().first().copied()
62 }
63
64 pub fn skip_whitespace(&mut self) {
65 while let Some(ch) = self.s.chars().next() {
66 if is_white_space(ch) {
67 self.advance_raw(ch.len_utf8(), ch.len_utf16());
68 } else {
69 return;
70 }
71 }
72 }
73
74 pub fn next_is_whitespace(&self) -> bool {
75 self.s.chars().next().is_some_and(is_white_space)
76 }
77
78 pub fn try_consume(&mut self, c: char) -> bool {
80 self.skip_whitespace();
81 if !self.s.starts_with(c) {
82 return false;
83 }
84
85 self.advance_raw(c.len_utf8(), c.len_utf16());
86 true
87 }
88
89 pub fn try_consume_str(&mut self, s: &str) -> bool {
91 self.skip_whitespace();
92 if !self.s.starts_with(s) {
93 return false;
94 }
95
96 self.advance_raw(s.len(), s.encode_utf16().count());
97 true
98 }
99
100 pub fn try_consume_any(&mut self, chars: &str) -> Option<char> {
102 self.skip_whitespace();
103 let first = self.s.chars().next()?;
104
105 for c in chars.chars() {
106 if c == first {
107 self.advance_raw(c.len_utf8(), c.len_utf16());
108 return Some(c);
109 }
110 }
111
112 None
113 }
114
115 pub fn try_parse_keyword(&mut self, keyword: &str) -> bool {
118 self.skip_whitespace();
119 if !self.s.starts_with(keyword) {
120 return false;
121 }
122
123 if self.s.as_bytes().get(keyword.len()).copied().is_some_and(is_alpha_numeric) {
124 return false;
125 }
126
127 self.advance_raw(keyword.len(), keyword.encode_utf16().count());
128 true
129 }
130
131 pub fn try_parse_keyword_lower_case(&mut self, keyword_in_lower_case: &str) -> bool {
134 self.skip_whitespace();
135 let mut bytes = 0;
136 let mut units = 0;
137 let mut text = self.s.chars();
138 for expected in keyword_in_lower_case.chars() {
139 match text.next() {
140 Some(ch) if to_lower_invariant(ch) == expected => {
141 bytes += ch.len_utf8();
142 units += ch.len_utf16();
143 }
144 _ => return false,
145 }
146 }
147
148 if self.s.as_bytes().get(bytes).copied().is_some_and(is_alpha_numeric) {
149 return false;
150 }
151
152 self.advance_raw(bytes, units);
153 true
154 }
155
156 pub fn advance(&mut self, c: i32) {
158 let mut bytes = 0;
159 let mut units = 0;
160 for ch in self.s.chars() {
161 if units >= c.max(0) as usize {
162 break;
163 }
164 bytes += ch.len_utf8();
165 units += ch.len_utf16();
166 }
167 self.advance_raw(bytes, units);
168 }
169
170 pub fn length(&self) -> i32 {
172 if self.s.is_ascii() {
173 self.s.len() as i32
174 } else {
175 self.s.encode_utf16().count() as i32
176 }
177 }
178
179 pub fn try_parse_identifier_with(&mut self, extra_valid_chars: &str) -> Option<&'a str> {
182 self.skip_whitespace();
183 let first = self.first_byte()?;
184 if !first.is_ascii_alphabetic() {
185 return None;
186 }
187 let mut len = 1;
188 for ch in self.s[1..].chars() {
189 if (ch.is_ascii() && is_alpha_numeric(ch as u8)) || extra_valid_chars.contains(ch) {
190 len += ch.len_utf8();
191 } else {
192 break;
193 }
194 }
195
196 let res = &self.s[..len];
197 self.advance_raw(len, res.encode_utf16().count());
198 Some(res)
199 }
200
201 pub fn try_parse_identifier(&mut self) -> Option<&'a str> {
203 self.skip_whitespace();
204 let first = self.first_byte()?;
205 if !first.is_ascii_alphabetic() {
206 return None;
207 }
208 let bytes = self.s.as_bytes();
209 let mut len = 1;
210 while len < bytes.len() && is_alpha_numeric(bytes[len]) {
211 len += 1;
212 }
213
214 let res = &self.s[..len];
215 self.advance_raw(len, len);
216 Some(res)
217 }
218
219 pub fn try_parse_call(&mut self) -> Option<&'a str> {
223 self.skip_whitespace();
224 let first = self.first_byte()?;
225 if !first.is_ascii_alphabetic() {
226 return None;
227 }
228 let bytes = self.s.as_bytes();
229 let mut len = 1;
230 while len < bytes.len() && (is_alpha_numeric(bytes[len]) || bytes[len] == b'.') {
231 len += 1;
232 }
233
234 let res = &self.s[..len];
235
236 let mut consumed_bytes = len;
238 let mut consumed_units = len;
239 for ch in self.s[len..].chars() {
240 consumed_bytes += ch.len_utf8();
241 consumed_units += ch.len_utf16();
242 if is_white_space(ch) {
243 continue;
244 }
245 if ch == '(' {
246 self.advance_raw(consumed_bytes, consumed_units);
247 return Some(res);
248 }
249
250 return None;
251 }
252
253 None
254 }
255
256 fn scan_number(&self, fail_on_inner_minus: bool) -> Option<usize> {
265 let mut len = 0;
266 let mut dot_count = 0;
267 for (c, &ch) in self.s.as_bytes().iter().enumerate() {
268 if ch.is_ascii_digit() {
269 len = c + 1;
270 } else if ch == b'.' && dot_count == 0 {
271 len = c + 1;
272 dot_count += 1;
273 } else if ch == b'-' {
274 if len != 0 {
275 if fail_on_inner_minus {
276 return None;
277 }
278 break;
279 }
280 len = c + 1;
281 } else {
282 break;
283 }
284 }
285 Some(len)
286 }
287
288 pub fn try_parse_float(&mut self) -> Option<f32> {
290 self.skip_whitespace();
291 if self.s.is_empty() {
292 return None;
293 }
294
295 let len = self.scan_number(false)?;
296 let res = self.s[..len].parse::<f32>().ok()?;
297 self.advance_raw(len, len);
298 Some(res)
299 }
300
301 pub fn try_parse_double(&mut self) -> Option<f64> {
303 self.skip_whitespace();
304 if self.s.is_empty() {
305 return None;
306 }
307
308 let len = self.scan_number(true)?;
309 let res = self.s[..len].parse::<f64>().ok()?;
310 self.advance_raw(len, len);
311 Some(res)
312 }
313
314 pub fn is_eof_with_whitespace(&mut self) -> bool {
316 self.skip_whitespace();
317 self.s.is_empty()
318 }
319
320 pub fn as_str(&self) -> &'a str {
322 self.s
323 }
324}
325
326impl std::fmt::Display for TokenParser<'_> {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 f.write_str(self.s)
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn consumes_characters_and_strings_after_white_space() {
338 let mut p = TokenParser::new(" ( >= x");
339 assert!(!p.try_consume(')'));
340 assert_eq!(p.position(), 2);
341 assert!(p.try_consume('('));
342 assert!(!p.try_consume_str(">>"));
343 assert!(p.try_consume_str(">="));
344 assert_eq!(p.try_consume_any("+-"), None);
345 assert_eq!(p.try_consume_any("yx"), Some('x'));
346 assert_eq!(p.length(), 0);
347 assert_eq!(p.position(), 8);
348 assert!(!p.try_consume('x'));
349 assert_eq!(p.try_consume_any("x"), None);
350 assert!(p.is_eof_with_whitespace());
351 }
352
353 #[test]
354 fn white_space_follows_the_reference_definition() {
355 let mut p = TokenParser::new("\u{1c}\u{a0}\u{2003}\t\r\nx");
356 assert!(p.next_is_whitespace());
357 p.skip_whitespace();
358 assert_eq!(p.position(), 6);
359 assert_eq!(p.to_string(), "x");
360 assert!(!p.next_is_whitespace());
361 }
362
363 #[test]
364 fn parses_keywords() {
365 let mut p = TokenParser::new(" This.TARGET.Offset");
366 assert!(!p.try_parse_keyword("this.target"));
367 assert!(p.try_parse_keyword_lower_case("this.target"));
368 assert_eq!(p.as_str(), ".Offset");
369 assert_eq!(p.position(), 12);
370
371 let mut p = TokenParser::new("pixel");
372 assert!(!p.try_parse_keyword_lower_case("pi"));
373 assert!(!p.try_parse_keyword("pi"));
374 assert_eq!(p.position(), 0);
375
376 let mut p = TokenParser::new("pi");
377 assert!(p.try_parse_keyword("pi"));
378 assert_eq!(p.length(), 0);
379
380 let mut p = TokenParser::new("p");
381 assert!(!p.try_parse_keyword("pi"));
382 assert!(!p.try_parse_keyword_lower_case("pi"));
383
384 let mut p = TokenParser::new("PI*2");
385 assert!(p.try_parse_keyword_lower_case("pi"));
386 assert_eq!(p.as_str(), "*2");
387 }
388
389 #[test]
390 fn parses_identifiers() {
391 let mut p = TokenParser::new(" abc1_d");
392 assert_eq!(p.try_parse_identifier(), Some("abc1"));
393 assert_eq!(p.try_parse_identifier(), None);
394 assert_eq!(p.as_str(), "_d");
395
396 let mut p = TokenParser::new("abc1_d.e f");
397 assert_eq!(p.try_parse_identifier_with("_."), Some("abc1_d.e"));
398 assert_eq!(p.position(), 8);
399
400 assert_eq!(TokenParser::new("1abc").try_parse_identifier(), None);
401 assert_eq!(TokenParser::new("1abc").try_parse_identifier_with("1"), None);
402 assert_eq!(TokenParser::new("").try_parse_identifier(), None);
403 }
404
405 #[test]
406 fn parses_calls() {
407 let mut p = TokenParser::new(" Matrix3x2.CreateScale (1)");
408 assert_eq!(p.try_parse_call(), Some("Matrix3x2.CreateScale"));
409 assert_eq!(p.as_str(), "1)");
410 assert_eq!(p.position(), 25);
411
412 let mut p = TokenParser::new(" abc + (1)");
414 assert_eq!(p.try_parse_call(), None);
415 assert_eq!(p.as_str(), "abc + (1)");
416 assert_eq!(TokenParser::new("abc").try_parse_call(), None);
417 assert_eq!(TokenParser::new("(1)").try_parse_call(), None);
418 }
419
420 #[test]
421 fn parses_numbers() {
422 let mut p = TokenParser::new(" 4.0-0.5)");
423 assert_eq!(p.try_parse_float(), Some(4.0));
424 assert_eq!(p.as_str(), "-0.5)");
425 assert_eq!(p.try_parse_float(), Some(-0.5));
426 assert_eq!(p.try_parse_float(), None);
427 assert_eq!(p.as_str(), ")");
428
429 assert_eq!(TokenParser::new("1.").try_parse_float(), Some(1.0));
430 assert_eq!(TokenParser::new(".5").try_parse_float(), Some(0.5));
431 assert_eq!(TokenParser::new("-.5").try_parse_float(), Some(-0.5));
432 assert_eq!(TokenParser::new("0.1").try_parse_float(), Some(0.1));
433 assert_eq!(TokenParser::new("-").try_parse_float(), None);
434 assert_eq!(TokenParser::new(".").try_parse_float(), None);
435 assert_eq!(TokenParser::new("-.").try_parse_float(), None);
436 assert_eq!(TokenParser::new("").try_parse_float(), None);
437 assert_eq!(TokenParser::new("x").try_parse_float(), None);
438 assert_eq!(
439 TokenParser::new("99999999999999999999999999999999999999999999").try_parse_float(),
440 Some(f32::INFINITY)
441 );
442
443 let mut p = TokenParser::new("1.2.3");
444 assert_eq!(p.try_parse_float(), Some(1.2));
445 assert_eq!(p.as_str(), ".3");
446
447 assert_eq!(TokenParser::new("2.5e").try_parse_double(), Some(2.5));
448 assert_eq!(TokenParser::new("-2.5").try_parse_double(), Some(-2.5));
449 assert_eq!(TokenParser::new("4.0-0.5").try_parse_double(), None);
451 }
452
453 #[test]
454 fn advance_counts_utf16_code_units() {
455 let mut p = TokenParser::new("a\u{e9}\u{1f600}b");
456 assert_eq!(p.length(), 5);
457 p.advance(2);
458 assert_eq!(p.position(), 2);
459 p.advance(2);
460 assert_eq!(p.position(), 4);
461 assert_eq!(p.as_str(), "b");
462 }
463}