From 5e20a29b4fdc8a2d442d1093681b396dcb4b816b Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 7 Jan 2020 11:18:04 +0000 Subject: Add structopt dependency in version 0.3.7 This patch series replaces argparse with structopt in the argument handling code. As a first step, we need structopt as a dependency. Import subrepo structopt/:structopt at efbdda4753592e27bc430fb01f7b9650b2f3174d Import subrepo bitflags/:bitflags at 30668016aca6bd3b02c766e8347e0b4080d4c296 Import subrepo clap/:clap at 784524f7eb193e35f81082cc69454c8c21b948f7 Import subrepo heck/:heck at 093d56fbf001e1506e56dbfa38631d99b1066df1 Import subrepo proc-macro-error/:proc-macro-error at 6c4cfe79a622c5de8ae68557993542be46eacae2 Import subrepo proc-macro2/:proc-macro2 at d5d48eddca4566e5438e8a2cbed4a74e049544de Import subrepo quote/:quote at 727436c6c137b20f0f34dde5d8fda2679b9747ad Import subrepo rustversion/:rustversion at 0c5663313516263059ce9059ef81fc7a1cf655ca Import subrepo syn-mid/:syn-mid at 5d3d85414a9e6674e1857ec22a87b96e04a6851a Import subrepo syn/:syn at e87c27e87f6f4ef8919d0372bdb056d53ef0d8f3 Import subrepo textwrap/:textwrap at abcd618beae3f74841032aa5b53c1086b0a57ca2 Import subrepo unicode-segmentation/:unicode-segmentation at 637c9874c4fe0c205ff27787faf150a40295c6c3 Import subrepo unicode-width/:unicode-width at 3033826f8bf05e82724140a981d5941e48fce393 Import subrepo unicode-xid/:unicode-xid at 4baae9fffb156ba229665b972a9cd5991787ceb7 --- syn/examples/trace-var/Cargo.toml | 2 + syn/examples/trace-var/README.md | 61 ++++++++++ syn/examples/trace-var/example/Cargo.toml | 9 ++ syn/examples/trace-var/example/src/main.rs | 15 +++ syn/examples/trace-var/trace-var/Cargo.toml | 14 +++ syn/examples/trace-var/trace-var/src/lib.rs | 180 ++++++++++++++++++++++++++++ 6 files changed, 281 insertions(+) create mode 100644 syn/examples/trace-var/Cargo.toml create mode 100644 syn/examples/trace-var/README.md create mode 100644 syn/examples/trace-var/example/Cargo.toml create mode 100644 syn/examples/trace-var/example/src/main.rs create mode 100644 syn/examples/trace-var/trace-var/Cargo.toml create mode 100644 syn/examples/trace-var/trace-var/src/lib.rs (limited to 'syn/examples/trace-var') diff --git a/syn/examples/trace-var/Cargo.toml b/syn/examples/trace-var/Cargo.toml new file mode 100644 index 0000000..b54454d --- /dev/null +++ b/syn/examples/trace-var/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "trace-var"] diff --git a/syn/examples/trace-var/README.md b/syn/examples/trace-var/README.md new file mode 100644 index 0000000..b93fae2 --- /dev/null +++ b/syn/examples/trace-var/README.md @@ -0,0 +1,61 @@ +An example of an attribute procedural macro. The `#[trace_var(...)]` attribute +prints the value of the given variables each time they are reassigned. + +- [`trace-var/src/lib.rs`](trace-var/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +Consider the following factorial implementation. + +```rust +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { + let mut p = 1; + while n > 1 { + p *= n; + n -= 1; + } + p +} +``` + +Invoking this with `factorial(8)` prints all the values of `p` and `n` during +the execution of the function. + +``` +p = 1 +p = 8 +n = 7 +p = 56 +n = 6 +p = 336 +n = 5 +p = 1680 +n = 4 +p = 6720 +n = 3 +p = 20160 +n = 2 +p = 40320 +n = 1 +``` + +The procedural macro uses a syntax tree [`Fold`] to rewrite every `let` +statement and assignment expression in the following way: + +[`Fold`]: https://docs.rs/syn/1.0/syn/fold/trait.Fold.html + +```rust +// Before +let VAR = INIT; + +// After +let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; +``` + +```rust +// Before +VAR = INIT + +// After +{ VAR = INIT; println!("VAR = {:?}", VAR); } +``` diff --git a/syn/examples/trace-var/example/Cargo.toml b/syn/examples/trace-var/example/Cargo.toml new file mode 100644 index 0000000..d2ad650 --- /dev/null +++ b/syn/examples/trace-var/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +trace-var = { path = "../trace-var" } diff --git a/syn/examples/trace-var/example/src/main.rs b/syn/examples/trace-var/example/src/main.rs new file mode 100644 index 0000000..da2c10b --- /dev/null +++ b/syn/examples/trace-var/example/src/main.rs @@ -0,0 +1,15 @@ +use trace_var::trace_var; + +fn main() { + println!("{}", factorial(8)); +} + +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { + let mut p = 1; + while n > 1 { + p *= n; + n -= 1; + } + p +} diff --git a/syn/examples/trace-var/trace-var/Cargo.toml b/syn/examples/trace-var/trace-var/Cargo.toml new file mode 100644 index 0000000..72f56e9 --- /dev/null +++ b/syn/examples/trace-var/trace-var/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "trace-var" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1.0", features = ["nightly"] } +quote = "1.0" +syn = { path = "../../../", features = ["full", "fold"] } diff --git a/syn/examples/trace-var/trace-var/src/lib.rs b/syn/examples/trace-var/trace-var/src/lib.rs new file mode 100644 index 0000000..0ecfb47 --- /dev/null +++ b/syn/examples/trace-var/trace-var/src/lib.rs @@ -0,0 +1,180 @@ +extern crate proc_macro; +use self::proc_macro::TokenStream; + +use quote::{quote, ToTokens}; +use std::collections::HashSet as Set; +use syn::fold::{self, Fold}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, parse_quote, Expr, Ident, ItemFn, Local, Pat, Stmt, Token}; + +/// Parses a list of variable names separated by commas. +/// +/// a, b, c +/// +/// This is how the compiler passes in arguments to our attribute -- it is +/// everything inside the delimiters after the attribute name. +/// +/// #[trace_var(a, b, c)] +/// ^^^^^^^ +struct Args { + vars: Set, +} + +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + let vars = Punctuated::::parse_terminated(input)?; + Ok(Args { + vars: vars.into_iter().collect(), + }) + } +} + +impl Args { + /// Determines whether the given `Expr` is a path referring to one of the + /// variables we intend to print. Expressions are used as the left-hand side + /// of the assignment operator. + fn should_print_expr(&self, e: &Expr) -> bool { + match *e { + Expr::Path(ref e) => { + if e.path.leading_colon.is_some() { + false + } else if e.path.segments.len() != 1 { + false + } else { + let first = e.path.segments.first().unwrap(); + self.vars.contains(&first.ident) && first.arguments.is_empty() + } + } + _ => false, + } + } + + /// Determines whether the given `Pat` is an identifier equal to one of the + /// variables we intend to print. Patterns are used as the left-hand side of + /// a `let` binding. + fn should_print_pat(&self, p: &Pat) -> bool { + match p { + Pat::Ident(ref p) => self.vars.contains(&p.ident), + _ => false, + } + } + + /// Produces an expression that assigns the right-hand side to the left-hand + /// side and then prints the value. + /// + /// // Before + /// VAR = INIT + /// + /// // After + /// { VAR = INIT; println!("VAR = {:?}", VAR); } + fn assign_and_print(&mut self, left: Expr, op: &dyn ToTokens, right: Expr) -> Expr { + let right = fold::fold_expr(self, right); + parse_quote!({ + #left #op #right; + println!(concat!(stringify!(#left), " = {:?}"), #left); + }) + } + + /// Produces a let-binding that assigns the right-hand side to the left-hand + /// side and then prints the value. + /// + /// // Before + /// let VAR = INIT; + /// + /// // After + /// let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; + fn let_and_print(&mut self, local: Local) -> Stmt { + let Local { pat, init, .. } = local; + let init = self.fold_expr(*init.unwrap().1); + let ident = match pat { + Pat::Ident(ref p) => &p.ident, + _ => unreachable!(), + }; + parse_quote! { + let #pat = { + #[allow(unused_mut)] + let #pat = #init; + println!(concat!(stringify!(#ident), " = {:?}"), #ident); + #ident + }; + } + } +} + +/// The `Fold` trait is a way to traverse an owned syntax tree and replace some +/// of its nodes. +/// +/// Syn provides two other syntax tree traversal traits: `Visit` which walks a +/// shared borrow of a syntax tree, and `VisitMut` which walks an exclusive +/// borrow of a syntax tree and can mutate it in place. +/// +/// All three traits have a method corresponding to each type of node in Syn's +/// syntax tree. All of these methods have default no-op implementations that +/// simply recurse on any child nodes. We can override only those methods for +/// which we want non-default behavior. In this case the traversal needs to +/// transform `Expr` and `Stmt` nodes. +impl Fold for Args { + fn fold_expr(&mut self, e: Expr) -> Expr { + match e { + Expr::Assign(e) => { + if self.should_print_expr(&e.left) { + self.assign_and_print(*e.left, &e.eq_token, *e.right) + } else { + Expr::Assign(fold::fold_expr_assign(self, e)) + } + } + Expr::AssignOp(e) => { + if self.should_print_expr(&e.left) { + self.assign_and_print(*e.left, &e.op, *e.right) + } else { + Expr::AssignOp(fold::fold_expr_assign_op(self, e)) + } + } + _ => fold::fold_expr(self, e), + } + } + + fn fold_stmt(&mut self, s: Stmt) -> Stmt { + match s { + Stmt::Local(s) => { + if s.init.is_some() && self.should_print_pat(&s.pat) { + self.let_and_print(s) + } else { + Stmt::Local(fold::fold_local(self, s)) + } + } + _ => fold::fold_stmt(self, s), + } + } +} + +/// Attribute to print the value of the given variables each time they are +/// reassigned. +/// +/// # Example +/// +/// ``` +/// #[trace_var(p, n)] +/// fn factorial(mut n: u64) -> u64 { +/// let mut p = 1; +/// while n > 1 { +/// p *= n; +/// n -= 1; +/// } +/// p +/// } +/// ``` +#[proc_macro_attribute] +pub fn trace_var(args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + + // Parse the list of variables the user wanted to print. + let mut args = parse_macro_input!(args as Args); + + // Use a syntax tree traversal to transform the function body. + let output = args.fold_item_fn(input); + + // Hand the resulting function body back to the compiler. + TokenStream::from(quote!(#output)) +} -- cgit v1.2.1