proclet_utils_macros/
lib.rs

1//! This crate contains proc macros used by the `proclet-utils` crate. Don't use it directly.
2
3use proc_macro::TokenStream;
4use proclet::{
5    op,
6    pm1::{Error, StringLiteral},
7    prelude::*,
8    proclet, punctuated,
9};
10use std::collections::{hash_map::Entry, HashMap, HashSet};
11
12#[doc(hidden)]
13#[proc_macro]
14pub fn _define_ops(input: TokenStream) -> TokenStream {
15    proclet(input, |input| {
16        let args = punctuated(StringLiteral::parser(), op(",")).parse_all(input)?;
17
18        let mut map = HashMap::<String, (bool, HashSet<char>)>::new();
19
20        for (op, _) in args {
21            let str = op.value();
22            let clen = str.chars().count();
23            if clen == 0 {
24                return Err(Error::with_span(op.span(), "empty operator"));
25            } else if clen > 1 {
26                let mut chars = op.value().chars();
27                let mut ci = chars.next().unwrap().len_utf8();
28                for ch in chars {
29                    let s = str[..ci].to_string();
30                    ci += ch.len_utf8();
31                    map.entry(s).or_insert((false, HashSet::new())).1.insert(ch);
32                }
33            }
34            match map.entry(op.into_value()) {
35                Entry::Occupied(e) => e.into_mut().0 = true,
36                Entry::Vacant(e) => {
37                    e.insert((true, HashSet::new()));
38                }
39            }
40        }
41
42        let mut output = String::from(concat!(
43            "const fn __proclet_define_ops",
44            "(str: &::core::primitive::str, next: ::core::option::Option<::core::primitive::char>)",
45            "-> ::proclet::Match<::std::borrow::Cow<'static, ::core::primitive::str>>",
46            "{ use ::proclet::Match; use ::core::option::Option; use ::std::borrow::Cow;",
47            "match (str.as_bytes(), next) {"
48        ));
49        for (str, (valid, follow)) in map {
50            let bss = to_byte_string_string(&str);
51            if !follow.is_empty() {
52                output.push('(');
53                output.push_str(&bss);
54                output.push_str(", Option::Some(");
55                let mut it = follow.into_iter().peekable();
56                while let Some(ch) = it.next() {
57                    output.push_str(&format!("\'\\u{{{:x}}}\'", u32::from(ch)));
58                    if it.peek().is_some() {
59                        output.push('|');
60                    }
61                }
62                output.push_str(")) => ");
63                if valid {
64                    output.push_str(&format!("Match::Partial(Cow::Borrowed({:?})),", &str));
65                } else {
66                    output.push_str("Match::NeedMore,")
67                }
68            }
69            if valid {
70                output.push('(');
71                output.push_str(&bss);
72                output.push_str(&format!(
73                    ", _) => Match::Complete(Cow::Borrowed({:?})),",
74                    &str
75                ));
76            }
77        }
78        output.push_str("_ => Match::NoMatch }}");
79        let output: TokenStream = output
80            .parse()
81            .expect("internal error: generated invalid code");
82        Ok(output)
83    })
84}
85
86fn to_byte_string_string(str: &str) -> String {
87    let mut output = String::from("b\"");
88    let mut chi = 0;
89    for ch in str.chars() {
90        let clen = ch.len_utf8();
91        for &b in str[chi..chi + clen].as_bytes() {
92            output.push_str(&format!("\\x{:02x}", b));
93        }
94        chi += clen;
95    }
96    output.push('\"');
97    output
98}