1use proc_macro2::{Span, TokenStream};
2use quote::{ToTokens, quote, quote_spanned};
3use syn::{
4 Attribute, Error, Fields, Ident, ItemStruct, LitInt, LitStr, Path, Result, Token, Visibility,
5 parse::{Parse, ParseStream},
6 parse_macro_input,
7 spanned::Spanned,
8};
9
10#[proc_macro_attribute]
11pub fn ambit(
12 attr: proc_macro::TokenStream,
13 item: proc_macro::TokenStream,
14) -> proc_macro::TokenStream {
15 ambit_impl(
16 parse_macro_input!(attr as Args),
17 parse_macro_input!(item as Decl),
18 )
19 .into()
20}
21
22fn ambit_impl(args: Args, decl: Decl) -> TokenStream {
23 let (decl_attrs, decl_vis, decl_ident) = (decl.attrs, decl.vis, decl.ident);
24 let name_lower = decl_ident.to_string().to_lowercase();
25
26 let Args { range } = args;
27 let (start, end) = (range.start, range.end);
28 let (range_kind, range_repr) = (&range.kind, &range.repr);
29 let is_nonzero = range_repr.raw.contains("NonZero");
30
31 let error_ident = Ident::new(&format!("{decl_ident}Error"), decl_ident.span());
32 let error_fstring = format!("invalid {name_lower}: {{}}");
33
34 let iter_ident = Ident::new(&format!("{decl_ident}Iter"), decl_ident.span());
35
36 let macro_ident = Ident::new(&name_lower, decl_ident.span());
37 let macro_arms = (start.value..=end.value)
38 .map(proc_macro2::Literal::u64_unsuffixed)
39 .map(|n| quote! { (#n) => { unsafe { #decl_ident::new_unchecked(#n) } }; });
40
41 let new_unchecked_method_body = if is_nonzero {
42 quote! { Self(unsafe { <#range_repr>::new_unchecked(n) }) }
43 } else {
44 quote! { Self(n) }
45 };
46
47 let value_method_body = if is_nonzero {
48 quote! { self.0.get() }
49 } else {
50 quote! { self.0 }
51 };
52
53 quote! {
54 #(#decl_attrs)*
55 #[repr(transparent)]
56 #decl_vis struct #decl_ident(#range_repr);
57
58 impl #decl_ident {
59 pub const MIN_VALUE: #range_kind = #start;
60 pub const MAX_VALUE: #range_kind = #end;
61
62 pub const MIN: Self = unsafe { Self::new_unchecked(#start) };
63 pub const MAX: Self = unsafe { Self::new_unchecked(#end) };
64
65 pub fn iter() -> #iter_ident {
66 #iter_ident(Some(Self::MIN))
67 }
68
69 pub const fn new(n: #range_kind) -> Result<Self, #error_ident> {
70 if n < #start || n > #end {
71 return Err(#error_ident(n));
72 }
73 Ok(unsafe { Self::new_unchecked(n) })
74 }
75
76 pub const unsafe fn new_unchecked(n: #range_kind) -> Self {
77 #new_unchecked_method_body
78 }
79
80 pub const fn value(&self) -> #range_kind {
81 #value_method_body
82 }
83
84 pub const fn is_min(&self) -> bool {
85 self.value() == Self::MIN.value()
86 }
87
88 pub const fn is_max(&self) -> bool {
89 self.value() == Self::MAX.value()
90 }
91
92 pub fn pred(&self) -> Option<Self> {
93 (!self.is_min()).then(|| unsafe { Self::new_unchecked(self.value() - 1 ) })
94 }
95
96 pub fn succ(&self) -> Option<Self> {
97 (!self.is_max()).then(|| unsafe { Self::new_unchecked(self.value() + 1 ) })
98 }
99 }
100
101 impl PartialEq<#range_kind> for #decl_ident {
102 fn eq(&self, &other: &#range_kind) -> bool {
103 self.value() == other
104 }
105 }
106
107 impl TryFrom<#range_kind> for #decl_ident {
108 type Error = #error_ident;
109
110 fn try_from(n: #range_kind) -> Result<Self, #error_ident> {
111 Self::new(n)
112 }
113 }
114
115 #decl_vis struct #iter_ident(Option<#decl_ident>);
116
117 impl Iterator for #iter_ident {
118 type Item = #decl_ident;
119
120 fn next(&mut self) -> Option<#decl_ident> {
121 if let Some(next) = self.0.as_ref()?.succ() {
122 self.0.replace(next)
123 } else {
124 self.0.take()
125 }
126 }
127 }
128
129 #[non_exhaustive]
130 #[derive(Debug)]
131 #decl_vis struct #error_ident(#decl_vis #range_kind);
132
133 impl std::error::Error for #error_ident {}
134 impl std::fmt::Display for #error_ident {
135 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
136 write!(f, #error_fstring, self.0)
137 }
138 }
139
140 #[macro_export]
141 macro_rules! #macro_ident {
142 #(#macro_arms)*
143 }
144 }
145}
146
147struct Decl {
148 attrs: Vec<Attribute>,
149 vis: Visibility,
150 ident: Ident,
151}
152
153impl Parse for Decl {
154 fn parse(input: ParseStream) -> Result<Self> {
155 let item: ItemStruct = input.parse()?;
156 let (attrs, vis, ident) = (item.attrs, item.vis, item.ident);
157
158 let None = item.generics.lt_token else {
159 return Err(Error::new(item.generics.span(), "Expected concrete struct"));
160 };
161
162 let Fields::Unit = item.fields else {
163 return Err(Error::new(item.fields.span(), "Expected unit struct"));
164 };
165
166 Ok(Self { attrs, vis, ident })
167 }
168}
169
170struct Args {
171 range: Range,
172}
173
174impl Parse for Args {
175 fn parse(input: ParseStream) -> Result<Self> {
176 mod kw {
177 syn::custom_keyword!(range);
178 }
179
180 let _: kw::range = input.parse()?;
181 let _: Token![=] = input.parse()?;
182 let s: LitStr = input.parse()?;
183 let range = syn::parse_str(&s.value())?;
184
185 Ok(Self { range })
186 }
187}
188
189struct Range {
190 start: Bound,
191 end: Bound, kind: ParsedStr<Ident>,
193 repr: ParsedStr<Path>,
194}
195
196impl Parse for Range {
197 fn parse(input: ParseStream) -> Result<Self> {
198 let start: Bound = LitInt::parse(input)?.try_into()?;
199 let end = if input.peek(Token![..=]) {
200 let _ = <Token![..=]>::parse(input)?;
201 LitInt::parse(input)?.try_into()?
202 } else {
203 let _ = <Token![..]>::parse(input)?;
204 let mut bound: Bound = LitInt::parse(input)?.try_into()?;
205 bound.value -= 1;
206 bound
207 };
208
209 if end.value <= start.value {
210 return Err(Error::new(input.span(), "expected ascending range"));
211 }
212
213 macro_rules! fits {
214 ($ty:ident) => {
215 $ty::try_from(end.value).is_ok()
216 };
217 }
218
219 let kind = ParsedStr::new(match () {
220 () if fits!(u8) => "u8",
221 () if fits!(u16) => "u16",
222 () if fits!(u32) => "u32",
223 _ => "u64",
224 })?;
225
226 let repr = ParsedStr::new(match (kind.raw, start.value == 0) {
227 (s, true) => s,
228 ("u8", false) => "::std::num::NonZero<u8>",
229 ("u16", false) => "::std::num::NonZero<u16>",
230 ("u32", false) => "::std::num::NonZero<u32>",
231 ("u64", false) => "::std::num::NonZero<u64>",
232 (_, false) => unreachable!(),
233 })?;
234
235 Ok(Self {
236 start,
237 end,
238 kind,
239 repr,
240 })
241 }
242}
243
244struct Bound {
245 value: u64,
246 span: Span,
247}
248
249impl TryFrom<LitInt> for Bound {
250 type Error = Error;
251
252 fn try_from(int: LitInt) -> Result<Self> {
253 Ok(Self {
254 value: int.base10_parse()?,
255 span: int.span(),
256 })
257 }
258}
259
260impl ToTokens for Bound {
261 fn to_tokens(&self, tokens: &mut TokenStream) {
262 let lit = proc_macro2::Literal::u64_unsuffixed(self.value);
263 tokens.extend(quote_spanned!(self.span => #lit));
264 }
265}
266
267struct ParsedStr<T> {
268 raw: &'static str,
269 parsed: T,
270}
271
272impl<T: Parse> ParsedStr<T> {
273 fn new(s: &'static str) -> Result<Self> {
274 let parsed = syn::parse_str(s)?;
275 Ok(Self { raw: s, parsed })
276 }
277}
278
279impl<T: ToTokens> ToTokens for ParsedStr<T> {
280 fn to_tokens(&self, tokens: &mut TokenStream) {
281 self.parsed.to_tokens(tokens);
282 }
283}