use quote::{ToTokens, Tokens};
use std::collections::HashSet;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use syn;
use syn::fold::Fold;
use proc_macro2::TokenStream;
pub fn expand(from: &Path, to: &Path) {
let mut source = String::new();
File::open(from).unwrap().read_to_string(&mut source).unwrap();
let ast = syn::parse_file(&source).expect("Parsing rules.rs module");
let mut m = MatchTokenParser {};
let ast = m.fold_file(ast);
let code = ast.into_tokens().to_string().replace("{ ", "{\n").replace(" }", "\n}");
File::create(to).unwrap().write_all(code.as_bytes()).unwrap();
}
struct MatchTokenParser {}
struct MatchToken {
expr: syn::Expr,
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)]
pub struct Tag {
kind: TagKind,
name: Option<syn::Ident>,
}
impl syn::synom::Synom for Tag {
named!(parse -> Self, do_parse!(
punct!(<) >>
closing: option!(punct!(/)) >>
name: alt!(
syn!(syn::Ident) => { |i| Some(i) }
|
punct!(_) => { |_| None }
) >>
punct!(>) >>
(
Tag {
kind: if closing.is_some() { TagKind::EndTag } else { TagKind::StartTag },
name: name
}
)
));
}
impl syn::synom::Synom for LHS {
named!(parse -> Self, do_parse!(
pats: alt!(
syn!(syn::Pat) => { |p| LHS::Pattern(p) }
|
many0!(syn!(Tag)) => { |t| LHS::Tags(t) }
) >>
(
pats
)
));
}
impl syn::synom::Synom for MatchTokenArm {
named!(parse -> Self, do_parse!(
binding: option!(
do_parse!(
name: syn!(syn::Ident) >>
punct!(@) >>
(
name
)
)
) >>
lhs: syn!(LHS) >>
punct!(=>) >>
rhs: do_parse!(
expr: alt!(
expr_nosemi => { |e| RHS::Expression(e) }
|
syn!(syn::Expr) => { |e| RHS::Expression(e) }
|
keyword!(else) => { |_| RHS::Else }
) >>
option!(punct!(,)) >>
(expr)
) >>
(
MatchTokenArm {
binding,
lhs,
rhs,
}
)
));
}
impl syn::synom::Synom for MatchToken {
named!(parse -> Self, do_parse!(
expr: syn!(syn::Expr) >>
arms: braces!(many0!(MatchTokenArm::parse)) >> (
MatchToken {
expr,
arms: arms.1
}
)
));
}
pub fn expand_match_token(body: &TokenStream) -> syn::Expr {
let match_token = syn::parse2::<MatchToken>(body.clone());
let ast = expand_match_token_macro(match_token.unwrap());
syn::parse2(ast.into()).unwrap()
}
fn expand_match_token_macro(match_token: MatchToken) -> Tokens {
let mut arms = match_token.arms;
let to_be_matched = match_token.expr;
let last_arm = arms.pop().unwrap();
let mut seen_tags: HashSet<Tag> = HashSet::new();
let mut wildcards_patterns: Vec<Tokens> = Vec::new();
let mut wildcards_expressions: Vec<syn::Expr> = Vec::new();
let mut wild_excluded_patterns: Vec<Tokens> = 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 {:?} {:?}", pat, expr);
}
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(&Tokens::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,
}
}
}
}
}
impl Fold for MatchTokenParser {
fn fold_stmt(&mut self, stmt: syn::Stmt) -> syn::Stmt {
match stmt {
syn::Stmt::Item(syn::Item::Macro(syn::ItemMacro{ ref mac, .. })) => {
if mac.path == parse_quote!(match_token) {
return syn::fold::fold_stmt(self, syn::Stmt::Expr(expand_match_token(&mac.tts)))
}
},
_ => {}
}
syn::fold::fold_stmt(self, stmt)
}
fn fold_expr(&mut self, expr: syn::Expr) -> syn::Expr {
match expr {
syn::Expr::Macro(syn::ExprMacro{ ref mac, .. }) => {
if mac.path == parse_quote!(match_token) {
return syn::fold::fold_expr(self, expand_match_token(&mac.tts))
}
},
_ => {}
}
syn::fold::fold_expr(self, expr)
}
}
fn make_tag_pattern(binding: &Tokens, tag: Tag) -> Tokens {
let kind = match tag.kind {
TagKind::StartTag => quote!(::tokenizer::StartTag),
TagKind::EndTag => quote!(::tokenizer::EndTag),
};
let name_field = if let Some(name) = tag.name {
let name = name.to_string();
quote!(name: local_name!(#name),)
} else {
quote!()
};
quote! {
::tree_builder::types::TagToken(#binding ::tokenizer::Tag { kind: #kind, #name_field .. })
}
}
named!(expr_nosemi -> syn::Expr, map!(alt!(
syn!(syn::ExprIf) => { syn::Expr::If }
|
syn!(syn::ExprIfLet) => { syn::Expr::IfLet }
|
syn!(syn::ExprWhile) => { syn::Expr::While }
|
syn!(syn::ExprWhileLet) => { syn::Expr::WhileLet }
|
syn!(syn::ExprForLoop) => { syn::Expr::ForLoop }
|
syn!(syn::ExprLoop) => { syn::Expr::Loop }
|
syn!(syn::ExprMatch) => { syn::Expr::Match }
|
syn!(syn::ExprCatch) => { syn::Expr::Catch }
|
syn!(syn::ExprYield) => { syn::Expr::Yield }
|
syn!(syn::ExprUnsafe) => { syn::Expr::Unsafe }
|
syn!(syn::ExprBlock) => { syn::Expr::Block }
), syn::Expr::from));