First code commit

This commit is contained in:
2025-03-29 22:56:04 +01:00
parent b80e89af6d
commit 3c16632a91
20 changed files with 743 additions and 0 deletions

43
src/elements/Common.ts Normal file
View File

@@ -0,0 +1,43 @@
import Parser from 'web-tree-sitter';
import { IPosition } from './interfaces/Common';
/**
* Position of a node in the source code.
*/
export class Position implements IPosition {
/**
* Convert a tree-sitter node to a position object.
* @param node The tree-sitter node to convert.
* @returns A position object representing the node.
*/
static fromNode(node: Parser.SyntaxNode): Position {
return new Position(
node.startPosition.row,
node.endPosition.row,
node.startPosition.column,
node.endPosition.column,
);
}
/**
* Position of a node in the source code.
* @param startLine Start line number
* @param endLine End line number
* @param startColumn Start column number
* @param endColumn End column number
*/
constructor(
public startLine: number,
public endLine: number,
public startColumn: number,
public endColumn: number,
) {}
/**
* Convert the position to a string representation.
* @returns String representation of the position
*/
toString(): string {
return `(${this.startLine}:${this.startColumn}-${this.endLine}:${this.endColumn})`;
}
}

View File

@@ -0,0 +1,53 @@
import { IPosition } from './interfaces/Common';
import { IHDLElement, IHDLElementInfo } from './interfaces/IHDLElement';
/**
* Base implementation for all HDL elements.
* @template T - Type of the HDL element info.
*/
export class HDLElement<T extends IHDLElementInfo> implements IHDLElement<T> {
protected _info: T;
/**
* Create a new HDL element.
* @param info Metadata about the HDL element
*/
constructor(info: T) {
this._info = info;
}
/**
* Get the name of the HDL element.
*/
get name(): string {
return this._info.name ?? '';
}
/**
* Get the machine-generated description of the HDL element.
*/
get description(): string | undefined {
return this._info.description;
}
/**
* Get the code comment associated with the HDL element.
*/
get comment(): string | undefined {
return this._info.comment;
}
/**
* Get the position of the HDL element in the source code.
*/
get position(): IPosition {
return this._info.position;
}
/**
* Get the full metadata object.
*/
get info(): T {
return this._info;
}
}

View File

@@ -0,0 +1,24 @@
import { HDLElement } from './HDLElement';
import { IHDLFunctionInfo, IHDLFunction } from './interfaces/IHDLFunction';
/**
* Function element in VHDL.
*/
export class HDLFunction
extends HDLElement<IHDLFunctionInfo>
implements IHDLFunction
{
/**
* @inheritdoc
*/
get parameters(): string {
return this._info.parameters;
}
/**
* @inheritdoc
*/
get returnType(): string {
return this._info.returnType;
}
}

View File

@@ -0,0 +1,20 @@
import { HDLElement } from './HDLElement';
import {
IHDLInstantiationInfo,
IHDLInstantiation,
} from './interfaces/IHDLInstantiation';
/**
* Instantiation element in VHDL.
*/
export class Instantiation
extends HDLElement<IHDLInstantiationInfo>
implements IHDLInstantiation
{
/**
* @inheritdoc
*/
get instanceType(): string {
return this._info.instanceType;
}
}

View File

@@ -0,0 +1,17 @@
import { HDLElement } from './HDLElement';
import { IHDLProcedureInfo, IHDLProcedure } from './interfaces/IHDLProcedure';
/**
* Procedure element in VHDL.
*/
export class HDLProcedure
extends HDLElement<IHDLProcedureInfo>
implements IHDLProcedure
{
/**
* @inheritdoc
*/
get parameters(): string {
return this._info.parameters;
}
}

View File

@@ -0,0 +1,24 @@
import { HDLElement } from './HDLElement';
import { IHDLProcessInfo, IHDLProcess } from './interfaces/IHDLProcess';
/**
* Process element in VHDL.
*/
export class HDLProcess
extends HDLElement<IHDLProcessInfo>
implements IHDLProcess
{
/**
* @inheritdoc
*/
get sensitivityList(): string[] | undefined {
return this._info.sensitivityList;
}
/**
* @inheritdoc
*/
get processType(): string | undefined {
return this._info.processType;
}
}

