Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Visualization Tool #71

Open
wants to merge 136 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
b840d88
Create draft of visualization page
Process-ing Jun 28, 2024
b36bbc5
Fix WebSocket connection
Process-ing Jun 28, 2024
231bba2
Move page files to public folder
Process-ing Jun 28, 2024
4574842
Create simple textareas for the AST and code
Process-ing Jun 28, 2024
7634afb
Improve AST and code containers
Process-ing Jun 28, 2024
5131cbb
Fix missing favicon error
Process-ing Jun 28, 2024
41832e1
Highlight AST node on hover
Process-ing Jun 28, 2024
cbbc700
Highlight code of AST node when node hovered
Process-ing Jun 28, 2024
bfd487f
Fix highlight of code with special characters
Process-ing Jun 28, 2024
e4eca73
Create AST import script
Process-ing Jul 1, 2024
61ada12
Refactor AST node indentation
Process-ing Jul 1, 2024
025c8f9
Refactor node to code linking
Process-ing Jul 2, 2024
7cb33b9
Refactor visualization script
Process-ing Jul 2, 2024
eafa60f
Implement highlighting of nodes and code when code is hovered
Process-ing Jul 2, 2024
b3f0777
Make tool highlight all possible code correspondences
Process-ing Jul 2, 2024
c2f54e6
Convert launch script into a module
Process-ing Jul 2, 2024
46681ff
Copy tool page folder to api
Process-ing Jul 3, 2024
ff11659
Export launcher script to TypeScript
Process-ing Jul 3, 2024
9d2a4e9
Convert launch script to VisualizationTool class
Process-ing Jul 3, 2024
207cd67
Convert page scripts to TypeScript
Process-ing Jul 4, 2024
e150520
Add VisualizationTool method to create a WebSocket connection
Process-ing Jul 4, 2024
e904b0f
Allow dynamic port allocation
Process-ing Jul 4, 2024
edc5920
Specify default port
Process-ing Jul 4, 2024
e905584
Launch visualization tool on startup
Process-ing Jul 4, 2024
1e4216f
Convert VisualizationTool to a singleton
Process-ing Jul 4, 2024
3bbf29d
Create function to wait for tool
Process-ing Jul 4, 2024
a1db69a
Create continue button
Process-ing Jul 4, 2024
dee8140
Establish communication between tool server and page
Process-ing Jul 5, 2024
d7900ed
Small refactor on VisualizationTool.ts
Process-ing Jul 5, 2024
027e14a
Convert tool not running warning into an error
Process-ing Jul 5, 2024
ea791bb
Remove outdated TODO
Process-ing Jul 5, 2024
df1f96d
Create function to update tool AST
Process-ing Jul 5, 2024
369807a
Add main script and remove default execution from the remaining modules
Process-ing Jul 5, 2024
1d2935c
Fix node highlighting
Process-ing Jul 5, 2024
00be294
Try to make code generation and linking more dynamic
Process-ing Jul 5, 2024
ee9b3c1
Fix AST updates
Process-ing Jul 5, 2024
9b120e9
Send tree data to web page
Process-ing Jul 8, 2024
2a53936
Create ToolJoinPoint class
Process-ing Jul 8, 2024
bc0788d
Use real AST on visualization tool
Process-ing Jul 8, 2024
9af14fa
Fix problems with indentation and incerment statements in for loops
Process-ing Jul 8, 2024
b833ff1
Fix search and replace bounds
Process-ing Jul 8, 2024
ed4a737
Fix condition search for while and do-while loops
Process-ing Jul 9, 2024
7d36c45
Fix multiple variable declaration statements search
Process-ing Jul 9, 2024
c5399c8
Fix more bugs with search of expressions inside loop parentheses
Process-ing Jul 9, 2024
14e0d93
Refactor highlighting
Process-ing Jul 9, 2024
b5a2422
Add favicon
Process-ing Jul 11, 2024
d8ff8f3
Make WebSocket reconnect on close
Process-ing Jul 12, 2024
8ee1c5c
Create color scheme
Process-ing Jul 12, 2024
432b570
Change default font
Process-ing Jul 12, 2024
51f5161
Fix child node ordering
Process-ing Jul 12, 2024
f946bfe
Upload SPeCS logo
Process-ing Jul 14, 2024
48f3b6b
Create tool header
Process-ing Jul 14, 2024
bd4a3bb
Add Lucide icon to resume button
Process-ing Jul 15, 2024
b8d44c5
Refactor page layout and style continue button
Process-ing Jul 15, 2024
bfbcf6e
Fix container sizes
Process-ing Jul 15, 2024
609af91
Refactor container style
Process-ing Jul 15, 2024
a52165c
Refactor containers
Process-ing Jul 15, 2024
f0652fa
Refactor AST container style
Process-ing Jul 15, 2024
20848ce
Group child node elements in dropdowns
Process-ing Jul 15, 2024
f3df008
Make dropdown collapsable
Process-ing Jul 15, 2024
92566fb
Stylish node dropdown buttons
Process-ing Jul 15, 2024
11863d2
Fix invalid HTML
Process-ing Jul 15, 2024
5a7a106
Finish old CSS refactor
Process-ing Jul 15, 2024
d7e45eb
Fix container size
Process-ing Jul 15, 2024
05ca470
Restylish code container
Process-ing Jul 16, 2024
41e19bf
Change continue button style and migrate icon to Material Icons
Process-ing Jul 16, 2024
af21eaf
Refactor highlighting
Process-ing Jul 16, 2024
ff3e123
Fix child node order
Process-ing Jul 16, 2024
621e987
Fix invalid HTML code
Process-ing Jul 16, 2024
3b675d9
Start using joinPointType instead of astName
Process-ing Jul 17, 2024
54f6422
Fix AST code corrections
Process-ing Jul 17, 2024
ca4fcce
Pass compiler dependant actions to Clava
Process-ing Jul 18, 2024
ab2667c
Move AST refining to Clava
Process-ing Jul 18, 2024
427a4f6
Perform node to code linking on Clava
Process-ing Jul 18, 2024
f6ae291
Remove code from ToolJoinPoint
Process-ing Jul 18, 2024
4d12d13
Fix highlighting of nodes with multiple code blocks
Process-ing Jul 18, 2024
2a086b9
Allow fixed highlighting of node on click
Process-ing Jul 19, 2024
5147724
Fix invalid CSS
Process-ing Jul 19, 2024
97469b0
Allow scroll to position on node click
Process-ing Jul 19, 2024
10ce681
Fix non-closed pre tag
Process-ing Jul 19, 2024
87e1a07
Make hover highlight weaker
Process-ing Jul 19, 2024
8fb069d
Fix accessibility issues
Process-ing Jul 19, 2024
be0d21a
Refactor highlighting and remove dropdown from leaf nodes
Process-ing Jul 19, 2024
83c022e
Refactor aspect of AST leaf node dropdown button
Process-ing Jul 19, 2024
a86853f
Add info to ToolJoinPoint
Process-ing Jul 20, 2024
cae1191
Create node info container
Process-ing Jul 20, 2024
6fd78d6
Refactor node info container style and behavior
Process-ing Jul 20, 2024
813c2bd
Fix scroll to code on click
Process-ing Jul 20, 2024
0acb3b8
Allow updating with random root
Process-ing Jul 22, 2024
f81f57c
Fix highlighting after update
Process-ing Jul 22, 2024
ed79f17
Refactor scroll
Process-ing Jul 22, 2024
d3d6c23
Create loading text
Process-ing Jul 22, 2024
ff94c73
Create divider
Process-ing Jul 22, 2024
6e28c45
Implement resizer behavior
Process-ing Jul 22, 2024
5b2d3a8
Add file tabs
Process-ing Jul 23, 2024
1cbc5b6
Clean CSS
Process-ing Jul 23, 2024
080cb01
Style file tabs and refactor some CSS
Process-ing Jul 23, 2024
2189d7f
Start division of code into files
Process-ing Jul 23, 2024
2356599
Finish division of code into files
Process-ing Jul 23, 2024
69f9aa6
Create components script
Process-ing Jul 23, 2024
b17f594
Refactor code file element logic
Process-ing Jul 23, 2024
c898a8e
Allow file focus on node click
Process-ing Jul 24, 2024
43641fe
Fix files clearing
Process-ing Jul 24, 2024
3e95a51
Add syntax higlighting color scheme and classes
Process-ing Jul 24, 2024
a87cf4d
Fix scrollIntoViewIfNeeded and add overflow to node info container
Process-ing Jul 24, 2024
296985e
Show node code when it cannot be found
Process-ing Jul 25, 2024
0c22ffb
Implement dark mode
Process-ing Jul 25, 2024
7b13c2b
Display when node does not have code
Process-ing Jul 25, 2024
e5b3b68
Switch from filename to filepath
Process-ing Jul 25, 2024
14023d6
Make file tab use full file path in title
Process-ing Jul 25, 2024
b23db67
Fix accessibility problems
Process-ing Jul 25, 2024
565b761
Refactor dark theme
Process-ing Jul 25, 2024
c890607
Refactor page scripts to use the main components functions
Process-ing Jul 25, 2024
2617f87
Pass AST rebuilding to Clava
Process-ing Jul 25, 2024
875f8f2
Refactor visualization.ts
Process-ing Jul 25, 2024
13d77e4
Refactor ast-import.ts
Process-ing Jul 25, 2024
5215ce9
Refactor communication.ts
Process-ing Jul 25, 2024
53c2bfc
Refactor files.ts
Process-ing Jul 25, 2024
64bc8c9
Refactor main.ts
Process-ing Jul 25, 2024
37aae5b
Refactor GenericVisualizationTool
Process-ing Jul 25, 2024
35aae09
Refactor GenericVisualizationTool.ts
Process-ing Jul 25, 2024
140a9b6
Create AstConverterUtils.ts
Process-ing Jul 25, 2024
66f9975
Create getSyntaxHighlightTags
Process-ing Jul 26, 2024
4f45647
Write ast-import.ts documentation
Process-ing Jul 26, 2024
ee89676
Write documentation for communication.ts
Process-ing Jul 26, 2024
6c8f409
Write documentation for files.ts
Process-ing Jul 26, 2024
e06acdb
Write documentation for ToolJoinPoint.ts
Process-ing Jul 26, 2024
dd23ba5
Write documentation for utils.ts
Process-ing Jul 26, 2024
bea7433
Add documentation to visualization.ts
Process-ing Jul 26, 2024
06450f9
Write documentation for GenericVisualizationTool
Process-ing Jul 26, 2024
5f4a70a
Write documentation of GenericAstConverter.ts
Process-ing Jul 26, 2024
ade699b
Write documentation for AstConverterUtils.ts
Process-ing Jul 26, 2024
615577a
Fix file tabs horizontal scroll bar missing
Process-ing Jul 29, 2024
5960e80
Refactor files tabs overflow behavior
Process-ing Jul 30, 2024
03a08b8
Add README
Process-ing Aug 14, 2024
a79ecb7
Add report
Process-ing Aug 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Lara-JS/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"tsconfig.json"
],
"scripts": {
"build": "node scripts/copy-folder.js -i src-api/libs -o api/libs && npx tsc -b src-api src-code",
"build": "node scripts/copy-folder.js -i src-api/libs -o api/libs && node scripts/copy-folder.js -i src-api/visualization/public -o api/visualization/public && npx tsc -b src-api src-code",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the visualization API being in its own namespace (i.e., src-api/visualization) instead of being a LARA API (i.e. src-api/lara/visualization)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was just because of what @lm-sousa recommended. In my opinion, it makes sense, because it is a component quite isolated from the rest of the framework, and it is meant to be extended by the compiler and not used directly.

Nonetheless, I am indifferent to this change and it is just a matter of moving the files to a new folder.

"build:api": "npx tsc -b src-api",
"build:code": "npx tsc -b src-code",
"build:watch": "npm run build -- --watch",
Expand Down Expand Up @@ -58,16 +58,20 @@
"dependencies": {
"chokidar": "^3.6.0",
"debug": "^4.3.5",
"express": "^4.19.2",
"java": "^0.14.0",
"supports-color": "^9.4.0",
"ws": "^8.17.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/java": "^0.9.5",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.10",
"@types/ws": "^8.5.11",
"@types/yargs": "^17.0.32",
"typescript-eslint": "^8.0.0-alpha.44",
"cross-env": "^7.0.3",
Expand Down
64 changes: 64 additions & 0 deletions Lara-JS/src-api/visualization/AstConverterUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @file AstConverterUtils.ts
* @brief Utility functions for the specializations of GenericAstConverter.
*/

/**
* @brief Adds indentation to the given code, with the exception of the first line.
*
* @param code Code
* @param indentation The indentation to use
* @returns The indented code
*/
const addIdentation = (code: string, indentation: number): string => {
return code.split('\n').map((line, i) => i > 0 ? ' '.repeat(indentation) + line : line).join('\n');
};


/**
* @brief Escapes the HTML special characters in the given text.
*
* @param text Text to escape
* @returns The escaped text
*/
const escapeHtml = (text: string): string => {
const specialCharMap: { [char: string]: string } = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
};

return text.replace(/[&<>]/g, (match) => specialCharMap[match]);
};

/**
* @brief Returns the opening and closing span tags with the given attributes.
*
* @param attrs Attributes to add to the span tag
* @returns An array with the opening and closing span tags
*/
const getSpanTags = (...attrs: string[]): string[] => {
return [`<span ${attrs.join(' ')}>`, '</span>'];
};

/**
* @brief Returns the span tags for the code of the given node.
*
* @param nodeId Node ID
* @returns An array with the opening and closing span tags
*/
const getNodeCodeTags = (nodeId: string): string[] => {
return getSpanTags('class="node-code"', `data-node-id="${nodeId}"`);
};

/**
* @brief Returns the span tags for syntax highlighting of code of the given type..
*
* @param type Code type
* @returns An array with the opening and closing span tags
*/
const getSyntaxHighlightTags = (type: 'comment' | 'keyword' | 'literal' | 'string' | 'type'): string[] => {
return getSpanTags(`class="${type}"`);
};

export { addIdentation, escapeHtml, getSpanTags, getNodeCodeTags, getSyntaxHighlightTags };
41 changes: 41 additions & 0 deletions Lara-JS/src-api/visualization/GenericAstConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LaraJoinPoint } from "../LaraJoinPoint.js";
import ToolJoinPoint from "./public/js/ToolJoinPoint.js";

