bend/hvm/
mutual_recursion.rs1use super::tree_children;
2use crate::{
3 diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE},
4 fun::transform::definition_merge::MERGE_SEPARATOR,
5 maybe_grow,
6};
7use hvm::ast::{Book, Tree};
8use indexmap::{IndexMap, IndexSet};
9use std::fmt::Debug;
10
11type Ref = String;
12type Stack<T> = Vec<T>;
13type RefSet = IndexSet<Ref>;
14
15#[derive(Default)]
16pub struct Graph(IndexMap<Ref, RefSet>);
17
18pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Diagnostics> {
19 let graph = Graph::from(book);
20 let cycles = graph.cycles();
21
22 if !cycles.is_empty() {
23 let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(cycles));
24 diagnostics.add_book_warning(msg.as_str(), WarningType::RecursionCycle);
25 }
26
27 diagnostics.fatal(())
28}
29fn show_cycles(mut cycles: Vec<Vec<Ref>>) -> String {
30 let tail = if cycles.len() > 5 {
31 format!("\n{:ERR_INDENT_SIZE$}and {} other cycles...", "", cycles.len() - 5)
32 } else {
33 String::new()
34 };
35
36 cycles = cycles.into_iter().flat_map(combinations_from_merges).collect::<Vec<_>>();
37
38 let mut cycles = cycles
39 .iter()
40 .take(5)
41 .map(|cycle| {
42 let cycle_str = cycle
43 .iter()
44 .filter(|nam| !nam.contains("__C"))
45 .chain(cycle.first())
46 .cloned()
47 .collect::<Vec<_>>()
48 .join(" -> ");
49 format!("{:ERR_INDENT_SIZE$}* {}", "", cycle_str)
50 })
51 .collect::<Vec<String>>()
52 .join("\n");
53
54 cycles.push_str(&tail);
55
56 cycles
57}
58
59impl Graph {
60 pub fn cycles(&self) -> Vec<Vec<Ref>> {
61 let mut cycles = Vec::new();
62 let mut stack = Stack::new();
63 let mut visited = RefSet::new();
64
65 for r#ref in self.0.keys() {
66 if !visited.contains(r#ref) {
67 self.find_cycles(r#ref, &mut visited, &mut stack, &mut cycles);
68 }
69 }
70
71 cycles
72 }
73
74 fn find_cycles(
75 &self,
76 r#ref: &Ref,
77 visited: &mut RefSet,
78 stack: &mut Stack<Ref>,
79 cycles: &mut Vec<Vec<Ref>>,
80 ) {
81 if let Some(cycle_start) = stack.iter().position(|n| n == r#ref) {
83 cycles.push(stack[cycle_start..].to_vec());
85 return;
86 }
87
88 if visited.insert(r#ref.clone()) {
90 stack.push(r#ref.clone());
92
93 if let Some(dependencies) = self.get(r#ref) {
95 for dep in dependencies {
97 self.find_cycles(dep, visited, stack, cycles);
98 }
99 }
100
101 stack.pop();
102 }
103 }
104}
105
106fn collect_refs(current: Ref, tree: &Tree, graph: &mut Graph) {
108 maybe_grow(|| match tree {
109 Tree::Ref { nam, .. } => graph.add(current, nam.clone()),
110 Tree::Con { fst: _, snd } => collect_refs(current.clone(), snd, graph),
111 tree => {
112 for subtree in tree_children(tree) {
113 collect_refs(current.clone(), subtree, graph);
114 }
115 }
116 });
117}
118
119impl From<&Book> for Graph {
120 fn from(book: &Book) -> Self {
121 let mut graph = Self::new();
122
123 for (r#ref, net) in book.defs.iter() {
124 collect_refs(r#ref.clone(), &net.root, &mut graph);
126
127 for (_, left, right) in net.rbag.iter() {
129 if let Tree::Ref { nam, .. } = left {
130 graph.add(r#ref.clone(), nam.clone());
131 }
132 if let Tree::Ref { nam, .. } = right {
133 graph.add(r#ref.clone(), nam.clone());
134 }
135 }
136 }
137
138 graph
139 }
140}
141
142impl Graph {
143 pub fn new() -> Self {
144 Self::default()
145 }
146
147 pub fn add(&mut self, r#ref: Ref, dependency: Ref) {
148 self.0.entry(r#ref).or_default().insert(dependency.clone());
149 self.0.entry(dependency).or_default();
150 }
151
152 pub fn get(&self, r#ref: &Ref) -> Option<&RefSet> {
153 self.0.get(r#ref)
154 }
155}
156
157impl Debug for Graph {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(f, "Graph{:?}", self.0)
160 }
161}
162
163fn combinations_from_merges(cycle: Vec<Ref>) -> Vec<Vec<Ref>> {
164 let mut combinations: Vec<Vec<Ref>> = vec![vec![]];
165 for r#ref in cycle {
166 if let Some(index) = r#ref.find(MERGE_SEPARATOR) {
167 let (left, right) = r#ref.split_at(index);
168 let right = &right[MERGE_SEPARATOR.len()..]; let mut new_combinations = Vec::new();
170 for combination in &combinations {
171 let mut left_comb = combination.clone();
172 left_comb.push(left.to_string());
173 new_combinations.push(left_comb);
174
175 let mut right_comb = combination.clone();
176 right_comb.push(right.to_string());
177 new_combinations.push(right_comb);
178 }
179 combinations = new_combinations;
180 } else {
181 for combination in &mut combinations {
182 combination.push(r#ref.clone());
183 }
184 }
185 }
186 combinations
187}