UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

170 lines 8.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MermaidExitPointDefaultMarkStyle = exports.MermaidEntryPointDefaultMarkStyle = void 0; exports.cfgToMermaid = cfgToMermaid; exports.cfgToMermaidUrl = cfgToMermaidUrl; const control_flow_graph_1 = require("../../control-flow/control-flow-graph"); const reconstruct_1 = require("../../reconstruct/reconstruct"); const auto_select_defaults_1 = require("../../reconstruct/auto-select/auto-select-defaults"); const info_1 = require("./info"); const model_1 = require("../../r-bridge/lang-4.x/ast/model/model"); const mermaid_1 = require("./mermaid"); exports.MermaidEntryPointDefaultMarkStyle = 'stroke:cyan,stroke-width:6.5px;'; exports.MermaidExitPointDefaultMarkStyle = 'stroke:green,stroke-width:6.5px;'; function getLexeme(n) { return n ? model_1.RNode.lexeme(n) ?? '' : undefined; } function cfgOfNode(vert, normalizedVertex, id, content, output) { if (normalizedVertex && content !== undefined) { const start = control_flow_graph_1.CfgVertex.isExpression(vert) ? '([' : '['; const end = control_flow_graph_1.CfgVertex.isExpression(vert) ? '])' : ']'; const name = `"\`${mermaid_1.Mermaid.escape(normalizedVertex.type)} (${id})${content ? '\n' + mermaid_1.Mermaid.escape(JSON.stringify(content)) : ''}${control_flow_graph_1.CfgVertex.getCallTargets(vert) ? '\n calls:' + mermaid_1.Mermaid.escape(JSON.stringify([...control_flow_graph_1.CfgVertex.getCallTargets(vert)])) : ''}\`"`; output += ` n${id}${start}${name}${end}\n`; } else { output += String(id).endsWith('-exit') ? ` n${id}((${id}))\n` : ` n${id}[[${id}]]\n`; } return output; } const getDirRegex = /flowchart\s+([A-Za-z]+)/; function shouldIncludeNode(simplify, v, include) { if (simplify) { // Only basic blocks are shown, so include the BB, if at least one child is selected return control_flow_graph_1.CfgVertex.isBlock(v) && control_flow_graph_1.CfgVertex.getBasicBlockElements(v) .filter(elem => !control_flow_graph_1.CfgVertex.isMarker(elem)) .some(elem => include.has(control_flow_graph_1.CfgVertex.getId(elem))); } else { // Basic blocks and vertices are shown, include the BB, if all children are highlighted return control_flow_graph_1.CfgVertex.isBlock(v) ? control_flow_graph_1.CfgVertex.getBasicBlockElements(v) .filter(elem => !control_flow_graph_1.CfgVertex.isMarker(elem)) .every(elem => include.has(control_flow_graph_1.CfgVertex.getId(elem))) : include.has(control_flow_graph_1.CfgVertex.getId(v)); } } /** * Convert the control flow graph to a mermaid string. * @see {@link MermaidCfgGraphPrinterInfo} for additional options. */ function cfgToMermaid(cfg, normalizedAst, { prefix = 'flowchart BT\n', simplify = false, markStyle = info_1.MermaidDefaultMarkStyle, entryPointStyle = exports.MermaidEntryPointDefaultMarkStyle, exitPointStyle = exports.MermaidExitPointDefaultMarkStyle, includeOnlyIds, mark, includeBasicBlockLabel = true, basicBlockCharacterLimit = 100 } = {}) { const hasBbandSimplify = simplify && cfg.graph.mayHaveBasicBlocks(); let output = prefix; if (includeOnlyIds) { const completed = new Set(includeOnlyIds); // foreach nast id we add all children for (const id of includeOnlyIds.values()) { const nastNode = normalizedAst.idMap.get(id); if (!nastNode) { continue; } for (const childId of model_1.RNode.collectAllIds(nastNode)) { completed.add(childId); } } // if we have a filter, we automatically add all vertices in the cfg that are *markers* for these ids and for (const [id, v] of cfg.graph.vertices()) { if (control_flow_graph_1.CfgVertex.isMarker(v) && completed.has(control_flow_graph_1.CfgVertex.unpackRootId(v))) { completed.add(id); } } includeOnlyIds = completed; } const dirIs = getDirRegex.exec(prefix)?.at(1) ?? 'LR'; const diagramIncludedIds = new Set(); for (const [id, vertex] of cfg.graph.vertices(false)) { const normalizedVertex = normalizedAst?.idMap.get(id); const content = getLexeme(normalizedVertex); if (control_flow_graph_1.CfgVertex.isBlock(vertex)) { const elems = control_flow_graph_1.CfgVertex.getBasicBlockElements(vertex); if (simplify) { if (includeOnlyIds && !elems.some(elem => includeOnlyIds.has(control_flow_graph_1.CfgVertex.getId(elem)))) { continue; } const ids = elems?.map(control_flow_graph_1.CfgVertex.getId) ?? []; const reconstruct = limitTo((0, reconstruct_1.reconstructToCode)(normalizedAst, { nodes: new Set(ids) }, auto_select_defaults_1.doNotAutoSelect).code, basicBlockCharacterLimit); const name = `"\`${includeBasicBlockLabel ? `Basic Block (${id})\n` : ''}${mermaid_1.Mermaid.escape(reconstruct)}\`"`; output += ` n${id}[[${name}]]\n`; diagramIncludedIds.add(control_flow_graph_1.CfgVertex.getId(vertex)); } else { if (includeOnlyIds && !elems.some(elem => includeOnlyIds.has(control_flow_graph_1.CfgVertex.getId(elem)))) { continue; } output += ` subgraph n${id} [Block ${normalizedVertex?.info.fullLexeme ?? id}]\n`; output += ` direction ${dirIs}\n`; diagramIncludedIds.add(id); let last = undefined; for (const element of elems ?? []) { if (includeOnlyIds && !includeOnlyIds.has(control_flow_graph_1.CfgVertex.getId(element))) { last = undefined; continue; } const eid = control_flow_graph_1.CfgVertex.getId(element); const childNormalizedVertex = normalizedAst?.idMap.get(eid); const childContent = getLexeme(childNormalizedVertex); output = cfgOfNode(vertex, childNormalizedVertex, eid, childContent, output); diagramIncludedIds.add(eid); // just to keep the order if (last) { output += ` ${last} -.-> n${eid}\n`; } last = `n${eid}`; } output += ' end\n'; } } else if (!includeOnlyIds || includeOnlyIds.has(id)) { output = cfgOfNode(vertex, normalizedVertex, id, content, output); diagramIncludedIds.add(id); } } for (const [from, targets] of cfg.graph.edges()) { if (!diagramIncludedIds.has(from)) { continue; } for (const [to, edge] of targets) { if (!diagramIncludedIds.has(to)) { continue; } const isCd = control_flow_graph_1.CfgEdge.isControlDependency(edge); const edgeType = isCd ? '-->' : '-.->'; const edgeSuffix = isCd ? ` (${control_flow_graph_1.CfgEdge.unpackWhen(edge)})` : ''; output += ` n${from} ${edgeType}|"${mermaid_1.Mermaid.escape(control_flow_graph_1.CfgEdge.typeToString(edge))}${edgeSuffix}"| n${to}\n`; } } for (const entryPoint of cfg.entryPoints) { if (diagramIncludedIds.has(entryPoint)) { output += ` style n${entryPoint} ${entryPointStyle}`; } } for (const exitPoint of cfg.exitPoints) { if (diagramIncludedIds.has(exitPoint)) { output += ` style n${exitPoint} ${exitPointStyle}`; } } if (mark) { for (const [id, vertex] of cfg.graph.vertices(true)) { if (shouldIncludeNode(hasBbandSimplify, vertex, mark)) { output += ` style n${id} ${markStyle.vertex}`; } } } return output; } /** * Use mermaid to visualize the normalized AST. */ function cfgToMermaidUrl(cfg, normalizedAst, info) { return mermaid_1.Mermaid.codeToUrl(cfgToMermaid(cfg, normalizedAst, info ?? {})); } /** * Limits a string to n chars, after which the remainder will be replaced with ... */ function limitTo(str, limit) { if (str.length <= limit) { return str; } return `${str.substring(0, limit)}...`; } //# sourceMappingURL=cfg.js.map