}
+impl extensions<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)
+ }
+}
+
+impl extensions<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)
+ }
+}
#[test]
fn test_tuple() {
fn bt_pop();
fn span_fatal(sp: span, msg: ~str) -> !;
fn span_err(sp: span, msg: ~str);
+ fn span_warn(sp: span, msg: ~str);
fn span_unimpl(sp: span, msg: ~str) -> !;
fn span_bug(sp: span, msg: ~str) -> !;
fn bug(msg: ~str) -> !;
self.print_backtrace();
self.parse_sess.span_diagnostic.span_err(sp, msg);
}
+ fn span_warn(sp: span, msg: ~str) {
+ self.print_backtrace();
+ self.parse_sess.span_diagnostic.span_warn(sp, msg);
+ }
fn span_unimpl(sp: span, msg: ~str) -> ! {
self.print_backtrace();
self.parse_sess.span_diagnostic.span_unimpl(sp, msg);
import pipes::parse_proto::proto_parser;
-import pipes::pipec::methods;
+import pipes::pipec::compile;
+import pipes::proto::{visit, protocol};
+import pipes::check::proto_check;
fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident,
tt: ~[ast::token_tree]) -> base::mac_result
let proto = rust_parser.parse_proto(id);
+ // check for errors
+ visit(proto, cx);
+
+ // compile
base::mr_item(proto.compile(cx))
}
--- /dev/null
+/// Correctness for protocols
+
+/*
+
+This section of code makes sure the protocol is likely to generate
+correct code. The correctness criteria include:
+
+ * No protocols transition to states that don't exist.
+ * Messages step to states with the right number of type parameters.
+
+In addition, this serves as a lint pass. Lint warns for the following
+things.
+
+ * States with no messages, it's better to step to !.
+
+It would also be nice to warn about unreachable states, but the
+visitor infrastructure for protocols doesn't currently work well for
+that.
+
+*/
+
+import dvec::extensions;
+
+import ext::base::ext_ctxt;
+
+import ast::{ident};
+
+import proto::{state, protocol, next_state, methods};
+import ast_builder::empty_span;
+
+impl proto_check of proto::visitor<(), (), ()> for ext_ctxt {
+ fn visit_proto(_proto: protocol,
+ _states: &[()]) { }
+
+ fn visit_state(state: state, _m: &[()]) {
+ if state.messages.len() == 0 {
+ self.span_warn(
+ empty_span(), // use a real span!
+ #fmt("state %s contains no messages, \
+ consider stepping to a terminal state instead",
+ *state.name))
+ }
+ }
+
+ fn visit_message(name: ident, _tys: &[@ast::ty],
+ this: state, next: next_state) {
+ alt next {
+ some({state: next, tys: next_tys}) {
+ let proto = this.proto;
+ if !proto.has_state(next) {
+ // This should be a span fatal, but then we need to
+ // track span information.
+ self.span_err(
+ empty_span(),
+ #fmt("message %s steps to undefined state, %s",
+ *name, *next));
+ }
+
+ let next = proto.get_state(next);
+
+ if next.ty_params.len() != next_tys.len() {
+ self.span_err(
+ empty_span(), // use a real span
+ #fmt("message %s target (%s) \
+ needs %u type parameters, but got %u",
+ *name, *next.name,
+ next.ty_params.len(),
+ next_tys.len()));
+ }
+ }
+ none { }
+ }
+ }
+}
\ No newline at end of file
import dvec::dvec;
import dvec::extensions;
+import tuple::extensions;
+
import ast::ident;
import util::interner;
import interner::{intern, get};
import parse;
import parse::*;
+import proto::*;
+
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 }
- }
- }
-}
-
-type next_state = option<{state: ident, tys: ~[@ast::ty]}>;
-
-enum message {
- // name, data, current state, next state
- message(ident, ~[@ast::ty], state, next_state)
-}
-
-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] {
- alt self {
- message(_, _, this, _) {
- this.ty_params
- }
- }
- }
-
+impl compile for message {
fn gen_send(cx: ext_ctxt) -> @ast::item {
#debug("pipec: gen_send");
alt self {
}
}
-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: next_state) {
- self.messages.push(message(name, data, self,
- next));
- }
-
- 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)))
- }
-
+impl compile for state {
fn to_type_decls(cx: ext_ctxt) -> ~[@ast::item] {
#debug("pipec: to_type_decls");
// This compiles into two different type declarations. Say the
}
}
-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, ~[])
- }
-
- /// Get or create a state.
- fn get_state(name: ident) -> state {
- self.states.find(|i| i.name == name).get()
- }
-
- 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
- }
-
+impl compile for protocol {
fn gen_init(cx: ext_ctxt) -> @ast::item {
let start_state = self.states[0];
}
};
- 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()
+ cx.parse_item(#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()))
}
fn compile(cx: ext_ctxt) -> @ast::item {
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)
- }
-}
--- /dev/null
+import to_str::to_str;
+import dvec::{dvec, extensions};
+
+import ast::{ident};
+
+import ast_builder::{path, methods, ast_builder};
+
+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 }
+ }
+ }
+}
+
+type next_state = option<{state: ident, tys: ~[@ast::ty]}>;
+
+enum message {
+ // name, data, current state, next state
+ message(ident, ~[@ast::ty], state, next_state)
+}
+
+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] {
+ alt self {
+ message(_, _, this, _) {
+ this.ty_params
+ }
+ }
+ }
+}
+
+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: next_state) {
+ self.messages.push(message(name, data, self,
+ next));
+ }
+
+ 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)))
+ }
+}
+
+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, ~[])
+ }
+
+ /// Get or create a state.
+ fn get_state(name: ident) -> state {
+ self.states.find(|i| i.name == name).get()
+ }
+
+ fn has_state(name: ident) -> bool {
+ self.states.find(|i| i.name == name) != none
+ }
+
+ 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
+ }
+}
+
+trait visitor<Tproto, Tstate, Tmessage> {
+ fn visit_proto(proto: protocol, st: &[Tstate]) -> Tproto;
+ fn visit_state(state: state, m: &[Tmessage]) -> Tstate;
+ fn visit_message(name: ident, tys: &[@ast::ty],
+ this: state, next: next_state) -> Tmessage;
+}
+
+fn visit<Tproto, Tstate, Tmessage, V: visitor<Tproto, Tstate, Tmessage>>(
+ proto: protocol, visitor: V) -> Tproto {
+
+ // the copy keywords prevent recursive use of dvec
+ let states = do (copy proto.states).map_to_vec |s| {
+ let messages = do (copy s.messages).map_to_vec |m| {
+ let message(name, tys, this, next) = m;
+ visitor.visit_message(name, tys, this, next)
+ };
+ visitor.visit_state(s, messages)
+ };
+ visitor.visit_proto(proto, states)
+}
mod ast_builder;
mod parse_proto;
mod pipec;
+ mod proto;
+ mod check;
}
}