Skip to main content

confetti_rs/
parser.rs

1use crate::lexer::{Lexer, Token, TokenType};
2use crate::{ConfArgument, ConfComment, ConfDirective, ConfError, ConfOptions, ConfUnit};
3
4/// Parser for the configuration language.
5pub struct Parser<'a> {
6    /// The lexer used to tokenize the input.
7    lexer: Lexer<'a>,
8    /// The current token.
9    current_token: Token,
10    /// The options for the parser.
11    options: ConfOptions,
12    /// The current depth of nested directives.
13    current_depth: usize,
14}
15
16impl<'a> Parser<'a> {
17    /// Creates a new parser.
18    pub fn new(input: &'a str, options: ConfOptions) -> Result<Self, ConfError> {
19        let mut lexer = Lexer::new(input, options.clone());
20        let current_token = lexer.next_token()?;
21
22        Ok(Self {
23            lexer,
24            current_token,
25            options,
26            current_depth: 0,
27        })
28    }
29
30    /// Advances to the next token.
31    fn advance(&mut self) -> Result<(), ConfError> {
32        self.current_token = self.lexer.next_token()?;
33        Ok(())
34    }
35
36    /// Parses a configuration unit.
37    pub fn parse(&mut self) -> Result<ConfUnit, ConfError> {
38        let mut directives = Vec::new();
39        let mut comments = Vec::new();
40
41        while self.current_token.token_type != TokenType::Eof {
42            match self.current_token.token_type {
43                TokenType::Comment => {
44                    let comment = self.parse_comment()?;
45                    comments.push(comment);
46                }
47                TokenType::Newline | TokenType::Whitespace | TokenType::Continuation => {
48                    self.advance()?;
49                }
50                _ => {
51                    let directive = self.parse_directive()?;
52                    directives.push(directive);
53                }
54            }
55        }
56
57        Ok(ConfUnit {
58            directives,
59            comments,
60        })
61    }
62
63    /// Parses a comment.
64    fn parse_comment(&mut self) -> Result<ConfComment, ConfError> {
65        if self.current_token.token_type != TokenType::Comment {
66            return Err(ConfError::ParserError {
67                position: self.current_token.span.start,
68                message: "Expected comment".to_string(),
69            });
70        }
71
72        let span = self.current_token.span.clone();
73        let content = self.lexer.input()[span.clone()].to_string();
74        let is_multi_line = content.starts_with("/*");
75
76        self.advance()?;
77
78        Ok(ConfComment {
79            content,
80            span,
81            is_multi_line,
82        })
83    }
84
85    /// Parses a directive.
86    fn parse_directive(&mut self) -> Result<ConfDirective, ConfError> {
87        // Check max depth
88        if self.current_depth >= self.options.max_depth {
89            return Err(ConfError::ParserError {
90                position: self.current_token.span.start,
91                message: format!(
92                    "Maximum directive depth of {} exceeded",
93                    self.options.max_depth
94                ),
95            });
96        }
97
98        // Parse the directive name
99        if self.current_token.token_type != TokenType::Argument {
100            return Err(ConfError::ParserError {
101                position: self.current_token.span.start,
102                message: "Expected directive name".to_string(),
103            });
104        }
105
106        let name_span = self.current_token.span.clone();
107        let name_value = self.lexer.input()[name_span.clone()].to_string();
108        let name = ConfArgument {
109            value: name_value,
110            span: name_span,
111            is_quoted: self.current_token.is_quoted,
112            is_triple_quoted: self.current_token.is_triple_quoted,
113            is_expression: self.current_token.is_expression,
114        };
115
116        self.advance()?;
117
118        // Parse arguments
119        let mut arguments = Vec::new();
120        while self.current_token.token_type == TokenType::Argument
121            || self.current_token.token_type == TokenType::Continuation
122        {
123            // Если это токен продолжения строки, пропускаем его и продолжаем
124            if self.current_token.token_type == TokenType::Continuation {
125                self.advance()?;
126                continue;
127            }
128
129            let arg_span = self.current_token.span.clone();
130            let arg_value = self.lexer.input()[arg_span.clone()].to_string();
131            let argument = ConfArgument {
132                value: arg_value,
133                span: arg_span,
134                is_quoted: self.current_token.is_quoted,
135                is_triple_quoted: self.current_token.is_triple_quoted,
136                is_expression: self.current_token.is_expression,
137            };
138
139            arguments.push(argument);
140            self.advance()?;
141        }
142
143        // Parse child directives if this is a block directive
144        let mut children = Vec::new();
145        if self.current_token.token_type == TokenType::LeftCurlyBrace {
146            self.advance()?; // Skip '{'
147            self.current_depth += 1;
148
149            // Skip newlines after opening brace
150            while self.current_token.token_type == TokenType::Newline {
151                self.advance()?;
152            }
153
154            // Parse child directives
155            while self.current_token.token_type != TokenType::RightCurlyBrace
156                && self.current_token.token_type != TokenType::Eof
157            {
158                match self.current_token.token_type {
159                    TokenType::Comment => {
160                        let _comment = self.parse_comment()?;
161                        // We don't add comments to children, they go to the ConfUnit
162                    }
163                    TokenType::Newline | TokenType::Whitespace => {
164                        self.advance()?;
165                    }
166                    _ => {
167                        let directive = self.parse_directive()?;
168                        children.push(directive);
169                    }
170                }
171            }
172
173            // Expect closing brace
174            if self.current_token.token_type != TokenType::RightCurlyBrace {
175                return Err(ConfError::ParserError {
176                    position: self.current_token.span.start,
177                    message: "Expected '}'".to_string(),
178                });
179            }
180
181            self.advance()?; // Skip '}'
182            self.current_depth -= 1;
183        } else if self.current_token.token_type == TokenType::Semicolon {
184            self.advance()?; // Skip ';'
185        } else if self.current_token.token_type != TokenType::Newline
186            && self.current_token.token_type != TokenType::Eof
187            && self.current_token.token_type != TokenType::Continuation
188        {
189            return Err(ConfError::ParserError {
190                position: self.current_token.span.start,
191                message: "Expected ';', '{', or newline".to_string(),
192            });
193        }
194
195        Ok(ConfDirective {
196            name,
197            arguments,
198            children,
199        })
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_parser_new() {
209        let input = "test";
210        let options = ConfOptions::default();
211        let parser = Parser::new(input, options);
212        assert!(parser.is_ok());
213    }
214
215    #[test]
216    fn test_parser_parse_empty() {
217        let input = "";
218        let options = ConfOptions::default();
219        let mut parser = Parser::new(input, options).unwrap();
220        let result = parser.parse();
221        assert!(result.is_ok());
222        let conf_unit = result.unwrap();
223        assert_eq!(conf_unit.directives.len(), 0);
224        assert_eq!(conf_unit.comments.len(), 0);
225    }
226
227    #[test]
228    fn test_parser_parse_simple_directive() {
229        let input = "server localhost";
230        let options = ConfOptions::default();
231        let mut parser = Parser::new(input, options).unwrap();
232        let result = parser.parse();
233        assert!(result.is_ok());
234        let conf_unit = result.unwrap();
235        assert_eq!(conf_unit.directives.len(), 1);
236        assert_eq!(conf_unit.directives[0].name.value, "server");
237        assert_eq!(conf_unit.directives[0].arguments.len(), 1);
238        assert_eq!(conf_unit.directives[0].arguments[0].value, "localhost");
239    }
240
241    #[test]
242    fn test_parser_parse_block_directive() {
243        let input = "server {\n  listen 80;\n}";
244        let options = ConfOptions::default();
245        let mut parser = Parser::new(input, options).unwrap();
246        let result = parser.parse();
247        assert!(result.is_ok());
248        let conf_unit = result.unwrap();
249        assert_eq!(conf_unit.directives.len(), 1);
250        assert_eq!(conf_unit.directives[0].name.value, "server");
251        assert_eq!(conf_unit.directives[0].arguments.len(), 0);
252        assert_eq!(conf_unit.directives[0].children.len(), 1);
253        assert_eq!(conf_unit.directives[0].children[0].name.value, "listen");
254        assert_eq!(conf_unit.directives[0].children[0].arguments.len(), 1);
255        assert_eq!(conf_unit.directives[0].children[0].arguments[0].value, "80");
256    }
257
258    #[test]
259    fn test_parser_parse_with_comments() {
260        let input = "# Comment\nserver localhost";
261        let options = ConfOptions {
262            allow_c_style_comments: true,
263            ..Default::default()
264        };
265        let mut parser = Parser::new(input, options).unwrap();
266        let result = parser.parse();
267        assert!(result.is_ok());
268        let conf_unit = result.unwrap();
269        assert_eq!(conf_unit.directives.len(), 1);
270        assert_eq!(conf_unit.comments.len(), 1);
271        assert_eq!(conf_unit.comments[0].content, "# Comment");
272    }
273
274    #[test]
275    fn test_parser_max_depth() {
276        let input = "a { b { c { d { e { f { g { h { i { j { k { } } } } } } } } } } }";
277        let options = ConfOptions {
278            max_depth: 5,
279            ..Default::default()
280        };
281        let mut parser = Parser::new(input, options).unwrap();
282        let result = parser.parse();
283        assert!(result.is_err());
284        if let Err(ConfError::ParserError { message, .. }) = result {
285            assert!(message.contains("Maximum directive depth"));
286        } else {
287            panic!("Expected ParserError");
288        }
289    }
290}