miniconf_parser/
parser.rs1use pest::Parser;
2use pest::iterators::Pair;
3use pest_derive::Parser;
4
5use crate::ast::{Document, Entry, Value};
6use crate::error::{MiniConfError, ParseErrorKind};
7
8const ROOT_SECTION: &str = "root";
9
10#[derive(Parser)]
11#[grammar = "grammar.pest"]
12pub struct MiniConfParser;
14
15pub fn parse_document(source: &str) -> Result<Document, MiniConfError> {
17 let mut document = Document::new();
18 document.ensure_section_mut(ROOT_SECTION);
19 let mut current_section = ROOT_SECTION.to_string();
20
21 let pairs = MiniConfParser::parse(Rule::text, source)?
22 .next()
23 .expect("text rule must produce a pair")
24 .into_inner();
25
26 for pair in pairs {
27 match pair.as_rule() {
28 Rule::section_header => {
29 let name = pair.into_inner().next().unwrap().as_str().to_string();
30 current_section = name;
31 document.ensure_section_mut(¤t_section);
32 }
33 Rule::key_value => {
34 handle_key_value(&mut document, ¤t_section, pair)?;
35 }
36 Rule::comment_line | Rule::EOI => {
37 }
39 _ => {}
40 }
41 }
42
43 Ok(document)
44}
45
46fn handle_key_value(
47 document: &mut Document,
48 current_section: &str,
49 pair: Pair<'_, Rule>,
50) -> Result<(), MiniConfError> {
51 let line = line_of(&pair);
52 let mut inner = pair.into_inner();
53 let key = inner.next().expect("key present").as_str().to_string();
54 let value_pair = inner
55 .find(|p| matches!(p.as_rule(), Rule::value))
56 .expect("value present");
57 let value = parse_value(value_pair)?;
58
59 let section = document.ensure_section_mut(current_section);
60 if section.entries.iter().any(|entry| entry.key == key) {
61 return Err(MiniConfError::semantic(
62 ParseErrorKind::DuplicateKey,
63 line,
64 format!("key `{key}` already defined in [{current_section}]"),
65 ));
66 }
67
68 section.entries.push(Entry::new(key, value, line));
69 Ok(())
70}
71
72fn parse_value(pair: Pair<'_, Rule>) -> Result<Value, MiniConfError> {
73 match pair.as_rule() {
74 Rule::value => {
75 let inner = pair.into_inner().next().expect("value inner");
76 parse_value(inner)
77 }
78 Rule::quoted_string => Ok(Value::String(unescape(pair.as_str()))),
79 Rule::bare_string => Ok(Value::String(pair.as_str().to_string())),
80 Rule::number => {
81 let line = line_of(&pair);
82 match pair.as_str().parse::<f64>() {
83 Ok(num) => Ok(Value::Number(num)),
84 Err(err) => Err(MiniConfError::semantic(
85 ParseErrorKind::InvalidValue,
86 line,
87 format!("invalid number: {err}"),
88 )),
89 }
90 }
91 Rule::boolean => Ok(Value::Bool(matches!(pair.as_str(), "true" | "yes"))),
92 Rule::array => parse_array(pair),
93 other => unreachable!("unexpected value rule: {other:?}"),
94 }
95}
96
97fn parse_array(pair: Pair<'_, Rule>) -> Result<Value, MiniConfError> {
98 let mut values = Vec::new();
99 for inner in pair.into_inner() {
100 if matches!(inner.as_rule(), Rule::value) {
101 values.push(parse_value(inner)?);
102 }
103 }
104 Ok(Value::Array(values))
105}
106
107fn unescape(raw: &str) -> String {
108 let body = &raw[1..raw.len() - 1];
109 let mut buf = String::with_capacity(body.len());
110 let mut chars = body.chars();
111 while let Some(ch) = chars.next() {
112 if ch == '\\' {
113 if let Some(next) = chars.next() {
114 match next {
115 '"' => buf.push('"'),
116 'n' => buf.push('\n'),
117 't' => buf.push('\t'),
118 '\\' => buf.push('\\'),
119 other => buf.push(other),
120 }
121 }
122 } else {
123 buf.push(ch);
124 }
125 }
126 buf
127}
128
129fn line_of(pair: &Pair<'_, Rule>) -> usize {
130 pair.as_span().start_pos().line_col().0
131}