diff --git a/duper-js-wasm/tests/duper_value.test.ts b/duper-js-wasm/tests/duper_value.test.ts index 1e047a6..a695162 100644 --- a/duper-js-wasm/tests/duper_value.test.ts +++ b/duper-js-wasm/tests/duper_value.test.ts @@ -1,6 +1,6 @@ import { assert, describe, expect, it } from "vitest"; import { DuperValue } from ".."; -import { Temporal } from "temporal-polyfill"; +import { Temporal } from "@js-temporal/polyfill"; describe("DuperValue", () => { it("infers types from JS types", () => { diff --git a/duper-js-wasm/tests/roundtrip.test.ts b/duper-js-wasm/tests/roundtrip.test.ts index 62e76ba..da2e03f 100644 --- a/duper-js-wasm/tests/roundtrip.test.ts +++ b/duper-js-wasm/tests/roundtrip.test.ts @@ -1,6 +1,6 @@ import { assert, describe, expect, it } from "vitest"; import { parse, stringify, DuperValue } from ".."; -import { Temporal } from "temporal-polyfill"; +import { Temporal } from "@js-temporal/polyfill"; describe("parse then stringify", () => { const input = ` diff --git a/duper-python/src/temporal.rs b/duper-python/src/temporal.rs index b18463c..e41b013 100644 --- a/duper-python/src/temporal.rs +++ b/duper-python/src/temporal.rs @@ -28,7 +28,7 @@ impl TemporalString { #[new] fn new(value: String, r#type: Option) -> PyResult { Ok(TemporalString { - temporal: match r#type.as_ref().map(|typ| typ.as_str()) { + temporal: match r#type.as_deref() { Some("Instant") => duper::DuperTemporal::try_instant_from(Cow::Owned(value)) .map_err(|err| { PyValueError::new_err(format!( @@ -117,10 +117,7 @@ impl TemporalString { } fn __repr__(&self) -> String { - let typ = match self.r#type() { - Some(typ) => typ, - None => "None", - }; + let typ = self.r#type().unwrap_or("None"); let value = self.temporal.as_ref(); format!("TemporalString(type={typ} value='{value}')") } diff --git a/duper/src/parser/mod.rs b/duper/src/parser/mod.rs index 69e6551..f7c733c 100644 --- a/duper/src/parser/mod.rs +++ b/duper/src/parser/mod.rs @@ -125,6 +125,32 @@ pub(crate) fn identifier<'a>() .map(|identifier| DuperIdentifier(Cow::Borrowed(identifier))) } +pub(crate) fn non_temporal_identifier<'a>() +-> impl Parser<'a, &'a str, DuperIdentifier<'a>, extra::Err>> + Clone { + let temporal_identifiers = choice(( + just("Instant"), + just("ZonedDateTime"), + just("PlainDate"), + just("PlainTime"), + just("PlainDateTime"), + just("PlainYearMonth"), + just("PlainMonthDay"), + just("Duration"), + )); + + temporal_identifiers + .then( + one_of("-_") + .or_not() + .then(ascii_alphanumeric()) + .repeated() + .at_least(1), + ) + .to_slice() + .map(|identifier| DuperIdentifier(Cow::Borrowed(identifier))) + .or(identifier()) +} + pub(crate) fn identified_trunk<'a>() -> impl Parser<'a, &'a str, DuperValue<'a>, extra::Err>> + Clone { let inner_trunk = choice(( @@ -184,7 +210,7 @@ pub(crate) fn identified_value<'a>() inner: DuperInner::Temporal(temporal), } }), - identifier() + non_temporal_identifier() .then(temporal_unspecified().delimited_by(just('('), just(')'))) .map(|(identifier, temporal)| DuperValue { identifier: Some(identifier), @@ -768,7 +794,7 @@ mod duper_parser_tests { )); let input = r#" - ' 2022-02-28T03:06:00.092121729Z ' + ' 2022-02-28T03:06:00.092121729+02:00 ' "#; let duper = DuperParser::parse_duper_value(input).unwrap(); assert!(matches!( @@ -777,7 +803,7 @@ mod duper_parser_tests { )); let input = r#" - PlainDate(' 2022-02-28T03:06:00.092121729Z ') + PlainDate(' 2022-02-28T03:06:00.092121729+02:00 ') "#; let duper = DuperParser::parse_duper_value(input).unwrap(); assert!(matches!( @@ -786,7 +812,7 @@ mod duper_parser_tests { )); let input = r#" - PlainTime(' 2022-02-28T03:06:00.092121729Z ') + PlainTime(' 2022-02-28T03:06:00.092121729+02:00 ') "#; let duper = DuperParser::parse_duper_value(input).unwrap(); assert!(matches!( @@ -803,6 +829,15 @@ mod duper_parser_tests { DuperInner::Temporal(DuperTemporal::PlainMonthDay(_)) )); + let input = r#" + PlainTimeDate('2022-11-09') // Non-recognized identifier + "#; + let duper = DuperParser::parse_duper_value(input).unwrap(); + assert!(matches!( + duper.inner, + DuperInner::Temporal(DuperTemporal::Unspecified(_)) + )); + let input = r#" UnknownIdent('--11-03') "#; @@ -896,6 +931,10 @@ mod duper_parser_tests { ZonedDateTime('2022-02-28T11:06:00.092121729+08:00[u-ca=chinese]') // Missing timezone "#; assert!(DuperParser::parse_duper_value(input).is_err()); + let input = r#" + PlainDateTime('2022-02-28T03:06:00Z') // Z offset not allowed in conversion from Instant + "#; + 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 809c045..6e4eb4c 100644 --- a/duper/src/parser/temporal.rs +++ b/duper/src/parser/temporal.rs @@ -33,6 +33,9 @@ pub(crate) fn temporal_instant<'a>() instant() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -48,6 +51,9 @@ pub(crate) fn temporal_zoned_date_time<'a>() zoned_date_time() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -63,6 +69,9 @@ pub(crate) fn temporal_plain_date<'a>() plain_date() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -78,6 +87,9 @@ pub(crate) fn temporal_plain_time<'a>() plain_time() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -93,6 +105,9 @@ pub(crate) fn temporal_plain_date_time<'a>() plain_date_time() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -108,6 +123,9 @@ pub(crate) fn temporal_plain_year_month<'a>() plain_year_month() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -123,6 +141,9 @@ pub(crate) fn temporal_plain_month_day<'a>() plain_month_day() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -138,6 +159,9 @@ pub(crate) fn temporal_duration<'a>() duration() .to_slice() .delimited_by(just('\''), just('\'')) + .recover_with(via_parser(just('\'').ignore_then( + none_of('\'').repeated().to_slice().then_ignore(just('\'')), + ))) .padded_by(whitespace_and_comments()), ) .then_ignore(just(')')) @@ -243,19 +267,19 @@ pub(crate) fn duration<'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, (), extra::Err>> + Clone { - choice((plain_year_month(), plain_month_day(), duration())) + choice((instant(), plain_year_month(), plain_month_day(), duration())) } // Atoms @@ -462,7 +486,9 @@ pub(crate) fn date<'a>() -> impl Parser<'a, &'a str, (), extra::Err