1
Fork 0

Error checking for protocols. We'll need spans though.

This commit is contained in:
Eric Holk 2012-07-16 17:27:04 -07:00
parent 156eceb24a
commit c8739cb0bc
7 changed files with 274 additions and 141 deletions

View file

@ -23,6 +23,29 @@ impl extensions <T:copy, U:copy> for (T, U) {
}
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() {

View file

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

View file

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

View file

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

View file

@ -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<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
@ -248,47 +179,7 @@ impl methods for state {
}
}
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];
@ -302,18 +193,12 @@ impl methods for protocol {
}
};
parse_item_from_source_str(
self.filename(),
@#fmt("fn init%s() -> (client::%s, server::%s)\
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()),
cx.cfg(),
~[],
ast::public,
cx.parse_sess()).get()
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<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

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

View file

@ -85,5 +85,7 @@ mod ext {
mod ast_builder;
mod parse_proto;
mod pipec;
mod proto;
mod check;
}
}