Skip to content

Commit ad841ae

Browse files
Sdoba16LesterEvSe
authored andcommitted
Added C3 algorithm and fix linearization errors
1 parent 8a2cbdf commit ad841ae

2 files changed

Lines changed: 156 additions & 1 deletion

File tree

src/driver/mod.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ pub struct ProjectGraph {
3939
pub dependencies: HashMap<usize, Vec<usize>>,
4040
}
4141

42+
#[derive(Debug)]
43+
pub enum C3Error {
44+
CycleDetected(Vec<usize>),
45+
InconsistentLinearization { module: usize },
46+
}
47+
4248
fn parse_and_get_program(prog_file: &Path) -> Result<parse::Program, String> {
4349
let prog_text = std::fs::read_to_string(prog_file).map_err(|e| e.to_string())?;
4450
let file = prog_text.into();
@@ -51,6 +57,39 @@ fn parse_and_get_program(prog_file: &Path) -> Result<parse::Program, String> {
5157
}
5258
}
5359

60+
// Function used by the C3 algorithm.
61+
fn merge(mut seqs: Vec<Vec<usize>>) -> Option<Vec<usize>> {
62+
let mut result = Vec::new();
63+
64+
loop {
65+
seqs.retain(|s| !s.is_empty());
66+
if seqs.is_empty() {
67+
return Some(result);
68+
}
69+
70+
let mut candidate = None;
71+
72+
'outer: for seq in &seqs {
73+
let head = seq[0];
74+
75+
if seqs.iter().all(|s| !s[1..].contains(&head)) {
76+
candidate = Some(head);
77+
break 'outer;
78+
}
79+
}
80+
81+
let head = candidate?;
82+
83+
result.push(head);
84+
85+
for seq in &mut seqs {
86+
if seq.first() == Some(&head) {
87+
seq.remove(0);
88+
}
89+
}
90+
}
91+
}
92+
5493
impl ProjectGraph {
5594
pub fn new(lib_cfg: &LibConfig, root_program: &parse::Program) -> Result<Self, String> {
5695
let mut modules: Vec<Module> = vec![Module {
@@ -87,7 +126,10 @@ impl ProjectGraph {
87126
}
88127

89128
if let Some(&existing_id) = lookup.get(&path) {
90-
dependencies.entry(curr_id).or_default().push(existing_id);
129+
let deps = dependencies.entry(curr_id).or_default();
130+
if !deps.contains(&existing_id) {
131+
deps.push(existing_id);
132+
}
91133
continue;
92134
}
93135

@@ -110,4 +152,54 @@ impl ProjectGraph {
110152
dependencies,
111153
})
112154
}
155+
156+
pub fn c3_linearize(&self) -> Result<Vec<usize>, C3Error> {
157+
self.linearize_module(0)
158+
}
159+
160+
fn linearize_module(&self, root: usize) -> Result<Vec<usize>, C3Error> {
161+
let mut memo = HashMap::<usize, Vec<usize>>::new();
162+
let mut visiting = Vec::<usize>::new();
163+
164+
self.linearize_rec(root, &mut memo, &mut visiting)
165+
}
166+
167+
fn linearize_rec(
168+
&self,
169+
module: usize,
170+
memo: &mut HashMap<usize, Vec<usize>>,
171+
visiting: &mut Vec<usize>,
172+
) -> Result<Vec<usize>, C3Error> {
173+
if let Some(result) = memo.get(&module) {
174+
return Ok(result.clone());
175+
}
176+
177+
if visiting.contains(&module) {
178+
let cycle_start = visiting.iter().position(|m| *m == module).unwrap();
179+
return Err(C3Error::CycleDetected(visiting[cycle_start..].to_vec()));
180+
}
181+
182+
visiting.push(module);
183+
184+
let parents = self.dependencies.get(&module).cloned().unwrap_or_default();
185+
186+
let mut seqs: Vec<Vec<usize>> = Vec::new();
187+
188+
for parent in &parents {
189+
let lin = self.linearize_rec(*parent, memo, visiting)?;
190+
seqs.push(lin);
191+
}
192+
193+
seqs.push(parents.clone());
194+
195+
let mut result = vec![module];
196+
let merged = merge(seqs).ok_or(C3Error::InconsistentLinearization { module })?;
197+
198+
result.extend(merged);
199+
200+
visiting.pop();
201+
memo.insert(module, result.clone());
202+
203+
Ok(result)
204+
}
113205
}

src/driver/tests.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,69 @@ mod graph_construction_and_dependency_resolution {
168168
}
169169
}
170170

171+
// Topological Sorting
172+
#[cfg(test)]
173+
mod c3_linearization {
174+
use super::*;
175+
176+
#[test]
177+
fn test_c3_simple_import() {
178+
// Setup similar to above
179+
let (graph, ids, _dir) = setup_graph(vec![
180+
("main.simf", "use lib::math::some_func;"),
181+
("libs/lib/math.simf", ""),
182+
]);
183+
184+
let order = graph.c3_linearize().expect("C3 failed");
185+
186+
let root_id = ids["main"];
187+
let math_id = ids["math"];
188+
189+
// Assuming linearization order: Dependent (Root) -> Dependency (Math)
190+
assert_eq!(order, vec![root_id, math_id]);
191+
}
192+
193+
#[test]
194+
fn test_c3_diamond_dependency_deduplication() {
195+
// Setup:
196+
// root -> imports A, B
197+
// A -> imports Common
198+
// B -> imports Common
199+
// Expected: Common loaded ONLY ONCE.
200+
201+
let (graph, ids, _dir) = setup_graph(vec![
202+
("main.simf", "use lib::A::foo; use lib::B::bar;"),
203+
("libs/lib/A.simf", "use lib::Common::dummy1;"),
204+
("libs/lib/B.simf", "use lib::Common::dummy2;"),
205+
("libs/lib/Common.simf", ""),
206+
]);
207+
208+
let order = graph.c3_linearize().expect("C3 failed");
209+
210+
// Verify order using IDs from the helper map
211+
let main_id = ids["main"];
212+
let a_id = ids["A"];
213+
let b_id = ids["B"];
214+
let common_id = ids["Common"];
215+
216+
// In the `resolve_complication_order` function, the order was reversed.
217+
// Therefore, `common` will be the first, and `main` will be last.
218+
assert_eq!(order, vec![main_id, a_id, b_id, common_id]);
219+
}
220+
221+
#[test]
222+
fn test_c3_detects_cycle() {
223+
let (graph, _, _dir) = setup_graph(vec![
224+
("main.simf", "use lib::A::entry;"),
225+
("libs/lib/A.simf", "use lib::B::func;"),
226+
("libs/lib/B.simf", "use lib::A::func;"),
227+
]);
228+
229+
let order = graph.c3_linearize();
230+
matches!(order, Err(C3Error::CycleDetected(_)));
231+
}
232+
}
233+
171234
#[cfg(test)]
172235
mod error_diganostic_and_terminal_formatting {
173236
use super::*;

0 commit comments

Comments
 (0)