Skip to main content

fourcc_rs_macros/
lib.rs

1//! Macro implementations for the `fourcc-rs` crate. See [fourcc-rs](../fourcc-rs) for more details.
2extern crate proc_macro2;
3use quote::quote;
4use syn::{parse::Parse, Lit};
5
6/// Macro to create `fourcc::FourCC`s from literals at compile time.
7///
8/// The macro accepts a single argument of a _string_ literal, _byte string_, or _integer_. Other types are rejected. It will also emit a compilation error if the given value is out of
9/// range or contains invalid characters.
10#[proc_macro]
11pub fn fourcc(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
12    fourcc_impl(quote! {::fourcc}, item)
13}
14
15#[proc_macro]
16#[doc(hidden)]
17pub fn fourcc_rexport(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
18    struct Invocation {
19        fcc_crate: syn::Path,
20        rest: proc_macro2::TokenStream,
21    }
22
23    impl Parse for Invocation {
24        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
25            let fcc_crate = input.parse()?;
26            let rest = input.parse()?;
27
28            Ok(Invocation { fcc_crate, rest })
29        }
30    }
31
32    let Invocation { fcc_crate, rest } = syn::parse(item).unwrap();
33    fourcc_impl(quote! { #fcc_crate }, rest.into())
34}
35
36fn fourcc_impl(
37    fcc_crate: proc_macro2::TokenStream,
38    item: proc_macro::TokenStream,
39) -> proc_macro::TokenStream {
40    if let Ok(literal) = syn::parse::<Lit>(item) {
41        match literal {
42            Lit::Bool(_) => {
43                return quote! { compile_error!("Can not create FourCC code from bool") }.into();
44            }
45            Lit::Byte(_) => {
46                return quote! { compile_error!("Can not create FourCC code from a single byte") }
47                    .into();
48            }
49            Lit::Char(_) => {
50                return quote! { compile_error!("Can not create FourCC code from a single char") }
51                    .into();
52            }
53            Lit::Float(_) => {
54                return quote! { compile_error!("Can not create FourCC code from a float") }.into();
55            }
56            Lit::Str(lit_str) => {
57                let value = lit_str.value();
58                match value.len() {
59                    0..4 => {
60                        let error = format!("String {value:?} is too short for FourCC");
61                        return quote! { compile_error!(#error) }.into();
62                    }
63                    4 => {
64                        let mut fourcc: u32 = 0;
65                        for (idx, char) in value.chars().enumerate() {
66                            if !char.is_ascii() {
67                                return quote! { compile_error!("Character '{}' at index {} can not be converted to ASCII for FourCC", char, idx) }.into();
68                            };
69                            let mut buf = vec![0u8];
70                            char.encode_utf8(&mut buf);
71                            fourcc |= (buf[0] as u32) << (24 - 8 * idx);
72                        }
73                        return quote! { #fcc_crate::FourCC(#fourcc) }.into();
74                    }
75                    5.. => {
76                        return quote! { compile_error!("String is too long for FourCC") }.into();
77                    }
78                }
79            }
80            Lit::ByteStr(lit_byte_str) => {
81                let value = lit_byte_str.value();
82                match value.len() {
83                    0..4 => {
84                        return quote! { compile_error!("Byte string is too short for FourCC") }
85                            .into();
86                    }
87                    4 => {
88                        let fourcc: u32 = comb_u8(value[0], value[1], value[2], value[3]);
89                        return quote! { #fcc_crate::FourCC(#fourcc) }.into();
90                    }
91                    5.. => {
92                        return quote! { compile_error!("String is too long for FourCC") }.into();
93                    }
94                }
95            }
96
97            Lit::Int(lit) => match lit.base10_parse::<u32>() {
98                Ok(v) => {
99                    return quote! { #fcc_crate::FourCC(#v) }.into();
100                }
101                Err(_) => {
102                    return quote! { compile_error!("FourCC can't be built from unparseable int") }
103                        .into();
104                }
105            },
106
107            Lit::CStr(_) => {
108                return quote! { compile_error!("CStr can not be converted to FourCC code") }
109                    .into();
110            }
111
112            Lit::Verbatim(_) | _ => {
113                return quote! { compile_error!("Verbatim can not be converted to FourCC code") }
114                    .into();
115            }
116        }
117    }
118
119    quote! { compile_error!("Can not convert to FourCC code") }.into()
120}
121
122#[inline]
123const fn comb_u8(v1: u8, v2: u8, v3: u8, v4: u8) -> u32 {
124    ((v1 as u32) << 24) | ((v2 as u32) << 16) | ((v3 as u32) << 8) | (v4 as u32)
125}