Add HDL elements and interfaces for VHDL types, generics, functions, procedures, constants, variables, assignments, and ports
This commit is contained in:
27
src/elements/HDLAssignment.ts
Normal file
27
src/elements/HDLAssignment.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
24
src/elements/HDLConstant.ts
Normal file
24
src/elements/HDLConstant.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
18
src/elements/HDLGeneric.ts
Normal file
18
src/elements/HDLGeneric.ts
Normal 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
22
src/elements/HDLPort.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
14
src/elements/HDLType.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
24
src/elements/HDLVariable.ts
Normal file
24
src/elements/HDLVariable.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
20
src/elements/interfaces/IHDLAssignment.ts
Normal file
20
src/elements/interfaces/IHDLAssignment.ts
Normal 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;
|
||||
}
|
||||
32
src/elements/interfaces/IHDLConstant.ts
Normal file
32
src/elements/interfaces/IHDLConstant.ts
Normal 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;
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
22
src/elements/interfaces/IHDLGeneric.ts
Normal file
22
src/elements/interfaces/IHDLGeneric.ts
Normal 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;
|
||||
}
|
||||
32
src/elements/interfaces/IHDLPort.ts
Normal file
32
src/elements/interfaces/IHDLPort.ts
Normal 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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
21
src/elements/interfaces/IHDLTypeInfo.ts
Normal file
21
src/elements/interfaces/IHDLTypeInfo.ts
Normal 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;
|
||||
}
|
||||
32
src/elements/interfaces/IHDLVariable.ts
Normal file
32
src/elements/interfaces/IHDLVariable.ts
Normal 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;
|
||||
}
|
||||
56
src/extractors/AssignmentExtractor.ts
Normal file
56
src/extractors/AssignmentExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
66
src/extractors/ConstantExtractor.ts
Normal file
66
src/extractors/ConstantExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
103
src/extractors/FunctionExtractor.ts
Normal file
103
src/extractors/FunctionExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
90
src/extractors/GenericExtractor.ts
Normal file
90
src/extractors/GenericExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
65
src/extractors/InstantiationExtractor.ts
Normal file
65
src/extractors/InstantiationExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
122
src/extractors/PortExtractor.ts
Normal file
122
src/extractors/PortExtractor.ts
Normal 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)];
|
||||
};
|
||||
}
|
||||
86
src/extractors/ProcedureExtractor.ts
Normal file
86
src/extractors/ProcedureExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
63
src/extractors/ProcessExtractor.ts
Normal file
63
src/extractors/ProcessExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
55
src/extractors/TypeExtractor.ts
Normal file
55
src/extractors/TypeExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
66
src/extractors/VariableExtractor.ts
Normal file
66
src/extractors/VariableExtractor.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
73
src/extractors/interfaces/IHDLElementExtractor.ts
Normal file
73
src/extractors/interfaces/IHDLElementExtractor.ts
Normal 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[];
|
||||
}
|
||||
218
src/index.ts
218
src/index.ts
@@ -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));
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strict": true,
|
||||
"strictFunctionTypes": false,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
Reference in New Issue
Block a user