Skip to content

Commit 326435e

Browse files
committed
refactor: Simplify the annotation parsing interfaces
1 parent 0d1cf1c commit 326435e

15 files changed

Lines changed: 243 additions & 237 deletions

src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import org.variantsync.diffdetective.datasets.PatchDiffParseOptions;
44
import org.variantsync.diffdetective.datasets.Repository;
5-
import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser;
5+
import org.variantsync.diffdetective.feature.AnnotationParser;
6+
import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser;
67

78
import java.nio.file.Path;
89

@@ -12,9 +13,9 @@
1213
* @author Kevin Jedelhauser, Paul Maximilian Bittner
1314
*/
1415
public class Marlin {
15-
public static final PreprocessorAnnotationParser ANNOTATION_PARSER =
16-
PreprocessorAnnotationParser.CreateCppAnnotationParser(
17-
new MarlinCPPDiffLineFormulaExtractor()
16+
public static final AnnotationParser ANNOTATION_PARSER =
17+
new CPPAnnotationParser(
18+
// TODO new MarlinCPPDiffLineFormulaExtractor()
1819
);
1920

2021
/**

src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.variantsync.diffdetective.datasets.predefined;
22

3-
import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor;
3+
import org.variantsync.diffdetective.feature.DiffLineFormulaExtractor;
44

55
import java.util.regex.Pattern;
66

@@ -11,7 +11,7 @@
1111
*
1212
* @author Paul Bittner
1313
*/
14-
public class MarlinCPPDiffLineFormulaExtractor extends CPPDiffLineFormulaExtractor {
14+
public class MarlinCPPDiffLineFormulaExtractor implements DiffLineFormulaExtractor {
1515
private static final Pattern ENABLED_PATTERN = Pattern.compile("ENABLED\\s*\\(([^)]*)\\)");
1616
private static final Pattern DISABLED_PATTERN = Pattern.compile("DISABLED\\s*\\(([^)]*)\\)");
1717

@@ -20,7 +20,7 @@ public String resolveFeatureMacroFunctions(String formula) {
2020
return
2121
replaceAll(ENABLED_PATTERN, "$1",
2222
replaceAll(DISABLED_PATTERN, "!($1)",
23-
super.resolveFeatureMacroFunctions(formula)));
23+
formula));
2424
}
2525

2626
private String replaceAll(Pattern pattern, String replacement, String string) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.variantsync.diffdetective.feature;
2+
3+
import org.prop4j.Node;
4+
5+
/**
6+
* Represents a new annotation (i.e., a change in the presence condition).
7+
* This includes, {@link AnnotationType#If adding to the presence condition},
8+
* {@link AnnotationType#Endif removing the most recent formula}
9+
* and {@link AnnotationType others}.
10+
*
11+
* @param type the type of this annotation
12+
* @param formula the formula associated to {@code type}.
13+
* Non-null iff {@link AnnotationType#requiresFormula type.requiresFormula}.
14+
*/
15+
public record Annotation(
16+
AnnotationType type,
17+
Node formula
18+
) {
19+
public Annotation(AnnotationType type, Node formula) {
20+
this.type = type;
21+
this.formula = formula;
22+
23+
if (type.requiresFormula && formula == null) {
24+
throw new IllegalArgumentException("Annotations of type " + type.name + " but got null");
25+
}
26+
if (!type.requiresFormula && formula != null) {
27+
throw new IllegalArgumentException("Annotations of type " + type.name + " do not accept a formula but it was given " + formula);
28+
}
29+
}
30+
31+
/**
32+
* Equivalent to {@link #Annotation(AnnotationType, Node) Annotation(type, null)}.
33+
* Hence, only useable iff {@link AnnotationType#requiresFormula !type.requiresFormula}.
34+
*/
35+
public Annotation(AnnotationType type) {
36+
this(type, null);
37+
}
38+
}
Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
11
package org.variantsync.diffdetective.feature;
22

3-
import org.prop4j.Node;
43
import org.variantsync.diffdetective.error.UnparseableFormulaException;
54

65
/**
76
* Interface for a parser that analyzes annotations in parsed text. The parser is responsible for determining the type
8-
* of the annotation (see {@link AnnotationType}), and parsing the annotation into a {@link Node}.
7+
* of the annotation (see {@link AnnotationType}), and parsing the annotation into a {@link org.prop4j.Node}.
98
* <p>
109
* See {@link PreprocessorAnnotationParser} for an example of how an implementation of AnnotationParser could look like.
1110
* </p>
1211
*/
1312
public interface AnnotationParser {
1413
/**
15-
* Determine the annotation type for the given piece of text (typically a line of source code).
14+
* Parse the given line as an annotation.
15+
* Note that {@code line} might also be a line in a diff (i.e., preceded by {@code -} or {@code +}).
1616
*
17-
* @param text The text of which the type is determined.
18-
* @return The annotation type of the piece of text.
17+
* @param line that might contain an annotation
18+
* @return the annotation type and the associated formula.
19+
* If {@code line} doesn't contain an annotation, returns {@code Annotation(AnnotationType.NONE)}.
20+
* @throws UnparseableFormulaException if an annotation is detected but it is malformed
1921
*/
20-
AnnotationType determineAnnotationType(String text);
21-
22-
/**
23-
* Parse the condition of the given text containing an annotation (typically a line of source code).
24-
*
25-
* @param text The text containing a conditional annotation
26-
* @return The formula of the condition in the given annotation.
27-
* If no such formula could be extracted, returns a Literal with the line's condition as name.
28-
* @throws UnparseableFormulaException if there is an error while parsing.
29-
*/
30-
Node parseAnnotation(String text) throws UnparseableFormulaException;
22+
Annotation parseAnnotation(String line) throws UnparseableFormulaException;
3123
}

src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,39 @@ public enum AnnotationType {
99
* The piece of text (e.g., "#if ...") contains a conditional annotation that starts a new
1010
* annotated subtree in the variation tree.
1111
*/
12-
If("if"),
12+
If("if", true),
1313

1414
/**
1515
* The piece of text (e.g., "#elif ...") contains a conditional annotation which is only checked
1616
* if the conditions of all preceding annotations belonging to the same annotation chain are not fulfilled.
1717
*/
18-
Elif("elif"),
18+
Elif("elif", true),
1919

2020
/**
2121
* The piece of text (e.g., "#else") contains an annotation that marks a subtree as used alternative
2222
* if the condition of the preceding annotation in the same annotation chain is not fulfilled.
2323
*/
24-
Else("else"),
24+
Else("else", false),
2525

2626
/**
2727
* The piece of text (e.g., "#endif") marks the end of an annotation (chain).
2828
*/
29-
Endif("endif"),
29+
Endif("endif", false),
3030

3131
/**
3232
* The piece of text contains no annotation. This usually means that it contains an artifact.
3333
*/
34-
None("NONE");
34+
None("NONE", false);
3535

3636
public final String name;
37+
/**
38+
* Does this type of annotation require a formula?
39+
*/
40+
public final boolean requiresFormula;
3741

38-
AnnotationType(String name) {
42+
AnnotationType(String name, boolean requiresFormula) {
3943
this.name = name;
44+
this.requiresFormula = requiresFormula;
4045
}
4146

4247
/**

src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@
1616
* @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß
1717
*/
1818
public interface DiffLineFormulaExtractor {
19-
/**
20-
* Extracts the feature formula as a string from a line (possibly within a diff).
21-
*
22-
* @param line The line of which to get the feature mapping
23-
* @return The feature mapping as a String of the given line
24-
*/
25-
Node extractFormula(final String line) throws UnparseableFormulaException;
26-
2719
/**
2820
* Resolves any macros in the given formula that are relevant for feature annotations.
2921
* For example, in {@link org.variantsync.diffdetective.datasets.predefined.MarlinCPPDiffLineFormulaExtractor Marlin},

src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java

Lines changed: 59 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,104 +2,90 @@
22

33
import org.prop4j.Node;
44
import org.variantsync.diffdetective.error.UnparseableFormulaException;
5-
import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor;
6-
import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor;
7-
5+
import java.util.regex.Matcher;
86
import java.util.regex.Pattern;
97

108
/**
119
* A parser of preprocessor-like annotations.
1210
*
1311
* @author Paul Bittner, Alexander Schultheiß
12+
* @see org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser
13+
* @see org.variantsync.diffdetective.feature.jpp.JPPAnnotationParser
1414
*/
15-
public class PreprocessorAnnotationParser implements AnnotationParser {
16-
/**
17-
* Matches the beginning or end of CPP conditional macros.
18-
* It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is
19-
* matched and only {@code "if"} is captured.
20-
* <p>
21-
* Note that this pattern doesn't handle comments between {@code #} and the macro name.
22-
*/
23-
protected final static Pattern CPP_PATTERN =
24-
Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)");
25-
15+
public abstract class PreprocessorAnnotationParser implements AnnotationParser {
2616
/**
27-
* Matches the beginning or end of JPP conditional macros.
28-
* It doesn't match the whole macro name, for example for {@code //#if defined(x)} only {@code "//#if"} is
29-
* matched and only {@code "if"} is captured.
17+
* Pattern that is used to extract the {@link AnnotationType} and the associated {@link org.prop4j.Node formula}.
3018
* <p>
19+
* The pattern needs to contain at least the two named capture groups {@code directive} and {@code formula}.
20+
* The {@code directive} group must match a string that can be processed by {@link #parseAnnotationType}
21+
* and whenever the resulting annotation type {@link AnnotationType#requiresFormula requires a formula},
22+
* the capture group {@code formula} needs to match the formula that should be processed by {@link parseFormula}.
3123
*/
32-
protected final static Pattern JPP_PATTERN =
33-
Pattern.compile("^[+-]?\\s*//\\s*#\\s*(if|elif|else|endif)");
34-
35-
/**
36-
* Default parser for C preprocessor annotations.
37-
* Created by invoking {@link #PreprocessorAnnotationParser(Pattern, DiffLineFormulaExtractor)}.
38-
*/
39-
public static final PreprocessorAnnotationParser CPPAnnotationParser =
40-
new PreprocessorAnnotationParser(CPP_PATTERN, new CPPDiffLineFormulaExtractor());
41-
42-
/**
43-
* Default parser for <a href="https://www.slashdev.ca/javapp/">JavaPP (Java PreProcessor)</a> annotations.
44-
* Created by invoking {@link #PreprocessorAnnotationParser(Pattern, DiffLineFormulaExtractor)}.
45-
*/
46-
public static final PreprocessorAnnotationParser JPPAnnotationParser =
47-
new PreprocessorAnnotationParser(JPP_PATTERN, new JPPDiffLineFormulaExtractor());
48-
49-
// Pattern that is used to identify the AnnotationType of a given annotation.
50-
private final Pattern annotationPattern;
51-
private final DiffLineFormulaExtractor extractor;
24+
protected final Pattern annotationPattern;
5225

5326
/**
5427
* Creates a new preprocessor annotation parser.
5528
*
56-
* @param annotationPattern Pattern that is used to identify the AnnotationType of a given annotation; {@link #CPP_PATTERN} provides an example
57-
* @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation
29+
* @param annotationPattern pattern that identifies the {@link AnnotationType} and the associated {@link org.prop4j.Node formula} of an annotation
30+
* @see #annotationPattern
5831
*/
59-
public PreprocessorAnnotationParser(final Pattern annotationPattern, DiffLineFormulaExtractor formulaExtractor) {
32+
public PreprocessorAnnotationParser(Pattern annotationPattern) {
6033
this.annotationPattern = annotationPattern;
61-
this.extractor = formulaExtractor;
6234
}
6335

64-
/**
65-
* Creates a new preprocessor annotation parser for C preprocessor annotations.
66-
*
67-
* @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation
68-
*/
69-
public static PreprocessorAnnotationParser CreateCppAnnotationParser(DiffLineFormulaExtractor formulaExtractor) {
70-
return new PreprocessorAnnotationParser(CPP_PATTERN, formulaExtractor);
36+
@Override
37+
public Annotation parseAnnotation(final String line) throws UnparseableFormulaException {
38+
// Match the formula from the macro line
39+
final Matcher matcher = annotationPattern.matcher(line);
40+
if (!matcher.find()) {
41+
return new Annotation(AnnotationType.None);
42+
}
43+
String directive = matcher.group("directive");
44+
String formula = matcher.group("formula");
45+
AnnotationType annotationType = parseAnnotationType(directive);
46+
47+
if (!annotationType.requiresFormula) {
48+
return new Annotation(annotationType);
49+
}
50+
51+
if (annotationType.requiresFormula && formula == null) {
52+
throw new UnparseableFormulaException("Annotations of type " + annotationType.name + " require a formula but none was given");
53+
}
54+
55+
return new Annotation(annotationType, parseFormula(directive, formula));
7156
}
7257

7358
/**
74-
* Creates a new preprocessor annotation parser for <a href="https://www.slashdev.ca/javapp/">JavaPP (Java PreProcessor)</a> annotations.
75-
*
76-
* @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation
59+
* Converts the string captured by the named capture group {@code directive} of {@link #annotationPattern} into an {@link AnnotationType}.
7760
*/
78-
public static PreprocessorAnnotationParser CreateJppAnnotationParser(DiffLineFormulaExtractor formulaExtractor) {
79-
return new PreprocessorAnnotationParser(JPP_PATTERN, formulaExtractor);
61+
protected AnnotationType parseAnnotationType(String directive) {
62+
if (directive.startsWith("if")) {
63+
return AnnotationType.If;
64+
} else if (directive.startsWith("elif")) {
65+
return AnnotationType.Elif;
66+
} else if (directive.equals("else")) {
67+
return AnnotationType.Else;
68+
} else if (directive.equals("endif")) {
69+
return AnnotationType.Endif;
70+
}
71+
72+
throw new IllegalArgumentException("The directive " + directive + " is not a valid conditional compilation directive");
8073
}
8174

8275
/**
83-
* Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF).
76+
* Parses the feature formula of a preprocessor annotation line.
77+
* It should abstract complex formulas (e.g., if they contain arithmetics or macro calls) as desired.
78+
* For example, for the line {@code "#if A && B == C"},
79+
* this method is should be called like {@code parseFormula("if", "A && B == C")}
80+
* (the exact arguments are determined by {@link annotationPattern}
81+
* and it should return something like {@code and(var("A"), var("B==C"))}.
82+
* <p>
83+
* This method is only called if {@code directive} actually requires a formula as determined by {@link #parseAnnotationType}.
8484
*
85-
* @param line The line of code of a preprocessor annotation.
86-
* @return The formula of the macro in the given line.
87-
* If no such formula could be parsed, returns a Literal with the line's condition as name.
88-
* @throws UnparseableFormulaException when {@link DiffLineFormulaExtractor#extractFormula(String)} throws.
85+
* @param directive as matched by the named capture group {@code directive} of {@link annotationPattern}
86+
* @param formula as matched by the named capture group {@code formula} of {@link annotationPattern}
87+
* @return the feature mapping
88+
* @throws UnparseableFormulaException if {@code formula} is ill-formed.
8989
*/
90-
@Override
91-
public Node parseAnnotation(String line) throws UnparseableFormulaException {
92-
return extractor.extractFormula(line);
93-
}
94-
95-
@Override
96-
public AnnotationType determineAnnotationType(String text) {
97-
var matcher = annotationPattern.matcher(text);
98-
int nameId = 1;
99-
if (matcher.find()) {
100-
return AnnotationType.fromName(matcher.group(nameId));
101-
} else {
102-
return AnnotationType.None;
103-
}
104-
}
90+
protected abstract Node parseFormula(String directive, String formula) throws UnparseableFormulaException;
10591
}

0 commit comments

Comments
 (0)