UNPKG

pss-langserver

Version:

A Language server for the Portable Stimulus Standard

484 lines (439 loc) 18.9 kB
/* * Copyright (C) 2025 Darshan(@thisisthedarshan) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { CharStream, CommonTokenStream, ParserRuleContext, Token } from "antlr4"; import pssVisitor from "../grammar/pssVisitor"; import { Action_declarationContext, Component_declarationContext, Data_declarationContext, Data_instantiationContext, Enum_declarationContext, Enum_itemContext, Function_declContext, IdentifierContext, Procedural_functionContext, Pss_entryContext } from "../grammar/pss"; import pss_lexer from "../grammar/pssLex"; import { getObjType } from "./helpers"; import { commentDocs, metaData, objType, params } from "../definitions/dataTypes"; import doxygenParserVisitor from "../grammar/doxygenParserVisitor"; import doxygenParser, { Doc_contentContext, Documentation_commentContext } from "../grammar/doxygenParser"; import doxygenLexer from "../grammar/doxygenLexer"; export class visitor extends pssVisitor<void> { /* Data types */ private identifiers: string[] = []; private astMeta: metaData[] = []; private tokenStream: CommonTokenStream; /* Getters */ getIdentifiers(): string[] { return this.identifiers; } getMeta(): metaData[] { return this.astMeta; } constructor(tokenStream: CommonTokenStream, fileURI: string) { super(); this.tokenStream = tokenStream; /* Visit different identifiers */ this.visitIdentifier = (ctx: IdentifierContext): void => { this.identifiers.push(ctx.getText()); }; /* Visit all declarations and generate data */ this.visitAction_declaration = (ctx: Action_declarationContext): void => { const comments = this.captureComments(ctx); this.astMeta.push({ [ctx.action_identifier().identifier()?.getText()]: { objectType: objType.ACTION, parent: undefined, onLine: { file: fileURI, lineNumber: ctx.action_identifier().identifier().start.line, columnNumber: ctx.action_identifier().identifier().start.column }, used: [], documentation: "", params: ctx.template_param_decl_list()?.getText(), type: ctx.action_super_spec()?.getText(), subComponents: undefined } }); super.visitChildren(ctx) } this.visitEnum_declaration = (ctx: Enum_declarationContext): void => { this.astMeta.push({ [ctx.enum_identifier().identifier()?.getText()]: { objectType: objType.ENUM, parent: undefined, onLine: { file: fileURI, lineNumber: ctx.enum_identifier().identifier().start.line, columnNumber: ctx.enum_identifier().identifier().start.column }, used: [], documentation: "", params: undefined, type: ctx.data_type()?.getText() || undefined, subComponents: ctx.enum_item_list().map(item => item.identifier()?.getText()) } }); super.visitChildren(ctx) } this.visitComponent_declaration = (ctx: Component_declarationContext): void => { let compItems: string[] = []; let compDataItems: string[] = []; let templateParameters: params[] = []; if (ctx.component_body_item_list()) { compItems = ctx.component_body_item_list().map(compBodyItems => { if (compBodyItems.abstract_action_declaration()) { return compBodyItems.abstract_action_declaration().action_declaration().action_identifier().identifier()?.getText() } else if (compBodyItems.action_declaration()) { return compBodyItems.action_declaration().action_identifier().identifier()?.getText() } else if (compBodyItems.component_pool_declaration()) { return compBodyItems.component_pool_declaration().identifier()?.getText() } else if (compBodyItems.enum_declaration()) { return compBodyItems.enum_declaration().enum_identifier().identifier()?.getText() } else if (compBodyItems.export_action()) { return compBodyItems.export_action().action_type_identifier().type_identifier().type_identifier_elem(0)?.identifier()?.getText() } else if (compBodyItems.function_decl()) { return compBodyItems.function_decl().function_prototype().function_identifier().identifier()?.getText() } else if (compBodyItems.procedural_function()) { return compBodyItems.procedural_function().function_prototype().function_identifier().identifier()?.getText() } else if (compBodyItems.struct_declaration()) { return compBodyItems.struct_declaration().struct_identifier().identifier()?.getText() } else if (compBodyItems.target_template_function()) { return compBodyItems.target_template_function().function_prototype().function_identifier().identifier()?.getText() } else { return ""; } }); compDataItems = ctx.component_body_item_list().flatMap(compBodyItem => { if (compBodyItem.component_data_declaration()) { return compBodyItem.component_data_declaration().data_declaration()?.data_instantiation_list()?.map(dataInst => { return dataInst.identifier()?.getText(); }); } else { return []; } }); } if (ctx.template_param_decl_list()) { if (ctx.template_param_decl_list().template_param_decl_list()) { templateParameters = ctx.template_param_decl_list().template_param_decl_list().map(paramDecl => { if (paramDecl.type_param_decl()) { if (paramDecl.type_param_decl().generic_type_param_decl()) { return { paramType: getObjType("type"), paramName: paramDecl.type_param_decl().generic_type_param_decl().identifier()?.getText(), paramDefault: paramDecl.type_param_decl().generic_type_param_decl().type_identifier()?.type_identifier_elem(0)?.identifier()?.getText() }; } else { const typeParamDecl = paramDecl.type_param_decl().category_type_param_decl(); const typeCategory = typeParamDecl.type_category(); let objStr = typeCategory.struct_kind() ? (typeCategory.struct_kind().object_kind() ? typeCategory.struct_kind().object_kind()?.getText() : typeCategory.struct_kind()?.getText()) : typeCategory.getText(); return { paramType: getObjType(objStr), paramName: typeParamDecl.identifier()?.getText(), paramDefault: typeParamDecl.type_identifier()?.type_identifier_elem(0)?.identifier()?.getText() }; } } else { return { paramType: getObjType(paramDecl.value_param_decl()?.data_type()?.type_identifier()?.type_identifier_elem(0)?.identifier()?.getText()), paramName: paramDecl.value_param_decl()?.identifier()?.getText(), paramDefault: paramDecl.value_param_decl()?.constant_expression().expression()?.getText() }; } }); } } const compItemsFiltered = compItems;//.filter(item => item !== "" && !(Array.isArray(item) && item.length === 0)); const compDataItemsFiltered = compDataItems;//.filter(item => item !== "" && !(Array.isArray(item) && item.length === 0)); this.astMeta.push({ [ctx.component_identifier().identifier().getText()]: { objectType: objType.COMPONENT, parent: undefined, onLine: { file: fileURI, lineNumber: ctx.component_identifier().identifier().start.line, columnNumber: ctx.component_identifier().identifier().start.column }, used: [], documentation: "", params: templateParameters, type: ctx.component_super_spec()?.type_identifier()?.type_identifier_elem(0)?.identifier()?.getText() || undefined, subComponents: [...compItemsFiltered, ...compDataItemsFiltered] } }); super.visitChildren(ctx) } this.visitData_declaration = (ctx: Data_declarationContext): void => { if (ctx.data_instantiation_list()) { ctx.data_instantiation_list().map(dataInstance => { this.astMeta.push({ [dataInstance.identifier()?.getText()]: { objectType: objType.DATA, parent: undefined, onLine: { file: fileURI, lineNumber: dataInstance.identifier().start.line, columnNumber: dataInstance.identifier().start.column }, used: [], documentation: "", params: dataInstance.array_dim()?.constant_expression()?.getText() || undefined, type: dataInstance.constant_expression()?.getText() || undefined, subComponents: undefined } }); }); } else { this.astMeta.push({ [ctx.data_instantiation(0)?.identifier()?.getText()]: { objectType: objType.DATA, parent: undefined, onLine: { file: fileURI, lineNumber: ctx.data_instantiation(0)?.identifier().start.line, columnNumber: ctx.data_instantiation(0)?.identifier().start.column }, used: [], documentation: "", params: ctx.data_instantiation(0)?.array_dim()?.constant_expression()?.getText() || undefined, type: ctx.data_instantiation(0)?.constant_expression()?.getText() || undefined, subComponents: undefined } }); } super.visitChildren(ctx) } this.visitProcedural_function = (ctx: Procedural_functionContext): void => { this.astMeta.push({ [ctx.function_prototype().function_identifier().identifier()?.getText()]: { objectType: objType.PROCEDURAL_FUNCTION, parent: undefined, onLine: { file: fileURI, lineNumber: ctx.function_prototype().function_identifier().identifier().start.line, columnNumber: ctx.function_prototype().function_identifier().identifier().start.column }, used: [], documentation: "", params: ctx.function_prototype().function_parameter_list_prototype().function_parameter_list().map(paramList => { let dataType: string = paramList.data_type() ? paramList.data_type()?.getText() : paramList.type_category() ? ( paramList.type_category().struct_kind()?.object_kind()?.getText() || paramList.type_category().struct_kind()?.getText() || paramList.type_category()?.getText() ) : paramList.TOKEN_STRUCT() ? paramList.TOKEN_STRUCT()?.getText() : paramList.TOKEN_TYPE()?.getText(); return { paramType: getObjType(dataType), paramName: paramList.identifier()?.getText(), paramDefault: paramList.constant_expression()?.expression()?.getText() ?? "" } }), type: ctx.function_prototype().function_return_type()?.getText(), subComponents: undefined } }); super.visitChildren(ctx) } this.visitEnum_item = (ctx: Enum_itemContext): void => { this.identifiers.push(ctx.getText()); } } private captureComments(ctx: ParserRuleContext, name: string = ''): string | commentDocs { /* Get start token index */ const tokenIndex = ctx.start.tokenIndex; /* Get comments before this rule */ const comments = this.getCommentsBeforeToken(tokenIndex); if (this.tokenStream.get(ctx.start.tokenIndex - 1).channel === pss_lexer.TOKEN_DOC_COMMENT) { let inputStream = new CharStream(comments.join('\n')); let lexer = new doxygenLexer(inputStream); let tokenStream = new CommonTokenStream(lexer); let parser = new doxygenParser(tokenStream); parser.removeErrorListeners(); let tree = parser.documentation_comment(); let visitor = new doxygen_visitor(name, false); tree.accept(visitor); return visitor.getComment(); } return comments?.join('\n'); } private getInlineComments(ctx: ParserRuleContext): string[] { const comments: string[] = []; /* Check if stop exists before using it */ if (!ctx.stop) return comments; const tokenIndex = ctx.stop.tokenIndex; /* Look at the next token */ const nextToken = this.tokenStream.get(tokenIndex + 1); /* Check if it's on the hidden channel and is a comment */ if (nextToken && nextToken.channel === Token.HIDDEN_CHANNEL) { const tokenType = nextToken.type; if ( tokenType === pss_lexer.TOKEN_SL_COMMENT || tokenType === pss_lexer.TOKEN_ML_COMMENT) { comments.push(nextToken.text); } } return comments; } private getCommentsBeforeToken(tokenIndex: number): string[] { const comments: string[] = []; /* Search for hidden channel tokens before the current token */ let i = tokenIndex - 1; while (i >= 0) { const token = this.tokenStream.get(i); /* Check if token is on the hidden channel and is a comment */ if (token.channel === Token.HIDDEN_CHANNEL) { const tokenType = token.type; /* Check if it's one of your comment types */ if (tokenType === pss_lexer.TOKEN_DOC_COMMENT || tokenType === pss_lexer.TOKEN_SL_COMMENT || tokenType === pss_lexer.TOKEN_ML_COMMENT) { comments.unshift(token.text); } else { /* Stop if we hit a non-comment hidden token (usually whitespace) */ break; } } else { /* Stop if we hit a non-hidden token */ break; } i--; } return comments; } } export class doxygen_visitor extends doxygenParserVisitor<void> { /* File Name */ private name: string = ''; /* Common and var specific comments */ private paramsNames: string[] = []; private paramDescriptions: string[] = []; private sees: string[] = []; private brief: string = ''; private details: string = ''; private returns: string = ''; private deprecates: string = ''; private attention: string = ''; private todo: string = ''; private examples: string = ''; /* File specific comments */ private file: string = ''; private author: string = ''; private date: string = ''; private version: string = ''; private isFileInfo: boolean = false; getComment(): string | commentDocs { if (this.isFileInfo) { return `# ${this.file} --- **Author: ${this.author}** Date: ${this.date} Version ${this.version} --- ### About: ${this.brief} ${this.details.length > 0 ? '---\n' + this.details : ''} ${this.deprecates.length > 0 ? '> [!CAUTION] \n> ' + this.deprecates : ''} `.trimStart() } else { return { brief: this.brief, details: this.details, name: this.name, paramDescriptions: this.paramDescriptions, paramNames: this.paramsNames, paramTypes: undefined, sees: this.sees, returns: this.returns } } } constructor(name: string, isFileInfo: boolean = false) { super(); this.isFileInfo = isFileInfo; this.name = name; /* This is the entry point for doxygen comment */ this.visitDocumentation_comment = (ctx: Documentation_commentContext) => { if (ctx.doc_content_list()) { /* Parse the document blocks one by one */ ctx.doc_content_list().map(doc_content => { if (doc_content.brief_command()) { /* Parse brief content */ this.brief = doc_content.brief_command().brief_text().getText(); } if (doc_content.param_command()) { /* Parse param content */ this.paramsNames.push(doc_content.param_command().param_identifier().getText()); this.paramDescriptions.push(doc_content.param_command().param_description().getText()); } if (doc_content.return_command()) { /* Parse return content */ this.returns = doc_content.return_command().return_description().getText(); } if (doc_content.deprecated_command()) { /* Parse deprecated content */ this.deprecates = doc_content.deprecated_command().deprecated_description().getText(); } if (doc_content.author_command()) { /* Parse author content */ this.author = doc_content.author_command().author_name().getText(); } if (doc_content.date_command()) { /* Parse date content */ this.date = doc_content.date_command().date_value().getText(); } if (doc_content.version_command()) { /* Parse version content */ this.version = doc_content.version_command().version_value().getText(); } if (doc_content.file_command()) { /* Parse file content */ this.file = doc_content.file_command().file_path().getText(); } if (doc_content.see_command()) { /* Parse see content */ this.sees.push(doc_content.see_command().see_link().getText()) } if (doc_content.attention_command()) { /* Parse attention content */ this.attention = doc_content.attention_command().attention_description().getText(); } if (doc_content.todo_command()) { /* Parse todo content */ this.todo = doc_content.todo_command().todo_description().getText(); } if (doc_content.example_command()) { /* Parse example content */ this.examples = doc_content.example_command().example_code().getText(); } }); } } } }