Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate ISO8601 parsing to boa_temporal #3500

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion boa_ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ rust-version.workspace = true
[features]
serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"]
arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"]
temporal = []

[dependencies]
boa_interner.workspace = true
Expand Down
2 changes: 0 additions & 2 deletions boa_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ pub mod operations;
pub mod pattern;
pub mod property;
pub mod statement;
#[cfg(feature = "temporal")]
pub mod temporal;
pub mod visitor;

use boa_interner::{Interner, ToIndentedString, ToInternedString};
Expand Down
2 changes: 1 addition & 1 deletion boa_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ trace = ["js"]
annex-b = ["boa_parser/annex-b"]

# Stage 3 proposals
temporal = ["boa_parser/temporal", "dep:icu_calendar"]
temporal = ["dep:icu_calendar"]

# Enable experimental features, like Stage 3 proposals.
experimental = ["temporal"]
Expand Down
1 change: 1 addition & 0 deletions boa_engine/src/builtins/temporal/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impl From<TemporalError> for JsNativeError {
ErrorKind::Range => JsNativeError::range().with_message(value.message()),
ErrorKind::Type => JsNativeError::typ().with_message(value.message()),
ErrorKind::Generic => JsNativeError::error().with_message(value.message()),
ErrorKind::Syntax => JsNativeError::syntax().with_message(value.message()),
}
}
}
Expand Down
37 changes: 7 additions & 30 deletions boa_engine/src/builtins/temporal/plain_date/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Boa's implementation of the ECMAScript `Temporal.PlainDate` builtin object.
#![allow(dead_code, unused_variables)]

use std::str::FromStr;

use crate::{
builtins::{
options::{get_option, get_options_object},
Expand All @@ -16,14 +14,8 @@ use crate::{
string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_parser::temporal::{IsoCursor, TemporalDateTimeString};
use boa_profiler::Profiler;
use boa_temporal::{
calendar::{AvailableCalendars, CalendarSlot},
date::Date as InnerDate,
datetime::DateTime,
options::ArithmeticOverflow,
};
use boa_temporal::{date::Date as InnerDate, datetime::DateTime, options::ArithmeticOverflow};

use super::calendar;

Expand Down Expand Up @@ -517,32 +509,17 @@ pub(crate) fn to_temporal_date(
};

// 6. Let result be ? ParseTemporalDateString(item).
let result = TemporalDateTimeString::parse(
false,
&mut IsoCursor::new(&date_like_string.to_std_string_escaped()),
)
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;

// 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true.
// 8. Let calendar be result.[[Calendar]].
// 9. If calendar is undefined, set calendar to "iso8601".
let identifier = result.date.calendar.unwrap_or("iso8601".to_string());

// 10. If IsBuiltinCalendar(calendar) is false, throw a RangeError exception.
let _ = AvailableCalendars::from_str(identifier.to_ascii_lowercase().as_str())?;

// 11. Set calendar to the ASCII-lowercase of calendar.
let calendar = CalendarSlot::Identifier(identifier.to_ascii_lowercase());

// 12. Perform ? ToTemporalOverflow(options).
let _ = get_option::<ArithmeticOverflow>(&options_obj, utf16!("overflow"), context)?;

// 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
Ok(PlainDate::new(InnerDate::new(
result.date.year,
result.date.month,
result.date.day,
calendar,
ArithmeticOverflow::Reject,
)?))
let result = date_like_string
.to_std_string_escaped()
.parse::<InnerDate>()
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;

Ok(PlainDate::new(result))
}
4 changes: 2 additions & 2 deletions boa_engine/src/builtins/temporal/time_zone/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,10 @@ pub(super) fn create_temporal_time_zone(
/// [spec]: https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
#[allow(clippy::unnecessary_wraps, unused)]
fn parse_timezone_offset_string(offset_string: &str, context: &mut Context) -> JsResult<i64> {
use boa_parser::temporal::{IsoCursor, TemporalTimeZoneString};
use boa_temporal::parser::{Cursor, TemporalTimeZoneString};

// 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset).
let parse_result = TemporalTimeZoneString::parse(&mut IsoCursor::new(offset_string))?;
let parse_result = TemporalTimeZoneString::parse(&mut Cursor::new(offset_string))?;

// 2. Assert: parseResult is not a List of errors.
// 3. Assert: parseResult contains a TemporalSign Parse Node.
Expand Down
1 change: 0 additions & 1 deletion boa_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ icu_properties.workspace = true

[features]
annex-b = []
temporal = ["boa_ast/temporal"]

[lints]
workspace = true
2 changes: 0 additions & 2 deletions boa_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ pub mod error;
pub mod lexer;
pub mod parser;
mod source;
#[cfg(feature = "temporal")]
pub mod temporal;

pub use error::Error;
pub use lexer::Lexer;
Expand Down
32 changes: 29 additions & 3 deletions boa_temporal/src/date.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
//! The `PlainDate` representation.

use crate::{
calendar::CalendarSlot,
calendar::{AvailableCalendars, CalendarSlot},
datetime::DateTime,
duration::{DateDuration, Duration},
iso::{IsoDate, IsoDateSlots},
options::{ArithmeticOverflow, TemporalUnit},
TemporalResult,
parser::parse_date_time,
TemporalError, TemporalResult,
};
use std::any::Any;
use std::{any::Any, str::FromStr};

/// The `Temporal.PlainDate` equivalent
#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -218,3 +219,28 @@ impl Date {
self.contextual_difference_date(other, largest_unit, &mut ())
}
}

// ==== Trait impls ====

impl FromStr for Date {
type Err = TemporalError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parse_record = parse_date_time(s)?;

let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned());
let _ = AvailableCalendars::from_str(calendar.to_ascii_lowercase().as_str())?;

let date = IsoDate::new(
parse_record.date.year,
parse_record.date.month,
parse_record.date.day,
ArithmeticOverflow::Reject,
)?;

Ok(Self::new_unchecked(
date,
CalendarSlot::Identifier(calendar),
))
}
}
41 changes: 40 additions & 1 deletion boa_temporal/src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Temporal implementation of `DateTime`

