Add HDL elements and interfaces for VHDL types, generics, functions, procedures, constants, variables, assignments, and ports

This commit is contained in:
2025-04-02 19:52:28 +02:00
parent d027a1831a
commit 93d441531d
32 changed files with 1525 additions and 48 deletions

View File

@@ -0,0 +1,27 @@
import { HDLElement } from './HDLElement';
import {
IHDLAssignment,
IHDLAssignmentInfo,
} from './interfaces/IHDLAssignment';
/**
* Concurrent signal assignment in VHDL.
*/
export class HDLAssignment
extends HDLElement<IHDLAssignmentInfo>
implements IHDLAssignment
{
/**
* @inheritdoc
*/
get target(): string {
return this._info.target;
}
/**
* @inheritdoc
*/
get expression(): string {
return this._info.expression;
}
}

View File

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

View File

@@ -1,4 +1,5 @@
import { HDLElement } from './HDLElement';
import { IHDLParameter } from './interfaces/Common';
import { IHDLFunctionInfo, IHDLFunction } from './interfaces/IHDLFunction';
/**
@@ -15,6 +16,13 @@ export class HDLFunction
return this._info.parameters;
}
/**
* @inheritdoc
*/
get parameterList(): IHDLParameter[] {
return this._info.parameterList;
}
/**
* @inheritdoc
*/

View File

@@ -0,0 +1,18 @@
import { HDLElement } from './HDLElement';
import { IHDLParameter } from './interfaces/Common';
import { IHDLGenericInfo, IHDLGeneric } from './interfaces/IHDLGeneric';
/**
* Generic element in VHDL.
*/
export class HDLGeneric
extends HDLElement<IHDLGenericInfo>
implements IHDLGeneric
{
/**
* @inheritdoc
*/
get genericList(): IHDLParameter[] | undefined {
return this._info.genericList;
}
}

22
src/elements/HDLPort.ts Normal file
View File

@@ -0,0 +1,22 @@
import { HDLElement } from './HDLElement';
import { IHDLParameter, IVirtualBus } from './interfaces/Common';
import { IHDLPortInfo, IHDLPort } from './interfaces/IHDLPort';
/**
* Port element for HDL
*/
export class HDLPort extends HDLElement<IHDLPortInfo> implements IHDLPort {
/**
* @inheritdoc
*/
get portList(): IHDLParameter[] | undefined {
return this._info.portList;
}
/**
* @inheritdoc
*/
get virtualBusList(): IVirtualBus[] | undefined {
return this._info.virtualBusList;
}
}

View File

@@ -1,4 +1,5 @@
import { HDLElement } from './HDLElement';
import { IHDLParameter } from './interfaces/Common';
import { IHDLProcedureInfo, IHDLProcedure } from './interfaces/IHDLProcedure';
/**
@@ -14,4 +15,11 @@ export class HDLProcedure
get parameters(): string {
return this._info.parameters;
}
/**
* @inheritdoc
*/
get parameterList(): IHDLParameter[] {
return this._info.parameterList;
}
}

14
src/elements/HDLType.ts Normal file
View File

@@ -0,0 +1,14 @@
import { HDLElement } from './HDLElement';
import { IHDLType, IHDLTypeInfo } from './interfaces/IHDLTypeInfo';
/**
* Type element in VHDL.
*/
export class HDLType extends HDLElement<IHDLTypeInfo> implements IHDLType {
/**
* @inheritdoc
*/
get typeExpression(): string {
return this._info.typeExpression;
}
}

View File

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

View File

@@ -50,3 +50,58 @@ export interface IPosition {
*/
endColumn: number;
}
/**
* Interface for HDL parameters/ports/generics.
*/
export interface IHDLParameter {
/**
* The name of the parameter.
*/
name: string;
/**
* The mode of the parameter.
* e.g., "in", "out", "inout"
*/
mode?: string;
/**
* The type of the parameter.
* e.g., "std_logic", "integer", "real"
*/
type: string;
/**
* Default value, if specified.
*/
defaultValue?: string;
/**
* Optional comment describing the generic.
*/
comment?: string;
}
export interface IVirtualBus {
/**
* The name of the virtual bus.
*/
name: string;
/**
* The mode of the virtual bus.
* e.g., "in", "out", "inout"
*/
mode?: string;
/**
* The signals of the virtual bus.
*/
signals: IHDLParameter[];
/**
* Optional comment describing the virtual bus.
*/
comment?: string;
}

View File

@@ -0,0 +1,20 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for concurrent signal assignment metadata.
*/
export interface IHDLAssignmentInfo extends IHDLElementInfo {
/** Name of the signal being assigned. */
target: string;
/** The expression assigned to the signal. */
expression: string;
}
/**
* Interface for concurrent signal assignment elements.
*/
export interface IHDLAssignment extends IHDLElement<IHDLAssignmentInfo> {
get target(): string;
get expression(): string;
}

