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