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
//! Parsing rule strings of life-like and other cellular automata.
//!
//! Currently the following rules are supported:
//!
//! * [Totalistic Life-like](http://www.conwaylife.com/wiki/Totalistic_Life-like_cellular_automaton),
//!   e.g., `B3/S23`.
//! * [Isotropic non-totalistic Life-like](http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton),
//!   e.g., `B2ci3ai4c8/S02ae3eijkq4iz5ar6i7e`.
//! * [Non-isotropic Life-like](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton),
//!   e.g., `MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA`.
//! * [Totalistic Hexagonal](http://www.conwaylife.com/wiki/Hexagonal_neighbourhood),
//!   e.g., `B2/S34H`.
//! * [Isotropic non-totalistic Hexagonal](http://www.conwaylife.com/wiki/Hexagonal_neighbourhood),
//!   e.g., `B2o3-o4m/S12m3o4m5H`.
//! * [Non-isotropic Hexagonal](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton),
//!   e.g., `MAPFgFoF2gXgH5oF4B+gH4A6AH`.
//! * [von Neumann neighborhood](http://www.conwaylife.com/wiki/Von_Neumann_neighbourhood),
//!   e.g., `B2/S013V`.
//! * [Non-isotropic von Neumann](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton),
//!   e.g., `MAPHmlphg`.
//! * The corresponding [Generations rules](http://www.conwaylife.com/wiki/Generations)
//! of the above rules, e.g., `3457/357/5`.
//!
//! For non-Generations rules, four different notations are supported:
//! * [B/S notation](http://www.conwaylife.com/wiki/Rulestring#B.2FS_notation) (`B3/S23`)
//! * [S/B notation](http://www.conwaylife.com/wiki/Rulestring#S.2FB_notation) (`23/3`)
//! * [MAP strings](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
//!   for [non-isotropic rules](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
//!   (`MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA`)
//!
//! For Generations rules, four different notations are supported:
//!
//! * B/S notation (`B357/S3457/C5`)
//! * The notation used by [Golly](http://golly.sourceforge.net/Help/Algorithms/Generations.html) (`3457/357/5`)
//! * The notation used by [Catagolue](https://catagolue.appspot.com/rules/generations) (`g5b357s3457`)
//! * [MAP strings](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
//!   for [non-isotropic rules](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
//!   (`MAPARYBFxZpF38WaRd/aZZ//hZpF39pln/+aZZ//pZp/ukWaRd/aZZ//mmWf/6Waf7paZZ//pZp/umWaf7paZbplg/5`)
//!
//! Please refer to [Life Wiki](http://www.conwaylife.com/wiki/Rulestring) for detailed definitions and
//! notations of these rule strings.
//!
//! # Example:
//! ```
//! use ca_rules::ParseLife;
//!
//! // Define a struct for your rule:
//! #[derive(Debug, Eq, PartialEq)]
//! struct Rule {
//!     b: Vec<u8>,
//!     s: Vec<u8>,
//! }
//!
//! // Implement a parser trait for your rule:
//! // The choice of the trait depends on the type of rules you want to parse.
//! impl ParseLife for Rule {
//!     // Implement a function to construct the rule from b and s data:
//!     fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self {
//!         Rule { b, s }
//!     }
//! }
//!
//! // Then you can parse a rule string:
//! let life = Rule::parse_rule("B3/S23").unwrap();
//! assert_eq!(
//!     life,
//!     Rule {
//!         b: vec![3],
//!         s: vec![2, 3],
//!     }
//! )
//! ```

mod error;
mod macros;
mod rules;

pub use error::ParseRuleError;
pub use rules::*;

#[cfg(test)]
mod test {
    use super::{ParseNtLife, ParseRuleError};
    use base64::{
        alphabet::STANDARD,
        decode_engine,
        engine::{
            fast_portable::{FastPortable, FastPortableConfig},
            DecodePaddingMode,
        },
    };

    #[derive(Debug, Eq, PartialEq)]
    struct Rule {
        b: Vec<u8>,
        s: Vec<u8>,
    }

    impl ParseNtLife for Rule {
        fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self {
            Rule { b, s }
        }
    }

    const ENGINE_CONFIG: FastPortableConfig =
        FastPortableConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent);
    const ENGINE: FastPortable = FastPortable::from(&STANDARD, ENGINE_CONFIG);

    #[test]
    fn base64() -> Result<(), ParseRuleError> {
        let s = "MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA";
        let bytes = decode_engine(&s[3..], &ENGINE).map_err(|_| ParseRuleError::Base64Error)?;
        assert_eq!(bytes.len(), 0x200 / 8);
        let mut b = Vec::new();
        let mut s = Vec::new();
        for (i, x) in bytes.iter().map(|x| x.reverse_bits()).enumerate() {
            for j in 0..8 {
                if x & (1 << j) != 0 {
                    let k = i * 8 + j;
                    let n = ((k & 0x1e0) >> 1 | (k & 0x0f)) as u8;
                    if k & 0x10 == 0 {
                        b.push(n);
                    } else {
                        s.push(n);
                    }
                }
            }
        }
        assert_eq!(Rule { b, s }, Rule::parse_rule("B3/S23")?);
        Ok(())
    }
}