git_bug/query/parse/parser/
mod.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//! Parser implementation for the query language.
12
13use super::tokenizer::{Token, TokenSpan, Tokenizer};
14use crate::query::{
15    ParseMode, Query,
16    parse::tokenizer::TokenKind,
17    queryable::{QueryKeyValue, Queryable},
18};
19
20pub mod relaxed;
21pub mod strict;
22
23#[derive(Debug, thiserror::Error)]
24/// The error returned by [`Query::from_slice`] and
25/// [`Query::from_continuous_str`].
26pub enum Error<E: Queryable>
27where
28    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
29{
30    #[error(transparent)]
31    /// The errors from the strict parser
32    Strict(#[from] strict::Error<E>),
33
34    #[error(transparent)]
35    /// The errors from the relaxed parser
36    Relaxed(#[from] relaxed::Error<E>),
37}
38
39// The argument passing is not in our control
40#[allow(clippy::trivially_copy_pass_by_ref)]
41fn format_unexpected_token(
42    expected: &TokenKind,
43    found: &Token,
44    f: &mut std::fmt::Formatter<'_>,
45) -> Result<(), std::fmt::Error> {
46    write!(
47        f,
48        "Expected token '{expected}' but got token '{found}' instead."
49    )
50}
51fn format_unknown_key<E: Queryable>(
52    err: &<E::KeyValue as QueryKeyValue>::Err,
53    key: &str,
54    _at: &TokenSpan,
55    f: &mut std::fmt::Formatter<'_>,
56) -> Result<(), std::fmt::Error>
57where
58    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
59{
60    write!(f, "The input stream countained a unknown key {key}: {err}")
61}
62
63#[allow(missing_docs)]
64pub mod parsing {
65    use crate::query::{
66        parse::{
67            parser::format_unexpected_token,
68            tokenizer::{Token, TokenKind, TokenSpan},
69        },
70        queryable::{QueryKeyValue, Queryable},
71    };
72
73    #[derive(Debug, thiserror::Error)]
74    pub enum Error<E: Queryable>
75    where
76        <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
77    {
78        #[error(fmt = format_unexpected_token)]
79        UnexpectedToken { expected: TokenKind, found: Token },
80
81        /// A key in a `MatchKey` expression was unknown.
82        #[error("The input stream countained a unknown key {key}: {err}")]
83        UnknownKey {
84            /// The returned error
85            err: <E::KeyValue as QueryKeyValue>::Err,
86
87            /// The key we found.
88            key: String,
89
90            /// The span this key takes up.
91            at: TokenSpan,
92        },
93    }
94}
95
96pub(crate) struct Parser<'a, E: Queryable> {
97    tokenizer: Tokenizer<'a>,
98    user_state: &'a <E::KeyValue as QueryKeyValue>::UserState,
99}
100
101impl<'a, E: Queryable> Parser<'a, E>
102where
103    <E::KeyValue as QueryKeyValue>::Err: std::fmt::Debug + std::fmt::Display,
104{
105    pub(crate) fn new(
106        user_state: &'a <E::KeyValue as QueryKeyValue>::UserState,
107        tokenizer: Tokenizer<'a>,
108    ) -> Self {
109        Self {
110            tokenizer,
111            user_state,
112        }
113    }
114
115    /// Start the parsing process and drive it to completion
116    pub(crate) fn parse(&'a mut self, mode: ParseMode) -> Result<Query<E>, Error<E>> {
117        let matcher = match mode {
118            ParseMode::Strict => strict::Parser::parse(self)?,
119            ParseMode::Relaxed => relaxed::Parser::parse(self)?,
120        };
121
122        Ok(Query {
123            root: Some(matcher),
124        })
125    }
126
127    fn parse_key_from(
128        &self,
129        key_tokens: &[Token],
130        value: String,
131        value_tokens_end: usize,
132    ) -> Result<E::KeyValue, parsing::Error<E>> {
133        let key = key_tokens.iter().fold(String::new(), |mut acc, t| {
134            if let TokenKind::Char(ch) = t.kind {
135                acc.push(ch);
136                acc
137            } else {
138                unreachable!("We filtered by chars in the `take_while`");
139            }
140        });
141
142        E::KeyValue::from_key_value(self.user_state, &key, value).map_err(|err| {
143            parsing::Error::UnknownKey {
144                err,
145                key,
146                at: TokenSpan {
147                    start: key_tokens.first().expect("Exists").span.start,
148                    stop: value_tokens_end,
149                },
150            }
151        })
152    }
153
154    fn parse_value_from(tokens: &[Token]) -> String {
155        tokens.iter().fold(String::new(), |mut acc, t| {
156            if let TokenKind::Char(ch) = t.kind {
157                acc.push(ch);
158                acc
159            } else {
160                unreachable!("We filtered by Chars()s in the `take_while`");
161            }
162        })
163    }
164
165    fn expect(&mut self, expected: TokenKind) -> Result<Token, parsing::Error<E>> {
166        let next = self.tokenizer.next_token();
167        if next.kind == expected {
168            Ok(next)
169        } else {
170            Err(parsing::Error::UnexpectedToken {
171                expected,
172                found: next,
173            })
174        }
175    }
176
177    fn take_while<F>(&mut self, pred: F) -> Result<Vec<Token>, parsing::Error<E>>
178    where
179        F: Fn(TokenKind) -> bool,
180    {
181        let mut output = vec![];
182        loop {
183            let token = self.tokenizer.peek();
184
185            if !pred(token.kind) {
186                return Ok(output);
187            }
188
189            output.push(self.tokenizer.next_token());
190        }
191    }
192}
193
194#[cfg(test)]
195pub(crate) mod test {
196    macro_rules! query {
197        ($name:ident {
198            lhs: $lhs:tt,
199            rhs: $rhs:tt $(,)?
200        }) => {
201            Query {
202                root: Some($name {
203                    lhs: Box::new(query!(@run $lhs)),
204                    rhs: Box::new(query!(@run $rhs)),
205                })
206            }
207        };
208
209        (@run {$name:ident {
210            lhs: $lhs:tt,
211            rhs: $rhs:tt $(,)?
212        } $(,)?}) => {
213                $name {
214                    lhs: Box::new(query!(@run $lhs)),
215                    rhs: Box::new(query!(@run $rhs)),
216                }
217        };
218        (@run { Match(Key1 , $value:tt) }) => {
219            Match {
220                key_value: Key1($value),
221            }
222        };
223        (@run { Match(Key2 , $value:expr) }) => {
224            Match {
225                key_value: Key2($value.to_owned()),
226            }
227        };
228        (@run { Match(Key3 , $number:expr) }) => {
229            Match {
230                key_value: Key3 {
231                    _value: $number,
232                    original: stringify!($number).to_owned(),
233                },
234            }
235        };
236    }
237    pub(crate) use query;
238
239    use crate::query::{
240        Matcher, Query,
241        queryable::{QueryKeyValue, Queryable},
242    };
243
244    impl PartialEq for Query<QueryTestObj> {
245        fn eq(&self, other: &Self) -> bool {
246            if let Some(me) = &self.root {
247                if let Some(other) = &other.root {
248                    me == other
249                } else {
250                    false
251                }
252            } else {
253                self.root.is_none() && other.root.is_none()
254            }
255        }
256    }
257
258    impl PartialEq for Matcher<QueryTestObj> {
259        fn eq(&self, other: &Self) -> bool {
260            match self {
261                Matcher::Or {
262                    lhs: me_lhs,
263                    rhs: me_rhs,
264                } => match other {
265                    Matcher::Or { lhs, rhs } => me_lhs == lhs && me_rhs == rhs,
266                    Matcher::And { .. } | Matcher::Match { .. } => false,
267                },
268                Matcher::And {
269                    lhs: me_lhs,
270                    rhs: me_rhs,
271                } => match other {
272                    Matcher::And { lhs, rhs } => me_lhs == lhs && me_rhs == rhs,
273                    Matcher::Or { .. } | Matcher::Match { .. } => false,
274                },
275                Matcher::Match {
276                    key_value: me_key_value,
277                } => match other {
278                    Matcher::Or { .. } | Matcher::And { .. } => false,
279                    Matcher::Match { key_value } => key_value == me_key_value,
280                },
281            }
282        }
283    }
284
285    #[derive(Debug)]
286    pub(crate) struct QueryTestObj;
287
288    impl Queryable for QueryTestObj {
289        type KeyValue = QueryTestKeyValue;
290
291        fn matches(&self, _key: &Self::KeyValue) -> bool {
292            true
293        }
294    }
295
296    #[derive(Debug, PartialEq, Eq, Clone)]
297    pub(crate) enum QueryTestKeyValue {
298        Key1(QueryTestKey1),
299        Key2(String),
300        Key3 { _value: u32, original: String },
301    }
302
303    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
304    pub(crate) enum QueryTestKey1 {
305        Value1,
306        Value2,
307    }
308
309    impl QueryKeyValue for QueryTestKeyValue {
310        type Err = QueryTestError;
311        type UserState = ();
312
313        fn from_key_value(
314            _user_state: &Self::UserState,
315            key: &str,
316            value: String,
317        ) -> Result<Self, Self::Err>
318        where
319            Self: Sized,
320        {
321            match key {
322                "key1" => Ok(Self::Key1(match value.as_str() {
323                    "value1" => QueryTestKey1::Value1,
324                    "value2" => QueryTestKey1::Value2,
325                    other => todo!("{other}"),
326                })),
327                "key2" => Ok(Self::Key2(value)),
328                "key3" => Ok(Self::Key3 {
329                    _value: value.parse().unwrap(),
330                    original: value,
331                }),
332                other => todo!("{other}"),
333            }
334        }
335
336        fn from_value(_user_state: &Self::UserState, value: String) -> Result<Self, Self::Err>
337        where
338            Self: Sized,
339        {
340            Ok(Self::Key2(value))
341        }
342
343        fn to_key_and_value(&self) -> (&str, &str)
344        where
345            Self: Sized,
346        {
347            match self {
348                QueryTestKeyValue::Key1(query_test_key1) => {
349                    let query_test_key1_str = match query_test_key1 {
350                        QueryTestKey1::Value1 => "value1",
351                        QueryTestKey1::Value2 => "value2",
352                    };
353
354                    ("key1", query_test_key1_str)
355                }
356                QueryTestKeyValue::Key2(val) => ("key2", val.as_str()),
357                QueryTestKeyValue::Key3 { original, .. } => ("key3", original.as_str()),
358            }
359        }
360    }
361
362    #[derive(Debug, thiserror::Error)]
363    pub(crate) enum QueryTestError {}
364}