@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
170 lines • 8.58 kB
JavaScript
;
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