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