Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
package org.eclipse.lsp.cobol.core.engine.directives;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Setter;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.eclipse.lsp.cobol.AntlrRangeUtils;
Expand All @@ -39,7 +42,56 @@
public class CompilerDirectivesVisitor extends CompilerDirectivesParserBaseVisitor<Object> {
private final AnalysisContext analysisContext;
private final MessageService messageService;
private final Position startPosition;
@Setter private Position startPosition;

private final Map<String, List<DirectiveInfo>> directivesByConflictGroup = new HashMap<>();

private static class DirectiveInfo {
final List<CicsOptionOccurrence> options;
final int directiveIndex;

DirectiveInfo(List<CicsOptionOccurrence> options, int directiveIndex) {
this.options = options;
this.directiveIndex = directiveIndex;
}
}

private int currentDirectiveIndex = 0;

private static class CicsOptionOccurrence {
final String optionText;
final Token token;
final Position basePosition;
final int positionInLiteral;
final boolean isFromLiteral;

CicsOptionOccurrence(
String optionText,
Token token,
Position basePosition,
int positionInLiteral,
boolean isFromLiteral) {
this.optionText = optionText;
this.token = token;
this.basePosition = basePosition;
this.positionInLiteral = positionInLiteral;
this.isFromLiteral = isFromLiteral;
}
}

private static class OptionInfo {
final String option;
final Token token;
final boolean isFromLiteral;
final int positionInLiteral;

OptionInfo(String option, Token token, boolean isFromLiteral, int positionInLiteral) {
this.option = option;
this.token = token;
this.isFromLiteral = isFromLiteral;
this.positionInLiteral = positionInLiteral;
}
}

public CompilerDirectivesVisitor(
AnalysisContext ctx, MessageService messageService, Position startPosition) {
Expand Down Expand Up @@ -161,6 +213,7 @@ public Object visitCicsTranslatorDirectives(
cicsDirectives.add(m.group());
}
}
processDirectiveOptions(getAllOptions(ctx));
return super.visitCicsTranslatorDirectives(ctx);
}

Expand All @@ -187,4 +240,252 @@ public Object visitCobolJavaInteroperabilityOptions(
}
return super.visitCobolJavaInteroperabilityOptions(ctx);
}

private void processDirectiveOptions(List<OptionInfo> options) {
Map<String, List<OptionInfo>> conflictGroups = new HashMap<>();

for (OptionInfo option : options) {
String conflictGroup = getConflictGroup(option.option.toUpperCase());
if (conflictGroup != null) {
conflictGroups.computeIfAbsent(conflictGroup, k -> new ArrayList<>()).add(option);
}
}

for (Map.Entry<String, List<OptionInfo>> entry : conflictGroups.entrySet()) {
String conflictGroup = entry.getKey();
List<OptionInfo> groupOptions = entry.getValue();

List<CicsOptionOccurrence> directiveOccurrences = new ArrayList<>();
for (OptionInfo option : groupOptions) {
directiveOccurrences.add(
new CicsOptionOccurrence(
option.option,
option.token,
startPosition,
option.positionInLiteral,
option.isFromLiteral));
}

directivesByConflictGroup
.computeIfAbsent(conflictGroup, k -> new ArrayList<>())
.add(new DirectiveInfo(directiveOccurrences, currentDirectiveIndex));
}

for (OptionInfo option : options) {
String conflictGroup = getConflictGroup(option.option.toUpperCase());
if (conflictGroup == null) {
List<String> cicsDirectives =
analysisContext
.getPreprocessorsDirectives()
.computeIfAbsent("CICS", e -> new ArrayList<>());
if (!cicsDirectives.contains(option.option)) {
cicsDirectives.add(option.option);
}
}
}
}

