port server parser to nom 5
authoralfadur
Mon, 08 Apr 2019 22:16:44 +0300
changeset 14780 09d46ab83361
parent 14779 d0ad9fd923fd
child 14781 1aea457856e2
port server parser to nom 5
rust/hedgewars-server/Cargo.toml
rust/hedgewars-server/src/protocol.rs
rust/hedgewars-server/src/protocol/parser.rs
--- a/rust/hedgewars-server/Cargo.toml	Mon Apr 08 20:58:41 2019 +0200
+++ b/rust/hedgewars-server/Cargo.toml	Mon Apr 08 22:16:44 2019 +0300
@@ -14,7 +14,7 @@
 mio = "0.6"
 slab = "0.4"
 netbuf = "0.4"
-nom = "4.2"
+nom = { git = "https://github.com/Geal/nom", branch = "5.0" }
 env_logger = "0.6"
 log = "0.4"
 base64 = "0.10"
--- a/rust/hedgewars-server/src/protocol.rs	Mon Apr 08 20:58:41 2019 +0200
+++ b/rust/hedgewars-server/src/protocol.rs	Mon Apr 08 22:16:44 2019 +0300
@@ -1,5 +1,5 @@
 use netbuf;
-use nom::{Err, IResult};
+use nom::{Err, ErrorKind, IResult};
 use std::io::{Read, Result};
 
 pub mod messages;
@@ -31,8 +31,7 @@
                 self.consumed = self.buf.len() - self.consumed - tail.len();
                 msgs
             }
-            Err(Err::Incomplete(_)) => unreachable!(),
-            Err(Err::Error(_)) | Err(Err::Failure(_)) => unreachable!(),
+            _ => unreachable!(),
         }
     }
 
--- a/rust/hedgewars-server/src/protocol/parser.rs	Mon Apr 08 20:58:41 2019 +0200
+++ b/rust/hedgewars-server/src/protocol/parser.rs	Mon Apr 08 22:16:44 2019 +0300
@@ -7,236 +7,545 @@
  * A unary command, such as `START_GAME nick` will be actually sent as `START_GAME\nnick\n\n`.
  */
 use nom::*;
+use std::{
+    num::ParseIntError,
+    ops::Range,
+    str,
+    str::{FromStr, Utf8Error},
+};
 
 use super::messages::{HWProtocolMessage, HWProtocolMessage::*};
 use crate::server::coretypes::{GameCfg, HedgehogInfo, TeamInfo, VoteType, MAX_HEDGEHOGS_PER_TEAM};
-use std::{ops::Range, str, str::FromStr};
+
 #[cfg(test)]
 use {
     super::test::gen_proto_msg,
     proptest::{proptest, proptest_helper},
 };
 
