Skip to main content

substrait_explain/parser/
common.rs

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