24
src/elements/HDLSignal.ts Normal file
View File

@@ -0,0 +1,24 @@
import { HDLElement } from './HDLElement';
import { IHDLSignalInfo, IHDLSignal } from './interfaces/IHDLSignal';
/**
* Signal element in VHDL.
*/
export class HDLSignal
extends HDLElement<IHDLSignalInfo>
implements IHDLSignal
{
/**
* @inheritdoc
*/
get type(): string {
return this._info.type;
}
/**
* @inheritdoc
*/
get defaultValue(): string | undefined {
return this._info.defaultValue;
}
}

View File

@@ -0,0 +1,52 @@
import Parser from 'web-tree-sitter';
/**
* Static interface for position objects.
*/
export interface IPosition_ {
/**
* Convert a tree-sitter node to a position object.
* @param node The tree-sitter node to convert.
* @returns A position object representing the node.
*/
fromNode(node: Parser.SyntaxNode): IPosition;
/**
* Create a new position object.
* @param startLine Start line number
* @param endLine End line number
* @param startColumn Start column number
* @param endColumn End column number
*/
new (
startLine: number,
endLine: number,
startColumn: number,
endColumn: number,
): IPosition;
}
/**
* Position of a node in the source code.
*/
export interface IPosition {
/**
* The line number of the start of the node.
*/
startLine: number;
/**
* The line number of the end of the node.
*/
endLine: number;
/**
* The column number of the start of the node.
*/
startColumn: number;
/**
* The column number of the end of the node.
*/
endColumn: number;
}

View File

@@ -0,0 +1,14 @@
export interface HDLElementTagNameMap {
process: 'HDLProcess';
type: 'HDLType';
signal: 'HDLSignal';
variable: 'HDLVariable';
constant: 'HDLConstant';
attribute: 'HDLAttribute';
function: 'HDLFunction';
procedure: 'HDLProcedure';
instance: 'HDLInstance';
generic: 'HDLGeneric';
port: 'HDLPort';
concurrent: 'HDLConcurrent';
}

View File

@@ -0,0 +1,66 @@
import { IPosition } from './Common';
/**
* Shared metadata for any HDL element.
*/
export interface IHDLElementInfo {
/**
* Name of the HDL element.
* e.g. "my_signal", "my_function", "my_process".
*/
name?: string;
/**
* Machine generated description of the element.
*/
description?: string;
/**
* Code comment associated with the HDL element.
*/
comment?: string;
/**
* Position of the HDL element in the source code.
*/
position: IPosition;
}
/**
* Static interface for all HDL elements.
* @template T - Type of the HDL element info.
*/
export interface IHDLElement_<T extends IHDLElementInfo> {
new (info: T): IHDLElement<T>;
}
/**
* Interface for all HDL elements.
* @template T - Type of the HDL element info.
*/
export interface IHDLElement<T extends IHDLElementInfo> {
/**
* Get the name of the HDL element.
*/
get name(): string;
/**
* Get the machine generated description of the HDL element.
*/
get description(): string | undefined;
/**
* Get the code comment associated with the HDL element.
*/
get comment(): string | undefined;
/**
* Get the position of the HDL element in the source code.
*/
get position(): IPosition;
/**
* Get the full metadata object.
*/
get info(): T;
}

View File

@@ -0,0 +1,31 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for function metadata.
*/
export interface IHDLFunctionInfo extends IHDLElementInfo {
/**
* Parameter list as string.
*/
parameters: string;
/**
* Return type.
*/
returnType: string;
}
/**
* Interface for function elements.
*/
export interface IHDLFunction extends IHDLElement<IHDLFunctionInfo> {
/**
* Get the parameter list of the function.
*/
get parameters(): string;
/**
* Get the return type of the function.
*/
get returnType(): string;
}

View File

@@ -0,0 +1,22 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for component instantiation metadata.
*/
export interface IHDLInstantiationInfo extends IHDLElementInfo {
/**
* Type of the instantiated.
* e.g. "my_component", "my_package.my_component".
*/
instanceType: string;
}
/**
* Interface for instantiation elements.
*/
export interface IHDLInstantiation extends IHDLElement<IHDLInstantiationInfo> {
/**
* Get the type of the instantiated element.
*/
get instanceType(): string;
}

