zng_ext_l10n_proc_macros/
lang.rs

1use proc_macro2::TokenStream;
2use quote::{quote, quote_spanned};
3use std::str::FromStr;
4use syn::{Ident, LitStr, ext::IdentExt, parse::Parse, parse_macro_input};
5
6pub fn expand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let Input { unic_langid, lang } = parse_macro_input!(input as Input);
8
9    let (raw, e_span) = match lang {
10        LangInput::LitStr(s) => (s.value(), s.span()),
11        LangInput::Ident(i) => (i.to_string(), i.span()),
12    };
13
14    let r = match unic_langid::LanguageIdentifier::from_str(&raw) {
15        Ok(lang) => {
16            let (lang, script, region, variants) = lang.into_parts();
17
18            let lang: Option<u64> = lang.into();
19            let lang = if let Some(lang) = lang {
20                quote!(unsafe { #unic_langid::subtags::Language::from_raw_unchecked(#lang) })
21            } else {
22                quote!(std::default::Default::default())
23            };
24
25            let script = if let Some(script) = script {
26                let script: u32 = script.into();
27                quote!(Some(unsafe { #unic_langid::subtags::Script::from_raw_unchecked(#script) }))
28            } else {
29                quote!(None)
30            };
31
32            let region = if let Some(region) = region {
33                let region: u32 = region.into();
34                quote!(Some(unsafe { #unic_langid::subtags::Region::from_raw_unchecked(#region) }))
35            } else {
36                quote!(None)
37            };
38
39            let variants = if !variants.is_empty() {
40                let v: Vec<_> = variants
41                    .iter()
42                    .map(|v| {
43                        let variant: u64 = v.into();
44                        quote!(unsafe { #unic_langid::subtags::Variant::from_raw_unchecked(#variant) })
45                    })
46                    .collect();
47                quote!(Some(Box::new([#(#v,)*])))
48            } else {
49                quote!(None)
50            };
51
52            quote! {
53                #unic_langid::LanguageIdentifier::from_raw_parts_unchecked(#lang, #script, #region, #variants)
54            }
55        }
56        Err(e) => {
57            let e = match e {
58                unic_langid::LanguageIdentifierError::Unknown => "unknown error".to_owned(),
59                unic_langid::LanguageIdentifierError::ParserError(e) => e.to_string(),
60            };
61            quote_spanned! {e_span=>
62                compile_error!(#e)
63            }
64        }
65    };
66
67    r.into()
68}
69
70struct Input {
71    unic_langid: TokenStream,
72    lang: LangInput,
73}
74impl Parse for Input {
75    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
76        Ok(Input {
77            unic_langid: non_user_braced!(input, "unic_langid").parse().unwrap(),
78            lang: non_user_braced!(input, "lang").parse().unwrap(),
79        })
80    }
81}
82
83enum LangInput {
84    LitStr(LitStr),
85    Ident(Ident),
86}
87impl Parse for LangInput {
88    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
89        if input.peek(Ident::peek_any) {
90            Ok(LangInput::Ident(input.parse()?))
91        } else {
92            Ok(LangInput::LitStr(input.parse()?))
93        }
94    }
95}