From f0ef4ef81b26388261ae56fdb019982ed3141668 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 5 Jul 2012 16:07:53 -0700 Subject: [PATCH] You can have any protocol you want, provided it's pingpong. This integrates the pipe compiler into the proto syntax extension. --- src/libsyntax/ext/pipes.rs | 11 +- src/libsyntax/ext/pipes/ast_builder.rs | 182 +++++++++++ src/libsyntax/ext/pipes/pipec.rs | 392 +++++++++++++++++++++++ src/libsyntax/syntax.rc | 5 +- src/test/run-pass/pipe-pingpong-proto.rs | 44 ++- 5 files changed, 627 insertions(+), 7 deletions(-) create mode 100644 src/libsyntax/ext/pipes/ast_builder.rs create mode 100644 src/libsyntax/ext/pipes/pipec.rs diff --git a/src/libsyntax/ext/pipes.rs b/src/libsyntax/ext/pipes.rs index 23bd95226c37..5a84d764b9f1 100644 --- a/src/libsyntax/ext/pipes.rs +++ b/src/libsyntax/ext/pipes.rs @@ -2,9 +2,16 @@ import codemap::span; import ext::base::ext_ctxt; +import pipes::pipec::*; + fn expand_proto(cx: ext_ctxt, span: span, id: ast::ident, tt: ast::token_tree) -> @ast::item { - cx.span_unimpl(span, - "Protocol compiler") + let proto = protocol(id); + let ping = proto.add_state(@"ping", send); + let pong = proto.add_state(@"pong", recv); + + ping.add_message(@"ping", []/~, pong, ~[]); + pong.add_message(@"pong", []/~, ping, ~[]); + proto.compile(cx) } \ No newline at end of file diff --git a/src/libsyntax/ext/pipes/ast_builder.rs b/src/libsyntax/ext/pipes/ast_builder.rs new file mode 100644 index 000000000000..b23e707f4eb2 --- /dev/null +++ b/src/libsyntax/ext/pipes/ast_builder.rs @@ -0,0 +1,182 @@ +// Functions for building ASTs, without having to fuss with spans. +// +// To start with, it will be use dummy spans, but it might someday do +// something smarter. + +import ast::{ident, node_id}; +import codemap::span; +import ext::base::mk_ctxt; + +fn ident(s: str) -> ast::ident { + @(copy s) +} + +fn empty_span() -> span { + {lo: 0, hi: 0, expn_info: none} +} + +fn span(+x: T) -> ast::spanned { + {node: x, + span: empty_span()} +} + +fn path(id: ident) -> @ast::path { + @{span: empty_span(), + global: false, + idents: ~[id], + rp: none, + types: ~[]} +} + +impl methods for ident { + fn +(id: ident) -> @ast::path { + path(self) + id + } +} + +impl methods for @ast::path { + fn +(id: ident) -> @ast::path { + @{idents: vec::append_one(self.idents, id) + with *self} + } + + fn add_ty(ty: @ast::ty) -> @ast::path { + @{types: vec::append_one(self.types, ty) + with *self} + } + + fn add_tys(+tys: ~[@ast::ty]) -> @ast::path { + @{types: vec::append(self.types, tys) + with *self} + } +} + +impl ast_builder for ext_ctxt { + fn ty_param(id: ast::ident, +bounds: ~[ast::ty_param_bound]) + -> ast::ty_param + { + {ident: id, id: self.next_id(), bounds: @bounds} + } + + fn arg(name: ident, ty: @ast::ty) -> ast::arg { + {mode: ast::infer(self.next_id()), + ty: ty, + ident: name, + // TODO: should this be the same as the infer id? + id: self.next_id()} + } + + fn arg_mode(name: ident, ty: @ast::ty, mode: ast::rmode) -> ast::arg { + {mode: ast::expl(mode), + ty: ty, + ident: name, + id: self.next_id()} + } + + fn expr_block(e: @ast::expr) -> ast::blk { + let blk = {view_items: ~[], + stmts: ~[], + expr: some(e), + id: self.next_id(), + rules: ast::default_blk}; + + {node: blk, + span: empty_span()} + } + + fn fn_decl(+inputs: ~[ast::arg], + output: @ast::ty) -> ast::fn_decl { + {inputs: inputs, + output: output, + purity: ast::impure_fn, + cf: ast::return_val, + // TODO: we'll probably want a variant that does constrained + // types. + constraints: ~[]} + } + + fn item(name: ident, + +node: ast::item_) -> @ast::item { + @{ident: name, + attrs: ~[], + id: self.next_id(), + node: node, + vis: ast::public, + span: empty_span()} + } + + fn item_fn_poly(name: ident, + +inputs: ~[ast::arg], + output: @ast::ty, + +ty_params: ~[ast::ty_param], + +body: ast::blk) -> @ast::item { + self.item(name, + ast::item_fn(self.fn_decl(inputs, output), + ty_params, + body)) + } + + fn item_fn(name: ident, + +inputs: ~[ast::arg], + output: @ast::ty, + +body: ast::blk) -> @ast::item { + self.item_fn_poly(name, inputs, output, ~[], body) + } + + fn item_enum_poly(name: ident, + +variants: ~[ast::variant], + +ty_params: ~[ast::ty_param]) -> @ast::item { + self.item(name, + ast::item_enum(variants, + ty_params, + ast::rp_none)) + } + + fn item_enum(name: ident, + +variants: ~[ast::variant]) -> @ast::item { + self.item_enum_poly(name, variants, ~[]) + } + + fn variant(name: ident, + +tys: ~[@ast::ty]) -> ast::variant { + let args = tys.map(|ty| {ty: ty, id: self.next_id()}); + + span({name: name, + attrs: ~[], + args: args, + id: self.next_id(), + disr_expr: none, + vis: ast::public}) + } + + fn item_mod(name: ident, + +items: ~[@ast::item]) -> @ast::item { + self.item(name, + ast::item_mod({ + view_items: ~[], + items: items})) + } + + fn ty_path(path: @ast::path) -> @ast::ty { + // TODO: make sure the node ids are legal. + @{id: self.next_id(), + node: ast::ty_path(path, self.next_id()), + span: empty_span()} + } + + fn item_ty_poly(name: ident, + ty: @ast::ty, + +params: ~[ast::ty_param]) -> @ast::item { + self.item(name, + ast::item_ty(ty, params, ast::rp_none)) + } + + fn item_ty(name: ident, + ty: @ast::ty) -> @ast::item { + self.item_ty_poly(name, ty, ~[]) + } + + fn ty_vars(+ty_params: ~[ast::ty_param]) -> ~[@ast::ty] { + ty_params.map(|p| self.ty_path(path(p.ident))) + } +} diff --git a/src/libsyntax/ext/pipes/pipec.rs b/src/libsyntax/ext/pipes/pipec.rs new file mode 100644 index 000000000000..0ff4ba92b366 --- /dev/null +++ b/src/libsyntax/ext/pipes/pipec.rs @@ -0,0 +1,392 @@ +// A protocol compiler for Rust. + +import to_str::to_str; + +import dvec::dvec; +import dvec::extensions; + +import ast::ident; +import util::interner; +import interner::{intern, get}; +import print::pprust; +import pprust::{item_to_str, ty_to_str}; +import ext::base::{mk_ctxt, ext_ctxt}; +import parse; +import parse::{parse_item_from_source_str}; + +import ast_builder::ast_builder; +import ast_builder::methods; +import ast_builder::path; + +enum direction { + send, recv +} + +impl of to_str for direction { + fn to_str() -> str { + alt self { + send { "send" } + recv { "recv" } + } + } +} + +impl methods for direction { + fn reverse() -> direction { + alt self { + send { recv } + recv { send } + } + } +} + +enum message { + // name, data, current state, next state, next tys + message(ident, ~[@ast::ty], state, state, ~[@ast::ty]) +} + +impl methods for message { + fn name() -> ident { + alt self { + message(id, _, _, _, _) { + id + } + } + } + + // Return the type parameters actually used by this message + fn get_params() -> ~[ast::ty_param] { + let mut used = ~[]; + alt self { + message(_, tys, this, _, next_tys) { + let parms = this.ty_params; + for vec::append(tys, next_tys).each |ty| { + alt ty.node { + ast::ty_path(path, _) { + if path.idents.len() == 1 { + let id = path.idents[0]; + + let found = parms.find(|p| id == p.ident); + + alt found { + some(p) { + if !used.contains(p) { + vec::push(used, p); + } + } + none { } + } + } + } + _ { } + } + } + } + } + used + } + + fn gen_send(cx: ext_ctxt) -> @ast::item { + alt self { + message(id, tys, this, next, next_tys) { + let arg_names = tys.mapi(|i, _ty| @("x_" + i.to_str())); + + let args = (arg_names, tys).map(|n, t| + *n + ": " + t.to_source()); + + let args_ast = (arg_names, tys).map( + |n, t| cx.arg_mode(n, t, ast::by_copy) + ); + + let args_ast = vec::append( + ~[cx.arg_mode(@"pipe", + cx.ty_path(path(this.data_name()) + .add_tys(cx.ty_vars(this.ty_params))), + ast::by_copy)], + args_ast); + + let args = [#fmt("-pipe: %s", *this.data_name())]/~ + args; + + let pat = alt (this.dir, next.dir) { + (send, send) { "(c, s)" } + (send, recv) { "(s, c)" } + (recv, send) { "(s, c)" } + (recv, recv) { "(c, s)" } + }; + + let mut body = #fmt("{ let %s = pipes::entangle();\n", pat); + body += #fmt("let message = %s::%s(%s);\n", + *this.proto.name, + *self.name(), + str::connect(vec::append_one(arg_names, @"s") + .map(|x| *x), + ", ")); + body += #fmt("pipes::send(pipe, message);\n"); + body += "c }"; + + let body = cx.parse_expr(body); + + cx.item_fn_poly(self.name(), + args_ast, + cx.ty_path(path(next.data_name()) + .add_tys(next_tys)), + self.get_params(), + cx.expr_block(body)) + } + } + } +} + +enum state { + state_(@{ + name: ident, + dir: direction, + ty_params: ~[ast::ty_param], + messages: dvec, + proto: protocol, + }), +} + +impl methods for state { + fn add_message(name: ident, +data: ~[@ast::ty], next: state, + +next_tys: ~[@ast::ty]) { + assert next_tys.len() == next.ty_params.len(); + self.messages.push(message(name, data, self, next, next_tys)); + } + + fn filename() -> str { + (*self).proto.filename() + } + + fn data_name() -> ident { + self.name + } + + fn to_ty(cx: ext_ctxt) -> @ast::ty { + cx.ty_path(path(self.name).add_tys(cx.ty_vars(self.ty_params))) + } + + fn to_type_decls(cx: ext_ctxt) -> [@ast::item]/~ { + // This compiles into two different type declarations. Say the + // state is called ping. This will generate both `ping` and + // `ping_message`. The first contains data that the user cares + // about. The second is the same thing, but extended with a + // next packet pointer, which is used under the covers. + + let name = self.data_name(); + + let mut items_msg = []/~; + + for self.messages.each |m| { + let message(_, tys, this, next, next_tys) = m; + + let name = m.name(); + let next_name = next.data_name(); + + let dir = alt this.dir { + send { @"server" } + recv { @"client" } + }; + + let v = cx.variant(name, + vec::append_one( + tys, + cx.ty_path((dir + next_name) + .add_tys(next_tys)))); + + vec::push(items_msg, v); + } + + ~[cx.item_enum_poly(name, items_msg, self.ty_params)] + } + + fn to_endpoint_decls(cx: ext_ctxt, dir: direction) -> [@ast::item]/~ { + let dir = alt dir { + send { (*self).dir } + recv { (*self).dir.reverse() } + }; + let mut items = ~[]; + for self.messages.each |m| { + if dir == send { + vec::push(items, m.gen_send(cx)) + } + } + + vec::push(items, + cx.item_ty_poly( + self.data_name(), + cx.ty_path( + (@"pipes" + @(dir.to_str() + "_packet")) + .add_ty(cx.ty_path( + (self.proto.name + self.data_name()) + .add_tys(cx.ty_vars(self.ty_params))))), + self.ty_params)); + items + } +} + +enum protocol { + protocol_(@{ + name: ident, + states: dvec, + }), +} + +fn protocol(name: ident) -> protocol { + protocol_(@{name: name, states: dvec()}) +} + +impl methods for protocol { + fn add_state(name: ident, dir: direction) -> state { + self.add_state_poly(name, dir, ~[]) + } + + fn add_state_poly(name: ident, dir: direction, + +ty_params: ~[ast::ty_param]) -> state { + let messages = dvec(); + + let state = state_(@{ + name: name, + dir: dir, + ty_params: ty_params, + messages: messages, + proto: self + }); + + self.states.push(state); + state + } + + fn filename() -> str { + "proto://" + *self.name + } + + fn gen_init(cx: ext_ctxt) -> @ast::item { + let start_state = self.states[0]; + + let body = alt start_state.dir { + send { cx.parse_expr("pipes::entangle()") } + recv { + cx.parse_expr("{ \ + let (s, c) = pipes::entangle(); \ + (c, s) \ + }") + } + }; + + parse_item_from_source_str( + self.filename(), + @#fmt("fn init%s() -> (client::%s, server::%s)\ + { %s }", + start_state.ty_params.to_source(), + start_state.to_ty(cx).to_source(), + start_state.to_ty(cx).to_source(), + body.to_source()), + cx.cfg(), + []/~, + ast::public, + cx.parse_sess()).get() + } + + fn compile(cx: ext_ctxt) -> @ast::item { + let mut items = ~[self.gen_init(cx)]; + let mut client_states = ~[]; + let mut server_states = ~[]; + + for self.states.each |s| { + items += s.to_type_decls(cx); + + client_states += s.to_endpoint_decls(cx, send); + server_states += s.to_endpoint_decls(cx, recv); + } + + vec::push(items, + cx.item_mod(@"client", + client_states)); + vec::push(items, + cx.item_mod(@"server", + server_states)); + + cx.item_mod(self.name, items) + } +} + +iface to_source { + // Takes a thing and generates a string containing rust code for it. + fn to_source() -> str; +} + +impl of to_source for @ast::item { + fn to_source() -> str { + item_to_str(self) + } +} + +impl of to_source for [@ast::item]/~ { + fn to_source() -> str { + str::connect(self.map(|i| i.to_source()), "\n\n") + } +} + +impl of to_source for @ast::ty { + fn to_source() -> str { + ty_to_str(self) + } +} + +impl of to_source for [@ast::ty]/~ { + fn to_source() -> str { + str::connect(self.map(|i| i.to_source()), ", ") + } +} + +impl of to_source for ~[ast::ty_param] { + fn to_source() -> str { + pprust::typarams_to_str(self) + } +} + +impl of to_source for @ast::expr { + fn to_source() -> str { + pprust::expr_to_str(self) + } +} + +impl parse_utils for ext_ctxt { + fn parse_item(s: str) -> @ast::item { + let res = parse::parse_item_from_source_str( + "***protocol expansion***", + @(copy s), + self.cfg(), + []/~, + ast::public, + self.parse_sess()); + alt res { + some(ast) { ast } + none { + #error("Parse error with ```\n%s\n```", s); + fail + } + } + } + + fn parse_expr(s: str) -> @ast::expr { + parse::parse_expr_from_source_str( + "***protocol expansion***", + @(copy s), + self.cfg(), + self.parse_sess()) + } +} + +impl methods for ([A]/~, [B]/~) { + fn zip() -> [(A, B)]/~ { + let (a, b) = self; + vec::zip(a, b) + } + + fn map(f: fn(A, B) -> C) -> [C]/~ { + let (a, b) = self; + vec::map2(a, b, f) + } +} diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index 5a384f04cb33..852c815e9fe2 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -79,5 +79,8 @@ mod ext { mod auto_serialize; mod source_util; - mod pipes; + mod pipes { + mod pipec; + mod ast_builder; + } } diff --git a/src/test/run-pass/pipe-pingpong-proto.rs b/src/test/run-pass/pipe-pingpong-proto.rs index 0afacb99c159..479b2059bfe3 100644 --- a/src/test/run-pass/pipe-pingpong-proto.rs +++ b/src/test/run-pass/pipe-pingpong-proto.rs @@ -1,7 +1,7 @@ -// xfail-test - // An example to make sure the protocol parsing syntax extension works. +// xfail-pretty + proto! pingpong { ping:send { ping -> pong @@ -12,6 +12,42 @@ proto! pingpong { } } +mod test { + import pipes::recv; + import pingpong::{ping, pong}; + + fn client(-chan: pingpong::client::ping) { + import pingpong::client; + + let chan = client::ping(chan); + log(error, "Sent ping"); + let pong(_chan) = option::unwrap(recv(chan)); + log(error, "Received pong"); + } + + fn server(-chan: pingpong::server::ping) { + import pingpong::server; + + let ping(chan) = option::unwrap(recv(chan)); + log(error, "Received ping"); + let _chan = server::pong(chan); + log(error, "Sent pong"); + } +} + fn main() { - // TODO: do something with the protocol -} \ No newline at end of file + let (client_, server_) = pingpong::init(); + let client_ = ~mut some(client_); + let server_ = ~mut some(server_); + + do task::spawn |move client_| { + let mut client__ = none; + *client_ <-> client__; + test::client(option::unwrap(client__)); + }; + do task::spawn |move server_| { + let mut server_ˊ = none; + *server_ <-> server_ˊ; + test::server(option::unwrap(server_ˊ)); + }; +}