Added function parameter type detection

This commit is contained in:
2025-08-04 13:40:21 -06:00
parent b362770d90
commit fce702b228
2 changed files with 224 additions and 174 deletions

View File

@@ -1,17 +1,5 @@
import * as vscode from 'vscode';
interface FunctionParameterInfo {
name: string;
type: string;
}
interface FunctionDefinition {
name: string;
location: vscode.Location;
type: 'FUNCTION' | 'SUB' | 'DEF';
parameters: string[]; // Array of parameter names
parameterCount: number; // Number of parameters
}
import { FunctionDefinition, parseFunctionDefinitions } from './functions';
// Global symbol index - maps lowercase function names to their definitions
const symbolIndex = new Map<string, FunctionDefinition[]>();
@@ -273,166 +261,6 @@ export async function activate(context: vscode.ExtensionContext) {
}
};
// Function to parse function definitions from a document
function parseFunctionDefinitions(document: vscode.TextDocument): FunctionDefinition[] {
const definitions: FunctionDefinition[] = [];
const lines = document.getText().split('\n');
const identifierRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/;
const labelRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?:/;
// Helper function to remove comments from a line
function removeComments(line: string): string {
let inQuotes = false;
let quoteChar = '';
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (!inQuotes && (char === '"' || char === "'")) {
inQuotes = true;
quoteChar = char;
} else if (inQuotes && char === quoteChar) {
inQuotes = false;
quoteChar = '';
} else if (!inQuotes && char === '!') {
return line.substring(0, i).trim();
}
}
return line;
}
// Helper function to check if a line should continue
function shouldContinue(line: string, allLines: string[], currentIndex: number): boolean {
const withoutComments = removeComments(line).trim();
// Check for explicit continuation with \
if (withoutComments.endsWith('\\')) {
return true;
}
// For function definitions, also check if we have incomplete parentheses
if (/^\s*(FUNCTION|DEF|SUB)\b/i.test(withoutComments)) {
const openParens = (withoutComments.match(/\(/g) || []).length;
const closeParens = (withoutComments.match(/\)/g) || []).length;
if (openParens > closeParens) {
return true;
}
}
return false;
}
// Helper function to parse parameters from a parameter string
function parseParameters(paramString: string): string[] {
if (!paramString.trim()) {
return [];
}
const parameters: string[] = [];
const parts = paramString.split(',');
for (const part of parts) {
const trimmedPart = part.trim();
if (trimmedPart) {
// Extract parameter name using the identifier regex
const match = identifierRegex.exec(trimmedPart);
if (match) {
parameters.push(match[0]);
}
}
}
return parameters;
}
let i = 0;
while (i < lines.length) {
const originalLine = lines[i];
const trimmedLine = originalLine.trim();
// Skip empty lines
if (trimmedLine === '') {
i++;
continue;
}
// Check if this line starts a function definition
const functionMatch = /^\s*(FUNCTION|DEF|SUB)\b/i.exec(trimmedLine);
if (!functionMatch) {
i++;
continue;
}
// Collect continuation lines for this function definition
let continuationLines = [originalLine];
let j = i;
while (j < lines.length && shouldContinue(lines[j], lines, j)) {
if (j + 1 < lines.length) {
j++;
continuationLines.push(lines[j]);
} else {
break;
}
}
// Build the complete logical line by joining all parts
let logicalParts: string[] = [];
for (let k = 0; k < continuationLines.length; k++) {
let part = continuationLines[k].trim();
part = removeComments(part);
if (part.endsWith('\\')) {
part = part.slice(0, -1).trim();
}
if (part) {
logicalParts.push(part);
}
}
const fullLogical = logicalParts.join(' ').trim();
// Check if this is not an external declaration
if (!/\bEXTERNAL\s*$/i.test(fullLogical)) {
// Extract the function type and name
const defType = functionMatch[1].toUpperCase() as 'FUNCTION' | 'SUB' | 'DEF';
// Find the identifier after the function keyword
const afterKeyword = fullLogical.substring(functionMatch[0].length).trim();
const nameMatch = identifierRegex.exec(afterKeyword);
if (nameMatch) {
const functionName = nameMatch[0];
const position = new vscode.Position(i, originalLine.indexOf(functionName));
const location = new vscode.Location(document.uri, position);
// Extract parameters
let parameters: string[] = [];
const afterName = afterKeyword.substring(nameMatch[0].length).trim();
// Check if there are parentheses after the function name
const parenMatch = /^\s*\(\s*(.*?)\s*\)/.exec(afterName);
if (parenMatch) {
// Extract parameter string and parse it
const paramString = parenMatch[1];
parameters = parseParameters(paramString);
}
definitions.push({
name: functionName,
location: location,
type: defType,
parameters: parameters,
parameterCount: parameters.length
});
}
}
// Move to the next logical group
i = j + 1;
}
return definitions;
}
// Function to build the initial symbol index
async function buildInitialSymbolIndex(): Promise<void> {
try {
@@ -785,7 +613,7 @@ export async function activate(context: vscode.ExtensionContext) {
// Build function signature with parameters
let signature = `${definition.type} ${definition.name}`;
if (definition.parameterCount > 0) {
signature += `(${definition.parameters.join(', ')})`;
signature += `(${definition.parameters.map(param => `${param.name}: ${param.type}`).join(', ')})`;
}
content.appendCodeblock(signature, '4690basic');

222
src/functions.ts Normal file
View File

@@ -0,0 +1,222 @@
import * as vscode from 'vscode';
export interface FunctionDefinition {
name: string;
location: vscode.Location;
type: 'FUNCTION' | 'SUB' | 'DEF';
parameters: FunctionParameterInfo[]; // Array of parameter names
parameterCount: number; // Number of parameters
}
interface FunctionParameterInfo {
name: string;
type: string;
}
const typeDeclarationBeginRegex = /^\s*(INTEGER(\*[124])?|REAL|STRING)/i;
// Function to parse function definitions from a document
export function parseFunctionDefinitions(document: vscode.TextDocument): FunctionDefinition[] {
const definitions: FunctionDefinition[] = [];
const lines = document.getText().split('\n');
const identifierRegex = /(\?|[a-zA-Z])([a-zA-Z0-9#\.]*)[\$#%]?/;
let i = 0;
while (i < lines.length) {
const originalLine = lines[i];
const trimmedLine = originalLine.trim();
// Skip empty lines
if (trimmedLine === '') {
i++;
continue;
}
// Check if this line starts a function definition
const functionMatch = /^\s*(FUNCTION|DEF|SUB)\b/i.exec(trimmedLine);
if (!functionMatch) {
i++;
continue;
}
// Collect continuation lines for this function definition
let continuationLines = [originalLine];
let j = i;
while (j < lines.length && shouldContinue(lines[j], lines, j)) {
if (j + 1 < lines.length) {
j++;
continuationLines.push(lines[j]);
} else {
break;
}
}
// Build the complete logical line by joining all parts
let logicalParts: string[] = [];
for (let k = 0; k < continuationLines.length; k++) {
let part = continuationLines[k].trim();
part = removeComments(part);
if (part.endsWith('\\')) {
part = part.slice(0, -1).trim();
}
if (part) {
logicalParts.push(part);
}
}
const fullLogical = logicalParts.join(' ').trim();
// Check if this is not an external declaration
if (!/\bEXTERNAL\s*$/i.test(fullLogical)) {
// Extract the function type and name
const defType = functionMatch[1].toUpperCase() as 'FUNCTION' | 'SUB' | 'DEF';
// Find the identifier after the function keyword
const afterKeyword = fullLogical.substring(functionMatch[0].length).trim();
const nameMatch = identifierRegex.exec(afterKeyword);
if (nameMatch) {
const functionName = nameMatch[0];
const position = new vscode.Position(i, originalLine.indexOf(functionName));
const location = new vscode.Location(document.uri, position);
// Extract parameters
let parameters: string[] = [];
const afterName = afterKeyword.substring(nameMatch[0].length).trim();
// Check if there are parentheses after the function name
const parenMatch = /^\s*\(\s*(.*?)\s*\)/.exec(afterName);
if (parenMatch) {
// Extract parameter string and parse it
const paramString = parenMatch[1];
parameters = parseParameters(paramString, identifierRegex);
}
const paramterTypes = parseTypeDeclarations(i, parameters, lines);
definitions.push({
name: functionName,
location: location,
type: defType,
parameters: paramterTypes,
parameterCount: parameters.length
});
}
}
// Move to the next logical group
i = j + 1;
}
return definitions;
}
// Helper function to remove comments from a line
function removeComments(line: string): string {
let inQuotes = false;
let quoteChar = '';
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (!inQuotes && (char === '"' || char === "'")) {
inQuotes = true;
quoteChar = char;
} else if (inQuotes && char === quoteChar) {
inQuotes = false;
quoteChar = '';
} else if (!inQuotes && char === '!') {
return line.substring(0, i).trim();
}
}
return line;
}
// Helper function to check if a line should continue
function shouldContinue(line: string, allLines: string[], currentIndex: number): boolean {
const withoutComments = removeComments(line).trim();
// Check for explicit continuation with \
if (withoutComments.endsWith('\\')) {
return true;
}
// For function definitions, also check if we have incomplete parentheses
if (/^\s*(FUNCTION|DEF|SUB)\b/i.test(withoutComments)) {
const openParens = (withoutComments.match(/\(/g) || []).length;
const closeParens = (withoutComments.match(/\)/g) || []).length;
if (openParens > closeParens) {
return true;
}
}
return false;
}
// Helper function to parse parameters from a parameter string
function parseParameters(paramString: string, identifierRegex: RegExp): string[] {
if (!paramString.trim()) {
return [];
}
const parameters: string[] = [];
const parts = paramString.split(',');
for (const part of parts) {
const trimmedPart = part.trim();
if (trimmedPart) {
// Extract parameter name using the identifier regex
const match = identifierRegex.exec(trimmedPart);
if (match) {
parameters.push(match[0]);
}
}
}
return parameters;
}
// Helper function to parse parameter types for the defined funtion.
function parseTypeDeclarations(startLine: number, parameters: string[], lines: string[], ): FunctionParameterInfo[] {
const enhancedParameters: FunctionParameterInfo[] = parameters.map(param => ({ name: param, type: '?' }));
let currentLine = startLine + 1;
let line = getFullLogicalLine(lines, currentLine);
let currentType = '';
let regexMatch = typeDeclarationBeginRegex.exec(line[0]);
while (regexMatch) {
const identifiers = line[0].split(',').map(ident => ident.trim());
identifiers[0] = identifiers[0].replace(/\s+/g, ' ').split(' ')[1];
currentType = regexMatch[0].trim().toUpperCase();
enhancedParameters.forEach(ep => {
if (identifiers.indexOf(ep.name) > -1 && ep.type === '?') {
ep.type = currentType;
}
});
currentLine += line[1];
line = getFullLogicalLine(lines, currentLine);
regexMatch = typeDeclarationBeginRegex.exec(line[0]);
}
return enhancedParameters;
}
function getFullLogicalLine(lines: string[], startLine: number): [string, number] {
let fullLogicalLine = '';
let currentCleanLogicalLine = removeComments(lines[startLine]).trim();
let linesConsumed = 0;
while (/\\/.test(currentCleanLogicalLine)) {
fullLogicalLine += ' ' + currentCleanLogicalLine.substring(0, currentCleanLogicalLine.indexOf('\\'));
linesConsumed++;
startLine++;
currentCleanLogicalLine = removeComments(lines[startLine]).trim();
}
fullLogicalLine += ' ' + currentCleanLogicalLine;
return [fullLogicalLine.replaceAll('\\', ''), linesConsumed + 1];
}