private List<OptionInfo> getAllOptions(
CompilerDirectivesParser.CicsTranslatorDirectivesContext ctx) {
currentDirectiveIndex++;
List<OptionInfo> allOptions = new ArrayList<>();
for (CompilerDirectivesParser.CicsTranslatorOptionsContext optCtx :
ctx.cicsTranslatorOptions()) {
Token t = optCtx.getStart();
if (t != null) {
String optionText = t.getText();
List<String> cicsDirectives =
analysisContext
.getPreprocessorsDirectives()
.computeIfAbsent("CICS", e -> new ArrayList<>());
cicsDirectives.add(optionText);
allOptions.add(new OptionInfo(optionText, t, false, -1));
}
}

final TerminalNode literal = ctx.LITERAL();
if (literal != null) {
String literalText = literal.getText();
List<String> cicsDirectives =
analysisContext
.getPreprocessorsDirectives()
.computeIfAbsent("CICS", e -> new ArrayList<>());

if ((literalText.startsWith("\"") && literalText.endsWith("\""))
|| (literalText.startsWith("'") && literalText.endsWith("'"))) {
String content = literalText.substring(1, literalText.length() - 1);
String[] options = content.split("[,\\s]+");

int currentPos = 0;
for (String option : options) {
option = option.trim();
if (!option.isEmpty()) {
cicsDirectives.add(option);
int optionPos = content.indexOf(option, currentPos);
if (optionPos != -1) {
currentPos = optionPos + option.length();
allOptions.add(new OptionInfo(option, literal.getSymbol(), true, optionPos + 1));
}
}
}
}
}
return allOptions;
}

/** postProcessDirectives */
public void postProcessDirectives() {
for (Map.Entry<String, List<DirectiveInfo>> entry : directivesByConflictGroup.entrySet()) {
List<DirectiveInfo> directives = entry.getValue();

String finalAssumedOption = null;
DirectiveInfo lastDirective = null;

for (DirectiveInfo directive : directives) {
if (lastDirective == null || directive.directiveIndex > lastDirective.directiveIndex) {
lastDirective = directive;
}
}

if (lastDirective != null && !lastDirective.options.isEmpty()) {
finalAssumedOption = lastDirective.options.get(lastDirective.options.size() - 1).optionText;
}

if (finalAssumedOption == null) {
continue;
}

for (DirectiveInfo directive : directives) {
List<CicsOptionOccurrence> options = directive.options;

if (options.size() == 1) {
CicsOptionOccurrence option = options.get(0);
if (!option.optionText.equalsIgnoreCase(finalAssumedOption)) {
generateWarningForOccurrence(option, finalAssumedOption);
}
} else {
String directiveAssumedOption = options.get(options.size() - 1).optionText;

for (int i = 0; i < options.size() - 1; i++) {
CicsOptionOccurrence option = options.get(i);
if (!option.optionText.equalsIgnoreCase(directiveAssumedOption)
&& !option.optionText.equalsIgnoreCase(finalAssumedOption)) {
generateWarningForOccurrence(option, finalAssumedOption);
}
}

CicsOptionOccurrence lastOption = options.get(options.size() - 1);
if (!lastOption.optionText.equalsIgnoreCase(finalAssumedOption)) {
generateWarningForOccurrence(lastOption, finalAssumedOption);
}
}
}
}
}

private void generateWarningForOccurrence(CicsOptionOccurrence occurrence, String assumedOption) {
Token token = occurrence.token;
if (token == null) return;

Range tokenRange;
if (occurrence.isFromLiteral) {
int tokenLine = token.getLine() - 1;
int tokenColumn = token.getCharPositionInLine() + occurrence.positionInLiteral;
tokenRange =
new Range(
new Position(tokenLine, tokenColumn),
new Position(tokenLine, tokenColumn + occurrence.optionText.length()));
} else {
tokenRange =
new Range(
new Position(token.getLine() - 1, token.getCharPositionInLine()),
new Position(
token.getLine() - 1, token.getCharPositionInLine() + token.getText().length()));
}

Range range =
occurrence.basePosition != null
? CompilerDirectivesUtils.shiftRange(tokenRange, occurrence.basePosition)
: tokenRange;

Location location = new Location(analysisContext.getExtendedDocument().getUri(), range);

analysisContext
.getAccumulatedErrors()
.add(
SyntaxError.syntaxError()
.errorSource(ErrorSource.PARSING)
.location(new OriginalLocation(location, null))
.suggestion(
messageService.getMessage(
"compilerDirective.warning.conflictingCicsOptions",
assumedOption.toUpperCase()))
.severity(ErrorSeverity.WARNING)
.build());
}