View File

@@ -0,0 +1,32 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for constant metadata.
*/
export interface IHDLConstantInfo extends IHDLElementInfo {
/**
* Type of the constant.
* e.g. "integer", "std_logic".
*/
type: string;
/**
* Default value of the constant.
*/
defaultValue?: string;
}
/**
* Interface for constant elements.
*/
export interface IHDLConstant extends IHDLElement<IHDLConstantInfo> {
/**
* Get the type of the constant.
*/
get type(): string;
/**
* Get the default value of the constant.
*/
get defaultValue(): string | undefined;
}

View File

@@ -1,3 +1,4 @@
import { IHDLParameter } from './Common';
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
@@ -9,6 +10,11 @@ export interface IHDLFunctionInfo extends IHDLElementInfo {
*/
parameters: string;
/**
* Structured list of parameters.
*/
parameterList: IHDLParameter[];
/**
* Return type.
*/
@@ -24,6 +30,11 @@ export interface IHDLFunction extends IHDLElement<IHDLFunctionInfo> {
*/
get parameters(): string;
/**
* Get structured parameter list.
*/
get parameterList(): IHDLParameter[];
/**
* Get the return type of the function.
*/

View File

@@ -0,0 +1,22 @@
import { IHDLParameter } from './Common';
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for process metadata.
*/
export interface IHDLGenericInfo extends IHDLElementInfo {
/**
* List of generics.
*/
genericList?: IHDLParameter[];
}
/**
* Interface for process elements.
*/
export interface IHDLGeneric extends IHDLElement<IHDLGenericInfo> {
/**
* Get the generic list of the element.
*/
get genericList(): IHDLParameter[] | undefined;
}

View File

@@ -0,0 +1,32 @@
import { IHDLParameter, IVirtualBus } from './Common';
import { IHDLElementInfo } from './IHDLElement';
/**
* Interface for port metadata.
*/
export interface IHDLPortInfo extends IHDLElementInfo {
/**
* List of ports.
*/
portList?: IHDLParameter[];
/**
* List of virtual buses.
*/
virtualBusList?: IVirtualBus[];
}
/**
* Interface for port elements.
*/
export interface IHDLPort {
/**
* Get the port list of the element.
*/
get portList(): IHDLParameter[] | undefined;
/**
* Get the virtual bus list of the element.
*/
get virtualBusList(): IVirtualBus[] | undefined;
}

View File

@@ -1,3 +1,4 @@
import { IHDLParameter } from './Common';
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
@@ -8,6 +9,11 @@ export interface IHDLProcedureInfo extends IHDLElementInfo {
* Parameter list as string.
*/
parameters: string;
/**
* Parameter list as structured array.
*/
parameterList: IHDLParameter[];
}
/**
@@ -18,4 +24,9 @@ export interface IHDLProcedure extends IHDLElement<IHDLProcedureInfo> {
* Get the parameter list of the procedure.
*/
get parameters(): string;
/**
* Get the parameter list of the procedure as structured array.
*/
get parameterList(): IHDLParameter[];
}

View File

@@ -0,0 +1,21 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for type metadata.
*/
export interface IHDLTypeInfo extends IHDLElementInfo {
/**
* Full VHDL array/type expression.
*/
typeExpression: string;
}
/**
* Interface for type elements.
*/
export interface IHDLType extends IHDLElement<IHDLTypeInfo> {
/**
* Get the full VHDL type expression.
*/
get typeExpression(): string;
}

View File

@@ -0,0 +1,32 @@
import { IHDLElement, IHDLElementInfo } from './IHDLElement';
/**
* Interface for variable metadata.
*/
export interface IHDLVariableInfo extends IHDLElementInfo {
/**
* Type of the variable.
* e.g. "integer", "std_logic".
*/
type: string;
/**
* Default value of the variable.
*/
defaultValue?: string;
}
/**
* Interface for variable elements.
*/
export interface IHDLVariable extends IHDLElement<IHDLVariableInfo> {
/**
* Get the type of the variable.
*/
get type(): string;
/**
* Get the default value of the variable.
*/
get defaultValue(): string | undefined;
}

View File

@@ -0,0 +1,56 @@
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLAssignment } from '../elements/HDLAssignment';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLAssignmentInfo } from '../elements/interfaces/IHDLAssignment';
/**
* AssignmentExtractor extracts concurrent signal assignments from a VHDL AST.
*/
export class AssignmentExtractor implements INodeExtractor<HDLAssignment> {
public readonly nodeType = 'simple_concurrent_signal_assignment';
/**
* @inheritdoc
*/
public readonly excludedParents = ['process_statement', 'declarative_part'];
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLAssignment> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLAssignment[] => {
const result: HDLAssignment[] = [];
const targetNode = node.childForFieldName('target');
const waveformsNode = node.namedChildren.find(
(c) => c.type === 'waveforms',
);
const target = targetNode?.text.trim() ?? 'unknown';
const expression = waveformsNode?.text.trim() ?? 'unknown';
const pos = Position.fromNode(node);
const comment = getLeadingComment(pos.startLine);
const info: IHDLAssignmentInfo = {
name: target,
target,
expression,
comment,
description: undefined,
position: pos,
};
result.push(new HDLAssignment(info));
return result;
};
}

