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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
//! A parser for [Plaintext](https://www.conwaylife.com/wiki/Plaintext) format.
use crate::{Coordinates, Input};
use displaydoc::Display;
use std::io::{BufReader, Error as IoError, Read};
use thiserror::Error;
/// Errors that can be returned when parsing a Plaintext file.
#[derive(Debug, Error, Display)]
pub enum Error {
/// Unexpected character: {0}.
UnexpectedChar(char),
/// Error when reading from input: {0}.
IoError(#[from] IoError),
}
/// A parser for [Plaintext](https://www.conwaylife.com/wiki/Plaintext) format.
///
/// As an iterator, it iterates over the living cells.
///
/// # Examples
///
/// ## Reading from a string:
///
/// ```rust
/// use ca_formats::plaintext::Plaintext;
///
/// const GLIDER: &str = r"! Glider
/// !
/// .O.
/// ..O
/// OOO";
///
/// let glider = Plaintext::new(GLIDER).unwrap();
///
/// let cells = glider.map(|cell| cell.unwrap()).collect::<Vec<_>>();
/// assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
/// ```
///
/// ## Reading from a file:
///
/// ``` rust
/// use std::fs::File;
/// use ca_formats::plaintext::Plaintext;
///
/// let file = File::open("tests/sirrobin.cells").unwrap();
/// let sirrobin = Plaintext::new_from_file(file).unwrap();
///
/// assert_eq!(sirrobin.count(), 282);
/// ```
#[must_use]
#[derive(Debug)]
pub struct Plaintext<I: Input> {
/// An iterator over lines of a Plaintext file.
lines: I::Lines,
/// An iterator over bytes of the current line.
current_line: Option<I::Bytes>,
/// Coordinates of the current cell.
position: Coordinates,
}
impl<I: Input> Plaintext<I> {
/// Creates a new parser instance from input.
pub fn new(input: I) -> Result<Self, Error> {
let mut lines = input.lines();
let mut current_line = None;
for item in &mut lines {
let line = I::line(item)?;
if !line.as_ref().starts_with('!') {
current_line = Some(I::bytes(line));
break;
}
}
Ok(Self {
lines,
current_line,
position: (0, 0),
})
}
}
impl<I, L> Plaintext<I>
where
I: Input<Lines = L>,
L: Input,
{
/// Parse the remaining unparsed lines as a new Plaintext.
pub fn remains(self) -> Result<Plaintext<L>, Error> {
Plaintext::new(self.lines)
}
}
impl<R: Read> Plaintext<BufReader<R>> {
/// Creates a new parser instance from something that implements [`Read`] trait, e.g., a [`File`](std::fs::File).
pub fn new_from_file(file: R) -> Result<Self, Error> {
Self::new(BufReader::new(file))
}
}
impl<I: Input> Clone for Plaintext<I>
where
I::Lines: Clone,
I::Bytes: Clone,
{
fn clone(&self) -> Self {
Self {
lines: self.lines.clone(),
current_line: self.current_line.clone(),
position: self.position,
}
}
}
/// An iterator over living cells in a Plaintext file.
impl<I: Input> Iterator for Plaintext<I> {
type Item = Result<Coordinates, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(c) = self.current_line.as_mut().and_then(Iterator::next) {
match c {
b'O' | b'*' => {
let cell = self.position;
self.position.0 += 1;
return Some(Ok(cell));
}
b'.' => self.position.0 += 1,
_ if c.is_ascii_whitespace() => continue,
_ => return Some(Err(Error::UnexpectedChar(char::from(c)))),
}
} else if let Some(item) = self.lines.next() {
match I::line(item) {
Ok(line) => {
if line.as_ref().starts_with('!') {
continue;
} else {
self.position.0 = 0;
self.position.1 += 1;
self.current_line = Some(I::bytes(line));
}
}
Err(e) => {
return Some(Err(Error::IoError(e)));
}
}
} else {
return None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plaintext_glider() -> Result<(), Error> {
const GLIDER: &str = r"!Name: Glider
!
.O.
..O
OOO";
let glider = Plaintext::new(GLIDER)?;
let _ = glider.clone();
let cells = glider.collect::<Result<Vec<_>, _>>()?;
assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
Ok(())
}
}