use nom::{line_ending, not_line_ending};
use std::io::Read;
named!(whitespace, take_while!(|c: u8| (c as char).is_whitespace()));
named!(comment_line<&[u8], &[u8]>,
do_parse!(
char!('#') >>
comment: not_line_ending >>
line_ending >>
(comment)
)
);
named!(header<&[u8], (u32, u32)>,
do_parse!(
whitespace >>
tag!("x") >>
whitespace >>
tag!("=") >>
whitespace >>
width: map_res!(nom::digit0, btoi::btoi) >>
whitespace >>
tag!(",") >>
whitespace >>
tag!("y") >>
whitespace >>
tag!("=") >>
whitespace >>
height: map_res!(nom::digit0, btoi::btoi) >>
not_line_ending >>
line_ending >>
(width, height)
)
);
fn parse_rle_digits(digits: &[u8]) -> Result<u32, btoi::ParseIntegerError> {
if digits.is_empty() {
Ok(1)
} else {
btoi::btoi(digits)
}
}
named!(pattern_unit<&[u8], PatternUnit>, do_parse!(
take_while!(|c: u8| (c as char).is_whitespace()) >>
reps: map_res!(nom::digit0, parse_rle_digits) >>
tag: one_of!("bo$") >>
( PatternUnit { reps, tag } )
));
named!(pattern<&[u8], Vec<PatternUnit>>, do_parse!(
units: many0!(pattern_unit) >>
tag!("!") >>
(units)
));
named!(rle<&[u8], (Vec<&[u8]>, (u32, u32), Vec<PatternUnit>)>,
do_parse!(
comments: many0!(comment_line) >>
dimensions: header >>
units: many0!(pattern_unit) >>
tag!("!") >>
(comments, dimensions, units)
)
);
#[derive(Debug, Fail)]
pub enum RleError {
#[fail(display = "Parsing error")]
Parse,
}
struct PatternUnit {
reps: u32,
tag: char,
}
pub struct Rle {
units: Vec<PatternUnit>,
}
impl Rle {
pub fn from_file<P>(path: P) -> Result<Self, failure::Error>
where
P: AsRef<std::path::Path>,
{
let file = std::fs::File::open(path)?;
let mut reader = std::io::BufReader::new(file);
let mut buf = vec![];
reader.read_to_end(&mut buf)?;
let (_rest, (_comments, (_width, _height), units)) =
rle(&buf).map_err(|_| RleError::Parse)?;
Ok(Self { units })
}
pub fn from_file_contents(contents: &[u8]) -> Result<Self, failure::Error> {
let (_rest, (_comments, (_width, _height), units)) =
rle(contents).map_err(|_| RleError::Parse)?;
Ok(Self { units })
}
pub fn from_pattern(pattern_str: &[u8]) -> Result<Self, failure::Error> {
let (_rest, units) = pattern(pattern_str).map_err(|_| RleError::Parse)?;
Ok(Self { units })
}
pub fn alive_cells(&self) -> Vec<(u32, u32)> {
let mut cells = vec![];
let mut x = 0;
let mut y = 0;
for unit in &self.units {
match unit.tag {
'b' => {
x += unit.reps;
}
'o' => {
for _ in 0..unit.reps {
cells.push((x, y));
x += 1;
}
}
'$' => {
x = 0;
y += unit.reps;
}
_ => unreachable!(),
}
}
cells
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_file() {
Rle::from_file("./assets/breeder1.rle").unwrap();
}
#[test]
#[should_panic]
fn from_file_err() {
Rle::from_file("nonexistent").unwrap();
}
#[test]
fn from_pattern() {
Rle::from_pattern(b"3b2o$2bobo$2bo2b$obo2b$2o!").unwrap();
}
#[test]
#[should_panic]
fn from_pattern_err() {
Rle::from_pattern(b"foo").unwrap();
}
#[test]
fn alive_cells() {
let rle = Rle::from_pattern(b"bob$2bo$3o!").unwrap();
let alive_cells = vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)];
assert_eq!(rle.alive_cells(), alive_cells);
}
}