quick arrays
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock37
-rw-r--r--Cargo.toml8
-rw-r--r--src/lib.rs245
4 files changed, 168 insertions, 123 deletions
diff --git a/.gitignore b/.gitignore
index ea8c4bf..ce1f66f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+rustc-ice-* \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 3511e42..c9fbaf3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,38 +1,55 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "amap"
-version = "0.1.1"
+version = "0.1.3"
dependencies = [
+ "itertools",
+ "proc-macro2",
"quote",
"syn",
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
-version = "2.0.29"
+version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
@@ -41,6 +58,6 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.11"
+version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
diff --git a/Cargo.toml b/Cargo.toml
index 34032d2..052bd8a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,15 +1,17 @@
[package]
name = "amap"
-version = "0.1.2"
+version = "0.1.3"
authors = ["bend-n <[email protected]>"]
description = "define `[Option<T>; N]` easily"
-edition = "2021"
+edition = "2024"
repository = "https://github.com/bend-n/amap.git"
license = "MIT"
[dependencies]
+itertools = "0.14.0"
+proc-macro2 = "1.0.101"
quote = "1.0.32"
syn = { version = "2.0.15", features = ["full"] }
[lib]
-proc_macro = true
+proc-macro = true
diff --git a/src/lib.rs b/src/lib.rs
index 7b1d278..08fb046 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,125 +1,137 @@
-use std::collections::{hash_map::Entry, HashMap};
-
-use proc_macro::TokenStream;
-use quote::quote;
+use itertools::Itertools;
+use proc_macro2::TokenStream;
+use quote::{ToTokens, quote};
use syn::{
+ Error, Expr, Lit, Pat, PatConst, Stmt, Token,
parse::{self, Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
- Error, Expr, Lit, Pat, Token,
};
#[derive(Clone)]
struct Index {
- indices: Vec<usize>,
+ indices: Vec<Expr>,
value: Expr,
}
-
-impl Parse for Index {
- fn parse(input: ParseStream<'_>) -> parse::Result<Index> {
- 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",
+fn indices(index: &Pat) -> syn::Result<Vec<Expr>> {
+ match index {
+ Pat::Lit(v) => match &v.lit {
+ Lit::Int(_) => Ok(vec![v.clone().into()]),
+ _ => Err(Error::new_spanned(v, "must be numeric literal"))?,
+ },
+ Pat::Or(v) => v.cases.iter().map(indices).flatten_ok().collect(),
+ Pat::Range(r) => {
+ let s = r.span();
+ let r = r.clone();
+ 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",
))?,
- };
- 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",
+ },
+ 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",
))?,
- };
- 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()?,
- }),
- }
+ },
+ e => Err(Error::new_spanned(
+ e,
+ "range end bound must include only literal ints",
+ ))?,
+ };
+
+ match r.limits {
+ syn::RangeLimits::Closed(..) => Ok((begin..=end)
+ .map(|x: usize| syn::parse::<Expr>(x.to_token_stream().into()).unwrap())
+ .collect()),
+ syn::RangeLimits::HalfOpen(..) => Ok((begin..end)
+ .map(|x: usize| syn::parse::<Expr>(x.to_token_stream().into()).unwrap())
+ .collect()),
}
- _ => Err(input.error("pattern must be literal(5) | or(5 | 4) | range(4..5)"))?,
}
+ Pat::Const(PatConst { block, .. }) => {
+ Ok(vec![if let [Stmt::Expr(x, None)] = &block.stmts[..] {
+ x.clone()
+ } else {
+ Expr::Block(syn::ExprBlock {
+ attrs: vec![],
+ label: None,
+ block: block.clone(),
+ })
+ }])
+ }
+ _ => Err(Error::new(
+ index.span(),
+ "pattern must be literal(5) | or(5 | 4) | range(4..5) | const { .. }",
+ ))?,
+ }
+}
+
+impl Parse for Index {
+ fn parse(input: ParseStream<'_>) -> parse::Result<Index> {
+ let index = Pat::parse_multi(input)?;
+ let indices = indices(&index)?;
+ input.parse::<Token![=>]>()?;
+ Ok(Index {
+ indices,
+ value: input.parse()?,
+ })
}
}
-struct Map(Vec<Option<Expr>>);
+struct Map(Punctuated<Index, Token![,]>);
impl Parse for Map {
fn parse(input: ParseStream) -> syn::Result<Self> {
let parsed = Punctuated::<Index, Token![,]>::parse_terminated(input)?;
if parsed.is_empty() {
return Err(input.error("no 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;
- }
- 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))
+ Ok(Map(parsed))
+ }
+}
+
+impl Map {
+ fn into(self, d: TokenStream, f: impl Fn(&Expr) -> TokenStream + Copy) -> TokenStream {
+ let map = self
+ .0
+ .into_iter()
+ .zip(1..)
+ .flat_map(|(Index { indices, value }, i)| {
+ indices.into_iter().map(move |x| {
+ let s = format!(
+ "duplicate / overlapping key @ pattern `{}` (#{i})",
+ x.to_token_stream()
+ .to_string()
+ .replace('{', "{{")
+ .replace('}', "}}")
+ );
+ let value = f(&value);
+ quote! {{
+ let (index, value) = { let (__ඞඞ, __set) = ((), ()); (#x, #value) };
+ assert!(!__set[index], #s);
+ __set[index] = true;
+ __ඞඞ[index] = value;
+ }}
+ })
+ });
+ quote! {{
+ let mut __ඞඞ = [#d; _];
+ const fn steal<const N:usize, T>(_: &[T; N]) -> [bool; N] { [false; N] }
+ let mut __set = steal(&__ඞඞ);
+ #(#map)*
+ __ඞඞ
+ }}
}
}
@@ -138,19 +150,32 @@ impl Parse for Map {
/// 2..=25 => Y::A,
/// 26 | 32 => Y::C,
/// 27..32 => Y::D,
-/// 45 => Y::B,
+/// 44 => Y::B,
/// };
-/// assert_eq!(X[45].as_ref().unwrap(), &Y::B);
+/// assert_eq!(X[44].as_ref().unwrap(), &Y::B);
/// ```
#[proc_macro]
-pub fn amap(input: TokenStream) -> TokenStream {
- let map = parse_macro_input!(input as Map);
- let map = map.0.iter().map(|index| match index {
- Some(v) => quote!(::core::option::Option::Some(#v)),
- None => quote!(::core::option::Option::None),
- });
- quote! {
- [#(#map), *]
- }
- .into()
+pub fn amap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ parse_macro_input!(input as Map)
+ .into(quote! { const { None } }, |x| quote! { Some(#x)})
+ .into()
+}
+
+#[proc_macro]
+/// This method uses default instead of Option<T>. Nightly required for use in const.
+/// ```
+/// # use amap::amap_d;
+/// let x: [u8; 42] = amap_d! {
+/// 4 => 2,
+/// 16..25 => 4,
+/// };
+/// assert_eq!(x[17], 4);
+/// ```
+pub fn amap_d(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ parse_macro_input!(input as Map)
+ .into(
+ quote! { ::core::default::Default::default() },
+ |x| quote! { #x },
+ )
+ .into()
}