-named!(end_of_message, tag!("\n\n"));
-named!(str_line<&[u8],   &str>, map_res!(not_line_ending, str::from_utf8));
-named!(  a_line<&[u8], String>, map!(str_line, String::from));
-named!(cmd_arg<&[u8], String>,
-    map!(map_res!(take_until_either!(" \n"), str::from_utf8), String::from));
-named!( u8_line<&[u8],     u8>, map_res!(str_line, FromStr::from_str));
-named!(u16_line<&[u8],    u16>, map_res!(str_line, FromStr::from_str));
-named!(u32_line<&[u8],    u32>, map_res!(str_line, FromStr::from_str));
-named!(yes_no_line<&[u8], bool>, alt!(
-      do_parse!(tag_no_case!("YES") >> (true))
-    | do_parse!(tag_no_case!("NO") >> (false))));
-named!(opt_param<&[u8], Option<String> >, alt!(
-      do_parse!(peek!(tag!("\n\n")) >> (None))
-    | do_parse!(tag!("\n") >> s: str_line >> (Some(s.to_string())))));
-named!(spaces<&[u8], &[u8]>, preceded!(tag!(" "), eat_separator!(" ")));
-named!(opt_space_param<&[u8], Option<String> >, alt!(
-      do_parse!(peek!(tag!("\n\n")) >> (None))
-    | do_parse!(spaces >> s: str_line >> (Some(s.to_string())))));
-named!(hog_line<&[u8], HedgehogInfo>,
-    do_parse!(name: str_line >> eol >> hat: str_line >>
-        (HedgehogInfo{name: name.to_string(), hat: hat.to_string()})));
-named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>,
-    do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >>
-              h3: hog_line >> eol >> h4: hog_line >> eol >>
-              h5: hog_line >> eol >> h6: hog_line >> eol >>
-              h7: hog_line >> eol >> h8: hog_line >>
-              ([h1, h2, h3, h4, h5, h6, h7, h8])));
-named!(voting<&[u8], VoteType>, alt!(
-      do_parse!(tag_no_case!("KICK") >> spaces >> n: a_line >>
-        (VoteType::Kick(n)))
-    | do_parse!(tag_no_case!("MAP") >>
-        n: opt!(preceded!(spaces, a_line)) >>
-        (VoteType::Map(n)))
-    | do_parse!(tag_no_case!("PAUSE") >>
-        (VoteType::Pause))
-    | do_parse!(tag_no_case!("NEWSEED") >>
-        (VoteType::NewSeed))
-    | do_parse!(tag_no_case!("HEDGEHOGS") >> spaces >> n: u8_line >>
-        (VoteType::HedgehogsPerTeam(n)))));
+#[derive(Debug, PartialEq)]
+pub struct HWProtocolError {}
+
+impl HWProtocolError {
+    fn new() -> Self {
+        HWProtocolError {}
+    }
+}
+
+impl<I> ParseError<I> for HWProtocolError {
+    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
+        HWProtocolError::new()
+    }
+
+    fn append(input: I, kind: ErrorKind, other: Self) -> Self {
+        HWProtocolError::new()
+    }
+}
+
+impl From<Utf8Error> for HWProtocolError {
+    fn from(_: Utf8Error) -> Self {
+        HWProtocolError::new()
+    }
+}
+
+impl From<ParseIntError> for HWProtocolError {
+    fn from(_: ParseIntError) -> Self {
+        HWProtocolError::new()
+    }
+}
+
+pub type HWResult<'a, O> = IResult<&'a [u8], O, HWProtocolError>;
+
+fn end_of_message(input: &[u8]) -> HWResult<&[u8]> {
+    tag("\n\n")(input)
+}
+
+fn convert_utf8(input: &[u8]) -> HWResult<&str> {
+    match str::from_utf8(input) {
+        Ok(str) => Ok((b"", str)),
+        Err(utf_err) => Result::Err(Err::Failure(utf_err.into())),
+    }
+}
+
+fn convert_from_str<T>(str: &str) -> HWResult<T>
+where
+    T: FromStr<Err = ParseIntError>,
+{
+    match T::from_str(str) {
+        Ok(x) => Ok((b"", x)),
+        Err(format_err) => Result::Err(Err::Failure(format_err.into())),
+    }
+}
+
+fn str_line(input: &[u8]) -> HWResult<&str> {
+    let (i, text) = not_line_ending(input)?;
+    Ok((i, convert_utf8(text)?.1))
+}
+
+fn a_line(input: &[u8]) -> HWResult<String> {
+    let (i, str) = str_line(input)?;
+    Ok((i, str.to_string()))
+}
+
+fn hw_tag<'a>(tag_str: &'a str) -> impl Fn(&'a [u8]) -> HWResult<'a, ()> {
+    move |i| tag(tag_str)(i).map(|(i, _)| (i, ()))
+}
+
+fn hw_tag_no_case<'a>(tag_str: &'a str) -> impl Fn(&'a [u8]) -> HWResult<'a, ()> {
+    move |i| tag_no_case(tag_str)(i).map(|(i, _)| (i, ()))
+}
+
+fn cmd_arg(input: &[u8]) -> HWResult<String> {
+    let delimiters = b" \n";
+    let (i, str) = take_while(move |c| !delimiters.contains(&c))(input)?;
+    Ok((i, convert_utf8(str)?.1.to_string()))
+}
+
+fn u8_line(input: &[u8]) -> HWResult<u8> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn u16_line(input: &[u8]) -> HWResult<u16> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn u32_line(input: &[u8]) -> HWResult<u32> {
+    let (i, str) = str_line(input)?;
+    Ok((i, convert_from_str(str)?.1))
+}
+
+fn yes_no_line(input: &[u8]) -> HWResult<bool> {
+    alt((
+        |i| tag_no_case(b"YES")(i).map(|(i, _)| (i, true)),
+        |i| tag_no_case(b"NO")(i).map(|(i, _)| (i, false)),
+    ))(input)
+}
+
+fn opt_arg<'a>(input: &'a [u8]) -> HWResult<'a, Option<String>> {
+    alt((
+        |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
+        |i| precededc(i, hw_tag("\n"), a_line).map(|(i, v)| (i, Some(v))),
+    ))(input)
+}
+
+fn spaces(input: &[u8]) -> HWResult<&[u8]> {
+    precededc(input, hw_tag(" "), |i| take_while(|c| c == b' ')(i))
+}
+
+fn opt_space_arg<'a>(input: &'a [u8]) -> HWResult<'a, Option<String>> {
+    alt((
+        |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
+        |i| precededc(i, spaces, a_line).map(|(i, v)| (i, Some(v))),
+    ))(input)
+}
 
