use syntax::ast::{
self,
Attribute,
EnumDef,
Expr,
Ident,
Item,
ItemKind,
Mac_,
TokenTree,
Variant,
Visibility,
};
use syntax::codemap::{self, Span};
use syntax::errors::DiagnosticBuilder;
use syntax::ext::base::ExtCtxt;
use syntax::ext::build::AstBuilder;
use syntax::parse::token::{DelimToken, InternedString, Token};
use syntax::parse::token::keywords;
use syntax::ptr::P;
use IntLit;
#[derive(Debug)]
pub struct IntegerEnum {
pub attrs: Vec<Attribute>,
pub is_pub: bool,
pub name: Ident,
pub repr: Ident,
pub min: P<Expr>,
pub max: P<Expr>,
}
impl IntegerEnum {
pub fn parse_tts<'a>(
cx: &'a ExtCtxt,
tts: &[TokenTree],
) -> Result<Self, DiagnosticBuilder<'a>> {
let mut parser = cx.new_parser_from_tts(tts);
let attrs = try!(parser.parse_outer_attributes());
let is_pub = parser.eat_keyword(keywords::Pub);
try!(parser.expect_keyword(keywords::Enum));
let name = try!(parser.parse_ident());
try!(parser.expect(&Token::Colon));
let repr = try!(parser.parse_ident());
try!(parser.expect(&Token::OpenDelim(DelimToken::Brace)));
let min = try!(parser.parse_pat_literal_maybe_minus());
let min_lit = match IntLit::from_expr(&*min) {
Ok(l) => l,
Err(_) => return Err(parser.span_fatal(min.span, "expected integer literal")),
};
try!(parser.expect(&Token::DotDotDot));
let max = try!(parser.parse_pat_literal_maybe_minus());
let max_lit = match IntLit::from_expr(&*max) {
Ok(l) => l,
Err(_) => return Err(parser.span_fatal(max.span, "expected integer literal")),
};
try!(parser.expect(&Token::CloseDelim(DelimToken::Brace)));
if max_lit < min_lit {
return Err(
parser.span_fatal(max.span, "maximum must be greater than or equal to minimum")
);
}
try!(parser.expect(&Token::Eof));
Ok(IntegerEnum {
attrs: attrs,
is_pub: is_pub,
name: name,
repr: repr,
min: min,
max: max,
})
}
pub fn into_items(mut self, cx: &ExtCtxt, sp: Span) -> Vec<P<Item>> {
self.add_derives(cx, sp);
self.add_repr(cx, sp);
let variants = self.variants(cx);
let impls_macro_item = self.impls_macro_item(&variants, cx, sp);
let enum_def = EnumDef { variants: variants };
let enum_kind = ItemKind::Enum(enum_def, Default::default());
let is_pub = self.is_pub;
let enum_item = cx.item(sp, self.name, self.attrs, enum_kind).map(|mut item| {
if is_pub { item.vis = Visibility::Public; }
item
});
vec![enum_item, impls_macro_item]
}
fn add_derives(&mut self, cx: &ExtCtxt, sp: Span) {
let derives = ["Clone", "Copy", "PartialEq", "Eq", "PartialOrd", "Ord"].iter()
.map(|s| InternedString::new(s))
.map(|s| cx.meta_word(sp, s))
.collect();
let derive_list = cx.meta_list(sp, InternedString::new("derive"), derives);
self.attrs.push(cx.attribute(sp, derive_list));
}
fn add_repr(&mut self, cx: &ExtCtxt, sp: Span) {
let repr = cx.meta_word(sp, self.repr.name.as_str());
let repr_list = cx.meta_list(sp, InternedString::new("repr"), vec![repr]);
self.attrs.push(cx.attribute(sp, repr_list));
}
fn variants(&self, cx: &ExtCtxt) -> Vec<Variant> {
let mut vec = Vec::new();
let max_lit = IntLit::from_expr(&*self.max).unwrap();
let mut current = self.min.clone();
loop {
let int_lit = IntLit::from_expr(&*current).unwrap();
let mut variant = cx.variant(current.span, int_lit.into_ident(cx), vec![]);
variant.node.disr_expr = Some(current);
vec.push(variant);
if int_lit == max_lit { break; }
current = int_lit.succ().into_expr(cx, self.min.span);
}
vec
}
fn impls_macro_item(&self, variants: &[Variant], cx: &ExtCtxt, sp: Span) -> P<Item> {
let path = cx.path_ident(sp, cx.ident_of("bounded_integer_impls"));
let first_variant = variants.first().unwrap();
let last_variant = variants.last().unwrap();
let tts = vec![
TokenTree::Token(sp, Token::Ident(self.name)),
TokenTree::Token(sp, Token::Comma),
TokenTree::Token(sp, Token::Ident(self.repr)),
TokenTree::Token(sp, Token::Comma),
TokenTree::Token(sp, Token::Ident(self.name)),
TokenTree::Token(sp, Token::ModSep),
TokenTree::Token(sp, Token::Ident(first_variant.node.name)),
TokenTree::Token(sp, Token::Comma),
TokenTree::Token(sp, Token::Ident(self.name)),
TokenTree::Token(sp, Token::ModSep),
TokenTree::Token(sp, Token::Ident(last_variant.node.name)),
];
let mac = codemap::respan(sp, Mac_ {
path: path,
tts: tts,
ctxt: ast::EMPTY_CTXT,
});
cx.item(sp, keywords::Invalid.ident(), vec![], ItemKind::Mac(mac))
}
}