#![feature(plugin, plugin_registrar, quote, rustc_private)]
#![plugin(easy_plugin_plugins)]
#![warn(missing_copy_implementations, missing_debug_implementations, missing_docs)]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", warn(clippy))]
#![cfg_attr(feature="clippy", allow(similar_names, used_underscore_binding))]
extern crate rustc_plugin;
extern crate syntax;
use rustc_plugin::{Registry};
use syntax::ast::*;
use syntax::codemap::{Span, Spanned};
use syntax::ext::base::{ExtCtxt, DummyResult, MacEager, MacResult};
use syntax::ext::build::{AstBuilder};
use syntax::parse::token::{DelimToken, Token};
use syntax::ptr::{P};
use syntax::util::small_vector::{SmallVector};
pub mod convert;
mod utility;
use utility::{ToExpr};
pub use utility::{PluginResultExt, ToError};
mod arguments;
pub use self::arguments::*;
#[macro_use]
mod specification;
pub use self::specification::*;
pub type PluginResult<T> = Result<T, (Span, String)>;
fn strip_function(
context: &mut ExtCtxt, function: P<Item>
) -> (P<Item>, Ident, Visibility, Vec<Attribute>) {
let ident = function.ident;
let visibility = function.vis.clone();
let attributes = function.attrs.clone();
let function = function.map(|mut f| {
f.ident = context.ident_of(&format!("{}_", ident.name));
f.vis = Visibility::Inherited;
f.attrs = vec![];
f
});
(function, ident, visibility, attributes)
}
fn expand_struct(
context: &mut ExtCtxt, span: Span, name: Ident, specification: &Specification
) -> P<Item> {
let fields = specification.iter().flat_map(|s| {
s.to_struct_fields(context, span).into_iter()
}).collect::<Vec<_>>();
let struct_ = if fields.is_empty() {
quote_item!(context, struct $name;).unwrap()
} else {
context.item_struct(span, name, VariantData::Struct(fields, DUMMY_NODE_ID))
};
struct_.map(|mut s| {
s.attrs = vec![quote_attr!(context, #[derive(Clone, Debug)])];
s
})
}
fn expand_structs(
context: &mut ExtCtxt, span: Span, specifications: &[(Ident, Specification)]
) -> Vec<P<Item>> {
specifications.iter().map(|&(n, ref s)| expand_struct(context, span, n, s)).collect()
}
fn expand_parse(
context: &mut ExtCtxt, span: Span, name: Ident, specification: &Specification, multiple: bool
) -> P<Item> {
let function = if multiple {
context.ident_of(&format!("parse{}", name.name))
} else {
context.ident_of("parse")
};
let fields = specification.iter().flat_map(|s| {
s.to_fields(context, span, &[]).into_iter()
}).collect::<Vec<_>>();
let result = if fields.is_empty() {
quote_expr!(context, $name)
} else {
let path = context.path(span, vec![name]);
context.expr_struct(span, path, fields)
};
let specification = specification.to_expr(context, span);
quote_item!(context,
#[allow(non_snake_case)]
fn $function(
session: &::syntax::parse::ParseSess, arguments: &[::syntax::ast::TokenTree]
) -> ::easy_plugin::PluginResult<$name> {
::easy_plugin::parse_args(session, arguments, &$specification.0).map(|_m| $result)
}
).unwrap()
}
fn expand_parses(
context: &mut ExtCtxt, span: Span, specifications: &[(Ident, Specification)]
) -> Vec<P<Item>> {
specifications.iter().map(|&(n, ref s)| expand_parse(context, span, n, s, true)).collect()
}
fn expand_easy_plugin_enum_(
context: &mut ExtCtxt,
span: Span,
arguments: Ident,
names: Vec<Ident>,
ttss: Vec<Vec<TokenTree>>,
function: P<Item>,
) -> PluginResult<Box<MacResult + 'static>> {
let specifications: Result<Vec<_>, _> = names.iter().zip(ttss.into_iter()).map(|(n, tts)| {
parse_spec(&tts).map(|s| (*n, s))
}).collect();
let specifications = try!(specifications);
let structs = expand_structs(context, span, &specifications);
let variants = names.iter().map(|n| {
context.variant(span, *n, vec![quote_ty!(context, $n)])
}).collect();
let enum_ = context.item_enum(span, arguments, EnumDef { variants: variants }).map(|mut e| {
e.attrs = vec![quote_attr!(context, #[derive(Clone, Debug)])];
e
});
let parses = expand_parses(context, span, &specifications);
let (function, identifier, visibility, attributes) = strip_function(context, function);
let inner = function.ident;
let stmts = names.iter().enumerate().map(|(i, ref n)| {
let parse = context.ident_of(&format!("parse{}", n));
if i + 1 < specifications.len() {
quote_stmt!(context,
if let Ok(arguments) = $parse(context.parse_sess, arguments) {
return match $inner(context, span, $arguments::$n(arguments)) {
Ok(result) => result,
Err((subspan, message)) => {
let span = if subspan == ::syntax::codemap::DUMMY_SP {
span
} else {
subspan
};
context.span_err(span, &message);
::syntax::ext::base::DummyResult::any(span)
}
};
}
)
} else {
quote_stmt!(context,
return match $parse(context.parse_sess, arguments).and_then(|a| {
$inner(context, span, $arguments::$n(a))
}) {
Ok(result) => result,
Err((subspan, message)) => {
let span = if subspan == ::syntax::codemap::DUMMY_SP {
span
} else {
subspan
};
context.span_err(span, &message);
::syntax::ext::base::DummyResult::any(span)
}
};
)
}
}).collect::<Vec<_>>();
let item = quote_item!(context,
fn $identifier(
context: &mut ::syntax::ext::base::ExtCtxt,
span: ::syntax::codemap::Span,
arguments: &[::syntax::ast::TokenTree],
) -> ::std::boxed::Box<::syntax::ext::base::MacResult> {
$structs
$enum_
$parses
$function
$stmts
}
).unwrap().map(|mut i| {
i.vis = visibility;
i.attrs = attributes;
i
});
Ok(MacEager::items(SmallVector::one(item)))
}
fn expand_easy_plugin_enum(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> PluginResult<Box<MacResult + 'static>> {
let specification = &[
Specifier::specific_ident("enum"),
Specifier::Ident("arguments".into()),
Specifier::Delimited(DelimToken::Brace, spec![
Specifier::Sequence(Amount::ZeroOrMore, None, spec![
Specifier::Ident("name".into()),
Specifier::Delimited(DelimToken::Brace, spec![
Specifier::Sequence(Amount::ZeroOrMore, None, spec![
Specifier::Tt("tt".into()),
]),
]),
Specifier::Specific(Token::Comma),
]),
]),
Specifier::Item("function".into()),
];
let matches = try!(parse_args(context.parse_sess, arguments, specification));
let arguments = matches.get("arguments").unwrap().to::<Spanned<Ident>>().node;
let names = matches.get("name").unwrap().to::<Vec<Match>>().into_iter().map(|s| {
s.to::<Spanned<Ident>>().node
}).collect();
let ttss = matches.get("tt").unwrap().to::<Vec<Match>>().into_iter().map(|s| {
s.to::<Vec<Match>>().into_iter().map(|s| s.to::<TokenTree>()).collect::<Vec<_>>()
}).collect();
let function = matches.get("function").unwrap().to::<P<Item>>();
expand_easy_plugin_enum_(context, span, arguments, names, ttss, function)
}
fn expand_easy_plugin_struct_(
context: &mut ExtCtxt, span: Span, arguments: Ident, tts: Vec<TokenTree>, function: P<Item>
) -> PluginResult<Box<MacResult + 'static>> {
let specification = try!(parse_spec(&tts));
let struct_ = expand_struct(context, span, arguments, &specification);
let parse = expand_parse(context, span, arguments, &specification, false);
let (function, identifier, visibility, attributes) = strip_function(context, function);
let inner = function.ident;
let item = quote_item!(context,
fn $identifier(
context: &mut ::syntax::ext::base::ExtCtxt,
span: ::syntax::codemap::Span,
arguments: &[::syntax::ast::TokenTree],
) -> ::std::boxed::Box<::syntax::ext::base::MacResult> {
$struct_
$parse
$function
match parse(context.parse_sess, arguments).and_then(|a| $inner(context, span, a)) {
Ok(result) => result,
Err((subspan, message)) => {
let span = if subspan == ::syntax::codemap::DUMMY_SP {
span
} else {
subspan
};
context.span_err(span, &message);
::syntax::ext::base::DummyResult::any(span)
},
}
}
).unwrap().map(|mut i| {
i.vis = visibility;
i.attrs = attributes;
i
});
Ok(MacEager::items(SmallVector::one(item)))
}
fn expand_easy_plugin_struct(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> PluginResult<Box<MacResult + 'static>> {
let specification = &[
Specifier::specific_ident("struct"),
Specifier::Ident("arguments".into()),
Specifier::Delimited(DelimToken::Brace, spec![
Specifier::Sequence(Amount::ZeroOrMore, None, spec![
Specifier::Tt("tt".into()),
]),
]),
Specifier::Item("function".into()),
];
let matches = try!(parse_args(context.parse_sess, arguments, specification));
let arguments = matches.get("arguments").unwrap().to::<Spanned<Ident>>().node;
let tts = matches.get("tt").unwrap().to::<Vec<Match>>().iter().map(|s| {
s.to::<TokenTree>()
}).collect::<Vec<_>>();
let function = matches.get("function").unwrap().to::<P<Item>>();
expand_easy_plugin_struct_(context, span, arguments, tts, function)
}
fn expand_easy_plugin_(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> PluginResult<Box<MacResult + 'static>> {
if arguments.is_empty() {
return span.to_error("unexpected end of arguments");
}
match arguments[0] {
TokenTree::Token(_, Token::Ident(ref ident)) => match &*ident.name.as_str() {
"enum" => expand_easy_plugin_enum(context, span, arguments),
"struct" => expand_easy_plugin_struct(context, span, arguments),
_ => arguments[0].to_error("expected `enum` or `struct`"),
},
_ => arguments[0].to_error("expected `enum` or `struct`"),
}
}
#[doc(hidden)]
pub fn expand_easy_plugin(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> Box<MacResult + 'static> {
match expand_easy_plugin_(context, span, arguments) {
Ok(result) => result,
Err((span, message)) => {
context.span_err(span, &message);
DummyResult::any(span)
},
}
}
#[doc(hidden)]
#[plugin_registrar]
pub fn plugin_registrar(registry: &mut Registry) {
registry.register_macro("easy_plugin", expand_easy_plugin);
registry.register_macro("parse_spec", expand_parse_spec);
}