Skip to main content

nginx_discovery/parser/
parse.rs

1//! Parser for NGINX configuration files
2use crate::ast::{Config, Directive, Value};
3use crate::error::{Error, Result};
4// use crate::prelude::ErrorBuilder;
5use crate::parser::{Lexer, Token, TokenKind};
6
7/// Parser for NGINX configuration
8pub struct Parser {
9    /// Tokens to parse
10    tokens: Vec<Token>,
11    /// Current position in token stream
12    pos: usize,
13}
14
15impl Parser {
16    /// Create a new parser from source text
17    ///
18    /// # Errors
19    ///
20    /// Returns an error if tokenization fails.
21    pub fn new(input: &str) -> Result<Self> {
22        let mut lexer = Lexer::new(input);
23        let tokens = lexer.tokenize()?;
24
25        Ok(Self { tokens, pos: 0 })
26    }
27
28    /// Parse the configuration
29    ///
30    /// # Errors
31    ///
32    /// Returns an error if:
33    /// - Unexpected tokens are encountered
34    /// - Directives are malformed
35    /// - Blocks are not properly closed
36    pub fn parse(&mut self) -> Result<Config> {
37        let mut directives = Vec::new();
38
39        while !self.is_eof() {
40            // Skip comments
41            if self.check_comment() {
42                self.advance();
43                continue;
44            }
45
46            let directive = self.parse_directive()?;
47            directives.push(directive);
48        }
49
50        Ok(Config::with_directives(directives))
51    }
52
53    /// Parse a single directive (simple or block)
54    fn parse_directive(&mut self) -> Result<Directive> {
55        let _start_token = self.current();
56        let name = self.expect_word()?;
57
58        let mut args = Vec::new();
59
60        // Collect arguments until we hit ; or {
61        while !self.check(&TokenKind::Semicolon)
62            && !self.check(&TokenKind::LeftBrace)
63            && !self.is_eof()
64        {
65            if self.check_comment() {
66                self.advance();
67                continue;
68            }
69
70            let arg = self.parse_value()?;
71            args.push(arg);
72        }
73
74        // Check if it's a block or simple directive
75        if self.check(&TokenKind::LeftBrace) {
76            // Block directive
77            self.advance(); // consume {
78
79            let children = self.parse_block_contents()?;
80
81            self.expect(&TokenKind::RightBrace)?;
82
83            Ok(Directive::block_with_values(name, args, children))
84        } else {
85            // Simple directive
86            self.expect(&TokenKind::Semicolon)?;
87
88            Ok(Directive::simple_with_values(name, args))
89        }
90    }
91
92    /// Parse the contents of a block
93    fn parse_block_contents(&mut self) -> Result<Vec<Directive>> {
94        let mut directives = Vec::new();
95
96        while !self.check(&TokenKind::RightBrace) && !self.is_eof() {
97            if self.check_comment() {
98                self.advance();
99                continue;
100            }
101
102            let directive = self.parse_directive()?;
103            directives.push(directive);
104        }
105
106        Ok(directives)
107    }
108
109    /// Parse a value (string, number, word, variable)
110    fn parse_value(&mut self) -> Result<Value> {
111        let token = self.current();
112
113        let value = match &token.kind {
114            TokenKind::String(s) => Value::single_quoted(s.clone()),
115            TokenKind::Word(s) | TokenKind::Number(s) => Value::literal(s.clone()),
116            TokenKind::Variable(s) => Value::variable(s.clone()),
117            _ => {
118                return Err(Error::syntax(
119                    "expected value",
120                    token.span.line,
121                    token.span.col,
122                    Some("word, string, number, or variable".to_string()),
123                    Some(format!("{}", token.kind)),
124                ));
125            }
126        };
127
128        self.advance();
129        Ok(value)
130    }
131
132    /// Expect a specific token kind
133    fn expect(&mut self, kind: &TokenKind) -> Result<Token> {
134        let token = self.current().clone(); // Clone here
135
136        if std::mem::discriminant(&token.kind) == std::mem::discriminant(kind) {
137            self.advance();
138            Ok(token) // No need to clone again
139        } else {
140            Err(Error::syntax(
141                "unexpected token".to_string(),
142                token.span.line,
143                token.span.col,
144                Some(format!("{kind}")),
145                Some(format!("{}", token.kind)),
146            ))
147        }
148    }
149
150    /// Expect a word token and return its value
151    fn expect_word(&mut self) -> Result<String> {
152        let token = self.current();
153
154        if let TokenKind::Word(name) = &token.kind {
155            let result = name.clone();
156            self.advance();
157            Ok(result)
158        } else {
159            Err(Error::syntax(
160                "expected directive name",
161                token.span.line,
162                token.span.col,
163                Some("word".to_string()),
164                Some(format!("{}", token.kind)),
165            ))
166        }
167    }
168
169    /// Get current token
170    fn current(&self) -> &Token {
171        self.tokens
172            .get(self.pos)
173            .unwrap_or(&self.tokens[self.tokens.len() - 1])
174    }
175
176    /// Check if current token matches a kind
177    fn check(&self, kind: &TokenKind) -> bool {
178        if self.is_eof() {
179            return false;
180        }
181        std::mem::discriminant(&self.current().kind) == std::mem::discriminant(kind)
182    }
183
184    /// Check if current token is a comment
185    fn check_comment(&self) -> bool {
186        matches!(self.current().kind, TokenKind::Comment(_))
187    }
188
189    /// Check if at end of tokens
190    fn is_eof(&self) -> bool {
191        matches!(self.current().kind, TokenKind::Eof)
192    }
193
194    /// Advance to next token
195    fn advance(&mut self) {
196        if !self.is_eof() {
197            self.pos += 1;
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_parse_simple_directive() {
208        let input = "user nginx;";
209        let mut parser = Parser::new(input).unwrap();
210        let config = parser.parse().unwrap();
211
212        assert_eq!(config.directives.len(), 1);
213        assert_eq!(config.directives[0].name(), "user");
214        assert_eq!(config.directives[0].args().len(), 1);
215    }
216
217    #[test]
218    fn test_parse_multiple_directives() {
219        let input = "user nginx;\nworker_processes auto;";
220        let mut parser = Parser::new(input).unwrap();
221        let config = parser.parse().unwrap();
222
223        assert_eq!(config.directives.len(), 2);
224        assert_eq!(config.directives[0].name(), "user");
225        assert_eq!(config.directives[1].name(), "worker_processes");
226    }
227
228    #[test]
229    fn test_parse_block_directive() {
230        let input = "server { listen 80; }";
231        let mut parser = Parser::new(input).unwrap();
232        let config = parser.parse().unwrap();
233
234        assert_eq!(config.directives.len(), 1);
235        assert!(config.directives[0].is_block());
236        assert_eq!(config.directives[0].children().unwrap().len(), 1);
237    }
238
239    #[test]
240    fn test_parse_nested_blocks() {
241        let input = r"
242http {
243    server {
244        listen 80;
245        location / {
246            root /var/www;
247        }
248    }
249}
250";
251        let mut parser = Parser::new(input).unwrap();
252        let config = parser.parse().unwrap();
253
254        assert_eq!(config.directives.len(), 1);
255
256        let http = &config.directives[0];
257        assert_eq!(http.name(), "http");
258        assert_eq!(http.children().unwrap().len(), 1);
259
260        let server = &http.children().unwrap()[0];
261        assert_eq!(server.name(), "server");
262        assert_eq!(server.children().unwrap().len(), 2); // listen + location
263    }
264
265    #[test]
266    fn test_parse_with_comments() {
267        let input = r"
268# Main config
269user nginx;  # Run as nginx
270";
271        let mut parser = Parser::new(input).unwrap();
272        let config = parser.parse().unwrap();
273
274        // Comments should be skipped
275        assert_eq!(config.directives.len(), 1);
276        assert_eq!(config.directives[0].name(), "user");
277    }
278
279    #[test]
280    fn test_parse_strings() {
281        let input = r#"root "/var/www/html";"#;
282        let mut parser = Parser::new(input).unwrap();
283        let config = parser.parse().unwrap();
284
285        assert_eq!(config.directives.len(), 1);
286        assert_eq!(config.directives[0].args().len(), 1);
287    }
288
289    #[test]
290    fn test_parse_variables() {
291        let input = "set $host localhost;";
292        let mut parser = Parser::new(input).unwrap();
293        let config = parser.parse().unwrap();
294
295        assert_eq!(config.directives.len(), 1);
296        assert!(config.directives[0].args()[0].is_variable());
297    }
298}