Skip to content

Commit 1bc7b5a

Browse files
committed
add macros (memoize)
1 parent 879c47b commit 1bc7b5a

File tree

5 files changed

+215
-19
lines changed

5 files changed

+215
-19
lines changed

Cargo.lock

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ test_lib = []
2222
[dependencies]
2323

2424
# Template dependencies
25-
chrono = { version = "0.4.38", optional = true }
25+
chrono = { version = "0.4.39", optional = true }
2626
dhat = { version = "0.3.3", optional = true }
2727
pico-args = "0.5.0"
2828
tinyjson = "2.5.1"
2929

30+
# Macros
31+
advent_of_code_macros = { path = "./macros" }
32+
3033
# Solution dependencies
31-
fastrand = "2.2.0"
3234
regex = "1.11.1"

macros/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "advent_of_code_macros"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
name = "advent_of_code_macros"
8+
path = "src/lib.rs"
9+
proc-macro = true
10+
11+
[dependencies]
12+
syn = { version="2.0.95", features=["full"] }
13+
quote = "1.0.38"

macros/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use proc_macro::TokenStream;
2+
3+
mod memoize;
4+
use crate::memoize::memoize_impl;
5+
6+
#[proc_macro_attribute]
7+
pub fn memoize(args: proc_macro::TokenStream, item: proc_macro::TokenStream) -> TokenStream {
8+
memoize_impl(args, item)
9+
}

macros/src/memoize.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use proc_macro::TokenStream;
2+
use quote::{quote, ToTokens};
3+
use syn::{parse::*, punctuated::*, spanned::*, *};
4+
5+
mod kw {
6+
syn::custom_keyword!(ignore);
7+
syn::custom_keyword!(key_function);
8+
}
9+
10+
#[derive(Default)]
11+
struct CacheOptions {
12+
ignore: Vec<Ident>,
13+
key_function: Option<(Ident, Ident)>,
14+
}
15+
16+
enum CacheOption {
17+
Ignore(Vec<Ident>),
18+
KeyFunction(Ident, Ident),
19+
}
20+
21+
impl Parse for CacheOption {
22+
fn parse(input: ParseStream) -> syn::Result<Self> {
23+
let la = input.lookahead1();
24+
25+
if la.peek(kw::ignore) {
26+
input.parse::<kw::ignore>().unwrap();
27+
input.parse::<Token![=]>().unwrap();
28+
let bracketed_content;
29+
bracketed!(bracketed_content in input);
30+
31+
let result = Punctuated::<LitStr, Token![,]>::parse_terminated(&bracketed_content)
32+
.unwrap()
33+
.into_iter()
34+
.map(|lit_str| lit_str.parse::<Ident>().unwrap())
35+
.collect::<Vec<_>>();
36+
37+
return Ok(CacheOption::Ignore(result));
38+
}
39+
40+
if la.peek(kw::key_function) {
41+
input.parse::<kw::key_function>().unwrap();
42+
input.parse::<Token![=]>().unwrap();
43+
let input = input.parse::<LitStr>().unwrap();
44+
let input_value = input.value();
45+
46+
let (key_function_name_str, key_function_return_str) =
47+
input_value.split_once(" -> ").unwrap();
48+
let key_function_name = Ident::new(key_function_name_str, input.span());
49+
let key_function_return = Ident::new(key_function_return_str, input.span());
50+
51+
return Ok(CacheOption::KeyFunction(
52+
key_function_name,
53+
key_function_return,
54+
));
55+
}
56+
57+
Err(la.error())
58+
}
59+
}
60+
61+
impl Parse for CacheOptions {
62+
fn parse(input: ParseStream) -> syn::Result<Self> {
63+
let mut opts = Self::default();
64+
65+
let attrs = Punctuated::<CacheOption, syn::Token![,]>::parse_terminated(input)?;
66+
67+
for opt in attrs {
68+
match opt {
69+
CacheOption::Ignore(ident) => opts.ignore.extend(ident),
70+
CacheOption::KeyFunction(name, return_type) => {
71+
opts.key_function = Some((name, return_type));
72+
}
73+
}
74+
}
75+
76+
Ok(opts)
77+
}
78+
}
79+
80+
pub fn memoize_impl(args: TokenStream, item: TokenStream) -> TokenStream {
81+
let options: CacheOptions = syn::parse(args).unwrap();
82+
83+
let ItemFn {
84+
sig,
85+
vis,
86+
block,
87+
attrs,
88+
} = parse_macro_input!(item as ItemFn);
89+
90+
let fn_input_names = sig
91+
.inputs
92+
.iter()
93+
.filter_map(|arg| match arg {
94+
FnArg::Typed(PatType { pat, .. }) => Some(*pat.clone()),
95+
FnArg::Receiver(_) => None,
96+
})
97+
.collect::<Vec<_>>();
98+
99+
let cache_input_names = sig
100+
.inputs
101+
.iter()
102+
.filter_map(|arg| match arg {
103+
FnArg::Typed(PatType { pat, .. }) => Some(*pat.clone()),
104+
FnArg::Receiver(_) => None,
105+
})
106+
.filter(|pat| match pat {
107+
Pat::Ident(PatIdent { ident, .. }) => {
108+
!options.ignore.iter().any(|ignore| ignore == ident)
109+
}
110+
_ => true,
111+
})
112+
.collect::<Vec<_>>();
113+
114+
let fn_return_type = match &sig.output {
115+
ReturnType::Default => quote! { () },
116+
ReturnType::Type(_, ty) => ty.to_token_stream(),
117+
};
118+
119+
let cache_key_name = match &options.key_function {
120+
Some((name, _)) => quote! { #name(#(#fn_input_names),*) },
121+
None => quote! { (#(#cache_input_names.clone()),*) },
122+
};
123+
124+
let cache_key_return_type = match &options.key_function {
125+
Some((_, return_type)) => quote! { #return_type },
126+
None => fn_return_type.clone(),
127+
};
128+
129+
let internal_fn_name = format!("__{}_internal", sig.ident);
130+
let cache_static_var_name = format!("__CACHE_{}", sig.ident.to_string().to_uppercase());
131+
132+
let internal_fn_ident = Ident::new(&internal_fn_name, sig.span());
133+
let cache_static_var_ident = Ident::new(&cache_static_var_name, sig.span());
134+
135+
let internal_sig = Signature {
136+
ident: internal_fn_ident.clone(),
137+
..sig.clone()
138+
};
139+
140+
quote!(
141+
thread_local! {
142+
static #cache_static_var_ident: std::cell::RefCell<advent_of_code::maneatingape::hash::FastMap<#cache_key_return_type, #fn_return_type>> = std::cell::RefCell::new(advent_of_code::maneatingape::hash::FastMapBuilder::new());
143+
}
144+
145+
#(#attrs)*
146+
#vis #internal_sig #block
147+
148+
#(#attrs)*
149+
#vis #sig {
150+
let cache_key = #cache_key_name;
151+
152+
let cached_result_option = #cache_static_var_ident.with(|cache| {
153+
cache.borrow().get(&cache_key).cloned()
154+
});
155+
156+
if let Some(cached_result) = cached_result_option {
157+
return cached_result;
158+
}
159+
160+
let result = #internal_fn_ident (#(#fn_input_names),*);
161+
162+
#cache_static_var_ident.with(|cache| {
163+
cache.borrow_mut().insert(cache_key, result);
164+
});
165+
166+
result
167+
}
168+
)
169+
.into()
170+
}

0 commit comments

Comments
 (0)