diff --git a/src/cli/repl/commands/cfg.ts b/src/cli/repl/commands/cfg.ts index 1ef0966daf..6b42ce15c6 100644 --- a/src/cli/repl/commands/cfg.ts +++ b/src/cli/repl/commands/cfg.ts @@ -1,10 +1,8 @@ import { ReplCommand } from './main' import { SteppingSlicer } from '../../../core' import { requestFromInput, RShell } from '../../../r-bridge' -import { - cfgToMermaid, cfgToMermaidUrl -} from '../../../util/mermaid' import { extractCFG } from '../../../util/cfg' +import { cfgToMermaid, cfgToMermaidUrl } from '../../../util/mermaid' async function controlflow(shell: RShell, remainingLine: string) { return await new SteppingSlicer({ @@ -15,7 +13,7 @@ async function controlflow(shell: RShell, remainingLine: string) { } export const controlflowCommand: ReplCommand = { - description: 'Get mermaid code for the control-flow graph of R code, start with \'file://\' to indicate a file.', + description: 'Get mermaid code for the control-flow graph of R code, start with \'file://\' to indicate a file', usageExample: ':controlflow', aliases: [ 'cfg' ], script: false, @@ -23,12 +21,12 @@ export const controlflowCommand: ReplCommand = { const result = await controlflow(shell, remainingLine) const cfg = extractCFG(result.normalize) - output.stdout(cfgToMermaid(cfg)) + output.stdout(cfgToMermaid(cfg, result.normalize)) } } export const controlflowStarCommand: ReplCommand = { - description: 'Get a mermaid url of the control-flow graph of R code, start with \'file://\' to indicate a file.', + description: 'Get a mermaid url of the control-flow graph of R code, start with \'file://\' to indicate a file', usageExample: ':controlflow', aliases: [ 'cfg*' ], script: false, @@ -36,6 +34,6 @@ export const controlflowStarCommand: ReplCommand = { const result = await controlflow(shell, remainingLine) const cfg = extractCFG(result.normalize) - output.stdout(cfgToMermaidUrl(cfg)) + output.stdout(cfgToMermaidUrl(cfg, result.normalize)) } } diff --git a/src/cli/repl/commands/dataflow.ts b/src/cli/repl/commands/dataflow.ts index 38938ffad1..40ed906102 100644 --- a/src/cli/repl/commands/dataflow.ts +++ b/src/cli/repl/commands/dataflow.ts @@ -4,7 +4,7 @@ import { requestFromInput, RShell } from '../../../r-bridge' import { graphToMermaid, graphToMermaidUrl -} from '../../../util/mermaid' +} from '../../../util/mermaid/dfg' async function dataflow(shell: RShell, remainingLine: string) { return await new SteppingSlicer({ @@ -15,7 +15,7 @@ async function dataflow(shell: RShell, remainingLine: string) { } export const dataflowCommand: ReplCommand = { - description: 'Get mermaid code for the dataflow graph of R code, start with \'file://\' to indicate a file.', + description: 'Get mermaid code for the dataflow graph of R code, start with \'file://\' to indicate a file', usageExample: ':dataflow', aliases: [ 'd', 'df' ], script: false, @@ -27,7 +27,7 @@ export const dataflowCommand: ReplCommand = { } export const dataflowStarCommand: ReplCommand = { - description: 'Get a mermaid url of the dataflow graph of R code, start with \'file://\' to indicate a file.', + description: 'Get a mermaid url of the dataflow graph of R code, start with \'file://\' to indicate a file', usageExample: ':dataflow*', aliases: [ 'd*', 'df*' ], script: false, diff --git a/src/cli/repl/commands/normalize.ts b/src/cli/repl/commands/normalize.ts index a43bd2c245..ee4cb73617 100644 --- a/src/cli/repl/commands/normalize.ts +++ b/src/cli/repl/commands/normalize.ts @@ -12,7 +12,7 @@ async function normalize(shell: RShell, remainingLine: string) { } export const normalizeCommand: ReplCommand = { - description: 'Get mermaid code for the normalized AST of R code, start with \'file://\' to indicate a file.', + description: 'Get mermaid code for the normalized AST of R code, start with \'file://\' to indicate a file', usageExample: ':normalize', aliases: [ 'n' ], script: false, @@ -24,7 +24,7 @@ export const normalizeCommand: ReplCommand = { } export const normalizeStarCommand: ReplCommand = { - description: 'Get a mermaid url of the normalized AST of R code, start with \'file://\' to indicate a file.', + description: 'Get a mermaid url of the normalized AST of R code, start with \'file://\' to indicate a file', usageExample: ':normalize', aliases: [ 'n*' ], script: false, diff --git a/src/cli/repl/commands/parse.ts b/src/cli/repl/commands/parse.ts index 57730fe037..685c6f7ba8 100644 --- a/src/cli/repl/commands/parse.ts +++ b/src/cli/repl/commands/parse.ts @@ -120,7 +120,7 @@ function depthListToTextTree(list: Readonly, config: XmlParserConfig, export const parseCommand: ReplCommand = { - description: 'Prints ASCII Art of the parsed, unmodified AST, start with \'file://\' to indicate a file.', + description: 'Prints ASCII Art of the parsed, unmodified AST, start with \'file://\' to indicate a file', usageExample: ':parse', aliases: [ 'p' ], script: false, diff --git a/src/dataflow/index.ts b/src/dataflow/index.ts index f9ada991b1..10953050ef 100644 --- a/src/dataflow/index.ts +++ b/src/dataflow/index.ts @@ -5,10 +5,4 @@ export const dataflowLogger = log.getSubLogger({ name: 'dataflow' }) export * from './graph' export * from './extractor' export * from './environments/environment' -export { diffGraphsToMermaidUrl } from '../util/mermaid' -export { diffGraphsToMermaid } from '../util/mermaid' -export { LabeledDiffGraph } from '../util/mermaid' -export { graphToMermaidUrl } from '../util/mermaid' -export { mermaidCodeToUrl } from '../util/mermaid' -export { graphToMermaid } from '../util/mermaid' -export { formatRange } from '../util/mermaid' +export * from '../util/mermaid/dfg' diff --git a/src/util/cfg.ts b/src/util/cfg.ts index 42260c26ec..008f16c00a 100644 --- a/src/util/cfg.ts +++ b/src/util/cfg.ts @@ -17,8 +17,6 @@ import { graph2quads, QuadSerializationConfiguration } from './quads' export interface CfgVertex { id: NodeId name: string - /** the content may be undefined, if the node is an artificial exit point node that i use to mark the exit point of an if condition, a function, etc. */ - content: string | undefined /** in case of a function definition */ children?: NodeId[] } @@ -155,13 +153,10 @@ export function extractCFG(ast: NormalizedAst): Co } -function getLexeme(n: RNodeWithParent) { - return n.info.fullLexeme ?? n.lexeme ?? '' -} function cfgLeaf(leaf: RNodeWithParent): ControlFlowInformation { const graph = new ControlFlowGraph() - graph.addVertex({ id: leaf.info.id, name: leaf.type, content: getLexeme(leaf) }) + graph.addVertex({ id: leaf.info.id, name: leaf.type }) return { graph, breaks: [], nexts: [], returns: [], exitPoints: [leaf.info.id], entryPoints: [leaf.info.id] } } @@ -179,8 +174,8 @@ function cfgIgnore(_leaf: RNodeWithParent): ControlFlowInformation { function cfgIfThenElse(ifNode: RNodeWithParent, condition: ControlFlowInformation, then: ControlFlowInformation, otherwise: ControlFlowInformation | undefined): ControlFlowInformation { const graph = new ControlFlowGraph() - graph.addVertex({ id: ifNode.info.id, name: ifNode.type, content: getLexeme(ifNode) }) - graph.addVertex({ id: ifNode.info.id + '-exit', name: 'if-exit', content: undefined }) + graph.addVertex({ id: ifNode.info.id, name: ifNode.type }) + graph.addVertex({ id: ifNode.info.id + '-exit', name: 'if-exit' }) graph.merge(condition.graph) graph.merge(then.graph) if(otherwise) { @@ -220,8 +215,8 @@ function cfgIfThenElse(ifNode: RNodeWithParent, condition: ControlFlowInformatio function cfgRepeat(repeat: RRepeatLoop, body: ControlFlowInformation): ControlFlowInformation { const graph = body.graph - graph.addVertex({ id: repeat.info.id, name: repeat.type, content: getLexeme(repeat) }) - graph.addVertex({ id: repeat.info.id + '-exit', name: 'repeat-exit', content: undefined }) + graph.addVertex({ id: repeat.info.id, name: repeat.type }) + graph.addVertex({ id: repeat.info.id + '-exit', name: 'repeat-exit' }) for(const entryPoint of body.entryPoints) { graph.addEdge(repeat.info.id, entryPoint, { label: 'FD' }) @@ -241,8 +236,8 @@ function cfgRepeat(repeat: RRepeatLoop, body: ControlFlowInfo function cfgWhile(whileLoop: RWhileLoop, condition: ControlFlowInformation, body: ControlFlowInformation): ControlFlowInformation { const graph = condition.graph - graph.addVertex({ id: whileLoop.info.id, name: whileLoop.type, content: getLexeme(whileLoop) }) - graph.addVertex({ id: whileLoop.info.id + '-exit', name: 'while-exit', content: undefined }) + graph.addVertex({ id: whileLoop.info.id, name: whileLoop.type }) + graph.addVertex({ id: whileLoop.info.id + '-exit', name: 'while-exit' }) graph.merge(body.graph) @@ -277,8 +272,8 @@ function cfgWhile(whileLoop: RWhileLoop, condition: ControlFl function cfgFor(forLoop: RForLoop, variable: ControlFlowInformation, vector: ControlFlowInformation, body: ControlFlowInformation): ControlFlowInformation { const graph = variable.graph - graph.addVertex({ id: forLoop.info.id, name: forLoop.type, content: getLexeme(forLoop) }) - graph.addVertex({ id: forLoop.info.id + '-exit', name: 'for-exit', content: undefined }) + graph.addVertex({ id: forLoop.info.id, name: forLoop.type }) + graph.addVertex({ id: forLoop.info.id + '-exit', name: 'for-exit' }) graph.merge(vector.graph) graph.merge(body.graph) @@ -299,10 +294,6 @@ function cfgFor(forLoop: RForLoop, variable: ControlFlowInfor } } - for(const entryPoint of body.entryPoints) { - graph.addEdge(forLoop.info.id, entryPoint, { label: 'FD' }) - } - for(const next of [...body.nexts, ...body.exitPoints]) { graph.addEdge(forLoop.info.id, next, { label: 'FD' }) } @@ -321,9 +312,9 @@ function cfgFor(forLoop: RForLoop, variable: ControlFlowInfor function cfgFunctionDefinition(fn: RFunctionDefinition, params: ControlFlowInformation[], body: ControlFlowInformation): ControlFlowInformation { const graph = new ControlFlowGraph() const children: NodeId[] = [fn.info.id + '-params', fn.info.id + '-exit'] - graph.addVertex({ id: fn.info.id + '-params', name: 'function-parameters', content: undefined }, false) - graph.addVertex({ id: fn.info.id + '-exit', name: 'function-exit', content: undefined }, false) - graph.addVertex({ id: fn.info.id, name: fn.type, content: getLexeme(fn), children }) + graph.addVertex({ id: fn.info.id + '-params', name: 'function-parameters' }, false) + graph.addVertex({ id: fn.info.id + '-exit', name: 'function-exit' }, false) + graph.addVertex({ id: fn.info.id, name: fn.type, children }) graph.merge(body.graph, true) children.push(...body.graph.rootVertexIds()) @@ -352,9 +343,10 @@ function cfgFunctionDefinition(fn: RFunctionDefinition, param function cfgBinaryOp(binOp: RNodeWithParent, lhs: ControlFlowInformation, rhs: ControlFlowInformation): ControlFlowInformation { const graph = new ControlFlowGraph().merge(lhs.graph).merge(rhs.graph) - const result: ControlFlowInformation = { graph, breaks: [...lhs.breaks, ...rhs.breaks], nexts: [...lhs.nexts, ...rhs.nexts], returns: [...lhs.returns, ...rhs.returns], entryPoints: [binOp.info.id], exitPoints: [...rhs.entryPoints] } + const result: ControlFlowInformation = { graph, breaks: [...lhs.breaks, ...rhs.breaks], nexts: [...lhs.nexts, ...rhs.nexts], returns: [...lhs.returns, ...rhs.returns], entryPoints: [binOp.info.id], exitPoints: [binOp.info.id + '-exit'] } - graph.addVertex({ id: binOp.info.id, name: binOp.type, content: getLexeme(binOp) }) + graph.addVertex({ id: binOp.info.id, name: binOp.type }) + graph.addVertex({ id: binOp.info.id + '-exit', name: 'binOp-exit' }) for(const exitPoint of lhs.exitPoints) { for(const entryPoint of rhs.entryPoints) { @@ -364,6 +356,9 @@ function cfgBinaryOp(binOp: RNodeWithParent, lhs: ControlFlowInformation, rhs: C for(const entryPoint of lhs.entryPoints) { graph.addEdge(entryPoint, binOp.info.id, { label: 'FD' }) } + for(const exitPoint of rhs.exitPoints) { + graph.addEdge(binOp.info.id + '-exit', exitPoint, { label: 'FD' }) + } return result } @@ -371,8 +366,8 @@ function cfgBinaryOp(binOp: RNodeWithParent, lhs: ControlFlowInformation, rhs: C function cfgAccess(access: RAccess, name: ControlFlowInformation, accessors: string | (ControlFlowInformation | null)[]): ControlFlowInformation { const result = name const graph = result.graph - graph.addVertex({ id: access.info.id, name: access.type, content: getLexeme(access) }) - graph.addVertex({ id: access.info.id + '-exit', name: 'access-exit', content: undefined }) + graph.addVertex({ id: access.info.id, name: access.type }) + graph.addVertex({ id: access.info.id + '-exit', name: 'access-exit' }) for(const entry of name.entryPoints) { graph.addEdge(entry, access.info.id, { label: 'FD' }) } @@ -403,7 +398,7 @@ function cfgUnaryOp(unary: RNodeWithParent, operand: ControlFlowInformation): Co const graph = operand.graph const result: ControlFlowInformation = { ...operand, graph, exitPoints: [unary.info.id] } - graph.addVertex({ id: unary.info.id, name: unary.type, content: getLexeme(unary) }) + graph.addVertex({ id: unary.info.id, name: unary.type }) return result } @@ -462,7 +457,7 @@ export function equalCfg(a: ControlFlowGraph | undefined, b: ControlFlowGraph | } for(const [id, aInfo] of aVert) { const bInfo = bVert.get(id) - if(bInfo === undefined || aInfo.name !== bInfo.name || aInfo.content !== bInfo.content || equalChildren(aInfo.children, bInfo.children)) { + if(bInfo === undefined || aInfo.name !== bInfo.name || equalChildren(aInfo.children, bInfo.children)) { return false } } @@ -501,7 +496,6 @@ export function cfg2quads(cfg: ControlFlowInformation, config: QuadSerialization .map(([id, v]) => ({ id, name: v.name, - content: v.content, children: v.children })), edges: [...cfg.graph.edges()].flatMap(([fromId, targets]) => diff --git a/src/util/mermaid/ast.ts b/src/util/mermaid/ast.ts new file mode 100644 index 0000000000..419e416c59 --- /dev/null +++ b/src/util/mermaid/ast.ts @@ -0,0 +1,24 @@ +import { RNodeWithParent, RoleInParent, visitAst } from '../../r-bridge' +import { escapeMarkdown, mermaidCodeToUrl } from './mermaid' + +export function normalizedAstToMermaid(ast: RNodeWithParent, prefix = ''): string { + let output = prefix + 'flowchart TD\n' + visitAst(ast, n => { + const name = `${n.type} (${n.info.id})\\n${n.lexeme ?? ' '}` + output += ` n${n.info.id}(["${escapeMarkdown(name)}"])\n` + if(n.info.parent !== undefined) { + const context = n.info + const roleSuffix = context.role === RoleInParent.ExpressionListChild || context.role === RoleInParent.FunctionCallArgument || context.role === RoleInParent.FunctionDefinitionParameter ? `-${context.index}` : '' + output += ` n${n.info.parent} -->|"${context.role}${roleSuffix}"| n${n.info.id}\n` + } + return false + }) + return output +} + +/** + * Use mermaid to visualize the normalized AST. + */ +export function normalizedAstToMermaidUrl(ast: RNodeWithParent, prefix = ''): string { + return mermaidCodeToUrl(normalizedAstToMermaid(ast, prefix)) +} diff --git a/src/util/mermaid/cfg.ts b/src/util/mermaid/cfg.ts new file mode 100644 index 0000000000..85e66e2074 --- /dev/null +++ b/src/util/mermaid/cfg.ts @@ -0,0 +1,38 @@ +import { NormalizedAst, RNodeWithParent } from '../../r-bridge' +import { ControlFlowInformation } from '../cfg' +import { escapeMarkdown, mermaidCodeToUrl } from './mermaid' + +function getLexeme(n?: RNodeWithParent) { + return n ? n.info.fullLexeme ?? n.lexeme ?? '' : '' +} + + +export function cfgToMermaid(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix = ''): string { + let output = prefix + 'flowchart TD\n' + + for(const [id, vertex] of cfg.graph.vertices()) { + const normalizedVertex = normalizedAst.idMap.get(id) + const content = getLexeme(normalizedVertex) + if(content.length > 0) { + const name = `"\`${escapeMarkdown(vertex.name)} (${id})\n${escapeMarkdown(JSON.stringify(content))}\`"` + output += ` n${id}[${name}]\n` + } else { + output += ` n${id}(( ))\n` + } + } + for(const [from, targets] of cfg.graph.edges()) { + for(const [to, edge] of targets) { + const edgeType = edge.label === 'CD' ? '-->' : '-.->' + const edgeSuffix = edge.label === 'CD' ? ` (${edge.when})` : '' + output += ` n${from} ${edgeType}|"${escapeMarkdown(edge.label)}${edgeSuffix}"| n${to}\n` + } + } + return output +} + +/** + * Use mermaid to visualize the normalized AST. + */ +export function cfgToMermaidUrl(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix = ''): string { + return mermaidCodeToUrl(cfgToMermaid(cfg, normalizedAst, prefix)) +} diff --git a/src/util/mermaid.ts b/src/util/mermaid/dfg.ts similarity index 75% rename from src/util/mermaid.ts rename to src/util/mermaid/dfg.ts index fc6ed12953..c33668dbd4 100644 --- a/src/util/mermaid.ts +++ b/src/util/mermaid/dfg.ts @@ -1,5 +1,5 @@ -import { NodeId, NoInfo, RNodeWithParent, RoleInParent, visitAst } from '../r-bridge' -import { SourceRange } from './range' +import { NodeId, NoInfo } from '../../r-bridge' +import { SourceRange } from '../range' import { BuiltIn, DataflowFunctionFlowInformation, @@ -10,11 +10,11 @@ import { DataflowMap, FunctionArgument, IdentifierReference -} from '../dataflow' -import { guard } from './assert' -import { jsonReplacer } from './json' -import { DataflowScopeName } from '../dataflow/environments' -import { ControlFlowInformation } from './cfg' +} from '../../dataflow' +import { guard } from '../assert' +import { jsonReplacer } from '../json' +import { DataflowScopeName } from '../../dataflow/environments' +import { escapeMarkdown, mermaidCodeToUrl } from './mermaid' interface MermaidGraph { @@ -96,11 +96,6 @@ function displayFunctionArgMapping(argMapping: FunctionArgument[]): string { } return result.length === 0 ? '' : `\n (${result.join(', ')})` } - -function escapeMarkdown(text: string): string { - return text.replaceAll(/([+\-*])/g, '\\$1').replaceAll('"', '\'\'') -} - function encodeEdge(from: string, to: string, types: Set, attribute: string): string { // sort from and to for same edges and relates be order independent if(types.has(EdgeType.SameReadRead) || types.has(EdgeType.SameDefDef) || types.has(EdgeType.Relates)) { @@ -183,22 +178,6 @@ export function graphToMermaid(graph: DataflowGraph, dataflowIdMap: DataflowMap< return `${mermaid.nodeLines.join('\n')}\n${mermaid.edgeLines.join('\n')}` } -/** - * Converts mermaid code (potentially produced by {@link graphToMermaid}) to an url that presents the graph in the mermaid editor. - * - * @param code - code to convert - */ -export function mermaidCodeToUrl(code: string): string { - const obj = { - code, - mermaid: {}, - updateEditor: false, - autoSync: true, - updateDiagram: false - } - return `https://mermaid.live/edit#base64:${Buffer.from(JSON.stringify(obj)).toString('base64')}` -} - /** * Converts a dataflow graph to a mermaid url that visualizes the graph. * @@ -228,55 +207,3 @@ export function diffGraphsToMermaid(left: LabeledDiffGraph, right: LabeledDiffGr export function diffGraphsToMermaidUrl(left: LabeledDiffGraph, right: LabeledDiffGraph, dataflowIdMap: DataflowMap | undefined, prefix: string): string { return mermaidCodeToUrl(diffGraphsToMermaid(left, right, dataflowIdMap, prefix)) } - -export function normalizedAstToMermaid(ast: RNodeWithParent, prefix = ''): string { - let output = prefix + 'flowchart TD\n' - visitAst(ast, n => { - const name = `${n.type} (${n.info.id})\\n${n.lexeme ?? ' '}` - output += ` n${n.info.id}(["${escapeMarkdown(name)}"])\n` - if(n.info.parent !== undefined) { - const context = n.info - const roleSuffix = context.role === RoleInParent.ExpressionListChild || context.role === RoleInParent.FunctionCallArgument || context.role === RoleInParent.FunctionDefinitionParameter ? `-${context.index}` : '' - output += ` n${n.info.parent} -->|"${context.role}${roleSuffix}"| n${n.info.id}\n` - } - return false - }) - return output -} - -/** - * Use mermaid to visualize the normalized AST. - */ -export function normalizedAstToMermaidUrl(ast: RNodeWithParent, prefix = ''): string { - return mermaidCodeToUrl(normalizedAstToMermaid(ast, prefix)) -} - - - -export function cfgToMermaid(cfg: ControlFlowInformation, prefix = ''): string { - let output = prefix + 'flowchart TD\n' - - for(const [id, vertex] of cfg.graph.vertices()) { - if(vertex.content) { - const name = `"\`${escapeMarkdown(vertex.name)} (${id})\n${escapeMarkdown(JSON.stringify(vertex.content))}\`"` - output += ` n${id}[${name}]\n` - } else { - output += ` n${id}(( ))\n` - } - } - for(const [from, targets] of cfg.graph.edges()) { - for(const [to, edge] of targets) { - const edgeType = edge.label === 'CD' ? '-->' : '-.->' - const edgeSuffix = edge.label === 'CD' ? ` (${edge.when})` : '' - output += ` n${from} ${edgeType}|"${escapeMarkdown(edge.label)}${edgeSuffix}"| n${to}\n` - } - } - return output -} - -/** - * Use mermaid to visualize the normalized AST. - */ -export function cfgToMermaidUrl(cfg: ControlFlowInformation, prefix = ''): string { - return mermaidCodeToUrl(cfgToMermaid(cfg, prefix)) -} diff --git a/src/util/mermaid/index.ts b/src/util/mermaid/index.ts new file mode 100644 index 0000000000..9c2c71bf3e --- /dev/null +++ b/src/util/mermaid/index.ts @@ -0,0 +1,3 @@ +export * from './ast' +export * from './cfg' +export * from './dfg' diff --git a/src/util/mermaid/mermaid.ts b/src/util/mermaid/mermaid.ts new file mode 100644 index 0000000000..31a3c84a41 --- /dev/null +++ b/src/util/mermaid/mermaid.ts @@ -0,0 +1,19 @@ +export function escapeMarkdown(text: string): string { + return text.replaceAll(/([+\-*])/g, '\\$1').replaceAll('"', '\'\'') +} + +/** + * Converts mermaid code (potentially produced by {@link graphToMermaid}) to an url that presents the graph in the mermaid editor. + * + * @param code - code to convert + */ +export function mermaidCodeToUrl(code: string): string { + const obj = { + code, + mermaid: {}, + updateEditor: false, + autoSync: true, + updateDiagram: false + } + return `https://mermaid.live/edit#base64:${Buffer.from(JSON.stringify(obj)).toString('base64')}` +} diff --git a/test/functionality/util/cfg.spec.ts b/test/functionality/util/cfg.spec.ts index 06f6220bee..9cbefe6dbd 100644 --- a/test/functionality/util/cfg.spec.ts +++ b/test/functionality/util/cfg.spec.ts @@ -10,8 +10,8 @@ import { } from '../../../src/util/cfg' import { SteppingSlicer } from '../../../src/core' import { requestFromInput, RFalse, RTrue, RType } from '../../../src/r-bridge' -import { cfgToMermaidUrl } from '../../../src/util/mermaid' import { defaultQuadIdGenerator } from '../../../src/util/quads' +import { cfgToMermaidUrl } from '../../../src/util/mermaid' describe('Control Flow Graph', withShell(shell => { function assertCfg(code: string, partialExpected: Partial) { @@ -33,8 +33,8 @@ describe('Control Flow Graph', withShell(shell => { assert.deepStrictEqual(cfg.returns, expected.returns, 'returns differ') assert.isTrue(equalCfg(cfg.graph, expected.graph), 'graphs differ') } catch(e: unknown) { - console.error(`expected: ${cfgToMermaidUrl(expected)}`) - console.error(`actual: ${cfgToMermaidUrl(cfg)}`) + console.error(`expected: ${cfgToMermaidUrl(expected, result.normalize)}`) + console.error(`actual: ${cfgToMermaidUrl(cfg, result.normalize)}`) throw e } }).timeout('3min') @@ -44,16 +44,29 @@ describe('Control Flow Graph', withShell(shell => { entryPoints: [ '3' ], exitPoints: [ '3-exit' ], graph: new ControlFlowGraph() - .addVertex({ id: '0', name: RType.Logical, content: 'TRUE' }) - .addVertex({ id: '1', name: RType.Number, content: '1' }) - .addVertex({ id: '3', name: RType.IfThenElse, content: 'if(TRUE) 1' }) - .addVertex({ id: '3-exit', name: 'if-exit', content: undefined }) + .addVertex({ id: '0', name: RType.Logical }) + .addVertex({ id: '1', name: RType.Number }) + .addVertex({ id: '3', name: RType.IfThenElse }) + .addVertex({ id: '3-exit', name: 'if-exit' }) .addEdge('0', '3', { label: 'FD' }) .addEdge('1', '0', { label: 'CD', when: RTrue }) .addEdge('3-exit', '1', { label: 'FD' }) .addEdge('3-exit', '0', { label: 'CD', when: RFalse }) }) + assertCfg('2 + 3', { + entryPoints: [ '2' ], + exitPoints: [ '2-exit' ], + graph: new ControlFlowGraph() + .addVertex({ id: '0', name: RType.Number }) + .addVertex({ id: '1', name: RType.Number }) + .addVertex({ id: '2', name: RType.BinaryOp }) + .addVertex({ id: '2-exit', name: 'binOp-exit' }) + .addEdge('0', '2', { label: 'FD' }) + .addEdge('1', '0', { label: 'FD' }) + .addEdge('2-exit', '1', { label: 'FD' }) + }) + it('Example Quad Export', async() => { const domain = 'https://uni-ulm.de/r-ast/' const context = 'test' @@ -74,18 +87,15 @@ describe('Control Flow Graph', withShell(shell => { <${domain}${context}/0> <${domain}vertices-0> <${domain}${context}/1> <${context}> . <${domain}${context}/1> <${domain}id> "3" <${context}> . <${domain}${context}/1> <${domain}name> "RIfThenElse" <${context}> . -<${domain}${context}/1> <${domain}content> "if(TRUE) 1" <${context}> . <${domain}${context}/0> <${domain}vertices-1> <${domain}${context}/2> <${context}> . <${domain}${context}/2> <${domain}id> "3-exit" <${context}> . <${domain}${context}/2> <${domain}name> "if-exit" <${context}> . <${domain}${context}/0> <${domain}vertices-2> <${domain}${context}/3> <${context}> . <${domain}${context}/3> <${domain}id> "0" <${context}> . <${domain}${context}/3> <${domain}name> "RLogical" <${context}> . -<${domain}${context}/3> <${domain}content> "TRUE" <${context}> . <${domain}${context}/0> <${domain}vertices-3> <${domain}${context}/4> <${context}> . <${domain}${context}/4> <${domain}id> "1" <${context}> . <${domain}${context}/4> <${domain}name> "RNumber" <${context}> . -<${domain}${context}/4> <${domain}content> "1" <${context}> . <${domain}${context}/0> <${domain}edges-0> <${domain}${context}/5> <${context}> . <${domain}${context}/5> <${domain}from> "1" <${context}> . <${domain}${context}/5> <${domain}to> "0" <${context}> .