hocon_rs/parser/
mod.rs

1mod array;
2mod comment;
3mod include;
4pub(crate) mod loader;
5mod object;
6pub mod read;
7mod string;
8mod substitution;
9
10use std::rc::Rc;
11
12use derive_more::Constructor;
13
14use crate::Result;
15use crate::config_options::ConfigOptions;
16use crate::error::Error;
17use crate::parser::read::Read;
18use crate::raw::raw_object::RawObject;
19
20#[derive(Constructor, Default, Debug, Clone)]
21pub(crate) struct Context {
22    pub(crate) include_chain: Vec<Rc<String>>,
23    pub(crate) depth: usize,
24}
25
26impl Context {
27    pub(crate) fn increase_depth(&mut self) -> usize {
28        self.depth += 1;
29        self.depth
30    }
31
32    pub(crate) fn decrease_depth(&mut self) -> usize {
33        self.depth -= 1;
34        self.depth
35    }
36}
37
38#[derive(Debug)]
39pub struct HoconParser<R> {
40    pub(crate) reader: R,
41    pub(crate) scratch: Vec<u8>,
42    pub(crate) options: ConfigOptions,
43    pub(crate) ctx: Context,
44}
45
46impl<'de, R: Read<'de>> HoconParser<R> {
47    pub fn new(reader: R) -> Self {
48        HoconParser {
49            reader,
50            scratch: vec![],
51            options: Default::default(),
52            ctx: Default::default(),
53        }
54    }
55
56    pub fn with_options(reader: R, options: ConfigOptions) -> Self {
57        HoconParser {
58            reader,
59            scratch: vec![],
60            options,
61            ctx: Default::default(),
62        }
63    }
64
65    pub(crate) fn with_options_and_ctx(reader: R, options: ConfigOptions, ctx: Context) -> Self {
66        HoconParser {
67            reader,
68            scratch: vec![],
69            options,
70            ctx,
71        }
72    }
73
74    pub(crate) fn parse_horizontal_whitespace(&mut self, scratch: &mut Vec<u8>) -> Result<()> {
75        loop {
76            match self.reader.peek_horizontal_whitespace() {
77                Ok(Some(n)) => {
78                    for _ in 0..n {
79                        let byte = self.reader.next()?;
80                        scratch.push(byte);
81                    }
82                }
83                Ok(None) | Err(Error::Eof) => break,
84                Err(err) => return Err(err),
85            }
86        }
87        Ok(())
88    }
89
90    pub(crate) fn drop_horizontal_whitespace(&mut self) -> Result<()> {
91        loop {
92            match self.reader.peek_horizontal_whitespace() {
93                Ok(Some(n)) => {
94                    self.reader.discard(n)?;
95                }
96                Ok(None) | Err(Error::Eof) => break,
97                Err(err) => return Err(err),
98            }
99        }
100        Ok(())
101    }
102
103    pub(crate) fn drop_whitespace(&mut self) -> Result<()> {
104        loop {
105            match self.reader.peek_whitespace() {
106                Ok(Some(n)) => {
107                    self.reader.discard(n)?;
108                }
109                Ok(None) | Err(Error::Eof) => break,
110                Err(err) => return Err(err),
111            }
112        }
113        Ok(())
114    }
115
116    pub(crate) fn drop_comma_separator(&mut self) -> Result<bool> {
117        match self.reader.peek() {
118            Ok(ch) => {
119                if ch == b',' {
120                    self.reader.discard(1)?;
121                }
122            }
123            Err(Error::Eof) => return Ok(true),
124            Err(err) => {
125                return Err(err);
126            }
127        }
128        Ok(false)
129    }
130
131    pub fn parse(&mut self) -> Result<RawObject> {
132        self.drop_whitespace_and_comments()?;
133        let raw_obj = match self.reader.peek() {
134            Ok(ch) => {
135                if ch == b'{' {
136                    self.parse_object(false)?
137                } else {
138                    self.parse_braces_omitted_object()?
139                }
140            }
141            Err(Error::Eof) => {
142                return Ok(RawObject::default());
143            }
144            Err(err) => {
145                return Err(err);
146            }
147        };
148        self.drop_whitespace_and_comments()?;
149        match self.reader.peek() {
150            Ok(ch) => {
151                return Err(Error::UnexpectedToken {
152                    expected: "end of file",
153                    found_beginning: ch,
154                });
155            }
156            Err(Error::Eof) => {}
157            Err(err) => {
158                return Err(err);
159            }
160        }
161        Ok(raw_obj)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use std::io::BufReader;
168
169    use crate::Result;
170    use crate::config_options::ConfigOptions;
171    use crate::parser::HoconParser;
172    use crate::parser::read::StreamRead;
173    use rstest::rstest;
174
175    #[rstest]
176    #[case("resources/base.conf")]
177    #[case("resources/concat.conf")]
178    #[case("resources/concat2.conf")]
179    #[case("resources/concat3.conf")]
180    #[case("resources/demo.conf")]
181    #[case("resources/deserialize.conf")]
182    #[case("resources/empty.conf")]
183    #[cfg_attr(feature = "urls_includes", case("resources/included.conf"))]
184    #[cfg_attr(feature = "urls_includes", case("resources/main.conf"))]
185    fn test_parse(#[case] path: impl AsRef<std::path::Path>) -> Result<()> {
186        let file = std::fs::File::open(&path)?;
187        let read = StreamRead::new(BufReader::new(file));
188        let options = ConfigOptions::new(false, vec!["resources".to_string()]);
189        let mut parser = HoconParser::with_options(read, options);
190        parser.parse()?;
191        Ok(())
192    }
193}