macro_find_and_replace/
lib.rs1extern crate proc_macro;
8use proc_macro::{TokenStream, TokenTree, Group};
9
10#[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#[proc_macro]
102pub fn replace_token_sequence(raw_input: TokenStream) -> TokenStream {
103 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 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 (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"); 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"); assert_eq!(comma.to_string(), ",", "Malformed arguments to macro: fourth token was not a comma");
203
204 return (needle, replacement, iter.collect());
205}