counting_macros/lib.rs
1//! Stateful macro for compile time counting.
2//!
3//! The counters use i32 for the backend.
4//! Only incrementing is supported.
5//! ```
6//! # use counting_macros::*;
7//! counter_create!(count);
8//!
9//! // Get the value of the counter & increment
10//! assert_eq!(counter_incr!(count), 0);
11//!
12//! // Get the value of the counter without incrementing
13//! assert_eq!(counter_peek!(count), 1);
14//!
15//! // Increment without getting value
16//! counter_next!(count);
17//! assert_eq!(counter_peek!(count), 2);
18//!
19//! // Change the value of the counter
20//! counter_set!(count, 12);
21//! assert_eq!(counter_incr!(count), 12);
22//! ```
23//!
24//! # Warning
25//! I'm not certain about the stability or safety of this, so I would not
26//! recomend this for use in serious projects.
27use proc_macro::TokenStream;
28use std::cell::RefCell;
29use std::collections::HashMap;
30
31use syn::parse::{Parse, ParseStream};
32use syn::parse_macro_input;
33use syn::{Ident, LitInt, Token};
34
35use quote::quote;
36
37// I have been unable to find any explanations of the parallization model of rustc,
38// I find it unlikely that multiple threads will be modifying a source file at the
39// same time.
40// Thus we are using thread_local.
41//
42// This library only works if rustc compiles each file in it's own thread &
43// expands macros as they linearly appear in the file.
44// If this is not the case then this entire concept should be scrapped.
45thread_local! {
46 static COUNTERS: RefCell<HashMap<String, i32>> =
47 RefCell::new(Default::default());
48}
49
50/// Get counter value then increment it.
51///
52/// ```
53/// # use counting_macros::*;
54/// # counter_create!(count);
55/// assert_eq!(counter_incr!(count), 0);
56/// assert_eq!(counter_incr!(count), 1);
57/// ```
58#[proc_macro]
59pub fn counter_incr(input: TokenStream) -> TokenStream {
60 let IdentString(counter) = parse_macro_input!(input as IdentString);
61
62 COUNTERS.with(|counters| {
63 let mut list = counters.borrow_mut();
64
65 let num = list[&counter];
66 list.insert(counter, num + 1).unwrap();
67
68 quote! {
69 { #num }
70 }
71 .into()
72 })
73}
74
75/// See current count without incrementing it.
76///
77/// ```
78/// # use counting_macros::*;
79/// # counter_create!(count);
80/// assert_eq!(counter_peek!(count), 0);
81/// assert_eq!(counter_incr!(count), 0);
82/// ```
83#[proc_macro]
84pub fn counter_peek(input: TokenStream) -> TokenStream {
85 let IdentString(counter) = parse_macro_input!(input as IdentString);
86
87 COUNTERS.with(|counters| {
88 let list = counters.borrow();
89
90 let num = list[&counter];
91
92 quote! {
93 { #num }
94 }
95 .into()
96 })
97}
98
99/// Change value of counter.
100///
101/// Counter takes an i32 integer, it can be negative.
102/// ```
103/// # use counting_macros::*;
104/// # counter_create!(count);
105/// counter_set!(count, -4);
106/// assert_eq!(counter_incr!(count), -4);
107/// ```
108#[proc_macro]
109pub fn counter_set(input: TokenStream) -> TokenStream {
110 let IdentStringNum(counter, num) = parse_macro_input!(input as IdentStringNum);
111
112 COUNTERS.with(|counters| {
113 let mut list = counters.borrow_mut();
114 list.insert(counter, num);
115 });
116
117 Default::default()
118}
119
120/// Create new counter.
121///
122/// This counter begins with a value of 0, if you want to change it use
123/// [counter_set!].
124/// ```
125/// # use counting_macros::*;
126/// counter_create!(count);
127/// assert_eq!(counter_incr!(count), 0);
128/// ```
129#[proc_macro]
130pub fn counter_create(input: TokenStream) -> TokenStream {
131 let IdentString(counter) = parse_macro_input!(input as IdentString);
132
133 COUNTERS.with(|counters| {
134 let mut list = counters.borrow_mut();
135 list.insert(counter, 0);
136 });
137
138 Default::default()
139}
140
141/// Increment counter.
142///
143/// ```
144/// # use counting_macros::*;
145/// # counter_create!(count);
146/// assert_eq!(counter_incr!(count), 0);
147/// counter_next!(count);
148/// assert_eq!(counter_incr!(count), 2);
149/// ```
150#[proc_macro]
151pub fn counter_next(input: TokenStream) -> TokenStream {
152 counter_incr(input);
153
154 Default::default()
155}
156
157struct IdentString(String);
158
159impl Parse for IdentString {
160 fn parse(input: ParseStream<'_>) -> syn::parse::Result<Self> {
161 let ident: Ident = input.parse()?;
162 Ok(IdentString(ident.to_string()))
163 }
164}
165
166struct IdentStringNum(String, i32);
167
168impl Parse for IdentStringNum {
169 fn parse(input: ParseStream<'_>) -> syn::parse::Result<Self> {
170 let IdentString(ident) = input.parse()?;
171 input.parse::<Token![,]>()?;
172 let lit: LitInt = input.parse()?;
173 let num = lit.base10_parse()?;
174
175 Ok(IdentStringNum(ident, num))
176 }
177}
178
179#[doc = include_str!("../README.md")]
180#[cfg(doctest)]
181#[proc_macro]
182pub fn ReadmeDoctests(_: TokenStream) -> TokenStream {}