#![feature(plugin_registrar, quote, rustc_private)]
#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", warn(clippy))]
extern crate rustc_plugin;
extern crate syntax;
use std::rc::{Rc};
use rustc_plugin::{Registry};
use syntax::print::pprust;
use syntax::ast::*;
use syntax::codemap::{self, Span};
use syntax::ext::base::{ExtCtxt, MacEager, MacResult};
use syntax::ext::build::{AstBuilder};
use syntax::parse::token::{self, Token};
use syntax::ptr::{P};
use syntax::tokenstream::{Delimited, TokenTree};
use syntax::util::small_vector::{SmallVector};
#[derive(Copy, Clone, Debug)]
enum Call {
Clone,
ToString,
AsStrToString,
MapAsStrToString,
}
impl Call {
fn from_specifier(specifier: &str) -> Call {
match specifier {
"ts" => Call::ToString,
"asts" => Call::AsStrToString,
"masts" => Call::MapAsStrToString,
_ => Call::Clone,
}
}
fn to_expr(self, context: &mut ExtCtxt, ident: Ident) -> P<Expr> {
match self {
Call::Clone => quote_expr!(context, $ident.clone()),
Call::ToString => quote_expr!(context, $ident.to_string()),
Call::AsStrToString => quote_expr!(context, $ident.as_str().to_string()),
Call::MapAsStrToString => quote_expr!(context, $ident.map(|f| f.as_str().to_string())),
}
}
}
fn get_doc_comment(context: &mut ExtCtxt, span: Span, value: &str) -> P<MetaItem> {
let value = LitKind::Str(token::intern_and_get_ident(value), StrStyle::Cooked);
context.meta_name_value(span, token::intern_and_get_ident("doc"), value)
}
fn get_lit_str(span: Span, value: &str) -> Lit {
codemap::respan(span, LitKind::Str(token::intern_and_get_ident(value), StrStyle::Cooked))
}
fn parse_expr(context: &mut ExtCtxt, tts: &[TokenTree]) -> P<Expr> {
context.new_parser_from_tts(tts).parse_expr().unwrap()
}
fn parse_ty(context: &mut ExtCtxt, tts: &[TokenTree]) -> P<Ty> {
context.new_parser_from_tts(tts).parse_ty().unwrap()
}
fn to_delimited(tt: &TokenTree) -> Rc<Delimited> {
match tt {
&TokenTree::Delimited(_, ref delimited) => delimited.clone(),
_ => unreachable!(),
}
}
fn to_ident(tt: &TokenTree) -> Ident {
match tt {
&TokenTree::Token(_, Token::Ident(ref name)) => name.clone(),
_ => unreachable!(),
}
}
fn camel_case_to_snake_case(ident: Ident) -> String {
let mut snake = String::new();
for c in ident.name.as_str().chars() {
if c.is_uppercase() {
if !snake.is_empty() {
snake.push('_');
}
snake.extend(c.to_lowercase());
} else {
snake.push(c);
}
}
snake
}
fn expand_convert_fn_doc_comment(
context: &mut ExtCtxt, span: Span, pty: &P<Ty>, dname: Ident, dvariant: Ident, fields: usize
) -> P<MetaItem> {
let ty = pprust::ty_to_string(&pty);
let doc = match fields {
0 => format!("Returns `Ok` if the supplied `{}` is `{}::{}`", ty, dname, dvariant),
1 => format!("Returns the `{}::{}` value in the supplied `{}`.", dname, dvariant, ty),
_ => format!("Returns the `{}::{}` values in the supplied `{}`.", dname, dvariant, ty),
};
get_doc_comment(context, span, &doc)
}
fn expand_convert_fn(
context: &mut ExtCtxt,
span: Span,
arguments: &[TokenTree],
pname: Ident,
pty: &P<Ty>,
pexpr: &P<Expr>,
dname: Ident,
) -> P<Item> {
let dvariant = to_ident(&arguments[0]);
let dfields = to_delimited(&arguments[1]);
let rty = parse_ty(context, &arguments[3..4]);
let doc = expand_convert_fn_doc_comment(context, span, pty, dname, dvariant, dfields.tts.len());
let name = context.ident_of(&format!("{}_to_{}", pname, camel_case_to_snake_case(dvariant)));
let fields = dfields.tts.iter().filter_map(|tt| {
match tt {
&TokenTree::Token(_, Token::Underscore) => Some("_".into()),
&TokenTree::Token(_, Token::Ident(ref ident)) => Some(ident.name.as_str().to_string()),
_ => None,
}
}).enumerate().map(|(i, s)| {
let mut buffer = String::new();
buffer.push(((97 + i) as u8) as char);
(Call::from_specifier(&s), context.ident_of(&buffer))
}).collect::<Vec<_>>();
let path = context.path_all(span, false, vec![dname, dvariant], vec![], vec![], vec![]);
let pats = fields.iter().cloned().map(|(_, p)| quote_pat!(context, ref $p)).collect();
let pat = context.pat_enum(span, path, pats);
let mut exprs = fields.into_iter().map(|(c, p)| c.to_expr(context, p)).collect::<Vec<_>>();
let expr = if exprs.len() == 1 {
exprs.remove(0)
} else {
context.expr_tuple(span, exprs)
};
let message = get_lit_str(span, &format!("expected `{}::{}` {}", dname, dvariant, pname));
quote_item!(context,
#[$doc]
pub fn $name($pname: &$pty) -> PluginResult<$rty> {
match $pexpr {
$pat => Ok($expr),
_ => $pname.to_error($message),
}
}
).unwrap()
}
fn expand_convert_exhaustive_fn(
context: &mut ExtCtxt,
pname: Ident,
pty: &P<Ty>,
pexpr: &P<Expr>,
dname: Ident,
dconverters: &Rc<Delimited>,
) -> P<Item> {
let name = context.ident_of(&format!("{}_exhaustive", pname));
let variants = dconverters.tts.chunks(5).map(|c| {
let name = &c[0];
if to_delimited(&c[1]).tts.is_empty() {
quote_arm!(context, $dname::$name => { })
} else {
quote_arm!(context, $dname::$name(..) => { })
}
}).collect::<Vec<_>>();
quote_item!(context,
#[allow(dead_code)]
fn $name($pname: &$pty) {
match $pexpr {
$variants
}
}
).unwrap()
}
fn expand_convert(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> Box<MacResult + 'static> {
assert_eq!(arguments.len(), 8);
let pname = to_ident(&arguments[0]);
let pty = match &arguments[2] {
&TokenTree::Delimited(_, ref delimited) => delimited.tts.clone(),
tt @ _ => vec![tt.clone()],
};
let pty = parse_ty(context, &pty[..]);
let pexpr = parse_expr(context, &to_delimited(&arguments[3]).tts[..]);
let dname = to_ident(&arguments[5]);
let dconverters = to_delimited(&arguments[7]);
assert_eq!(dconverters.tts.len() % 5, 0);
let mut items = dconverters.tts.chunks(5).map(|c| {
expand_convert_fn(context, span, c, pname, &pty, &pexpr, dname)
}).collect::<Vec<_>>();
items.push(expand_convert_exhaustive_fn(context, pname, &pty, &pexpr, dname, &dconverters));
MacEager::items(SmallVector::many(items))
}
#[doc(hidden)]
#[plugin_registrar]
pub fn plugin_registrar(registry: &mut Registry) {
registry.register_macro("__easy_plugin_convert", expand_convert);
}