git_bug/query/parse/parser/
relaxed.rs

1// git-bug-rs - A rust library for interfacing with git-bug repositories
2//
3// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
4// SPDX-License-Identifier: GPL-3.0-or-later
5//
6// This file is part of git-bug-rs/git-gub.
7//
8// You should have received a copy of the License along with this program.
9// If not, see <https://www.gnu.org/licenses/agpl.txt>.
10
11//! The relaxed query parser
12
13use crate::query::{
14    Matcher,
15    parse::{
16        parser::{format_unexpected_token, format_unknown_key},
17        tokenizer::{Token, TokenKind, TokenSpan},
18    },
19    queryable::{QueryKeyValue, Queryable},
20};
21
22#[derive(Debug, thiserror::Error)]
23#[allow(missing_docs)]
24pub enum Error<E: Queryable>
25where
26    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
27{
28    #[error(fmt = format_unexpected_token)]
29    UnexpectedToken { expected: TokenKind, found: Token },
30
31    /// An unexpected token was found, and multiple possible token kinds were
32    /// possible.
33    #[error("The input stream contained {found} but expected one of: {expected:?}")]
34    UnexpectedTokens {
35        /// The token kinds that could have occurred.
36        expected: Vec<TokenKind>,
37
38        /// The token we actually found.
39        found: Token,
40    },
41
42    /// A key in a `MatchKey` expression was unknown.
43    #[error(fmt = format_unknown_key::<E>)]
44    UnknownKey {
45        /// The returned error
46        err: <E::KeyValue as QueryKeyValue>::Err,
47
48        /// The key we found.
49        key: String,
50
51        /// The span this key takes up.
52        at: TokenSpan,
53    },
54
55    /// A value for the default key in a `MatchKey` expression was unknown.
56    #[error("The input stream countained a unknown value '{value}' for the default key: {err}")]
57    UnknownDefaultKey {
58        /// The value that was unknown for the default key.
59        value: String,
60
61        /// The returned error from the [`QueryKeyValue::from_value`] method.
62        err: <<E as Queryable>::KeyValue as QueryKeyValue>::Err,
63
64        /// The span in the query this value takes up.
65        at: TokenSpan,
66    },
67
68    /// An expression that needs both a left hand side (LHS) and right hand side
69    /// (RHS) was specified without a LHS.
70    #[error("Expected a LHS for {at}, but got nothing.")]
71    MissingLhs {
72        /// Which expression requested the LHS.
73        at: Token,
74    },
75}
76
77impl<E: Queryable> From<super::parsing::Error<E>> for Error<E>
78where
79    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
80{
81    fn from(value: super::parsing::Error<E>) -> Self {
82        match value {
83            super::parsing::Error::UnexpectedToken { expected, found } => {
84                Self::UnexpectedToken { expected, found }
85            }
86            super::parsing::Error::UnknownKey { err, key, at } => Self::UnknownKey { err, key, at },
87        }
88    }
89}
90
91pub(super) struct Parser<'a, E: Queryable> {
92    parent: &'a mut super::Parser<'a, E>,
93}
94
95impl<'a, E: Queryable> Parser<'a, E>
96where
97    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
98{
99    pub(super) fn parse(parent: &'a mut super::Parser<'a, E>) -> Result<Matcher<E>, Error<E>> {
100        let mut me = Parser { parent };
101        me.actual_parse(None)
102    }
103
104    fn actual_parse(&mut self, matcher: Option<Matcher<E>>) -> Result<Matcher<E>, Error<E>> {
105        fn unwrap_matcher<E: Queryable>(
106            matcher: Option<Matcher<E>>,
107            token: Token,
108        ) -> Result<Matcher<E>, Error<E>>
109        where
110            <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
111        {
112            if let Some(matcher) = matcher {
113                Ok(matcher)
114            } else {
115                Err(Error::MissingLhs { at: token })
116            }
117        }
118
119        self.clean();
120
121        let peek_next = self.parent.tokenizer.peek();
122        match peek_next.kind {
123            TokenKind::And => {
124                let token = self.parent.expect(TokenKind::And)?;
125                self.parent.expect(TokenKind::Break)?;
126
127                let next = Matcher::And {
128                    lhs: Box::new(unwrap_matcher(matcher, token)?),
129                    rhs: Box::new(self.parse_matcher()?),
130                };
131                self.actual_parse(Some(next))
132            }
133            TokenKind::Or => {
134                let token = self.parent.expect(TokenKind::Or)?;
135                self.parent.expect(TokenKind::Break)?;
136
137                let next = Matcher::Or {
138                    lhs: Box::new(unwrap_matcher(matcher, token)?),
139                    rhs: Box::new(self.parse_matcher()?),
140                };
141                self.actual_parse(Some(next))
142            }
143
144            TokenKind::ParenOpen | TokenKind::Char(_) => {
145                let next = merge_matcher(matcher, self.parse_matcher()?);
146
147                self.actual_parse(Some(next))
148            }
149
150            TokenKind::Colon => Err(Error::UnexpectedTokens {
151                expected: vec![
152                    TokenKind::And,
153                    TokenKind::Or,
154                    TokenKind::ParenOpen,
155                    TokenKind::Char('*'),
156                ],
157                found: peek_next,
158            }),
159            TokenKind::Eof => Ok(matcher.expect("This should be some at this point")),
160            TokenKind::Break | TokenKind::ParenClose => unreachable!("Sorted out above."),
161        }
162    }
163
164    fn clean(&mut self) {
165        // Allow for leading white space and trailing closing parentheses
166        while let kind @ (TokenKind::ParenClose | TokenKind::Break) =
167            self.parent.tokenizer.peek().kind()
168        {
169            let next = self.parent.tokenizer.next_token();
170            assert_eq!(next.kind, kind);
171        }
172    }
173
174    fn parse_matcher(&mut self) -> Result<Matcher<E>, Error<E>> {
175        let peek_next = self.parent.tokenizer.peek();
176
177        match peek_next.kind {
178            TokenKind::ParenOpen => self.parse_and_or_or(),
179            TokenKind::Char(_) => self.parse_match_key(),
180            TokenKind::Colon
181            | TokenKind::Eof
182            | TokenKind::ParenClose
183            | TokenKind::And
184            | TokenKind::Or => Err(Error::UnexpectedTokens {
185                expected: vec![TokenKind::ParenOpen, TokenKind::Char('*')],
186                found: peek_next,
187            }),
188            TokenKind::Break => {
189                self.parent.expect(TokenKind::Break)?;
190                self.parse_matcher()
191            }
192        }
193    }
194
195    fn parse_match_key(&mut self) -> Result<Matcher<E>, Error<E>> {
196        // A normal MatchKey is `<key>:<value>`.
197        // But we also support only passing `<value>`, and defaulting to the `search`
198        // key.
199
200        let key_tokens = self
201            .parent
202            .take_while(|t| matches!(t, TokenKind::Char(_)))?;
203        let key = if TokenKind::Colon == self.parent.tokenizer.peek().kind {
204            // We have a normalized MatchKey.
205            self.parent.expect(TokenKind::Colon)?;
206            let (value, value_span_end) = {
207                let value_tokens = self
208                    .parent
209                    .take_while(|t| matches!(t, TokenKind::Char(_)))?;
210                (
211                    super::Parser::<'a, E>::parse_value_from(&value_tokens),
212                    value_tokens.last().map_or(0, |last| last.span.stop),
213                )
214            };
215
216            self.parent
217                .parse_key_from(&key_tokens, value, value_span_end)?
218        } else {
219            // We don't have a colon after the key values. Thus assume, that the
220            // “key_tokens” are actually already the “value_tokens”.
221
222            let value = super::Parser::<'a, E>::parse_value_from(&key_tokens);
223            E::KeyValue::from_value(self.parent.user_state, value.clone()).map_err(|err| {
224                Error::UnknownDefaultKey {
225                    value,
226                    err,
227                    at: TokenSpan {
228                        start: key_tokens.first().expect("Exists").span.start,
229                        stop: key_tokens.last().expect("Exists").span.stop,
230                    },
231                }
232            })?
233        };
234
235        Ok(Matcher::Match { key_value: key })
236    }
237
238    fn parse_and_or_or(&mut self) -> Result<Matcher<E>, Error<E>> {
239        self.parent.expect(TokenKind::ParenOpen)?;
240        let lhs = Box::new(self.parse_matcher()?);
241        self.parent.expect(TokenKind::Break)?;
242
243        // TODO(@bpeetz): I have no idea at all why this is needed here, but somehow
244        // ParenClose and Break end up here. <2025-05-11>
245        self.clean();
246
247        let mode = {
248            let next = self.parent.tokenizer.next_token();
249            match next.kind {
250                TokenKind::And => TokenKind::And,
251                TokenKind::Or => TokenKind::Or,
252                _ => {
253                    return Err(Error::UnexpectedTokens {
254                        expected: vec![TokenKind::And, TokenKind::Or],
255                        found: next,
256                    });
257                }
258            }
259        };
260
261        self.parent.expect(TokenKind::Break)?;
262        let rhs = Box::new(self.parse_matcher()?);
263
264        // Do not enforce balanced parentheses.
265        // This is needed to support things like “(x AND y AND z)” (notice the missing
266        // parentheses between them).
267        if self.parent.tokenizer.peek().kind() == TokenKind::ParenClose {
268            self.parent.expect(TokenKind::ParenClose)?;
269        }
270
271        if mode == TokenKind::And {
272            Ok(Matcher::And { lhs, rhs })
273        } else if mode == TokenKind::Or {
274            Ok(Matcher::Or { lhs, rhs })
275        } else {
276            unreachable!("This should have been filtered out in the match above");
277        }
278    }
279}
280
281fn merge_matcher<E: Queryable>(old: Option<Matcher<E>>, new: Matcher<E>) -> Matcher<E> {
282    if let Some(old) = old {
283        Matcher::And {
284            lhs: Box::new(old),
285            rhs: Box::new(new),
286        }
287    } else {
288        new
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use pretty_assertions::assert_eq;
295
296    use crate::query::{
297        Matcher::{And, Match, Or},
298        ParseMode, Query,
299        parse::parser::test::{
300            QueryTestKey1::{Value1, Value2},
301            QueryTestKeyValue::{Key1, Key2, Key3},
302            QueryTestObj, query,
303        },
304    };
305
306    #[test]
307    fn test_default_key() {
308        let input = "(value1 AND 'state p2 󰵘')";
309        let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Relaxed)
310            .map_err(|err| panic!("{err}"))
311            .unwrap();
312
313        assert_eq!(
314            query,
315            query! {
316                And {
317                    lhs: {Match (Key2, "value1")},
318                    rhs: {Match (Key2, "state p2 \u{f0d58}")}
319                }
320            }
321        );
322    }
323
324    #[test]
325    fn test_and_defaults() {
326        let input = "key1:value1 key2:other key3:20";
327        let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Relaxed)
328            .map_err(|err| panic!("{err}"))
329            .unwrap();
330
331        assert_eq!(
332            query,
333            query! {
334                And {
335                    lhs: {And {
336                        lhs: {Match(Key1, Value1)},
337                        rhs: {Match(Key2, "other")},
338                    }},
339                    rhs: {Match(Key3, 20)}
340                }
341            },
342        );
343    }
344
345    #[test]
346    fn test_full_defaults() {
347        let input = "value1 other 20";
348        let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Relaxed)
349            .map_err(|err| panic!("{err}"))
350            .unwrap();
351
352        assert_eq!(
353            query,
354            query! {
355               And {
356                    lhs: {
357                        And {
358                            lhs: {Match(Key2, "value1")},
359                            rhs: {Match(Key2, "other")},
360                        },
361                    },
362                    rhs: {Match(Key2,"20")}
363                }
364            },
365        );
366    }
367
368    #[test]
369    fn test_and_chain() {
370        let input = "key1:value1 AND key1:value2 AND key2:value3 OR key3:21";
371        let query = Query::<QueryTestObj>::from_continuous_str(&(), input, ParseMode::Relaxed)
372            .map_err(|err| panic!("{err}"))
373            .unwrap();
374
375        assert_eq!(
376            query,
377            query! {
378                Or {
379                    lhs: {And {
380                            lhs: {And {
381                                    lhs: {Match(Key1, Value1)},
382                                    rhs: {Match(Key1, Value2)}
383                            }},
384                            rhs: {Match(Key2, "value3")},
385                    }},
386                    rhs: {Match(Key3, 21)},
387                }
388            }
389        );
390    }
391
392    #[test]
393    #[ignore = "The order of the AND and OR conjunctions is wrong."]
394    fn test_full_query() {
395        let input = "(
396                        (
397                            key1:value1
398                            AND 'key2:bitter sweet 🫠'
399                            AND key3:20199
400                        ) OR (
401                            key2:abc
402                            AND key1:value2
403                            AND key1:value1
404                        )
405                     ) OR (
406                        (
407                            c
408                            AND key3:20
409                        ) OR (
410                            orwell
411                            AND key3:1902
412                        )
413                     )"
414        .replace('\n', "");
415        let query = Query::<QueryTestObj>::from_continuous_str(&(), &input, ParseMode::Relaxed)
416            .map_err(|err| panic!("{err}"))
417            .unwrap();
418
419        assert_eq!(
420            query,
421            query! {
422                Or {
423                    lhs: {Or {
424                            lhs: {And {
425                                    lhs: {And {
426                                            lhs: {Match(Key1, Value1)},
427                                            rhs: {Match(Key2, "bitter sweet 🫠")},
428                                        }
429                                    },
430                                    rhs: {Match(Key3, 20199)},
431                                } },
432                            rhs: {And {
433                                    lhs: {And {
434                                        lhs: {Match(Key2, "abc")},
435                                        rhs: {Match(Key1, Value2)},
436                                    }},
437                                    rhs: {Match(Key1, Value1)},
438                                } }
439                    }   },
440                    rhs: {Or {
441                            lhs: {And {
442                                lhs: {Match(Key2, "c")},
443                                rhs: {Match(Key3, 20)},
444                            }},
445                            rhs: {And {
446                                lhs: {Match(Key2, "orwell")},
447                                rhs: {Match(Key3, 1902)},
448                            }}
449                        }
450                    },
451                }
452            }
453        );
454    }
455}