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)
);
}
}