From bea8ec26414e799327519698c05df28c8a6afb5f Mon Sep 17 00:00:00 2001 From: yuankunzhang Date: Sat, 10 May 2025 13:13:18 +0800 Subject: [PATCH] upgrade winnow to 0.7.10 --- Cargo.lock | 14 +---- Cargo.toml | 3 +- src/items/combined.rs | 7 ++- src/items/date.rs | 29 +++++------ src/items/mod.rs | 117 +++++++++++++++++++++++++++--------------- src/items/ordinal.rs | 8 +-- src/items/relative.rs | 12 ++--- src/items/time.rs | 65 +++++++++++------------ src/items/weekday.rs | 6 +-- 9 files changed, 141 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8f1b30..fe847c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,15 +118,6 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -147,7 +138,6 @@ name = "parse_datetime" version = "0.9.0" dependencies = [ "chrono", - "nom", "num-traits", "regex", "winnow", @@ -353,9 +343,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b4af84b..404696c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,5 @@ readme = "README.md" [dependencies] regex = "1.10.4" chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] } -nom = "8.0.0" -winnow = "0.5.34" +winnow = "0.7.10" num-traits = "0.2.19" diff --git a/src/items/combined.rs b/src/items/combined.rs index 99e4749..c1df933 100644 --- a/src/items/combined.rs +++ b/src/items/combined.rs @@ -13,7 +13,10 @@ //! > seconds are allowed, with either comma or period preceding the fraction. //! > ISO 8601 fractional minutes and hours are not supported. Typically, hosts //! > support nanosecond timestamp resolution; excess precision is silently discarded. -use winnow::{combinator::alt, seq, trace::trace, PResult, Parser}; +use winnow::{ + combinator::{alt, trace}, + seq, ModalResult, Parser, +}; use crate::items::space; @@ -29,7 +32,7 @@ pub struct DateTime { pub(crate) time: Time, } -pub fn parse(input: &mut &str) -> PResult { +pub fn parse(input: &mut &str) -> ModalResult { seq!(DateTime { date: trace("date iso", alt((date::iso1, date::iso2))), // Note: the `T` is lowercased by the main parse function diff --git a/src/items/date.rs b/src/items/date.rs index 44796cb..86cc992 100644 --- a/src/items/date.rs +++ b/src/items/date.rs @@ -27,16 +27,15 @@ //! > ‘September’. use winnow::{ - ascii::{alpha1, dec_uint}, - combinator::{alt, opt, preceded}, + ascii::alpha1, + combinator::{alt, opt, preceded, trace}, seq, stream::AsChar, token::{take, take_while}, - trace::trace, - PResult, Parser, + ModalResult, Parser, }; -use super::s; +use super::{dec_uint, s}; use crate::ParseDateTimeError; #[derive(PartialEq, Eq, Clone, Debug, Default)] @@ -46,14 +45,14 @@ pub struct Date { pub year: Option, } -pub fn parse(input: &mut &str) -> PResult { +pub fn parse(input: &mut &str) -> ModalResult { alt((iso1, iso2, us, literal1, literal2)).parse_next(input) } /// Parse `YYYY-MM-DD` or `YY-MM-DD` /// /// This is also used by [`combined`](super::combined). -pub fn iso1(input: &mut &str) -> PResult { +pub fn iso1(input: &mut &str) -> ModalResult { seq!(Date { year: year.map(Some), _: s('-'), @@ -67,7 +66,7 @@ pub fn iso1(input: &mut &str) -> PResult { /// Parse `YYYYMMDD` /// /// This is also used by [`combined`](super::combined). -pub fn iso2(input: &mut &str) -> PResult { +pub fn iso2(input: &mut &str) -> ModalResult { s(( take(4usize).try_map(|s: &str| s.parse::()), take(2usize).try_map(|s: &str| s.parse::()), @@ -82,7 +81,7 @@ pub fn iso2(input: &mut &str) -> PResult { } /// Parse `MM/DD/YYYY`, `MM/DD/YY` or `MM/DD` -fn us(input: &mut &str) -> PResult { +fn us(input: &mut &str) -> ModalResult { seq!(Date { month: month, _: s('/'), @@ -93,7 +92,7 @@ fn us(input: &mut &str) -> PResult { } /// Parse `14 November 2022`, `14 Nov 2022`, "14nov2022", "14-nov-2022", "14-nov2022", "14nov-2022" -fn literal1(input: &mut &str) -> PResult { +fn literal1(input: &mut &str) -> ModalResult { seq!(Date { day: day, _: opt(s('-')), @@ -104,7 +103,7 @@ fn literal1(input: &mut &str) -> PResult { } /// Parse `November 14, 2022` and `Nov 14, 2022` -fn literal2(input: &mut &str) -> PResult { +fn literal2(input: &mut &str) -> ModalResult { seq!(Date { month: literal_month, day: day, @@ -115,7 +114,7 @@ fn literal2(input: &mut &str) -> PResult { .parse_next(input) } -pub fn year(input: &mut &str) -> PResult { +pub fn year(input: &mut &str) -> ModalResult { // 2147485547 is the maximum value accepted // by GNU, but chrono only behaves like GNU // for years in the range: [0, 9999], so we @@ -140,7 +139,7 @@ pub fn year(input: &mut &str) -> PResult { .parse_next(input) } -fn month(input: &mut &str) -> PResult { +fn month(input: &mut &str) -> ModalResult { s(dec_uint) .try_map(|x| { (1..=12) @@ -151,7 +150,7 @@ fn month(input: &mut &str) -> PResult { .parse_next(input) } -fn day(input: &mut &str) -> PResult { +fn day(input: &mut &str) -> ModalResult { s(dec_uint) .try_map(|x| { (1..=31) @@ -163,7 +162,7 @@ fn day(input: &mut &str) -> PResult { } /// Parse the name of a month (case-insensitive) -fn literal_month(input: &mut &str) -> PResult { +fn literal_month(input: &mut &str) -> ModalResult { s(alpha1) .verify_map(|s: &str| { Some(match s { diff --git a/src/items/mod.rs b/src/items/mod.rs index ca78c2a..51fe78a 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -34,18 +34,18 @@ mod relative; mod time; mod weekday; mod epoch { - use winnow::{ascii::dec_int, combinator::preceded, PResult, Parser}; + use winnow::{combinator::preceded, ModalResult, Parser}; - use super::s; - pub fn parse(input: &mut &str) -> PResult { + use super::{dec_int, s}; + pub fn parse(input: &mut &str) -> ModalResult { s(preceded("@", dec_int)).parse_next(input) } } mod timezone { use super::time; - use winnow::PResult; + use winnow::ModalResult; - pub(crate) fn parse(input: &mut &str) -> PResult { + pub(crate) fn parse(input: &mut &str) -> ModalResult { time::timezone(input) } } @@ -53,15 +53,14 @@ mod timezone { use chrono::NaiveDate; use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike}; -use winnow::error::{AddContext, ParserError, StrContext}; -use winnow::error::{ContextError, ErrMode}; -use winnow::trace::trace; +use winnow::error::{StrContext, StrContextValue}; use winnow::{ - ascii::multispace0, - combinator::{alt, delimited, not, peek, preceded, repeat, separated}, + ascii::{digit1, multispace0}, + combinator::{alt, delimited, not, opt, peek, preceded, repeat, separated, trace}, + error::{ContextError, ErrMode, ParserError}, stream::AsChar, - token::{none_of, take_while}, - PResult, Parser, + token::{none_of, one_of, take_while}, + ModalResult, Parser, }; use crate::ParseDateTimeError; @@ -93,7 +92,7 @@ where /// Parse the space in-between tokens /// /// You probably want to use the [`s`] combinator instead. -fn space<'a, E>(input: &mut &'a str) -> PResult<(), E> +fn space<'a, E>(input: &mut &'a str) -> winnow::Result<(), E> where E: ParserError<&'a str>, { @@ -110,7 +109,7 @@ where /// The last comment should be ignored. /// /// The plus is undocumented, but it seems to be ignored. -fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> PResult<(), E> +fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> winnow::Result<(), E> where E: ParserError<&'a str>, { @@ -127,7 +126,7 @@ where /// /// A comment is given between parentheses, which must be balanced. Any other /// tokens can be within the comment. -fn comment<'a, E>(input: &mut &'a str) -> PResult<(), E> +fn comment<'a, E>(input: &mut &'a str) -> winnow::Result<(), E> where E: ParserError<&'a str>, { @@ -139,8 +138,45 @@ where .parse_next(input) } +/// Parse a signed decimal integer. +/// +/// Rationale for not using `winnow::ascii::dec_int`: When upgrading winnow from +/// 0.5 to 0.7, we discovered that `winnow::ascii::dec_int` now accepts only the +/// following two forms: +/// +/// - 0 +/// - [+-][1-9][0-9]* +/// +/// Inputs like [+-]0[0-9]* (e.g., `+012`) are therefore rejected. We provide a +/// custom implementation to support such zero-prefixed integers. +fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result +where + E: ParserError<&'a str>, +{ + (opt(one_of(['+', '-'])), digit1) + .void() + .take() + .verify_map(|s: &str| s.parse().ok()) + .parse_next(input) +} + +/// Parse an unsigned decimal integer. +/// +/// See the rationale for `dec_int` for why we don't use +/// `winnow::ascii::dec_uint`. +fn dec_uint<'a, E>(input: &mut &'a str) -> winnow::Result +where + E: ParserError<&'a str>, +{ + digit1 + .void() + .take() + .verify_map(|s: &str| s.parse().ok()) + .parse_next(input) +} + // Parse an item -pub fn parse_one(input: &mut &str) -> PResult { +pub fn parse_one(input: &mut &str) -> ModalResult { trace( "parse_one", alt(( @@ -157,7 +193,7 @@ pub fn parse_one(input: &mut &str) -> PResult { .parse_next(input) } -pub fn parse(input: &mut &str) -> PResult> { +pub fn parse(input: &mut &str) -> ModalResult> { let mut items = Vec::new(); let mut date_seen = false; let mut time_seen = false; @@ -170,12 +206,13 @@ pub fn parse(input: &mut &str) -> PResult> { match item { Item::DateTime(ref dt) => { if date_seen || time_seen { - return Err(ErrMode::Backtrack(ContextError::new().add_context( - &input, - StrContext::Expected(winnow::error::StrContextValue::Description( + let mut ctx_err = ContextError::new(); + ctx_err.push(StrContext::Expected( + winnow::error::StrContextValue::Description( "date or time cannot appear more than once", - )), - ))); + ), + )); + return Err(ErrMode::Backtrack(ctx_err)); } date_seen = true; @@ -186,12 +223,11 @@ pub fn parse(input: &mut &str) -> PResult> { } Item::Date(ref d) => { if date_seen { - return Err(ErrMode::Backtrack(ContextError::new().add_context( - &input, - StrContext::Expected(winnow::error::StrContextValue::Description( - "date cannot appear more than once", - )), + let mut ctx_err = ContextError::new(); + ctx_err.push(StrContext::Expected(StrContextValue::Description( + "date cannot appear more than once", ))); + return Err(ErrMode::Backtrack(ctx_err)); } date_seen = true; @@ -201,34 +237,31 @@ pub fn parse(input: &mut &str) -> PResult> { } Item::Time(_) => { if time_seen { - return Err(ErrMode::Backtrack(ContextError::new().add_context( - &input, - StrContext::Expected(winnow::error::StrContextValue::Description( - "time cannot appear more than once", - )), + let mut ctx_err = ContextError::new(); + ctx_err.push(StrContext::Expected(StrContextValue::Description( + "time cannot appear more than once", ))); + return Err(ErrMode::Backtrack(ctx_err)); } time_seen = true; } Item::Year(_) => { if year_seen { - return Err(ErrMode::Backtrack(ContextError::new().add_context( - &input, - StrContext::Expected(winnow::error::StrContextValue::Description( - "year cannot appear more than once", - )), + let mut ctx_err = ContextError::new(); + ctx_err.push(StrContext::Expected(StrContextValue::Description( + "year cannot appear more than once", ))); + return Err(ErrMode::Backtrack(ctx_err)); } year_seen = true; } Item::TimeZone(_) => { if tz_seen { - return Err(ErrMode::Backtrack(ContextError::new().add_context( - &input, - StrContext::Expected(winnow::error::StrContextValue::Description( - "timezone cannot appear more than once", - )), + let mut ctx_err = ContextError::new(); + ctx_err.push(StrContext::Expected(StrContextValue::Description( + "timezone cannot appear more than once", ))); + return Err(ErrMode::Backtrack(ctx_err)); } tz_seen = true; } diff --git a/src/items/ordinal.rs b/src/items/ordinal.rs index 8bf65f4..2762999 100644 --- a/src/items/ordinal.rs +++ b/src/items/ordinal.rs @@ -5,14 +5,14 @@ use super::s; use winnow::{ ascii::{alpha1, dec_uint}, combinator::{alt, opt}, - PResult, Parser, + ModalResult, Parser, }; -pub fn ordinal(input: &mut &str) -> PResult { +pub fn ordinal(input: &mut &str) -> ModalResult { alt((text_ordinal, number_ordinal)).parse_next(input) } -fn number_ordinal(input: &mut &str) -> PResult { +fn number_ordinal(input: &mut &str) -> ModalResult { let sign = opt(alt(('+'.value(1), '-'.value(-1)))).map(|s| s.unwrap_or(1)); (s(sign), s(dec_uint)) .verify_map(|(s, u): (i32, u32)| { @@ -22,7 +22,7 @@ fn number_ordinal(input: &mut &str) -> PResult { .parse_next(input) } -fn text_ordinal(input: &mut &str) -> PResult { +fn text_ordinal(input: &mut &str) -> ModalResult { s(alpha1) .verify_map(|s: &str| { Some(match s { diff --git a/src/items/relative.rs b/src/items/relative.rs index b825c58..3a605a7 100644 --- a/src/items/relative.rs +++ b/src/items/relative.rs @@ -34,7 +34,7 @@ use winnow::{ ascii::{alpha1, float}, combinator::{alt, opt}, - PResult, Parser, + ModalResult, Parser, }; use super::{ordinal::ordinal, s}; @@ -63,7 +63,7 @@ impl Relative { } } -pub fn parse(input: &mut &str) -> PResult { +pub fn parse(input: &mut &str) -> ModalResult { alt(( s("tomorrow").value(Relative::Days(1)), s("yesterday").value(Relative::Days(-1)), @@ -76,7 +76,7 @@ pub fn parse(input: &mut &str) -> PResult { .parse_next(input) } -fn seconds(input: &mut &str) -> PResult { +fn seconds(input: &mut &str) -> ModalResult { ( opt(alt((s(float), ordinal.map(|x| x as f64)))), s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")), @@ -86,17 +86,17 @@ fn seconds(input: &mut &str) -> PResult { .parse_next(input) } -fn other(input: &mut &str) -> PResult { +fn other(input: &mut &str) -> ModalResult { (opt(ordinal), integer_unit, ago) .map(|(n, unit, ago)| unit.mul(n.unwrap_or(1) * if ago { -1 } else { 1 })) .parse_next(input) } -fn ago(input: &mut &str) -> PResult { +fn ago(input: &mut &str) -> ModalResult { opt(s("ago")).map(|o| o.is_some()).parse_next(input) } -fn integer_unit(input: &mut &str) -> PResult { +fn integer_unit(input: &mut &str) -> ModalResult { s(alpha1) .verify_map(|s: &str| { Some(match s.strip_suffix('s').unwrap_or(s) { diff --git a/src/items/time.rs b/src/items/time.rs index 91bf4c7..6177f97 100644 --- a/src/items/time.rs +++ b/src/items/time.rs @@ -41,16 +41,16 @@ use std::fmt::Display; use chrono::FixedOffset; use winnow::{ - ascii::{dec_uint, digit1, float}, + ascii::{digit1, float}, combinator::{alt, opt, peek, preceded}, - error::{AddContext, ContextError, ErrMode, StrContext}, + error::{ContextError, ErrMode, StrContext, StrContextValue}, seq, stream::AsChar, token::take_while, - PResult, Parser, + ModalResult, Parser, }; -use super::{relative, s}; +use super::{dec_uint, relative, s}; #[derive(PartialEq, Debug, Clone, Default)] pub struct Offset { @@ -132,14 +132,14 @@ enum Suffix { Pm, } -pub fn parse(input: &mut &str) -> PResult