Skip to content

Commit eebe299

Browse files
Merge pull request #15 from chuck-flowers/v0.3.0
v0.3.0
2 parents fdd99a4 + 6440577 commit eebe299

File tree

13 files changed

+831
-452
lines changed

13 files changed

+831
-452
lines changed

attribution-examples/ez-trace/ez-trace-macros/src/lib.rs

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@ extern crate proc_macro;
22

33
use attribution::attr_args;
44
use proc_macro::TokenStream;
5-
use syn::ItemFn;
6-
use syn::parse_macro_input;
5+
use proc_macro2::Span;
76
use quote::quote;
7+
use syn::parse_macro_input;
88
use syn::parse_quote;
9+
use syn::punctuated::Punctuated;
10+
use syn::FnArg;
11+
use syn::ItemFn;
12+
use syn::LitStr;
13+
use syn::Pat;
914

1015
#[attr_args]
11-
struct EzTraceArgs {}
16+
enum EzTraceArgs {
17+
StartAndEnd { start: String, end: String },
18+
Start { start: String },
19+
End { end: String },
20+
Silent,
21+
}
1222

1323
#[proc_macro_attribute]
1424
pub fn ez_trace(attr: TokenStream, tagged: TokenStream) -> TokenStream {
@@ -18,20 +28,97 @@ pub fn ez_trace(attr: TokenStream, tagged: TokenStream) -> TokenStream {
1828

1929
(quote! {
2030
#function
21-
}).into()
31+
})
32+
.into()
2233
}
2334