View File

@@ -0,0 +1,66 @@
/* eslint-disable max-len */
/* eslint-disable no-console */
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLConstant } from '../elements/HDLConstant';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLConstantInfo } from '../elements/interfaces/IHDLConstant';
/**
* ConstantExtractor extracts constant declarations from a VHDL AST.
*/
export class ConstantExtractor implements INodeExtractor<HDLConstant> {
/**
* @inheritdoc
*/
public readonly nodeType = 'constant_declaration';
/**
* @inheritdoc
*/
public readonly excludedParents = [
'function_body',
'procedure_body',
'process_statement',
];
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLConstant> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLConstant[] => {
const result: HDLConstant[] = [];
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 = getLeadingComment(pos.startLine);
const info: IHDLConstantInfo = {
name,
type,
defaultValue,
comment,
description: undefined,
position: pos,
};
result.push(new HDLConstant(info));
}
return result;
};
}

View File

@@ -0,0 +1,103 @@
import { IHDLParameter } from 'elements/interfaces/Common';
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLFunction } from '../elements/HDLFunction';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLFunctionInfo } from '../elements/interfaces/IHDLFunction';
/**
* FunctionExtractor extracts function declarations from a VHDL AST.
*/
export class FunctionExtractor implements INodeExtractor<HDLFunction> {
/**
* @inheritdoc
*/
public readonly nodeType = 'function_body';
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLFunction> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLFunction[] => {
const result: HDLFunction[] = [];
const nameNode = node.namedChildren.find(
(c) => c.type === 'identifier',
);
const parameterClause = node.namedChildren.find(
(c) => c.type === 'function_parameter_clause',
);
const returnNode = node.namedChildren.find((c) => c.type === 'return');
const returnTypeNode = returnNode?.namedChildren.find(
(c) => c.type === 'type_mark',
);
const name = nameNode?.text.trim() ?? 'unnamed';
const returnType = returnTypeNode?.text.trim() ?? 'void';
const paramLines: string[] = [];
const parameterList: IHDLParameter[] = [];
if (parameterClause) {
for (const decl of parameterClause.namedChildren) {
const identifierList = decl.namedChildren
.find((c) => c.type === 'identifier_list')
?.text.trim();
const typeNode = decl.namedChildren.find(
(c) => c.type === 'subtype_indication',
);
const modeNode = decl.namedChildren.find(
(c) => c.type === 'mode',
);
const mode = modeNode?.text.trim(); // optional!
const type = typeNode?.text.trim() ?? '';
if (identifierList) {
const names = identifierList
.split(',')
.map((n) => n.trim());
for (const name of names) {
parameterList.push({ name, mode, type });
paramLines.push(
mode
? `${name}: ${mode} ${type}`
: `${name}: ${type}`,
);
}
}
}
}
const parameters = paramLines.join('; ');
const pos = Position.fromNode(node);
const comment = getLeadingComment(pos.startLine);
const info: IHDLFunctionInfo = {
name,
parameters,
parameterList,
returnType,
position: pos,
comment,
description: undefined,
};
result.push(new HDLFunction(info));
return result;
};
}

View File

