1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::io::BufRead;
use std::time::Instant;

use crate::error::Error;
use crate::EngineCommand;

/// A struct to represent each output produced from a USI engine process.
#[derive(Debug)]
pub struct EngineOutput {
    response: Option<EngineCommand>,
    raw_str: String,
    timestamp: Instant,
}

impl EngineOutput {
    pub fn response(&self) -> &Option<EngineCommand> {
        &self.response
    }

    pub fn raw_str(&self) -> &str {
        &self.raw_str
    }

    pub fn timestamp(&self) -> &Instant {
        &self.timestamp
    }
}

/// `EngineCommandReader<R>` produces a structured output from a reader.
///
/// # Examples
///
/// ```
/// use usi::{BestMoveParams, EngineCommand, EngineCommandReader};
///
/// let buf = "usiok\nreadyok\n";
/// let mut reader = EngineCommandReader::new(buf.as_bytes());
/// assert_eq!(Some(EngineCommand::UsiOk), *reader.next_command().unwrap().response());
/// assert_eq!(Some(EngineCommand::ReadyOk), *reader.next_command().unwrap().response());
///```
///
#[derive(Debug)]
pub struct EngineCommandReader<R: BufRead> {
    receive: R,
}

impl<R: BufRead> EngineCommandReader<R> {
    pub fn new(receive: R) -> EngineCommandReader<R> {
        EngineCommandReader { receive }
    }

    pub fn next_command(&mut self) -> Result<EngineOutput, Error> {
        let mut buf = String::new();

        loop {
            let bytes_read = self.receive.read_line(&mut buf)?;
            if bytes_read == 0 {
                return Ok(EngineOutput {
                    response: None,
                    raw_str: buf,
                    timestamp: Instant::now(),
                });
            }

            if !buf.trim().is_empty() {
                break;
            }
            buf.clear();
        }

        let res = EngineCommand::parse(&buf)?;
        Ok(EngineOutput {
            response: Some(res),
            raw_str: buf,
            timestamp: Instant::now(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::BestMoveParams;

    #[test]
    fn it_works() {
        let buf = "\nusiok\n\n     readyok\n  bestmove 5e5f\n";

        let mut reader = EngineCommandReader::new(buf.as_bytes());

        let output = reader.next_command().expect("failed to read the output");

        if !matches!(*output.response(), Some(EngineCommand::UsiOk)) {
            unreachable!("unexpected {:?}", output.response());
        }
        assert_eq!("usiok\n", output.raw_str());

        let output = reader.next_command().expect("failed to read the output");
        if !matches!(*output.response(), Some(EngineCommand::ReadyOk)) {
            unreachable!("unexpected {:?}", output.response());
        }
        assert_eq!("     readyok\n", output.raw_str());

        let output = reader.next_command().expect("failed to read the output");
        if !matches!(
            *output.response(),
            Some(EngineCommand::BestMove(BestMoveParams::MakeMove(_, None)))
        ) {
            unreachable!("unexpected {:?}", output.response());
        }
        assert_eq!("  bestmove 5e5f\n", output.raw_str());
    }
}