alphabet_macro/
lib.rs

1//! Provides the alphabet!() macro. It can be used to create const alphabets easily.
2//!
3//! Usually you would have to write alphabets in a cumbersome way:
4//! ```
5//! const HEX: [char; 16] = ['0', '1', '2', '3',
6//!     '4', '5', '6', '7',
7//!     '8', '9', 'a', 'b',
8//!     'c', 'd', 'e', 'f'];
9//!
10//! assert_eq!(HEX.len(), 16);
11//! assert_eq!(HEX[5], '5');
12//! assert_eq!(HEX[10], 'a');
13//! ```
14//!
15//! But with the alphabet!() macro this can be done way easier.
16//! ```
17//! use alphabet_macro::alphabet;
18//!
19//! alphabet!(HEX = "0123456789abcdef");
20//!
21//! assert_eq!(HEX.len(), 16);
22//! assert_eq!(HEX[5], '5');
23//! assert_eq!(HEX[10], 'a');
24//! ```
25//!
26//! The alphabet!() macro expands to the snippet above, while being easier to read, write and understand.
27
28use proc_macro::TokenStream;
29use quote::quote;
30use syn::{parse, self};
31
32struct Alphabet {
33    meta: Option<Vec<syn::Attribute>>,
34    vis: syn::Visibility,
35    ident: syn::Ident,
36    contents: String
37}
38
39/// Declares a constant alphabet. You can choose any valid identifier as the name for the alphabet.
40///
41/// # Examples
42///
43/// Basic usage:
44/// ```
45/// use alphabet_macro::alphabet;
46///
47/// alphabet!(BINARY  = "01");
48/// alphabet!(ENGLISH = "abcdefghijklmnopqrstuvwxyz");
49/// alphabet!(GERMAN  = "aäbcdefghijklmnoöpqrstuüvwxyzß");
50/// alphabet!(HEBREW  = "אבגדהוזחטיכלמנסעפצקרשת");
51///
52/// assert_eq!(BINARY.len(), 2);
53/// assert_eq!(ENGLISH.len(), 26);
54/// assert_eq!(GERMAN.len(), 30);
55/// assert_eq!(HEBREW.len(), 22);
56/// ```
57///
58/// You can also specify a visibility for the generated alphabet:
59/// ```
60/// use alphabet_macro::alphabet;
61///
62/// alphabet!(pub BINARY = "01");
63/// ```
64///
65/// You can pass attributes to the generated alphabet as well. This includes doc comments, which internally use attributes:
66/// ```
67/// use alphabet_macro::alphabet;
68///
69/// alphabet! {
70///     /// An alphabet for binary strings.
71///     pub BINARY = "01";
72/// }
73/// ```
74///
75/// # Syntax
76///
77/// Alphabets follow the following syntax, with the nonterminal `alphabet` being the contents of the alphabet! macro:
78/// ```bnf
79/// alphabet ::= attribute* visibility? identifier "=" literal-string ";"?
80/// ```
81#[proc_macro]
82pub fn alphabet(input: TokenStream) -> TokenStream {
83    let Alphabet {
84        meta,
85        vis,
86        ident,
87        contents
88    } = syn::parse_macro_input!(input as Alphabet);
89
90    let chars: Vec<char> = contents.chars().collect();
91    let alphabet_len = chars.len();
92    TokenStream::from(match meta {
93        None => quote! {
94            #vis const #ident: [char; #alphabet_len] = [#(#chars),*];
95        },
96        Some(meta) => quote! {
97            #(#meta)* #vis const #ident: [char; #alphabet_len] = [#(#chars),*];
98        }
99    })
100}
101
102impl syn::parse::Parse for Alphabet {
103    fn parse(buf: parse::ParseStream) -> syn::Result<Self> {
104        let meta;
105        if buf.peek(syn::Token![#]) {
106            // #foo[bar]
107            // ^^^^^^^^^
108            meta = Some(buf.call(syn::Attribute::parse_outer)?);
109        } else {
110            meta = None;
111        }
112
113        let vis;
114        if buf.peek(syn::Token![pub]) || buf.peek(syn::Token![crate]) {
115            // alphabet!(pub FOO = "abcdef0123456789");
116            //           ^^^
117            vis = buf.parse::<syn::Visibility>()?;
118        } else {
119            vis = syn::Visibility::Inherited;
120        }
121
122        // alphabet!(FOO = "abcdef0123456789");
123        //           ^^^
124        let ident: syn::Ident = buf.parse()?;
125
126        // alphabet!(FOO = "abcdef0123456789");
127        //               ^
128        buf.parse::<syn::Token![=]>()?;
129
130        // alphabet!(FOO = "abcdef0123456789");
131        //                 ^^^^^^^^^^^^^^^^^^
132        let contents: syn::LitStr = buf.parse()?;
133        let contents = contents.value();
134
135        // Allow an optional semicolon. Makes most sense when used in {}-form:
136        // alphabet! {
137        //     pub ENGLISH = "abcdefghijklmnopqrstuvwxyz";
138        // }
139        if buf.peek(syn::Token![;]) {
140            buf.parse::<syn::Token![;]>()?;
141        }
142
143        Ok(Alphabet {
144            meta,
145            vis,
146            ident,
147            contents
148        })
149    }
150}