diff --git a/src/libcore/tuple.rs b/src/libcore/tuple.rs index 0724d01afaf4..040f34ca00bc 100644 --- a/src/libcore/tuple.rs +++ b/src/libcore/tuple.rs @@ -23,6 +23,29 @@ impl extensions for (T, U) { } +impl extensions 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) + } +} + +impl extensions 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) + } +} #[test] fn test_tuple() { diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index b7d17a9d6520..f6b15ff19d9d 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -100,6 +100,7 @@ iface ext_ctxt { 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) -> !; @@ -148,6 +149,10 @@ fn mk_ctxt(parse_sess: parse::parse_sess, 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); diff --git a/src/libsyntax/ext/pipes.rs b/src/libsyntax/ext/pipes.rs index 3a6a2ff7a523..bea1c8cae0cb 100644 --- a/src/libsyntax/ext/pipes.rs +++ b/src/libsyntax/ext/pipes.rs @@ -8,7 +8,9 @@ import parse::common::parser_common; 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 @@ -22,5 +24,9 @@ fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident, let proto = rust_parser.parse_proto(id); + // check for errors + visit(proto, cx); + + // compile base::mr_item(proto.compile(cx)) } diff --git a/src/libsyntax/ext/pipes/check.rs b/src/libsyntax/ext/pipes/check.rs new file mode 100644 index 000000000000..7cf96707bca9 --- /dev/null +++ b/src/libsyntax/ext/pipes/check.rs @@ -0,0 +1,74 @@ +/// 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 diff --git a/src/libsyntax/ext/pipes/pipec.rs b/src/libsyntax/ext/pipes/pipec.rs index fa8fd67f0315..4af75fa90e7b 100644 --- a/src/libsyntax/ext/pipes/pipec.rs +++ b/src/libsyntax/ext/pipes/pipec.rs @@ -5,6 +5,8 @@ import to_str::to_str; import dvec::dvec; import dvec::extensions; +import tuple::extensions; + import ast::ident; import util::interner; import interner::{intern, get}; @@ -14,57 +16,13 @@ import ext::base::{mk_ctxt, ext_ctxt}; 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 { @@ -154,34 +112,7 @@ impl methods for message { } } -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: 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 @@ -248,47 +179,7 @@ impl methods for state { } } -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, ~[]) - } - - /// 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]; @@ -302,18 +193,12 @@ impl methods for protocol { } }; - 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 { @@ -407,15 +292,3 @@ impl parse_utils for ext_ctxt { 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/ext/pipes/proto.rs b/src/libsyntax/ext/pipes/proto.rs new file mode 100644 index 000000000000..1df3466286cb --- /dev/null +++ b/src/libsyntax/ext/pipes/proto.rs @@ -0,0 +1,150 @@ +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, + 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, + }), +} + +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 { + 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>( + 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) +} diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index 99a877a82ac1..9750f2d5bd12 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -85,5 +85,7 @@ mod ext { mod ast_builder; mod parse_proto; mod pipec; + mod proto; + mod check; } }