1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! This crate contains proc macros used by the `proclet-utils` crate. Don't use it directly.

use proc_macro::TokenStream;
use proclet::{
    op,
    pm1::{Error, StringLiteral},
    prelude::*,
    proclet, punctuated,
};
use std::collections::{hash_map::Entry, HashMap, HashSet};

#[doc(hidden)]
#[proc_macro]
pub fn _define_ops(input: TokenStream) -> TokenStream {
    proclet(input, |input| {
        let args = punctuated(StringLiteral::parser(), op(",")).parse_all(input)?;

        let mut map = HashMap::<String, (bool, HashSet<char>)>::new();

        for (op, _) in args {
            let str = op.value();
            let clen = str.chars().count();
            if clen == 0 {
                return Err(Error::with_span(op.span(), "empty operator"));
            } else if clen > 1 {
                let mut chars = op.value().chars();
                let mut ci = chars.next().unwrap().len_utf8();
                for ch in chars {
                    let s = str[..ci].to_string();
                    ci += ch.len_utf8();
                    map.entry(s).or_insert((false, HashSet::new())).1.insert(ch);
                }
            }
            match map.entry(op.into_value()) {
                Entry::Occupied(e) => e.into_mut().0 = true,
                Entry::Vacant(e) => {
                    e.insert((true, HashSet::new()));
                }
            }
        }

        let mut output = String::from(concat!(
            "const fn __proclet_define_ops",
            "(str: &::core::primitive::str, next: ::core::option::Option<::core::primitive::char>)",
            "-> ::proclet::Match<::std::borrow::Cow<'static, ::core::primitive::str>>",
            "{ use ::proclet::Match; use ::core::option::Option; use ::std::borrow::Cow;",
            "match (str.as_bytes(), next) {"
        ));
        for (str, (valid, follow)) in map {
            let bss = to_byte_string_string(&str);
            if !follow.is_empty() {
                output.push('(');
                output.push_str(&bss);
                output.push_str(", Option::Some(");
                let mut it = follow.into_iter().peekable();
                while let Some(ch) = it.next() {
                    output.push_str(&format!("\'\\u{{{:x}}}\'", u32::from(ch)));
                    if it.peek().is_some() {
                        output.push('|');
                    }
                }
                output.push_str(")) => ");
                if valid {
                    output.push_str(&format!("Match::Partial(Cow::Borrowed({:?})),", &str));
                } else {
                    output.push_str("Match::NeedMore,")
                }
            }
            if valid {
                output.push('(');
                output.push_str(&bss);
                output.push_str(&format!(
                    ", _) => Match::Complete(Cow::Borrowed({:?})),",
                    &str
                ));
            }
        }
        output.push_str("_ => Match::NoMatch }}");
        let output: TokenStream = output
            .parse()
            .expect("internal error: generated invalid code");
        Ok(output)
    })
}

fn to_byte_string_string(str: &str) -> String {
    let mut output = String::from("b\"");
    let mut chi = 0;
    for ch in str.chars() {
        let clen = ch.len_utf8();
        for &b in str[chi..chi + clen].as_bytes() {
            output.push_str(&format!("\\x{:02x}", b));
        }
        chi += clen;
    }
    output.push('\"');
    output
}