• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![allow(clippy::type_complexity)]
2 
3 use std::cell::RefCell;
4 pub(crate) mod array;
5 pub(crate) mod datetime;
6 pub(crate) mod document;
7 pub(crate) mod error;
8 pub(crate) mod inline_table;
9 pub(crate) mod key;
10 pub(crate) mod numbers;
11 pub(crate) mod state;
12 pub(crate) mod strings;
13 pub(crate) mod table;
14 pub(crate) mod trivia;
15 pub(crate) mod value;
16 
17 pub(crate) use crate::error::TomlError;
18 
parse_document<S: AsRef<str>>(raw: S) -> Result<crate::ImDocument<S>, TomlError>19 pub(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 
parse_key(raw: &str) -> Result<crate::Key, TomlError>35 pub(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 
parse_key_path(raw: &str) -> Result<Vec<crate::Key>, TomlError>48 pub(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 
parse_value(raw: &str) -> Result<crate::Value, TomlError>64 pub(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 
80 pub(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 
new_input(s: &str) -> Input<'_>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 {
check_depth(_depth: usize) -> Result<(), super::error::CustomError>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 
enter(&mut self) -> Result<(), super::error::CustomError>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 
exit(&mut self)128         fn exit(&mut self) {
129             #[cfg(not(feature = "unbounded"))]
130             {
131                 self.current -= 1;
132             }
133         }
134     }
135 
check_recursion<'b, O>( mut parser: impl Parser<Input<'b>, O, ContextError>, ) -> impl Parser<Input<'b>, O, ContextError>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")]
158 mod test {
159     use super::*;
160     use snapbox::assert_data_eq;
161     use snapbox::prelude::*;
162 
163     #[test]
documents()164     fn documents() {
165         let documents = [
166             "",
167             r#"
168 # This is a TOML document.
169 
170 title = "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
197 hosts = [
198     "alpha",
199     "omega"
200 ]
201 
202    'some.weird .stuff'   =  """
203                          like
204                          that
205                       #   """ # this broke my syntax highlighting
206    " also. like " = '''
207 that
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]
216 key = "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]
documents_parse_only()241     fn documents_parse_only() {
242         let parse_only = ["\u{FEFF}
243 [package]
244 name = \"foo\"
245 version = \"0.0.1\"
246 authors = []
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]
invalid_documents()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 }
274