usi/protocol/
parser.rs

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                    // "pv" or "str" must be the final item.
132                    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                    // "pv" or "str" must be the final item.
202                    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}