Skip to main content

fluidattacks_blends_domain/stack/resolve/
state.rs

1use alloc::collections::VecDeque;
2
3use crate::stack::{StackGraph, StackNode};
4use crate::NodeId;
5
6/// The symbol stack a path carries through the graph, top first.
7#[derive(Clone, PartialEq, Eq, Debug, Default)]
8#[non_exhaustive]
9pub struct StackState<'a> {
10    /// Symbols still to resolve.
11    pub symbols: VecDeque<&'a str>,
12}
13
14/// Why a node cannot be applied to a [`StackState`].
15#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16#[non_exhaustive]
17pub enum TransitionError {
18    /// A pop found no symbol to consume.
19    EmptySymbolStack,
20    /// A pop found a different symbol on top.
21    IncorrectPoppedSymbol,
22    /// The node is not in the graph.
23    UnknownNode,
24}
25
26impl<'a> StackState<'a> {
27    /// An empty state.
28    #[must_use]
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Applies node `id` of `graph`.
34    ///
35    /// # Errors
36    ///
37    /// Returns the [`TransitionError`] that blocks the node, leaving the state unchanged.
38    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}