about summary refs log tree commit diff
path: root/src/libsyntax
diff options
context:
space:
mode:
authorEric Holk <eric.holk@gmail.com>2012-07-05 16:07:53 -0700
committerEric Holk <eric.holk@gmail.com>2012-07-06 10:42:40 -0700
commitf0ef4ef81b26388261ae56fdb019982ed3141668 (patch)
treee9f9f41893e5e1c9eebe59bc624f9c44bf387111 /src/libsyntax
parent05cdda3a2c147fdc8caef4e6c662f518ec325e0a (diff)
downloadrust-f0ef4ef81b26388261ae56fdb019982ed3141668.tar.gz
rust-f0ef4ef81b26388261ae56fdb019982ed3141668.zip
You can have any protocol you want, provided it's pingpong.
This integrates the pipe compiler into the proto syntax extension.
Diffstat (limited to 'src/libsyntax')
-rw-r--r--src/libsyntax/ext/pipes.rs11
-rw-r--r--src/libsyntax/ext/pipes/ast_builder.rs182
-rw-r--r--src/libsyntax/ext/pipes/pipec.rs392
-rw-r--r--src/libsyntax/syntax.rc5
4 files changed, 587 insertions, 3 deletions
diff --git a/src/libsyntax/ext/pipes.rs b/src/libsyntax/ext/pipes.rs
index 23bd95226c3..5a84d764b9f 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 00000000000..b23e707f4eb
--- /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<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)))
+    }
+}
diff --git a/src/libsyntax/ext/pipes/pipec.rs b/src/libsyntax/ext/pipes/pipec.rs
new file mode 100644
index 00000000000..0ff4ba92b36
--- /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<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)
+    }
+}
diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc
index 5a384f04cb3..852c815e9fe 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;
+    }
 }