@@ -0,0 +1,90 @@
import { Position } from 'elements/Common';
import { HDLGeneric } from 'elements/HDLGeneric';
import { IHDLParameter } from 'elements/interfaces/Common';
import { IHDLGenericInfo } from 'elements/interfaces/IHDLGeneric';
import Parser from 'web-tree-sitter';
import {
INodeExtractor,
NodeHandler,
GetLeadingComment,
} from './interfaces/IHDLElementExtractor';
/**
* GenericExtractor extracts generic declarations from a VHDL AST.
*/
export class GenericExtractor implements INodeExtractor<HDLGeneric> {
/**
* @inheritdoc
*/
public get nodeType(): string {
return 'generic_clause';
}
/**
* @inheritdoc
*/
public readonly excludedParents = [
'function_body',
'procedure_body',
'process_statement',
];
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLGeneric> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLGeneric[] => {
const result: HDLGeneric[] = [];
const genericNodes = node.descendantsOfType(
'constant_interface_declaration',
);
const generics: IHDLParameter[] = [];
for (const genericNode of genericNodes) {
const nameNode =
genericNode.childForFieldName('identifier') ??
genericNode.descendantsOfType('identifier')[0];
const typeNode =
genericNode.childForFieldName('subtype_indication') ??
genericNode.descendantsOfType('subtype_indication')[0];
const defaultNode =
genericNode.childForFieldName('default_expression') ??
genericNode.descendantsOfType('default_expression')[0];
const name = nameNode?.text ?? '';
const type = typeNode?.text ?? '';
const defaultValue = defaultNode?.text ?? undefined;
const comment = getLeadingComment(
nameNode?.startPosition.row ?? genericNode.startPosition.row,
);
generics.push({
name,
type,
defaultValue,
comment,
});
}
const pos = Position.fromNode(node);
const info: IHDLGenericInfo = {
name: 'GenericClause',
genericList: generics,
position: pos,
comment: getLeadingComment(pos.startLine),
description: undefined,
};
result.push(new HDLGeneric(info));
return result;
};
}

View File

@@ -1,17 +1,21 @@
import Parser from 'web-tree-sitter';
import { ICommentOptions } from './interfaces/ICommentOptions';
import {
GetLeadingComment,
IHDLElementExtractor,
INodeExtractor,
} from './interfaces/IHDLElementExtractor';
import {
IHDLElement,
IHDLElementInfo,
} from '../elements/interfaces/IHDLElement';
/**
* Abstract base class for all HDL element extractors.
* 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>,
> {
export class HDLElementExtractor<T extends IHDLElement<IHDLElementInfo>>
implements IHDLElementExtractor<T>
{
protected readonly _comments: Map<number, string> = new Map();
constructor(
@@ -20,6 +24,8 @@ export abstract class HDLElementExtractor<
markerPrefix: '@',
stripPrefix: true,
},
protected readonly nodeExtractor: INodeExtractor<T>,
) {}
/**
@@ -60,7 +66,9 @@ export abstract class HDLElementExtractor<
* @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 {
private readonly getLeadingComment: GetLeadingComment = (
line: number,
): string | undefined => {
const lines: string[] = [];
let currentLine = line - 1;
@@ -70,7 +78,7 @@ export abstract class HDLElementExtractor<
}
return lines.length > 0 ? lines.join('\n') : undefined;
}
};
/**
* Recursively find all nodes of a given type in the syntax tree.
@@ -84,6 +92,10 @@ export abstract class HDLElementExtractor<
): Parser.SyntaxNode[] {
const result: Parser.SyntaxNode[] = [];
if (this.nodeExtractor.excludedParents?.includes(node.type)) {
return result;
}
if (node.type === type) {
result.push(node);
}
@@ -96,7 +108,25 @@ export abstract class HDLElementExtractor<
}
/**
* The main extraction method to be implemented by subclasses.
* Extract all elements of the specified type from the syntax tree.
* Uses the provided handler function to process each node.
* @returns An array of extracted elements.
*/
abstract extract(): T[];
public extract(): T[] {
this.extractComments();
const result: T[] = [];
const nodes = this.findNodesByType(
this.root,
this.nodeExtractor.nodeType,
);
for (const node of nodes) {
result.push(
...this.nodeExtractor.nodeHandler(node, this.getLeadingComment),
);
}
return result;
}
}

View File

@@ -0,0 +1,65 @@
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { Instantiation } from '../elements/HDLInstantiation';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLInstantiationInfo } from '../elements/interfaces/IHDLInstantiation';
/**
* InstantiationExtractor extracts component instantiations from a VHDL AST.
*/
export class InstantiationExtractor implements INodeExtractor<Instantiation> {
/**
* @inheritdoc
*/
public get nodeType(): string {
return 'component_instantiation_statement';
}
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<Instantiation> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): Instantiation[] => {
const result: Instantiation[] = [];
// Name of the instance (label)
const labelNode = node.namedChildren.find((c) => c.type === 'label');
const identifier = labelNode?.namedChildren.find(
(c) => c.type === 'identifier',
);
const name = identifier?.text.trim() ?? 'unnamed';
// Try to extract instantiated entity name
const entityInstantiation = node.namedChildren.find(
(c) => c.type === 'entity_instantiation',
);
const selectedName = entityInstantiation?.namedChildren.find(
(c) => c.type === 'selected_name',
);
const instanceType = selectedName?.text.trim() ?? 'unknown';
const pos = Position.fromNode(node);
const comment = getLeadingComment(pos.startLine);
const info: IHDLInstantiationInfo = {
name,
instanceType,
position: pos,
comment,
description: undefined,
};
result.push(new Instantiation(info));
return result;
};
}

