1use crate::Input;
4use displaydoc::Display;
5use lazy_regex::regex;
6use std::io::{BufReader, Error as IoError, Read};
7use thiserror::Error;
8
9#[derive(Debug, Error, Display)]
11pub enum Error {
12 InvalidHeaderLine(String),
14 InvalidNodeLine(String),
16 IoError(#[from] IoError),
18}
19
20#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
22pub struct Node {
23 pub id: usize,
24 pub data: NodeData,
25}
26
27#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
29pub enum NodeData {
30 Level1 { nw: u8, ne: u8, sw: u8, se: u8 },
34 Level3(u64),
38 Node {
43 level: u8,
44 nw: usize,
45 ne: usize,
46 sw: usize,
47 se: usize,
48 },
49}
50
51impl NodeData {
52 pub const fn level(&self) -> u8 {
53 match self {
54 Self::Level1 { .. } => 1,
55 Self::Level3(_) => 3,
56 Self::Node { level, .. } => *level,
57 }
58 }
59}
60
61fn parse_level3(line: &str) -> Option<NodeData> {
63 let mut node = 0;
64 let (mut x, mut y) = (0_u8, 0_u8);
65 for char in line.bytes() {
66 match char {
67 b'.' => x += 1,
68 b'*' => {
69 if x >= 8 || y >= 8 {
70 return None;
71 }
72 node |= 1 << ((7 - y) * 8 + (7 - x));
73 x += 1;
74 }
75 b'$' => {
76 x = 0;
77 y += 1;
78 }
79 c if c.is_ascii_whitespace() => (),
80 _ => return None,
81 }
82 }
83 Some(NodeData::Level3(node))
84}
85
86fn parse_level1(line: &str) -> Option<NodeData> {
88 let re = regex!(r"^1\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)");
89 let cap = re.captures(line)?;
90 let nw = cap[1].parse().ok()?;
91 let ne = cap[2].parse().ok()?;
92 let sw = cap[3].parse().ok()?;
93 let se = cap[4].parse().ok()?;
94 Some(NodeData::Level1 { nw, ne, sw, se })
95}
96
97fn parse_node(line: &str) -> Option<NodeData> {
99 let re = regex!(r"^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)");
100 let cap = re.captures(line)?;
101 let level = cap[1].parse().ok()?;
102 let nw = cap[2].parse().ok()?;
103 let ne = cap[3].parse().ok()?;
104 let sw = cap[4].parse().ok()?;
105 let se = cap[5].parse().ok()?;
106 Some(NodeData::Node {
107 level,
108 nw,
109 ne,
110 sw,
111 se,
112 })
113}
114
115fn parse_rule(line: &str) -> Option<String> {
117 let re = regex!(r"^#R\s*(?P<rule>.*\S)\s*$");
118 let cap = re.captures(line)?;
119 let rule = cap["rule"].to_string();
120 Some(rule)
121}
122
123fn parse_gen(line: &str) -> Option<u64> {
125 let re = regex!(r"^#G\s*(?P<gen>\d+)\s*$");
126 let cap = re.captures(line)?;
127 let gen = cap["gen"].parse().ok()?;
128 Some(gen)
129}
130
131#[must_use]
179#[derive(Debug)]
180pub struct Macrocell<I: Input> {
181 rule: Option<String>,
183 gen: Option<u64>,
185 lines: I::Lines,
187 current_line: Option<I::Line>,
189 id: usize,
191}
192
193impl<I: Input> Macrocell<I> {
194 pub fn new(input: I) -> Result<Self, Error> {
196 let mut lines = input.lines();
197 let mut rule = None;
198 let mut gen = None;
199 let mut current_line = None;
200 for item in &mut lines {
201 let line = I::line(item)?;
202 if line.as_ref().starts_with("[M2]") {
203 continue;
204 } else if line.as_ref().starts_with("#R") {
205 rule.replace(
206 parse_rule(line.as_ref())
207 .ok_or_else(|| Error::InvalidHeaderLine(line.as_ref().to_string()))?,
208 );
209 } else if line.as_ref().starts_with("#G") {
210 gen.replace(
211 parse_gen(line.as_ref())
212 .ok_or_else(|| Error::InvalidHeaderLine(line.as_ref().to_string()))?,
213 );
214 } else if !line.as_ref().starts_with('#') {
215 current_line = Some(line);
216 break;
217 }
218 }
219 Ok(Self {
220 rule,
221 gen,
222 lines,
223 current_line,
224 id: 1,
225 })
226 }
227
228 pub fn rule(&self) -> Option<&str> {
230 self.rule.as_deref()
231 }
232
233 pub const fn gen(&self) -> Option<u64> {
235 self.gen
236 }
237}
238
239impl<I, L> Macrocell<I>
240where
241 I: Input<Lines = L>,
242 L: Input,
243{
244 pub fn remains(self) -> Result<Macrocell<L>, Error> {
246 Macrocell::new(self.lines)
247 }
248}
249
250impl<R: Read> Macrocell<BufReader<R>> {
251 pub fn new_from_file(file: R) -> Result<Self, Error> {
253 Self::new(BufReader::new(file))
254 }
255}
256
257impl<I: Input> Clone for Macrocell<I>
258where
259 I::Lines: Clone,
260 I::Line: Clone,
261{
262 fn clone(&self) -> Self {
263 Self {
264 rule: self.rule.clone(),
265 gen: self.gen,
266 lines: self.lines.clone(),
267 current_line: self.current_line.clone(),
268 id: self.id,
269 }
270 }
271}
272
273impl<I: Input> Iterator for Macrocell<I> {
275 type Item = Result<Node, Error>;
276
277 fn next(&mut self) -> Option<Self::Item> {
278 loop {
279 if let Some(line) = self.current_line.take() {
280 if line.as_ref().starts_with('#') {
281 continue;
282 } else if line.as_ref().starts_with(&['.', '*', '$'][..]) {
283 if let Some(data) = parse_level3(line.as_ref()) {
284 let node = Node { id: self.id, data };
285 self.id += 1;
286 return Some(Ok(node));
287 } else {
288 return Some(Err(Error::InvalidNodeLine(line.as_ref().to_string())));
289 }
290 } else if line.as_ref().starts_with("1 ") {
291 if let Some(data) = parse_level1(line.as_ref()) {
292 let node = Node { id: self.id, data };
293 self.id += 1;
294 return Some(Ok(node));
295 } else {
296 return Some(Err(Error::InvalidNodeLine(line.as_ref().to_string())));
297 }
298 } else if let Some(data) = parse_node(line.as_ref()) {
299 let node = Node { id: self.id, data };
300 self.id += 1;
301 return Some(Ok(node));
302 } else {
303 return Some(Err(Error::InvalidNodeLine(line.as_ref().to_string())));
304 }
305 } else if let Some(item) = self.lines.next() {
306 match I::line(item) {
307 Ok(line) => {
308 self.current_line = Some(line);
309 }
310 Err(e) => {
311 return Some(Err(Error::IoError(e)));
312 }
313 }
314 } else {
315 return None;
316 }
317 }
318 }
319}
320
321#[allow(clippy::unusual_byte_groupings)]
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn macrocell_parse_line() {
328 assert_eq!(
329 parse_level3("$$..*$...*$.***$$$$"),
330 Some(NodeData::Level3(
331 0b_00000000_00000000_00100000_00010000_01110000_00000000_00000000_00000000
332 ))
333 );
334 assert_eq!(parse_level3("$$..*$...*$.***$$$$*"), None);
335 assert_eq!(
336 parse_level1("1 2 3 4 255"),
337 Some(NodeData::Level1 {
338 nw: 2,
339 ne: 3,
340 sw: 4,
341 se: 255,
342 })
343 );
344 assert_eq!(parse_level1("1 2 3 4 256"), None);
345 assert_eq!(
346 parse_node("10 20 30 40 50"),
347 Some(NodeData::Node {
348 level: 10,
349 nw: 20,
350 ne: 30,
351 sw: 40,
352 se: 50,
353 })
354 );
355 assert_eq!(parse_node("10 20 30 40"), None);
356 }
357
358 #[test]
359 fn macrocell_glider() -> Result<(), Error> {
360 const GLIDER: &str = r"[M2] (golly 3.4)
361#R B3/S23
362$$$$$$*$.*$
363.......*$
364**$
3654 0 1 2 3";
366
367 let glider = Macrocell::new(GLIDER)?;
368
369 let _ = glider.clone();
370
371 assert_eq!(glider.rule(), Some("B3/S23"));
372
373 let nodes = glider.collect::<Result<Vec<_>, _>>()?;
374 assert_eq!(
375 nodes,
376 vec![
377 Node {
378 id: 1,
379 data: NodeData::Level3(
380 0b_00000000_00000000_00000000_00000000_00000000_00000000_10000000_01000000
381 )
382 },
383 Node {
384 id: 2,
385 data: NodeData::Level3(
386 0b_00000001_00000000_00000000_00000000_00000000_00000000_00000000_00000000
387 )
388 },
389 Node {
390 id: 3,
391 data: NodeData::Level3(
392 0b_11000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000
393 )
394 },
395 Node {
396 id: 4,
397 data: NodeData::Node {
398 level: 4,
399 nw: 0,
400 ne: 1,
401 sw: 2,
402 se: 3,
403 }
404 }
405 ]
406 );
407 Ok(())
408 }
409}