///|
pub(all) enum GraphExportFormat {
GraphJson
GraphDot
GraphText
} derive(Eq, Debug, ToJson)
///|
pub(all) struct GraphNodeRecord {
id : String
kind : String
description : String
incoming : Int
outgoing : Int
source_reachable : Bool
sink_reachable : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct GraphEdgeRecord {
from : String
to : String
label : String
boundary_crossing : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct GraphExport {
format : String
nodes : Array[GraphNodeRecord]
edges : Array[GraphEdgeRecord]
digest : String
} derive(Eq, Debug, ToJson)
///|
pub fn export_graph(model : Model, format : GraphExportFormat) -> String {
match format {
GraphJson => export_graph_json(model)
GraphDot => export_graph_dot(model)
GraphText => export_graph_text(model)
}
}
///|
pub fn export_graph_json(model : Model) -> String {
graph_export(model, "json").to_json().stringify(indent=2)
}
///|
pub fn export_graph_dot(model : Model) -> String {
let out = StringBuilder()
out.write_string("digraph MoonTrustFlow {")
for node in model.nodes {
out.write_string("\n \"")
out.write_string(node.name)
out.write_string("\" [label=\"")
out.write_string(node.name + " (" + node_kind_name(node.kind) + ")")
out.write_string("\"];")
}
for edge in model.edges {
out.write_string("\n \"")
out.write_string(edge.from)
out.write_string("\" -> \"")
out.write_string(edge.to)
out.write_string("\" [label=\"")
out.write_string(edge.label)
out.write_string("\"];")
}
out.write_string("\n}")
out.to_string()
}
///|
pub fn export_graph_text(model : Model) -> String {
let graph = graph_export(model, "text")
let out = StringBuilder()
out.write_string("graph digest=\{graph.digest}")
out.write_string(
"\nnodes=\{graph.nodes.length()} edges=\{graph.edges.length()}",
)
for node in graph.nodes {
out.write_string("\nnode \{node.id} kind=\{node.kind}")
out.write_string(" in=\{node.incoming} out=\{node.outgoing}")
out.write_string(" source_reachable=\{node.source_reachable}")
out.write_string(" sink_reachable=\{node.sink_reachable}")
}
for edge in graph.edges {
out.write_string(
"\nedge \{edge.from} -> \{edge.to} label=\"\{edge.label}\"",
)
}
out.to_string()
}
///|
pub fn graph_export(model : Model, format : String) -> GraphExport {
let nodes : Array[GraphNodeRecord] = []
let edges : Array[GraphEdgeRecord] = []
for node in model.nodes {
nodes.push({
id: node.name,
kind: node_kind_name(node.kind),
description: node.description,
incoming: graph_incoming(model, node.name),
outgoing: graph_outgoing(model, node.name),
source_reachable: has_reachable_source(model, node.name),
sink_reachable: has_reachable_sink(model, node.name),
})
}
for edge in model.edges {
edges.push({
from: edge.from,
to: edge.to,
label: edge.label,
boundary_crossing: is_boundary_crossing(model, edge),
})
}
{ format, nodes, edges, digest: graph_digest(model) }
}
///|
pub fn graph_digest(model : Model) -> String {
let mut score = model.nodes.length() * 31 + model.edges.length() * 17
for node in model.nodes {
score += node.name.length() * 3 + node_kind_name(node.kind).length()
}
for edge in model.edges {
score += edge.from.length() + edge.to.length() + edge.label.length()
}
"mtf-\{score}-\{model.nodes.length()}-\{model.edges.length()}"
}
///|
pub fn graph_node_names_by_kind(
model : Model,
kind : NodeKind,
) -> Array[String] {
let result : Array[String] = []
for node in model.nodes {
if node.kind == kind {
result.push(node.name)
}
}
result
}
///|
pub fn graph_critical_nodes(model : Model) -> Array[String] {
let result : Array[String] = []
for node in model.nodes {
let in_degree = graph_incoming(model, node.name)
let out_degree = graph_outgoing(model, node.name)
if in_degree > 1 || out_degree > 1 || node.kind == Boundary {
result.push(node.name)
}
}
result
}
///|
pub fn graph_boundary_edges(model : Model) -> Array[Edge] {
let result : Array[Edge] = []
for edge in model.edges {
if is_boundary_crossing(model, edge) {
result.push(edge)
}
}
result
}
///|
fn graph_incoming(model : Model, name : String) -> Int {
let mut count = 0
for edge in model.edges {
if edge.to == name {
count += 1
}
}
count
}
///|
fn graph_outgoing(model : Model, name : String) -> Int {
let mut count = 0
for edge in model.edges {
if edge.from == name {
count += 1
}
}
count
}
///|
fn graph_kind(model : Model, name : String) -> NodeKind? {
for node in model.nodes {
if node.name == name {
return Some(node.kind)
}
}
None
}
///|
fn is_boundary_crossing(model : Model, edge : Edge) -> Bool {
match (graph_kind(model, edge.from), graph_kind(model, edge.to)) {
(Some(Boundary), _) => true
(_, Some(Boundary)) => true
(Some(Source), Some(Sink)) => true
_ => false
}
}