extern crate proc_macro;
use quote::quote;
use syn::{braced, Token};
use std::collections::HashSet;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result};
#[proc_macro]
pub fn match_token(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
let match_token = syn::parse2::<MatchToken>(input).expect("Parsing match_token! input failed");
let output = expand_match_token_macro(match_token);
proc_macro::TokenStream::from(output)
}
struct MatchToken {
ident: syn::Ident,
arms: Vec<MatchTokenArm>,
}
struct MatchTokenArm {
binding: Option<syn::Ident>,
lhs: Lhs,
rhs: Rhs,
}
enum Lhs {
Tags(Vec<Tag>),
Pattern(syn::Pat),
}
enum Rhs {
Expression(syn::Expr),
Else,
}
#[derive(PartialEq, Eq, Hash, Clone)]
enum TagKind {
StartTag,
EndTag,
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct Tag {
kind: TagKind,
name: Option<syn::Ident>,
}
impl Parse for Tag {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Token![<]>()?;
let closing: Option<Token![/]> = input.parse()?;
let name = match input.call(syn::Ident::parse_any)? {
ref wildcard if wildcard == "_" => None,
other => Some(other),
};
input.parse::<Token![>]>()?;
Ok(Tag {
kind: if closing.is_some() {
TagKind::EndTag
} else {
TagKind::StartTag
},
name,
})
}
}
impl Parse for Lhs {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Token![<]) {
let mut tags = Vec::new();
while !input.peek(Token![=>]) {
tags.push(input.parse()?);
}
Ok(Lhs::Tags(tags))
} else {
let p = input.call(syn::Pat::parse_single)?;
Ok(Lhs::Pattern(p))
}
}
}
impl Parse for MatchTokenArm {
fn parse(input: ParseStream) -> Result<Self> {
let binding = if input.peek2(Token![@]) {
let binding = input.parse::<syn::Ident>()?;
input.parse::<Token![@]>()?;
Some(binding)
} else {
None
};
let lhs = input.parse::<Lhs>()?;
input.parse::<Token![=>]>()?;
let rhs = if input.peek(syn::token::Brace) {
let block = input.parse::<syn::Block>().unwrap();
let block = syn::ExprBlock {
attrs: vec![],
label: None,
block,
};
input.parse::<Option<Token![,]>>()?;
Rhs::Expression(syn::Expr::Block(block))
} else if input.peek(Token![else]) {
input.parse::<Token![else]>()?;
input.parse::<Token![,]>()?;
Rhs::Else
} else {
let expr = input.parse::<syn::Expr>().unwrap();
input.parse::<Option<Token![,]>>()?;
Rhs::Expression(expr)
};
Ok(MatchTokenArm { binding, lhs, rhs })
}
}
impl Parse for MatchToken {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse::<syn::Ident>()?;
let content;
braced!(content in input);
let mut arms = vec![];
while !content.is_empty() {
arms.push(content.parse()?);
}
Ok(MatchToken { ident, arms })
}
}
fn expand_match_token_macro(match_token: MatchToken) -> proc_macro2::TokenStream {
let mut arms = match_token.arms;
let to_be_matched = match_token.ident;
let last_arm = arms.pop().unwrap();
let mut seen_tags: HashSet<Tag> = HashSet::new();
let mut wildcards_patterns: Vec<proc_macro2::TokenStream> = Vec::new();
let mut wildcards_expressions: Vec<syn::Expr> = Vec::new();
let mut wild_excluded_patterns: Vec<proc_macro2::TokenStream> = Vec::new();
let mut arms_code = Vec::new();
for MatchTokenArm { binding, lhs, rhs } in arms {
let binding = match binding {
Some(ident) => quote!(#ident @),
None => quote!(),
};
match (lhs, rhs) {
(Lhs::Pattern(_), Rhs::Else) => {
panic!("'else' may not appear with an ordinary pattern")
},
(Lhs::Pattern(pat), Rhs::Expression(expr)) => {
if !wildcards_patterns.is_empty() {
panic!("ordinary patterns may not appear after wildcard tags");
}
arms_code.push(quote!(#binding #pat => #expr,))
},
(Lhs::Tags(tags), Rhs::Else) => {
for tag in tags {
if !seen_tags.insert(tag.clone()) {
panic!("duplicate tag");
}
if tag.name.is_none() {
panic!("'else' may not appear with a wildcard tag");
}
wild_excluded_patterns
.push(make_tag_pattern(&proc_macro2::TokenStream::new(), tag));
}
},
(Lhs::Tags(tags), Rhs::Expression(expr)) => {
let mut wildcard = None;
for tag in tags {
if !seen_tags.insert(tag.clone()) {
panic!("duplicate tag");
}
match tag.name {
Some(_) => {
if !wildcards_patterns.is_empty() {
panic!("specific tags may not appear after wildcard tags");
}
if wildcard == Some(true) {
panic!("wildcard tags must appear alone");
}
if wildcard.is_some() {
arms_code.push(quote!( | ))
}
arms_code.push(make_tag_pattern(&binding, tag));
wildcard = Some(false);
},
None => {
if wildcard.is_some() {
panic!("wildcard tags must appear alone");
}
wildcard = Some(true);
wildcards_patterns.push(make_tag_pattern(&binding, tag));
wildcards_expressions.push(expr.clone());
},
}
}
match wildcard {
None => panic!("[internal macro error] tag arm with no tags"),
Some(false) => arms_code.push(quote!( => #expr,)),
Some(true) => {}, }
},
}
}
let MatchTokenArm { binding, lhs, rhs } = last_arm;
let (last_pat, last_expr) = match (binding, lhs, rhs) {
(Some(_), _, _) => panic!("the last arm cannot have an @-binding"),
(None, Lhs::Tags(_), _) => panic!("the last arm cannot have tag patterns"),
(None, _, Rhs::Else) => panic!("the last arm cannot use 'else'"),
(None, Lhs::Pattern(p), Rhs::Expression(e)) => (p, e),
};
quote! {
match #to_be_matched {
#(
#arms_code
)*
last_arm_token => {
let enable_wildcards = match last_arm_token {
#(
#wild_excluded_patterns => false,
)*
_ => true,
};
match (enable_wildcards, last_arm_token) {
#(
(true, #wildcards_patterns) => #wildcards_expressions,
)*
(_, #last_pat) => #last_expr,
}
}
}
}
}
fn make_tag_pattern(binding: &proc_macro2::TokenStream, tag: Tag) -> proc_macro2::TokenStream {
let kind = match tag.kind {
TagKind::StartTag => quote!(crate::tokenizer::StartTag),
TagKind::EndTag => quote!(crate::tokenizer::EndTag),
};
let name_field = if let Some(name) = tag.name {
let name = name.to_string();
quote!(name: local_name!(#name),)
} else {
quote!()
};
quote! {
crate::tree_builder::types::Token::Tag(#binding crate::tokenizer::Tag { kind: #kind, #name_field .. })
}
}