Skip to content

Commit 59910b3

Browse files
authored
Merge pull request #53 from tmr232/cfgbot
CFGBot Utils
2 parents c440158 + da1275f commit 59910b3

File tree

5 files changed

+168
-39
lines changed

5 files changed

+168
-39
lines changed

bun.lockb

0 Bytes
Binary file not shown.

scripts/cfg-helper.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type Parser from "web-tree-sitter";
2+
import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts";
3+
import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts";
4+
import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts";
5+
6+
export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG {
7+
const builder = newCFGBuilder(language, { flatSwitch: true });
8+
9+
let cfg = builder.buildCFG(func);
10+
11+
cfg = trimFor(cfg);
12+
cfg = simplifyCFG(cfg, mergeNodeAttrs);
13+
return cfg;
14+
}

scripts/render-function.ts

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ import * as path from "node:path";
22
import { parseArgs } from "node:util";
33
import { Graphviz } from "@hpcc-js/wasm-graphviz";
44
import type Parser from "web-tree-sitter";
5-
import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts";
5+
import type { SyntaxNode } from "web-tree-sitter";
6+
import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts";
67
import {
7-
type Language,
8-
newCFGBuilder,
9-
supportedLanguages,
10-
} from "../src/control-flow/cfg.ts";
11-
import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts";
8+
deserializeColorList,
9+
getDarkColorList,
10+
getLightColorList,
11+
listToScheme,
12+
} from "../src/control-flow/colors.ts";
1213
import { graphToDot } from "../src/control-flow/render.ts";
1314
import { getLanguage, iterFunctions } from "../src/file-parsing/bun.ts";
15+
import { buildCFG } from "./cfg-helper.ts";
1416

1517
function isLanguage(language: string): language is Language {
1618
return supportedLanguages.includes(language as Language);
@@ -24,20 +26,30 @@ function normalizeFuncdef(funcdef: string): string {
2426
.trim();
2527
}
2628

27-
function buildCFG(func: Parser.SyntaxNode, language: Language): CFG {
28-
const builder = newCFGBuilder(language, { flatSwitch: true });
29-
30-
let cfg = builder.buildCFG(func);
31-
32-
cfg = trimFor(cfg);
33-
cfg = simplifyCFG(cfg, mergeNodeAttrs);
34-
return cfg;
29+
export function getFuncDef(sourceCode: string, func: SyntaxNode): string {
30+
const body = func.childForFieldName("body");
31+
if (!body) {
32+
throw new Error("No function body");
33+
}
34+
return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex));
3535
}
3636

3737
function writeError(message: string): void {
3838
Bun.write(Bun.stderr, `${message}\n`);
3939
}
4040

