Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion defaultDebugConfigs.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
"unexpected\\s+error\\s+usage",
"syntax\\s+error",
"Error:\\s+dynamic\\s+item\\s+not\\s+allocated\\s+",
"Dynamic\\s+item\\s+not\\s+allocated\\s+"
"Dynamic\\s+item\\s+not\\s+allocated\\s+",
"no\\s+such\\s+method\\s+",
"no\\s+such\\s+program\\s+"
]
},
"changeVariableValue": {
Expand Down
80 changes: 78 additions & 2 deletions src/provider/CobolEvaluatableExpressionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EvaluatableExpressionProvider, TextDocument, Position, EvaluatableExpression, ProviderResult, window, Range } from "vscode";
import * as path from "path";

/** Max column index to retrieve line content */
const MAX_COLUMN_INDEX = 300;
Expand All @@ -16,14 +17,89 @@ export class CobolEvaluatableExpressionProvider implements EvaluatableExpression
}
const selectionRange = this.getSelectionRangeInEditor();
if (selectionRange) {
return new EvaluatableExpression(selectionRange);
const variableName = document.getText(selectionRange);
const expression = this.buildExpression(variableName, document, position);
return new EvaluatableExpression(selectionRange, expression);
}
const wordRange = document.getWordRangeAtPosition(position)
if (!wordRange) {
return undefined;
}
const trnsformedWordRange = this.transformWordRange(document, wordRange, position.line + 1);
return new EvaluatableExpression(trnsformedWordRange);
const variableName = document.getText(trnsformedWordRange);
const expression = this.buildExpression(variableName, document, position);
return new EvaluatableExpression(trnsformedWordRange, expression);
}

/**
* Builds the display expression for a variable, adding -c PROGRAM or -c PROGRAM:>METHOD
* prefix so the value is always resolved in the context of the correct scope.
* Method scope is only added when the variable declaration is found inside the method block.
*/
private buildExpression(variableName: string, document: TextDocument, position: Position): string {
const programName = path.parse(document.fileName).name.toUpperCase();
const methodName = this.findMethodScopeForVariable(document, position, variableName);
if (methodName) {
return `-c ${programName}:>${methodName} ${variableName}`;
}
return `-c ${programName} ${variableName}`;
}

/**
* Determines if a variable belongs to a method scope by:
* 1. Searching backwards from the cursor for the nearest enclosing method-id (fast boundary).
* 2. If inside a method, searching for the variable declaration only within that method block.
* If found → method scope; if not found → program scope.
*/
private findMethodScopeForVariable(document: TextDocument, position: Position, variableName: string): string | undefined {
const method = this.findEnclosingMethod(document, position.line);
if (!method) {
return undefined;
}
const parenIdx = variableName.indexOf('(');
const baseName = parenIdx >= 0 ? variableName.substring(0, parenIdx).trim() : variableName;
return this.isDeclaredBetween(document, method.startLine, position.line, baseName) ? method.name : undefined;
}

/**
* Searches backwards from fromLine to find the nearest enclosing method-id.
* Returns the method start line and resolved name (alias preferred), or undefined if in program scope.
*/
private findEnclosingMethod(document: TextDocument, fromLine: number): { startLine: number; name: string } | undefined {
const METHOD_NAME_GROUP = 1;
const METHOD_ALIAS_GROUP = 2;
const METHOD_ID_PATTERN = /\bmethod-id\s*\.\s*([a-zA-Z0-9_\-]+)(?:\s*\([^)]*\))?(?:\s+as\s+"([^"]+)")?/i;
const END_METHOD_PATTERN = /\bend\s+method\b/i;
for (let line = fromLine; line >= 0; line--) {
const lineText = document.lineAt(line).text;
if (END_METHOD_PATTERN.test(lineText) && line < fromLine) {
return undefined;
}
const match = METHOD_ID_PATTERN.exec(lineText);
if (match) {
const name = match[METHOD_ALIAS_GROUP] !== undefined ? match[METHOD_ALIAS_GROUP] : match[METHOD_NAME_GROUP];
return { startLine: line, name };
}
}
return undefined;
}

/**
* Checks whether a variable declaration exists between startLine and endLine (inclusive).
* Recognizes level-number declarations, declare statements, and perform varying inline declarations.
*/
private isDeclaredBetween(document: TextDocument, startLine: number, endLine: number, variableName: string): boolean {
const escaped = variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const levelPattern = new RegExp(`^\\s*\\d+\\s+${escaped}[\\s,.]`, 'i');
const declarePattern = new RegExp(`\\bdeclare\\s+${escaped}\\b`, 'i');
const varyingPattern = new RegExp(`\\bvarying\\s+${escaped}\\s+as\\b`, 'i');
for (let line = startLine; line <= endLine; line++) {
const text = document.lineAt(line).text;
if (levelPattern.test(text) || declarePattern.test(text) || varyingPattern.test(text)) {
return true;
}
}
return false;
}

/**
Expand Down
Loading