mapack_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, ToTokens};
4use quote_into::quote_into;
5
6#[derive(Debug, Clone)]
7struct Field {
8    ident: syn::Ident,
9    ty: syn::Path,
10    key: String,
11    auto_encode: bool,
12    auto_decode: bool,
13}
14
15impl syn::parse::Parse for Field {
16    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
17        let mut auto_encode = true;
18        let mut auto_decode = true;
19
20        let attrs = input.call(syn::Attribute::parse_outer)?;
21        for attr in attrs {
22            if let syn::Meta::Path(mp) = attr.meta {
23                match mp.to_token_stream().to_string().as_str() {
24                    "no_decode" => auto_decode = false,
25                    "no_encode" => auto_encode = false,
26                    _ => {}
27                }
28            }
29        }
30
31        let ident: syn::Ident = input.parse()?;
32        input.parse::<syn::Token![:]>()?;
33        let ty: syn::Path = input.parse()?;
34
35        let key = ident.to_string();
36        if key == "id" {
37            return Err(syn::Error::new(input.span(), "key `id` is reserved"));
38        }
39        Ok(Self { ident, ty, key, auto_decode, auto_encode })
40    }
41}
42
43#[derive(Debug)]
44struct Layer {
45    ident: syn::Ident,
46    name: syn::Ident,
47    fields: Vec<Field>,
48}
49
50impl syn::parse::Parse for Layer {
51    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
52        let name = input.parse::<syn::Ident>()?;
53
54        let mut layer = Self {
55            ident: format_ident!("Point{}", to_camelcase(&name.to_string())),
56            name,
57            fields: Vec::new(),
58        };
59
60        input.parse::<syn::Token![:]>()?;
61
62        let content;
63        syn::braced!(content in input);
64        let fields = content.parse_terminated(Field::parse, syn::Token![,])?;
65        layer.fields = fields.iter().cloned().collect();
66
67        Ok(layer)
68    }
69}
70
71#[derive(Debug)]
72struct Tile {
73    layers: Vec<Layer>,
74}
75
76impl syn::parse::Parse for Tile {
77    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
78        let mut layers = Vec::<Layer>::new();
79        loop {
80            if input.is_empty() {
81                break;
82            }
83
84            let layer: Layer = input.parse()?;
85            layers.push(layer);
86
87            if input.is_empty() {
88                break;
89            }
90            input.parse::<syn::Token![,]>()?;
91        }
92
93        Ok(Self { layers })
94    }
95}
96
97#[proc_macro]
98pub fn mapack(code: TokenStream) -> TokenStream {
99    let tile = syn::parse_macro_input!(code as Tile);
100    let mut s = TokenStream2::new();
101    let ci = crate_ident();
102
103    quote_into! {s +=
104        #{
105            for layer in tile.layers.iter() {
106                let Layer { ident, fields, name } = layer;
107                let name_str = name.to_string();
108                let keys_len = fields.len();
109                quote_into! {s +=
110                    #[derive(Debug, Clone)]
111                    pub struct #ident {
112                        pub coordinate: #ci::Coordinate,
113                        pub id: Option<u64>,
114
115                        #{for Field { ident, ty, .. } in fields.iter() {
116                            quote_into!(s += pub #ident: #ty,);
117                        }}
118                    }
119
120                    impl #ident {
121                        pub const NAME: &str = #name_str;
122                        pub const KEYS: [&str; #keys_len] = [
123                            #{for Field { key, .. } in fields {
124                                quote_into!(s += #key,)}
125                            }
126                        ];
127
128                        pub fn new(coordinate: #ci::Coordinate) -> Self {
129                            Self {
130                                coordinate,
131                                id: None,
132                                #{for Field { ident, ty, .. } in fields.iter() {
133                                    quote_into!(s += #ident: #ty::default(),);
134                                }}
135                            }
136                        }
137
138                        #[allow(dead_code)]
139                        pub fn decode_point(
140                            zom: u8, tx: u32, ty: u32,
141                            feature: &#ci::Feature, values: &[#ci::Value],
142                        ) -> Result<Self, &'static str> {
143                            #{point_decode(s, layer)}
144                        }
145
146                        pub fn decode_layer(
147                            zom: u8, tx: u32, ty: u32,
148                            layer: &#ci::Layer
149                        ) -> #ci::protobuf::Result<Vec<Self>> {
150                            let mut points = Vec::<Self>::with_capacity(layer.features.len());
151
152                            for feature in layer.features.iter() {
153                                match Self::decode_point(zom, tx, ty, feature, &layer.values) {
154                                    Ok(v) => points.push(v),
155                                    Err(e) => {
156                                        println!("found an invalid marker: {e:?}")
157                                    }
158                                }
159                            }
160
161                            Ok(points)
162                        }
163
164                        #{for field in fields {
165                            let Field {ty, key, ..} = field;
166                            if field.auto_decode {
167                                let ident = format_ident!("decode_{key}");
168                                quote_into! {s +=
169                                    fn #ident(v: &#ci::Value) -> Option<#ty> {
170                                        #{point_auto_decode(s, field);}
171                                    }
172                                }
173                            }
174                            if field.auto_encode {
175                                let ident = format_ident!("encode_{}", field.key);
176                                quote_into! {s +=
177                                    fn #ident(&self) -> #ci::Value {
178                                        #{point_auto_encode(s, field);}
179                                    }
180                                }
181                            }
182                        }}
183                    }
184                }
185            }
186        }
187
188        #[derive(Debug)]
189        pub struct Tile {
190            #{for Layer { ident, name, .. } in tile.layers.iter() {
191                quote_into!(s += pub #name: Vec<#ident>, );
192            }}
193        }
194
195        impl Tile {
196            pub fn new() -> Self {
197                Self {#{
198                    for Layer { name, .. } in tile.layers.iter() {
199                        quote_into!(s += #name: Vec::new(), );
200                    }
201                }}
202            }
203
204            #[allow(dead_code)]
205            pub fn decode(zom: u8, tx: u32, ty: u32, pbf: Vec<u8>) -> #ci::protobuf::Result<Self> {
206                let mut tile = Self::new();
207                let vec_tile = <#ci::Tile as #ci::protobuf::Message>::parse_from_bytes(&pbf)?;
208                if vec_tile.layers.is_empty() { return Ok(tile); }
209
210                for layer in vec_tile.layers.iter() {#{
211                    tile_decode(s, &tile.layers)
212                }}
213
214                Ok(tile)
215            }
216
217            #[allow(dead_code)]
218            pub fn encode(&self) -> #ci::protobuf::Result<Vec<u8>> {
219                let mut vec_tile = #ci::Tile::default();
220
221                #{for layer in tile.layers.iter() {
222                    quote_into!(s += 'a: {#{tile_encode(s, layer)}});
223                }}
224
225                #ci::protobuf::Message::write_to_bytes(&vec_tile)
226            }
227        }
228    }
229
230    s.into()
231}
232
233fn tile_encode(s: &mut TokenStream2, Layer { ident, name, fields }: &Layer) {
234    let keys_len = fields.len();
235    let ci = crate_ident();
236    let name_str = name.to_string();
237
238    quote_into! {s +=
239        let mut values = Vec::<#ci::Value>::with_capacity(self.#name.len() * #keys_len);
240        let mut features = Vec::<#ci::Feature>::with_capacity(self.#name.len());
241
242        for point in self.#name.iter() {
243            #{for Field { key, .. } in fields.iter() {
244                let ptv = format_ident!("encode_{key}");
245                let val = format_ident!("{key}_value");
246                quote_into! {s +=
247                    let #val = values.len() as u32;
248                    values.push(point.#ptv());
249                }
250            }}
251
252            features.push(#ci::Feature {
253                id: point.id,
254                tags: vec![#{for (idx, Field { key, .. }) in fields.iter().enumerate() {
255                    let idx = idx as u32;
256                    let val = format_ident!("{key}_value");
257                    quote_into!(s += #idx, #val,);
258                }}],
259                geometry: point.coordinate.to_geometry().to_vec(),
260                type_: Some(#ci::protobuf::EnumOrUnknown::new(#ci::GeomType::POINT)),
261                ..Default::default()
262            });
263        }
264
265        vec_tile.layers.push(#ci::Layer {
266            name: Some(String::from(#name_str)),
267            extent: Some(4096),
268            version: Some(2),
269            features,
270            keys: #ident::KEYS.map(|k| k.to_string()).to_vec(),
271            values,
272            ..Default::default()
273        });
274    }
275}
276
277fn tile_decode(s: &mut TokenStream2, layers: &[Layer]) {
278    quote_into! {s +=
279        if layer.version() != 2 { continue }
280
281        match layer.name() {
282            #{for Layer { name, ident, .. } in layers {
283                let name_str = name.to_string();
284                quote_into! {s += #name_str => {
285                    tile.#name = #ident::decode_layer(zom, tx, ty, layer)?;
286                }}
287            }}
288            _ => {}
289        }
290
291        continue;
292    }
293}
294
295fn point_auto_encode(s: &mut TokenStream2, field: &Field) {
296    let ci = crate_ident();
297
298    let Field { ident, ty, .. } = field;
299    let ty_str = ty.to_token_stream().to_string();
300
301    if ty.segments.last().unwrap().ident == "Gene" {
302        quote_into!(s += #ci::Value::from_string(self.#ident.as_hex()));
303        return;
304    }
305    match ty_str.as_str() {
306        "bool" => quote_into!(s += #ci::Value::from_bool(self.#ident)),
307        "u8" | "u16" | "u32" | "u64" => {
308            quote_into!(s += #ci::Value::from_uint(self.#ident as u64))
309        }
310        "String" => {
311            quote_into!(s += #ci::Value::from_string(self.#ident.clone()))
312        }
313        _ => quote_into! {s +=
314            compile_error!(concat!("bad prop type for auto encoding: ", #ty_str));
315        },
316    }
317}
318
319fn point_auto_decode(s: &mut TokenStream2, field: &Field) {
320    let ty = &field.ty;
321    let ty_str = ty.to_token_stream().to_string();
322    if ty.segments.last().unwrap().ident == "Gene" {
323        quote_into!(s += v.string_value().parse::<#ty>().ok());
324        return;
325    }
326    match ty_str.as_str() {
327        "bool" => quote_into!(s += Some(v.bool_value())),
328        "String" => quote_into! {s += Some(v.string_value().to_string())},
329        "u8" | "u16" | "u32" | "u64" => {
330            quote_into!(s += Some(v.uint_value() as #ty))
331        }
332        _ => quote_into! {s +=
333            compile_error!(concat!("bad prop type for auto decoding: ", #ty_str));
334        },
335    }
336}
337
338fn point_decode(s: &mut TokenStream2, Layer { fields, .. }: &Layer) {
339    let ci = crate_ident();
340
341    quote_into! {s +=
342        if feature.geometry.len() != 3 {
343            return Err("bad geometry");
344        }
345
346        let tags = &feature.tags;
347        // if tags.is_empty() {
348        //     return Err("no tags");
349        // }
350        if tags.len() % 2 != 0 {
351            return Err("bad tags length");
352        }
353
354        let geometry: [u32; 3] = feature.geometry.clone().try_into().unwrap();
355        let mut point = Self::new(#ci::Coordinate::from_geometry(zom, tx, ty, geometry));
356        point.id = feature.id;
357
358        let mut tags_iter = tags.iter();
359        loop {
360            let Some(k) = tags_iter.next() else { break };
361            let Some(v) = tags_iter.next() else { break };
362            let k = *k as usize;
363            let v = *v as usize;
364            if k >= Self::KEYS.len() || v >= values.len() {
365                return Err("invalid tags");
366            }
367            let v = &values[v];
368
369            match Self::KEYS[k] {
370                #{for Field { ident, key, .. } in fields {
371                    let pfv = format_ident!("decode_{key}");
372                    quote_into! {s += #key => {
373                        if let Some(value) = Self::#pfv(v) {
374                            point.#ident = value;
375                        } else {
376                            return Err(concat!("could not decode ", #key, "s value"));
377                        }
378                    }}
379                }}
380                _ => unreachable!()
381            }
382        }
383
384        Ok(point)
385    }
386}
387
388fn to_camelcase(input: &str) -> String {
389    let mut out = String::with_capacity(input.len());
390    for word in input.split('_') {
391        let (h, r) = word.split_at(1);
392        out.push_str(&h.to_uppercase());
393        out.push_str(&r.to_lowercase());
394    }
395
396    out
397}
398
399fn crate_ident() -> syn::Ident {
400    // let found_crate = crate_name("shah").unwrap();
401    // let name = match &found_crate {
402    //     FoundCrate::Itself => "shah",
403    //     FoundCrate::Name(name) => name,
404    // };
405
406    syn::Ident::new("mapack", proc_macro2::Span::call_site())
407}