diff --git a/Cargo.lock b/Cargo.lock index 5a5b9d88..3f2033ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,12 +67,6 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - [[package]] name = "cast" version = "0.3.0" @@ -167,16 +161,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concolor" version = "0.0.11" @@ -532,6 +516,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom8" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d908f0297c3526d34e478d438b07eefe3d7b0416494d7ffccb17f1c7f7262c" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -924,11 +924,11 @@ dependencies = [ name = "toml_edit" version = "0.15.0" dependencies = [ - "combine", "criterion", "indexmap", "itertools", "kstring", + "nom8", "serde", "serde_json", "snapbox", diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml index 0e72e3b2..a3a608e6 100644 --- a/crates/toml_edit/Cargo.toml +++ b/crates/toml_edit/Cargo.toml @@ -42,7 +42,7 @@ serde = ["dep:serde", "toml_datetime/serde"] [dependencies] indexmap = "1.9.1" -combine = "4.6.6" +nom8 = "0.1.0" itertools = "0.10.5" serde = { version = "1.0.145", features = ["derive"], optional = true } kstring = { version = "2.0.0", features = ["max_inline"], optional = true } diff --git a/crates/toml_edit/src/document.rs b/crates/toml_edit/src/document.rs index d2898cde..a0319d44 100644 --- a/crates/toml_edit/src/document.rs +++ b/crates/toml_edit/src/document.rs @@ -70,7 +70,13 @@ impl FromStr for Document { /// Parses a document from a &str fn from_str(s: &str) -> Result { - parser::document(s.as_bytes()) + use nom8::prelude::*; + + let b = s.as_bytes(); + parser::document::document + .parse(b) + .finish() + .map_err(|e| Self::Err::new(e, b)) } } diff --git a/crates/toml_edit/src/key.rs b/crates/toml_edit/src/key.rs index f9cce7ce..bb640b82 100644 --- a/crates/toml_edit/src/key.rs +++ b/crates/toml_edit/src/key.rs @@ -1,11 +1,9 @@ use std::borrow::Cow; use std::str::FromStr; -use combine::stream::position::Stream; - use crate::encode::{to_string_repr, StringStyle}; use crate::parser; -use crate::parser::is_unquoted_char; +use crate::parser::key::is_unquoted_char; use crate::repr::{Decor, Repr}; use crate::InternalString; @@ -105,37 +103,23 @@ impl Key { } fn try_parse_simple(s: &str) -> Result { - use combine::stream::position::{IndexPositioner, Positioner}; - use combine::EasyParser; + use nom8::prelude::*; let b = s.as_bytes(); - let result = parser::simple_key().easy_parse(Stream::new(b)); + let result = parser::key::simple_key.parse(b).finish(); match result { - Ok((_, ref rest)) if !rest.input.is_empty() => Err(parser::TomlError::from_unparsed( - (&rest.positioner - as &dyn Positioner) - .position(), - b, - )), - Ok(((raw, key), _)) => Ok(Key::new(key).with_repr_unchecked(Repr::new_unchecked(raw))), + Ok((raw, key)) => Ok(Key::new(key).with_repr_unchecked(Repr::new_unchecked(raw))), Err(e) => Err(parser::TomlError::new(e, b)), } } fn try_parse_path(s: &str) -> Result, parser::TomlError> { - use combine::stream::position::{IndexPositioner, Positioner}; - use combine::EasyParser; + use nom8::prelude::*; let b = s.as_bytes(); - let result = parser::key_path().easy_parse(Stream::new(b)); + let result = parser::key::key.parse(b).finish(); match result { - Ok((_, ref rest)) if !rest.input.is_empty() => Err(parser::TomlError::from_unparsed( - (&rest.positioner - as &dyn Positioner) - .position(), - b, - )), - Ok((keys, _)) => Ok(keys), + Ok(keys) => Ok(keys), Err(e) => Err(parser::TomlError::new(e, b)), } } diff --git a/crates/toml_edit/src/parser/array.rs b/crates/toml_edit/src/parser/array.rs index 95604ecc..885c5b58 100644 --- a/crates/toml_edit/src/parser/array.rs +++ b/crates/toml_edit/src/parser/array.rs @@ -1,19 +1,27 @@ -use combine::parser::byte::byte; -use combine::parser::range::recognize_with_value; -use combine::stream::RangeStream; -use combine::*; +use nom8::combinator::cut; +use nom8::combinator::opt; +use nom8::multi::separated_list1; +use nom8::sequence::delimited; use crate::parser::trivia::ws_comment_newline; use crate::parser::value::value; -use crate::{Array, Value}; +use crate::{Array, Item, Value}; + +use crate::parser::prelude::*; // ;; Array // array = array-open array-values array-close -parse!(array() -> Array, { - between(byte(ARRAY_OPEN), byte(ARRAY_CLOSE), - array_values()) -}); +pub(crate) fn array(input: Input<'_>) -> IResult, Array, ParserError<'_>> { + delimited( + ARRAY_OPEN, + cut(array_values), + cut(ARRAY_CLOSE) + .context(Context::Expression("array")) + .context(Context::Expected(ParserValue::CharLiteral(']'))), + ) + .parse(input) +} // note: we're omitting ws and newlines here, because // they should be part of the formatted values @@ -27,42 +35,42 @@ const ARRAY_SEP: u8 = b','; // note: this rule is modified // array-values = [ ( array-value array-sep array-values ) / // array-value / ws-comment-newline ] -parse!(array_values() -> Array, { +pub(crate) fn array_values(input: Input<'_>) -> IResult, Array, ParserError<'_>> { ( - optional( - recognize_with_value( - sep_end_by1(array_value(), byte(ARRAY_SEP)) - ).map(|(r, v): (&'a [u8], Array)| (v, r[r.len() - 1] == b',')) + opt( + (separated_list1(ARRAY_SEP, array_value), opt(ARRAY_SEP)).map( + |(v, trailing): (Vec, Option)| { + ( + Array::with_vec(v.into_iter().map(Item::Value).collect()), + trailing.is_some(), + ) + }, + ), ), - ws_comment_newline(), - ).and_then::<_, _, std::str::Utf8Error>(|(array, trailing)| { - let (mut array, comma) = array.unwrap_or_default(); - array.set_trailing_comma(comma); - array.set_trailing(std::str::from_utf8(trailing)?); - Ok(array) - }) -}); + ws_comment_newline, + ) + .map_res::<_, _, std::str::Utf8Error>(|(array, trailing)| { + let (mut array, comma) = array.unwrap_or_default(); + array.set_trailing_comma(comma); + array.set_trailing(std::str::from_utf8(trailing)?); + Ok(array) + }) + .parse(input) +} -parse!(array_value() -> Value, { - attempt(( - ws_comment_newline(), - value(), - ws_comment_newline(), - )).and_then::<_, _, std::str::Utf8Error>(|(ws1, v, ws2)| { - let v = v.decorated( - std::str::from_utf8(ws1)?, - std::str::from_utf8(ws2)?, - ); - Ok(v) - }) -}); +pub(crate) fn array_value(input: Input<'_>) -> IResult, Value, ParserError<'_>> { + (ws_comment_newline, value, ws_comment_newline) + .map_res::<_, _, std::str::Utf8Error>(|(ws1, v, ws2)| { + let v = v.decorated(std::str::from_utf8(ws1)?, std::str::from_utf8(ws2)?); + Ok(v) + }) + .parse(input) +} #[cfg(test)] mod test { use super::*; - use combine::stream::position::Stream; - #[test] fn arrays() { let inputs = [ @@ -101,12 +109,13 @@ mod test { r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, ]; for input in inputs { - parsed_value_eq!(input); + let parsed = array.parse(input.as_bytes()).finish(); + assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned())); } let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#]; for input in invalid_inputs { - let parsed = array().easy_parse(Stream::new(input.as_bytes())); + let parsed = array.parse(input.as_bytes()).finish(); assert!(parsed.is_err()); } } diff --git a/crates/toml_edit/src/parser/datetime.rs b/crates/toml_edit/src/parser/datetime.rs index b3360c04..bdf7c027 100644 --- a/crates/toml_edit/src/parser/datetime.rs +++ b/crates/toml_edit/src/parser/datetime.rs @@ -1,94 +1,82 @@ -use combine::parser::byte::byte; -use combine::parser::range::{recognize, take_while1}; -use combine::stream::RangeStream; -use combine::*; -use toml_datetime::*; +use std::ops::RangeInclusive; use crate::parser::errors::CustomError; +use crate::parser::prelude::*; use crate::parser::trivia::from_utf8_unchecked; +use nom8::branch::alt; +use nom8::bytes::one_of; +use nom8::bytes::take_while_m_n; +use nom8::combinator::cut; +use nom8::combinator::opt; +use nom8::sequence::preceded; +use toml_datetime::*; + // ;; Date and Time (as defined in RFC 3339) // date-time = offset-date-time / local-date-time / local-date / local-time -// offset-date-time = full-date "T" full-time -// local-date-time = full-date "T" partial-time +// offset-date-time = full-date time-delim full-time +// local-date-time = full-date time-delim partial-time // local-date = full-date // local-time = partial-time // full-time = partial-time time-offset -parse!(date_time() -> Datetime, { - choice!( - ( - full_date(), - optional(( - attempt(( - satisfy(is_time_delim), - look_ahead(time_hour()) - )), - partial_time(), - optional(time_offset()), - )) - ) +pub(crate) fn date_time(input: Input<'_>) -> IResult, Datetime, ParserError<'_>> { + alt(( + (full_date, opt((time_delim, partial_time, opt(time_offset)))) .map(|(date, opt)| { match opt { // Offset Date-Time - Some((_, time, offset)) => { - Datetime { date: Some(date), time: Some(time), offset } - } + Some((_, time, offset)) => Datetime { + date: Some(date), + time: Some(time), + offset, + }, // Local Date - None => { - Datetime { date: Some(date), time: None, offset: None} + None => Datetime { + date: Some(date), + time: None, + offset: None, }, } - }), - // Local Time - partial_time() - .message("While parsing a Time") - .map(|t| { - t.into() }) - ) - .message("While parsing a Date-Time") -}); + .context(Context::Expression("date-time")), + partial_time + .map(|t| t.into()) + .context(Context::Expression("time")), + )) + .parse(input) +} // full-date = date-fullyear "-" date-month "-" date-mday -parse!(full_date() -> Date, { - ( - attempt((date_fullyear(), byte(b'-'))), - date_month(), - byte(b'-'), - date_mday(), - ).map(|((year, _), month, _, day)| { - Date { year, month, day } - }) -}); +pub(crate) fn full_date(input: Input<'_>) -> IResult, Date, ParserError<'_>> { + (date_fullyear, b'-', cut((date_month, b'-', date_mday))) + .map(|(year, _, (month, _, day))| Date { year, month, day }) + .parse(input) +} // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] -parse!(partial_time() -> Time, { +pub(crate) fn partial_time(input: Input<'_>) -> IResult, Time, ParserError<'_>> { ( - attempt(( - time_hour(), - byte(b':'), - )), - time_minute(), - byte(b':'), - time_second(), - optional(attempt(time_secfrac())), - ).map(|((hour, _), minute, _, second, nanosecond)| { - Time { hour, minute, second, nanosecond: nanosecond.unwrap_or_default() } - }) -}); + time_hour, + b':', + cut((time_minute, b':', time_second, opt(time_secfrac))), + ) + .map(|(hour, _, (minute, _, second, nanosecond))| Time { + hour, + minute, + second, + nanosecond: nanosecond.unwrap_or_default(), + }) + .parse(input) +} // time-offset = "Z" / time-numoffset // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute -parse!(time_offset() -> Offset, { - attempt(satisfy(|c| c == b'Z' || c == b'z')).map(|_| Offset::Z) - .or( - ( - attempt(choice([byte(b'+'), byte(b'-')])), - time_hour(), - byte(b':'), - time_minute(), - ).map(|(sign, hours, _, minutes)| { +pub(crate) fn time_offset(input: Input<'_>) -> IResult, Offset, ParserError<'_>> { + alt(( + one_of((b'Z', b'z')).value(Offset::Z), + (one_of((b'+', b'-')), cut((time_hour, b':', time_minute))).map( + |(sign, (hours, _, minutes))| { let hours = hours as i8; let hours = match sign { b'+' => hours, @@ -96,121 +84,148 @@ parse!(time_offset() -> Offset, { _ => unreachable!("Parser prevents this"), }; Offset::Custom { hours, minutes } - }) - ).message("While parsing a Time Offset") -}); + }, + ), + )) + .context(Context::Expression("time offset")) + .parse(input) +} // date-fullyear = 4DIGIT -parse!(date_fullyear() -> u16, { - signed_digits(4).map(|d| d as u16) -}); +pub(crate) fn date_fullyear(input: Input<'_>) -> IResult, u16, ParserError<'_>> { + unsigned_digits::<4, 4> + .map(|s: &str| s.parse::().expect("4DIGIT should match u8")) + .parse(input) +} // date-month = 2DIGIT ; 01-12 -parse!(date_month() -> u8, { - unsigned_digits(2).map(|d| d as u8).and_then(|v| { - if (1..=12).contains(&v) { - Ok(v) - } else { - Err(CustomError::OutOfRange) - } - }) -}); +pub(crate) fn date_month(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + unsigned_digits::<2, 2> + .map_res(|s: &str| { + let d = s.parse::().expect("2DIGIT should match u8"); + if (1..=12).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse(input) +} // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year -parse!(date_mday() -> u8, { - unsigned_digits(2).map(|d| d as u8).and_then(|v| { - if (1..=31).contains(&v) { - Ok(v) - } else { - Err(CustomError::OutOfRange) - } - }) -}); +pub(crate) fn date_mday(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + unsigned_digits::<2, 2> + .map_res(|s: &str| { + let d = s.parse::().expect("2DIGIT should match u8"); + if (1..=31).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse(input) +} // time-delim = "T" / %x20 ; T, t, or space -fn is_time_delim(c: u8) -> bool { - matches!(c, b'T' | b't' | b' ') +pub(crate) fn time_delim(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + one_of(TIME_DELIM).parse(input) } +const TIME_DELIM: (u8, u8, u8) = (b'T', b't', b' '); + // time-hour = 2DIGIT ; 00-23 -parse!(time_hour() -> u8, { - unsigned_digits(2).map(|d| d as u8).and_then(|v| { - if (0..=23).contains(&v) { - Ok(v) - } else { - Err(CustomError::OutOfRange) - } - }) -}); +pub(crate) fn time_hour(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + unsigned_digits::<2, 2> + .map_res(|s: &str| { + let d = s.parse::().expect("2DIGIT should match u8"); + if (0..=23).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse(input) +} // time-minute = 2DIGIT ; 00-59 -parse!(time_minute() -> u8, { - unsigned_digits(2).map(|d| d as u8).and_then(|v| { - if (0..=59).contains(&v) { - Ok(v) - } else { - Err(CustomError::OutOfRange) - } - }) -}); +pub(crate) fn time_minute(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + unsigned_digits::<2, 2> + .map_res(|s: &str| { + let d = s.parse::().expect("2DIGIT should match u8"); + if (0..=59).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse(input) +} // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules -parse!(time_second() -> u8, { - unsigned_digits(2).map(|d| d as u8).and_then(|v| { - if (0..=60).contains(&v) { - Ok(v) - } else { - Err(CustomError::OutOfRange) - } - }) -}); +pub(crate) fn time_second(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + unsigned_digits::<2, 2> + .map_res(|s: &str| { + let d = s.parse::().expect("2DIGIT should match u8"); + if (0..=60).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse(input) +} // time-secfrac = "." 1*DIGIT -parse!(time_secfrac() -> u32, { - static SCALE: [u32; 10] = - [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1]; - byte(b'.').and(take_while1(|c: u8| c.is_ascii_digit())).and_then::<_, _, CustomError>(|(_, repr): (u8, &[u8])| { - let mut repr = unsafe { from_utf8_unchecked(repr, "`is_ascii_digit` filters out on-ASCII") }; - let max_digits = SCALE.len() - 1; - if max_digits < repr.len() { - // Millisecond precision is required. Further precision of fractional seconds is - // implementation-specific. If the value contains greater precision than the - // implementation can support, the additional precision must be truncated, not rounded. - repr = &repr[0..max_digits]; - } +pub(crate) fn time_secfrac(input: Input<'_>) -> IResult, u32, ParserError<'_>> { + static SCALE: [u32; 10] = [ + 0, + 100_000_000, + 10_000_000, + 1_000_000, + 100_000, + 10_000, + 1_000, + 100, + 10, + 1, + ]; + const INF: usize = usize::MAX; + preceded(b'.', unsigned_digits::<1, INF>) + .map_res(|mut repr: &str| -> Result { + let max_digits = SCALE.len() - 1; + if max_digits < repr.len() { + // Millisecond precision is required. Further precision of fractional seconds is + // implementation-specific. If the value contains greater precision than the + // implementation can support, the additional precision must be truncated, not rounded. + repr = &repr[0..max_digits]; + } + + let v = repr.parse::().map_err(|_| CustomError::OutOfRange)?; + let num_digits = repr.len(); - let v = repr.parse::().map_err(|_| CustomError::OutOfRange)?; - let num_digits = repr.len(); - - // scale the number accordingly. - let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?; - let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?; - Ok(v) - }) -}); - -parse!(signed_digits(count: usize) -> i32, { - recognize(skip_count_min_max( - *count, *count, - satisfy(|c: u8| c.is_ascii_digit()), - )).and_then(|b: &[u8]| { - let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") }; - s.parse::() - }) -}); - -parse!(unsigned_digits(count: usize) -> u32, { - recognize(skip_count_min_max( - *count, *count, - satisfy(|c: u8| c.is_ascii_digit()), - )).and_then(|b: &[u8]| { - let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") }; - s.parse::() - }) -}); + // scale the number accordingly. + let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?; + let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?; + Ok(v) + }) + .parse(input) +} + +pub(crate) fn unsigned_digits( + input: Input<'_>, +) -> IResult, &str, ParserError<'_>> { + take_while_m_n(MIN, MAX, DIGIT) + .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") }) + .parse(input) +} + +// DIGIT = %x30-39 ; 0-9 +const DIGIT: RangeInclusive = b'0'..=b'9'; #[cfg(test)] mod test { + use super::*; + #[test] fn offset_date_time() { let inputs = [ @@ -219,7 +234,7 @@ mod test { "1979-05-27T00:32:00.999999-07:00", ]; for input in inputs { - parsed_date_time_eq!(input, is_datetime); + date_time.parse(input.as_bytes()).finish().unwrap(); } } @@ -227,7 +242,7 @@ mod test { fn local_date_time() { let inputs = ["1979-05-27T07:32:00", "1979-05-27T00:32:00.999999"]; for input in inputs { - parsed_date_time_eq!(input, is_datetime); + date_time.parse(input.as_bytes()).finish().unwrap(); } } @@ -235,7 +250,7 @@ mod test { fn local_date() { let inputs = ["1979-05-27", "2017-07-20"]; for input in inputs { - parsed_date_time_eq!(input, is_datetime); + date_time.parse(input.as_bytes()).finish().unwrap(); } } @@ -243,13 +258,13 @@ mod test { fn local_time() { let inputs = ["07:32:00", "00:32:00.999999"]; for input in inputs { - parsed_date_time_eq!(input, is_datetime); + date_time.parse(input.as_bytes()).finish().unwrap(); } } #[test] fn time_fraction_truncated() { let input = "1987-07-05T17:45:00.123456789012345Z"; - parsed_date_time_eq!(input, is_datetime); + date_time.parse(input.as_bytes()).finish().unwrap(); } } diff --git a/crates/toml_edit/src/parser/document.rs b/crates/toml_edit/src/parser/document.rs index 16032ffe..77a43d00 100644 --- a/crates/toml_edit/src/parser/document.rs +++ b/crates/toml_edit/src/parser/document.rs @@ -1,19 +1,23 @@ use std::cell::RefCell; -use combine::parser::byte::byte; -use combine::stream::position::{IndexPositioner, Positioner, Stream}; -use combine::stream::RangeStream; -use combine::Parser; -use combine::*; +use nom8::bytes::any; +use nom8::bytes::one_of; +use nom8::combinator::cut; +use nom8::combinator::eof; +use nom8::combinator::opt; +use nom8::combinator::peek; +use nom8::error::FromExternalError; +use nom8::multi::many0_count; use crate::document::Document; use crate::key::Key; use crate::parser::inline_table::KEYVAL_SEP; use crate::parser::key::key; +use crate::parser::prelude::*; +use crate::parser::state::ParseState; use crate::parser::table::table; use crate::parser::trivia::{comment, line_ending, line_trailing, newline, ws}; use crate::parser::value::value; -use crate::parser::{ParseState, TomlError}; use crate::table::TableKeyValue; use crate::Item; @@ -25,86 +29,96 @@ use crate::Item; // ( ws keyval ws [ comment ] ) / // ( ws table ws [ comment ] ) / // ws ) -pub(crate) fn document(s: &[u8]) -> Result { - // Remove BOM if present - let s = s.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(s); - - let parser = RefCell::new(ParseState::default()); - let input = Stream::new(s); - - let parsed = parse_ws(&parser) - .with(choice(( - eof(), - skip_many1( - look_ahead(any()) - .then(|e| { - dispatch!(e; - crate::parser::trivia::COMMENT_START_SYMBOL => parse_comment(&parser), - crate::parser::table::STD_TABLE_OPEN => table(&parser), - crate::parser::trivia::LF | - crate::parser::trivia::CR => parse_newline(&parser), - _ => keyval(&parser), - ) - }) - .skip(parse_ws(&parser)), - ), - ))) - .easy_parse(input); - match parsed { - Ok((_, ref rest)) if !rest.input.is_empty() => Err(TomlError::from_unparsed( - (&rest.positioner - as &dyn Positioner) - .position(), - s, +pub(crate) fn document(input: Input<'_>) -> IResult, Document, ParserError<'_>> { + let state = RefCell::new(ParseState::default()); + let state_ref = &state; + + let (i, _o) = ( + // Remove BOM if present + opt(b"\xEF\xBB\xBF"), + parse_ws(state_ref), + many0_count(( + dispatch! {peek(any); + crate::parser::trivia::COMMENT_START_SYMBOL => cut(parse_comment(state_ref)), + crate::parser::table::STD_TABLE_OPEN => cut(table(state_ref)), + crate::parser::trivia::LF | + crate::parser::trivia::CR => parse_newline(state_ref), + _ => cut(keyval(state_ref)), + }, + parse_ws(state_ref), )), - Ok(..) => { - let doc = parser - .into_inner() - .into_document() - .map_err(|e| TomlError::custom(e.to_string()))?; - Ok(doc) - } - Err(e) => Err(TomlError::new(e, s)), - } + eof, + ) + .parse(input)?; + state + .into_inner() + .into_document() + .map(|document| (i, document)) + .map_err(|err| { + nom8::Err::Error(ParserError::from_external_error( + i, + nom8::error::ErrorKind::MapRes, + err, + )) + }) } -toml_parser!(parse_comment, parser, { - (comment(), line_ending()).and_then::<_, _, std::str::Utf8Error>(|(c, e)| { - let c = std::str::from_utf8(c)?; - parser.borrow_mut().on_comment(c, e); - Ok(()) - }) -}); +pub(crate) fn parse_comment<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'_>> + 's { + move |i| { + (comment, line_ending) + .map_res::<_, _, std::str::Utf8Error>(|(c, e)| { + let c = std::str::from_utf8(c)?; + state.borrow_mut().on_comment(c, e); + Ok(()) + }) + .parse(i) + } +} -toml_parser!(parse_ws, parser, ws().map(|w| parser.borrow_mut().on_ws(w))); +pub(crate) fn parse_ws<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| ws.map(|w| state.borrow_mut().on_ws(w)).parse(i) +} -toml_parser!(parse_newline, parser, { - newline().map(|_| parser.borrow_mut().on_ws("\n")) -}); +pub(crate) fn parse_newline<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| newline.map(|_| state.borrow_mut().on_ws("\n")).parse(i) +} -toml_parser!(keyval, parser, { - parse_keyval().and_then(|(p, kv)| parser.borrow_mut().on_keyval(p, kv)) -}); +pub(crate) fn keyval<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| { + parse_keyval + .map_res(|(p, kv)| state.borrow_mut().on_keyval(p, kv)) + .parse(i) + } +} // keyval = key keyval-sep val -parser! { - fn parse_keyval['a, I]()(I) -> (Vec, TableKeyValue) - where - [I: RangeStream< - Range = &'a [u8], - Token = u8>, - I::Error: ParseError::Position>, - ::Position>>::StreamError: - From + - From + - From + - From - ] { - ( - key(), - byte(KEYVAL_SEP), - (ws(), value(), line_trailing()) - ).and_then::<_, _, std::str::Utf8Error>(|(key, _, v)| { +pub(crate) fn parse_keyval( + input: Input<'_>, +) -> IResult, (Vec, TableKeyValue), ParserError<'_>> { + ( + key, + cut(( + one_of(KEYVAL_SEP) + .context(Context::Expected(ParserValue::CharLiteral('.'))) + .context(Context::Expected(ParserValue::CharLiteral('='))), + ( + ws, + value, + line_trailing + .context(Context::Expected(ParserValue::CharLiteral('\n'))) + .context(Context::Expected(ParserValue::CharLiteral('#'))), + ), + )), + ) + .map_res::<_, _, std::str::Utf8Error>(|(key, (_, v))| { let mut path = key; let key = path.pop().expect("grammar ensures at least 1"); @@ -116,21 +130,20 @@ parser! { TableKeyValue { key, value: Item::Value(v), - } + }, )) }) - } + .parse(input) } #[cfg(test)] mod test { use super::*; - use snapbox::assert_eq; - #[test] fn documents() { let documents = [ + "", r#" # This is a TOML document. @@ -188,20 +201,18 @@ key = "value" "#, ]; for input in documents { - let doc = document(input.as_bytes()); - let doc = match doc { + let parsed = document.parse(input.as_bytes()).finish(); + let doc = match parsed { Ok(doc) => doc, Err(err) => { panic!( - "Parse error: {}\nFailed to parse:\n```\n{}\n```", + "Parse error: {:?}\nFailed to parse:\n```\n{}\n```", err, input ) } }; - dbg!(doc.to_string()); - dbg!(input); - assert_eq(input, doc.to_string()); + snapbox::assert_eq(input, doc.to_string()); } let parse_only = ["\u{FEFF} @@ -211,12 +222,12 @@ version = \"0.0.1\" authors = [] "]; for input in parse_only { - let doc = document(input.as_bytes()); - match doc { + let parsed = document.parse(input.as_bytes()).finish(); + match parsed { Ok(_) => (), Err(err) => { panic!( - "Parse error: {}\nFailed to parse:\n```\n{}\n```", + "Parse error: {:?}\nFailed to parse:\n```\n{}\n```", err, input ) } @@ -226,9 +237,8 @@ authors = [] let invalid_inputs = [r#" hello = 'darkness' # my old friend $"#]; for input in invalid_inputs { - let doc = document(input.as_bytes()); - - assert!(doc.is_err()); + let parsed = document.parse(input.as_bytes()).finish(); + assert!(parsed.is_err(), "Input: {:?}", input); } } } diff --git a/crates/toml_edit/src/parser/errors.rs b/crates/toml_edit/src/parser/errors.rs index a2ff053d..37773c19 100644 --- a/crates/toml_edit/src/parser/errors.rs +++ b/crates/toml_edit/src/parser/errors.rs @@ -1,11 +1,9 @@ use std::error::Error as StdError; use std::fmt::{Display, Formatter, Result}; -use combine::easy::Errors as ParseError; -use combine::stream::easy::Error; -use combine::stream::position::SourcePosition; use itertools::Itertools; +use crate::parser::prelude::*; use crate::Key; /// Type representing a TOML parse error @@ -16,20 +14,21 @@ pub struct TomlError { } impl TomlError { - pub(crate) fn new(error: ParseError, input: &[u8]) -> Self { - let fancy = FancyError::new(error, input); - let message = fancy.to_string(); - let line_col = Some((fancy.position.line as usize, fancy.position.column as usize)); + pub(crate) fn new(error: ParserError<'_>, original: Input<'_>) -> Self { + use nom8::input::Offset; + let offset = original.offset(error.input); + let position = translate_position(original, offset); + let message = ParserErrorDisplay { + error: &error, + original, + position, + } + .to_string(); + let line_col = Some(position); Self { message, line_col } } - pub(crate) fn from_unparsed(pos: usize, input: &[u8]) -> Self { - Self::new( - ParseError::new(pos, CustomError::UnparsedLine.into()), - input, - ) - } - + #[cfg(feature = "serde")] pub(crate) fn custom(message: String) -> Self { Self { message, @@ -70,53 +69,103 @@ impl StdError for TomlError { } #[derive(Debug)] -pub(crate) struct FancyError<'a> { - errors: Vec>, - position: SourcePosition, - input: &'a [u8], +pub(crate) struct ParserError<'b> { + input: Input<'b>, + context: Vec, + cause: Option>, } -impl<'a> FancyError<'a> { - pub(crate) fn new(error: ParseError, input: &'a [u8]) -> Self { - let position = translate_position(input, error.position); - let errors: Vec<_> = error - .errors - .into_iter() - .map(|e| { - e.map_token(char::from) - .map_range(|s| String::from_utf8_lossy(s).into_owned()) - }) - .collect(); +impl<'b> nom8::error::ParseError> for ParserError<'b> { + fn from_error_kind(input: Input<'b>, _kind: nom8::error::ErrorKind) -> Self { + Self { + input, + context: Default::default(), + cause: Default::default(), + } + } + + fn append(_input: Input<'b>, _kind: nom8::error::ErrorKind, other: Self) -> Self { + other + } + + fn from_char(_input: Input<'b>, _: char) -> Self { + unimplemented!("this shouldn't be called with a binary parser") + } + + fn or(self, other: Self) -> Self { + other + } +} + +impl<'b> nom8::error::ContextError, Context> for ParserError<'b> { + fn add_context(_input: Input<'b>, ctx: Context, mut other: Self) -> Self { + other.context.push(ctx); + other + } +} + +impl<'b, E: std::error::Error + Send + Sync + 'static> nom8::error::FromExternalError, E> + for ParserError<'b> +{ + fn from_external_error(input: Input<'b>, _kind: nom8::error::ErrorKind, e: E) -> Self { Self { - errors, - position, input, + context: Default::default(), + cause: Some(Box::new(e)), } } } -impl<'a> Display for FancyError<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let SourcePosition { line, column } = self.position; +// For tests +impl<'b> std::cmp::PartialEq for ParserError<'b> { + fn eq(&self, other: &Self) -> bool { + self.input == other.input + && self.context == other.context + && self.cause.as_ref().map(ToString::to_string) + == other.cause.as_ref().map(ToString::to_string) + } +} + +struct ParserErrorDisplay<'a> { + error: &'a ParserError<'a>, + original: Input<'a>, + position: (usize, usize), +} +impl<'a> std::fmt::Display for ParserErrorDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (line, column) = self.position; let line_num = line + 1; let col_num = column + 1; - let offset = line_num.to_string().len(); + let gutter = line_num.to_string().len(); let content = self - .input + .original .split(|b| *b == b'\n') .nth((line) as usize) .expect("valid line number"); let content = String::from_utf8_lossy(content); + let expression = self.error.context.iter().find_map(|c| match c { + Context::Expression(c) => Some(c), + _ => None, + }); + let expected = self + .error + .context + .iter() + .filter_map(|c| match c { + Context::Expected(c) => Some(c), + _ => None, + }) + .collect::>(); + writeln!( f, "TOML parse error at line {}, column {}", line_num, col_num )?; - // | - for _ in 0..=offset { + for _ in 0..=gutter { write!(f, " ")?; } writeln!(f, "|")?; @@ -126,7 +175,7 @@ impl<'a> Display for FancyError<'a> { writeln!(f, "{}", content)?; // | ^ - for _ in 0..=offset { + for _ in 0..=gutter { write!(f, " ")?; } write!(f, "|")?; @@ -135,16 +184,59 @@ impl<'a> Display for FancyError<'a> { } writeln!(f, "^")?; - Error::fmt_errors(self.errors.as_ref(), f) + if let Some(expression) = expression { + writeln!(f, "Invalid {}", expression)?; + } + + if !expected.is_empty() { + write!(f, "Expected ")?; + for (i, expected) in expected.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", expected)?; + } + writeln!(f)?; + } + if let Some(cause) = &self.error.cause { + write!(f, "{}", cause)?; + } + + Ok(()) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum Context { + Expression(&'static str), + Expected(ParserValue), +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum ParserValue { + CharLiteral(char), + StringLiteral(&'static str), + Description(&'static str), +} + +impl std::fmt::Display for ParserValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParserValue::CharLiteral('\n') => "newline".fmt(f), + ParserValue::CharLiteral('`') => "'`'".fmt(f), + ParserValue::CharLiteral(c) if c.is_ascii_control() => { + write!(f, "`{}`", c.escape_debug()) + } + ParserValue::CharLiteral(c) => write!(f, "`{}`", c), + ParserValue::StringLiteral(c) => write!(f, "`{}`", c), + ParserValue::Description(c) => write!(f, "{}", c), + } } } -fn translate_position(input: &[u8], index: usize) -> SourcePosition { +fn translate_position(input: &[u8], index: usize) -> (usize, usize) { if input.is_empty() { - return SourcePosition { - line: 0, - column: index as i32, - }; + return (0, index); } let safe_index = index.min(input.len() - 1); @@ -162,14 +254,14 @@ fn translate_position(input: &[u8], index: usize) -> SourcePosition { None => 0, }; let line = input[0..line_start].iter().filter(|b| **b == b'\n').count(); - let line = line as i32; + let line = line; let column = std::str::from_utf8(&input[line_start..=index]) .map(|s| s.chars().count() - 1) .unwrap_or_else(|_| index - line_start); - let column = (column + column_offset) as i32; + let column = column + column_offset; - SourcePosition { line, column } + (line, column) } #[cfg(test)] @@ -181,7 +273,7 @@ mod test_translate_position { let input = b""; let index = 0; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 0, column: 0 }); + assert_eq!(position, (0, 0)); } #[test] @@ -189,7 +281,7 @@ mod test_translate_position { let input = b"Hello"; let index = 0; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 0, column: 0 }); + assert_eq!(position, (0, 0)); } #[test] @@ -197,13 +289,7 @@ mod test_translate_position { let input = b"Hello"; let index = input.len() - 1; let position = translate_position(&input[..], index); - assert_eq!( - position, - SourcePosition { - line: 0, - column: input.len() as i32 - 1 - } - ); + assert_eq!(position, (0, input.len() - 1)); } #[test] @@ -211,13 +297,7 @@ mod test_translate_position { let input = b"Hello"; let index = input.len(); let position = translate_position(&input[..], index); - assert_eq!( - position, - SourcePosition { - line: 0, - column: input.len() as i32 - } - ); + assert_eq!(position, (0, input.len())); } #[test] @@ -225,7 +305,7 @@ mod test_translate_position { let input = b"Hello\nWorld\n"; let index = 2; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 0, column: 2 }); + assert_eq!(position, (0, 2)); } #[test] @@ -233,7 +313,7 @@ mod test_translate_position { let input = b"Hello\nWorld\n"; let index = 5; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 0, column: 5 }); + assert_eq!(position, (0, 5)); } #[test] @@ -241,7 +321,7 @@ mod test_translate_position { let input = b"Hello\nWorld\n"; let index = 6; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 1, column: 0 }); + assert_eq!(position, (1, 0)); } #[test] @@ -249,7 +329,7 @@ mod test_translate_position { let input = b"Hello\nWorld\n"; let index = 8; let position = translate_position(&input[..], index); - assert_eq!(position, SourcePosition { line: 1, column: 2 }); + assert_eq!(position, (1, 2)); } } @@ -263,8 +343,6 @@ pub(crate) enum CustomError { key: Vec, actual: &'static str, }, - InvalidHexEscape(u32), - UnparsedLine, OutOfRange, } @@ -315,10 +393,6 @@ impl Display for CustomError { path, actual ) } - CustomError::InvalidHexEscape(h) => { - writeln!(f, "Invalid hex escape code: {:x} ", h) - } - CustomError::UnparsedLine => writeln!(f, "Could not parse the line"), CustomError::OutOfRange => writeln!(f, "Value is out of range"), } } diff --git a/crates/toml_edit/src/parser/inline_table.rs b/crates/toml_edit/src/parser/inline_table.rs index 442e9832..dae8f1e9 100644 --- a/crates/toml_edit/src/parser/inline_table.rs +++ b/crates/toml_edit/src/parser/inline_table.rs @@ -1,30 +1,39 @@ -use combine::parser::byte::byte; -use combine::stream::RangeStream; -use combine::*; -use indexmap::map::Entry; +use nom8::bytes::one_of; +use nom8::combinator::cut; +use nom8::multi::separated_list0; +use nom8::sequence::delimited; use crate::key::Key; use crate::parser::errors::CustomError; use crate::parser::key::key; +use crate::parser::prelude::*; use crate::parser::trivia::ws; use crate::parser::value::value; use crate::table::TableKeyValue; use crate::{InlineTable, InternalString, Item, Value}; +use indexmap::map::Entry; + // ;; Inline Table // inline-table = inline-table-open inline-table-keyvals inline-table-close -parse!(inline_table() -> InlineTable, { - between(byte(INLINE_TABLE_OPEN), byte(INLINE_TABLE_CLOSE), - inline_table_keyvals().and_then(|(kv, p)| table_from_pairs(kv, p))) -}); +pub(crate) fn inline_table(input: Input<'_>) -> IResult, InlineTable, ParserError<'_>> { + delimited( + INLINE_TABLE_OPEN, + cut(inline_table_keyvals.map_res(|(kv, p)| table_from_pairs(kv, p))), + cut(INLINE_TABLE_CLOSE) + .context(Context::Expression("inline table")) + .context(Context::Expected(ParserValue::CharLiteral('}'))), + ) + .parse(input) +} fn table_from_pairs( v: Vec<(Vec, TableKeyValue)>, preamble: &str, ) -> Result { let mut root = InlineTable::new(); - root.preamble = InternalString::from(preamble); + root.preamble = preamble.into(); // Assuming almost all pairs will be directly in `root` root.items.reserve(v.len()); @@ -83,40 +92,43 @@ pub(crate) const KEYVAL_SEP: u8 = b'='; // ( key keyval-sep val inline-table-sep inline-table-keyvals-non-empty ) / // ( key keyval-sep val ) -parse!(inline_table_keyvals() -> (Vec<(Vec, TableKeyValue)>, &'a str), { - ( - sep_by(keyval(), byte(INLINE_TABLE_SEP)), - ws(), - ) -}); +fn inline_table_keyvals( + input: Input<'_>, +) -> IResult, (Vec<(Vec, TableKeyValue)>, &str), ParserError<'_>> { + (separated_list0(INLINE_TABLE_SEP, keyval), ws).parse(input) +} -parse!(keyval() -> (Vec, TableKeyValue), { +fn keyval(input: Input<'_>) -> IResult, (Vec, TableKeyValue), ParserError<'_>> { ( - key(), - byte(KEYVAL_SEP), - (ws(), value(), ws()), - ).map(|(key, _, v)| { - let mut path = key; - let key = path.pop().expect("grammar ensures at least 1"); + key, + cut(( + one_of(KEYVAL_SEP) + .context(Context::Expected(ParserValue::CharLiteral('.'))) + .context(Context::Expected(ParserValue::CharLiteral('='))), + (ws, value, ws), + )), + ) + .map(|(key, (_, v))| { + let mut path = key; + let key = path.pop().expect("grammar ensures at least 1"); - let (pre, v, suf) = v; - let v = v.decorated(pre, suf); - ( - path, - TableKeyValue { - key, - value: Item::Value(v), - } - ) - }) -}); + let (pre, v, suf) = v; + let v = v.decorated(pre, suf); + ( + path, + TableKeyValue { + key, + value: Item::Value(v), + }, + ) + }) + .parse(input) +} #[cfg(test)] mod test { use super::*; - use combine::stream::position::Stream; - #[test] fn inline_tables() { let inputs = [ @@ -127,11 +139,12 @@ mod test { r#"{ hello.world = "a" }"#, ]; for input in inputs { - parsed_value_eq!(input); + let parsed = inline_table.parse(input.as_bytes()).finish(); + assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned())); } let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#]; for input in invalid_inputs { - let parsed = inline_table().easy_parse(Stream::new(input.as_bytes())); + let parsed = inline_table.parse(input.as_bytes()).finish(); assert!(parsed.is_err()); } } diff --git a/crates/toml_edit/src/parser/key.rs b/crates/toml_edit/src/parser/key.rs index ceb1873f..52c61569 100644 --- a/crates/toml_edit/src/parser/key.rs +++ b/crates/toml_edit/src/parser/key.rs @@ -1,9 +1,12 @@ -use combine::parser::byte::byte; -use combine::parser::range::{recognize_with_value, take_while1}; -use combine::stream::RangeStream; -use combine::*; +use std::ops::RangeInclusive; + +use nom8::bytes::any; +use nom8::bytes::take_while1; +use nom8::combinator::peek; +use nom8::multi::separated_list1; use crate::key::Key; +use crate::parser::prelude::*; use crate::parser::strings::{basic_string, literal_string}; use crate::parser::trivia::{from_utf8_unchecked, ws}; use crate::repr::{Decor, Repr}; @@ -11,48 +14,58 @@ use crate::InternalString; // key = simple-key / dotted-key // dotted-key = simple-key 1*( dot-sep simple-key ) -parse!(key() -> Vec, { - sep_by1( - attempt(( - ws(), - simple_key(), - ws(), - )).map(|(pre, (raw, key), suffix)| { - Key::new(key).with_repr_unchecked(Repr::new_unchecked(raw)).with_decor(Decor::new(pre, suffix)) +pub(crate) fn key(input: Input<'_>) -> IResult, Vec, ParserError<'_>> { + separated_list1( + DOT_SEP, + (ws, simple_key, ws).map(|(pre, (raw, key), suffix)| { + Key::new(key) + .with_repr_unchecked(Repr::new_unchecked(raw)) + .with_decor(Decor::new(pre, suffix)) }), - byte(DOT_SEP) - ).expected("key") -}); + ) + .context(Context::Expression("key")) + .parse(input) +} // simple-key = quoted-key / unquoted-key // quoted-key = basic-string / literal-string -parse!(simple_key() -> (&'a str, InternalString), { - recognize_with_value( - look_ahead(any()).then(|e| { - dispatch!(e; - crate::parser::strings::QUOTATION_MARK => basic_string().map(|s: String| s.into()), - crate::parser::strings::APOSTROPHE => literal_string().map(|s: &'a str| s.into()), - _ => unquoted_key().map(|s: &'a str| s.into()), - ) +pub(crate) fn simple_key( + input: Input<'_>, +) -> IResult, (&str, InternalString), ParserError<'_>> { + dispatch! {peek(any); + crate::parser::strings::QUOTATION_MARK => basic_string + .map(|s: std::borrow::Cow<'_, str>| s.as_ref().into()), + crate::parser::strings::APOSTROPHE => literal_string.map(|s: &str| s.into()), + _ => unquoted_key.map(|s: &str| s.into()), + } + .with_recognized() + .map(|(k, b)| { + let s = unsafe { from_utf8_unchecked(b, "If `quoted_key` or `unquoted_key` are valid, then their `recognize`d value is valid") }; + (s, k) }) - ).map(|(b, k)| { - let s = unsafe { from_utf8_unchecked(b, "If `quoted_key` or `unquoted_key` are valid, then their `recognize`d value is valid") }; - (s, k) - }) -}); + .parse(input) +} // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ -parse!(unquoted_key() -> &'a str, { - take_while1(is_unquoted_char).map(|b| { - unsafe { from_utf8_unchecked(b, "`is_unquoted_char` filters out on-ASCII") } - }) -}); +fn unquoted_key(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + take_while1(UNQUOTED_CHAR) + .map(|b| unsafe { from_utf8_unchecked(b, "`is_unquoted_char` filters out on-ASCII") }) + .parse(input) +} -#[inline] pub(crate) fn is_unquoted_char(c: u8) -> bool { - matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_') + use nom8::input::FindToken; + UNQUOTED_CHAR.find_token(c) } +const UNQUOTED_CHAR: ( + RangeInclusive, + RangeInclusive, + RangeInclusive, + u8, + u8, +) = (b'A'..=b'Z', b'a'..=b'z', b'0'..=b'9', b'-', b'_'); + // dot-sep = ws %x2E ws ; . Period const DOT_SEP: u8 = b'.'; @@ -60,9 +73,6 @@ const DOT_SEP: u8 = b'.'; mod test { use super::*; - use combine::stream::position::Stream; - use snapbox::assert_eq; - #[test] fn keys() { let cases = [ @@ -72,11 +82,8 @@ mod test { ]; for (input, expected) in cases { - let parsed = simple_key().easy_parse(Stream::new(input.as_bytes())); - assert!(parsed.is_ok()); - let ((.., k), rest) = parsed.unwrap(); - assert_eq(k.as_str(), expected); - assert_eq!(rest.input.len(), 0); + let parsed = simple_key.parse(input.as_bytes()).finish(); + assert_eq!(parsed, Ok((input, expected.into())), "Parsing {input:?}"); } } } diff --git a/crates/toml_edit/src/parser/macros.rs b/crates/toml_edit/src/parser/macros.rs index 8f27f577..591b2f7b 100644 --- a/crates/toml_edit/src/parser/macros.rs +++ b/crates/toml_edit/src/parser/macros.rs @@ -1,112 +1,13 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! parse ( - ($name:ident( $($arg: ident : $arg_type: ty),* ) -> $ret:ty, $code:expr) => ( - parser!{ - pub(crate) fn $name['a, I]($($arg : $arg_type),*)(I) -> $ret - where - [I: RangeStream< - Range = &'a [u8], - Token = u8>, - I::Error: ParseError::Position>, - ::Position>>::StreamError: - From + - From + - From + - From<$crate::parser::errors::CustomError> - ] - { - $code +macro_rules! dispatch { + ($match_parser: expr; $( $pat:pat $(if $pred:expr)? => $expr: expr ),+ $(,)? ) => { + move |i| + { + let (i, initial) = $match_parser.parse(i)?; + match initial { + $( + $pat $(if $pred)? => $expr.parse(i), + )* } } - ); -); - -#[doc(hidden)] -#[macro_export] -macro_rules! toml_parser ( - ($name:ident, $argh:ident, $closure:expr) => ( - parser!{ - fn $name['a, 'b, I]($argh: &'b RefCell)(I) -> () - where - [I: RangeStream< - Range = &'a [u8], - Token = u8>, - I::Error: ParseError::Position>, - ::Position>>::StreamError: - From + - From + - From + - From<$crate::parser::errors::CustomError> - ] - { - $closure - } - } - ); -); - -#[cfg(test)] -macro_rules! parsed_eq { - ($parsed:ident, $expected:expr) => {{ - assert!($parsed.is_ok(), "{:?}", $parsed.err().unwrap()); - let (v, rest) = $parsed.unwrap(); - assert_eq!(v, $expected); - assert!(rest.input.is_empty()); - }}; -} - -#[cfg(test)] -macro_rules! parsed_float_eq { - ($input:ident, $expected:expr) => {{ - let parsed = crate::parser::numbers::float().easy_parse(Stream::new($input.as_bytes())); - let (v, rest) = match parsed { - Ok(parsed) => parsed, - Err(err) => { - panic!("Unexpected error for {:?}: {:?}", $input, err); - } - }; - if $expected.is_nan() { - assert!(v.is_nan()); - } else if $expected.is_infinite() { - assert!(v.is_infinite()); - assert_eq!($expected.is_sign_positive(), v.is_sign_positive()); - } else { - dbg!($expected); - dbg!(v); - assert!(($expected - v).abs() < std::f64::EPSILON); - } - assert!(rest.input.is_empty()); - }}; -} - -#[cfg(test)] -macro_rules! parsed_value_eq { - ($input:expr) => { - use combine::EasyParser; - let parsed = crate::parser::value::value() - .easy_parse(combine::stream::position::Stream::new($input.as_bytes())); - let (v, rest) = match parsed { - Ok(parsed) => parsed, - Err(err) => { - panic!("Unexpected error for {:?}: {:?}", $input, err); - } - }; - snapbox::assert_eq(v.to_string(), $input); - assert!(rest.input.is_empty()); - }; -} - -#[cfg(test)] -macro_rules! parsed_date_time_eq { - ($input:expr, $is:ident) => {{ - use combine::EasyParser; - let parsed = crate::parser::value::value() - .easy_parse(combine::stream::position::Stream::new($input.as_bytes())); - assert!(parsed.is_ok()); - let (v, rest) = parsed.unwrap(); - snapbox::assert_eq(v.to_string(), $input); - assert!(rest.input.is_empty()); - assert!(v.$is()); - }}; + } } diff --git a/crates/toml_edit/src/parser/mod.rs b/crates/toml_edit/src/parser/mod.rs index 03c9ed34..bb94b8fc 100644 --- a/crates/toml_edit/src/parser/mod.rs +++ b/crates/toml_edit/src/parser/mod.rs @@ -1,26 +1,62 @@ -#![allow(clippy::unneeded_field_pattern)] -#![allow(clippy::toplevel_ref_arg)] - #[macro_use] -mod macros; -mod array; +pub(crate) mod macros; + +pub(crate) mod array; pub(crate) mod datetime; -mod document; -mod errors; -mod inline_table; -mod key; +pub(crate) mod document; +pub(crate) mod errors; +pub(crate) mod inline_table; +pub(crate) mod key; pub(crate) mod numbers; -mod state; +pub(crate) mod state; pub(crate) mod strings; -mod table; -mod trivia; -mod value; +pub(crate) mod table; +pub(crate) mod trivia; +pub(crate) mod value; + +pub use errors::TomlError; + +mod prelude { + pub(crate) use super::errors::Context; + pub(crate) use super::errors::ParserError; + pub(crate) use super::errors::ParserValue; + pub(crate) use nom8::IResult; + pub(crate) use nom8::Parser as _; + + #[cfg(test)] + pub(crate) use nom8::FinishIResult as _; + + pub(crate) type Input<'b> = &'b [u8]; -pub(crate) use self::document::document; -pub use self::errors::TomlError; -pub(crate) use self::key::is_unquoted_char; -pub(crate) use self::key::key as key_path; -pub(crate) use self::key::simple_key; -pub(crate) use self::value::value as value_parser; + pub(crate) fn ok_error(res: IResult) -> Result, nom8::Err> { + match res { + Ok(ok) => Ok(Some(ok)), + Err(nom8::Err::Error(_)) => Ok(None), + Err(err) => Err(err), + } + } -use self::state::ParseState; + #[allow(dead_code)] + pub(crate) fn trace( + context: impl std::fmt::Display, + mut parser: impl nom8::Parser, + ) -> impl FnMut(I) -> IResult { + static DEPTH: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + move |input: I| { + let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst) * 2; + eprintln!("{:depth$}--> {} {:?}", "", context, input); + match parser.parse(input) { + Ok((i, o)) => { + DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + eprintln!("{:depth$}<-- {} {:?}", "", context, i); + Ok((i, o)) + } + Err(err) => { + DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + eprintln!("{:depth$}<-- {} {:?}", "", context, err); + Err(err) + } + } + } + } +} diff --git a/crates/toml_edit/src/parser/numbers.rs b/crates/toml_edit/src/parser/numbers.rs index 58a2c4a3..3ac32a29 100644 --- a/crates/toml_edit/src/parser/numbers.rs +++ b/crates/toml_edit/src/parser/numbers.rs @@ -1,207 +1,270 @@ -use combine::parser::byte::{byte, bytes, digit, hex_digit, oct_digit}; -use combine::parser::range::{range, recognize}; -use combine::stream::RangeStream; -use combine::*; +use std::ops::RangeInclusive; +use nom8::branch::alt; +use nom8::bytes::any; +use nom8::bytes::one_of; +use nom8::bytes::tag; +use nom8::combinator::cut; +use nom8::combinator::opt; +use nom8::combinator::peek; +use nom8::multi::many0_count; +use nom8::sequence::preceded; + +use crate::parser::prelude::*; use crate::parser::trivia::from_utf8_unchecked; // ;; Boolean // boolean = true / false -pub(crate) const TRUE: &[u8] = b"true"; -pub(crate) const FALSE: &[u8] = b"false"; -parse!(boolean() -> bool, { - choice(( - (byte(TRUE[0]), range(&TRUE[1..]),), - (byte(FALSE[0]), range(&FALSE[1..]),), - )).map(|p| p.0 == b't') -}); -parse!(true_() -> bool, { - range(crate::parser::numbers::TRUE).map(|_| true) -}); -parse!(false_() -> bool, { - range(crate::parser::numbers::FALSE).map(|_| false) -}); +#[allow(dead_code)] // directly define in `fn value` +pub(crate) fn boolean(input: Input<'_>) -> IResult, bool, ParserError<'_>> { + alt((true_, false_)).parse(input) +} + +pub(crate) fn true_(input: Input<'_>) -> IResult, bool, ParserError<'_>> { + (peek(TRUE[0]), cut(TRUE)).value(true).parse(input) +} +const TRUE: &[u8] = b"true"; + +pub(crate) fn false_(input: Input<'_>) -> IResult, bool, ParserError<'_>> { + (peek(FALSE[0]), cut(FALSE)).value(false).parse(input) +} +const FALSE: &[u8] = b"false"; // ;; Integer // integer = dec-int / hex-int / oct-int / bin-int -parse!(integer() -> i64, { - choice!( - attempt(hex_int()), - attempt(oct_int()), - attempt(bin_int()), - dec_int() - .and_then(|s| s.replace('_', "").parse()) - .message("While parsing an Integer") - ) -}); +pub(crate) fn integer(input: Input<'_>) -> IResult, i64, ParserError<'_>> { + dispatch! {peek(opt((any, any))); + Some((b'0', b'x')) => hex_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 16)), + Some((b'0', b'o')) => oct_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 8)), + Some((b'0', b'b')) => bin_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 2)), + _ => dec_int.map_res(|s| s.replace('_', "").parse()), + } + .parse(input) +} // dec-int = [ minus / plus ] unsigned-dec-int // unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT ) -parse!(dec_int() -> &'a str, { - recognize(( - optional(choice([byte(b'-'), byte(b'+')])), - choice(( - byte(b'0'), +pub(crate) fn dec_int(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + ( + opt(one_of((b'+', b'-'))), + alt(( ( - satisfy(|c| (b'1'..=b'9').contains(&c)), - skip_many(( - optional(byte(b'_')), - skip_many1(digit()), - )), - ).map(|t| t.0), + one_of(DIGIT1_9), + many0_count(alt(( + digit.value(()), + ( + one_of(b'_'), + cut(digit).context(Context::Expected(ParserValue::Description("digit"))), + ) + .value(()), + ))), + ) + .value(()), + digit.value(()), )), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") } - }) -}); + ) + .recognize() + .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") }) + .context(Context::Expression("integer")) + .parse(input) +} +const DIGIT1_9: RangeInclusive = b'1'..=b'9'; // hex-prefix = %x30.78 ; 0x // hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG ) -parse!(hex_int() -> i64, { - bytes(b"0x").with( - recognize(( - hex_digit(), - skip_many(( - optional(byte(b'_')), - skip_many1(hex_digit()), - )), - ).map(|t| t.0) - )).and_then(|b: &[u8]| { - let s = unsafe { from_utf8_unchecked(b, "`hex_digit` and `_` filter out non-ASCII") }; - i64::from_str_radix(&s.replace('_', ""), 16) - }).message("While parsing a hexadecimal Integer") -}); +pub(crate) fn hex_int(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + preceded( + HEX_PREFIX, + cut(( + hexdig, + many0_count(alt(( + hexdig.value(()), + ( + one_of(b'_'), + cut(hexdig).context(Context::Expected(ParserValue::Description("digit"))), + ) + .value(()), + ))), + )) + .recognize(), + ) + .map(|b| unsafe { from_utf8_unchecked(b, "`hexdig` and `_` filter out non-ASCII") }) + .context(Context::Expression("hexadecimal integer")) + .parse(input) +} +const HEX_PREFIX: &[u8] = b"0x"; // oct-prefix = %x30.6F ; 0o // oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 ) -parse!(oct_int() -> i64, { - bytes(b"0o").with( - recognize(( - oct_digit(), - skip_many(( - optional(byte(b'_')), - skip_many1(oct_digit()), - )), - ).map(|t| t.0) - )).and_then(|b: &[u8]| { - let s = unsafe { from_utf8_unchecked(b, "`oct_digit` and `_` filter out non-ASCII") }; - i64::from_str_radix(&s.replace('_', ""), 8) - }).message("While parsing a octal Integer") -}); +pub(crate) fn oct_int(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + preceded( + OCT_PREFIX, + cut(( + one_of(DIGIT0_7), + many0_count(alt(( + one_of(DIGIT0_7).value(()), + ( + one_of(b'_'), + cut(one_of(DIGIT0_7)) + .context(Context::Expected(ParserValue::Description("digit"))), + ) + .value(()), + ))), + )) + .recognize(), + ) + .map(|b| unsafe { from_utf8_unchecked(b, "`DIGIT0_7` and `_` filter out non-ASCII") }) + .context(Context::Expression("octal integer")) + .parse(input) +} +const OCT_PREFIX: &[u8] = b"0o"; +const DIGIT0_7: RangeInclusive = b'0'..=b'7'; // bin-prefix = %x30.62 ; 0b // bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 ) -parse!(bin_int() -> i64, { - bytes(b"0b").with( - recognize(( - satisfy(|c: u8| c == b'0' || c == b'1'), - skip_many(( - optional(byte(b'_')), - skip_many1(satisfy(|c: u8| c == b'0' || c == b'1')), - )), - ).map(|t| t.0) - )).and_then(|b: &[u8]| { - let s = unsafe { from_utf8_unchecked(b, "`is_digit` and `_` filter out non-ASCII") }; - i64::from_str_radix(&s.replace('_', ""), 2) - }).message("While parsing a binary Integer") -}); +pub(crate) fn bin_int(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + preceded( + BIN_PREFIX, + cut(( + one_of(DIGIT0_1), + many0_count(alt(( + one_of(DIGIT0_1).value(()), + ( + one_of(b'_'), + cut(one_of(DIGIT0_1)) + .context(Context::Expected(ParserValue::Description("digit"))), + ) + .value(()), + ))), + )) + .recognize(), + ) + .map(|b| unsafe { from_utf8_unchecked(b, "`DIGIT0_1` and `_` filter out non-ASCII") }) + .context(Context::Expression("binary integer")) + .parse(input) +} +const BIN_PREFIX: &[u8] = b"0b"; +const DIGIT0_1: RangeInclusive = b'0'..=b'1'; // ;; Float // float = float-int-part ( exp / frac [ exp ] ) // float =/ special-float // float-int-part = dec-int -parse!(float() -> f64, { - choice(( - parse_float() - .and_then(|s| s.replace('_', "").parse()), - special_float(), - )).message("While parsing a Float") -}); +pub(crate) fn float(input: Input<'_>) -> IResult, f64, ParserError<'_>> { + alt(( + float_.map_res(|s| s.replace('_', "").parse()), + special_float, + )) + .context(Context::Expression("floating-point number")) + .parse(input) +} -parse!(parse_float() -> &'a str, { - recognize(( - attempt((dec_int(), look_ahead(one_of([b'e', b'E', b'.'])))), - choice(( - exp(), - ( - frac(), - optional(exp()), - ).map(|_| "") - )), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`dec_int`, `one_of`, `exp`, and `frac` filter out non-ASCII") } - }) -}); +pub(crate) fn float_(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + (dec_int, alt((exp, (frac, opt(exp)).map(|_| "")))) + .recognize() + .map(|b: &[u8]| unsafe { + from_utf8_unchecked( + b, + "`dec_int`, `one_of`, `exp`, and `frac` filter out non-ASCII", + ) + }) + .parse(input) +} // frac = decimal-point zero-prefixable-int // decimal-point = %x2E ; . -parse!(frac() -> &'a str, { - recognize(( - byte(b'.'), - parse_zero_prefixable_int(), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`.` and `parse_zero_prefixable_int` filter out non-ASCII") } - }) -}); +pub(crate) fn frac(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + ( + b'.', + cut(zero_prefixable_int).context(Context::Expected(ParserValue::Description("digit"))), + ) + .recognize() + .map(|b: &[u8]| unsafe { + from_utf8_unchecked( + b, + "`.` and `parse_zero_prefixable_int` filter out non-ASCII", + ) + }) + .parse(input) +} // zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT ) -parse!(parse_zero_prefixable_int() -> &'a str, { - recognize(( - skip_many1(digit()), - skip_many(( - optional(byte(b'_')), - skip_many1(digit()), - )), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") } - }) -}); +pub(crate) fn zero_prefixable_int(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + ( + digit, + many0_count(alt(( + digit.value(()), + ( + one_of(b'_'), + cut(digit).context(Context::Expected(ParserValue::Description("digit"))), + ) + .value(()), + ))), + ) + .recognize() + .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") }) + .parse(input) +} // exp = "e" float-exp-part // float-exp-part = [ minus / plus ] zero-prefixable-int -parse!(exp() -> &'a str, { - recognize(( - one_of([b'e', b'E']), - optional(one_of([b'+', b'-'])), - parse_zero_prefixable_int(), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`one_of` and `parse_zero_prefixable_int` filter out non-ASCII") } - }) -}); +pub(crate) fn exp(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + ( + one_of((b'e', b'E')), + opt(one_of([b'+', b'-'])), + cut(zero_prefixable_int), + ) + .recognize() + .map(|b: &[u8]| unsafe { + from_utf8_unchecked( + b, + "`one_of` and `parse_zero_prefixable_int` filter out non-ASCII", + ) + }) + .parse(input) +} // special-float = [ minus / plus ] ( inf / nan ) -parse!(special_float() -> f64, { - attempt(optional(one_of([b'+', b'-'])).and(choice((inf(), nan()))).map(|(s, f)| { - match s { +pub(crate) fn special_float(input: Input<'_>) -> IResult, f64, ParserError<'_>> { + (opt(one_of((b'+', b'-'))), alt((inf, nan))) + .map(|(s, f)| match s { Some(b'+') | None => f, Some(b'-') => -f, _ => unreachable!("one_of should prevent this"), - } - })) -}); - + }) + .parse(input) +} // inf = %x69.6e.66 ; inf -pub(crate) const INF: &[u8] = b"inf"; -parse!(inf() -> f64, { - bytes(INF).map(|_| f64::INFINITY) -}); - +pub(crate) fn inf(input: Input<'_>) -> IResult, f64, ParserError<'_>> { + tag(INF).value(f64::INFINITY).parse(input) +} +const INF: &[u8] = b"inf"; // nan = %x6e.61.6e ; nan -pub(crate) const NAN: &[u8] = b"nan"; -parse!(nan() -> f64, { - bytes(NAN).map(|_| f64::NAN) -}); +pub(crate) fn nan(input: Input<'_>) -> IResult, f64, ParserError<'_>> { + tag(NAN).value(f64::NAN).parse(input) +} +const NAN: &[u8] = b"nan"; + +// DIGIT = %x30-39 ; 0-9 +pub(crate) fn digit(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + one_of(DIGIT).parse(input) +} +const DIGIT: RangeInclusive = b'0'..=b'9'; + +// HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" +pub(crate) fn hexdig(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + one_of(HEXDIG).parse(input) +} +pub(crate) const HEXDIG: (RangeInclusive, RangeInclusive, RangeInclusive) = + (DIGIT, b'A'..=b'F', b'a'..=b'f'); #[cfg(test)] mod test { use super::*; - use crate::parser::*; - use combine::stream::position::Stream; - #[test] fn integers() { let cases = [ @@ -219,15 +282,29 @@ mod test { (&std::i64::MAX.to_string()[..], std::i64::MAX), ]; for &(input, expected) in &cases { - let parsed = numbers::integer().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, expected); + let parsed = integer.parse(input.as_bytes()).finish(); + assert_eq!(parsed, Ok(expected), "Parsing {input:?}"); } let overflow = "1000000000000000000000000000000000"; - let parsed = numbers::integer().easy_parse(Stream::new(overflow.as_bytes())); + let parsed = integer.parse(overflow.as_bytes()).finish(); assert!(parsed.is_err()); } + #[track_caller] + fn assert_float_eq(actual: f64, expected: f64) { + if expected.is_nan() { + assert!(actual.is_nan()); + } else if expected.is_infinite() { + assert!(actual.is_infinite()); + assert_eq!(expected.is_sign_positive(), actual.is_sign_positive()); + } else { + dbg!(expected); + dbg!(actual); + assert!((expected - actual).abs() < std::f64::EPSILON); + } + } + #[test] fn floats() { let cases = [ @@ -250,7 +327,8 @@ mod test { // ("1e+400", std::f64::INFINITY), ]; for &(input, expected) in &cases { - parsed_float_eq!(input, expected); + let parsed = float.parse(input.as_bytes()).finish().unwrap(); + assert_float_eq(parsed, expected); } } } diff --git a/crates/toml_edit/src/parser/strings.rs b/crates/toml_edit/src/parser/strings.rs index 3b078976..fa86da89 100644 --- a/crates/toml_edit/src/parser/strings.rs +++ b/crates/toml_edit/src/parser/strings.rs @@ -1,69 +1,99 @@ use std::borrow::Cow; use std::char; - -use combine::error::Commit; -use combine::parser::byte::{byte, bytes, hex_digit}; -use combine::parser::range::{range, recognize, take_while, take_while1}; -use combine::stream::RangeStream; -use combine::*; +use std::ops::RangeInclusive; + +use nom8::branch::alt; +use nom8::bytes::any; +use nom8::bytes::none_of; +use nom8::bytes::one_of; +use nom8::bytes::tag; +use nom8::bytes::take_while; +use nom8::bytes::take_while1; +use nom8::bytes::take_while_m_n; +use nom8::combinator::cut; +use nom8::combinator::fail; +use nom8::combinator::opt; +use nom8::combinator::peek; +use nom8::combinator::success; +use nom8::multi::many0_count; +use nom8::multi::many1_count; +use nom8::sequence::delimited; +use nom8::sequence::preceded; +use nom8::sequence::terminated; use crate::parser::errors::CustomError; -use crate::parser::trivia::{ - from_utf8_unchecked, is_non_ascii, is_wschar, newline, ws, ws_newlines, -}; +use crate::parser::numbers::HEXDIG; +use crate::parser::prelude::*; +use crate::parser::trivia::{from_utf8_unchecked, newline, ws, ws_newlines, NON_ASCII, WSCHAR}; // ;; String // string = ml-basic-string / basic-string / ml-literal-string / literal-string -parse!(string() -> String, { - choice(( - ml_basic_string(), - basic_string(), - ml_literal_string(), - literal_string().map(|s: &'a str| s.into()), +pub(crate) fn string(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + alt(( + ml_basic_string, + basic_string, + ml_literal_string, + literal_string.map(|s: &str| Cow::Borrowed(s)), )) -}); + .parse(input) +} // ;; Basic String // basic-string = quotation-mark *basic-char quotation-mark -parse!(basic_string() -> String, { - between( - byte(QUOTATION_MARK), byte(QUOTATION_MARK), - many(basic_chars()) - ) - .message("While parsing a Basic String") -}); +pub(crate) fn basic_string(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + let (mut input, _) = one_of(QUOTATION_MARK).parse(input)?; + + let mut c = Cow::Borrowed(""); + if let Some((i, ci)) = ok_error(basic_chars.parse(input))? { + input = i; + c = ci; + } + while let Some((i, ci)) = ok_error(basic_chars.parse(input))? { + input = i; + c.to_mut().push_str(&ci); + } + + let (input, _) = cut(one_of(QUOTATION_MARK)) + .context(Context::Expression("basic string")) + .parse(input)?; + + Ok((input, c)) +} // quotation-mark = %x22 ; " pub(crate) const QUOTATION_MARK: u8 = b'"'; // basic-char = basic-unescaped / escaped -parse!(basic_chars() -> Cow<'a, str>, { - choice(( +fn basic_chars(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + alt(( // Deviate from the official grammar by batching the unescaped chars so we build a string a // chunk at a time, rather than a `char` at a time. - take_while1(is_basic_unescaped).and_then(std::str::from_utf8).map(Cow::Borrowed), - escaped().map(|c| Cow::Owned(String::from(c))), + take_while1(BASIC_UNESCAPED) + .map_res(std::str::from_utf8) + .map(Cow::Borrowed), + escaped.map(|c| Cow::Owned(String::from(c))), )) -}); + .parse(input) +} // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii -#[inline] -fn is_basic_unescaped(c: u8) -> bool { - is_wschar(c) | matches!(c, 0x21 | 0x23..=0x5B | 0x5D..=0x7E) | is_non_ascii(c) -} +pub(crate) const BASIC_UNESCAPED: ( + (u8, u8), + u8, + RangeInclusive, + RangeInclusive, + RangeInclusive, +) = (WSCHAR, 0x21, 0x23..=0x5B, 0x5D..=0x7E, NON_ASCII); // escaped = escape escape-seq-char -parse!(escaped() -> char, { - satisfy(|c| c == ESCAPE) - .then(|_| parser(move |input| { - escape_seq_char().parse_stream(input).into_result() - })) -}); +fn escaped(input: Input<'_>) -> IResult, char, ParserError<'_>> { + preceded(ESCAPE, escape_seq_char).parse(input) +} // escape = %x5C ; \ -const ESCAPE: u8 = b'\\'; +pub(crate) const ESCAPE: u8 = b'\\'; // escape-seq-char = %x22 ; " quotation mark U+0022 // escape-seq-char =/ %x5C ; \ reverse solidus U+005C @@ -74,243 +104,253 @@ const ESCAPE: u8 = b'\\'; // escape-seq-char =/ %x74 ; t tab U+0009 // escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX // escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX -parse!(escape_seq_char() -> char, { - satisfy(is_escape_seq_char) - .message("While parsing escape sequence") - .then(|c| { - parser(move |input| { - match c { - b'b' => Ok(('\u{8}', Commit::Peek(()))), - b'f' => Ok(('\u{c}', Commit::Peek(()))), - b'n' => Ok(('\n', Commit::Peek(()))), - b'r' => Ok(('\r', Commit::Peek(()))), - b't' => Ok(('\t', Commit::Peek(()))), - b'u' => hexescape(4).parse_stream(input).into_result(), - b'U' => hexescape(8).parse_stream(input).into_result(), - b'\\' => Ok(('\\', Commit::Peek(()))), - b'"' => Ok(('"', Commit::Peek(()))), - _ => unreachable!("{:?} filtered out by is_escape_seq_char", c), - } - }) - }) -}); - -#[inline] -fn is_escape_seq_char(c: u8) -> bool { - matches!( - c, - b'"' | b'\\' | b'b' | b'f' | b'n' | b'r' | b't' | b'u' | b'U' - ) +fn escape_seq_char(input: Input<'_>) -> IResult, char, ParserError<'_>> { + dispatch! {any; + b'b' => success('\u{8}'), + b'f' => success('\u{c}'), + b'n' => success('\n'), + b'r' => success('\r'), + b't' => success('\t'), + b'u' => cut(hexescape::<4>).context(Context::Expression("unicode 4-digit hex code")), + b'U' => cut(hexescape::<8>).context(Context::Expression("unicode 8-digit hex code")), + b'\\' => success('\\'), + b'"' => success('"'), + _ => { + cut(fail::<_, char, _>) + .context(Context::Expression("escape sequence")) + .context(Context::Expected(ParserValue::CharLiteral('b'))) + .context(Context::Expected(ParserValue::CharLiteral('f'))) + .context(Context::Expected(ParserValue::CharLiteral('n'))) + .context(Context::Expected(ParserValue::CharLiteral('r'))) + .context(Context::Expected(ParserValue::CharLiteral('t'))) + .context(Context::Expected(ParserValue::CharLiteral('u'))) + .context(Context::Expected(ParserValue::CharLiteral('U'))) + .context(Context::Expected(ParserValue::CharLiteral('\\'))) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + } + } + .parse(input) } -parse!(hexescape(n: usize) -> char, { - recognize(skip_count_min_max( - *n, *n, - hex_digit(), - )) +pub(crate) fn hexescape( + input: Input<'_>, +) -> IResult, char, ParserError<'_>> { + take_while_m_n(0, N, HEXDIG) + .verify(|b: &[u8]| b.len() == N) .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") }) - .and_then(|s| u32::from_str_radix(s, 16)) - .and_then(|h| char::from_u32(h).ok_or(CustomError::InvalidHexEscape(h))) -}); + .map_opt(|s| u32::from_str_radix(s, 16).ok()) + .map_res(|h| char::from_u32(h).ok_or(CustomError::OutOfRange)) + .parse(input) +} // ;; Multiline Basic String // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string-delim -parse!(ml_basic_string() -> String, { - ( - range(ML_BASIC_STRING_DELIM), - ( - optional(newline()), - ml_basic_body(), - ).map(|t| t.1), - // Deviate from grammar by pulling mll_quotes into here so we can handle the confusion with - // it and the closing delim - choice(( - attempt(( - bytes(b"\"\""), range(ML_BASIC_STRING_DELIM) - )).map(|_| Some("\"\"")), - attempt(( - bytes(b"\""), range(ML_BASIC_STRING_DELIM) - )).map(|_| Some("\"")), - range(ML_BASIC_STRING_DELIM).map(|_| None), - )), - ).map(|(_, mut b, q)| { - if let Some(q) = q { - b.push_str(q); - } - b - }).message("While parsing a Multiline Basic String") -}); +fn ml_basic_string(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + delimited( + ML_BASIC_STRING_DELIM, + preceded(opt(newline), cut(ml_basic_body)), + cut(ML_BASIC_STRING_DELIM), + ) + .context(Context::Expression("multiline basic string")) + .parse(input) +} // ml-basic-string-delim = 3quotation-mark -const ML_BASIC_STRING_DELIM: &[u8] = b"\"\"\""; +pub(crate) const ML_BASIC_STRING_DELIM: &[u8] = b"\"\"\""; // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] -parse!(ml_basic_body() -> String, { - ( - many(mlb_content()), - many(attempt(( - mlb_quotes(), - many1(mlb_content()), - ).map(|(q, c): (&str, String)| { - let mut total = q.to_owned(); - total.push_str(&c); - total - }))), - // Deviate: see `ml_basic_string` - //optional(mll_quotes()), - ).map(|(mut c, qc): (String, String)| { - c.push_str(&qc); - c - }) -}); +fn ml_basic_body(mut input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + let mut c = Cow::Borrowed(""); + if let Some((i, ci)) = ok_error(mlb_content.parse(input))? { + input = i; + c = ci; + } + while let Some((i, ci)) = ok_error(mlb_content.parse(input))? { + input = i; + c.to_mut().push_str(&ci); + } + + while let Some((i, qi)) = ok_error(mlb_quotes(none_of(b'\"').value(())).parse(input))? { + if let Some((i, ci)) = ok_error(mlb_content.parse(i))? { + input = i; + c.to_mut().push_str(qi); + c.to_mut().push_str(&ci); + while let Some((i, ci)) = ok_error(mlb_content.parse(input))? { + input = i; + c.to_mut().push_str(&ci); + } + } else { + break; + } + } + + if let Some((i, qi)) = ok_error(mlb_quotes(tag(ML_BASIC_STRING_DELIM).value(())).parse(input))? + { + input = i; + c.to_mut().push_str(qi); + } + + Ok((input, c)) +} // mlb-content = mlb-char / newline / mlb-escaped-nl // mlb-char = mlb-unescaped / escaped -parse!(mlb_content() -> Cow<'a, str>, { - choice(( +fn mlb_content(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + alt(( // Deviate from the official grammar by batching the unescaped chars so we build a string a // chunk at a time, rather than a `char` at a time. - take_while1(is_mlb_unescaped).and_then(std::str::from_utf8).map(Cow::Borrowed), - attempt(escaped().map(|c| Cow::Owned(String::from(c)))), - newline().map(|_| Cow::Borrowed("\n")), - mlb_escaped_nl().map(|_| Cow::Borrowed("")), + take_while1(MLB_UNESCAPED) + .map_res(std::str::from_utf8) + .map(Cow::Borrowed), + // Order changed fromg grammar so `escaped` can more easily `cut` on bad escape sequences + mlb_escaped_nl.map(|_| Cow::Borrowed("")), + escaped.map(|c| Cow::Owned(String::from(c))), + newline.map(|_| Cow::Borrowed("\n")), )) -}); + .parse(input) +} // mlb-quotes = 1*2quotation-mark -parse!(mlb_quotes() -> &'a str, { - choice(( - attempt(bytes(b"\"\"")), - attempt(bytes(b"\"")), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`bytes` out npn-ASCII") } - }) -}); +fn mlb_quotes<'i>( + mut term: impl nom8::Parser, (), ParserError<'i>>, +) -> impl FnMut(Input<'i>) -> IResult, &str, ParserError<'i>> { + move |input| { + let res = terminated(b"\"\"", peek(term.by_ref())) + .map(|b| unsafe { from_utf8_unchecked(b, "`bytes` out non-ASCII") }) + .parse(input); + + match res { + Err(nom8::Err::Error(_)) => terminated(b"\"", peek(term.by_ref())) + .map(|b| unsafe { from_utf8_unchecked(b, "`bytes` out non-ASCII") }) + .parse(input), + res => res, + } + } +} // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii -#[inline] -fn is_mlb_unescaped(c: u8) -> bool { - is_wschar(c) | matches!(c, 0x21 | 0x23..=0x5B | 0x5D..=0x7E) | is_non_ascii(c) -} +pub(crate) const MLB_UNESCAPED: ( + (u8, u8), + u8, + RangeInclusive, + RangeInclusive, + RangeInclusive, +) = (WSCHAR, 0x21, 0x23..=0x5B, 0x5D..=0x7E, NON_ASCII); // mlb-escaped-nl = escape ws newline *( wschar / newline // When the last non-whitespace character on a line is a \, // it will be trimmed along with all whitespace // (including newlines) up to the next non-whitespace // character or closing delimiter. -parse!(mlb_escaped_nl() -> (), { - skip_many1(attempt(( - byte(ESCAPE), - ws(), - ws_newlines(), - ))) -}); +fn mlb_escaped_nl(input: Input<'_>) -> IResult, (), ParserError<'_>> { + many1_count((ESCAPE, ws, ws_newlines)) + .value(()) + .parse(input) +} // ;; Literal String // literal-string = apostrophe *literal-char apostrophe -parse!(literal_string() -> &'a str, { - between( - byte(APOSTROPHE), byte(APOSTROPHE), - take_while(is_literal_char) - ).and_then(std::str::from_utf8) - .message("While parsing a Literal String") -}); +pub(crate) fn literal_string(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + delimited(APOSTROPHE, cut(take_while(LITERAL_CHAR)), cut(APOSTROPHE)) + .map_res(std::str::from_utf8) + .context(Context::Expression("literal string")) + .parse(input) +} // apostrophe = %x27 ; ' apostrophe pub(crate) const APOSTROPHE: u8 = b'\''; // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii -#[inline] -fn is_literal_char(c: u8) -> bool { - matches!(c, 0x09 | 0x20..=0x26 | 0x28..=0x7E) | is_non_ascii(c) -} +pub(crate) const LITERAL_CHAR: ( + u8, + RangeInclusive, + RangeInclusive, + RangeInclusive, +) = (0x9, 0x20..=0x26, 0x28..=0x7E, NON_ASCII); // ;; Multiline Literal String // ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body // ml-literal-string-delim -parse!(ml_literal_string() -> String, { - ( - range(ML_LITERAL_STRING_DELIM), - ( - optional(newline()), - ml_literal_body(), - ).map(|t| t.1.replace("\r\n", "\n")), - // Deviate from grammar by pulling mll_quotes into here so we can handle the confusion with - // it and the closing delim - choice(( - attempt(( - bytes(b"''"), range(ML_LITERAL_STRING_DELIM) - )).map(|_| Some("''")), - attempt(( - bytes(b"'"), range(ML_LITERAL_STRING_DELIM) - )).map(|_| Some("'")), - range(ML_LITERAL_STRING_DELIM).map(|_| None), - )) - ).map(|(_, mut b, q)| { - if let Some(q) = q { - b.push_str(q); - } - b - }).message("While parsing a Multiline Literal String") -}); +fn ml_literal_string(input: Input<'_>) -> IResult, Cow<'_, str>, ParserError<'_>> { + delimited( + (ML_LITERAL_STRING_DELIM, opt(newline)), + cut(ml_literal_body.map(|t| { + if t.contains("\r\n") { + Cow::Owned(t.replace("\r\n", "\n")) + } else { + Cow::Borrowed(t) + } + })), + cut(ML_LITERAL_STRING_DELIM), + ) + .context(Context::Expression("multiline literal string")) + .parse(input) +} // ml-literal-string-delim = 3apostrophe -const ML_LITERAL_STRING_DELIM: &[u8] = b"'''"; +pub(crate) const ML_LITERAL_STRING_DELIM: &[u8] = b"'''"; // ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] -parse!(ml_literal_body() -> &'a str, { - recognize(( - skip_many(mll_content()), - skip_many(attempt((mll_quotes(), skip_many1(mll_content())))), - // Deviate: see ml_literal_string - //optional(mll_quotes()), - )).and_then(std::str::from_utf8) -}); +fn ml_literal_body(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + ( + many0_count(mll_content), + many0_count(( + mll_quotes(none_of(APOSTROPHE).value(())), + many1_count(mll_content), + )), + opt(mll_quotes(tag(ML_LITERAL_STRING_DELIM).value(()))), + ) + .recognize() + .map_res(std::str::from_utf8) + .parse(input) +} // mll-content = mll-char / newline -parse!(mll_content() -> u8, { - choice(( - satisfy(is_mll_char), - newline().map(|_| b'\n') - )) -}); +fn mll_content(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + alt((one_of(MLL_CHAR), newline)).parse(input) +} // mll-char = %x09 / %x20-26 / %x28-7E / non-ascii -#[inline] -fn is_mll_char(c: u8) -> bool { - matches!(c, 0x09 | 0x20..=0x26 | 0x28..=0x7E) | is_non_ascii(c) -} +const MLL_CHAR: ( + u8, + RangeInclusive, + RangeInclusive, + RangeInclusive, +) = (0x9, 0x20..=0x26, 0x28..=0x7E, NON_ASCII); // mll-quotes = 1*2apostrophe -parse!(mll_quotes() -> &'a str, { - choice(( - attempt(bytes(b"''")), - attempt(bytes(b"'")), - )).map(|b: &[u8]| { - unsafe { from_utf8_unchecked(b, "`bytes` out npn-ASCII") } - }) -}); +fn mll_quotes<'i>( + mut term: impl nom8::Parser, (), ParserError<'i>>, +) -> impl FnMut(Input<'i>) -> IResult, &str, ParserError<'i>> { + move |input| { + let res = terminated(b"''", peek(term.by_ref())) + .map(|b| unsafe { from_utf8_unchecked(b, "`bytes` out non-ASCII") }) + .parse(input); + + match res { + Err(nom8::Err::Error(_)) => terminated(b"'", peek(term.by_ref())) + .map(|b| unsafe { from_utf8_unchecked(b, "`bytes` out non-ASCII") }) + .parse(input), + res => res, + } + } +} #[cfg(test)] mod test { use super::*; - use crate::parser::*; - use combine::stream::position::Stream; - #[test] fn basic_string() { let input = r#""I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF. \U0002070E""#; - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!( - parsed, - "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}" - ); + let expected = "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}"; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } #[test] @@ -328,15 +368,14 @@ Violets are blue"#, ]; for &(input, expected) in &cases { - dbg!(input); - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, expected); + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } let invalid_cases = [r#"""" """#, r#"""" \""""#]; for input in &invalid_cases { - let parsed = strings::ml_basic_string().easy_parse(Stream::new(input.as_bytes())); + let parsed = string.parse(input.as_bytes()).finish(); assert!(parsed.is_err()); } } @@ -357,9 +396,9 @@ The quick brown \ """"#, ]; for input in &inputs { - dbg!(input); - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, "The quick brown fox jumps over the lazy dog."); + let expected = "The quick brown fox jumps over the lazy dog."; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } let empties = [ r#""""\ @@ -369,9 +408,10 @@ The quick brown \ \ """"#, ]; - for empty in &empties { - let parsed = strings::string().easy_parse(Stream::new(empty.as_bytes())); - parsed_eq!(parsed, ""); + for input in &empties { + let expected = ""; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } } @@ -385,23 +425,32 @@ The quick brown \ ]; for input in &inputs { - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, &input[1..input.len() - 1]); + let expected = &input[1..input.len() - 1]; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } } #[test] fn ml_literal_string() { - let input = r#"'''I [dw]on't need \d{2} apples'''"#; - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, &input[3..input.len() - 3]); + let inputs = [ + r#"'''I [dw]on't need \d{2} apples'''"#, + r#"''''one_quote''''"#, + ]; + for input in &inputs { + let expected = &input[3..input.len() - 3]; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); + } + let input = r#"''' The first newline is trimmed in raw strings. All other whitespace is preserved. '''"#; - let parsed = strings::string().easy_parse(Stream::new(input.as_bytes())); - parsed_eq!(parsed, &input[4..input.len() - 3]); + let expected = &input[4..input.len() - 3]; + let parsed = string.parse(input.as_bytes()).finish(); + assert_eq!(parsed.as_deref(), Ok(expected), "Parsing {input:?}"); } } diff --git a/crates/toml_edit/src/parser/table.rs b/crates/toml_edit/src/parser/table.rs index d4807453..89294704 100644 --- a/crates/toml_edit/src/parser/table.rs +++ b/crates/toml_edit/src/parser/table.rs @@ -1,13 +1,17 @@ use std::cell::RefCell; +#[allow(unused_imports)] +use std::ops::DerefMut; -use combine::parser::byte::byte; -use combine::parser::range::range; -use combine::stream::RangeStream; -use combine::*; +use nom8::bytes::take; +use nom8::combinator::cut; +use nom8::combinator::peek; +use nom8::sequence::delimited; +// https://github.com/rust-lang/rust/issues/41358 use crate::parser::key::key; +use crate::parser::prelude::*; +use crate::parser::state::ParseState; use crate::parser::trivia::line_trailing; -use crate::parser::ParseState; // std-table-open = %x5B ws ; [ Left square bracket pub(crate) const STD_TABLE_OPEN: u8 = b'['; @@ -21,43 +25,63 @@ const ARRAY_TABLE_CLOSE: &[u8] = b"]]"; // ;; Standard Table // std-table = std-table-open key *( table-key-sep key) std-table-close -toml_parser!(std_table, parser, { - ( - between(byte(STD_TABLE_OPEN), byte(STD_TABLE_CLOSE), key()), - line_trailing().and_then(std::str::from_utf8), - ) - .and_then(|(h, t)| parser.borrow_mut().on_std_header(h, t)) -}); +pub(crate) fn std_table<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| { + ( + delimited( + STD_TABLE_OPEN, + cut(key), + cut(STD_TABLE_CLOSE) + .context(Context::Expected(ParserValue::CharLiteral('.'))) + .context(Context::Expected(ParserValue::StringLiteral("]"))), + ), + cut(line_trailing.map_res(std::str::from_utf8)) + .context(Context::Expected(ParserValue::CharLiteral('\n'))) + .context(Context::Expected(ParserValue::CharLiteral('#'))), + ) + .map_res(|(h, t)| state.borrow_mut().deref_mut().on_std_header(h, t)) + .parse(i) + } +} // ;; Array Table // array-table = array-table-open key *( table-key-sep key) array-table-close -toml_parser!(array_table, parser, { - ( - between(range(ARRAY_TABLE_OPEN), range(ARRAY_TABLE_CLOSE), key()), - line_trailing().and_then(std::str::from_utf8), - ) - .and_then(|(h, t)| parser.borrow_mut().on_array_header(h, t)) -}); +pub(crate) fn array_table<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| { + ( + delimited( + ARRAY_TABLE_OPEN, + cut(key), + cut(ARRAY_TABLE_CLOSE) + .context(Context::Expected(ParserValue::CharLiteral('.'))) + .context(Context::Expected(ParserValue::StringLiteral("]]"))), + ), + cut(line_trailing.map_res(std::str::from_utf8)) + .context(Context::Expected(ParserValue::CharLiteral('\n'))) + .context(Context::Expected(ParserValue::CharLiteral('#'))), + ) + .map_res(|(h, t)| state.borrow_mut().deref_mut().on_array_header(h, t)) + .parse(i) + } +} // ;; Table // table = std-table / array-table -parser! { - pub(crate) fn table['a, 'b, I](parser: &'b RefCell)(I) -> () - where - [I: RangeStream< - Range = &'a [u8], - Token = u8>, - I::Error: ParseError::Position>, - ::Position>>::StreamError: - From + - From + - From + - From - ] { - array_table(parser) - .or(std_table(parser)) - .message("While parsing a Table Header") +pub(crate) fn table<'s, 'i>( + state: &'s RefCell, +) -> impl FnMut(Input<'i>) -> IResult, (), ParserError<'i>> + 's { + move |i| { + dispatch!(peek::<_, &[u8],_,_>(take(2usize)); + b"[[" => array_table(state), + _ => std_table(state), + ) + .context(Context::Expression("table header")) + .parse(i) } } diff --git a/crates/toml_edit/src/parser/trivia.rs b/crates/toml_edit/src/parser/trivia.rs index 93b001db..aacb8b7a 100644 --- a/crates/toml_edit/src/parser/trivia.rs +++ b/crates/toml_edit/src/parser/trivia.rs @@ -1,7 +1,17 @@ -use combine::parser::byte::{byte, crlf, newline as lf}; -use combine::parser::range::{recognize, take_while, take_while1}; -use combine::stream::RangeStream; -use combine::*; +use std::ops::RangeInclusive; + +use nom8::branch::alt; +use nom8::bytes::one_of; +use nom8::bytes::take_while; +use nom8::bytes::take_while1; +use nom8::combinator::eof; +use nom8::combinator::opt; +use nom8::multi::many0_count; +use nom8::multi::many1_count; +use nom8::prelude::*; +use nom8::sequence::terminated; + +use crate::parser::prelude::*; pub(crate) unsafe fn from_utf8_unchecked<'b>( bytes: &'b [u8], @@ -17,120 +27,94 @@ pub(crate) unsafe fn from_utf8_unchecked<'b>( // wschar = ( %x20 / ; Space // %x09 ) ; Horizontal tab -#[inline] -pub(crate) fn is_wschar(c: u8) -> bool { - matches!(c, b' ' | b'\t') -} +pub(crate) const WSCHAR: (u8, u8) = (b' ', b'\t'); // ws = *wschar -parse!(ws() -> &'a str, { - take_while(is_wschar).map(|b| { - unsafe { from_utf8_unchecked(b, "`is_wschar` filters out on-ASCII") } - }) -}); +pub(crate) fn ws(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + take_while(WSCHAR) + .map(|b| unsafe { from_utf8_unchecked(b, "`is_wschar` filters out on-ASCII") }) + .parse(input) +} // non-ascii = %x80-D7FF / %xE000-10FFFF -#[inline] -pub(crate) fn is_non_ascii(c: u8) -> bool { - // - ASCII is 0xxxxxxx - // - First byte for UTF-8 is 11xxxxxx - // - Subsequent UTF-8 bytes are 10xxxxxx - matches!(c, 0x80..=0xff) -} +// - ASCII is 0xxxxxxx +// - First byte for UTF-8 is 11xxxxxx +// - Subsequent UTF-8 bytes are 10xxxxxx +pub(crate) const NON_ASCII: RangeInclusive = 0x80..=0xff; // non-eol = %x09 / %x20-7E / non-ascii -#[inline] -fn is_non_eol(c: u8) -> bool { - matches!(c, 0x09 | 0x20..=0x7E) | is_non_ascii(c) -} +pub(crate) const NON_EOL: (u8, RangeInclusive, RangeInclusive) = + (0x09, 0x20..=0x7E, NON_ASCII); // comment-start-symbol = %x23 ; # pub(crate) const COMMENT_START_SYMBOL: u8 = b'#'; // comment = comment-start-symbol *non-eol -parse!(comment() -> &'a [u8], { - recognize(( - byte(COMMENT_START_SYMBOL), - take_while(is_non_eol), - )) -}); +pub(crate) fn comment(input: Input<'_>) -> IResult, &[u8], ParserError<'_>> { + (COMMENT_START_SYMBOL, take_while(NON_EOL)) + .recognize() + .parse(input) +} // newline = ( %x0A / ; LF // %x0D.0A ) ; CRLF +pub(crate) fn newline(input: Input<'_>) -> IResult, u8, ParserError<'_>> { + alt(( + one_of(LF).value(b'\n'), + (one_of(CR), one_of(LF)).value(b'\n'), + )) + .parse(input) +} pub(crate) const LF: u8 = b'\n'; pub(crate) const CR: u8 = b'\r'; -parse!(newline() -> char, { - choice((lf(), crlf())) - .map(|_| '\n') - .expected("newline") -}); // ws-newline = *( wschar / newline ) -parse!(ws_newline() -> &'a str, { - recognize( - skip_many(choice(( - newline().map(|_| &b"\n"[..]), - take_while1(is_wschar), - ))), - ).map(|b| { - unsafe { from_utf8_unchecked(b, "`is_wschar` and `newline` filters out on-ASCII") } - }) -}); +pub(crate) fn ws_newline(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + many0_count(alt((newline.value(&b"\n"[..]), take_while1(WSCHAR)))) + .recognize() + .map(|b| unsafe { + from_utf8_unchecked(b, "`is_wschar` and `newline` filters out on-ASCII") + }) + .parse(input) +} // ws-newlines = newline *( wschar / newline ) -parse!(ws_newlines() -> &'a str, { - recognize(( - newline(), - ws_newline(), - )).map(|b| { - unsafe { from_utf8_unchecked(b, "`is_wschar` and `newline` filters out on-ASCII") } - }) -}); +pub(crate) fn ws_newlines(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + (newline, ws_newline) + .recognize() + .map(|b| unsafe { + from_utf8_unchecked(b, "`is_wschar` and `newline` filters out on-ASCII") + }) + .parse(input) +} // note: this rule is not present in the original grammar // ws-comment-newline = *( ws-newline-nonempty / comment ) -parse!(ws_comment_newline() -> &'a [u8], { - recognize( - skip_many( - choice(( - skip_many1( - choice(( - take_while1(is_wschar), - newline().map(|_| &b"\n"[..]), - )) - ), - comment().map(|_| ()), - )) - ) - ) -}); +pub(crate) fn ws_comment_newline(input: Input<'_>) -> IResult, &[u8], ParserError<'_>> { + many0_count(alt(( + many1_count(alt((take_while1(WSCHAR), newline.value(&b"\n"[..])))).value(()), + comment.value(()), + ))) + .recognize() + .parse(input) +} // note: this rule is not present in the original grammar // line-ending = newline / eof -parse!(line_ending() -> &'a str, { - choice(( - newline().map(|_| "\n"), - eof().map(|_| "") - )) -}); +pub(crate) fn line_ending(input: Input<'_>) -> IResult, &str, ParserError<'_>> { + alt((newline.value("\n"), eof.value(""))).parse(input) +} // note: this rule is not present in the original grammar // line-trailing = ws [comment] skip-line-ending -parse!(line_trailing() -> &'a [u8], { - recognize(( - ws(), - optional(comment()), - )).skip(line_ending()) -}); +pub(crate) fn line_trailing(input: Input<'_>) -> IResult, &[u8], ParserError<'_>> { + terminated((ws, opt(comment)).recognize(), line_ending).parse(input) +} #[cfg(test)] mod test { use super::*; - use crate::parser::*; - use combine::stream::position::Stream; - use snapbox::assert_eq; - #[test] fn trivia() { let inputs = [ @@ -154,11 +138,10 @@ mod test { "#, ]; for input in inputs { - let parsed = trivia::ws_comment_newline().easy_parse(Stream::new(input.as_bytes())); - assert!(parsed.is_ok()); - let (t, rest) = parsed.unwrap(); - assert!(rest.input.is_empty()); - assert_eq(t, input.as_bytes()); + let parsed = ws_comment_newline.parse(input.as_bytes()).finish(); + assert!(parsed.is_ok(), "{:?}", parsed); + let parsed = parsed.unwrap(); + assert_eq!(parsed, input.as_bytes()); } } } diff --git a/crates/toml_edit/src/parser/value.rs b/crates/toml_edit/src/parser/value.rs index d55713dd..662b8311 100644 --- a/crates/toml_edit/src/parser/value.rs +++ b/crates/toml_edit/src/parser/value.rs @@ -1,11 +1,13 @@ -use combine::parser::range::recognize_with_value; -use combine::stream::RangeStream; -use combine::*; +use nom8::branch::alt; +use nom8::bytes::any; +use nom8::combinator::fail; +use nom8::combinator::peek; use crate::parser::array::array; use crate::parser::datetime::date_time; use crate::parser::inline_table::inline_table; use crate::parser::numbers::{float, integer}; +use crate::parser::prelude::*; use crate::parser::strings::string; use crate::parser::trivia::from_utf8_unchecked; use crate::repr::{Formatted, Repr}; @@ -13,50 +15,75 @@ use crate::value as v; use crate::Value; // val = string / boolean / array / inline-table / date-time / float / integer -parse!(value() -> v::Value, { - recognize_with_value(look_ahead(any()).then(|e| { - dispatch!(e; +pub(crate) fn value(input: Input<'_>) -> IResult, v::Value, ParserError<'_>> { + dispatch!{peek(any); crate::parser::strings::QUOTATION_MARK | - crate::parser::strings::APOSTROPHE => string().map(|s| { + crate::parser::strings::APOSTROPHE => string.map(|s| { v::Value::String(Formatted::new( - s, + s.into_owned() )) }), - crate::parser::array::ARRAY_OPEN => array().map(v::Value::Array), - crate::parser::inline_table::INLINE_TABLE_OPEN => inline_table().map(v::Value::InlineTable), + crate::parser::array::ARRAY_OPEN => array.map(v::Value::Array), + crate::parser::inline_table::INLINE_TABLE_OPEN => inline_table.map(v::Value::InlineTable), // Date/number starts - b'+' | b'-' | b'0'..=b'9' | - // Report as if they were numbers because its most likely a typo - b'.' | b'_' => { + b'+' | b'-' | b'0'..=b'9' => { // Uncommon enough not to be worth optimizing at this time - choice(( - date_time() + alt(( + date_time .map(v::Value::from), - float() + float .map(v::Value::from), - integer() + integer .map(v::Value::from), )) }, + // Report as if they were numbers because its most likely a typo + b'_' => { + integer + .map(v::Value::from) + .context(Context::Expected(ParserValue::Description("leading digit"))) + }, + // Report as if they were numbers because its most likely a typo + b'.' => { + float + .map(v::Value::from) + .context(Context::Expected(ParserValue::Description("leading digit"))) + }, b't' => { - crate::parser::numbers::true_().map(v::Value::from).expected("quoted string") + crate::parser::numbers::true_.map(v::Value::from) + .context(Context::Expression("string")) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + .context(Context::Expected(ParserValue::CharLiteral('\''))) }, b'f' => { - crate::parser::numbers::false_().map(v::Value::from).expected("quoted string") + crate::parser::numbers::false_.map(v::Value::from) + .context(Context::Expression("string")) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + .context(Context::Expected(ParserValue::CharLiteral('\''))) }, b'i' => { - crate::parser::numbers::inf().map(v::Value::from).expected("quoted string") + crate::parser::numbers::inf.map(v::Value::from) + .context(Context::Expression("string")) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + .context(Context::Expected(ParserValue::CharLiteral('\''))) }, b'n' => { - crate::parser::numbers::nan().map(v::Value::from).expected("quoted string") + crate::parser::numbers::nan.map(v::Value::from) + .context(Context::Expression("string")) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + .context(Context::Expected(ParserValue::CharLiteral('\''))) }, _ => { - // Pick something random to fail, we'll override `expected` anyways - crate::parser::numbers::nan().map(v::Value::from).expected("quoted string") + fail + .context(Context::Expression("string")) + .context(Context::Expected(ParserValue::CharLiteral('"'))) + .context(Context::Expected(ParserValue::CharLiteral('\''))) }, - ) - })).and_then(|(raw, value)| apply_raw(value, raw)) -}); + } + .with_recognized() + .map_res(|(value, raw)| apply_raw(value, raw)) + .parse(input) +} fn apply_raw(mut val: Value, raw: &[u8]) -> Result { match val { @@ -88,6 +115,8 @@ fn apply_raw(mut val: Value, raw: &[u8]) -> Result { #[cfg(test)] mod test { + use super::*; + #[test] fn values() { let inputs = [ @@ -108,7 +137,8 @@ trimmed in raw strings. r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, ]; for input in inputs { - parsed_value_eq!(input); + let parsed = value.parse(input.as_bytes()).finish(); + assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned())); } } } diff --git a/crates/toml_edit/src/value.rs b/crates/toml_edit/src/value.rs index 48029914..12df12d4 100644 --- a/crates/toml_edit/src/value.rs +++ b/crates/toml_edit/src/value.rs @@ -1,7 +1,6 @@ use std::iter::FromIterator; use std::str::FromStr; -use combine::stream::position::Stream; use toml_datetime::*; use crate::key::Key; @@ -212,19 +211,12 @@ impl FromStr for Value { /// Parses a value from a &str fn from_str(s: &str) -> Result { - use combine::stream::position::{IndexPositioner, Positioner}; - use combine::EasyParser; + use nom8::prelude::*; let b = s.as_bytes(); - let parsed = parser::value_parser().easy_parse(Stream::new(b)); + let parsed = parser::value::value.parse(b).finish(); match parsed { - Ok((_, ref rest)) if !rest.input.is_empty() => Err(Self::Err::from_unparsed( - (&rest.positioner - as &dyn Positioner) - .position(), - b, - )), - Ok((mut value, _)) => { + Ok(mut value) => { // Only take the repr and not decor, as its probably not intended value.decor_mut().clear(); Ok(value) diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr index 1372096c..36fd8cc4 100644 --- a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr @@ -1,6 +1,5 @@ -TOML parse error at line 1, column 18 +TOML parse error at line 1, column 17 | 1 | no-leads = 1987-7-05T17:45:00Z - | ^ -expected 1 more elements -While parsing a Date-Time + | ^ +Invalid date-time diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr index 9ec56c7b..c28861d4 100644 --- a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr @@ -2,6 +2,4 @@ TOML parse error at line 1, column 27 | 1 | no-secs = 1987-07-05T17:45Z | ^ -Unexpected `Z` -Expected `:` -While parsing a Date-Time +Invalid date-time diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr index 62a77d25..c8abb9ba 100644 --- a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 18 | 1 | no-t = 1987-07-0517:45:00Z | ^ -Unexpected `1` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr index 13c18294..6604f0df 100644 --- a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr @@ -1,6 +1,5 @@ -TOML parse error at line 1, column 23 +TOML parse error at line 1, column 22 | 1 | with-milli = 1987-07-5T17:45:00.12Z - | ^ -expected 1 more elements -While parsing a Date-Time + | ^ +Invalid date-time diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr index 16e126ed..a20e3719 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr @@ -3,4 +3,3 @@ TOML parse error at line 5, column 1 5 | apple.color = "red" | ^ Duplicate key `color` - diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr index 210e442e..43f84e4b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr @@ -3,4 +3,3 @@ TOML parse error at line 6, column 1 6 | size = "small" | ^ Duplicate key `size` - diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr index a1c49025..5f2396a0 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr @@ -2,6 +2,5 @@ TOML parse error at line 4, column 1 | 4 | [fruit.type] | ^ +Invalid table header Duplicate key `type` in table `fruit` - -While parsing a Table Header diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr index be31ca3e..0c4d4cdf 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr @@ -3,4 +3,3 @@ TOML parse error at line 7, column 1 7 | categories = ["algorithms"] | ^ Duplicate key `categories` in table `project` - diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr index 4f51ae65..6f0fb425 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr @@ -3,4 +3,3 @@ TOML parse error at line 3, column 1 3 | ssl-version.min = 'tlsv1.2' | ^ Dotted key `ssl-version` attempted to extend non-table type (string) - diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr index 586f016a..f247f969 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr @@ -3,4 +3,3 @@ TOML parse error at line 2, column 1 2 | dupe = true | ^ Duplicate key `dupe` in document root - diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr index 6ff97fa3..e951645e 100644 --- a/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr @@ -2,6 +2,5 @@ TOML parse error at line 2, column 1 | 2 | [a] | ^ +Invalid table header Duplicate key `a` in document root - -While parsing a Table Header diff --git a/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr b/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr index 95002644..69c6769b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr @@ -1,6 +1,6 @@ -TOML parse error at line 1, column 10 +TOML parse error at line 1, column 9 | 1 | [naughty..naughty] - | ^ -Unexpected `.` -While parsing a Table Header + | ^ +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/empty-table.stderr b/crates/toml_edit/tests/fixtures/invalid/empty-table.stderr index 46e13837..1ff9d6d7 100644 --- a/crates/toml_edit/tests/fixtures/invalid/empty-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/empty-table.stderr @@ -2,6 +2,4 @@ TOML parse error at line 1, column 2 | 1 | [] | ^ -Unexpected `]` -Expected key -While parsing a Table Header +Invalid key diff --git a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr index ae05cd22..42f971ff 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 18 | 1 | leading-zero = -03.14 | ^ -Unexpected `3` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr index 0539b608..0075d325 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 18 | 1 | leading-zero = +03.14 | ^ -Unexpected `3` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr index ba27e778..59b4a370 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 17 | 1 | leading-zero = 03.14 | ^ -Unexpected `3` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr index ac781327..5d15c62d 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr @@ -2,14 +2,5 @@ TOML parse error at line 1, column 10 | 1 | answer = .12345 | ^ -Unexpected `.` -Expected `-`, `+`, `inf`, `nan`, `0x`, `0o` or `0b` -expected 4 more elements -expected 2 more elements -While parsing a Time -While parsing a hexadecimal Integer -While parsing a octal Integer -While parsing a binary Integer -While parsing an Integer -While parsing a Date-Time -While parsing a Float +Invalid floating-point number +Expected leading digit diff --git a/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr b/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr index ddf64350..2cb07139 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr @@ -2,7 +2,5 @@ TOML parse error at line 1, column 12 | 1 | answer = 1. | ^ -Unexpected ` -` +Invalid floating-point number Expected digit -While parsing a Float diff --git a/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr index 852f5cf0..1e170938 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | bad = 1._2 | ^ -Unexpected `_` +Invalid floating-point number Expected digit -While parsing a Float diff --git a/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr index 5522b626..36a87cb1 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr @@ -2,7 +2,5 @@ TOML parse error at line 1, column 11 | 1 | bad = 1.2_ | ^ -Unexpected ` -` -Expected digit -While parsing a Float +Invalid floating-point number +Expected digit, digit diff --git a/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr index 7e53a5dc..bbc7aede 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | bad = 1_.2 | ^ -Unexpected `.` +Invalid integer Expected digit -While parsing an Integer diff --git a/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr index 1a903fa2..41607f5f 100644 --- a/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr @@ -2,14 +2,5 @@ TOML parse error at line 1, column 7 | 1 | bad = _1.2 | ^ -Unexpected `_` -Expected `-`, `+`, `inf`, `nan`, `0x`, `0o` or `0b` -expected 4 more elements -expected 2 more elements -While parsing a Time -While parsing a hexadecimal Integer -While parsing a octal Integer -While parsing a binary Integer -While parsing an Integer -While parsing a Date-Time -While parsing a Float +Invalid integer +Expected leading digit diff --git a/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr b/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr index 54a52fbd..5967ff1d 100644 --- a/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr @@ -2,6 +2,5 @@ TOML parse error at line 7, column 11 | 7 | native = { | ^ -Unexpected ` -` -Expected key +Invalid inline table +Expected `}` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr index 5ced7591..ec273a8b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 11 | 1 | bad = 0b102 | ^ -Unexpected `2` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr index 05669dd1..35afb866 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 11 | 1 | bad = 0x12g | ^ -Unexpected `g` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr index 97ff183d..f58d74b6 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 11 | 1 | bad = 0o758 | ^ -Unexpected `8` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr index a1106553..c0f4b181 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 18 | 1 | leading-zero = -012 | ^ -Unexpected `1` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr index 475bc120..9e847996 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 18 | 1 | leading-zero = +012 | ^ -Unexpected `1` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr index 8f5c4d4c..419996a8 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 17 | 1 | leading-zero = 012 | ^ -Unexpected `1` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr index e731ba31..da11e07c 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr @@ -2,7 +2,5 @@ TOML parse error at line 1, column 11 | 1 | bad = 123_ | ^ -Unexpected ` -` +Invalid integer Expected digit -While parsing an Integer diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr index 24635fd8..756621a8 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr @@ -2,14 +2,5 @@ TOML parse error at line 1, column 7 | 1 | bad = _123 | ^ -Unexpected `_` -Expected `-`, `+`, `inf`, `nan`, `0x`, `0o` or `0b` -expected 4 more elements -expected 2 more elements -While parsing a Time -While parsing a hexadecimal Integer -While parsing a octal Integer -While parsing a binary Integer -While parsing an Integer -While parsing a Date-Time -While parsing a Float +Invalid integer +Expected leading digit diff --git a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr index 3f17deea..757e661c 100644 --- a/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | bad = 1__23 | ^ -Unexpected `_` +Invalid integer Expected digit -While parsing an Integer diff --git a/crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr b/crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr index b08b31ba..f59405fd 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 14 | 1 | [[agencies]] owner = "S Cjelli" | ^ -Unexpected `o` -Expected newline or end of input -While parsing a Table Header +Invalid table header +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr b/crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr index 3961a5ce..2fab5108 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | [error] this = "should not be here" | ^ -Unexpected `t` -Expected newline or end of input -While parsing a Table Header +Invalid table header +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/key-empty.stderr index 1d0241c7..a6ff759d 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-empty.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-empty.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 2 | 1 | = 1 | ^ -Unexpected `=` -Expected key +Invalid key diff --git a/crates/toml_edit/tests/fixtures/invalid/key-hash.stderr b/crates/toml_edit/tests/fixtures/invalid/key-hash.stderr index 2350d93c..4e086c25 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-hash.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-hash.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 2 | 1 | a# = 1 | ^ -Unexpected `#` -Expected `.` or `=` +Expected `.`, `=` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-newline.stderr b/crates/toml_edit/tests/fixtures/invalid/key-newline.stderr index e9361179..114521ed 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-newline.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-newline.stderr @@ -2,6 +2,4 @@ TOML parse error at line 1, column 2 | 1 | a | ^ -Unexpected ` -` -Expected `.` or `=` +Expected `.`, `=` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr b/crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr index de2d375e..cf811e1b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 7 | 1 | a = 1 b = 2 | ^ -Unexpected `b` -Expected newline or end of input +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr index 6e23a74a..4179bf07 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 6 | 1 | [abc = 1 | ^ -Unexpected `=` -Expected `.` or `]` -While parsing a Table Header +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr index 0851e4cb..c7d76e32 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr @@ -1,7 +1,5 @@ -TOML parse error at line 1, column 2 +TOML parse error at line 1, column 1 | 1 | [ - | ^ -Unexpected end of input -Expected key -While parsing a Table Header + | ^ +Invalid table header diff --git a/crates/toml_edit/tests/fixtures/invalid/key-space.stderr b/crates/toml_edit/tests/fixtures/invalid/key-space.stderr index 3fd9d23a..867ae6c6 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-space.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-space.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 3 | 1 | a b = 1 | ^ -Unexpected `b` -Expected `.` or `=` +Expected `.`, `=` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr index d9590d32..4081804a 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr @@ -2,6 +2,5 @@ TOML parse error at line 2, column 6 | 2 | [xyz = 5 | ^ -Unexpected `=` -Expected `.` or `]` -While parsing a Table Header +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr b/crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr index ad54644e..6cb510be 100644 --- a/crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr @@ -2,5 +2,5 @@ TOML parse error at line 1, column 6 | 1 | key= = 1 | ^ -Unexpected `=` -Expected quoted string +Invalid string +Expected `"`, `'` diff --git a/crates/toml_edit/tests/fixtures/invalid/llbrace.stderr b/crates/toml_edit/tests/fixtures/invalid/llbrace.stderr index e33fb629..3ca489df 100644 --- a/crates/toml_edit/tests/fixtures/invalid/llbrace.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/llbrace.stderr @@ -2,7 +2,4 @@ TOML parse error at line 1, column 3 | 1 | [ [table]] | ^ -Unexpected `[` -Unexpected ` ` -Expected key -While parsing a Table Header +Invalid key diff --git a/crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr b/crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr index 81ef3bb5..fa3f51b1 100644 --- a/crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 8 | 1 | [[table] ] | ^ -Unexpected `]` -Expected `.` or `]]` -While parsing a Table Header +Invalid table header +Expected `.`, `]]` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr index 7ec3bfa6..89851d08 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr @@ -1,7 +1,6 @@ -TOML parse error at line 1, column 13 +TOML parse error at line 1, column 14 | 1 | naughty = "\xAg" - | ^ -Unexpected `x` -While parsing escape sequence -While parsing a Basic String + | ^ +Invalid escape sequence +Expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr index 64bfa6cd..8fe1e0cc 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr @@ -1,7 +1,6 @@ -TOML parse error at line 1, column 42 +TOML parse error at line 1, column 43 | 1 | invalid-escape = "This string has a bad \a escape character." - | ^ -Unexpected `a` -While parsing escape sequence -While parsing a Basic String + | ^ +Invalid escape sequence +Expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr index 4e2c605f..613940d9 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 15 | 1 | notgood = "a\uDCFF" | ^ -Invalid hex escape code: dcff - -While parsing a Basic String +Invalid unicode 4-digit hex code +Value is out of range diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr index 2192bd18..ae3f7382 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr @@ -1,6 +1,5 @@ -TOML parse error at line 1, column 14 +TOML parse error at line 1, column 13 | 1 | str = "val\ue" - | ^ -expected 3 more elements -While parsing a Basic String + | ^ +Invalid unicode 4-digit hex code diff --git a/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr b/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr index 52887059..f039b3a3 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr @@ -1,7 +1,6 @@ -TOML parse error at line 1, column 12 +TOML parse error at line 1, column 13 | 1 | answer = "\x33" - | ^ -Unexpected `x` -While parsing escape sequence -While parsing a Basic String + | ^ +Invalid escape sequence +Expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr index fdeeb418..420fab61 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr @@ -2,7 +2,4 @@ TOML parse error at line 1, column 42 | 1 | no-ending-quote = "One time, at band camp | ^ -Unexpected ` -` -Expected `"` -While parsing a Basic String +Invalid basic string diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr index 75cc2b2b..62a1750b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr @@ -2,5 +2,5 @@ TOML parse error at line 1, column 7 | 1 | value=trust | ^ -Unexpected `t` -Expected quoted string +Invalid string +Expected `"`, `'` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr index 75f73091..5ffa8eb0 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr @@ -2,5 +2,5 @@ TOML parse error at line 1, column 10 | 1 | value = [ZZZ] | ^ -Unexpected `Z` -Expected newline or `#` +Invalid array +Expected `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr index add99d50..5c5117aa 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr @@ -2,5 +2,5 @@ TOML parse error at line 1, column 17 | 1 | value = { key = ZZZ } | ^ -Unexpected `Z` -Expected quoted string +Invalid string +Expected `"`, `'` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr index e16f11a2..9bc4cac9 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr @@ -2,5 +2,5 @@ TOML parse error at line 1, column 7 | 1 | value=ZZZ | ^ -Unexpected `Z` -Expected quoted string +Invalid string +Expected `"`, `'` diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr index 7499e608..96b1456f 100644 --- a/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 10 | 1 | value=123hello | ^ -Unexpected `h` -Expected `#` +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr index d771f5c3..ae0ae9b9 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr @@ -2,6 +2,5 @@ TOML parse error at line 13, column 1 | 13 | [[albums]] | ^ +Invalid table header Duplicate key `albums` in document root - -While parsing a Table Header diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr index a5dbf1de..0b5694c4 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | [[albums] | ^ -Unexpected `]` -Expected `.` or `]]` -While parsing a Table Header +Invalid table header +Expected `.`, `]]` diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr index 65253f80..b2732185 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr @@ -2,6 +2,4 @@ TOML parse error at line 1, column 3 | 1 | [[]] | ^ -Unexpected `]` -Expected key -While parsing a Table Header +Invalid key diff --git a/crates/toml_edit/tests/fixtures/invalid/table-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/table-empty.stderr index 46e13837..1ff9d6d7 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-empty.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-empty.stderr @@ -2,6 +2,4 @@ TOML parse error at line 1, column 2 | 1 | [] | ^ -Unexpected `]` -Expected key -While parsing a Table Header +Invalid key diff --git a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr index c4a3172a..a40cd447 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 4 | 1 | [a]b] | ^ -Unexpected `b` -Expected `#` -While parsing a Table Header +Invalid table header +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr index 68691354..8f78b998 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 3 | 1 | [a[b] | ^ -Unexpected `[` -Expected `.` or `]` -While parsing a Table Header +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr b/crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr index 01a9ee82..fc898d24 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 10 | 1 | [invalid key] | ^ -Unexpected `k` -Expected `.` or `]` -While parsing a Table Header +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr b/crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr index 3550cc83..28574437 100644 --- a/crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 5 | 1 | [key#group] | ^ -Unexpected `#` -Expected `.` or `]` -While parsing a Table Header +Invalid table header +Expected `.`, `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr index 62283a99..c262785a 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr @@ -2,5 +2,5 @@ TOML parse error at line 2, column 46 | 2 | "Is there life after an array separator?", No | ^ -Unexpected `N` +Invalid array Expected `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr index 66c84ee4..e6f20145 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 13 | 1 | answer = 42 the ultimate answer? | ^ -Unexpected `t` -Expected newline or end of input +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr index a757e60f..1ac8e02e 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr @@ -2,5 +2,4 @@ TOML parse error at line 1, column 41 | 1 | string = "Is there life after strings?" No. | ^ -Unexpected `N` -Expected newline or end of input +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr index 051b37ef..cde352f9 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr @@ -2,6 +2,5 @@ TOML parse error at line 1, column 9 | 1 | [error] this shouldn't be here | ^ -Unexpected `t` -Expected newline or end of input -While parsing a Table Header +Invalid table header +Expected newline, `#` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr b/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr index 277e603f..d9b2224b 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr @@ -2,5 +2,5 @@ TOML parse error at line 2, column 46 | 2 | "Is there life before an array separator?" No, | ^ -Unexpected `N` +Invalid array Expected `]` diff --git a/crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr b/crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr index eded832c..ecb8abc4 100644 --- a/crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr +++ b/crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr @@ -2,5 +2,5 @@ TOML parse error at line 3, column 3 | 3 | I don't belong, | ^ -Unexpected `I` +Invalid array Expected `]` diff --git a/crates/toml_edit/tests/test_parse.rs b/crates/toml_edit/tests/test_parse.rs index 231d3feb..52332b54 100644 --- a/crates/toml_edit/tests/test_parse.rs +++ b/crates/toml_edit/tests/test_parse.rs @@ -21,27 +21,6 @@ macro_rules! test_key { }}; } -macro_rules! parse_error { - ($input:expr, $ty:ty, $err_msg:expr) => {{ - let res = $input.parse::<$ty>(); - assert!(res.is_err()); - let err = res.unwrap_err(); - assert!( - err.to_string().find($err_msg).is_some(), - "Error was: `{:?}`", - err.to_string() - ); - }}; -} - -#[test] -fn test_parse_error() { - parse_error!("'hello'bla", Value, "Could not parse the line"); - parse_error!(r#"{a = 2"#, Value, "Expected `}`"); - - parse_error!("'\"", Key, "Expected `'`"); -} - #[test] fn test_key_from_str() { test_key!("a", "a");