pss-langserver
Version:
A Language server for the Portable Stimulus Standard
465 lines (464 loc) • 17.5 kB
JavaScript
"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.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;
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 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;
}
async function scanDirectory(dirPath, files) {
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
await 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 && 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;
}