@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
272 lines • 9.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.dfgToAscii = dfgToAscii;
const dagre_1 = require("dagre");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const vertex_1 = require("../../dataflow/graph/vertex");
const edge_1 = require("../../dataflow/graph/edge");
function combineAscii(has, add) {
if (has === ' ' || has === add) {
return add;
}
const [a, b] = [has, add].sort();
let res = add;
if (b === edgesChar.vertical && a === edgesChar.horizontal) {
res = '┼';
}
else if ((b === edgesChar.topRight || b === edgesChar.topLeft || b === '┬') && a === edgesChar.horizontal) {
res = '┬';
}
else if ((b === edgesChar.bottomRight || b === edgesChar.bottomLeft || b === '┴') && a === edgesChar.horizontal) {
res = '┴';
}
else if ((b === edgesChar.topRight || b === edgesChar.bottomRight || b === '┤') && a === edgesChar.vertical) {
res = '┤';
}
else if ((b === edgesChar.topLeft || b === edgesChar.bottomLeft || b === '├') && a === edgesChar.vertical) {
res = '├';
}
else if ((b === '┤' || b === '├') && a === edgesChar.horizontal) {
res = '┼';
}
else if ((b === '┬' || b === '┴') && a === edgesChar.vertical) {
res = '┼';
}
return res;
}
class AsciiCanvas {
filler;
grid;
shiftX;
shiftY;
constructor(filler = ' ', shiftX = 0, shiftY = 0) {
this.grid = [];
this.filler = filler;
this.shiftX = shiftX;
this.shiftY = shiftY;
}
set(x, y, char, overwrite = false) {
x += this.shiftX;
y += this.shiftY;
if (x < 0 || y < 0 || isNaN(x) || isNaN(y)) {
return;
}
while (this.grid.length <= y) {
this.grid.push([]);
}
while (this.grid[y].length <= x) {
this.grid[y].push(this.filler);
}
this.grid[y][x] = overwrite ? char : combineAscii(this.grid[y][x], char);
}
drawText(x, y, text) {
for (let i = 0; i < text.length; i++) {
this.set(x + i, y, text[i]);
}
}
toString() {
return this.grid.map(r => r.join('')).join('\n');
}
}
/**
* Converts the given dataflow graph to an ASCII representation.
*/
function dfgToAscii(dfg) {
const g = new dagre_1.graphlib.Graph();
const verts = Array.from(dfg.vertices(true));
g.setGraph({
nodesep: 1,
ranksep: 4,
edgesep: 0,
rankdir: verts.length < 15 ? 'LR' : 'TB',
ranker: 'longest-path',
});
for (const [id, v] of verts) {
let label = (0, node_id_1.recoverName)(id, dfg.idMap) ?? v.tag;
if (label.length < 3) {
label = label.padStart(2, ' ').padEnd(3, ' ');
}
g.setNode(String(id), {
label,
width: Math.max(3, label.length * 1.2),
height: 4,
shape: 'rectangle'
});
}
const edgesDone = new Set();
let longestId = 0;
for (const [from, edges] of dfg.edges()) {
longestId = Math.max(longestId, String(from).length);
for (const [to, e] of edges) {
if (!g.hasNode(String(from)) || !g.hasNode(String(to)) || edgesDone.has(`${to}-${from}`)) {
continue;
}
longestId = Math.max(longestId, String(to).length);
g.setEdge(String(from), String(to), edge_1.DfEdge.typesToNames(e));
edgesDone.add(`${from}-${to}`);
}
}
(0, dagre_1.layout)(g, {
minlen: 2
});
const canvas = new AsciiCanvas();
renderEdges(g, canvas);
renderVertices(dfg, g, canvas);
const lines = canvas.toString().split('\n').filter(line => line.trim() !== '');
const edgeLines = [];
// add all edges
for (const [from, edges] of dfg.edges()) {
for (const [to, e] of edges) {
if (!g.hasNode(String(from)) || !g.hasNode(String(to))) {
continue;
}
edgeLines.push(`${from.toString().padStart(longestId, ' ')} -> ${to.toString().padStart(longestId, ' ')}: ${Array.from(edge_1.DfEdge.typesToNames(e)).join(', ')}`);
}
}
// always merge two edgelines with padding
if (edgeLines.length > 0) {
lines.push('Edges:');
}
const longestFirstLine = Math.max(...edgeLines.map(l => l.length));
for (let i = 0; i < edgeLines.length; i += 2) {
const line1 = edgeLines[i];
const line2 = edgeLines[i + 1];
if (line2) {
lines.push(line1.padEnd(Math.min(50, longestFirstLine), ' ') + line2);
}
else {
lines.push(line1);
}
}
return lines.join('\n');
}
const type2Edge = {
[vertex_1.VertexType.FunctionCall]: 'c',
[vertex_1.VertexType.Use]: 'u',
[vertex_1.VertexType.FunctionDefinition]: 'f',
[vertex_1.VertexType.VariableDefinition]: 'v',
[vertex_1.VertexType.Value]: '0'
};
function renderVertices(dfg, g, canvas) {
for (const nodeId of g.nodes()) {
const node = g.node(nodeId);
if (!node) {
continue;
}
const label = node.label;
const x = Math.round(node.x);
const y = Math.round(node.y);
const tag = dfg.getVertex(node_id_1.NodeId.normalize(nodeId))?.tag;
let e = '+';
if (tag && tag in type2Edge) {
e = type2Edge[tag];
}
canvas.drawText(x - 1, y - 1, `${e}${'-'.repeat(label.length)}${e}`);
canvas.drawText(x - 1 + Math.round(label.length / 2 - nodeId.length / 2), y - 1, `<${nodeId}>`);
canvas.drawText(x - 1, y, `|${label}|`);
canvas.drawText(x - 1, y + 1, `${e}${'-'.repeat(label.length)}${e}`);
}
}
const edgesChar = {
vertical: '│',
horizontal: '─',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
};
function determineCornerChar(lastDirection, px, py, tx, ty) {
if (px === tx) {
return edgesChar.vertical;
}
else if (py === ty) {
return edgesChar.horizontal;
}
else if (px < tx) {
if (py < ty) {
return lastDirection === 'horizontal' ? edgesChar.topRight : edgesChar.bottomLeft;
}
else {
return lastDirection === 'horizontal' ? edgesChar.bottomRight : edgesChar.topLeft;
}
}
else {
if (py < ty) {
return lastDirection === 'horizontal' ? edgesChar.topLeft : edgesChar.bottomRight;
}
else {
return lastDirection === 'horizontal' ? edgesChar.bottomLeft : edgesChar.topRight;
}
}
}
function renderEdges(g, canvas) {
const otherEdges = new Set();
for (const e of g.edges()) {
const edge = g.edge(e);
let points = edge.points;
// we rework edges into sequences of straight lines only, adding intermediate points as needed
const newPoints = [points[0]];
for (let i = 1; i < points.length; i++) {
const prev = points[i - 1];
const curr = points[i];
if (prev.x !== curr.x && prev.y !== curr.y) {
const intermediate = { x: prev.x, y: curr.y };
newPoints.push(intermediate);
}
newPoints.push(curr);
}
points = newPoints;
let lastDirection = null;
// let single edges overwrite themselves
const writtenPoints = new Set();
for (let i = 0; i < points.length - 1; i++) {
const p = points[i - 1] ?? points[i];
const a = points[i];
const b = points[i + 1];
const px = Math.round(p.x);
const py = Math.round(p.y);
const x1 = Math.round(a.x);
const y1 = Math.round(a.y);
const x2 = Math.round(b.x);
const y2 = Math.round(b.y);
if (x1 === x2) {
// vertical
const [start, end] = y1 < y2 ? [y1, y2] : [y2, y1];
for (let y = start; y <= end; y++) {
const key = `${x1},${y}`;
const overwrite = writtenPoints.has(key) && !otherEdges.has(key);
if (y === (y1 < y2 ? start : end)) {
const cornerChar = determineCornerChar(lastDirection, px, py, x2, y2);
canvas.set(x1, y, cornerChar, overwrite);
}
else {
canvas.set(x1, y, edgesChar.vertical, overwrite);
}
writtenPoints.add(`${x1},${y}`);
}
lastDirection = 'vertical';
}
else if (y1 === y2) {
// horizontal
const [start, end] = x1 < x2 ? [x1, x2] : [x2, x1];
for (let x = start; x <= end; x++) {
const key = `${x},${y1}`;
const overwrite = writtenPoints.has(key) && !otherEdges.has(key);
if (x === (x1 < x2 ? start : end)) {
const cornerChar = determineCornerChar(lastDirection, px, py, x2, y2);
canvas.set(x, y1, cornerChar, overwrite);
}
else {
canvas.set(x, y1, edgesChar.horizontal, overwrite);
}
writtenPoints.add(`${x},${y1}`);
}
lastDirection = 'horizontal';
}
}
for (const p of writtenPoints) {
otherEdges.add(p);
}
}
}
//# sourceMappingURL=dfg-ascii.js.map