UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

264 lines 13.7 kB
"use strict"; 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