[DEV-7133] Check for cyclic dependencies
Merge branch 'jira-7133' * jira-7133: [DEV-7133] Clearly show the cycles in the output [DEV-7133] Check for cyclic dependencies [DEV-7133] Remove dependency from "lv:function/lv:param" [DEV-7133] Add AsgError::Cyclemaster
commit
03fa2ffc0b
|
@ -332,9 +332,7 @@
|
|||
<variable name="fname" as="xs:string"
|
||||
select="parent::lv:function/@name" />
|
||||
|
||||
<preproc:sym-dep name=":{$fname}:{@name}">
|
||||
<preproc:sym-ref name="{$fname}" />
|
||||
</preproc:sym-dep>
|
||||
<preproc:sym-dep name=":{$fname}:{@name}" />
|
||||
</template>
|
||||
|
||||
<template mode="preproc:depgen" priority="7"
|
||||
|
|
|
@ -45,7 +45,7 @@ AC_CHECK_PROGS(CARGO, [cargo])
|
|||
|
||||
test -n "$CARGO" || AC_MSG_ERROR([cargo not found])
|
||||
|
||||
rustc_ver_req=1.41.0
|
||||
rustc_ver_req=1.42.0
|
||||
|
||||
AC_CHECK_PROGS(RUSTC, [rustc])
|
||||
AC_MSG_CHECKING([rustc version >= $rustc_ver_req])
|
||||
|
|
|
@ -64,7 +64,7 @@ where
|
|||
impl<'i, O, Ix> BaseAsg<O, Ix>
|
||||
where
|
||||
Ix: IndexType,
|
||||
O: IdentObjectState<'i, O>,
|
||||
O: IdentObjectState<'i, O> + IdentObjectData<'i>,
|
||||
{
|
||||
/// Create an ASG with the provided initial capacity.
|
||||
///
|
||||
|
@ -138,19 +138,69 @@ where
|
|||
ObjectRef(index)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check graph for cycles
|
||||
///
|
||||
/// We want to catch any cycles before we start using the graph.
|
||||
/// Unfortunately, we need to allow cycles for our [`IdentKind::Func`]
|
||||
/// so we cannot use the typical algorithms in a straightforward manner.
|
||||
///
|
||||
/// We loop through all SCCs and check that they are not all functions. If
|
||||
/// they are, we ignore the cycle, otherwise we will return an error.
|
||||
fn check_cycles(&self) -> AsgResult<(), Ix> {
|
||||
// While `tarjan_scc` does do a topological sort, it does not suit our
|
||||
// needs because we need to filter out some allowed cycles. It would
|
||||
// still be possible to use this, but we also need to only check nodes
|
||||
// that are attached to our "roots". We are doing our own sort and as of
|
||||
// the initial writing, this does not have a significant performance
|
||||
// impact.
|
||||
let sccs = petgraph::algo::tarjan_scc(&self.graph);
|
||||
|
||||
let cycles: Vec<_> = sccs
|
||||
.into_iter()
|
||||
.filter_map(|scc| {
|
||||
// For single-node SCCs, we just need to make sure they are
|
||||
// not neighbors with themselves.
|
||||
if scc.len() == 1
|
||||
&& !self.graph.neighbors(scc[0]).any(|nx| nx == scc[0])
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_all_funcs = scc.iter().all(|nx| {
|
||||
let ident = self.get(*nx).expect("missing node");
|
||||
matches!(ident.kind(), Some(IdentKind::Func(_, _)))
|
||||
});
|
||||
|
||||
if is_all_funcs {
|
||||
None
|
||||
} else {
|
||||
let cycles =
|
||||
scc.iter().map(|nx| ObjectRef::from(*nx)).collect();
|
||||
Some(cycles)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if cycles.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(AsgError::Cycles(cycles))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, O, Ix> Asg<'i, O, Ix> for BaseAsg<O, Ix>
|
||||
where
|
||||
Ix: IndexType,
|
||||
O: IdentObjectState<'i, O>,
|
||||
O: IdentObjectState<'i, O> + IdentObjectData<'i>,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
name: &'i Symbol<'i>,
|
||||
kind: IdentKind,
|
||||
src: Source<'i>,
|
||||
) -> AsgResult<ObjectRef<Ix>> {
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix> {
|
||||
if let Some(existing) = self.lookup(name) {
|
||||
let node = self.graph.node_weight_mut(existing.0).unwrap();
|
||||
|
||||
|
@ -183,7 +233,7 @@ where
|
|||
&mut self,
|
||||
name: &'i Symbol<'i>,
|
||||
expected_kind: IdentKind,
|
||||
) -> AsgResult<ObjectRef<Ix>> {
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix> {
|
||||
// TODO: resolution!
|
||||
let node = self.graph.add_node(Some(O::extern_(name, expected_kind)));
|
||||
|
||||
|
@ -196,7 +246,7 @@ where
|
|||
&mut self,
|
||||
identi: ObjectRef<Ix>,
|
||||
text: FragmentText,
|
||||
) -> AsgResult<ObjectRef<Ix>> {
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix> {
|
||||
// This should _never_ happen as long as you're only using ObjectRef
|
||||
// values produced by these methods.
|
||||
let node = self
|
||||
|
@ -269,12 +319,15 @@ where
|
|||
Ix: IndexType,
|
||||
O: IdentObjectData<'i> + IdentObjectState<'i, O>,
|
||||
{
|
||||
fn sort(&'i self, roots: &[ObjectRef<Ix>]) -> AsgResult<Sections<'i, O>> {
|
||||
fn sort(
|
||||
&'i self,
|
||||
roots: &[ObjectRef<Ix>],
|
||||
) -> AsgResult<Sections<'i, O>, Ix> {
|
||||
let mut deps = Sections::new();
|
||||
|
||||
// This is technically a topological sort, but functions have
|
||||
// cycles. Once we have more symbol metadata, we can filter them out
|
||||
// and actually invoke toposort.
|
||||
self.check_cycles()?;
|
||||
|
||||
// This is technically a topological sort, but functions have cycles.
|
||||
let mut dfs = DfsPostOrder::empty(&self.graph);
|
||||
|
||||
for index in roots {
|
||||
|
@ -352,6 +405,7 @@ mod test {
|
|||
use super::super::graph::AsgError;
|
||||
use super::*;
|
||||
use crate::ir::asg::{Dim, IdentObject, TransitionError, TransitionResult};
|
||||
use crate::ir::legacyir::SymDtype;
|
||||
use crate::sym::SymbolIndex;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
@ -461,7 +515,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn declare_new_unique_idents() -> AsgResult<()> {
|
||||
fn declare_new_unique_idents() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
// NB: The index ordering is important! We first use a larger
|
||||
|
@ -519,7 +573,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_by_symbol() -> AsgResult<()> {
|
||||
fn lookup_by_symbol() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "lookup");
|
||||
|
@ -538,7 +592,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn declare_extern() -> AsgResult<()> {
|
||||
fn declare_extern() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "extern");
|
||||
|
@ -553,7 +607,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn declare_returns_existing() -> AsgResult<()> {
|
||||
fn declare_returns_existing() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "symdup");
|
||||
|
@ -582,7 +636,7 @@ mod test {
|
|||
|
||||
// Builds upon declare_returns_existing.
|
||||
#[test]
|
||||
fn declare_fails_if_transition_fails() -> AsgResult<()> {
|
||||
fn declare_fails_if_transition_fails() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "symdup");
|
||||
|
@ -615,7 +669,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn add_fragment_to_ident() -> AsgResult<()> {
|
||||
fn add_fragment_to_ident() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "tofrag");
|
||||
|
@ -646,7 +700,7 @@ mod test {
|
|||
// TODO: fragment fail
|
||||
|
||||
#[test]
|
||||
fn add_ident_dep_to_ident() -> AsgResult<()> {
|
||||
fn add_ident_dep_to_ident() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -667,7 +721,7 @@ mod test {
|
|||
|
||||
// same as above test
|
||||
#[test]
|
||||
fn add_dep_lookup_existing() -> AsgResult<()> {
|
||||
fn add_dep_lookup_existing() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -683,7 +737,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn add_dep_lookup_missing() -> AsgResult<()> {
|
||||
fn add_dep_lookup_missing() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -700,7 +754,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn declare_return_missing_symbol() -> AsgResult<()> {
|
||||
fn declare_return_missing_symbol() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -757,7 +811,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort() -> AsgResult<()> {
|
||||
fn graph_sort() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let mut meta = vec![];
|
||||
|
@ -801,7 +855,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_missing_node() -> AsgResult<()> {
|
||||
fn graph_sort_missing_node() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -832,7 +886,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_no_roots() -> AsgResult<()> {
|
||||
fn graph_sort_no_roots() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = symbol_dummy!(1, "sym");
|
||||
|
@ -846,4 +900,477 @@ mod test {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_simple_cycle() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
|
||||
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
|
||||
|
||||
let sym_node = sut.declare(
|
||||
&sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep_node = sut.declare(
|
||||
&dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
let (_, _) = sut.add_dep_lookup(&dep, &sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> =
|
||||
vec![vec![dep_node.into(), sym_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_two_simple_cycles() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
|
||||
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym2");
|
||||
let dep = Symbol::new_dummy(SymbolIndex::from_u32(4), "dep");
|
||||
let dep2 = Symbol::new_dummy(SymbolIndex::from_u32(5), "dep2");
|
||||
|
||||
let sym_node = sut.declare(
|
||||
&sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym2_node = sut.declare(
|
||||
&sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep_node = sut.declare(
|
||||
&dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep2_node = sut.declare(
|
||||
&dep2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(dep_node, FragmentText::from("baz"))?;
|
||||
sut.set_fragment(dep2_node, FragmentText::from("huh"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
let (_, _) = sut.add_dep_lookup(&dep, &sym);
|
||||
let (_, _) = sut.add_dep_lookup(&sym2, &dep2);
|
||||
let (_, _) = sut.add_dep_lookup(&dep2, &sym2);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> = vec![
|
||||
vec![dep_node.into(), sym_node.into()],
|
||||
vec![dep2_node.into(), sym2_node.into()],
|
||||
];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_no_cycle_with_edge_to_same_node() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
|
||||
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
|
||||
|
||||
let sym_node = sut.declare(
|
||||
&sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep_node = sut.declare(
|
||||
&dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cycle_with_a_few_steps() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
|
||||
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
|
||||
let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
|
||||
|
||||
let sym1_node = sut.declare(
|
||||
&sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym2_node = sut.declare(
|
||||
&sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym3_node = sut.declare(
|
||||
&sym3,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
|
||||
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
|
||||
let (_, _) = sut.add_dep_lookup(&sym3, &sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
|
||||
) -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
|
||||
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
|
||||
let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
|
||||
|
||||
let sym1_node = sut.declare(
|
||||
&sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym2_node = sut.declare(
|
||||
&sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym3_node = sut.declare(
|
||||
&sym3,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
|
||||
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
|
||||
let (_, _) = sut.add_dep_lookup(&sym3, &sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_bookended_by_functions() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
|
||||
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
|
||||
let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
|
||||
|
||||
let sym1_node = sut.declare(
|
||||
&sym1,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym2_node = sut.declare(
|
||||
&sym2,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym3_node = sut.declare(
|
||||
&sym3,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
|
||||
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
|
||||
let (_, _) = sut.add_dep_lookup(&sym3, &sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_ignored() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
|
||||
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
|
||||
|
||||
let sym_node = sut.declare(
|
||||
&sym,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep_node = sut.declare(
|
||||
&dep,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
let (_, _) = sut.add_dep_lookup(&dep, &sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_cyclic_function_is_bookended() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
|
||||
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
|
||||
let sym3 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym3");
|
||||
|
||||
let sym1_node = sut.declare(
|
||||
&sym1,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym2_node = sut.declare(
|
||||
&sym2,
|
||||
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let sym3_node = sut.declare(
|
||||
&sym3,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym1_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(sym2_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(sym3_node, FragmentText::from("baz"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym1, &sym2);
|
||||
let (_, _) = sut.add_dep_lookup(&sym2, &sym3);
|
||||
let (_, _) = sut.add_dep_lookup(&sym3, &sym1);
|
||||
|
||||
let result = sut.sort(&vec![sym1_node]);
|
||||
|
||||
let expected: Vec<Vec<ObjectRef<u8>>> =
|
||||
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||||
match result {
|
||||
Ok(_) => panic!("sort did not detect cycle"),
|
||||
Err(AsgError::Cycles(scc)) => assert_eq!(expected, scc),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_sort_ignore_non_linked() -> AsgResult<(), u8> {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
|
||||
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
|
||||
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
|
||||
let ignored = Symbol::new_dummy(SymbolIndex::from_u32(4), "ignored");
|
||||
|
||||
let sym_node = sut.declare(
|
||||
&sym,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let dep_node = sut.declare(
|
||||
&dep,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let ignored_node = sut.declare(
|
||||
&ignored,
|
||||
IdentKind::Tpl,
|
||||
Source {
|
||||
virtual_: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
sut.set_fragment(sym_node, FragmentText::from("foo"))?;
|
||||
sut.set_fragment(dep_node, FragmentText::from("bar"))?;
|
||||
sut.set_fragment(ignored_node, FragmentText::from("baz"))?;
|
||||
|
||||
let (_, _) = sut.add_dep_lookup(&sym, &dep);
|
||||
let (_, _) = sut.add_dep_lookup(&ignored, &sym);
|
||||
|
||||
let result = sut.sort(&vec![sym_node]);
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("unexpected error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ use super::object::{
|
|||
use super::Sections;
|
||||
use crate::sym::Symbol;
|
||||
use petgraph::graph::{IndexType, NodeIndex};
|
||||
use std::fmt::Debug;
|
||||
use std::result::Result;
|
||||
|
||||
/// An abstract semantic graph of [objects][super::object].
|
||||
|
@ -72,7 +73,7 @@ where
|
|||
name: &'i Symbol<'i>,
|
||||
kind: IdentKind,
|
||||
src: Source<'i>,
|
||||
) -> AsgResult<ObjectRef<Ix>>;
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix>;
|
||||
|
||||
/// Declare an abstract identifier.
|
||||
///
|
||||
|
@ -98,7 +99,7 @@ where
|
|||
&mut self,
|
||||
name: &'i Symbol<'i>,
|
||||
expected_kind: IdentKind,
|
||||
) -> AsgResult<ObjectRef<Ix>>;
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix>;
|
||||
|
||||
/// Set the fragment associated with a concrete identifier.
|
||||
///
|
||||
|
@ -109,7 +110,7 @@ where
|
|||
&mut self,
|
||||
identi: ObjectRef<Ix>,
|
||||
text: FragmentText,
|
||||
) -> AsgResult<ObjectRef<Ix>>;
|
||||
) -> AsgResult<ObjectRef<Ix>, Ix>;
|
||||
|
||||
/// Retrieve an object from the graph by [`ObjectRef`].
|
||||
///
|
||||
|
@ -171,14 +172,17 @@ where
|
|||
O: IdentObjectData<'i>,
|
||||
Ix: IndexType,
|
||||
{
|
||||
fn sort(&'i self, roots: &[ObjectRef<Ix>]) -> AsgResult<Sections<'i, O>>;
|
||||
fn sort(
|
||||
&'i self,
|
||||
roots: &[ObjectRef<Ix>],
|
||||
) -> AsgResult<Sections<'i, O>, Ix>;
|
||||
}
|
||||
|
||||
/// A [`Result`] with a hard-coded [`AsgError`] error type.
|
||||
///
|
||||
/// This is the result of every [`Asg`] operation that could potentially
|
||||
/// fail in error.
|
||||
pub type AsgResult<T> = Result<T, AsgError>;
|
||||
pub type AsgResult<T, Ix> = Result<T, AsgError<Ix>>;
|
||||
|
||||
/// Reference to an [object][super::object] stored within the [`Asg`].
|
||||
///
|
||||
|
@ -222,7 +226,7 @@ pub type Node<O> = Option<O>;
|
|||
/// so this stores only owned values.
|
||||
/// The caller will know the problem values.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AsgError {
|
||||
pub enum AsgError<Ix: Debug> {
|
||||
/// The provided identifier is not in a state that is permitted to
|
||||
/// receive a fragment.
|
||||
///
|
||||
|
@ -238,9 +242,12 @@ pub enum AsgError {
|
|||
|
||||
/// The node was not expected in the current context
|
||||
UnexpectedNode(String),
|
||||
|
||||
/// The graph has a cyclic dependency
|
||||
Cycles(Vec<Vec<ObjectRef<Ix>>>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AsgError {
|
||||
impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::BadFragmentDest(msg) => {
|
||||
|
@ -252,17 +259,20 @@ impl std::fmt::Display for AsgError {
|
|||
Self::UnexpectedNode(msg) => {
|
||||
write!(fmt, "unexpected node: {}", msg)
|
||||
}
|
||||
Self::Cycles(cycles) => {
|
||||
write!(fmt, "Cyclic dependencies detected: {:?}", cycles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AsgError {
|
||||
impl<Ix: Debug> std::error::Error for AsgError<Ix> {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransitionError> for AsgError {
|
||||
impl<Ix: Debug> From<TransitionError> for AsgError<Ix> {
|
||||
fn from(e: TransitionError) -> Self {
|
||||
match e {
|
||||
TransitionError::Incompatible(msg) => Self::IncompatibleIdent(msg),
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
|
||||
use crate::global;
|
||||
use crate::ir::asg::{
|
||||
Asg, DefaultAsg, IdentKind, IdentObject, ObjectRef, Sections, SortableAsg,
|
||||
Source,
|
||||
Asg, AsgError, DefaultAsg, IdentKind, IdentObject, IdentObjectData,
|
||||
ObjectRef, Sections, SortableAsg, Source,
|
||||
};
|
||||
use crate::obj::xmle::writer::XmleWriter;
|
||||
use crate::obj::xmlo::reader::{XmloError, XmloEvent, XmloReader};
|
||||
|
@ -78,7 +78,32 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
|
|||
.filter_map(|sym| depgraph.lookup(sym)),
|
||||
);
|
||||
|
||||
let mut sorted = depgraph.sort(&roots)?;
|
||||
let mut sorted = match depgraph.sort(&roots) {
|
||||
Ok(sections) => sections,
|
||||
Err(AsgError::Cycles(cycles)) => {
|
||||
let msg: Vec<String> = cycles
|
||||
.into_iter()
|
||||
.map(|cycle| {
|
||||
let mut path: Vec<String> = cycle
|
||||
.into_iter()
|
||||
.map(|obj| {
|
||||
format!(
|
||||
"{}",
|
||||
depgraph.get(obj).unwrap().name().unwrap()
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
path.reverse();
|
||||
path.push(path[0].clone());
|
||||
format!("cycle: {}", path.join(" -> "))
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Err(msg.join("\n").into());
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
//println!("Sorted ({}): {:?}", sorted.len(), sorted);
|
||||
|
||||
|
|
Loading…
Reference in New Issue