diff --git a/src/extension.ts b/src/extension.ts index 9f599d9..962ff4c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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(); @@ -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 { 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'); diff --git a/src/functions.ts b/src/functions.ts new file mode 100644 index 0000000..a646017 --- /dev/null +++ b/src/functions.ts @@ -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]; +}