toml_edit/parser/
mod.rs

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