1use std::collections::BTreeMap;
13use std::collections::BTreeSet;
14use std::collections::HashSet;
15
16mod succ;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19enum Direction {
20 BottomTop,
22
23 LeftRight,
25}
26
27pub fn parse(text: &str) -> BTreeMap<String, BTreeSet<String>> {
61 use Direction::BottomTop;
62 use Direction::LeftRight;
63
64 let direction = if "|:".chars().any(|c| text.contains(c)) {
66 BottomTop
67 } else {
68 LeftRight
69 };
70 let lines: Vec<Vec<char>> = text.lines().map(|line| line.chars().collect()).collect();
71
72 let get = |y: isize, x: isize| -> char {
74 if y < 0 || x < 0 {
75 ' '
76 } else {
77 lines
78 .get(y as usize)
79 .cloned()
80 .map_or(' ', |line| line.get(x as usize).cloned().unwrap_or(' '))
81 }
82 };
83
84 let get_name = |y: isize, x: isize| -> String {
86 (0..x)
87 .rev()
88 .map(|x| get(y, x))
89 .take_while(|&ch| is_name(ch, direction))
90 .collect::<Vec<_>>()
91 .into_iter()
92 .rev()
93 .chain(
94 (x..)
95 .map(|x| get(y, x))
96 .take_while(|&ch| is_name(ch, direction)),
97 )
98 .collect()
99 };
100
101 #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
103 struct State {
104 y: isize,
105 x: isize,
106 expected: &'static str,
107 is_range: bool,
108 }
109
110 let get_parents = |y: isize, x: isize| -> Vec<(String, bool)> {
113 let mut parents = Vec::new();
114 let mut visited = HashSet::new();
115 let mut visit = |state: State, to_visit: &mut Vec<State>| {
116 if visited.insert(state) {
117 let y = state.y;
118 let x = state.x;
119 let expected = state.expected;
120 let ch = get(y, x);
121 if is_name(ch, direction) && expected.contains('t') {
122 parents.push((get_name(y, x), state.is_range));
124 return;
125 }
126 if !expected.contains(ch) {
127 return;
128 }
129
130 let is_range = state.is_range || ch == ':' || ch == '.';
132 let s = |y, x, expected| State {
133 y,
134 x,
135 expected,
136 is_range,
137 };
138
139 match (ch, direction) {
140 (' ', _) => {}
141 ('|', BottomTop) | (':', BottomTop) => {
142 to_visit.push(s(y + 1, x - 1, "/"));
143 to_visit.push(s(y + 1, x, ":|/\\t"));
144 to_visit.push(s(y + 1, x + 1, "\\"));
145 }
146 ('\\', BottomTop) => {
147 to_visit.push(s(y + 1, x + 1, ":|\\t"));
148 to_visit.push(s(y + 1, x, ":|t"));
149 }
150 ('/', BottomTop) => {
151 to_visit.push(s(y + 1, x - 1, ":|/t"));
152 to_visit.push(s(y + 1, x, ":|t"));
153 }
154 ('-', LeftRight) | ('.', LeftRight) => {
155 to_visit.push(s(y - 1, x - 1, "\\"));
156 to_visit.push(s(y, x - 1, ".-/\\t"));
157 to_visit.push(s(y + 1, x - 1, "/"));
158 }
159 ('\\', LeftRight) => {
160 to_visit.push(s(y - 1, x - 1, ".-\\t"));
161 to_visit.push(s(y, x - 1, ".-t"));
162 }
163 ('/', LeftRight) => {
164 to_visit.push(s(y + 1, x - 1, ".-/t"));
165 to_visit.push(s(y, x - 1, ".-t"));
166 }
167 _ => unreachable!(),
168 }
169 }
170 };
171
172 let s = |y, x, expected| State {
173 y,
174 x,
175 expected,
176 is_range: false,
177 };
178 let mut to_visit: Vec<State> = match direction {
179 BottomTop => [
180 s(y + 1, x - 1, "/"),
181 s(y + 1, x, "|:"),
182 s(y + 1, x + 1, "\\"),
183 ],
184 LeftRight => [
185 s(y - 1, x - 1, "\\"),
186 s(y, x - 1, "-."),
187 s(y + 1, x - 1, "/"),
188 ],
189 }
190 .iter()
191 .cloned()
192 .filter(|state| state.expected.contains(get(state.y, state.x)))
193 .collect();
194 while let Some(state) = to_visit.pop() {
195 visit(state, &mut to_visit);
196 }
197
198 parents
199 };
200
201 let mut edges: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
203 for y in 0..lines.len() as isize {
204 for x in 0..lines[y as usize].len() as isize {
205 let ch = get(y, x);
206 if is_name(ch, direction) {
207 let name = get_name(y, x);
208 edges.entry(name.clone()).or_default();
209 for (parent, is_range) in get_parents(y, x) {
210 if !is_range {
211 edges.get_mut(&name).unwrap().insert(parent);
212 } else {
213 assert!(
217 parent.len() < name.len()
218 || (parent.len() == name.len() && parent < name),
219 "empty range: {:?} to {:?}",
220 parent,
221 name
222 );
223
224 let mut current: String = parent.clone();
225 loop {
226 let next = succ::str_succ(¤t);
227 edges.entry(next.clone()).or_default().insert(current);
228
229 if next == name {
230 break;
231 }
232
233 assert!(
234 next.len() < name.len()
235 || (next.len() == name.len() && next < name),
236 "mismatched range endpoints: {:?} to {:?}",
237 parent,
238 name
239 );
240
241 current = next;
242 }
243 }
244 }
245 }
246 match (ch, direction) {
248 ('-', BottomTop) => panic!("'-' is incompatible with BottomTop direction"),
249 ('|', LeftRight) => panic!("'|' is incompatible with LeftRight direction"),
250 _ => {}
251 }
252 }
253 }
254
255 edges
256}
257
258pub fn commit(
265 dag: &BTreeMap<String, BTreeSet<String>>,
266 mut commit_func: impl FnMut(String, Vec<Box<[u8]>>) -> Box<[u8]>,
267) {
268 let mut committed: BTreeMap<String, Box<[u8]>> = BTreeMap::new();
269
270 while committed.len() < dag.len() {
271 let mut made_progress = false;
272 for (name, parents) in dag.iter() {
273 if !committed.contains_key(name)
274 && parents.iter().all(|name| committed.contains_key(name))
275 {
276 let parent_ids = parents.iter().map(|name| committed[name].clone()).collect();
277 let new_id = commit_func(name.clone(), parent_ids);
278 committed.insert(name.to_string(), new_id);
279 made_progress = true;
280 }
281 }
282 assert!(made_progress, "graph contains cycles");
283 }
284}
285
286pub fn drawdag(text: &str, commit_func: impl FnMut(String, Vec<Box<[u8]>>) -> Box<[u8]>) {
288 commit(&parse(text), commit_func)
289}
290
291fn is_name(ch: char, direction: Direction) -> bool {
292 match (ch, direction) {
293 ('.', Direction::BottomTop) => true,
294 _ => ch.is_alphanumeric() || ",()_'\"".contains(ch),
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 struct CommitLog {
303 log: String,
304 }
305
306 impl CommitLog {
307 fn new() -> Self {
308 Self { log: String::new() }
309 }
310
311 fn commit(&mut self, name: String, parents: Vec<Box<[u8]>>) -> Box<[u8]> {
312 let new_id = self.log.chars().filter(|&ch| ch == '\n').count();
313 let parents_str: Vec<String> = parents
314 .into_iter()
315 .map(|p| String::from_utf8(p.into_vec()).unwrap())
316 .collect();
317 self.log += &format!(
318 "{}: {{ parents: {:?}, name: {} }}\n",
319 new_id, parents_str, name
320 );
321 format!("{}", new_id).as_bytes().to_vec().into_boxed_slice()
322 }
323 }
324
325 fn assert_drawdag(text: &str, expected: &str) {
326 let mut log = CommitLog::new();
327 drawdag(text, |n, p| log.commit(n, p));
328 assert_eq!(log.log, expected);
329 }
330
331 fn p(text: &str) -> Vec<String> {
334 parse(text)
335 .into_iter()
336 .map(|(k, vs)| {
337 let vs = vs.into_iter().collect::<Vec<_>>().join(", ");
338 format!("{} -> [{}]", k, vs)
339 })
340 .collect()
341 }
342
343 #[test]
344 #[should_panic]
345 fn test_drawdag_cycle1() {
346 let mut log = CommitLog::new();
347 drawdag("A-B B-A", |n, p| log.commit(n, p));
348 }
349
350 #[test]
351 #[should_panic]
352 fn test_drawdag_cycle2() {
353 let mut log = CommitLog::new();
354 drawdag("A-B-C-A", |n, p| log.commit(n, p));
355 }
356
357 #[test]
358 #[should_panic]
359 fn test_drawdag_mismatched_range1() {
360 let mut log = CommitLog::new();
361 drawdag("0..A", |n, p| log.commit(n, p));
362 }
363
364 #[test]
365 #[should_panic]
366 fn test_drawdag_mismatched_range2() {
367 let mut log = CommitLog::new();
368 drawdag("(09)..(A0)", |n, p| log.commit(n, p));
369 }
370
371 #[test]
372 fn test_drawdag() {
373 assert_drawdag(
374 "A-C-B",
375 r#"0: { parents: [], name: A }
3761: { parents: ["0"], name: C }
3772: { parents: ["1"], name: B }
378"#,
379 );
380
381 assert_drawdag(
382 r#"
383 C-D-\ /--I--J--\
384A-B------E-F-G-H--------K--L"#,
385 r#"0: { parents: [], name: A }
3861: { parents: ["0"], name: B }
3872: { parents: [], name: C }
3883: { parents: ["2"], name: D }
3894: { parents: ["1", "3"], name: E }
3905: { parents: ["4"], name: F }
3916: { parents: ["5"], name: G }
3927: { parents: ["6"], name: H }
3938: { parents: ["6"], name: I }
3949: { parents: ["8"], name: J }
39510: { parents: ["7", "9"], name: K }
39611: { parents: ["10"], name: L }
397"#,
398 );
399
400 assert_drawdag(
401 r#"
402 G
403 |
404I D C F
405 \ \| |
406 H B E
407 \|/
408 A
409"#,
410 r#"0: { parents: [], name: A }
4111: { parents: ["0"], name: B }
4122: { parents: ["1"], name: C }
4133: { parents: ["1"], name: D }
4144: { parents: ["0"], name: E }
4155: { parents: ["4"], name: F }
4166: { parents: ["5"], name: G }
4177: { parents: ["0"], name: H }
4188: { parents: ["7"], name: I }
419"#,
420 );
421
422 assert_drawdag(
423 r#"
424 A
425 /|\
426 H B E
427 / /| |
428I D C F
429 |
430 G
431"#,
432 r#"0: { parents: [], name: C }
4331: { parents: [], name: D }
4342: { parents: [], name: G }
4353: { parents: [], name: I }
4364: { parents: ["0", "1"], name: B }
4375: { parents: ["2"], name: F }
4386: { parents: ["3"], name: H }
4397: { parents: ["5"], name: E }
4408: { parents: ["4", "7", "6"], name: A }
441"#,
442 );
443 }
444
445 #[test]
446 fn test_parse_range() {
447 assert_eq!(p("A..D"), ["A -> []", "B -> [A]", "C -> [B]", "D -> [C]"]);
448 assert_eq!(
449 p(r"
450 A1A,B23z,(9z)..A1A,B23z,(10c)
451 "),
452 [
453 "A1A,B23z,(10a) -> [A1A,B23z,(9z)]",
454 "A1A,B23z,(10b) -> [A1A,B23z,(10a)]",
455 "A1A,B23z,(10c) -> [A1A,B23z,(10b)]",
456 "A1A,B23z,(9z) -> []"
457 ]
458 );
459 assert_eq!(
460 p(r"
461 B08
462 :
463 B04"),
464 [
465 "B04 -> []",
466 "B05 -> [B04]",
467 "B06 -> [B05]",
468 "B07 -> [B06]",
469 "B08 -> [B07]"
470 ]
471 );
472 assert_eq!(
473 p(r"
474 B10
475 | \
476 : C
477 | /
478 B08
479 :
480 B06"),
481 [
482 "B06 -> []",
483 "B07 -> [B06]",
484 "B08 -> [B07]",
485 "B09 -> [B08]",
486 "B10 -> [B09, C]",
487 "C -> [B08]"
488 ]
489 );
490 assert_eq!(
491 p(r"
492 AE
493 | \
494 : C
495 | /
496 AB
497 :
498 X"),
499 [
500 "AA -> [Z]",
501 "AB -> [AA]",
502 "AC -> [AB]",
503 "AD -> [AC]",
504 "AE -> [AD, C]",
505 "C -> [AB]",
506 "X -> []",
507 "Y -> [X]",
508 "Z -> [Y]"
509 ]
510 );
511 }
512
513 #[test]
514 fn test_parse_special_names() {
515 assert_eq!(
516 p("ancestor(desc(\"D\"),desc('_A'))--B"),
517 [
518 "B -> [ancestor(desc(\"D\"),desc('_A'))]",
519 "ancestor(desc(\"D\"),desc('_A')) -> []"
520 ]
521 );
522 assert_eq!(
523 p(r#"
524 B
525 |
526 .
527 "#),
528 [". -> []", "B -> [.]"]
529 );
530 }
531}