1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//! This module implements parsing for ISO 8601 grammar.

use crate::{TemporalError, TemporalResult};

use datetime::DateRecord;
use nodes::{IsoDate, IsoDateTime, IsoTime, TimeZone};
use time::TimeSpec;

mod annotations;
pub(crate) mod datetime;
pub(crate) mod duration;
mod grammar;
mod nodes;
mod time;
pub(crate) mod time_zone;

use self::{datetime::DateTimeFlags, grammar::is_annotation_open};

#[cfg(test)]
mod tests;

// TODO: optimize where possible.

/// `assert_syntax!` is a parser specific utility macro for asserting a syntax test, and returning a
/// `SyntaxError` with the provided message if the test fails.
#[macro_export]
macro_rules! assert_syntax {
    ($cond:expr, $msg:literal) => {
        if !$cond {
            return Err(TemporalError::syntax().with_message($msg));
        }
    };
}

/// A utility function for parsing a `DateTime` string
pub(crate) fn parse_date_time(target: &str) -> TemporalResult<IsoParseRecord> {
    datetime::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> {
    datetime::parse_annotated_date_time(
        DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ,
        &mut Cursor::new(target),
    )
}

/// A utility function for parsing a `YearMonth` string
pub(crate) fn parse_year_month(target: &str) -> TemporalResult<IsoParseRecord> {
    let mut cursor = Cursor::new(target);
    let ym = datetime::parse_year_month(&mut cursor);

    let Ok(year_month) = ym else {
        cursor.pos = 0;
        return datetime::parse_annotated_date_time(DateTimeFlags::empty(), &mut cursor);
    };

    let calendar = if cursor.check_or(false, is_annotation_open) {
        let set = annotations::parse_annotation_set(false, &mut cursor)?;
        set.calendar
    } else {
        None
    };

    cursor.close()?;

    Ok(IsoParseRecord {
        date: DateRecord {
            year: year_month.0,
            month: year_month.1,
            day: 1,
        },
        time: None,
        tz: None,
        calendar,
    })
}

/// A utilty function for parsing a `MonthDay` String.
pub(crate) fn parse_month_day(target: &str) -> TemporalResult<IsoParseRecord> {
    let mut cursor = Cursor::new(target);
    let md = datetime::parse_month_day(&mut cursor);

    let Ok(month_day) = md else {
        cursor.pos = 0;
        return datetime::parse_annotated_date_time(DateTimeFlags::empty(), &mut cursor);
    };

    let calendar = if cursor.check_or(false, is_annotation_open) {
        let set = annotations::parse_annotation_set(false, &mut cursor)?;
        set.calendar
    } else {
        None
    };

    cursor.close()?;

    Ok(IsoParseRecord {
        date: DateRecord {
            year: 0,
            month: month_day.0,
            day: month_day.1,
        },
        time: None,
        tz: None,
        calendar,
    })
}

/// An `IsoParseRecord` is an intermediary record returned by ISO parsing functions.
///
/// `IsoParseRecord` is converted into the ISO AST Nodes.
#[derive(Default, Debug)]
pub(crate) struct IsoParseRecord {
    /// Parsed Date Record
    pub(crate) date: DateRecord,
    /// Parsed Time
    pub(crate) time: Option<TimeSpec>,
    /// Parsed `TimeZone` data (UTCOffset | IANA name)
    pub(crate) tz: Option<TimeZone>,
    /// The parsed calendar value.
    pub(crate) calendar: Option<String>,
}

// TODO: Phase out the below and integrate parsing with Temporal components.

/// Parse a [`TemporalTimeZoneString`][proposal].
///
/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalTimeZoneString
#[derive(Debug, Clone, Copy)]
pub struct TemporalTimeZoneString;

impl TemporalTimeZoneString {
    /// Parses a targeted string as a `TimeZone`.
    ///
    /// # Errors
    ///
    /// The parse will error if the provided target is not valid
    /// Iso8601 grammar.
    pub fn parse(cursor: &mut Cursor) -> TemporalResult<TimeZone> {
        time_zone::parse_time_zone(cursor)
    }
}

/// Parser for a [`TemporalInstantString`][proposal].
///
/// [proposal]: https://tc39.es/proposal-temporal/#prod-TemporalInstantString
#[derive(Debug, Clone, Copy)]
pub struct TemporalInstantString;

impl TemporalInstantString {
    /// Parses a targeted string as an `Instant`.
    ///
    /// # Errors
    ///
    /// The parse will error if the provided target is not valid
    /// Iso8601 grammar.
    pub fn parse(cursor: &mut Cursor) -> TemporalResult<IsoDateTime> {
        let parse_record = datetime::parse_annotated_date_time(
            DateTimeFlags::UTC_REQ | DateTimeFlags::TIME_REQ,
            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,
        })
    }
}

