UNPKG

pss-langserver

Version:

A Language server for the Portable Stimulus Standard

503 lines (502 loc) 18.9 kB
"use strict"; /* * 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/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.findByteSize = exports.mapTokenModifiers = exports.mapTokenTypes = void 0; exports.generateUniqueId = generateUniqueId; exports.isWithinCommentBlock = isWithinCommentBlock; exports.getObjType = getObjType; exports.getFlowObjType = getFlowObjType; exports.getExecType = getExecType; exports.getStructKind = getStructKind; exports.buildMarkdownComment = buildMarkdownComment; exports.fullRange = fullRange; exports.updateAST = updateAST; exports.updateASTNew = updateASTNew; exports.scanDirectory = scanDirectory; exports.updateASTMeta = updateASTMeta; exports.updateASTNewMeta = updateASTNewMeta; exports.getPackedStructSize = getPackedStructSize; exports.getNodeFromName = getNodeFromName; exports.getNodeFromNameArray = getNodeFromNameArray; exports.collectAllPSSNodes = collectAllPSSNodes; exports.collectAllInstanceNodes = collectAllInstanceNodes; const node_1 = require("vscode-languageserver/node"); const dataTypes_1 = require("../definitions/dataTypes"); const ast_1 = require("./ast"); const fs = require('fs-extra'); const path = require('path'); function generateUniqueId() { return `d-${Date.now()}-${Math.floor(Math.random() * 1000)}`; } function isWithinCommentBlock(document, lineNumber) { for (let i = lineNumber; i >= 0; i--) { const lineText = document.lineAt(i).text.trim(); if ((lineText.startsWith('/*') || lineText.startsWith("/**")) && !lineText.endsWith('*/')) { return true; /* Found the start of an unclosed block comment */ } if (lineText.endsWith('*/')) { return false; /* Found the end of the block comment */ } } return false; } function getObjType(str) { switch (str.toUpperCase()) { case "COMPONENT": return dataTypes_1.objType.COMPONENT; case "ACTION": return dataTypes_1.objType.ACTION; case "ACTION": return dataTypes_1.objType.ACTIVITY; case "FUNCTION": return dataTypes_1.objType.FUNCTION; case "PROCEDURAL_FUNCTION": return dataTypes_1.objType.PROCEDURAL_FUNCTION; case "ENUM": return dataTypes_1.objType.ENUM; case "REGISTER_BODY_ITEM": return dataTypes_1.objType.REGISTER_BODY_ITEM; case "REGISTER_GROUP": return dataTypes_1.objType.REGISTER_GROUP; case "REGISTER_COMP": return dataTypes_1.objType.REGISTER_COMP; case "REGISTER_DEF": return dataTypes_1.objType.REGISTER_DEF; case "REGISTER": return dataTypes_1.objType.REGISTER; case "BUFFER": return dataTypes_1.objType.BUFFER; case "STRUCT": return dataTypes_1.objType.STRUCT; case "STATE": return dataTypes_1.objType.STATE; case "STREAM": return dataTypes_1.objType.STREAM; case "RESOURCE_OBJECT": return dataTypes_1.objType.RESOURCE_OBJECT; case "POOLS": return dataTypes_1.objType.POOLS; case "PACKAGE": return dataTypes_1.objType.PACKAGE; case "MONITOR": return dataTypes_1.objType.MONITOR; case "CONSTRAINT": return dataTypes_1.objType.CONSTRAINT; case "DATA": return dataTypes_1.objType.DATA; case "TYPEDEF": return dataTypes_1.objType.TYPEDEF; case "CHANDLE": return dataTypes_1.objType.CHANDLE; case "INTEGER": return dataTypes_1.objType.INTEGER; case "BIT": return dataTypes_1.objType.BIT; case "STRING": return dataTypes_1.objType.STRING; case "BOOL": return dataTypes_1.objType.BOOL; case "FLOAT32": return dataTypes_1.objType.FLOAT32; case "FLOAT64": return dataTypes_1.objType.FLOAT64; case "REF": return dataTypes_1.objType.REF; case "ARRAY": return dataTypes_1.objType.ARRAY; case "LIST": return dataTypes_1.objType.LIST; case "MAP": return dataTypes_1.objType.MAP; case "SET": return dataTypes_1.objType.SET; case "INSTANCE": return dataTypes_1.objType.INSTANCE; case "MEMORY_SPACE": return dataTypes_1.objType.MEMORY_SPACE; case "MEMORY_REGION": return dataTypes_1.objType.MEMORY_REGION; case "MEMORY_CLAIM": return dataTypes_1.objType.MEMORY_CLAIM; case "VARARGS": return dataTypes_1.objType.VARARGS; case "TEMPLATE_ITEM": return dataTypes_1.objType.TEMPLATE_ITEM; case "ACTION_HANDLE": return dataTypes_1.objType.ACTION_HANDLE; case "ADDRESS_SPACE": return dataTypes_1.objType.ADDRESS_SPACE; case "ADDRESS_REGION": return dataTypes_1.objType.ADDRESS_REGION; case "ADDRESS_CLAIM": return dataTypes_1.objType.ADDRESS_CLAIM; case "STRUCT_ITEM": return dataTypes_1.objType.STRUCT_ITEM; case "EXEC_PRESOLVE": return dataTypes_1.objType.EXEC_PRESOLVE; case "EXEC_POSTSOLVE": return dataTypes_1.objType.EXEC_POSTSOLVE; case "EXEC_PREBODY": return dataTypes_1.objType.EXEC_PREBODY; case "EXEC_BODY": return dataTypes_1.objType.EXEC_BODY; case "EXEC_HEADER": return dataTypes_1.objType.EXEC_HEADER; case "EXEC_DECLARATION": return dataTypes_1.objType.EXEC_DECLARATION; case "EXEC_RUNSTART": return dataTypes_1.objType.EXEC_RUNSTART; case "EXEC_RUNEND": return dataTypes_1.objType.EXEC_RUNEND; case "EXEC_INITDOWN": return dataTypes_1.objType.EXEC_INITDOWN; case "EXEC_INITUP": return dataTypes_1.objType.EXEC_INITUP; case "EXEC_INIT": return dataTypes_1.objType.EXEC_INIT; case "EXEC_TARGET": return dataTypes_1.objType.EXEC_TARGET; case "EXEC_FILE": return dataTypes_1.objType.EXEC_FILE; case "CALL_SUPER": return dataTypes_1.objType.CALL_SUPER; default: return dataTypes_1.objType.UNKNOWN; } } function getFlowObjType(str) { switch (str.toUpperCase()) { case "STREAM": return dataTypes_1.objType.STREAM; case "STATE": return dataTypes_1.objType.STATE; case "POOLS": return dataTypes_1.objType.POOLS; default: return dataTypes_1.objType.BUFFER; } } function getExecType(str) { switch (str.toUpperCase()) { case "EXEC_PRESOLVE": return dataTypes_1.objType.EXEC_PRESOLVE; case "EXEC_POSTSOLVE": return dataTypes_1.objType.EXEC_POSTSOLVE; case "EXEC_PREBODY": return dataTypes_1.objType.EXEC_PREBODY; case "EXEC_BODY": return dataTypes_1.objType.EXEC_BODY; case "EXEC_HEADER": return dataTypes_1.objType.EXEC_HEADER; case "EXEC_DECLARATION": return dataTypes_1.objType.EXEC_DECLARATION; case "EXEC_RUNSTART": return dataTypes_1.objType.EXEC_RUNSTART; case "EXEC_RUNEND": return dataTypes_1.objType.EXEC_RUNEND; case "EXEC_INITDOWN": return dataTypes_1.objType.EXEC_INITDOWN; case "EXEC_INITUP": return dataTypes_1.objType.EXEC_INITUP; default: return dataTypes_1.objType.EXEC_INIT; } } function getStructKind(st) { switch (st.toUpperCase()) { case "BUFFER": return dataTypes_1.objType.BUFFER; case "STATE": return dataTypes_1.objType.STATE; case "STREAM": return dataTypes_1.objType.STREAM; case "RESOURCE_OBJECT": return dataTypes_1.objType.RESOURCE_OBJECT; default: return dataTypes_1.objType.STRUCT; } } exports.mapTokenTypes = ((arr) => (str) => arr.indexOf(str))(dataTypes_1.semanticTokensLegend.tokenTypes); exports.mapTokenModifiers = ((arr) => (str) => str in arr ? 1 << arr.indexOf(str) : 0)(dataTypes_1.semanticTokensLegend.tokenModifiers); function buildMarkdownComment(docs) { let markdown = `## ${docs.name}\n\n`; // Brief markdown += `${docs.brief}\n\n`; // Details if (docs.details) markdown += `${docs.details}\n\n`; // Parameters if (docs.paramNames.length > 0) { markdown += "### Parameters\n"; docs.paramNames.forEach((name, i) => { const desc = docs.paramDescriptions[i] || "No description"; markdown += `- **${name}** : ${desc}\n`; }); markdown += "\n"; } // See Also if (docs.sees.length > 0) { markdown += "### See Also\n"; markdown += docs.sees.map(see => `- ${see}`).join("\n") + "\n\n"; } // Returns if (docs.returns) { markdown += "### Returns\n"; markdown += `${docs.returns}\n\n`; } return markdown; } function fullRange(document) { const lastLine = document.lineCount - 1; const lastChar = document.getText().split("\n")[lastLine]?.length || 0; return node_1.Range.create(node_1.Position.create(0, 0), /* Start position at first character */ node_1.Position.create(lastLine, lastChar) /* End position at last character */); } async function updateAST(fileURI, documentText) { var items = (0, ast_1.buildAST)(fileURI, documentText); return items; } async function updateASTNew(fileURI, documentText) { var items = (0, ast_1.buildASTNew)(fileURI, documentText); return items; } function scanDirectory(dirPath, files, ignore = []) { try { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory() && !ignore.some(folderName => folderName === entry.name)) { scanDirectory(fullPath, files); } else if (entry.isFile() && entry.name.endsWith('.pss')) { files.push(fullPath); } } } catch (error) { console.error(`Error scanning directory: ${dirPath}`, error); throw error; } } function updateASTMeta(old, newData) { const seenKeys = new Set(newData.flatMap(Object.keys)); const uniqueArray2 = old.filter(item => Object.keys(item).every(key => !seenKeys.has(key))); return [...newData, ...uniqueArray2]; } function mergePSSNodes(oldNode, newNode) { const merged = { ...oldNode, ...newNode }; if (newNode.children && oldNode.children) { const childMap = new Map(); oldNode.children.forEach(child => childMap.set(child.name, child)); newNode.children.forEach(child => { const oldChild = childMap.get(child.name); childMap.set(child.name, oldChild ? mergePSSNodes(oldChild, child) : { ...child }); }); merged.children = Array.from(childMap.values()); } return merged; } function updateASTNewMeta(oldArray, newArray) { // if (newArray.length === 0) { return oldArray; } // const mergedMap = new Map<string, PSSLangObjects>(); // oldArray.forEach(item => mergedMap.set(item.name, { ...item })); // newArray.forEach(item => { // const oldItem = mergedMap.get(item.name); // mergedMap.set(item.name, oldItem ? mergePSSNodes(oldItem, item) : { ...item }); // }); // return Array.from(mergedMap.values()); return [...oldArray, ...newArray]; } /** This function returns sizes for known object */ function getObjectSize(obj) { /* Base type sizes */ const baseTypes = { 'int': 32, 'bit': 1 }; /* Helper function to recursively calculate type size */ function parseTypeSize(type) { /* Check for array type */ if (type.startsWith('array<')) { const match = type.match(/array<([^,]+),(\d+)>/); if (match) { const innerType = match[1]; const arraySize = parseInt(match[2]); return getObjectSize(innerType) * arraySize; } return 0; } /* Check for range notation */ const rangeMatch = type.match(/^(int|bit)\[(\d+):(\d+)\]$/); if (rangeMatch) { const baseType = rangeMatch[1]; const start = parseInt(rangeMatch[2]); const end = parseInt(rangeMatch[3]); return Math.abs(end - start) + 1; } /* Check for single index notation */ const indexMatch = type.match(/^(int|bit)\[(\d+)\]$/); if (indexMatch) { return parseInt(indexMatch[2]); } /* Base type lookup */ return baseTypes[type] || 0; } return parseTypeSize(obj); } /** This lambda function finds the next closest byte size for given bit size */ const findByteSize = (input) => { const remainder = input % 8; return remainder === 0 ? 8 : 8 - remainder; }; exports.findByteSize = findByteSize; /** This function returns the size of a packed struct - for use with registers */ function getPackedStructSize(obj, packedStructName) { let packedStructNode; if (Array.isArray(obj)) { /* Iteratively get the packedStruct from each object in pssLangObject array */ obj.forEach(arrayItem => { packedStructNode = getNodeFromName(arrayItem, packedStructName); if (typeof (packedStructName) !== 'undefined') { return; } }); } else { packedStructNode = getNodeFromName(obj, packedStructName); } if (typeof (packedStructNode) === 'undefined') { return 0; } let size = 0; packedStructNode.children.map(structItemNode => { const structItem = structItemNode; size += getObjectSize(structItem.instanceType); }); /* todo */ return size; } /** This function goes through a PSS Lang Objects data type and finds the object with given name and returns it */ function getNodeFromName(object, name, ignore = dataTypes_1.objType.UNKNOWN) { const stack = [object]; /* This logic uses an iterative Depth First Search algorithm to find the child */ while (stack.length > 0) { const current = stack.pop(); if (current.name === name) { if (Array.isArray(ignore)) { if (!ignore.some(ignored => current.type === ignored)) { return current; } } else { if (current.type !== ignore) { return current; } } } for (const child of current.children) { stack.push(child); } } return undefined; } /** This function just iterates through objects and returns the result from getNodeFromName function */ function getNodeFromNameArray(objects, name, ignore = dataTypes_1.objType.UNKNOWN) { for (const obj of objects) { const node = getNodeFromName(obj, name, ignore); if (typeof (node) !== 'undefined') { return node; } } return undefined; } /** * Collects nodes from PSSLangObjects arrays that match the given criteria. * If multiple criteria are provided, nodes must match all of them. */ function collectAllPSSNodes(objects, options = {}, skipOptions = {}) { const { name, type, fileURI } = options; const { skipName, skipType, skipFileURI } = skipOptions; const results = []; /* If no filtering criteria provided, return empty array */ if (!name && type === undefined && !fileURI) { return results; } /* Process each root object */ for (const object of objects) { /* Use iterative DFS to traverse the tree */ const stack = [object]; while (stack.length > 0) { const current = stack.pop(); /* Check if current node matches all specified criteria */ let isMatch = true; if (name !== undefined && current.name !== name) { isMatch = false; } if (type !== undefined && current.type !== type) { isMatch = false; } if (fileURI !== undefined && (!current.definedOn || current.definedOn.file !== fileURI)) { isMatch = false; } /* Skip certain files */ if (skipName !== undefined && current.name === skipName) { isMatch = false; } if (skipType !== undefined && current.type === skipType) { isMatch = false; } if (skipFileURI !== undefined && (!current.definedOn || current.definedOn.file === skipFileURI)) { isMatch = false; } /* If all criteria matched, add to results */ if (isMatch) { results.push(current); } /* Add all children to stack for processing */ for (const child of current.children) { stack.push(child); } } } return results; } // Type guard to check if a node is an InstanceNode function isInstanceNode(node) { return node.type === dataTypes_1.objType.INSTANCE; } function collectAllInstanceNodes(objects, instanceType) { const results = []; // Process each root object for (const object of objects) { // Use iterative DFS with a stack const stack = [object]; while (stack.length > 0) { const current = stack.pop(); // Check if the current node is an InstanceNode with matching instanceType if (isInstanceNode(current) && current.instanceType === instanceType) { results.push(current); } // Add all children to the stack for further processing for (const child of current.children) { stack.push(child); } } } return results; }