Skip to main content

miniconf_pest_parser/
parser.rs

1use pest::Parser;
2use pest_derive::Parser;
3use std::collections::HashMap;
4
5#[derive(Parser)]
6#[grammar = "miniconf.pest"]
7struct MiniConfParser;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum Value {
11    Str(String),
12    Num(f64),
13    Bool(bool),
14    Null,
15    Array(Vec<Value>),
16    Object(HashMap<String, Value>),
17}
18
19#[derive(Debug, Clone, Default, PartialEq)]
20pub struct Document {
21    /// Map<section, Map<key, value>>
22    pub sections: HashMap<String, HashMap<String, Value>>,
23}
24
25#[derive(thiserror::Error, Debug)]
26pub enum MiniConfError {
27    #[error("parse error: {0}")]
28    Pest(#[from] pest::error::Error<Rule>),
29    #[error("number parse error: {0}")]
30    Num(#[from] std::num::ParseFloatError),
31}
32
33impl Document {
34    pub fn parse(input: &str) -> Result<Self, MiniConfError> {
35        let mut current_section = String::from("root");
36        let mut doc = Document::default();
37        doc.sections.entry(current_section.clone()).or_default();
38
39        let pairs = MiniConfParser::parse(Rule::file, input)?;
40
41        for pair in pairs.flatten() {
42            match pair.as_rule() {
43                Rule::section => {
44                    let mut inner = pair.into_inner();
45                    let name = inner.next().unwrap().as_str().to_string();
46                    current_section = name;
47                    doc.sections.entry(current_section.clone()).or_default();
48                }
49                Rule::kv => {
50                    let mut inner = pair.into_inner();
51                    let key = inner.next().unwrap().as_str().to_string();
52                    let val_pair = inner.next().unwrap();
53                    let val = parse_value(val_pair)?;
54                    doc.sections
55                        .entry(current_section.clone())
56                        .or_default()
57                        .insert(key, val);
58                }
59                _ => {}
60            }
61        }
62        Ok(doc)
63    }
64}
65
66fn parse_value(pair: pest::iterators::Pair<Rule>) -> Result<Value, MiniConfError> {
67    Ok(match pair.as_rule() {
68        Rule::string => {
69            let raw = pair.as_str();
70            let inner = &raw[1..raw.len() - 1];
71            let s = inner
72                .replace("\\\"", "\"")
73                .replace("\\n", "\n")
74                .replace("\\t", "\t");
75            Value::Str(s)
76        }
77        Rule::number => Value::Num(pair.as_str().parse::<f64>()?),
78        Rule::boolean => Value::Bool(pair.as_str() == "true"),
79        Rule::null => Value::Null,
80        Rule::array => {
81            let items = pair
82                .into_inner()
83                .filter(|p| matches!(p.as_rule(), Rule::value))
84                .map(parse_value)
85                .collect::<Result<Vec<_>, _>>()?;
86            Value::Array(items)
87        }
88        Rule::object => {
89            let mut map = HashMap::new();
90            let mut it = pair.into_inner().peekable();
91            while let Some(next) = it.next() {
92                if next.as_rule() == Rule::ident {
93                    let key = next.as_str().to_string();
94                    let val_pair = it.next().expect("value after key");
95                    let val = parse_value(val_pair)?;
96                    map.insert(key, val);
97                }
98            }
99            Value::Object(map)
100        }
101        Rule::value => {
102            let inner = pair.into_inner().next().unwrap();
103            parse_value(inner)?
104        }
105        _ => unreachable!("unexpected rule: {:?}", pair.as_rule()),
106    })
107}