1/* 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseEdge, BaseNode, NodeID } from '../core/graph/BaseExplicitGraph'; 17import { GraphTraits } from '../core/graph/GraphTraits'; 18import { Printer } from './Printer'; 19 20function escapeStr(input: string): string { 21 let str = input; 22 for (let i = 0; i < str.length; ++i) { 23 switch (str[i]) { 24 case '\n': 25 str = str.substring(0, i) + '\\n' + str.substring(i + 1); 26 ++i; 27 break; 28 case '\t': 29 str = str.substring(0, i) + ' ' + str.substring(i + 1); 30 ++i; 31 break; 32 case '\\': 33 if (i + 1 < str.length) { 34 switch (str[i + 1]) { 35 case 'l': 36 continue; // don't disturb \l 37 case '|': 38 case '{': 39 case '}': 40 str = str.substring(0, i) + str.substring(i + 1); 41 continue; 42 default: 43 break; 44 } 45 } 46 str = str.substring(0, i) + '\\\\' + str.substring(i + 1); 47 ++i; 48 break; 49 case '{': 50 case '}': 51 case '<': 52 case '>': 53 case '|': 54 case '"': 55 str = str.substring(0, i) + '\\' + str[i] + str.substring(i + 1); 56 ++i; 57 break; 58 default: 59 } 60 } 61 return str; 62} 63 64export class GraphPrinter<GraphType extends GraphTraits<BaseNode>> extends Printer { 65 graph: GraphType; 66 title!: string; 67 startID: NodeID | undefined = undefined; 68 69 constructor(g: GraphType, t?: string) { 70 super(); 71 this.graph = g; 72 if (t) { 73 this.title = t; 74 } 75 } 76 77 public setStartID(n: NodeID): void { 78 this.startID = n; 79 } 80 81 public dump(): string { 82 this.printer.clear(); 83 this.writeGraph(); 84 return this.printer.toString(); 85 } 86 87 public writeGraph(): void { 88 this.writeHeader(); 89 this.writeNodes(); 90 this.writeFooter(); 91 } 92 93 public writeNodes(): void { 94 let itor: IterableIterator<BaseNode> = this.graph.nodesItor(); 95 if (this.startID) { 96 // from start id 97 let nodes = new Set<BaseNode>(); 98 let startNode = this.graph.getNode(this.startID)!; 99 let worklist = [startNode]; 100 while (worklist.length > 0) { 101 let n = worklist.shift()!; 102 if (nodes.has(n)) { 103 continue; 104 } 105 nodes.add(n); 106 n.getOutgoingEdges()?.forEach(e => worklist.push(e.getDstNode())); 107 } 108 itor = nodes.values(); 109 } 110 111 for (let node of itor) { 112 let nodeAttr = node.getDotAttr(); 113 if (nodeAttr === '') { 114 continue; 115 } 116 let nodeLabel = escapeStr(node.getDotLabel()); 117 118 this.printer.writeLine(`\tNode${node.getID()} [shape=recode,${nodeAttr},label="${nodeLabel}"];`); 119 120 for (let edge of node.getOutgoingEdges()) { 121 this.writeEdge(edge); 122 } 123 } 124 } 125 126 public writeEdge(edge: BaseEdge): void { 127 let edgeAttr = edge.getDotAttr(); 128 if (edgeAttr === '') { 129 return; 130 } 131 this.printer.writeLine(`\tNode${edge.getSrcID()} -> Node${edge.getDstID()}[${edgeAttr}]`); 132 } 133 134 public writeHeader(): void { 135 const GraphName = this.graph.getGraphName(); 136 137 let graphNameStr = `digraph "${escapeStr(this.title || GraphName || 'unnamed')}" {\n`; 138 this.printer.writeLine(graphNameStr); 139 140 let labelStr = `\tlabel="${escapeStr(this.title || GraphName)}";\n`; 141 this.printer.writeLine(labelStr); 142 143 // TODO: need graph attr? 144 } 145 146 public writeFooter(): void { 147 this.printer.writeLine('}\n'); 148 } 149} 150