View File

@@ -0,0 +1,122 @@
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLPort } from '../elements/HDLPort';
import {
INodeExtractor,
NodeHandler,
GetLeadingComment,
} from './interfaces/IHDLElementExtractor';
import { IHDLParameter, IVirtualBus } from '../elements/interfaces/Common';
import { IHDLPortInfo } from '../elements/interfaces/IHDLPort';
/**
* PortExtractor extracts port declarations and virtual buses from a VHDL AST.
*/
export class PortExtractor implements INodeExtractor<HDLPort> {
public readonly nodeType = 'port_clause';
public readonly excludedParents = [];
private extractText(node: Parser.SyntaxNode | null | undefined): string {
return node?.text?.trim() ?? '';
}
public readonly nodeHandler: NodeHandler<HDLPort> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLPort[] => {
const ports: IHDLParameter[] = [];
const virtualBuses: IVirtualBus[] = [];
let currentVB: IVirtualBus | null = null;
let commentBuffer: string[] = [];
for (const child of node.children) {
if (child.type === 'comment') {
const text = child.text.replace(/^--@\s*/, '').trim();
if (text.startsWith('@virtualbus')) {
const parts = text.split(/\s+/);
const name = parts[1];
const dirIndex = parts.indexOf('@dir');
const dir =
dirIndex !== -1 ? parts[dirIndex + 1] : undefined;
const comment = parts.slice(dirIndex + 2).join(' ');
currentVB = {
name,
mode: dir,
comment,
signals: [],
};
virtualBuses.push(currentVB);
commentBuffer = [];
continue;
}
if (text.startsWith('@end')) {
currentVB = null;
commentBuffer = [];
continue;
}
commentBuffer.push(text);
continue;
}
if (child.type === 'signal_interface_declaration') {
const idNode = child.children.find(
(c) => c.type === 'identifier_list',
);
const modeNode = child.children.find((c) => c.type === 'mode');
const typeNode = child.children.find(
(c) => c.type === 'subtype_indication',
);
const defaultNode = child.children.find(
(c) => c.type === 'default_expression',
);
const name = this.extractText(
idNode?.namedChildCount === 1
? idNode.namedChild(0)
: idNode,
);
const mode = this.extractText(modeNode);
const type = this.extractText(typeNode);
const defaultValue = this.extractText(defaultNode);
const paramComment =
commentBuffer.length > 0
? commentBuffer.join('\n')
: getLeadingComment(Position.fromNode(child).startLine);
const param: IHDLParameter = {
name,
mode,
type,
defaultValue,
comment: paramComment,
};
if (currentVB) {
currentVB.signals.push(param);
commentBuffer = [];
} else {
ports.push(param);
}
}
}
const info: IHDLPortInfo = {
name: '',
portList: ports,
virtualBusList: virtualBuses,
position: Position.fromNode(node),
};
return [new HDLPort(info)];
};
}

View File

@@ -0,0 +1,86 @@
import { IHDLParameter } from 'elements/interfaces/Common';
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLProcedure } from '../elements/HDLProcedure';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLProcedureInfo } from '../elements/interfaces/IHDLProcedure';
/**
* ProcedureExtractor extracts procedure declarations from a VHDL AST.
*/
export class ProcedureExtractor implements INodeExtractor<HDLProcedure> {
/**
* @inheritdoc
*/
public readonly nodeType = 'procedure_body';
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLProcedure> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLProcedure[] => {
const result: HDLProcedure[] = [];
const nameNode = node.namedChildren.find(
(c) => c.type === 'identifier',
);
const parameterClause = node.namedChildren.find(
(c) => c.type === 'procedure_parameter_clause',
);
const parameters: string[] = [];
const parameterList: IHDLParameter[] = [];
if (parameterClause) {
for (const decl of parameterClause.namedChildren) {
const nameList = decl.namedChildren.find(
(c) => c.type === 'identifier_list',
);
const modeNode = decl.namedChildren.find(
(c) => c.type === 'mode',
);
const typeNode = decl.namedChildren.find(
(c) => c.type === 'subtype_indication',
);
const mode = modeNode?.text.trim() ?? '';
const type = typeNode?.text.trim() ?? '';
if (nameList) {
const names = nameList.text.split(',').map((n) => n.trim());
for (const name of names) {
parameterList.push({ name, mode, type });
parameters.push(`${name}: ${mode} ${type}`);
}
}
}
}
const name = nameNode?.text.trim() ?? 'unnamed';
const pos = Position.fromNode(node);
const comment = getLeadingComment(pos.startLine);
const info: IHDLProcedureInfo = {
name,
parameters: parameters.join('; '),
parameterList,
position: pos,
comment,
description: undefined,
};
result.push(new HDLProcedure(info));
return result;
};
}

