nine_spec/
lib.rs

1#[macro_use]
2extern crate lazy_static;
3
4extern crate regex;
5
6mod tests;
7
8pub struct Message {
9  pub name:         String,
10  pub full_name:    String,
11  pub message_type: MessageType,
12  pub number:       u8,
13  pub fields:       Vec<Field>
14}
15
16#[derive(Copy, Clone)]
17pub enum MessageType {
18  Request,
19  Response
20}
21
22impl MessageType {
23  fn char(self) -> char {
24    match self {
25      MessageType::Request => 'T',
26      MessageType::Response => 'R',
27    }
28  }
29}
30
31pub struct Field {
32  pub name:       String,
33  pub times:      Option<String>,
34  pub field_type: FieldType
35}
36
37#[derive(Debug, Eq, PartialEq, Copy, Clone)]
38pub enum FieldType {
39  U8,
40  U16,
41  U32,
42  U64,
43  QID,
44  Stat,
45  Bytes,
46  String,
47}
48
49impl FieldType {
50  fn integer(self) -> bool {
51    match self {
52      FieldType::U8 | FieldType::U16 | FieldType::U32 | FieldType::U64 => true,
53      _ => false,
54    }
55  }
56}
57
58impl std::fmt::Display for Message {
59  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
60    try!(write!(f, "size[4] {}:{} tag[2]", self.full_name, self.number));
61    for field in &self.fields {
62      try!(write!(f, " {}", field))
63    }
64    Ok(())
65  }
66}
67
68impl std::fmt::Display for Field {
69  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
70    match self.times {
71      Some(ref times) => write!(f, "{}*({}[{}])", times, self.name, self.field_type),
72      None => write!(f, "{}[{}]", self.name, self.field_type)
73    }
74  }
75}
76
77impl std::fmt::Display for FieldType {
78  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
79    let s = match self {
80      &FieldType::U8 => "1",
81      &FieldType::U16 => "2",
82      &FieldType::U32 => "4",
83      &FieldType::U64 => "8",
84      &FieldType::QID => "13",
85      &FieldType::Stat => "n",
86      &FieldType::Bytes => "count",
87      &FieldType::String => "s"
88    };
89    write!(f, "{}", s)
90  }
91}
92
93macro_rules! re {
94  ($name:ident $pattern:expr) => (
95    lazy_static! {
96      static ref $name: regex::Regex = regex::Regex::new($pattern).unwrap();
97    }
98  )
99}
100
101fn field(input: &str) -> (&str, Option<Field>) {
102  re!{ SCALAR r"^\s*([a-z]+)\s*[[](1|2|4|8|13|count|n|s)]\s*" }
103  re!{ ARRAY r"^\s*([a-z]+)\s*[*]\s*[(]([^)]*)[)]\s*" }
104  if let Some(captures) = SCALAR.captures(input) {
105    let name = captures.at(1).unwrap();
106    let field_type = match captures.at(2).unwrap() {
107      "1"     => FieldType::U8,
108      "2"     => FieldType::U16,
109      "4"     => FieldType::U32,
110      "8"     => FieldType::U64,
111      "13"    => FieldType::QID,
112      "count" => FieldType::Bytes,
113      "n"     => FieldType::Stat,
114      "s"     => FieldType::String,
115      _       => return (input, None),
116    };
117    let rest = &input[captures.at(0).unwrap().len()..];
118    (rest, Some(Field{name: name.to_owned(), times: None, field_type: field_type}))
119  } else if let Some(captures) = ARRAY.captures(input) {
120    let times = captures.at(1).unwrap();
121    let field_text = captures.at(2).unwrap();
122    let rest = &input[captures.at(0).unwrap().len()..];
123    if let (_, Some(mut field)) = field(field_text) {
124      field.times = Some(times.to_owned());
125      (rest, Some(field))
126    } else {
127      (input, None)
128    }
129  } else {
130    (input, None)
131  }
132}
133
134fn name(input: &str) -> (&str, Option<(MessageType, &str, u8)>) {
135  re!(NAME r"\s*(T|R)([a-z]+)\s*:\s*([1-9][0-9]*)\s*");
136  if let Some(captures) = NAME.captures(input) {
137    let message_type = match captures.at(1).unwrap() {
138      "T" => MessageType::Request,
139      "R" => MessageType::Response,
140      _ => return (input, None),
141    };
142    let name = captures.at(2).unwrap();
143    let number = captures.at(3).unwrap().parse().unwrap();
144    let rest = &input[captures.at(0).unwrap().len()..];
145    (rest, Some((message_type, name, number)))
146  } else {
147    (input, None)
148  }
149}
150
151fn error(line_number: usize, line: &str, message: &str) -> Result<Vec<Message>, String> {
152  Err(if line_number > 0 {
153    format!("line {}: {}\nerror: {}", line_number, line, message)
154  } else {
155    format!("error: {}", message)
156  })
157}
158
159pub fn strip(definition: &str) -> Vec<(usize, &str)> {
160  definition.lines().enumerate().flat_map(|(i, mut line)| {
161    if let Some(i) = line.find('#') {
162      line = &line[0..i]
163    }
164
165    line = line.trim();
166
167    if line.len() == 0 {
168      None
169    } else {
170      Some((i, line))
171    }
172  }).collect()
173}
174
175pub fn parse(definition: &str) -> Result<Vec<Message>, String> {
176  let mut messages = vec![];
177  let mut names = std::collections::HashSet::<String>::new();
178  let mut numbers = std::collections::HashSet::<u8>::new();
179
180  for (i, line) in strip(definition) {
181    let n = i + 1;
182    let (s, size) = field(line);
183    match size {
184      Some(field) => if field.name != "size" || field.field_type != FieldType::U32 {
185        return error(n, line, "each message must begin with size[4]");
186      },
187      None => return error(n, line, "each message must begin with size[4]"),
188    }
189
190    let (s, name) = name(s);
191    let (message_type, message_name, number) = match name {
192      Some(result) => result,
193      None => return error(n, line, "size tag must be followed by a message name"),
194    };
195
196    let (s, tag) = field(s);
197    match tag {
198      Some(field) => if field.name != "tag" || field.field_type != FieldType::U16 {
199        return error(n, line, "each message must have tag[2] as its first field");
200      },
201      None => return error(n, line, "each message must have tag[2] as its first field"),
202    }
203
204    let mut fields: Vec<Field> = vec![];
205    let mut s = s;
206    while s.len() > 0 {
207      let (rest, option) = field(s);
208      let field = match option {
209        Some(field) => field,
210        _ => return error(n, line, &("bad field in message: ".to_owned() + rest)),
211      };
212      if let Some(ref times) = field.times {
213        if let Some(previous) = fields.last() {
214          if &previous.name != times || !previous.field_type.integer() {
215            return error(n, line, "array field must be preceded by integer times field");
216          }
217        } else {
218          return error(n, line, "array field must be preceded by times field");
219        }
220      };
221      fields.push(field);
222      s = rest;
223    }
224
225    let message = Message {
226      name: message_name.to_owned(),
227      full_name: message_type.char().to_string() + message_name,
228      message_type: message_type,
229      number: number,
230      fields: fields
231    };
232    if names.contains(&message.full_name) {
233      return error(n, line, &format!("duplicate message name: {}", message.full_name));
234    };
235    names.insert(message.full_name.clone());
236    if numbers.contains(&message.number) {
237      let msg = &format!("duplicate message number: {}:{}", message.full_name, message.number);
238      return error(n, line, msg);
239    }
240    numbers.insert(message.number);
241    messages.push(message);
242  }
243
244  {
245    let mut requests = std::collections::HashMap::<String, &Message>::new();
246    let mut responses = std::collections::HashMap::<String, &Message>::new();
247
248    for message in &messages {
249      match message.message_type {
250        MessageType::Request => requests.insert(message.name.clone(), message),
251        MessageType::Response => responses.insert(message.name.clone(), message),
252      };
253    }
254
255    for name in requests.keys() {
256      if !responses.contains_key(name) {
257        return error(0, "", &format!("request without corresponding response: {}", name));
258      }
259    }
260
261    for (name, response) in &responses {
262      match requests.get(name) {
263        Some(request) => if response.number != request.number + 1 {
264          return error(0, "", &format!(
265            "Response number not equal to request number + 1: {}\n{}: {}\n{}\n{}\n",
266            name, request.full_name, request.number, response.full_name, response.number)
267          );
268        },
269        None => if name != "error" { 
270          return error(0, "", &format!("response without corresponding request: {}", name));
271        }
272      }
273    }
274  }
275
276  Ok(messages)
277}