24-
fn wrap_function(function: &mut ItemFn, _: &EzTraceArgs) {
25-
let function_name = function.sig.ident.to_string();
26-
let function_name_lit = syn::LitStr::new(&function_name, proc_macro2::Span::call_site());
27-
let body = &function.block;
28-
let new_body: syn::Block = parse_quote! {
29-
{
30-
let result = #body;
31-
println!("{}: {}", #function_name_lit, result);
32-
result
33-
}
35+
fn wrap_function(function: &mut ItemFn, attr: &EzTraceArgs) {
36+
fn make_template(msg: Option<&String>) -> Option<syn::LitStr> {
37+
msg.as_ref()
38+
.map(|template| syn::LitStr::new(template, Span::call_site()))
39+
}
40+
41+
// Get the name of the function
42+
let name = LitStr::new(&function.sig.ident.to_string(), Span::call_site());
43+
44+
// Build the comma separated list of arg names
45+
let args: Punctuated<_, syn::Token![,]> = function
46+
.sig
47+
.inputs
48+
.iter()
49+
.map(|input| match input {
50+
FnArg::Receiver(_) => quote! { &self },
51+
FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {
52+
Pat::Ident(pat_ident) => {
53+
let ident = &pat_ident.ident;
54+
quote! { &#ident }
55+
}
56+
_ => panic!("Only simple parameter types are supported"),
57+
},
58+
})
59+
.collect();
60+
61+
// Create the template that is used to format the args
62+
let mut arg_templates = if args.is_empty() {
63+
String::from("")
64+
} else {
65+
String::from("{}")
3466
};
3567

36-
function.block = Box::new(new_body);
37-
}
68+
for _ in args.iter().skip(1) {
69+
arg_templates.push_str(", {}");
70+
}
71+
72+
// Build the string builder which will be used at runtime
73+
let arg_templates = LitStr::new(&arg_templates, Span::call_site());
74+
let args = quote! {
75+
format!(#arg_templates, #args);
76+
};
77+
78+
let start_param_val = match attr {
79+
EzTraceArgs::StartAndEnd { start, .. } => Some(start),
80+
EzTraceArgs::Start { start } => Some(start),
81+
_ => None,
82+
};
83+
84+
let end_param_val = match attr {
85+
EzTraceArgs::StartAndEnd { end, .. } => Some(end),
86+
EzTraceArgs::End { end } => Some(end),
87+
_ => None,
88+
};
89+
90+
// Create the start message if one has been defined
91+
let start_msg = make_template(start_param_val)
92+
.map(|template| {
93+
quote! {
94+
let args = #args;
95+
println!(#template, name = #name, args = args);
96+
}
97+
})
98+
.unwrap_or_default();
99+
100+
// Create the end message if one has been defined
101+
let end_msg = make_template(end_param_val)
102+
.map(|template| {
103+
quote! {
104+
let args = #args;
105+
println!(#template, name = #name, args = args, result = result);
106+
}
107+
})
108+
.unwrap_or_default();
109+
110+
// Wrap the old body with the messages if either are defined
111+
if !start_msg.is_empty() || !end_msg.is_empty() {
112+
let body = &function.block;
113+
let new_body: syn::Block = parse_quote! {
114+
{
115+
#start_msg
116+
let result = #body;
117+
#end_msg
118+
result
119+
}
120+
};
121+
122+
function.block = Box::new(new_body);
123+
}
124+
}

attribution-examples/ez-trace/fibonacci/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ fn main() {
66
println!("Finished");
77
}
88

9-
#[ez_trace]
9+
#[ez_trace(start = "{name}({args})", end = "{name}({args}) -> {result}")]
1010
fn fibonacci(n: u32) -> u32 {
1111
if n == 0 || n == 1 {
1212
n
1313
} else {
1414
fibonacci(n - 1) + fibonacci(n - 2)
1515
}
16-
}
16+
}

attribution-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "attribution-macros"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
authors = ["Timothy Flowers <[email protected]>"]
55
description = "The macros used by the attribution crate"
66
categories = ["development-tools::procedural-macro-helpers"]
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use crate::identifiers::build_unnamed_idents;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use quote::quote;
4+
use syn::parse_quote;
5+
use syn::punctuated::Punctuated;
6+
use syn::Expr;
7+
use syn::Fields;
8+
use syn::FieldsNamed;
9+
use syn::FieldsUnnamed;
10+
use syn::Ident;
11+
use syn::ItemStruct;
12+
use syn::Token;
13+
use syn::Variant;
14+
15+
pub fn build_struct_constructor(input_struct: &ItemStruct) -> TokenStream2 {
16+
let struct_name = &input_struct.ident;
17+
18+
let ctor_body = match &input_struct.fields {
19+
Fields::Named(named) => named_constructor_body(named),
20+
Fields::Unnamed(unnamed) => unnamed_constructor_body(unnamed),
21+
Fields::Unit => quote! { {} },
22+
};
23+
24+
quote! {
25+
Ok(#struct_name #ctor_body)
26+
}
27+
}
28+
29+
pub fn build_variant_constructor(enum_name: &Ident, variant: &Variant) -> Expr {
30+
let variant_name = &variant.ident;
31+
32+
let constructor_body = match &variant.fields {
33+
Fields::Named(named) => named_constructor_body(named),
34+
Fields::Unnamed(unnamed) => unnamed_constructor_body(unnamed),
35+
Fields::Unit => quote! { {} },
36+
};
37+
38+
parse_quote! {
39+
Ok(#enum_name::#variant_name #constructor_body)
40+
}
41+
}
42+
43+
/// Builds the constructor body for a struct with named fields.
44+
fn named_constructor_body(FieldsNamed { named, .. }: &FieldsNamed) -> TokenStream2 {
45+
let idents: Punctuated<&Ident, Token![,]> =
46+
named.iter().map(|el| el.ident.as_ref().unwrap()).collect();
47+
quote! { { #idents } }
48+
}
49+
50+
/// Builds the constructor body for a struct with unnamed fields.
51+
fn unnamed_constructor_body(FieldsUnnamed { unnamed, .. }: &FieldsUnnamed) -> TokenStream2 {
52+
let idents: Punctuated<Ident, Token![,]> = build_unnamed_idents(unnamed.len()).collect();
53+
quote! { (#idents) }
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
59+
use super::*;
60+
use quote::ToTokens;
61+
62+
#[test]
63+
fn build_struct_constructor_test() {
64+
let input_struct = parse_quote! {
65+
struct Foo {
66+
a: u32,
67+
b: String,
68+
c: bool
69+
}
70+
};
71+
72+
let actual = build_struct_constructor(&input_struct);
73+
let expected = quote! {
74+
Ok(Foo { a, b, c })
75+
};
76+
77+
assert_eq!(expected.to_string(), actual.to_string());
78+
}
79+
80+
#[test]
81+
fn build_variant_constructor_test() {
82+
let enum_ident: Ident = parse_quote! { EnumName };
83+
let variant = parse_quote! {
84+
Foo (u32, String, bool)
85+
};
86+
87+
let actual = build_variant_constructor(&enum_ident, &variant);
88+
let expected: Expr = parse_quote! {
89+
EnumName::Foo(_0, _1, _2)
90+
};
91+
92+
assert_eq!(
93+
expected.to_token_stream().to_string(),
94+
actual.to_token_stream().to_string()
95+
);
96+
}
97+
98+
#[test]
99+
fn named_constructor_body_test() {
100+
let fields_named = parse_quote! {
101+
{ a: u32, b: String, c: bool }
102+
};
103+
104+
let actual = named_constructor_body(&fields_named);
105+
let expected = quote! {
106+
{ a, b, c }
107+
};
108+
109+
assert_eq!(expected.to_string(), actual.to_string());
110+
}
111+
112+
#[test]
113+
fn unnamed_constructor_body_test() {
114+
let fields_unnamed = parse_quote! {
115+
(u32, String, bool)
116+
};
117+
118+
let actual = unnamed_constructor_body(&fields_unnamed);
119+
let expected = quote! {
120+
(_0, _1, _2)
121+
};
122+
123+
assert_eq!(expected.to_string(), actual.to_string());
124+
}
125+
}

0 commit comments

Comments
 (0)