fluidattacks_blends_domain/stack/resolve/
seed.rs1use 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 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}