From bb4bbbdd38593632eb8a2cff7ae39d6c3f1a4627 Mon Sep 17 00:00:00 2001 From: Eric Rodrigues Pires Date: Thu, 6 Nov 2025 08:53:20 -0300 Subject: [PATCH] Fix bugs in parsing --- duper/src/parser/mod.rs | 79 +++++++++++++++++++++++++++++++-- duper/src/parser/temporal.rs | 85 +++++++++++++++++++++++++----------- duper_website/docs/index.md | 6 +-- 3 files changed, 137 insertions(+), 33 deletions(-) diff --git a/duper/src/parser/mod.rs b/duper/src/parser/mod.rs index 1ddd750..5c54ab1 100644 --- a/duper/src/parser/mod.rs +++ b/duper/src/parser/mod.rs @@ -757,16 +757,76 @@ mod duper_parser_tests { '2022-02-28' "#; let duper = DuperParser::parse_duper_value(input).unwrap(); - assert!(matches!(duper.inner, DuperInner::Temporal(_))); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::Unspecified(_)) + )); let input = r#" '2022-02-28T11:06:00.092121729+08:00[Asia/Shanghai][u-ca=chinese]' "#; let duper = DuperParser::parse_duper_value(input).unwrap(); - assert!(matches!(duper.inner, DuperInner::Temporal(_))); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::Unspecified(_)) + )); + + let input = r#" + ZonedDateTime('2022-02-28T11:06:00.092121729+08:00[Asia/Shanghai][u-ca=chinese]') + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::ZonedDateTime(_)) + )); + + let input = r#" + ' 2022-02-28T03:06:00.092121729Z ' + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::Unspecified(_)) + )); + + let input = r#" + PlainDate(' 2022-02-28T03:06:00.092121729Z ') + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::PlainDate(_)) + )); + + let input = r#" + PlainTime(' 2022-02-28T03:06:00.092121729Z ') + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::PlainTime(_)) + )); let input = r#" - '2022-02-28T03:06:00.092121729Z' + PlainMonthDay('--11-03') + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::PlainMonthDay(_)) + )); + + let input = r#" + UnknownIdent('--11-03') + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::Unspecified(_)) + )); + + let input = r#" + 'PT0.0021S' "#; let duper = DuperParser::parse_duper_value(input).unwrap(); assert!(matches!(duper.inner, DuperInner::Temporal(_))); @@ -837,7 +897,18 @@ mod duper_parser_tests { assert!(DuperParser::parse_duper_value(input).is_err()); // Temporal - // TO-DO: Temporal - Tests for parsing errors + let input = r#" + 'invalid' + "#; + assert!(DuperParser::parse_duper_value(input).is_err()); + let input = r#" + '2025-02-29' // Inexistent date + "#; + assert!(DuperParser::parse_duper_value(input).is_err()); + let input = r#" + ZonedDateTime('2022-02-28T11:06:00.092121729+08:00[u-ca=chinese]') // Missing timezone + "#; + assert!(DuperParser::parse_duper_value(input).is_err()); // Floats and decimal let input = r#" diff --git a/duper/src/parser/temporal.rs b/duper/src/parser/temporal.rs index 45d31df..015dbba 100644 --- a/duper/src/parser/temporal.rs +++ b/duper/src/parser/temporal.rs @@ -321,13 +321,39 @@ pub(crate) fn time<'a>() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (u32, u32), extra::Err>> + Clone { choice(( - text::digits(10).repeated().exactly(4).to_slice(), - one_of("+-") - .then(text::digits(10).repeated().exactly(6)) - .to_slice(), + text::digits(10).exactly(4).to_slice(), + one_of("+-").then(text::digits(10).exactly(6)).to_slice(), )) .from_str::() .unwrapped() - .then_ignore(just('-').or_not()) .then( - just('0') - .then(one_of('0'..='9')) - .to_slice() - .or(just('1').then(one_of('0'..='2')).to_slice()) - .from_str::() - .unwrapped(), + just('-') + .ignore_then( + just('0') + .then(one_of('0'..='9')) + .to_slice() + .or(just('1').then(one_of('0'..='2')).to_slice()) + .from_str::() + .unwrapped(), + ) + .or(just('0') + .then(one_of('0'..='9')) + .to_slice() + .or(just('1').then(one_of('0'..='2')).to_slice()) + .from_str::() + .unwrapped()), ) } @@ -426,8 +458,9 @@ pub(crate) fn date<'a>() -> impl Parser<'a, &'a str, (), extra::Err() -> impl Parser<'a, &'a str, (), extra::Err>> + Clone { diff --git a/duper_website/docs/index.md b/duper_website/docs/index.md index c3c845c..5d7a6e8 100644 --- a/duper_website/docs/index.md +++ b/duper_website/docs/index.md @@ -79,11 +79,11 @@ Duper excels in a variety of use cases: | Raw strings | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | | Identifiers/tags | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | | Unambiguous | ✅ | ✅ | ✅ | ❌\[3] | ⚠️\[4] | ✅ | -| Simple | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| Simple | ⚠️\[2] | ✅ | ✅ | ❌ | ✅ | ✅ | | JSON-compatible | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | Popular | ❌ | ✅ | ⚠️ | ✅ | ✅ | ⚠️ | - [1] Using the [`!!binary` scalar type](https://yaml.org/type/binary.html) and base64 text; limited support in implementations. -- [2] Compliant with the [Temporal specification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal). -- [3] Unquoted strings can get confused with other scalars and [vice-versa](https://www.bram.us/2022/01/11/yaml-the-norway-problem/). +- [2] Fully compliant with the [Temporal specification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal). +- [3] Unquoted strings can get [confused with other scalars](https://www.bram.us/2022/01/11/yaml-the-norway-problem/) and vice-versa. - [4] Tabs allowed in [strings](https://toml.io/en/v1.0.0#string). -- 2.51.2