bitranslit_derive/
lib.rs

1extern crate proc_macro;
2extern crate syn;
3#[macro_use]
4extern crate quote;
5
6use proc_macro::TokenStream;
7use syn::parse::{Parse, ParseStream};
8use syn::punctuated::Punctuated;
9use syn::{braced, parse_macro_input, token, Field, Ident, Result, Token, Type};
10
11#[derive(Debug)]
12enum Item {
13    Struct(ExprStruct),
14}
15
16#[derive(Debug)]
17struct ExprStruct {
18    ident: Ident,
19    _brace_token: token::Brace,
20    fields: Punctuated<Field, Token![,]>,
21}
22
23impl Parse for Item {
24    fn parse(input: ParseStream) -> Result<Self> {
25        input.parse().map(Item::Struct)
26        // let lookahead = input.lookahead1();
27        // if lookahead.peek(Token![struct]) {
28        //     input.parse().map(Item::Struct)
29        // } else {
30        //     Err(lookahead.error())
31        // }
32    }
33}
34
35impl Parse for ExprStruct {
36    fn parse(input: ParseStream) -> Result<Self> {
37        let content;
38        Ok(ExprStruct {
39            ident: input.parse()?,
40            _brace_token: braced!(content in input),
41            fields: content.parse_terminated(Field::parse_named, Token![,])?,
42        })
43    }
44}
45
46/// Simplify the construction of a language pack. The macro requires at least
47/// one mapping and has additionally three optional mappings. Each mapping is an
48/// array of tuples. The different mapping types are:
49///
50/// - `mapping` (required): Single character mappings that work both directions.
51/// - `pre_processor_mapping` (optional): Mappings of single characters to multiple characters that work both directions.
52/// - `reverse_specific_mapping` (optional): Single characters that are only applied in reverse transliterations.
53/// - `reverse_specific_pre_processor_mapping` (optional): Mappings of multiple characters to singe characters that are only applied in reverse transliteration.
54///
55/// ```text
56/// language_pack!(LanguageName {
57///   mapping: ...,
58///   pre_processor_mapping: ...,
59///   reverse_specific_mapping: ...,
60///   reverse_specific_pre_processor_mapping: ...,
61/// });
62///
63/// ```
64///
65/// See the languages modules for real examples.
66#[proc_macro]
67pub fn language_pack(tokens: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(tokens as Item);
69    let expanded = match input {
70        Item::Struct(expr) => {
71            let language = expr.ident;
72            let language_label = language.to_string().to_lowercase();
73            let code_field = expr
74                .fields
75                .iter()
76                .find(|&f| *f.ident.as_ref().unwrap() == "code")
77                .unwrap();
78
79            let mapping_field = expr
80                .fields
81                .iter()
82                .find(|&f| *f.ident.as_ref().unwrap() == "mapping")
83                .unwrap();
84
85            let pre_processor_mapping_field = expr
86                .fields
87                .iter()
88                .find(|&f| *f.ident.as_ref().unwrap() == "pre_processor_mapping");
89
90            let reverse_specific_mapping_field = expr
91                .fields
92                .iter()
93                .find(|&f| *f.ident.as_ref().unwrap() == "reverse_specific_mapping");
94
95            let reverse_specific_pre_processor_mapping_field = expr
96                .fields
97                .iter()
98                .find(|&f| *f.ident.as_ref().unwrap() == "reverse_specific_pre_processor_mapping");
99
100            let code = match &code_field.ty {
101                Type::Path(type_path) => {
102                    let code = &type_path.path.segments.first().unwrap().ident;
103                    quote! { #code }
104                }
105                _ => panic!("Not a valid language code for language {}", language),
106            };
107
108            let mapping = match &mapping_field.ty {
109                Type::Path(type_path) => {
110                    let mapping = &type_path.path.segments.first().unwrap().ident;
111                    quote! { #mapping.iter().cloned().collect() }
112                }
113                _ => panic!("Not a valid mapping for language {}", language),
114            };
115
116            let pre_processor_mapping = if let Some(field) = pre_processor_mapping_field {
117                match &field.ty {
118                    Type::Path(type_path) => {
119                        let mapping = &type_path.path.segments.first().unwrap().ident;
120
121                        quote! { Some(#mapping.iter().cloned().collect()) }
122                    }
123                    _ => panic!(
124                        "Not a valid pre_processor_mapping for language {}",
125                        language
126                    ),
127                }
128            } else {
129                quote! { None }
130            };
131
132            let reverse_specific_mapping = if let Some(field) = reverse_specific_mapping_field {
133                match &field.ty {
134                    Type::Path(type_path) => {
135                        let mapping = &type_path.path.segments.first().unwrap().ident;
136
137                        quote! { Some(#mapping.iter().cloned().collect()) }
138                    }
139                    _ => panic!(
140                        "Not a valid pre_processor_mapping for language {}",
141                        language
142                    ),
143                }
144            } else {
145                quote! { None }
146            };
147
148            let reverse_specific_pre_processor_mapping =
149                if let Some(field) = reverse_specific_pre_processor_mapping_field {
150                    match &field.ty {
151                        Type::Path(type_path) => {
152                            let mapping = &type_path.path.segments.first().unwrap().ident;
153
154                            quote! { Some(#mapping.iter().cloned().collect()) }
155                        }
156                        _ => panic!(
157                            "Not a valid pre_processor_mapping for language {}",
158                            language
159                        ),
160                    }
161                } else {
162                    quote! { None }
163                };
164
165            quote! {
166                use std::{convert::From, default:: Default};
167                use crate::transliterator::{Transliterator, TransliteratorBuilder};
168
169                #[derive(Clone, Debug)]
170                pub struct #language {
171                    language: String,
172                    code: String,
173                }
174
175                impl Default for #language {
176                    fn default() -> Self {
177                        Self {
178                            language: #language_label.to_string(),
179                            code: #code.to_string()
180                        }
181                    }
182                }
183
184                impl #language {
185                    pub fn new() -> Self {
186                        Default::default()
187                    }
188                }
189
190                impl From<#language> for Transliterator {
191                    fn from(language: #language) -> Self {
192                        TransliteratorBuilder::default()
193                            .language(language.language)
194                            .code(language.code)
195                            .mapping(#mapping)
196                            .pre_processor_mapping(#pre_processor_mapping)
197                            .reverse_specific_mapping(#reverse_specific_mapping)
198                            .reverse_specific_pre_processor_mapping(#reverse_specific_pre_processor_mapping)
199                            .build()
200                            .unwrap()
201                    }
202                }
203
204
205            }
206        }
207    };
208
209    TokenStream::from(expanded)
210}