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