quick arrays
big changes
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 37 | ||||
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/lib.rs | 245 |
4 files changed, 168 insertions, 123 deletions
@@ -1 +1,2 @@ /target +rustc-ice-*
\ No newline at end of file @@ -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" @@ -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 @@ -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() } |