config_lib/parsers/
conf.rs1use crate::error::{Error, Result};
25use crate::value::Value;
26use std::collections::BTreeMap;
27
28pub fn parse(source: &str) -> Result<Value> {
30 let mut parser = ConfParser::new(source);
31 parser.parse()
32}
33
34struct ConfParser<'a> {
37 input: &'a str,
38 position: usize,
39 line: usize,
40 column: usize,
41}
42
43impl<'a> ConfParser<'a> {
44 fn new(input: &'a str) -> Self {
46 Self {
47 input,
48 position: 0,
49 line: 1,
50 column: 1,
51 }
52 }
53
54 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 if self.peek() == Some('[') {
68 current_section = Some(self.parse_section_header()?);
69 continue;
70 }
71
72 let (key, value) = self.parse_key_value()?;
74
75 match ¤t_section {
76 Some(section) => {
77 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 root.insert(key, value);
89 }
90 }
91 }
92
93 Ok(Value::table(root))
94 }
95
96 fn parse_section_header(&mut self) -> Result<String> {
98 self.expect('[')?;
99 let start = self.position;
100
101 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 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 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 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 self.parse_unquoted_value()
164 }
165 }
166 }
167
168 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 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 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 #[allow(dead_code)]
266 fn parse_number(&mut self) -> Result<Value> {
267 let start = self.position;
268 let mut has_dot = false;
269
270 if self.peek() == Some('-') || self.peek() == Some('+') {
272 self.advance();
273 }
274
275 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 fn parse_unquoted_value(&mut self) -> Result<Value> {
310 let start = self.position;
311
312 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 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 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 self.parse_simple_value(raw_value)
350 }
351
352 fn parse_simple_value(&self, value: &str) -> Result<Value> {
354 if let Ok(i) = value.parse::<i64>() {
356 return Ok(Value::integer(i));
357 }
358
359 if let Ok(f) = value.parse::<f64>() {
361 return Ok(Value::float(f));
362 }
363
364 Ok(Value::string(value.to_string()))
366 }
367
368 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 fn skip_whitespace_and_comments(&mut self) {
381 loop {
382 self.skip_whitespace();
383
384 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 if self.peek() == Some('\n') || self.peek() == Some('\r') {
397 self.advance();
398 continue;
399 }
400
401 break;
402 }
403 }
404
405 fn peek(&self) -> Option<char> {
407 self.input.chars().nth(self.position)
408 }
409
410 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 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 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}