/**
* @brief Object type for storing the code of each file
*/
export type FilesCode = {
[filepath: string]: string;
};

/**
* @brief Interface for the AST converter.
* @details This interface includes all the compiler specific operations that
* are required by the LARA visualization tool to work.
*/
export default interface GenericAstConverter {
/**
* @brief Updates/Rebuilds the AST on the compiler.
*/
updateAst(): void;

/**
* @brief Converts the compiler AST joinpoints to ToolJoinPoint.
*
* @param root Root of the AST
* @returns The AST converted to ToolJoinPoint
*/
getToolAst(root: LaraJoinPoint): ToolJoinPoint;

/**
* @brief Converts the compiled code to HTML code.
* @details This method should perform the mapping of each AST joinpoint to
* their respective code, using HTML tags, and optionally its syntax
* highlighting. See the getNodeCodeTags and getSyntaxHighlightTags methods
* in AstConverterUtils.js for more information.
*
* @param root Root of the AST
* @returns The HTML code
*/
getPrettyHtmlCode(root: LaraJoinPoint): FilesCode;
}
218 changes: 218 additions & 0 deletions Lara-JS/src-api/visualization/GenericVisualizationTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import express from 'express';
import http from 'http';
import path from 'path';
import { fileURLToPath } from 'url';
import WebSocket, { WebSocketServer } from 'ws';
import { AddressInfo } from 'net';

