├── .gitignore ├── Cargo.toml ├── README.md ├── examples └── macro_rules.rs ├── macros ├── Cargo.toml ├── README.md └── src │ ├── ast.rs │ ├── expand.rs │ ├── lib.rs │ └── parse.rs └── src ├── lib.rs └── match_set.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-rules" 3 | version = "0.4.0" 4 | authors = ["Nick Cameron "] 5 | edition = "2021" 6 | repository = "https://github.com/nrc/proc-macro-rules" 7 | description = "Emulate macro-rules pattern matching in procedural macros" 8 | readme = "README.md" 9 | license = "Apache-2.0/MIT" 10 | 11 | [dependencies] 12 | proc-macro-rules-macros = { path = "macros" } 13 | proc-macro2 = "1.0.56" 14 | syn = { version = "2.0.15", features = ["full", "extra-traits"] } 15 | 16 | [dev-dependencies] 17 | quote = "1.0.26" 18 | 19 | [workspace] 20 | members = [ 21 | "macros", 22 | ] 23 | 24 | [[example]] 25 | name = "macro_rules" 26 | crate-type = ["proc-macro"] 27 | 28 | [patch.crates-io] 29 | proc-macro-rules-macros = { path = "macros" } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proc-macro-rules 2 | 3 | `macro_rules`-style syntax matching for procedural macros. 4 | 5 | This crate is work-in-progress, incomplete, and probably buggy! 6 | 7 | Example: 8 | 9 | ```rust 10 | rules!(tokens => { 11 | ($finish:ident ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => { 12 | for f in found { 13 | do_something(finish, f, inner, rest[0]); 14 | } 15 | } 16 | (foo $($bar:expr)?) => { 17 | match bar { 18 | Some(e) => foo_with_expr(e), 19 | None => foo_no_expr(), 20 | } 21 | } 22 | }); 23 | ``` 24 | 25 | ## Using proc-macro-rules 26 | 27 | Add `proc-macro-rules = "0.3.0"` (or `proc-macro-rules = "0.2.1"` for versions between 1.31 and 1.56) to your Cargo.toml. 28 | 29 | Import the `rules` macro with `use proc_macro_rules::rules`, then use with `rules!(tokens => { branches });` where `tokens` is an expression which evaluates to a `TokenStream` (such as the argument in the definition of a procedural macro). 30 | 31 | Each branch in `branches` should have the form `( pattern ) => { body }` where `pattern` is a macro-rules-style pattern (using all the same syntax for meta-variables, AST nodes, repetition, etc.) and `body` is rust code executed when the pattern is matched. Within `body`, any meta-variables in the pattern are bound to variables of an appropriate type from either the [proc_macro2](https://github.com/alexcrichton/proc-macro2) or [syn](https://github.com/dtolnay/syn) crates. Where a meta-variable is inside a repetition or option clause, it will be wrapped in a `Vec` or `Option`, respectively. 32 | 33 | For example, in the first branch in the above example `ident` has type `syn::Ident` and `inner` has type `Vec`. 34 | 35 | 36 | ## Building and testing 37 | 38 | Use `cargo build` to build, `cargo test --all` to test. 39 | 40 | 41 | ## Contributing 42 | 43 | Contributions are very welcome! It would be great to know things which are missing or incorrect (in general we should have the same behaviour as `macro_rules`, so anything different is incorrect). Issues, code, docs, tests, and corrections are all welcome. 44 | -------------------------------------------------------------------------------- /examples/macro_rules.rs: -------------------------------------------------------------------------------- 1 | // This is an example of using the proc_macro_rules crate to convert a macro_rules 2 | // macro to a procedural one. The two macros should work in exactly the same way. 3 | 4 | // The main differences are: 5 | // * we must manually handle the token streams and their type conversion 6 | // * we use the rules! macro 7 | // * we use the quote! macro for each body (which requires using `#` instead of `$`) 8 | 9 | #![allow(unused_macros)] 10 | 11 | extern crate proc_macro; 12 | 13 | use proc_macro::TokenStream; 14 | use proc_macro_rules::rules; 15 | use quote::quote; 16 | 17 | // Declarative version using macro_rules. 18 | macro_rules! vec { 19 | () => { 20 | Vec::new() 21 | }; 22 | ( $( $x:expr ),+ ) => { 23 | { 24 | let mut temp_vec = Vec::new(); 25 | $( 26 | temp_vec.push($x); 27 | )* 28 | temp_vec 29 | } 30 | }; 31 | } 32 | 33 | // Procedural version. 34 | #[proc_macro] 35 | pub fn vec(input: TokenStream) -> TokenStream { 36 | rules!(input.into() => { 37 | () => { quote! { 38 | Vec::new() 39 | }} 40 | ( $( $x:expr ),+ ) => { quote! { 41 | let mut temp_vec = Vec::new(); 42 | #( 43 | temp_vec.push(#x); 44 | )* 45 | temp_vec 46 | }} 47 | }) 48 | .into() 49 | } 50 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-rules-macros" 3 | version = "0.4.0" 4 | authors = ["Nick Cameron "] 5 | edition = "2021" 6 | repository = "https://github.com/nrc/proc-macro-rules" 7 | description = "Emulate macro-rules pattern matching in procedural macros" 8 | readme = "README.md" 9 | license = "Apache-2.0/MIT" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | once_cell = "1.17.1" 16 | quote = "1.0.26" 17 | syn = { version = "2.0.15", features = ["full", "extra-traits"] } 18 | proc-macro2 = "1.0.56" 19 | -------------------------------------------------------------------------------- /macros/README.md: -------------------------------------------------------------------------------- 1 | # proc-macro-rules macros 2 | 3 | You are probably looking for the [proc-macro-rules](https://crates.io/crates/proc-macro-rules) crate. 4 | -------------------------------------------------------------------------------- /macros/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::collect_vars; 2 | 3 | use once_cell::sync::OnceCell; 4 | use proc_macro2::{Delimiter, Ident, Literal, Punct, Span}; 5 | use std::sync::Mutex; 6 | use syn::{Block, Expr}; 7 | 8 | #[derive(Debug)] 9 | pub(crate) struct Rules { 10 | pub(crate) clause: Expr, 11 | pub(crate) rules: Vec, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub(crate) struct Rule { 16 | pub(crate) lhs: SubRule, 17 | pub(crate) rhs: Rhs, 18 | } 19 | 20 | #[derive(Debug, Clone)] 21 | pub(crate) enum Rhs { 22 | Expr(Expr), 23 | Block(Block), 24 | } 25 | 26 | #[derive(Debug, Clone)] 27 | pub(crate) struct SubRule { 28 | pub(crate) matchers: Vec, 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub(crate) enum Fragment { 33 | Var(Ident, Type), 34 | Repeat(SubRule, RepeatKind, Option), 35 | Ident(Ident), 36 | Punct(Punct), 37 | Literal(Literal), 38 | Group(SubRule, Delimiter), 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub(crate) enum RepeatKind { 43 | // `*` 44 | ZeroOrMore, 45 | // `+` 46 | OneOrMore, 47 | // `?` 48 | ZeroOrOne, 49 | } 50 | 51 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 52 | pub(crate) enum Type { 53 | Item, 54 | Block, 55 | Stmt, 56 | Expr, 57 | Pat, 58 | Lifetime, 59 | Path, 60 | Ty, 61 | Ident, 62 | Meta, 63 | Tt, 64 | Vis, 65 | Literal, 66 | } 67 | 68 | #[derive(Debug, Clone, Eq, PartialEq)] 69 | pub(crate) struct MetaVar { 70 | pub(crate) name: Ident, 71 | pub(crate) ty: MetaVarType, 72 | } 73 | 74 | #[derive(Debug, Clone, Eq, PartialEq)] 75 | pub(crate) enum MetaVarType { 76 | Vec(Box), 77 | Option(Box), 78 | T(Type), 79 | } 80 | 81 | pub(crate) struct RuleBuilder { 82 | pub(crate) matchers: Vec, 83 | pub(crate) variables: Vec, 84 | pub(crate) parent_name: Option, 85 | pub(crate) name: Ident, 86 | } 87 | 88 | pub(crate) enum FragmentBuilder { 89 | Var(Ident, Type), 90 | Repeat(RuleBuilder, RepeatKind, Option), 91 | Ident(Ident), 92 | Punct(Punct), 93 | Literal(Literal), 94 | Group(RuleBuilder, Delimiter), 95 | } 96 | 97 | impl SubRule { 98 | pub(crate) fn into_builder(self, parent_name: Option) -> RuleBuilder { 99 | let mut variables = vec![]; 100 | collect_vars(&self, &mut variables); 101 | let name = Ident::new(&next_builder_name(), Span::call_site()); 102 | RuleBuilder { 103 | matchers: self 104 | .matchers 105 | .into_iter() 106 | .map(|m| m.into_builder(Some(name.clone()))) 107 | .collect(), 108 | variables, 109 | name, 110 | parent_name, 111 | } 112 | } 113 | } 114 | 115 | fn next_builder_name() -> String { 116 | static NEXT_ID: OnceCell> = OnceCell::with_value(Mutex::new(0)); 117 | let mut next_id = NEXT_ID.get().unwrap().lock().unwrap(); 118 | *next_id += 1; 119 | format!("MatchesBuilder{}", next_id) 120 | } 121 | 122 | impl Fragment { 123 | pub(crate) fn into_builder(self, parent_name: Option) -> FragmentBuilder { 124 | match self { 125 | Fragment::Var(i, t) => FragmentBuilder::Var(i, t), 126 | Fragment::Repeat(r, rep, sep) => { 127 | FragmentBuilder::Repeat(r.into_builder(parent_name), rep, sep) 128 | } 129 | Fragment::Ident(i) => FragmentBuilder::Ident(i), 130 | Fragment::Punct(p) => FragmentBuilder::Punct(p), 131 | Fragment::Literal(l) => FragmentBuilder::Literal(l), 132 | Fragment::Group(r, d) => FragmentBuilder::Group(r.into_builder(parent_name), d), 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /macros/src/expand.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use crate::{collect_vars, verify_rule}; 3 | 4 | use proc_macro2::{Delimiter, Span, TokenStream as TokenStream2}; 5 | use quote::{quote, ToTokens, TokenStreamExt}; 6 | use syn::{Ident, Token}; 7 | 8 | impl ToTokens for Rules { 9 | fn to_tokens(&self, tokens: &mut TokenStream2) { 10 | let clause = &self.clause; 11 | let rules = &self.rules; 12 | tokens.append_all(quote!({ 13 | let tts: &proc_macro2::TokenStream = &#clause; 14 | #( 15 | if let Some(value) = #rules { 16 | value 17 | } else 18 | )* { 19 | panic!("No rule matched input"); 20 | } 21 | })); 22 | } 23 | } 24 | 25 | impl ToTokens for Rule { 26 | fn to_tokens(&self, tokens: &mut TokenStream2) { 27 | verify_rule(&self.lhs); 28 | 29 | let body = &self.rhs; 30 | 31 | let rule = &self.lhs; 32 | let mut variables = vec![]; 33 | collect_vars(rule, &mut variables); 34 | let rule = rule.clone().into_builder(None); 35 | let vars = var_names(&variables); 36 | let builder_name = &rule.name; 37 | let matches = matches(builder_name, &variables); 38 | 39 | tokens.append_all(quote!({ 40 | #matches 41 | 42 | impl proc_macro_rules::syn::parse::Parse for Matches { 43 | fn parse(ps: proc_macro_rules::syn::parse::ParseStream) -> proc_macro_rules::syn::parse::Result { 44 | let mut ms: proc_macro_rules::MatchSet<#builder_name> = proc_macro_rules::MatchSet::new(ps.fork()); 45 | // parse the whole initial ParseStream to avoid 'unexpected token' errors 46 | let _: Result = ps.parse(); 47 | 48 | #rule 49 | 50 | let result = ms.finalise()?; 51 | // FIXME(#8) pick best match 52 | result.into_iter().filter_map(|p| if p.input.is_empty() { 53 | Some(p.matches.finalise()) 54 | } else { 55 | None 56 | }).next().ok_or_else(|| proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "pattern could not be parsed")) 57 | } 58 | } 59 | 60 | match proc_macro_rules::syn::parse2(tts.clone()) { 61 | Ok(Matches { #(#vars,)* }) => { 62 | let value = { #body }; 63 | 64 | // This is needed because the body may have ended in a return 65 | // statement which makes the `Some` construction unreachable. 66 | #[allow(unreachable_code)] 67 | { 68 | Some(value) 69 | } 70 | } 71 | 72 | // It can be useful to debug here. 73 | Err(e) => None, 74 | } 75 | })); 76 | } 77 | } 78 | 79 | fn var_names(variables: &[MetaVar]) -> Vec { 80 | variables.iter().map(|v| v.name.clone()).collect() 81 | } 82 | 83 | fn matches(builder_name: &Ident, variables: &[MetaVar]) -> TokenStream2 { 84 | let decls = variables; 85 | let builder = builder(builder_name, variables); 86 | let unwraps = variables.iter().map(|v| { 87 | let name = &v.name; 88 | match v.ty { 89 | MetaVarType::Vec(_) | MetaVarType::Option(_) => quote! { #name: self.#name }, 90 | MetaVarType::T(_) => quote! { #name: self.#name.unwrap() }, 91 | } 92 | }); 93 | let fork_impl = matches_fork(builder_name, variables); 94 | quote! { 95 | #builder 96 | 97 | struct Matches { 98 | #(#decls,)* 99 | } 100 | 101 | impl #builder_name { 102 | fn finalise(self) -> Matches { 103 | Matches { 104 | #(#unwraps,)* 105 | } 106 | } 107 | } 108 | 109 | impl proc_macro_rules::Fork for #builder_name { 110 | type Parent = (); 111 | fn hoist(&self, outer: &mut ()) {} 112 | #fork_impl 113 | } 114 | } 115 | } 116 | 117 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 118 | enum HoistRepeat { 119 | None, 120 | Repeat, 121 | Option, 122 | } 123 | 124 | // repeat will have to do opt toof 125 | fn sub_matches( 126 | builder_name: &Ident, 127 | outer_builder_name: &Ident, 128 | variables: &[MetaVar], 129 | repeat: HoistRepeat, 130 | ) -> TokenStream2 { 131 | let builder = builder(builder_name, variables); 132 | let hoists: Vec<_> = match repeat { 133 | HoistRepeat::Repeat => variables 134 | .iter() 135 | .map(|v| { 136 | let name = &v.name; 137 | let unpack = match &v.ty { 138 | MetaVarType::Vec(_) | MetaVarType::Option(_) => quote! { self.#name.clone() }, 139 | MetaVarType::T(_) => { 140 | quote! { self.#name.as_ref().expect("hoist failed (a)").clone() } 141 | } 142 | }; 143 | quote! { 144 | outer.#name.push(#unpack); 145 | } 146 | }) 147 | .collect(), 148 | HoistRepeat::Option => variables 149 | .iter() 150 | .map(|v| { 151 | let name = &v.name; 152 | let unpack = match &v.ty { 153 | MetaVarType::Vec(_) => quote! { 154 | if self.#name.is_empty() { 155 | None 156 | } else { 157 | Some(self.#name.clone()) 158 | } 159 | }, 160 | MetaVarType::T(_) | MetaVarType::Option(_) => quote! { self.#name.clone() }, 161 | }; 162 | quote! { 163 | outer.#name = #unpack; 164 | } 165 | }) 166 | .collect(), 167 | HoistRepeat::None => variables 168 | .iter() 169 | .map(|v| { 170 | let name = &v.name; 171 | let unpack = match &v.ty { 172 | MetaVarType::Vec(_) | MetaVarType::Option(_) => quote! { self.#name.clone() }, 173 | MetaVarType::T(_) => { 174 | quote! { Some(self.#name.as_ref().expect("hoist failed (c)").clone()) } 175 | } 176 | }; 177 | quote! { 178 | outer.#name = #unpack; 179 | } 180 | }) 181 | .collect(), 182 | }; 183 | let fork_impl = matches_fork(builder_name, variables); 184 | quote! { 185 | #builder 186 | 187 | impl proc_macro_rules::Fork for #builder_name { 188 | type Parent = #outer_builder_name; 189 | fn hoist(&self, outer: &mut #outer_builder_name) { 190 | #(#hoists)* 191 | } 192 | 193 | #fork_impl 194 | } 195 | 196 | } 197 | } 198 | 199 | fn builder(builder_name: &Ident, variables: &[MetaVar]) -> TokenStream2 { 200 | let opt_decls = variables.iter().map(|v| { 201 | let name = &v.name; 202 | let ty = &v.ty; 203 | match &v.ty { 204 | MetaVarType::Vec(_) | MetaVarType::Option(_) => quote! { #name: #ty }, 205 | MetaVarType::T(_) => quote! { #name: Option<#ty> }, 206 | } 207 | }); 208 | quote! { 209 | #[derive(Clone)] 210 | struct #builder_name { 211 | #(#opt_decls,)* 212 | } 213 | } 214 | } 215 | 216 | fn matches_fork(builder_name: &Ident, variables: &[MetaVar]) -> TokenStream2 { 217 | let names = &var_names(variables); 218 | let values = variables.iter().map(|v| match v.ty { 219 | MetaVarType::Vec(_) => quote! { Vec::new() }, 220 | _ => quote! { None }, 221 | }); 222 | quote! { 223 | fn new() -> #builder_name { 224 | #builder_name { #(#names: #values,)* } 225 | } 226 | 227 | // FIXME(#9) this is inefficient and requires the matched types to 228 | // be Clone. We could do better by using an immutable data structure. 229 | fn fork(&self) -> #builder_name { 230 | self.clone() 231 | } 232 | } 233 | } 234 | 235 | impl ToTokens for Rhs { 236 | fn to_tokens(&self, tokens: &mut TokenStream2) { 237 | match self { 238 | Rhs::Expr(e) => e.to_tokens(tokens), 239 | Rhs::Block(b) => b.to_tokens(tokens), 240 | } 241 | } 242 | } 243 | 244 | // This `ToTokens` impl produces a matcher for the sub-rule. 245 | impl ToTokens for RuleBuilder { 246 | fn to_tokens(&self, tokens: &mut TokenStream2) { 247 | for m in &self.matchers { 248 | m.to_tokens(tokens) 249 | } 250 | } 251 | } 252 | 253 | impl ToTokens for FragmentBuilder { 254 | fn to_tokens(&self, tokens: &mut TokenStream2) { 255 | match self { 256 | FragmentBuilder::Var(id, _) => tokens.append_all(quote! { 257 | ms.expect(|ps, matches| { 258 | matches.#id = Some(ps.parse()?); 259 | Ok(()) 260 | })?; 261 | }), 262 | FragmentBuilder::Repeat(rule, RepeatKind::ZeroOrMore, sep) => { 263 | let sub_builder_name = &rule.name; 264 | let match_builder = sub_matches( 265 | sub_builder_name, 266 | rule.parent_name.as_ref().unwrap(), 267 | &rule.variables, 268 | HoistRepeat::Repeat, 269 | ); 270 | 271 | let sep = match sep { 272 | Some(sep) => quote! { 273 | ms.expect(|ps, _| { 274 | if ps.peek(proc_macro_rules::syn::Token!(#sep)) { 275 | let _: proc_macro2::TokenTree = ps.parse().unwrap(); 276 | } else { 277 | terminate = true; 278 | } 279 | Ok(()) 280 | })?; 281 | }, 282 | None => TokenStream2::new(), 283 | }; 284 | 285 | tokens.append_all(quote! { 286 | let mut terminate = false; 287 | while !terminate && ms.fork(|ps, match_handler| { 288 | #match_builder 289 | 290 | let mut ms: proc_macro_rules::MatchSet<#sub_builder_name> = 291 | proc_macro_rules::MatchSet::new(ps); 292 | 293 | #rule 294 | #sep 295 | 296 | let mb = ms.finalise()?; 297 | match_handler.hoist(&mb); 298 | 299 | Ok(()) 300 | }) {} 301 | ms.reset_states(); 302 | }); 303 | } 304 | FragmentBuilder::Repeat(rule, RepeatKind::OneOrMore, sep) => { 305 | let sub_builder_name = &rule.name; 306 | let match_builder = sub_matches( 307 | sub_builder_name, 308 | rule.parent_name.as_ref().unwrap(), 309 | &rule.variables, 310 | HoistRepeat::Repeat, 311 | ); 312 | 313 | let sep = match sep { 314 | Some(sep) => quote! { 315 | ms.expect(|ps, _| { 316 | if ps.peek(proc_macro_rules::syn::Token!(#sep)) { 317 | let _: proc_macro2::TokenTree = ps.parse().unwrap(); 318 | } else { 319 | terminate = true; 320 | } 321 | Ok(()) 322 | })?; 323 | }, 324 | None => TokenStream2::new(), 325 | }; 326 | 327 | tokens.append_all(quote! { 328 | let mut count = 0; 329 | let mut terminate = false; 330 | while !terminate && ms.fork(|ps, match_handler| { 331 | #match_builder 332 | 333 | let mut ms: proc_macro_rules::MatchSet<#sub_builder_name> = 334 | proc_macro_rules::MatchSet::new(ps); 335 | 336 | #rule 337 | #sep 338 | 339 | let mb = ms.finalise()?; 340 | match_handler.hoist(&mb); 341 | 342 | Ok(()) 343 | }) { 344 | count += 1; 345 | } 346 | if count == 0 { 347 | return Err(proc_macro_rules::syn::Error::new( 348 | proc_macro2::Span::call_site(), 349 | "At least one iteration required", 350 | )); 351 | } 352 | ms.reset_states(); 353 | }); 354 | } 355 | FragmentBuilder::Repeat(rule, RepeatKind::ZeroOrOne, sep) => { 356 | let sub_builder_name = &rule.name; 357 | let match_builder = sub_matches( 358 | sub_builder_name, 359 | rule.parent_name.as_ref().unwrap(), 360 | &rule.variables, 361 | HoistRepeat::Option, 362 | ); 363 | let sep = match sep { 364 | Some(sep) => quote! { 365 | ms.expect(|ps, _| { 366 | if ps.peek(proc_macro_rules::syn::Token!(#sep)) { 367 | let _: proc_macro2::TokenTree = ps.parse().unwrap(); 368 | } 369 | Ok(()) 370 | })?; 371 | }, 372 | None => TokenStream2::new(), 373 | }; 374 | 375 | tokens.append_all(quote! { 376 | ms.fork(|ps, match_handler| { 377 | #match_builder 378 | 379 | let mut ms: proc_macro_rules::MatchSet<#sub_builder_name> = 380 | proc_macro_rules::MatchSet::new(ps); 381 | 382 | #rule 383 | #sep 384 | 385 | let mb = ms.finalise()?; 386 | match_handler.hoist(&mb); 387 | 388 | Ok(()) 389 | }); 390 | ms.reset_states(); 391 | }); 392 | } 393 | FragmentBuilder::Ident(i) => { 394 | let i_str = &i.to_string(); 395 | tokens.append_all(quote! { 396 | ms.expect(|ps, _| { 397 | let i: proc_macro2::Ident = ps.parse()?; 398 | #[allow(clippy::cmp_owned)] 399 | if i.to_string() == #i_str { 400 | Ok(()) 401 | } else { 402 | Err(proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "bad ident")) 403 | } 404 | })?; 405 | }); 406 | } 407 | FragmentBuilder::Punct(p) => { 408 | let p_str = &p.to_string(); 409 | tokens.append_all(quote! { 410 | ms.expect(|ps, _| { 411 | let p: proc_macro2::Punct = ps.parse()?; 412 | #[allow(clippy::cmp_owned)] 413 | if p.to_string() == #p_str { 414 | Ok(()) 415 | } else { 416 | Err(proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "bad punct")) 417 | } 418 | })?; 419 | }); 420 | } 421 | FragmentBuilder::Literal(l) => { 422 | let l_str = &l.to_string(); 423 | tokens.append_all(quote! { 424 | ms.expect(|ps, _| { 425 | let l: proc_macro2::Literal = ps.parse()?; 426 | #[allow(clippy::cmp_owned)] 427 | if l.to_string() == #l_str { 428 | Ok(()) 429 | } else { 430 | Err(proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "bad literal")) 431 | } 432 | })?; 433 | }); 434 | } 435 | FragmentBuilder::Group(rule, delimiter) => { 436 | let d_toks = match delimiter { 437 | Delimiter::Parenthesis => quote!(proc_macro2::Delimiter::Parenthesis), 438 | Delimiter::Brace => quote!(proc_macro2::Delimiter::Brace), 439 | Delimiter::Bracket => quote!(proc_macro2::Delimiter::Bracket), 440 | Delimiter::None => quote!(proc_macro2::Delimiter::None), 441 | }; 442 | 443 | let sub_builder_name = &rule.name; 444 | let match_builder = sub_matches( 445 | sub_builder_name, 446 | rule.parent_name.as_ref().unwrap(), 447 | &rule.variables, 448 | HoistRepeat::None, 449 | ); 450 | 451 | tokens.append_all(quote! { 452 | ms.expect(|ps, matches| { 453 | let tok: proc_macro2::TokenTree = ps.parse()?; 454 | match tok { 455 | proc_macro2::TokenTree::Group(g) => { 456 | if g.delimiter() != #d_toks { 457 | return Err( 458 | proc_macro_rules::syn::Error::new( 459 | proc_macro2::Span::call_site(), 460 | "bad delimiter", 461 | )); 462 | } 463 | { 464 | #match_builder 465 | 466 | struct MatchParser(#sub_builder_name); 467 | 468 | impl proc_macro_rules::syn::parse::Parse for MatchParser { 469 | fn parse(ps: proc_macro_rules::syn::parse::ParseStream) -> proc_macro_rules::syn::parse::Result { 470 | let mut ms: proc_macro_rules::MatchSet<#sub_builder_name> = 471 | proc_macro_rules::MatchSet::new(ps.fork()); 472 | // parse the whole initial ParseStream to avoid 'unexpected token' errors 473 | let _: Result = ps.parse(); 474 | 475 | #rule 476 | 477 | let result = ms.finalise()?; 478 | result.into_iter().filter_map(|p| if p.input.is_empty() { 479 | Some(MatchParser(p.matches)) 480 | } else { 481 | None 482 | }).next().ok_or_else(|| proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "group could not be parsed")) 483 | } 484 | } 485 | 486 | let mp: MatchParser = proc_macro_rules::syn::parse2(g.stream())?; 487 | proc_macro_rules::Fork::hoist(&mp.0, matches); 488 | } 489 | Ok(()) 490 | } 491 | _ => Err(proc_macro_rules::syn::Error::new(proc_macro2::Span::call_site(), "expected group")), 492 | } 493 | })?; 494 | }); 495 | } 496 | } 497 | } 498 | } 499 | 500 | impl ToTokens for Type { 501 | fn to_tokens(&self, tokens: &mut TokenStream2) { 502 | match self { 503 | Type::Item => { 504 | tokens.append(Ident::new("syn", Span::call_site())); 505 | Token!(::)(Span::call_site()).to_tokens(tokens); 506 | tokens.append(Ident::new("Item", Span::call_site())); 507 | } 508 | Type::Block => { 509 | tokens.append(Ident::new("syn", Span::call_site())); 510 | Token!(::)(Span::call_site()).to_tokens(tokens); 511 | tokens.append(Ident::new("Block", Span::call_site())); 512 | } 513 | Type::Stmt => { 514 | tokens.append(Ident::new("syn", Span::call_site())); 515 | Token!(::)(Span::call_site()).to_tokens(tokens); 516 | tokens.append(Ident::new("Stmt", Span::call_site())); 517 | } 518 | Type::Expr => { 519 | tokens.append(Ident::new("syn", Span::call_site())); 520 | Token!(::)(Span::call_site()).to_tokens(tokens); 521 | tokens.append(Ident::new("Expr", Span::call_site())); 522 | } 523 | Type::Pat => { 524 | tokens.append(Ident::new("syn", Span::call_site())); 525 | Token!(::)(Span::call_site()).to_tokens(tokens); 526 | tokens.append(Ident::new("Pat", Span::call_site())); 527 | } 528 | Type::Lifetime => { 529 | tokens.append(Ident::new("syn", Span::call_site())); 530 | Token!(::)(Span::call_site()).to_tokens(tokens); 531 | tokens.append(Ident::new("Lifetime", Span::call_site())); 532 | } 533 | Type::Path => { 534 | tokens.append(Ident::new("syn", Span::call_site())); 535 | Token!(::)(Span::call_site()).to_tokens(tokens); 536 | tokens.append(Ident::new("Path", Span::call_site())); 537 | } 538 | Type::Ty => { 539 | tokens.append(Ident::new("syn", Span::call_site())); 540 | Token!(::)(Span::call_site()).to_tokens(tokens); 541 | tokens.append(Ident::new("Type", Span::call_site())); 542 | } 543 | Type::Ident => { 544 | tokens.append(Ident::new("syn", Span::call_site())); 545 | Token!(::)(Span::call_site()).to_tokens(tokens); 546 | tokens.append(Ident::new("Ident", Span::call_site())); 547 | } 548 | Type::Meta => { 549 | tokens.append(Ident::new("syn", Span::call_site())); 550 | Token!(::)(Span::call_site()).to_tokens(tokens); 551 | tokens.append(Ident::new("Meta", Span::call_site())); 552 | } 553 | Type::Tt => { 554 | tokens.append(Ident::new("proc_macro2", Span::call_site())); 555 | Token!(::)(Span::call_site()).to_tokens(tokens); 556 | tokens.append(Ident::new("TokenTree", Span::call_site())); 557 | } 558 | Type::Vis => { 559 | tokens.append(Ident::new("syn", Span::call_site())); 560 | Token!(::)(Span::call_site()).to_tokens(tokens); 561 | tokens.append(Ident::new("Visibility", Span::call_site())); 562 | } 563 | Type::Literal => { 564 | tokens.append(Ident::new("proc_macro2", Span::call_site())); 565 | Token!(::)(Span::call_site()).to_tokens(tokens); 566 | tokens.append(Ident::new("Literal", Span::call_site())); 567 | } 568 | } 569 | } 570 | } 571 | 572 | impl ToTokens for MetaVar { 573 | fn to_tokens(&self, tokens: &mut TokenStream2) { 574 | self.name.to_tokens(tokens); 575 | Token!(:)(Span::call_site()).to_tokens(tokens); 576 | self.ty.to_tokens(tokens); 577 | } 578 | } 579 | 580 | impl ToTokens for MetaVarType { 581 | fn to_tokens(&self, tokens: &mut TokenStream2) { 582 | match self { 583 | MetaVarType::Vec(ty) => { 584 | tokens.append(Ident::new("Vec", Span::call_site())); 585 | Token!(<)(Span::call_site()).to_tokens(tokens); 586 | ty.to_tokens(tokens); 587 | Token!(>)(Span::call_site()).to_tokens(tokens); 588 | } 589 | MetaVarType::Option(ty) => { 590 | tokens.append(Ident::new("Option", Span::call_site())); 591 | Token!(<)(Span::call_site()).to_tokens(tokens); 592 | ty.to_tokens(tokens); 593 | Token!(>)(Span::call_site()).to_tokens(tokens); 594 | } 595 | MetaVarType::T(ty) => ty.to_tokens(tokens), 596 | } 597 | } 598 | } 599 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "256"] 2 | 3 | extern crate proc_macro; 4 | extern crate proc_macro2; 5 | extern crate quote; 6 | extern crate syn; 7 | 8 | use proc_macro::TokenStream; 9 | use quote::ToTokens; 10 | use syn::parse_macro_input; 11 | 12 | mod ast; 13 | mod expand; 14 | mod parse; 15 | 16 | #[proc_macro] 17 | pub fn rules(input: TokenStream) -> TokenStream { 18 | parse_macro_input!(input as ast::Rules) 19 | .into_token_stream() 20 | .into() 21 | } 22 | 23 | fn verify_rule(_rule: &ast::SubRule) { 24 | // FIXME(#11) pattern rule verification 25 | } 26 | 27 | // FIXME(#12) we could save some computation by using intermediate results from the SubRules. 28 | fn collect_vars(rule: &ast::SubRule, vars: &mut Vec) { 29 | for m in &rule.matchers { 30 | match m { 31 | ast::Fragment::Var(id, ty) => vars.push(ast::MetaVar { 32 | name: id.clone(), 33 | ty: ast::MetaVarType::T(*ty), 34 | }), 35 | ast::Fragment::Repeat(sub_rule, rkind, _) => { 36 | let mut sub = vec![]; 37 | collect_vars(sub_rule, &mut sub); 38 | for s in sub { 39 | vars.push(match rkind { 40 | ast::RepeatKind::ZeroOrMore | ast::RepeatKind::OneOrMore => ast::MetaVar { 41 | name: s.name, 42 | ty: ast::MetaVarType::Vec(Box::new(s.ty)), 43 | }, 44 | ast::RepeatKind::ZeroOrOne => ast::MetaVar { 45 | name: s.name, 46 | ty: ast::MetaVarType::Option(Box::new(s.ty)), 47 | }, 48 | }) 49 | } 50 | } 51 | ast::Fragment::Group(sub_rule, _) => { 52 | collect_vars(sub_rule, vars); 53 | } 54 | _ => {} 55 | } 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use super::*; 62 | use crate::ast::*; 63 | use proc_macro2::Span; 64 | use syn::Ident; 65 | 66 | #[test] 67 | fn test_collect_vars() { 68 | fn run_test(rule: ast::SubRule, expected: Vec) { 69 | let mut result = vec![]; 70 | collect_vars(&rule, &mut result); 71 | assert_eq!(result, expected); 72 | } 73 | 74 | // `` 75 | let ast = ast::SubRule { matchers: vec![] }; 76 | run_test(ast, vec![]); 77 | 78 | // `$foo:vis` 79 | let ast = ast::SubRule { 80 | matchers: vec![Fragment::Var( 81 | Ident::new("foo", Span::call_site()), 82 | Type::Vis, 83 | )], 84 | }; 85 | run_test( 86 | ast, 87 | vec![MetaVar { 88 | name: Ident::new("foo", Span::call_site()), 89 | ty: MetaVarType::T(Type::Vis), 90 | }], 91 | ); 92 | 93 | // `foo` 94 | let ast = ast::SubRule { 95 | matchers: vec![Fragment::Ident(Ident::new("foo", Span::call_site()))], 96 | }; 97 | run_test(ast, vec![]); 98 | 99 | // `foo $bar:Tt $($foo:expr)*` 100 | let ast = ast::SubRule { 101 | matchers: vec![ 102 | Fragment::Ident(Ident::new("foo", Span::call_site())), 103 | Fragment::Var(Ident::new("bar", Span::call_site()), Type::Tt), 104 | Fragment::Repeat( 105 | SubRule { 106 | matchers: vec![Fragment::Var( 107 | Ident::new("foo", Span::call_site()), 108 | Type::Expr, 109 | )], 110 | }, 111 | RepeatKind::OneOrMore, 112 | None, 113 | ), 114 | ], 115 | }; 116 | run_test( 117 | ast, 118 | vec![ 119 | MetaVar { 120 | name: Ident::new("bar", Span::call_site()), 121 | ty: MetaVarType::T(Type::Tt), 122 | }, 123 | MetaVar { 124 | name: Ident::new("foo", Span::call_site()), 125 | ty: MetaVarType::Vec(Box::new(MetaVarType::T(Type::Expr))), 126 | }, 127 | ], 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /macros/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | 3 | use proc_macro2::TokenTree as TokenTree2; 4 | use syn::parse::{Parse, ParseStream, Result as ParseResult}; 5 | use syn::{ 6 | braced, parenthesized, parse2, 7 | token::{Brace, Paren}, 8 | Ident, Token, 9 | }; 10 | 11 | impl Parse for Rules { 12 | fn parse(input: ParseStream) -> ParseResult { 13 | let clause = input.parse()?; 14 | input.parse::]>()?; 15 | let content; 16 | braced!(content in input); 17 | let mut rules = vec![]; 18 | while !content.is_empty() { 19 | rules.push(content.parse()?); 20 | } 21 | 22 | Ok(Rules { clause, rules }) 23 | } 24 | } 25 | 26 | impl Parse for Rule { 27 | fn parse(input: ParseStream) -> ParseResult { 28 | let content; 29 | parenthesized!(content in input); 30 | let lhs = content.parse()?; 31 | 32 | input.parse::]>()?; 33 | let rhs = input.parse()?; 34 | Ok(Rule { lhs, rhs }) 35 | } 36 | } 37 | 38 | impl Parse for Rhs { 39 | fn parse(input: ParseStream) -> ParseResult { 40 | if input.peek(Brace) { 41 | Ok(Rhs::Block(input.parse()?)) 42 | } else { 43 | let e = input.parse()?; 44 | input.parse::()?; 45 | Ok(Rhs::Expr(e)) 46 | } 47 | } 48 | } 49 | 50 | impl Parse for SubRule { 51 | fn parse(input: ParseStream) -> ParseResult { 52 | let mut matchers = vec![]; 53 | while !input.is_empty() { 54 | matchers.push(input.parse()?); 55 | } 56 | Ok(SubRule { matchers }) 57 | } 58 | } 59 | 60 | impl Parse for Fragment { 61 | fn parse(input: ParseStream) -> ParseResult { 62 | if input.peek(Token![$]) { 63 | let _: Token![$] = input.parse()?; 64 | if input.peek(Paren) { 65 | let content; 66 | parenthesized!(content in input); 67 | let rule = content.parse()?; 68 | let fork = input.fork(); 69 | let (separator, kind) = match fork.parse::() { 70 | Ok(_) => (None, input.parse()?), 71 | Err(_) => { 72 | let tok: TokenTree2 = input.parse()?; 73 | let p = match tok { 74 | TokenTree2::Punct(p) => p, 75 | _ => { 76 | return Err(input.error("Separator not punctuation")); 77 | } 78 | }; 79 | (Some(p), input.parse()?) 80 | } 81 | }; 82 | Ok(Fragment::Repeat(rule, kind, separator)) 83 | } else { 84 | let ident = input.parse()?; 85 | let _: Token!(:) = input.parse()?; 86 | let ty = input.parse()?; 87 | Ok(Fragment::Var(ident, ty)) 88 | } 89 | } else { 90 | let tok: TokenTree2 = input.parse()?; 91 | Ok(match tok { 92 | TokenTree2::Literal(l) => Fragment::Literal(l), 93 | TokenTree2::Punct(p) => Fragment::Punct(p), 94 | TokenTree2::Ident(i) => Fragment::Ident(i), 95 | TokenTree2::Group(g) => { 96 | let rule = parse2(g.stream())?; 97 | Fragment::Group(rule, g.delimiter()) 98 | } 99 | }) 100 | } 101 | } 102 | } 103 | 104 | impl Parse for Type { 105 | fn parse(input: ParseStream) -> ParseResult { 106 | let ident: Ident = input.parse()?; 107 | match &*ident.to_string() { 108 | "item" => Ok(Type::Item), 109 | "block" => Ok(Type::Block), 110 | "stmt" => Ok(Type::Stmt), 111 | "expr" => Ok(Type::Expr), 112 | "pat" => Ok(Type::Pat), 113 | "lifetime" => Ok(Type::Lifetime), 114 | "path" => Ok(Type::Path), 115 | "ty" => Ok(Type::Ty), 116 | "ident" => Ok(Type::Ident), 117 | "meta" => Ok(Type::Meta), 118 | "tt" => Ok(Type::Tt), 119 | "vis" => Ok(Type::Vis), 120 | "literal" => Ok(Type::Literal), 121 | s => Err(input.error(format!("Bad fragment type: {}", s))), 122 | } 123 | } 124 | } 125 | 126 | impl Parse for RepeatKind { 127 | fn parse(input: ParseStream) -> ParseResult { 128 | if input.peek(Token!(*)) { 129 | let _: Token!(*) = input.parse()?; 130 | Ok(RepeatKind::ZeroOrMore) 131 | } else if input.peek(Token!(+)) { 132 | let _: Token!(+) = input.parse()?; 133 | Ok(RepeatKind::OneOrMore) 134 | } else if input.peek(Token!(?)) { 135 | let _: Token!(?) = input.parse()?; 136 | Ok(RepeatKind::ZeroOrOne) 137 | } else { 138 | Err(input.error("Unexpected repeat operator")) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `macro_rules`-style syntax matching for procedural macros. 2 | //! 3 | //! This crate is work-in-progress, incomplete, and probably buggy! 4 | //! 5 | //! Example: 6 | //! 7 | //! ```rust 8 | //! use proc_macro_rules::rules; 9 | //! use proc_macro2::TokenStream; 10 | //! 11 | //! fn main() { 12 | //! # use quote::quote; 13 | //! # test_rules(quote! { A (B C) #[inner...] ... }); 14 | //! # test_rules(quote! { foo 1 + 1 }); 15 | //! # test_rules(quote! { foo }); 16 | //! # } 17 | //! # 18 | //! # fn test_rules(tokens: TokenStream) { 19 | //! # const IGNORE: &str = stringify! { 20 | //! let tokens: TokenStream = /* ... */; 21 | //! # }; 22 | //! 23 | //! rules!(tokens => { 24 | //! ($finish:ident ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => { 25 | //! for f in found { 26 | //! do_something(&finish, f, &inner, &rest[0]); 27 | //! } 28 | //! } 29 | //! (foo $($bar:expr)?) => { 30 | //! match bar { 31 | //! Some(e) => foo_with_expr(e), 32 | //! None => foo_no_expr(), 33 | //! } 34 | //! } 35 | //! }); 36 | //! } 37 | //! # 38 | //! # use syn::{Expr, Ident}; 39 | //! # use proc_macro2::TokenTree; 40 | //! # 41 | //! # fn do_something( 42 | //! # finish: &Ident, 43 | //! # f: Ident, 44 | //! # inner: &[TokenTree], 45 | //! # rest: &TokenTree, 46 | //! # ) {} 47 | //! # fn foo_with_expr(e: Expr) {} 48 | //! # fn foo_no_expr() {} 49 | //! ``` 50 | //! 51 | //! Import the `rules` macro with `use proc_macro_rules::rules`, then use with 52 | //! `rules!(tokens => { branches });` where `tokens` is an expression which 53 | //! evaluates to a `TokenStream` (such as the argument in the definition of a 54 | //! procedural macro). 55 | //! 56 | //! Each branch in `branches` should have the form `( pattern ) => { body }` where 57 | //! `pattern` is a macro-rules-style pattern (using all the same syntax for 58 | //! meta-variables, AST nodes, repetition, etc.) and `body` is rust code executed 59 | //! when the pattern is matched. Within `body`, any meta-variables in the pattern 60 | //! are bound to variables of an appropriate type from either the 61 | //! [proc_macro2](https://github.com/alexcrichton/proc-macro2) or 62 | //! [syn](https://github.com/dtolnay/syn) crates. Where a meta-variable is 63 | //! inside a repetition or option clause, it will be wrapped in a `Vec` or 64 | //! `Option`, respectively. 65 | //! 66 | //! For example, in the first branch in the above example `ident` has type 67 | //! `syn::Ident` and `inner` has type `Vec`. 68 | 69 | #[doc(hidden)] 70 | pub extern crate syn; 71 | 72 | pub use crate::match_set::{Fork, MatchSet}; 73 | pub use proc_macro_rules_macros::rules; 74 | 75 | mod match_set; 76 | 77 | // Regression tests 78 | #[cfg(test)] 79 | mod tests { 80 | use crate as proc_macro_rules; 81 | use super::*; 82 | 83 | #[test] 84 | fn test_smoke() { 85 | let tokens: proc_macro2::TokenStream = "hi (a b c) # [there] the - rest".parse().unwrap(); 86 | rules!(tokens => { 87 | ($finish:ident ($($found:ident)+) # [ $($inner:tt)? ] $($rest:expr)*) => { 88 | assert_eq!(finish.to_string(), "hi"); 89 | assert_eq!(found.len(), 3); 90 | assert!(inner.is_some()); 91 | assert_eq!(rest.len(), 1); 92 | return; 93 | } 94 | }); 95 | panic!(); 96 | } 97 | 98 | #[test] 99 | fn test_empty() { 100 | let tokens: proc_macro2::TokenStream = "".parse().unwrap(); 101 | rules!(tokens => { 102 | () => { 103 | return; 104 | } 105 | }); 106 | panic!(); 107 | } 108 | 109 | #[test] 110 | #[should_panic] 111 | fn test_no_match() { 112 | let tokens: proc_macro2::TokenStream = "foo".parse().unwrap(); 113 | rules!(tokens => { 114 | (bar) => {} 115 | }); 116 | } 117 | 118 | #[test] 119 | fn test_branches() { 120 | fn apply(tokens: proc_macro2::TokenStream, expected_branch: usize) { 121 | rules!(tokens => { 122 | (foo) => { 123 | if expected_branch == 0 { 124 | return; 125 | } else { 126 | panic!("branch: 0, expected: {}", expected_branch); 127 | } 128 | } 129 | ($x:ident) => { 130 | if expected_branch == 1 { 131 | assert_eq!(x.to_string(), "bar"); 132 | return; 133 | } else { 134 | // TODO failing here! 135 | panic!("branch: 1, expected: {}", expected_branch); 136 | } 137 | } 138 | ($($x:ident)*) => { 139 | if expected_branch == 2 { 140 | assert_eq!(x.len(), 3); 141 | assert_eq!(x[0].to_string(), "a"); 142 | assert_eq!(x[1].to_string(), "b"); 143 | assert_eq!(x[2].to_string(), "c"); 144 | return; 145 | } else { 146 | panic!("branch: 2, expected: {}", expected_branch); 147 | } 148 | } 149 | }); 150 | panic!("Hit no branches, expected: {}", expected_branch); 151 | } 152 | 153 | apply("foo".parse().unwrap(), 0); 154 | apply("bar".parse().unwrap(), 1); 155 | apply("a b c".parse().unwrap(), 2); 156 | } 157 | 158 | #[test] 159 | fn test_opt() { 160 | fn apply(tokens: proc_macro2::TokenStream) { 161 | rules!(tokens => { 162 | (foo $(bar),? baz) => { 163 | return; 164 | } 165 | }); 166 | panic!(); 167 | } 168 | 169 | apply("foo baz".parse().unwrap()); 170 | apply("foo bar baz".parse().unwrap()); 171 | apply("foo bar, baz".parse().unwrap()); 172 | } 173 | 174 | #[test] 175 | fn test_plus() { 176 | fn apply(tokens: proc_macro2::TokenStream) { 177 | rules!(tokens => { 178 | (foo $(bar),+ baz) => { 179 | return; 180 | } 181 | }); 182 | panic!(); 183 | } 184 | 185 | apply("foo bar baz".parse().unwrap()); 186 | apply("foo bar, baz".parse().unwrap()); 187 | apply("foo bar, bar baz".parse().unwrap()); 188 | apply("foo bar, bar, baz".parse().unwrap()); 189 | apply("foo bar, bar, bar baz".parse().unwrap()); 190 | apply("foo bar, bar, bar, baz".parse().unwrap()); 191 | } 192 | 193 | #[test] 194 | fn test_star() { 195 | fn apply(tokens: proc_macro2::TokenStream) { 196 | rules!(tokens => { 197 | (foo $(bar),* baz) => { 198 | return; 199 | } 200 | }); 201 | panic!(); 202 | } 203 | 204 | apply("foo baz".parse().unwrap()); 205 | apply("foo bar baz".parse().unwrap()); 206 | apply("foo bar, baz".parse().unwrap()); 207 | apply("foo bar, bar baz".parse().unwrap()); 208 | apply("foo bar, bar, baz".parse().unwrap()); 209 | apply("foo bar, bar, bar baz".parse().unwrap()); 210 | apply("foo bar, bar, bar, baz".parse().unwrap()); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/match_set.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use std::collections::HashSet; 3 | use syn::parse::{Error, ParseBuffer, ParseStream}; 4 | 5 | pub struct MatchSet<'a, M: Fork> { 6 | positions: Vec>, 7 | garbage: HashSet, 8 | } 9 | 10 | pub trait Fork { 11 | type Parent; 12 | 13 | fn fork(&self) -> Self; 14 | fn new() -> Self; 15 | fn hoist(&self, outer: &mut Self::Parent); 16 | } 17 | 18 | pub struct Position<'a, M: Fork> { 19 | pub input: ParseBuffer<'a>, 20 | pub matches: M, 21 | state: FragmentState, 22 | } 23 | 24 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 25 | enum FragmentState { 26 | Ready, 27 | Exhausted, 28 | } 29 | 30 | pub struct MatchHandler<'a, 'b: 'a, M: Fork> { 31 | outer_position: &'a mut Position<'b, M>, 32 | forked: Vec>, 33 | } 34 | 35 | impl<'a, M: Fork> MatchSet<'a, M> { 36 | pub fn new(initial: ParseBuffer<'a>) -> MatchSet<'a, M> { 37 | MatchSet { 38 | positions: vec![Position { 39 | input: initial, 40 | matches: M::new(), 41 | state: FragmentState::Ready, 42 | }], 43 | garbage: HashSet::new(), 44 | } 45 | } 46 | 47 | pub fn finalise(self) -> syn::parse::Result>> { 48 | if self.positions.is_empty() { 49 | return Err(Error::new(Span::call_site(), "No match")); 50 | } 51 | Ok(self.positions) 52 | } 53 | 54 | // return value = if any positions were forked 55 | pub fn fork(&mut self, mut f: F) -> bool 56 | where 57 | for<'b> F: FnMut(ParseBuffer<'a>, &mut MatchHandler<'b, 'a, M>) -> Result<(), Error>, 58 | { 59 | debug_assert!(self.garbage.is_empty()); 60 | 61 | let mut forked = vec![]; 62 | for p in self.positions.iter_mut() { 63 | if p.state != FragmentState::Ready { 64 | continue; 65 | } 66 | p.state = FragmentState::Exhausted; 67 | let forked_input = p.input.fork(); 68 | let mut match_handler = MatchHandler { 69 | outer_position: p, 70 | forked: vec![], 71 | }; 72 | if f(forked_input, &mut match_handler).is_ok() { 73 | forked.append(&mut match_handler.forked); 74 | } 75 | } 76 | 77 | self.positions 78 | .iter() 79 | .filter(|p| p.state == FragmentState::Ready) 80 | .count() 81 | > 0 82 | } 83 | 84 | pub fn reset_states(&mut self) { 85 | for p in self.positions.iter_mut() { 86 | p.state = FragmentState::Ready; 87 | } 88 | } 89 | 90 | // returns err if the set is non-empty 91 | pub fn expect(&mut self, mut f: F) -> Result<(), Error> 92 | where 93 | for<'b> F: FnMut(ParseStream<'b>, &'b mut M) -> Result<(), Error>, 94 | { 95 | debug_assert!(self.garbage.is_empty()); 96 | 97 | for (i, p) in self.positions.iter_mut().enumerate() { 98 | match f(&p.input, &mut p.matches) { 99 | Ok(_) => {} 100 | Err(_) => { 101 | self.garbage.insert(i); 102 | } 103 | } 104 | } 105 | 106 | self.compact(); 107 | 108 | if self.positions.is_empty() { 109 | Err(Error::new(Span::call_site(), "no positions passed expect")) 110 | } else { 111 | Ok(()) 112 | } 113 | } 114 | 115 | fn compact(&mut self) { 116 | if self.garbage.is_empty() { 117 | return; 118 | } 119 | 120 | let mut new = Vec::with_capacity(self.positions.len() - self.garbage.len()); 121 | for (i, p) in self.positions.drain(..).enumerate() { 122 | if !self.garbage.contains(&i) { 123 | new.push(p); 124 | } 125 | } 126 | 127 | self.positions = new; 128 | self.garbage = HashSet::new(); 129 | } 130 | } 131 | 132 | impl<'a, M: Fork> Position<'a, M> { 133 | pub fn stream(&'a self) -> ParseStream<'a> { 134 | &self.input 135 | } 136 | 137 | pub fn fork(&self) -> Position<'a, M> { 138 | Position { 139 | input: self.input.fork(), 140 | matches: self.matches.fork(), 141 | state: FragmentState::Ready, 142 | } 143 | } 144 | } 145 | 146 | impl<'a, 'b: 'a, M: Fork> MatchHandler<'a, 'b, M> { 147 | pub fn hoist>(&mut self, positions: &[Position<'b, MM>]) { 148 | assert!(!positions.is_empty()); 149 | 150 | for p in &positions[1..] { 151 | let mut new = Position { 152 | input: p.input.fork(), 153 | matches: self.outer_position.matches.fork(), 154 | state: FragmentState::Ready, 155 | }; 156 | p.matches.hoist(&mut new.matches); 157 | self.forked.push(new); 158 | } 159 | 160 | positions[0].matches.hoist(&mut self.outer_position.matches); 161 | let _: Result = self.outer_position.input.parse(); 162 | self.outer_position.state = FragmentState::Ready; 163 | self.outer_position.input = positions[0].input.fork(); 164 | } 165 | 166 | pub fn matches(&mut self) -> &mut M { 167 | &mut self.outer_position.matches 168 | } 169 | } 170 | --------------------------------------------------------------------------------