use std::str::FromStr;

use crate::{
calendar::CalendarSlot,
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
options::ArithmeticOverflow,
TemporalResult,
parser::parse_date_time,
TemporalError, TemporalResult,
};

/// The `DateTime` struct.
Expand Down Expand Up @@ -93,3 +96,39 @@ impl DateTime {
&self.calendar
}
}

// ==== Trait impls ====

impl FromStr for DateTime {
type Err = TemporalError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parse_record = parse_date_time(s)?;

let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned());

let time = if let Some(time) = parse_record.time {
IsoTime::from_components(
i32::from(time.hour),
i32::from(time.minute),
i32::from(time.second),
time.fraction,
)?
} else {
IsoTime::default()
};

let date = IsoDate::new(
parse_record.date.year,
parse_record.date.month,
parse_record.date.day,
ArithmeticOverflow::Reject,
)?;

Ok(Self::new_unchecked(
date,
time,
CalendarSlot::Identifier(calendar),
))
}
}
55 changes: 54 additions & 1 deletion boa_temporal/src/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use crate::{
date::Date,
datetime::DateTime,
options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit},
parser::{duration::parse_duration, Cursor},
utils,
zoneddatetime::ZonedDateTime,
TemporalError, TemporalResult, NS_PER_DAY,
};
use std::any::Any;
use std::{any::Any, str::FromStr};

// ==== `DateDuration` ====

Expand Down Expand Up @@ -1688,3 +1689,55 @@ impl Duration {
Ok(result)
}
}

// ==== FromStr trait impl ====

