Skip to content

Commit 9b1d1ca

Browse files
committed
feat(diagram): add call graph diagram - #354
1 parent dd77455 commit 9b1d1ca

File tree

5 files changed

+82
-39
lines changed

5 files changed

+82
-39
lines changed

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@
121121
"icon": "$(graph)",
122122
"category": "flowR"
123123
},
124+
{
125+
"command": "vscode-flowr.call-graph",
126+
"title": "Show Call Graph of the File",
127+
"icon": "$(graph)",
128+
"category": "flowR"
129+
},
124130
{
125131
"command": "vscode-flowr.slice.cursor",
126132
"title": "Show Relevant Code for Cursor Position Once (Backward Slice)",

src/diagram.ts

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import * as vscode from 'vscode';
22
import { getFlowrSession, registerCommand } from './extension';
33
import { DiagramSettingsKeys, DiagramSettingsPrefix, getConfig } from './settings';
44
import path from 'path';
5-
import assert from 'assert';
65
import type { DiagramOption, DiagramOptions , DiagramOptionsCheckbox, DiagramOptionsDropdown } from './diagram-generator';
76
import { createDiagramWebview } from './diagram-generator';
8-
import { assertUnreachable } from '@eagleoutice/flowr/util/assert';
97
import type { CfgSimplificationPassName } from '@eagleoutice/flowr/control-flow/cfg-simplification';
108
import { CfgSimplificationPasses } from '@eagleoutice/flowr/control-flow/cfg-simplification';
9+
import { assertUnreachable } from '@eagleoutice/flowr/util/assert';
1110

1211
export function registerDiagramCommands(context: vscode.ExtensionContext, output: vscode.OutputChannel) {
1312
const coordinator = new DiagramUpdateCoordinator(output);
@@ -30,12 +29,17 @@ export function registerDiagramCommands(context: vscode.ExtensionContext, output
3029
const activeEditor = vscode.window.activeTextEditor;
3130
return await coordinator.createDiagramPanel(FlowrDiagramType.Controlflow, activeEditor);
3231
});
32+
registerCommand(context, 'vscode-flowr.call-graph', async() => {
33+
const activeEditor = vscode.window.activeTextEditor;
34+
return await coordinator.createDiagramPanel(FlowrDiagramType.CallGraph, activeEditor);
35+
});
3336
}
3437

3538
enum FlowrDiagramType {
3639
Dataflow = 'flowr-dataflow',
3740
Controlflow = 'flowr-cfg',
38-
Ast = 'flowr-ast'
41+
Ast = 'flowr-ast',
42+
CallGraph = 'flowr-call-graph',
3943
}
4044

4145
interface DiagramPanelInformation {
@@ -78,7 +82,7 @@ class DiagramUpdateCoordinator {
7882
return;
7983
}
8084

81-
const title = `${nameFromDiagramType(type)} (${path.basename(editor.document.fileName)})`;
85+
const title = `${DiagramTitleMap[type]} (${path.basename(editor.document.fileName)})`;
8286
const options = optionsFromDiagramType(type);
8387
const mermaid = await diagramFromTypeAndEditor(type, editor, options);
8488
const panel = createDiagramWebview(type as string, title, mermaid, this.output, options);
@@ -158,15 +162,6 @@ class DiagramUpdateCoordinator {
158162
}
159163
}
160164

161-
function nameFromDiagramType(type: FlowrDiagramType): string {
162-
switch(type) {
163-
case FlowrDiagramType.Dataflow: return 'Dataflow Graph';
164-
case FlowrDiagramType.Controlflow: return 'Control Flow Graph';
165-
case FlowrDiagramType.Ast: return 'AST';
166-
default: return 'Flowr';
167-
}
168-
}
169-
170165
const DefaultDiagramOptions = {
171166
mode: {
172167
type: 'dropdown',
@@ -220,21 +215,22 @@ const CFGDiagramOptions = {
220215
}])) as { [K in CfgSimplificationPassName]: DiagramOptionsCheckbox<CfgSimplificationPassName> } )
221216
} satisfies DiagramOptions;
222217

