-
Notifications
You must be signed in to change notification settings - Fork 2
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
Process-ing
wants to merge
136
commits into
staging
Choose a base branch
from
feature/ast-visualization
base: staging
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 b36bbc5
Fix WebSocket connection
Process-ing 231bba2
Move page files to public folder
Process-ing 4574842
Create simple textareas for the AST and code
Process-ing 7634afb
Improve AST and code containers
Process-ing 5131cbb
Fix missing favicon error
Process-ing 41832e1
Highlight AST node on hover
Process-ing cbbc700
Highlight code of AST node when node hovered
Process-ing bfd487f
Fix highlight of code with special characters
Process-ing e4eca73
Create AST import script
Process-ing 61ada12
Refactor AST node indentation
Process-ing 025c8f9
Refactor node to code linking
Process-ing 7cb33b9
Refactor visualization script
Process-ing eafa60f
Implement highlighting of nodes and code when code is hovered
Process-ing b3f0777
Make tool highlight all possible code correspondences
Process-ing c2f54e6
Convert launch script into a module
Process-ing 46681ff
Copy tool page folder to api
Process-ing ff11659
Export launcher script to TypeScript
Process-ing 9d2a4e9
Convert launch script to VisualizationTool class
Process-ing 207cd67
Convert page scripts to TypeScript
Process-ing e150520
Add VisualizationTool method to create a WebSocket connection
Process-ing e904b0f
Allow dynamic port allocation
Process-ing edc5920
Specify default port
Process-ing e905584
Launch visualization tool on startup
Process-ing 1e4216f
Convert VisualizationTool to a singleton
Process-ing 3bbf29d
Create function to wait for tool
Process-ing a1db69a
Create continue button
Process-ing dee8140
Establish communication between tool server and page
Process-ing d7900ed
Small refactor on VisualizationTool.ts
Process-ing 027e14a
Convert tool not running warning into an error
Process-ing ea791bb
Remove outdated TODO
Process-ing df1f96d
Create function to update tool AST
Process-ing 369807a
Add main script and remove default execution from the remaining modules
Process-ing 1d2935c
Fix node highlighting
Process-ing 00be294
Try to make code generation and linking more dynamic
Process-ing ee9b3c1
Fix AST updates
Process-ing 9b120e9
Send tree data to web page
Process-ing 2a53936
Create ToolJoinPoint class
Process-ing bc0788d
Use real AST on visualization tool
Process-ing 9af14fa
Fix problems with indentation and incerment statements in for loops
Process-ing b833ff1
Fix search and replace bounds
Process-ing ed4a737
Fix condition search for while and do-while loops
Process-ing 7d36c45
Fix multiple variable declaration statements search
Process-ing c5399c8
Fix more bugs with search of expressions inside loop parentheses
Process-ing 14e0d93
Refactor highlighting
Process-ing b5a2422
Add favicon
Process-ing d8ff8f3
Make WebSocket reconnect on close
Process-ing 8ee1c5c
Create color scheme
Process-ing 432b570
Change default font
Process-ing 51f5161
Fix child node ordering
Process-ing f946bfe
Upload SPeCS logo
Process-ing 48f3b6b
Create tool header
Process-ing bd4a3bb
Add Lucide icon to resume button
Process-ing b8d44c5
Refactor page layout and style continue button
Process-ing bfbcf6e
Fix container sizes
Process-ing 609af91
Refactor container style
Process-ing a52165c
Refactor containers
Process-ing f0652fa
Refactor AST container style
Process-ing 20848ce
Group child node elements in dropdowns
Process-ing f3df008
Make dropdown collapsable
Process-ing 92566fb
Stylish node dropdown buttons
Process-ing 11863d2
Fix invalid HTML
Process-ing 5a7a106
Finish old CSS refactor
Process-ing d7e45eb
Fix container size
Process-ing 05ca470
Restylish code container
Process-ing 41e19bf
Change continue button style and migrate icon to Material Icons
Process-ing af21eaf
Refactor highlighting
Process-ing ff3e123
Fix child node order
Process-ing 621e987
Fix invalid HTML code
Process-ing 3b675d9
Start using joinPointType instead of astName
Process-ing 54f6422
Fix AST code corrections
Process-ing ca4fcce
Pass compiler dependant actions to Clava
Process-ing ab2667c
Move AST refining to Clava
Process-ing 427a4f6
Perform node to code linking on Clava
Process-ing f6ae291
Remove code from ToolJoinPoint
Process-ing 4d12d13
Fix highlighting of nodes with multiple code blocks
Process-ing 2a086b9
Allow fixed highlighting of node on click
Process-ing 5147724
Fix invalid CSS
Process-ing 97469b0
Allow scroll to position on node click
Process-ing 10ce681
Fix non-closed pre tag
Process-ing 87e1a07
Make hover highlight weaker
Process-ing 8fb069d
Fix accessibility issues
Process-ing be0d21a
Refactor highlighting and remove dropdown from leaf nodes
Process-ing 83c022e
Refactor aspect of AST leaf node dropdown button
Process-ing a86853f
Add info to ToolJoinPoint
Process-ing cae1191
Create node info container
Process-ing 6fd78d6
Refactor node info container style and behavior
Process-ing 813c2bd
Fix scroll to code on click
Process-ing 0acb3b8
Allow updating with random root
Process-ing f81f57c
Fix highlighting after update
Process-ing ed79f17
Refactor scroll
Process-ing d3d6c23
Create loading text
Process-ing ff94c73
Create divider
Process-ing 6e28c45
Implement resizer behavior
Process-ing 5b2d3a8
Add file tabs
Process-ing 1cbc5b6
Clean CSS
Process-ing 080cb01
Style file tabs and refactor some CSS
Process-ing 2189d7f
Start division of code into files
Process-ing 2356599
Finish division of code into files
Process-ing 69f9aa6
Create components script
Process-ing b17f594
Refactor code file element logic
Process-ing c898a8e
Allow file focus on node click
Process-ing 43641fe
Fix files clearing
Process-ing 3e95a51
Add syntax higlighting color scheme and classes
Process-ing a87cf4d
Fix scrollIntoViewIfNeeded and add overflow to node info container
Process-ing 296985e
Show node code when it cannot be found
Process-ing 0c22ffb
Implement dark mode
Process-ing 7b13c2b
Display when node does not have code
Process-ing e5b3b68
Switch from filename to filepath
Process-ing 14023d6
Make file tab use full file path in title
Process-ing b23db67
Fix accessibility problems
Process-ing 565b761
Refactor dark theme
Process-ing c890607
Refactor page scripts to use the main components functions
Process-ing 2617f87
Pass AST rebuilding to Clava
Process-ing 875f8f2
Refactor visualization.ts
Process-ing 13d77e4
Refactor ast-import.ts
Process-ing 5215ce9
Refactor communication.ts
Process-ing 53c2bfc
Refactor files.ts
Process-ing 64bc8c9
Refactor main.ts
Process-ing 37aae5b
Refactor GenericVisualizationTool
Process-ing 35aae09
Refactor GenericVisualizationTool.ts
Process-ing 140a9b6
Create AstConverterUtils.ts
Process-ing 66f9975
Create getSyntaxHighlightTags
Process-ing 4f45647
Write ast-import.ts documentation
Process-ing ee89676
Write documentation for communication.ts
Process-ing 6c8f409
Write documentation for files.ts
Process-ing e06acdb
Write documentation for ToolJoinPoint.ts
Process-ing dd23ba5
Write documentation for utils.ts
Process-ing bea7433
Add documentation to visualization.ts
Process-ing 06450f9
Write documentation for GenericVisualizationTool
Process-ing 5f4a70a
Write documentation of GenericAstConverter.ts
Process-ing ade699b
Write documentation for AstConverterUtils.ts
Process-ing 615577a
Fix file tabs horizontal scroll bar missing
Process-ing 5960e80
Refactor files tabs overflow behavior
Process-ing 03a08b8
Add README
Process-ing a79ecb7
Add report
Process-ing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
}; | ||
|
||
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
218
Lara-JS/src-api/visualization/GenericVisualizationTool.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
)?There was a problem hiding this comment.
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.