Skip to main content

ixdtf/
core.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Core functionality for `ixdtf`'s parsers
6
7use crate::encoding::EncodingType;
8use crate::{ParseError, ParserResult};
9
10// ==== Mini cursor implementation for Iso8601 targets ====
11
12/// `Cursor` is a small cursor implementation for parsing Iso8601 grammar.
13#[derive(Debug)]
14pub(crate) struct Cursor<'a, T: EncodingType> {
15    pos: usize,
16    source: &'a [T::CodeUnit],
17}
18
19impl<'a, T: EncodingType> Cursor<'a, T> {
20    /// Create a new cursor from a source UTF8 string.
21    #[must_use]
22    pub fn new(source: &'a [T::CodeUnit]) -> Self {
23        Self { pos: 0, source }
24    }
25
26    /// Returns a string value from a slice of the cursor.
27    pub(crate) fn slice(&self, start: usize, end: usize) -> Option<&'a [T::CodeUnit]> {
28        T::slice(self.source, start, end)
29    }
30
31    /// Get current position
32    pub(crate) const fn pos(&self) -> usize {
33        self.pos
34    }
35
36    /// Get current position
37    pub(crate) fn set_position(&mut self, pos: usize) {
38        self.pos = pos;
39    }
40
41    /// Returns current position in source as `char`.
42    pub(crate) fn current(&self) -> ParserResult<Option<u8>> {
43        self.peek_n(0)
44    }
45
46    /// Peeks the value at `n` as a `char`.
47    pub(crate) fn peek_n(&self, n: usize) -> ParserResult<Option<u8>> {
48        T::get_ascii(self.source, self.pos + n)
49    }
50
51    /// Runs the provided check on the current position.
52    pub(crate) fn check<F>(&self, f: F) -> ParserResult<Option<bool>>
53    where
54        F: FnOnce(u8) -> bool,
55    {
56        Ok(self.current()?.map(f))
57    }
58
59    /// Runs the provided check on current position returns the default value if None.
60    pub(crate) fn check_or<F>(&self, default: bool, f: F) -> ParserResult<bool>
61    where
62        F: FnOnce(u8) -> bool,
63    {
64        Ok(self.current()?.map_or(default, f))
65    }
66
67    /// Returns `Cursor`'s current char and advances to the next position.
68    pub(crate) fn next(&mut self) -> ParserResult<Option<u8>> {
69        let result = self.current();
70        self.advance_n(1);
71        result
72    }
73
74    /// Advances the cursor and returns the current char if it satisfies `f`.
75    pub(crate) fn next_if(&mut self, f: impl FnOnce(u8) -> bool) -> ParserResult<Option<u8>> {
76        if let Some(ch) = self.current()? {
77            if f(ch) {
78                self.advance();
79                return Ok(Some(ch));
80            }
81        }
82        Ok(None)
83    }
84
85    /// Returns the next value as a digit
86    ///
87    /// # Errors
88    ///   - Returns an [`ParseError::AbruptEnd`] error if cursor ends.
89    pub(crate) fn next_digit(&mut self) -> ParserResult<Option<u8>> {
90        let ascii_char = self.next_or(ParseError::AbruptEnd { location: "digit" })?;
91        if ascii_char.is_ascii_digit() {
92            Ok(Some(ascii_char - 48))
93        } else {
94            Ok(None)
95        }
96    }
97
98    /// A utility next method that returns an [`ParseError::AbruptEnd`] error if invalid.
99    pub(crate) fn next_or(&mut self, err: ParseError) -> ParserResult<u8> {
100        self.next()?.ok_or(err)
101    }
102
103    /// Advances the cursor's position by n code points.
104    pub(crate) fn advance_n(&mut self, n: usize) {
105        self.pos += n;
106    }
107
108    // Advances the cursor by 1 code point.
109    pub(crate) fn advance(&mut self) {
110        self.advance_n(1)
111    }
112
113    /// Utility function to advance when a condition is true
114    pub(crate) fn advance_if(&mut self, condition: bool) {
115        if condition {
116            self.advance();
117        }
118    }
119
120    /// Closes the current cursor by checking if all contents have been consumed. If not, returns an error for invalid syntax.
121    pub(crate) fn close(&mut self) -> ParserResult<()> {
122        if self.pos < self.source.len() {
123            return Err(ParseError::InvalidEnd);
124        }
125        Ok(())
126    }
127}