impl FromStr for Duration {
type Err = TemporalError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parse_record = parse_duration(&mut Cursor::new(s))?;

let minutes = if parse_record.time.fhours > 0.0 {
parse_record.time.fhours * 60.0
} else {
f64::from(parse_record.time.minutes)
};

let seconds = if parse_record.time.fminutes > 0.0 {
parse_record.time.fminutes * 60.0
} else if parse_record.time.seconds > 0 {
f64::from(parse_record.time.seconds)
} else {
minutes.rem_euclid(1.0) * 60.0
};

let milliseconds = if parse_record.time.fseconds > 0.0 {
parse_record.time.fseconds * 1000.0
} else {
seconds.rem_euclid(1.0) * 1000.0
};

let micro = milliseconds.rem_euclid(1.0) * 1000.0;
let nano = micro.rem_euclid(1.0) * 1000.0;

let sign = if parse_record.sign { 1f64 } else { -1f64 };

Ok(Self {
date: DateDuration::new(
f64::from(parse_record.date.years) * sign,
f64::from(parse_record.date.months) * sign,
f64::from(parse_record.date.weeks) * sign,
f64::from(parse_record.date.days) * sign,
),
time: TimeDuration::new(
f64::from(parse_record.time.hours) * sign,
minutes.floor() * sign,
seconds.floor() * sign,
milliseconds.floor() * sign,
micro.floor() * sign,
nano.floor() * sign,
),
})
}
}
15 changes: 15 additions & 0 deletions boa_temporal/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum ErrorKind {
Type,
/// RangeError
Range,
/// SyntaxError
Syntax,
}

impl fmt::Display for ErrorKind {
Expand All @@ -20,6 +22,7 @@ impl fmt::Display for ErrorKind {
Self::Generic => "Error",
Self::Type => "TypeError",
Self::Range => "RangeError",
Self::Syntax => "SyntaxError",
}
.fmt(f)
}
Expand Down Expand Up @@ -61,6 +64,18 @@ impl TemporalError {
Self::new(ErrorKind::Type)
}

/// Create a syntax error.
#[must_use]
pub fn syntax() -> Self {
Self::new(ErrorKind::Syntax)
}

/// Create an abrupt end error.
#[must_use]
pub fn abrupt_end() -> Self {
Self::syntax().with_message("Abrupt end to parsing target.")
}

/// Add a message to the error.
#[must_use]
pub fn with_message<S>(mut self, msg: S) -> Self
Expand Down
34 changes: 28 additions & 6 deletions boa_temporal/src/iso.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,12 @@ impl IsoDate {
/// time slots.
#[derive(Debug, Default, Clone, Copy)]
pub struct IsoTime {
hour: i32, // 0..=23
minute: i32, // 0..=59
second: i32, // 0..=59
millisecond: i32, // 0..=999
microsecond: i32, // 0..=999
nanosecond: i32, // 0..=999
pub(crate) hour: i32, // 0..=23
pub(crate) minute: i32, // 0..=59
pub(crate) second: i32, // 0..=59
pub(crate) millisecond: i32, // 0..=999
pub(crate) microsecond: i32, // 0..=999
pub(crate) nanosecond: i32, // 0..=999
}

impl IsoTime {
Expand Down Expand Up @@ -281,6 +281,28 @@ impl IsoTime {
}
}

/// Returns an `IsoTime` based off parse components.
pub(crate) fn from_components(
hour: i32,
minute: i32,
second: i32,
fraction: f64,
) -> TemporalResult<Self> {
let millisecond = fraction * 1000f64;
let micros = millisecond.rem_euclid(1f64) * 1000f64;
let nanos = micros.rem_euclid(1f64).mul_add(1000f64, 0.5).floor();

Self::new(
hour,
minute,
second,
millisecond as i32,
micros as i32,
nanos as i32,
ArithmeticOverflow::Reject,
)
}

/// Checks if the time is a valid `IsoTime`
pub(crate) fn is_valid(&self) -> bool {
if !(0..=23).contains(&self.hour) {
Expand Down
1 change: 1 addition & 0 deletions boa_temporal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod fields;
pub mod iso;
pub mod month_day;
pub mod options;
pub mod parser;
pub mod time;
pub(crate) mod utils;
pub mod year_month;
Expand Down
Loading
Loading