rgrit_proc/
lib.rs

1use rgrit_core::Compression;
2use rgrit_core::GfxFormat;
3use rgrit_rs::Bitmap;
4use rgrit_rs::BitmapBuilder;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::Ident;
9use syn::LitInt;
10use syn::{parse::Parse, parse_macro_input, LitStr};
11
12#[derive(Debug, Clone)]
13struct Grit {
14    bitmap: Bitmap,
15}
16
17impl Parse for Grit {
18    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
19        let lit = input.parse::<LitStr>()?;
20
21        if input.is_empty() {
22            let bitmap = BitmapBuilder::new(lit.value())
23                .with_transparency(rgrit_core::Transparency::Disabled)
24                .with_bit_depth_override(rgrit_core::BitDepth::Custom(16))
25                .with_format(GfxFormat::Bitmap)
26                .build()
27                .map_err(|e| {
28                    let msg = format!("Failed to load {}: {}", lit.value(), e);
29                    syn::Error::new(lit.span(), msg)
30                })?;
31
32            Ok(Grit { bitmap })
33        } else {
34            if !input.peek(syn::Token![,]) {
35                return Err(syn::Error::new(input.span(), "Expected comma after input"));
36            }
37            input.parse::<syn::Token![,]>()?;
38
39            let mut builder = BitmapBuilder::new(lit.value());
40
41            while !input.is_empty() {
42                let ident = input.parse::<Ident>()?;
43                input.parse::<syn::Token![=]>()?;
44
45                match ident.to_string().as_str() {
46                    "transparency" => {
47                        if input.peek(Ident) {
48                            let ident = input.parse::<Ident>()?;
49                            match ident.to_string().as_str() {
50                                "Disabled" => {
51                                    builder = builder
52                                        .with_transparency(rgrit_core::Transparency::Disabled)
53                                }
54                                _ => {
55                                    return Err(syn::Error::new(
56                                        ident.span(),
57                                        "Unknown transparency",
58                                    ))
59                                }
60                            }
61                        } else if input.peek(LitInt) {
62                            todo!()
63                        } else {
64                            return Err(syn::Error::new(
65                                input.span(),
66                                "Expected identifier or literal",
67                            ));
68                        }
69                    }
70                    "bit_depth" => {
71                        if input.peek(Ident) {
72                            let ident = input.parse::<Ident>()?;
73                            match ident.to_string().as_str() {
74                                "A3I5" => {
75                                    builder =
76                                        builder.with_bit_depth_override(rgrit_core::BitDepth::A3I5)
77                                }
78                                "A5I3" => {
79                                    builder =
80                                        builder.with_bit_depth_override(rgrit_core::BitDepth::A5I3)
81                                }
82                                "FourByFour" | "4x4" => {
83                                    builder = builder
84                                        .with_bit_depth_override(rgrit_core::BitDepth::FourByFour)
85                                }
86                                _ => {
87                                    return Err(syn::Error::new(ident.span(), "Unknown bit depth"))
88                                }
89                            }
90                        } else if input.peek(LitInt) {
91                            let lit = input.parse::<LitInt>()?;
92                            builder = builder.with_bit_depth_override(
93                                rgrit_core::BitDepth::Custom(lit.base10_parse()?),
94                            );
95                        } else {
96                            return Err(syn::Error::new(
97                                input.span(),
98                                "Expected identifier or literal",
99                            ));
100                        }
101                    }
102                    "format" => {
103                        let format_ident = input.parse::<Ident>()?;
104
105                        match format_ident.to_string().as_str() {
106                            "Bitmap" => builder = builder.with_format(GfxFormat::Bitmap),
107                            "Tile" => builder = builder.with_format(GfxFormat::Tile),
108                            _ => {
109                                return Err(syn::Error::new(format_ident.span(), "Unknown format"))
110                            }
111                        }
112                    }
113                    "tile_width" => {
114                        let lit = input.parse::<LitInt>()?;
115                        builder = builder.with_tile_width(lit.base10_parse()?);
116                    }
117                    "tile_height" => {
118                        let lit = input.parse::<LitInt>()?;
119                        builder = builder.with_tile_height(lit.base10_parse()?);
120                    }
121                    "meta_width" => {
122                        let lit = input.parse::<LitInt>()?;
123                        builder = builder.with_meta_width(lit.base10_parse()?);
124                    }
125                    "meta_height" => {
126                        let lit = input.parse::<LitInt>()?;
127                        builder = builder.with_meta_height(lit.base10_parse()?);
128                    }
129                    "area_left" => {
130                        let lit = input.parse::<LitInt>()?;
131                        builder = builder.with_area_left(lit.base10_parse()?);
132                    }
133                    "area_right" => {
134                        let lit = input.parse::<LitInt>()?;
135                        builder = builder.with_area_right(lit.base10_parse()?);
136                    }
137                    "area_width" => {
138                        let lit = input.parse::<LitInt>()?;
139                        builder = builder.with_area_width(lit.base10_parse()?);
140                    }
141                    "area_top" => {
142                        let lit = input.parse::<LitInt>()?;
143                        builder = builder.with_area_top(lit.base10_parse()?);
144                    }
145                    "area_bottom" => {
146                        let lit = input.parse::<LitInt>()?;
147                        builder = builder.with_area_bottom(lit.base10_parse()?);
148                    }
149                    "area_height" => {
150                        let lit = input.parse::<LitInt>()?;
151                        builder = builder.with_area_height(lit.base10_parse()?);
152                    }
153                    "compression" => {
154                        let compression_ident = input.parse::<Ident>()?;
155
156                        match compression_ident.to_string().as_str() {
157                            "Off" => builder = builder.with_compression(Compression::Off),
158                            "LZ77" => builder = builder.with_compression(Compression::LZ77),
159                            "Huffman" => builder = builder.with_compression(Compression::Huffman),
160                            "RLE" => builder = builder.with_compression(Compression::RLE),
161                            "OffHeader" => {
162                                builder = builder.with_compression(Compression::OffHeader)
163                            }
164                            _ => {
165                                return Err(syn::Error::new(
166                                    compression_ident.span(),
167                                    "Unknown compression",
168                                ))
169                            }
170                        }
171                    }
172                    _ => return Err(syn::Error::new(ident.span(), "Unknown attribute")),
173                };
174
175                if input.peek(syn::Token![,]) {
176                    input.parse::<syn::Token![,]>()?;
177                }
178            }
179
180            let bitmap = builder.build().map_err(|e| {
181                let msg = format!("Failed to load {}: {}", lit.value(), e);
182                syn::Error::new(lit.span(), msg)
183            })?;
184
185            Ok(Grit { bitmap })
186        }
187    }
188}
189
190#[proc_macro]
191pub fn grit(input: TokenStream) -> TokenStream {
192    let input = parse_macro_input!(input as Grit);
193
194    // Put all the fields into a struct as `&'static [u8]`.
195    let gfx = input.bitmap.gfx;
196    let palette = input.bitmap.palette;
197    let map = input.bitmap.map;
198    let meta = input.bitmap.meta;
199
200    // Also put some metadata so we can automatically display it.
201    let bit_depth = match input.bitmap.spec.bit_depth {
202        Some(rgrit_core::BitDepth::A3I5) => quote! { Some(rgrit_core::BitDepth::A3I5) },
203        Some(rgrit_core::BitDepth::A5I3) => quote! { Some(rgrit_core::BitDepth::A5I3) },
204        Some(rgrit_core::BitDepth::FourByFour) => quote! { Some(rgrit_core::BitDepth::FourByFour) },
205        Some(rgrit_core::BitDepth::Custom(n)) => quote! { Some(rgrit_core::BitDepth::Custom(#n)) },
206
207        None => quote! { None },
208    };
209    let format = match input.bitmap.spec.format {
210        rgrit_core::GfxFormat::Bitmap => quote! { rgrit_core::GfxFormat::Bitmap },
211        rgrit_core::GfxFormat::Tile => quote! { rgrit_core::GfxFormat::Tile },
212    };
213    let transparency = match input.bitmap.spec.transparency {
214        rgrit_core::Transparency::Disabled => quote! { rgrit_core::Transparency::Disabled },
215        rgrit_core::Transparency::Color(rgrit_core::Color::RGB { r, g, b }) => {
216            quote! { grit_core::Transparency::Color(rgrit_core::Color::RGB { r: #r, g: #g, b: #b }) }
217        }
218        rgrit_core::Transparency::Color(rgrit_core::Color::GBR16(clr)) => {
219            quote! { grit_core::Transparency::Color(rgrit_core::Color::GBR16(#clr)) }
220        }
221    };
222
223    quote! {
224        rgrit::StaticBitmap {
225            gfx: &[#(#gfx),*],
226            palette: &[#(#palette),*],
227            map: &[#(#map),*],
228            meta: &[#(#meta),*],
229            spec: rgrit_core::BitmapSpec {
230                bit_depth: #bit_depth,
231                format: #format,
232                transparency: #transparency,
233            },
234        }
235    }
236    .into()
237}