1use std::fmt::{Display, Formatter};
7
8#[derive(Debug, Clone, Eq, PartialEq)]
9pub enum Command {
10 AEI,
11 IsReady,
12 NewGame,
13 SetPosition(String),
14 SetOption { name: OptionName, value: Option<String> },
15 MakeMove(String),
16 Go { ponder: bool },
17 Stop,
18 Quit,
19}
20
21#[derive(Debug, Clone, Eq, PartialEq)]
22pub enum Response {
23 ProtocolV1,
24 AeiOk,
25 ReadyOk,
26 Id { ty: IdType, value: String },
27 BestMove(String),
28 Info { ty: InfoType, value: String },
29 Log(String),
30}
31
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub enum IdType {
34 Name,
35 Author,
36 Version,
37}
38
39#[derive(Debug, Copy, Clone, Eq, PartialEq)]
40pub enum InfoType {
41 Score,
42 Depth,
43 Nodes,
44 Pv,
45 Time,
46 CurrMoveNumber,
47 String,
48}
49
50#[derive(Debug, Clone, Eq, PartialEq)]
51pub enum OptionName {
52 TC(TCOptionName),
53 Opponent,
54 OpponentRating,
55 Rating,
56 Rated,
57 Event,
58 Other(String),
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq)]
62pub enum TCOptionName {
63 TcMove,
64 TcReserve,
65 TcPercent,
66 TcMax,
67 TcTotal,
68 TcTurns,
69 TcTurnTime,
70 GReserve,
71 SReserve,
72 GUsed,
73 SUsed,
74 LastMoveUsed,
75 MoveUsed,
76}
77
78impl Display for Response {
79 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Response::ProtocolV1 => write!(f, "protocol-version 1"),
82 Response::AeiOk => write!(f, "aeiok"),
83 Response::ReadyOk => write!(f, "readyok"),
84 Response::Id { ty, value } => {
85 assert!(!value.contains('\n'));
86 write!(f, "id {} {}", ty, value)
87 }
88 Response::BestMove(mv) => {
89 assert!(!mv.contains('\n'));
90 write!(f, "bestmove {}", mv)
91 }
92 Response::Info { ty, value } => {
93 assert!(!value.contains('\n'));
94 write!(f, "info {} {}", ty, value)
95 }
96 Response::Log(log) => {
97 assert!(!log.contains('\n'));
98 write!(f, "log {}", log)
99 }
100 }
101 }
102}
103
104impl Display for IdType {
105 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106 match self {
107 IdType::Name => write!(f, "name"),
108 IdType::Author => write!(f, "author"),
109 IdType::Version => write!(f, "version"),
110 }
111 }
112}
113
114impl Display for InfoType {
115 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116 match self {
117 InfoType::Score => write!(f, "score"),
118 InfoType::Depth => write!(f, "depth"),
119 InfoType::Nodes => write!(f, "nodes"),
120 InfoType::Pv => write!(f, "pv"),
121 InfoType::Time => write!(f, "time"),
122 InfoType::CurrMoveNumber => write!(f, "currmovenumber"),
123 InfoType::String => write!(f, "string"),
124 }
125 }
126}
127
128impl Command {
129 pub fn parse(input: &str) -> Result<Command, nom::Err<nom::error::Error<&str>>> {
130 parse::command()(input).map(|(left, command)| {
131 assert!(left.is_empty());
132 command
133 })
134 }
135}
136
137mod parse {
138 use nom::branch::alt;
139 use nom::bytes::complete::{tag, take_until, take_while};
140 use nom::combinator::{eof, map, opt, value};
141 use nom::sequence::{preceded, terminated, tuple};
142 use nom::IResult;
143
144 use crate::interface::aei::{Command, OptionName, TCOptionName};
145
146 pub fn command<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Command> {
147 let remainder = || take_while(|_| true);
148
149 let set_position = preceded(
150 tag("setposition "),
151 map(remainder(), |s: &str| Command::SetPosition(s.to_owned())),
152 );
153
154 const OPTION_MAP: &[(&str, OptionName)] = &[
155 ("tcmove", OptionName::TC(TCOptionName::TcMove)),
156 ("tcreserve", OptionName::TC(TCOptionName::TcReserve)),
157 ("tcpercent", OptionName::TC(TCOptionName::TcPercent)),
158 ("tcmax", OptionName::TC(TCOptionName::TcMax)),
159 ("tctotal", OptionName::TC(TCOptionName::TcTotal)),
160 ("tcturns", OptionName::TC(TCOptionName::TcTurns)),
161 ("tcturntime", OptionName::TC(TCOptionName::TcTurnTime)),
162 ("greserve", OptionName::TC(TCOptionName::GReserve)),
163 ("sreserve", OptionName::TC(TCOptionName::SReserve)),
164 ("gused", OptionName::TC(TCOptionName::GUsed)),
165 ("sused", OptionName::TC(TCOptionName::SUsed)),
166 ("lastmoveused", OptionName::TC(TCOptionName::LastMoveUsed)),
167 ("moveused", OptionName::TC(TCOptionName::MoveUsed)),
168 ("opponent", OptionName::Opponent),
169 ("opponent_rating", OptionName::OpponentRating),
170 ("rating", OptionName::Rating),
171 ("rated", OptionName::Rated),
172 ("event", OptionName::Event),
173 ];
174
175 let option_name = map(take_until(" "), |s: &str| {
176 OPTION_MAP
177 .iter()
178 .find(|&&(k, _)| k == s)
179 .map_or_else(|| OptionName::Other(s.to_owned()), |(_, v)| v.clone())
180 });
181
182 let set_option = map(
183 tuple((
184 tag("setoption name "),
185 option_name,
186 opt(preceded(tag(" value "), remainder())),
187 )),
188 |(_, name, value)| Command::SetOption {
189 name,
190 value: value.map(ToOwned::to_owned),
191 },
192 );
193
194 let make_move = preceded(tag("makemove "), map(remainder(), |s| Command::MakeMove(s.to_owned())));
195
196 let main = alt((
197 value(Command::AEI, tag("aei")),
198 value(Command::IsReady, tag("isready")),
199 value(Command::NewGame, tag("newgame")),
200 set_position,
201 set_option,
202 make_move,
203 value(Command::Go { ponder: true }, tag("go ponder")),
204 value(Command::Go { ponder: false }, tag("go")),
205 value(Command::Stop, tag("stop")),
206 value(Command::Quit, tag("quit")),
207 ));
208
209 terminated(main, eof)
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use crate::interface::aei::{Command, OptionName};
216
217 #[test]
218 fn set_option() {
219 let parsed = Command::parse("setoption name opponent_rating value 1325");
220 let expected = Command::SetOption {
221 name: OptionName::OpponentRating,
222 value: Some("1325".to_owned()),
223 };
224 assert_eq!(parsed, Ok(expected))
225 }
226}