hugr_core/hugr/views/
render.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Helper methods to compute the node/edge/port style when rendering a HUGR
//! into dot or mermaid format.

use portgraph::render::{EdgeStyle, NodeStyle, PortStyle};
use portgraph::{LinkView, NodeIndex, PortIndex, PortView};

use crate::ops::{NamedOp, OpType};
use crate::types::EdgeKind;
use crate::HugrView;

/// Configuration for rendering a HUGR graph.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct RenderConfig {
    /// Show the node index in the graph nodes.
    pub node_indices: bool,
    /// Show port offsets in the graph edges.
    pub port_offsets_in_edges: bool,
    /// Show type labels on edges.
    pub type_labels_in_edges: bool,
}

impl Default for RenderConfig {
    fn default() -> Self {
        Self {
            node_indices: true,
            port_offsets_in_edges: true,
            type_labels_in_edges: true,
        }
    }
}

/// Formatter method to compute a node style.
pub(super) fn node_style<H: HugrView + ?Sized>(
    h: &H,
    config: RenderConfig,
) -> Box<dyn FnMut(NodeIndex) -> NodeStyle + '_> {
    fn node_name<H: HugrView + ?Sized>(h: &H, n: NodeIndex) -> String {
        match h.get_optype(n.into()) {
            OpType::FuncDecl(f) => format!("FuncDecl: \"{}\"", f.name),
            OpType::FuncDefn(f) => format!("FuncDefn: \"{}\"", f.name),
            op => op.name().to_string(),
        }
    }

    if config.node_indices {
        Box::new(move |n| {
            NodeStyle::Box(format!(
                "({ni}) {name}",
                ni = n.index(),
                name = node_name(h, n)
            ))
        })
    } else {
        Box::new(move |n| NodeStyle::Box(node_name(h, n)))
    }
}

/// Formatter method to compute a port style.
pub(super) fn port_style<H: HugrView + ?Sized>(
    h: &H,
    _config: RenderConfig,
) -> Box<dyn FnMut(PortIndex) -> PortStyle + '_> {
    let graph = h.portgraph();
    Box::new(move |port| {
        let node = graph.port_node(port).unwrap();
        let optype = h.get_optype(node.into());
        let offset = graph.port_offset(port).unwrap();
        match optype.port_kind(offset).unwrap() {
            EdgeKind::Function(pf) => PortStyle::new(html_escape::encode_text(&format!("{}", pf))),
            EdgeKind::Const(ty) | EdgeKind::Value(ty) => {
                PortStyle::new(html_escape::encode_text(&format!("{}", ty)))
            }
            EdgeKind::StateOrder => match graph.port_links(port).count() > 0 {
                true => PortStyle::text("", false),
                false => PortStyle::Hidden,
            },
            _ => PortStyle::text("", true),
        }
    })
}

/// Formatter method to compute an edge style.
#[allow(clippy::type_complexity)]
pub(super) fn edge_style<H: HugrView + ?Sized>(
    h: &H,
    config: RenderConfig,
) -> Box<
    dyn FnMut(
            <H::Portgraph<'_> as LinkView>::LinkEndpoint,
            <H::Portgraph<'_> as LinkView>::LinkEndpoint,
        ) -> EdgeStyle
        + '_,
> {
    let graph = h.portgraph();
    Box::new(move |src, tgt| {
        let src_node = graph.port_node(src).unwrap();
        let src_optype = h.get_optype(src_node.into());
        let src_offset = graph.port_offset(src).unwrap();
        let tgt_offset = graph.port_offset(tgt).unwrap();

        let port_kind = src_optype.port_kind(src_offset).unwrap();

        // StateOrder edges: Dotted line.
        // Control flow edges: Dashed line.
        // Static and Value edges: Solid line with label.
        let style = match port_kind {
            EdgeKind::StateOrder => EdgeStyle::Dotted,
            EdgeKind::ControlFlow => EdgeStyle::Dashed,
            EdgeKind::Const(_) | EdgeKind::Function(_) | EdgeKind::Value(_) => EdgeStyle::Solid,
        };

        // Compute the label for the edge, given the setting flags.
        fn type_label(e: EdgeKind) -> Option<String> {
            match e {
                EdgeKind::Const(ty) | EdgeKind::Value(ty) => Some(format!("{}", ty)),
                EdgeKind::Function(pf) => Some(format!("{}", pf)),
                _ => None,
            }
        }
        //
        // Only static and value edges have types to display.
        let label = match (
            config.port_offsets_in_edges,
            type_label(port_kind).filter(|_| config.type_labels_in_edges),
        ) {
            (true, Some(ty)) => {
                format!("{}:{}\n{ty}", src_offset.index(), tgt_offset.index())
            }
            (true, _) => format!("{}:{}", src_offset.index(), tgt_offset.index()),
            (false, Some(ty)) => ty.to_string(),
            _ => return style,
        };
        style.with_label(label)
    })
}