fluidattacks-blends-domain 0.6.1

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
//! Generic cfg walk over the syntax graph.

use alloc::borrow::ToOwned;
use alloc::string::String;

use crate::syntax::cfg::dispatchers;
use crate::syntax::SyntaxGraph;
use crate::{CodeGraph, NodeId};

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SyntaxCfgError {
    MissingCfgBuilder { node_type: String },
    MissingSyntaxNode { n_id: NodeId },
    UnexpectedSyntaxShape { n_id: NodeId },
}

impl core::fmt::Display for SyntaxCfgError {
    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::MissingCfgBuilder { node_type } => {
                write!(formatter, "missing cfg builder for {node_type}")
            }
            Self::MissingSyntaxNode { n_id } => {
                write!(
                    formatter,
                    "the cfg walk reached id {} absent from the syntax graph",
                    n_id.0
                )
            }
            Self::UnexpectedSyntaxShape { n_id } => {
                write!(
                    formatter,
                    "the cfg walk found an unexpected syntax shape at id {}",
                    n_id.0
                )
            }
        }
    }
}

pub type CfgBuilder = fn(
    args: &mut SyntaxCfgArgs<'_>,
    n_id: NodeId,
    nxt_id: Option<NodeId>,
) -> Result<NodeId, SyntaxCfgError>;

pub struct SyntaxCfgArgs<'a> {
    pub graph: &'a mut SyntaxGraph,
    pub is_multifile: bool,
}

impl SyntaxCfgArgs<'_> {
    pub fn generic(
        &mut self,
        n_id: NodeId,
        nxt_id: Option<NodeId>,
    ) -> Result<NodeId, SyntaxCfgError> {
        let node_type = self
            .graph
            .label_type(n_id)
            .ok_or(SyntaxCfgError::MissingSyntaxNode { n_id })?;

        let builder = dispatchers::dispatcher(node_type).ok_or_else(|| {
            SyntaxCfgError::MissingCfgBuilder {
                node_type: node_type.to_owned(),
            }
        })?;

        builder(self, n_id, nxt_id)
    }
}

pub fn add_syntax_cfg(graph: &mut SyntaxGraph, is_multifile: bool) -> Result<(), SyntaxCfgError> {
    let mut args = SyntaxCfgArgs {
        graph,
        is_multifile,
    };
    args.generic(NodeId(1), None)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{add_syntax_cfg, SyntaxCfgError};
    use crate::syntax::{SyntaxGraph, SyntaxNode};
    use crate::{Cfg, NodeId};
    use alloc::borrow::ToOwned;
    use alloc::string::ToString;

    #[test]
    fn unmapped_node_type_fails_with_the_missing_builder() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(
            NodeId(1),
            SyntaxNode::Metadata {
                path: "test.yaml".to_owned(),
                structure: alloc::collections::BTreeMap::new(),
                instances: alloc::collections::BTreeMap::new(),
                imports: alloc::vec::Vec::new(),
                package: None,
            },
        );

        let result = add_syntax_cfg(&mut graph, false);

        assert_eq!(
            result,
            Err(SyntaxCfgError::MissingCfgBuilder {
                node_type: "Metadata".to_owned()
            })
        );
    }

    #[test]
    fn every_error_renders_its_failure_mode() {
        assert_eq!(
            SyntaxCfgError::MissingCfgBuilder {
                node_type: "Metadata".to_owned()
            }
            .to_string(),
            "missing cfg builder for Metadata"
        );
        assert_eq!(
            SyntaxCfgError::MissingSyntaxNode { n_id: NodeId(7) }.to_string(),
            "the cfg walk reached id 7 absent from the syntax graph"
        );
        assert_eq!(
            SyntaxCfgError::UnexpectedSyntaxShape { n_id: NodeId(9) }.to_string(),
            "the cfg walk found an unexpected syntax shape at id 9"
        );
    }

    #[test]
    fn missing_root_node_fails() {
        let mut graph = SyntaxGraph::new();

        let result = add_syntax_cfg(&mut graph, false);

        assert_eq!(
            result,
            Err(SyntaxCfgError::MissingSyntaxNode { n_id: NodeId(1) })
        );
    }

    #[test]
    fn failed_walk_keeps_the_partial_overlay() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(NodeId(1), SyntaxNode::File);
        graph.add_node(
            NodeId(2),
            SyntaxNode::Literal {
                value: "1".to_owned(),
                value_type: "number".to_owned(),
            },
        );
        graph.add_node(
            NodeId(3),
            SyntaxNode::Metadata {
                path: "test.yaml".to_owned(),
                structure: alloc::collections::BTreeMap::new(),
                instances: alloc::collections::BTreeMap::new(),
                imports: alloc::vec::Vec::new(),
                package: None,
            },
        );
        graph.add_ast_edge(NodeId(1), NodeId(2));
        graph.add_ast_edge(NodeId(1), NodeId(3));

        let result = add_syntax_cfg(&mut graph, false);

        assert!(matches!(
            result,
            Err(SyntaxCfgError::MissingCfgBuilder { .. })
        ));
        assert_eq!(
            graph
                .edges
                .get(&NodeId(1))
                .and_then(|adjacent| adjacent.get(&NodeId(2)))
                .and_then(|edge| edge.cfg),
            Some(Cfg)
        );
    }
}