You can have any protocol you want, provided it's pingpong.

This integrates the pipe compiler into the proto syntax extension.
This commit is contained in:
Eric Holk 2012-07-05 16:07:53 -07:00
parent 05cdda3a2c
commit f0ef4ef81b
5 changed files with 627 additions and 7 deletions

View File

@ -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)
}

View File

@ -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<T>(+x: T) -> ast::spanned<T> {
{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)))
}
}

View File

@ -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<message>,
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<state>,
}),
}
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<A: copy, B: copy> for ([A]/~, [B]/~) {
fn zip() -> [(A, B)]/~ {
let (a, b) = self;
vec::zip(a, b)
}
fn map<C>(f: fn(A, B) -> C) -> [C]/~ {
let (a, b) = self;
vec::map2(a, b, f)
}
}

View File

@ -79,5 +79,8 @@ mod ext {
mod auto_serialize;
mod source_util;
mod pipes;
mod pipes {
mod pipec;
mod ast_builder;
}
}

View File

@ -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
}
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_ˊ));
};
}