View File

@@ -0,0 +1,21 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for procedure metadata.
*/
export interface IHDLProcedureInfo extends IHDLElementInfo {
/**
* Parameter list as string.
*/
parameters: string;
}
/**
* Interface for procedure elements.
*/
export interface IHDLProcedure extends IHDLElement<IHDLProcedureInfo> {
/**
* Get the parameter list of the procedure.
*/
get parameters(): string;
}

View File

@@ -0,0 +1,33 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for process metadata.
*/
export interface IHDLProcessInfo extends IHDLElementInfo {
/**
* Sensitivity list of the process.
*/
sensitivityList?: string[];
/**
* Type of process (optional: FSM, combinatorial, etc.).
*/
processType?: string;
}
/**
* Interface for process elements.
*/
export interface IHDLProcess extends IHDLElement<IHDLProcessInfo> {
/**
* Get the sensitivity list of the process.
* e.g. "clk", "reset".
*/
get sensitivityList(): string[] | undefined;
/**
* Get the type of the process.
* e.g. "FSM", "combinatorial", etc.
*/
get processType(): string | undefined;
}

View File

@@ -0,0 +1,33 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for signal metadata.
*/
export interface IHDLSignalInfo extends IHDLElementInfo {
/**
* Type of the signal.
* e.g. "std_logic", "std_logic_vector(7 downto 0)".
*/
type: string;
/**
* Default value of the signal.
*/
defaultValue?: string;
}
/**
* Interface for signal elements.
*/
export interface IHDLSignal extends IHDLElement<IHDLSignalInfo> {
/**
* Get the type of the signal.
* e.g. "std_logic", "std_logic_vector(7 downto 0)".
*/
get type(): string;
/**
* Get the default value of the signal.
*/
get defaultValue(): string | undefined;
}

View File

@@ -0,0 +1,102 @@
import Parser from 'web-tree-sitter';
import { ICommentOptions } from './interfaces/ICommentOptions';
import {
IHDLElement,
IHDLElementInfo,
} from '../elements/interfaces/IHDLElement';
/**
* Abstract base class for all HDL element extractors.
* Collects and provides access to leading comments and implements recursive AST traversal.
*/
export abstract class HDLElementExtractor<
T extends IHDLElement<IHDLElementInfo>,
> {
protected readonly _comments: Map<number, string> = new Map();
constructor(
protected readonly root: Parser.SyntaxNode,
protected readonly commentOptions: ICommentOptions = {
markerPrefix: '@',
stripPrefix: true,
},
) {}
/**
* Collect all comments in the file and map them by line number.
*/
protected extractComments(): void {
const stack: Parser.SyntaxNode[] = [this.root];
while (stack.length > 0) {
const node = stack.pop();
if (!node) continue;
if (node.type === 'comment') {
let comment = node.text.trim().replace(/^--\s*/, '');
const prefix = this.commentOptions.markerPrefix;
if (prefix && !comment.startsWith(prefix)) return;
if (prefix && this.commentOptions.stripPrefix) {
comment = comment.substring(prefix.length);
if (comment.startsWith(' ')) comment = comment.substring(1);
}
this._comments.set(node.startPosition.row, comment);
}
for (const child of node.namedChildren) {
stack.push(child);
}
}
}
/**
* Try to find a contiguous block of comment lines directly above the given line.
* Stops at the first non-comment or skipped line.
* @param line The line number to check for comments.
* @returns The comment block as a string, or undefined if no comments are found.
*/
protected getLeadingComment(line: number): string | undefined {
const lines: string[] = [];
let currentLine = line - 1;
while (this._comments.has(currentLine)) {
lines.unshift(this._comments.get(currentLine)!);
currentLine--;
}
return lines.length > 0 ? lines.join('\n') : undefined;
}
/**
* Recursively find all nodes of a given type in the syntax tree.
* @param node The starting node.
* @param type The node type to search for.
* @returns A flat array of matching nodes.
*/
protected findNodesByType(
node: Parser.SyntaxNode,
type: string,
): Parser.SyntaxNode[] {
const result: Parser.SyntaxNode[] = [];
if (node.type === type) {
result.push(node);
}
for (const child of node.namedChildren) {
result.push(...this.findNodesByType(child, type));
}
return result;
}
/**
* The main extraction method to be implemented by subclasses.
*/
abstract extract(): T[];
}

