quick arrays
| -rw-r--r-- | Cargo.lock | 10 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | README.md | 39 | ||||
| -rw-r--r-- | src/lib.rs | 143 | ||||
| -rw-r--r-- | tests/maps.rs | 12 |
5 files changed, 164 insertions, 46 deletions
@@ -4,7 +4,7 @@ version = 3 [[package]] name = "amap" -version = "0.1.0" +version = "0.1.1" dependencies = [ "quote", "syn", @@ -21,18 +21,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1,7 +1,7 @@ [package] name = "amap" -version = "0.1.0" -author = ["bend-n <[email protected]>"] +version = "0.1.1" +authors = ["bend-n <[email protected]>"] description = "define `[Option<T>; N]` easily" edition = "2021" repository = "https://github.com/bend-n/amap.git" @@ -9,7 +9,7 @@ license = "MIT" [dependencies] quote = "1.0.32" -syn = "2.0.15" +syn = { version = "2.0.15", features = ["full"] } [lib] proc_macro = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..befd6cb --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# amap + +Simple array initialization macro. + +## Ever wanted to create a const `HashMap<usize, T>`, and started thinking, wouldn't it be nice if this was a array? + +No? + +Well now you can! +Its as simple as + +```rust +amap! { + 4 => 56, + 2 => 32, +} // creates a [Option<i32>; 5] for all your indexing needs +``` + +### Think it would be too much boilerplate to have multiple keys for one value? + +Patterns got you covered! + +```rust +amap! { + 0..=4 => 2, + 5 | 6 => 3, +} +``` + +### Want to put it in a constant? No problem! + +It's just a array! + +```rust +const ID_MAP: [Option<i32>; 6] = amap! { + 5 => 6, + 2 => 1, +} +``` @@ -1,55 +1,124 @@ +use std::collections::{hash_map::Entry, HashMap}; + use proc_macro::TokenStream; use quote::quote; use syn::{ parse::{self, Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, - Error, Expr, LitInt, Token, + spanned::Spanned, + Error, Expr, Lit, Pat, Token, }; #[derive(Clone)] struct Index { - index: usize, + indices: Vec<usize>, value: Expr, } -impl std::fmt::Debug for Index { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.index) - } -} - impl Parse for Index { fn parse(input: ParseStream<'_>) -> parse::Result<Index> { - let index = input.parse::<LitInt>()?; - let index = index.base10_parse()?; - input.parse::<Token![=>]>()?; - let value = input.parse()?; - Ok(Index { index, value }) + let index = Pat::parse_multi(input)?; + match index { + Pat::Lit(v) => match v.lit { + Lit::Int(v) => { + input.parse::<Token![=>]>()?; + Ok(Index { + indices: vec![v.base10_parse()?], + value: input.parse()?, + }) + } + _ => Err(Error::new_spanned(v, "must be numeric literal"))?, + }, + Pat::Or(v) => { + let mut index = Vec::with_capacity(v.cases.len()); + for p in v.cases { + match p { + Pat::Lit(v) => match v.lit { + Lit::Int(v) => index.push(v.base10_parse()?), + _ => Err(Error::new_spanned(v, "must be numeric literal"))?, + }, + _ => Err(Error::new_spanned( + p, + "pattern must include only literal ints", + ))?, + } + } + input.parse::<Token![=>]>()?; + Ok(Index { + indices: index, + value: input.parse()?, + }) + } + Pat::Range(r) => { + let s = r.span(); + let begin = match *r.start.ok_or(Error::new(s, "range must be bounded"))? { + Expr::Lit(v) => match v.lit { + Lit::Int(v) => v.base10_parse()?, + _ => Err(Error::new_spanned( + v, + "range start bound must be integer literal", + ))?, + }, + e => Err(Error::new_spanned( + e, + "range start bound must include only literal ints", + ))?, + }; + let end = match *r.end.ok_or(Error::new(s, "range must be bounded"))? { + Expr::Lit(v) => match v.lit { + Lit::Int(v) => v.base10_parse()?, + _ => Err(Error::new_spanned( + v, + "range end bound must be integer literal", + ))?, + }, + e => Err(Error::new_spanned( + e, + "range end bound must include only literal ints", + ))?, + }; + input.parse::<Token![=>]>()?; + match r.limits { + syn::RangeLimits::Closed(..) => Ok(Index { + indices: (begin..=end).collect(), + value: input.parse()?, + }), + syn::RangeLimits::HalfOpen(..) => Ok(Index { + indices: (begin..end).collect(), + value: input.parse()?, + }), + } + } + _ => Err(input.error("pattern must be literal(5) | or(5 | 4) | range(4..5)"))?, + } } } -struct Map(Vec<Option<Index>>); +struct Map(Vec<Option<Expr>>); impl Parse for Map { fn parse(input: ParseStream) -> syn::Result<Self> { let parsed = Punctuated::<Index, Token![,]>::parse_terminated(input)?; - let mut all = parsed.into_iter().collect::<Vec<_>>(); - if all.len() == 0 { + if parsed.is_empty() { return Err(input.error("no keys")); } - all.sort_unstable_by(|a, b| a.index.cmp(&b.index)); - let max = all[all.len() - 1].index; - let mut out: Vec<Option<Index>> = vec![None; max + 1]; - for Index { value, index } in all { - let o = out.get_mut(index).unwrap(); - match o { - Some(_) => { - // err.combine(Error::new_spanned(&v.value, "other duplicate key")); - return Err(Error::new_spanned(&value, "duplicate keys")); + let mut flat = HashMap::new(); + let mut largest = 0; + for Index { value, indices } in parsed.into_iter() { + for index in indices { + if index > largest { + largest = index; } - None => *o = Some(Index { value, index }), + match flat.entry(index) { + Entry::Occupied(_) => Err(input.error("duplicate key"))?, + Entry::Vacant(v) => v.insert(value.clone()), + }; } } + let mut out = vec![None; largest + 1]; + for (index, expr) in flat.into_iter() { + out[index] = Some(expr) + } Ok(Map(out)) } } @@ -60,13 +129,15 @@ impl Parse for Map { /// # use amap::amap; /// #[derive(Debug, PartialEq)] /// enum Y { -/// A, -/// B, -/// C, +/// A, +/// B, +/// C, +/// D, /// } /// static X: [Option<Y>; 46] = amap! { -/// 2 => Y::A, -/// 5 => Y::C, +/// 2..=25 => Y::A, +/// 26 | 32 => Y::C, +/// 27..32 => Y::D, /// 45 => Y::B, /// }; /// assert_eq!(X[45].as_ref().unwrap(), &Y::B); @@ -74,13 +145,9 @@ impl Parse for Map { #[proc_macro] pub fn amap(input: TokenStream) -> TokenStream { let map = parse_macro_input!(input as Map); - let map = map.0.iter().map(|index| { - if let Some(index) = index { - let v = &index.value; - quote!(Some(#v)) - } else { - quote!(None) - } + let map = map.0.iter().map(|index| match index { + Some(v) => quote!(Some(#v)), + None => quote!(None), }); quote! { [#(#map), *] diff --git a/tests/maps.rs b/tests/maps.rs new file mode 100644 index 0000000..1cda047 --- /dev/null +++ b/tests/maps.rs @@ -0,0 +1,12 @@ +use amap::amap; + +fn main() { + assert_eq!( + amap! { + 2 => 7, + 3 | 6 => 3, + 4..6 => 2, + }, + [None, None, Some(7), Some(3), Some(2), Some(2), Some(3)] + ); +} |