hemtt_config/model/
str.rs

1use hemtt_tokens::{Symbol, Token};
2use peekmore::PeekMoreIterator;
3
4use crate::{
5    error::Error,
6    rapify::{Rapify, WriteExt},
7    Options,
8};
9
10use super::Parse;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13/// A string value
14pub struct Str(pub String);
15
16impl Parse for Str {
17    fn parse(
18        _options: &Options,
19        tokens: &mut PeekMoreIterator<impl Iterator<Item = Token>>,
20        from: &Token,
21    ) -> Result<Self, Error>
22    where
23        Self: Sized,
24    {
25        if let Some(token) = tokens.next() {
26            if token.symbol() != &Symbol::DoubleQuote {
27                return Err(Error::UnexpectedToken {
28                    token: Box::new(token),
29                    expected: vec![Symbol::DoubleQuote],
30                });
31            }
32        } else {
33            return Err(Error::UnexpectedEOF {
34                token: Box::new(from.clone()),
35            });
36        }
37        let mut string = String::new();
38        'outer: loop {
39            if let Some(token) = tokens.peek() {
40                match token.symbol() {
41                    Symbol::DoubleQuote => 'inner: loop {
42                        tokens.next();
43                        if let Some(token) = tokens.peek() {
44                            match token.symbol() {
45                                Symbol::DoubleQuote => {
46                                    tokens.next();
47                                    string.push('"');
48                                    break 'inner;
49                                }
50                                Symbol::Whitespace(_) => continue,
51                                Symbol::Escape => {
52                                    if tokens.peek_nth(1).unwrap().symbol()
53                                        == &Symbol::Word(String::from("n"))
54                                    {
55                                        tokens.next();
56                                        tokens.next();
57                                        string.push('\n');
58                                        loop {
59                                            if let Some(token) = tokens.peek() {
60                                                match token.symbol() {
61                                                    Symbol::Whitespace(_) => {
62                                                        tokens.next();
63                                                        continue;
64                                                    }
65                                                    Symbol::DoubleQuote => {
66                                                        tokens.next();
67                                                        break 'inner;
68                                                    }
69                                                    _ => break 'outer,
70                                                }
71                                            }
72                                            return Err(Error::UnexpectedEOF {
73                                                token: Box::new(from.clone()),
74                                            });
75                                        }
76                                    }
77                                    break;
78                                }
79                                _ => break 'outer,
80                            }
81                        }
82                        break 'outer;
83                    },
84                    _ => {
85                        string.push_str(&tokens.next().unwrap().to_string());
86                    }
87                }
88            } else {
89                return Err(Error::UnexpectedEOF {
90                    token: Box::new(from.clone()),
91                });
92            }
93        }
94        Ok(Self(string))
95    }
96}
97
98impl Rapify for Str {
99    fn rapify<O: std::io::Write>(
100        &self,
101        output: &mut O,
102        _offset: usize,
103    ) -> Result<usize, std::io::Error> {
104        output.write_cstring(&self.0)?;
105        Ok(self.0.len() + 1)
106    }
107
108    fn rapified_length(&self) -> usize {
109        self.0.len() + 1
110    }
111
112    fn rapified_code(&self) -> Option<u8> {
113        Some(0)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use hemtt_tokens::Token;
120    use peekmore::PeekMore;
121
122    use crate::model::Parse;
123
124    #[test]
125    fn string() {
126        let mut tokens = hemtt_preprocessor::preprocess_string(r#""test""#)
127            .unwrap()
128            .into_iter()
129            .peekmore();
130        let string = super::Str::parse(
131            &crate::Options::default(),
132            &mut tokens,
133            &Token::builtin(None),
134        )
135        .unwrap();
136        assert_eq!(string, super::Str("test".to_string()));
137    }
138
139    #[test]
140    fn string_escape() {
141        let mut tokens = hemtt_preprocessor::preprocess_string(r#""test is ""cool""""#)
142            .unwrap()
143            .into_iter()
144            .peekmore();
145        let string = super::Str::parse(
146            &crate::Options::default(),
147            &mut tokens,
148            &Token::builtin(None),
149        )
150        .unwrap();
151        assert_eq!(string, super::Str(r#"test is "cool""#.to_string()));
152    }
153
154    #[test]
155    // fn who_in_the_f_thought_this_was_a_good_idea() {
156    fn multiline_string() {
157        let mut tokens = hemtt_preprocessor::preprocess_string(r#""test" \n "is" \n "cool""#)
158            .unwrap()
159            .into_iter()
160            .peekmore();
161        let string = super::Str::parse(
162            &crate::Options::default(),
163            &mut tokens,
164            &Token::builtin(None),
165        )
166        .unwrap();
167        assert_eq!(string, super::Str("test\nis\ncool".to_string()));
168    }
169}