Skip to main content

fluidattacks_blends_domain/syntax/cfg/
generate.rs

1//! Generic cfg walk over the syntax graph.
2
3use alloc::borrow::ToOwned;
4use alloc::string::String;
5
6use crate::syntax::cfg::dispatchers;
7use crate::syntax::SyntaxGraph;
8use crate::{CodeGraph, NodeId};
9
10#[derive(Clone, PartialEq, Eq, Debug)]
11pub enum SyntaxCfgError {
12    MissingCfgBuilder { node_type: String },
13    MissingSyntaxNode { n_id: NodeId },
14    UnexpectedSyntaxShape { n_id: NodeId },
15}
16
17impl core::fmt::Display for SyntaxCfgError {
18    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19        match self {
20            Self::MissingCfgBuilder { node_type } => {
21                write!(formatter, "missing cfg builder for {node_type}")
22            }
23            Self::MissingSyntaxNode { n_id } => {
24                write!(
25                    formatter,
26                    "the cfg walk reached id {} absent from the syntax graph",
27                    n_id.0
28                )
29            }
30            Self::UnexpectedSyntaxShape { n_id } => {
31                write!(
32                    formatter,
33                    "the cfg walk found an unexpected syntax shape at id {}",
34                    n_id.0
35                )
36            }
37        }
38    }
39}
40
41pub type CfgBuilder = fn(
42    args: &mut SyntaxCfgArgs<'_>,
43    n_id: NodeId,
44    nxt_id: Option<NodeId>,
45) -> Result<NodeId, SyntaxCfgError>;
46
47pub struct SyntaxCfgArgs<'a> {
48    pub graph: &'a mut SyntaxGraph,
49    pub is_multifile: bool,
50}
51
52impl SyntaxCfgArgs<'_> {
53    pub fn generic(
54        &mut self,
55        n_id: NodeId,
56        nxt_id: Option<NodeId>,
57    ) -> Result<NodeId, SyntaxCfgError> {
58        let node_type = self
59            .graph
60            .label_type(n_id)
61            .ok_or(SyntaxCfgError::MissingSyntaxNode { n_id })?;
62
63        let builder = dispatchers::dispatcher(node_type).ok_or_else(|| {
64            SyntaxCfgError::MissingCfgBuilder {
65                node_type: node_type.to_owned(),
66            }
67        })?;
68
69        builder(self, n_id, nxt_id)
70    }
71}
72
73pub fn add_syntax_cfg(graph: &mut SyntaxGraph, is_multifile: bool) -> Result<(), SyntaxCfgError> {
74    let mut args = SyntaxCfgArgs {
75        graph,
76        is_multifile,
77    };
78    args.generic(NodeId(1), None)?;
79    Ok(())
80}
81
82#[cfg(test)]
83mod tests {
84    use super::{add_syntax_cfg, SyntaxCfgError};
85    use crate::syntax::{SyntaxGraph, SyntaxNode};
86    use crate::{Cfg, NodeId};
87    use alloc::borrow::ToOwned;
88    use alloc::string::ToString;
89
90    #[test]
91    fn unmapped_node_type_fails_with_the_missing_builder() {
92        let mut graph = SyntaxGraph::new();
93        graph.add_node(
94            NodeId(1),
95            SyntaxNode::Metadata {
96                path: "test.yaml".to_owned(),
97                structure: alloc::collections::BTreeMap::new(),
98                instances: alloc::collections::BTreeMap::new(),
99                imports: alloc::vec::Vec::new(),
100                package: None,
101            },
102        );
103
104        let result = add_syntax_cfg(&mut graph, false);
105
106        assert_eq!(
107            result,
108            Err(SyntaxCfgError::MissingCfgBuilder {
109                node_type: "Metadata".to_owned()
110            })
111        );
112    }
113
114    #[test]
115    fn every_error_renders_its_failure_mode() {
116        assert_eq!(
117            SyntaxCfgError::MissingCfgBuilder {
118                node_type: "Metadata".to_owned()
119            }
120            .to_string(),
121            "missing cfg builder for Metadata"
122        );
123        assert_eq!(
124            SyntaxCfgError::MissingSyntaxNode { n_id: NodeId(7) }.to_string(),
125            "the cfg walk reached id 7 absent from the syntax graph"
126        );
127        assert_eq!(
128            SyntaxCfgError::UnexpectedSyntaxShape { n_id: NodeId(9) }.to_string(),
129            "the cfg walk found an unexpected syntax shape at id 9"
130        );
131    }
132
133    #[test]
134    fn missing_root_node_fails() {
135        let mut graph = SyntaxGraph::new();
136
137        let result = add_syntax_cfg(&mut graph, false);
138
139        assert_eq!(
140            result,
141            Err(SyntaxCfgError::MissingSyntaxNode { n_id: NodeId(1) })
142        );
143    }
144
145    #[test]
146    fn failed_walk_keeps_the_partial_overlay() {
147        let mut graph = SyntaxGraph::new();
148        graph.add_node(NodeId(1), SyntaxNode::File);
149        graph.add_node(
150            NodeId(2),
151            SyntaxNode::Literal {
152                value: "1".to_owned(),
153                value_type: "number".to_owned(),
154            },
155        );
156        graph.add_node(
157            NodeId(3),
158            SyntaxNode::Metadata {
159                path: "test.yaml".to_owned(),
160                structure: alloc::collections::BTreeMap::new(),
161                instances: alloc::collections::BTreeMap::new(),
162                imports: alloc::vec::Vec::new(),
163                package: None,
164            },
165        );
166        graph.add_ast_edge(NodeId(1), NodeId(2));
167        graph.add_ast_edge(NodeId(1), NodeId(3));
168
169        let result = add_syntax_cfg(&mut graph, false);
170
171        assert!(matches!(
172            result,
173            Err(SyntaxCfgError::MissingCfgBuilder { .. })
174        ));
175        assert_eq!(
176            graph
177                .edges
178                .get(&NodeId(1))
179                .and_then(|adjacent| adjacent.get(&NodeId(2)))
180                .and_then(|edge| edge.cfg),
181            Some(Cfg)
182        );
183    }
184}