fluidattacks_blends_domain/stack/resolve/
state.rs1use alloc::collections::VecDeque;
2
3use crate::stack::{StackGraph, StackNode};
4use crate::NodeId;
5
6#[derive(Clone, PartialEq, Eq, Debug, Default)]
8#[non_exhaustive]
9pub struct StackState<'a> {
10 pub symbols: VecDeque<&'a str>,
12}
13
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16#[non_exhaustive]
17pub enum TransitionError {
18 EmptySymbolStack,
20 IncorrectPoppedSymbol,
22 UnknownNode,
24}
25
26impl<'a> StackState<'a> {
27 #[must_use]
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn apply(&mut self, graph: &'a StackGraph, id: NodeId) -> Result<(), TransitionError> {
39 match graph.nodes.get(&id).ok_or(TransitionError::UnknownNode)? {
40 StackNode::PushSymbol(push) => self.symbols.push_front(&push.symbol),
41 StackNode::PopSymbol(pop) => match self.symbols.front() {
42 None => return Err(TransitionError::EmptySymbolStack),
43 Some(top) if *top != pop.symbol => {
44 return Err(TransitionError::IncorrectPoppedSymbol)
45 }
46 Some(_) => {
47 self.symbols.pop_front();
48 }
49 },
50 StackNode::Root | StackNode::Scope(_) => {}
51 }
52 Ok(())
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::{StackState, TransitionError};
59 use crate::stack::{Definition, StackGraph, StackNode, StackScope, SymbolPop, SymbolPush};
60 use crate::NodeId;
61 use alloc::borrow::ToOwned;
62 use alloc::collections::VecDeque;
63 use alloc::vec::Vec;
64
65 fn push(symbol: &str) -> StackNode {
66 StackNode::PushSymbol(SymbolPush {
67 symbol: symbol.to_owned(),
68 precedence: 0,
69 is_reference: true,
70 })
71 }
72
73 fn pop(symbol: &str) -> StackNode {
74 StackNode::PopSymbol(SymbolPop {
75 symbol: symbol.to_owned(),
76 precedence: 0,
77 definition: Some(Definition::default()),
78 import: None,
79 })
80 }
81
82 fn graph(nodes: &[StackNode]) -> StackGraph {
83 let mut graph = StackGraph::new();
84 for (id, node) in (10..).zip(nodes) {
85 graph.add_node(NodeId(id), node.clone());
86 }
87 graph
88 }
89
90 fn run<'a>(graph: &'a StackGraph, state: &mut StackState<'a>) -> Result<(), TransitionError> {
91 graph
92 .nodes
93 .keys()
94 .try_for_each(|id| state.apply(graph, *id))
95 }
96
97 fn apply_last(nodes: &[StackNode]) -> (Result<(), TransitionError>, bool) {
98 let graph = graph(nodes);
99 let ids: Vec<NodeId> = graph.nodes.keys().copied().collect();
100 let (last, setup) = ids.split_last().unwrap();
101 let mut state = StackState::new();
102 for id in setup {
103 state.apply(&graph, *id).unwrap();
104 }
105 let before = state.clone();
106 let result = state.apply(&graph, *last);
107 (result, state == before)
108 }
109
110 #[test]
111 fn pushes_stack_symbols_with_the_latest_on_top() {
112 let graph = graph(&[push("b"), push("a")]);
113 let mut state = StackState::new();
114 run(&graph, &mut state).unwrap();
115
116 assert_eq!(state.symbols, VecDeque::from(["a", "b"]));
117 }
118
119 #[test]
120 fn a_pop_consumes_the_matching_top_symbol() {
121 let graph = graph(&[push("b"), push("a"), pop("a")]);
122 let mut state = StackState::new();
123 run(&graph, &mut state).unwrap();
124
125 assert_eq!(state.symbols, VecDeque::from(["b"]));
126 }
127
128 #[test]
129 fn a_pop_on_an_empty_stack_fails_without_changing_the_state() {
130 assert_eq!(
131 apply_last(&[pop("a")]),
132 (Err(TransitionError::EmptySymbolStack), true)
133 );
134 }
135
136 #[test]
137 fn a_pop_of_another_symbol_fails_without_changing_the_state() {
138 assert_eq!(
139 apply_last(&[push("b"), pop("a")]),
140 (Err(TransitionError::IncorrectPoppedSymbol), true)
141 );
142 }
143
144 #[test]
145 fn roots_and_scopes_leave_the_state_unchanged() {
146 let graph = graph(&[
147 push("a"),
148 StackNode::Root,
149 StackNode::Scope(StackScope::default()),
150 ]);
151 let mut state = StackState::new();
152 run(&graph, &mut state).unwrap();
153
154 assert_eq!(state.symbols, VecDeque::from(["a"]));
155 }
156
157 #[test]
158 fn a_missing_node_cannot_be_applied() {
159 let graph = graph(&[]);
160
161 assert_eq!(
162 StackState::new().apply(&graph, NodeId(99)),
163 Err(TransitionError::UnknownNode)
164 );
165 }
166}