Skip to content

Commit 5407946

Browse files
refactor: reduce overhead of AnnotationParser implementations
1 parent f200c7a commit 5407946

10 files changed

Lines changed: 180 additions & 118 deletions

File tree

src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java

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

3-
import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser;
3+
import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser;
44
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions;
55

66
/**
@@ -27,7 +27,7 @@ public enum DiffStoragePolicy {
2727
/**
2828
* Creates PatchDiffParseOptions with the given annotation parser.
2929
*/
30-
public PatchDiffParseOptions withAnnotationParser(CPPAnnotationParser annotationParser) {
30+
public PatchDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) {
3131
return new PatchDiffParseOptions(
3232
this.diffStoragePolicy(),
3333
this.variationDiffParseOptions().withAnnotationParser(annotationParser)

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

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

33
import org.variantsync.diffdetective.datasets.PatchDiffParseOptions;
44
import org.variantsync.diffdetective.datasets.Repository;
5+
import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser;
56
import org.variantsync.diffdetective.feature.PropositionalFormulaParser;
6-
import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser;
77

88
import java.nio.file.Path;
99

@@ -13,8 +13,9 @@
1313
* @author Kevin Jedelhauser, Paul Maximilian Bittner
1414
*/
1515
public class Marlin {
16-
public static final CPPAnnotationParser ANNOTATION_PARSER =
17-
new CPPAnnotationParser(
16+
public static final PreprocessorAnnotationParser ANNOTATION_PARSER =
17+
new PreprocessorAnnotationParser(
18+
PreprocessorAnnotationParser.CPP_PATTERN,
1819
PropositionalFormulaParser.Default,
1920
new MarlinCPPDiffLineFormulaExtractor()
2021
);
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.variantsync.diffdetective.feature;
2+
3+
import org.prop4j.Node;
4+
import org.variantsync.diffdetective.error.UnparseableFormulaException;
5+
import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor;
6+
import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor;
7+
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* A parser of preprocessor-like annotations.
12+
*
13+
* @author Paul Bittner, Alexander Schultheiß
14+
*/
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+
public final static Pattern CPP_PATTERN =
24+
Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)");
25+
26+
/**
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.
30+
* <p>
31+
*/
32+
public 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, PropositionalFormulaParser, DiffLineFormulaExtractor)}.
38+
*/
39+
public static final PreprocessorAnnotationParser CPPAnnotationParser =
40+
new PreprocessorAnnotationParser(CPP_PATTERN, PropositionalFormulaParser.Default, 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, PropositionalFormulaParser, DiffLineFormulaExtractor)}.
45+
*/
46+
public static final PreprocessorAnnotationParser JPPAnnotationParser =
47+
new PreprocessorAnnotationParser(JPP_PATTERN, PropositionalFormulaParser.Default, new JPPDiffLineFormulaExtractor());
48+
49+
// Pattern that is used to identify the AnnotationType of a given annotation.
50+
private final Pattern annotationPattern;
51+
private final PropositionalFormulaParser formulaParser;
52+
private final DiffLineFormulaExtractor extractor;
53+
54+
/**
55+
* Invokes {@link #PreprocessorAnnotationParser(Pattern, PropositionalFormulaParser, DiffLineFormulaExtractor)} with
56+
* the {@link PropositionalFormulaParser#Default default formula parser} and a new {@link DiffLineFormulaExtractor}.
57+
*
58+
* @param annotationPattern Pattern that is used to identify the AnnotationType of a given annotation; {@link #CPP_PATTERN} provides an example
59+
*/
60+
public PreprocessorAnnotationParser(final Pattern annotationPattern, final DiffLineFormulaExtractor formulaExtractor) {
61+
this(annotationPattern, PropositionalFormulaParser.Default, formulaExtractor);
62+
}
63+
64+
/**
65+
* Creates a new preprocessor annotation parser.
66+
*
67+
* @param annotationPattern Pattern that is used to identify the AnnotationType of a given annotation; {@link #CPP_PATTERN} provides an example
68+
* @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula <code>f</code> in <code>#if f</code>).
69+
* @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser.
70+
*/
71+
public PreprocessorAnnotationParser(final Pattern annotationPattern, final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) {
72+
this.annotationPattern = annotationPattern;
73+
this.formulaParser = formulaParser;
74+
this.extractor = formulaExtractor;
75+
}
76+
77+
/**
78+
* Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF).
79+
*
80+
* @param line The line of code of a preprocessor annotation.
81+
* @return The formula of the macro in the given line.
82+
* If no such formula could be parsed, returns a Literal with the line's condition as name.
83+
* @throws UnparseableFormulaException when {@link DiffLineFormulaExtractor#extractFormula(String)} throws.
84+
*/
85+
public Node parseAnnotation(String line) throws UnparseableFormulaException {
86+
return this.formulaParser.parse(extractor.extractFormula(line));
87+
}
88+
89+
@Override
90+
public AnnotationType determineAnnotationType(String text) {
91+
var matcher = annotationPattern.matcher(text);
92+
int nameId = 1;
93+
if (matcher.find()) {
94+
return AnnotationType.fromName(matcher.group(nameId));
95+
} else {
96+
return AnnotationType.None;
97+
}
98+
}
99+
}

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

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

