fluidattacks-blends-domain 0.40.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use alloc::collections::VecDeque;
use alloc::vec::Vec;

use crate::stack::resolve::{
    PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
};
use crate::stack::{StackGraph, StackNode};
use crate::NodeId;

impl<'a> PartialPath<'a> {
    /// The walk that only visits `node`: the symbol stack `node` needs on entry and the one it leaves.
    ///
    /// # Errors
    ///
    /// Returns [`TransitionError::UnknownNode`] when `graph` has no `node`.
    pub fn seed(graph: &'a StackGraph, node: NodeId) -> Result<Self, TransitionError> {
        let rest = PartialSymbolStack::from_variable(SymbolStackVariable::INITIAL);
        let over_rest = |symbol: &'a str| {
            PartialSymbolStack::new(VecDeque::from([symbol]), Some(SymbolStackVariable::INITIAL))
        };
        let (pre, post) = match graph.nodes.get(&node).ok_or(TransitionError::UnknownNode)? {
            StackNode::PushSymbol(push) => (rest, over_rest(&push.symbol)),
            StackNode::PopSymbol(pop) => (over_rest(&pop.symbol), rest),
            StackNode::Root | StackNode::Scope(_) => (rest.clone(), rest),
        };
        Ok(Self {
            start: node,
            end: node,
            pre,
            post,
            edges: Vec::new(),
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::stack::resolve::{
        PartialPath, PartialSymbolStack, SymbolStackVariable, TransitionError,
    };
    use crate::stack::{StackGraph, StackNode, StackScope, SymbolPop, SymbolPush};
    use crate::NodeId;
    use alloc::borrow::ToOwned;

    const PUSH_X: NodeId = NodeId(1);
    const POP_X: NodeId = NodeId(2);
    const SCOPE: NodeId = NodeId(3);
    const ROOT: NodeId = NodeId(4);

    fn graph() -> StackGraph {
        let mut graph = StackGraph::new();
        graph.add_node(
            PUSH_X,
            StackNode::PushSymbol(SymbolPush {
                symbol: "x".to_owned(),
                precedence: 0,
                is_reference: true,
            }),
        );
        graph.add_node(
            POP_X,
            StackNode::PopSymbol(SymbolPop {
                symbol: "x".to_owned(),
                precedence: 0,
                definition: None,
                import: None,
            }),
        );
        graph.add_node(SCOPE, StackNode::Scope(StackScope::default()));
        graph.add_node(ROOT, StackNode::Root);
        graph
    }

    fn symbols<'a>(names: &[&'a str]) -> PartialSymbolStack<'a> {
        PartialSymbolStack::new(
            names.iter().copied().collect(),
            Some(SymbolStackVariable::INITIAL),
        )
    }

    fn conditions(
        graph: &StackGraph,
        node: NodeId,
    ) -> Result<(PartialSymbolStack<'_>, PartialSymbolStack<'_>), TransitionError> {
        PartialPath::seed(graph, node).map(|seed| (seed.pre, seed.post))
    }

    #[test]
    fn a_push_leaves_its_symbol_on_top_of_the_stack_it_finds() {
        assert_eq!(
            conditions(&graph(), PUSH_X),
            Ok((symbols(&[]), symbols(&["x"])))
        );
    }

    #[test]
    fn a_pop_needs_its_symbol_on_top_and_leaves_the_rest() {
        assert_eq!(
            conditions(&graph(), POP_X),
            Ok((symbols(&["x"]), symbols(&[])))
        );
    }

    #[test]
    fn a_scope_leaves_the_stack_as_it_finds_it() {
        assert_eq!(
            conditions(&graph(), SCOPE),
            Ok((symbols(&[]), symbols(&[])))
        );
    }

    #[test]
    fn the_root_leaves_the_stack_as_it_finds_it() {
        assert_eq!(conditions(&graph(), ROOT), Ok((symbols(&[]), symbols(&[]))));
    }

    #[test]
    fn a_seed_starts_and_ends_at_its_node_without_edges() {
        let graph = graph();
        let seed = PartialPath::seed(&graph, PUSH_X).expect("the push is in the graph");

        assert_eq!(
            (seed.start, seed.end, seed.edges.len()),
            (PUSH_X, PUSH_X, 0)
        );
    }

    #[test]
    fn a_node_missing_from_the_graph_has_no_seed() {
        assert_eq!(
            PartialPath::seed(&graph(), NodeId(99)),
            Err(TransitionError::UnknownNode)
        );
    }

    #[test]
    fn a_seed_joined_with_itself_stays_the_same_walk() {
        let graph = graph();
        let seeds = [PUSH_X, POP_X, SCOPE]
            .map(|node| PartialPath::seed(&graph, node).expect("the node is in the graph"));

        assert_eq!(
            seeds.each_ref().map(|seed| seed.concatenate(&graph, seed)),
            seeds.map(Ok)
        );
    }
}