Skip to content

Commit a5133f4

Browse files
authored
Merge pull request #31 from tmr232/cpp-support
C++ Support
2 parents ea7835a + 2eb54ff commit a5133f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+8647
-4039
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
1111
- Added JetBrains frontend for use in JetBrains IDE plugin
1212
- Demo: Added a link to the JetBrains plugin page
1313
- Demo learned to change font-size
14+
- Documented the process of adding a new language
15+
- [Biome](https://biomejs.dev/linter/) has been added as an additional linter
16+
- [Oxlint](https://oxc.rs/docs/guide/usage/linter) has been added to auto-fix some common issues
17+
- The `generate-parsers.ts` script has been updated to support copying existing `.wasm` files from tree-sitter grammar packages
18+
- Initial support for C++
19+
- A basic [typedoc](https://typedoc.org/) configuration was added, to help in rendering docs
20+
21+
### Changed
22+
23+
- Adding a new language now requires less wiring code, as many language declarations were merged.
1424

1525
## [0.0.8] - 2024-10-10
1626

biome.jsonc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3+
"vcs": {
4+
"enabled": false,
5+
"clientKind": "git",
6+
"useIgnoreFile": false,
7+
},
8+
"files": {
9+
"ignoreUnknown": false,
10+
"ignore": ["./dist", "*.svelte"],
11+
},
12+
"formatter": {
13+
"enabled": true,
14+
"indentStyle": "space",
15+
},
16+
"organizeImports": {
17+
"enabled": true,
18+
},
19+
"linter": {
20+
"enabled": true,
21+
"rules": {
22+
"recommended": true,
23+
"style": {
24+
"noParameterAssign": "off",
25+
},
26+
},
27+
},
28+
"javascript": {
29+
"formatter": {
30+
"quoteStyle": "double",
31+
},
32+
},
33+
}

bun.lockb

11.5 KB
Binary file not shown.

docs/AddNewLanguage.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: Adding a New Language
3+
group: Documents
4+
category: Guides
5+
---
6+
7+
# Adding a New Language
8+
9+
## Add the Relevant Parser
10+
11+
We're using [tree-sitter] to parse code into ASTs.
12+
Each language requires its own parser.
13+
Find yours in [tree-sitter's list of parsers][tree-sitter parsers].
14+
15+
Once you find the parser, you need to install it:
16+
17+
```shell
18+
bun add --dev tree-sitter-<language>
19+
```
20+
21+
After installing it, add it to `./scripts/generate-parsers.ts`
22+
and run `bun generate-parsers` to try and generate the `.wasm` parser file from it.
23+
24+
If the package contains a pre-built `.wasm` file, this will work.
25+
If it fails, Follow the [tree-sitter instructions for generating .wasm language files][build wasm] to set up emsrcipten,
26+
and run `bun generate-parsers` again.
27+
28+
Once the command completes successfully, your new parser should be inside `./parsers`.
29+
30+
## Generating the CFG
31+
32+
Each CFG-builder resides in its own file inside `./src/control-flow`.
33+
Name yours `cfg-<language>.ts`.
34+
35+
Your builder is expected to expose a `createCFGBuilder(options: BuilderOptions): CFGBuilder` function.
36+
A naive implementation to get started with would look something like this:
37+
38+
```typescript
39+
import type Parser from "web-tree-sitter";
40+
import type { BasicBlock, BuilderOptions, CFGBuilder } from "./cfg-defs";
41+
import {
42+
type Context,
43+
GenericCFGBuilder,
44+
type StatementHandlers,
45+
} from "./generic-cfg-builder.ts";
46+
47+
export function createCFGBuilder(options: BuilderOptions): CFGBuilder {
48+
return new GenericCFGBuilder(statementHandlers, options);
49+
}
50+
51+
const statementHandlers: StatementHandlers = {
52+
named: {},
53+
default: defaultProcessStatement,
54+
};
55+
56+
function defaultProcessStatement(
57+
syntax: Parser.SyntaxNode,
58+
ctx: Context,
59+
): BasicBlock {
60+
const newNode = ctx.builder.addNode(
61+
"STATEMENT",
62+
syntax.text,
63+
syntax.startIndex,
64+
);
65+
ctx.link.syntaxToNode(syntax, newNode);
66+
return { entry: newNode, exit: newNode };
67+
}
68+
```
69+
70+
Once you have your initial builder file, there's quite a lot of wiring to do,
71+
to register the language in all the relevant places.
72+
Search for `ADD-LANGUAGES-HERE` in the code, and add the language in all the relevant places.
73+
Those will include:
74+
75+
- Language & builder definitions in `src/control-flow/cfg.ts`
76+
- Mapping languages to `.wasm` files in `src/components/utils.ts`
77+
- Mapping VSCode's `languageId` to our language definitions in `src/vscode/extension.ts`
78+
- Adding test-collectors and tests in `src/test/commentTestCollector.ts`
79+
- Adding the language in the demo's UI in `src/components/Demo.svelte`
80+
81+
### Implementing the Builder
82+
83+
Once all the wiring is in place, it's time to actually generate the CFG.
84+
It is highly recommended that you read the other CFG implementation for reference.
85+
86+
While you're working, the [tree-sitter playground] will prove highly valuable in understanding the AST
87+
and creating queries.
88+
89+
[tree-sitter]: https://tree-sitter.github.io/tree-sitter/
90+
[tree-sitter parsers]: https://github.com/tree-sitter/tree-sitter/wiki/List-of-parsers
91+
[tree-sitter playground]: https://tree-sitter.github.io/tree-sitter/playground
92+
[build-wasm]: https://github.com/tree-sitter/tree-sitter/blob/master/lib/binding_web/README.md#generate-wasm-language-files

docs/CommentTests.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
title: Running & Writing Tests
3+
group: Documents
4+
category: Guides
5+
---
6+
7+
# Comment Tests
8+
9+
The comment-tests framework allows us to define CFG generation tests in the source-code that we test on.
10+
This makes test-writing easier, as we don't need to include code as strings in our tests.
11+
12+
## Running Tests
13+
14+
Use `bun test` to run all the tests.
15+
16+
### Visualizing Failures
17+
18+
If you have failing tests, you might want to visualize them.
19+
To do that, collect the test results as they get updated:
20+
21+
```shell
22+
bun web-tests --watch
23+
```
24+
25+
And run the web server to visualize them:
26+
27+
```shell
28+
bun web
29+
```
30+
31+
## Test Types
32+
33+
The current available test types are:
34+
35+
1. `nodes`: asserts the expected node-count in the CFG
36+
2. `exits`: asserts the expected exit-node count in the CFG
37+
3. `reaches`: asserts reachability between node pairs
38+
4. `render`: asserts that the code CFG for ths code renders successfully
39+
40+
Additionally, code-segmentation and snapshot-tests are added automatically for the code used in comment-tests.
41+
42+
## Writing Tests
43+
44+
1. Write your code in a new function in the matching file under `src/test/commentTestSamples`
45+
2. Add a comment right above the function, declaring the relevant tests.
46+
The commend format is JSON, but without the curly braces.
47+
48+
## Adding Languages
49+
50+
When we add a new language, we need to add a test-collector for that language.
51+
A test collector exports a `getTestFuncs(code: string): Generator<TestFunction>` function.
52+
To do that, we need to parse the code, and extract all functions and comments inside it.
53+
It's best to look at one of the `collect-<language>.ts` files to see how this is done.
54+
55+
Once we have a collector, we add it in `src/test/commentTestCollector.ts` and map file-extensions to use with it.
56+
Then, we add a test file under `src/test/commentTestSamples`.

oxlintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"$schema": "./node_modules/oxlint/configuration_schema.json"
3+
}

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"esbuild-plugin-copy": "^2.1.1"
1515
},
1616
"devDependencies": {
17+
"@biomejs/biome": "1.9.4",
1718
"@codemirror/lang-cpp": "^6.0.2",
1819
"@codemirror/lang-go": "^6.0.1",
1920
"@codemirror/lang-python": "^6.1.6",
@@ -26,16 +27,19 @@
2627
"eslint": "^9.12.0",
2728
"graphology-utils": "^2.5.2",
2829
"lz-string": "^1.5.0",
30+
"oxlint": "0.13.2",
2931
"prettier": "3.3.3",
3032
"prettier-plugin-svelte": "^3.2.7",
3133
"svelte": "^4.2.19",
3234
"svelte-awesome-color-picker": "^3.1.4",
3335
"svelte-codemirror-editor": "^1.4.1",
3436
"tree-sitter-c": "^0.23.1",
3537
"tree-sitter-cli": "^0.23.2",
38+
"tree-sitter-cpp": "^0.23.4",
3639
"tree-sitter-go": "^0.23.1",
3740
"tree-sitter-python": "^0.23.2",
38-
"typescript-eslint": "^8.8.0",
41+
"typedoc": "^0.27.1",
42+
"typescript-eslint": "^8.16.0",
3943
"vite": "^5.4.8"
4044
},
4145
"peerDependencies": {
@@ -56,7 +60,7 @@
5660
"build-demo": "bun run --cwd ./src/demo/ vite build --outDir ../../dist/demo --base '/function-graph-overview/'",
5761
"build-jetbrains": "bun run --cwd ./src/jetbrains/ vite build",
5862
"format": "bun prettier . --write --log-level silent",
59-
"lint": "bun format && bun run eslint || bun run tsc --noEmit",
63+
"lint": "bun format && bun run biome lint --fix || bun run oxlint --ignore-pattern=\"*.svelte\" --fix || bun run eslint || bun run tsc --noEmit",
6064
"generate-parsers": "bun run ./scripts/generate-parsers.ts"
6165
},
6266
"//": "START EXTENSION ATTRIBUTES",

parsers/tree-sitter-cpp.wasm

3.28 MB
Binary file not shown.

scripts/collect-comment-tests.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { intoRecords } from "../src/test/commentTestUtils";
2-
import { watch } from "fs";
3-
import { parseArgs } from "util";
2+
import { watch } from "node:fs";
3+
import { parseArgs } from "node:util";
44
import { collectTests } from "../src/test/commentTestCollector";
55

6-
const watchDir = import.meta.dir + "/../src";
6+
const watchDir = `${import.meta.dir}/../src`;
77

88
const { values } = parseArgs({
99
args: Bun.argv,
@@ -20,13 +20,13 @@ const { values } = parseArgs({
2020
async function generateJson() {
2121
try {
2222
const records = intoRecords(await collectTests());
23-
Bun.write("./dist/tests/commentTests.json", JSON.stringify(records));
23+
await Bun.write("./dist/tests/commentTests.json", JSON.stringify(records));
2424
} catch (error) {
2525
console.log(error);
2626
}
2727
}
2828

29-
generateJson();
29+
await generateJson();
3030
if (values.watch) {
3131
const watcher = watch(
3232
watchDir,

scripts/generate-parsers.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,58 @@
1+
/**
2+
* The `generate-parsers` script copies or builds the relevant tree-sitter
3+
* parsers in to the `./parsers` directory.
4+
*
5+
* To add a new parsers, add it's package name to the `parsersToBuild` array.
6+
*/
17
import { $ } from "bun";
8+
import * as fs from "node:fs";
9+
import { fileURLToPath } from "node:url";
210

3-
const treeSitter = Bun.file("./node_modules/web-tree-sitter/tree-sitter.wasm");
4-
await Bun.write("./parsers/tree-sitter.wasm", treeSitter);
11+
/**
12+
* The parsers to include
13+
*/
14+
const parsersToBuild = [
15+
"tree-sitter-go",
16+
"tree-sitter-c",
17+
"tree-sitter-python",
18+
"tree-sitter-cpp",
19+
];
520

6-
const parsers = ["tree-sitter-go", "tree-sitter-c", "tree-sitter-python"];
21+
function locatePrebuiltWasm(packageName: string): string {
22+
return fileURLToPath(
23+
import.meta.resolve(`${packageName}/${packageName}.wasm`),
24+
);
25+
}
26+
27+
function hasPrebuiltWasm(packageName: string): boolean {
28+
try {
29+
locatePrebuiltWasm(packageName);
30+
} catch {
31+
return false;
32+
}
33+
return true;
34+
}
35+
36+
for (const name of parsersToBuild) {
37+
const targetWasmPath = `./parsers/${name}.wasm`;
38+
if (await Bun.file(targetWasmPath).exists()) {
39+
console.log(`${name}: .wasm found, skipping copy.`);
40+
} else if (hasPrebuiltWasm(name)) {
41+
console.log(`${name}: copying .wasm`);
42+
fs.copyFileSync(locatePrebuiltWasm(name), targetWasmPath);
43+
} else {
44+
console.log(`${name}: building .wasm`);
45+
await $`bun x --bun tree-sitter build --wasm -o ${targetWasmPath} ./node_modules/${name}/`;
46+
}
47+
48+
await $`git add ${targetWasmPath}`;
49+
}
750

8-
for (const name of parsers) {
9-
await $`bun x --bun tree-sitter build --wasm -o ./parsers/${name}.wasm ./node_modules/${name}/`;
51+
const treeSitterPath = "./parsers/tree-sitter.wasm";
52+
if (!(await Bun.file(treeSitterPath).exists())) {
53+
const treeSitter = Bun.file(
54+
"./node_modules/web-tree-sitter/tree-sitter.wasm",
55+
);
56+
await Bun.write(treeSitterPath, treeSitter);
57+
await $`git add ${treeSitterPath}`;
1058
}

scripts/watch-with-esbuild.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import config from "./esbuild.config";
44
try {
55
const context = await esbuild.context(config);
66
await context.watch();
7-
} catch (_e) {
7+
} catch {
88
process.exit(1);
99
}

src/components/CodeSegmentation.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
) {
8383
const { trim, simplify } = options;
8484
const tree = parsers[language].parse(code);
85-
const functionSyntax = getFirstFunction(tree);
85+
const functionSyntax = getFirstFunction(tree, language);
8686
const builder = newCFGBuilder(language, {});
8787
8888
let cfg = builder.buildCFG(functionSyntax);

0 commit comments

Comments
 (0)