View File

@@ -0,0 +1,63 @@
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLProcess } from '../elements/HDLProcess';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLProcessInfo } from '../elements/interfaces/IHDLProcess';
/**
* ProcessExtractor is a class that extracts process declarations from a VHDL AST.
*/
export class ProcessExtractor implements INodeExtractor<HDLProcess> {
/**
* @inheritdoc
*/
public get nodeType(): string {
return 'process_statement';
}
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLProcess> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLProcess[] => {
const result: HDLProcess[] = [];
const labelNode = node.namedChildren.find((c) => c.type === 'label');
const identifier = labelNode?.namedChildren.find(
(c) => c.type === 'identifier',
);
const sensitivityListNode = node.namedChildren.find(
(c) => c.type === 'sensitivity_list',
);
const name = identifier?.text.trim() ?? 'unnamed';
const pos = Position.fromNode(node);
const sensitivityList = sensitivityListNode
? sensitivityListNode.namedChildren.map((n) => n.text.trim())
: undefined;
const comment = getLeadingComment(pos.startLine);
const info: IHDLProcessInfo = {
name,
position: pos,
comment,
description: undefined,
sensitivityList,
processType: undefined,
};
result.push(new HDLProcess(info));
return result;
};
}

View File

@@ -1,52 +1,66 @@
import { HDLElementExtractor } from './HDLElementExtractor';
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLSignal } from '../elements/HDLSignal';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLSignalInfo } from '../elements/interfaces/IHDLSignal';
/**
* Extractor for VHDL signal declarations.
* SignalExtractor extracts signal declarations from a VHDL AST.
*/
export class SignalExtractor extends HDLElementExtractor<HDLSignal> {
export class SignalExtractor implements INodeExtractor<HDLSignal> {
/**
* Extract all signal declarations from the syntax tree.
* @returns An array of HDLSignal objects representing the extracted signals.
* @inheritdoc
*/
extract(): HDLSignal[] {
this.extractComments();
public get nodeType(): string {
return 'signal_declaration';
}
/**
* @inheritdoc
*/
public readonly excludedParents = [
'function_body',
'procedure_body',
'process_statement',
];
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLSignal> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLSignal[] => {
const result: HDLSignal[] = [];
const signalNodes = this.findNodesByType(
this.root,
'signal_declaration',
);
const nameNode = node.descendantsOfType('identifier_list')[0];
const typeNode = node.descendantsOfType('subtype_indication')[0];
const defaultNode = node.descendantsOfType('default_expression')[0];
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);
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 = getLeadingComment(pos.startLine);
for (const name of names) {
const comment = this.getLeadingComment(pos.startLine);
const info: IHDLSignalInfo = {
name,
type,
defaultValue,
comment,
description: undefined,
position: pos,
};
const info: IHDLSignalInfo = {
name,
type,
defaultValue,
comment,
description: undefined,
position: pos,
};
result.push(new HDLSignal(info));
}
result.push(new HDLSignal(info));
}
return result;
}
};
}

View File

@@ -0,0 +1,55 @@
import { IHDLTypeInfo } from 'elements/interfaces/IHDLTypeInfo';
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLType } from '../elements/HDLType';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
/**
* TypeExtractor extracts full type declarations from a VHDL AST.
*/
export class TypeExtractor implements INodeExtractor<HDLType> {
/**
* @inheritdoc
*/
public readonly nodeType = 'full_type_declaration';
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLType> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLType[] => {
const result: HDLType[] = [];
const nameNode = node.namedChildren.find(
(c) => c.type === 'identifier',
);
const typeExprNode = node.namedChildren.find(
(c) => c.type === 'constrained_array_definition',
);
const name = nameNode?.text.trim() ?? 'unnamed_type';
const typeExpression = typeExprNode?.text.trim() ?? 'unknown';
const pos = Position.fromNode(node);
const comment = getLeadingComment(pos.startLine);
const info: IHDLTypeInfo = {
name,
typeExpression,
comment,
description: undefined,
position: pos,
};
result.push(new HDLType(info));
return result;
};
}

View File