218+
const DiagramOptionsMap = {
219+
'flowr-dataflow': DFGDiagramOptions,
220+
'flowr-cfg': CFGDiagramOptions,
221+
'flowr-ast': DefaultDiagramOptions,
222+
'flowr-call-graph': DefaultDiagramOptions
223+
} as const satisfies Record<FlowrDiagramType, DiagramOptions>;
224+
225+
const DiagramTitleMap = {
226+
'flowr-dataflow': 'Dataflow Graph',
227+
'flowr-cfg': 'Control Flow Graph',
228+
'flowr-ast': 'AST',
229+
'flowr-call-graph': 'Call Graph'
230+
} as const satisfies Record<FlowrDiagramType, string>;
231+
223232
function optionsFromDiagramType(type: FlowrDiagramType) {
224-
let options;
225-
226-
switch(type) {
227-
case FlowrDiagramType.Dataflow:
228-
options = DFGDiagramOptions;
229-
break;
230-
case FlowrDiagramType.Controlflow:
231-
options = CFGDiagramOptions;
232-
break;
233-
case FlowrDiagramType.Ast:
234-
options = DefaultDiagramOptions;
235-
break;
236-
default: assertUnreachable(type);
237-
}
233+
const options = DiagramOptionsMap[type];
238234

239235
for(const option of Object.values(options)) {
240236
if('keyInSet' in option && option.keyInSet) { // option is encoded in a set
@@ -276,6 +272,7 @@ async function diagramFromTypeAndEditor(type: FlowrDiagramType, editor: vscode.T
276272
return await session.retrieveCfgMermaid(editor.document, editor.selections, opts.mode.currentValue, opts.simplifyCfg.currentValue, simplificationPassesFromOptions(opts));
277273
}
278274
case FlowrDiagramType.Ast: return await session.retrieveAstMermaid(editor.document, editor.selections, options.mode.currentValue);
279-
default: assert(false);
275+
case FlowrDiagramType.CallGraph: return await session.retrieveCallgraphMermaid(editor.document, editor.selections, options.mode.currentValue);
276+
default: assertUnreachable(type);
280277
}
281278
}

src/flowr/internal-session.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ function configureFlowrLogging(output: vscode.OutputChannel) {
8383
setFlowrLoggingSensitivity(output);
8484
}
8585

86+
type WorkActions = 'slice' | 'ast' | 'cfg' | 'dfg' | 'callgraph';
87+
8688
export class FlowrInternalSession implements FlowrSession {
8789

8890
private static treeSitterInitialized: boolean = false;
@@ -101,7 +103,7 @@ export class FlowrInternalSession implements FlowrSession {
101103
updateStatusBar();
102104
}
103105

104-
private async startWorkWithProgressBar<T = void>(document: vscode.TextDocument, actionFn: (analyzer: FlowrAnalyzer) => Promise<T>, action: string, showErrorMessage: boolean, defaultOnErr = {} as T): Promise<T> {
106+
private async startWorkWithProgressBar<T = void>(document: vscode.TextDocument, actionFn: (analyzer: FlowrAnalyzer) => Promise<T>, action: WorkActions, showErrorMessage: boolean, defaultOnErr = {} as T): Promise<T> {
105107
if(!this.parser) {
106108
return defaultOnErr;
107109
}
@@ -114,11 +116,12 @@ export class FlowrInternalSession implements FlowrSession {
114116
location: vscode.ProgressLocation.Notification,
115117
title: (() => {
116118
switch(action) {
117-
case 'slice': return 'Creating Slice...';
118-
case 'ast': return 'Creating AST...';
119-
case 'cfg': return 'Creating Control Flow Graph...';
120-
case 'dfg': return 'Creating Data Flow Graph...';
121-
default: return 'Working...';
119+
case 'slice': return 'Creating Slice...';
120+
case 'ast': return 'Creating AST...';
121+
case 'cfg': return 'Creating Control Flow Graph...';
122+
case 'dfg': return 'Creating Data Flow Graph...';
123+
case 'callgraph': return 'Creating Call Graph...';
124+
default: return 'Working...';
122125
}
123126
})(),
124127
cancellable: false
@@ -255,6 +258,23 @@ export class FlowrInternalSession implements FlowrSession {
255258
}, 'dfg', true, '');
256259
}
257260

261+
async retrieveCallgraphMermaid(document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified?: boolean): Promise<string> {
262+
return await this.startWorkWithProgressBar(document, async(analyzer) => {
263+
const callGraph = await analyzer.callGraph();
264+
const ast = await analyzer.normalize();
265+
const selectionNodes = selectionsToNodeIds(ast.ast.files.map(f => f.root), selections);
266+
267+
return graphToMermaid({
268+
graph: callGraph,
269+
simplified,
270+
includeEnvironments: false,
271+
includeOnlyIds: selectionMode === 'hide' ? selectionNodes : undefined,
272+
mark: selectionMode === 'highlight' ? new Set(selectionNodes?.values().map(v => String(v))) : undefined,
273+
}).string;
274+
}, 'callgraph', true, '');
275+
}
276+
277+
258278
async retrieveAstMermaid(document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode): Promise<string> {
259279
return await this.startWorkWithProgressBar(document, async(analyzer) => {
260280
const result = await analyzer.normalize();

src/flowr/server-session.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,25 @@ export class FlowrServerSession implements FlowrSession {
168168
});
169169
}
170170

171+
async retrieveCallgraphMermaid(document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified = false): Promise<string> {
172+
const { result, ast, dfi, hasError } = await this.retrieveQuery(document, [{ type: 'call-graph' }]);
173+
174+
if(hasError || !ast || !dfi) {
175+
return '';
176+
}
177+
178+
const selectionNodes = selectionsToNodeIds(ast.ast.files.map(f => f.root), selections);
179+
180+
return graphToMermaid({
181+
graph: DataflowGraph.fromJson(result['call-graph'].graph as unknown as DataflowGraphJson),
182+
simplified,
183+
includeEnvironments: false,
184+
includeOnlyIds: selectionMode === 'hide' ? selectionNodes : undefined,
185+
mark: selectionMode === 'highlight' ? new Set(selectionNodes?.values().map(v => String(v))) : undefined,
186+
}).string;
187+
}
188+
189+
171190
async retrieveDataflowMermaid(document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified = false): Promise<string> {
172191
const response = await this.requestFileAnalysis(document);
173192
const selectionNodes = selectionsToNodeIds(response.results.normalize.ast.files.map(f => f.root), selections);

src/flowr/utils.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ export interface FlowrSession {
3131
showErrorMessage?: boolean,
3232
info?: { dfi: DataflowInformation, ast: NormalizedAst }
3333
) => Promise<SliceReturn>
34-
retrieveDataflowMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified?: boolean) => Promise<string>
35-
retrieveAstMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode) => Promise<string>
36-
retrieveCfgMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified: boolean, simplifications: CfgSimplificationPassName[]) => Promise<string>
37-
retrieveQuery: <T extends SupportedQueryTypes>(document: vscode.TextDocument, query: Queries<T>) => Promise<{ result: QueryResults<T>, hasError: boolean, dfi?: DataflowInformation, ast?: NormalizedAst }>
38-
runRepl: (output: Omit<FlowrReplOptions, 'parser'>) => Promise<void>
34+
retrieveDataflowMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified?: boolean) => Promise<string>
35+
retrieveAstMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode) => Promise<string>
36+
retrieveCfgMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified: boolean, simplifications: CfgSimplificationPassName[]) => Promise<string>
37+
retrieveCallgraphMermaid: (document: vscode.TextDocument, selections: readonly vscode.Selection[], selectionMode: DiagramSelectionMode, simplified?: boolean) => Promise<string>;
38+
retrieveQuery: <T extends SupportedQueryTypes>(document: vscode.TextDocument, query: Queries<T>) => Promise<{ result: QueryResults<T>, hasError: boolean, dfi?: DataflowInformation, ast?: NormalizedAst }>
39+
runRepl: (output: Omit<FlowrReplOptions, 'parser'>) => Promise<void>
3940
}
4041

4142
export function getPositionAt(position: vscode.Position, document: vscode.TextDocument): vscode.Range | undefined {

0 commit comments

Comments
 (0)