methods_enum/
lib.rs

1#![doc = include_str!("../README.md")]
2//!
3//! Two macros for easy implementation of 'state' design pattern and other dynamic polymorphism using enum instead of dyn Trait   
4//!
5//! [crate documentation](crate)
6
7use core::str::FromStr;
8use proc_macro::TokenTree::{Group, Ident, Punct};
9use proc_macro::{token_stream::IntoIter, Delimiter, Delimiter::Brace, Spacing, Span, TokenStream};
10use proc_macro::{Group as Gr, Ident as Idn, Punct as Pn};
11use std::iter::once;
12
13enum ParseStates {
14    Start,
15    Vis,
16    Name,
17    Args,
18    Minus,
19    Gt,
20    Out,
21}
22use ParseStates::{Args, Gt, Minus, Name, Out, Start, Vis};
23
24// region: region gen
25
26#[derive(Default)]
27struct Attr {
28    enum_name: String,
29    enum_ident: Option<Idn>,
30    run_method: String,
31    drv_dbg: bool,
32    out_ident: Option<Idn>,
33    out_dbg: bool,
34    strict_types: bool,
35}
36impl Attr {
37    fn new(attr_ts: TokenStream) -> Attr {
38        let mut attr_it = attr_ts.into_iter();
39        let attr = match [attr_it.next(), attr_it.next(), attr_it.next()] {
40            [Some(Ident(id)), Some(Punct(p)), Some(Ident(r_id))] if ",:".contains(p.as_char()) => {
41                Attr {
42                    enum_name: id.to_string(),
43                    enum_ident: Some(id),
44                    run_method: r_id.to_string(),
45                    drv_dbg: p.as_char() == ':',
46                    ..Default::default()
47                }
48            }
49            _ => panic!("#[gen]: Syntax error in attribute #[methods_enum::gen(?? "),
50        };
51        match [attr_it.next(), attr_it.next()] {
52            [None, None] => attr,
53            [Some(Punct(p)), Some(Ident(out_id))] if ",=".contains(p.as_char()) => Attr {
54                out_ident: Some(out_id),
55                out_dbg: p.as_char() == '=',
56                strict_types: matches!(attr_it.next(), Some(Punct(p)) if p.as_char() == '!'),
57                ..attr
58            },
59            _ => panic!(
60                "#[gen]: Syntax error in attribute #[methods_enum::gen({}:{}??..",
61                attr.enum_name, attr.run_method
62            ),
63        }
64    }
65}
66
67#[derive(Default)]
68struct Meth {
69    ident: Option<Idn>,
70    prev_ts: TokenStream,
71    vis: TokenStream,
72    args: TokenStream,
73    params: String,
74    typs: String,
75    out_span: Option<Span>,
76    out: TokenStream,
77    body: TokenStream,
78}
79
80impl Meth {
81    /// on successful parsing of the arguments returns `Minus`, otherwise - `Start`
82    fn args_parsing(&mut self, args_gr: Gr) -> ParseStates {
83        let mut args_it = args_gr.stream().into_iter();
84        let mut lg = 0;
85        let mut first = true;
86        let mut is_self = false;
87        self.params = String::new();
88        self.typs = String::new();
89        let st = loop {
90            match args_it.next() {
91                Some(Punct(p)) if p.as_char() == ',' && lg == 0 => {
92                    match [args_it.next(), args_it.next()] {
93                        [Some(Ident(id)), Some(Punct(p))] if p.as_char() == ':' => {
94                            if first {
95                                if !is_self {
96                                    break Start;
97                                }
98                                first = false;
99                            } else {
100                                self.params.push_str(", ");
101                                self.typs.push_str(", ");
102                            }
103                            self.params.push_str(&id.to_string());
104                        }
105                        [Some(_), _] => break Start,
106                        [None, _] => break if is_self { Minus } else { Start },
107                    }
108                }
109                Some(Punct(p)) if "<>".contains(p.as_char()) => {
110                    lg = lg + if p.as_char() == '<' { 1 } else { -1 };
111                    self.typs.push(p.as_char());
112                }
113                Some(Ident(id)) if id.to_string() == "impl" => break Start,
114                Some(Ident(id)) if first && id.to_string() == "self" => is_self = true,
115                Some(Ident(id)) if !first && id.to_string() == "mut" => self.typs.push_str("mut "),
116                Some(tt) if !first => self.typs.push_str(&tt.to_string()),
117                None => break if is_self { Minus } else { Start },
118                _ => (),
119            }
120        };
121        if let Minus = st {
122            self.args = args_gr.stream();
123            self.out_span = None;
124            self.out = TokenStream::new();
125        }
126        self.prev_ts.extend(once(Group(args_gr)));
127        st
128    }
129
130    fn prev_extend(&mut self, tt: proc_macro::TokenTree, new_st: ParseStates) -> ParseStates {
131        self.prev_ts.extend(once(tt));
132        new_st
133    }
134
135    fn vec(iit: &mut IntoIter, attr: &Attr) -> Vec<Meth> {
136        let mut methods: Vec<Meth> = Vec::new();
137        let mut m = Meth::default();
138        let mut state = Start;
139        for tt in iit {
140            state = match (state, tt) {
141                (Start, Ident(id)) if id.to_string() == "pub" => {
142                    m.vis.extend(once(Ident(id.clone())));
143                    m.prev_extend(Ident(id), Vis)
144                }
145                (Vis, Group(gr)) if gr.delimiter() == Delimiter::Parenthesis => {
146                    m.vis.extend(once(Group(gr.clone())));
147                    m.prev_extend(Group(gr), Vis)
148                }
149                (st @ (Start | Vis), Ident(id)) if id.to_string() == "fn" => {
150                    if let Start = st {
151                        m.vis = TokenStream::new()
152                    };
153                    m.prev_extend(Ident(id), Name)
154                }
155                (Name, Ident(id)) => {
156                    m.prev_ts.extend(once(Ident(id.clone())));
157                    if id.to_string() == attr.run_method {
158                        break;
159                    }
160                    m.ident = Some(id);
161                    Args
162                }
163                (Args, Group(gr)) if gr.delimiter() == Delimiter::Parenthesis => m.args_parsing(gr),
164                (Minus, Punct(p)) if p.as_char() == '-' => m.prev_extend(Punct(p), Gt),
165                (Gt, Punct(p)) if p.as_char() == '>' => {
166                    m.out_span = Some(p.span());
167                    m.prev_extend(Punct(p), Out)
168                }
169                (Out, Group(gr)) if gr.delimiter() == Brace && attr.out_ident.is_none() => {
170                    m.prev_extend(Group(gr), Start) // skip fn with body
171                }
172                (Minus, Group(gr)) if gr.delimiter() == Brace => m.prev_extend(Group(gr), Start),
173                (Out, Ident(id)) if id.to_string() == "where" => m.prev_extend(Ident(id), Start),
174                (Minus | Out, Punct(p)) if p.as_char() == ';' => {
175                    methods.push(m);
176                    m = Meth::default();
177                    Start
178                }
179                (Out, Group(gr)) if gr.delimiter() == Brace => {
180                    m.body = gr.stream();
181                    methods.push(m);
182                    m = Meth::default();
183                    Start
184                }
185                (Out, tt) => {
186                    m.out.extend(TokenStream::from(tt.clone()));
187                    m.prev_extend(tt, Out)
188                }
189                (_, tt) => m.prev_extend(tt, Start),
190            }
191        }
192        m.ident = None;
193        methods.push(m);
194        methods
195    }
196}
197
198fn ts_to_doc(ts: &TokenStream) -> String {
199    let s = ts.to_string().replace("& ", "&").replace(":: ", "::");
200    let inds: Vec<_> = s.match_indices(&['!', '(', ',', ':', '<', '>']).map(|t| t.0).collect();
201    ([0].iter().chain(inds.iter()))
202        .zip(inds.iter().chain(&[s.len()]))
203        .map(|(&a, &b)| s[a..b].trim_end())
204        .collect()
205}
206
207/// Based on the method signatures of the `impl` block, it generates: `enum` with parameters
208/// from argument tuples and generates `{}` bodies of these methods with calling the argument
209/// handler method from this `enum`.
210///
211/// This allows the handler method to control the behavior of the methods depending on the context.
212///
213/// #### Macro call syntax
214/// **`#[methods_enum::gen(`*EnumName* `, ` | `: ` *handler_name* ( `, ` | ` = ` *OutName* `!`<sup>?</sup> )<sup>?</sup> `)]`**
215///
216/// where:
217/// - ***EnumName***: The name of the automatically generated enum.
218/// - ***handler_name***: Handler method name
219/// - ***OutName*** (in case of more than one return type and/or to specify a default return values)
220/// : The name of an automatically generated enum with variants from the return types.
221///
222/// Replacing the delimiter **`, `** after *EnumName* with **`: `** or before *OutName* with **` = `**
223/// will automatically add the `#[derive(Debug)]` attribute to the corresponding enum.
224///
225/// Setting `!` after *OutName* enables checking the returned variant by its name, not by its type.
226///
227/// The macro attribute is set before an individual (non-Trait) impl block. Based on the method signatures of the impl block, it generates: `enum` with parameters from argument tuples and generates `{}` bodies of these methods with calling the argument handler method from this `enum`.  
228/// This allows the handler method to control the behavior of methods depending on the context, including structuring enum-matching by state.
229///
230/// ## Usage example
231///
232/// [Chapter 17.3 "Implementing an Object-Oriented Design Pattern" of the rust-book](https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html) shows the implementation of the *state pattern* in Rust, which provides the following behavior:
233/// ```rust ignore
234/// pub fn main() {
235///     let mut post = blog::Post::new();
236///
237///     post.add_text("I ate a salad for lunch today");
238///     assert_eq!("", post.content());
239///     post.request_review(); // without request_review() - approve() should not work
240///     post.approve();  
241///     assert_eq!("I ate a salad for lunch today", post.content());
242/// }
243/// ```
244/// with macro **`#[gen()]`** this is solved like this:
245/// ```rust
246/// mod blog {
247///     enum State {
248///         Draft,
249///         PendingReview,
250///         Published,
251///     }
252///
253///     pub struct Post {
254///         state: State,
255///         content: String,
256///     }
257///
258///     #[methods_enum::gen(Meth, run_methods)]
259///     impl Post {
260///         pub fn add_text(&mut self, text: &str);
261///         pub fn request_review(&mut self);
262///         pub fn approve(&mut self);
263///         pub fn content(&mut self) -> &str;
264///
265///         #[rustfmt::skip]
266///         fn run_methods(&mut self, method: Meth) -> &str {
267///             match self.state {
268///                 State::Draft => match method {
269///                     Meth::add_text(text) => { self.content.push_str(text); "" }
270///                     Meth::request_review() => { self.state = State::PendingReview; "" }
271///                     _ => "",
272///                 },
273///                 State::PendingReview => match method {
274///                     Meth::approve() => { self.state = State::Published; "" }
275///                     _ => "",
276///                 },
277///                 State::Published => match method {
278///                     Meth::content() => &self.content,
279///                     _ => "",
280///                 },
281///             }
282///         }
283///
284///         pub fn new() -> Post {
285///             Post { state: State::Draft, content: String::new() }
286///         }
287///     }
288/// }
289/// ```
290/// In the handler method (in this case, `run_methods`), simply write for each state which methods should work and how.
291///
292/// The macro duplicates the output for the compiler in the doc-comments.
293/// Therefore, in the IDE[^rust_analyzer], you can always see the declaration of the generated `enum` and the generated method bodies.
294/// 
295/// [^rust_analyzer]: *rust-analyzer may not expand proc-macro when running under nightly or old rust edition.* In this case it is recommended to set in its settings: [`"rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" }`](https://rust-analyzer.github.io/manual.html#toolchain)
296///
297/// ## Restrictions
298/// 
299/// - The **`#[gen(...)]`** macro does not work on generic methods (including lifetime generics). As a general rule, methods with <...> before the argument list, with `where` before the body, or `impl` in the argument type declaration will be silently ignored for inclusion in `enum`.
300/// - The macro will ignore signatures with destructured arguments.
301/// - The macro ignores also methods with a `mut` prefix in front of a method argument name (except  `self`): move such an argument to a mut variable in the body of the handler method.
302/// - The `self` form of all methods of the same `enum` must be the same and match the `self` form of the handler method. As a rule, it is either `&mut self` everywhere or `self` in methods + `mut self` in the handler method. However, it is allowed to group method signatures into multiple `impl` blocks with different `enum` and handler methods. See example below.
303/// 
304/// ## [gen macro details and use cases](attr.gen.html#gen-macro-details-and-use-cases)
305///
306#[doc = include_str!("gen_details.md")]
307#[proc_macro_attribute]
308pub fn gen(attr_ts: TokenStream, item_ts: TokenStream) -> TokenStream {
309    // std::fs::write("target/debug/item_ts.log", format!("{}\n\n{0:#?}", item_ts)).unwrap();
310
311    let attr = Attr::new(attr_ts);
312
313    let mut item_it = item_ts.into_iter();
314
315    let mut item_ts = TokenStream::from_iter(
316        item_it.by_ref().take_while(|tt| !matches!(tt, Ident(id) if id.to_string() == "impl")),
317    );
318    item_ts.extend(once(Ident(Idn::new("impl", Span::call_site()))));
319
320    let mut block_it = match [item_it.next(), item_it.next(), item_it.next()] {
321        [Some(Ident(item_n)), Some(Group(gr)), None] if gr.delimiter() == Brace => {
322            item_ts.extend(once(Ident(item_n)));
323            gr.stream().into_iter()
324        }
325        m => panic!(
326            "#[gen]: SYNTAX ERROR 
327'attribute #[gen] must be set on block impl without treyds and generics': {m:?}"
328        ),
329    };
330
331    let methods = Meth::vec(&mut block_it, &attr);
332
333    let head = r##"
334        #[derive(Debug)]
335        #[allow(non_camel_case_types)]
336        /// Formed by macro [`#[methods_enum::gen(...)]`](https://docs.rs/methods-enum):
337        /// ```
338        /// #[derive(Debug)]
339        /// #[allow(non_camel_case_types)]
340        #[doc = "enum "##;
341    let head_w_o_dbg = head.lines().filter(|s| !s.ends_with("g)]")).collect::<Vec<_>>().join("\n");
342    //                 (name.0, out.1, span.2)
343    let mut outs: Vec<(String, String, Span)> = Vec::new();
344    let mut enum_doc = " {".to_string();
345    let mut enum_ts = TokenStream::new();
346    for m in methods.iter() {
347        if let Some(ident) = &m.ident {
348            enum_ts.extend(once(Ident(ident.clone())));
349            let typs = m.typs.replace('&', "&'a ");
350            enum_ts.extend(TokenStream::from_str(&format!("({typs}), ")));
351            enum_doc.push_str(&format!("\n    {ident}({typs}), "));
352            if let Some(out_span) = m.out_span {
353                outs.push((ident.to_string(), ts_to_doc(&m.out), out_span));
354            }
355        }
356    }
357    let lftm = if enum_doc.contains('&') { "<'a>" } else { "" };
358    enum_doc.push_str("\n}\n```\n---\nMethod bodies generated by the same macro:\n```");
359
360    let is_result = attr.out_ident.is_none() && outs.iter().any(|t| t.1.contains("Result<"));
361    let self_run_enum = format!("self.{}({}::", attr.run_method, attr.enum_name);
362    let mut methods_ts = TokenStream::new();
363    for m in methods {
364        methods_ts.extend(m.prev_ts);
365        if let Some(ident) = m.ident {
366            enum_doc.push_str(&format!(
367                "\n{}fn {ident}({})",
368                (ts_to_doc(&m.vis) + " ").trim_start(),
369                ts_to_doc(&m.args)
370            ));
371            let mut body_ts = TokenStream::new();
372            let out = if m.out.is_empty() {
373                enum_doc.push_str(" {");
374                if is_result {
375                    enum_doc.push_str("\n    #![allow(unused_must_use)]");
376                    body_ts.extend(TokenStream::from_str("#![allow(unused_must_use)]").unwrap());
377                }
378                String::new()
379            } else {
380                let name = ident.to_string();
381                let find_out = outs.iter().find(|t| t.0 == name).unwrap().1.clone();
382                enum_doc.push_str(&format!(" -> {find_out} {{"));
383                find_out
384            };
385            let call_run = format!("{self_run_enum}{ident}({}))", m.params);
386            if attr.out_ident.is_none() || m.out.is_empty() {
387                enum_doc.push_str(&format!("\n    {call_run}"));
388                body_ts.extend(TokenStream::from_str(&call_run).unwrap());
389                if m.out.is_empty() {
390                    enum_doc.push_str(";");
391                    body_ts.extend(once(Punct(Pn::new(';', Spacing::Alone))));
392                }
393            } else if let Some(out_ident) = &attr.out_ident {
394                enum_doc.push_str(&format!("\n    match {call_run} {{"));
395                body_ts.extend(TokenStream::from_str(&format!("match {call_run}")).unwrap());
396                let out_enum = out_ident.to_string() + "::";
397                let varname = format!("_{}", out_ident).to_lowercase();
398                let lside = if attr.strict_types {
399                    format!("{out_enum}{ident}(x)")
400                } else {
401                    (outs.iter())
402                        .filter_map(|(n, o, _)| (o == &out).then(|| out_enum.clone() + n + "(x)"))
403                        .reduce(|s, n| s + " | " + &n)
404                        .unwrap()
405                };
406                enum_doc.push_str(&format!("\n        {lside} => x,\n        {varname} => "));
407                let mut match_ts =
408                    TokenStream::from_str(&format!("{lside} => x, {varname} => ")).unwrap();
409                if m.body.is_empty() {
410                    let panic_s = format!(
411                        "panic!(\"Type mismatch in the {ident}() method:
412                    expected- {},
413                    found- {out_enum}{{}}\", {varname}.stype())",
414                        lside
415                            .replace("(x)", &format!("({out})"))
416                            .replace(" | ", "\n                            | ")
417                    );
418                    enum_doc.push_str(&panic_s);
419                    match_ts.extend(TokenStream::from_str(&panic_s).unwrap());
420                } else {
421                    enum_doc.push_str(
422                        &ts_to_doc(&m.body)
423                            .replace(" {", " {\n            ")
424                            .replace(", _ =>", ",\n            _ =>"),
425                    );
426                    match_ts.extend(m.body);
427                }
428                enum_doc.push_str("\n    }");
429                body_ts.extend(once(Group(Gr::new(Brace, match_ts))));
430            }
431            enum_doc.push_str("\n}");
432            methods_ts.extend(once(Group(Gr::new(Brace, body_ts))));
433        }
434    }
435    methods_ts.extend(block_it);
436    item_ts.extend(once(Group(Gr::new(Brace, methods_ts))));
437
438    let mut res_ts = TokenStream::from_str(&format!(
439        "{}{}{lftm}{}\"] enum ",
440        if attr.drv_dbg { head } else { &head_w_o_dbg },
441        attr.enum_name,
442        (enum_doc + "\n```").escape_debug().to_string()
443    ))
444    .unwrap();
445    res_ts.extend(once(Ident(attr.enum_ident.unwrap())));
446    res_ts.extend(TokenStream::from_str(lftm).unwrap());
447    res_ts.extend(once(Group(Gr::new(Brace, enum_ts))));
448
449    res_ts.extend(item_ts);
450
451    if let Some(out_ident) = &attr.out_ident {
452        enum_doc = " {\n    Unit,".to_string();
453        enum_ts = TokenStream::from_str("Unit, ").unwrap();
454        let indent = "\n            ";
455        let mut stype = format!(
456            "    fn stype(&self) -> &'static str {{
457        match self {{{indent}{out_ident}::Unit => \"Unit\","
458        );
459        let mut lftm = "";
460        for (name, mut out, span) in outs {
461            enum_ts.extend(once(Ident(Idn::new(&name, span))));
462            stype.push_str(&format!("{indent}{out_ident}::{name}(..) => \"{name}({out})\","));
463            if out.contains('&') {
464                lftm = "<'a>";
465                out = out.replace('&', "&'a ");
466            }
467            enum_ts.extend(TokenStream::from_str(&format!("({out}), ")));
468            enum_doc.push_str(&format!("\n    {name}({out}), "));
469        }
470        stype = format!("impl{lftm} {out_ident}{lftm} {{\n{stype}\n        }}\n    }}\n}}");
471        enum_doc = (enum_doc + "\n}\n\n" + &stype + "\n```").escape_debug().to_string();
472
473        res_ts.extend(TokenStream::from_str(&format!(
474            "{}{out_ident}{lftm}{enum_doc}\"] enum ",
475            if attr.out_dbg { head } else { &head_w_o_dbg }
476        )));
477        res_ts.extend(once(Ident(out_ident.clone())));
478        res_ts.extend(TokenStream::from_str(lftm).unwrap());
479        res_ts.extend(once(Group(Gr::new(Brace, enum_ts))));
480        res_ts.extend(TokenStream::from_str(&stype).unwrap());
481    }
482
483    if std::env::var("M_ENUM_DBG").map_or(false, |v| &v != "0") {
484        println!(
485            "\nM_ENUM_DBG - output to compiler input for enum {}:\n{}\n",
486            attr.enum_name, res_ts
487        );
488    }
489
490    res_ts
491}
492
493// endregion: gen
494
495//     #####     #####     #####     #####     #####     #####     #####     #####
496
497// region: region impl_match
498
499use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
500use std::hash::{Hash, Hasher};
501use std::mem;
502
503struct Flags {
504    panic: bool,
505    no_semnt: bool,
506}
507
508#[derive(Default)]
509struct Item {
510    name: String,
511    ident: Option<Idn>, // for enum here - its id, for impl - the id of the trait
512    it_enum: bool,
513    no_def: bool,
514    prev_ts: TokenStream,
515    group: TokenStream,
516    methods: Vec<MethIM>,
517}
518impl Item {
519    fn prev_extend(&mut self, tt: proc_macro::TokenTree, new_state: ParseStates) -> ParseStates {
520        if !self.no_def {
521            self.prev_ts.extend(once(tt))
522        }
523        if let Vis = new_state {
524            self.ident = None;
525            self.name = String::new();
526            Name
527        } else {
528            new_state
529        }
530    }
531
532    fn vec(ts: TokenStream) -> (Vec<Item>, HashMap<String, bool>, Flags) {
533        let mut items = Vec::new();
534        let mut mmap: HashMap<String, bool> = HashMap::new(); // v: bool = there is a generic
535        let mut impl_n = String::new();
536        let mut item = Item::default();
537        let mut lg = 0;
538        let mut state = Args;
539        let mut flags = Flags { no_semnt: true, panic: true };
540        if cfg!(debug_assertions) {
541            flags.no_semnt = false;
542            flags.panic = false;
543        }
544        for tt in ts {
545            state = match (state, tt, lg) {
546                (Args, Group(gr), 0) if gr.delimiter() == Delimiter::Parenthesis => {
547                    if cfg!(debug_assertions) {
548                        for fl in gr.stream() {
549                            match fl {
550                                Punct(p) if p.as_char() == '!' => flags.panic = true,
551                                Ident(id) => match &id.to_string().to_lowercase()[..] {
552                                    "ns" | "sn" => {
553                                        flags.no_semnt = true;
554                                        flags.panic = true;
555                                    }
556                                    _ => (),
557                                },
558                                _ => (),
559                            }
560                        }
561                    }
562                    Start
563                }
564                (Start | Args, Punct(p), 0) if p.as_char() == '@' => {
565                    item.it_enum = true;
566                    item.no_def = true;
567                    item.prev_extend(Punct(p), Vis)
568                }
569                (Start | Args, Ident(id), 0) => match &id.to_string()[..] {
570                    "impl" => item.prev_extend(Ident(id), Vis),
571                    "enum" => {
572                        item.it_enum = true;
573                        item.prev_extend(Ident(id), Vis)
574                    }
575                    _ => item.prev_extend(Ident(id), Start),
576                },
577                (Name, Ident(id), 0) if id.to_string() == "for" => item.prev_extend(Ident(id), Out),
578                (st @ (Name | Out), Ident(id), 0) => {
579                    match st {
580                        Name => item.ident = Some(id.clone()),
581                        _ => item.name = id.to_string(),
582                    }
583                    item.prev_extend(Ident(id), st)
584                }
585                (Name | Out, Group(gr), 0) if gr.delimiter() == Brace => {
586                    if item.ident.is_some() {
587                        if item.it_enum {
588                            item.group = gr.stream();
589                            item.name = item.ident.as_ref().unwrap().to_string();
590                            items.push(mem::take(&mut item));
591                        } else {
592                            if item.name.is_empty() {
593                                item.name = item.ident.as_ref().unwrap().to_string();
594                                item.ident = None;
595                            }
596                            if impl_n.is_empty() || impl_n == item.name {
597                                if impl_n.is_empty() {
598                                    impl_n = item.name.clone();
599                                }
600                                item.fill_methods(gr.stream(), &mut mmap);
601                                items.push(mem::take(&mut item));
602                            } else {
603                                item.prev_ts.extend(once(Group(gr)));
604                            }
605                        }
606                    }
607                    Start
608                }
609                (st, Punct(p), _) if "<>".contains(p.as_char()) => {
610                    lg = 0.max(lg + if p.as_char() == '<' { 1 } else { -1 });
611                    item.prev_extend(Punct(p), st)
612                }
613                (Args, tt, _) => item.prev_extend(tt, Start),
614                (st, tt, _) => item.prev_extend(tt, st),
615            }
616        }
617        item.name = String::new();
618        items.push(item);
619        (items, mmap, flags)
620    }
621
622    fn fill_methods(&mut self, ts: TokenStream, mmap: &mut HashMap<String, bool>) {
623        let mut m = MethIM::default();
624        let mut args: Option<TokenStream> = None;
625        let mut state = Start;
626        for tt in ts {
627            state = match (state, tt) {
628                (Start, Ident(id)) if id.to_string() == "fn" => m.prev_extend(Ident(id), Name),
629                (Name, Ident(id)) => {
630                    m.name = self.ident.as_ref().map_or(id.to_string(), |t| format!("{id}() {t}"));
631                    args = None;
632                    m.prev_extend(Ident(id), Args)
633                }
634                (Args, Punct(p)) if p.as_char() == '<' => {
635                    args = Some(TokenStream::from_iter(once(Ident(Idn::new("impl", p.span())))));
636                    m.prev_extend(Punct(p), Gt)
637                }
638                (Args, Group(gr)) if gr.delimiter() == Delimiter::Parenthesis => {
639                    args = Some(gr.stream());
640                    m.prev_extend(Group(gr), Gt)
641                }
642                (Gt, Group(gr)) if gr.delimiter() == Brace => m.prev_extend(Group(gr), Start),
643                (Gt, Punct(p)) if p.as_char() == ';' => m.prev_extend(Punct(p), Start),
644                (Gt, Punct(p)) if p.as_char() == '~' => Out,
645                (Gt | Args, tt) => m.prev_extend(tt, Gt),
646                (Out, Group(gr)) if gr.delimiter() == Brace => {
647                    if m.found_match(&gr) {
648                        mmap.insert(
649                            m.name.clone(), // v: bool = there is a generic
650                            args.take().map_or(false, |t| {
651                                t.into_iter()
652                                    .any(|tr| matches!(tr, Ident(id) if id.to_string() == "impl"))
653                            }),
654                        );
655                        self.methods.push(mem::take(&mut m));
656                    } else {
657                        m.prev_ts.extend(once(Group(gr)))
658                    }
659                    Start
660                }
661                (_, tt) => m.prev_extend(tt, Start),
662            }
663        }
664        m.name = String::new();
665        self.methods.push(m);
666    }
667}
668
669#[derive(Default)]
670struct MethIM {
671    name: String,
672    prev_ts: TokenStream,
673    body: TokenStream,
674    dflt_arm: Option<Gr>,
675    tail: TokenStream,
676}
677impl MethIM {
678    fn prev_extend(&mut self, tt: proc_macro::TokenTree, new_st: ParseStates) -> ParseStates {
679        self.prev_ts.extend(once(tt));
680        new_st
681    }
682
683    fn found_match(&mut self, body: &Gr) -> bool {
684        self.body = TokenStream::new();
685        let mut iit = body.stream().into_iter();
686        let mut found = false;
687        while let Some(tt) = iit.next() {
688            match (found, tt) {
689                (false, Ident(id)) if id.to_string() == "match" => {
690                    self.body.extend(once(Ident(id)));
691                    found = true;
692                }
693                (true, Punct(p)) if p.as_char() == ';' => {
694                    self.tail.extend(once(Punct(p)).chain(iit));
695                    return true;
696                }
697                (true, Group(gr)) if gr.delimiter() == Brace => {
698                    let mut isfat_arrow = false;
699                    let mut gr_iit = gr.stream().into_iter();
700                    while let Some(tt) = gr_iit.next() {
701                        if let Punct(p) = tt {
702                            if p.as_char() == '=' {
703                                if let Some(Punct(gt)) = gr_iit.next() {
704                                    if gt.as_char() == '>' {
705                                        isfat_arrow = true;
706                                        break;
707                                    }
708                                }
709                            }
710                        }
711                    }
712                    if isfat_arrow {
713                        self.body.extend(once(Group(gr)));
714                        found = false;
715                    } else {
716                        self.dflt_arm = Some(gr);
717                        self.tail.extend(iit);
718                        return true;
719                    }
720                }
721                (_, tt) => self.body.extend(once(tt)),
722            }
723        }
724        found
725    }
726}
727
728struct VarMeth {
729    ident: Idn,
730    fields: Option<Gr>,
731    block: Gr,
732    opt_trait: Option<Idn>,
733}
734
735#[derive(Default)]
736struct Var {
737    ident: Option<Idn>,
738    fields: Option<Gr>,
739    methods: HashMap<String, VarMeth>,
740}
741impl Var {
742    fn vec(item: &mut Item) -> (Vec<Var>, String) {
743        let mut iit = mem::take(&mut item.group).into_iter();
744        let mut enm: Vec<Var> = Vec::new();
745        let mut err = String::new();
746        let mut err_state = false;
747        let dd = TokenStream::from_str("..").unwrap();
748        let mut var = Var::default();
749        while let Some(tt) = iit.next() {
750            if err_state {
751                match tt {
752                    Punct(p) if p.as_char() == ',' => {
753                        err_state = false;
754                        item.group.extend(once(Punct(p)));
755                        enm.push(mem::take(&mut var));
756                    }
757                    _ => (),
758                }
759            } else {
760                match tt {
761                    Punct(p) if p.as_char() == '#' && var.ident.is_none() => match iit.next() {
762                        Some(Group(gr)) if gr.delimiter() == Delimiter::Bracket => {
763                            item.group.extend([Punct(p), Group(gr)]);
764                        }
765                        Some(Punct(p1)) if p.as_char() == '!' => match iit.next() {
766                            Some(Group(gr)) if gr.delimiter() == Delimiter::Bracket => {
767                                item.group.extend([Punct(p), Punct(p1), Group(gr)]);
768                            }
769                            _ => (),
770                        },
771                        _ => (),
772                    },
773                    Ident(id) => {
774                        if var.ident.is_none() {
775                            var.ident = Some(id.clone());
776                            item.group.extend(once(Ident(id)));
777                        // } else if id.to_string() == "fn" {
778                        } else {
779                            // method
780                            let mut opt_tt = iit.next();
781                            match opt_tt {
782                                Some(Group(ref g)) if g.delimiter() == Delimiter::Parenthesis => {
783                                    opt_tt = iit.next()
784                                }
785                                _ => (),
786                            }
787                            let opt_trait = match opt_tt {
788                                Some(Ident(trait_id)) => {
789                                    opt_tt = iit.next();
790                                    Some(trait_id)
791                                }
792                                _ => None,
793                            };
794                            let in_enum_var =
795                                format!("in `enum {}::{}`", item.name, var.ident.as_ref().unwrap());
796                            match opt_tt {
797                                Some(Group(block)) if block.delimiter() == Brace => {
798                                    let name = (opt_trait.as_ref())
799                                        .map_or(id.to_string(), |t| format!("{id}() {t}"));
800                                    let m = VarMeth {
801                                        ident: id,
802                                        fields: var.fields.clone(),
803                                        block,
804                                        opt_trait,
805                                    };
806                                    if var.methods.insert(name.clone(), m).is_some() {
807                                        err += &format!(
808                                            "\nRepetition of method name `{name}` \
809{in_enum_var} (last arm-block used)"
810                                        );
811                                    }
812                                }
813                                Some(tt2) => {
814                                    err += &format!(
815                                        "\nInvalid syntax in method `{id}` {in_enum_var} \
816- expected arm-block: `{{...}}`, found: `{tt2}`"
817                                    );
818                                    err_state = true;
819                                }
820                                None => {
821                                    err += &format!(
822                                        "\nUnexpected end of macro on method`{id}` {in_enum_var}"
823                                    );
824                                    err_state = true;
825                                }
826                            };
827                        }
828                    }
829                    Group(gr) if gr.delimiter() != Delimiter::Bracket => {
830                        match (var.methods.is_empty(), var.fields.is_none()) {
831                            (true, true) => {
832                                var.fields = Some(Gr::new(gr.delimiter(), dd.clone()));
833                                item.group.extend(once(Group(gr)));
834                            }
835                            (_, false) => var.fields = Some(gr),
836                            _ => (),
837                        }
838                    }
839                    Punct(p) if p.as_char() == ',' => {
840                        if var.ident.is_some() {
841                            item.group.extend(once(Punct(p)));
842                            enm.push(mem::take(&mut var));
843                        }
844                    }
845                    _ => (),
846                }
847            }
848        }
849
850        if var.ident.is_some() {
851            enm.push(var)
852        }
853        (enm, err)
854    }
855}
856
857/// This is an item-like macro that wraps a state `enum` declaration and one or more `impl` blocks, allowing you to write match-expressions without match-arms in the method bodies of these `impl`, writing the match-arms into the corresponding `enum` variants.
858///
859/// ## Usage example
860///
861/// [Chapter 17.3 "Implementing an Object-Oriented Design Pattern" of the rust-book](https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html) shows the implementation of the *state pattern* in Rust, which provides the following behavior:
862/// ```rust ignore
863/// pub fn main() {
864///     let mut post = blog::Post::new();
865///
866///     post.add_text("I ate a salad for lunch today");
867///     assert_eq!("", post.content());
868///     post.request_review(); // without request_review() - approve() should not work
869///     post.approve();  
870///     assert_eq!("I ate a salad for lunch today", post.content());
871/// }
872/// ```
873/// with the macro **`impl_match!`** this is solved like this:
874/// ```rust
875/// mod blog {
876///     pub struct Post {
877///         state: State,
878///         content: String,
879///     }
880///
881///     methods_enum::impl_match! {
882///
883///     impl Post {
884///         pub fn add_text(&mut self, text: &str)  ~{ match self.state {} }
885///         pub fn request_review(&mut self)        ~{ match self.state {} }
886///         pub fn approve(&mut self)               ~{ match self.state {} }
887///         pub fn content(&mut self) -> &str       ~{ match self.state { "" } }
888///
889///         pub fn new() -> Post {
890///             Post { state: State::Draft, content: String::new() }
891///         }
892///     }
893///
894///     pub enum State {
895///         Draft:          add_text(text)   { self.content.push_str(text) }
896///                         request_review() { self.state = State::PendingReview },
897///         PendingReview:  approve()        { self.state = State::Published },
898///         Published:      content()        { &self.content }
899///     }
900///
901///     } // <-- impl_match!
902/// }
903/// ```
904/// All the macro does is complete the unfinished match-expressions in method bodies marked with `~` for all `enum` variants branches in the form:   
905/// `(EnumName)::(Variant) => { match-arm block from enum declaration }`.  
906/// If a `{}` block (without `=>`) is set at the end of an unfinished match-expressions, it will be placed in all variants branches that do not have this method in `enum`:   
907/// `(EnumName)::(Variant) => { default match-arm block }`.  
908/// Thus, you see all the code that the compiler will receive, but in a form structured according to the design pattern.
909///
910/// **rust-analyzer**[^rust_analyzer] perfectly defines identifiers in all blocks. All hints, auto-completions and replacements in the IDE are processed in match-arm displayed in `enum` as if they were in their native match-block. Plus, the "inline macro" command works in the IDE, displaying the resulting code.
911///
912/// [^rust_analyzer]: *rust-analyzer may not expand proc-macro when running under nightly or old rust edition.* In this case it is recommended to set in its settings: [`"rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" }`](https://rust-analyzer.github.io/manual.html#toolchain)
913/// 
914/// ## Other features
915///
916/// - You can also include `impl (Trait) for ...` blocks in a macro. The name of the `Trait` (without the path) is specified in the enum before the corresponding arm-block. Example with `Display` - below.
917///
918/// - An example of a method with generics is also shown there: `mark_obj<T: Display>()`.   
919/// There is an uncritical nuance with generics, described in the [documentation](impl_match!#currently-this-mode-has-the-following-non-critical-restrictions).
920///
921/// - `@` - character before the `enum` declaration, in the example: `@enum Shape {...` disables passing to the `enum` compiler: only match-arms will be processed. This may be required if this `enum` is already declared elsewhere in the code, including outside the macro.
922///
923/// - If you are using `enum` with fields, then before the name of the method that uses them, specify the template for decomposing fields into variables (the IDE[^rust_analyzer] works completely correctly with such variables). The template to decompose is accepted by downstream methods of the same enumeration variant and can be reassigned. Example:
924/// ```rust
925/// methods_enum::impl_match! {
926///
927/// enum Shape<'a> {
928/// //     Circle(f64, &'a str),                  // if you uncomment or remove these 4 lines
929/// //     Rectangle { width: f64, height: f64 }, //    it will work the same
930/// // }
931/// // @enum Shape<'a> {
932///     Circle(f64, &'a str): (radius, mark)
933///         zoom(scale)    { Shape::Circle(radius * scale, mark) }      // template change
934///         fmt(f) Display { write!(f, "{mark}(R: {radius:.1})") };     (_, mark) 
935///         mark_obj(obj)  { format!("{} {}", mark, obj) };             (radius, _)
936///         to_rect()      { *self = Shape::Rectangle { width: radius * 2., height: radius * 2.,} }
937///     ,
938///     Rectangle { width: f64, height: f64}: { width: w, height}
939///         zoom(scale)    { Shape::Rectangle { width: w * scale, height: height * scale } }
940///         fmt(f) Display { write!(f, "Rectangle(W: {w:.1}, H: {height:.1})") }; {..}
941///         mark_obj(obj)  { format!("⏹️ {}", obj) }
942/// }
943/// impl<'a> Shape<'a> {
944///     fn zoom(&mut self, scale: f64)                      ~{ *self = match *self }
945///     fn to_rect(&mut self) -> &mut Self                  ~{ match *self {}; self }
946///     fn mark_obj<T: Display>(&self, obj: &T) -> String   ~{ match self }
947/// }
948///
949/// use std::fmt::{Display, Formatter, Result};
950///
951/// impl<'a> Display for Shape<'a>{
952///     fn fmt(&self, f: &mut Formatter<'_>) -> Result      ~{ match self }
953/// }
954///
955/// } // <--impl_match!
956///
957/// pub fn main() {
958///     let mut rect = Shape::Rectangle { width: 10., height: 10. };
959///     assert_eq!(format!("{rect}"), "Rectangle(W: 10.0, H: 10.0)");
960///     rect.zoom(3.);
961///     let mut circle = Shape::Circle(15., "⭕");
962///     assert_eq!(circle.mark_obj(&rect.mark_obj(&circle)), "⭕ ⏹️ ⭕(R: 15.0)");
963///     // "Rectangle(W: 30.0, H: 30.0)"
964///     assert_eq!(circle.to_rect().to_string(), rect.to_string());
965/// }
966/// ```
967/// - Debug flags. They can be placed through spaces in parentheses at the very beginning of the macro,   
968/// eg: `impl_match! { (ns ) `...
969///     - flag `ns` or `sn` in any case - replaces the semantic binding of the names of methods and traits in `enum` variants with a compilation error if they are incorrectly specified.
970///     - flag `!` - causes a compilation error in the same case, but without removing the semantic binding.
971///
972/// ## [impl_match macro details](macro.impl_match.html#impl_match-macro-details)
973#[doc = include_str!("impl_match_details.md")]
974#[proc_macro]
975pub fn impl_match(input_ts: TokenStream) -> TokenStream {
976    // std::fs::write("target/debug/input_ts.log", format!("{}\n\n{0:#?}", input_ts)).unwrap();
977
978    let (mut items, mmap, flags) = Item::vec(input_ts);
979    let opt_enm_idx = (items.iter().enumerate().find_map(|(i, it)| it.no_def.then(|| i)))
980        .or_else(|| items.iter().enumerate().find_map(|(i, it)| it.it_enum.then(|| i)));
981    let ((mut enm, mut err), enm_i) =
982        opt_enm_idx.map_or(((Vec::new(), String::new()), None), |i| {
983            let enm_it = items.get_mut(i).unwrap();
984            (Var::vec(enm_it), enm_it.ident.take())
985        });
986    let fat_arrow = TokenStream::from_str("=>").unwrap();
987    let empty_gr = Gr::new(Brace, TokenStream::new());
988    let dd = TokenStream::from_str("..").unwrap();
989    let dd_gr = |g: &Gr| Gr::new(g.delimiter(), dd.clone());
990
991    let mut res_ts = TokenStream::new();
992    for item in items.iter_mut() {
993        res_ts.extend(mem::take(&mut item.prev_ts));
994        if !item.name.is_empty() && !item.no_def {
995            let group = if item.it_enum {
996                mem::take(&mut item.group)
997            } else {
998                let mut group = TokenStream::new();
999                for mut m in mem::take(&mut item.methods) {
1000                    group.extend(m.prev_ts);
1001                    if !m.name.is_empty() {
1002                        let mut match_block = TokenStream::new();
1003                        for var in enm.iter_mut() {
1004                            let (fields, arm_block) = match var.methods.get_mut(&m.name) {
1005                                Some(VarMeth { fields, block, .. }) => {
1006                                    (fields.take(), mem::replace(block, empty_gr.clone()))
1007                                }
1008                                None => {
1009                                    if m.dflt_arm.is_none() {
1010                                        continue;
1011                                    }
1012                                    (var.fields.as_ref().map(dd_gr), m.dflt_arm.clone().unwrap())
1013                                }
1014                            };
1015                            match_block.extend(TokenStream::from_iter([
1016                                Ident(enm_i.as_ref().unwrap().clone()),
1017                                Punct(Pn::new(':', Spacing::Joint)),
1018                                Punct(Pn::new(':', Spacing::Alone)),
1019                                Ident(var.ident.as_ref().unwrap().clone()),
1020                            ]));
1021                            match_block.extend(fields.map(Group));
1022                            match_block.extend(fat_arrow.clone());
1023                            match_block.extend(once(Group(arm_block)));
1024                        }
1025                        m.body.extend(once(Group(Gr::new(Brace, match_block))).chain(m.tail));
1026                        group.extend(once(Group(Gr::new(Brace, m.body))));
1027                    }
1028                }
1029                group
1030            };
1031            res_ts.extend(once(Group(Gr::new(Brace, group))));
1032        }
1033    }
1034
1035    // semantic+highlighting var methods / traits
1036    if !flags.no_semnt {
1037        if enm_i.is_some() {
1038            let item_n = (items.iter())
1039                .find_map(|it| (!it.it_enum && !it.name.is_empty()).then(|| it.name.clone()))
1040                .unwrap_or_default();
1041            let span = Span::call_site();
1042            let item_ts = TokenStream::from_iter([
1043                Ident(Idn::new(&item_n, span)),
1044                Punct(Pn::new(':', Spacing::Joint)),
1045                Punct(Pn::new(':', Spacing::Alone)),
1046            ]);
1047            let sm = Punct(Pn::new(';', Spacing::Alone));
1048            let mut fn_ts = TokenStream::new();
1049            if !item_n.is_empty() {
1050                for var in enm.iter_mut() {
1051                    for (k, m) in var.methods.iter_mut() {
1052                        if !mmap.get(k).map_or(false, |&v| v) {
1053                            fn_ts.extend(if let Some(trait_i) = m.opt_trait.take() {
1054                                TokenStream::from_iter([
1055                                    Punct(Pn::new('<', Spacing::Alone)),
1056                                    Ident(Idn::new(&item_n, span)),
1057                                    Ident(Idn::new("as", span)),
1058                                    Ident(trait_i),
1059                                    Punct(Pn::new('>', Spacing::Alone)),
1060                                    Punct(Pn::new(':', Spacing::Joint)),
1061                                    Punct(Pn::new(':', Spacing::Alone)),
1062                                ])
1063                            } else {
1064                                item_ts.clone()
1065                            });
1066                            fn_ts.extend([Ident(m.ident.clone()), sm.clone()]);
1067                        }
1068                    }
1069                }
1070            }
1071            if !fn_ts.is_empty() {
1072                let mut hasher = DefaultHasher::new();
1073                (item_n + "-" + mmap.keys().next().unwrap_or(&String::new())).hash(&mut hasher);
1074                res_ts.extend(
1075                    TokenStream::from_str(&format!(
1076                        r##"#[allow(unused)]
1077                            #[doc(hidden)]
1078                            #[doc = " Semantic bindings for impl_match! macro"]
1079                            mod _{}"##,
1080                        hasher.finish()
1081                    ))
1082                    .unwrap(),
1083                );
1084                let mut mod_ts = TokenStream::from_str("use super::*; fn methods()").unwrap();
1085                mod_ts.extend(once(Group(Gr::new(Brace, fn_ts))));
1086                res_ts.extend(once(Group(Gr::new(Brace, mod_ts))));
1087            }
1088        }
1089    }
1090
1091    // errors
1092    if enm_i.is_some() {
1093        let mset: HashSet<String> = HashSet::from_iter(mmap.into_keys());
1094        let enm_n = enm_i.as_ref().unwrap().to_string();
1095        for var in enm.iter() {
1096            for name in var.methods.keys() {
1097                if !mset.contains(name) {
1098                    let mut free_m: Vec<String> = mset
1099                        .difference(&HashSet::from_iter(var.methods.keys().cloned()))
1100                        .cloned()
1101                        .collect();
1102                    free_m.sort();
1103                    let enm_var = format!("`enum {enm_n}::{}`", var.ident.as_ref().unwrap());
1104                    if free_m.is_empty() {
1105                        err += &format!(
1106                            "\nInvalid method `{name}` in {enm_var}:
1107`impl(-s)` contains no freely methods to implement `match{{...}}` from {enm_var}"
1108                        )
1109                    } else {
1110                        err += &format!(
1111                            "\nInvalid method name `{name}` in {enm_var} - expected{}: `{}`",
1112                            if free_m.len() == 1 { "" } else { " one of" },
1113                            free_m.join("`|`")
1114                        )
1115                    }
1116                };
1117            }
1118        }
1119    }
1120    if !err.is_empty() {
1121        eprintln!("\nErr in impl_match! macro:{err}\n");
1122        if flags.panic {
1123            panic!("Err in impl_match! macro:{err}");
1124        }
1125    }
1126
1127    res_ts
1128}
1129
1130// endregion: impl_match