| #![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<S: AsRef<str>>(raw: S) -> Result<crate::ImDocument<S>, 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<crate::Key, TomlError> { |
| 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<Vec<crate::Key>, 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<crate::Value, TomlError> { |
| 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<winnow::Located<&'b winnow::BStr>, 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<Input<'b>, O, ContextError>, |
| ) -> impl Parser<Input<'b>, 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); |
| } |
| } |
| } |