import { LaraJoinPoint } from '../LaraJoinPoint.js';
import JoinPoints from '../weaver/JoinPoints.js';
import GenericAstConverter, { FilesCode } from './GenericAstConverter.js';
import ToolJoinPoint from './public/js/ToolJoinPoint.js';

/**
* @brief Abstract class for a the LARA visualization tool.
* @details To use this class in a compiler, this class must be extended and
* the getAstConverter method must be implemented, returning the compiler
* specialization of the GenericAstConverter class.
*/
export default abstract class GenericVisualizationTool {
#hostname: string | undefined;
#port: number | undefined;
#wss: WebSocketServer | undefined;
#serverClosed: boolean = false;
#toolAst: ToolJoinPoint | undefined;
#prettyHtmlCode: FilesCode | undefined;

/**
* @brief True whether the visualization tool is launched, and false otherwise
*/
public get isLaunched(): boolean {
return this.#wss !== undefined && this.#serverClosed === false;
}

/**
* @brief Hostname to which the visualization tool is listening to.
*/
public get hostname(): string | undefined {
return this.#hostname;
}

/**
* @brief Port to which the visualization tool is listening to.
*/
public get port(): number | undefined {
return this.#port;
}

/**
* @brief URL to which the visualization tool is listening to.
*/
public get url(): string | undefined {
return this.#hostname && this.#port ? `http://${this.#hostname}:${this.#port}` : undefined;
}

/**
* @brief Updates the stored tool AST and code with the info retrieved from the astConverter.
*
* @param astRoot The root of the wanted AST
*/
private updateAstAndCode(astRoot: LaraJoinPoint): void {
const astConverter = this.getAstConverter();
astConverter.updateAst();

this.#toolAst = astConverter.getToolAst(astRoot);
this.#prettyHtmlCode = astConverter.getPrettyHtmlCode(astRoot);
}

/**
* @brief WebSocket server error handler.
*
* @param error The error that occurred
*/
private onWssError(error: NodeJS.ErrnoException): void {
switch (error.code) {
case 'EADDRINUSE':
console.error(`[server]: Port ${this.#port} is already in use`);
break;

case 'EACCES':
console.error(`[server]: Permission denied to use port ${this.#port}`);
break;

default:
console.error(`[server]: Unknown error occurred: ${error.message}`);
break;
};

this.#wss!.close();
}

/**
* @brief Launches the visualization tool.
*
* @param hostname The hostname to listen to
* @param port The port to listen to
*/
private async launch(hostname: string, port: number): Promise<void> {
const app = express();
const server = http.createServer(app);
this.#wss = new WebSocketServer({ server: server });

const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
app.use(express.static(path.join(dirname, 'public')));

this.#wss.on('connection', ws => this.updateClient(ws));
this.#wss.on('close', () => { this.#serverClosed = true; });
this.#wss.on('error', error => this.onWssError(error));

return new Promise(res => {
server.listen(port, hostname, () => {
const addressInfo = server.address() as AddressInfo;
this.#hostname = addressInfo.address;
this.#port = addressInfo.port;
this.#serverClosed = false;

res();
});
});
}

/**
* @brief Sends a message to a specific client
*
* @param ws Client WebSocket
* @param data Data to be sent
*/
private sendToClient(ws: WebSocket, data: any): void {
ws.send(JSON.stringify(data));
}

/**
* @brief Sends a message to all the clients
*
* @param data Message data
*/
private sendToAllClients(data: any): void {
this.#wss!.clients.forEach(ws => this.sendToClient(ws, data));
}

/**
* @brief Updates the client with the current tool AST and code.
*
* @param ws Client WebSocket
*/
private updateClient(ws: WebSocket): void {
this.sendToClient(ws, {
message: 'update',
ast: this.#toolAst!.toJson(),
code: this.#prettyHtmlCode!,
});
}

/**
* @brief Updates all the clients with the current tool AST and code.
*/
private updateAllClients(): void {
this.#wss!.clients.forEach(ws => this.updateClient(ws));
}

/**
* @brief Waits for the tool to be ready to receive the AST and code.
*/
private async waitForTool(): Promise<void> {
return new Promise(res => {
let placeClientOnWait: (ws: WebSocket) => void;

const waitOnMessage = (message: string) => {
const data = JSON.parse(message);
if (data.message === 'continue') {
this.#wss!.clients.forEach(ws => {
this.#wss!.off('connection', placeClientOnWait);
ws.off('message', waitOnMessage);
});

this.sendToAllClients({ message: 'continue' });
res();
}
}

placeClientOnWait = (ws: WebSocket) => {
ws.on('message', waitOnMessage);
this.sendToClient(ws, { message: 'wait' });
}

this.#wss!.clients.forEach(placeClientOnWait);
this.#wss!.on('connection', placeClientOnWait);
});
}