@@ -0,0 +1,66 @@
/* eslint-disable max-len */
/* eslint-disable no-console */
import Parser from 'web-tree-sitter';
import { Position } from '../elements/Common';
import { HDLVariable } from '../elements/HDLVariable';
import {
GetLeadingComment,
INodeExtractor,
NodeHandler,
} from './interfaces/IHDLElementExtractor';
import { IHDLVariableInfo } from '../elements/interfaces/IHDLVariable';
/**
* VariableExtractor extracts variable declarations from a VHDL AST.
*/
export class VariableExtractor implements INodeExtractor<HDLVariable> {
/**
* @inheritdoc
*/
public readonly nodeType = 'variable_declaration';
/**
* @inheritdoc
*/
public readonly excludedParents = [
'function_body',
'procedure_body',
'process_statement',
];
/**
* @inheritdoc
*/
public readonly nodeHandler: NodeHandler<HDLVariable> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
): HDLVariable[] => {
const result: HDLVariable[] = [];
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 = getLeadingComment(pos.startLine);
const info: IHDLVariableInfo = {
name,
type,
defaultValue,
comment,
description: undefined,
position: pos,
};
result.push(new HDLVariable(info));
}
return result;
};
}

View File

@@ -0,0 +1,73 @@
import { IHDLElement, IHDLElementInfo } from 'elements/interfaces/IHDLElement';
import Parser from 'web-tree-sitter';
import { ICommentOptions } from './ICommentOptions';
/**
* 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.
*/
export type GetLeadingComment = (line: number) => string | undefined;
/**
* NodeHandler is a function type that takes a Parser.SyntaxNode and a GetLeadingComment function,
* and returns an array of elements of type T.
* @param node The syntax node to process.
* @param getLeadingComment A function to retrieve leading comments for the node.
* @returns An array of elements of type T.
*/
export type NodeHandler<T extends IHDLElement<IHDLElementInfo>> = (
node: Parser.SyntaxNode,
getLeadingComment: GetLeadingComment,
) => T[];
/**
* INodeExtractor is an interface that defines a method to extract elements from a syntax tree.
* It provides a node type and a handler function to process each node.
*/
export interface INodeExtractor<T extends IHDLElement<IHDLElementInfo>> {
/**
* The type of node to extract from the syntax tree.
*/
readonly nodeType: string;
/**
* Optional list of parent node types under which this element should be ignored.
*/
readonly excludedParents?: string[];
/**
* The handler function to process each node of the specified type.
* It takes a node and a function to get leading comments.
* @param node The syntax node to process.
* @param getLeadingComment A function to retrieve leading comments for the node.
* @returns An array of elements of type T.
*/
readonly nodeHandler: NodeHandler<T>;
}
/**
* IHDLElementExtractor_ is an interface that defines a constructor
* for extracting elements from a syntax tree.
* It takes a root node, optional comment options, a node type, and a handler function.
*/
export interface IHDLElementExtractor_<
_T extends IHDLElement<IHDLElementInfo>,
> {
new (
root: Parser.SyntaxNode,
commentOptions?: ICommentOptions,
nodeType?: string,
handler?: NodeHandler<_T>,
): IHDLElementExtractor<_T>;
}
export interface IHDLElementExtractor<T extends IHDLElement<IHDLElementInfo>> {
/**
* Extract all elements of the specified type from the syntax tree.
* Uses the provided handler function to process each node.
* @returns An array of extracted elements.
*/
extract(): T[];
}

View File

