1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
// Copyright (c) Facebook, Inc. and its affiliates
// SPDX-License-Identifier: MIT OR Apache-2.0
use serde_reflection::{ContainerFormat, Format, FormatHolder, Registry, Result};
use std::collections::{BTreeMap, BTreeSet, HashSet};
/// Compute dependencies while ignoring external names.
fn get_dependencies<'a>(
format: &'a ContainerFormat,
external: &BTreeSet<String>,
) -> Result<BTreeSet<&'a str>> {
let mut result = BTreeSet::new();
format.visit(&mut |format| {
if let Format::TypeName(x) = format {
if !external.contains(x) {
result.insert(x.as_str());
}
}
Ok(())
})?;
Ok(result)
}
/// Build a map of dependencies between the entries of a `Registry`.
/// * By definition, an entry named `x` depends on `y` iff the container format of `x` in the registry
/// syntactically contains a reference to `y` (i.e. an expression `Format::TypeName(y)`).
/// * Dependencies can play a role in code generation in some languages (e.g. Rust or C++) where inductive
/// definitions may require explicit "boxing" (i.e. adding pointer indirections) to ensure finite object sizes.
pub fn get_dependency_map(registry: &Registry) -> Result<BTreeMap<&str, BTreeSet<&str>>> {
get_dependency_map_with_external_dependencies(registry, &BTreeSet::new())
}
/// Same as get_dependency_map but allow to specify a set of externally-provided names to ignore.
pub fn get_dependency_map_with_external_dependencies<'a>(
registry: &'a Registry,
external: &BTreeSet<String>,
) -> Result<BTreeMap<&'a str, BTreeSet<&'a str>>> {
let mut children = BTreeMap::new();
for (name, format) in registry {
children.insert(name.as_str(), get_dependencies(format, external)?);
}
Ok(children)
}
/// Classic topological sorting algorithm except that it doesn't abort in case of cycles.
pub fn best_effort_topological_sort<T>(children: &BTreeMap<T, BTreeSet<T>>) -> Vec<T>
where
T: Clone + std::cmp::Ord + std::cmp::Eq + std::hash::Hash,
{
// Build the initial queue so that we pick up nodes with less children first (and otherwise
// those with smaller key first).
// This is a heuristic to break cycles preferably at large nodes (see comment below).
let mut queue: Vec<_> = children.keys().rev().cloned().collect();
queue.sort_by(|node1, node2| children[node1].len().cmp(&children[node2].len()));
let mut result = Vec::new();
// Nodes already inserted in result.
let mut sorted = HashSet::new();
// Nodes for which children have been enqueued.
let mut seen = HashSet::new();
while let Some(node) = queue.pop() {
if sorted.contains(&node) {
continue;
}
if seen.contains(&node) {
// Second time we see this node.
// * If `node` does not belong to a cycle in the graph, then by now, all its children
// have been sorted.
// * If `node` has children that depend back on it. We may be visiting `node` again
// before some of those children. Just insert `node` here. By ignoring edges going back
// to `node` now, we are effectively deciding to "break the cycle" there in future
// applications (e.g. `node` may be forward-declared in C++ and `Box`-ed in Rust).
sorted.insert(node.clone());
result.push(node);
continue;
}
// First time we see this node:
// 1. Mark it so that it is no longer enqueued.
seen.insert(node.clone());
// 2. Schedule all the (yet unseen) children then this node for a second visit.
// (If possible, visit children by increasing key.)
queue.push(node.clone());
for child in children[&node].iter().rev() {
if !seen.contains(child) {
queue.push(child.clone());
}
}
}
result
}