// ==== Mini cursor implementation for Iso8601 targets ====

/// `Cursor` is a small cursor implementation for parsing Iso8601 grammar.
#[derive(Debug)]
pub struct Cursor {
    pos: u32,
    source: Vec<char>,
}

impl Cursor {
    /// Create a new cursor from a source `String` value.
    #[must_use]
    pub fn new(source: &str) -> Self {
        Self {
            pos: 0,
            source: source.chars().collect(),
        }
    }

    /// Returns a string value from a slice of the cursor.
    fn slice(&self, start: u32, end: u32) -> String {
        self.source[start as usize..end as usize].iter().collect()
    }

    /// Get current position
    const fn pos(&self) -> u32 {
        self.pos
    }

    /// Peek the value at next position (current + 1).
    fn peek(&self) -> Option<char> {
        self.peek_n(1)
    }

    /// Peek the value at n len from current.
    fn peek_n(&self, n: u32) -> Option<char> {
        let target = (self.pos + n) as usize;
        if target < self.source.len() {
            Some(self.source[target])
        } else {
            None
        }
    }

    /// Runs the provided check on the current position.
    fn check<F>(&self, f: F) -> Option<bool>
    where
        F: FnOnce(char) -> bool,
    {
        self.peek_n(0).map(f)
    }

    /// Runs the provided check on current position returns the default value if None.
    fn check_or<F>(&self, default: bool, f: F) -> bool
    where
        F: FnOnce(char) -> bool,
    {
        self.peek_n(0).map_or(default, f)
    }

    /// Returns `Cursor`'s current char and advances to the next position.
    fn next(&mut self) -> Option<char> {
        let result = self.peek_n(0);
        self.advance();
        result
    }

    /// Returns the next value as a digit.
    ///
    /// # Errors
    ///   - Returns a SyntaxError if value is not an ascii digit
    ///   - Returns an AbruptEnd error if cursor ends.
    fn next_digit(&mut self) -> TemporalResult<u8> {
        let p_digit = self.abrupt_next()?.to_digit(10);
        let Some(digit) = p_digit else {
            return Err(TemporalError::syntax()
                .with_message("Expected decimal digit, found non-digit character."));
        };
        Ok(digit as u8)
    }

    /// Utility method that returns next charactor unwrapped char
    ///
    /// # Panics
    ///
    /// This will panic if the next value has not been confirmed to exist.
    fn expect_next(&mut self) -> char {
        self.next().expect("Invalid use of expect_next.")
    }

    /// A utility next method that returns an `AbruptEnd` error if invalid.
    fn abrupt_next(&mut self) -> TemporalResult<char> {
        self.next().ok_or_else(TemporalError::abrupt_end)
    }

    /// Advances the cursor's position by 1.
    fn advance(&mut self) {
        self.pos += 1;
    }

    /// Utility function to advance when a condition is true
    fn advance_if(&mut self, condition: bool) {
        if condition {
            self.advance();
        }
    }

    /// Advances the cursor's position by `n`.
    fn advance_n(&mut self, n: u32) {
        self.pos += n;
    }

    /// Closes the current cursor by checking if all contents have been consumed. If not, returns an error for invalid syntax.
    fn close(&mut self) -> TemporalResult<()> {
        if (self.pos as usize) < self.source.len() {
            return Err(TemporalError::syntax()
                .with_message("Unexpected syntax at the end of an ISO target."));
        }
        Ok(())
    }
}