diff --git a/serde2/serde2_macros/Cargo.toml b/serde2/serde2_macros/Cargo.toml new file mode 100644 index 00000000..ce207c55 --- /dev/null +++ b/serde2/serde2_macros/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "serde2_macros" +version = "0.1.0" +authors = ["Erick Tryzelaar "] + +[[lib]] +name = "serde2_macros" +path = "src/lib.rs" +plugin = true diff --git a/serde2/serde2_macros/src/lib.rs b/serde2/serde2_macros/src/lib.rs new file mode 100644 index 00000000..e9c93af8 --- /dev/null +++ b/serde2/serde2_macros/src/lib.rs @@ -0,0 +1,622 @@ +#![crate_type = "dylib"] +#![license = "MIT/ASL2"] + +#![feature(plugin_registrar, quote)] + +extern crate syntax; +extern crate rustc; + +use std::gc::Gc; + +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::{ + 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; + +#[plugin_registrar] +#[doc(hidden)] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_syntax_extension( + token::intern("deriving_serializable"), + ItemDecorator(expand_deriving_serializable)); + + /* + reg.register_syntax_extension( + token::intern("deriving_deserializable"), + ItemDecorator(expand_deriving_deserializable)); + */ +} + +fn expand_deriving_serializable(cx: &mut ExtCtxt, + sp: Span, + mitem: Gc, + item: Gc, + mut push: |Gc|) { + + + foo(cx, sp, mitem, item, &mut push); + + /* + let inline = cx.meta_word(sp, token::InternedString::new("inline")); + let attrs = vec!(cx.attribute(sp, inline)); + + let trait_def = TraitDef { + span: sp, + attributes: vec!(), + path: Path::new_(vec!("serde2", "ser", "Serialize"), None, + vec!(box Literal(Path::new_local("__S")), + box Literal(Path::new_local("__E"))), true), + additional_bounds: Vec::new(), + generics: LifetimeBounds { + lifetimes: Vec::new(), + bounds: vec!(("__S", None, vec!(Path::new_( + vec!("serde2", "ser", "VisitorState"), None, + vec!(box Literal(Path::new_local("__E"))), true))), + ("__E", None, vec!())) + }, + methods: vec!( + MethodDef { + name: "serialize", + generics: LifetimeBounds::empty(), + 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 + ) + ), + attributes: attrs, + combine_substructure: combine_substructure(|a, b, c| { + serializable_substructure(a, b, c) + }), + }) + }; + + trait_def.expand(cx, mitem, item, push); + */ +} + +fn foo(cx: &ExtCtxt, + sp: Span, + _mitem: Gc, + item: Gc, + push: &mut |Gc|) { + match item.node { + ast::ItemStruct(ref struct_def, ref generics) => { + foo_struct(cx, sp, &**struct_def, item.ident, generics, push) + } + _ => { } + } +} + +fn foo_struct(cx: &ExtCtxt, + sp: Span, + struct_def: &ast::StructDef, + ident: ast::Ident, + generics: &ast::Generics, + push: &mut |Gc|) { + + let ident_str = token::get_ident(ident); + //let visitor_name = token::gensym_ident(ident_str.get().as_slice()); + + // FIXME: is there a better way to gensym a symbol that is pretty-printable? + let visitor_name = token::str_to_ident( + format!( + "{}{}", + ident_str, + token::gensym("").uint()).as_slice()); + + let item: Gc = quote_item!(cx, + struct $visitor_name<'a> { + state: uint, + value: &'a $ident, + } + ).unwrap(); + + (*push)(item); + + let ident_expr = cx.expr_str(sp, ident_str); + + let item: Gc = quote_item!(cx, + impl, R> ::serde2::Serialize for $ident { + #[inline] + fn serialize(&self, s: &mut S) -> R { + s.visit_named_map($ident_expr, $visitor_name { + value: self, + state: 0, + }) + } + } + ).unwrap(); + + (*push)(item); + + let arms: Vec = struct_def.fields.iter() + .enumerate() + .map(|(i, field)| { + let first = if i == 0 { + quote_expr!(cx, true) + } else { + quote_expr!(cx, false) + }; + + let name = field.node.ident().unwrap(); + let span = field.span; + + let name_expr = cx.expr_str(span, token::get_ident(name)); + + quote_arm!(cx, + $i => { + self.state += 1; + Some(s.visit_map_elt($first, $name_expr, &self.value.$name)) + } + ) + }) + .collect(); + + let item: Gc = quote_item!(cx, + impl< + 'a, + S: ::serde2::VisitorState, + R + > ::serde2::Visitor for $visitor_name<'a> { + #[inline] + fn visit(&mut self, s: &mut S) -> Option { + match self.state { + $arms + _ => None, + } + } + } + ).unwrap(); + + (*push)(item); +} + +fn serializable_substructure(cx: &ExtCtxt, span: Span, + substr: &Substructure) -> Gc { + let serializer = substr.nonself_args[0]; + + return match *substr.fields { + Struct(ref fields) => { + if fields.is_empty() { + // unit structs have no fields and need to return `Ok()` + quote_expr!(cx, Ok(())) + } else { + let type_name = cx.expr_str( + span, + token::get_ident(substr.type_ident) + ); + let len = fields.len(); + + let mut stmts: Vec> = fields.iter() + .enumerate() + .map(|(i, &FieldInfo { name, self_, span, .. })| { + let name = match name { + Some(id) => token::get_ident(id), + None => token::intern_and_get_ident(format!("_field{}", i).as_slice()), + }; + + let name = cx.expr_str(span, name); + + quote_stmt!( + cx, + try!($serializer.serialize_struct_elt($name, &$self_)) + ) + }) + .collect(); + + quote_expr!(cx, { + try!($serializer.serialize_struct_start($type_name, $len)); + $stmts + $serializer.serialize_struct_end() + }) + } + } + + EnumMatching(_idx, variant, ref fields) => { + let type_name = cx.expr_str( + span, + token::get_ident(substr.type_ident) + ); + let variant_name = cx.expr_str( + span, + token::get_ident(variant.node.name) + ); + let len = fields.len(); + + let stmts: Vec> = fields.iter() + .map(|&FieldInfo { self_, span, .. }| { + quote_stmt!( + cx, + try!($serializer.serialize_enum_elt(&$self_)) + ) + }) + .collect(); + + quote_expr!(cx, { + try!($serializer.serialize_enum_start($type_name, $variant_name, $len)); + $stmts + $serializer.serialize_enum_end() + }) + } + + _ => 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!("serde2", "de", "Deserializable"), None, + vec!(box Literal(Path::new_local("__D")), + box Literal(Path::new_local("__E"))), true), + additional_bounds: Vec::new(), + generics: LifetimeBounds { + lifetimes: Vec::new(), + bounds: vec!(("__D", None, vec!(Path::new_( + vec!("serde2", "de", "Deserializer"), None, + vec!(box Literal(Path::new_local("__E"))), true))), + ("__E", None, vec!())) + }, + methods: vec!( + MethodDef { + name: "deserialize_token", + generics: LifetimeBounds::empty(), + explicit_self: None, + args: vec!( + Ptr( + box Literal(Path::new_local("__D")), + Borrowed(None, MutMutable) + ), + Literal(Path::new(vec!("serde2", "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(), + 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, span: Span, + substr: &Substructure) -> Gc { + let deserializer = substr.nonself_args[0]; + let token = substr.nonself_args[1]; + + match *substr.fields { + StaticStruct(_, ref fields) => { + deserialize_struct( + cx, + span, + substr.type_ident, + fields, + deserializer, + token) + } + StaticEnum(_, ref fields) => { + deserialize_enum( + cx, + span, + substr.type_ident, + fields.as_slice(), + deserializer, + token) + } + _ => cx.bug("expected StaticEnum or StaticStruct in deriving(Deserializable)") + } +} + +fn deserialize_struct( + cx: &ExtCtxt, + span: Span, + type_ident: Ident, + fields: &StaticFields, + deserializer: Gc, + token: Gc +) -> Gc { + let struct_block = deserialize_struct_from_struct( + cx, + span, + type_ident, + fields, + deserializer + ); + + let map_block = deserialize_struct_from_map( + cx, + span, + type_ident, + fields, + deserializer + ); + + quote_expr!( + cx, + match $token { + ::serde2::de::StructStart(_, _) => $struct_block, + ::serde2::de::MapStart(_) => $map_block, + token => { + let expected_tokens = [ + ::serde2::de::StructStartKind, + ::serde2::de::MapStartKind, + ]; + Err($deserializer.syntax_error(token, expected_tokens)) + } + } + ) +} + +fn deserialize_struct_from_struct( + cx: &ExtCtxt, + span: Span, + type_ident: Ident, + fields: &StaticFields, + deserializer: Gc +) -> Gc { + let expect_struct_field = cx.ident_of("expect_struct_field"); + + let call = deserializable_static_fields( + cx, + span, + type_ident, + fields, + |cx, span, name| { + let name = cx.expr_str(span, name); + quote_expr!( + cx, + try!($deserializer.expect_struct_field($name)) + ) + } + ); + + quote_expr!(cx, { + let result = $call; + try!($deserializer.expect_struct_end()); + Ok(result) + }) +} + +fn deserialize_struct_from_map( + cx: &ExtCtxt, + span: Span, + type_ident: Ident, + fields: &StaticFields, + deserializer: Gc +) -> Gc { + let fields = match *fields { + Unnamed(_) => fail!(), + Named(ref fields) => fields.as_slice(), + }; + + // Declare each field. + let let_fields: Vec> = fields.iter() + .map(|&(name, span)| { + quote_stmt!(cx, let mut $name = None) + }) + .collect(); + + // Declare key arms. + let key_arms: Vec = fields.iter() + .map(|&(name, span)| { + let s = cx.expr_str(span, token::get_ident(name)); + quote_arm!(cx, + $s => { + $name = Some( + try!(::serde2::de::Deserializable::deserialize($deserializer)) + ); + continue; + }) + }) + .collect(); + + let extract_fields: Vec> = fields.iter() + .map(|&(name, span)| { + let name_str = cx.expr_str(span, token::get_ident(name)); + quote_stmt!(cx, + let $name = match $name { + Some($name) => $name, + None => try!($deserializer.missing_field($name_str)), + }; + ) + }) + .collect(); + + let result = cx.expr_struct_ident( + span, + type_ident, + fields.iter() + .map(|&(name, span)| { + cx.field_imm(span, name, cx.expr_ident(span, name)) + }) + .collect() + ); + + quote_expr!(cx, { + $let_fields + + loop { + let token = match try!($deserializer.expect_token()) { + ::serde2::de::End => { break; } + token => token, + }; + + { + let key = match token { + ::serde2::de::Str(s) => s, + ::serde2::de::String(ref s) => s.as_slice(), + token => { + let expected_tokens = [ + ::serde2::de::StrKind, + ::serde2::de::StringKind, + ]; + return Err($deserializer.syntax_error(token, expected_tokens)); + } + }; + + match key { + $key_arms + _ => { } + } + } + + try!($deserializer.ignore_field(token)) + } + + $extract_fields + Ok($result) + }) +} + +fn deserialize_enum( + cx: &ExtCtxt, + span: Span, + type_ident: Ident, + fields: &[(Ident, Span, StaticFields)], + deserializer: Gc, + token: Gc +) -> Gc { + let type_name = cx.expr_str(span, token::get_ident(type_ident)); + + let variants = fields.iter() + .map(|&(name, span, _)| { + cx.expr_str(span, token::get_ident(name)) + }) + .collect(); + + let variants = cx.expr_vec(span, variants); + + let arms: Vec = fields.iter() + .enumerate() + .map(|(i, &(name, span, ref parts))| { + let call = deserializable_static_fields( + cx, + span, + name, + parts, + |cx, span, _| { + quote_expr!(cx, try!($deserializer.expect_enum_elt())) + } + ); + + quote_arm!(cx, $i => $call,) + }) + .collect(); + + quote_expr!(cx, { + let i = try!($deserializer.expect_enum_start($token, $type_name, $variants)); + + let result = match i { + $arms + _ => { unreachable!() } + }; + + try!($deserializer.expect_enum_end()); + + Ok(result) + }) +} + +/// 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: &ExtCtxt, + span: Span, + outer_pat_ident: Ident, + fields: &StaticFields, + getarg: |&ExtCtxt, Span, token::InternedString| -> Gc +) -> Gc { + match *fields { + Unnamed(ref fields) => { + if fields.is_empty() { + cx.expr_ident(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(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(span, outer_pat_ident, fields) + } + } +} +*/ diff --git a/serde2/src/json/ser.rs b/serde2/src/json/ser.rs index f44b89e6..c00f888b 100644 --- a/serde2/src/json/ser.rs +++ b/serde2/src/json/ser.rs @@ -131,7 +131,7 @@ impl ser::VisitorState> for Serializer { try!(write!(self.writer, ",")); } - //value.serialize(self) + value.serialize(self) } #[inline]