use quote::{ToTokens, Tokens};
use self::visit::{Visitor, RecursiveVisitor};
use std::collections::HashSet;
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
use std::path::Path;
use std::slice;
use syn;
mod visit;
pub fn expand_match_tokens(from: &Path, to: &Path) {
let mut source = String::new();
File::open(from).unwrap().read_to_string(&mut source).unwrap();
let mut crate_ = syn::parse_crate(&source).expect("Parsing rules.rs module");
RecursiveVisitor { node_visitor: ExpanderVisitor }.visit_crate(&mut crate_);
let mut tokens = Tokens::new();
crate_.to_tokens(&mut tokens);
let code = tokens.to_string().replace("{ ", "{\n").replace(" }", "\n}");
File::create(to).unwrap().write_all(code.as_bytes()).unwrap();
}
struct ExpanderVisitor;
impl Visitor for ExpanderVisitor {
fn visit_expression(&mut self, expr: &mut syn::Expr) {
let tts;
if let syn::Expr::Mac(ref mut macro_) = *expr {
if macro_.path == syn::Path::from("match_token") {
tts = mem::replace(&mut macro_.tts, Vec::new());
} else {
return
}
} else {
return
}
let (to_be_matched, arms) = parse_match_token_macro(tts);
let tokens = expand_match_token_macro(to_be_matched, arms);
*expr = syn::parse_expr(&tokens.to_string()).expect("Parsing a match expression");
}
}
fn parse_match_token_macro(tts: Vec<syn::TokenTree>) -> (syn::Ident, Vec<Arm>) {
use syn::TokenTree::Delimited;
use syn::DelimToken::{Brace, Paren};
let mut tts = tts.into_iter();
let inner_tts = if let Some(Delimited(syn::Delimited { delim: Paren, tts })) = tts.next() {
tts
} else {
panic!("expected one top-level () block")
};
assert_eq!(tts.len(), 0);
let mut tts = inner_tts.into_iter();
let ident = if let Some(syn::TokenTree::Token(syn::Token::Ident(ident))) = tts.next() {
ident
} else {
panic!("expected ident")
};
let block = if let Some(Delimited(syn::Delimited { delim: Brace, tts })) = tts.next() {
tts
} else {
panic!("expected one {} block")
};
assert_eq!(tts.len(), 0);
let mut tts = block.iter();
let mut arms = Vec::new();
while tts.len() > 0 {
arms.push(parse_arm(&mut tts))
}
(ident, arms)
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
enum TagKind {
StartTag,
EndTag,
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct Tag {
kind: TagKind,
name: Option<syn::Ident>,
}
#[derive(Debug)]
enum LHS {
Pattern(Tokens),
Tags(Vec<Tag>),
}
#[derive(Debug)]
enum RHS {
Else,
Expression(Tokens),
}
#[derive(Debug)]
struct Arm {
binding: Option<syn::Ident>,
lhs: LHS,
rhs: RHS,
}
fn parse_arm(tts: &mut slice::Iter<syn::TokenTree>) -> Arm {
Arm {
binding: parse_binding(tts),
lhs: parse_lhs(tts),
rhs: parse_rhs(tts),
}
}
fn parse_binding(tts: &mut slice::Iter<syn::TokenTree>) -> Option<syn::Ident> {
let start = tts.clone();
if let (Some(&syn::TokenTree::Token(syn::Token::Ident(ref ident))),
Some(&syn::TokenTree::Token(syn::Token::At))) = (tts.next(), tts.next()) {
Some(ident.clone())
} else {
*tts = start;
None
}
}
fn consume_if_present(tts: &mut slice::Iter<syn::TokenTree>, expected: syn::Token) -> bool {
if let Some(&syn::TokenTree::Token(ref first)) = tts.as_slice().first() {
if *first == expected {
tts.next();
return true
}
}
false
}
fn parse_lhs(tts: &mut slice::Iter<syn::TokenTree>) -> LHS {
if consume_if_present(tts, syn::Token::Lt) {
let mut tags = Vec::new();
loop {
tags.push(Tag {
kind: if consume_if_present(tts, syn::Token::BinOp(syn::BinOpToken::Slash)) {
TagKind::EndTag
} else {
TagKind::StartTag
},
name: if consume_if_present(tts, syn::Token::Underscore) {
None
} else {
if let Some(&syn::TokenTree::Token(syn::Token::Ident(ref ident))) = tts.next() {
Some(ident.clone())
} else {
panic!("expected identifier (tag name)")
}
}
});
assert!(consume_if_present(tts, syn::Token::Gt), "expected '>' closing a tag pattern");
if !consume_if_present(tts, syn::Token::Lt) {
break
}
}
assert!(consume_if_present(tts, syn::Token::FatArrow));
LHS::Tags(tags)
} else {
let mut pattern = Tokens::new();
for tt in tts {
if let &syn::TokenTree::Token(syn::Token::FatArrow) = tt {
return LHS::Pattern(pattern)
}
tt.to_tokens(&mut pattern)
}
panic!("did not find =>")
}
}
fn parse_rhs(tts: &mut slice::Iter<syn::TokenTree>) -> RHS {
use syn::DelimToken::Brace;
let start = tts.clone();
let first = tts.next();
let after_first = tts.clone();
let second = tts.next();
if let (Some(&syn::TokenTree::Token(syn::Token::Ident(ref ident))),
Some(&syn::TokenTree::Token(syn::Token::Comma))) = (first, second) {
if ident == "else" {
return RHS::Else
}
}
let mut expression = Tokens::new();
if let Some(&syn::TokenTree::Delimited(syn::Delimited { delim: Brace, .. })) = first {
first.to_tokens(&mut expression);
*tts = after_first;
consume_if_present(tts, syn::Token::Comma);
} else {
*tts = start;
for tt in tts {
tt.to_tokens(&mut expression);
if let &syn::TokenTree::Token(syn::Token::Comma) = tt {
break
}
}
}
RHS::Expression(expression)
}
fn expand_match_token_macro(to_be_matched: syn::Ident, mut arms: Vec<Arm>) -> Tokens {
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<Tokens> = Vec::new();
let mut wild_excluded_patterns: Vec<Tokens> = Vec::new();
let mut arms_code = Vec::new();
for Arm { 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 Arm { 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: &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: atom!(#name),)
} else {
quote!()
};
quote! {
::tree_builder::types::TagToken(#binding ::tokenizer::Tag { kind: #kind, #name_field .. })
}
}