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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Provides the alphabet!() macro. It can be used to create const alphabets easily.
//!
//! Usually you would have to write alphabets in a cumbersome way:
//! ```
//! const HEX: [char; 16] = ['0', '1', '2', '3',
//!     '4', '5', '6', '7',
//!     '8', '9', 'a', 'b',
//!     'c', 'd', 'e', 'f'];
//!
//! assert_eq!(HEX.len(), 16);
//! assert_eq!(HEX[5], '5');
//! assert_eq!(HEX[10], 'a');
//! ```
//!
//! But with the alphabet!() macro this can be done way easier.
//! ```
//! use alphabet_macro::alphabet;
//!
//! alphabet!(HEX = "0123456789abcdef");
//!
//! assert_eq!(HEX.len(), 16);
//! assert_eq!(HEX[5], '5');
//! assert_eq!(HEX[10], 'a');
//! ```
//!
//! The alphabet!() macro expands to the snippet above, while being easier to read, write and understand.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse, self};

struct Alphabet {
    meta: Option<Vec<syn::Attribute>>,
    vis: syn::Visibility,
    ident: syn::Ident,
    contents: String
}

/// Declares a constant alphabet. You can choose any valid identifier as the name for the alphabet.
///
/// # Examples
///
/// Basic usage:
/// ```
/// use alphabet_macro::alphabet;
///
/// alphabet!(BINARY  = "01");
/// alphabet!(ENGLISH = "abcdefghijklmnopqrstuvwxyz");
/// alphabet!(GERMAN  = "aäbcdefghijklmnoöpqrstuüvwxyzß");
/// alphabet!(HEBREW  = "אבגדהוזחטיכלמנסעפצקרשת");
///
/// assert_eq!(BINARY.len(), 2);
/// assert_eq!(ENGLISH.len(), 26);
/// assert_eq!(GERMAN.len(), 30);
/// assert_eq!(HEBREW.len(), 22);
/// ```
///
/// You can also specify a visibility for the generated alphabet:
/// ```
/// use alphabet_macro::alphabet;
///
/// alphabet!(pub BINARY = "01");
/// ```
///
/// You can pass attributes to the generated alphabet as well. This includes doc comments, which internally use attributes:
/// ```
/// use alphabet_macro::alphabet;
///
/// alphabet! {
///     /// An alphabet for binary strings.
///     pub BINARY = "01";
/// }
/// ```
///
/// # Syntax
///
/// Alphabets follow the following syntax, with the nonterminal `alphabet` being the contents of the alphabet! macro:
/// ```bnf
/// alphabet ::= attribute* visibility? identifier "=" literal-string ";"?
/// ```
#[proc_macro]
pub fn alphabet(input: TokenStream) -> TokenStream {
    let Alphabet {
        meta,
        vis,
        ident,
        contents
    } = syn::parse_macro_input!(input as Alphabet);

    let chars: Vec<char> = contents.chars().collect();
    let alphabet_len = chars.len();
    TokenStream::from(match meta {
        None => quote! {
            #vis const #ident: [char; #alphabet_len] = [#(#chars),*];
        },
        Some(meta) => quote! {
            #(#meta)* #vis const #ident: [char; #alphabet_len] = [#(#chars),*];
        }
    })
}

impl syn::parse::Parse for Alphabet {
    fn parse(buf: parse::ParseStream) -> syn::Result<Self> {
        let meta;
        if buf.peek(syn::Token![#]) {
            // #foo[bar]
            // ^^^^^^^^^
            meta = Some(buf.call(syn::Attribute::parse_outer)?);
        } else {
            meta = None;
        }

        let vis;
        if buf.peek(syn::Token![pub]) || buf.peek(syn::Token![crate]) {
            // alphabet!(pub FOO = "abcdef0123456789");
            //           ^^^
            vis = buf.parse::<syn::Visibility>()?;
        } else {
            vis = syn::Visibility::Inherited;
        }

        // alphabet!(FOO = "abcdef0123456789");
        //           ^^^
        let ident: syn::Ident = buf.parse()?;

        // alphabet!(FOO = "abcdef0123456789");
        //               ^
        buf.parse::<syn::Token![=]>()?;

        // alphabet!(FOO = "abcdef0123456789");
        //                 ^^^^^^^^^^^^^^^^^^
        let contents: syn::LitStr = buf.parse()?;
        let contents = contents.value();

        // Allow an optional semicolon. Makes most sense when used in {}-form:
        // alphabet! {
        //     pub ENGLISH = "abcdefghijklmnopqrstuvwxyz";
        // }
        if buf.peek(syn::Token![;]) {
            buf.parse::<syn::Token![;]>()?;
        }

        Ok(Alphabet {
            meta,
            vis,
            ident,
            contents
        })
    }
}