41+
export async function getColorScheme(colors?: string) {
42+
if (!colors || colors === "dark") {
43+
return listToScheme(getDarkColorList());
44+
}
45+
if (colors === "light") {
46+
return listToScheme(getLightColorList());
47+
}
48+
return colors
49+
? listToScheme(deserializeColorList(await Bun.file(colors).text()))
50+
: undefined;
51+
}
52+
4153
async function main() {
4254
process.on("SIGINT", () => {
4355
// close watcher when Ctrl-C is pressed
@@ -58,6 +70,9 @@ async function main() {
5870
out: {
5971
type: "string",
6072
},
73+
colors: {
74+
type: "string",
75+
},
6176
},
6277
strict: true,
6378
allowPositionals: true,
@@ -79,15 +94,26 @@ async function main() {
7994

8095
const possibleMatches: { name: string; func: Parser.SyntaxNode }[] = [];
8196
const sourceCode = await Bun.file(filepath).text();
97+
const startIndex = Number.parseInt(functionName);
98+
let startPosition: { row: number; column: number } | undefined;
99+
try {
100+
startPosition = JSON.parse(functionName);
101+
} catch {
102+
startPosition = undefined;
103+
}
82104
for (const func of iterFunctions(sourceCode, language)) {
83-
const body = func.childForFieldName("body");
84-
if (!body) {
105+
let funcDef: string;
106+
try {
107+
funcDef = getFuncDef(sourceCode, func);
108+
} catch {
85109
continue;
86110
}
87-
const funcDef = normalizeFuncdef(
88-
sourceCode.slice(func.startIndex, body.startIndex),
89-
);
90-
if (funcDef.includes(functionName)) {
111+
if (
112+
funcDef.includes(functionName) ||
113+
startIndex === func.startIndex ||
114+
(startPosition?.row === func.startPosition.row &&
115+
startPosition.column === func.startPosition.column)
116+
) {
91117
possibleMatches.push({ name: funcDef, func: func });
92118
}
93119
}
@@ -108,7 +134,10 @@ async function main() {
108134
const func: Parser.SyntaxNode = possibleMatches[0].func;
109135
const graphviz = await Graphviz.load();
110136
const cfg = buildCFG(func, language);
111-
const svg = graphviz.dot(graphToDot(cfg));
137+
138+
const colorScheme = await getColorScheme(values.colors);
139+
140+
const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme));
112141

113142
if (values.out) {
114143
await Bun.write(values.out, svg);
@@ -117,4 +146,6 @@ async function main() {
117146
}
118147
}
119148

120-
await main();
149+
if (require.main === module) {
150+
await main();
151+
}

scripts/render-graph.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import type {
77
GraphNode,
88
} from "../src/control-flow/cfg-defs.ts";
99
import { graphToDot } from "../src/control-flow/render.ts";
10+
import { getColorScheme } from "./render-function.ts";
1011

1112
async function main() {
1213
const {
14+
values,
1315
positionals: [_runtime, _this, gist_url],
1416
} = parseArgs({
1517
args: Bun.argv,
1618
strict: true,
1719
allowPositionals: true,
20+
options: {
21+
colors: {
22+
type: "string",
23+
},
24+
},
1825
});
1926

2027
if (!gist_url) {
@@ -39,11 +46,11 @@ async function main() {
3946
throw new Error("No entry found");
4047
}
4148
const cfg: CFG = { graph, entry, offsetToNode: [] };
42-
const dot = graphToDot(cfg);
49+
const colorScheme = await getColorScheme(values.colors);
50+
4351
const graphviz = await Graphviz.load();
44-
const svg = graphviz.dot(dot);
52+
const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme));
4553
console.log(svg);
46-
// console.log(dot);
4754
}
4855

4956
if (require.main === module) {

scripts/scan-codebase.ts

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,121 @@ import * as path from "node:path";
88
*/
99
import { parseArgs } from "node:util";
1010
import { Glob } from "bun";
11-
import { newCFGBuilder } from "../src/control-flow/cfg";
1211
import {
1312
fileTypes,
1413
getLanguage,
1514
iterFunctions,
1615
} from "../src/file-parsing/bun.ts";
16+
import { buildCFG } from "./cfg-helper.ts";
17+
import { getFuncDef } from "./render-function.ts";
1718

18-
function iterSourceFiles(root: string): IterableIterator<string> {
19+
export function iterSourceFiles(root: string): IterableIterator<string> {
1920
const sourceGlob = new Glob(
2021
`**/*.{${fileTypes.map(({ ext }) => ext).join(",")}}`,
2122
);
2223
return sourceGlob.scanSync(root);
2324
}
25+
function* iterFilenames(
26+
root: string,
27+
dirsToInclude: string[],
28+
): IterableIterator<string> {
29+
if (dirsToInclude.length === 1 && dirsToInclude[0] === "*") {
30+
yield* iterSourceFiles(root);
31+
} else {
32+
for (const dir of dirsToInclude) {
33+
for (const filename of iterSourceFiles(path.join(root, dir))) {
34+
// We want the path relative to the root
35+
yield path.join(dir, filename);
36+
}
37+
}
38+
}
39+
}
40+
41+
async function* iterFunctionInfo(
42+
root: string,
43+
filenames: IterableIterator<string>,
44+
): AsyncIterableIterator<{
45+
node_count: number;
46+
start_position: { row: number; column: number };
47+
funcdef: string;
48+
filename: string;
49+
}> {
50+
for (const filename of filenames) {
51+
const code = await Bun.file(path.join(root, filename)).text();
52+
const language = getLanguage(filename);
53+
for (const func of iterFunctions(code, language)) {
54+
const cfg = buildCFG(func, language);
55+
yield {
56+
node_count: cfg.graph.order,
57+
start_position: func.startPosition,
58+
funcdef: getFuncDef(code, func),
59+
filename: filename.replaceAll("\\", "/"),
60+
};
61+
}
62+
}
63+
}
64+
65+
async function generateIndex(
66+
/** Project name on GitHub */
67+
project: string,
68+
/** Git ref */
69+
ref: string,
70+
/** Root on local filesystem */
71+
root: string,
72+
/** Directories to index, relative to the root */
73+
dirsToInclude: string[],
74+
) {
75+
const filenames = iterFilenames(root, dirsToInclude);
76+
const functions = await Array.fromAsync(iterFunctionInfo(root, filenames));
77+
return {
78+
version: 1,
79+
content: {
80+
index_type: "github",
81+
project,
82+
ref,
83+
functions,
84+
},
85+
};
86+
}
2487

2588
async function main() {
26-
const { values } = parseArgs({
89+
const {
90+
values,
91+
positionals: [_runtime, _this, ...dirsToInclude],
92+
} = parseArgs({
2793
args: Bun.argv,
2894
options: {
95+
project: {
96+
type: "string",
97+
},
98+
ref: {
99+
type: "string",
100+
},
29101
root: {
30102
type: "string",
31103
},
104+
out: {
105+
type: "string",
106+
},
32107
},
33108
strict: true,
34109
allowPositionals: true,
35110
});
36111

37-
const root = values.root ?? ".";
112+
if (!values.project || !values.ref || !values.root) {
113+
throw new Error("Missing arguments");
114+
}
38115

39-
for (const filename of iterSourceFiles(root)) {
40-
const filepath = path.join(root, filename);
41-
const code = await Bun.file(filepath).text();
42-
const language = getLanguage(filename);
43-
for (const func of iterFunctions(code, language)) {
44-
const builder = newCFGBuilder(language, {});
45-
const cfg = builder.buildCFG(func);
46-
console.log(filepath, func.startPosition, cfg.graph.order);
47-
}
116+
const output = JSON.stringify(
117+
await generateIndex(values.project, values.ref, values.root, dirsToInclude),
118+
);
119+
if (values.out) {
120+
await Bun.write(values.out, output);
121+
} else {
122+
await Bun.write(Bun.stdout, output);
48123
}
49124
}
50125

51-
await main();
126+
if (require.main === module) {
127+
await main();
128+
}

0 commit comments

Comments
 (0)