Browse Source

Migrate iso parsing to boa_temporal (#3500)

pull/3497/head
Kevin 11 months ago committed by GitHub
parent
commit
c113b74b09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      boa_ast/Cargo.toml
  2. 2
      boa_ast/src/lib.rs
  3. 2
      boa_engine/Cargo.toml
  4. 1
      boa_engine/src/builtins/temporal/error.rs
  5. 37
      boa_engine/src/builtins/temporal/plain_date/mod.rs
  6. 4
      boa_engine/src/builtins/temporal/time_zone/mod.rs
  7. 1
      boa_parser/Cargo.toml
  8. 2
      boa_parser/src/lib.rs
  9. 32
      boa_temporal/src/date.rs
  10. 41
      boa_temporal/src/datetime.rs
  11. 55
      boa_temporal/src/duration.rs
  12. 15
      boa_temporal/src/error.rs
  13. 34
      boa_temporal/src/iso.rs
  14. 1
      boa_temporal/src/lib.rs
  15. 72
      boa_temporal/src/parser/annotations.rs
  16. 153
      boa_temporal/src/parser/date_time.rs
  17. 107
      boa_temporal/src/parser/duration.rs
  18. 0
      boa_temporal/src/parser/grammar.rs
  19. 151
      boa_temporal/src/parser/mod.rs
  20. 33
      boa_temporal/src/parser/nodes.rs
  21. 92
      boa_temporal/src/parser/tests.rs
  22. 48
      boa_temporal/src/parser/time.rs
  23. 79
      boa_temporal/src/parser/time_zone.rs

1
boa_ast/Cargo.toml

@ -13,7 +13,6 @@ rust-version.workspace = true
[features] [features]
serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"] serde = ["dep:serde", "boa_interner/serde", "bitflags/serde", "num-bigint/serde"]
arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"] arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"]
temporal = []
[dependencies] [dependencies]
boa_interner.workspace = true boa_interner.workspace = true

2
boa_ast/src/lib.rs

@ -91,8 +91,6 @@ pub mod operations;
pub mod pattern; pub mod pattern;
pub mod property; pub mod property;
pub mod statement; pub mod statement;
#[cfg(feature = "temporal")]
pub mod temporal;
pub mod visitor; pub mod visitor;
use boa_interner::{Interner, ToIndentedString, ToInternedString}; use boa_interner::{Interner, ToIndentedString, ToInternedString};

2
boa_engine/Cargo.toml

@ -47,7 +47,7 @@ trace = ["js"]
annex-b = ["boa_parser/annex-b"] annex-b = ["boa_parser/annex-b"]
# Stage 3 proposals # Stage 3 proposals
temporal = ["boa_parser/temporal", "dep:icu_calendar"] temporal = ["dep:icu_calendar"]
# Enable experimental features, like Stage 3 proposals. # Enable experimental features, like Stage 3 proposals.
experimental = ["temporal"] experimental = ["temporal"]

1
boa_engine/src/builtins/temporal/error.rs

@ -8,6 +8,7 @@ impl From<TemporalError> for JsNativeError {
ErrorKind::Range => JsNativeError::range().with_message(value.message()), ErrorKind::Range => JsNativeError::range().with_message(value.message()),
ErrorKind::Type => JsNativeError::typ().with_message(value.message()), ErrorKind::Type => JsNativeError::typ().with_message(value.message()),
ErrorKind::Generic => JsNativeError::error().with_message(value.message()), ErrorKind::Generic => JsNativeError::error().with_message(value.message()),
ErrorKind::Syntax => JsNativeError::syntax().with_message(value.message()),
} }
} }
} }

37
boa_engine/src/builtins/temporal/plain_date/mod.rs

@ -1,8 +1,6 @@
//! Boa's implementation of the ECMAScript `Temporal.PlainDate` builtin object. //! Boa's implementation of the ECMAScript `Temporal.PlainDate` builtin object.
#![allow(dead_code, unused_variables)] #![allow(dead_code, unused_variables)]
use std::str::FromStr;
use crate::{ use crate::{
builtins::{ builtins::{
options::{get_option, get_options_object}, options::{get_option, get_options_object},
@ -16,14 +14,8 @@ use crate::{
string::{common::StaticJsStrings, utf16}, string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
}; };
use boa_parser::temporal::{IsoCursor, TemporalDateTimeString};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use boa_temporal::{ use boa_temporal::{date::Date as InnerDate, datetime::DateTime, options::ArithmeticOverflow};
calendar::{AvailableCalendars, CalendarSlot},
date::Date as InnerDate,
datetime::DateTime,
options::ArithmeticOverflow,
};
use super::calendar; use super::calendar;
@ -517,32 +509,17 @@ pub(crate) fn to_temporal_date(
}; };
// 6. Let result be ? ParseTemporalDateString(item). // 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. // 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true.
// 8. Let calendar be result.[[Calendar]]. // 8. Let calendar be result.[[Calendar]].
// 9. If calendar is undefined, set calendar to "iso8601". // 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. // 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. // 11. Set calendar to the ASCII-lowercase of calendar.
let calendar = CalendarSlot::Identifier(identifier.to_ascii_lowercase());
// 12. Perform ? ToTemporalOverflow(options). // 12. Perform ? ToTemporalOverflow(options).
let _ = get_option::<ArithmeticOverflow>(&options_obj, utf16!("overflow"), context)?;
// 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
Ok(PlainDate::new(InnerDate::new( let result = date_like_string
result.date.year, .to_std_string_escaped()
result.date.month, .parse::<InnerDate>()
result.date.day, .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
calendar,
ArithmeticOverflow::Reject, Ok(PlainDate::new(result))
)?))
} }

4
boa_engine/src/builtins/temporal/time_zone/mod.rs

