combi/tokens/
options.rs

1//! A combi parser for parsing a structure out of order.
2
3use std::{
4    collections::{HashMap, LinkedList},
5    marker::PhantomData,
6};
7
8use crate::{
9    core::{choice, mapall, mapsuc, seq, seqdiff, DiffRes},
10    tokens::{
11        basic::{collectuntil, getident, gettoken, matchpunct, peekident, peekpunct, terminal},
12        derived::listseptrailing,
13        error::error,
14        TokenDiagnostic, TokenIter, TokenParser,
15    },
16    Combi, CombiResult,
17};
18use proc_macro2::{Span, TokenStream};
19use proc_macro_error2::{Diagnostic, Level};
20use syn::Ident;
21
22pub trait OptParse: Sized {
23    type Curr;
24    type Rest;
25    type All;
26
27    fn construct(
28        self,
29        sep_tk: char,
30        prev: impl TokenParser<(Ident, TokenStream)>,
31    ) -> impl TokenParser<(Self::All, HashMap<Ident, TokenStream>)>;
32
33    fn error_key(&self, options: &mut Vec<&'static str>);
34
35    fn gen(self, sep_tk: char) -> impl TokenParser<Self::All> {
36        let mut options = Vec::new();
37        self.error_key(&mut options);
38        let options_available = options.join(", ");
39        let options_available2 = options_available.clone();
40        mapall(
41            self.construct(
42                sep_tk,
43                error(gettoken, move |t| {
44                    Diagnostic::spanned(
45                        t.span(),
46                        Level::Error,
47                        format!("Expected {options_available}"),
48                    )
49                }),
50            ),
51            move |(value, others)| {
52                let errors = others
53                    .into_keys()
54                    .map(|k| {
55                        Diagnostic::spanned(
56                            k.span(),
57                            Level::Error,
58                            format!("{k} is not available, must be one of: {options_available2}"),
59                        )
60                    })
61                    .collect::<LinkedList<_>>();
62                if errors.is_empty() {
63                    CombiResult::Suc(value)
64                } else {
65                    CombiResult::Err(
66                        TokenDiagnostic::from_list(errors).expect(
67                            "Non-empty, so at least one element, so we must have a diagnostic",
68                        ),
69                    )
70                }
71            },
72        )
73    }
74}
75
76pub struct OptEnd;
77
78impl OptParse for OptEnd {
79    type Curr = ();
80    type Rest = ();
81    type All = ();
82
83    fn construct(
84        self,
85        _sep_tk: char,
86        prev: impl TokenParser<(Ident, TokenStream)>,
87    ) -> impl TokenParser<(Self::All, HashMap<Ident, TokenStream>)> {
88        mapall(listseptrailing(',', prev), |values| {
89            let mut uniques: HashMap<Ident, TokenStream> = HashMap::new();
90            let mut errors = LinkedList::new();
91            for (key, value) in values {
92                if let Some((k2, _)) = uniques.get_key_value(&key) {
93                    errors.push_back(
94                        Diagnostic::spanned(
95                            key.span(),
96                            Level::Error,
97                            format!("Duplicate option `{key}`"),
98                        )
99                        .span_error(k2.span(), String::from("originally defined here")),
100                    )
101                } else {
102                    uniques.insert(key, value);
103                }
104            }
105            if errors.is_empty() {
106                CombiResult::Suc(((), uniques))
107            } else {
108                CombiResult::Err(
109                    TokenDiagnostic::from_list(errors)
110                        .expect("Non-empty, so at least one element, so we must have a diagnostic"),
111                )
112            }
113        })
114    }
115
116    fn error_key(&self, _options: &mut Vec<&'static str>) {}
117}
118
119pub struct OptField<O, P: TokenParser<O>, F: Fn() -> P> {
120    name: &'static str,
121    parser: F,
122    phantom: PhantomData<O>,
123}
124
125impl<O, P: TokenParser<O>, F: Fn() -> P> OptField<O, P, F> {
126    pub fn new(name: &'static str, parser: F) -> Self {
127        Self {
128            name,
129            parser,
130            phantom: PhantomData,
131        }
132    }
133}
134
135impl<O, P: TokenParser<O>, R: OptParse, F: Fn() -> P> OptParse for (OptField<O, P, F>, R) {
136    type Curr = O;
137    type Rest = R::All;
138    type All = (Option<O>, R::All);
139
140    fn construct(
141        self,
142        sep_tk: char,
143        prev: impl TokenParser<(Ident, TokenStream)>,
144    ) -> impl TokenParser<(Self::All, HashMap<Ident, TokenStream>)> {
145        let (
146            OptField {
147                name,
148                parser,
149                phantom: _,
150            },
151            rest,
152        ) = self;
153
154        mapall(
155            rest.construct(
156                sep_tk,
157                choice(
158                    peekident(name),
159                    seq(
160                        mapsuc(seq(getident(), matchpunct(sep_tk)), |(k, _)| k),
161                        collectuntil(peekpunct(',')),
162                    ),
163                    prev,
164                ),
165            ),
166            move |(rest, mut uniques)| {
167                if let Some((key, _)) = uniques.get_key_value(&Ident::new(name, Span::call_site()))
168                {
169                    let key = key.clone();
170                    let val = uniques
171                        .remove(&key)
172                        .expect("Key was use for access already taken from the map");
173
174                    match (seqdiff(parser(), terminal)).comp(TokenIter::from(val, key.span())) {
175                        (DiffRes::First(_), CombiResult::Suc(_)) => {
176                            unreachable!("Would pass to second")
177                        } // TODO: find nicer way around this from combi
178                        (DiffRes::Second(()), CombiResult::Suc((val, ()))) => {
179                            CombiResult::Suc(((Some(val), rest), uniques))
180                        }
181                        (DiffRes::First(_), CombiResult::Con(c)) => CombiResult::Con(c),
182                        (DiffRes::First(_), CombiResult::Err(e)) => CombiResult::Err(e),
183                        (DiffRes::Second(()), CombiResult::Con(c)) => CombiResult::Con(c),
184                        (DiffRes::Second(()), CombiResult::Err(e)) => CombiResult::Err(e),
185                    }
186                } else {
187                    CombiResult::Suc(((None, rest), uniques))
188                }
189            },
190        )
191    }
192
193    fn error_key(&self, options: &mut Vec<&'static str>) {
194        options.push(self.0.name);
195        self.1.error_key(options);
196    }
197}
198
199pub struct MustField<O, P: TokenParser<O>, F: Fn() -> P> {
200    name: &'static str,
201    parser: F,
202    phantom: PhantomData<O>,
203}
204
205impl<O, P: TokenParser<O>, F: Fn() -> P> MustField<O, P, F> {
206    pub fn new(name: &'static str, parser: F) -> Self {
207        Self {
208            name,
209            parser,
210            phantom: PhantomData,
211        }
212    }
213}
214
215impl<O, P: TokenParser<O>, R: OptParse, F: Fn() -> P> OptParse for (MustField<O, P, F>, R) {
216    type Curr = O;
217    type Rest = R::All;
218    type All = (O, R::All);
219
220    fn construct(
221        self,
222        sep_tk: char,
223        prev: impl TokenParser<(Ident, TokenStream)>,
224    ) -> impl TokenParser<(Self::All, HashMap<Ident, TokenStream>)> {
225        let (
226            MustField {
227                name,
228                parser,
229                phantom: _,
230            },
231            rest,
232        ) = self;
233
234        mapall(
235            rest.construct(
236                sep_tk,
237                choice(
238                    peekident(name),
239                    seq(
240                        mapsuc(seq(getident(), matchpunct(sep_tk)), |(k, _)| k),
241                        collectuntil(peekpunct(',')),
242                    ),
243                    prev,
244                ),
245            ),
246            move |(rest, mut uniques)| {
247                if let Some((key, _)) = uniques.get_key_value(&Ident::new(name, Span::call_site()))
248                {
249                    let key = key.clone();
250                    let val = uniques
251                        .remove(&key)
252                        .expect("Key was use for access already taken from the map");
253
254                    match (seqdiff(parser(), terminal)).comp(TokenIter::from(val, key.span())) {
255                        (DiffRes::First(_), CombiResult::Suc(_)) => {
256                            unreachable!("Would pass to second")
257                        } // TODO: find nicer way around this from combi
258                        (DiffRes::Second(()), CombiResult::Suc((val, ()))) => {
259                            CombiResult::Suc(((val, rest), uniques))
260                        }
261                        (DiffRes::First(_), CombiResult::Con(c)) => CombiResult::Con(c),
262                        (DiffRes::First(_), CombiResult::Err(e)) => CombiResult::Err(e),
263                        (DiffRes::Second(()), CombiResult::Con(c)) => CombiResult::Con(c),
264                        (DiffRes::Second(()), CombiResult::Err(e)) => CombiResult::Err(e),
265                    }
266                } else {
267                    CombiResult::Con(TokenDiagnostic::from(Diagnostic::spanned(
268                        Span::call_site(),
269                        Level::Error,
270                        format!("Missing required field `{name}`"),
271                    )))
272                }
273            },
274        )
275    }
276
277    fn error_key(&self, options: &mut Vec<&'static str>) {
278        options.push(self.0.name);
279        self.1.error_key(options);
280    }
281}
282
283pub struct DefaultField<O, P: TokenParser<O>, F: Fn() -> P, D: Fn() -> O> {
284    name: &'static str,
285    parser: F,
286    default: D,
287    phantom: PhantomData<O>,
288}
289
290impl<O, P: TokenParser<O>, F: Fn() -> P, D: Fn() -> O> DefaultField<O, P, F, D> {
291    pub fn new(name: &'static str, parser: F, default: D) -> Self {
292        Self {
293            name,
294            parser,
295            default,
296            phantom: PhantomData,
297        }
298    }
299}
300
301impl<O, P: TokenParser<O>, R: OptParse, F: Fn() -> P, D: Fn() -> O> OptParse
302    for (DefaultField<O, P, F, D>, R)
303{
304    type Curr = O;
305    type Rest = R::All;
306    type All = (O, R::All);
307
308    fn construct(
309        self,
310        sep_tk: char,
311        prev: impl TokenParser<(Ident, TokenStream)>,
312    ) -> impl TokenParser<(Self::All, HashMap<Ident, TokenStream>)> {
313        let (
314            DefaultField {
315                name,
316                parser,
317                default,
318                phantom: _,
319            },
320            rest,
321        ) = self;
322
323        mapall(
324            rest.construct(
325                sep_tk,
326                choice(
327                    peekident(name),
328                    seq(
329                        mapsuc(seq(getident(), matchpunct(sep_tk)), |(k, _)| k),
330                        collectuntil(peekpunct(',')),
331                    ),
332                    prev,
333                ),
334            ),
335            move |(rest, mut uniques)| {
336                if let Some((key, _)) = uniques.get_key_value(&Ident::new(name, Span::call_site()))
337                {
338                    let key = key.clone();
339                    let val = uniques
340                        .remove(&key)
341                        .expect("Key was use for access already taken from the map");
342
343                    match (seqdiff(parser(), terminal)).comp(TokenIter::from(val, key.span())) {
344                        (DiffRes::First(_), CombiResult::Suc(_)) => {
345                            unreachable!("Would pass to second")
346                        } // TODO: find nicer way around this from combi
347                        (DiffRes::Second(()), CombiResult::Suc((val, ()))) => {
348                            CombiResult::Suc(((val, rest), uniques))
349                        }
350                        (DiffRes::First(_), CombiResult::Con(c)) => CombiResult::Con(c),
351                        (DiffRes::First(_), CombiResult::Err(e)) => CombiResult::Err(e),
352                        (DiffRes::Second(()), CombiResult::Con(c)) => CombiResult::Con(c),
353                        (DiffRes::Second(()), CombiResult::Err(e)) => CombiResult::Err(e),
354                    }
355                } else {
356                    CombiResult::Suc(((default(), rest), uniques))
357                }
358            },
359        )
360    }
361
362    fn error_key(&self, options: &mut Vec<&'static str>) {
363        options.push(self.0.name);
364        self.1.error_key(options);
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    #![allow(clippy::unwrap_used)]
371
372    use crate::tokens::basic::matchident;
373
374    use super::*;
375    use quote::quote;
376
377    fn get_result<T>(
378        parser: &impl TokenParser<T>,
379        input: TokenStream,
380    ) -> Result<T, TokenDiagnostic> {
381        parser
382            .comp(TokenIter::from(input, Span::call_site()))
383            .1
384            .to_result()
385    }
386
387    #[test]
388    fn basic_parse() {
389        let config_opts = (
390            OptField::new("foo", || mapsuc(getident(), |_| true)),
391            (OptField::new("bar", getident), OptEnd),
392        )
393            .gen(':');
394
395        let input1 = quote! {
396            foo: foo,
397            bar: bar,
398        };
399
400        let input2 = quote! {
401            bar: bar,
402            foo: foo,
403        };
404
405        let (_, (_, ())) = get_result(&config_opts, input1).unwrap();
406        let (_, (_, ())) = get_result(&config_opts, input2).unwrap();
407    }
408
409    #[test]
410    fn must_parse() {
411        let config_opts = (
412            OptField::new("foo", || mapsuc(getident(), |_| true)),
413            (
414                OptField::new("bar", getident),
415                (MustField::new("baz", || matchident("bazingah")), OptEnd),
416            ),
417        )
418            .gen(':');
419
420        let input1 = quote! {
421            foo: foo,
422            bar: bar,
423            baz: bazingah,
424        };
425
426        let input2 = quote! {
427            bar: bar,
428            baz: bazingah,
429            foo: foo,
430        };
431
432        let error1 = quote! {
433            bar: bar,
434            foo: foo,
435        };
436
437        let (_, (_, (_, ()))) = get_result(&config_opts, input1).unwrap();
438        let (_, (_, (_, ()))) = get_result(&config_opts, input2).unwrap();
439
440        assert!(get_result(&config_opts, error1).is_err());
441    }
442}