3+
import org.prop4j.Literal;
34
import org.prop4j.Node;
45
import org.prop4j.NodeReader;
56
import org.variantsync.diffdetective.util.fide.FixTrueFalse;
@@ -36,6 +37,11 @@ public interface PropositionalFormulaParser {
3637
node = FixTrueFalse.EliminateTrueAndFalseInplace(node).get();
3738
}
3839

40+
if (node == null) {
41+
// Logger.warn("Could not parse expression '{}' to feature mapping. Using it as literal.", fmString);
42+
node = new Literal(text);
43+
}
44+
3945
return node;
4046
};
4147
}

src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java

Lines changed: 0 additions & 99 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.variantsync.diffdetective.feature.jpp;
2+
3+
import org.antlr.v4.runtime.CharStreams;
4+
import org.antlr.v4.runtime.CommonTokenStream;
5+
import org.antlr.v4.runtime.tree.ParseTree;
6+
import org.variantsync.diffdetective.feature.AbstractingFormulaExtractor;
7+
import org.variantsync.diffdetective.feature.ParseErrorListener;
8+
import org.variantsync.diffdetective.feature.antlr.JPPExpressionLexer;
9+
import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser;
10+
11+
import java.util.regex.Pattern;
12+
13+
/**
14+
* Extracts the expression from a <a href="https://www.slashdev.ca/javapp/">JavaPP (Java PreProcessor)</a> statement .
15+
* For example, given the annotation "//#if defined(A) || B()", the extractor would extract "A || B".
16+
* The extractor detects if and elif annotations (other annotations do not have expressions).
17+
* The given JPP statement might also be a line in a diff (i.e., preceeded by a - or +).
18+
*
19+
* @author Alexander Schultheiß
20+
*/
21+
public class JPPDiffLineFormulaExtractor extends AbstractingFormulaExtractor {
22+
private static final String JPP_ANNOTATION_REGEX = "^[+-]?\\s*//\\s*#\\s*(if|elif)(\\s+(.*)|(\\(.*\\)))$";
23+
private static final Pattern JPP_ANNOTATION_PATTERN = Pattern.compile(JPP_ANNOTATION_REGEX);
24+
25+
public JPPDiffLineFormulaExtractor() {
26+
super(JPP_ANNOTATION_PATTERN);
27+
}
28+
29+
/**
30+
* Abstract the given formula.
31+
* <p>
32+
* First, the visitor uses ANTLR to parse the formula into a parse tree gives the tree to a {@link ControllingJPPExpressionVisitor}.
33+
* The visitor traverses the tree starting from the root, searching for subtrees that must be abstracted.
34+
* If such a subtree is found, the visitor calls an {@link AbstractingJPPExpressionVisitor} to abstract the part of
35+
* the formula in the subtree.
36+
* </p>
37+
*
38+
* @param formula that is to be abstracted
39+
* @return the abstracted formula
40+
*/
41+
@Override
42+
protected String abstractFormula(String formula) {
43+
JPPExpressionLexer lexer = new JPPExpressionLexer(CharStreams.fromString(formula));
44+
CommonTokenStream tokens = new CommonTokenStream(lexer);
45+
JPPExpressionParser parser = new JPPExpressionParser(tokens);
46+
parser.addErrorListener(new ParseErrorListener(formula));
47+
ParseTree tree = parser.expression();
48+
return tree.accept(new ControllingJPPExpressionVisitor()).toString();
49+
}
50+
}

