zng_ext_l10n_proc_macros/
lang.rs

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
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use std::str::FromStr;
use syn::{ext::IdentExt, parse::Parse, parse_macro_input, Ident, LitStr};

pub fn expand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let Input { unic_langid, lang } = parse_macro_input!(input as Input);

    let (raw, e_span) = match lang {
        LangInput::LitStr(s) => (s.value(), s.span()),
        LangInput::Ident(i) => (i.to_string(), i.span()),
    };

    let r = match unic_langid::LanguageIdentifier::from_str(&raw) {
        Ok(lang) => {
            let (lang, script, region, variants) = lang.into_parts();

            let lang: Option<u64> = lang.into();
            let lang = if let Some(lang) = lang {
                quote!(unsafe { #unic_langid::subtags::Language::from_raw_unchecked(#lang) })
            } else {
                quote!(std::default::Default::default())
            };

            let script = if let Some(script) = script {
                let script: u32 = script.into();
                quote!(Some(unsafe { #unic_langid::subtags::Script::from_raw_unchecked(#script) }))
            } else {
                quote!(None)
            };

            let region = if let Some(region) = region {
                let region: u32 = region.into();
                quote!(Some(unsafe { #unic_langid::subtags::Region::from_raw_unchecked(#region) }))
            } else {
                quote!(None)
            };

            let variants = if !variants.is_empty() {
                let v: Vec<_> = variants
                    .iter()
                    .map(|v| {
                        let variant: u64 = v.into();
                        quote!(unsafe { #unic_langid::subtags::Variant::from_raw_unchecked(#variant) })
                    })
                    .collect();
                quote!(Some(Box::new([#(#v,)*])))
            } else {
                quote!(None)
            };

            quote! {
                #unic_langid::LanguageIdentifier::from_raw_parts_unchecked(#lang, #script, #region, #variants)
            }
        }
        Err(e) => {
            let e = match e {
                unic_langid::LanguageIdentifierError::Unknown => "unknown error".to_owned(),
                unic_langid::LanguageIdentifierError::ParserError(e) => e.to_string(),
            };
            quote_spanned! {e_span=>
                compile_error!(#e)
            }
        }
    };

    r.into()
}

struct Input {
    unic_langid: TokenStream,
    lang: LangInput,
}
impl Parse for Input {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(Input {
            unic_langid: non_user_braced!(input, "unic_langid").parse().unwrap(),
            lang: non_user_braced!(input, "lang").parse().unwrap(),
        })
    }
}

enum LangInput {
    LitStr(LitStr),
    Ident(Ident),
}
impl Parse for LangInput {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        if input.peek(Ident::peek_any) {
            Ok(LangInput::Ident(input.parse()?))
        } else {
            Ok(LangInput::LitStr(input.parse()?))
        }
    }
}