diff options
Diffstat (limited to 'src/libsyntax')
| -rw-r--r-- | src/libsyntax/ext/auto_serialize2.rs | 616 | ||||
| -rw-r--r-- | src/libsyntax/ext/base.rs | 2 | ||||
| -rw-r--r-- | src/libsyntax/syntax.rc | 2 |
3 files changed, 620 insertions, 0 deletions
diff --git a/src/libsyntax/ext/auto_serialize2.rs b/src/libsyntax/ext/auto_serialize2.rs new file mode 100644 index 00000000000..cef38d5ca97 --- /dev/null +++ b/src/libsyntax/ext/auto_serialize2.rs @@ -0,0 +1,616 @@ +/* + +The compiler code necessary to implement the #[auto_serialize2] +extension. The idea here is that type-defining items may be tagged +with #[auto_serialize2], which will cause us to generate a little +companion module with the same name as the item. + +For example, a type like: + + type node_id = uint; + +would generate two functions like: + + impl node_id: Serializable { + fn serialize<S: Serializer>(s: S) { + s.emit_uint(self) + } + + static fn deserialize<D: Deserializer>(d: D) -> node_id { + d.read_uint() + } + } + +Other interesting scenarios are whe the item has type parameters or +references other non-built-in types. A type definition like: + + type spanned<T> = {node: T, span: span}; + +would yield functions like: + + impl<T: Serializable> spanned<T>: Serializable { + fn serialize<S: Serializer>(s: S) { + do s.emit_rec { + s.emit_rec_field("node", 0, self.node.serialize(s)); + s.emit_rec_field("span", 1, self.span.serialize(s)); + } + } + + static fn deserialize<D: Deserializer>(d: D) -> spanned<T> { + do d.read_rec { + { + node: d.read_rec_field(~"node", 0, || deserialize(d)), + span: d.read_rec_field(~"span", 1, || deserialize(d)), + } + } + } + } + +FIXME (#2810)--Hygiene. Search for "__" strings. We also assume "std" is the +standard library. + +Misc notes: +----------- + +I use move mode arguments for ast nodes that will get inserted as is +into the tree. This is intended to prevent us from inserting the same +node twice. + +*/ + +use base::*; +use codemap::span; +use std::map; +use std::map::HashMap; + +export expand; + +// Transitional reexports so qquote can find the paths it is looking for +mod syntax { + pub use ext; + pub use parse; +} + +fn expand(cx: ext_ctxt, + span: span, + _mitem: ast::meta_item, + in_items: ~[@ast::item]) -> ~[@ast::item] { + fn not_auto_serialize2(a: ast::attribute) -> bool { + attr::get_attr_name(a) != ~"auto_serialize2" + } + + fn filter_attrs(item: @ast::item) -> @ast::item { + @{attrs: vec::filter(item.attrs, not_auto_serialize2), + .. *item} + } + + do vec::flat_map(in_items) |item| { + match item.node { + ast::item_ty(@{node: ast::ty_rec(fields), _}, tps) => { + ~[ + filter_attrs(item), + mk_rec_impl(cx, item.span, item.ident, fields, tps), + ] + }, + ast::item_class(@{ fields, _}, tps) => { + ~[ + filter_attrs(item), + mk_struct_impl(cx, item.span, item.ident, fields, tps), + ] + }, + ast::item_enum(enum_def, tps) => { + ~[ + filter_attrs(item), + mk_enum_impl(cx, item.span, item.ident, enum_def, tps), + ] + }, + _ => { + cx.span_err(span, ~"#[auto_serialize2] can only be applied \ + to structs, record types, and enum \ + definitions"); + ~[item] + } + } + } +} + +fn mk_impl( + cx: ext_ctxt, + span: span, + ident: ast::ident, + tps: ~[ast::ty_param], + ser_body: @ast::stmt, + deser_body: @ast::expr +) -> @ast::item { + // Make a path to the std::serialization2::Serializable trait. + let path = cx.path( + span, + ~[ + cx.ident_of(~"std"), + cx.ident_of(~"serialization2"), + cx.ident_of(~"Serializable"), + ] + ); + + // All the type parameters need to bound to + // std::serialization::Serializable. + let trait_tps = do tps.map |tp| { + let t_bound = ast::bound_trait(@{ + id: cx.next_id(), + node: ast::ty_path(path, cx.next_id()), + span: span, + }); + + { + ident: tp.ident, + id: cx.next_id(), + bounds: @vec::append(~[t_bound], *tp.bounds) + } + }; + + let opt_trait = Some(@{ + path: path, + ref_id: cx.next_id(), + impl_id: cx.next_id(), + }); + + let ty = cx.ty_path( + span, + ~[ident], + tps.map(|tp| cx.ty_path(span, ~[tp.ident], ~[])) + ); + + let methods = ~[ + mk_ser_method(cx, span, cx.blk(span, ~[ser_body])), + mk_deser_method(cx, span, ty, cx.expr_blk(deser_body)), + ]; + + @{ + // This is a new-style impl declaration. + // XXX: clownshoes + ident: ast::token::special_idents::clownshoes_extensions, + attrs: ~[], + id: cx.next_id(), + node: ast::item_impl(trait_tps, opt_trait, ty, methods), + vis: ast::public, + span: span, + } +} + +fn mk_ser_method( + cx: ext_ctxt, + span: span, + ser_body: ast::blk +) -> @ast::method { + let ser_bound = cx.ty_path( + span, + ~[ + cx.ident_of(~"std"), + cx.ident_of(~"serialization2"), + cx.ident_of(~"Serializer"), + ], + ~[] + ); + + let ser_tps = ~[{ + ident: cx.ident_of(~"__S"), + id: cx.next_id(), + bounds: @~[ast::bound_trait(ser_bound)], + }]; + + let ser_inputs = ~[{ + mode: ast::expl(ast::by_ref), + ty: cx.ty_path(span, ~[cx.ident_of(~"__S")], ~[]), + ident: cx.ident_of(~"__s"), + id: cx.next_id(), + }]; + + let ser_output = @{ + id: cx.next_id(), + node: ast::ty_nil, + span: span, + }; + + let ser_decl = { + inputs: ser_inputs, + output: ser_output, + cf: ast::return_val, + }; + + @{ + ident: cx.ident_of(~"serialize"), + attrs: ~[], + tps: ser_tps, + self_ty: { node: ast::sty_by_ref, span: span }, + purity: ast::impure_fn, + decl: ser_decl, + body: ser_body, + id: cx.next_id(), + span: span, + self_id: cx.next_id(), + vis: ast::public, + } +} + +fn mk_deser_method( + cx: ext_ctxt, + span: span, + ty: @ast::ty, + deser_body: ast::blk +) -> @ast::method { + let deser_bound = cx.ty_path( + span, + ~[ + cx.ident_of(~"std"), + cx.ident_of(~"serialization2"), + cx.ident_of(~"Deserializer"), + ], + ~[] + ); + + let deser_tps = ~[{ + ident: cx.ident_of(~"__D"), + id: cx.next_id(), + bounds: @~[ast::bound_trait(deser_bound)], + }]; + + let deser_inputs = ~[{ + mode: ast::expl(ast::by_ref), + ty: cx.ty_path(span, ~[cx.ident_of(~"__D")], ~[]), + ident: cx.ident_of(~"__d"), + id: cx.next_id(), + }]; + + let deser_decl = { + inputs: deser_inputs, + output: ty, + cf: ast::return_val, + }; + + @{ + ident: cx.ident_of(~"deserialize"), + attrs: ~[], + tps: deser_tps, + self_ty: { node: ast::sty_static, span: span }, + purity: ast::impure_fn, + decl: deser_decl, + body: deser_body, + id: cx.next_id(), + span: span, + self_id: cx.next_id(), + vis: ast::public, + } +} + +fn mk_rec_impl( + cx: ext_ctxt, + span: span, + ident: ast::ident, + fields: ~[ast::ty_field], + tps: ~[ast::ty_param] +) -> @ast::item { + // Records and structs don't have the same fields types, but they share + // enough that if we extract the right subfields out we can share the + // serialization generator code. + let fields = do fields.map |field| { + { + span: field.span, + ident: field.node.ident, + mutbl: field.node.mt.mutbl, + } + }; + + let ser_body = mk_ser_fields(cx, span, fields); + let deser_body = do mk_deser_fields(cx, span, fields) |fields| { + ast::expr_rec(fields, None) + }; + + mk_impl(cx, span, ident, tps, ser_body, deser_body) +} + +fn mk_struct_impl( + cx: ext_ctxt, + span: span, + ident: ast::ident, + fields: ~[@ast::struct_field], + tps: ~[ast::ty_param] +) -> @ast::item { + // Records and structs don't have the same fields types, but they share + // enough that if we extract the right subfields out we can share the + // serialization generator code. + let fields = do fields.map |field| { + let (ident, mutbl) = match field.node.kind { + ast::named_field(ident, mutbl, _) => (ident, mutbl), + _ => fail ~"[auto_serialize2] does not support \ + unnamed fields", + }; + + { + span: field.span, + ident: ident, + mutbl: match mutbl { + ast::class_mutable => ast::m_mutbl, + ast::class_immutable => ast::m_imm, + }, + } + }; + + let ser_body = mk_ser_fields(cx, span, fields); + let deser_body = do mk_deser_fields(cx, span, fields) |fields| { + ast::expr_struct(cx.path(span, ~[ident]), fields, None) + }; + + mk_impl(cx, span, ident, tps, ser_body, deser_body) +} + +fn mk_ser_fields( + cx: ext_ctxt, + span: span, + fields: ~[{ span: span, ident: ast::ident, mutbl: ast::mutability }] +) -> @ast::stmt { + let ext_cx = cx; // required for #ast{} + + let stmts = do fields.mapi |idx, field| { + let name = cx.lit_str(field.span, @cx.str_of(field.ident)); + let idx = cx.lit_uint(field.span, idx); + + // XXX: The next couple stanzas are just to write + // `self.$(name).serialize(s)`. It'd be nice if the #ast macro could + // write this for us, but it doesn't appear to support quaziquoting a + // value inside a field chain. + let expr_self = cx.expr( + span, + ast::expr_path( + cx.path(span, ~[cx.ident_of(~"self")]) + ) + ); + + let expr_name = cx.expr( + span, + ast::expr_field(expr_self, field.ident, ~[]) + ); + + let expr_serialize = cx.expr( + span, + ast::expr_field(expr_name, cx.ident_of(~"serialize"), ~[]) + ); + + let expr_arg = cx.expr( + span, + ast::expr_path( + cx.path(span, ~[cx.ident_of(~"__s")]) + ) + ); + + let expr = cx.expr( + span, + ast::expr_call(expr_serialize, ~[expr_arg], false) + ); + + #ast[stmt]{ __s.emit_rec_field($(name), $(idx), || $(expr))) } + }; + + let fields_lambda = cx.lambda(cx.blk(span, stmts)); + #ast[stmt]{ __s.emit_rec($(fields_lambda)) } +} + +fn mk_deser_fields( + cx: ext_ctxt, + span: span, + fields: ~[{ span: span, ident: ast::ident, mutbl: ast::mutability }], + f: fn(~[ast::field]) -> ast::expr_ +) -> @ast::expr { + let ext_cx = cx; // required for #ast{} + + let fields = do fields.mapi |idx, field| { + let name = cx.lit_str( + field.span, + @cx.str_of(field.ident) + ); + let idx = cx.lit_uint(field.span, idx); + let expr = #ast{ + __d.read_rec_field($(name), $(idx), || deserialize(__d)) + }; + + { + node: { mutbl: field.mutbl, ident: field.ident, expr: expr }, + span: field.span, + } + }; + + let fields_expr = cx.expr(span, f(fields)); + let fields_lambda = cx.lambda(cx.expr_blk(fields_expr)); + #ast{ __d.read_rec($(fields_lambda)) } +} + +fn mk_enum_impl( + cx: ext_ctxt, + span: span, + ident: ast::ident, + enum_def: ast::enum_def, + tps: ~[ast::ty_param] +) -> @ast::item { + let ser_body = mk_enum_ser_body( + cx, + ident, + span, + enum_def.variants + ); + + let deser_body = mk_enum_deser_body( + cx, + ident, + span, + enum_def.variants + ); + + mk_impl(cx, span, ident, tps, ser_body, deser_body) +} + +fn ser_variant( + cx: ext_ctxt, + v_span: span, + v_name: ast::ident, + v_idx: @ast::expr, + args: ~[ast::variant_arg] +) -> ast::arm { + let ext_cx = cx; // required for #ast{} + + // Name the variant arguments. + let names = args.mapi(|i, _arg| cx.ident_of(fmt!("__v%u", i))); + + // Bind the names to the variant argument type. + let pats = args.mapi(|i, arg| cx.binder_pat(arg.ty.span, names[i])); + + let pat_node = if pats.is_empty() { + ast::pat_ident( + ast::bind_by_implicit_ref, + cx.path(v_span, ~[v_name]), + None + ) + } else { + ast::pat_enum( + cx.path(v_span, ~[v_name]), + Some(pats) + ) + }; + + let pat = @{ + id: cx.next_id(), + node: pat_node, + span: v_span, + }; + + // Create the s.emit_variant_arg statements. + let stmts = do args.mapi |a_idx, _arg| { + let v = cx.var_ref(v_span, names[a_idx]); + let a_idx = cx.lit_uint(v_span, a_idx); + + #ast[stmt]{ + __s.emit_enum_variant_arg($(a_idx), || $(v).serialize(__s)); + } + }; + + let v_name = cx.lit_str(v_span, @cx.str_of(v_name)); + let v_sz = cx.lit_uint(v_span, stmts.len()); + let lambda = cx.lambda(cx.blk(v_span, stmts)); + let body = #ast{ + __s.emit_enum_variant($(v_name), $(v_idx), $(v_sz), $(lambda)) + }; + + { pats: ~[pat], guard: None, body: cx.expr_blk(body) } +} + +fn mk_enum_ser_body( + cx: ext_ctxt, + e_name: ast::ident, + e_span: span, + variants: ~[ast::variant] +) -> @ast::stmt { + let ext_cx = cx; // required for #ast{} + + let arms = do variants.mapi |v_idx, variant| { + let v_span = variant.span; + let v_name = variant.node.name; + let v_idx = cx.lit_uint(v_span, v_idx); + + match variant.node.kind { + ast::tuple_variant_kind(args) => + ser_variant(cx, v_span, v_name, v_idx, args), + ast::struct_variant_kind(*) => + fail ~"struct variants unimplemented", + ast::enum_variant_kind(*) => + fail ~"enum variants unimplemented", + } + }; + + let match_expr = cx.expr( + e_span, + ast::expr_match(#ast{ self }, arms) + ); + let e_name = cx.lit_str(e_span, @cx.str_of(e_name)); + + #ast[stmt]{ __s.emit_enum($(e_name), || $(match_expr)) } +} + +fn mk_enum_deser_body( + cx: ext_ctxt, + e_name: ast::ident, + e_span: span, + variants: ~[ast::variant] +) -> @ast::expr { + let ext_cx = cx; // required for #ast{} + + let mut arms = do variants.mapi |v_idx, variant| { + let v_span = variant.span; + let v_name = variant.node.name; + + let body = match variant.node.kind { + ast::tuple_variant_kind(args) => { + let tys = args.map(|a| a.ty); + + if tys.is_empty() { + // for a nullary variant v, do "v" + cx.var_ref(v_span, v_name) + } else { + // for an n-ary variant v, do "v(a_1, ..., a_n)" + + let arg_exprs = do tys.mapi |a_idx, _ty| { + let a_idx = cx.lit_uint(v_span, a_idx); + #ast{ + __d.read_enum_variant_arg($(a_idx), || { + deserialize(__d) + }) + } + }; + + cx.expr( + v_span, + ast::expr_call( + cx.var_ref(v_span, v_name), + arg_exprs, + false + ) + ) + } + }, + ast::struct_variant_kind(*) => + fail ~"struct variants unimplemented", + ast::enum_variant_kind(*) => + fail ~"enum variants unimplemented", + }; + + let pat = @{ + id: cx.next_id(), + node: ast::pat_lit(cx.lit_uint(v_span, v_idx)), + span: v_span, + }; + + { + pats: ~[pat], + guard: None, + body: cx.expr_blk(body), + } + }; + + let impossible_case = { + pats: ~[@{ id: cx.next_id(), node: ast::pat_wild, span: e_span}], + guard: None, + + // FIXME(#3198): proper error message + body: cx.expr_blk(cx.expr(e_span, ast::expr_fail(None))), + }; + + vec::push(arms, impossible_case); + + let e_name = cx.lit_str(e_span, @cx.str_of(e_name)); + let alt_expr = cx.expr(e_span, ast::expr_match(#ast{ i }, arms)); + + #ast{ + __d.read_enum($(e_name), || { + __d.read_enum_variant(|i| { + $(alt_expr) + }) + }) + } +} diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index acc8cf4f5d3..1cbcd0f6ddb 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -82,6 +82,8 @@ fn syntax_expander_table() -> HashMap<~str, syntax_extension> { syntax_expanders.insert(~"fmt", builtin(ext::fmt::expand_syntax_ext)); syntax_expanders.insert(~"auto_serialize", item_decorator(ext::auto_serialize::expand)); + syntax_expanders.insert(~"auto_serialize2", + item_decorator(ext::auto_serialize2::expand)); syntax_expanders.insert(~"env", builtin(ext::env::expand_syntax_ext)); syntax_expanders.insert(~"concat_idents", builtin(ext::concat_idents::expand_syntax_ext)); diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index c62ec28f359..a8ae9bd63a1 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -129,6 +129,8 @@ mod ext { #[legacy_exports] mod auto_serialize; #[legacy_exports] + mod auto_serialize2; + #[legacy_exports] mod source_util; mod pipes { |