@@ -1,10 +1,52 @@
/* eslint-disable no-console */
import { HDLGeneric } from 'elements/HDLGeneric';
import { HDLPort } from 'elements/HDLPort';
import { HDLType } from 'elements/HDLType';
import { AssignmentExtractor } from 'extractors/AssignmentExtractor';
import { FunctionExtractor } from 'extractors/FunctionExtractor';
import { GenericExtractor } from 'extractors/GenericExtractor';
import { HDLElementExtractor } from 'extractors/HDLElementExtractor';
import { InstantiationExtractor } from 'extractors/InstantiationExtractor';
import { PortExtractor } from 'extractors/PortExtractor';
import { ProcedureExtractor } from 'extractors/ProcedureExtractor';
import { TypeExtractor } from 'extractors/TypeExtractor';
import { VariableExtractor } from 'extractors/VariableExtractor';
import { writeFileSync } from 'fs';
import { join } from 'path';
import Parser from 'web-tree-sitter';
import { HDLSignal } from './elements/HDLSignal';
import { ConstantExtractor } from './extractors/ConstantExtractor';
import { ProcessExtractor } from './extractors/ProcessExtractor';
import { SignalExtractor } from './extractors/SignalExtractor';
import { VHDLParser } from './parser/VHDLParser';
/**
* Recursively converts a Tree-sitter SyntaxNode into a plain JSON object.
* @param node The Tree-sitter SyntaxNode.
* @returns A plain JSON object representing the node.
*/
function syntaxNodeToJson(node: Parser.SyntaxNode): unknown {
return {
type: node.type,
text: node.text,
startPosition: node.startPosition,
endPosition: node.endPosition,
children: node.namedChildren.map(syntaxNodeToJson),
};
}
/**
* Dumps the full AST of a Tree-sitter parsed file to a JSON file.
* @param rootNode The root node of the syntax tree.
* @param outputPath The path to the output JSON file.
*/
export function dumpAST(rootNode: Parser.SyntaxNode, outputPath: string): void {
const ast = syntaxNodeToJson(rootNode);
writeFileSync(outputPath, JSON.stringify(ast, null, 2));
console.log(`🧠 AST written to ${outputPath}`);
}
/**
* Main function to extract signals from a VHDL file and write them to a JSON file.
*/
@@ -15,16 +57,37 @@ async function main(): Promise<void> {
const filePath = join(__dirname, '../test/AsyncFIFO.vhd');
const tree = await parser.parseFile(filePath);
const extractor = new SignalExtractor(tree.rootNode, {
dumpAST(tree.rootNode, join(__dirname, '../test/ast.json'));
const commentOptions = {
markerPrefix: '@',
stripPrefix: true,
});
const signals: HDLSignal[] = extractor.extract();
};
const output = join(__dirname, '../test/signals.json');
const processExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new ProcessExtractor(),
);
const processes = processExtractor.extract();
writeFileSync(
output,
join(__dirname, '../test/processes.json'),
JSON.stringify(processes, null, 2),
);
console.log('✅ Processes written to test/processes.json');
const extractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new SignalExtractor(),
);
const signals: HDLSignal[] = extractor.extract();
writeFileSync(
join(__dirname, '../test/signals.json'),
JSON.stringify(
signals.map((s) => s.info),
null,
@@ -32,7 +95,150 @@ async function main(): Promise<void> {
),
);
console.log('✅ Signals written to:', output);
console.log('✅ Signals written to test/signals.json');
const constantExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new ConstantExtractor(),
);
const constants = constantExtractor.extract();
writeFileSync(
join(__dirname, '../test/constants.json'),
JSON.stringify(constants, null, 2),
);
console.log('✅ Constants written to test/constants.json');
const instantiationExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new InstantiationExtractor(),
);
const instantiations = instantiationExtractor.extract();
writeFileSync(
join(__dirname, '../test/instantiations.json'),
JSON.stringify(instantiations, null, 2),
);
console.log('✅ Instantiations written to test/instantiations.json');
const functionExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new FunctionExtractor(),
);
const functions = functionExtractor.extract();
writeFileSync(
join(__dirname, '../test/functions.json'),
JSON.stringify(
functions.map((f) => f.info),
null,
2,
),
);
console.log('✅ Functions written to test/functions.json');
const assignmentExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new AssignmentExtractor(),
);
const assignments = assignmentExtractor.extract();
writeFileSync(
join(__dirname, '../test/assignments.json'),
JSON.stringify(assignments, null, 2),
);
console.log('✅ Assignments written to test/assignments.json');
const variableExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new VariableExtractor(),
);
const variables = variableExtractor.extract();
writeFileSync(
join(__dirname, '../test/variables.json'),
JSON.stringify(variables, null, 2),
);
console.log('✅ Variables written to test/variables.json');
const procedureExtractor = new HDLElementExtractor(
tree.rootNode,
commentOptions,
new ProcedureExtractor(),
);
const procedures = procedureExtractor.extract();
writeFileSync(
join(__dirname, '../test/procedures.json'),
JSON.stringify(procedures, null, 2),
);
console.log('✅ Procedures written to test/procedures.json');
const typeExtractor = new HDLElementExtractor<HDLType>(
tree.rootNode,
commentOptions,
new TypeExtractor(),
);
const types = typeExtractor.extract();
writeFileSync(
join(__dirname, '../test/types.json'),
JSON.stringify(
types.map((t) => t.info),
null,
2,
),
);
console.log(`✅ Types written to test/types.json`);
const genericExtractor = new HDLElementExtractor<HDLGeneric>(
tree.rootNode,
commentOptions,
new GenericExtractor(),
);
const generics = genericExtractor.extract();
writeFileSync(
join(__dirname, '../test/generics.json'),
JSON.stringify(
generics.map((g) => g.info),
null,
2,
),
);
console.log(`✅ Generics written to test/generics.json`);
const portExtractor = new HDLElementExtractor<HDLPort>(
tree.rootNode,
commentOptions,
new PortExtractor(),
);
const port = portExtractor.extract();
writeFileSync(
join(__dirname, '../test/ports.json'),
JSON.stringify(
port.map((p) => p.info),
null,
2,
),
);
console.log(`✅ Ports written to test/ports.json`);
}
main().catch((e) => console.error('❌ Signal extraction failed:', e));