StrParser
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 30 Mar 2025 03:38:46 +0000 (20:38 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 30 Mar 2025 03:38:46 +0000 (20:38 -0700)
rust/pspp/src/format/parse.rs

index 6ee2d3bea30fa0540652247dbb750b7308d471d4..c6e05b6f126c3f03466cbc56faf2a1701defab02 100644 (file)
@@ -192,22 +192,7 @@ impl<'a> ParseValue<'a> {
         if input.is_empty() || input == "." {
             return Ok(Value::sysmis());
         }
-        fn strip_prefix<'a>(input: &'a str, prefix: &str) -> (bool, &'a str) {
-            if prefix.is_empty() {
-                (false, input)
-            } else if let Some(rest) = input.strip_prefix(prefix) {
-                (true, rest.trim_start())
-            } else {
-                (false, input)
-            }
-        }
-        fn strip_one_of<'a>(input: &'a str, chars: &[char]) -> (Option<char>, &'a str) {
-            let mut iter = input.chars();
-            match iter.next() {
-                Some(c) if chars.contains(&c) => (Some(c), iter.as_str().trim_start()),
-                _ => (None, input),
-            }
-        }
+        let mut p = StrParser::new(input.trim());
         fn strip_integer(mut input: &str, grouping: Option<char>) -> &str {
             while let Some(rest) = input.strip_prefix(|c: char| c.is_ascii_digit()) {
                 let rest = if let Some(grouping) = grouping {
@@ -220,31 +205,34 @@ impl<'a> ParseValue<'a> {
             input
         }
 
-        let (_, input) = strip_prefix(input, &*style.prefix.s);
-        let (sign, input) = strip_one_of(input, &['-', '+']);
-        let input = if sign.is_some() {
-            strip_prefix(input, &*style.prefix.s).1
-        } else {
-            input
-        };
-        let (integer, input) = take(input, strip_integer(input, style.grouping.map(char::from)));
-        let (decimals, input) = if let Some(rest) = input.strip_prefix(style.decimal.as_str()) {
-            take(rest, rest.trim_start_matches(|c: char| c.is_ascii_digit()))
+        if p.strip_prefix(&*style.prefix.s) {
+            p.strip_ws();
+        }
+        let sign = p.strip_one_of(&['-', '+']).inspect(|_| p.strip_ws());
+        if sign.is_some() && p.strip_prefix(&*style.prefix.s) {
+            p.strip_ws();
+        }
+        let integer = p.advance(strip_integer(p.0, style.grouping.map(char::from)));
+        let decimals = if p.strip_prefix(style.decimal.as_str()) {
+            p.strip_matches(|c| c.is_ascii_digit())
         } else {
-            ("", input)
+            ""
         };
-        let (exp_sign, exponent, input) = if input.starts_with(['e', 'E', 'd', 'D', '+', '-']) {
-            let (_e, rest) = strip_one_of(input, &['e', 'E', 'd', 'D']);
-            let (exp_sign, rest) = strip_one_of(rest, &['-', '+']);
-            let (exponent, rest) =
-                take(rest, rest.trim_start_matches(|c: char| c.is_ascii_digit()));
-            (exp_sign, exponent, rest)
+        let (exp_sign, exponent) = if p.0.starts_with(['e', 'E', 'd', 'D', '+', '-']) {
+            let _e = p
+                .strip_one_of(&['e', 'E', 'd', 'D'])
+                .inspect(|_| p.strip_ws());
+            let exp_sign = p.strip_one_of(&['-', '+']).inspect(|_| p.strip_ws());
+            let exponent = p.strip_matches(|c| c.is_ascii_digit());
+            (exp_sign, exponent)
         } else {
-            (None, "", input)
+            (None, "")
         };
-        let (_, input) = strip_prefix(input, &*style.suffix.s);
+        if p.strip_prefix(&*style.suffix.s) {
+            p.strip_ws();
+        }
 
-        if !input.is_empty() {
+        if !p.0.is_empty() {
             return Err(ParseErrorKind::NotNumeric);
         }
 
@@ -263,6 +251,7 @@ impl<'a> ParseValue<'a> {
             }
             write!(&mut number, "{exponent}").unwrap();
         }
+        println!("{number}");
 
         match f64::from_str(&number) {
             Ok(value) => Ok(Value::Number(Some(value))),
@@ -440,6 +429,51 @@ where
     }
 }
 
+#[derive(Copy, Clone, Debug)]
+pub struct StrParser<'a>(pub &'a str);
+
+impl<'a> StrParser<'a> {
+    pub fn new(s: &'a str) -> Self {
+        Self(s)
+    }
+
+    pub fn strip_prefix(&mut self, prefix: &'a str) -> bool {
+        if prefix.is_empty() {
+            false
+        } else if let Some(rest) = self.0.strip_prefix(prefix) {
+            self.0 = rest;
+            true
+        } else {
+            false
+        }
+    }
+
+    fn strip_one_of(&mut self, chars: &[char]) -> Option<char> {
+        let mut iter = self.0.chars();
+        match iter.next() {
+            Some(c) if chars.contains(&c) => {
+                self.0 = iter.as_str();
+                Some(c)
+            }
+            _ => None,
+        }
+    }
+
+    fn strip_matches(&mut self, f: impl Fn(char) -> bool) -> &'a str {
+        self.advance(self.0.trim_start_matches(f))
+    }
+
+    fn strip_ws(&mut self) {
+        self.0 = self.0.trim_start();
+    }
+
+    fn advance(&mut self, rest: &'a str) -> &'a str {
+        let head = &self.0[..self.0.len() - rest.len()];
+        self.0 = rest;
+        head
+    }
+}
+
 #[cfg(test)]
 mod test {
     use std::{