src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import org.variantsync.diffdetective.datasets.Repository;
66
import org.variantsync.diffdetective.diff.git.PatchDiff;
77
import org.variantsync.diffdetective.diff.result.DiffParseException;
8-
import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser;
8+
import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser;
99
import org.variantsync.diffdetective.mining.RWCompositePatternNodeFormat;
1010
import org.variantsync.diffdetective.mining.RWCompositePatternTreeFormat;
1111
import org.variantsync.diffdetective.mining.VariationDiffMiner;
@@ -102,7 +102,7 @@ private static void render(final Path fileToRender) {
102102
try {
103103
t = VariationDiff.fromFile(fileToRender,
104104
new VariationDiffParseOptions(
105-
CPPAnnotationParser.Default, collapseMultipleCodeLines, ignoreEmptyLines
105+
PreprocessorAnnotationParser.CPPAnnotationParser, collapseMultipleCodeLines, ignoreEmptyLines
106106
));
107107
} catch (IOException | DiffParseException e) {
108108
Logger.error(e, "Could not read given file '{}'", fileToRender);

src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.variantsync.diffdetective.variation.diff.parse;
22

33
import org.variantsync.diffdetective.feature.AnnotationParser;
4-
import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser;
4+
import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser;
55

66
/**
77
* Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s.
@@ -37,7 +37,7 @@ public VariationDiffParseOptions(
3737
/**
3838
* Creates VariationDiffParseOptions with the given annotation parser.
3939
*/
40-
public VariationDiffParseOptions withAnnotationParser(CPPAnnotationParser annotationParser) {
40+
public VariationDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) {
4141
return new VariationDiffParseOptions(
4242
annotationParser,
4343
this.collapseMultipleCodeLines(),
@@ -47,10 +47,10 @@ public VariationDiffParseOptions withAnnotationParser(CPPAnnotationParser annota
4747

4848
/**
4949
* Default value for VariationDiffParseOptions that does not remember parsed unix diffs
50-
* and uses the default value for the parsing annotations ({@link CPPAnnotationParser#Default}).
50+
* and uses the default value for the parsing annotations ({@link PreprocessorAnnotationParser#CPPAnnotationParser}).
5151
*/
5252
public static final VariationDiffParseOptions Default = new VariationDiffParseOptions(
53-
CPPAnnotationParser.Default,
53+
PreprocessorAnnotationParser.CPPAnnotationParser,
5454
false,
5555
false
5656
);

src/test/java/JPPParserTest.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import org.junit.jupiter.params.ParameterizedTest;
22
import org.junit.jupiter.params.provider.MethodSource;
33
import org.variantsync.diffdetective.error.UnparseableFormulaException;
4+
import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor;
45

56
import java.util.List;
67

8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
711
// Test cases for a parser of https://www.slashdev.ca/javapp/
812
public class JPPParserTest {
913
private record TestCase(String formula, String expected) {
@@ -70,17 +74,18 @@ private static List<JPPParserTest.ThrowingTestCase> throwingTestCases() {
7074
@ParameterizedTest
7175
@MethodSource("testCases")
7276
public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException {
73-
// assertEquals(
74-
// testCase.expected,
75-
// // TODO:
76-
// );
77+
assertEquals(
78+
testCase.expected,
79+
new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula())
80+
);
7781
}
7882

7983
@ParameterizedTest
8084
@MethodSource("throwingTestCases")
8185
public void throwingTestCase(JPPParserTest.ThrowingTestCase testCase) {
82-
// assertThrows(UnparseableFormulaException.class, () -> //TODO
83-
// );
86+
assertThrows(UnparseableFormulaException.class, () ->
87+
new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula)
88+
);
8489
}
8590

8691
}

0 commit comments

Comments
 (0)