-/** Recognizes messages which do not take any parameters */
-named!(basic_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("PING") >> (Ping))
-    | do_parse!(tag!("PONG") >> (Pong))
-    | do_parse!(tag!("LIST") >> (List))
-    | do_parse!(tag!("BANLIST")        >> (BanList))
-    | do_parse!(tag!("GET_SERVER_VAR") >> (GetServerVar))
-    | do_parse!(tag!("TOGGLE_READY")   >> (ToggleReady))
-    | do_parse!(tag!("START_GAME")     >> (StartGame))
-    | do_parse!(tag!("ROUNDFINISHED")  >> _m: opt_param >> (RoundFinished))
-    | do_parse!(tag!("TOGGLE_RESTRICT_JOINS")  >> (ToggleRestrictJoin))
-    | do_parse!(tag!("TOGGLE_RESTRICT_TEAMS")  >> (ToggleRestrictTeams))
-    | do_parse!(tag!("TOGGLE_REGISTERED_ONLY") >> (ToggleRegisteredOnly))
-));
+fn hedgehog_array(input: &[u8]) -> HWResult<[HedgehogInfo; 8]> {
+    fn hedgehog_line(input: &[u8]) -> HWResult<HedgehogInfo> {
+        let (i, name) = terminatedc(input, a_line, eol)?;
+        let (i, hat) = a_line(i)?;
+        Ok((i, HedgehogInfo { name, hat }))
+    }
+
+    let (i, h1) = terminatedc(input, hedgehog_line, eol)?;
+    let (i, h2) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h3) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h4) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h5) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h6) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h7) = terminatedc(i, hedgehog_line, eol)?;
+    let (i, h8) = hedgehog_line(i)?;
+
+    Ok((i, [h1, h2, h3, h4, h5, h6, h7, h8]))
+}
 
-/** Recognizes messages which take exactly one parameter */
-named!(one_param_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("NICK")    >> eol >> n: a_line >> (Nick(n)))
-    | do_parse!(tag!("INFO")    >> eol >> n: a_line >> (Info(n)))
-    | do_parse!(tag!("CHAT")    >> eol >> m: a_line >> (Chat(m)))
-    | do_parse!(tag!("PART")    >> msg: opt_param   >> (Part(msg)))
-    | do_parse!(tag!("FOLLOW")  >> eol >> n: a_line >> (Follow(n)))
-    | do_parse!(tag!("KICK")    >> eol >> n: a_line >> (Kick(n)))
-    | do_parse!(tag!("UNBAN")   >> eol >> n: a_line >> (Unban(n)))
-    | do_parse!(tag!("EM")      >> eol >> m: a_line >> (EngineMessage(m)))
-    | do_parse!(tag!("TEAMCHAT")    >> eol >> m: a_line >> (TeamChat(m)))
-    | do_parse!(tag!("ROOM_NAME")   >> eol >> n: a_line >> (RoomName(n)))
-    | do_parse!(tag!("REMOVE_TEAM") >> eol >> n: a_line >> (RemoveTeam(n)))
+fn voting(input: &[u8]) -> HWResult<VoteType> {
+    alt((
+        |i| tag_no_case("PAUSE")(i).map(|(i, _)| (i, VoteType::Pause)),
+        |i| tag_no_case("NEWSEED")(i).map(|(i, _)| (i, VoteType::NewSeed)),
+        |i| {
+            precededc(i, |i| precededc(i, hw_tag_no_case("KICK"), spaces), a_line)
+                .map(|(i, s)| (i, VoteType::Kick(s)))
+        },
+        |i| {
+            precededc(
+                i,
+                |i| precededc(i, hw_tag_no_case("HEDGEHOGS"), spaces),
+                u8_line,
+            )
+            .map(|(i, n)| (i, VoteType::HedgehogsPerTeam(n)))
+        },
+        |i| precededc(i, hw_tag_no_case("MAP"), opt_space_arg).map(|(i, v)| (i, VoteType::Map(v))),
+    ))(input)
+}
 
