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