Skip to main content

substrait_explain/parser/
common.rs

1use std::{fmt, thread};
2
3use pest::error::{Error as PestError, ErrorVariant};
4use pest::iterators::{Pair, Pairs};
5use pest::{Parser as PestParser, Span};
6use pest_derive::Parser as PestDeriveParser;
7use thiserror::Error;
8
9use crate::extensions::SimpleExtensions;
10use crate::extensions::simple::MissingReference;
11
12#[derive(PestDeriveParser)]
13#[grammar = "parser/expression_grammar.pest"] // Path relative to src
14pub(crate) struct ExpressionParser;
15
16/// An error that occurs when parsing a message within a specific line. Contains
17/// context pointing at that specific error.
18#[derive(Error, Debug, Clone)]
19#[error("{kind} Error parsing {message}:\n{error}")]
20pub struct MessageParseError {
21    message: &'static str,
22    kind: ErrorKind,
23    #[source]
24    error: Box<PestError<Rule>>,
25}
26
27#[derive(Debug, Clone)]
28pub(crate) enum ErrorKind {
29    Syntax,
30    InvalidValue,
31    Lookup(MissingReference),
32}
33
34impl MessageParseError {
35    pub(crate) fn invalid(message: &'static str, span: Span, description: impl ToString) -> Self {
36        let error = PestError::new_from_span(
37            ErrorVariant::CustomError {
38                message: description.to_string(),
39            },
40            span,
41        );
42        Self::new(message, ErrorKind::InvalidValue, Box::new(error))
43    }
44
45    pub(crate) fn lookup(
46        message: &'static str,
47        missing: MissingReference,
48        span: Span,
49        description: impl ToString,
50    ) -> Self {
51        let error = PestError::new_from_span(
52            ErrorVariant::CustomError {
53                message: description.to_string(),
54            },
55            span,
56        );
57        Self::new(message, ErrorKind::Lookup(missing), Box::new(error))
58    }
59}
60
61impl MessageParseError {
62    pub(crate) fn new(message: &'static str, kind: ErrorKind, error: Box<PestError<Rule>>) -> Self {
63        Self {
64            message,
65            kind,
66            error,
67        }
68    }
69}
70
71impl fmt::Display for ErrorKind {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            ErrorKind::Syntax => write!(f, "Syntax"),
75            ErrorKind::InvalidValue => write!(f, "Invalid value"),
76            ErrorKind::Lookup(e) => write!(f, "Invalid reference ({e})"),
77        }
78    }
79}
80
81pub(crate) fn unwrap_single_pair(pair: Pair<Rule>) -> Pair<Rule> {
82    let mut pairs = pair.into_inner();
83    let pair = pairs.next().unwrap();
84    assert_eq!(pairs.next(), None);
85    pair
86}
87
88/// Unescapes a quoted string literal, handling escape sequences.
89///
90/// # Arguments
91/// * `pair` - The pest pair containing the string to unescape (must be Rule::string_literal or Rule::quoted_name).
92///
93/// # Returns
94/// * `String` with the unescaped contents.
95///
96/// # Panics
97/// Panics if the rule is not `string_literal` or `quoted_name` (this should never happen
98/// if the pest grammar is working correctly).
99///
100pub(crate) fn unescape_string(pair: Pair<Rule>) -> String {
101    let s = pair.as_str();
102
103    // Determine opener/closer based on rule type
104    let (opener, closer) = match pair.as_rule() {
105        Rule::string_literal => ('\'', '\''),
106        Rule::quoted_name => ('"', '"'),
107        _ => panic!(
108            "unescape_string called with unexpected rule: {:?}",
109            pair.as_rule()
110        ),
111    };
112
113    let mut result = String::new();
114    let mut chars = s.chars();
115    let first = chars.next().expect("Empty string literal");
116
117    assert_eq!(
118        first, opener,
119        "Expected opening quote '{opener}', got '{first}'"
120    );
121
122    // Skip the opening quote
123    while let Some(c) = chars.next() {
124        match c {
125            c if c == closer => {
126                // Skip the closing quote, and assert that there are no more characters.
127                assert_eq!(
128                    chars.next(),
129                    None,
130                    "Unexpected characters after closing quote"
131                );
132                break;
133            }
134            '\\' => {
135                let next = chars
136                    .next()
137                    .expect("Incomplete escape sequence at end of string");
138                match next {
139                    'n' => result.push('\n'),
140                    't' => result.push('\t'),
141                    'r' => result.push('\r'),
142                    // For all other characters (especially `"`, `'`, and `\`), we just
143                    // push the character.
144                    _ => result.push(next),
145                }
146            }
147            _ => result.push(c),
148        }
149    }
150    result
151}
152
153// A trait for converting a pest::iterators::Pair<Rule> into a Rust type. This
154// is used to convert from the uniformly structured nesting
155// pest::iterators::Pair<Rule> into more structured types.
156pub(crate) trait ParsePair: Sized {
157    // The rule that this type is parsed from.
158    fn rule() -> Rule;
159
160    // The name of the protobuf message type that this type corresponds to.
161    fn message() -> &'static str;
162
163    // Parse a single instance of this type from a pest::iterators::Pair<Rule>.
164    // The input must match the rule returned by `rule`; otherwise, a panic is
165    // expected.
166    fn parse_pair(pair: Pair<Rule>) -> Self;
167
168    fn parse_str(s: &str) -> Result<Self, MessageParseError> {
169        let mut pairs = <ExpressionParser as PestParser<Rule>>::parse(Self::rule(), s)
170            .map_err(|e| MessageParseError::new(Self::message(), ErrorKind::Syntax, Box::new(e)))?;
171        assert_eq!(pairs.as_str(), s);
172        let pair = pairs.next().unwrap();
173        assert_eq!(pairs.next(), None);
174        Ok(Self::parse_pair(pair))
175    }
176}
177
178/// A trait for types that are parsed from a `pest::iterators::Pair<Rule>` that
179/// depends on the context - e.g. extension lookups or other contextual
180/// information. This is used for types that are not directly parsed from the
181/// grammar, but rather require additional context to parse correctly.
182pub(crate) trait ScopedParsePair: Sized {
183    // The rule that this type is parsed from.
184    fn rule() -> Rule;
185
186    // The name of the protobuf message type that this type corresponds to.
187    fn message() -> &'static str;
188
189    // Parse a single instance of this type from a `pest::iterators::Pair<Rule>`.
190    // The input must match the rule returned by `rule`; otherwise, a panic is
191    // expected.
192    fn parse_pair(
193        extensions: &SimpleExtensions,
194        pair: Pair<Rule>,
195    ) -> Result<Self, MessageParseError>;
196}
197
198pub(crate) fn iter_pairs(pair: Pairs<'_, Rule>) -> RuleIter<'_> {
199    RuleIter {
200        iter: pair,
201        done: false,
202    }
203}
204
205pub(crate) struct RuleIter<'a> {
206    iter: Pairs<'a, Rule>,
207    // Set to true when done is called, so destructor doesn't panic
208    done: bool,
209}
210
211impl<'a> From<Pairs<'a, Rule>> for RuleIter<'a> {
212    fn from(iter: Pairs<'a, Rule>) -> Self {
213        RuleIter { iter, done: false }
214    }
215}
216
217impl<'a> RuleIter<'a> {
218    pub(crate) fn peek(&self) -> Option<Pair<'a, Rule>> {
219        self.iter.peek()
220    }
221
222    // Pop the next pair if it matches the rule. Returns None if not.
223    pub(crate) fn try_pop(&mut self, rule: Rule) -> Option<Pair<'a, Rule>> {
224        match self.peek() {
225            Some(pair) if pair.as_rule() == rule => {
226                self.iter.next();
227                Some(pair)
228            }
229            _ => None,
230        }
231    }
232
233    // Pop the next pair, asserting it matches the given rule. Panics if not.
234    pub(crate) fn pop(&mut self, rule: Rule) -> Pair<'a, Rule> {
235        let pair = self.iter.next().expect("expected another pair");
236        assert_eq!(
237            pair.as_rule(),
238            rule,
239            "expected rule {:?}, got {:?}",
240            rule,
241            pair.as_rule()
242        );
243        pair
244    }
245
246    // Parse the next pair if it matches the rule. Returns None if not.
247    pub(crate) fn parse_if_next<T: ParsePair>(&mut self) -> Option<T> {
248        match self.peek() {
249            Some(pair) if pair.as_rule() == T::rule() => {
250                self.iter.next();
251                Some(T::parse_pair(pair))
252            }
253            _ => None,
254        }
255    }
256
257    // Parse the next pair if it matches the rule. Returns None if not.
258    pub(crate) fn parse_if_next_scoped<T: ScopedParsePair>(
259        &mut self,
260        extensions: &SimpleExtensions,
261    ) -> Option<Result<T, MessageParseError>> {
262        match self.peek() {
263            Some(pair) if pair.as_rule() == T::rule() => {
264                self.iter.next();
265                Some(T::parse_pair(extensions, pair))
266            }
267            _ => None,
268        }
269    }
270
271    // Parse the next pair, assuming it matches the rule. Panics if not.
272    pub(crate) fn parse_next<T: ParsePair>(&mut self) -> T {
273        let pair = self.iter.next().unwrap();
274        T::parse_pair(pair)
275    }
276
277    // Parse the next pair, assuming it matches the rule. Panics if not.
278    pub(crate) fn parse_next_scoped<T: ScopedParsePair>(
279        &mut self,
280        extensions: &SimpleExtensions,
281    ) -> Result<T, MessageParseError> {
282        let pair = self.iter.next().unwrap();
283        T::parse_pair(extensions, pair)
284    }
285
286    pub(crate) fn done(mut self) {
287        self.done = true;
288        // A rule may end with the `EOI` marker to force full input consumption
289        // (e.g. `virtual_read_relation`). That marker carries no data, so it is
290        // not leftover content — skip it before asserting the iterator is empty.
291        let next = match self.iter.next() {
292            Some(pair) if pair.as_rule() == Rule::EOI => self.iter.next(),
293            other => other,
294        };
295        assert_eq!(next, None);
296    }
297}
298
299/// Make sure that the iterator was completely consumed when the iterator is
300/// dropped - that we didn't leave any partially-parsed tokens.
301///
302/// This is not strictly necessary, but it's a good way to catch bugs.
303impl Drop for RuleIter<'_> {
304    fn drop(&mut self) {
305        if self.done || thread::panicking() {
306            return;
307        }
308        // If the iterator is not done, something probably went wrong.
309        assert_eq!(self.iter.next(), None);
310    }
311}
312
313#[cfg(test)]
314pub(crate) mod test_support {
315    use pest::Parser as PestParser;
316
317    use super::{ErrorKind, ExpressionParser, MessageParseError, ParsePair, ScopedParsePair};
318    use crate::extensions::SimpleExtensions;
319
320    /// Test-only adapter for parsing individual grammar fragments from strings.
321    ///
322    /// Production parsing goes through [`ParsePair`] and the structural [`Parser`](crate::Parser).
323    pub(crate) trait Parse {
324        fn parse(input: &str) -> Result<Self, MessageParseError>
325        where
326            Self: Sized;
327    }
328
329    impl<T: ParsePair> Parse for T {
330        fn parse(input: &str) -> Result<Self, MessageParseError> {
331            T::parse_str(input)
332        }
333    }
334
335    /// Test-only adapter for parsing context-dependent grammar fragments from strings.
336    ///
337    /// Production parsing goes through [`ScopedParsePair`] and the structural
338    /// [`Parser`](crate::Parser).
339    pub(crate) trait ScopedParse: Sized {
340        fn parse(extensions: &SimpleExtensions, input: &str) -> Result<Self, MessageParseError>
341        where
342            Self: Sized;
343    }
344
345    impl<T: ScopedParsePair> ScopedParse for T {
346        fn parse(extensions: &SimpleExtensions, input: &str) -> Result<Self, MessageParseError> {
347            let mut pairs = ExpressionParser::parse(Self::rule(), input).map_err(|e| {
348                MessageParseError::new(Self::message(), ErrorKind::Syntax, Box::new(e))
349            })?;
350            assert_eq!(pairs.as_str(), input);
351            let pair = pairs.next().unwrap();
352            assert_eq!(pairs.next(), None);
353            Self::parse_pair(extensions, pair)
354        }
355    }
356}