life_backend/format/plaintext/
core.rs

1use anyhow::Result;
2use std::fmt;
3use std::io::Read;
4use std::str::FromStr;
5
6use super::{PlaintextLine, PlaintextParser};
7use crate::{Format, Position, Rule};
8
9/// A representation for Plaintext file format.
10///
11/// The detail of this format is described in:
12///
13/// - [Plaintext - LifeWiki](https://conwaylife.com/wiki/Plaintext)
14///
15/// # Examples
16///
17/// Parses the given Plaintext file, and checks live cells included in it:
18///
19/// ```
20/// use std::fs::File;
21/// use life_backend::format::Plaintext;
22/// use life_backend::Position;
23/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
24/// let file = File::open("patterns/rpentomino.cells")?;
25/// let parser = Plaintext::new(file)?;
26/// assert!(parser.live_cells().eq([Position(1, 0), Position(2, 0), Position(0, 1), Position(1, 1), Position(1, 2)]));
27/// # Ok(())
28/// # }
29/// ```
30///
31/// Parses the given string in Plaintext format:
32///
33/// ```
34/// use life_backend::format::Plaintext;
35/// use life_backend::Position;
36/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
37/// let pattern = "\
38///     !Name: R-pentomino\n\
39///     .OO\n\
40///     OO.\n\
41///     .O.\n\
42/// ";
43/// let parser = pattern.parse::<Plaintext>()?;
44/// assert!(parser.live_cells().eq([Position(1, 0), Position(2, 0), Position(0, 1), Position(1, 1), Position(1, 2)]));
45/// # Ok(())
46/// # }
47/// ```
48///
49#[derive(Clone, Debug)]
50pub struct Plaintext {
51    pub(super) name: Option<String>,
52    pub(super) comments: Vec<String>,
53    pub(super) contents: Vec<PlaintextLine>,
54}
55
56// Inherent methods
57
58impl Plaintext {
59    /// Creates from the specified implementor of [`Read`], such as [`File`] or `&[u8]`.
60    ///
61    /// [`Read`]: std::io::Read
62    /// [`File`]: std::fs::File
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use life_backend::format::Plaintext;
68    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
69    /// let pattern = "\
70    ///     !Name: T-tetromino\n\
71    ///     OOO\n\
72    ///     .O.\n\
73    /// ";
74    /// let parser = Plaintext::new(pattern.as_bytes())?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    ///
79    #[inline]
80    pub fn new<R>(read: R) -> Result<Self>
81    where
82        R: Read,
83    {
84        PlaintextParser::parse(read)
85    }
86
87    /// Returns the name of the pattern.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use life_backend::format::Plaintext;
93    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
94    /// let pattern = "\
95    ///     !Name: T-tetromino\n\
96    ///     OOO\n\
97    ///     .O.\n\
98    /// ";
99    /// let parser = Plaintext::new(pattern.as_bytes())?;
100    /// assert_eq!(parser.name(), Some("T-tetromino".to_string()));
101    /// # Ok(())
102    /// # }
103    /// ```
104    ///
105    pub fn name(&self) -> Option<String> {
106        self.name.clone()
107    }
108
109    /// Returns comments of the pattern.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use life_backend::format::Plaintext;
115    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
116    /// let pattern = "\
117    ///     !Name: T-tetromino\n\
118    ///     !comment0\n\
119    ///     !comment1\n\
120    ///     OOO\n\
121    ///     .O.\n\
122    /// ";
123    /// let parser = Plaintext::new(pattern.as_bytes())?;
124    /// assert_eq!(parser.comments().len(), 2);
125    /// assert_eq!(parser.comments()[0], "comment0");
126    /// assert_eq!(parser.comments()[1], "comment1");
127    /// # Ok(())
128    /// # }
129    /// ```
130    ///
131    #[inline]
132    pub const fn comments(&self) -> &Vec<String> {
133        &self.comments
134    }
135
136    /// Creates an owning iterator over the series of live cell positions in ascending order.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use life_backend::format::Plaintext;
142    /// use life_backend::Position;
143    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
144    /// let pattern = "\
145    ///     !Name: T-tetromino\n\
146    ///     OOO\n\
147    ///     .O.\n\
148    /// ";
149    /// let parser = Plaintext::new(pattern.as_bytes())?;
150    /// assert!(parser.live_cells().eq([Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)]));
151    /// # Ok(())
152    /// # }
153    /// ```
154    ///
155    pub fn live_cells(&self) -> impl Iterator<Item = Position<usize>> + '_ {
156        self.contents.iter().flat_map(|PlaintextLine(y, xs)| xs.iter().map(move |x| Position(*x, *y)))
157    }
158}
159
160// Trait implementations
161
162impl Format for Plaintext {
163    fn rule(&self) -> Rule {
164        Rule::conways_life()
165    }
166    fn live_cells(&self) -> Box<dyn Iterator<Item = Position<usize>> + '_> {
167        Box::new(self.live_cells())
168    }
169}
170
171impl fmt::Display for Plaintext {
172    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173        if let Some(name) = self.name() {
174            writeln!(f, "!Name: {name}")?;
175        }
176        for line in self.comments() {
177            writeln!(f, "!{line}")?;
178        }
179        if !self.contents.is_empty() {
180            let max_x = self.contents.iter().flat_map(|PlaintextLine(_, xs)| xs.iter()).copied().max().unwrap(); // this unwrap() never panic because flat_map() always returns at least one value under !self.contents.is_empty()
181            let dead_cell_chars = ".".repeat(max_x) + "."; // this code avoids `".".repeat(max_x + 1)` because `max_x + 1` overflows if max_x == usize::MAX
182            let mut prev_y = 0;
183            for PlaintextLine(curr_y, xs) in &self.contents {
184                for _ in prev_y..(*curr_y) {
185                    writeln!(f, "{dead_cell_chars}")?;
186                }
187                let line = {
188                    let capacity = if max_x < usize::MAX { max_x + 1 } else { max_x };
189                    let (mut buf, prev_x) = xs.iter().fold((String::with_capacity(capacity), 0), |(mut buf, prev_x), &curr_x| {
190                        buf += &dead_cell_chars[0..(curr_x - prev_x)];
191                        buf += "O";
192                        (buf, curr_x + 1)
193                    });
194                    if prev_x <= max_x {
195                        buf += &dead_cell_chars[0..(max_x - prev_x + 1)]; // `!xs.is_empty()` is guaranteed by the structure of Plaintext, so `prev_x > 0` is also guaranteed. Thus `max_x - prev_x + 1` never overflow
196                    }
197                    buf
198                };
199                writeln!(f, "{line}")?;
200                prev_y = curr_y + 1;
201            }
202        }
203        Ok(())
204    }
205}
206
207impl FromStr for Plaintext {
208    type Err = anyhow::Error;
209    #[inline]
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        Self::new(s.as_bytes())
212    }
213}