Skip to main content

ambit_macros/
lib.rs

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, // inclusive
192    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}