1002 lines
29 KiB
Rust
1002 lines
29 KiB
Rust
|
// ASG lowering into xmle sections
|
||
|
//
|
||
|
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
|
||
|
//
|
||
|
// This file is part of TAME.
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
//! Lowering of the [ASG](crate::ir::asg) into `xmle` [`Sections`].
|
||
|
//!
|
||
|
//! See the [parent module](super) for more information.
|
||
|
|
||
|
use super::Sections;
|
||
|
use crate::{
|
||
|
ir::asg::{
|
||
|
Asg, BaseAsg, IdentKind, IdentObjectData, IdentObjectState, IndexType,
|
||
|
ObjectRef, UnresolvedError,
|
||
|
},
|
||
|
sym::GlobalSymbolResolve,
|
||
|
};
|
||
|
use petgraph::visit::DfsPostOrder;
|
||
|
|
||
|
// Result of [`sort`].
|
||
|
pub type SortResult<T, Ix> = Result<T, SortError<Ix>>;
|
||
|
|
||
|
/// Lower ASG into [`Sections`] by sorting relocatable text fragments.
|
||
|
///
|
||
|
/// This performs the equivalent of a topological sort,
|
||
|
/// although function cycles are permitted.
|
||
|
/// The actual operation performed is a post-order depth-first traversal.
|
||
|
pub fn sort<'a, O, Ix>(
|
||
|
asg: &'a BaseAsg<O, Ix>,
|
||
|
roots: &[ObjectRef<Ix>],
|
||
|
) -> SortResult<Sections<'a, O>, Ix>
|
||
|
where
|
||
|
Ix: IndexType,
|
||
|
O: IdentObjectData + IdentObjectState<O>,
|
||
|
{
|
||
|
let mut deps = Sections::new();
|
||
|
|
||
|
// TODO: we should check for cycles as we sort (as the POC did).
|
||
|
check_cycles(asg)?;
|
||
|
|
||
|
// This is technically a topological sort, but functions have cycles.
|
||
|
let mut dfs = DfsPostOrder::empty(&asg.graph);
|
||
|
|
||
|
for index in roots {
|
||
|
dfs.stack.push((*index).into());
|
||
|
}
|
||
|
|
||
|
while let Some(index) = dfs.next(&asg.graph) {
|
||
|
let ident = asg.get(index).expect("missing node").resolved()?;
|
||
|
|
||
|
match ident.kind() {
|
||
|
Some(kind) => match kind {
|
||
|
IdentKind::Meta
|
||
|
| IdentKind::Worksheet
|
||
|
| IdentKind::Param(_, _)
|
||
|
| IdentKind::Type(_)
|
||
|
| IdentKind::Func(_, _)
|
||
|
| IdentKind::Const(_, _) => deps.st.push_body(ident),
|
||
|
IdentKind::MapHead | IdentKind::Map | IdentKind::MapTail => {
|
||
|
deps.map.push_body(ident)
|
||
|
}
|
||
|
IdentKind::RetMapHead
|
||
|
| IdentKind::RetMap
|
||
|
| IdentKind::RetMapTail => deps.retmap.push_body(ident),
|
||
|
_ => deps.rater.push_body(ident),
|
||
|
},
|
||
|
None => {
|
||
|
return Err(SortError::MissingObjectKind(
|
||
|
ident
|
||
|
.name()
|
||
|
.map(|name| format!("{}", name.lookup_str()))
|
||
|
.unwrap_or("<unknown>".into())
|
||
|
.into(),
|
||
|
))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(deps)
|
||
|
}
|
||
|
|
||
|
/// 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<O, Ix>(asg: &BaseAsg<O, Ix>) -> SortResult<(), Ix>
|
||
|
where
|
||
|
Ix: IndexType,
|
||
|
O: IdentObjectData + IdentObjectState<O>,
|
||
|
{
|
||
|
// 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(&asg.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
|
||
|
&& !asg.graph.neighbors(scc[0]).any(|nx| nx == scc[0])
|
||
|
{
|
||
|
return None;
|
||
|
}
|
||
|
|
||
|
let is_all_funcs = scc.iter().all(|nx| {
|
||
|
let ident = Asg::get(asg, *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(SortError::Cycles(cycles))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Error during graph sorting.
|
||
|
///
|
||
|
/// These errors reflect barriers to meaningfully understanding the
|
||
|
/// properties of the data in the graph with respect to sorting.
|
||
|
/// It does not represent bad underlying data that does not affect the
|
||
|
/// sorting process.
|
||
|
#[derive(Debug, PartialEq)]
|
||
|
pub enum SortError<Ix: IndexType> {
|
||
|
/// An unresolved object was encountered during sorting.
|
||
|
///
|
||
|
/// An unresolved object means that the graph has an incomplete picture
|
||
|
/// of the program,
|
||
|
/// and so sorting cannot be reliably performed.
|
||
|
/// Since all objects are supposed to be resolved prior to sorting,
|
||
|
/// this represents either a problem with the program being compiled
|
||
|
/// or a bug in the compiler itself.
|
||
|
UnresolvedObject(UnresolvedError),
|
||
|
|
||
|
/// The kind of an object encountered during sorting could not be
|
||
|
/// determined.
|
||
|
///
|
||
|
/// Sorting uses the object kind to place objects into their appropriate
|
||
|
/// sections.
|
||
|
/// It should never be the case that a resolved object has no kind,
|
||
|
/// so this likely represents a compiler bug.
|
||
|
MissingObjectKind(String),
|
||
|
|
||
|
/// The graph has a cyclic dependency.
|
||
|
Cycles(Vec<Vec<ObjectRef<Ix>>>),
|
||
|
}
|
||
|
|
||
|
impl<Ix: IndexType> From<UnresolvedError> for SortError<Ix> {
|
||
|
fn from(err: UnresolvedError) -> Self {
|
||
|
Self::UnresolvedObject(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<Ix: IndexType> std::fmt::Display for SortError<Ix> {
|
||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||
|
match self {
|
||
|
Self::UnresolvedObject(err) => std::fmt::Display::fmt(err, fmt),
|
||
|
Self::MissingObjectKind(name) => write!(
|
||
|
fmt,
|
||
|
"internal error: missing object kind for object `{}` (this may be a compiler bug!)",
|
||
|
name,
|
||
|
),
|
||
|
Self::Cycles(_) => write!(fmt, "cyclic dependencies"),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<Ix: IndexType> std::error::Error for SortError<Ix> {
|
||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||
|
match self {
|
||
|
Self::UnresolvedObject(err) => Some(err),
|
||
|
_ => None,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod test {
|
||
|
use std::cell::RefCell;
|
||
|
|
||
|
use super::*;
|
||
|
use crate::{
|
||
|
ir::{
|
||
|
asg::{
|
||
|
DataType, Dim, FragmentText, IdentObject, Source,
|
||
|
TransitionResult,
|
||
|
},
|
||
|
legacyir::SymDtype,
|
||
|
},
|
||
|
sym::{GlobalSymbolIntern, SymbolId},
|
||
|
};
|
||
|
|
||
|
#[derive(Debug, Default, PartialEq)]
|
||
|
struct StubIdentObject {
|
||
|
given_declare: Option<SymbolId>,
|
||
|
given_extern: Option<(IdentKind, Source)>,
|
||
|
given_resolve: Option<(IdentKind, Source)>,
|
||
|
given_set_fragment: Option<FragmentText>,
|
||
|
fail_resolved: RefCell<Option<UnresolvedError>>,
|
||
|
}
|
||
|
|
||
|
impl<'i> IdentObjectData for StubIdentObject {
|
||
|
fn name(&self) -> Option<SymbolId> {
|
||
|
self.given_declare
|
||
|
}
|
||
|
|
||
|
fn kind(&self) -> Option<&IdentKind> {
|
||
|
self.given_resolve.as_ref().map(|args| &args.0)
|
||
|
}
|
||
|
|
||
|
fn src(&self) -> Option<&Source> {
|
||
|
None
|
||
|
}
|
||
|
|
||
|
fn fragment(&self) -> Option<&FragmentText> {
|
||
|
None
|
||
|
}
|
||
|
|
||
|
fn as_ident(&self) -> Option<&IdentObject> {
|
||
|
None
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'i> IdentObjectState<StubIdentObject> for StubIdentObject {
|
||
|
fn declare(ident: SymbolId) -> Self {
|
||
|
Self {
|
||
|
given_declare: Some(ident),
|
||
|
..Default::default()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn resolve(
|
||
|
mut self,
|
||
|
kind: IdentKind,
|
||
|
src: Source,
|
||
|
) -> TransitionResult<StubIdentObject> {
|
||
|
self.given_resolve = Some((kind, src));
|
||
|
Ok(self)
|
||
|
}
|
||
|
|
||
|
fn resolved(&self) -> Result<&StubIdentObject, UnresolvedError> {
|
||
|
if self.fail_resolved.borrow().is_some() {
|
||
|
return Err(self.fail_resolved.replace(None).unwrap());
|
||
|
}
|
||
|
|
||
|
Ok(self)
|
||
|
}
|
||
|
|
||
|
fn extern_(
|
||
|
mut self,
|
||
|
kind: IdentKind,
|
||
|
src: Source,
|
||
|
) -> TransitionResult<StubIdentObject> {
|
||
|
self.given_extern = Some((kind, src));
|
||
|
Ok(self)
|
||
|
}
|
||
|
|
||
|
fn set_fragment(
|
||
|
mut self,
|
||
|
text: FragmentText,
|
||
|
) -> TransitionResult<StubIdentObject> {
|
||
|
self.given_set_fragment.replace(text);
|
||
|
Ok(self)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type Sut = BaseAsg<StubIdentObject, u16>;
|
||
|
|
||
|
macro_rules! assert_section_sym {
|
||
|
( $iterable:expr, $s:ident ) => {{
|
||
|
let mut pos = 0;
|
||
|
|
||
|
for obj in $iterable.iter() {
|
||
|
let sym = obj.name().expect("missing object");
|
||
|
assert_eq!($s.get(pos).map(|s| *s), Some(sym));
|
||
|
|
||
|
pos = pos + 1;
|
||
|
}
|
||
|
|
||
|
assert_eq!(pos, $s.len());
|
||
|
};};
|
||
|
}
|
||
|
|
||
|
macro_rules! add_syms {
|
||
|
($sut:ident, $base:expr, {$($dest:ident <- $name:ident: $kind:expr,)*}) => {
|
||
|
$(
|
||
|
let sym = stringify!($name).intern();
|
||
|
|
||
|
$sut.declare(sym, $kind, Source::default()).unwrap();
|
||
|
let (_, _) = $sut.add_dep_lookup($base, sym);
|
||
|
|
||
|
$dest.push(sym);
|
||
|
)*
|
||
|
};
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let mut st = vec![];
|
||
|
let mut map = vec![];
|
||
|
let mut retmap = vec![];
|
||
|
|
||
|
let base = "sym1".intern();
|
||
|
let base_node = sut
|
||
|
.declare(base, IdentKind::Map, Source::default())
|
||
|
.unwrap();
|
||
|
|
||
|
add_syms!(sut, base, {
|
||
|
st <- meta1: IdentKind::Meta,
|
||
|
st <- work1: IdentKind::Worksheet,
|
||
|
st <- const1: IdentKind::Const(Dim::from_u8(0), DataType::Float),
|
||
|
map <- map1: IdentKind::MapHead,
|
||
|
map <- map2: IdentKind::Map,
|
||
|
map <- map3: IdentKind::MapTail,
|
||
|
retmap <- retmap1: IdentKind::RetMapHead,
|
||
|
retmap <- retmap2: IdentKind::RetMap,
|
||
|
retmap <- retmap3: IdentKind::RetMapTail,
|
||
|
retmap <- retmap4: IdentKind::RetMapTail,
|
||
|
retmap <- retmap5: IdentKind::RetMap,
|
||
|
map <- map4: IdentKind::MapHead,
|
||
|
map <- map5: IdentKind::Map,
|
||
|
st <- meta2: IdentKind::Meta,
|
||
|
st <- work2: IdentKind::Worksheet,
|
||
|
map <- map6: IdentKind::MapTail,
|
||
|
retmap <- retmap6: IdentKind::RetMapHead,
|
||
|
st <- const2: IdentKind::Const(Dim::from_u8(2), DataType::Float),
|
||
|
});
|
||
|
|
||
|
map.push(base);
|
||
|
|
||
|
let sections = sort(&sut, &vec![base_node])?;
|
||
|
|
||
|
assert_section_sym!(sections.st, st);
|
||
|
assert_section_sym!(sections.map, map);
|
||
|
assert_section_sym!(sections.retmap, retmap);
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_missing_node() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
|
||
|
match sort(&sut, &vec![sym_node]) {
|
||
|
Ok(_) => panic!("Unexpected success - dependency is not in graph"),
|
||
|
Err(SortError::MissingObjectKind(_)) => (),
|
||
|
_ => {
|
||
|
panic!("Incorrect error result when dependency is not in graph")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_no_roots() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
|
||
|
let sections = sort(&sut, &vec![])?;
|
||
|
|
||
|
assert_eq!(Sections::new(), sections);
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_simple_cycle() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep_node = sut
|
||
|
.declare(
|
||
|
dep,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
let (_, _) = sut.add_dep_lookup(dep, sym);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||
|
vec![vec![dep_node.into(), sym_node.into()]];
|
||
|
match result {
|
||
|
Ok(_) => panic!("sort did not detect cycle"),
|
||
|
Err(SortError::Cycles(scc)) => assert_eq!(expected, scc),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_two_simple_cycles() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let sym2 = "sym2".intern();
|
||
|
let dep = "dep".intern();
|
||
|
let dep2 = "dep2".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym2_node = sut
|
||
|
.declare(
|
||
|
sym2,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep_node = sut
|
||
|
.declare(
|
||
|
dep,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep2_node = sut
|
||
|
.declare(
|
||
|
dep2,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep2_node, FragmentText::from("huh"))
|
||
|
.unwrap();
|
||
|
|
||
|
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 = sort(&sut, &vec![sym_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> = 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(SortError::Cycles(scc)) => assert_eq!(expected, scc),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep_node = sut
|
||
|
.declare(
|
||
|
dep,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym_node]);
|
||
|
|
||
|
match result {
|
||
|
Ok(_) => (),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_cycle_with_a_few_steps() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym1 = "sym1".intern();
|
||
|
let sym2 = "sym2".intern();
|
||
|
let sym3 = "sym3".intern();
|
||
|
|
||
|
let sym1_node = sut
|
||
|
.declare(
|
||
|
sym1,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym2_node = sut
|
||
|
.declare(
|
||
|
sym2,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym3_node = sut
|
||
|
.declare(
|
||
|
sym3,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||
|
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||
|
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym1_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||
|
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||
|
match result {
|
||
|
Ok(_) => panic!("sort did not detect cycle"),
|
||
|
Err(SortError::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(
|
||
|
) -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym1 = "sym1".intern();
|
||
|
let sym2 = "sym2".intern();
|
||
|
let sym3 = "sym3".intern();
|
||
|
|
||
|
let sym1_node = sut
|
||
|
.declare(
|
||
|
sym1,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym2_node = sut
|
||
|
.declare(
|
||
|
sym2,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym3_node = sut
|
||
|
.declare(
|
||
|
sym3,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||
|
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||
|
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym1_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||
|
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||
|
match result {
|
||
|
Ok(_) => panic!("sort did not detect cycle"),
|
||
|
Err(SortError::Cycles(scc)) => assert_eq!(expected, scc),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_cyclic_bookended_by_functions() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym1 = "sym1".intern();
|
||
|
let sym2 = "sym2".intern();
|
||
|
let sym3 = "sym3".intern();
|
||
|
|
||
|
let sym1_node = sut
|
||
|
.declare(
|
||
|
sym1,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym2_node = sut
|
||
|
.declare(
|
||
|
sym2,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym3_node = sut
|
||
|
.declare(
|
||
|
sym3,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||
|
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||
|
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym1_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||
|
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||
|
match result {
|
||
|
Ok(_) => panic!("sort did not detect cycle"),
|
||
|
Err(SortError::Cycles(scc)) => assert_eq!(expected, scc),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_cyclic_function_ignored() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep_node = sut
|
||
|
.declare(
|
||
|
dep,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
let (_, _) = sut.add_dep_lookup(dep, sym);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym_node]);
|
||
|
|
||
|
match result {
|
||
|
Ok(_) => (),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_cyclic_function_is_bookended() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym1 = "sym1".intern();
|
||
|
let sym2 = "sym2".intern();
|
||
|
let sym3 = "sym3".intern();
|
||
|
|
||
|
let sym1_node = sut
|
||
|
.declare(
|
||
|
sym1,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym2_node = sut
|
||
|
.declare(
|
||
|
sym2,
|
||
|
IdentKind::Func(Dim::default(), SymDtype::Empty),
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let sym3_node = sut
|
||
|
.declare(
|
||
|
sym3,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym1_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym2_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(sym3_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym1, sym2);
|
||
|
let (_, _) = sut.add_dep_lookup(sym2, sym3);
|
||
|
let (_, _) = sut.add_dep_lookup(sym3, sym1);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym1_node]);
|
||
|
|
||
|
let expected: Vec<Vec<ObjectRef<u16>>> =
|
||
|
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
|
||
|
match result {
|
||
|
Ok(_) => panic!("sort did not detect cycle"),
|
||
|
Err(SortError::Cycles(scc)) => assert_eq!(expected, scc),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn graph_sort_ignore_non_linked() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "sym".intern();
|
||
|
let dep = "dep".intern();
|
||
|
let ignored = "ignored".intern();
|
||
|
|
||
|
let sym_node = sut
|
||
|
.declare(
|
||
|
sym,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let dep_node = sut
|
||
|
.declare(
|
||
|
dep,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
let ignored_node = sut
|
||
|
.declare(
|
||
|
ignored,
|
||
|
IdentKind::Tpl,
|
||
|
Source {
|
||
|
virtual_: true,
|
||
|
..Default::default()
|
||
|
},
|
||
|
)
|
||
|
.unwrap();
|
||
|
|
||
|
sut.set_fragment(sym_node, FragmentText::from("foo"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(dep_node, FragmentText::from("bar"))
|
||
|
.unwrap();
|
||
|
sut.set_fragment(ignored_node, FragmentText::from("baz"))
|
||
|
.unwrap();
|
||
|
|
||
|
let (_, _) = sut.add_dep_lookup(sym, dep);
|
||
|
let (_, _) = sut.add_dep_lookup(ignored, sym);
|
||
|
|
||
|
let result = sort(&sut, &vec![sym_node]);
|
||
|
|
||
|
match result {
|
||
|
Ok(_) => (),
|
||
|
Err(e) => panic!("unexpected error: {}", e),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
/// A graph containing unresolved objects cannot be sorted.
|
||
|
#[test]
|
||
|
fn graph_sort_fail_unresolved() -> SortResult<(), u16> {
|
||
|
let mut sut = Sut::new();
|
||
|
|
||
|
let sym = "unresolved".intern();
|
||
|
let node = sut
|
||
|
.declare(sym, IdentKind::Meta, Default::default())
|
||
|
.unwrap();
|
||
|
let ident = sut.get(node).unwrap();
|
||
|
|
||
|
let expected = UnresolvedError::Missing { name: sym };
|
||
|
|
||
|
// Cause resolved() to fail.
|
||
|
ident.fail_resolved.replace(Some(expected.clone()));
|
||
|
|
||
|
let result = sort(&sut, &vec![node])
|
||
|
.expect_err("expected sort failure due to unresolved identifier");
|
||
|
|
||
|
match result {
|
||
|
SortError::UnresolvedObject(err) => {
|
||
|
assert_eq!(expected, err);
|
||
|
}
|
||
|
_ => panic!("expected SortError::Unresolved: {:?}", result),
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|