haitaka_usi/
parser.rs

1//! This module implements the USI parser.
2//!
3//! The [PEG grammar](https://en.wikipedia.org/wiki/Parsing_expression_grammar) used by this crate
4//! is part of the source code as [usi.pest](https://github.com/tofutofu/haitaka-usi/blob/main/src/usi.pest)
5//!
6//! The main parse functions are
7//! - [`GuiMessage::parse`]
8//! - [`GuiMessage::parse_first_valid`]
9//! - [`EngineMessage::parse`]
10//! - [`EngineMessage::parse_first_valid`]
11//!
12#![allow(clippy::result_large_err)]
13
14use core::str::FromStr;
15use haitaka_types::Move;
16use pest::Parser; // Parser trait
17use pest::error::Error as PestError;
18use pest::iterators::{Pair, Pairs};
19use pest_derive::Parser; // Parser proc macro
20use std::fmt::Debug;
21use std::time::Duration;
22
23use crate::engine::{
24    BestMoveParams, EngineMessage, IdParams, InfoParam, OptionParam, ScoreBound, StatusCheck,
25};
26use crate::gui::{EngineParams, GameStatus, GuiMessage, MateParam};
27
28#[derive(Parser)]
29#[grammar = "usi.pest"]
30struct UsiParser;
31
32/// This function visualizes the PEST parse tree of any input.
33pub fn dbg(s: &str) {
34    let res = UsiParser::parse(Rule::start, s);
35    if let Ok(pairs) = res {
36        println!("{:#?}", pairs);
37    } else {
38        println!("{:#?}", res);
39    }
40}
41
42// macros - a few spoonfuls of sugar
43
44/// Extract the string value of a PEST Span as `str`.
45/// Also trims leading and trailing whitespace.
46macro_rules! as_str {
47    ($sp:ident) => {
48        $sp.as_span().as_str().trim()
49    };
50}
51
52/// Extract the string value of a PEST Span as `String`.
53/// Also trims leading and trailing whitespace.
54macro_rules! as_string {
55    ($sp:ident) => {
56        $sp.as_span().as_str().trim().to_string()
57    };
58}
59
60/// Convert "<empty>" into Some(""). Used in parsing `option ... default <empty>`.
61macro_rules! convert_empty {
62    ($s:ident) => {
63        if $s.eq_ignore_ascii_case("<empty>") {
64            Some(String::from(""))
65        } else {
66            Some(String::from($s))
67        }
68    };
69}
70
71/// Extract a Move from a PEST Pair.
72macro_rules! as_move {
73    ($sp:ident) => {
74        Move::from_str(as_str!($sp)).unwrap()
75    };
76}
77
78impl GuiMessage {
79    /// Parse one USI message, sent by the GUI and received by the Engine.
80    ///
81    /// If the string contains multiple messages, only the first one is returned.
82    ///
83    /// Note that all USI protocol messages must be terminated by a newline ('\n', '\r' or '\r\n').
84    /// This function will return a ParseError if the input string does not end with either
85    /// a newline or newline followed by ascii whitespace.
86    ///
87    /// SAFETY: The parser should be able to process any newline-terminated input. An input string
88    /// `input` that does not conform to the USI protocol is returned as `Ok(EngineMessage::Unknown(input))`.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use haitaka_usi::*;
94    /// let input = "usi\n";
95    /// let msg = GuiMessage::parse(input).unwrap();
96    /// assert_eq!(msg, GuiMessage::Usi);
97    /// ```
98    pub fn parse(input: &str) -> Result<Self, PestError<Rule>> {
99        match UsiParser::parse(Rule::start, input) {
100            Ok(pairs) => Ok(Self::inner_parse(pairs.into_iter().next().unwrap())),
101            Err(err) => Err(err),
102        }
103    }
104
105    /// Parses the input and returns the first valid protocol GUI message, skipping Unknowns.
106    /// Returns `None` if no valid message is found.
107    ///
108    /// # Panics
109    ///
110    /// This function will panic if the input string is not newline terminated.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use haitaka_usi::*;
116    /// let input = "yo\nyo usinewgame\n";
117    /// let msg = GuiMessage::parse_first_valid(input).unwrap();
118    /// assert_eq!(msg, GuiMessage::UsiNewGame);
119    /// ```
120    pub fn parse_first_valid(input: &str) -> Option<Self> {
121        GuiMessageStream::new(input).find(|msg| !matches!(msg, GuiMessage::Unknown(_)))
122    }
123
124    fn inner_parse(p: Pair<'_, Rule>) -> Self {
125        match p.as_rule() {
126            Rule::usi => Self::parse_usi(),
127            Rule::debug => Self::parse_debug(p),
128            Rule::isready => Self::parse_isready(),
129            Rule::setoption => Self::parse_setoption(p),
130            Rule::register_user => Self::parse_register(p),
131            Rule::usinewgame => Self::parse_usinewgame(),
132            Rule::position => Self::parse_position(p),
133            Rule::go => Self::parse_go(p),
134            Rule::stop => Self::parse_stop(),
135            Rule::ponderhit => Self::parse_ponderhit(),
136            Rule::gameover => Self::parse_gameover(p),
137            Rule::quit => Self::parse_quit(),
138            _ => Self::parse_unknown(p.as_str()),
139        }
140    }
141
142    // unknown
143    fn parse_unknown(s: &str) -> Self {
144        Self::Unknown(s.to_owned())
145    }
146
147    // usi
148    fn parse_usi() -> Self {
149        Self::Usi
150    }
151
152    // debug
153    fn parse_debug(pair: Pair<Rule>) -> Self {
154        let on = !as_string!(pair).ends_with("off");
155        Self::Debug(on)
156    }
157
158    // isready
159    fn parse_isready() -> Self {
160        Self::IsReady
161    }
162
163    // setoption
164    fn parse_setoption(pair: Pair<Rule>) -> Self {
165        let mut name: String = String::default();
166        let mut value: Option<String> = None;
167        for sp in pair.into_inner() {
168            match sp.as_rule() {
169                Rule::setoption_name => {
170                    name = as_string!(sp);
171                }
172                Rule::setoption_value => {
173                    value = Some(as_string!(sp));
174                }
175                _ => unreachable!(),
176            }
177        }
178        Self::SetOption { name, value }
179    }
180
181    // register
182    fn parse_register(pair: Pair<Rule>) -> Self {
183        let mut name: Option<String> = None;
184        let mut code: Option<String> = None;
185        for sp in pair.into_inner() {
186            match sp.as_rule() {
187                Rule::register_later => {}
188                Rule::register_with_name_and_code => {
189                    for spi in sp.into_inner() {
190                        match spi.as_rule() {
191                            Rule::register_name => {
192                                name = Some(as_string!(spi));
193                            }
194                            Rule::register_code => {
195                                code = Some(as_string!(spi));
196                            }
197                            _ => unreachable!(),
198                        }
199                    }
200                }
201                _ => unreachable!(),
202            }
203        }
204        Self::Register { name, code }
205    }
206
207    // usinewgame
208    fn parse_usinewgame() -> Self {
209        Self::UsiNewGame
210    }
211
212    // position
213    fn parse_position(pair: Pair<Rule>) -> Self {
214        let mut sfen: Option<String> = None;
215        let mut moves: Option<Vec<Move>> = None;
216        for sp in pair.into_inner() {
217            match sp.as_rule() {
218                Rule::startpos => {
219                    assert!(sfen.is_none());
220                }
221                Rule::sfenpos => {
222                    sfen = Some(
223                        as_str!(sp)
224                            .strip_prefix("sfen ")
225                            .unwrap()
226                            .trim()
227                            .to_string(),
228                    );
229                }
230                Rule::moves => {
231                    moves = Some(parse_moves(sp));
232                }
233                _ => unreachable!(),
234            }
235        }
236        Self::Position { sfen, moves }
237    }
238
239    // go
240    fn parse_go(pair: Pair<Rule>) -> Self {
241        let mut params = EngineParams::new();
242
243        for sp in pair.into_inner() {
244            match sp.as_rule() {
245                Rule::searchmoves => {
246                    params = params.searchmoves(parse_moves(sp));
247                }
248                Rule::depth => {
249                    params = params.depth(parse_digits::<u16>(sp));
250                }
251                Rule::nodes => {
252                    params = params.nodes(parse_digits::<u32>(sp));
253                }
254                Rule::mate => {
255                    for spi in sp.into_inner() {
256                        match spi.as_rule() {
257                            Rule::millisecs => {
258                                params = params.mate(MateParam::Timeout(parse_millisecs(spi)))
259                            }
260                            Rule::infinite => params = params.mate(MateParam::Infinite),
261                            _ => unreachable!(),
262                        }
263                    }
264                }
265                Rule::byoyomi => {
266                    params = params.byoyomi(parse_millisecs(sp));
267                }
268                Rule::btime => {
269                    params = params.btime(parse_millisecs(sp));
270                }
271                Rule::wtime => {
272                    params = params.wtime(parse_millisecs(sp));
273                }
274                Rule::binc => {
275                    params = params.binc(parse_millisecs(sp));
276                }
277                Rule::winc => {
278                    params = params.winc(parse_millisecs(sp));
279                }
280                Rule::movestogo => {
281                    params = params.movestogo(parse_digits::<u16>(sp));
282                }
283                Rule::ponder => {
284                    params = params.ponder();
285                }
286                Rule::movetime => params = params.movetime(parse_millisecs(sp)),
287                Rule::infinite => {
288                    params = params.infinite();
289                }
290                _ => unreachable!(),
291            }
292        }
293        Self::Go(params)
294    }
295
296    // stop
297    fn parse_stop() -> Self {
298        Self::Stop
299    }
300
301    // ponderhit
302    fn parse_ponderhit() -> Self {
303        Self::PonderHit
304    }
305
306    // gameover
307    fn parse_gameover(pair: Pair<Rule>) -> Self {
308        if let Some(sp) = pair.into_inner().next() {
309            match sp.as_rule() {
310                Rule::win => return Self::GameOver(GameStatus::Win),
311                Rule::lose => return Self::GameOver(GameStatus::Lose),
312                Rule::draw => return Self::GameOver(GameStatus::Draw),
313                _ => unreachable!(),
314            }
315        }
316        unreachable!()
317    }
318
319    // quit
320    fn parse_quit() -> Self {
321        Self::Quit
322    }
323}
324
325/// The GuiMessageStream struct enables iteration over a multi-line text string.
326pub struct GuiMessageStream<'a> {
327    /// Inner PEST iterator over grammar Rules
328    pairs: Pairs<'a, Rule>,
329}
330
331impl<'a> GuiMessageStream<'a> {
332    /// Create a new `GuiMessageStream` from an input string.
333    ///
334    /// SAFETY: Since the grammar is designed to process any input, this should never fail.
335    pub fn new(input: &'a str) -> Self {
336        Self::parse(input)
337    }
338
339    /// Parse a multi-line input string and return a GuiMessageStream instance.
340    ///
341    /// SAFETY: Since the parser should be able to handle any input, this should never fail.
342    pub fn parse(input: &'a str) -> Self {
343        Self::try_parse(input).expect("Internal error: Failed to initialize UsiParser.")
344    }
345
346    pub fn try_parse(input: &'a str) -> Result<Self, PestError<Rule>> {
347        let pairs = UsiParser::parse(Rule::start, input);
348        match pairs {
349            Ok(pairs) => Ok(Self { pairs }),
350            Err(err) => Err(err),
351        }
352    }
353}
354
355impl Iterator for GuiMessageStream<'_> {
356    type Item = GuiMessage;
357
358    fn next(&mut self) -> Option<Self::Item> {
359        if let Some(pair) = self.pairs.by_ref().next() {
360            let res = GuiMessage::inner_parse(pair);
361            return Some(res);
362        }
363        None
364    }
365}
366
367// EngineMessage parser
368
369impl EngineMessage {
370    /// Parse one USI message, sent by the Engine and received by the GUI.
371    ///
372    /// If the string contains multiple messages, only the first one is returned.
373    ///
374    /// Note that all USI protocol messages must be terminated by a newline ('\n', '\r' or '\r\n').
375    /// This function will return a ParseError if the input string does not end with either
376    /// a newline or newline followed by ascii whitespace.
377    ///
378    /// SAFETY: The parser should be able to process any newline-terminated input. An input string `input`
379    /// that does not conform to the USI protocol is returned as `Ok(GuiMessage::Unknown(input))`.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use haitaka_usi::*;
385    /// use haitaka_types::*;
386    /// let input = "bestmove 3c3d\n";
387    /// let msg = EngineMessage::parse(input).unwrap();
388    /// let mv = "3c3d".parse::<Move>().unwrap();
389    /// assert_eq!(msg,
390    ///     EngineMessage::BestMove(
391    ///         BestMoveParams::BestMove {
392    ///             bestmove: mv,
393    ///             ponder: None })
394    /// );
395    /// let input = "bestmove resign\n";
396    /// let msg = EngineMessage::parse(input).unwrap();
397    /// assert_eq!(msg,
398    ///     EngineMessage::BestMove(
399    ///         BestMoveParams::Resign
400    ///     )
401    /// );
402    /// ```
403    pub fn parse(input: &str) -> Result<Self, PestError<Rule>> {
404        match UsiParser::parse(Rule::start, input) {
405            Ok(pairs) => Ok(Self::inner_parse(pairs.into_iter().next().unwrap())),
406            Err(err) => Err(err),
407        }
408    }
409
410    /// Parses the input and returns the first valid protocol Engine message, skipping Unknowns.
411    /// Returns `None` if no valid Engine message is found.
412    ///
413    /// # Panics
414    ///
415    /// This function will panic if the input string is not newline terminated.
416    ///
417    pub fn parse_first_valid(input: &str) -> Option<Self> {
418        EngineMessageStream::new(input).find(|msg| !matches!(msg, EngineMessage::Unknown(_)))
419    }
420
421    fn inner_parse(p: Pair<'_, Rule>) -> Self {
422        match p.as_rule() {
423            Rule::id => Self::parse_id(p),
424            Rule::usiok => Self::parse_usiok(),
425            Rule::readyok => Self::parse_readyok(),
426            Rule::bestmove => Self::parse_bestmove(p),
427            Rule::copyprotection => Self::parse_copyprotection(p),
428            Rule::registration => Self::parse_registration(p),
429            Rule::option => Self::parse_option(p),
430            Rule::info => Self::parse_info(p),
431            _ => Self::parse_unknown(p.as_str()),
432        }
433    }
434
435    // unknown
436    fn parse_unknown(s: &str) -> Self {
437        Self::Unknown(s.to_owned())
438    }
439
440    // id
441    fn parse_id(pair: Pair<Rule>) -> Self {
442        if let Some(sp) = pair.into_inner().next() {
443            match sp.as_rule() {
444                Rule::id_name => return EngineMessage::Id(IdParams::Name(parse_tokens(sp))),
445                Rule::id_author => return EngineMessage::Id(IdParams::Author(parse_tokens(sp))),
446                _ => unreachable!(),
447            }
448        }
449        unreachable!()
450    }
451
452    // usiok
453    fn parse_usiok() -> Self {
454        EngineMessage::UsiOk
455    }
456
457    // readyok
458    fn parse_readyok() -> Self {
459        EngineMessage::ReadyOk
460    }
461
462    // bestmove
463    fn parse_bestmove(pair: Pair<Rule>) -> Self {
464        let mut bestmove: Option<Move> = None;
465        let mut ponder: Option<Move> = None;
466
467        for sp in pair.into_inner() {
468            match sp.as_rule() {
469                Rule::one_move => {
470                    bestmove = Some(as_move!(sp));
471                }
472                Rule::ponder_move => {
473                    ponder = Some(parse_move(sp));
474                }
475                Rule::resign => return EngineMessage::BestMove(BestMoveParams::Resign),
476                Rule::win => return EngineMessage::BestMove(BestMoveParams::Win),
477                _ => unreachable!(),
478            }
479        }
480
481        if let Some(bestmove) = bestmove {
482            EngineMessage::BestMove(BestMoveParams::BestMove { bestmove, ponder })
483        } else {
484            unreachable!()
485        }
486    }
487
488    // copyprotection
489    fn parse_copyprotection(pair: Pair<Rule>) -> Self {
490        let state = Self::parse_status_check(pair);
491        EngineMessage::CopyProtection(state)
492    }
493
494    // registration
495    fn parse_registration(pair: Pair<Rule>) -> Self {
496        let state = Self::parse_status_check(pair);
497        EngineMessage::Registration(state)
498    }
499
500    fn parse_status_check(pair: Pair<Rule>) -> StatusCheck {
501        for sp in pair.into_inner() {
502            if let Rule::status_check = sp.as_rule() {
503                let s = as_str!(sp);
504                match s {
505                    "checking" => return StatusCheck::Checking,
506                    "ok" => return StatusCheck::Ok,
507                    "error" => return StatusCheck::Error,
508                    _ => unreachable!(),
509                };
510            }
511        }
512        unreachable!()
513    }
514
515    // option
516    fn parse_option(pair: Pair<Rule>) -> Self {
517        if let Some(sp) = pair.into_inner().next() {
518            match sp.as_rule() {
519                Rule::check_option => return Self::parse_check_option(sp),
520                Rule::spin_option => return Self::parse_spin_option(sp),
521                Rule::combo_option => return Self::parse_combo_option(sp),
522                Rule::string_option => return Self::parse_string_option(sp),
523                Rule::button_option => return Self::parse_button_option(sp),
524                Rule::filename_option => return Self::parse_filename_option(sp),
525                _ => unreachable!(),
526            }
527        }
528        unreachable!()
529    }
530
531    // options name ... type check ...
532    fn parse_check_option(pair: Pair<Rule>) -> Self {
533        let mut name: Option<String> = None;
534        let mut default: Option<bool> = None;
535        for sp in pair.into_inner() {
536            match sp.as_rule() {
537                Rule::option_name => name = Some(parse_tokens(sp)),
538                Rule::check_default => default = Some(as_string!(sp).eq_ignore_ascii_case("true")),
539                _ => (),
540            }
541        }
542        if let Some(name) = name {
543            Self::Option(OptionParam::Check { name, default })
544        } else {
545            unreachable!()
546        }
547    }
548
549    // option name ... type spin ...
550    fn parse_spin_option(pair: Pair<Rule>) -> Self {
551        let mut name: Option<String> = None;
552        let mut default: Option<i32> = None;
553        let mut min: Option<i32> = None;
554        let mut max: Option<i32> = None;
555
556        for sp in pair.into_inner() {
557            match sp.as_rule() {
558                Rule::option_name => name = Some(parse_tokens(sp)),
559                Rule::spin_default => default = Some(parse_integer::<i32>(sp)),
560                Rule::spin_min => min = Some(parse_integer::<i32>(sp)),
561                Rule::spin_max => max = Some(parse_integer::<i32>(sp)),
562                _ => (),
563            }
564        }
565
566        if let Some(name) = name {
567            Self::Option(OptionParam::Spin {
568                name,
569                default,
570                min,
571                max,
572            })
573        } else {
574            unreachable!()
575        }
576    }
577
578    // option name ... type combo ...
579    fn parse_combo_option(pair: Pair<Rule>) -> Self {
580        let mut name: Option<String> = None;
581        let mut default: Option<String> = None;
582        let mut vars: Vec<String> = Vec::new();
583
584        for sp in pair.into_inner() {
585            match sp.as_rule() {
586                Rule::option_name => name = Some(parse_tokens(sp)),
587                Rule::combo_default => default = Some(parse_tokens(sp)),
588                Rule::var_token => vars.push(parse_tokens(sp)),
589                _ => (),
590            }
591        }
592
593        if let Some(name) = name {
594            Self::Option(OptionParam::Combo {
595                name,
596                default,
597                vars,
598            })
599        } else {
600            unreachable!()
601        }
602    }
603
604    // option name ... type string
605    fn parse_string_option(pair: Pair<Rule>) -> Self {
606        let mut name: Option<String> = None;
607        let mut default: Option<String> = None;
608
609        for sp in pair.into_inner() {
610            match sp.as_rule() {
611                Rule::option_name => name = Some(parse_tokens(sp)),
612                Rule::token => {
613                    default = {
614                        let s = as_string!(sp);
615                        convert_empty!(s)
616                    }
617                }
618                _ => (),
619            }
620        }
621
622        if let Some(name) = name {
623            Self::Option(OptionParam::String { name, default })
624        } else {
625            unreachable!()
626        }
627    }
628
629    // option name ... type button
630    fn parse_button_option(pair: Pair<Rule>) -> Self {
631        for sp in pair.into_inner() {
632            if sp.as_rule() == Rule::option_name {
633                let name = parse_tokens(sp);
634                return Self::Option(OptionParam::Button { name });
635            }
636        }
637        unreachable!()
638    }
639
640    // option name ... type filename ...
641    fn parse_filename_option(pair: Pair<Rule>) -> Self {
642        let mut name: Option<String> = None;
643        let mut default: Option<String> = None;
644
645        for sp in pair.into_inner() {
646            match sp.as_rule() {
647                Rule::option_name => name = Some(parse_tokens(sp)),
648                Rule::token => {
649                    default = {
650                        let s = as_string!(sp);
651                        convert_empty!(s)
652                    }
653                }
654                _ => (),
655            }
656        }
657
658        if let Some(name) = name {
659            Self::Option(OptionParam::Filename { name, default })
660        } else {
661            unreachable!()
662        }
663    }
664
665    // info
666    fn parse_info(pair: Pair<Rule>) -> Self {
667        let mut v: Vec<InfoParam> = Vec::<InfoParam>::new();
668        for sp in pair.into_inner() {
669            let info: InfoParam = match sp.as_rule() {
670                Rule::info_depth => InfoParam::Depth(parse_digits::<u16>(sp)),
671                Rule::info_seldepth => InfoParam::SelDepth(parse_digits::<u16>(sp)),
672                Rule::info_time => InfoParam::Time(parse_millisecs(sp)),
673                Rule::info_nodes => InfoParam::Nodes(parse_digits::<u64>(sp)),
674                Rule::info_currmovenumber => InfoParam::CurrMoveNumber(parse_digits::<u16>(sp)),
675                Rule::info_currmove => InfoParam::CurrMove(parse_move(sp)),
676                Rule::info_hashfull => InfoParam::HashFull(parse_digits::<u16>(sp)),
677                Rule::info_nps => InfoParam::Nps(parse_digits::<u64>(sp)),
678                Rule::info_cpuload => InfoParam::CpuLoad(parse_digits::<u16>(sp)),
679                Rule::info_multipv => InfoParam::MultiPv(parse_digits::<u16>(sp)),
680                Rule::info_string => InfoParam::String(parse_tokens(sp)),
681                Rule::info_pv => InfoParam::Pv(parse_moves(sp)),
682                Rule::info_refutation => InfoParam::Refutation(parse_moves(sp)),
683                Rule::info_currline => Self::parse_currline(sp),
684                Rule::info_score_cp => Self::parse_score_cp(sp),
685                Rule::info_score_mate => Self::parse_score_mate(sp),
686                _ => unreachable!(),
687            };
688            v.push(info);
689        }
690        EngineMessage::Info(v)
691    }
692
693    // info currline ...
694    fn parse_currline(pair: Pair<Rule>) -> InfoParam {
695        let mut cpu_nr: Option<u16> = None;
696        let mut line: Vec<Move> = Vec::<Move>::new();
697
698        for sp in pair.into_inner() {
699            match sp.as_rule() {
700                Rule::cpunr => cpu_nr = Some(parse_digits::<u16>(sp)),
701                Rule::moves => line = parse_moves(sp),
702                _ => unreachable!(),
703            }
704        }
705        InfoParam::CurrLine { cpu_nr, line }
706    }
707
708    // info score cp ...
709    fn parse_score_cp(pair: Pair<Rule>) -> InfoParam {
710        let mut v: Option<i32> = None;
711        let mut bound: ScoreBound = ScoreBound::Exact;
712
713        for sp in pair.into_inner() {
714            match sp.as_rule() {
715                Rule::integer => {
716                    let s = as_str!(sp); // Extract the string representation
717                    v = Some(s.parse::<i32>().unwrap_or_else(|err| {
718                        unreachable!(
719                            "PEST grammar bug: failed to parse integer '{}': {:?}",
720                            s, err
721                        )
722                    }));
723                }
724                Rule::lowerbound => bound = ScoreBound::Lower,
725                Rule::upperbound => bound = ScoreBound::Upper,
726                _ => unreachable!(), // really?
727            }
728        }
729
730        if let Some(value) = v {
731            InfoParam::ScoreCp(value, bound)
732        } else {
733            unreachable!()
734        }
735    }
736
737    // info score mate ...
738    fn parse_score_mate(pair: Pair<Rule>) -> InfoParam {
739        let mut v: Option<i32> = None;
740        let mut bound: ScoreBound = ScoreBound::Exact;
741
742        for sp in pair.into_inner() {
743            match sp.as_rule() {
744                Rule::integer => {
745                    let s = as_str!(sp); // Extract the string representation
746                    v = Some(s.parse::<i32>().unwrap());
747                }
748                Rule::plus => bound = ScoreBound::MatePlus,
749                Rule::minus => bound = ScoreBound::MateMin,
750                Rule::lowerbound => bound = ScoreBound::Lower,
751                Rule::upperbound => bound = ScoreBound::Upper,
752                _ => unreachable!(),
753            }
754        }
755        InfoParam::ScoreMate(v, bound)
756    }
757}
758
759/// The EngineMessageStream struct enables iteration over a multi-line text string.
760pub struct EngineMessageStream<'a> {
761    /// Inner PEST iterator over grammar Rules
762    pairs: Pairs<'a, Rule>,
763}
764
765impl<'a> EngineMessageStream<'a> {
766    /// Create a new `EngineMessageStream` from an input string.
767    ///
768    /// SAFETY: Since the grammar is designed to process any input, this should never fail.
769    pub fn new(input: &'a str) -> Self {
770        Self::parse(input)
771    }
772
773    /// Parse an input string and return a new `EngineMessageStream`.
774    ///
775    /// SAFETY: Since the grammar is designed to process any input, this should never fail.
776    pub fn parse(input: &'a str) -> Self {
777        Self::try_parse(input).expect("Internal error: Failed to initialize UsiParser.")
778    }
779
780    pub fn try_parse(input: &'a str) -> Result<Self, PestError<Rule>> {
781        let pairs = UsiParser::parse(Rule::start, input);
782        match pairs {
783            Ok(pairs) => Ok(Self { pairs }),
784            Err(err) => Err(err),
785        }
786    }
787}
788
789impl Iterator for EngineMessageStream<'_> {
790    type Item = EngineMessage;
791
792    fn next(&mut self) -> Option<Self::Item> {
793        if let Some(pair) = self.pairs.by_ref().next() {
794            let res = EngineMessage::inner_parse(pair);
795            return Some(res);
796        }
797        None
798    }
799}
800
801// HELPERS
802
803// SAFETY: The PEST grammar ensures that all low-level parse/unwrap calls are safe.
804// Panics are justified since any panic would indicate a serious bug either in the
805// way this module hooks up the functions to the grammar or in the grammar itself.
806
807fn parse_move(pair: Pair<Rule>) -> Move {
808    for sp in pair.into_inner() {
809        if let Rule::one_move = sp.as_rule() {
810            return as_move!(sp);
811        }
812    }
813    unreachable!()
814}
815
816fn parse_moves(pair: Pair<Rule>) -> Vec<Move> {
817    let mut moves = Vec::<Move>::new();
818
819    for sp in pair.into_inner() {
820        match sp.as_rule() {
821            Rule::one_move => {
822                moves.push(as_move!(sp));
823            }
824            Rule::moves => {
825                let mvs: Vec<Move> = parse_moves(sp);
826                moves.extend(mvs);
827            }
828            _ => unreachable!(),
829        }
830    }
831
832    moves
833}
834
835fn parse_digits<T>(pair: Pair<Rule>) -> T
836where
837    T: FromStr,
838    T::Err: Debug,
839{
840    for sp in pair.into_inner() {
841        if let Rule::digits = sp.as_rule() {
842            return as_str!(sp).parse::<T>().unwrap();
843        }
844    }
845    unreachable!()
846}
847
848fn parse_integer<T>(pair: Pair<Rule>) -> T
849where
850    T: FromStr,
851    T::Err: Debug,
852{
853    for sp in pair.into_inner() {
854        if let Rule::integer = sp.as_rule() {
855            return as_str!(sp).parse::<T>().unwrap();
856        }
857    }
858    unreachable!()
859}
860
861fn parse_millisecs(pair: Pair<Rule>) -> Duration {
862    for sp in pair.into_inner() {
863        if let Rule::millisecs = sp.as_rule() {
864            let milliseconds: u64 = as_str!(sp).parse::<u64>().unwrap();
865            return Duration::from_millis(milliseconds);
866        }
867        if let Rule::digits = sp.as_rule() {
868            let milliseconds: u64 = as_str!(sp).parse::<u64>().unwrap();
869            return Duration::from_millis(milliseconds);
870        }
871    }
872    unreachable!()
873}
874
875fn parse_tokens(pair: Pair<'_, Rule>) -> String {
876    if let Some(sp) = pair.into_inner().next() {
877        match sp.as_rule() {
878            Rule::tokens => return as_string!(sp).to_owned(),
879            Rule::token => return as_string!(sp).to_owned(),
880            _ => return parse_tokens(sp),
881        }
882    }
883    unreachable!()
884}