#[macro_use]
extern crate brev;
#[macro_use]
extern crate lazy_static;
extern crate handlebars;
extern crate regex;
extern crate rustc_serialize;
struct Message {
name: String,
message_type: MessageType,
number: u8,
fields: Vec<Field>
}
enum MessageType {
Request,
Response
}
struct Field {
name: String,
times: Option<String>,
field_type: FieldType
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum FieldType {
U8,
U16,
U32,
U64,
QID,
Stat,
Data,
String,
}
impl FieldType {
fn numeric(self) -> bool {
self == FieldType::U8 ||
self == FieldType::U16 ||
self == FieldType::U32 ||
self == FieldType::U64
}
}
impl Message {
fn full_name(&self) -> String {
let mut full = String::new();
full.push(match self.message_type {
MessageType::Request => 'T',
MessageType::Response => 'R',
});
full.push_str(&self.name);
full
}
}
impl std::fmt::Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
try!(write!(f, "size[4] {}:{} tag[2]", self.full_name(), self.number));
for field in &self.fields {
try!(write!(f, " {}", field))
}
Ok(())
}
}
impl std::fmt::Display for Field {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.times {
Some(ref times) => write!(f, "{}*({}[{}])", times, self.name, self.field_type),
None => write!(f, "{}[{}]", self.name, self.field_type)
}
}
}
impl std::fmt::Display for FieldType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = match self {
&FieldType::U8 => "1",
&FieldType::U16 => "2",
&FieldType::U32 => "4",
&FieldType::U64 => "8",
&FieldType::QID => "13",
&FieldType::Stat => "n",
&FieldType::Data => "count",
&FieldType::String => "s"
};
write!(f, "{}", s)
}
}
struct Context {
messages: Vec<Message>
}
impl rustc_serialize::json::ToJson for Context {
fn to_json(&self) -> rustc_serialize::json::Json {
let mut o = std::collections::BTreeMap::<String, rustc_serialize::json::Json>::new();
o.insert("messages".to_string(), self.messages.to_json());
o.to_json()
}
}
impl rustc_serialize::json::ToJson for Message {
fn to_json(&self) -> rustc_serialize::json::Json {
let mut o = std::collections::BTreeMap::<String, rustc_serialize::json::Json>::new();
o.insert("name".to_string(), self.name.to_json());
o.to_json()
}
}
macro_rules! re {
($name:ident $pattern:expr) => (
lazy_static! {
static ref $name: regex::Regex = regex::Regex::new($pattern).unwrap();
}
)
}
fn field(input: &str) -> (&str, Option<Field>) {
re!{ SCALAR r"^\s*([a-z]+)\s*[[](1|2|4|8|13|count|n|s)]\s*" }
re!{ ARRAY r"^\s*([a-z]+)\s*[*]\s*[(]([^)]*)[)]\s*" }
if let Some(captures) = SCALAR.captures(input) {
let name = captures.at(1).unwrap();
let field_type = match captures.at(2).unwrap() {
"1" => FieldType::U8,
"2" => FieldType::U16,
"4" => FieldType::U32,
"8" => FieldType::U64,
"13" => FieldType::QID,
"count" => FieldType::Data,
"n" => FieldType::Stat,
"s" => FieldType::String,
_ => return (input, None),
};
let rest = &input[captures.at(0).unwrap().len()..];
(rest, Some(Field{name: name.to_owned(), times: None, field_type: field_type}))
} else if let Some(captures) = ARRAY.captures(input) {
let times = captures.at(1).unwrap();
let field_text = captures.at(2).unwrap();
let rest = &input[captures.at(0).unwrap().len()..];
if let (_, Some(mut field)) = field(field_text) {
field.times = Some(times.to_owned());
(rest, Some(field))
} else {
(input, None)
}
} else {
(input, None)
}
}
fn name(input: &str) -> (&str, Option<(MessageType, &str, u8)>) {
re!(NAME r"\s*(T|R)([a-z]+)\s*:\s*([1-9][0-9]*)\s*");
if let Some(captures) = NAME.captures(input) {
let message_type = match captures.at(1).unwrap() {
"T" => MessageType::Request,
"R" => MessageType::Response,
_ => return (input, None),
};
let name = captures.at(2).unwrap();
let number = captures.at(3).unwrap().parse().unwrap();
let rest = &input[captures.at(0).unwrap().len()..];
(rest, Some((message_type, name, number)))
} else {
(input, None)
}
}
fn error(line_number: usize, line: &str, message: &str) -> Error {
Error {
line_number: line_number,
line: line.to_owned(),
message: message.to_owned(),
}
}
struct Error {
line_number: usize,
line: String,
message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "line {}: {}\nerror: {}", self.line_number, self.line, self.message)
}
}
fn parse(definition: &str) -> Result<Vec<Message>, Error> {
let mut messages = vec![];
let mut names = std::collections::HashSet::<String>::new();
let mut stripped = String::new();
let mut n = 0;
for line in definition.lines() {
n += 1;
let mut s = line;
if let Some(i) = s.find('#') {
s = &s[0..i];
}
s = s.trim();
if s.len() == 0 {
continue;
}
stripped = stripped + s + "\n";
let (s, size) = field(s);
match size {
Some(field) => if field.name != "size" || field.field_type != FieldType::U32 {
return Err(error(n, line, "each message must begin with size[4]"));
},
None => return Err(error(n, line, "each message must begin with size[4]")),
}
let (s, name) = name(s);
let (message_type, message_name, number) = match name {
Some(result) => result,
None => return Err(error(n, line, "size tag must be followed by a message name")),
};
let (s, tag) = field(s);
match tag {
Some(field) => if field.name != "tag" || field.field_type != FieldType::U16 {
return Err(error(n, line, "each message must have tag[2] as its first field"));
},
None => return Err(error(n, line, "each message must have tag[2] as its first field")),
}
let mut fields: Vec<Field> = vec![];
let mut s = s;
while s.len() > 0 {
let (rest, option) = field(s);
let field = match option {
Some(field) => field,
_ => return Err(error(n, line, &("bad field in message: ".to_owned() + rest))),
};
if let Some(ref times) = field.times {
if let Some(previous) = fields.last() {
if &previous.name != times || !previous.field_type.numeric() {
return Err(error(n, line, "array field must be preceded by numeric times field"));
}
} else {
return Err(error(n, line, "array field must be preceded by times field"));
}
};
fields.push(field);
s = rest;
}
let message = Message {
name: message_name.to_owned(),
message_type: message_type,
number: number,
fields: fields
};
let full_name = message.full_name();
if names.contains(&full_name) {
return Err(error(n, line, &format!("duplicate message: {}", full_name)));
};
names.insert(full_name);
messages.push(message);
}
{
let mut requests = std::collections::HashMap::<String, &Message>::new();
let mut responses = std::collections::HashMap::<String, &Message>::new();
for message in &messages {
match message.message_type {
MessageType::Request => requests.insert(message.name.clone(), message),
MessageType::Response => responses.insert(message.name.clone(), message),
};
};
for name in requests.keys() {
if !responses.contains_key(name) {
die!("request without corresponding response: {}", name)
}
}
for (name, response) in &responses {
match requests.get(name) {
Some(request) => if response.number != request.number + 1 {
warn!("Response number not equal to request number + 1: {}", name);
warn!("{}: {}", request.full_name(), request.number);
die!("{}: {}", response.full_name(), response.number);
},
None => if name != "error" { die!("response without corresponding request: {}", name) }
}
}
let mut round_tripped = String::new();
for message in &messages {
round_tripped.push_str(&format!("{}\n", message));
}
if round_tripped != stripped {
warn!("round tripped string does not match input:");
warn!("input:\n{}", stripped);
warn!("round_tripped:\n{}", round_tripped);
}
}
Ok(messages)
}
fn main() {
let text = brev::slurp("9p2000.txt");
let messages = match parse(&text) {
Ok(messages) => messages,
Err(error) => brev::die(error),
};
for message in &messages {
println!("{}", message);
}
let mut handlebars = handlebars::Handlebars::new();
let path = std::path::Path::new("templates/protocol.rs");
if let Err(error) = handlebars.register_template_file("protocol", path) {
die!("error parsing template: {}", error);
}
let rendered = match handlebars.render("protocol", &Context{messages: messages}) {
Err(error) => die!("error rendering template: {:?}", error),
Ok(s) => s
};
brev::say(rendered);
}