From bf589470379914ec892de700df9bf0aad8258f9c Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Sat, 28 Jun 2014 08:39:02 -0700 Subject: [PATCH] initial support for `#[deriving_deserializable]` --- de.rs | 14 ++ serde_macros.rs | 401 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 380 insertions(+), 35 deletions(-) diff --git a/de.rs b/de.rs index 59868c0c..1e6adaf9 100644 --- a/de.rs +++ b/de.rs @@ -153,6 +153,13 @@ pub trait Deserializer: Iterator> { } } + #[inline] + fn expect_tuple_sep< + T: Deserializable + >(&mut self) -> Result { + Deserializable::deserialize(self) + } + #[inline] fn expect_tuple_end(&mut self) -> Result<(), E> { match try!(self.expect_token()) { @@ -221,6 +228,13 @@ pub trait Deserializer: Iterator> { } } + #[inline] + fn expect_enum_sep< + T: Deserializable + >(&mut self) -> Result { + Deserializable::deserialize(self) + } + #[inline] fn expect_enum_end(&mut self) -> Result<(), E> { match try!(self.expect_token()) { diff --git a/serde_macros.rs b/serde_macros.rs index 139ddef8..b29b27c4 100644 --- a/serde_macros.rs +++ b/serde_macros.rs @@ -8,13 +8,42 @@ extern crate rustc; use std::gc::Gc; -use syntax::ast::{MetaItem, Item, Expr, MutMutable, LitNil}; +use syntax::ast::{ + Ident, + MetaItem, + Item, + Expr, + MutMutable, + LitNil, +}; use syntax::ast; use syntax::codemap::Span; use syntax::ext::base::{ExtCtxt, ItemDecorator}; use syntax::ext::build::AstBuilder; -use syntax::ext::deriving::generic::{MethodDef, EnumMatching, FieldInfo, Struct, Substructure, TraitDef, combine_substructure}; -use syntax::ext::deriving::generic::ty::{Borrowed, LifetimeBounds, Literal, Path, Ptr, Tuple, borrowed_explicit_self}; +use syntax::ext::deriving::generic::{ + EnumMatching, + FieldInfo, + MethodDef, + Named, + StaticFields, + StaticStruct, + StaticEnum, + Struct, + Substructure, + TraitDef, + Unnamed, + combine_substructure, +}; +use syntax::ext::deriving::generic::ty::{ + Borrowed, + LifetimeBounds, + Literal, + Path, + Ptr, + Self, + Tuple, + borrowed_explicit_self, +}; use syntax::parse::token; use rustc::plugin::Registry; @@ -24,14 +53,18 @@ use rustc::plugin::Registry; pub fn plugin_registrar(reg: &mut Registry) { reg.register_syntax_extension( token::intern("deriving_serializable"), - ItemDecorator(derive_serialize)); + ItemDecorator(expand_deriving_serializable)); + + reg.register_syntax_extension( + token::intern("deriving_deserializable"), + ItemDecorator(expand_deriving_deserializable)); } -fn derive_serialize(cx: &mut ExtCtxt, - sp: Span, - mitem: Gc, - item: Gc, - push: |Gc|) { +fn expand_deriving_serializable(cx: &mut ExtCtxt, + sp: Span, + mitem: Gc, + item: Gc, + push: |Gc|) { let inline = cx.meta_word(sp, token::InternedString::new("inline")); let attrs = vec!(cx.attribute(sp, inline)); @@ -46,19 +79,35 @@ fn derive_serialize(cx: &mut ExtCtxt, name: "serialize", generics: LifetimeBounds { lifetimes: Vec::new(), - bounds: vec!(("__S", ast::StaticSize, vec!(Path::new_( - vec!("serde", "ser", "Serializer"), None, - vec!(box Literal(Path::new_local("__E"))), true))), - ("__E", ast::StaticSize, vec!())) + bounds: vec!( + ( + "__S", + ast::StaticSize, + vec!( + Path::new_( + vec!("serde", "ser", "Serializer"), + None, + vec!(box Literal(Path::new_local("__E"))), + true + ) + ) + ), + ("__E", ast::StaticSize, vec!())) }, explicit_self: borrowed_explicit_self(), args: vec!(Ptr(box Literal(Path::new_local("__S")), Borrowed(None, MutMutable))), - ret_ty: Literal(Path::new_(vec!("std", "result", "Result"), - None, - vec!(box Tuple(Vec::new()), - box Literal(Path::new_local("__E"))), - true)), + ret_ty: Literal( + Path::new_( + vec!("std", "result", "Result"), + None, + vec!( + box Tuple(Vec::new()), + box Literal(Path::new_local("__E")) + ), + true + ) + ), attributes: attrs, const_nonmatching: true, combine_substructure: combine_substructure(|a, b, c| { @@ -80,7 +129,7 @@ fn serializable_substructure(cx: &mut ExtCtxt, trait_span: Span, // unit structs have no fields and need to return `Ok()` cx.expr_ok(trait_span, cx.expr_lit(trait_span, LitNil)) } else { - let mut stmts: Vec>> = Vec::new(); + let mut stmts = vec!(); let call = cx.expr_method_call( trait_span, @@ -101,30 +150,31 @@ fn serializable_substructure(cx: &mut ExtCtxt, trait_span: Span, self_, span, .. - }) in fields.iter().enumerate() { + }) in fields.iter().enumerate() { let name = match name { Some(id) => token::get_ident(id), - None => { - token::intern_and_get_ident(format!("_field{}", - i).as_slice()) - } + None => token::intern_and_get_ident(format!("_field{}", i).as_slice()), }; - let call = cx.expr_method_call(span, - serializer, - emit_struct_sep, - vec!( - cx.expr_str(span, name), - cx.expr_addr_of(span, self_), - )); + let call = cx.expr_method_call( + span, + serializer, + emit_struct_sep, + vec!( + cx.expr_str(span, name), + cx.expr_addr_of(span, self_), + ) + ); let call = cx.expr_try(span, call); stmts.push(cx.stmt_expr(call)); } - let call = cx.expr_method_call(trait_span, - serializer, - cx.ident_of("serialize_struct_end"), - vec!()); + let call = cx.expr_method_call( + trait_span, + serializer, + cx.ident_of("serialize_struct_end"), + vec!() + ); cx.expr_block(cx.block(trait_span, stmts, Some(call))) } @@ -177,3 +227,284 @@ fn serializable_substructure(cx: &mut ExtCtxt, trait_span: Span, _ => cx.bug("expected Struct or EnumMatching in deriving_serializable") } } + +pub fn expand_deriving_deserializable(cx: &mut ExtCtxt, + span: Span, + mitem: Gc, + item: Gc, + push: |Gc|) { + let trait_def = TraitDef { + span: span, + attributes: Vec::new(), + path: Path::new(vec!("serde", "de", "Deserializable")), + additional_bounds: Vec::new(), + generics: LifetimeBounds::empty(), + methods: vec!( + MethodDef { + name: "deserialize_token", + generics: LifetimeBounds { + lifetimes: Vec::new(), + bounds: vec!( + ( + "__D", + ast::StaticSize, + vec!( + Path::new_( + vec!("serde", "de", "Deserializer"), + None, + vec!( + box Literal(Path::new_local("__E")) + ), + true + ) + ) + ), + ("__E", ast::StaticSize, vec!())) + }, + explicit_self: None, + args: vec!( + Ptr( + box Literal(Path::new_local("__D")), + Borrowed(None, MutMutable) + ), + Literal(Path::new(vec!("serde", "de", "Token"))), + ), + ret_ty: Literal( + Path::new_( + vec!("std", "result", "Result"), + None, + vec!( + box Self, + box Literal(Path::new_local("__E")) + ), + true + ) + ), + attributes: Vec::new(), + const_nonmatching: true, + combine_substructure: combine_substructure(|a, b, c| { + deserializable_substructure(a, b, c) + }), + }) + }; + + trait_def.expand(cx, mitem, item, push) +} + +fn deserializable_substructure(cx: &mut ExtCtxt, trait_span: Span, + substr: &Substructure) -> Gc { + let deserializer = substr.nonself_args[0]; + + match *substr.fields { + StaticStruct(_, ref summary) => { + let mut stmts = vec!(); + + let call = cx.expr_method_call( + trait_span, + deserializer, + cx.ident_of("expect_struct_start"), + vec!( + substr.nonself_args[1], + cx.expr_str(trait_span, token::get_ident(substr.type_ident)), + ) + ); + let call = cx.expr_try(trait_span, call); + stmts.push(cx.stmt_expr(call)); + + let expect_struct_field = cx.ident_of("expect_struct_field"); + + let call = deserializable_static_fields( + cx, + trait_span, + substr.type_ident, + summary, + |cx, span, name| { + cx.expr_try(span, + cx.expr_method_call( + span, + deserializer, + expect_struct_field, + vec!( + cx.expr_str(span, name), + ) + ) + ) + } + ); + + let result = cx.ident_of("result"); + + stmts.push( + cx.stmt_let( + trait_span, + false, + result, + call + ) + ); + + let call = cx.expr_method_call( + trait_span, + deserializer, + cx.ident_of("expect_struct_end"), + vec!() + ); + let call = cx.expr_try(trait_span, call); + stmts.push(cx.stmt_expr(call)); + + cx.expr_block( + cx.block( + trait_span, + stmts, + Some( + cx.expr_ok( + trait_span, + cx.expr_ident(trait_span, result) + ) + ) + ) + ) + } + StaticEnum(_, ref fields) => { + let mut stmts = vec!(); + + let mut arms = vec!(); + let mut variants = vec!(); + + let expect_enum_sep = cx.ident_of("expect_enum_sep"); + for (i, &(name, v_span, ref parts)) in fields.iter().enumerate() { + variants.push(cx.expr_str(v_span, token::get_ident(name))); + + let deserializabled = deserializable_static_fields(cx, + v_span, + name, + parts, + |cx, span, _| { + cx.expr_try(span, + cx.expr_method_call( + span, + deserializer, + expect_enum_sep, + vec!() + ) + ) + }); + + arms.push( + cx.arm( + v_span, + vec!( + cx.pat_lit(v_span, cx.expr_uint(v_span, i)), + ), + deserializabled + ) + ); + } + + arms.push(cx.arm_unreachable(trait_span)); + + + let call = cx.expr_method_call( + trait_span, + deserializer, + cx.ident_of("expect_enum_start"), + vec!( + substr.nonself_args[1], + cx.expr_str(trait_span, token::get_ident(substr.type_ident)), + cx.expr_vec(trait_span, variants), + ) + ); + let call = cx.expr_try(trait_span, call); + + let variant = cx.ident_of("i"); + stmts.push( + cx.stmt_let( + trait_span, + false, + variant, + call + ) + ); + + let result = cx.ident_of("result"); + let call = cx.expr_match( + trait_span, + cx.expr_ident(trait_span, variant), + arms + ); + stmts.push( + cx.stmt_let( + trait_span, + false, + result, + call + ) + ); + + let call = cx.expr_method_call( + trait_span, + deserializer, + cx.ident_of("expect_enum_end"), + vec!() + ); + let call = cx.expr_try(trait_span, call); + stmts.push(cx.stmt_expr(call)); + + cx.expr_block( + cx.block( + trait_span, + stmts, + Some( + cx.expr_ok( + trait_span, + cx.expr_ident(trait_span, result) + ) + ) + ) + ) + } + _ => cx.bug("expected StaticEnum or StaticStruct in deriving(Deserializable)") + } +} + +/// Create a deserializer for a single enum variant/struct: +/// - `outer_pat_ident` is the name of this enum variant/struct +/// - `getarg` should retrieve the `uint`-th field with name `@str`. +fn deserializable_static_fields( + cx: &mut ExtCtxt, + trait_span: Span, + outer_pat_ident: Ident, + fields: &StaticFields, + getarg: |&mut ExtCtxt, Span, token::InternedString| -> Gc +) -> Gc { + match *fields { + Unnamed(ref fields) => { + if fields.is_empty() { + cx.expr_ident(trait_span, outer_pat_ident) + } else { + let fields = fields.iter().enumerate().map(|(i, &span)| { + getarg( + cx, + span, + token::intern_and_get_ident(format!("_field{}", i).as_slice()) + ) + }).collect(); + + cx.expr_call_ident(trait_span, outer_pat_ident, fields) + } + } + Named(ref fields) => { + // use the field's span to get nicer error messages. + let fields = fields.iter().map(|&(name, span)| { + let arg = getarg( + cx, + span, + token::get_ident(name) + ); + cx.field_imm(span, name, arg) + }).collect(); + + cx.expr_struct_ident(trait_span, outer_pat_ident, fields) + } + } +}