// Copyright 2016 Kyle Mayes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use syntax::ast::{Expr, Field, Ident, Item, StructField, Ty, Visibility};
use syntax::ext::base::{ExtCtxt};
use syntax::ext::quote::rt::{ExtParseUtils};
use syntax::ptr::{P};
use super::*;
//================================================
// Macros
//================================================
macro_rules! ast { ($ty:expr) => (concat!("::syntax::ast::", $ty)); }
macro_rules! p { ($ty:expr) => (concat!("::syntax::ptr::P<", $ty, ">")); }
macro_rules! spanned { ($ty:expr) => (concat!("::syntax::codemap::Spanned<", $ty, ">")); }
//================================================
// Traits
//================================================
// SpecifierExt __________________________________
trait SpecifierExt {
fn to_ty(&self, context: &ExtCtxt) -> P<Ty>;
fn to_struct_fields(&self, context: &ExtCtxt) -> Vec<StructField>;
fn to_field(&self, context: &ExtCtxt, stack: &[Amount], source: &Expr) -> Option<Field>;
fn to_fields(&self, context: &ExtCtxt, stack: &[Amount], source: &Expr) -> Vec<Field>;
}
impl SpecifierExt for Specifier {
fn to_ty(&self, context: &ExtCtxt) -> P<Ty> {
let ty = match *self {
Specifier::Attr(_) => ast!("Attribute").into(),
Specifier::BinOp(_) => spanned!("::syntax::parse::token::BinOpToken").into(),
Specifier::Block(_) => p!(ast!("Block")).into(),
Specifier::Delim(_) => spanned!("::syntax::tokenstream::Delimited").into(),
Specifier::Expr(_) => p!(ast!("Expr")).into(),
Specifier::Ident(_) => spanned!(ast!("Ident")).into(),
Specifier::Item(_) => p!(ast!("Item")).into(),
Specifier::Lftm(_) => spanned!(ast!("Name")).into(),
Specifier::Lit(_) => ast!("Lit").into(),
Specifier::Meta(_) => p!(ast!("MetaItem")).into(),
Specifier::Pat(_) => p!(ast!("Pat")).into(),
Specifier::Path(_) => ast!("Path").into(),
Specifier::Stmt(_) => ast!("Stmt").into(),
Specifier::Ty(_) => p!(ast!("Ty")).into(),
Specifier::Tok(_) => spanned!("::syntax::parse::token::Token").into(),
Specifier::Tt(_) => "::syntax::tokenstream::TokenTree".into(),
Specifier::Extractor(_, ref extractor) =>
return extractor::get_extract_storage(context, &extractor.extractor),
Specifier::Sequence(ref name, ref sequence) if name.is_some() => {
if sequence.amount == Amount::ZeroOrOne {
spanned!("bool").into()
} else {
spanned!("usize").into()
}
},
Specifier::Enum(_, ref enum_) => enum_.name.clone(),
Specifier::Struct(_, ref struct_) => struct_.name.clone(),
_ => unreachable!(),
};
let tts = context.parse_tts(ty);
context.new_parser_from_tts(&tts).parse_ty().unwrap()
}
fn to_struct_fields(&self, context: &ExtCtxt) -> Vec<StructField> {
let ty = match *self {
Specifier::Specific(_) => return vec![],
Specifier::Delimited(ref delimited) =>
return specification_to_struct_fields(context, &delimited.specification),
Specifier::Sequence(ref name, ref sequence) if name.is_none() => {
let mut fields = specification_to_struct_fields(context, &sequence.specification);
for field in &mut fields {
let ty = field.ty.clone();
if sequence.amount == Amount::ZeroOrOne {
field.ty = quote_ty!(context, Option<$ty>);
} else {
field.ty = quote_ty!(context, Vec<$ty>);
}
}
return fields;
},
_ => self.to_ty(context),
};
let name = context.ident_of(self.get_name().unwrap());
let field = quote_struct_field!(context, pub $name: $ty);
vec![field]
}
fn to_field(&self, context: &ExtCtxt, stack: &[Amount], source: &Expr) -> Option<Field> {
let name = match self.get_name() {
Some(name) => context.ident_of(name),
None => return None,
};
let expr = if stack.is_empty() {
match *self {
Specifier::Enum(ref name, _) | Specifier::Struct(ref name, _) => {
let ident = self.get_ident(context).unwrap();
quote_expr!(context, $ident::extract($source.get_arguments($name)))
},
_ => quote_expr!(context, $source.get(stringify!($name))),
}
} else {
match *self {
Specifier::Enum(ref name, _) | Specifier::Struct(ref name, _) => {
let ident = self.get_ident(context).unwrap();
let root = quote_expr!(context, $source.get_sequence($name));
to_field_expr(context, stack, &root, |c, r| {
quote_expr!(c, $r.to_arguments_option($ident::extract))
}, |c, r| {
quote_expr!(c, $r.to_arguments_vec($ident::extract))
})
},
_ => {
let root = quote_expr!(context, $source.get_sequence(stringify!($name)));
to_field_expr(context, stack, &root, |c, r| {
quote_expr!(c, $r.to_option())
}, |c, r| {
quote_expr!(c, $r.to_vec())
})
},
}
};
Some(quote_field!(context, $name: $expr))
}
fn to_fields(&self, context: &ExtCtxt, stack: &[Amount], source: &Expr) -> Vec<Field> {
match *self {
Specifier::Delimited(ref delimited) =>
specification_to_fields(context, &delimited.specification, stack, source),
Specifier::Sequence(ref name, ref sequence) if name.is_none() => {
let mut stack = stack.to_vec();
stack.push(sequence.amount);
specification_to_fields(context, &sequence.specification, &stack, source)
},
_ => self.to_field(context, stack, source).into_iter().collect(),
}
}
}
//================================================
// Functions
//================================================
fn to_field_expr<F: Fn(&ExtCtxt, &Expr) -> P<Expr>, G: Fn(&ExtCtxt, &Expr) -> P<Expr>>(
context: &ExtCtxt, stack: &[Amount], root: &Expr, option: F, vec: G
) -> P<Expr> {
if stack.len() == 1 {
if stack[0] == Amount::ZeroOrOne {
option(context, root)
} else {
vec(context, root)
}
} else {
let s = quote_expr!(context, s);
let mut expr = if stack[stack.len() - 1] == Amount::ZeroOrOne {
quote_expr!(context, |s| ${option(context, &s)})
} else {
quote_expr!(context, |s| ${vec(context, &s)})
};
for amount in stack.iter().skip(1).take(stack.len() - 2) {
if *amount == Amount::ZeroOrOne {
expr = quote_expr!(context, |s| s.to_sequence_option($expr));
} else {
expr = quote_expr!(context, |s| s.to_sequence_vec($expr));
}
}
if stack[0] == Amount::ZeroOrOne {
quote_expr!(context, $root.to_sequence_option($expr))
} else {
quote_expr!(context, $root.to_sequence_vec($expr))
}
}
}
pub fn expand_enum_items(context: &ExtCtxt, name: Ident, variants: &[Variant]) -> Vec<P<Item>> {
let mut items = vec![];
let pats = variants.iter().enumerate().map(|(i, v)| {
let variant = context.ident_of(&v.name);
let expr = quote_expr!(context, _enum);
let fields = specification_to_fields(context, &v.specification, &[], &expr);
quote_arm!(context, $i => $name::$variant { $($fields), * },)
}).collect::<Vec<_>>();
// Generate the enum item.
let variants = variants.iter().map(|v| {
let name = context.ident_of(&v.name);
let mut fields = specification_to_struct_fields(context, &v.specification);
for field in &mut fields {
field.vis = Visibility::Inherited;
}
quote_variant!(context, $name { $($fields), * })
}).collect::<Vec<_>>();
items.push(quote_item!(context, #[derive(Clone, Debug)] enum $name { $($variants), * }).unwrap());
// Generate the enum impl item.
let item = quote_item!(context,
impl $name {
#[allow(dead_code)]
fn extract(_enum: &::easy_plugin::Arguments) -> $name {
match _enum.variant.expect("expected variant") {
$($pats)*
_ => unreachable!()
}
}
}
).unwrap();
items.push(item);
items
}
fn specification_to_struct_fields(
context: &ExtCtxt, specification: &[Specifier]
) -> Vec<StructField> {
specification.iter().flat_map(|s| s.to_struct_fields(context).into_iter()).collect()
}
fn specification_to_fields(
context: &ExtCtxt, specification: &[Specifier], stack: &[Amount], expr: &Expr
) -> Vec<Field> {
specification.iter().flat_map(|s| s.to_fields(context, stack, expr).into_iter()).collect()
}
pub fn expand_struct_items(
context: &ExtCtxt, name: Ident, specification: &[Specifier]
) -> Vec<P<Item>> {
let mut items = vec![];
// Generate the struct item.
let fields = specification_to_struct_fields(context, specification);
let item = if fields.is_empty() {
quote_item!(context, #[derive(Clone, Debug)] struct $name;).unwrap()
} else {
quote_item!(context, #[derive(Clone, Debug)] struct $name { $($fields), * }).unwrap()
};
items.push(item);
// Generate the struct impl item.
let expr = quote_expr!(context, _struct);
let fields = specification_to_fields(context, specification, &[], &expr);
let expr = if fields.is_empty() {
quote_expr!(context, $name)
} else {
quote_expr!(context, $name { $($fields), * })
};
let item = quote_item!(context,
impl $name {
#[allow(dead_code)]
fn extract(_struct: &::easy_plugin::Arguments) -> $name {
$expr
}
}
).unwrap();
items.push(item);
items
}