@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
264 lines • 13.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataflowMermaid = void 0;
exports.mermaidNodeBrackets = mermaidNodeBrackets;
exports.printIdentifier = printIdentifier;
exports.diffGraphsToMermaid = diffGraphsToMermaid;
exports.diffGraphsToMermaidUrl = diffGraphsToMermaidUrl;
const mermaid_1 = require("./mermaid");
const graph_1 = require("../../dataflow/graph/graph");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const identifier_1 = require("../../dataflow/environments/identifier");
const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const edge_1 = require("../../dataflow/graph/edge");
const vertex_1 = require("../../dataflow/graph/vertex");
const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
const info_1 = require("./info");
const range_1 = require("../range");
const model_1 = require("../../r-bridge/lang-4.x/ast/model/model");
const info_2 = require("../../dataflow/info");
function subflowToMermaid(nodeId, subflow, mermaid, idPrefix = '') {
if (subflow === undefined) {
return;
}
const subflowId = mermaid_1.Mermaid.escapeId(`${idPrefix}flow-${nodeId}`);
if (mermaid.simplified) {
// get parent
const idMap = mermaid.rootGraph.idMap;
const node = idMap?.get(nodeId);
const nodeLexeme = model_1.RNode.lexeme(node) ?? 'function';
const location = node?.location?.[0] ? ` (L. ${node?.location?.[0]})` : '';
mermaid.nodeLines.push(`\nsubgraph "${subflowId}" ["${mermaid_1.Mermaid.escape(nodeLexeme)}${location}"]`);
}
else {
mermaid.nodeLines.push(`\nsubgraph "${subflowId}" [function ${nodeId}]`);
}
const subgraph = graphToMermaidGraph(subflow.graph, {
graph: mermaid.rootGraph,
rootGraph: mermaid.rootGraph,
idPrefix,
includeEnvironments: mermaid.includeEnvironments,
mark: mermaid.mark,
prefix: null,
simplified: mermaid.simplified
});
mermaid.nodeLines.push(...subgraph.nodeLines);
mermaid.edgeLines.push(...subgraph.edgeLines);
for (const present of subgraph.presentEdges) {
mermaid.presentEdges.add(present);
}
for (const [color, pool] of [['purple', subflow.in], ['green', subflow.out], ['orange', subflow.unknownReferences]]) {
for (const out of pool) {
if (!mermaid.mark?.has(out.nodeId)) {
// in/out/active for unmarked
mermaid.nodeLines.push(` style ${idPrefix}${out.nodeId} stroke:${color},stroke-width:4px; `);
}
}
}
mermaid.nodeLines.push('end');
mermaid.edgeLines.push(`${idPrefix}${nodeId} -.-|function| ${subflowId}\n`);
/* mark edge as present */
const edgeId = encodeEdge(idPrefix + nodeId, subflowId, new Set(['function']));
mermaid.presentEdges.add(edgeId);
}
function printArg(arg) {
if (arg === undefined) {
return '??';
}
else if (arg === r_function_call_1.EmptyArgument) {
return '[empty]';
}
else if (graph_1.FunctionArgument.isNamed(arg)) {
const deps = arg.cds ? ', :may:' + arg.cds.map(c => c.id + (c.when ? '+' : '-')).join(',') : '';
return `${arg.name} (${arg.nodeId}${deps})`;
}
else if (graph_1.FunctionArgument.isPositional(arg)) {
const deps = arg.cds ? ' (:may:' + arg.cds.map(c => c.id + (c.when ? '+' : '-')).join(',') + ')' : '';
return `${arg.nodeId}${deps}`;
}
else {
return '??';
}
}
function displayFunctionArgMapping(argMapping) {
const result = [];
for (const arg of argMapping) {
result.push(mermaid_1.Mermaid.escape(printArg(arg)));
}
return result.length === 0 ? '' : `\n (${result.join(', ')})`;
}
function encodeEdge(from, to, types) {
return `${from}->${to}["${Array.from(types).join(':')}"]`;
}
/**
* Translates a vertex tag to the corresponding mermaid node brackets.
*/
function mermaidNodeBrackets(tag) {
let open;
let close;
if (tag === vertex_1.VertexType.FunctionDefinition || tag === vertex_1.VertexType.VariableDefinition) {
open = '[';
close = ']';
}
else if (tag === vertex_1.VertexType.FunctionCall) {
open = '[[';
close = ']]';
}
else if (tag === 'value') {
open = '{{';
close = '}}';
}
else {
open = '([';
close = '])';
}
return { open, close };
}
/**
* Prints an identifier definition in a human-readable format.
*/
function printIdentifier(id) {
return `**${id.name ? identifier_1.Identifier.toString(id.name) : 'undefined'}** (id: ${id.nodeId}, type: ${identifier_1.ReferenceTypeReverseMapping.get(id.type)},${id.cds ? ' cds: {' + id.cds.map(c => c.id + (c.when ? '+' : '-')).join(',') + '},' : ''} def. @${id.definedAt})`;
}
function printEnvironmentToLines(env) {
if (env === undefined) {
return ['??'];
}
else if (env.builtInEnv) {
return ['Built-in'];
}
const lines = [...printEnvironmentToLines(env.parent), `${env.id}${'-'.repeat(40)}`];
const longestName = Math.max(...[...env.memory.keys()].map(x => x.length));
for (const [name, defs] of env.memory.entries()) {
const printName = `${name}:`;
lines.push(` ${printName.padEnd(longestName + 1, ' ')} {${defs.map(printIdentifier).join(', ')}}`);
}
return lines;
}
function vertexToMermaid(info, mermaid, id, idPrefix, mark, includeOnlyIds) {
const fCall = info.tag === vertex_1.VertexType.FunctionCall;
const { open, close } = mermaidNodeBrackets(info.tag);
const origId = id;
id = mermaid_1.Mermaid.escapeId(id);
if (info.environment && mermaid.includeEnvironments) {
if (info.environment.level > 0 || info.environment.current.memory.size !== 0) {
mermaid.nodeLines.push(` %% Environment of ${id} [level: ${info.environment.level}]:`, printEnvironmentToLines(info.environment.current).map(x => ` %% ${x}`).join('\n'));
}
}
const node = mermaid.rootGraph.idMap?.get(info.id);
const lexeme = node?.lexeme ?? (node?.type === type_1.RType.ExpressionList ? node?.grouping?.[0]?.lexeme : '') ?? '??';
if (mermaid.simplified) {
const location = node?.location?.[0] ? ` (L. ${node?.location?.[0]})` : '';
const escapedName = '**' + mermaid_1.Mermaid.escape(node ? `${lexeme}` : '??') + '**' + location + (node ? `\n*${node.type}*` : '');
mermaid.nodeLines.push(` ${idPrefix}${id}${open}"\`${escapedName}\`"${close}`);
}
else {
const escapedName = mermaid_1.Mermaid.escape(node ? `[${node.type}] ${lexeme}` : '??');
const deps = info.cds ? ', :may:' + info.cds.map(c => c.id + (c.when ? '+' : '-')).join(',') : '';
const lnks = info.link?.origin ? ', :links:' + info.link.origin.join(',') : '';
const sources = info.source ? ', sources: ' + JSON.stringify(info.source) : '';
const n = node?.info.fullRange ?? node?.location ?? (node?.type === type_1.RType.ExpressionList ? node?.grouping?.[0].location : undefined);
mermaid.nodeLines.push(` ${idPrefix}${id}${open}"\`${escapedName}${escapedName.length > 10 ? '\n ' : ' '}(${id}${deps}${lnks}${sources})\n *${range_1.SourceRange.format(n)}*${fCall ? displayFunctionArgMapping(info.args) : '' + (info.tag === vertex_1.VertexType.FunctionDefinition && info.mode && info.mode.length > 0 ? mermaid_1.Mermaid.escape(JSON.stringify(info.mode)) : '')}\`"${close}`);
}
if (mark?.has(id)) {
mermaid.nodeLines.push(` style ${idPrefix}${id} ${mermaid.markStyle.vertex} `);
}
if (mermaid.rootGraph.unknownSideEffects.values().some(l => node_id_1.NodeId.normalize(l) === node_id_1.NodeId.normalize(origId))) {
mermaid.nodeLines.push(` style ${idPrefix}${id} stroke:red,stroke-width:5px; `);
}
if (info.tag === vertex_1.VertexType.FunctionDefinition) {
subflowToMermaid(id, info.subflow, mermaid, idPrefix);
}
const edges = mermaid.rootGraph.outgoingEdges(node_id_1.NodeId.normalize(origId));
if (edges === undefined) {
mermaid.nodeLines.push(' %% No edges found for ' + id);
return;
}
const artificialCdEdges = (info.cds ?? []).map(x => [x.id, { types: new Set([x.when ? 'CD-True' : 'CD-False']), file: x.file }]);
// eslint-disable-next-line prefer-const
for (let [target, edge] of [...edges, ...artificialCdEdges]) {
if (includeOnlyIds && !includeOnlyIds.has(target)) {
continue;
}
const originalTarget = target;
target = mermaid_1.Mermaid.escapeId(target);
const edgeTypes = typeof edge.types == 'number' ? new Set(edge_1.DfEdge.splitTypes(edge)) : edge.types;
const edgeId = encodeEdge(idPrefix + id, idPrefix + target, edgeTypes);
if (!mermaid.presentEdges.has(edgeId)) {
mermaid.presentEdges.add(edgeId);
const style = node_id_1.NodeId.isBuiltIn(target) ? '-.->' : '-->';
mermaid.edgeLines.push(` ${idPrefix}${id} ${style}|"${[...edgeTypes].map(e => typeof e === 'number' ? edge_1.DfEdge.typeToName(e) : e).join(', ')}${'file' in edge && edge.file ? `, from: ${edge.file}` : ''}"| ${idPrefix}${target}`);
if (mermaid.mark?.has(id + '->' + target)) {
// who invented this syntax?!
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} ${mermaid.markStyle.edge}`);
}
if (edgeTypes.has('CD-True') || edgeTypes.has('CD-False')) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:gray,color:gray;`);
}
if (node_id_1.NodeId.isBuiltIn(target)) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:gray;`);
if (!mermaid.presentVertices.has(target)) {
mermaid.nodeLines.push(` ${idPrefix}${target}["\`Built-In:\n${mermaid_1.Mermaid.escape(String(originalTarget).replace('built-in:', ''))}\`"]`);
mermaid.nodeLines.push(` style ${idPrefix}${target} stroke:gray,fill:gray,stroke-width:2px,opacity:.8;`);
mermaid.presentVertices.add(target);
}
}
}
}
}
// make the passing of root ids more performant again
function graphToMermaidGraph(rootIds, { simplified, graph, prefix = 'flowchart BT', idPrefix = '', includeEnvironments = !simplified, mark, rootGraph, presentEdges = new Set(), markStyle = info_1.MermaidDefaultMarkStyle, includeOnlyIds }) {
const mermaid = { nodeLines: prefix === null ? [] : [prefix], edgeLines: [], presentEdges, presentVertices: new Set(), mark, rootGraph: rootGraph ?? graph, includeEnvironments, markStyle, simplified };
for (const [id, info] of graph.vertices(true)) {
if (rootIds.has(id)) {
vertexToMermaid(info, mermaid, id, idPrefix, mark, includeOnlyIds);
}
}
return mermaid;
}
/** uses same id map but ensures, it is different from the rhs so that mermaid can work with that */
function diffGraphsToMermaid(left, right, prefix) {
// we add the prefix ourselves
const { string: leftGraph, mermaid } = exports.DataflowMermaid.convert({ graph: left.graph, prefix: '', idPrefix: `l-${left.label}`, includeEnvironments: true, mark: left.mark });
const { string: rightGraph } = exports.DataflowMermaid.convert({ graph: right.graph, prefix: '', idPrefix: `r-${right.label}`, includeEnvironments: true, mark: right.mark, presentEdges: mermaid.presentEdges });
return `${prefix}flowchart BT\nsubgraph "${left.label}"\n${leftGraph}\nend\nsubgraph "${right.label}"\n${rightGraph}\nend`;
}
/**
* Converts two dataflow graphs to a mermaid url that visualizes their differences.
*/
function diffGraphsToMermaidUrl(left, right, prefix) {
return mermaid_1.Mermaid.codeToUrl(diffGraphsToMermaid(left, right, prefix));
}
/**
* The helper object for all things regarding the mermaid based visualization of dataflow graphs!
*/
exports.DataflowMermaid = {
name: 'DataflowMermaid',
/**
* Converts a dataflow graph to mermaid graph code that visualizes the graph.
* @see {@link DataflowMermaid.url} - render the given graph to a url to mermaid.live
*/
convert(config) {
const mermaid = graphToMermaidGraph(config.includeOnlyIds ?? config.graph.rootIds(), config);
return { string: `${mermaid.nodeLines.join('\n')}\n${mermaid.edgeLines.join('\n')}`, mermaid };
},
/**
* This is a simplified version of {@link DataflowMermaid.convertToMermaid}
*/
raw(graph, includeEnvironments, mark, simplified = false) {
graph = info_2.DataflowInformation.is(graph) ? graph.graph : graph;
return exports.DataflowMermaid.convert({ graph, includeEnvironments, mark, simplified }).string;
},
/**
* Converts a dataflow graph to a mermaid url that visualizes the graph.
* This is basically a combination of {@link DataflowMermaid.mermaidRaw} and {@link Mermaid.codeToUrl}.
* @param graph - the dataflow graph to render
* @param includeEnvironments - whether to include the environment content in the output
* @param mark - which vertices to highlight in the visualization
* @param simplified - whether to show a simplified use of the graph with fewer details on the vertices and edges
*/
url(graph, includeEnvironments, mark, simplified = false) {
return mermaid_1.Mermaid.codeToUrl(exports.DataflowMermaid.raw(graph, includeEnvironments, mark, simplified));
}
};
//# sourceMappingURL=dfg.js.map