-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathrender-function.ts
More file actions
159 lines (139 loc) · 4.19 KB
/
render-function.ts
File metadata and controls
159 lines (139 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import * as path from "node:path";
import { parseArgs } from "node:util";
import { Graphviz } from "@hpcc-js/wasm-graphviz";
import type { Node as SyntaxNode } from "web-tree-sitter";
import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts";
import {
deserializeColorList,
getDarkColorList,
getLightColorList,
listToScheme,
} from "../src/control-flow/colors.ts";
import { graphToDot } from "../src/control-flow/render.ts";
import { getLanguage, iterFunctions } from "../src/file-parsing/bun.ts";
import { buildCFG } from "./cfg-helper.ts";
function isLanguage(language: string): language is Language {
return supportedLanguages.includes(language as Language);
}
function normalizeFuncdef(funcdef: string): string {
return funcdef
.replaceAll("\r", "")
.replaceAll("\n", " ")
.replaceAll(/\s+/g, " ")
.trim();
}
export function getFuncDef(sourceCode: string, func: SyntaxNode): string {
const body = func.childForFieldName("body");
if (!body) {
throw new Error("No function body");
}
return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex));
}
function writeError(message: string): void {
Bun.write(Bun.stderr, `${message}\n`);
}
export async function getColorScheme(colors?: string) {
if (!colors || colors === "dark") {
return listToScheme(getDarkColorList());
}
if (colors === "light") {
return listToScheme(getLightColorList());
}
return colors
? listToScheme(deserializeColorList(await Bun.file(colors).text()))
: undefined;
}
async function main() {
process.on("SIGINT", () => {
// close watcher when Ctrl-C is pressed
writeError("Terminated by user.");
process.exit(0);
});
const {
values,
positionals: [_runtime, _this, filepath, functionName],
} = parseArgs({
args: Bun.argv,
options: {
language: {
type: "string",
},
out: {
type: "string",
},
colors: {
type: "string",
},
dot: {
type: "string",
},
},
strict: true,
allowPositionals: true,
});
if (filepath === undefined || functionName === undefined) {
writeError(
`Usage: bun run ${path.relative(process.cwd(), import.meta.path)} [--language LANGUAGE] [--out PATH] <source-file> <function-identifier>`,
);
process.exit(1);
}
if (values.language !== undefined && !isLanguage(values.language)) {
writeError(`Language must be one of ${supportedLanguages}`);
process.exit(1);
}
const language: Language = values.language ?? getLanguage(filepath);
const possibleMatches: { name: string; func: SyntaxNode }[] = [];
const sourceCode = await Bun.file(filepath).text();
const startIndex = Number.parseInt(functionName);
let startPosition: { row: number; column: number } | undefined;
try {
startPosition = JSON.parse(functionName);
} catch {
startPosition = undefined;
}
for (const func of iterFunctions(sourceCode, language)) {
let funcDef: string;
try {
funcDef = getFuncDef(sourceCode, func);
} catch {
continue;
}
if (
funcDef.includes(functionName) ||
startIndex === func.startIndex ||
(startPosition?.row === func.startPosition.row &&
startPosition.column === func.startPosition.column)
) {
possibleMatches.push({ name: funcDef, func: func });
}
}
if (possibleMatches.length === 0) {
writeError("No matches found.");
process.exit(1);
}
if (possibleMatches.length !== 1) {
writeError("Multiple matches found, please be more specific:");
for (const { name } of possibleMatches) {
writeError(`\t${name}`);
}
process.exit(1);
}
// @ts-expect-error: possibleMatches will always have exactly one value.
const func: SyntaxNode = possibleMatches[0].func;
const graphviz = await Graphviz.load();
const cfg = buildCFG(func, language);
const colorScheme = await getColorScheme(values.colors);
const dot = graphToDot(cfg, false, colorScheme);
if (values.dot) {
await Bun.write(values.dot, dot);
}
const svg = graphviz.dot(dot);
if (values.out) {
await Bun.write(values.out, svg);
} else {
await Bun.write(Bun.stdout, svg);
}
}
if (require.main === module) {
await main();
}