toml_edit/parser/
mod.rs

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