toml_edit/parser/
mod.rs

1#![allow(clippy::type_complexity)]
2
3#[macro_use]
4pub(crate) mod macros;
5
6pub(crate) mod array;
7pub(crate) mod datetime;
8pub(crate) mod document;
9pub(crate) mod errors;
10pub(crate) mod inline_table;
11pub(crate) mod key;
12pub(crate) mod numbers;
13pub(crate) mod state;
14pub(crate) mod strings;
15pub(crate) mod table;
16pub(crate) mod trivia;
17pub(crate) mod value;
18
19pub use errors::TomlError;
20
21pub(crate) fn parse_document(raw: &str) -> Result<crate::Document, TomlError> {
22    use prelude::*;
23
24    let b = new_input(raw);
25    let mut doc = document::document
26        .parse(b)
27        .finish()
28        .map_err(|e| TomlError::new(e, b))?;
29    doc.span = Some(0..(raw.len()));
30    doc.original = Some(raw.to_owned());
31    Ok(doc)
32}
33
34pub(crate) fn parse_key(raw: &str) -> Result<crate::Key, TomlError> {
35    use prelude::*;
36
37    let b = new_input(raw);
38    let result = key::simple_key.parse(b).finish();
39    match result {
40        Ok((raw, key)) => {
41            Ok(crate::Key::new(key).with_repr_unchecked(crate::Repr::new_unchecked(raw)))
42        }
43        Err(e) => Err(TomlError::new(e, b)),
44    }
45}
46
47pub(crate) fn parse_key_path(raw: &str) -> Result<Vec<crate::Key>, TomlError> {
48    use prelude::*;
49
50    let b = new_input(raw);
51    let result = key::key.parse(b).finish();
52    match result {
53        Ok(mut keys) => {
54            for key in &mut keys {
55                key.despan(raw);
56            }
57            Ok(keys)
58        }
59        Err(e) => Err(TomlError::new(e, b)),
60    }
61}
62
63pub(crate) fn parse_value(raw: &str) -> Result<crate::Value, TomlError> {
64    use prelude::*;
65
66    let b = new_input(raw);
67    let parsed = value::value(RecursionCheck::default()).parse(b).finish();
68    match parsed {
69        Ok(mut value) => {
70            // Only take the repr and not decor, as its probably not intended
71            value.decor_mut().clear();
72            value.despan(raw);
73            Ok(value)
74        }
75        Err(e) => Err(TomlError::new(e, b)),
76    }
77}
78
79pub(crate) mod prelude {
80    pub(crate) use super::errors::Context;
81    pub(crate) use super::errors::ParserError;
82    pub(crate) use super::errors::ParserValue;
83    pub(crate) use nom8::IResult;
84    pub(crate) use nom8::Parser as _;
85
86    pub(crate) use nom8::FinishIResult as _;
87
88    pub(crate) type Input<'b> = nom8::input::Located<&'b [u8]>;
89
90    pub(crate) fn new_input(s: &str) -> Input<'_> {
91        nom8::input::Located::new(s.as_bytes())
92    }
93
94    pub(crate) fn ok_error<I, O, E>(res: IResult<I, O, E>) -> Result<Option<(I, O)>, nom8::Err<E>> {
95        match res {
96            Ok(ok) => Ok(Some(ok)),
97            Err(nom8::Err::Error(_)) => Ok(None),
98            Err(err) => Err(err),
99        }
100    }
101
102    #[allow(dead_code)]
103    pub(crate) fn trace<I: std::fmt::Debug, O: std::fmt::Debug, E: std::fmt::Debug>(
104        context: impl std::fmt::Display,
105        mut parser: impl nom8::Parser<I, O, E>,
106    ) -> impl FnMut(I) -> IResult<I, O, E> {
107        static DEPTH: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
108        move |input: I| {
109            let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst) * 2;
110            eprintln!("{:depth$}--> {} {:?}", "", context, input);
111            match parser.parse(input) {
112                Ok((i, o)) => {
113                    DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
114                    eprintln!("{:depth$}<-- {} {:?}", "", context, i);
115                    Ok((i, o))
116                }
117                Err(err) => {
118                    DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
119                    eprintln!("{:depth$}<-- {} {:?}", "", context, err);
120                    Err(err)
121                }
122            }
123        }
124    }
125
126    #[cfg(not(feature = "unbounded"))]
127    #[derive(Copy, Clone, Debug, Default)]
128    pub(crate) struct RecursionCheck {
129        current: usize,
130    }
131
132    #[cfg(not(feature = "unbounded"))]
133    impl RecursionCheck {
134        pub(crate) fn check_depth(depth: usize) -> Result<(), super::errors::CustomError> {
135            if depth < 128 {
136                Ok(())
137            } else {
138                Err(super::errors::CustomError::RecursionLimitExceeded)
139            }
140        }
141
142        pub(crate) fn recursing(
143            mut self,
144            input: Input<'_>,
145        ) -> Result<Self, nom8::Err<ParserError<'_>>> {
146            self.current += 1;
147            if self.current < 128 {
148                Ok(self)
149            } else {
150                Err(nom8::Err::Error(
151                    nom8::error::FromExternalError::from_external_error(
152                        input,
153                        nom8::error::ErrorKind::Eof,
154                        super::errors::CustomError::RecursionLimitExceeded,
155                    ),
156                ))
157            }
158        }
159    }
160
161    #[cfg(feature = "unbounded")]
162    #[derive(Copy, Clone, Debug, Default)]
163    pub(crate) struct RecursionCheck {}
164
165    #[cfg(feature = "unbounded")]
166    impl RecursionCheck {
167        pub(crate) fn check_depth(_depth: usize) -> Result<(), super::errors::CustomError> {
168            Ok(())
169        }
170
171        pub(crate) fn recursing(
172            self,
173            _input: Input<'_>,
174        ) -> Result<Self, nom8::Err<ParserError<'_>>> {
175            Ok(self)
176        }
177    }
178}
179
180#[cfg(test)]
181mod test {
182    use super::*;
183
184    #[test]
185    fn documents() {
186        let documents = [
187            "",
188            r#"
189# This is a TOML document.
190
191title = "TOML Example"
192
193    [owner]
194    name = "Tom Preston-Werner"
195    dob = 1979-05-27T07:32:00-08:00 # First class dates
196
197    [database]
198    server = "192.168.1.1"
199    ports = [ 8001, 8001, 8002 ]
200    connection_max = 5000
201    enabled = true
202
203    [servers]
204
205    # Indentation (tabs and/or spaces) is allowed but not required
206[servers.alpha]
207    ip = "10.0.0.1"
208    dc = "eqdc10"
209
210    [servers.beta]
211    ip = "10.0.0.2"
212    dc = "eqdc10"
213
214    [clients]
215    data = [ ["gamma", "delta"], [1, 2] ]
216
217    # Line breaks are OK when inside arrays
218hosts = [
219    "alpha",
220    "omega"
221]
222
223   'some.wierd .stuff'   =  """
224                         like
225                         that
226                      #   """ # this broke my sintax highlighting
227   " also. like " = '''
228that
229'''
230   double = 2e39 # this number looks familiar
231# trailing comment"#,
232            r#""#,
233            r#"  "#,
234            r#" hello = 'darkness' # my old friend
235"#,
236            r#"[parent . child]
237key = "value"
238"#,
239            r#"hello.world = "a"
240"#,
241            r#"foo = 1979-05-27 # Comment
242"#,
243        ];
244        for input in documents {
245            dbg!(input);
246            let mut parsed = parse_document(input);
247            if let Ok(parsed) = &mut parsed {
248                parsed.despan();
249            }
250            let doc = match parsed {
251                Ok(doc) => doc,
252                Err(err) => {
253                    panic!(
254                        "Parse error: {:?}\nFailed to parse:\n```\n{}\n```",
255                        err, input
256                    )
257                }
258            };
259
260            snapbox::assert_eq(input, doc.to_string());
261        }
262    }
263
264    #[test]
265    fn documents_parse_only() {
266        let parse_only = ["\u{FEFF}
267[package]
268name = \"foo\"
269version = \"0.0.1\"
270authors = []
271"];
272        for input in parse_only {
273            dbg!(input);
274            let mut parsed = parse_document(input);
275            if let Ok(parsed) = &mut parsed {
276                parsed.despan();
277            }
278            match parsed {
279                Ok(_) => (),
280                Err(err) => {
281                    panic!(
282                        "Parse error: {:?}\nFailed to parse:\n```\n{}\n```",
283                        err, input
284                    )
285                }
286            }
287        }
288    }
289
290    #[test]
291    fn invalid_documents() {
292        let invalid_inputs = [r#" hello = 'darkness' # my old friend
293$"#];
294        for input in invalid_inputs {
295            dbg!(input);
296            let mut parsed = parse_document(input);
297            if let Ok(parsed) = &mut parsed {
298                parsed.despan();
299            }
300            assert!(parsed.is_err(), "Input: {:?}", input);
301        }
302    }
303}