-    | do_parse!(tag!("PROTO")   >> eol >> d: u16_line >> (Proto(d)))
+fn no_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    fn messagec<'a>(
+        input: &'a [u8],
+        name: &'a str,
+        msg: HWProtocolMessage,
+    ) -> HWResult<'a, HWProtocolMessage> {
+        tag(name)(input).map(|(i, _)| (i, msg.clone()))
+    }
+
+    alt((
+        |i| messagec(i, "PING", Ping),
+        |i| messagec(i, "PONG", Pong),
+        |i| messagec(i, "LIST", List),
+        |i| messagec(i, "BANLIST", BanList),
+        |i| messagec(i, "GET_SERVER_VAR", GetServerVar),
+        |i| messagec(i, "TOGGLE_READY", ToggleReady),
+        |i| messagec(i, "START_GAME", StartGame),
+        |i| messagec(i, "TOGGLE_RESTRICT_JOINS", ToggleRestrictJoin),
+        |i| messagec(i, "TOGGLE_RESTRICT_TEAMS", ToggleRestrictTeams),
+        |i| messagec(i, "TOGGLE_REGISTERED_ONLY", ToggleRegisteredOnly),
+    ))(input)
+}
 
-    | do_parse!(tag!("QUIT")   >> msg: opt_param >> (Quit(msg)))
-));
+fn single_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    fn messagec<'a, T, F, G>(
+        input: &'a [u8],
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> HWResult<'a, HWProtocolMessage>
+    where
+        F: Fn(&[u8]) -> HWResult<T>,
+        G: Fn(T) -> HWProtocolMessage,
+    {
+        precededc(input, hw_tag(name), parser).map(|(i, v)| (i, constructor(v)))
+    }
+
+    alt((
+        |i| messagec(i, "NICK\n", a_line, Nick),
+        |i| messagec(i, "INFO\n", a_line, Info),
+        |i| messagec(i, "CHAT\n", a_line, Chat),
+        |i| messagec(i, "PART", opt_arg, Part),
+        |i| messagec(i, "FOLLOW\n", a_line, Follow),
+        |i| messagec(i, "KICK\n", a_line, Kick),
+        |i| messagec(i, "UNBAN\n", a_line, Unban),
+        |i| messagec(i, "EM\n", a_line, EngineMessage),
+        |i| messagec(i, "TEAMCHAT\n", a_line, TeamChat),
+        |i| messagec(i, "ROOM_NAME\n", a_line, RoomName),
+        |i| messagec(i, "REMOVE_TEAM\n", a_line, RemoveTeam),
+        |i| messagec(i, "ROUNDFINISHED", opt_arg, |_| RoundFinished),
+        |i| messagec(i, "PROTO\n", u16_line, Proto),
+        |i| messagec(i, "QUIT", opt_arg, Quit),
+    ))(input)
+}
 
