Skip to main content

fluidattacks_blends_domain/stack/resolve/
seed.rs

1use alloc::collections::VecDeque;
2use alloc::vec::Vec;
3
4use crate::stack::resolve::{
5    PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
6};
7use crate::stack::{StackGraph, StackNode};
8use crate::NodeId;
9
10impl<'a> PartialPath<'a> {
11    /// The walk that only visits `node`: the symbol stack `node` needs on entry and the one it leaves.
12    ///
13    /// # Errors
14    ///
15    /// Returns [`TransitionError::UnknownNode`] when `graph` has no `node`.
16    pub fn seed(graph: &'a StackGraph, node: NodeId) -> Result<Self, TransitionError> {
17        let rest = PartialSymbolStack::from_variable(SymbolStackVariable::INITIAL);
18        let over_rest = |symbol: &'a str| {
19            PartialSymbolStack::new(VecDeque::from([symbol]), Some(SymbolStackVariable::INITIAL))
20        };
21        let (pre, post) = match graph.nodes.get(&node).ok_or(TransitionError::UnknownNode)? {
22            StackNode::PushSymbol(push) => (rest, over_rest(&push.symbol)),
23            StackNode::PopSymbol(pop) => (over_rest(&pop.symbol), rest),
24            StackNode::Root | StackNode::Scope(_) => (rest.clone(), rest),
25        };
26        Ok(Self {
27            start: node,
28            end: node,
29            pre,
30            post,
31            edges: Vec::new(),
32        })
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use crate::stack::resolve::{
39        PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
40    };
41    use crate::stack::{StackGraph, StackNode, StackScope, SymbolPop, SymbolPush};
42    use crate::NodeId;
43    use alloc::borrow::ToOwned;
44
45    const PUSH_X: NodeId = NodeId(1);
46    const POP_X: NodeId = NodeId(2);
47    const SCOPE: NodeId = NodeId(3);
48    const ROOT: NodeId = NodeId(4);
49
50    fn graph() -> StackGraph {
51        let mut graph = StackGraph::new();
52        graph.add_node(
53            PUSH_X,
54            StackNode::PushSymbol(SymbolPush {
55                symbol: "x".to_owned(),
56                precedence: 0,
57                is_reference: true,
58            }),
59        );
60        graph.add_node(
61            POP_X,
62            StackNode::PopSymbol(SymbolPop {
63                symbol: "x".to_owned(),
64                precedence: 0,
65                definition: None,
66                import: None,
67            }),
68        );
69        graph.add_node(SCOPE, StackNode::Scope(StackScope::default()));
70        graph.add_node(ROOT, StackNode::Root);
71        graph
72    }
73
74    fn symbols<'a>(names: &[&'a str]) -> PartialSymbolStack<'a> {
75        PartialSymbolStack::new(
76            names.iter().copied().collect(),
77            Some(SymbolStackVariable::INITIAL),
78        )
79    }
80
81    fn conditions(
82        graph: &StackGraph,
83        node: NodeId,
84    ) -> Result<(PartialSymbolStack<'_>, PartialSymbolStack<'_>), TransitionError> {
85        PartialPath::seed(graph, node).map(|seed| (seed.pre, seed.post))
86    }
87
88    #[test]
89    fn a_push_leaves_its_symbol_on_top_of_the_stack_it_finds() {
90        assert_eq!(
91            conditions(&graph(), PUSH_X),
92            Ok((symbols(&[]), symbols(&["x"])))
93        );
94    }
95
96    #[test]
97    fn a_pop_needs_its_symbol_on_top_and_leaves_the_rest() {
98        assert_eq!(
99            conditions(&graph(), POP_X),
100            Ok((symbols(&["x"]), symbols(&[])))
101        );
102    }
103
104    #[test]
105    fn a_scope_leaves_the_stack_as_it_finds_it() {
106        assert_eq!(
107            conditions(&graph(), SCOPE),
108            Ok((symbols(&[]), symbols(&[])))
109        );
110    }
111
112    #[test]
113    fn the_root_leaves_the_stack_as_it_finds_it() {
114        assert_eq!(conditions(&graph(), ROOT), Ok((symbols(&[]), symbols(&[]))));
115    }
116
117    #[test]
118    fn a_seed_starts_and_ends_at_its_node_without_edges() {
119        let graph = graph();
120        let seed = PartialPath::seed(&graph, PUSH_X).expect("the push is in the graph");
121
122        assert_eq!(
123            (seed.start, seed.end, seed.edges.len()),
124            (PUSH_X, PUSH_X, 0)
125        );
126    }
127
128    #[test]
129    fn a_node_missing_from_the_graph_has_no_seed() {
130        assert_eq!(
131            PartialPath::seed(&graph(), NodeId(99)),
132            Err(TransitionError::UnknownNode)
133        );
134    }
135
136    #[test]
137    fn a_seed_joined_with_itself_stays_the_same_walk() {
138        let graph = graph();
139        let seeds = [PUSH_X, POP_X, SCOPE]
140            .map(|node| PartialPath::seed(&graph, node).expect("the node is in the graph"));
141
142        assert_eq!(
143            seeds.each_ref().map(|seed| seed.concatenate(&graph, seed)),
144            seeds.map(Ok)
145        );
146    }
147}