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>> {
66 use Direction::BottomTop;
67 use Direction::LeftRight;
68
69 let direction = if "|:".chars().any(|c| text.contains(c)) {
71 BottomTop
72 } else {
73 LeftRight
74 };
75 let lines: Vec<Vec<char>> = text.lines().map(|line| line.chars().collect()).collect();
76
77 let get = |y: isize, x: isize| -> char {
79 if y < 0 || x < 0 {
80 ' '
81 } else {
82 lines
83 .get(y as usize)
84 .cloned()
85 .map_or(' ', |line| line.get(x as usize).cloned().unwrap_or(' '))
86 }
87 };
88
89 let get_name = |y: isize, x: isize| -> String {
91 (0..x)
92 .rev()
93 .map(|x| get(y, x))
94 .take_while(|&ch| is_name(ch, direction))
95 .collect::<Vec<_>>()
96 .into_iter()
97 .rev()
98 .chain(
99 (x..)
100 .map(|x| get(y, x))
101 .take_while(|&ch| is_name(ch, direction)),
102 )
103 .collect()
104 };
105
106 #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
108 struct State {
109 y: isize,
110 x: isize,
111 expected: &'static str,
112 is_range: bool,
113 }
114
115 let get_parents = |y: isize, x: isize| -> Vec<(String, bool)> {
118 let mut parents = Vec::new();
119 let mut visited = HashSet::new();
120 let mut visit = |state: State, to_visit: &mut Vec<State>| {
121 if visited.insert(state) {
122 let y = state.y;
123 let x = state.x;
124 let expected = state.expected;
125 let ch = get(y, x);
126 if is_name(ch, direction) && expected.contains('t') {
127 parents.push((get_name(y, x), state.is_range));
129 return;
130 }
131 if !expected.contains(ch) {
132 return;
133 }
134
135 let is_range = state.is_range || ch == ':' || ch == '.';
137 let s = |y, x, expected| State {
138 y,
139 x,
140 expected,
141 is_range,
142 };
143
144 match (ch, direction) {
145 (' ', _) => {}
146 ('|', BottomTop) | (':', BottomTop) => {
147 to_visit.push(s(y + 1, x - 1, "/"));
148 to_visit.push(s(y + 1, x, ":|/\\t"));
149 to_visit.push(s(y + 1, x + 1, "\\"));
150 }
151 ('\\', BottomTop) => {
152 to_visit.push(s(y + 1, x + 1, ":|\\t"));
153 to_visit.push(s(y + 1, x, ":|t"));
154 }
155 ('/', BottomTop) => {
156 to_visit.push(s(y + 1, x - 1, ":|/t"));
157 to_visit.push(s(y + 1, x, ":|t"));
158 }
159 ('-', LeftRight) | ('.', LeftRight) => {
160 to_visit.push(s(y - 1, x - 1, "\\"));
161 to_visit.push(s(y, x - 1, ".-/\\t"));
162 to_visit.push(s(y + 1, x - 1, "/"));
163 }
164 ('\\', LeftRight) => {
165 to_visit.push(s(y - 1, x - 1, ".-\\t"));
166 to_visit.push(s(y, x - 1, ".-t"));
167 }
168 ('/', LeftRight) => {
169 to_visit.push(s(y + 1, x - 1, ".-/t"));
170 to_visit.push(s(y, x - 1, ".-t"));
171 }
172 _ => unreachable!(),
173 }
174 }
175 };
176
177 let s = |y, x, expected| State {
178 y,
179 x,
180 expected,
181 is_range: false,
182 };
183 let mut to_visit: Vec<State> = match direction {
184 BottomTop => [
185 s(y + 1, x - 1, "/"),
186 s(y + 1, x, "|:"),
187 s(y + 1, x + 1, "\\"),
188 ],
189 LeftRight => [
190 s(y - 1, x - 1, "\\"),
191 s(y, x - 1, "-."),
192 s(y + 1, x - 1, "/"),
193 ],
194 }
195 .iter()
196 .cloned()
197 .filter(|state| state.expected.contains(get(state.y, state.x)))
198 .collect();
199 while let Some(state) = to_visit.pop() {
200 visit(state, &mut to_visit);
201 }
202
203 parents
204 };
205
206 let mut edges: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
208 for y in 0..lines.len() as isize {
209 for x in 0..lines[y as usize].len() as isize {
210 let ch = get(y, x);
211 if is_name(ch, direction) {
212 let name = get_name(y, x);
213 edges.entry(name.clone()).or_default();
214 for (parent, is_range) in get_parents(y, x) {
215 if !is_range {
216 edges.get_mut(&name).unwrap().insert(parent);
217 } else {
218 assert!(
222 parent.len() < name.len()
223 || (parent.len() == name.len() && parent < name),
224 "empty range: {:?} to {:?}",
225 parent,
226 name
227 );
228
229 let mut current: String = parent.clone();
230 loop {
231 let next = succ::str_succ(¤t);
232 edges.entry(next.clone()).or_default().insert(current);
233
234 if next == name {
235 break;
236 }
237
238 assert!(
239 next.len() < name.len()
240 || (next.len() == name.len() && next < name),
241 "mismatched range endpoints: {:?} to {:?}",
242 parent,
243 name
244 );
245
246 current = next;
247 }
248 }
249 }
250 }
251 match (ch, direction) {
253 ('-', BottomTop) => panic!("'-' is incompatible with BottomTop direction"),
254 ('|', LeftRight) => panic!("'|' is incompatible with LeftRight direction"),
255 _ => {}
256 }
257 }
258 }
259
260 edges
261}
262
263pub fn commit(
270 dag: &BTreeMap<String, BTreeSet<String>>,
271 mut commit_func: impl FnMut(String, Vec<Box<[u8]>>) -> Box<[u8]>,
272) {
273 let mut committed: BTreeMap<String, Box<[u8]>> = BTreeMap::new();
274
275 while committed.len() < dag.len() {
276 let mut made_progress = false;
277 for (name, parents) in dag.iter() {
278 if !committed.contains_key(name)
279 && parents.iter().all(|name| committed.contains_key(name))
280 {
281 let parent_ids = parents.iter().map(|name| committed[name].clone()).collect();
282 let new_id = commit_func(name.clone(), parent_ids);
283 committed.insert(name.to_string(), new_id);
284 made_progress = true;
285 }
286 }
287 assert!(made_progress, "graph contains cycles");
288 }
289}
290
291pub fn drawdag(text: &str, commit_func: impl FnMut(String, Vec<Box<[u8]>>) -> Box<[u8]>) {
293 commit(&parse(text), commit_func)
294}
295
296fn is_name(ch: char, direction: Direction) -> bool {
297 match (ch, direction) {
298 ('.', Direction::BottomTop) => true,
299 _ => ch.is_alphanumeric() || ",()_'\"".contains(ch),
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 struct CommitLog {
308 log: String,
309 }
310
311 impl CommitLog {
312 fn new() -> Self {
313 Self { log: String::new() }
314 }
315
316 fn commit(&mut self, name: String, parents: Vec<Box<[u8]>>) -> Box<[u8]> {
317 let new_id = self.log.chars().filter(|&ch| ch == '\n').count();
318 let parents_str: Vec<String> = parents
319 .into_iter()
320 .map(|p| String::from_utf8(p.into_vec()).unwrap())
321 .collect();
322 self.log += &format!(
323 "{}: {{ parents: {:?}, name: {} }}\n",
324 new_id, parents_str, name
325 );
326 format!("{}", new_id).as_bytes().to_vec().into_boxed_slice()
327 }
328 }
329
330 fn assert_drawdag(text: &str, expected: &str) {
331 let mut log = CommitLog::new();
332 drawdag(text, |n, p| log.commit(n, p));
333 assert_eq!(log.log, expected);
334 }
335
336 fn p(text: &str) -> Vec<String> {
339 parse(text)
340 .into_iter()
341 .map(|(k, vs)| {
342 let vs = vs.into_iter().collect::<Vec<_>>().join(", ");
343 format!("{} -> [{}]", k, vs)
344 })
345 .collect()
346 }
347
348 #[test]
349 #[should_panic]
350 fn test_drawdag_cycle1() {
351 let mut log = CommitLog::new();
352 drawdag("A-B B-A", |n, p| log.commit(n, p));
353 }
354
355 #[test]
356 #[should_panic]
357 fn test_drawdag_cycle2() {
358 let mut log = CommitLog::new();
359 drawdag("A-B-C-A", |n, p| log.commit(n, p));
360 }
361
362 #[test]
363 #[should_panic]
364 fn test_drawdag_mismatched_range1() {
365 let mut log = CommitLog::new();
366 drawdag("0..A", |n, p| log.commit(n, p));
367 }
368
369 #[test]
370 #[should_panic]
371 fn test_drawdag_mismatched_range2() {
372 let mut log = CommitLog::new();
373 drawdag("(09)..(A0)", |n, p| log.commit(n, p));
374 }
375
376 #[test]
377 fn test_drawdag() {
378 assert_drawdag(
379 "A-C-B",
380 r#"0: { parents: [], name: A }
3811: { parents: ["0"], name: C }
3822: { parents: ["1"], name: B }
383"#,
384 );
385
386 assert_drawdag(
387 r#"
388 C-D-\ /--I--J--\
389A-B------E-F-G-H--------K--L"#,
390 r#"0: { parents: [], name: A }
3911: { parents: ["0"], name: B }
3922: { parents: [], name: C }
3933: { parents: ["2"], name: D }
3944: { parents: ["1", "3"], name: E }
3955: { parents: ["4"], name: F }
3966: { parents: ["5"], name: G }
3977: { parents: ["6"], name: H }
3988: { parents: ["6"], name: I }
3999: { parents: ["8"], name: J }
40010: { parents: ["7", "9"], name: K }
40111: { parents: ["10"], name: L }
402"#,
403 );
404
405 assert_drawdag(
406 r#"
407 G
408 |
409I D C F
410 \ \| |
411 H B E
412 \|/
413 A
414"#,
415 r#"0: { parents: [], name: A }
4161: { parents: ["0"], name: B }
4172: { parents: ["1"], name: C }
4183: { parents: ["1"], name: D }
4194: { parents: ["0"], name: E }
4205: { parents: ["4"], name: F }
4216: { parents: ["5"], name: G }
4227: { parents: ["0"], name: H }
4238: { parents: ["7"], name: I }
424"#,
425 );
426
427 assert_drawdag(
428 r#"
429 A
430 /|\
431 H B E
432 / /| |
433I D C F
434 |
435 G
436"#,
437 r#"0: { parents: [], name: C }
4381: { parents: [], name: D }
4392: { parents: [], name: G }
4403: { parents: [], name: I }
4414: { parents: ["0", "1"], name: B }
4425: { parents: ["2"], name: F }
4436: { parents: ["3"], name: H }
4447: { parents: ["5"], name: E }
4458: { parents: ["4", "7", "6"], name: A }
446"#,
447 );
448 }
449
450 #[test]
451 fn test_parse_range() {
452 assert_eq!(p("A..D"), ["A -> []", "B -> [A]", "C -> [B]", "D -> [C]"]);
453 assert_eq!(
454 p(r"
455 A1A,B23z,(9z)..A1A,B23z,(10c)
456 "),
457 [
458 "A1A,B23z,(10a) -> [A1A,B23z,(9z)]",
459 "A1A,B23z,(10b) -> [A1A,B23z,(10a)]",
460 "A1A,B23z,(10c) -> [A1A,B23z,(10b)]",
461 "A1A,B23z,(9z) -> []"
462 ]
463 );
464 assert_eq!(
465 p(r"
466 B08
467 :
468 B04"),
469 [
470 "B04 -> []",
471 "B05 -> [B04]",
472 "B06 -> [B05]",
473 "B07 -> [B06]",
474 "B08 -> [B07]"
475 ]
476 );
477 assert_eq!(
478 p(r"
479 B10
480 | \
481 : C
482 | /
483 B08
484 :
485 B06"),
486 [
487 "B06 -> []",
488 "B07 -> [B06]",
489 "B08 -> [B07]",
490 "B09 -> [B08]",
491 "B10 -> [B09, C]",
492 "C -> [B08]"
493 ]
494 );
495 assert_eq!(
496 p(r"
497 AE
498 | \
499 : C
500 | /
501 AB
502 :
503 X"),
504 [
505 "AA -> [Z]",
506 "AB -> [AA]",
507 "AC -> [AB]",
508 "AD -> [AC]",
509 "AE -> [AD, C]",
510 "C -> [AB]",
511 "X -> []",
512 "Y -> [X]",
513 "Z -> [Y]"
514 ]
515 );
516 }
517
518 #[test]
519 fn test_parse_special_names() {
520 assert_eq!(
521 p("ancestor(desc(\"D\"),desc('_A'))--B"),
522 [
523 "B -> [ancestor(desc(\"D\"),desc('_A'))]",
524 "ancestor(desc(\"D\"),desc('_A')) -> []"
525 ]
526 );
527 assert_eq!(
528 p(r#"
529 B
530 |
531 .
532 "#),
533 [". -> []", "B -> [.]"]
534 );
535 }
536}