quick arrays
-rw-r--r--Cargo.lock10
-rw-r--r--Cargo.toml6
-rw-r--r--README.md39
-rw-r--r--src/lib.rs143
-rw-r--r--tests/maps.rs12
5 files changed, 164 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8afdb15..3511e42 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index e1ce0d6..2b7c679 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
+}
+```
diff --git a/src/lib.rs b/src/lib.rs
index 78ba724..da0672d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)]
+ );
+}