@ -364,10 +364,10 @@ pub(super) fn create_temporal_time_zone(
/// [spec]: https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring /// [spec]: https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
#[allow(clippy::unnecessary_wraps, unused)] #[allow(clippy::unnecessary_wraps, unused)]
fn parse_timezone_offset_string(offset_string: &str, context: &mut Context) -> JsResult<i64> { 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). // 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. // 2. Assert: parseResult is not a List of errors.
// 3. Assert: parseResult contains a TemporalSign Parse Node. // 3. Assert: parseResult contains a TemporalSign Parse Node.

1
boa_parser/Cargo.toml

@ -25,7 +25,6 @@ icu_properties.workspace = true
[features] [features]
annex-b = [] annex-b = []
temporal = ["boa_ast/temporal"]
[lints] [lints]
workspace = true workspace = true

2
boa_parser/src/lib.rs

@ -28,8 +28,6 @@ pub mod error;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
mod source; mod source;
#[cfg(feature = "temporal")]
pub mod temporal;
pub use error::Error; pub use error::Error;
pub use lexer::Lexer; pub use lexer::Lexer;

32
boa_temporal/src/date.rs

@ -1,14 +1,15 @@
//! The `PlainDate` representation. //! The `PlainDate` representation.
use crate::{ use crate::{
calendar::CalendarSlot, calendar::{AvailableCalendars, CalendarSlot},
datetime::DateTime, datetime::DateTime,
duration::{DateDuration, Duration}, duration::{DateDuration, Duration},
iso::{IsoDate, IsoDateSlots}, iso::{IsoDate, IsoDateSlots},
options::{ArithmeticOverflow, TemporalUnit}, options::{ArithmeticOverflow, TemporalUnit},
TemporalResult, parser::parse_date_time,
TemporalError, TemporalResult,
}; };
use std::any::Any; use std::{any::Any, str::FromStr};
/// The `Temporal.PlainDate` equivalent /// The `Temporal.PlainDate` equivalent
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -218,3 +219,28 @@ impl Date {
self.contextual_difference_date(other, largest_unit, &mut ()) 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
boa_temporal/src/datetime.rs

@ -1,10 +1,13 @@
//! Temporal implementation of `DateTime` //! Temporal implementation of `DateTime`
use std::str::FromStr;
use crate::{ use crate::{
calendar::CalendarSlot, calendar::CalendarSlot,
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
options::ArithmeticOverflow, options::ArithmeticOverflow,
TemporalResult, parser::parse_date_time,
TemporalError, TemporalResult,
}; };
/// The `DateTime` struct. /// The `DateTime` struct.
@ -93,3 +96,39 @@ impl DateTime {
&self.calendar &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
boa_temporal/src/duration.rs

@ -6,11 +6,12 @@ use crate::{
date::Date, date::Date,
datetime::DateTime, datetime::DateTime,
options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit},
parser::{duration::parse_duration, Cursor},
utils, utils,
zoneddatetime::ZonedDateTime, zoneddatetime::ZonedDateTime,
TemporalError, TemporalResult, NS_PER_DAY, TemporalError, TemporalResult, NS_PER_DAY,
}; };
use std::any::Any; use std::{any::Any, str::FromStr};
// ==== `DateDuration` ==== // ==== `DateDuration` ====
@ -1688,3 +1689,55 @@ impl Duration {
Ok(result) 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
boa_temporal/src/error.rs

@ -12,6 +12,8 @@ pub enum ErrorKind {
Type, Type,
/// RangeError /// RangeError
Range, Range,
/// SyntaxError
Syntax,
} }
impl fmt::Display for ErrorKind { impl fmt::Display for ErrorKind {
@ -20,6 +22,7 @@ impl fmt::Display for ErrorKind {
Self::Generic => "Error", Self::Generic => "Error",
Self::Type => "TypeError", Self::Type => "TypeError",
Self::Range => "RangeError", Self::Range => "RangeError",
Self::Syntax => "SyntaxError",
} }
.fmt(f) .fmt(f)
} }
@ -61,6 +64,18 @@ impl TemporalError {
Self::new(ErrorKind::Type) 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. /// Add a message to the error.
#[must_use] #[must_use]
pub fn with_message<S>(mut self, msg: S) -> Self pub fn with_message<S>(mut self, msg: S) -> Self

34
boa_temporal/src/iso.rs

@ -209,12 +209,12 @@ impl IsoDate {
/// time slots. /// time slots.
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct IsoTime { pub struct IsoTime {
hour: i32, // 0..=23 pub(crate) hour: i32, // 0..=23
minute: i32, // 0..=59 pub(crate) minute: i32, // 0..=59
second: i32, // 0..=59 pub(crate) second: i32, // 0..=59
millisecond: i32, // 0..=999 pub(crate) millisecond: i32, // 0..=999
microsecond: i32, // 0..=999 pub(crate) microsecond: i32, // 0..=999
nanosecond: i32, // 0..=999 pub(crate) nanosecond: i32, // 0..=999
} }
impl IsoTime { impl IsoTime {
@ -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` /// Checks if the time is a valid `IsoTime`
pub(crate) fn is_valid(&self) -> bool { pub(crate) fn is_valid(&self) -> bool {
if !(0..=23).contains(&self.hour) { if !(0..=23).contains(&self.hour) {

1
boa_temporal/src/lib.rs

@ -36,6 +36,7 @@ pub mod fields;
pub mod iso; pub mod iso;
pub mod month_day; pub mod month_day;
pub mod options; pub mod options;
pub mod parser;
pub mod time; pub mod time;
pub(crate) mod utils; pub(crate) mod utils;
pub mod year_month; pub mod year_month;

72
boa_parser/src/temporal/annotations.rs → boa_temporal/src/parser/annotations.rs

@ -1,20 +1,17 @@
/// Parsing for Temporal's `Annotations`. /// Parsing for Temporal's `Annotations`.
use crate::{ use crate::{
error::{Error, ParseResult}, parser::{
lexer::Error as LexError,
temporal::{
grammar::{ grammar::{
is_a_key_char, is_a_key_leading_char, is_annotation_close, is_a_key_char, is_a_key_leading_char, is_annotation_close,
is_annotation_key_value_separator, is_annotation_value_component, is_critical_flag, is_annotation_key_value_separator, is_annotation_value_component, is_critical_flag,
}, },
time_zone, time_zone,
time_zone::TimeZoneAnnotation, time_zone::TimeZoneAnnotation,
IsoCursor, Cursor,
}, },
TemporalError, TemporalResult,
}; };
use boa_ast::{Position, Span};
use super::grammar::{is_annotation_open, is_hyphen}; use super::grammar::{is_annotation_open, is_hyphen};
/// A `KeyValueAnnotation` Parse Node. /// A `KeyValueAnnotation` Parse Node.
@ -37,20 +34,14 @@ pub(crate) struct AnnotationSet {
/// Parse a `TimeZoneAnnotation` `Annotations` set /// Parse a `TimeZoneAnnotation` `Annotations` set
pub(crate) fn parse_annotation_set( pub(crate) fn parse_annotation_set(
zoned: bool, zoned: bool,
cursor: &mut IsoCursor, cursor: &mut Cursor,
) -> ParseResult<AnnotationSet> { ) -> TemporalResult<AnnotationSet> {
// Parse the first annotation. // Parse the first annotation.
let tz_annotation = time_zone::parse_ambiguous_tz_annotation(cursor)?; let tz_annotation = time_zone::parse_ambiguous_tz_annotation(cursor)?;
if tz_annotation.is_none() && zoned { if tz_annotation.is_none() && zoned {
return Err(Error::unexpected( return Err(TemporalError::syntax()
"Annotation", .with_message("iso8601 ZonedDateTime requires a TimeZoneAnnotation."));
Span::new(
Position::new(1, cursor.pos() + 1),
Position::new(1, cursor.pos() + 2),
),
"iso8601 ZonedDateTime requires a TimeZoneAnnotation.",
));
} }
// Parse any `Annotations` // Parse any `Annotations`
@ -77,12 +68,11 @@ pub(crate) struct RecognizedAnnotations {
} }
/// Parse any number of `KeyValueAnnotation`s /// Parse any number of `KeyValueAnnotation`s
pub(crate) fn parse_annotations(cursor: &mut IsoCursor) -> ParseResult<RecognizedAnnotations> { pub(crate) fn parse_annotations(cursor: &mut Cursor) -> TemporalResult<RecognizedAnnotations> {
let mut annotations = RecognizedAnnotations::default(); let mut annotations = RecognizedAnnotations::default();
let mut calendar_crit = false; let mut calendar_crit = false;
while cursor.check_or(false, is_annotation_open) { while cursor.check_or(false, is_annotation_open) {
let start = Position::new(1, cursor.pos() + 1);
let kv = parse_kv_annotation(cursor)?; let kv = parse_kv_annotation(cursor)?;
if &kv.key == "u-ca" { if &kv.key == "u-ca" {
@ -93,13 +83,12 @@ pub(crate) fn parse_annotations(cursor: &mut IsoCursor) -> ParseResult<Recognize
} }
if calendar_crit || kv.critical { if calendar_crit || kv.critical {
return Err(Error::general( return Err(TemporalError::syntax().with_message(
"Cannot have critical flag with duplicate calendar annotations", "Cannot have critical flag with duplicate calendar annotations",
start,
)); ));
} }
} else if kv.critical { } else if kv.critical {
return Err(Error::general("Unrecognized critical annotation.", start)); return Err(TemporalError::syntax().with_message("Unrecognized critical annotation."));
} }
} }
@ -107,22 +96,18 @@ pub(crate) fn parse_annotations(cursor: &mut IsoCursor) -> ParseResult<Recognize
} }
/// Parse an annotation with an `AnnotationKey`=`AnnotationValue` pair. /// Parse an annotation with an `AnnotationKey`=`AnnotationValue` pair.
fn parse_kv_annotation(cursor: &mut IsoCursor) -> ParseResult<KeyValueAnnotation> { fn parse_kv_annotation(cursor: &mut Cursor) -> TemporalResult<KeyValueAnnotation> {
debug_assert!(cursor.check_or(false, is_annotation_open)); debug_assert!(cursor.check_or(false, is_annotation_open));
let potential_critical = cursor.next().ok_or_else(|| Error::AbruptEnd)?; let potential_critical = cursor.next().ok_or_else(TemporalError::abrupt_end)?;
let (leading_char, critical) = if is_critical_flag(potential_critical) { let (leading_char, critical) = if is_critical_flag(potential_critical) {
(cursor.next().ok_or_else(|| Error::AbruptEnd)?, true) (cursor.next().ok_or_else(TemporalError::abrupt_end)?, true)
} else { } else {
(potential_critical, false) (potential_critical, false)
}; };
if !is_a_key_leading_char(leading_char) { if !is_a_key_leading_char(leading_char) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Invalid AnnotationKey leading character"));
"Invalid AnnotationKey leading character",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
// Parse AnnotationKey. // Parse AnnotationKey.
@ -147,7 +132,7 @@ fn parse_kv_annotation(cursor: &mut IsoCursor) -> ParseResult<KeyValueAnnotation
} }
/// Parse an `AnnotationKey`. /// Parse an `AnnotationKey`.
fn parse_annotation_key(cursor: &mut IsoCursor) -> ParseResult<String> { fn parse_annotation_key(cursor: &mut Cursor) -> TemporalResult<String> {
let key_start = cursor.pos(); let key_start = cursor.pos();
while let Some(potential_key_char) = cursor.next() { while let Some(potential_key_char) = cursor.next() {
// End of key. // End of key.
@ -157,19 +142,15 @@ fn parse_annotation_key(cursor: &mut IsoCursor) -> ParseResult<String> {
} }
if !is_a_key_char(potential_key_char) { if !is_a_key_char(potential_key_char) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Invalid AnnotationKey Character"));
"Invalid AnnotationKey Character",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
} }
Err(Error::AbruptEnd) Err(TemporalError::abrupt_end())
} }
/// Parse an `AnnotationValue`. /// Parse an `AnnotationValue`.
fn parse_annotation_value(cursor: &mut IsoCursor) -> ParseResult<String> { fn parse_annotation_value(cursor: &mut Cursor) -> TemporalResult<String> {
let value_start = cursor.pos(); let value_start = cursor.pos();
while let Some(potential_value_char) = cursor.next() { while let Some(potential_value_char) = cursor.next() {
if is_annotation_close(potential_value_char) { if is_annotation_close(potential_value_char) {
@ -182,24 +163,19 @@ fn parse_annotation_value(cursor: &mut IsoCursor) -> ParseResult<String> {
.peek_n(1) .peek_n(1)
.map_or(false, is_annotation_value_component) .map_or(false, is_annotation_value_component)
{ {
return Err(LexError::syntax( return Err(TemporalError::syntax()
"Missing AttributeValueComponent after '-'", .with_message("Missing AttributeValueComponent after '-'"));
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance(); cursor.advance();
continue; continue;
} }
if !is_annotation_value_component(potential_value_char) { if !is_annotation_value_component(potential_value_char) {
return Err(LexError::syntax( return Err(
"Invalid character in AnnotationValue", TemporalError::syntax().with_message("Invalid character in AnnotationValue")
Position::new(1, value_start + cursor.pos() + 1), );
)
.into());
} }
} }
Err(Error::AbruptEnd) Err(TemporalError::abrupt_end())
} }

153
boa_parser/src/temporal/date_time.rs → boa_temporal/src/parser/date_time.rs

@ -1,20 +1,19 @@
//! Parsing for Temporal's ISO8601 `Date` and `DateTime`. //! Parsing for Temporal's ISO8601 `Date` and `DateTime`.
use crate::{ use crate::{
error::{Error, ParseResult}, parser::{
lexer::Error as LexError,
temporal::{
annotations, annotations,
grammar::{is_date_time_separator, is_sign, is_utc_designator}, grammar::{is_date_time_separator, is_sign, is_utc_designator},
nodes::TimeZone,
time, time,
time::TimeSpec, time::TimeSpec,
time_zone, IsoCursor, IsoParseRecord, time_zone, Cursor, IsoParseRecord,
}, },
TemporalError, TemporalResult,
}; };
use boa_ast::{temporal::TimeZone, Position, Span};
use super::grammar::{is_annotation_open, is_hyphen}; use super::grammar::{is_annotation_open, is_hyphen};
use bitflags::bitflags;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
/// A `DateTime` Parse Node that contains the date, time, and offset info. /// A `DateTime` Parse Node that contains the date, time, and offset info.
@ -38,6 +37,16 @@ pub(crate) struct DateRecord {
pub(crate) day: i32, pub(crate) day: i32,
} }
bitflags! {
/// Parsing flags for `AnnotatedDateTime` parsing.
#[derive(Debug, Clone, Copy)]
pub struct DateTimeFlags: u8 {
const ZONED = 0b0000_0001;
const TIME_REQ = 0b0000_0010;
const UTC_REQ = 0b0000_0100;
}
}
/// This function handles parsing for [`AnnotatedDateTime`][datetime], /// This function handles parsing for [`AnnotatedDateTime`][datetime],
/// [`AnnotatedDateTimeTimeRequred`][time], and /// [`AnnotatedDateTimeTimeRequred`][time], and
/// [`TemporalInstantString.`][instant] according to the requirements /// [`TemporalInstantString.`][instant] according to the requirements
@ -47,27 +56,22 @@ pub(crate) struct DateRecord {
/// [time]: https://tc39.es/proposal-temporal/#prod-AnnotatedDateTimeTimeRequired /// [time]: https://tc39.es/proposal-temporal/#prod-AnnotatedDateTimeTimeRequired
/// [instant]: https://tc39.es/proposal-temporal/#prod-TemporalInstantString /// [instant]: https://tc39.es/proposal-temporal/#prod-TemporalInstantString
pub(crate) fn parse_annotated_date_time( pub(crate) fn parse_annotated_date_time(
zoned: bool, flags: DateTimeFlags,
time_required: bool, cursor: &mut Cursor,
utc_required: bool, ) -> TemporalResult<IsoParseRecord> {
cursor: &mut IsoCursor, let date_time = parse_date_time(
) -> ParseResult<IsoParseRecord> { flags.contains(DateTimeFlags::TIME_REQ),
let date_time = parse_date_time(time_required, utc_required, cursor)?; flags.contains(DateTimeFlags::UTC_REQ),
cursor,
)?;
// Peek Annotation presence // Peek Annotation presence
// Throw error if annotation does not exist and zoned is true, else return. // Throw error if annotation does not exist and zoned is true, else return.
let annotation_check = cursor.check_or(false, is_annotation_open); let annotation_check = cursor.check_or(false, is_annotation_open);
if !annotation_check { if !annotation_check {
if zoned { if flags.contains(DateTimeFlags::ZONED) {
return Err(Error::expected( return Err(TemporalError::syntax()
["TimeZoneAnnotation".into()], .with_message("ZonedDateTime must have a TimeZoneAnnotation."));
"No Annotation",
Span::new(
Position::new(1, cursor.pos() + 1),
Position::new(1, cursor.pos() + 1),
),
"iso8601 grammar",
));
} }
return Ok(IsoParseRecord { return Ok(IsoParseRecord {
@ -84,7 +88,8 @@ pub(crate) fn parse_annotated_date_time(
tz = tz_info; tz = tz_info;
} }
let annotation_set = annotations::parse_annotation_set(zoned, cursor)?; let annotation_set =
annotations::parse_annotation_set(flags.contains(DateTimeFlags::ZONED), cursor)?;
if let Some(annotated_tz) = annotation_set.tz { if let Some(annotated_tz) = annotation_set.tz {
tz = annotated_tz.tz; tz = annotated_tz.tz;
@ -108,17 +113,14 @@ pub(crate) fn parse_annotated_date_time(
fn parse_date_time( fn parse_date_time(
time_required: bool, time_required: bool,
utc_required: bool, utc_required: bool,
cursor: &mut IsoCursor, cursor: &mut Cursor,
) -> ParseResult<DateTimeRecord> { ) -> TemporalResult<DateTimeRecord> {
let date = parse_date(cursor)?; let date = parse_date(cursor)?;
// If there is no `DateTimeSeparator`, return date early. // If there is no `DateTimeSeparator`, return date early.
if !cursor.check_or(false, is_date_time_separator) { if !cursor.check_or(false, is_date_time_separator) {
if time_required { if time_required {
return Err(Error::general( return Err(TemporalError::syntax().with_message("Missing a required Time target."));
"Missing a required TimeSpec.",
Position::new(1, cursor.pos() + 1),
));
} }
return Ok(DateTimeRecord { return Ok(DateTimeRecord {
@ -139,10 +141,7 @@ fn parse_date_time(
Some(time_zone::parse_date_time_utc(cursor)?) Some(time_zone::parse_date_time_utc(cursor)?)
} else { } else {
if utc_required { if utc_required {
return Err(Error::general( return Err(TemporalError::syntax().with_message("DateTimeUTCOffset is required."));
"DateTimeUTCOffset is required.",
Position::new(1, cursor.pos() + 1),
));
} }
None None
}; };
@ -155,9 +154,11 @@ fn parse_date_time(
} }
/// Parses `Date` record. /// Parses `Date` record.
fn parse_date(cursor: &mut IsoCursor) -> ParseResult<DateRecord> { fn parse_date(cursor: &mut Cursor) -> TemporalResult<DateRecord> {
let year = parse_date_year(cursor)?; let year = parse_date_year(cursor)?;
let divided = cursor.check(is_hyphen).ok_or_else(|| Error::AbruptEnd)?; let divided = cursor
.check(is_hyphen)
.ok_or_else(TemporalError::abrupt_end)?;
if divided { if divided {
cursor.advance(); cursor.advance();
@ -167,11 +168,7 @@ fn parse_date(cursor: &mut IsoCursor) -> ParseResult<DateRecord> {
if cursor.check_or(false, is_hyphen) { if cursor.check_or(false, is_hyphen) {
if !divided { if !divided {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Invalid date separator"));
"Invalid date separator",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance(); cursor.advance();
} }
@ -182,8 +179,8 @@ fn parse_date(cursor: &mut IsoCursor) -> ParseResult<DateRecord> {
} }
/// Determines if the string can be parsed as a `DateSpecYearMonth`. /// Determines if the string can be parsed as a `DateSpecYearMonth`.
pub(crate) fn peek_year_month(cursor: &IsoCursor) -> ParseResult<bool> { pub(crate) fn peek_year_month(cursor: &Cursor) -> TemporalResult<bool> {
let mut ym_peek = if is_sign(cursor.peek().ok_or_else(|| Error::AbruptEnd)?) { let mut ym_peek = if is_sign(cursor.peek().ok_or_else(TemporalError::abrupt_end)?) {
7 7
} else { } else {
4 4
@ -192,7 +189,7 @@ pub(crate) fn peek_year_month(cursor: &IsoCursor) -> ParseResult<bool> {
if cursor if cursor
.peek_n(ym_peek) .peek_n(ym_peek)
.map(is_hyphen) .map(is_hyphen)
.ok_or_else(|| Error::AbruptEnd)? .ok_or_else(TemporalError::abrupt_end)?
{ {
ym_peek += 1; ym_peek += 1;
} }
@ -207,7 +204,7 @@ pub(crate) fn peek_year_month(cursor: &IsoCursor) -> ParseResult<bool> {
} }
/// Parses a `DateSpecYearMonth` /// Parses a `DateSpecYearMonth`
pub(crate) fn parse_year_month(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)> { pub(crate) fn parse_year_month(cursor: &mut Cursor) -> TemporalResult<(i32, i32)> {
let year = parse_date_year(cursor)?; let year = parse_date_year(cursor)?;
if cursor.check_or(false, is_hyphen) { if cursor.check_or(false, is_hyphen) {
@ -220,11 +217,11 @@ pub(crate) fn parse_year_month(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)
} }
/// Determines if the string can be parsed as a `DateSpecYearMonth`. /// Determines if the string can be parsed as a `DateSpecYearMonth`.
pub(crate) fn peek_month_day(cursor: &IsoCursor) -> ParseResult<bool> { pub(crate) fn peek_month_day(cursor: &Cursor) -> TemporalResult<bool> {
let mut md_peek = if cursor let mut md_peek = if cursor
.peek_n(1) .peek_n(1)
.map(is_hyphen) .map(is_hyphen)
.ok_or_else(|| Error::AbruptEnd)? .ok_or_else(TemporalError::abrupt_end)?
{ {
4 4
} else { } else {
@ -234,7 +231,7 @@ pub(crate) fn peek_month_day(cursor: &IsoCursor) -> ParseResult<bool> {
if cursor if cursor
.peek_n(md_peek) .peek_n(md_peek)
.map(is_hyphen) .map(is_hyphen)
.ok_or_else(|| Error::AbruptEnd)? .ok_or_else(TemporalError::abrupt_end)?
{ {
md_peek += 1; md_peek += 1;
} }
@ -249,21 +246,19 @@ pub(crate) fn peek_month_day(cursor: &IsoCursor) -> ParseResult<bool> {
} }
/// Parses a `DateSpecMonthDay` /// Parses a `DateSpecMonthDay`
pub(crate) fn parse_month_day(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)> { pub(crate) fn parse_month_day(cursor: &mut Cursor) -> TemporalResult<(i32, i32)> {
let dash_one = cursor.check(is_hyphen).ok_or_else(|| Error::AbruptEnd)?; let dash_one = cursor
.check(is_hyphen)
.ok_or_else(TemporalError::abrupt_end)?;
let dash_two = cursor let dash_two = cursor
.peek_n(1) .peek_n(1)
.map(is_hyphen) .map(is_hyphen)
.ok_or_else(|| Error::AbruptEnd)?; .ok_or_else(TemporalError::abrupt_end)?;
if dash_two && dash_one { if dash_two && dash_one {
cursor.advance_n(2); cursor.advance_n(2);
} else if dash_two && !dash_one { } else if dash_two && !dash_one {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("MonthDay requires two dashes"));
"MonthDay requires two dashes",
Position::new(1, cursor.pos()),
)
.into());
} }
let month = parse_date_month(cursor)?; let month = parse_date_month(cursor)?;
@ -278,8 +273,8 @@ pub(crate) fn parse_month_day(cursor: &mut IsoCursor) -> ParseResult<(i32, i32)>
// ==== Unit Parsers ==== // ==== Unit Parsers ====
fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult<i32> { fn parse_date_year(cursor: &mut Cursor) -> TemporalResult<i32> {
if is_sign(cursor.peek().ok_or_else(|| Error::AbruptEnd)?) { if is_sign(cursor.peek().ok_or_else(TemporalError::abrupt_end)?) {
let year_start = cursor.pos(); let year_start = cursor.pos();
let sign = if cursor.check_or(false, |ch| ch == '+') { let sign = if cursor.check_or(false, |ch| ch == '+') {
1 1
@ -290,12 +285,9 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult<i32> {
cursor.advance(); cursor.advance();
for _ in 0..6 { for _ in 0..6 {
let year_digit = cursor.peek().ok_or_else(|| Error::AbruptEnd)?; let year_digit = cursor.peek().ok_or_else(TemporalError::abrupt_end)?;
if !year_digit.is_ascii_digit() { if !year_digit.is_ascii_digit() {
return Err(Error::lex(LexError::syntax( return Err(TemporalError::syntax().with_message("DateYear must contain digit"));
"DateYear must contain digit",
Position::new(1, cursor.pos() + 1),
)));
} }
cursor.advance(); cursor.advance();
} }
@ -303,16 +295,13 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult<i32> {
let year_string = cursor.slice(year_start + 1, cursor.pos()); let year_string = cursor.slice(year_start + 1, cursor.pos());
let year_value = year_string let year_value = year_string
.parse::<i32>() .parse::<i32>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, year_start + 1)))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
// 13.30.1 Static Semantics: Early Errors // 13.30.1 Static Semantics: Early Errors
// //
// It is a Syntax Error if DateYear is "-000000" or "−000000" (U+2212 MINUS SIGN followed by 000000). // It is a Syntax Error if DateYear is "-000000" or "−000000" (U+2212 MINUS SIGN followed by 000000).
if sign == -1 && year_value == 0 { if sign == -1 && year_value == 0 {
return Err(Error::lex(LexError::syntax( return Err(TemporalError::syntax().with_message("Cannot have negative 0 years."));
"Cannot have negative 0 years.",
Position::new(1, year_start + 1),
)));
} }
return Ok(sign * year_value); return Ok(sign * year_value);
@ -321,13 +310,9 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult<i32> {
let year_start = cursor.pos(); let year_start = cursor.pos();
for _ in 0..4 { for _ in 0..4 {
let year_digit = cursor.peek().ok_or_else(|| Error::AbruptEnd)?; let year_digit = cursor.peek().ok_or_else(TemporalError::abrupt_end)?;
if !year_digit.is_ascii_digit() { if !year_digit.is_ascii_digit() {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("DateYear must contain digit"));
"DateYear must contain digit",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance(); cursor.advance();
} }
@ -335,38 +320,30 @@ fn parse_date_year(cursor: &mut IsoCursor) -> ParseResult<i32> {
let year_string = cursor.slice(year_start, cursor.pos()); let year_string = cursor.slice(year_start, cursor.pos());
let year_value = year_string let year_value = year_string
.parse::<i32>() .parse::<i32>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos() + 1)))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
Ok(year_value) Ok(year_value)
} }
fn parse_date_month(cursor: &mut IsoCursor) -> ParseResult<i32> { fn parse_date_month(cursor: &mut Cursor) -> TemporalResult<i32> {
let month_value = cursor let month_value = cursor
.slice(cursor.pos(), cursor.pos() + 2) .slice(cursor.pos(), cursor.pos() + 2)
.parse::<i32>() .parse::<i32>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos() + 1)))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
if !(1..=12).contains(&month_value) { if !(1..=12).contains(&month_value) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("DateMonth must be in a range of 1-12"));
"DateMonth must be in a range of 1-12",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance_n(2); cursor.advance_n(2);
Ok(month_value) Ok(month_value)
} }
fn parse_date_day(cursor: &mut IsoCursor) -> ParseResult<i32> { fn parse_date_day(cursor: &mut Cursor) -> TemporalResult<i32> {
let day_value = cursor let day_value = cursor
.slice(cursor.pos(), cursor.pos() + 2) .slice(cursor.pos(), cursor.pos() + 2)
.parse::<i32>() .parse::<i32>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos())))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
if !(1..=31).contains(&day_value) { if !(1..=31).contains(&day_value) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("DateDay must be in a range of 1-31"));
"DateDay must be in a range of 1-31",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance_n(2); cursor.advance_n(2);
Ok(day_value) Ok(day_value)

107
boa_parser/src/temporal/duration.rs → boa_temporal/src/parser/duration.rs

@ -1,16 +1,14 @@
use boa_ast::Position;
use crate::{ use crate::{
error::{Error, ParseResult}, parser::{
temporal::{
grammar::{ grammar::{
is_day_designator, is_decimal_separator, is_duration_designator, is_hour_designator, is_day_designator, is_decimal_separator, is_duration_designator, is_hour_designator,
is_minute_designator, is_month_designator, is_second_designator, is_sign, is_minute_designator, is_month_designator, is_second_designator, is_sign,
is_time_designator, is_week_designator, is_year_designator, is_time_designator, is_week_designator, is_year_designator,
}, },
time::parse_fraction, time::parse_fraction,
IsoCursor, Cursor,
}, },
TemporalError, TemporalResult,
}; };
/// A ISO8601 `DurationRecord` Parse Node. /// A ISO8601 `DurationRecord` Parse Node.
@ -54,8 +52,11 @@ pub(crate) struct TimeDuration {
pub(crate) fseconds: f64, pub(crate) fseconds: f64,
} }
pub(crate) fn parse_duration(cursor: &mut IsoCursor) -> ParseResult<DurationParseRecord> { pub(crate) fn parse_duration(cursor: &mut Cursor) -> TemporalResult<DurationParseRecord> {
let sign = if cursor.check(is_sign).ok_or_else(|| Error::AbruptEnd)? { let sign = if cursor
.check(is_sign)
.ok_or_else(TemporalError::abrupt_end)?
{
let sign = cursor.check_or(false, |ch| ch == '+'); let sign = cursor.check_or(false, |ch| ch == '+');
cursor.advance(); cursor.advance();
sign sign
@ -65,12 +66,11 @@ pub(crate) fn parse_duration(cursor: &mut IsoCursor) -> ParseResult<DurationPars
if !cursor if !cursor
.check(is_duration_designator) .check(is_duration_designator)
.ok_or_else(|| Error::AbruptEnd)? .ok_or_else(TemporalError::abrupt_end)?
{ {
return Err(Error::general( return Err(
"DurationString missing DurationDesignator.", TemporalError::syntax().with_message("DurationString missing DurationDesignator.")
Position::new(1, cursor.pos() + 1), );
));
} }
cursor.advance(); cursor.advance();
@ -89,10 +89,7 @@ pub(crate) fn parse_duration(cursor: &mut IsoCursor) -> ParseResult<DurationPars
}; };
if cursor.peek().is_some() { if cursor.peek().is_some() {
return Err(Error::general( return Err(TemporalError::syntax().with_message("Unrecognized value in DurationString."));
"Unrecognized value in DurationString.",
Position::new(1, cursor.pos()),
));
} }
Ok(DurationParseRecord { Ok(DurationParseRecord {
@ -111,7 +108,7 @@ enum DateUnit {
Day, Day,
} }
pub(crate) fn parse_date_duration(cursor: &mut IsoCursor) -> ParseResult<DateDuration> { pub(crate) fn parse_date_duration(cursor: &mut Cursor) -> TemporalResult<DateDuration> {
let mut date = DateDuration::default(); let mut date = DateDuration::default();
let mut previous_unit = DateUnit::None; let mut previous_unit = DateUnit::None;
@ -125,52 +122,46 @@ pub(crate) fn parse_date_duration(cursor: &mut IsoCursor) -> ParseResult<DateDur
let value = cursor let value = cursor
.slice(digit_start, cursor.pos()) .slice(digit_start, cursor.pos())
.parse::<i32>() .parse::<i32>()
.map_err(|err| { .map_err(|err| TemporalError::syntax().with_message(err.to_string()))?;
Error::general(err.to_string(), Position::new(digit_start, cursor.pos()))
})?;
match cursor.peek() { match cursor.peek() {
Some(ch) if is_year_designator(ch) => { Some(ch) if is_year_designator(ch) => {
if previous_unit > DateUnit::Year { if previous_unit > DateUnit::Year {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
date.years = value; date.years = value;
previous_unit = DateUnit::Year; previous_unit = DateUnit::Year;
} }
Some(ch) if is_month_designator(ch) => { Some(ch) if is_month_designator(ch) => {
if previous_unit > DateUnit::Month { if previous_unit > DateUnit::Month {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
date.months = value; date.months = value;
previous_unit = DateUnit::Month; previous_unit = DateUnit::Month;
} }
Some(ch) if is_week_designator(ch) => { Some(ch) if is_week_designator(ch) => {
if previous_unit > DateUnit::Week { if previous_unit > DateUnit::Week {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
date.weeks = value; date.weeks = value;
previous_unit = DateUnit::Week; previous_unit = DateUnit::Week;
} }
Some(ch) if is_day_designator(ch) => { Some(ch) if is_day_designator(ch) => {
if previous_unit > DateUnit::Day { if previous_unit > DateUnit::Day {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
date.days = value; date.days = value;
previous_unit = DateUnit::Day; previous_unit = DateUnit::Day;
} }
Some(_) | None => return Err(Error::AbruptEnd), Some(_) | None => return Err(TemporalError::abrupt_end()),
} }
cursor.advance(); cursor.advance();
@ -187,14 +178,13 @@ enum TimeUnit {
Second, Second,
} }
pub(crate) fn parse_time_duration(cursor: &mut IsoCursor) -> ParseResult<TimeDuration> { pub(crate) fn parse_time_duration(cursor: &mut Cursor) -> TemporalResult<TimeDuration> {
let mut time = TimeDuration::default(); let mut time = TimeDuration::default();
if !cursor.check_or(false, |ch| ch.is_ascii()) { if !cursor.check_or(false, |ch| ch.is_ascii()) {
return Err(Error::general( return Err(
"No time values provided after TimeDesignator.", TemporalError::syntax().with_message("No time values provided after TimeDesignator.")
Position::new(1, cursor.pos()), );
));
} }
let mut previous_unit = TimeUnit::None; let mut previous_unit = TimeUnit::None;
@ -209,9 +199,7 @@ pub(crate) fn parse_time_duration(cursor: &mut IsoCursor) -> ParseResult<TimeDur
let value = cursor let value = cursor
.slice(digit_start, cursor.pos()) .slice(digit_start, cursor.pos())
.parse::<i32>() .parse::<i32>()
.map_err(|err| { .map_err(|err| TemporalError::syntax().with_message(err.to_string()))?;
Error::general(err.to_string(), Position::new(digit_start, cursor.pos()))
})?;
let fraction = if cursor.check_or(false, is_decimal_separator) { let fraction = if cursor.check_or(false, is_decimal_separator) {
fraction_present = true; fraction_present = true;
@ -223,10 +211,9 @@ pub(crate) fn parse_time_duration(cursor: &mut IsoCursor) -> ParseResult<TimeDur
match cursor.peek() { match cursor.peek() {
Some(ch) if is_hour_designator(ch) => { Some(ch) if is_hour_designator(ch) => {
if previous_unit > TimeUnit::Hour { if previous_unit > TimeUnit::Hour {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
time.hours = value; time.hours = value;
time.fhours = fraction; time.fhours = fraction;
@ -234,10 +221,9 @@ pub(crate) fn parse_time_duration(cursor: &mut IsoCursor) -> ParseResult<TimeDur
} }
Some(ch) if is_minute_designator(ch) => { Some(ch) if is_minute_designator(ch) => {
if previous_unit > TimeUnit::Minute { if previous_unit > TimeUnit::Minute {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
time.minutes = value; time.minutes = value;
time.fminutes = fraction; time.fminutes = fraction;
@ -245,26 +231,23 @@ pub(crate) fn parse_time_duration(cursor: &mut IsoCursor) -> ParseResult<TimeDur
} }
Some(ch) if is_second_designator(ch) => { Some(ch) if is_second_designator(ch) => {
if previous_unit > TimeUnit::Second { if previous_unit > TimeUnit::Second {
return Err(Error::general( return Err(
"Not a valid DateDuration order", TemporalError::syntax().with_message("Not a valid DateDuration order")
Position::new(1, cursor.pos()), );
));
} }
time.seconds = value; time.seconds = value;
time.fseconds = fraction; time.fseconds = fraction;
previous_unit = TimeUnit::Second; previous_unit = TimeUnit::Second;
} }
Some(_) | None => return Err(Error::AbruptEnd), Some(_) | None => return Err(TemporalError::abrupt_end()),
} }
cursor.advance(); cursor.advance();
if fraction_present { if fraction_present {
if cursor.check_or(false, |ch| ch.is_ascii_digit()) { if cursor.check_or(false, |ch| ch.is_ascii_digit()) {
return Err(Error::general( return Err(TemporalError::syntax()
"Invalid TimeDuration continuation after FractionPart.", .with_message("Invalid TimeDuration continuation after FractionPart."));
Position::new(1, cursor.pos()),
));
} }
break; break;

0
boa_parser/src/temporal/grammar.rs → boa_temporal/src/parser/grammar.rs

151
boa_parser/src/temporal/mod.rs → boa_temporal/src/parser/mod.rs

@ -1,25 +1,40 @@
//! Implementation of Iso8601 grammar lexing/parsing //! Implementation of Iso8601 grammar lexing/parsing
use crate::error::ParseResult; use crate::TemporalResult;
use date_time::DateRecord;
use nodes::{IsoDate, IsoDateTime, IsoTime, TimeZone};
use time::TimeSpec;
mod annotations; mod annotations;
mod date_time; pub(crate) mod date_time;
mod duration; pub(crate) mod duration;
mod grammar; mod grammar;
mod nodes;
mod time; mod time;
mod time_zone; pub(crate) mod time_zone;
use boa_ast::temporal::{IsoDate, IsoDateTime, IsoDuration, IsoTime, TimeZone};
use date_time::DateRecord; use self::date_time::DateTimeFlags;
use time::TimeSpec;
#[cfg(feature = "experimental")]
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
// TODO: optimize where possible. // TODO: optimize where possible.
/// A utility function for parsing a `DateTime` string
pub(crate) fn parse_date_time(target: &str) -> TemporalResult<IsoParseRecord> {
date_time::parse_annotated_date_time(DateTimeFlags::empty(), &mut Cursor::new(target))
}
/// A utility function for parsing an `Instant` string
#[allow(unused)]
pub(crate) fn parse_instant(target: &str) -> TemporalResult<IsoParseRecord> {
date_time::parse_annotated_date_time(
DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ,
&mut Cursor::new(target),
)
}
/// An `IsoParseRecord` is an intermediary record returned by ISO parsing functions. /// An `IsoParseRecord` is an intermediary record returned by ISO parsing functions.
/// ///
/// `IsoParseRecord` is converted into the ISO AST Nodes. /// `IsoParseRecord` is converted into the ISO AST Nodes.
@ -35,40 +50,7 @@ pub(crate) struct IsoParseRecord {
pub(crate) calendar: Option<String>, pub(crate) calendar: Option<String>,
} }
/// Parse a [`TemporalDateTimeString`][proposal]. // TODO: Phase out the below and integrate parsing with Temporal components.
///
/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
#[derive(Debug, Clone, Copy)]
pub struct TemporalDateTimeString;
impl TemporalDateTimeString {
/// Parses a targeted string as a `DateTime`.
///
/// # Errors
///
/// The parse will error if the provided target is not valid
/// Iso8601 grammar.
pub fn parse(zoned: bool, cursor: &mut IsoCursor) -> ParseResult<IsoDateTime> {
let parse_record = date_time::parse_annotated_date_time(zoned, false, false, cursor)?;
let date = IsoDate {
year: parse_record.date.year,
month: parse_record.date.month,
day: parse_record.date.day,
calendar: parse_record.calendar,
};
let time = parse_record.time.map_or_else(IsoTime::default, |time| {
IsoTime::from_components(time.hour, time.minute, time.second, time.fraction)
});
Ok(IsoDateTime {
date,
time,
tz: parse_record.tz,
})
}
}
/// Parse a [`TemporalTimeZoneString`][proposal]. /// Parse a [`TemporalTimeZoneString`][proposal].
/// ///
@ -83,7 +65,7 @@ impl TemporalTimeZoneString {
/// ///
/// The parse will error if the provided target is not valid /// The parse will error if the provided target is not valid
/// Iso8601 grammar. /// Iso8601 grammar.
pub fn parse(cursor: &mut IsoCursor) -> ParseResult<TimeZone> { pub fn parse(cursor: &mut Cursor) -> TemporalResult<TimeZone> {
time_zone::parse_time_zone(cursor) time_zone::parse_time_zone(cursor)
} }
} }
@ -101,7 +83,8 @@ impl TemporalYearMonthString {
/// ///
/// The parse will error if the provided target is not valid /// The parse will error if the provided target is not valid
/// Iso8601 grammar. /// Iso8601 grammar.
pub fn parse(cursor: &mut IsoCursor) -> ParseResult<IsoDate> { pub fn parse(cursor: &mut Cursor) -> TemporalResult<IsoDate> {
// TODO: Remove peek in favor of AnnotatedDateTime flag.
if date_time::peek_year_month(cursor)? { if date_time::peek_year_month(cursor)? {
let ym = date_time::parse_year_month(cursor)?; let ym = date_time::parse_year_month(cursor)?;
@ -120,7 +103,7 @@ impl TemporalYearMonthString {
}); });
} }
let parse_record = date_time::parse_annotated_date_time(false, false, false, cursor)?; let parse_record = date_time::parse_annotated_date_time(DateTimeFlags::empty(), cursor)?;
Ok(IsoDate { Ok(IsoDate {
year: parse_record.date.year, year: parse_record.date.year,
@ -144,7 +127,8 @@ impl TemporalMonthDayString {
/// ///
/// The parse will error if the provided target is not valid /// The parse will error if the provided target is not valid
/// Iso8601 grammar. /// Iso8601 grammar.
pub fn parse(cursor: &mut IsoCursor) -> ParseResult<IsoDate> { pub fn parse(cursor: &mut Cursor) -> TemporalResult<IsoDate> {
// TODO: Remove peek in favor of AnnotatedDateTime flag.
if date_time::peek_month_day(cursor)? { if date_time::peek_month_day(cursor)? {
let md = date_time::parse_month_day(cursor)?; let md = date_time::parse_month_day(cursor)?;
@ -163,7 +147,7 @@ impl TemporalMonthDayString {
}); });
} }
let parse_record = date_time::parse_annotated_date_time(false, false, false, cursor)?; let parse_record = date_time::parse_annotated_date_time(DateTimeFlags::empty(), cursor)?;
Ok(IsoDate { Ok(IsoDate {
year: parse_record.date.year, year: parse_record.date.year,
@ -187,8 +171,11 @@ impl TemporalInstantString {
/// ///
/// The parse will error if the provided target is not valid /// The parse will error if the provided target is not valid
/// Iso8601 grammar. /// Iso8601 grammar.
pub fn parse(cursor: &mut IsoCursor) -> ParseResult<IsoDateTime> { pub fn parse(cursor: &mut Cursor) -> TemporalResult<IsoDateTime> {
let parse_record = date_time::parse_annotated_date_time(false, true, true, cursor)?; let parse_record = date_time::parse_annotated_date_time(
DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ,
cursor,
)?;
let date = IsoDate { let date = IsoDate {
year: parse_record.date.year, year: parse_record.date.year,
@ -209,74 +196,16 @@ impl TemporalInstantString {
} }
} }
// TODO: implement TemporalTimeString.
/// Parser for a [`TemporalDurationString`][proposal].
///
/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalDurationString
#[derive(Debug, Clone, Copy)]
pub struct TemporalDurationString;
impl TemporalDurationString {
/// Parses a targeted string as a `Duration`.
///
/// # Errors
///
/// The parse will error if the provided target is not valid
/// Iso8601 grammar.
pub fn parse(cursor: &mut IsoCursor) -> ParseResult<IsoDuration> {
let parse_record = duration::parse_duration(cursor)?;
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 { 1 } else { -1 };
Ok(IsoDuration {
years: parse_record.date.years * sign,
months: parse_record.date.months * sign,
weeks: parse_record.date.weeks * sign,
days: parse_record.date.days * sign,
hours: parse_record.time.hours * sign,
minutes: minutes.floor() * f64::from(sign),
seconds: seconds.floor() * f64::from(sign),
milliseconds: milliseconds.floor() * f64::from(sign),
microseconds: micro.floor() * f64::from(sign),
nanoseconds: nano.floor() * f64::from(sign),
})
}
}
// ==== Mini cursor implementation for Iso8601 targets ==== // ==== Mini cursor implementation for Iso8601 targets ====
/// `IsoCursor` is a small cursor implementation for parsing Iso8601 grammar. /// `Cursor` is a small cursor implementation for parsing Iso8601 grammar.
#[derive(Debug)] #[derive(Debug)]
pub struct IsoCursor { pub struct Cursor {
pos: u32, pos: u32,
source: Vec<char>, source: Vec<char>,
} }
impl IsoCursor { impl Cursor {
/// Create a new cursor from a source `String` value. /// Create a new cursor from a source `String` value.
#[must_use] #[must_use]
pub fn new(source: &str) -> Self { pub fn new(source: &str) -> Self {

33
boa_ast/src/temporal/mod.rs → boa_temporal/src/parser/nodes.rs

@ -1,5 +1,7 @@
//! AST nodes for Temporal's implementation of ISO8601 grammar. //! AST nodes for Temporal's implementation of ISO8601 grammar.
// TODO: Slowly remove the below nodes in favor of Temporal components.
/// An ISO Date Node consisting of non-validated date fields and calendar value. /// An ISO Date Node consisting of non-validated date fields and calendar value.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct IsoDate { pub struct IsoDate {
@ -37,9 +39,9 @@ impl IsoTime {
pub fn from_components(hour: u8, minute: u8, second: u8, fraction: f64) -> Self { pub fn from_components(hour: u8, minute: u8, second: u8, fraction: f64) -> Self {
// Note: Precision on nanoseconds drifts, so opting for round over floor or ceil for now. // Note: Precision on nanoseconds drifts, so opting for round over floor or ceil for now.
// e.g. 0.329402834 becomes 329.402833.999 // e.g. 0.329402834 becomes 329.402833.999
let millisecond = fraction * 1000.0; let millisecond = fraction * 1000f64;
let micros = millisecond.rem_euclid(1.0) * 1000.0; let micros = millisecond.rem_euclid(1f64) * 1000f64;
let nanos = micros.rem_euclid(1.0) * 1000.0; let nanos = micros.rem_euclid(1f64) * 1000f64;
Self { Self {
hour, hour,
@ -86,28 +88,3 @@ pub struct UTCOffset {
/// Any sub second components of the `UTCOffset` /// Any sub second components of the `UTCOffset`
pub fraction: f64, pub fraction: f64,
} }
/// An `IsoDuration` Node output by the ISO parser.
#[derive(Debug, Default, Clone, Copy)]
pub struct IsoDuration {
/// Years value.
pub years: i32,
/// Months value.
pub months: i32,
/// Weeks value.
pub weeks: i32,
/// Days value.
pub days: i32,
/// Hours value.
pub hours: i32,
/// Minutes value.
pub minutes: f64,
/// Seconds value.
pub seconds: f64,
/// Milliseconds value.
pub milliseconds: f64,
/// Microseconds value.
pub microseconds: f64,
/// Nanoseconds value.
pub nanoseconds: f64,
}

92
boa_parser/src/temporal/tests.rs → boa_temporal/src/parser/tests.rs

@ -1,6 +1,12 @@
use super::{ use std::str::FromStr;
IsoCursor, TemporalDateTimeString, TemporalDurationString, TemporalInstantString,
TemporalMonthDayString, TemporalYearMonthString, use crate::{
datetime::DateTime,
duration::Duration,
parser::{
parse_date_time, Cursor, TemporalInstantString, TemporalMonthDayString,
TemporalYearMonthString,
},
}; };
#[test] #[test]
@ -8,17 +14,19 @@ fn temporal_parser_basic() {
let basic = "20201108"; let basic = "20201108";
let basic_separated = "2020-11-08"; let basic_separated = "2020-11-08";
let basic_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic)).unwrap(); let basic_result = basic.parse::<DateTime>().unwrap();
let sep_result = let sep_result = basic_separated.parse::<DateTime>().unwrap();
TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic_separated)).unwrap();
assert_eq!(basic_result.date.year, 2020); assert_eq!(basic_result.iso_date().year(), 2020);
assert_eq!(basic_result.date.month, 11); assert_eq!(basic_result.iso_date().month(), 11);
assert_eq!(basic_result.date.day, 8); assert_eq!(basic_result.iso_date().day(), 8);
assert_eq!(basic_result.date.year, sep_result.date.year); assert_eq!(basic_result.iso_date().year(), sep_result.iso_date().year());
assert_eq!(basic_result.date.month, sep_result.date.month); assert_eq!(
assert_eq!(basic_result.date.day, sep_result.date.day); basic_result.iso_date().month(),
sep_result.iso_date().month()
);
assert_eq!(basic_result.iso_date().day(), sep_result.iso_date().day());
} }
#[test] #[test]
@ -28,9 +36,9 @@ fn temporal_date_time_max() {
let date_time = let date_time =
"+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]"; "+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]";
let result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(date_time)).unwrap(); let result = date_time.parse::<DateTime>().unwrap();
let time_results = &result.time; let time_results = result.iso_time();
assert_eq!(time_results.hour, 12); assert_eq!(time_results.hour, 12);
assert_eq!(time_results.minute, 28); assert_eq!(time_results.minute, 28);
@ -38,17 +46,6 @@ fn temporal_date_time_max() {
assert_eq!(time_results.millisecond, 329); assert_eq!(time_results.millisecond, 329);
assert_eq!(time_results.microsecond, 402); assert_eq!(time_results.microsecond, 402);
assert_eq!(time_results.nanosecond, 834); assert_eq!(time_results.nanosecond, 834);
let tz = &result.tz.unwrap();
// OffsetSubMinute is Empty when TimeZoneIdentifier is present.
assert!(&tz.offset.is_none());
let tz_name = &tz.name.clone().unwrap();
assert_eq!(tz_name, "America/Argentina/ComodRivadavia");
assert_eq!(&result.date.calendar, &Some("iso8601".to_string()));
} }
#[test] #[test]
@ -56,10 +53,10 @@ fn temporal_year_parsing() {
let long = "+002020-11-08"; let long = "+002020-11-08";
let bad_year = "-000000-11-08"; let bad_year = "-000000-11-08";
let result_good = TemporalDateTimeString::parse(false, &mut IsoCursor::new(long)).unwrap(); let result_good = long.parse::<DateTime>().unwrap();
assert_eq!(result_good.date.year, 2020); assert_eq!(result_good.iso_date().year(), 2020);
let err_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(bad_year)); let err_result = bad_year.parse::<DateTime>();
assert!(err_result.is_err()); assert!(err_result.is_err());
} }
@ -68,19 +65,19 @@ fn temporal_annotated_date_time() {
let basic = "2020-11-08[America/Argentina/ComodRivadavia][u-ca=iso8601][foo=bar]"; let basic = "2020-11-08[America/Argentina/ComodRivadavia][u-ca=iso8601][foo=bar]";
let omitted = "+0020201108[u-ca=iso8601][f-1a2b=a0sa-2l4s]"; let omitted = "+0020201108[u-ca=iso8601][f-1a2b=a0sa-2l4s]";
let result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(basic)).unwrap(); let result = parse_date_time(basic).unwrap();
let tz = &result.tz.unwrap().name.unwrap(); let tz = &result.tz.unwrap().name.unwrap();
assert_eq!(tz, "America/Argentina/ComodRivadavia"); assert_eq!(tz, "America/Argentina/ComodRivadavia");
assert_eq!(&result.date.calendar, &Some("iso8601".to_string())); assert_eq!(&result.calendar, &Some("iso8601".to_string()));
let omit_result = TemporalDateTimeString::parse(false, &mut IsoCursor::new(omitted)).unwrap(); let omit_result = parse_date_time(omitted).unwrap();
assert!(&omit_result.tz.is_none()); assert!(&omit_result.tz.is_none());
assert_eq!(&omit_result.date.calendar, &Some("iso8601".to_string())); assert_eq!(&omit_result.calendar, &Some("iso8601".to_string()));
} }
#[test] #[test]
@ -93,7 +90,7 @@ fn temporal_year_month() {
]; ];
for ym in possible_year_months { for ym in possible_year_months {
let result = TemporalYearMonthString::parse(&mut IsoCursor::new(ym)).unwrap(); let result = TemporalYearMonthString::parse(&mut Cursor::new(ym)).unwrap();
assert_eq!(result.year, 2020); assert_eq!(result.year, 2020);
assert_eq!(result.month, 11); assert_eq!(result.month, 11);
@ -109,7 +106,7 @@ fn temporal_month_day() {
let possible_month_day = ["11-07", "1107[+04:00]", "--11-07", "--1107[+04:00]"]; let possible_month_day = ["11-07", "1107[+04:00]", "--11-07", "--1107[+04:00]"];
for md in possible_month_day { for md in possible_month_day {
let result = TemporalMonthDayString::parse(&mut IsoCursor::new(md)).unwrap(); let result = TemporalMonthDayString::parse(&mut Cursor::new(md)).unwrap();
assert_eq!(result.month, 11); assert_eq!(result.month, 11);
assert_eq!(result.day, 7); assert_eq!(result.day, 7);
@ -125,7 +122,7 @@ fn temporal_invalid_annotations() {
]; ];
for invalid in invalid_annotations { for invalid in invalid_annotations {
let err_result = TemporalMonthDayString::parse(&mut IsoCursor::new(invalid)); let err_result = TemporalMonthDayString::parse(&mut Cursor::new(invalid));
assert!(err_result.is_err()); assert!(err_result.is_err());
} }
} }
@ -139,13 +136,14 @@ fn temporal_valid_instant_strings() {
]; ];
for test in instants { for test in instants {
let result = TemporalInstantString::parse(&mut IsoCursor::new(test)); let result = TemporalInstantString::parse(&mut Cursor::new(test));
assert!(result.is_ok()); assert!(result.is_ok());
} }
} }
#[test] #[test]
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
#[allow(clippy::float_cmp)]
fn temporal_duration_parsing() { fn temporal_duration_parsing() {
let durations = [ let durations = [
"p1y1m1dt1h1m1s", "p1y1m1dt1h1m1s",
@ -155,23 +153,21 @@ fn temporal_duration_parsing() {
]; ];
for dur in durations { for dur in durations {
let ok_result = TemporalDurationString::parse(&mut IsoCursor::new(dur)); let ok_result = Duration::from_str(dur);
assert!(ok_result.is_ok()); assert!(ok_result.is_ok());
} }
let sub = durations[2]; let sub_second = durations[2].parse::<Duration>().unwrap();
let sub_second = TemporalDurationString::parse(&mut IsoCursor::new(sub)).unwrap();
assert_eq!(sub_second.milliseconds, -123.0); assert_eq!(sub_second.time().milliseconds(), -123.0);
assert_eq!(sub_second.microseconds, -456.0); assert_eq!(sub_second.time().microseconds(), -456.0);
assert_eq!(sub_second.nanoseconds, -789.0); assert_eq!(sub_second.time().nanoseconds(), -789.0);
let dur = durations[3]; let test_result = durations[3].parse::<Duration>().unwrap();
let test_result = TemporalDurationString::parse(&mut IsoCursor::new(dur)).unwrap();
assert_eq!(test_result.years, -1); assert_eq!(test_result.date().years(), -1f64);
assert_eq!(test_result.weeks, -3); assert_eq!(test_result.date().weeks(), -3f64);
assert_eq!(test_result.minutes, -30.0); assert_eq!(test_result.time().minutes(), -30.0);
} }
#[test] #[test]
@ -184,7 +180,7 @@ fn temporal_invalid_durations() {
]; ];
for test in invalids { for test in invalids {
let err = TemporalDurationString::parse(&mut IsoCursor::new(test)); let err = test.parse::<Duration>();
assert!(err.is_err()); assert!(err.is_err());
} }
} }

48
boa_parser/src/temporal/time.rs → boa_temporal/src/parser/time.rs

@ -2,12 +2,9 @@
use super::{ use super::{
grammar::{is_decimal_separator, is_time_separator}, grammar::{is_decimal_separator, is_time_separator},
IsoCursor, Cursor,
};
use crate::{
error::{Error, ParseResult},
lexer::Error as LexError,
}; };
use crate::{TemporalError, TemporalResult};
/// Parsed Time info /// Parsed Time info
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
@ -22,10 +19,8 @@ pub(crate) struct TimeSpec {
pub(crate) fraction: f64, pub(crate) fraction: f64,
} }
use boa_ast::Position;
/// Parse `TimeSpec` /// Parse `TimeSpec`
pub(crate) fn parse_time_spec(cursor: &mut IsoCursor) -> ParseResult<TimeSpec> { pub(crate) fn parse_time_spec(cursor: &mut Cursor) -> TemporalResult<TimeSpec> {
let hour = parse_hour(cursor)?; let hour = parse_hour(cursor)?;
let mut separator = false; let mut separator = false;
@ -50,9 +45,7 @@ pub(crate) fn parse_time_spec(cursor: &mut IsoCursor) -> ParseResult<TimeSpec> {
if separator && is_time_separator { if separator && is_time_separator {
cursor.advance(); cursor.advance();
} else if is_time_separator { } else if is_time_separator {
return Err( return Err(TemporalError::syntax().with_message("Invalid TimeSeparator"));
LexError::syntax("Invalid TimeSeparator", Position::new(1, cursor.pos())).into(),
);
} }
} else { } else {
return Ok(TimeSpec { return Ok(TimeSpec {
@ -79,17 +72,13 @@ pub(crate) fn parse_time_spec(cursor: &mut IsoCursor) -> ParseResult<TimeSpec> {
}) })
} }
pub(crate) fn parse_hour(cursor: &mut IsoCursor) -> ParseResult<u8> { pub(crate) fn parse_hour(cursor: &mut Cursor) -> TemporalResult<u8> {
let hour_value = cursor let hour_value = cursor
.slice(cursor.pos(), cursor.pos() + 2) .slice(cursor.pos(), cursor.pos() + 2)
.parse::<u8>() .parse::<u8>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos())))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
if !(0..=23).contains(&hour_value) { if !(0..=23).contains(&hour_value) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Hour must be in a range of 0-23"));
"Hour must be in a range of 0-23",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance_n(2); cursor.advance_n(2);
Ok(hour_value) Ok(hour_value)
@ -97,19 +86,15 @@ pub(crate) fn parse_hour(cursor: &mut IsoCursor) -> ParseResult<u8> {
// NOTE: `TimeSecond` is a 60 inclusive `MinuteSecond`. // NOTE: `TimeSecond` is a 60 inclusive `MinuteSecond`.
/// Parse `MinuteSecond` /// Parse `MinuteSecond`
pub(crate) fn parse_minute_second(cursor: &mut IsoCursor, inclusive: bool) -> ParseResult<u8> { pub(crate) fn parse_minute_second(cursor: &mut Cursor, inclusive: bool) -> TemporalResult<u8> {
let min_sec_value = cursor let min_sec_value = cursor
.slice(cursor.pos(), cursor.pos() + 2) .slice(cursor.pos(), cursor.pos() + 2)
.parse::<u8>() .parse::<u8>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos())))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
let valid_range = if inclusive { 0..=60 } else { 0..=59 }; let valid_range = if inclusive { 0..=60 } else { 0..=59 };
if !valid_range.contains(&min_sec_value) { if !valid_range.contains(&min_sec_value) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("MinuteSecond must be in a range of 0-59"));
"MinuteSecond must be in a range of 0-59",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance_n(2); cursor.advance_n(2);
@ -120,27 +105,26 @@ pub(crate) fn parse_minute_second(cursor: &mut IsoCursor, inclusive: bool) -> Pa
/// ///
/// This is primarily used in ISO8601 to add percision past /// This is primarily used in ISO8601 to add percision past
/// a second. /// a second.
pub(crate) fn parse_fraction(cursor: &mut IsoCursor) -> ParseResult<f64> { pub(crate) fn parse_fraction(cursor: &mut Cursor) -> TemporalResult<f64> {
// Decimal is skipped by next call. // Decimal is skipped by next call.
let mut fraction_components = Vec::from(['.']); let mut fraction_components = Vec::from(['.']);
while let Some(ch) = cursor.next() { while let Some(ch) = cursor.next() {
if !ch.is_ascii_digit() { if !ch.is_ascii_digit() {
if fraction_components.len() > 10 { if fraction_components.len() > 10 {
return Err(Error::general( return Err(
"Fraction exceeds 9 DecimalDigits", TemporalError::syntax().with_message("Fraction exceeds 9 DecimalDigits")
Position::new(1, cursor.pos() - 1), );
));
} }
let fraction_value = fraction_components let fraction_value = fraction_components
.iter() .iter()
.collect::<String>() .collect::<String>()
.parse::<f64>() .parse::<f64>()
.map_err(|e| Error::general(e.to_string(), Position::new(1, cursor.pos() - 1)))?; .map_err(|e| TemporalError::syntax().with_message(e.to_string()))?;
return Ok(fraction_value); return Ok(fraction_value);
} }
fraction_components.push(ch); fraction_components.push(ch);
} }
Err(Error::AbruptEnd) Err(TemporalError::abrupt_end())
} }

79
boa_parser/src/temporal/time_zone.rs → boa_temporal/src/parser/time_zone.rs

@ -7,18 +7,11 @@ use super::{
is_decimal_separator, is_sign, is_time_separator, is_tz_char, is_tz_leading_char, is_decimal_separator, is_sign, is_time_separator, is_tz_char, is_tz_leading_char,
is_tz_name_separator, is_utc_designator, is_tz_name_separator, is_utc_designator,
}, },
nodes::{TimeZone, UTCOffset},
time::{parse_fraction, parse_hour, parse_minute_second}, time::{parse_fraction, parse_hour, parse_minute_second},
IsoCursor, Cursor,
};
use crate::{
error::{Error, ParseResult},
lexer::Error as LexError,
};
use boa_ast::{
temporal::{TimeZone, UTCOffset},
Position,
}; };
use crate::{TemporalError, TemporalResult};
/// A `TimeZoneAnnotation`. /// A `TimeZoneAnnotation`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -33,14 +26,14 @@ pub(crate) struct TimeZoneAnnotation {
// ==== Time Zone Annotation Parsing ==== // ==== Time Zone Annotation Parsing ====
pub(crate) fn parse_ambiguous_tz_annotation( pub(crate) fn parse_ambiguous_tz_annotation(
cursor: &mut IsoCursor, cursor: &mut Cursor,
) -> ParseResult<Option<TimeZoneAnnotation>> { ) -> TemporalResult<Option<TimeZoneAnnotation>> {
// Peek position + 1 to check for critical flag. // Peek position + 1 to check for critical flag.
let mut current_peek = 1; let mut current_peek = 1;
let critical = cursor let critical = cursor
.peek_n(current_peek) .peek_n(current_peek)
.map(is_critical_flag) .map(is_critical_flag)
.ok_or_else(|| Error::AbruptEnd)?; .ok_or_else(TemporalError::abrupt_end)?;
// Advance cursor if critical flag present. // Advance cursor if critical flag present.
if critical { if critical {
@ -49,7 +42,7 @@ pub(crate) fn parse_ambiguous_tz_annotation(
let leading_char = cursor let leading_char = cursor
.peek_n(current_peek) .peek_n(current_peek)
.ok_or_else(|| Error::AbruptEnd)?; .ok_or_else(TemporalError::abrupt_end)?;
if is_tz_leading_char(leading_char) || is_sign(leading_char) { if is_tz_leading_char(leading_char) || is_sign(leading_char) {
// Ambigious start values when lowercase alpha that is shared between `TzLeadingChar` and `KeyLeadingChar`. // Ambigious start values when lowercase alpha that is shared between `TzLeadingChar` and `KeyLeadingChar`.
@ -64,16 +57,12 @@ pub(crate) fn parse_ambiguous_tz_annotation(
{ {
return Ok(None); return Ok(None);
} else if is_annotation_close(ch) { } else if is_annotation_close(ch) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Invalid Annotation"));
"Invalid Annotation",
Position::new(1, peek_pos + 1),
)
.into());
} }
peek_pos += 1; peek_pos += 1;
} }
return Err(Error::AbruptEnd); return Err(TemporalError::abrupt_end());
} }
let tz = parse_tz_annotation(cursor)?; let tz = parse_tz_annotation(cursor)?;
return Ok(Some(tz)); return Ok(Some(tz));
@ -83,16 +72,13 @@ pub(crate) fn parse_ambiguous_tz_annotation(
return Ok(None); return Ok(None);
}; };
Err(Error::lex(LexError::syntax( Err(TemporalError::syntax().with_message("Unexpected character in ambiguous annotation."))
"Unexpected character in ambiguous annotation.",
Position::new(1, cursor.pos() + 1),
)))
} }
fn parse_tz_annotation(cursor: &mut IsoCursor) -> ParseResult<TimeZoneAnnotation> { fn parse_tz_annotation(cursor: &mut Cursor) -> TemporalResult<TimeZoneAnnotation> {
debug_assert!(is_annotation_open(cursor.peek().expect("annotation start"))); debug_assert!(is_annotation_open(cursor.peek().expect("annotation start")));
let potential_critical = cursor.next().ok_or_else(|| Error::AbruptEnd)?; let potential_critical = cursor.next().ok_or_else(TemporalError::abrupt_end)?;
let critical = is_critical_flag(potential_critical); let critical = is_critical_flag(potential_critical);
if critical { if critical {
@ -102,11 +88,7 @@ fn parse_tz_annotation(cursor: &mut IsoCursor) -> ParseResult<TimeZoneAnnotation
let tz = parse_time_zone(cursor)?; let tz = parse_time_zone(cursor)?;
if !cursor.check_or(false, is_annotation_close) { if !cursor.check_or(false, is_annotation_close) {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Invalid TimeZoneAnnotation."));
"Invalid TimeZoneAnnotation.",
Position::new(1, cursor.pos() + 1),
)
.into());
} }
cursor.advance(); cursor.advance();
@ -117,10 +99,10 @@ fn parse_tz_annotation(cursor: &mut IsoCursor) -> ParseResult<TimeZoneAnnotation
/// Parses the [`TimeZoneIdentifier`][tz] node. /// Parses the [`TimeZoneIdentifier`][tz] node.
/// ///
/// [tz]: https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier /// [tz]: https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier
pub(crate) fn parse_time_zone(cursor: &mut IsoCursor) -> ParseResult<TimeZone> { pub(crate) fn parse_time_zone(cursor: &mut Cursor) -> TemporalResult<TimeZone> {
let is_iana = cursor let is_iana = cursor
.check(is_tz_leading_char) .check(is_tz_leading_char)
.ok_or_else(|| Error::AbruptEnd)?; .ok_or_else(TemporalError::abrupt_end)?;
let is_offset = cursor.check_or(false, is_sign); let is_offset = cursor.check_or(false, is_sign);
if is_iana { if is_iana {
@ -133,24 +115,17 @@ pub(crate) fn parse_time_zone(cursor: &mut IsoCursor) -> ParseResult<TimeZone> {
}); });
} }
Err(LexError::syntax( Err(TemporalError::syntax().with_message("Invalid leading character for a TimeZoneIdentifier"))
"Invalid leading character for a TimeZoneIdentifier",
Position::new(1, cursor.pos() + 1),
)
.into())
} }
/// Parse a `TimeZoneIANAName` Parse Node /// Parse a `TimeZoneIANAName` Parse Node
fn parse_tz_iana_name(cursor: &mut IsoCursor) -> ParseResult<TimeZone> { fn parse_tz_iana_name(cursor: &mut Cursor) -> TemporalResult<TimeZone> {
let tz_name_start = cursor.pos(); let tz_name_start = cursor.pos();
while let Some(potential_value_char) = cursor.next() { while let Some(potential_value_char) = cursor.next() {
if is_tz_name_separator(potential_value_char) { if is_tz_name_separator(potential_value_char) {
if !cursor.peek_n(1).map_or(false, is_tz_char) { if !cursor.peek_n(1).map_or(false, is_tz_char) {
return Err(LexError::syntax( return Err(TemporalError::syntax()
"Missing TimeZoneIANANameComponent after '/'", .with_message("Missing TimeZoneIANANameComponent after '/'"));
Position::new(1, cursor.pos() + 2),
)
.into());
} }
continue; continue;
} }
@ -164,13 +139,13 @@ fn parse_tz_iana_name(cursor: &mut IsoCursor) -> ParseResult<TimeZone> {
} }
} }
Err(Error::AbruptEnd) Err(TemporalError::abrupt_end())
} }
// ==== Utc Offset Parsing ==== // ==== Utc Offset Parsing ====
/// Parse a full precision `UtcOffset` /// Parse a full precision `UtcOffset`
pub(crate) fn parse_date_time_utc(cursor: &mut IsoCursor) -> ParseResult<TimeZone> { pub(crate) fn parse_date_time_utc(cursor: &mut Cursor) -> TemporalResult<TimeZone> {
if cursor.check_or(false, is_utc_designator) { if cursor.check_or(false, is_utc_designator) {
cursor.advance(); cursor.advance();
return Ok(TimeZone { return Ok(TimeZone {
@ -185,11 +160,7 @@ pub(crate) fn parse_date_time_utc(cursor: &mut IsoCursor) -> ParseResult<TimeZon
if cursor.check_or(false, is_time_separator) { if cursor.check_or(false, is_time_separator) {
if !separated { if !separated {
return Err(LexError::syntax( return Err(TemporalError::syntax().with_message("Unexpected TimeSeparator"));
"Unexpected TimeSeparator",
Position::new(1, cursor.pos()),
)
.into());
} }
cursor.advance(); cursor.advance();
} }
@ -220,7 +191,7 @@ pub(crate) fn parse_date_time_utc(cursor: &mut IsoCursor) -> ParseResult<TimeZon
} }
/// Parse an `UtcOffsetMinutePrecision` node /// Parse an `UtcOffsetMinutePrecision` node
pub(crate) fn parse_utc_offset_minute_precision(cursor: &mut IsoCursor) -> ParseResult<UTCOffset> { pub(crate) fn parse_utc_offset_minute_precision(cursor: &mut Cursor) -> TemporalResult<UTCOffset> {
let sign = if let Some(ch) = cursor.next() { let sign = if let Some(ch) = cursor.next() {
if ch == '+' { if ch == '+' {
1_i8 1_i8
@ -228,14 +199,14 @@ pub(crate) fn parse_utc_offset_minute_precision(cursor: &mut IsoCursor) -> Parse
-1_i8 -1_i8
} }
} else { } else {
return Err(Error::AbruptEnd); return Err(TemporalError::abrupt_end());
}; };
let hour = parse_hour(cursor)?; let hour = parse_hour(cursor)?;
// If at the end of the utc, then return. // If at the end of the utc, then return.
if cursor if cursor
.check(|ch| !(ch.is_ascii_digit() || is_time_separator(ch))) .check(|ch| !(ch.is_ascii_digit() || is_time_separator(ch)))
.ok_or_else(|| Error::AbruptEnd)? .ok_or_else(TemporalError::abrupt_end)?
{ {
return Ok(UTCOffset { return Ok(UTCOffset {
sign, sign,
Loading…
Cancel
Save