gambit_parser/
unescaped.rs

1//! A wrapper for strings that have escape characters in them
2use std::fmt::{self, Display, Formatter};
3use std::iter::FusedIterator;
4use std::str::Chars;
5
6/// A string with backslash escapes in it
7///
8/// This wrapper allows referencing escaped strings in the source while preventing improper use.
9/// Use [EscapedStr::as_raw_str] to access the underlying buffer, or the [Display] trait to get
10/// owned versions. Use [EscapedStr::unescape] to access a [char] iter.
11#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
12pub struct EscapedStr {
13    escaped: str,
14}
15
16impl EscapedStr {
17    // NOTE this could panic if not called on a validated string
18    pub(crate) fn new<S: AsRef<str> + ?Sized>(escaped: &S) -> &Self {
19        unsafe { &*(escaped.as_ref() as *const str as *const EscapedStr) }
20    }
21
22    /// Access the underly buffer with escape sequences in it
23    pub fn as_raw_str(&self) -> &str {
24        &self.escaped
25    }
26
27    /// Get an iterator over the true characters
28    pub fn unescape(&self) -> Unescaped<'_> {
29        Unescaped {
30            chars: self.escaped.chars(),
31        }
32    }
33}
34
35impl Display for EscapedStr {
36    fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), fmt::Error> {
37        write!(out, "{}", self.unescape())
38    }
39}
40
41/// An iterator over the true characters of an [EscapedStr]
42#[derive(Debug, Clone)]
43pub struct Unescaped<'a> {
44    chars: Chars<'a>,
45}
46
47impl<'a> Display for Unescaped<'a> {
48    fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), fmt::Error> {
49        for chr in self.clone() {
50            write!(out, "{}", chr)?;
51        }
52        Ok(())
53    }
54}
55
56impl<'a> Iterator for Unescaped<'a> {
57    type Item = char;
58
59    fn next(&mut self) -> Option<Self::Item> {
60        match self.chars.next() {
61            Some('\\') => Some(self.chars.next().unwrap()),
62            chr => chr,
63        }
64    }
65
66    fn size_hint(&self) -> (usize, Option<usize>) {
67        let (min, max) = self.chars.size_hint();
68        ((min + 1) / 2, max)
69    }
70}
71
72impl<'a> FusedIterator for Unescaped<'a> {}
73
74#[cfg(test)]
75mod tests {
76    use super::EscapedStr;
77
78    #[test]
79    fn test_formatting() {
80        let escaped = EscapedStr::new("air \\\" quote");
81        let res = escaped.to_string();
82        assert_eq!(res, "air \" quote");
83    }
84}