UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

231 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatRange = formatRange; exports.printIdentifier = printIdentifier; exports.graphToMermaid = graphToMermaid; exports.graphToMermaidUrl = graphToMermaidUrl; exports.diffGraphsToMermaid = diffGraphsToMermaid; exports.diffGraphsToMermaidUrl = diffGraphsToMermaidUrl; const assert_1 = require("../assert"); const mermaid_1 = require("./mermaid"); const graph_1 = require("../../dataflow/graph/graph"); 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 environment_1 = require("../../dataflow/environments/environment"); const type_1 = require("../../r-bridge/lang-4.x/ast/model/type"); /** * Prints a {@link SourceRange|range} as a human readable string. */ function formatRange(range) { if (range === undefined) { return '??-??'; } else if (range[0] === range[2]) { if (range[1] === range[3]) { return `${range[0]}.${range[1]}`; } else { return `${range[0]}.${range[1]}-${range[3]}`; } } return `${range[0]}.${range[1]}-${range[2]}.${range[3]}`; } function subflowToMermaid(nodeId, exitPoints, subflow, mermaid, idPrefix = '') { if (subflow === undefined) { return; } const subflowId = `${idPrefix}flow-${nodeId}`; if (mermaid.simplified) { // get parent const idMap = mermaid.rootGraph.idMap; const node = idMap?.get(nodeId); const nodeLexeme = node?.info.fullLexeme ?? node?.lexeme ?? '??'; const location = node?.location?.[0] ? ` (L. ${node?.location?.[0]})` : ''; mermaid.nodeLines.push(`\nsubgraph "${subflowId}" ["${(0, mermaid_1.escapeMarkdown)(nodeLexeme ?? 'function')}${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 ((0, graph_1.isNamedArgument)(arg)) { const deps = arg.controlDependencies ? ', :may:' + arg.controlDependencies.map(c => c.id + (c.when ? '+' : '-')).join(',') : ''; return `${arg.name} (${arg.nodeId}${deps})`; } else if ((0, graph_1.isPositionalArgument)(arg)) { const deps = arg.controlDependencies ? ' (:may:' + arg.controlDependencies.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(printArg(arg)); } return result.length === 0 ? '' : `\n (${result.join(', ')})`; } function encodeEdge(from, to, types) { return `${from}->${to}["${[...types].join(':')}"]`; } function mermaidNodeBrackets(tag) { let open; let close; if (tag === 'function-definition' || tag === 'variable-definition') { open = '['; close = ']'; } else if (tag === vertex_1.VertexType.FunctionCall) { open = '[['; close = ']]'; } else if (tag === 'value') { open = '{{'; close = '}}'; } else { open = '(['; close = '])'; } return { open, close }; } function printIdentifier(id) { return `**${id.name}** (id: ${id.nodeId}, type: ${identifier_1.ReferenceTypeReverseMapping.get(id.type)},${id.controlDependencies ? ' cds: {' + id.controlDependencies.map(c => c.id + (c.when ? '+' : '-')).join(',') + '},' : ''} def. @${id.definedAt})`; } function printEnvironmentToLines(env) { if (env === undefined) { return ['??']; } else if (env.id === environment_1.BuiltInEnvironment.id) { 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) { const fCall = info.tag === vertex_1.VertexType.FunctionCall; const { open, close } = mermaidNodeBrackets(info.tag); 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 = '**' + (0, mermaid_1.escapeMarkdown)(node ? `${lexeme}` : '??') + '**' + location + (node ? `\n*${node.type}*` : ''); mermaid.nodeLines.push(` ${idPrefix}${id}${open}"\`${escapedName}\`"${close}`); } else { const escapedName = (0, mermaid_1.escapeMarkdown)(node ? `[${node.type}] ${lexeme}` : '??'); const deps = info.cds ? ', :may:' + info.cds.map(c => c.id + (c.when ? '+' : '-')).join(',') : ''; 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})\n *${formatRange(n)}*${fCall ? displayFunctionArgMapping(info.args) : ''}\`"${close}`); } if (mark?.has(id)) { mermaid.nodeLines.push(` style ${idPrefix}${id} ${mermaid.markStyle.vertex} `); } if (mermaid.rootGraph.unknownSideEffects.has(id)) { mermaid.nodeLines.push(` style ${idPrefix}${id} stroke:red,stroke-width:5px; `); } const edges = mermaid.rootGraph.get(id, true); (0, assert_1.guard)(edges !== undefined, `node ${id} must be found`); const artificialCdEdges = (info.cds ?? []).map(x => [x.id, { types: new Set([x.when ? 'CD-True' : 'CD-False']) }]); for (const [target, edge] of [...edges[1], ...artificialCdEdges]) { const edgeTypes = typeof edge.types == 'number' ? new Set((0, edge_1.splitEdgeTypes)(edge.types)) : edge.types; const edgeId = encodeEdge(idPrefix + id, idPrefix + target, edgeTypes); if (!mermaid.presentEdges.has(edgeId)) { mermaid.presentEdges.add(edgeId); mermaid.edgeLines.push(` ${idPrefix}${id} -->|"${[...edgeTypes].map(e => typeof e === 'number' ? (0, edge_1.edgeTypeToName)(e) : e).join(', ')}"| ${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 (info.tag === 'function-definition') { subflowToMermaid(id, info.exitPoints, info.subflow, mermaid, idPrefix); } } // 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 = { vertex: 'stroke:teal,stroke-width:7px,stroke-opacity:.8;', edge: 'stroke:teal,stroke-width:4.2px,stroke-opacity:.8' } }) { const mermaid = { nodeLines: prefix === null ? [] : [prefix], edgeLines: [], presentEdges, 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); } } return mermaid; } function graphToMermaid(config) { const mermaid = graphToMermaidGraph(config.graph.rootIds(), config); return { string: `${mermaid.nodeLines.join('\n')}\n${mermaid.edgeLines.join('\n')}`, mermaid }; } /** * Converts a dataflow graph to a mermaid url that visualizes the graph. * * @param graph - The graph to convert * @param includeEnvironments - Whether to include the environments in the mermaid graph code * @param mark - Special nodes to mark (e.g., those included in the slice) * @param simplified - Whether to simplify the graph */ function graphToMermaidUrl(graph, includeEnvironments, mark, simplified = false) { return (0, mermaid_1.mermaidCodeToUrl)(graphToMermaid({ graph, includeEnvironments, mark, simplified }).string); } /** 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 } = graphToMermaid({ graph: left.graph, prefix: '', idPrefix: `l-${left.label}`, includeEnvironments: true, mark: left.mark }); const { string: rightGraph } = graphToMermaid({ 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`; } function diffGraphsToMermaidUrl(left, right, prefix) { return (0, mermaid_1.mermaidCodeToUrl)(diffGraphsToMermaid(left, right, prefix)); } //# sourceMappingURL=dfg.js.map