private String getConflictGroup(String option) {
switch (option.toUpperCase()) {
case "QUOTE":
case "APOST":
return "DELIMITER";

case "CBLCARD":
case "NOCBLCARD":
return "CBLCARD";

case "COBOL2":
case "CO2":
case "COBOL3":
case "CO3":
return "COBOL_VERSION";

case "CPSM":
case "NOCPSM":
return "CPSM";

case "DEBUG":
case "NODEBUG":
return "DEBUG";

case "EDF":
case "NOEDF":
return "EDF";

case "FEPI":
case "NOFEPI":
return "FEPI";

case "LENGTH":
case "NOLENGTH":
return "LENGTH";

case "LINKAGE":
case "NOLINKAGE":
return "LINKAGE";

case "NUM":
case "NONUM":
return "NUM";

case "OPTIONS":
case "NOOPTIONS":
return "OPTIONS";

case "SEQ":
case "NOSEQ":
return "SEQ";

case "SPIE":
case "NOSPIE":
return "SPIE";

case "VBREF":
case "NOVBREF":
return "VBREF";

default:
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public CompilerDirectivesStage(MessageService messageService) {
public StageResult<Void> run(
AnalysisContext ctx, StageResult<List<CompilerDirectiveNode>> prevStageResult) {
String text = ctx.getExtendedDocument().getCurrentText().toString();

CompilerDirectivesVisitor visitor = new CompilerDirectivesVisitor(ctx, messageService, null);
String[] lines = NEW_LINE_PATTERN.split(text);
for (int i = 0; i < lines.length; i++) {
Matcher directivesLine = COMPILER_DIRECTIVE_LINE.matcher(lines[i]);
Expand All @@ -60,18 +60,24 @@ public StageResult<Void> run(
continue;
}
process(
visitor,
directivesLine.group("directives"),
ctx,
new Position(i, directivesLine.start("directives")));
String newText = new String(new char[lines[i].length()]).replace('\0', ' ');
Range range = new Range(new Position(i, 0), new Position(i, lines[i].length()));
ctx.getExtendedDocument().replace(range, newText);
}
visitor.postProcessDirectives();

return new StageResult<>(null);
}

private void process(String directives, AnalysisContext ctx, Position startPosition) {
private void process(
CompilerDirectivesVisitor visitor,
String directives,
AnalysisContext ctx,
Position startPosition) {
if (!DIALECT_FILLER_PATTERN.matcher(directives).matches()) {
CompilerDirectivesLexer lexer =
new CompilerDirectivesLexer(CharStreams.fromString(directives));
Expand All @@ -80,8 +86,8 @@ private void process(String directives, AnalysisContext ctx, Position startPosit
parser.removeErrorListeners();
parser.setErrorHandler(new CobolErrorStrategy(messageService));
parser.addErrorListener(new CompilerDirectivesErrorListener(ctx, startPosition));
new CompilerDirectivesVisitor(ctx, messageService, startPosition)
.visit(parser.compilerOptions());
visitor.setStartPosition(startPosition);
visitor.visit(parser.compilerOptions());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ compilerDirective.javaShareable.Off=The JAVA-SHAREABLE state was already OFF.
compilerDirective.tooManyBlanks=At most one blank character is allowed after >>.
compilerDirective.extraText=Line with a compiler directive can only be padded with blanks.
compilerDirective.javaiop.spaceAfterComma=No space allowed.
compilerDirective.warning.conflictingCicsOptions=Conflicting options detected. %s assumed.
cicsParser.missingEndExec=Missing token END-EXEC for the CICS EXEC block
cicsParser.invalidInput=Extraneous input %s
cics.invalidExecBlock=Invalid CICS EXEC block
Expand Down
Loading
Loading