#![allow(clippy::type_complexity)] use std::cell::RefCell; pub(crate) mod array; pub(crate) mod datetime; pub(crate) mod document; pub(crate) mod error; pub(crate) mod inline_table; pub(crate) mod key; pub(crate) mod numbers; pub(crate) mod state; pub(crate) mod strings; pub(crate) mod table; pub(crate) mod trivia; pub(crate) mod value; pub(crate) use crate::error::TomlError; pub(crate) fn parse_document>(raw: S) -> Result, TomlError> { use prelude::*; let b = new_input(raw.as_ref()); let state = RefCell::new(state::ParseState::new()); let state_ref = &state; document::document(state_ref) .parse(b.clone()) .map_err(|e| TomlError::new(e, b))?; let doc = state .into_inner() .into_document(raw) .map_err(|e| TomlError::custom(e.to_string(), None))?; Ok(doc) } pub(crate) fn parse_key(raw: &str) -> Result { use prelude::*; let b = new_input(raw); let result = key::simple_key.parse(b.clone()); match result { Ok((raw, key)) => { Ok(crate::Key::new(key).with_repr_unchecked(crate::Repr::new_unchecked(raw))) } Err(e) => Err(TomlError::new(e, b)), } } pub(crate) fn parse_key_path(raw: &str) -> Result, TomlError> { use prelude::*; let b = new_input(raw); let result = key::key.parse(b.clone()); match result { Ok(mut keys) => { for key in &mut keys { key.despan(raw); } Ok(keys) } Err(e) => Err(TomlError::new(e, b)), } } pub(crate) fn parse_value(raw: &str) -> Result { use prelude::*; let b = new_input(raw); let parsed = value::value.parse(b.clone()); match parsed { Ok(mut value) => { // Only take the repr and not decor, as its probably not intended value.decor_mut().clear(); value.despan(raw); Ok(value) } Err(e) => Err(TomlError::new(e, b)), } } pub(crate) mod prelude { pub(crate) use winnow::combinator::dispatch; pub(crate) use winnow::error::ContextError; pub(crate) use winnow::error::FromExternalError; pub(crate) use winnow::error::StrContext; pub(crate) use winnow::error::StrContextValue; pub(crate) use winnow::PResult; pub(crate) use winnow::Parser; pub(crate) type Input<'b> = winnow::Stateful, RecursionCheck>; pub(crate) fn new_input(s: &str) -> Input<'_> { winnow::Stateful { input: winnow::Located::new(winnow::BStr::new(s)), state: Default::default(), } } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub(crate) struct RecursionCheck { #[cfg(not(feature = "unbounded"))] current: usize, } #[cfg(not(feature = "unbounded"))] const LIMIT: usize = 80; impl RecursionCheck { pub(crate) fn check_depth(_depth: usize) -> Result<(), super::error::CustomError> { #[cfg(not(feature = "unbounded"))] if LIMIT <= _depth { return Err(super::error::CustomError::RecursionLimitExceeded); } Ok(()) } fn enter(&mut self) -> Result<(), super::error::CustomError> { #[cfg(not(feature = "unbounded"))] { self.current += 1; if LIMIT <= self.current { return Err(super::error::CustomError::RecursionLimitExceeded); } } Ok(()) } fn exit(&mut self) { #[cfg(not(feature = "unbounded"))] { self.current -= 1; } } } pub(crate) fn check_recursion<'b, O>( mut parser: impl Parser, O, ContextError>, ) -> impl Parser, O, ContextError> { move |input: &mut Input<'b>| { input.state.enter().map_err(|err| { winnow::error::ErrMode::from_external_error( input, winnow::error::ErrorKind::Eof, err, ) .cut() })?; let result = parser.parse_next(input); input.state.exit(); result } } } #[cfg(test)] #[cfg(feature = "parse")] #[cfg(feature = "display")] mod test { use super::*; use snapbox::assert_data_eq; use snapbox::prelude::*; #[test] fn documents() { let documents = [ "", r#" # This is a TOML document. title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00-08:00 # First class dates [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true [servers] # Indentation (tabs and/or spaces) is allowed but not required [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" [clients] data = [ ["gamma", "delta"], [1, 2] ] # Line breaks are OK when inside arrays hosts = [ "alpha", "omega" ] 'some.weird .stuff' = """ like that # """ # this broke my syntax highlighting " also. like " = ''' that ''' double = 2e39 # this number looks familiar # trailing comment"#, r#""#, r#" "#, r#" hello = 'darkness' # my old friend "#, r#"[parent . child] key = "value" "#, r#"hello.world = "a" "#, r#"foo = 1979-05-27 # Comment "#, ]; for input in documents { dbg!(input); let parsed = parse_document(input).map(|d| d.into_mut()); let doc = match parsed { Ok(doc) => doc, Err(err) => { panic!( "Parse error: {:?}\nFailed to parse:\n```\n{}\n```", err, input ) } }; assert_data_eq!(doc.to_string(), input.raw()); } } #[test] fn documents_parse_only() { let parse_only = ["\u{FEFF} [package] name = \"foo\" version = \"0.0.1\" authors = [] "]; for input in parse_only { dbg!(input); let parsed = parse_document(input).map(|d| d.into_mut()); match parsed { Ok(_) => (), Err(err) => { panic!( "Parse error: {:?}\nFailed to parse:\n```\n{}\n```", err, input ) } } } } #[test] fn invalid_documents() { let invalid_inputs = [r#" hello = 'darkness' # my old friend $"#]; for input in invalid_inputs { dbg!(input); let parsed = parse_document(input).map(|d| d.into_mut()); assert!(parsed.is_err(), "Input: {:?}", input); } } }