// 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 .
//! 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 = Result>;
/// 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,
roots: &[ObjectRef],
) -> SortResult, Ix>
where
Ix: IndexType,
O: IdentObjectData + IdentObjectState,
{
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("".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(asg: &BaseAsg) -> SortResult<(), Ix>
where
Ix: IndexType,
O: IdentObjectData + IdentObjectState,
{
// 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 {
/// 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>>),
}
impl From for SortError {
fn from(err: UnresolvedError) -> Self {
Self::UnresolvedObject(err)
}
}
impl std::fmt::Display for SortError {
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 std::error::Error for SortError {
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,
given_extern: Option<(IdentKind, Source)>,
given_resolve: Option<(IdentKind, Source)>,
given_set_fragment: Option,
fail_resolved: RefCell