View File

@@ -0,0 +1,51 @@
import { HDLElementExtractor } from './HDLElementExtractor';
import { Position } from '../elements/Common';
import { HDLSignal } from '../elements/HDLSignal';
import { IHDLSignalInfo } from '../elements/interfaces/IHDLSignal';
/**
* Extractor for VHDL signal declarations.
*/
export class SignalExtractor extends HDLElementExtractor<HDLSignal> {
/**
* Extract all signal declarations from the syntax tree.
*/
extract(): HDLSignal[] {
this.extractComments();
const result: HDLSignal[] = [];
const signalNodes = this.findNodesByType(
this.root,
'signal_declaration',
);
for (const node of signalNodes) {
const nameNode = node.descendantsOfType('identifier_list')[0];
const typeNode = node.descendantsOfType('subtype_indication')[0];
const defaultNode = node.descendantsOfType('default_expression')[0];
const names = nameNode.text.split(',').map((s) => s.trim());
const type = typeNode?.text ?? '';
const defaultValue = defaultNode?.text ?? undefined;
const pos = Position.fromNode(nameNode);
for (const name of names) {
const comment = this.getLeadingComment(pos.startLine);
const info: IHDLSignalInfo = {
name,
type,
defaultValue,
comment,
description: undefined,
position: pos,
};
result.push(new HDLSignal(info));
}
}
return result;
}
}

View File

@@ -0,0 +1,16 @@
/**
* Options for how to treat HDL comments.
*/
export interface ICommentOptions {
/**
* Line must start with this prefix (after '--').
* Example: '@' to accept '--@ comment'.
*/
markerPrefix?: string;
/**
* If true, strip the marker prefix and one space (if present).
* Example: '--@ Hello' becomes 'Hello'
*/
stripPrefix?: boolean;
}

56
src/parser/VHDLParser.ts Normal file
View File

@@ -0,0 +1,56 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import Parser from 'web-tree-sitter';
import { IParser } from './interfaces/IParser';
/**
* VHDLParser is a class that provides methods to parse VHDL code
* using the tree-sitter library.
*/
export class VHDLParser implements IParser {
private _parser!: Parser;
/**
* @inheritdoc
*/
async init(): Promise<void> {
await Parser.init();
this._parser = new Parser();
const lang = await Parser.Language.load(
join(__dirname, '../vendor/tree-sitter-vhdl.wasm'),
);
this._parser.setLanguage(lang);
}
/**
* @inheritdoc
*/
parse(code: string): Parser.Tree {
return this._parser.parse(code);
}
/**
* @inheritdoc
*/
async parseFile(path: string): Promise<Parser.Tree> {
const code = readFileSync(path, 'utf-8');
return this.parse(code);
}
/**
* @inheritdoc
*/
async toJson(node: Parser.SyntaxNode): Promise<unknown> {
return {
type: node.type,
text: node.text,
startPosition: node.startPosition,
endPosition: node.endPosition,
children: await Promise.all(
node.namedChildren.map((child) => this.toJson(child)),
),
};
}
}

View File

@@ -0,0 +1,41 @@
import Parser from 'web-tree-sitter';
/**
* Static parser interface
*/
export interface IParser_ {
new (): IParser;
}
/**
* Parser interface
*/
export interface IParser {
/**
* Initialize the parser and load the language.
* @returns A promise that resolves when the parser is initialized.
*/
init(): Promise<void>;
/**
* Parse a string of code and return the parse tree.
* @param code The code to parse
* @returns The parsed tree.
*/
parse(code: string): Parser.Tree;
/**
* Parse a file and return the parse tree.
* @param path The path to the file to parse
* @returns The parsed tree.
*/
parseFile(path: string): Promise<Parser.Tree>;
/**
* Convert a tree-sitter node to a JSON object.
* This is useful for debugging and visualization purposes.
* @param node The tree-sitter node to convert.
* @returns A JSON object representing the node.
*/
toJson(node: Parser.SyntaxNode): Promise<unknown>;
}