rlx_core/render/scenes/lines/
grammar.rs1#![deny(
15 clippy::unwrap_used,
16 clippy::expect_used,
17 clippy::indexing_slicing,
18 clippy::panic,
19 clippy::unreachable
20)]
21
22pub fn expand(axiom: &str, rules: &[(char, String)], depth: u32) -> String {
29 let mut current = axiom.to_string();
30 for _ in 0..depth {
31 let mut next = String::with_capacity(current.len().saturating_mul(2));
32 for ch in current.chars() {
33 match rules.iter().find(|(pred, _)| *pred == ch) {
34 Some((_, succ)) => next.push_str(succ),
35 None => next.push(ch),
36 }
37 }
38 current = next;
39 }
40 current
41}
42
43pub const STREAM_DEPTH: u32 = 32;
51
52pub fn is_draw(ch: char) -> bool {
54 matches!(ch, 'F' | 'G')
55}
56
57pub fn stream_draws(axiom: &str, rules: &[(char, String)], depth: u32) -> u64 {
63 let rule_of = |ch: char| rules.iter().position(|(pred, _)| *pred == ch);
64 let mut draws: Vec<u64> = rules.iter().map(|(p, _)| u64::from(is_draw(*p))).collect();
67 let yields = |s: &str, draws: &[u64]| -> u64 {
68 s.chars().fold(0u64, |sum, ch| {
69 let n = match rule_of(ch) {
70 Some(r) => draws.get(r).copied().unwrap_or(0),
71 None => u64::from(is_draw(ch)),
72 };
73 sum.saturating_add(n)
74 })
75 };
76 for _ in 0..depth {
77 draws = rules.iter().map(|(_, succ)| yields(succ, &draws)).collect();
78 }
79 yields(axiom, &draws)
80}
81
82pub fn nesting(s: &str) -> usize {
85 let mut open = 0usize;
86 let mut deepest = 0usize;
87 for ch in s.chars() {
88 match ch {
89 '[' => {
90 open += 1;
91 deepest = deepest.max(open);
92 }
93 ']' => open = open.saturating_sub(1),
94 _ => {}
95 }
96 }
97 deepest
98}
99
100pub fn bracket_bound(axiom: &str, rules: &[(char, String)], depth: u32) -> usize {
106 let per_level = rules.iter().map(|(_, s)| nesting(s)).max().unwrap_or(0);
107 nesting(axiom).saturating_add(per_level.saturating_mul(depth as usize))
108}
109
110#[derive(Debug, Clone, Copy)]
112struct Frame {
113 source: usize,
115 pos: usize,
117}
118
119#[derive(Debug, Clone)]
127pub struct Stream {
128 axiom: Vec<char>,
129 rules: Vec<(char, Vec<char>)>,
130 depth: u32,
131 frames: Vec<Frame>,
132}
133
134impl Stream {
135 pub fn new(axiom: &str, rules: &[(char, String)], depth: u32) -> Self {
137 let mut stream = Self {
138 axiom: axiom.chars().collect(),
139 rules: rules
140 .iter()
141 .map(|(pred, succ)| (*pred, succ.chars().collect()))
142 .collect(),
143 depth,
144 frames: Vec::with_capacity(depth as usize + 1),
145 };
146 stream.restart();
147 stream
148 }
149
150 pub fn restart(&mut self) {
152 self.frames.clear();
153 self.frames.push(Frame { source: 0, pos: 0 });
154 }
155
156 pub fn next_symbol(&mut self) -> Option<char> {
158 loop {
159 let level = self.frames.len().checked_sub(1)?;
160 let top = self.frames.last_mut()?;
161 let text = match top.source {
162 0 => Some(&self.axiom),
163 r => self.rules.get(r - 1).map(|(_, succ)| succ),
164 };
165 let Some(&ch) = text.and_then(|t| t.get(top.pos)) else {
166 self.frames.pop();
167 continue;
168 };
169 top.pos += 1;
170 if level < self.depth as usize
171 && let Some(r) = self.rules.iter().position(|(pred, _)| *pred == ch)
172 {
173 self.frames.push(Frame {
174 source: r + 1,
175 pos: 0,
176 });
177 continue;
178 }
179 return Some(ch);
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
191 fn the_stream_is_the_expansion_symbol_for_symbol() {
192 let grammars: [(&str, Vec<(char, String)>); 3] = [
193 ("A", vec![('A', "AB".into()), ('B', "A".into())]),
194 ("F", vec![('F', "F+F--F+F".into())]),
195 ("X", vec![('X', "F[+X]F".into()), ('F', "FF".into())]),
196 ];
197 for (axiom, rules) in &grammars {
198 for depth in 0..=5 {
199 let mut stream = Stream::new(axiom, rules, depth);
200 let streamed: String = std::iter::from_fn(|| stream.next_symbol()).collect();
201 assert_eq!(streamed, expand(axiom, rules, depth), "{axiom} at {depth}");
202 assert!(stream.frames.capacity() == depth as usize + 1);
203 }
204 }
205 }
206
207 #[test]
210 fn the_draw_count_is_counted_not_expanded() {
211 let rules = [('X', "F[+X]F".to_string()), ('F', "FF".to_string())];
212 for depth in 0..=6 {
213 let eager = expand("X", &rules, depth)
214 .chars()
215 .filter(|&c| is_draw(c))
216 .count();
217 assert_eq!(
218 stream_draws("X", &rules, depth),
219 eager as u64,
220 "depth {depth}"
221 );
222 }
223 let doubling = [('F', "FF".to_string())];
224 assert_eq!(stream_draws("F", &doubling, 63), 1u64 << 63);
225 assert_eq!(stream_draws("F", &doubling, 64), u64::MAX, "saturates");
226 let still = [('F', "F+".to_string())];
227 assert_eq!(stream_draws("F", &still, STREAM_DEPTH), 1, "no rule grows");
228 }
229
230 #[test]
232 fn the_bracket_bound_holds_the_streams_deepest_nesting() {
233 let rules = [('X', "F[+X][-[X]]".to_string())];
234 assert_eq!(nesting("F[+X][-[X]]"), 2);
235 for depth in 0..=6 {
236 let deepest = nesting(&expand("[X]", &rules, depth));
237 assert!(
238 deepest <= bracket_bound("[X]", &rules, depth),
239 "depth {depth}: opened {deepest}"
240 );
241 }
242 }
243
244 #[test]
245 fn expand_is_exact_and_deterministic() {
246 let rules = [('A', "AB".to_string()), ('B', "A".to_string())];
248 assert_eq!(expand("A", &rules, 0), "A");
249 assert_eq!(expand("A", &rules, 1), "AB");
250 assert_eq!(expand("A", &rules, 2), "ABA");
251 assert_eq!(expand("A", &rules, 3), "ABAAB");
252 assert_eq!(expand("A", &rules, 5), "ABAABABAABAAB");
253
254 let koch = [('F', "F+F--F+F".to_string())];
256 assert_eq!(expand("F", &koch, 1), "F+F--F+F");
257 assert_eq!(
258 expand("F", &koch, 2),
259 "F+F--F+F+F+F--F+F--F+F--F+F+F+F--F+F"
260 );
261
262 assert_eq!(expand("F", &koch, 3), expand("F", &koch, 3));
264 }
265
266 #[test]
267 fn characters_without_a_rule_pass_through() {
268 let rules = [('X', "F[+X]F".to_string()), ('F', "FF".to_string())];
271 assert_eq!(expand("X", &rules, 1), "F[+X]F");
272 assert_eq!(expand("X", &rules, 2), "FF[+F[+X]F]FF");
273 }
274}