/**
* @brief Visualizes the given AST.
* @details This function launches the visualization tool, if it is not
* already launched, and updates the web interface with the AST and code,
* otherwise. This can involve the recompilation of the code.
*
* @param astRoot Root of the AST to be visualized
* @param port The port to listen to
* @param hostname The hostname to listen to
*/
public async visualize(astRoot: LaraJoinPoint = JoinPoints.root(), port: number = 3000, hostname: string = '127.0.0.1'): Promise<void> {
this.updateAstAndCode(astRoot!);

if (!this.isLaunched) {
await this.launch(hostname, port);
} else {
this.updateAllClients();
}

console.log(`\nVisualization tool is running at ${this.url}\n`);
await this.waitForTool();
}

/**
* @brief Returns the compiler AST converter.
*/
protected abstract getAstConverter(): GenericAstConverter;
};
36 changes: 36 additions & 0 deletions Lara-JS/src-api/visualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# LARA Visualization Tool

Web tool for visualization and analysis of the AST and its source code.

## Integration

Internally, the tool follows a system for interaction with the compiler, to be able to apply code linkage and correction, among others, while still being independent of the specific compiler. This system is an implementation of the Factory Method pattern, and the integration with Clava is illustrated in the following diagram:

![Compiler Abstracted System](./compiler-abstracted-system.svg)

To integrate the tool in another compiler:

1. Implement the `GenericAstConverter` interface, with its functions properly implemented. More information can be found in their documentation.
2. Create a class that extends `GenericVisualizationTool`, and override `getAstConverter` so that it returns an instance of the class declared in the previous step.
3. Use an instance of the previous class as the entry point of the visualization tool API, for the compiler in question.

## Usage

After integration, and being `VisualizationTool` the extended derived class of `GenericVisualizationTool`, to launch or update the visualization tool, execute the following statement:

```js
await VisualizationTool.visualize();
```

Once ready, Clava will provide the URL that should be opened in the browser to access the web interface. The function can also change the AST root and URL domain and port.

Other properties will allow the user to know other important information from the server:

```js
VisualizationTool.isLaunched; // true if the server is running
VisualizationTool.url; // URL where the server is running
VisualizationTool.port; // port to which the server is listening
VisualizationTool.hostname; // hostname to which the server is listening
```

For more details, refer to the `GenericVisualizationTool` documentation.
Binary file not shown.
Loading
Loading