1use 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 } (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 } (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 } (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}