1use itertools::Itertools;
2use std::str::SplitWhitespace;
3use std::time::Duration;
4
5use super::{
6 BestMoveParams, CheckmateParams, EngineCommand, IdParams, InfoParams, OptionKind, OptionParams,
7 ScoreKind,
8};
9use crate::error::Error;
10
11pub struct EngineCommandParser<'a> {
12 iter: SplitWhitespace<'a>,
13}
14
15impl<'a> EngineCommandParser<'a> {
16 pub fn new(cmd: &str) -> EngineCommandParser {
17 EngineCommandParser {
18 iter: cmd.split_whitespace(),
19 }
20 }
21
22 pub fn parse(mut self) -> Result<EngineCommand, Error> {
23 let command = self.iter.next();
24 if command.is_none() {
25 return Err(Error::IllegalSyntax);
26 }
27
28 let command = command.unwrap();
29 Ok(match command {
30 "bestmove" => self.parse_bestmove()?,
31 "checkmate" => self.parse_checkmate()?,
32 "id" => self.parse_id()?,
33 "info" => self.parse_info()?,
34 "option" => self.parse_option()?,
35 "readyok" => EngineCommand::ReadyOk,
36 "usiok" => EngineCommand::UsiOk,
37 _ => EngineCommand::Unknown,
38 })
39 }
40
41 fn parse_bestmove(mut self) -> Result<EngineCommand, Error> {
42 match (self.iter.next(), self.iter.next(), self.iter.next()) {
43 (Some("resign"), None, None) => Ok(EngineCommand::BestMove(BestMoveParams::Resign)),
44 (Some("win"), None, None) => Ok(EngineCommand::BestMove(BestMoveParams::Win)),
45 (Some(m), None, None) => Ok(EngineCommand::BestMove(BestMoveParams::MakeMove(
46 m.to_string(),
47 None,
48 ))),
49 (Some(m), Some("ponder"), Some(pm)) => Ok(EngineCommand::BestMove(
50 BestMoveParams::MakeMove(m.to_string(), Some(pm.to_string())),
51 )),
52 _ => Err(Error::IllegalSyntax),
53 }
54 }
55
56 fn parse_checkmate(mut self) -> Result<EngineCommand, Error> {
57 match self.iter.next() {
58 Some("notimplemented") => Ok(EngineCommand::Checkmate(CheckmateParams::NoMate)),
59 Some("timeout") => Ok(EngineCommand::Checkmate(CheckmateParams::Timeout)),
60 Some("nomate") => Ok(EngineCommand::Checkmate(CheckmateParams::NoMate)),
61 Some(s) => {
62 let mut moves = vec![s.to_string()];
63 self.iter.for_each(|s| {
64 moves.push(s.to_string());
65 });
66 Ok(EngineCommand::Checkmate(CheckmateParams::Mate(moves)))
67 }
68 _ => Err(Error::IllegalSyntax),
69 }
70 }
71
72 fn parse_id(mut self) -> Result<EngineCommand, Error> {
73 match self.iter.next() {
74 Some("name") => Ok(EngineCommand::Id(IdParams::Name(self.iter.join(" ")))),
75 Some("author") => Ok(EngineCommand::Id(IdParams::Author(self.iter.join(" ")))),
76 _ => Err(Error::IllegalSyntax),
77 }
78 }
79
80 fn parse_info(self) -> Result<EngineCommand, Error> {
81 let mut iter = self.iter.peekable();
82 let mut entries = Vec::new();
83
84 while let Some(kind) = iter.next() {
85 match kind {
86 "depth" => {
87 let depth: i32 = iter
88 .next()
89 .and_then(|s| s.parse().ok())
90 .ok_or(Error::IllegalSyntax)?;
91
92 let mut sel_depth = None;
93 if let Some(&peek_kind) = iter.peek() {
94 if peek_kind == "seldepth" {
95 iter.next();
96
97 sel_depth = Some(
98 iter.next()
99 .and_then(|s| s.parse().ok())
100 .ok_or(Error::IllegalSyntax)?,
101 );
102 }
103 }
104
105 entries.push(InfoParams::Depth(depth, sel_depth));
106 }
107 "time" => {
108 let ms: u64 = iter
109 .next()
110 .and_then(|s| s.parse().ok())
111 .ok_or(Error::IllegalSyntax)?;
112 entries.push(InfoParams::Time(Duration::from_millis(ms)));
113 }
114 "multipv" => {
115 let multipv: i32 = iter
116 .next()
117 .and_then(|s| s.parse().ok())
118 .ok_or(Error::IllegalSyntax)?;
119 entries.push(InfoParams::MultiPv(multipv));
120 }
121 "nodes" => {
122 let nodes: i32 = iter
123 .next()
124 .and_then(|s| s.parse().ok())
125 .ok_or(Error::IllegalSyntax)?;
126 entries.push(InfoParams::Nodes(nodes));
127 }
128 "pv" => {
129 let pvs = iter.map(|v| v.to_string()).collect::<Vec<_>>();
130 entries.push(InfoParams::Pv(pvs));
131 break;
133 }
134 "score" => match (iter.next(), iter.next()) {
135 (Some("cp"), Some(cp)) => {
136 let cp: i32 = cp.parse()?;
137
138 if let Some(&peek_kind) = iter.peek() {
139 match peek_kind {
140 "lowerbound" => {
141 iter.next();
142 entries.push(InfoParams::Score(cp, ScoreKind::CpLowerbound));
143 }
144 "upperbound" => {
145 iter.next();
146 entries.push(InfoParams::Score(cp, ScoreKind::CpUpperbound));
147 }
148 _ => {
149 entries.push(InfoParams::Score(cp, ScoreKind::CpExact));
150 }
151 }
152 }
153 }
154 (Some("mate"), Some("+")) => {
155 entries.push(InfoParams::Score(1, ScoreKind::MateSignOnly))
156 }
157 (Some("mate"), Some("-")) => {
158 entries.push(InfoParams::Score(-1, ScoreKind::MateSignOnly))
159 }
160 (Some("mate"), Some(ply)) => {
161 let ply: i32 = ply.parse()?;
162
163 if let Some(&peek_kind) = iter.peek() {
164 match peek_kind {
165 "lowerbound" => {
166 iter.next();
167 entries.push(InfoParams::Score(ply, ScoreKind::MateLowerbound));
168 }
169 "upperbound" => {
170 iter.next();
171 entries.push(InfoParams::Score(ply, ScoreKind::MateUpperbound));
172 }
173 _ => {
174 entries.push(InfoParams::Score(ply, ScoreKind::MateExact));
175 }
176 }
177 }
178 }
179 _ => return Err(Error::IllegalSyntax),
180 },
181 "currmove" => {
182 let currmove = iter.next().ok_or(Error::IllegalSyntax)?;
183 entries.push(InfoParams::CurrMove(currmove.to_string()));
184 }
185 "hashfull" => {
186 let hashfull: i32 = iter
187 .next()
188 .and_then(|s| s.parse().ok())
189 .ok_or(Error::IllegalSyntax)?;
190 entries.push(InfoParams::HashFull(hashfull));
191 }
192 "nps" => {
193 let nps: i32 = iter
194 .next()
195 .and_then(|s| s.parse().ok())
196 .ok_or(Error::IllegalSyntax)?;
197 entries.push(InfoParams::Nps(nps));
198 }
199 "string" => {
200 entries.push(InfoParams::Text(iter.join(" ")));
201 break;
203 }
204 _ => return Err(Error::IllegalSyntax),
205 }
206 }
207
208 Ok(EngineCommand::Info(entries))
209 }
210
211 fn parse_option(mut self) -> Result<EngineCommand, Error> {
212 let opt_name = match (self.iter.next(), self.iter.next(), self.iter.next()) {
213 (Some("name"), Some(opt_name), Some("type")) => opt_name,
214 _ => return Err(Error::IllegalSyntax),
215 };
216
217 let opt_type = match self.iter.next() {
218 Some("check") => {
219 let default = self
220 .iter
221 .find(|v| *v != "default")
222 .and_then(|s| s.parse().ok());
223
224 OptionKind::Check { default }
225 }
226 Some("spin") => {
227 let mut default = None;
228 let mut min = None;
229 let mut max = None;
230
231 while let Some(kind) = self.iter.next() {
232 match kind {
233 "default" => default = self.iter.next().and_then(|s| s.parse().ok()),
234 "min" => min = self.iter.next().and_then(|s| s.parse().ok()),
235 "max" => max = self.iter.next().and_then(|s| s.parse().ok()),
236 _ => {}
237 }
238 }
239
240 OptionKind::Spin { default, min, max }
241 }
242 Some("combo") => {
243 let mut default = None;
244 let mut vars = Vec::new();
245
246 while let Some(kind) = self.iter.next() {
247 match kind {
248 "default" => default = self.iter.next().map(parse_default),
249 "var" => {
250 self.iter.for_each(|v| {
251 vars.push(v.to_string());
252 });
253 break;
254 }
255 _ => {}
256 }
257 }
258
259 OptionKind::Combo { default, vars }
260 }
261 Some("button") => {
262 let default = self.iter.find(|v| *v != "default").map(parse_default);
263
264 OptionKind::Button { default }
265 }
266 Some("string") => {
267 let default = self.iter.find(|v| *v != "default").map(parse_default);
268
269 OptionKind::String { default }
270 }
271 Some("filename") => {
272 let default = self.iter.find(|v| *v != "default").map(parse_default);
273
274 OptionKind::Filename { default }
275 }
276 _ => return Err(Error::IllegalSyntax),
277 };
278
279 Ok(EngineCommand::Option(OptionParams {
280 name: opt_name.to_string(),
281 value: opt_type,
282 }))
283 }
284}
285
286fn parse_default(s: &str) -> String {
287 if s == "<empty>" {
288 String::new()
289 } else {
290 s.to_string()
291 }
292}