use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem,
NestedMetaItem};
use quote::Tokens;
use std::collections::BTreeMap as Map;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
pub trait CustomDerive {
fn expand(&self, input: MacroInput) -> Result<Expanded, String>;
}
pub struct Expanded {
pub new_items: Vec<Item>,
pub original: Option<MacroInput>,
}
#[derive(Default)]
pub struct Registry<'a> {
derives: Map<String, Box<CustomDerive + 'a>>,
}
impl<T> CustomDerive for T
where T: Fn(MacroInput) -> Result<Expanded, String>
{
fn expand(&self, input: MacroInput) -> Result<Expanded, String> {
self(input)
}
}
impl<'a> Registry<'a> {
pub fn new() -> Self {
Default::default()
}
pub fn add_derive<T>(&mut self, name: &str, derive: T)
where T: CustomDerive + 'a
{
self.derives.insert(name.into(), Box::new(derive));
}
pub fn expand_file<S, D>(&self, src: S, dst: D) -> Result<(), String>
where S: AsRef<Path>,
D: AsRef<Path>
{
let mut src = match File::open(src) {
Ok(open) => open,
Err(err) => return Err(err.to_string()),
};
let mut content = String::new();
if let Err(err) = src.read_to_string(&mut content) {
return Err(err.to_string());
}
let krate = try!(super::parse_crate(&content));
let expanded = try!(expand_crate(self, krate));
let out = try!(pretty(quote!(#expanded)));
let mut dst = match File::create(dst) {
Ok(create) => create,
Err(err) => return Err(err.to_string()),
};
if let Err(err) = dst.write_all(out.as_bytes()) {
return Err(err.to_string());
}
Ok(())
}
}
fn expand_crate(reg: &Registry, krate: Crate) -> Result<Crate, String> {
let mut items = Vec::new();
for item in krate.items {
try!(expand_item(reg, item, Vec::new(), &mut items));
}
Ok(Crate { items: items, ..krate })
}
fn expand_item(reg: &Registry,
mut item: Item,
cfg: Vec<NestedMetaItem>,
out: &mut Vec<Item>)
-> Result<(), String> {
let (body, generics) = match item.node {
ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics),
ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics),
_ => {
item.attrs.extend(combine_cfgs(cfg));
out.push(item);
return Ok(());
}
};
let macro_input = MacroInput {
ident: item.ident,
vis: item.vis,
attrs: item.attrs,
generics: generics,
body: body,
};
expand_macro_input(reg, macro_input, cfg, out)
}
fn expand_macro_input(reg: &Registry,
mut input: MacroInput,
inherited_cfg: Vec<NestedMetaItem>,
out: &mut Vec<Item>)
-> Result<(), String> {
let mut derives = Vec::new();
let mut all_cfg = inherited_cfg;
input.attrs = input.attrs
.into_iter()
.flat_map(|attr| {
let (new_derives, cfg, attr) = parse_attr(reg, attr);
derives.extend(new_derives);
all_cfg.extend(cfg);
attr
})
.collect();
for derive in derives {
let expanded = try!(reg.derives[derive.name.as_ref()].expand(input));
for new_item in expanded.new_items {
let mut extended_cfg = all_cfg.clone();
extended_cfg.extend(derive.cfg.clone());
try!(expand_item(reg, new_item, extended_cfg, out));
}
input = match expanded.original {
Some(input) => input,
None => return Ok(()),
};
}
input.attrs.extend(combine_cfgs(all_cfg));
out.push(input.into());
Ok(())
}
struct Derive {
name: Ident,
cfg: Option<NestedMetaItem>,
}
fn parse_attr(reg: &Registry,
attr: Attribute)
-> (Vec<Derive>, Vec<NestedMetaItem>, Option<Attribute>) {
if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
return (Vec::new(), Vec::new(), Some(attr));
}
let (name, nested) = match attr.value {
MetaItem::List(name, nested) => (name, nested),
_ => return (Vec::new(), Vec::new(), Some(attr)),
};
match name.as_ref() {
"derive" => {
let (derives, attr) = parse_derive_attr(reg, nested);
let derives = derives.into_iter()
.map(|d| {
Derive {
name: d,
cfg: None,
}
})
.collect();
(derives, Vec::new(), attr)
}
"cfg_attr" => {
let (derives, attr) = parse_cfg_attr(reg, nested);
(derives, Vec::new(), attr)
}
"cfg" => (Vec::new(), nested, None),
_ => {
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List(name, nested),
is_sugared_doc: false,
};
(Vec::new(), Vec::new(), Some(attr))
}
}
}
fn parse_derive_attr(reg: &Registry,
nested: Vec<NestedMetaItem>)
-> (Vec<Ident>, Option<Attribute>) {
let mut derives = Vec::new();
let remaining: Vec<_> = nested.into_iter()
.flat_map(|meta| {
let word = match meta {
NestedMetaItem::MetaItem(MetaItem::Word(word)) => word,
_ => return Some(meta),
};
if reg.derives.contains_key(word.as_ref()) {
derives.push(word);
None
} else {
Some(NestedMetaItem::MetaItem(MetaItem::Word(word)))
}
})
.collect();
let attr = if remaining.is_empty() {
None
} else {
Some(Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("derive".into(), remaining),
is_sugared_doc: false,
})
};
(derives, attr)
}
fn parse_cfg_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Derive>, Option<Attribute>) {
if nested.len() != 2 {
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(), nested),
is_sugared_doc: false,
};
return (Vec::new(), Some(attr));
}
let mut iter = nested.into_iter();
let cfg = iter.next().unwrap();
let arg = iter.next().unwrap();
let (name, nested) = match arg {
NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
_ => {
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
is_sugared_doc: false,
};
return (Vec::new(), Some(attr));
}
};
if name == "derive" {
let (derives, attr) = parse_derive_attr(reg, nested);
let derives = derives.into_iter()
.map(|d| {
Derive {
name: d,
cfg: Some(cfg.clone()),
}
})
.collect();
let attr = attr.map(|attr| {
Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(),
vec![cfg, NestedMetaItem::MetaItem(attr.value)]),
is_sugared_doc: false,
}
});
(derives, attr)
} else {
let attr = Attribute {
style: AttrStyle::Outer,
value:
MetaItem::List("cfg_attr".into(),
vec![cfg, NestedMetaItem::MetaItem(MetaItem::List(name, nested))]),
is_sugared_doc: false,
};
(Vec::new(), Some(attr))
}
}
fn combine_cfgs(cfg: Vec<NestedMetaItem>) -> Option<Attribute> {
let cfg: Vec<_> = cfg.into_iter()
.flat_map(|cfg| {
let (name, nested) = match cfg {
NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
_ => return vec![cfg],
};
if name == "all" {
nested
} else {
vec![NestedMetaItem::MetaItem(MetaItem::List(name, nested))]
}
})
.collect();
let value = match cfg.len() {
0 => return None,
1 => cfg,
_ => vec![NestedMetaItem::MetaItem(MetaItem::List("all".into(), cfg))],
};
Some(Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg".into(), value),
is_sugared_doc: false,
})
}
#[cfg(not(feature = "pretty"))]
fn pretty(tokens: Tokens) -> Result<String, String> {
Ok(tokens.to_string())
}
#[cfg(feature = "pretty")]
fn pretty(tokens: Tokens) -> Result<String, String> {
use syntax::parse::{self, ParseSess};
use syntax::print::pprust;
let name = "syn".to_string();
let source = tokens.to_string();
let cfg = Vec::new();
let sess = ParseSess::new();
let krate = match parse::parse_crate_from_source_str(name, source, cfg, &sess) {
Ok(krate) => krate,
Err(mut err) => {
err.emit();
return Err("pretty printer failed to parse expanded code".into());
}
};
if sess.span_diagnostic.has_errors() {
return Err("pretty printer failed to parse expanded code".into());
}
let mut reader = &tokens.to_string().into_bytes()[..];
let mut writer = Vec::new();
let ann = pprust::NoAnn;
try!(pprust::print_crate(
sess.codemap(),
&sess.span_diagnostic,
&krate,
"".to_string(),
&mut reader,
Box::new(&mut writer),
&ann,
false).map_err(|err| err.to_string()));
String::from_utf8(writer).map_err(|err| err.to_string())
}