-/** Recognizes messages preceded with CMD */
-named!(cmd_message<&[u8], HWProtocolMessage>, preceded!(tag!("CMD\n"), alt!(
-      do_parse!(tag_no_case!("STATS") >> (Stats))
-    | do_parse!(tag_no_case!("FIX")   >> (Fix))
-    | do_parse!(tag_no_case!("UNFIX") >> (Unfix))
-    | do_parse!(tag_no_case!("RESTART_SERVER") >> spaces >> tag!("YES") >> (RestartServer))
-    | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly))
-    | do_parse!(tag_no_case!("SUPER_POWER")     >> (SuperPower))
-    | do_parse!(tag_no_case!("PART")     >> m: opt_space_param >> (Part(m)))
-    | do_parse!(tag_no_case!("QUIT")     >> m: opt_space_param >> (Quit(m)))
-    | do_parse!(tag_no_case!("DELEGATE") >> spaces >> n: a_line  >> (Delegate(n)))
-    | do_parse!(tag_no_case!("SAVE")     >> spaces >> n: cmd_arg >> spaces >> l: cmd_arg >> (Save(n, l)))
-    | do_parse!(tag_no_case!("DELETE")   >> spaces >> n: a_line  >> (Delete(n)))
-    | do_parse!(tag_no_case!("SAVEROOM") >> spaces >> r: a_line  >> (SaveRoom(r)))
-    | do_parse!(tag_no_case!("LOADROOM") >> spaces >> r: a_line  >> (LoadRoom(r)))
-    | do_parse!(tag_no_case!("GLOBAL")   >> spaces >> m: a_line  >> (Global(m)))
-    | do_parse!(tag_no_case!("WATCH")    >> spaces >> i: a_line  >> (Watch(i)))
-    | do_parse!(tag_no_case!("GREETING") >> spaces >> m: a_line  >> (Greeting(m)))
-    | do_parse!(tag_no_case!("VOTE")     >> spaces >> m: yes_no_line >> (Vote(m)))
-    | do_parse!(tag_no_case!("FORCE")    >> spaces >> m: yes_no_line >> (ForceVote(m)))
-    | do_parse!(tag_no_case!("INFO")     >> spaces >> n: a_line  >> (Info(n)))
-    | do_parse!(tag_no_case!("MAXTEAMS") >> spaces >> n: u8_line >> (MaxTeams(n)))
-    | do_parse!(tag_no_case!("CALLVOTE") >>
-        v: opt!(preceded!(spaces, voting)) >> (CallVote(v)))
-    | do_parse!(
-        tag_no_case!("RND") >> alt!(spaces | peek!(end_of_message)) >>
-        v: str_line >>
-        (Rnd(v.split_whitespace().map(String::from).collect())))
-)));
+fn cmd_message<'a>(input: &'a [u8]) -> HWResult<'a, HWProtocolMessage> {
+    fn cmdc_no_arg<'a>(
+        input: &'a [u8],
+        name: &'a str,
+        msg: HWProtocolMessage,
+    ) -> HWResult<'a, HWProtocolMessage> {
+        tag_no_case(name)(input).map(|(i, _)| (i, msg.clone()))
+    }
+
+    fn cmdc_single_arg<'a, T, F, G>(
+        input: &'a [u8],
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> HWResult<'a, HWProtocolMessage>
+    where
+        F: Fn(&'a [u8]) -> HWResult<'a, T>,
+        G: Fn(T) -> HWProtocolMessage,
+    {
+        precededc(input, |i| pairc(i, hw_tag_no_case(name), spaces), parser)
+            .map(|(i, v)| (i, constructor(v)))
+    }
+
+    fn cmd_no_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+        alt((
+            |i| cmdc_no_arg(i, "STATS", Stats),
+            |i| cmdc_no_arg(i, "FIX", Fix),
+            |i| cmdc_no_arg(i, "UNFIX", Unfix),
+            |i| cmdc_no_arg(i, "REGISTERED_ONLY", ToggleServerRegisteredOnly),
+            |i| cmdc_no_arg(i, "SUPER_POWER", SuperPower),
+        ))(input)
+    }
 
