Skip to content

Commit 4c8c242

Browse files
author
Julia Pham
committed
fix: improved error reports for number in feature names, duplicates, group keywords and imports
1 parent 624360c commit 4c8c242

3 files changed

Lines changed: 79 additions & 14 deletions

File tree

src/main/java/de/vill/main/UVLErrorListener.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
import de.vill.exception.ParseError;
77
import org.antlr.v4.runtime.*;
88

9+
import java.util.Arrays;
10+
import java.util.HashSet;
911
import java.util.List;
12+
import java.util.Set;
1013
import java.util.regex.*;
1114

1215
public class UVLErrorListener extends BaseErrorListener {
1316

17+
private static final Set<String> GROUP_KEYWORDS = new HashSet<>(Arrays.asList( "mandatory", "optional", "alternative", "or"));
18+
1419
private final List<ParseError> errorList;
1520

1621
public UVLErrorListener(List<ParseError> errorList) {
@@ -24,7 +29,8 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
2429
}
2530

2631
private ErrorReport translateToReport(String message, Object offendingSymbol, Recognizer<?, ?> recognizer, int line, int charPosition) {
27-
// Token recognition error -> LEXICAL
32+
33+
// Token recognition error
2834
Matcher m = Pattern.compile("token recognition error at: '(.*)'").matcher(message);
2935
if (m.find()) {
3036
String character = m.group(1);
@@ -37,7 +43,7 @@ private ErrorReport translateToReport(String message, Object offendingSymbol, Re
3743
.build();
3844
}
3945

40-
// Missing token -> SYNTAX
46+
// Missing token
4147
m = Pattern.compile("missing '?([<>\\w]+)'? at '?(.*?)'?$").matcher(message);
4248
if (m.find()) {
4349
String missing = tokenToReadable(m.group(1));
@@ -50,10 +56,20 @@ private ErrorReport translateToReport(String message, Object offendingSymbol, Re
5056
.build();
5157
}
5258

53-
// Extraneous input -> SYNTAX
59+
// Extraneous input
5460
m = Pattern.compile("extraneous input '?(.*?)'? expecting (.*)").matcher(message);
5561
if (m.find()) {
56-
String extra = tokenToReadable(m.group(1));
62+
String extraString = m.group(1);
63+
String extra = tokenToReadable(extraString);
64+
if (Character.isDigit(extraString.charAt(0))){
65+
return new ErrorReport.Builder(ErrorCategory.SYNTAX,
66+
"Wrong feature name: " + extra)
67+
.line(line).charPosition(charPosition)
68+
.reference(m.group(1))
69+
.cause("Feature names can not start with a number.")
70+
.hint("Rename the feature.")
71+
.build();
72+
}
5773
return new ErrorReport.Builder(ErrorCategory.SYNTAX,
5874
"Unexpected input " + extra)
5975
.line(line).charPosition(charPosition)
@@ -63,18 +79,14 @@ private ErrorReport translateToReport(String message, Object offendingSymbol, Re
6379
.build();
6480
}
6581

66-
// Mismatched input -> SYNTAX
82+
// Mismatched input
6783
m = Pattern.compile("mismatched input '?(.*?)'? expecting (.*)").matcher(message);
6884
if (m.find()) {
6985
String found = tokenToReadable(m.group(1));
7086
String expected = simplifySet(m.group(2));
7187

7288
// Sonderfall: nach Gruppierung ohne Features
73-
// ANTLR meldet entweder "a new line" oder ein Gruppen-Keyword als found,
74-
// wenn eine Gruppe leer ist und expected "an indentation" ist
75-
if (expected.equals("an indentation") && (found.equals("a new line")
76-
|| found.contains("'mandatory'") || found.contains("'optional'")
77-
|| found.contains("'alternative'") || found.contains("'or'"))) {
89+
if (expected.equals("an indentation") && (found.equals("a new line") || found.contains("'mandatory'") || found.contains("'optional'")|| found.contains("'alternative'") || found.contains("'or'"))) {
7890
return new ErrorReport.Builder(ErrorCategory.SYNTAX,
7991
"Missing features after group type")
8092
.line(line).charPosition(charPosition)
@@ -84,8 +96,24 @@ private ErrorReport translateToReport(String message, Object offendingSymbol, Re
8496
.build();
8597
}
8698

87-
// Sonderfall: mehr als ein root Feature
99+
// Case: More than one root feature
88100
if (expected.equals("an indentation or a dedentation")) {
101+
// Check if the offending token is actually a group keyword
102+
String offendingText = null;
103+
if (offendingSymbol instanceof Token) {
104+
offendingText = ((Token) offendingSymbol).getText();
105+
}
106+
if (offendingText != null && GROUP_KEYWORDS.contains(offendingText.toLowerCase())) {
107+
return new ErrorReport.Builder(ErrorCategory.SYNTAX,
108+
"Group keyword '" + offendingText + "' at wrong indentation level")
109+
.line(line).charPosition(charPosition)
110+
.field(ErrorField.GROUP)
111+
.reference(offendingText)
112+
.cause("'" + offendingText + "' is a group type keyword but appears at the wrong indentation level.")
113+
.hint("Indent '" + offendingText + "' further so it is nested under a parent feature.")
114+
.build();
115+
}
116+
89117
return new ErrorReport.Builder(ErrorCategory.SYNTAX,
90118
"More than one root feature detected")
91119
.line(line).charPosition(charPosition)

src/main/java/de/vill/main/UVLListener.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class UVLListener extends UVLJavaParserBaseListener {
5858
private Stack<Group> groupStack = new Stack<>();
5959

6060
private final Map<String, Feature> importedFeatures = new HashMap<>();
61+
private final Map<String, Integer> featureLineNumbers = new HashMap<>();
6162

6263
private Stack<Constraint> constraintStack = new Stack<>();
6364

@@ -222,11 +223,31 @@ public void exitCardinalityGroup(UVLJavaParser.CardinalityGroupContext ctx) {
222223
groupStack.pop();
223224
}
224225

226+
private static final Set<String> GROUP_KEYWORDS = new HashSet<>(Arrays.asList(
227+
"mandatory", "optional", "alternative", "or"
228+
));
229+
225230
@Override
226231
public void enterFeature(UVLJavaParser.FeatureContext ctx) {
227232
String featureReference = ctx.reference().getText().replace("\"", "");
228233
int line = ctx.getStart().getLine();
229234
int charPos = ctx.getStart().getCharPositionInLine();
235+
236+
// Group keywords cannot be used as feature names — this typically
237+
// indicates a wrong indentation rather than an intentional feature name.
238+
if (GROUP_KEYWORDS.contains(featureReference.toLowerCase())) {
239+
errorList.add(new ParseError(new ErrorReport.Builder(ErrorCategory.SYNTAX,
240+
"'" + featureReference + "' is a reserved group keyword and cannot be used as a feature name")
241+
.line(line).charPosition(charPos)
242+
.field(ErrorField.FEATURE)
243+
.reference(featureReference)
244+
.cause("The name '" + featureReference + "' is a reserved group type keyword.")
245+
.hint("Check if the indentation is correct. Group types must be indented under a parent feature.")
246+
.build()));
247+
skippedFeatureDepth = 1;
248+
return;
249+
}
250+
230251
Feature feature = ParsingUtilities.parseFeatureInitialization(featureReference, fmBuilder.getFeatureModel().getImports());
231252
if (feature == null) {
232253
errorList.add(new ParseError(new ErrorReport.Builder(ErrorCategory.CONTEXT,
@@ -240,18 +261,20 @@ public void enterFeature(UVLJavaParser.FeatureContext ctx) {
240261
skippedFeatureDepth = 1;
241262
return;
242263
} else if (importedFeatures.containsKey(featureReference)) {
264+
int originalLine = featureLineNumbers.getOrDefault(featureReference, 0);
243265
errorList.add(new ParseError(new ErrorReport.Builder(ErrorCategory.CONTEXT,
244266
"Duplicate feature name: '" + featureReference + "'")
245267
.line(line).charPosition(charPos)
246268
.field(ErrorField.FEATURE)
247269
.reference(featureReference)
248-
.cause("A feature with the name '" + featureReference + "' already exists in the feature tree.")
270+
.cause("A feature with the name '" + featureReference + "' already exists in the feature tree (first defined at line " + originalLine + ").")
249271
.hint("Rename one of the duplicate features to make names unique.")
250272
.build()));
251273
skippedFeatureDepth = 1;
252274
return;
253275
}
254276
importedFeatures.put(featureReference, feature);
277+
featureLineNumbers.put(featureReference, line);
255278
featureStack.push(feature);
256279
Group parentGroup = groupStack.peek();
257280
fmBuilder.addFeature(feature, parentGroup);

src/main/java/de/vill/main/UVLModelFactory.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,14 @@ private FeatureModel parseFeatureModelWithImports(String text, String rootPath,
307307

308308
for (Import importLine : featureModel.getImports()) {
309309
if (visitedImports.containsKey(importLine.getNamespace()) && visitedImports.get(importLine.getNamespace()) == null) {
310-
throw new ParseError("Cyclic import detected! " + "The import of " + importLine.getNamespace() + " in " + featureModel.getNamespace() + " creates a cycle", importLine.getLineNumber());
310+
throw new ParseError(new ErrorReport.Builder(ErrorCategory.CONTEXT,
311+
"Cyclic import detected: '" + importLine.getNamespace() + "' in '" + featureModel.getNamespace() + "'")
312+
.line(importLine.getLineNumber())
313+
.field(ErrorField.IMPORT)
314+
.reference(importLine.getNamespace())
315+
.cause("The import of '" + importLine.getNamespace() + "' in '" + featureModel.getNamespace() + "' creates a cycle.")
316+
.hint("Remove the circular dependency between the two models.")
317+
.build());
311318
} else {
312319
try {
313320
String path = getPath(rootPath, importLine);
@@ -346,7 +353,14 @@ private FeatureModel parseFeatureModelWithImports(String text, String rootPath,
346353

347354

348355
} catch (IOException e) {
349-
throw new ParseError("Could not resolve import: " + e.getMessage(), importLine.getLineNumber());
356+
throw new ParseError(new ErrorReport.Builder(ErrorCategory.CONTEXT,
357+
"Could not resolve import: '" + importLine.getNamespace() + "'")
358+
.line(importLine.getLineNumber())
359+
.field(ErrorField.IMPORT)
360+
.reference(importLine.getNamespace())
361+
.cause("The file for the imported model could not be found or read: " + e.getMessage())
362+
.hint("Check that the import path is correct and the .uvl file exists in the expected directory.")
363+
.build());
350364
}
351365
}
352366
}

0 commit comments

Comments
 (0)