use alloc::collections::VecDeque;
use alloc::vec::Vec;
use crate::stack::resolve::{
PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
};
use crate::stack::{StackGraph, StackNode};
use crate::NodeId;
impl<'a> PartialPath<'a> {
pub fn seed(graph: &'a StackGraph, node: NodeId) -> Result<Self, TransitionError> {
let rest = PartialSymbolStack::from_variable(SymbolStackVariable::INITIAL);
let over_rest = |symbol: &'a str| {
PartialSymbolStack::new(VecDeque::from([symbol]), Some(SymbolStackVariable::INITIAL))
};
let (pre, post) = match graph.nodes.get(&node).ok_or(TransitionError::UnknownNode)? {
StackNode::PushSymbol(push) => (rest, over_rest(&push.symbol)),
StackNode::PopSymbol(pop) => (over_rest(&pop.symbol), rest),
StackNode::Root | StackNode::Scope(_) => (rest.clone(), rest),
};
Ok(Self {
start: node,
end: node,
pre,
post,
edges: Vec::new(),
})
}
}
#[cfg(test)]
mod tests {
use crate::stack::resolve::{
PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
};
use crate::stack::{StackGraph, StackNode, StackScope, SymbolPop, SymbolPush};
use crate::NodeId;
use alloc::borrow::ToOwned;
const PUSH_X: NodeId = NodeId(1);
const POP_X: NodeId = NodeId(2);
const SCOPE: NodeId = NodeId(3);
const ROOT: NodeId = NodeId(4);
fn graph() -> StackGraph {
let mut graph = StackGraph::new();
graph.add_node(
PUSH_X,
StackNode::PushSymbol(SymbolPush {
symbol: "x".to_owned(),
precedence: 0,
is_reference: true,
}),
);
graph.add_node(
POP_X,
StackNode::PopSymbol(SymbolPop {
symbol: "x".to_owned(),
precedence: 0,
definition: None,
import: None,
}),
);
graph.add_node(SCOPE, StackNode::Scope(StackScope::default()));
graph.add_node(ROOT, StackNode::Root);
graph
}
fn symbols<'a>(names: &[&'a str]) -> PartialSymbolStack<'a> {
PartialSymbolStack::new(
names.iter().copied().collect(),
Some(SymbolStackVariable::INITIAL),
)
}
fn conditions(
graph: &StackGraph,
node: NodeId,
) -> Result<(PartialSymbolStack<'_>, PartialSymbolStack<'_>), TransitionError> {
PartialPath::seed(graph, node).map(|seed| (seed.pre, seed.post))
}
#[test]
fn a_push_leaves_its_symbol_on_top_of_the_stack_it_finds() {
assert_eq!(
conditions(&graph(), PUSH_X),
Ok((symbols(&[]), symbols(&["x"])))
);
}
#[test]
fn a_pop_needs_its_symbol_on_top_and_leaves_the_rest() {
assert_eq!(
conditions(&graph(), POP_X),
Ok((symbols(&["x"]), symbols(&[])))
);
}
#[test]
fn a_scope_leaves_the_stack_as_it_finds_it() {
assert_eq!(
conditions(&graph(), SCOPE),
Ok((symbols(&[]), symbols(&[])))
);
}
#[test]
fn the_root_leaves_the_stack_as_it_finds_it() {
assert_eq!(conditions(&graph(), ROOT), Ok((symbols(&[]), symbols(&[]))));
}
#[test]
fn a_seed_starts_and_ends_at_its_node_without_edges() {
let graph = graph();
let seed = PartialPath::seed(&graph, PUSH_X).expect("the push is in the graph");
assert_eq!(
(seed.start, seed.end, seed.edges.len()),
(PUSH_X, PUSH_X, 0)
);
}
#[test]
fn a_node_missing_from_the_graph_has_no_seed() {
assert_eq!(
PartialPath::seed(&graph(), NodeId(99)),
Err(TransitionError::UnknownNode)
);
}
#[test]
fn a_seed_joined_with_itself_stays_the_same_walk() {
let graph = graph();
let seeds = [PUSH_X, POP_X, SCOPE]
.map(|node| PartialPath::seed(&graph, node).expect("the node is in the graph"));
assert_eq!(
seeds.each_ref().map(|seed| seed.concatenate(&graph, seed)),
seeds.map(Ok)
);
}
}