-named!(complex_message<&[u8], HWProtocolMessage>, alt!(
-      do_parse!(tag!("PASSWORD")  >> eol >>
-                    p: a_line     >> eol >>
-                    s: a_line     >>
-                    (Password(p, s)))
-    | do_parse!(tag!("CHECKER")   >> eol >>
-                    i: u16_line   >> eol >>
-                    n: a_line     >> eol >>
-                    p: a_line     >>
-                    (Checker(i, n, p)))
-    | do_parse!(tag!("CREATE_ROOM") >> eol >>
-                    n: a_line       >>
-                    p: opt_param    >>
-                    (CreateRoom(n, p)))
-    | do_parse!(tag!("JOIN_ROOM")   >> eol >>
-                    n: a_line       >>
-                    p: opt_param    >>
-                    (JoinRoom(n, p)))
-    | do_parse!(tag!("ADD_TEAM")    >> eol >>
-                    name: a_line    >> eol >>
-                    color: u8_line  >> eol >>
-                    grave: a_line   >> eol >>
-                    fort: a_line    >> eol >>
-                    voice_pack: a_line >> eol >>
-                    flag: a_line    >> eol >>
-                    difficulty: u8_line >> eol >>
-                    hedgehogs: _8_hogs >>
-                    (AddTeam(Box::new(TeamInfo{
-                        name, color, grave, fort,
-                        voice_pack, flag, difficulty,
-                        hedgehogs, hedgehogs_number: 0
-                     }))))
-    | do_parse!(tag!("HH_NUM")    >> eol >>
-                    n: a_line     >> eol >>
-                    c: u8_line    >>
-                    (SetHedgehogsNumber(n, c)))
-    | do_parse!(tag!("TEAM_COLOR")    >> eol >>
-                    n: a_line     >> eol >>
-                    c: u8_line    >>
-                    (SetTeamColor(n, c)))
-    | do_parse!(tag!("BAN")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (Ban(n, r, t)))
-    | do_parse!(tag!("BAN_IP")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (BanIP(n, r, t)))
-    | do_parse!(tag!("BAN_NICK")    >> eol >>
-                    n: a_line     >> eol >>
-                    r: a_line     >> eol >>
-                    t: u32_line   >>
-                    (BanNick(n, r, t)))
-));
+    fn cmd_single_arg_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+        alt((
+            |i| cmdc_single_arg(i, "RESTART_SERVER", |i| tag("YES")(i), |_| RestartServer),
+            |i| cmdc_single_arg(i, "DELEGATE", a_line, Delegate),
+            |i| cmdc_single_arg(i, "DELETE", a_line, Delete),
+            |i| cmdc_single_arg(i, "SAVEROOM", a_line, SaveRoom),
+            |i| cmdc_single_arg(i, "LOADROOM", a_line, LoadRoom),
+            |i| cmdc_single_arg(i, "GLOBAL", a_line, Global),
+            |i| cmdc_single_arg(i, "WATCH", a_line, Watch),
+            |i| cmdc_single_arg(i, "GREETING", a_line, Greeting),
+            |i| cmdc_single_arg(i, "VOTE", yes_no_line, Vote),
+            |i| cmdc_single_arg(i, "FORCE", yes_no_line, ForceVote),
+            |i| cmdc_single_arg(i, "INFO", a_line, Info),
+            |i| cmdc_single_arg(i, "MAXTEAMS", u8_line, MaxTeams),
+            |i| cmdc_single_arg(i, "CALLVOTE", |i| opt!(i, voting), CallVote),
+        ))(input)
+    }
+
+    precededc(
+        input,
+        hw_tag("CMD\n"),
+        alt((
+            cmd_no_arg_message,
+            cmd_single_arg_message,
+            |i| precededc(i, hw_tag_no_case("PART"), opt_space_arg).map(|(i, s)| (i, Part(s))),
+            |i| precededc(i, hw_tag_no_case("QUIT"), opt_space_arg).map(|(i, s)| (i, Quit(s))),
+            |i| {
+                precededc(i, hw_tag_no_case("SAVE"), |i| {
+                    pairc(
+                        i,
+                        |i| precededc(i, spaces, cmd_arg),
+                        |i| precededc(i, spaces, cmd_arg),
+                    )
+                })
+                .map(|(i, (n, l))| (i, Save(n, l)))
+            },
+            |i| {
+                let (i, _) = tag_no_case("RND")(i)?;
+                let (i, _) = alt((spaces, |i: &'a [u8]| peek!(i, end_of_message)))(i)?;
+                let (i, v) = str_line(i)?;
+                Ok((i, Rnd(v.split_whitespace().map(String::from).collect())))
+            },
+        )),
+    )
+}
+
+fn config_message<'a>(input: &'a [u8]) -> HWResult<'a, HWProtocolMessage> {
+    fn cfgc_single_arg<'a, T, F, G>(
+        input: &'a [u8],
+        name: &'a str,
+        parser: F,
+        constructor: G,
+    ) -> HWResult<'a, GameCfg>
+    where
+        F: Fn(&[u8]) -> HWResult<T>,
+        G: Fn(T) -> GameCfg,
+    {
+        precededc(input, |i| terminatedc(i, hw_tag(name), eol), parser)
+            .map(|(i, v)| (i, constructor(v)))
+    }
+
+    let (i, cfg) = precededc(
+        input,
+        hw_tag("CFG\n"),
+        alt((
+            |i| cfgc_single_arg(i, "THEME", a_line, GameCfg::Theme),
+            |i| cfgc_single_arg(i, "SCRIPT", a_line, GameCfg::Script),
+            |i| cfgc_single_arg(i, "MAP", a_line, GameCfg::MapType),
+            |i| cfgc_single_arg(i, "MAPGEN", u32_line, GameCfg::MapGenerator),
+            |i| cfgc_single_arg(i, "MAZE_SIZE", u32_line, GameCfg::MazeSize),
+            |i| cfgc_single_arg(i, "TEMPLATE", u32_line, GameCfg::Template),
+            |i| cfgc_single_arg(i, "FEATURE_SIZE", u32_line, GameCfg::FeatureSize),
+            |i| cfgc_single_arg(i, "SEED", a_line, GameCfg::Seed),
+            |i| cfgc_single_arg(i, "DRAWNMAP", a_line, GameCfg::DrawnMap),
+            |i| {
+                precededc(
+                    i,
+                    |i| terminatedc(i, hw_tag("AMMO"), eol),
+                    |i| {
+                        let (i, name) = a_line(i)?;
+                        let (i, value) = opt_arg(i)?;
+                        Ok((i, GameCfg::Ammo(name, value)))
+                    },
+                )
+            },
+            |i| {
+                precededc(
+                    i,
+                    |i| terminatedc(i, hw_tag("SCHEME"), eol),
+                    |i| {
+                        let (i, name) = a_line(i)?;
+                        let (i, values) = alt((
+                            |i: &'a [u8]| peek!(i, end_of_message).map(|(i, _)| (i, None)),
+                            |i| {
+                                precededc(i, eol, |i| separated_list(eol, a_line)(i))
+                                    .map(|(i, v)| (i, Some(v)))
+                            },
+                        ))(i)?;
+                        Ok((i, GameCfg::Scheme(name, values.unwrap_or_default())))
+                    },
+                )
+            },
+        )),
+    )?;
+    Ok((i, Cfg(cfg)))
+}
 
