use alloc::collections::VecDeque;
use crate::stack::{StackGraph, StackNode};
use crate::NodeId;
#[derive(Clone, PartialEq, Eq, Debug, Default)]
#[non_exhaustive]
pub struct StackState<'a> {
pub symbols: VecDeque<&'a str>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum TransitionError {
EmptySymbolStack,
IncorrectPoppedSymbol,
UnknownNode,
}
impl<'a> StackState<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn apply(&mut self, graph: &'a StackGraph, id: NodeId) -> Result<(), TransitionError> {
match graph.nodes.get(&id).ok_or(TransitionError::UnknownNode)? {
StackNode::PushSymbol(push) => self.symbols.push_front(&push.symbol),
StackNode::PopSymbol(pop) => match self.symbols.front() {
None => return Err(TransitionError::EmptySymbolStack),
Some(top) if *top != pop.symbol => {
return Err(TransitionError::IncorrectPoppedSymbol)
}
Some(_) => {
self.symbols.pop_front();
}
},
StackNode::Root | StackNode::Scope(_) => {}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{StackState, TransitionError};
use crate::stack::{Definition, StackGraph, StackNode, StackScope, SymbolPop, SymbolPush};
use crate::NodeId;
use alloc::borrow::ToOwned;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
fn push(symbol: &str) -> StackNode {
StackNode::PushSymbol(SymbolPush {
symbol: symbol.to_owned(),
precedence: 0,
is_reference: true,
})
}
fn pop(symbol: &str) -> StackNode {
StackNode::PopSymbol(SymbolPop {
symbol: symbol.to_owned(),
precedence: 0,
definition: Some(Definition::default()),
import: None,
})
}
fn graph(nodes: &[StackNode]) -> StackGraph {
let mut graph = StackGraph::new();
for (id, node) in (10..).zip(nodes) {
graph.add_node(NodeId(id), node.clone());
}
graph
}
fn run<'a>(graph: &'a StackGraph, state: &mut StackState<'a>) -> Result<(), TransitionError> {
graph
.nodes
.keys()
.try_for_each(|id| state.apply(graph, *id))
}
fn apply_last(nodes: &[StackNode]) -> (Result<(), TransitionError>, bool) {
let graph = graph(nodes);
let ids: Vec<NodeId> = graph.nodes.keys().copied().collect();
let (last, setup) = ids.split_last().unwrap();
let mut state = StackState::new();
for id in setup {
state.apply(&graph, *id).unwrap();
}
let before = state.clone();
let result = state.apply(&graph, *last);
(result, state == before)
}
#[test]
fn pushes_stack_symbols_with_the_latest_on_top() {
let graph = graph(&[push("b"), push("a")]);
let mut state = StackState::new();
run(&graph, &mut state).unwrap();
assert_eq!(state.symbols, VecDeque::from(["a", "b"]));
}
#[test]
fn a_pop_consumes_the_matching_top_symbol() {
let graph = graph(&[push("b"), push("a"), pop("a")]);
let mut state = StackState::new();
run(&graph, &mut state).unwrap();
assert_eq!(state.symbols, VecDeque::from(["b"]));
}
#[test]
fn a_pop_on_an_empty_stack_fails_without_changing_the_state() {
assert_eq!(
apply_last(&[pop("a")]),
(Err(TransitionError::EmptySymbolStack), true)
);
}
#[test]
fn a_pop_of_another_symbol_fails_without_changing_the_state() {
assert_eq!(
apply_last(&[push("b"), pop("a")]),
(Err(TransitionError::IncorrectPoppedSymbol), true)
);
}
#[test]
fn roots_and_scopes_leave_the_state_unchanged() {
let graph = graph(&[
push("a"),
StackNode::Root,
StackNode::Scope(StackScope::default()),
]);
let mut state = StackState::new();
run(&graph, &mut state).unwrap();
assert_eq!(state.symbols, VecDeque::from(["a"]));
}
#[test]
fn a_missing_node_cannot_be_applied() {
let graph = graph(&[]);
assert_eq!(
StackState::new().apply(&graph, NodeId(99)),
Err(TransitionError::UnknownNode)
);
}
}