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}