fluidattacks-blends-domain 0.41.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
//! Counterpart of `blends/utilities/text_nodes.py`.

use alloc::string::String;

use crate::ast::node_utils::{children, ensure_node, NodeResolutionError};
use crate::ast::{AstGraph, AstNode};
use crate::NodeId;

fn build_node_to_str(
    graph: &AstGraph,
    node: &AstNode,
    separator: &str,
    out: &mut String,
) -> Result<(), NodeResolutionError> {
    // A node with its own text already spans its children.
    if let Some(text) = node.text.as_deref() {
        out.push_str(text);
        out.push_str(separator);
        return Ok(());
    }
    children(graph, node)?
        .into_iter()
        .try_for_each(|child| build_node_to_str(graph, child.0, separator, out))
}

fn node_to_str_joined(
    graph: &AstGraph,
    node: &AstNode,
    separator: &str,
) -> Result<String, NodeResolutionError> {
    let mut out = String::new();
    build_node_to_str(graph, node, separator, &mut out)?;
    out.truncate(out.len().saturating_sub(separator.len()));
    Ok(out)
}

pub fn node_to_str_result(graph: &AstGraph, node: &AstNode) -> Result<String, NodeResolutionError> {
    node_to_str_joined(graph, node, "")
}

#[must_use]
pub fn node_to_str(graph: &AstGraph, n_id: NodeId) -> String {
    ensure_node(graph, n_id)
        .and_then(|n| node_to_str_result(graph, n.0))
        .unwrap_or_default()
}

#[cfg(test)]
mod tests {
    use super::node_to_str;
    use crate::ast::{AstGraph, AstNode};
    use crate::NodeId;
    use alloc::borrow::ToOwned;

    #[test]
    fn joins_the_text_of_the_leaves_in_order() {
        let mut ast = AstGraph::new();
        ast.add_node(
            NodeId(1),
            AstNode::new(1, 1, "block_mapping_pair".to_owned()),
        );
        let mut key = AstNode::new(1, 1, "string_scalar".to_owned());
        key.text = Some("empty-key".to_owned());
        ast.add_node(NodeId(2), key);
        let mut colon = AstNode::new(1, 10, ":".to_owned());
        colon.text = Some(":".to_owned());
        ast.add_node(NodeId(3), colon);
        ast.add_edge(NodeId(1), NodeId(2), 0);
        ast.add_edge(NodeId(1), NodeId(3), 1);

        assert_eq!(node_to_str(&ast, NodeId(1)), "empty-key:");
        assert_eq!(node_to_str(&ast, NodeId(2)), "empty-key");
    }

    #[test]
    fn a_node_with_text_is_not_joined_with_its_children() {
        let mut ast = AstGraph::new();
        let mut literal = AstNode::new(1, 1, "interpreted_string_literal".to_owned());
        literal.text = Some("\"/a\"".to_owned());
        ast.add_node(NodeId(1), literal);
        for (index, text) in [(0_u32, "\""), (1, "/a"), (2, "\"")] {
            let id = NodeId(u64::from(index) + 2);
            let mut child = AstNode::new(1, 1, "token".to_owned());
            child.text = Some(text.to_owned());
            ast.add_node(id, child);
            ast.add_edge(NodeId(1), id, index);
        }

        assert_eq!(node_to_str(&ast, NodeId(1)), "\"/a\"");
    }
}