macro_find_and_replace/
lib.rs

1//! # macro_find_and_replace
2//!
3//! This crate provides macros to find-and-replace tokens. 
4//! 
5//! For more information about tokens in general and what counts as a single token, see the [TokenStream](https://doc.rust-lang.org/proc_macro/struct.TokenStream.html) type in the `proc_macro` crate, which is the input and output type of procedural macros.
6
7extern crate proc_macro;
8use proc_macro::{TokenStream, TokenTree, Group};
9
10/// Replaces all occurences of a given single token with another single token.
11/// 
12/// Takes arguments in the order `replace_token!{needle, replacement, code to search}`. You may use any kind of brackets.
13/// 
14/// # Panics
15/// At compile time, if the arguments cannot be correctly parsed.
16/// 
17/// # Examples
18///
19/// Replaces all plus signs with minus signs:
20/// ```
21/// # extern crate macro_find_and_replace;
22/// # use macro_find_and_replace::replace_token;
23/// replace_token!{+, -,
24///     let x = (1 + 2) + 3;
25/// }
26/// assert_eq!(x, -4); 
27/// ```
28/// 
29/// Replaces the identifier `MY_ARRAY` with an array literal
30/// ```
31/// # extern crate macro_find_and_replace;
32/// # use macro_find_and_replace::replace_token;
33/// replace_token!{MY_ARRAY, [8, 0],
34///     let x = MY_ARRAY;
35/// }
36/// assert_eq!(x, [8, 0]);
37/// ```
38#[proc_macro]
39pub fn replace_token(raw_input: TokenStream) -> TokenStream {
40    let (needle, replacement, input) = parse_args(raw_input);
41    return TokenReplacer{needle, replacement}.process(input);
42}
43
44struct TokenReplacer {
45    needle: TokenTree,
46    replacement: TokenTree
47}
48
49impl TokenReplacer {
50    fn process(&self, input: TokenStream) -> TokenStream {
51        input.into_iter().map(|t| {
52            match t {
53                _ if token_eq(&t, &self.needle) => self.replacement.clone(),
54                TokenTree::Group(g) => TokenTree::Group(Group::new(g.delimiter(), self.process(g.stream()))),
55                _ => t
56            } 
57        }).collect()
58    } 
59}
60
61/// Replaces all occurences of a given sequence of tokens with another sequence of tokens.
62/// 
63/// Takes arguments in the form `replace_token!{[needle tokens], [replacement tokens], code to search}`. 
64/// The brackets around the needle and replacement tokens are not included in the search.
65/// You may use any kind of brackets.
66/// 
67/// # Panics
68/// At compile time, if the arguments cannot be correctly parsed.
69/// 
70/// # Examples
71/// 
72/// Replaces the sequence `9 + 10` with `21`. Note that spacing between tokens is generally ignored:
73/// ```
74/// # extern crate macro_find_and_replace;
75/// # use macro_find_and_replace::replace_token_sequence;
76/// replace_token_sequence!{[9 + 10], [21],
77///     let x = 9+10;
78/// }
79/// assert_eq!(x, 21);
80/// ```
81/// 
82/// Replaces the content of a match arm:
83/// ```
84/// # extern crate macro_find_and_replace;
85/// # use macro_find_and_replace::replace_token_sequence;
86/// enum Numbers {
87///     I32(i32),
88///     I64(i64),
89///     ISize(isize)
90/// }
91/// let n = Numbers::I32(12);
92/// replace_token_sequence!{[MY_ARM], [(y * 2) as f64],
93///     let x = match(n) {
94///         Numbers::I32(y) => MY_ARM,
95///         Numbers::I64(y) => MY_ARM,
96///         Numbers::ISize(y) => MY_ARM,
97///     };
98/// }
99/// assert_eq!(x, 24.0);
100/// ```
101#[proc_macro]
102pub fn replace_token_sequence(raw_input: TokenStream) -> TokenStream {
103    // Destructure or panic
104    let (needle_tree, replacement_tree, input) = if let (TokenTree::Group(n), TokenTree::Group(r), i) = parse_args(raw_input) {
105        (n.stream(), r.stream(), i)
106    } else {
107        panic!("{}", "First or second argument to replace_token_sequence!() was not a group.
108The correct usage is: replace_token_sequence!{[needle], [replacement], code to search}");
109    };
110
111    let mut needle : Vec<TokenTree> = needle_tree.into_iter().collect();
112    let mut replacement : Vec<TokenTree> = replacement_tree.into_iter().collect();
113
114    // Default to single replacement if needle and replacement are both singleton
115    if needle.len() == 1 && replacement.len() == 1 {
116        return TokenReplacer{needle : needle.swap_remove(0), replacement : replacement.swap_remove(0)}.process(input);
117    }
118    
119    return TokenSequenceReplacer{needle, replacement}.process(input);
120}
121
122struct TokenSequenceReplacer {
123    needle: Vec<TokenTree>,
124    replacement: Vec<TokenTree>
125}
126
127impl TokenSequenceReplacer {
128    fn process(&self, input: TokenStream) -> TokenStream {
129        let mut buffer : Vec<TokenTree> =  Vec::with_capacity(self.needle.len());
130        let mut output : Vec<TokenTree> =  Vec::new();
131
132        for mut t in input {
133            if let TokenTree::Group(g) = t {
134                t = TokenTree::Group(Group::new(g.delimiter(), self.process(g.stream())));
135            }
136            
137            if buffer.len() < self.needle.len() - 1 {
138                buffer.push(t);
139                continue;
140            } else if buffer.len() == self.needle.len() - 1 {
141                buffer.push(t);
142            }
143            else {
144                output.push(buffer.remove(0));
145                buffer.push(t); 
146            }
147            
148            if iters_eq_by(buffer.iter(), self.needle.iter(), |a, b| {token_eq(a, b)}) {
149                buffer.clear();
150                output.append(&mut self.replacement.clone())
151            }
152        }
153        return output.into_iter().chain(buffer).collect();
154    }
155}
156
157fn iters_eq_by<I, J, F>(a: I, b: J, mut eq: F) -> bool 
158where 
159    I: IntoIterator,
160    J: IntoIterator,
161    F: FnMut(I::Item, J::Item) -> bool
162{
163    let mut a = a.into_iter();
164    let mut b = b.into_iter();
165   
166    loop {
167        match (a.next(), b.next()) {
168            (Some(a), Some(b)) => if !eq(a, b) { return false; },
169            (None, None) => break,
170            (Some(_), None) | (None, Some(_)) => return false,
171        }
172    } 
173    return true;
174}
175
176fn token_eq(a: &TokenTree, b: &TokenTree) -> bool {
177    match (a, b) {
178        (TokenTree::Ident(a), TokenTree::Ident(b)) =>  a.to_string() == b.to_string(),
179        // Checking for a.spacing() == b.spacing() as well is too conservative
180        (TokenTree::Punct(a), TokenTree::Punct(b)) => a.to_string() == b.to_string(),
181        (TokenTree::Literal(a), TokenTree::Literal(b)) => a.to_string() == b.to_string(),
182        (TokenTree::Group(a), TokenTree::Group(b)) => {
183            (a.delimiter() == b.delimiter()) 
184            && (a.to_string() == b.to_string()) 
185            && iters_eq_by(a.clone().stream(), b.clone().stream(), |a, b| {token_eq(&a, &b)})
186        },
187        (_, _) => false,
188    }
189}
190
191fn parse_args(input: TokenStream) -> (TokenTree, TokenTree, TokenStream) {
192    let mut iter = input.into_iter();  
193
194    let needle = iter.next().expect("Not enough tokens passed to macro");
195
196    let comma = iter.next().expect("Not enough tokens passed to macro"); // Ignore comma
197    assert_eq!(comma.to_string(), ",", "Malformed arguments to macro: second token was not a comma");
198
199    let replacement = iter.next().expect("Not enough tokens passed to macro");
200
201    let comma = iter.next().expect("Not enough tokens passed to macro"); // Ignore comma
202    assert_eq!(comma.to_string(), ",", "Malformed arguments to macro: fourth token was not a comma"); 
203
204    return (needle, replacement, iter.collect());
205}