-
Notifications
You must be signed in to change notification settings - Fork 438
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #232 from HigherOrderCO/feature/sc-500/add-simple-…
…infinite-expansion-check-to-eagerly [sc-500] Add mutual recursion cycle check
- Loading branch information
Showing
15 changed files
with
258 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ | |
"interner", | ||
"itertools", | ||
"lcons", | ||
"linearization", | ||
"linearizes", | ||
"linearizing", | ||
"lnet", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod mutual_recursion; | ||
pub mod prune; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use hvmc::ast::{Book, Tree}; | ||
use indexmap::{IndexMap, IndexSet}; | ||
use std::fmt::Debug; | ||
|
||
type Ref = String; | ||
type Stack<T> = Vec<T>; | ||
type RefSet = IndexSet<Ref>; | ||
|
||
#[derive(Default)] | ||
pub struct Graph(IndexMap<Ref, RefSet>); | ||
|
||
pub fn check_cycles(book: &Book) -> Result<(), String> { | ||
let graph = Graph::from(book); | ||
let cycles = graph.cycles(); | ||
|
||
if cycles.is_empty() { | ||
Ok(()) | ||
} else { | ||
Err(format!( | ||
"{}\n{}\n{}\n{}", | ||
"Mutual recursion cycle detected in compiled functions:", | ||
pretty_print_cycles(&cycles), | ||
"This program will expand infinitely in strict evaluation mode.", | ||
"Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information." | ||
)) | ||
} | ||
} | ||
|
||
fn pretty_print_cycles(cycles: &[Vec<Ref>]) -> String { | ||
cycles | ||
.iter() | ||
.enumerate() | ||
.map(|(i, cycle)| { | ||
let cycle_str = cycle.iter().chain(cycle.first()).cloned().collect::<Vec<_>>().join(" -> "); | ||
format!("Cycle {}: {}", 1 + i, cycle_str) | ||
}) | ||
.collect::<Vec<String>>() | ||
.join("\n") | ||
} | ||
|
||
impl Graph { | ||
pub fn cycles(&self) -> Vec<Vec<Ref>> { | ||
let mut cycles = Vec::new(); | ||
let mut stack = Stack::new(); | ||
let mut visited = RefSet::new(); | ||
|
||
for r#ref in self.0.keys() { | ||
if !visited.contains(r#ref) { | ||
self.find_cycles(r#ref, &mut visited, &mut stack, &mut cycles); | ||
} | ||
} | ||
|
||
cycles | ||
} | ||
|
||
fn find_cycles( | ||
&self, | ||
r#ref: &Ref, | ||
visited: &mut RefSet, | ||
stack: &mut Stack<Ref>, | ||
cycles: &mut Vec<Vec<Ref>>, | ||
) { | ||
// Check if the current ref is already in the stack, which indicates a cycle. | ||
if let Some(cycle_start) = stack.iter().position(|n| n == r#ref) { | ||
// If found, add the cycle to the cycles vector. | ||
cycles.push(stack[cycle_start ..].to_vec()); | ||
return; | ||
} | ||
|
||
// If the ref has not been visited yet, mark it as visited. | ||
if visited.insert(r#ref.clone()) { | ||
// Add the current ref to the stack to keep track of the path. | ||
stack.push(r#ref.clone()); | ||
|
||
// Get the dependencies of the current ref. | ||
if let Some(dependencies) = self.get(r#ref) { | ||
// Search for cycles from each dependency. | ||
for dep in dependencies { | ||
self.find_cycles(dep, visited, stack, cycles); | ||
} | ||
} | ||
|
||
stack.pop(); | ||
} | ||
} | ||
} | ||
|
||
fn collect_refs(current: Ref, tree: &Tree, graph: &mut Graph) { | ||
match tree { | ||
Tree::Ref { nam } => graph.add(current, nam.clone()), | ||
Tree::Ctr { box lft, rgt, .. } => { | ||
if let Tree::Ref { nam } = lft { | ||
graph.add(current.clone(), nam.clone()); | ||
} | ||
collect_refs(current, rgt, graph); | ||
} | ||
Tree::Op { rhs: fst, out: snd, .. } | Tree::Mat { sel: fst, ret: snd } => { | ||
collect_refs(current.clone(), fst, graph); | ||
collect_refs(current, snd, graph); | ||
} | ||
Tree::Era | Tree::Num { .. } | Tree::Var { .. } => (), | ||
} | ||
} | ||
|
||
impl From<&Book> for Graph { | ||
fn from(book: &Book) -> Self { | ||
let mut graph = Self::new(); | ||
|
||
for (r#ref, net) in book.iter() { | ||
// Collect active refs from root. | ||
collect_refs(r#ref.clone(), &net.root, &mut graph); | ||
for (left, _) in net.redexes.iter() { | ||
// If left is an active reference, add to the graph. | ||
if let Tree::Ref { nam } = left { | ||
graph.add(r#ref.clone(), nam.clone()); | ||
} | ||
} | ||
} | ||
|
||
graph | ||
} | ||
} | ||
|
||
impl Graph { | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
pub fn add(&mut self, r#ref: Ref, dependency: Ref) { | ||
self.0.entry(r#ref).or_default().insert(dependency.clone()); | ||
self.0.entry(dependency).or_default(); | ||
} | ||
|
||
pub fn get(&self, r#ref: &Ref) -> Option<&RefSet> { | ||
self.0.get(r#ref) | ||
} | ||
} | ||
|
||
impl Debug for Graph { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "Graph{:?}", self.0) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
(A) = (B) | ||
(B) = (C) | ||
(C) = (A) | ||
|
||
(Main) = (A) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
(Len []) = 0 | ||
(Len (List.cons _ tail)) = (+ 1 (Len tail)) | ||
|
||
(Main) = (Len [* * * * * *]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
(A) = (B) | ||
(B) = (C) | ||
(C) = (A) | ||
|
||
(H) = (I) | ||
(I) = (H) | ||
|
||
(N x) = (x (N x)) | ||
|
||
(M) = (M) | ||
|
||
(Main) = * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
data Bool = True | False | ||
|
||
(if 0 then else) = else | ||
(if _ then else) = then | ||
|
||
(isOdd n) = (if (== n 0) False (isEven (- n 1))) | ||
(isEven n) = (if (== n 0) True (isOdd (- n 1))) | ||
|
||
(Main) = (isOdd 4) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
source: tests/golden_tests.rs | ||
input_file: tests/golden_tests/mutual_recursion/a_b_c.hvm | ||
--- | ||
Mutual recursion cycle detected in compiled functions: | ||
Cycle 1: A -> B -> C -> A | ||
This program will expand infinitely in strict evaluation mode. | ||
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
source: tests/golden_tests.rs | ||
input_file: tests/golden_tests/mutual_recursion/len.hvm | ||
--- | ||
@Len = ({2 @Len$S1 {2 #0 a}} a) | ||
@Len$S0 = {2 a b} | ||
& #1 ~ <+ c b> | ||
& @Len ~ (a c) | ||
@Len$S1 = {2 * @Len$S0} | ||
@List.cons = (a (b {2 {2 a {2 b c}} {2 * c}})) | ||
@List.nil = {2 * {2 a a}} | ||
@main = a | ||
& @Len ~ (b a) | ||
& @List.cons ~ (* (c b)) | ||
& @List.cons ~ (* (d c)) | ||
& @List.cons ~ (* (e d)) | ||
& @List.cons ~ (* (f e)) | ||
& @List.cons ~ (* (g f)) | ||
& @List.cons ~ (* (@List.nil g)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
source: tests/golden_tests.rs | ||
input_file: tests/golden_tests/mutual_recursion/multiple.hvm | ||
--- | ||
Mutual recursion cycle detected in compiled functions: | ||
Cycle 1: A -> B -> C -> A | ||
Cycle 2: H -> I -> H | ||
Cycle 3: M -> M | ||
Cycle 4: N -> N | ||
This program will expand infinitely in strict evaluation mode. | ||
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
source: tests/golden_tests.rs | ||
input_file: tests/golden_tests/mutual_recursion/odd_even.hvm | ||
--- | ||
Mutual recursion cycle detected in compiled functions: | ||
Cycle 1: isEven -> isOdd -> isEven | ||
This program will expand infinitely in strict evaluation mode. | ||
Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information. |