config_lib/parsers/
conf.rs

1//! # CONF Format Parser
2//!
3//! High-performance parser for standard .conf configuration files.
4//!
5//! Supports the common configuration format used by many Unix/Linux applications:
6//!
7//! ```conf
8//! # Comments start with #
9//! key = value
10//! quoted_value = "string with spaces"
11//! number = 42
12//! float = 3.14
13//! boolean = true
14//!
15//! # Sections
16//! [section]
17//! nested_key = value
18//!
19//! # Arrays (space or comma separated)
20//! array = item1 item2 item3
21//! comma_array = item1, item2, item3
22//! ```
23
24use crate::error::{Error, Result};
25use crate::value::Value;
26use std::collections::BTreeMap;
27
28/// Parse CONF format configuration
29pub fn parse(source: &str) -> Result<Value> {
30    let mut parser = ConfParser::new(source);
31    parser.parse()
32}
33
34/// High-performance CONF parser with zero-allocation lexing
35/// CONF parser state
36struct ConfParser<'a> {
37    input: &'a str,
38    position: usize,
39    line: usize,
40    column: usize,
41}
42
43impl<'a> ConfParser<'a> {
44    /// Create a new parser
45    fn new(input: &'a str) -> Self {
46        Self {
47            input,
48            position: 0,
49            line: 1,
50            column: 1,
51        }
52    }
53
54    /// Parse the entire configuration
55    fn parse(&mut self) -> Result<Value> {
56        let mut root = BTreeMap::new();
57        let mut current_section = None;
58
59        while !self.is_at_end() {
60            self.skip_whitespace_and_comments();
61
62            if self.is_at_end() {
63                break;
64            }
65
66            // Check for section header
67            if self.peek() == Some('[') {
68                current_section = Some(self.parse_section_header()?);
69                continue;
70            }
71
72            // Parse key-value pair
73            let (key, value) = self.parse_key_value()?;
74
75            match &current_section {
76                Some(section) => {
77                    // Add to section
78                    let section_table = root
79                        .entry(section.clone())
80                        .or_insert_with(|| Value::table(BTreeMap::new()));
81
82                    if let Value::Table(table) = section_table {
83                        table.insert(key, value);
84                    }
85                }
86                None => {
87                    // Add to root
88                    root.insert(key, value);
89                }
90            }
91        }
92
93        Ok(Value::table(root))
94    }
95
96    /// Parse a section header like [section_name]
97    fn parse_section_header(&mut self) -> Result<String> {
98        self.expect('[')?;
99        let start = self.position;
100
101        // Find the closing bracket
102        while let Some(ch) = self.peek() {
103            if ch == ']' {
104                break;
105            }
106            if ch == '\n' {
107                return Err(Error::parse(
108                    "Unterminated section header",
109                    self.line,
110                    self.column,
111                ));
112            }
113            self.advance();
114        }
115
116        let section_name = self.input[start..self.position].trim().to_string();
117        self.expect(']')?;
118
119        Ok(section_name)
120    }
121
122    /// Parse a key-value pair
123    fn parse_key_value(&mut self) -> Result<(String, Value)> {
124        let key = self.parse_key()?;
125        self.skip_whitespace();
126        self.expect('=')?;
127        self.skip_whitespace();
128        let value = self.parse_value()?;
129
130        Ok((key, value))
131    }
132
133    /// Parse a configuration key
134    fn parse_key(&mut self) -> Result<String> {
135        let start = self.position;
136
137        while let Some(ch) = self.peek() {
138            if ch.is_alphanumeric() || ch == '_' || ch == '-' || ch == '.' {
139                self.advance();
140            } else {
141                break;
142            }
143        }
144
145        if start == self.position {
146            return Err(Error::parse("Expected key name", self.line, self.column));
147        }
148
149        Ok(self.input[start..self.position].to_string())
150    }
151
152    /// Parse a configuration value
153    fn parse_value(&mut self) -> Result<Value> {
154        self.skip_whitespace();
155
156        match self.peek() {
157            Some('"') => self.parse_quoted_string(),
158            Some('\'') => self.parse_single_quoted_string(),
159            Some('[') => self.parse_array(),
160            _ => {
161                // For all other cases (including numbers), use unquoted value parsing
162                // which handles space-separated arrays
163                self.parse_unquoted_value()
164            }
165        }
166    }
167
168    /// Parse a quoted string
169    fn parse_quoted_string(&mut self) -> Result<Value> {
170        self.expect('"')?;
171        let _start = self.position;
172        let mut result = String::new();
173
174        while let Some(ch) = self.peek() {
175            if ch == '"' {
176                break;
177            }
178            if ch == '\\' {
179                self.advance();
180                match self.peek() {
181                    Some('n') => result.push('\n'),
182                    Some('t') => result.push('\t'),
183                    Some('r') => result.push('\r'),
184                    Some('\\') => result.push('\\'),
185                    Some('"') => result.push('"'),
186                    Some(other) => {
187                        result.push('\\');
188                        result.push(other);
189                    }
190                    None => {
191                        return Err(Error::parse(
192                            "Unterminated escape sequence",
193                            self.line,
194                            self.column,
195                        ))
196                    }
197                }
198                self.advance();
199            } else {
200                result.push(ch);
201                self.advance();
202            }
203        }
204
205        self.expect('"')?;
206        Ok(Value::string(result))
207    }
208
209    /// Parse a single-quoted string (no escape sequences)
210    fn parse_single_quoted_string(&mut self) -> Result<Value> {
211        self.expect('\'')?;
212        let start = self.position;
213
214        while let Some(ch) = self.peek() {
215            if ch == '\'' {
216                break;
217            }
218            self.advance();
219        }
220
221        let content = self.input[start..self.position].to_string();
222        self.expect('\'')?;
223        Ok(Value::string(content))
224    }
225
226    /// Parse an array [item1, item2, item3]
227    fn parse_array(&mut self) -> Result<Value> {
228        self.expect('[')?;
229        let mut items = Vec::new();
230
231        self.skip_whitespace();
232
233        if self.peek() == Some(']') {
234            self.advance();
235            return Ok(Value::array(items));
236        }
237
238        loop {
239            items.push(self.parse_value()?);
240            self.skip_whitespace();
241
242            match self.peek() {
243                Some(',') => {
244                    self.advance();
245                    self.skip_whitespace();
246                }
247                Some(']') => {
248                    self.advance();
249                    break;
250                }
251                _ => {
252                    return Err(Error::parse(
253                        "Expected ',' or ']' in array",
254                        self.line,
255                        self.column,
256                    ))
257                }
258            }
259        }
260
261        Ok(Value::array(items))
262    }
263
264    /// Parse a number (integer or float)
265    #[allow(dead_code)]
266    fn parse_number(&mut self) -> Result<Value> {
267        let start = self.position;
268        let mut has_dot = false;
269
270        // Handle sign
271        if self.peek() == Some('-') || self.peek() == Some('+') {
272            self.advance();
273        }
274
275        // Parse digits and optional decimal point
276        while let Some(ch) = self.peek() {
277            if ch.is_ascii_digit() {
278                self.advance();
279            } else if ch == '.' && !has_dot {
280                has_dot = true;
281                self.advance();
282            } else {
283                break;
284            }
285        }
286
287        let number_str = &self.input[start..self.position];
288
289        if has_dot {
290            number_str.parse::<f64>().map(Value::float).map_err(|_| {
291                Error::parse(
292                    format!("Invalid float: {number_str}"),
293                    self.line,
294                    self.column,
295                )
296            })
297        } else {
298            number_str.parse::<i64>().map(Value::integer).map_err(|_| {
299                Error::parse(
300                    format!("Invalid integer: {number_str}"),
301                    self.line,
302                    self.column,
303                )
304            })
305        }
306    }
307
308    /// Parse an unquoted value (string, boolean, or array)
309    fn parse_unquoted_value(&mut self) -> Result<Value> {
310        let start = self.position;
311
312        // Read until end of line, comment, or special character
313        while let Some(ch) = self.peek() {
314            if ch == '\n' || ch == '\r' || ch == '#' {
315                break;
316            }
317            self.advance();
318        }
319
320        let raw_value = self.input[start..self.position].trim();
321
322        if raw_value.is_empty() {
323            return Ok(Value::null());
324        }
325
326        // Try to parse as boolean
327        match raw_value.to_lowercase().as_str() {
328            "true" | "yes" | "on" => return Ok(Value::bool(true)),
329            "false" | "no" | "off" => return Ok(Value::bool(false)),
330            "null" | "nil" | "" => return Ok(Value::null()),
331            _ => {}
332        }
333
334        // Check if it's a space or comma separated array
335        if raw_value.contains(' ') || raw_value.contains(',') {
336            let items: Vec<Value> = raw_value
337                .split([' ', ','])
338                .map(|s| s.trim())
339                .filter(|s| !s.is_empty())
340                .map(|s| self.parse_simple_value(s))
341                .collect::<Result<Vec<_>>>()?;
342
343            if items.len() > 1 {
344                return Ok(Value::array(items));
345            }
346        }
347
348        // Parse as simple value
349        self.parse_simple_value(raw_value)
350    }
351
352    /// Parse a simple value (no arrays or complex types)
353    fn parse_simple_value(&self, value: &str) -> Result<Value> {
354        // Try integer
355        if let Ok(i) = value.parse::<i64>() {
356            return Ok(Value::integer(i));
357        }
358
359        // Try float
360        if let Ok(f) = value.parse::<f64>() {
361            return Ok(Value::float(f));
362        }
363
364        // Default to string
365        Ok(Value::string(value.to_string()))
366    }
367
368    /// Skip whitespace but not newlines
369    fn skip_whitespace(&mut self) {
370        while let Some(ch) = self.peek() {
371            if ch == ' ' || ch == '\t' {
372                self.advance();
373            } else {
374                break;
375            }
376        }
377    }
378
379    /// Skip whitespace and comments
380    fn skip_whitespace_and_comments(&mut self) {
381        loop {
382            self.skip_whitespace();
383
384            // Skip comments
385            if self.peek() == Some('#') {
386                while let Some(ch) = self.peek() {
387                    self.advance();
388                    if ch == '\n' {
389                        break;
390                    }
391                }
392                continue;
393            }
394
395            // Skip newlines
396            if self.peek() == Some('\n') || self.peek() == Some('\r') {
397                self.advance();
398                continue;
399            }
400
401            break;
402        }
403    }
404
405    /// Peek at the current character
406    fn peek(&self) -> Option<char> {
407        self.input.chars().nth(self.position)
408    }
409
410    /// Advance to the next character
411    fn advance(&mut self) -> Option<char> {
412        if let Some(ch) = self.peek() {
413            self.position += 1;
414            if ch == '\n' {
415                self.line += 1;
416                self.column = 1;
417            } else {
418                self.column += 1;
419            }
420            Some(ch)
421        } else {
422            None
423        }
424    }
425
426    /// Expect a specific character
427    fn expect(&mut self, expected: char) -> Result<()> {
428        match self.advance() {
429            Some(ch) if ch == expected => Ok(()),
430            Some(ch) => Err(Error::parse(
431                format!("Expected '{expected}', found '{ch}'"),
432                self.line,
433                self.column,
434            )),
435            None => Err(Error::parse(
436                format!("Expected '{expected}', found end of input"),
437                self.line,
438                self.column,
439            )),
440        }
441    }
442
443    /// Check if we're at the end of input
444    fn is_at_end(&self) -> bool {
445        self.position >= self.input.len()
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452
453    #[test]
454    fn test_simple_key_value() {
455        let config = parse("key = value").unwrap();
456        assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
457    }
458
459    #[test]
460    fn test_numbers() {
461        let config = parse("int = 42\nfloat = 1.234").unwrap();
462        assert_eq!(config.get("int").unwrap().as_integer().unwrap(), 42);
463        assert_eq!(config.get("float").unwrap().as_float().unwrap(), 1.234);
464    }
465
466    #[test]
467    fn test_booleans() {
468        let config = parse("bool1 = true\nbool2 = false").unwrap();
469        assert!(config.get("bool1").unwrap().as_bool().unwrap());
470        assert!(!config.get("bool2").unwrap().as_bool().unwrap());
471    }
472
473    #[test]
474    fn test_quoted_strings() {
475        let config = parse(r#"quoted = "hello world""#).unwrap();
476        assert_eq!(
477            config.get("quoted").unwrap().as_string().unwrap(),
478            "hello world"
479        );
480    }
481
482    #[test]
483    fn test_sections() {
484        let config = parse("[section]\nkey = value").unwrap();
485        assert_eq!(
486            config.get("section.key").unwrap().as_string().unwrap(),
487            "value"
488        );
489    }
490
491    #[test]
492    fn test_arrays() {
493        let config = parse("arr = item1 item2 item3").unwrap();
494        let arr = config.get("arr").unwrap().as_array().unwrap();
495        assert_eq!(arr.len(), 3);
496        assert_eq!(arr[0].as_string().unwrap(), "item1");
497    }
498
499    #[test]
500    fn test_comments() {
501        let config = parse("# This is a comment\nkey = value # inline comment").unwrap();
502        assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
503    }
504}