-named!(cfg_message<&[u8], HWProtocolMessage>, preceded!(tag!("CFG\n"), map!(alt!(
-      do_parse!(tag!("THEME")    >> eol >>
-                name: a_line     >>
-                (GameCfg::Theme(name)))
-    | do_parse!(tag!("SCRIPT")   >> eol >>
-                name: a_line     >>
-                (GameCfg::Script(name)))
-    | do_parse!(tag!("AMMO")     >> eol >>
-                name: a_line     >>
-                value: opt_param >>
-                (GameCfg::Ammo(name, value)))
-    | do_parse!(tag!("SCHEME")   >> eol >>
-                name: a_line     >>
-                values: opt!(preceded!(eol, separated_list!(eol, a_line))) >>
-                (GameCfg::Scheme(name, values.unwrap_or_default())))
-    | do_parse!(tag!("FEATURE_SIZE") >> eol >>
-                value: u32_line    >>
-                (GameCfg::FeatureSize(value)))
-    | do_parse!(tag!("MAP")      >> eol >>
-                value: a_line    >>
-                (GameCfg::MapType(value)))
-    | do_parse!(tag!("MAPGEN")   >> eol >>
-                value: u32_line  >>
-                (GameCfg::MapGenerator(value)))
-    | do_parse!(tag!("MAZE_SIZE") >> eol >>
-                value: u32_line   >>
-                (GameCfg::MazeSize(value)))
-    | do_parse!(tag!("SEED")     >> eol >>
-                value: a_line    >>
-                (GameCfg::Seed(value)))
-    | do_parse!(tag!("TEMPLATE") >> eol >>
-                value: u32_line  >>
-                (GameCfg::Template(value)))
-    | do_parse!(tag!("DRAWNMAP") >> eol >>
-                value: a_line    >>
-                (GameCfg::DrawnMap(value)))
-), Cfg)));
+fn complex_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    alt((
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("PASSWORD"), eol),
+                |i| {
+                    let (i, pass) = terminatedc(i, a_line, eol)?;
+                    let (i, salt) = a_line(i)?;
+                    Ok((i, Password(pass, salt)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("CHECKER"), eol),
+                |i| {
+                    let (i, protocol) = terminatedc(i, u16_line, eol)?;
+                    let (i, name) = terminatedc(i, a_line, eol)?;
+                    let (i, pass) = a_line(i)?;
+                    Ok((i, Checker(protocol, name, pass)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("CREATE_ROOM"), eol),
+                |i| {
+                    let (i, name) = a_line(i)?;
+                    let (i, pass) = opt_arg(i)?;
+                    Ok((i, CreateRoom(name, pass)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("JOIN_ROOM"), eol),
+                |i| {
+                    let (i, name) = a_line(i)?;
+                    let (i, pass) = opt_arg(i)?;
+                    Ok((i, JoinRoom(name, pass)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("ADD_TEAM"), eol),
+                |i| {
+                    let (i, name) = terminatedc(i, a_line, eol)?;
+                    let (i, color) = terminatedc(i, u8_line, eol)?;
+                    let (i, grave) = terminatedc(i, a_line, eol)?;
+                    let (i, fort) = terminatedc(i, a_line, eol)?;
+                    let (i, voice_pack) = terminatedc(i, a_line, eol)?;
+                    let (i, flag) = terminatedc(i, a_line, eol)?;
+                    let (i, difficulty) = terminatedc(i, u8_line, eol)?;
+                    let (i, hedgehogs) = hedgehog_array(i)?;
+                    Ok((
+                        i,
+                        AddTeam(Box::new(TeamInfo {
+                            name,
+                            color,
+                            grave,
+                            fort,
+                            voice_pack,
+                            flag,
+                            difficulty,
+                            hedgehogs,
+                            hedgehogs_number: 0,
+                        })),
+                    ))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("HH_NUM"), eol),
+                |i| {
+                    let (i, name) = terminatedc(i, a_line, eol)?;
+                    let (i, count) = u8_line(i)?;
+                    Ok((i, SetHedgehogsNumber(name, count)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("TEAM_COLOR"), eol),
+                |i| {
+                    let (i, name) = terminatedc(i, a_line, eol)?;
+                    let (i, color) = u8_line(i)?;
+                    Ok((i, SetTeamColor(name, color)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("BAN"), eol),
+                |i| {
+                    let (i, n) = terminatedc(i, a_line, eol)?;
+                    let (i, r) = terminatedc(i, a_line, eol)?;
+                    let (i, t) = u32_line(i)?;
+                    Ok((i, Ban(n, r, t)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("BAN_IP"), eol),
+                |i| {
+                    let (i, n) = terminatedc(i, a_line, eol)?;
+                    let (i, r) = terminatedc(i, a_line, eol)?;
+                    let (i, t) = u32_line(i)?;
+                    Ok((i, BanIP(n, r, t)))
+                },
+            )
+        },
+        |i| {
+            precededc(
+                i,
+                |i| terminatedc(i, hw_tag("BAN_NICK"), eol),
+                |i| {
+                    let (i, n) = terminatedc(i, a_line, eol)?;
+                    let (i, r) = terminatedc(i, a_line, eol)?;
+                    let (i, t) = u32_line(i)?;
+                    Ok((i, BanNick(n, r, t)))
+                },
+            )
+        },
+    ))(input)
+}
 
-named!(malformed_message<&[u8], HWProtocolMessage>,
-    do_parse!(separated_list!(eol, a_line) >> (Malformed)));
+fn empty_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    let (i, _) = alt((end_of_message, eol))(input)?;
+    Ok((i, Empty))
+}
 
-named!(empty_message<&[u8], HWProtocolMessage>,
-    do_parse!(alt!(end_of_message | eol) >> (Empty)));
+fn malformed_message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    let (i, _) = separated_listc(input, eol, a_line)?;
+    Ok((i, Malformed))
+}
 
-named!(message<&[u8], HWProtocolMessage>, alt!(terminated!(
-    alt!(
-          basic_message
-        | one_param_message
-        | cmd_message
-        | complex_message
-        | cfg_message
-        ), end_of_message
-    )
-    | terminated!(malformed_message, end_of_message)
-    | empty_message
-    )
-);
+fn message(input: &[u8]) -> HWResult<HWProtocolMessage> {
+    alt((
+        |i| {
+            terminatedc(
+                i,
+                alt((
+                    no_arg_message,
+                    single_arg_message,
+                    cmd_message,
+                    config_message,
+                    complex_message,
+                )),
+                end_of_message,
+            )
+        },
+        |i| terminatedc(i, malformed_message, end_of_message),
+        empty_message,
+    ))(input)
+}
 
-named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message)));
+pub fn extract_messages<'a>(input: &'a [u8]) -> HWResult<Vec<HWProtocolMessage>> {
+    many0(|i: &'a [u8]| complete!(i, message))(input)
+}
 
 #[cfg(test)]
 proptest! {