use std::collections::HashMap;
use std::collections::VecDeque;
#[cfg(feature = "copious-debugging")]
use std::fmt::Display;
use std::ops::Index;
use crate::arena::Arena;
use crate::arena::Handle;
use crate::arena::List;
use crate::arena::ListArena;
use crate::arena::ListCell;
use crate::arena::SupplementalArena;
use crate::cycles::CycleDetector;
use crate::graph::Node;
use crate::graph::StackGraph;
use crate::graph::Symbol;
use crate::partial::PartialPath;
use crate::partial::PartialPaths;
use crate::partial::PartialSymbolStack;
use crate::paths::Path;
use crate::paths::Paths;
use crate::paths::SymbolStack;
pub struct Database {
pub(crate) partial_paths: Arena<PartialPath>,
symbol_stack_keys: ListArena<Handle<Symbol>>,
symbol_stack_key_cache: HashMap<SymbolStackCacheKey, SymbolStackKeyHandle>,
paths_by_start_node: SupplementalArena<Node, Vec<Handle<PartialPath>>>,
root_paths_by_precondition: SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
}
impl Database {
pub fn new() -> Database {
Database {
partial_paths: Arena::new(),
symbol_stack_keys: List::new_arena(),
symbol_stack_key_cache: HashMap::new(),
paths_by_start_node: SupplementalArena::new(),
root_paths_by_precondition: SupplementalArena::new(),
}
}
pub fn add_partial_path(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: PartialPath,
) -> Handle<PartialPath> {
let start_node = path.start_node;
copious_debugging!(
" Add {} path to database {}",
if graph[start_node].is_root() {
"root"
} else {
"node"
},
path.display(graph, partials)
);
let symbol_stack_precondition = path.symbol_stack_precondition;
let handle = self.partial_paths.add(path);
if graph[start_node].is_root() {
let key = SymbolStackKey::from_partial_symbol_stack(
partials,
self,
symbol_stack_precondition,
);
let key_handle = key.back_handle();
self.root_paths_by_precondition[key_handle].push(handle);
} else {
self.paths_by_start_node[start_node].push(handle);
}
handle
}
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
pub fn find_candidate_partial_paths_from_root<R>(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
mut symbol_stack: SymbolStackKey,
result: &mut R,
) where
R: std::iter::Extend<Handle<PartialPath>>,
{
loop {
copious_debugging!(
" Search for symbol stack <{}>",
symbol_stack.display(graph, self)
);
let key_handle = symbol_stack.back_handle();
if let Some(paths) = self.root_paths_by_precondition.get(key_handle) {
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
if symbol_stack.pop_back(self).is_none() {
break;
}
}
}
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
pub fn find_candidate_partial_paths_from_node<R>(
&self,
graph: &StackGraph,
partials: &mut PartialPaths,
start_node: Handle<Node>,
result: &mut R,
) where
R: std::iter::Extend<Handle<PartialPath>>,
{
copious_debugging!(" Search for start node {}", start_node.display(graph));
if let Some(paths) = self.paths_by_start_node.get(start_node) {
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
}
}
impl Index<Handle<PartialPath>> for Database {
type Output = PartialPath;
#[inline(always)]
fn index(&self, handle: Handle<PartialPath>) -> &PartialPath {
self.partial_paths.get(handle)
}
}
#[derive(Clone, Copy)]
pub struct SymbolStackKey {
symbols: List<Handle<Symbol>>,
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct SymbolStackCacheKey {
head: Handle<Symbol>,
tail: SymbolStackKeyHandle,
}
type SymbolStackKeyCell = ListCell<Handle<Symbol>>;
type SymbolStackKeyHandle = Handle<SymbolStackKeyCell>;
impl SymbolStackKey {
fn empty() -> SymbolStackKey {
SymbolStackKey {
symbols: List::empty(),
}
}
fn push_back(&mut self, db: &mut Database, symbol: Handle<Symbol>) {
let cache_key = SymbolStackCacheKey {
head: symbol,
tail: self.back_handle(),
};
if let Some(handle) = db.symbol_stack_key_cache.get(&cache_key) {
self.symbols = List::from_handle(*handle);
return;
}
self.symbols.push_front(&mut db.symbol_stack_keys, symbol);
let handle = self.back_handle();
db.symbol_stack_key_cache.insert(cache_key, handle);
}
fn pop_back(&mut self, db: &Database) -> Option<Handle<Symbol>> {
self.symbols.pop_front(&db.symbol_stack_keys).copied()
}
pub fn from_symbol_stack(
paths: &mut Paths,
db: &mut Database,
mut stack: SymbolStack,
) -> SymbolStackKey {
let mut result = SymbolStackKey::empty();
while let Some(symbol) = stack.pop_front(paths) {
result.push_back(db, symbol.symbol);
}
result
}
pub fn from_partial_symbol_stack(
partials: &mut PartialPaths,
db: &mut Database,
mut stack: PartialSymbolStack,
) -> SymbolStackKey {
let mut result = SymbolStackKey::empty();
while let Some(symbol) = stack.pop_front(partials) {
result.push_back(db, symbol.symbol);
}
result
}
fn back_handle(self) -> SymbolStackKeyHandle {
self.symbols.handle()
}
#[cfg(feature = "copious-debugging")]
fn display<'a>(self, graph: &'a StackGraph, db: &'a Database) -> impl Display + 'a {
DisplaySymbolStackKey(self, graph, db)
}
}
#[cfg(feature = "copious-debugging")]
struct DisplaySymbolStackKey<'a>(SymbolStackKey, &'a StackGraph, &'a Database);
#[cfg(feature = "copious-debugging")]
impl<'a> Display for DisplaySymbolStackKey<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
fn display_one(
mut key: SymbolStackKey,
graph: &StackGraph,
db: &Database,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
let last = match key.pop_back(db) {
Some(last) => last,
None => return Ok(()),
};
display_one(key, graph, db, f)?;
last.display(graph).fmt(f)
}
display_one(self.0, self.1, self.2, f)
}
}
pub struct PathStitcher {
candidate_paths: Vec<Handle<PartialPath>>,
queue: VecDeque<Path>,
next_iteration: VecDeque<Path>,
cycle_detector: CycleDetector<Path>,
#[cfg(feature = "copious-debugging")]
phase_number: usize,
}
impl PathStitcher {
pub fn new<I>(
graph: &StackGraph,
paths: &mut Paths,
partials: &mut PartialPaths,
db: &mut Database,
starting_nodes: I,
) -> PathStitcher
where
I: IntoIterator<Item = Handle<Node>>,
{
copious_debugging!("==> Start phase 0");
let mut candidate_paths = Vec::new();
for node in starting_nodes {
copious_debugging!(" Initial node {}", node.display(graph));
db.find_candidate_partial_paths_from_node(graph, partials, node, &mut candidate_paths);
}
let next_iteration = candidate_paths
.iter()
.filter_map(|partial_path| {
Path::from_partial_path(graph, paths, partials, &db[*partial_path])
})
.collect();
copious_debugging!("==> End phase 0");
PathStitcher {
candidate_paths,
queue: VecDeque::new(),
next_iteration,
cycle_detector: CycleDetector::new(),
#[cfg(feature = "copious-debugging")]
phase_number: 1,
}
}
pub fn previous_phase_paths(&self) -> impl Iterator<Item = &Path> + '_ {
self.next_iteration.iter()
}
pub fn previous_phase_paths_slice(&mut self) -> &[Path] {
self.next_iteration.make_contiguous();
self.next_iteration.as_slices().0
}
pub fn previous_phase_paths_slice_mut(&mut self) -> &mut [Path] {
self.next_iteration.make_contiguous();
self.next_iteration.as_mut_slices().0
}
fn stitch_path(
&mut self,
graph: &StackGraph,
paths: &mut Paths,
partials: &mut PartialPaths,
db: &mut Database,
path: &Path,
) {
copious_debugging!("--> Candidate path {}", path.display(graph, paths));
self.candidate_paths.clear();
if graph[path.end_node].is_root() {
let key = SymbolStackKey::from_symbol_stack(paths, db, path.symbol_stack);
db.find_candidate_partial_paths_from_root(
graph,
partials,
key,
&mut self.candidate_paths,
);
} else {
db.find_candidate_partial_paths_from_node(
graph,
partials,
path.end_node,
&mut self.candidate_paths,
);
}
self.next_iteration.reserve(self.candidate_paths.len());
for extension in &self.candidate_paths {
let extension = &db[*extension];
copious_debugging!(" Extend {}", path.display(graph, paths),);
copious_debugging!(" with {}", extension.display(graph, partials));
let mut new_path = path.clone();
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
if let Err(err) = new_path.append_partial_path(graph, paths, partials, extension) {
copious_debugging!(" is invalid: {:?}", err);
continue;
}
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
if let Err(err) = new_path.resolve(graph, paths) {
copious_debugging!(" cannot resolve: {:?}", err);
continue;
}
copious_debugging!(" is {}", new_path.display(graph, paths));
self.next_iteration.push_back(new_path);
}
}
pub fn is_complete(&self) -> bool {
self.next_iteration.is_empty()
}
pub fn process_next_phase(
&mut self,
graph: &StackGraph,
paths: &mut Paths,
partials: &mut PartialPaths,
db: &mut Database,
) {
copious_debugging!("==> Start phase {}", self.phase_number);
std::mem::swap(&mut self.queue, &mut self.next_iteration);
while let Some(path) = self.queue.pop_front() {
if !self
.cycle_detector
.should_process_path(&path, |probe| probe.cmp(graph, paths, &path))
{
continue;
}
self.stitch_path(graph, paths, partials, db, &path);
}
#[cfg(feature = "copious-debugging")]
{
copious_debugging!("==> End phase {}", self.phase_number);
self.phase_number += 1;
}
}
pub fn find_all_complete_paths<I>(
graph: &StackGraph,
paths: &mut Paths,
partials: &mut PartialPaths,
db: &mut Database,
starting_nodes: I,
) -> Vec<Path>
where
I: IntoIterator<Item = Handle<Node>>,
{
let mut result = Vec::new();
let mut stitcher = PathStitcher::new(graph, paths, partials, db, starting_nodes);
while !stitcher.is_complete() {
let complete_paths = stitcher
.previous_phase_paths()
.filter(|path| path.is_complete(graph));
result.extend(complete_paths.cloned());
stitcher.process_next_phase(graph, paths, partials, db);
}
result
}
}
pub struct ForwardPartialPathStitcher {
candidate_partial_paths: Vec<Handle<PartialPath>>,
queue: VecDeque<PartialPath>,
next_iteration: VecDeque<PartialPath>,
cycle_detector: CycleDetector<PartialPath>,
#[cfg(feature = "copious-debugging")]
phase_number: usize,
}
impl ForwardPartialPathStitcher {
pub fn new<I>(
graph: &StackGraph,
partials: &mut PartialPaths,
db: &mut Database,
starting_nodes: I,
) -> ForwardPartialPathStitcher
where
I: IntoIterator<Item = Handle<Node>>,
{
copious_debugging!("==> Start phase 0");
let mut candidate_partial_paths = Vec::new();
for node in starting_nodes {
copious_debugging!(" Initial node {}", node.display(graph));
db.find_candidate_partial_paths_from_node(
graph,
partials,
node,
&mut candidate_partial_paths,
);
}
let next_iteration = candidate_partial_paths
.iter()
.copied()
.filter(|handle| db[*handle].starts_at_reference(graph))
.map(|handle| db[handle].clone())
.collect();
copious_debugging!("==> End phase 0");
ForwardPartialPathStitcher {
candidate_partial_paths,
queue: VecDeque::new(),
next_iteration,
cycle_detector: CycleDetector::new(),
#[cfg(feature = "copious-debugging")]
phase_number: 1,
}
}
pub fn previous_phase_partial_paths(&self) -> impl Iterator<Item = &PartialPath> + '_ {
self.next_iteration.iter()
}
pub fn previous_phase_partial_paths_slice(&mut self) -> &[PartialPath] {
self.next_iteration.make_contiguous();
self.next_iteration.as_slices().0
}
pub fn previous_phase_partial_paths_slice_mut(&mut self) -> &mut [PartialPath] {
self.next_iteration.make_contiguous();
self.next_iteration.as_mut_slices().0
}
fn stitch_partial_path(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
db: &mut Database,
partial_path: &PartialPath,
) {
self.candidate_partial_paths.clear();
if graph[partial_path.end_node].is_root() {
let key = SymbolStackKey::from_partial_symbol_stack(
partials,
db,
partial_path.symbol_stack_postcondition,
);
db.find_candidate_partial_paths_from_root(
graph,
partials,
key,
&mut self.candidate_partial_paths,
);
} else {
db.find_candidate_partial_paths_from_node(
graph,
partials,
partial_path.end_node,
&mut self.candidate_partial_paths,
);
}
self.next_iteration
.reserve(self.candidate_partial_paths.len());
for extension in &self.candidate_partial_paths {
let mut extension = db[*extension].clone();
copious_debugging!(" Extend {}", partial_path.display(graph, partials));
copious_debugging!(" with {}", extension.display(graph, partials));
extension.ensure_no_overlapping_variables(partials, partial_path);
copious_debugging!(" -> {}", extension.display(graph, partials));
let mut new_partial_path = partial_path.clone();
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
{
if let Err(err) = new_partial_path.concatenate(graph, partials, &extension) {
copious_debugging!(" is invalid: {:?}", err);
continue;
}
if !new_partial_path.starts_at_reference(graph) {
copious_debugging!(" is invalid: slips off of reference");
continue;
}
if let Err(err) = new_partial_path.resolve(graph, partials) {
copious_debugging!(" is invalid: cannot resolve: {:?}", err);
continue;
}
if graph[new_partial_path.end_node].is_jump_to() {
copious_debugging!(" is invalid: cannot resolve: ambiguous scope stack");
continue;
}
}
copious_debugging!(" is {}", new_partial_path.display(graph, partials));
self.next_iteration.push_back(new_partial_path);
}
}
pub fn is_complete(&self) -> bool {
self.next_iteration.is_empty()
}
pub fn process_next_phase(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
db: &mut Database,
) {
copious_debugging!("==> Start phase {}", self.phase_number);
std::mem::swap(&mut self.queue, &mut self.next_iteration);
while let Some(partial_path) = self.queue.pop_front() {
copious_debugging!(
"--> Candidate partial path {}",
partial_path.display(graph, partials)
);
if !self
.cycle_detector
.should_process_path(&partial_path, |probe| {
probe.cmp(graph, partials, &partial_path)
})
{
copious_debugging!(" Cycle detected");
continue;
}
self.stitch_partial_path(graph, partials, db, &partial_path);
}
#[cfg(feature = "copious-debugging")]
{
copious_debugging!("==> End phase {}", self.phase_number);
self.phase_number += 1;
}
}
pub fn find_all_complete_partial_paths<I>(
graph: &StackGraph,
partials: &mut PartialPaths,
db: &mut Database,
starting_nodes: I,
) -> Vec<PartialPath>
where
I: IntoIterator<Item = Handle<Node>>,
{
let mut result = Vec::new();
let mut stitcher = ForwardPartialPathStitcher::new(graph, partials, db, starting_nodes);
while !stitcher.is_complete() {
let complete_partial_paths = stitcher
.previous_phase_partial_paths()
.filter(|partial_path| partial_path.is_complete(graph));
result.extend(complete_partial_paths.cloned());
stitcher.process_next_phase(graph, partials, db);
}
result
}
}