scylla/utils/
parse.rs

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
/// An error that can occur during parsing.
#[derive(Copy, Clone)]
pub(crate) struct ParseError {
    pub(crate) remaining: usize,
    pub(crate) cause: ParseErrorCause,
}

impl ParseError {
    /// Given the original string, returns the 1-based position
    /// of the error in characters.
    /// If an incorrect string was given, the function may return 0.
    pub(crate) fn calculate_position(&self, original: &str) -> Option<usize> {
        calculate_position(original, self.remaining)
    }

    /// Returns the error cause.
    pub(crate) fn get_cause(&self) -> ParseErrorCause {
        self.cause
    }
}

/// Cause of the parsing error.
/// Should be lightweight so that it can be quickly discarded.
#[derive(Copy, Clone)]
pub(crate) enum ParseErrorCause {
    Expected(&'static str),
    Other(&'static str),
}

impl ToString for ParseErrorCause {
    fn to_string(&self) -> String {
        match self {
            ParseErrorCause::Expected(e) => format!("expected {:?}", e),
            ParseErrorCause::Other(e) => e.to_string(),
        }
    }
}

pub(crate) type ParseResult<T> = Result<T, ParseError>;

/// A utility class for building simple recursive-descent parsers.
///
/// Basically, a wrapper over &str with nice methods that help with parsing.
#[derive(Clone, Copy)]
#[must_use]
pub(crate) struct ParserState<'s> {
    s: &'s str,
}

impl<'s> ParserState<'s> {
    /// Creates a new parser from given input string.
    pub(crate) fn new(s: &'s str) -> Self {
        Self { s }
    }

    /// Applies given parsing function until it returns false
    /// and returns the final parser state.
    pub(crate) fn parse_while(
        self,
        mut parser: impl FnMut(Self) -> ParseResult<(bool, Self)>,
    ) -> ParseResult<Self> {
        let mut me = self;
        loop {
            let (proceed, new_me) = parser(me)?;
            if !proceed {
                return Ok(new_me);
            }
            me = new_me;
        }
    }

    /// If the input string contains given string at the beginning,
    /// returns a new parser state with given string skipped.
    /// Otherwise, returns an error.
    pub(crate) fn accept(self, part: &'static str) -> ParseResult<Self> {
        match self.s.strip_prefix(part) {
            Some(s) => Ok(Self { s }),
            None => Err(self.error(ParseErrorCause::Expected(part))),
        }
    }

    /// Returns new parser state with whitespace skipped from the beginning.
    pub(crate) fn skip_white(self) -> Self {
        let (_, me) = self.take_while(char::is_whitespace);
        me
    }

    /// Skips characters from the beginning while they satisfy given predicate
    /// and returns new parser state which
    pub(crate) fn take_while(self, mut pred: impl FnMut(char) -> bool) -> (&'s str, Self) {
        let idx = self.s.find(move |c| !pred(c)).unwrap_or(self.s.len());
        let new = Self { s: &self.s[idx..] };
        (&self.s[..idx], new)
    }

    /// Returns the number of remaining bytes to parse.
    pub(crate) fn get_remaining(self) -> usize {
        self.s.len()
    }

    /// Returns true if the input string was parsed completely.
    pub(crate) fn is_at_eof(self) -> bool {
        self.s.is_empty()
    }

    /// Returns an error with given cause, associated with given position.
    pub(crate) fn error(self, cause: ParseErrorCause) -> ParseError {
        ParseError {
            remaining: self.get_remaining(),
            cause,
        }
    }

    /// Given the original string, returns the 1-based position
    /// of the error in characters.
    /// If an incorrect string was given, the function may return None.
    pub(crate) fn calculate_position(self, original: &str) -> Option<usize> {
        calculate_position(original, self.get_remaining())
    }
}

fn calculate_position(original: &str, remaining: usize) -> Option<usize> {
    let prefix_len = original.len().checked_sub(remaining)?;
    let prefix = original.get(..prefix_len)?;
    Some(prefix.chars().count() + 1)
}