openethereum/ipc/codegen/src/serialization.rs

566 lines
16 KiB
Rust
Raw Normal View History

2016-04-20 13:17:11 +02:00
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use aster;
use syntax::ast::{
MetaItem,
Item,
ImplItemKind,
ImplItem,
MethodSig,
Arg,
PatKind,
FunctionRetTy,
Ty,
TraitRef,
Ident,
Generics,
2016-04-21 21:18:13 +02:00
TyKind,
2016-04-20 13:17:11 +02:00
};
use syntax::ast;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::ptr::P;
pub struct Error;
pub fn expand_serialization_implementation(
cx: &mut ExtCtxt,
span: Span,
meta_item: &MetaItem,
annotatable: &Annotatable,
push: &mut FnMut(Annotatable)
) {
let item = match *annotatable {
Annotatable::Item(ref item) => item,
_ => {
cx.span_err(meta_item.span, "`#[derive(Binary)]` may only be applied to structs and enums");
return;
}
};
let builder = aster::AstBuilder::new().span(span);
let impl_item = match serialize_item(cx, &builder, &item) {
Ok(item) => item,
Err(Error) => {
// An error occured, but it should have been reported already.
return;
}
};
push(Annotatable::Item(impl_item))
}
fn serialize_item(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
item: &Item,
) -> Result<P<ast::Item>, Error> {
let generics = match item.node {
ast::ItemKind::Struct(_, ref generics) => generics,
ast::ItemKind::Enum(_, ref generics) => generics,
_ => {
cx.span_err(
item.span,
2016-04-20 17:02:17 +02:00
"`#[derive(Binary)]` may only be applied to structs and enums");
2016-04-20 13:17:11 +02:00
return Err(Error);
}
};
let ty = builder.ty().path()
2016-04-20 17:02:17 +02:00
.segment(item.ident).with_generics(generics.clone()).build()
2016-04-20 13:17:11 +02:00
.build();
2016-04-20 17:02:17 +02:00
let where_clause = &generics.where_clause;
2016-04-20 13:17:11 +02:00
let binary_expressions = try!(binary_expr(cx,
&builder,
&item,
2016-04-20 17:02:17 +02:00
&generics,
2016-04-20 13:17:11 +02:00
ty.clone()));
2016-04-20 17:02:17 +02:00
let (size_expr, read_expr, write_expr) =
(binary_expressions.size, binary_expressions.read, binary_expressions.write);
2016-04-20 13:17:11 +02:00
Ok(quote_item!(cx,
2016-04-20 17:02:17 +02:00
impl $generics ::ipc::BinaryConvertable for $ty $where_clause {
2016-04-20 13:17:11 +02:00
fn size(&self) -> usize {
$size_expr
}
2016-04-20 18:01:53 +02:00
fn to_bytes(&self, buffer: &mut [u8]) -> Result<(), BinaryConvertError> {
2016-04-20 13:17:11 +02:00
$write_expr
}
2016-04-20 18:01:53 +02:00
fn from_bytes(buffer: &[u8]) -> Result<Self, BinaryConvertError> {
2016-04-20 22:02:29 +02:00
$read_expr
2016-04-20 13:17:11 +02:00
}
}
).unwrap())
}
fn binary_expr(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
item: &Item,
impl_generics: &ast::Generics,
ty: P<ast::Ty>,
) -> Result<BinaryExpressions, Error> {
match item.node {
ast::ItemKind::Struct(ref variant_data, _) => {
2016-04-20 14:55:08 +02:00
binary_expr_item_struct(
2016-04-20 13:17:11 +02:00
cx,
builder,
impl_generics,
ty,
item.span,
variant_data,
)
}
ast::ItemKind::Enum(ref enum_def, _) => {
binary_expr_enum(
cx,
builder,
item.ident,
impl_generics,
ty,
2016-04-20 17:02:17 +02:00
item.span,
2016-04-20 13:17:11 +02:00
enum_def,
)
}
_ => {
cx.span_bug(item.span,
2016-04-20 17:02:17 +02:00
"expected ItemStruct or ItemEnum in #[derive(Binary)]");
Err(Error)
2016-04-20 13:17:11 +02:00
}
}
}
struct BinaryExpressions {
2016-04-20 14:55:08 +02:00
pub size: P<ast::Expr>,
2016-04-20 17:02:17 +02:00
pub write: P<ast::Expr>,
2016-04-20 14:55:08 +02:00
pub read: P<ast::Expr>,
2016-04-20 13:17:11 +02:00
}
2016-04-21 21:18:13 +02:00
fn is_variable_size(ty: &P<Ty>) -> bool {
match ty.node {
TyKind::Vec(_) => true,
TyKind::FixedLengthVec(_, _) => false,
TyKind::Path(_, ref path) => {
path.segments[0].identifier.name.as_str() == "Option" ||
path.segments[0].identifier.name.as_str() == "Result" ||
path.segments[0].identifier.name.as_str() == "String"
},
_ => { false }
}
}
fn variable_sizes(cx: &ExtCtxt, fields: &[ast::StructField]) -> Vec<bool> {
fields.iter().map(|field| is_variable_size(&field.ty)).collect::<Vec<bool>>()
}
2016-04-20 14:55:08 +02:00
fn binary_expr_struct(
2016-04-20 13:17:11 +02:00
cx: &ExtCtxt,
builder: &aster::AstBuilder,
ty: P<ast::Ty>,
2016-04-20 14:55:08 +02:00
fields: &[ast::StructField],
2016-04-20 17:02:17 +02:00
value_ident: Option<ast::Ident>,
2016-04-20 22:02:29 +02:00
instance_ident: Option<ast::Ident>,
2016-04-20 14:55:08 +02:00
) -> Result<BinaryExpressions, Error> {
2016-04-21 21:18:13 +02:00
let variable_sizes = variable_size_indexes(fields);
2016-04-20 17:02:17 +02:00
let size_exprs: Vec<P<ast::Expr>> = fields.iter().enumerate().map(|(index, field)| {
2016-04-21 21:18:13 +02:00
if variable_sizes[index] {
let index_ident = builder.id(format!("__field{}", index));
value_ident.and_then(|x| {
let field_id = builder.id(field.ident.unwrap());
Some(quote_expr!(cx, $x . $field_id .size()))
})
.unwrap_or_else(|| quote_expr!(cx, $index_ident .size()))
}
else {
let field_type_ident = builder.id(&::syntax::print::pprust::ty_to_string(&field.ty));
quote_expr!(cx, mem::sizeof_of::<field_type_ident>());
}
2016-04-20 17:02:17 +02:00
}).collect();
2016-04-20 14:55:08 +02:00
let mut total_size_expr = size_exprs[0].clone();
2016-04-20 17:02:17 +02:00
for index in 1..size_exprs.len() {
let next_expr = size_exprs[index].clone();
2016-04-20 14:55:08 +02:00
total_size_expr = quote_expr!(cx, $total_size_expr + $next_expr);
}
2016-04-20 17:02:17 +02:00
let mut write_stmts = Vec::<ast::Stmt>::new();
write_stmts.push(quote_stmt!(cx, let mut offset = 0usize;).unwrap());
2016-04-20 22:02:29 +02:00
let mut map_stmts = Vec::<ast::Stmt>::new();
let field_amount = builder.id(&format!("{}",fields.len()));
map_stmts.push(quote_stmt!(cx, let mut map = vec![0usize; $field_amount];).unwrap());
map_stmts.push(quote_stmt!(cx, let mut total = 0usize;).unwrap());
2016-04-20 17:02:17 +02:00
for (index, field) in fields.iter().enumerate() {
let size_expr = &size_exprs[index];
write_stmts.push(quote_stmt!(cx, let next_line = offset + $size_expr; ).unwrap());
match value_ident {
Some(x) => {
2016-04-20 18:10:41 +02:00
let field_id = builder.id(field.ident.unwrap());
2016-04-20 17:02:17 +02:00
write_stmts.push(
2016-04-20 18:10:41 +02:00
quote_stmt!(cx, $x . $field_id .to_bytes(&mut buffer[offset..next_line]);).unwrap())
2016-04-20 17:02:17 +02:00
},
None => {
2016-04-20 18:01:53 +02:00
let index_ident = builder.id(format!("__field{}", index));
2016-04-20 17:02:17 +02:00
write_stmts.push(
2016-04-20 18:01:53 +02:00
quote_stmt!(cx, $index_ident .to_bytes(&mut buffer[offset..next_line]);).unwrap())
2016-04-20 17:02:17 +02:00
}
}
write_stmts.push(quote_stmt!(cx, offset = next_line; ).unwrap());
2016-04-20 22:02:29 +02:00
let field_index = builder.id(&format!("{}", index));
let field_type_ident = builder.id(&::syntax::print::pprust::ty_to_string(&field.ty));
map_stmts.push(quote_stmt!(cx, map[$field_index] = total;).unwrap());
map_stmts.push(quote_stmt!(cx, total = total + mem::size_of::<$field_type_ident>();).unwrap());
};
let read_expr = if value_ident.is_some() {
let instance_create = named_fields_sequence(cx, &ty, fields);
quote_expr!(cx, { $map_stmts; $instance_create; Ok(result) })
}
else {
let map_variant = P(fields_sequence(cx, &ty, fields, &instance_ident.unwrap_or(builder.id("Self"))));
quote_expr!(cx, { $map_stmts; Ok($map_variant) })
2016-04-20 17:02:17 +02:00
};
2016-04-20 14:55:08 +02:00
2016-04-20 13:17:11 +02:00
Ok(BinaryExpressions {
2016-04-20 14:55:08 +02:00
size: total_size_expr,
2016-04-20 18:01:53 +02:00
write: quote_expr!(cx, { $write_stmts; Ok(()) } ),
2016-04-20 22:02:29 +02:00
read: read_expr,
2016-04-20 17:02:17 +02:00
})
2016-04-20 13:17:11 +02:00
}
2016-04-20 14:55:08 +02:00
fn binary_expr_item_struct(
2016-04-20 13:17:11 +02:00
cx: &ExtCtxt,
builder: &aster::AstBuilder,
impl_generics: &ast::Generics,
ty: P<ast::Ty>,
span: Span,
variant_data: &ast::VariantData,
) -> Result<BinaryExpressions, Error> {
match *variant_data {
ast::VariantData::Tuple(ref fields, _) => {
2016-04-20 14:55:08 +02:00
binary_expr_struct(
2016-04-20 13:17:11 +02:00
cx,
&builder,
ty,
fields,
2016-04-20 17:02:17 +02:00
Some(builder.id("self")),
2016-04-20 22:02:29 +02:00
None,
2016-04-20 13:17:11 +02:00
)
}
ast::VariantData::Struct(ref fields, _) => {
2016-04-20 14:55:08 +02:00
binary_expr_struct(
2016-04-20 13:17:11 +02:00
cx,
&builder,
ty,
fields,
2016-04-20 17:02:17 +02:00
Some(builder.id("self")),
2016-04-20 22:02:29 +02:00
None,
2016-04-20 13:17:11 +02:00
)
2016-04-20 17:02:17 +02:00
},
_ => {
2016-04-20 18:01:53 +02:00
cx.span_bug(span,
&format!("#[derive(Binary)] Unsupported struct content, expected tuple/struct, found: {:?}",
variant_data));
2016-04-20 17:02:17 +02:00
Err(Error)
},
2016-04-20 13:17:11 +02:00
}
}
2016-04-20 14:55:08 +02:00
fn binary_expr_enum(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
type_ident: Ident,
impl_generics: &ast::Generics,
ty: P<ast::Ty>,
2016-04-20 17:02:17 +02:00
span: Span,
2016-04-20 14:55:08 +02:00
enum_def: &ast::EnumDef,
) -> Result<BinaryExpressions, Error> {
let arms: Vec<_> = try!(
enum_def.variants.iter()
.enumerate()
.map(|(variant_index, variant)| {
binary_expr_variant(
cx,
builder,
type_ident,
impl_generics,
ty.clone(),
2016-04-20 17:02:17 +02:00
span,
2016-04-20 14:55:08 +02:00
variant,
variant_index,
)
})
.collect()
);
2016-04-20 22:02:29 +02:00
let (size_arms, write_arms, mut read_arms) = (
2016-04-20 17:02:17 +02:00
arms.iter().map(|x| x.size.clone()).collect::<Vec<ast::Arm>>(),
arms.iter().map(|x| x.write.clone()).collect::<Vec<ast::Arm>>(),
arms.iter().map(|x| x.read.clone()).collect::<Vec<ast::Arm>>());
2016-04-20 14:55:08 +02:00
2016-04-20 22:02:29 +02:00
read_arms.push(quote_arm!(cx, _ => { Err(BinaryConvertError) } ));
2016-04-20 14:55:08 +02:00
Ok(BinaryExpressions {
2016-04-20 18:01:53 +02:00
size: quote_expr!(cx, 1usize + match *self { $size_arms }),
2016-04-20 17:02:17 +02:00
write: quote_expr!(cx, match *self { $write_arms }; ),
2016-04-20 22:02:29 +02:00
read: quote_expr!(cx, match buffer[0] { $read_arms }),
2016-04-20 17:02:17 +02:00
})
2016-04-20 14:55:08 +02:00
}
struct BinaryArm {
2016-04-20 17:02:17 +02:00
size: ast::Arm,
write: ast::Arm,
read: ast::Arm,
2016-04-20 14:55:08 +02:00
}
2016-04-20 22:02:29 +02:00
fn fields_sequence(
ext_cx: &ExtCtxt,
ty: &P<ast::Ty>,
fields: &[ast::StructField],
variant_ident: &ast::Ident,
) -> ast::Expr {
use syntax::parse::token;
use syntax::ast::TokenTree::Token;
::quasi::parse_expr_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
tt.push(Token(_sp, token::Ident(variant_ident.clone(), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Paren)));
for (idx, field) in fields.iter().enumerate() {
if field.ident.is_some() {
tt.push(Token(_sp, token::Ident(field.ident.clone().unwrap(), token::Plain)));
tt.push(Token(_sp, token::Colon));
}
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("try!"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Paren)));
tt.push(Token(
_sp,
token::Ident(
ext_cx.ident_of(&::syntax::print::pprust::ty_to_string(&field.ty)),
token::Plain)));
tt.push(Token(_sp, token::ModSep));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("from_bytes"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Paren)));
tt.push(Token(_sp, token::BinOp(token::And)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx)), token::Plain)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::DotDot));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1)), token::Plain)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::CloseDelim(token::Paren)));
tt.push(Token(_sp, token::CloseDelim(token::Paren)));
tt.push(Token(_sp, token::Comma));
}
tt.push(Token(_sp, token::CloseDelim(token::Paren)));
tt
})
).unwrap()
}
fn named_fields_sequence(
ext_cx: &ExtCtxt,
ty: &P<ast::Ty>,
fields: &[ast::StructField],
) -> ast::Stmt {
use syntax::parse::token;
use syntax::ast::TokenTree::Token;
::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts(
ext_cx.parse_sess(),
ext_cx.cfg(),
{
let _sp = ext_cx.call_site();
let mut tt = ::std::vec::Vec::new();
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("let"), token::Plain)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("result"), token::Plain)));
tt.push(Token(_sp, token::Eq));
tt.push(Token(
_sp,
token::Ident(
ext_cx.ident_of(&::syntax::print::pprust::ty_to_string(ty)),
token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Brace)));
for (idx, field) in fields.iter().enumerate() {
tt.push(Token(_sp, token::Ident(field.ident.clone().unwrap(), token::Plain)));
tt.push(Token(_sp, token::Colon));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("try!"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Paren)));
tt.push(Token(
_sp,
token::Ident(
ext_cx.ident_of(&::syntax::print::pprust::ty_to_string(&field.ty)),
token::Plain)));
tt.push(Token(_sp, token::ModSep));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("from_bytes"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Paren)));
tt.push(Token(_sp, token::BinOp(token::And)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("buffer"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx)), token::Plain)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::DotDot));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of("map"), token::Plain)));
tt.push(Token(_sp, token::OpenDelim(token::Bracket)));
tt.push(Token(_sp, token::Ident(ext_cx.ident_of(&format!("{}", idx+1)), token::Plain)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::CloseDelim(token::Bracket)));
tt.push(Token(_sp, token::CloseDelim(token::Paren)));
tt.push(Token(_sp, token::CloseDelim(token::Paren)));
tt.push(Token(_sp, token::Comma));
}
tt.push(Token(_sp, token::CloseDelim(token::Brace)));
tt
})
).unwrap()
}
2016-04-20 14:55:08 +02:00
fn binary_expr_variant(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
type_ident: Ident,
generics: &ast::Generics,
ty: P<ast::Ty>,
2016-04-20 17:02:17 +02:00
span: Span,
2016-04-20 14:55:08 +02:00
variant: &ast::Variant,
variant_index: usize,
) -> Result<BinaryArm, Error> {
2016-04-20 17:02:17 +02:00
let type_name = ::syntax::print::pprust::ty_to_string(&ty);
2016-04-20 14:55:08 +02:00
let variant_ident = variant.node.name;
2016-04-20 19:22:24 +02:00
let variant_index_ident = builder.id(format!("{}", variant_index));
2016-04-20 14:55:08 +02:00
match variant.node.data {
2016-04-20 18:01:53 +02:00
ast::VariantData::Unit(_) => {
let pat = builder.pat().path()
.id(type_ident).id(variant_ident)
.build();
2016-04-20 22:02:29 +02:00
let variant_val = builder.id(format!("{}::{}", type_ident, variant_ident));
2016-04-20 18:01:53 +02:00
Ok(BinaryArm {
size: quote_arm!(cx, $pat => { 0usize } ),
2016-04-20 19:22:24 +02:00
write: quote_arm!(cx, $pat => { buffer[0] = $variant_index_ident; Ok(()) } ),
2016-04-20 22:02:29 +02:00
read: quote_arm!(cx, $variant_index_ident => { Ok($variant_val) } ),
2016-04-20 18:01:53 +02:00
})
},
2016-04-20 14:55:08 +02:00
ast::VariantData::Tuple(ref fields, _) => {
let field_names: Vec<ast::Ident> = (0 .. fields.len())
.map(|i| builder.id(format!("__field{}", i)))
.collect();
let pat = builder.pat().enum_()
.id(type_ident).id(variant_ident).build()
.with_pats(
field_names.iter()
.map(|field| builder.pat().ref_id(field))
)
.build();
let binary_expr = try!(binary_expr_struct(
cx,
&builder,
ty,
fields,
None,
2016-04-20 22:02:29 +02:00
Some(builder.id(format!("{}::{}", type_ident, variant_ident))),
2016-04-20 14:55:08 +02:00
));
2016-04-20 17:02:17 +02:00
let (size_expr, write_expr, read_expr) = (binary_expr.size, vec![binary_expr.write], binary_expr.read);
2016-04-20 14:55:08 +02:00
Ok(BinaryArm {
2016-04-20 17:02:17 +02:00
size: quote_arm!(cx, $pat => { $size_expr } ),
2016-04-20 19:22:24 +02:00
write: quote_arm!(cx,
$pat => {
buffer[0] = $variant_index_ident;
let buffer = &mut buffer[1..];
$write_expr
}),
2016-04-20 22:02:29 +02:00
read: quote_arm!(cx, $variant_index_ident => { $read_expr } ),
2016-04-20 14:55:08 +02:00
})
}
ast::VariantData::Struct(ref fields, _) => {
let field_names: Vec<_> = (0 .. fields.len())
.map(|i| builder.id(format!("__field{}", i)))
.collect();
let pat = builder.pat().struct_()
.id(type_ident).id(variant_ident).build()
.with_pats(
field_names.iter()
.zip(fields.iter())
2016-04-20 17:02:17 +02:00
.map(|(id, field)|(field.ident.unwrap(), builder.pat().ref_id(id))))
2016-04-20 14:55:08 +02:00
.build();
let binary_expr = try!(binary_expr_struct(
cx,
&builder,
ty,
fields,
None,
2016-04-20 22:02:29 +02:00
Some(builder.id(format!("{}::{}", type_ident, variant_ident))),
2016-04-20 14:55:08 +02:00
));
2016-04-20 17:02:17 +02:00
let (size_expr, write_expr, read_expr) = (binary_expr.size, vec![binary_expr.write], binary_expr.read);
2016-04-20 14:55:08 +02:00
Ok(BinaryArm {
2016-04-20 17:02:17 +02:00
size: quote_arm!(cx, $pat => { $size_expr } ),
2016-04-20 19:22:24 +02:00
write: quote_arm!(cx,
$pat => {
buffer[0] = $variant_index_ident;
let buffer = &mut buffer[1..];
$write_expr
}),
2016-04-20 17:02:17 +02:00
read: quote_arm!(cx, $pat => { $read_expr } ),
2016-04-20 14:55:08 +02:00
})
2016-04-20 17:02:17 +02:00
},
2016-04-20 14:55:08 +02:00
}
}