From ad882681719f63b54abd3c62342287afb41f2503 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Sat, 10 Feb 2024 13:39:24 +0100 Subject: [PATCH 01/41] Update nixpkgs This gets rid of the call to the nixpkgs internal `_fromFetchGit` because the `shallow = true` option has been adopted upstream. --- default.nix | 9 +-------- nix/sources.json | 6 +++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/default.nix b/default.nix index 4ca2a4d3..5e05140a 100644 --- a/default.nix +++ b/default.nix @@ -16,14 +16,7 @@ pkgs.stdenv.mkDerivation rec { src = with pkgs.lib.fileset; toSource { root = ./.; - # This should be `gitTracked ./.`. However, this currently doesn't accept - # shallow repositories as used in GitHub CI. - fileset = - (import (sources.nixpkgs + "/lib/fileset/internal.nix") {inherit (pkgs) lib;})._fromFetchGit - "gitTracked" - "argument" - ./. - {shallow = true;}; + fileset = gitTracked ./.; }; nativeBuildInputs = with pkgs; [ diff --git a/nix/sources.json b/nix/sources.json index a22793c4..94a36557 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": null, "owner": "NixOS", "repo": "nixpkgs", - "rev": "a77ab169a83a4175169d78684ddd2e54486ac651", - "sha256": "0r9a87aqhqr7dkhfy5zrx2dgqq11ma2rfvkfwqhz1xqg7y6mcxxg", + "rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6", + "sha256": "1ww2vrgn8xrznssbd05hdlr3d4br6wbjlqprys1al8ahxkyl5syi", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/a77ab169a83a4175169d78684ddd2e54486ac651.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/6832d0d99649db3d65a0e15fa51471537b2c56a6.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } From 78f6560bb5cc953812e08cbba1f14e29ad6ce1ee Mon Sep 17 00:00:00 2001 From: Maximilian Glumann Date: Wed, 14 Feb 2024 18:27:11 +0100 Subject: [PATCH 02/41] javadoc show private --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 4052eb59..8f3e492a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ docs javadoc + private true From 359a009240cf7d9433cab6b6cc6a43c8e4f12693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Mon, 19 Feb 2024 19:45:23 +0100 Subject: [PATCH 03/41] refactor: introduce AnnotationParser interface to allow for parser exchange --- .../examplesearch/ExampleFinder.java | 13 +- .../feature/AnnotationParser.java | 24 +++ .../diffdetective/feature/AnnotationType.java | 58 +++++++ .../feature/CPPAnnotationParser.java | 39 ++++- .../diff/parse/VariationDiffParseOptions.java | 15 +- .../diff/parse/VariationDiffParser.java | 148 ++++++++---------- 6 files changed, 199 insertions(+), 98 deletions(-) create mode 100644 src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java diff --git a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java index f782571d..3d911c62 100644 --- a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java +++ b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java @@ -7,7 +7,7 @@ import org.variantsync.diffdetective.diff.git.GitPatch; import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.diff.text.TextBasedDiff; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.AnnotationParser; import org.variantsync.diffdetective.show.Show; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.IO; @@ -50,9 +50,9 @@ public class ExampleFinder implements Analysis.Hooks { new DefaultEdgeLabelFormat<>(), false, 1000, - RenderOptions.DEFAULT().nodesize()/3, - 0.5*RenderOptions.DEFAULT().edgesize(), - RenderOptions.DEFAULT().arrowsize()/2, + RenderOptions.DEFAULT().nodesize() / 3, + 0.5 * RenderOptions.DEFAULT().edgesize(), + RenderOptions.DEFAULT().arrowsize() / 2, 2, true, List.of() @@ -63,11 +63,12 @@ public class ExampleFinder implements Analysis.Hooks { /** * Creates a new ExampleFinder. + * * @param isGoodExample Function that decides whether a VariationDiff is an example candidate or not. * Should return {@link Optional#empty()} when the given tree is not a good example and thus, should not be considered. * Should return a VariationDiff when the given tree is a good example candidate and should be exported. * The returned VariationDiff might be the exact same VariationDiff or a subtree (e.g., to only export a certain subtree that is relevant). - * @param renderer The renderer to use for rendering example candidates. + * @param renderer The renderer to use for rendering example candidates. */ public ExampleFinder(final ExplainedFilter> isGoodExample, VariationDiffRenderer renderer) { this.isGoodExample = isGoodExample; @@ -101,7 +102,7 @@ private boolean checkIfExample(Analysis analysis, String localDiff) { final Repository currentRepo = analysis.getRepository(); final VariationDiff variationDiff = analysis.getCurrentVariationDiff(); - final CPPAnnotationParser annotationParser = analysis.getRepository().getParseOptions().variationDiffParseOptions().annotationParser(); + final AnnotationParser annotationParser = analysis.getRepository().getParseOptions().variationDiffParseOptions().annotationParser(); // We do not want a variationDiff for the entire file but only for the local change to have a small example. final VariationDiff localTree; diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java new file mode 100644 index 00000000..0950dd4f --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java @@ -0,0 +1,24 @@ +package org.variantsync.diffdetective.feature; + +import org.prop4j.Node; +import org.variantsync.diffdetective.error.UnparseableFormulaException; + +public interface AnnotationParser { + /** + * Determine the annotation type for the given piece of text. + * + * @param text The text of which the type is determined. + * @return The annotation type of the piece of text. + */ + AnnotationType determineAnnotationType(String text); + + /** + * Parse the condition of the given text containing an annotation. + * + * @param text The text containing a conditional annotation + * @return The formula of the condition in the given annotation. + * If no such formula could be extracted, returns a Literal with the line's condition as name. + * @throws UnparseableFormulaException if there is an error while parsing. + */ + Node parseAnnotation(String text) throws UnparseableFormulaException; +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java new file mode 100644 index 00000000..f66a12bc --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java @@ -0,0 +1,58 @@ +package org.variantsync.diffdetective.feature; + +/** + * AnnotationType is an enum that describes whether a piece of text marks the start of an + * annotation, the end of an annotation, or no annotation at all. + */ +public enum AnnotationType { + /** + * The piece of text (e.g., "#if ...") contains a conditional annotation that starts a new + * annotated subtree in the variation tree. + */ + If("if"), + + /** + * The piece of text (e.g., "#elif ...") contains a conditional annotation which is only checked + * if the conditions of all preceding annotations belonging to the same annotation chain are not fulfilled. + */ + Elif("elif"), + + /** + * The piece of text (e.g., "#else") contains an annotation that marks a subtree as used alternative + * if the condition of the preceding annotation in the same annotation chain is not fulfilled. + */ + Else("else"), + + /** + * The piece of text (e.g., "#endif") marks the end of an annotation (chain). + */ + Endif("endif"), + + /** + * The piece of text contains no annotation. This usually means that it contains an artifact. + */ + None("NONE"); + + public final String name; + + AnnotationType(String name) { + this.name = name; + } + + /** + * Creates a NodeType from its value names. + * + * @param name a string that equals the name of one value of this enum (ignoring case) + * @return The NodeType that has the given name + * @see Enum#name() + */ + public static AnnotationType fromName(final String name) { + for (AnnotationType candidate : values()) { + if (candidate.toString().equalsIgnoreCase(name)) { + return candidate; + } + } + + throw new IllegalArgumentException("Given string \"" + name + "\" is not the name of an AnnotationName."); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java index d413bc48..e45c6454 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java @@ -4,16 +4,29 @@ import org.prop4j.Node; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import java.util.regex.Pattern; + /** * A parser of C-preprocessor annotations. + * * @author Paul Bittner */ -public class CPPAnnotationParser { +public class CPPAnnotationParser implements AnnotationParser { /** * Default CPPAnnotationParser. Created by invoking {@link #CPPAnnotationParser()}. */ public static final CPPAnnotationParser Default = new CPPAnnotationParser(); + /** + * Matches the beginning or end of CPP conditional macros. + * It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is + * matched and only {@code "if"} is captured. + *

+ * Note that this pattern doesn't handle comments between {@code #} and the macro name. + */ + private final static Pattern ANNOTATION = + Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)"); + private final PropositionalFormulaParser formulaParser; private final CPPDiffLineFormulaExtractor extractor; @@ -27,8 +40,9 @@ public CPPAnnotationParser() { /** * Creates a new preprocessor annotation parser. + * * @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula f in #if f). - * @param extractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. + * @param extractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. */ public CPPAnnotationParser(final PropositionalFormulaParser formulaParser, CPPDiffLineFormulaExtractor extractor) { this.formulaParser = formulaParser; @@ -37,23 +51,25 @@ public CPPAnnotationParser(final PropositionalFormulaParser formulaParser, CPPDi /** * Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF). + * * @param line The line of code of a preprocessor annotation. * @return The formula of the macro in the given line. - * If no such formula could be parsed, returns a Literal with the line's condition as name. + * If no such formula could be parsed, returns a Literal with the line's condition as name. * @throws UnparseableFormulaException when {@link CPPDiffLineFormulaExtractor#extractFormula(String)} throws. */ - public Node parseDiffLine(String line) throws UnparseableFormulaException { + public Node parseAnnotation(String line) throws UnparseableFormulaException { return parseCondition(extractor.extractFormula(line)); } /** * Parses a condition of a preprocessor macro (i.e., IF, IFDEF, ELIF). * The given input should not start with preprocessor annotations. - * If the input starts with a preprocessor annotation, use {@link #parseDiffLine} instead. + * If the input starts with a preprocessor annotation, use {@link #parseAnnotation} instead. * The input should have been prepared by {@link CPPDiffLineFormulaExtractor}. + * * @param condition The condition of a preprocessor annotation. * @return The formula of the condition. - * If no such formula could be parsed, returns a Literal with the condition as name. + * If no such formula could be parsed, returns a Literal with the condition as name. */ public Node parseCondition(String condition) { Node formula = formulaParser.parse(condition); @@ -65,4 +81,15 @@ public Node parseCondition(String condition) { return formula; } + + @Override + public AnnotationType determineAnnotationType(String text) { + var matcher = ANNOTATION.matcher(text); + if (matcher.find()) { + return AnnotationType.fromName(matcher.group(0)); + } else { + return AnnotationType.None; + } + } + } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java index 4f0d600d..34f6da29 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java @@ -1,22 +1,25 @@ package org.variantsync.diffdetective.variation.diff.parse; +import org.variantsync.diffdetective.feature.AnnotationParser; import org.variantsync.diffdetective.feature.CPPAnnotationParser; /** * Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s. - * @param annotationParser A parser for parsing c preprocessor annotations. + * + * @param annotationParser A parser for parsing c preprocessor annotations. * @param collapseMultipleCodeLines Whether multiple consecutive code lines with the same diff - * type should be collapsed into a single artifact node. - * @param ignoreEmptyLines Whether to add {@code DiffNode}s for empty lines (regardless of their {@code DiffType}). - * If {@link #collapseMultipleCodeLines} is {@code true} empty lines are also not added to - * existing {@code DiffNode}s. + * type should be collapsed into a single artifact node. + * @param ignoreEmptyLines Whether to add {@code DiffNode}s for empty lines (regardless of their {@code DiffType}). + * If {@link #collapseMultipleCodeLines} is {@code true} empty lines are also not added to + * existing {@code DiffNode}s. * @author Paul Bittner */ public record VariationDiffParseOptions( - CPPAnnotationParser annotationParser, + AnnotationParser annotationParser, boolean collapseMultipleCodeLines, boolean ignoreEmptyLines ) { + /** * Creates VariationDiffParseOptions with the default parser as specified in {@link #Default}. */ diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index b8cb4cdf..8d26896b 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -14,19 +14,19 @@ import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.diff.text.DiffLineNumber; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.AnnotationType; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.NodeType; import org.variantsync.diffdetective.variation.diff.DiffNode; -import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.DiffType; import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.Stack; -import java.util.regex.Pattern; /** * Parser that parses {@link VariationDiff}s from text-based diffs. @@ -38,16 +38,16 @@ * /* * #ifdef A * *\/ - * + *

* #ifdef /* * *\/ A * #endif - * + *

* # /**\/ ifdef * #endif - * + *

* # \ - * ifdef + * ifdef * #endif * */ @@ -58,20 +58,11 @@ public class VariationDiffParser { * instead of some source code file. * * @param diffType the diff type of this line, may be {@code null} if this line has no valid - * diff type - * @param content the actual line content without a line delimiter + * diff type + * @param content the actual line content without a line delimiter */ - public record DiffLine(DiffType diffType, String content) {} - - /** - * Matches the beginning of conditional macros. - * It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is - * matched and only {@code "if"} is captured. - *

- * Note that this pattern doesn't handle comments between {@code #} and the macro name. - */ - private final static Pattern macroPattern = - Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)"); + public record DiffLine(DiffType diffType, String content) { + } /* Settings */ @@ -131,9 +122,9 @@ public static VariationDiff createVariationDiff( * This parsing algorithm is described in detail in Sören Viegener's bachelor's thesis. * * @param fullDiff The full diff of a patch obtained from a buffered reader. - * @param options {@link VariationDiffParseOptions} for the parsing process. + * @param options {@link VariationDiffParseOptions} for the parsing process. * @return A parsed {@link VariationDiff} upon success or an error indicating why parsing failed. - * @throws IOException when reading from {@code fullDiff} fails. + * @throws IOException when reading from {@code fullDiff} fails. * @throws DiffParseException if an error in the diff or macro syntax is detected */ public static VariationDiff createVariationDiff( @@ -141,7 +132,7 @@ public static VariationDiff createVariationDiff( final VariationDiffParseOptions options ) throws IOException, DiffParseException { return new VariationDiffParser( - options + options ).parse(() -> { String line = fullDiff.readLine(); if (line == null) { @@ -156,10 +147,10 @@ public static VariationDiff createVariationDiff( * This method is similar to {@link #createVariationDiff(BufferedReader, VariationDiffParseOptions)} * but acts as if all lines where unmodified. * - * @param file The source code file (not a diff) to be parsed. + * @param file The source code file (not a diff) to be parsed. * @param options {@link VariationDiffParseOptions} for the parsing process. * @return A parsed {@link VariationDiff}. - * @throws IOException iff {@code file} throws an {@code IOException} + * @throws IOException iff {@code file} throws an {@code IOException} * @throws DiffParseException if an error in the diff or macro syntax is detected */ public static VariationDiff createVariationTree( @@ -167,7 +158,7 @@ public static VariationDiff createVariationTree( VariationDiffParseOptions options ) throws IOException, DiffParseException { return new VariationDiffParser( - options + options ).parse(() -> { String line = file.readLine(); if (line == null) { @@ -175,9 +166,9 @@ public static VariationDiff createVariationTree( } else { if (line.startsWith("+") || line.startsWith("-")) { Logger.warn( - "The source file given to createVariationTree contains a plus or " + - "minus sign at the start of a line. Please ensure that you are " + - "actually parsing a source file and not a diff." + "The source file given to createVariationTree contains a plus or " + + "minus sign at the start of a line. Please ensure that you are " + + "actually parsing a source file and not a diff." ); } @@ -201,14 +192,14 @@ private VariationDiffParser( * Parses the line diff {@code fullDiff}. * * @param lines should supply successive lines of the diff to be parsed, or {@code null} if - * there are no more lines to be parsed. + * there are no more lines to be parsed. * @return the parsed {@code VariationDiff} - * @throws IOException iff {@code lines.get()} throws {@code IOException} + * @throws IOException iff {@code lines.get()} throws {@code IOException} * @throws DiffParseException if an error in the line diff or the underlying preprocessor syntax - * is detected + * is detected */ private VariationDiff parse( - FailableSupplier lines + FailableSupplier lines ) throws IOException, DiffParseException { DiffNode root = DiffNode.createRoot(new DiffLinesLabel()); beforeStack.push(root); @@ -238,12 +229,12 @@ private VariationDiff parse( // Do beforeLine and afterLine represent the same unchanged diff line? isNon = diffType == DiffType.NON && - (isNon || (!beforeLine.hasStarted() && !afterLine.hasStarted())); + (isNon || (!beforeLine.hasStarted() && !afterLine.hasStarted())); // Add the physical line to the logical line. final DiffLineNumber lineNumberFinal = lineNumber; diffType.forAllTimesOfExistence(beforeLine, afterLine, - node -> node.consume(currentLine, lineNumberFinal) + node -> node.consume(currentLine, lineNumberFinal) ); // Parse the completed logical line @@ -270,21 +261,21 @@ private VariationDiff parse( Logger.debug("beforeLine: " + beforeLine); Logger.debug("afterLine: " + afterLine); throw new DiffParseException( - DiffError.INVALID_LINE_CONTINUATION, - lineNumber + DiffError.INVALID_LINE_CONTINUATION, + lineNumber ); } if (beforeStack.size() > 1) { throw new DiffParseException( - DiffError.NOT_ALL_ANNOTATIONS_CLOSED, - beforeStack.peek().getFromLine() + DiffError.NOT_ALL_ANNOTATIONS_CLOSED, + beforeStack.peek().getFromLine() ); } if (afterStack.size() > 1) { throw new DiffParseException( - DiffError.NOT_ALL_ANNOTATIONS_CLOSED, - afterStack.peek().getFromLine() + DiffError.NOT_ALL_ANNOTATIONS_CLOSED, + afterStack.peek().getFromLine() ); } @@ -299,8 +290,8 @@ private VariationDiff parse( /** * Parses one logical line and most notably, handles conditional macros. * - * @param line a logical line with {@code line.isComplete() == true} - * @param diffType whether {@code line} was added, inserted or unchanged + * @param line a logical line with {@code line.isComplete() == true} + * @param diffType whether {@code line} was added, inserted or unchanged * @param lastLineNumber the last physical line of {@code line} * @throws DiffParseException if erroneous preprocessor macros are detected */ @@ -314,21 +305,18 @@ private void parseLine( // Is this line a conditional macro? // Note: The following line doesn't handle comments and line continuations correctly. - var matcher = macroPattern.matcher(line.toString()); - var conditionalMacroName = matcher.find() - ? matcher.group(1) - : null; + var annotationType = options.annotationParser().determineAnnotationType(line.toString()); - if ("endif".equals(conditionalMacroName)) { + if (annotationType == AnnotationType.Endif) { lastArtifact = null; // Do not create a node for ENDIF, but update the line numbers of the closed if-chain // and remove that if-chain from the relevant stacks. diffType.forAllTimesOfExistence(beforeStack, afterStack, stack -> - popIfChain(stack, fromLine) + popIfChain(stack, fromLine) ); } else if (options.collapseMultipleCodeLines() - && conditionalMacroName == null + && annotationType == AnnotationType.None && lastArtifact != null && lastArtifact.diffType.equals(diffType) && lastArtifact.getToLine().inDiff() == fromLine.inDiff()) { @@ -337,24 +325,22 @@ private void parseLine( lastArtifact.setToLine(toLine); } else { try { - NodeType nodeType = NodeType.ARTIFACT; - if (conditionalMacroName != null) { - try { - nodeType = NodeType.fromName(conditionalMacroName); - } catch (IllegalArgumentException e) { - throw new DiffParseException(DiffError.INVALID_MACRO_NAME, fromLine); - } + NodeType nodeType; + if (annotationType == AnnotationType.None) { + nodeType = NodeType.ARTIFACT; + } else { + nodeType = NodeType.fromName(annotationType.name); } DiffNode newNode = new DiffNode( - diffType, - nodeType, - fromLine, - toLine, - nodeType == NodeType.ARTIFACT || nodeType == NodeType.ELSE - ? null - : options.annotationParser().parseDiffLine(line.toString()), - new DiffLinesLabel(line.getLines()) + diffType, + nodeType, + fromLine, + toLine, + nodeType == NodeType.ARTIFACT || nodeType == NodeType.ELSE + ? null + : options.annotationParser().parseAnnotation(line.toString()), + new DiffLinesLabel(line.getLines()) ); addNode(newNode); @@ -370,13 +356,13 @@ private void parseLine( * If there were ELSEs or ELIFs between an IF and an ENDIF, they were placed on the stack and * have to be popped now. The {@link DiffNode#getToLine() end line numbers} are adjusted * - * @param stack the stack which should be popped + * @param stack the stack which should be popped * @param elseLineNumber the first line of the else which causes this IF to be popped * @throws DiffParseException if {@code stack} doesn't contain an IF node */ private void popIfChain( - Stack> stack, - DiffLineNumber elseLineNumber + Stack> stack, + DiffLineNumber elseLineNumber ) throws DiffParseException { DiffLineNumber previousLineNumber = elseLineNumber; do { @@ -385,13 +371,13 @@ private void popIfChain( // Set the line number of now closed annotations to the beginning of the // following annotation. annotation.setToLine(new DiffLineNumber( - Math.max(previousLineNumber.inDiff(), annotation.getToLine().inDiff()), - stack == beforeStack - ? previousLineNumber.beforeEdit() - : annotation.getToLine().beforeEdit(), - stack == afterStack - ? previousLineNumber.afterEdit() - : annotation.getToLine().afterEdit() + Math.max(previousLineNumber.inDiff(), annotation.getToLine().inDiff()), + stack == beforeStack + ? previousLineNumber.beforeEdit() + : annotation.getToLine().beforeEdit(), + stack == afterStack + ? previousLineNumber.afterEdit() + : annotation.getToLine().afterEdit() )); previousLineNumber = annotation.getFromLine(); @@ -418,8 +404,8 @@ private void addNode(DiffNode newNode) throws DiffParseException if (newNode.isElif() || newNode.isElse()) { if (stack.size() == 1) { throw new DiffParseException( - DiffError.ELSE_OR_ELIF_WITHOUT_IF, - newNode.getFromLine() + DiffError.ELSE_OR_ELIF_WITHOUT_IF, + newNode.getFromLine() ); } @@ -435,7 +421,8 @@ private void addNode(DiffNode newNode) throws DiffParseException /** * Parses the given commit of the given repository. - * @param repo The repository from which a commit should be parsed. + * + * @param repo The repository from which a commit should be parsed. * @param commitHash Hash of the commit to parse. * @return A CommitDiff describing edits to variability introduced by the given commit relative * to its first parent commit. @@ -463,12 +450,13 @@ public static CommitDiff parseCommit(Repository repo, String commitHash) throws /** * Parses the given patch of the given repository. - * @param repo The repository from which a patch should be parsed. - * @param file The file that was edited by the patch. + * + * @param repo The repository from which a patch should be parsed. + * @param file The file that was edited by the patch. * @param commitHash The hash of the commit in which the patch was made. * @return A PatchDiff describing edits to variability introduced by the given patch relative to * the corresponding commit's first parent commit. - * @throws IOException when an error occurred. + * @throws IOException when an error occurred. * @throws AssertionError when no such patch exists. */ public static PatchDiff parsePatch(Repository repo, String file, String commitHash) throws IOException { From c3cba814d4552dc4e32de739319c2d31e0a87f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Mon, 19 Feb 2024 19:48:59 +0100 Subject: [PATCH 04/41] refactor: move C preprocessor-related implementations to own package --- .../datasets/PatchDiffParseOptions.java | 7 +- .../datasets/predefined/Marlin.java | 11 +- .../MarlinCPPDiffLineFormulaExtractor.java | 9 +- .../AbstractingCExpressionVisitor.java | 324 -------------- .../feature/BooleanAbstraction.java | 394 +++++++++++------- .../ControllingCExpressionVisitor.java | 350 ---------------- .../cpp/AbstractingCExpressionVisitor.java | 342 +++++++++++++++ .../{ => cpp}/CPPAnnotationParser.java | 5 +- .../CPPDiffLineFormulaExtractor.java | 12 +- .../cpp/ControllingCExpressionVisitor.java | 368 ++++++++++++++++ .../internal/SimpleRenderer.java | 37 +- .../diff/parse/VariationDiffParseOptions.java | 2 +- src/test/java/CPPParserTest.java | 235 +++++------ src/test/java/TestMultiLineMacros.java | 21 - src/test/java/TreeDiffingTest.java | 73 ++-- 15 files changed, 1162 insertions(+), 1028 deletions(-) delete mode 100644 src/main/java/org/variantsync/diffdetective/feature/AbstractingCExpressionVisitor.java delete mode 100644 src/main/java/org/variantsync/diffdetective/feature/ControllingCExpressionVisitor.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java rename src/main/java/org/variantsync/diffdetective/feature/{ => cpp}/CPPAnnotationParser.java (93%) rename src/main/java/org/variantsync/diffdetective/feature/{ => cpp}/CPPDiffLineFormulaExtractor.java (96%) create mode 100644 src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java diff --git a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java index d8f7d3d4..0857343d 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java @@ -1,12 +1,13 @@ package org.variantsync.diffdetective.datasets; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; /** * Parse options that should be used when parsing commits and patches within a commit history. - * @param diffStoragePolicy Decides if and how unix diffs should be remembered in a parsed - * {@link org.variantsync.diffdetective.diff.git.PatchDiff} when parsing commits. + * + * @param diffStoragePolicy Decides if and how unix diffs should be remembered in a parsed + * {@link org.variantsync.diffdetective.diff.git.PatchDiff} when parsing commits. * @param variationDiffParseOptions Options for parsing a patch to a {@link * org.variantsync.diffdetective.variation.diff.VariationDiff}. For * more information, see {@link VariationDiffParseOptions}. diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java index 48c777a1..6d8cc1ec 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java @@ -2,8 +2,8 @@ import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; import org.variantsync.diffdetective.datasets.Repository; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; import org.variantsync.diffdetective.feature.PropositionalFormulaParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import java.nio.file.Path; @@ -14,13 +14,14 @@ */ public class Marlin { public static final CPPAnnotationParser ANNOTATION_PARSER = - new CPPAnnotationParser( - PropositionalFormulaParser.Default, - new MarlinCPPDiffLineFormulaExtractor() - ); + new CPPAnnotationParser( + PropositionalFormulaParser.Default, + new MarlinCPPDiffLineFormulaExtractor() + ); /** * Clones Marlin from Github. + * * @param localDir Directory to clone the repository to. * @return Marlin repository */ diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java index 15819e3e..8ff1d535 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java @@ -1,6 +1,6 @@ package org.variantsync.diffdetective.datasets.predefined; -import org.variantsync.diffdetective.feature.CPPDiffLineFormulaExtractor; +import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor; import java.util.regex.Pattern; @@ -8,6 +8,7 @@ * Extracts formulas from preprocessor annotations in the marlin firmware. * In particular, it resolves the 'ENABLED' and 'DISABLED' macros that are used in Marlin * to check for features being (de-)selected. + * * @author Paul Bittner */ public class MarlinCPPDiffLineFormulaExtractor extends CPPDiffLineFormulaExtractor { @@ -17,9 +18,9 @@ public class MarlinCPPDiffLineFormulaExtractor extends CPPDiffLineFormulaExtract @Override protected String resolveFeatureMacroFunctions(String formula) { return - replaceAll(ENABLED_PATTERN, "$1", - replaceAll(DISABLED_PATTERN, "!($1)", - super.resolveFeatureMacroFunctions(formula))); + replaceAll(ENABLED_PATTERN, "$1", + replaceAll(DISABLED_PATTERN, "!($1)", + super.resolveFeatureMacroFunctions(formula))); } private String replaceAll(Pattern pattern, String replacement, String string) { diff --git a/src/main/java/org/variantsync/diffdetective/feature/AbstractingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/AbstractingCExpressionVisitor.java deleted file mode 100644 index 415efa31..00000000 --- a/src/main/java/org/variantsync/diffdetective/feature/AbstractingCExpressionVisitor.java +++ /dev/null @@ -1,324 +0,0 @@ -package org.variantsync.diffdetective.feature; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.variantsync.diffdetective.feature.antlr.CExpressionParser; -import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; - -import java.util.function.Function; - -/** - * Visitor that abstracts all symbols of a formula, given as ANTLR parse tree, that might interfere with further formula analysis. - * This visitor traverses the given tree and substitutes all formula substrings with replacements by calling {@link BooleanAbstraction}. - * - *

- * Not all formulas or parts of a formula might require abstraction (e.g., 'A && B'). Therefore, this visitor should not be used directly. - * Instead, you may use a {@link ControllingCExpressionVisitor} which internally uses an {@link AbstractingCExpressionVisitor} - * to control how formulas are abstracted, and only abstracts those parts of a formula that require it. - *

- */ -@SuppressWarnings("CheckReturnValue") -public class AbstractingCExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { - - public AbstractingCExpressionVisitor() {} - - // conditionalExpression - // : logicalOrExpression ('?' expression ':' conditionalExpression)? - // ; - @Override public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { - return visitExpression(ctx, - childContext -> childContext instanceof CExpressionParser.LogicalOrExpressionContext - || childContext instanceof CExpressionParser.ExpressionContext - || childContext instanceof CExpressionParser.ConditionalExpressionContext); - } - - // primaryExpression - // : macroExpression - // | Identifier - // | Constant - // | StringLiteral+ - // | '(' expression ')' - // | unaryOperator primaryExpression - // | specialOperator - // ; - @Override public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { - // macroExpression - if (ctx.macroExpression() != null) { - return ctx.macroExpression().accept(this); - } - // Identifier - if (ctx.Identifier() != null) { - // Terminal - return new StringBuilder(BooleanAbstraction.abstractAll(ctx.Identifier().getText().trim())); - } - // Constant - if (ctx.Constant() != null) { - // Terminal - return new StringBuilder(BooleanAbstraction.abstractAll(ctx.Constant().getText().trim())); - } - // StringLiteral+ - if (!ctx.StringLiteral().isEmpty()) { - // Terminal - StringBuilder sb = new StringBuilder(); - ctx.StringLiteral().stream().map(ParseTree::getText).map(String::trim).map(BooleanAbstraction::abstractAll).forEach(sb::append); - return sb; - } - // '(' expression ')' - if (ctx.expression() != null) { - StringBuilder sb = ctx.expression().accept(this); - sb.insert(0, BooleanAbstraction.BRACKET_L); - sb.append(BooleanAbstraction.BRACKET_R); - return sb; - } - // unaryOperator primaryExpression - if (ctx.unaryOperator() != null) { - StringBuilder sb = ctx.unaryOperator().accept(this); - sb.append(ctx.primaryExpression().accept(this)); - return sb; - } - // specialOperator - if (ctx.specialOperator() != null) { - return ctx.specialOperator().accept(this); - } - - // Unreachable - throw new IllegalStateException("Unreachable code."); - } - - // unaryOperator - // : '&' | '*' | '+' | '-' | '~' | '!' - // ; - @Override public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { - if (ctx.And() != null) { - return new StringBuilder(BooleanAbstraction.U_AND); - } - if (ctx.Star() != null) { - return new StringBuilder(BooleanAbstraction.U_STAR); - } - if (ctx.Plus() != null) { - return new StringBuilder(BooleanAbstraction.U_PLUS); - } - if (ctx.Minus() != null) { - return new StringBuilder(BooleanAbstraction.U_MINUS); - } - if (ctx.Tilde() != null) { - return new StringBuilder(BooleanAbstraction.U_TILDE); - } - if (ctx.Not() != null) { - return new StringBuilder(BooleanAbstraction.U_NOT); - } - throw new IllegalStateException(); - } - - // namespaceExpression - // : primaryExpression (':' primaryExpression)* - // ; - @Override - public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.PrimaryExpressionContext); - } - - // multiplicativeExpression - // : namespaceExpression (('*'|'/'|'%') namespaceExpression)* - // ; - @Override public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.NamespaceExpressionContext); - } - - // additiveExpression - // : multiplicativeExpression (('+'|'-') multiplicativeExpression)* - // ; - @Override public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.MultiplicativeExpressionContext); - } - - // shiftExpression - // : additiveExpression (('<<'|'>>') additiveExpression)* - // ; - @Override public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AdditiveExpressionContext); - } - - // relationalExpression - // : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* - // ; - @Override public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.ShiftExpressionContext); - } - - // equalityExpression - // : relationalExpression (('=='| '!=') relationalExpression)* - // ; - @Override public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.RelationalExpressionContext); - } - - // andExpression - // : equalityExpression ( '&' equalityExpression)* - // ; - @Override public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.EqualityExpressionContext); - } - - // exclusiveOrExpression - // : andExpression ('^' andExpression)* - // ; - @Override public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AndExpressionContext); - } - - // inclusiveOrExpression - // : exclusiveOrExpression ('|' exclusiveOrExpression)* - // ; - @Override public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.ExclusiveOrExpressionContext); - } - - // specialOperator - // : HasAttribute ('(' specialOperatorArgument ')')? - // | HasCPPAttribute ('(' specialOperatorArgument ')')? - // | HasCAttribute ('(' specialOperatorArgument ')')? - // | HasBuiltin ('(' specialOperatorArgument ')')? - // | HasInclude ('(' specialOperatorArgument ')')? - // | Defined ('(' specialOperatorArgument ')') - // | Defined specialOperatorArgument? - // ; - @Override public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.SpecialOperatorArgumentContext); - } - - // specialOperatorArgument - // : HasAttribute - // | HasCPPAttribute - // | HasCAttribute - // | HasBuiltin - // | HasInclude - // | Defined - // | Identifier - // | PathLiteral - // | StringLiteral - // ; - @Override - public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { - return new StringBuilder(BooleanAbstraction.abstractAll(ctx.getText().trim())); - } - - // logicalAndExpression - // : logicalOperand ( '&&' logicalOperand)* - // ; - @Override public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { - return visitExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalOperandContext); - } - - // logicalOrExpression - // : logicalAndExpression ( '||' logicalAndExpression)* - // ; - @Override public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { - return visitExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalAndExpressionContext); - } - - // logicalOperand - // : inclusiveOrExpression - // ; - @Override - public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { - return ctx.inclusiveOrExpression().accept(this); - } - - // macroExpression - // : Identifier '(' argumentExpressionList? ')' - // ; - @Override - public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { - StringBuilder sb = new StringBuilder(); - sb.append(ctx.Identifier().getText().trim().toUpperCase()).append("_"); - if (ctx.argumentExpressionList() != null) { - sb.append(ctx.argumentExpressionList().accept(this)); - } - return sb; - } - - // argumentExpressionList - // : assignmentExpression (',' assignmentExpression)* - // | assignmentExpression (assignmentExpression)* - // ; - @Override - public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { - StringBuilder sb = new StringBuilder(); - sb.append(BooleanAbstraction.BRACKET_L); - for (int i = 0; i < ctx.assignmentExpression().size(); i++) { - sb.append(ctx.assignmentExpression(i).accept(this)); - if (i < ctx.assignmentExpression().size()-1) { - // For each ',' separating arguments - sb.append("__"); - } - } - sb.append(BooleanAbstraction.BRACKET_R); - return sb; - } - - // assignmentExpression - // : conditionalExpression - // | DigitSequence // for - // | PathLiteral - // | StringLiteral - // | primaryExpression assignmentOperator assignmentExpression - // ; - @Override - public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { - if (ctx.conditionalExpression() != null) { - // conditionalExpression - return ctx.conditionalExpression().accept(this); - } else if (ctx.primaryExpression() != null) { - // primaryExpression assignmentOperator assignmentExpression - StringBuilder sb = new StringBuilder(); - sb.append(ctx.primaryExpression().accept(this)); - sb.append(ctx.assignmentOperator().accept(this)); - sb.append(ctx.assignmentExpression().accept(this)); - return sb; - } else { - // all other cases require direct abstraction - return new StringBuilder(BooleanAbstraction.abstractAll(ctx.getText().trim())); - } - } - - // assignmentOperator - // : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' - // ; - @Override - public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { - return new StringBuilder(BooleanAbstraction.abstractToken(ctx.getText().trim())); - } - - // expression - // : assignmentExpression (',' assignmentExpression)* - // ; - @Override - public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AssignmentExpressionContext); - } - - /** - * Abstract all child nodes in the parse tree. - * @param expressionContext The root of the subtree to abstract - * @param instanceCheck A check for expected child node types - * @return The abstracted formula of the subtree - */ - private StringBuilder visitExpression(ParserRuleContext expressionContext, Function instanceCheck) { - StringBuilder sb = new StringBuilder(); - for (ParseTree subtree : expressionContext.children) { - if (instanceCheck.apply(subtree)) { - // Some operand (i.e., a subtree) that we have to visit - sb.append(subtree.accept(this)); - } else if (subtree instanceof TerminalNode terminal) { - // Some operator (i.e., a leaf node) that requires direct abstraction - sb.append(BooleanAbstraction.abstractToken(terminal.getText().trim())); - } else { - // sanity check: loop does not work as expected - throw new IllegalStateException(); - } - } - return sb; - } -} \ No newline at end of file diff --git a/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java b/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java index 44fe8989..684af5bc 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java +++ b/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java @@ -1,5 +1,7 @@ package org.variantsync.diffdetective.feature; +import org.variantsync.diffdetective.feature.cpp.AbstractingCExpressionVisitor; + import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -10,122 +12,225 @@ * higher-order logic (e.g., including arithmetics of function calls) * to a propositional formula. * Non-boolean expressions are replaced by respectively named variables. + * * @author Paul Bittner */ public class BooleanAbstraction { - private BooleanAbstraction(){} + private BooleanAbstraction() { + } - /** Abstraction value for equality checks ==. */ - public static final String EQ = "__EQ__"; - /** Abstraction value for inequality checks !=. */ - public static final String NEQ = "__NEQ__"; - /** Abstraction value for greater-equals checks >=. */ + /** + * Abstraction value for equality checks ==. + */ + public static final String EQ = "__EQ__"; + /** + * Abstraction value for inequality checks !=. + */ + public static final String NEQ = "__NEQ__"; + /** + * Abstraction value for greater-equals checks >=. + */ public static final String GEQ = "__GEQ__"; - /** Abstraction value for smaller-equals checks <=. */ + /** + * Abstraction value for smaller-equals checks <=. + */ public static final String LEQ = "__LEQ__"; - /** Abstraction value for greater checks >. */ + /** + * Abstraction value for greater checks >. + */ public static final String GT = "__GT__"; - /** Abstraction value for smaller checks <. */ + /** + * Abstraction value for smaller checks <. + */ public static final String LT = "__LT__"; - /** Abstraction value for subtractions -. */ + /** + * Abstraction value for subtractions -. + */ public static final String SUB = "__SUB__"; - /** Abstraction value for additions +. */ + /** + * Abstraction value for additions +. + */ public static final String ADD = "__ADD__"; - /** Abstraction value for multiplications *. */ + /** + * Abstraction value for multiplications *. + */ public static final String MUL = "__MUL__"; - /** Abstraction value for divisions /. */ + /** + * Abstraction value for divisions /. + */ public static final String DIV = "__DIV__"; - /** Abstraction value for modulo %. */ + /** + * Abstraction value for modulo %. + */ public static final String MOD = "__MOD__"; - /** Abstraction value for bitwise left shift <<. */ + /** + * Abstraction value for bitwise left shift <<. + */ public static final String LSHIFT = "__LSHIFT__"; - /** Abstraction value for bitwise right shift >>. */ + /** + * Abstraction value for bitwise right shift >>. + */ public static final String RSHIFT = "__RSHIFT__"; - /** Abstraction value for bitwise not ~. */ + /** + * Abstraction value for bitwise not ~. + */ public static final String NOT = "__NOT__"; - /** Abstraction value for bitwise and &. */ + /** + * Abstraction value for bitwise and &. + */ public static final String AND = "__AND__"; - /** Abstraction value for bitwise or |. */ + /** + * Abstraction value for bitwise or |. + */ public static final String OR = "__OR__"; - /** Abstraction value for bitwise xor ^. */ + /** + * Abstraction value for bitwise xor ^. + */ public static final String XOR = "__XOR__"; - /** Abstraction value for the condition of the ternary operator ?. */ + /** + * Abstraction value for the condition of the ternary operator ?. + */ public static final String THEN = "__THEN__"; - /** Abstraction value for the alternative of the ternary operator :, or just colons. */ + /** + * Abstraction value for the alternative of the ternary operator :, or just colons. + */ public static final String COLON = "__COLON__"; - /** Abstraction value for opening brackets (. */ + /** + * Abstraction value for opening brackets (. + */ public static final String BRACKET_L = "__LB__"; - /** Abstraction value for closing brackets ). */ + /** + * Abstraction value for closing brackets ). + */ public static final String BRACKET_R = "__RB__"; - /** Abstraction value for unary 'and' &. */ + /** + * Abstraction value for unary 'and' &. + */ public static final String U_AND = "__U_AND__"; - /** Abstraction value for unary star *. */ + /** + * Abstraction value for unary star *. + */ public static final String U_STAR = "__U_STAR__"; - /** Abstraction value for unary plus +. */ + /** + * Abstraction value for unary plus +. + */ public static final String U_PLUS = "__U_PLUS__"; - /** Abstraction value for unary minus -. */ + /** + * Abstraction value for unary minus -. + */ public static final String U_MINUS = "__U_MINUS__"; - /** Abstraction value for unary tilde ~. */ + /** + * Abstraction value for unary tilde ~. + */ public static final String U_TILDE = "__U_TILDE__"; - /** Abstraction value for unary not !. */ + /** + * Abstraction value for unary not !. + */ public static final String U_NOT = "__U_NOT__"; - /** Abstraction value for logical and &&. */ + /** + * Abstraction value for logical and &&. + */ public static final String L_AND = "__L_AND__"; - /** Abstraction value for logical or ||. */ + /** + * Abstraction value for logical or ||. + */ public static final String L_OR = "__L_OR__"; - /** Abstraction value for dots in paths .. */ + /** + * Abstraction value for dots in paths .. + */ public static final String DOT = "__DOT__"; - /** Abstraction value for quotation marks in paths ". */ + /** + * Abstraction value for quotation marks in paths ". + */ public static final String QUOTE = "__QUOTE__"; - /** Abstraction value for single quotation marks '. */ + /** + * Abstraction value for single quotation marks '. + */ public static final String SQUOTE = "__SQUOTE__"; - /** Abstraction value for assign operator =. */ + /** + * Abstraction value for assign operator =. + */ public static final String ASSIGN = "__ASSIGN__"; - /** Abstraction value for star assign operator *=. */ + /** + * Abstraction value for star assign operator *=. + */ public static final String STAR_ASSIGN = "__STA___ASSIGN__"; - /** Abstraction value for div assign operator /=. */ + /** + * Abstraction value for div assign operator /=. + */ public static final String DIV_ASSIGN = "__DIV___ASSIGN__"; - /** Abstraction value for mod assign operator %=. */ + /** + * Abstraction value for mod assign operator %=. + */ public static final String MOD_ASSIGN = "__MOD___ASSIGN__"; - /** Abstraction value for plus assign operator +=. */ + /** + * Abstraction value for plus assign operator +=. + */ public static final String PLUS_ASSIGN = "__PLU___ASSIGN__"; - /** Abstraction value for minus assign operator -=. */ + /** + * Abstraction value for minus assign operator -=. + */ public static final String MINUS_ASSIGN = "__MIN___ASSIGN__"; - /** Abstraction value for left shift assign operator <<=. */ + /** + * Abstraction value for left shift assign operator <<=. + */ public static final String LEFT_SHIFT_ASSIGN = "__LSH___ASSIGN__"; - /** Abstraction value for right shift assign operator >>=. */ + /** + * Abstraction value for right shift assign operator >>=. + */ public static final String RIGHT_SHIFT_ASSIGN = "__RSH___ASSIGN__"; - /** Abstraction value for 'and' assign operator &=. */ + /** + * Abstraction value for 'and' assign operator &=. + */ public static final String AND_ASSIGN = "__AND___ASSIGN__"; - /** Abstraction value for xor assign operator ^=. */ + /** + * Abstraction value for xor assign operator ^=. + */ public static final String XOR_ASSIGN = "__XOR___ASSIGN__"; - /** Abstraction value for 'or' assign operator |=. */ + /** + * Abstraction value for 'or' assign operator |=. + */ public static final String OR_ASSIGN = "__OR___ASSIGN__"; - /** Abstraction value for whitespace . */ + /** + * Abstraction value for whitespace . + */ public static final String WHITESPACE = "_"; - /** Abstraction value for backslash \. */ + /** + * Abstraction value for backslash \. + */ public static final String BSLASH = "__B_SLASH__"; // The preprocessor has six special operators that require additional abstraction. // These operators are documented under https://gcc.gnu.org/onlinedocs/cpp/Conditional-Syntax.html - /** Abstraction value for has_attribute operator __has_attribute(ATTRIBUTE). + /** + * Abstraction value for has_attribute operator __has_attribute(ATTRIBUTE). * One of the six special operators that require abstraction. - * */ + */ public static final String HAS_ATTRIBUTE = "HAS_ATTRIBUTE_"; - /** Abstraction value for has_cpp_attribute operator __has_cpp_attribute(ATTRIBUTE). - * One of the six special preprocessor operators that require abstraction. */ + /** + * Abstraction value for has_cpp_attribute operator __has_cpp_attribute(ATTRIBUTE). + * One of the six special preprocessor operators that require abstraction. + */ public static final String HAS_CPP_ATTRIBUTE = "HAS_CPP_ATTRIBUTE_"; - /** Abstraction value for has_c_attribute operator __has_c_attribute(ATTRIBUTE). - * One of the six special preprocessor operators that require abstraction. */ + /** + * Abstraction value for has_c_attribute operator __has_c_attribute(ATTRIBUTE). + * One of the six special preprocessor operators that require abstraction. + */ public static final String HAS_C_ATTRIBUTE = "HAS_C_ATTRIBUTE_"; - /** Abstraction value for has_builtin operator __has_builtin(BUILTIN). - * One of the six special preprocessor operators that require abstraction. */ + /** + * Abstraction value for has_builtin operator __has_builtin(BUILTIN). + * One of the six special preprocessor operators that require abstraction. + */ public static final String HAS_BUILTIN = "HAS_BUILTIN_"; - /** Abstraction value for has_include operator __has_include(INCLUDE). - * One of the six special preprocessor operators that require abstraction. */ + /** + * Abstraction value for has_include operator __has_include(INCLUDE). + * One of the six special preprocessor operators that require abstraction. + */ public static final String HAS_INCLUDE = "HAS_INCLUDE_"; - /** Abstraction value for defined operator defined. - * One of the six special preprocessor operators that require abstraction. */ + /** + * Abstraction value for defined operator defined. + * One of the six special preprocessor operators that require abstraction. + */ public static final String DEFINED = "DEFINED_"; private record Replacement(Pattern pattern, String replacement) { @@ -137,99 +242,100 @@ private record Replacement(Pattern pattern, String replacement) { private Replacement { } - /** - * Creates a new replacement matching {@code original} literally. - * - * @param original a string which is searched for literally (without any special - * characters) - * @param replacement the literal replacement for strings matched by {@code original} - */ - public static Replacement literal(String original, String replacement) { - return new Replacement( - Pattern.compile(Pattern.quote(original)), - Matcher.quoteReplacement(replacement) - ); - } + /** + * Creates a new replacement matching {@code original} literally. + * + * @param original a string which is searched for literally (without any special + * characters) + * @param replacement the literal replacement for strings matched by {@code original} + */ + public static Replacement literal(String original, String replacement) { + return new Replacement( + Pattern.compile(Pattern.quote(original)), + Matcher.quoteReplacement(replacement) + ); + } - /** - * Creates a new replacement matching {@code original} literally but only on word - * boundaries. - *

- * A word boundary is defined as the transition from a word character (alphanumerical - * characters) to a non-word character (everything else) or the transition from any - * character to a bracket (the characters {@code (} and {@code )}). - * - * @param original a string which is searched for as a whole word literally (without any - * special characters) - * @param replacement the literal replacement for strings matched by {@code original} - */ - public static Replacement onlyFullWord(String original, String replacement) { - return new Replacement( - Pattern.compile("(?<=\\b|[()])" + Pattern.quote(original) + "(?=\\b|[()])"), - Matcher.quoteReplacement(replacement) - ); - } + /** + * Creates a new replacement matching {@code original} literally but only on word + * boundaries. + *

+ * A word boundary is defined as the transition from a word character (alphanumerical + * characters) to a non-word character (everything else) or the transition from any + * character to a bracket (the characters {@code (} and {@code )}). + * + * @param original a string which is searched for as a whole word literally (without any + * special characters) + * @param replacement the literal replacement for strings matched by {@code original} + */ + public static Replacement onlyFullWord(String original, String replacement) { + return new Replacement( + Pattern.compile("(?<=\\b|[()])" + Pattern.quote(original) + "(?=\\b|[()])"), + Matcher.quoteReplacement(replacement) + ); + } - /** - * Replaces all patterns found in {@code value} by its replacement. - */ - public String applyTo(String value) { - return pattern.matcher(value).replaceAll(replacement); - } + /** + * Replaces all patterns found in {@code value} by its replacement. + */ + public String applyTo(String value) { + return pattern.matcher(value).replaceAll(replacement); } + } private static final List REPLACEMENTS = List.of( - // These replacements are carefully ordered by their length (longest first) to ensure that - // the longest match is replaced first. - Replacement.literal("<<", LSHIFT), - Replacement.literal(">>", RSHIFT), - Replacement.literal("==", EQ), - Replacement.literal("!=", NEQ), - Replacement.literal(">=", GEQ), - Replacement.literal("<=", LEQ), - Replacement.literal(">", GT), - Replacement.literal("<", LT), - Replacement.literal("+", ADD), - Replacement.literal("-", SUB), - Replacement.literal("*", MUL), - Replacement.literal("/", DIV), - Replacement.literal("%", MOD), - Replacement.literal("^", XOR), - Replacement.literal("~", NOT), - Replacement.literal("?", THEN), - Replacement.literal(":", COLON), - Replacement.literal( "&&", L_AND), - Replacement.literal( "||", L_OR), - Replacement.literal( ".", DOT), - Replacement.literal( "\"", QUOTE), - Replacement.literal( "'", SQUOTE), - Replacement.literal( "(", BRACKET_L), - Replacement.literal( ")", BRACKET_R), - Replacement.literal( "__has_attribute", HAS_ATTRIBUTE), - Replacement.literal( "__has_cpp_attribute", HAS_CPP_ATTRIBUTE), - Replacement.literal( "__has_c_attribute", HAS_C_ATTRIBUTE), - Replacement.literal( "__has_builtin", HAS_BUILTIN), - Replacement.literal( "__has_include", HAS_INCLUDE), - Replacement.literal( "defined", DEFINED), - Replacement.literal( "=", ASSIGN), - Replacement.literal( "*=", STAR_ASSIGN), - Replacement.literal( "/=", DIV_ASSIGN), - Replacement.literal( "%=", MOD_ASSIGN), - Replacement.literal( "+=", PLUS_ASSIGN), - Replacement.literal( "-=", MINUS_ASSIGN), - Replacement.literal( "<<=", LEFT_SHIFT_ASSIGN), - Replacement.literal( ">>=", RIGHT_SHIFT_ASSIGN), - Replacement.literal( "&=", AND_ASSIGN), - Replacement.literal( "^=", XOR_ASSIGN), - Replacement.literal( "|=", OR_ASSIGN), - Replacement.literal( "\\", BSLASH), - new Replacement( Pattern.compile("\\s+"), WHITESPACE), - Replacement.onlyFullWord("&", AND), // && has to be left untouched - Replacement.onlyFullWord("|", OR) // || has to be left untouched + // These replacements are carefully ordered by their length (longest first) to ensure that + // the longest match is replaced first. + Replacement.literal("<<", LSHIFT), + Replacement.literal(">>", RSHIFT), + Replacement.literal("==", EQ), + Replacement.literal("!=", NEQ), + Replacement.literal(">=", GEQ), + Replacement.literal("<=", LEQ), + Replacement.literal(">", GT), + Replacement.literal("<", LT), + Replacement.literal("+", ADD), + Replacement.literal("-", SUB), + Replacement.literal("*", MUL), + Replacement.literal("/", DIV), + Replacement.literal("%", MOD), + Replacement.literal("^", XOR), + Replacement.literal("~", NOT), + Replacement.literal("?", THEN), + Replacement.literal(":", COLON), + Replacement.literal("&&", L_AND), + Replacement.literal("||", L_OR), + Replacement.literal(".", DOT), + Replacement.literal("\"", QUOTE), + Replacement.literal("'", SQUOTE), + Replacement.literal("(", BRACKET_L), + Replacement.literal(")", BRACKET_R), + Replacement.literal("__has_attribute", HAS_ATTRIBUTE), + Replacement.literal("__has_cpp_attribute", HAS_CPP_ATTRIBUTE), + Replacement.literal("__has_c_attribute", HAS_C_ATTRIBUTE), + Replacement.literal("__has_builtin", HAS_BUILTIN), + Replacement.literal("__has_include", HAS_INCLUDE), + Replacement.literal("defined", DEFINED), + Replacement.literal("=", ASSIGN), + Replacement.literal("*=", STAR_ASSIGN), + Replacement.literal("/=", DIV_ASSIGN), + Replacement.literal("%=", MOD_ASSIGN), + Replacement.literal("+=", PLUS_ASSIGN), + Replacement.literal("-=", MINUS_ASSIGN), + Replacement.literal("<<=", LEFT_SHIFT_ASSIGN), + Replacement.literal(">>=", RIGHT_SHIFT_ASSIGN), + Replacement.literal("&=", AND_ASSIGN), + Replacement.literal("^=", XOR_ASSIGN), + Replacement.literal("|=", OR_ASSIGN), + Replacement.literal("\\", BSLASH), + new Replacement(Pattern.compile("\\s+"), WHITESPACE), + Replacement.onlyFullWord("&", AND), // && has to be left untouched + Replacement.onlyFullWord("|", OR) // || has to be left untouched ); /** * Apply all possible abstraction replacements for substrings of the given formula. + * * @param formula the formula to abstract * @return a fully abstracted formula */ diff --git a/src/main/java/org/variantsync/diffdetective/feature/ControllingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/ControllingCExpressionVisitor.java deleted file mode 100644 index 658237a8..00000000 --- a/src/main/java/org/variantsync/diffdetective/feature/ControllingCExpressionVisitor.java +++ /dev/null @@ -1,350 +0,0 @@ -package org.variantsync.diffdetective.feature; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.variantsync.diffdetective.feature.antlr.CExpressionParser; -import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; - -import java.util.function.Function; - -/** - * Visitor that controls how formulas given as an ANTLR parse tree are abstracted. - * To this end, the visitor traverses the parse tree, searching for subtrees that should be abstracted. - * If such a subtree is found, the visitor calls an {@link AbstractingCExpressionVisitor} to abstract the entire subtree. - * Only those parts of a formula are abstracted that require abstraction, leaving ancestors in the tree unchanged. - */ -@SuppressWarnings("CheckReturnValue") -public class ControllingCExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { - private final AbstractingCExpressionVisitor abstractingVisitor = new AbstractingCExpressionVisitor(); - - public ControllingCExpressionVisitor() {} - - // conditionalExpression - // : logicalOrExpression ('?' expression ':' conditionalExpression)? - // ; - @Override public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { - if (ctx.expression() != null) { - // logicalOrExpression '?' expression ':' conditionalExpression - // We have to abstract the expression if it is a ternary expression - return ctx.accept(abstractingVisitor); - } else { - // logicalOrExpression - return ctx.logicalOrExpression().accept(this); - } - } - - // primaryExpression - // : macroExpression - // | Identifier - // | Constant - // | StringLiteral+ - // | '(' expression ')' - // | unaryOperator primaryExpression - // | specialOperator - // ; - @Override public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { - // macroExpression - if (ctx.macroExpression() != null) { - return ctx.macroExpression().accept(abstractingVisitor); - } - // Identifier - if (ctx.Identifier() != null) { - // Terminal - return ctx.accept(abstractingVisitor); - } - // Constant - if (ctx.Constant() != null) { - // Terminal - return new StringBuilder(ctx.Constant().getText().trim()); - } - // StringLiteral+ - if (!ctx.StringLiteral().isEmpty()) { - return ctx.accept(abstractingVisitor); - } - // '(' expression ')' - if (ctx.expression() != null) { - StringBuilder sb = ctx.expression().accept(this); - sb.insert(0, "("); - sb.append(")"); - return sb; - } - // unaryOperator primaryExpression - if (ctx.unaryOperator() != null) { - StringBuilder sb = ctx.unaryOperator().accept(this); - sb.append(ctx.primaryExpression().accept(this)); - return sb; - } - // specialOperator - if (ctx.specialOperator() != null) { - return ctx.specialOperator().accept(abstractingVisitor); - } - - // Unreachable - throw new IllegalStateException("Unreachable code."); - } - - // unaryOperator - // : '&' | '*' | '+' | '-' | '~' | '!' - // ; - @Override public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { return new StringBuilder(ctx.getText()); } - - - // namespaceExpression - // : primaryExpression (':' primaryExpression)* - // ; - @Override - public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { - if (ctx.primaryExpression().size() > 1) { - // primaryExpression (('*'|'/'|'%') primaryExpression)+ - // We have to abstract the arithmetic expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // primaryExpression - // There is exactly one child expression - return ctx.primaryExpression(0).accept(this); - } - } - - // multiplicativeExpression - // : primaryExpression (('*'|'/'|'%') primaryExpression)* - // ; - @Override public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { - if (ctx.namespaceExpression().size() > 1) { - // primaryExpression (('*'|'/'|'%') primaryExpression)+ - // We have to abstract the arithmetic expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // primaryExpression - // There is exactly one child expression - return ctx.namespaceExpression(0).accept(this); - } - } - - // additiveExpression - // : multiplicativeExpression (('+'|'-') multiplicativeExpression)* - // ; - @Override public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { - if (ctx.multiplicativeExpression().size() > 1) { - // multiplicativeExpression (('+'|'-') multiplicativeExpression)+ - // We have to abstract the arithmetic expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // multiplicativeExpression - // There is exactly one child expression - return ctx.multiplicativeExpression(0).accept(this); - } - } - - // shiftExpression - // : additiveExpression (('<<'|'>>') additiveExpression)* - // ; - @Override public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { - if (ctx.additiveExpression().size() > 1) { - // additiveExpression (('<<'|'>>') additiveExpression)+ - // We have to abstract the shift expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // additiveExpression - // There is exactly one child expression - return ctx.additiveExpression(0).accept(this); - } - } - - // relationalExpression - // : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* - // ; - @Override public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { - if (ctx.shiftExpression().size() > 1) { - // shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)+ - // We have to abstract the relational expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // shiftExpression - // There is exactly one child expression - return ctx.shiftExpression(0).accept(this); - } - } - - // equalityExpression - // : relationalExpression (('=='| '!=') relationalExpression)* - // ; - @Override public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { - if (ctx.relationalExpression().size() > 1) { - // relationalExpression (('=='| '!=') relationalExpression)+ - // We have to abstract the equality expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // relationalExpression - // There is exactly one child expression - return ctx.relationalExpression(0).accept(this); - } - } - - // specialOperator - // : HasAttribute ('(' specialOperatorArgument ')')? - // | HasCPPAttribute ('(' specialOperatorArgument ')')? - // | HasCAttribute ('(' specialOperatorArgument ')')? - // | HasBuiltin ('(' specialOperatorArgument ')')? - // | HasInclude ('(' (PathLiteral | StringLiteral) ')')? - // | Defined ('(' specialOperatorArgument ')') - // | Defined specialOperatorArgument? - // ; - @Override public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { - // We have to abstract the special operator - return ctx.accept(abstractingVisitor); - } - - // specialOperatorArgument - // : HasAttribute - // | HasCPPAttribute - // | HasCAttribute - // | HasBuiltin - // | HasInclude - // | Defined - // | Identifier - // ; - @Override - public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { - return ctx.accept(abstractingVisitor); - } - - // macroExpression - // : Identifier '(' argumentExpressionList? ')' - // ; - @Override - public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { - return ctx.accept(abstractingVisitor); - } - - // argumentExpressionList - // : assignmentExpression (',' assignmentExpression)* - // | assignmentExpression (assignmentExpression)* - // ; - @Override - public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { - return ctx.accept(abstractingVisitor); - } - - // assignmentExpression - // : conditionalExpression - // | DigitSequence // for - // | PathLiteral - // | StringLiteral - // | primaryExpression assignmentOperator assignmentExpression - // ; - @Override - public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { - if (ctx.conditionalExpression() != null) { - return ctx.conditionalExpression().accept(this); - } else { - return ctx.accept(abstractingVisitor); - } - } - - // assignmentOperator - // : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' - // ; - @Override - public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { - return ctx.accept(abstractingVisitor); - } - - // expression - // : assignmentExpression (',' assignmentExpression)* - // ; - @Override - public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { - if (ctx.assignmentExpression().size() > 1) { - // assignmentExpression (',' assignmentExpression)+ - return ctx.accept(abstractingVisitor); - } else { - // assignmentExpression - return ctx.assignmentExpression(0).accept(this); - } - } - - // andExpression - // : equalityExpression ( '&' equalityExpression)* - // ; - @Override public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { - if (ctx.equalityExpression().size() > 1) { - // equalityExpression ( '&' equalityExpression)+ - // We have to abstract the 'and' expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // equalityExpression - // There is exactly one child expression - return ctx.equalityExpression(0).accept(this); - } - } - - // exclusiveOrExpression - // : andExpression ('^' andExpression)* - // ; - @Override public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { - if (ctx.andExpression().size() > 1) { - // andExpression ('^' andExpression)+ - // We have to abstract the xor expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // andExpression - // There is exactly one child expression - return ctx.andExpression(0).accept(this); - } - } - - // inclusiveOrExpression - // : exclusiveOrExpression ('|' exclusiveOrExpression)* - // ; - @Override public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { - if (ctx.exclusiveOrExpression().size() > 1) { - // exclusiveOrExpression ('|' exclusiveOrExpression)+ - // We have to abstract the 'or' expression if there is more than one operand - return ctx.accept(abstractingVisitor); - } else { - // exclusiveOrExpression - // There is exactly one child expression - return ctx.exclusiveOrExpression(0).accept(this); - } - } - - // logicalAndExpression - // : logicalOperand ( '&&' logicalOperand)* - // ; - @Override public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { - return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalOperandContext); - } - - // logicalOrExpression - // : logicalAndExpression ( '||' logicalAndExpression)* - // ; - @Override public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { - return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalAndExpressionContext); - } - - // logicalOperand - // : inclusiveOrExpression - // ; - @Override - public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { - return ctx.inclusiveOrExpression().accept(this); - } - - private StringBuilder visitLogicalExpression(ParserRuleContext expressionContext, Function instanceCheck) { - StringBuilder sb = new StringBuilder(); - for (ParseTree subtree : expressionContext.children) { - if (instanceCheck.apply(subtree)) { - // logicalAndExpression | InclusiveOrExpression - sb.append(subtree.accept(this)); - } else if (subtree instanceof TerminalNode terminal) { - // '&&' | '||' - sb.append(terminal.getText().trim()); - } else { - // loop does not work as expected - throw new IllegalStateException(); - } - } - return sb; - } -} \ No newline at end of file diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java new file mode 100644 index 00000000..c317e4e2 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java @@ -0,0 +1,342 @@ +package org.variantsync.diffdetective.feature.cpp; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.variantsync.diffdetective.feature.BooleanAbstraction; +import org.variantsync.diffdetective.feature.antlr.CExpressionParser; +import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; + +import java.util.function.Function; + +/** + * Visitor that abstracts all symbols of a formula, given as ANTLR parse tree, that might interfere with further formula analysis. + * This visitor traverses the given tree and substitutes all formula substrings with replacements by calling {@link BooleanAbstraction}. + * + *

+ * Not all formulas or parts of a formula might require abstraction (e.g., 'A && B'). Therefore, this visitor should not be used directly. + * Instead, you may use a {@link ControllingCExpressionVisitor} which internally uses an {@link AbstractingCExpressionVisitor} + * to control how formulas are abstracted, and only abstracts those parts of a formula that require it. + *

+ */ +@SuppressWarnings("CheckReturnValue") +public class AbstractingCExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { + + public AbstractingCExpressionVisitor() { + } + + // conditionalExpression + // : logicalOrExpression ('?' expression ':' conditionalExpression)? + // ; + @Override + public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { + return visitExpression(ctx, + childContext -> childContext instanceof CExpressionParser.LogicalOrExpressionContext + || childContext instanceof CExpressionParser.ExpressionContext + || childContext instanceof CExpressionParser.ConditionalExpressionContext); + } + + // primaryExpression + // : macroExpression + // | Identifier + // | Constant + // | StringLiteral+ + // | '(' expression ')' + // | unaryOperator primaryExpression + // | specialOperator + // ; + @Override + public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { + // macroExpression + if (ctx.macroExpression() != null) { + return ctx.macroExpression().accept(this); + } + // Identifier + if (ctx.Identifier() != null) { + // Terminal + return new StringBuilder(BooleanAbstraction.abstractAll(ctx.Identifier().getText().trim())); + } + // Constant + if (ctx.Constant() != null) { + // Terminal + return new StringBuilder(BooleanAbstraction.abstractAll(ctx.Constant().getText().trim())); + } + // StringLiteral+ + if (!ctx.StringLiteral().isEmpty()) { + // Terminal + StringBuilder sb = new StringBuilder(); + ctx.StringLiteral().stream().map(ParseTree::getText).map(String::trim).map(BooleanAbstraction::abstractAll).forEach(sb::append); + return sb; + } + // '(' expression ')' + if (ctx.expression() != null) { + StringBuilder sb = ctx.expression().accept(this); + sb.insert(0, BooleanAbstraction.BRACKET_L); + sb.append(BooleanAbstraction.BRACKET_R); + return sb; + } + // unaryOperator primaryExpression + if (ctx.unaryOperator() != null) { + StringBuilder sb = ctx.unaryOperator().accept(this); + sb.append(ctx.primaryExpression().accept(this)); + return sb; + } + // specialOperator + if (ctx.specialOperator() != null) { + return ctx.specialOperator().accept(this); + } + + // Unreachable + throw new IllegalStateException("Unreachable code."); + } + + // unaryOperator + // : '&' | '*' | '+' | '-' | '~' | '!' + // ; + @Override + public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { + if (ctx.And() != null) { + return new StringBuilder(BooleanAbstraction.U_AND); + } + if (ctx.Star() != null) { + return new StringBuilder(BooleanAbstraction.U_STAR); + } + if (ctx.Plus() != null) { + return new StringBuilder(BooleanAbstraction.U_PLUS); + } + if (ctx.Minus() != null) { + return new StringBuilder(BooleanAbstraction.U_MINUS); + } + if (ctx.Tilde() != null) { + return new StringBuilder(BooleanAbstraction.U_TILDE); + } + if (ctx.Not() != null) { + return new StringBuilder(BooleanAbstraction.U_NOT); + } + throw new IllegalStateException(); + } + + // namespaceExpression + // : primaryExpression (':' primaryExpression)* + // ; + @Override + public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.PrimaryExpressionContext); + } + + // multiplicativeExpression + // : namespaceExpression (('*'|'/'|'%') namespaceExpression)* + // ; + @Override + public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.NamespaceExpressionContext); + } + + // additiveExpression + // : multiplicativeExpression (('+'|'-') multiplicativeExpression)* + // ; + @Override + public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.MultiplicativeExpressionContext); + } + + // shiftExpression + // : additiveExpression (('<<'|'>>') additiveExpression)* + // ; + @Override + public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AdditiveExpressionContext); + } + + // relationalExpression + // : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* + // ; + @Override + public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.ShiftExpressionContext); + } + + // equalityExpression + // : relationalExpression (('=='| '!=') relationalExpression)* + // ; + @Override + public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.RelationalExpressionContext); + } + + // andExpression + // : equalityExpression ( '&' equalityExpression)* + // ; + @Override + public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.EqualityExpressionContext); + } + + // exclusiveOrExpression + // : andExpression ('^' andExpression)* + // ; + @Override + public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AndExpressionContext); + } + + // inclusiveOrExpression + // : exclusiveOrExpression ('|' exclusiveOrExpression)* + // ; + @Override + public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.ExclusiveOrExpressionContext); + } + + // specialOperator + // : HasAttribute ('(' specialOperatorArgument ')')? + // | HasCPPAttribute ('(' specialOperatorArgument ')')? + // | HasCAttribute ('(' specialOperatorArgument ')')? + // | HasBuiltin ('(' specialOperatorArgument ')')? + // | HasInclude ('(' specialOperatorArgument ')')? + // | Defined ('(' specialOperatorArgument ')') + // | Defined specialOperatorArgument? + // ; + @Override + public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.SpecialOperatorArgumentContext); + } + + // specialOperatorArgument + // : HasAttribute + // | HasCPPAttribute + // | HasCAttribute + // | HasBuiltin + // | HasInclude + // | Defined + // | Identifier + // | PathLiteral + // | StringLiteral + // ; + @Override + public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { + return new StringBuilder(BooleanAbstraction.abstractAll(ctx.getText().trim())); + } + + // logicalAndExpression + // : logicalOperand ( '&&' logicalOperand)* + // ; + @Override + public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { + return visitExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalOperandContext); + } + + // logicalOrExpression + // : logicalAndExpression ( '||' logicalAndExpression)* + // ; + @Override + public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { + return visitExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalAndExpressionContext); + } + + // logicalOperand + // : inclusiveOrExpression + // ; + @Override + public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { + return ctx.inclusiveOrExpression().accept(this); + } + + // macroExpression + // : Identifier '(' argumentExpressionList? ')' + // ; + @Override + public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { + StringBuilder sb = new StringBuilder(); + sb.append(ctx.Identifier().getText().trim().toUpperCase()).append("_"); + if (ctx.argumentExpressionList() != null) { + sb.append(ctx.argumentExpressionList().accept(this)); + } + return sb; + } + + // argumentExpressionList + // : assignmentExpression (',' assignmentExpression)* + // | assignmentExpression (assignmentExpression)* + // ; + @Override + public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { + StringBuilder sb = new StringBuilder(); + sb.append(BooleanAbstraction.BRACKET_L); + for (int i = 0; i < ctx.assignmentExpression().size(); i++) { + sb.append(ctx.assignmentExpression(i).accept(this)); + if (i < ctx.assignmentExpression().size() - 1) { + // For each ',' separating arguments + sb.append("__"); + } + } + sb.append(BooleanAbstraction.BRACKET_R); + return sb; + } + + // assignmentExpression + // : conditionalExpression + // | DigitSequence // for + // | PathLiteral + // | StringLiteral + // | primaryExpression assignmentOperator assignmentExpression + // ; + @Override + public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { + if (ctx.conditionalExpression() != null) { + // conditionalExpression + return ctx.conditionalExpression().accept(this); + } else if (ctx.primaryExpression() != null) { + // primaryExpression assignmentOperator assignmentExpression + StringBuilder sb = new StringBuilder(); + sb.append(ctx.primaryExpression().accept(this)); + sb.append(ctx.assignmentOperator().accept(this)); + sb.append(ctx.assignmentExpression().accept(this)); + return sb; + } else { + // all other cases require direct abstraction + return new StringBuilder(BooleanAbstraction.abstractAll(ctx.getText().trim())); + } + } + + // assignmentOperator + // : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' + // ; + @Override + public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { + return new StringBuilder(BooleanAbstraction.abstractToken(ctx.getText().trim())); + } + + // expression + // : assignmentExpression (',' assignmentExpression)* + // ; + @Override + public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { + return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.AssignmentExpressionContext); + } + + /** + * Abstract all child nodes in the parse tree. + * + * @param expressionContext The root of the subtree to abstract + * @param instanceCheck A check for expected child node types + * @return The abstracted formula of the subtree + */ + private StringBuilder visitExpression(ParserRuleContext expressionContext, Function instanceCheck) { + StringBuilder sb = new StringBuilder(); + for (ParseTree subtree : expressionContext.children) { + if (instanceCheck.apply(subtree)) { + // Some operand (i.e., a subtree) that we have to visit + sb.append(subtree.accept(this)); + } else if (subtree instanceof TerminalNode terminal) { + // Some operator (i.e., a leaf node) that requires direct abstraction + sb.append(BooleanAbstraction.abstractToken(terminal.getText().trim())); + } else { + // sanity check: loop does not work as expected + throw new IllegalStateException(); + } + } + return sb; + } +} \ No newline at end of file diff --git a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java similarity index 93% rename from src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java rename to src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java index e45c6454..ef010976 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/CPPAnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java @@ -1,8 +1,11 @@ -package org.variantsync.diffdetective.feature; +package org.variantsync.diffdetective.feature.cpp; import org.prop4j.Literal; import org.prop4j.Node; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.AnnotationParser; +import org.variantsync.diffdetective.feature.AnnotationType; +import org.variantsync.diffdetective.feature.PropositionalFormulaParser; import java.util.regex.Pattern; diff --git a/src/main/java/org/variantsync/diffdetective/feature/CPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java similarity index 96% rename from src/main/java/org/variantsync/diffdetective/feature/CPPDiffLineFormulaExtractor.java rename to src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java index 6b853d4e..9a499af8 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/CPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java @@ -1,12 +1,12 @@ -package org.variantsync.diffdetective.feature; +package org.variantsync.diffdetective.feature.cpp; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.ATNConfigSet; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.tree.ParseTree; import org.tinylog.Logger; -import org.variantsync.diffdetective.error.UnparseableFormulaException; import org.variantsync.diffdetective.error.UncheckedUnParseableFormulaException; +import org.variantsync.diffdetective.error.UnparseableFormulaException; import org.variantsync.diffdetective.feature.antlr.CExpressionLexer; import org.variantsync.diffdetective.feature.antlr.CExpressionParser; @@ -21,6 +21,7 @@ * "A || B". The extractor detects if, ifdef, ifndef and elif annotations. * (Other annotations do not have expressions.) * The given pre-processor statement might also a line in a diff (i.e., preceeded by a - or +). + * * @author Paul Bittner, Sören Viegener, Benjamin Moosherr */ public class CPPDiffLineFormulaExtractor { @@ -33,6 +34,7 @@ public class CPPDiffLineFormulaExtractor { * For example, in {@link org.variantsync.diffdetective.datasets.predefined.MarlinCPPDiffLineFormulaExtractor Marlin}, * feature annotations are given by the custom ENABLED and DISABLED macros, * which have to be unwrapped. + * * @param formula The formula whose feature macros to resolve. * @return The parseable formula as string. The default implementation returns the input string. */ @@ -42,13 +44,14 @@ protected String resolveFeatureMacroFunctions(String formula) { /** * Extracts the feature formula as a string from a macro line (possibly within a diff). + * * @param line The line of which to get the feature mapping * @return The feature mapping as a String of the given line */ public String extractFormula(final String line) throws UnparseableFormulaException { final Matcher matcher = CPP_ANNOTATION_REGEX_PATTERN.matcher(line); final Supplier couldNotExtractFormula = () -> - new UnparseableFormulaException("Could not extract formula from line \""+ line + "\"."); + new UnparseableFormulaException("Could not extract formula from line \"" + line + "\"."); // Retrieve the formula from the macro line String fm; @@ -91,7 +94,8 @@ public String extractFormula(final String line) throws UnparseableFormulaExcepti * The visitor traverses the tree starting from the root, searching for subtrees that must be abstracted. * If such a subtree is found, the visitor calls an {@link AbstractingCExpressionVisitor} to abstract the part of * the formula in the subtree. - *

+ *

+ * * @param formula that is to be abstracted * @return the abstracted formula */ diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java new file mode 100644 index 00000000..8bc9fed2 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java @@ -0,0 +1,368 @@ +package org.variantsync.diffdetective.feature.cpp; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.variantsync.diffdetective.feature.antlr.CExpressionParser; +import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; + +import java.util.function.Function; + +/** + * Visitor that controls how formulas given as an ANTLR parse tree are abstracted. + * To this end, the visitor traverses the parse tree, searching for subtrees that should be abstracted. + * If such a subtree is found, the visitor calls an {@link AbstractingCExpressionVisitor} to abstract the entire subtree. + * Only those parts of a formula are abstracted that require abstraction, leaving ancestors in the tree unchanged. + */ +@SuppressWarnings("CheckReturnValue") +public class ControllingCExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { + private final AbstractingCExpressionVisitor abstractingVisitor = new AbstractingCExpressionVisitor(); + + public ControllingCExpressionVisitor() { + } + + // conditionalExpression + // : logicalOrExpression ('?' expression ':' conditionalExpression)? + // ; + @Override + public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { + if (ctx.expression() != null) { + // logicalOrExpression '?' expression ':' conditionalExpression + // We have to abstract the expression if it is a ternary expression + return ctx.accept(abstractingVisitor); + } else { + // logicalOrExpression + return ctx.logicalOrExpression().accept(this); + } + } + + // primaryExpression + // : macroExpression + // | Identifier + // | Constant + // | StringLiteral+ + // | '(' expression ')' + // | unaryOperator primaryExpression + // | specialOperator + // ; + @Override + public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { + // macroExpression + if (ctx.macroExpression() != null) { + return ctx.macroExpression().accept(abstractingVisitor); + } + // Identifier + if (ctx.Identifier() != null) { + // Terminal + return ctx.accept(abstractingVisitor); + } + // Constant + if (ctx.Constant() != null) { + // Terminal + return new StringBuilder(ctx.Constant().getText().trim()); + } + // StringLiteral+ + if (!ctx.StringLiteral().isEmpty()) { + return ctx.accept(abstractingVisitor); + } + // '(' expression ')' + if (ctx.expression() != null) { + StringBuilder sb = ctx.expression().accept(this); + sb.insert(0, "("); + sb.append(")"); + return sb; + } + // unaryOperator primaryExpression + if (ctx.unaryOperator() != null) { + StringBuilder sb = ctx.unaryOperator().accept(this); + sb.append(ctx.primaryExpression().accept(this)); + return sb; + } + // specialOperator + if (ctx.specialOperator() != null) { + return ctx.specialOperator().accept(abstractingVisitor); + } + + // Unreachable + throw new IllegalStateException("Unreachable code."); + } + + // unaryOperator + // : '&' | '*' | '+' | '-' | '~' | '!' + // ; + @Override + public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { + return new StringBuilder(ctx.getText()); + } + + + // namespaceExpression + // : primaryExpression (':' primaryExpression)* + // ; + @Override + public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { + if (ctx.primaryExpression().size() > 1) { + // primaryExpression (('*'|'/'|'%') primaryExpression)+ + // We have to abstract the arithmetic expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // primaryExpression + // There is exactly one child expression + return ctx.primaryExpression(0).accept(this); + } + } + + // multiplicativeExpression + // : primaryExpression (('*'|'/'|'%') primaryExpression)* + // ; + @Override + public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { + if (ctx.namespaceExpression().size() > 1) { + // primaryExpression (('*'|'/'|'%') primaryExpression)+ + // We have to abstract the arithmetic expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // primaryExpression + // There is exactly one child expression + return ctx.namespaceExpression(0).accept(this); + } + } + + // additiveExpression + // : multiplicativeExpression (('+'|'-') multiplicativeExpression)* + // ; + @Override + public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { + if (ctx.multiplicativeExpression().size() > 1) { + // multiplicativeExpression (('+'|'-') multiplicativeExpression)+ + // We have to abstract the arithmetic expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // multiplicativeExpression + // There is exactly one child expression + return ctx.multiplicativeExpression(0).accept(this); + } + } + + // shiftExpression + // : additiveExpression (('<<'|'>>') additiveExpression)* + // ; + @Override + public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { + if (ctx.additiveExpression().size() > 1) { + // additiveExpression (('<<'|'>>') additiveExpression)+ + // We have to abstract the shift expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // additiveExpression + // There is exactly one child expression + return ctx.additiveExpression(0).accept(this); + } + } + + // relationalExpression + // : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* + // ; + @Override + public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { + if (ctx.shiftExpression().size() > 1) { + // shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)+ + // We have to abstract the relational expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // shiftExpression + // There is exactly one child expression + return ctx.shiftExpression(0).accept(this); + } + } + + // equalityExpression + // : relationalExpression (('=='| '!=') relationalExpression)* + // ; + @Override + public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { + if (ctx.relationalExpression().size() > 1) { + // relationalExpression (('=='| '!=') relationalExpression)+ + // We have to abstract the equality expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // relationalExpression + // There is exactly one child expression + return ctx.relationalExpression(0).accept(this); + } + } + + // specialOperator + // : HasAttribute ('(' specialOperatorArgument ')')? + // | HasCPPAttribute ('(' specialOperatorArgument ')')? + // | HasCAttribute ('(' specialOperatorArgument ')')? + // | HasBuiltin ('(' specialOperatorArgument ')')? + // | HasInclude ('(' (PathLiteral | StringLiteral) ')')? + // | Defined ('(' specialOperatorArgument ')') + // | Defined specialOperatorArgument? + // ; + @Override + public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { + // We have to abstract the special operator + return ctx.accept(abstractingVisitor); + } + + // specialOperatorArgument + // : HasAttribute + // | HasCPPAttribute + // | HasCAttribute + // | HasBuiltin + // | HasInclude + // | Defined + // | Identifier + // ; + @Override + public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { + return ctx.accept(abstractingVisitor); + } + + // macroExpression + // : Identifier '(' argumentExpressionList? ')' + // ; + @Override + public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { + return ctx.accept(abstractingVisitor); + } + + // argumentExpressionList + // : assignmentExpression (',' assignmentExpression)* + // | assignmentExpression (assignmentExpression)* + // ; + @Override + public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { + return ctx.accept(abstractingVisitor); + } + + // assignmentExpression + // : conditionalExpression + // | DigitSequence // for + // | PathLiteral + // | StringLiteral + // | primaryExpression assignmentOperator assignmentExpression + // ; + @Override + public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { + if (ctx.conditionalExpression() != null) { + return ctx.conditionalExpression().accept(this); + } else { + return ctx.accept(abstractingVisitor); + } + } + + // assignmentOperator + // : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' + // ; + @Override + public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { + return ctx.accept(abstractingVisitor); + } + + // expression + // : assignmentExpression (',' assignmentExpression)* + // ; + @Override + public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { + if (ctx.assignmentExpression().size() > 1) { + // assignmentExpression (',' assignmentExpression)+ + return ctx.accept(abstractingVisitor); + } else { + // assignmentExpression + return ctx.assignmentExpression(0).accept(this); + } + } + + // andExpression + // : equalityExpression ( '&' equalityExpression)* + // ; + @Override + public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { + if (ctx.equalityExpression().size() > 1) { + // equalityExpression ( '&' equalityExpression)+ + // We have to abstract the 'and' expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // equalityExpression + // There is exactly one child expression + return ctx.equalityExpression(0).accept(this); + } + } + + // exclusiveOrExpression + // : andExpression ('^' andExpression)* + // ; + @Override + public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { + if (ctx.andExpression().size() > 1) { + // andExpression ('^' andExpression)+ + // We have to abstract the xor expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // andExpression + // There is exactly one child expression + return ctx.andExpression(0).accept(this); + } + } + + // inclusiveOrExpression + // : exclusiveOrExpression ('|' exclusiveOrExpression)* + // ; + @Override + public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { + if (ctx.exclusiveOrExpression().size() > 1) { + // exclusiveOrExpression ('|' exclusiveOrExpression)+ + // We have to abstract the 'or' expression if there is more than one operand + return ctx.accept(abstractingVisitor); + } else { + // exclusiveOrExpression + // There is exactly one child expression + return ctx.exclusiveOrExpression(0).accept(this); + } + } + + // logicalAndExpression + // : logicalOperand ( '&&' logicalOperand)* + // ; + @Override + public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { + return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalOperandContext); + } + + // logicalOrExpression + // : logicalAndExpression ( '||' logicalAndExpression)* + // ; + @Override + public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { + return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalAndExpressionContext); + } + + // logicalOperand + // : inclusiveOrExpression + // ; + @Override + public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { + return ctx.inclusiveOrExpression().accept(this); + } + + private StringBuilder visitLogicalExpression(ParserRuleContext expressionContext, Function instanceCheck) { + StringBuilder sb = new StringBuilder(); + for (ParseTree subtree : expressionContext.children) { + if (instanceCheck.apply(subtree)) { + // logicalAndExpression | InclusiveOrExpression + sb.append(subtree.accept(this)); + } else if (subtree instanceof TerminalNode terminal) { + // '&&' | '||' + sb.append(terminal.getText().trim()); + } else { + // loop does not work as expected + throw new IllegalStateException(); + } + } + return sb; + } +} \ No newline at end of file diff --git a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java index 64854a9e..ed8cb71b 100644 --- a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java +++ b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java @@ -5,18 +5,18 @@ import org.variantsync.diffdetective.datasets.Repository; import org.variantsync.diffdetective.diff.git.PatchDiff; import org.variantsync.diffdetective.diff.result.DiffParseException; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; -import org.variantsync.diffdetective.mining.VariationDiffMiner; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import org.variantsync.diffdetective.mining.RWCompositePatternNodeFormat; import org.variantsync.diffdetective.mining.RWCompositePatternTreeFormat; +import org.variantsync.diffdetective.mining.VariationDiffMiner; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.FileUtils; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; -import org.variantsync.diffdetective.variation.diff.render.VariationDiffRenderer; import org.variantsync.diffdetective.variation.diff.render.RenderOptions; +import org.variantsync.diffdetective.variation.diff.render.VariationDiffRenderer; import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.MappingsDiffNodeFormat; import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer; @@ -33,6 +33,7 @@ * directory. * This class is mostly used for debuggin purposes within DiffDetective and * contains mostly quick-and-dirty hardcoded configuration options. + * * @author Paul Bittner */ public class SimpleRenderer { @@ -41,9 +42,9 @@ public class SimpleRenderer { // .setNodeFormat(new ReleaseMiningDiffNodeFormat()), .setNodeFormat(new MappingsDiffNodeFormat<>()) .setDpi(RenderOptions.DEFAULT().dpi() / 2) - .setNodesize(3*RenderOptions.DEFAULT().nodesize()) - .setEdgesize(2*RenderOptions.DEFAULT().edgesize()) - .setArrowsize(2*RenderOptions.DEFAULT().arrowsize()) + .setNodesize(3 * RenderOptions.DEFAULT().nodesize()) + .setEdgesize(2 * RenderOptions.DEFAULT().edgesize()) + .setArrowsize(2 * RenderOptions.DEFAULT().arrowsize()) .setFontsize(8) // .addExtraArguments("--format", "patternsrelease") .setCleanUpTemporaryFiles(false) @@ -63,18 +64,18 @@ public class SimpleRenderer { private static final RenderOptions renderExampleOptions = new RenderOptions.Builder() .setTreeFormat(new RWCompositePatternTreeFormat()) - .setNodesize(3*RenderOptions.DEFAULT().nodesize()) - .setEdgesize(2*RenderOptions.DEFAULT().edgesize()) - .setArrowsize(2*RenderOptions.DEFAULT().arrowsize()) + .setNodesize(3 * RenderOptions.DEFAULT().nodesize()) + .setEdgesize(2 * RenderOptions.DEFAULT().edgesize()) + .setArrowsize(2 * RenderOptions.DEFAULT().arrowsize()) .setFontsize(8) .addExtraArguments("--startlineno", "4201") .build(); private static final RenderOptions renderCompositePatterns = new RenderOptions.Builder() - .setNodesize(3*RenderOptions.DEFAULT().nodesize()) - .setEdgesize(2*RenderOptions.DEFAULT().edgesize()) - .setArrowsize(2*RenderOptions.DEFAULT().arrowsize()) - .setFontsize(2*RenderOptions.DEFAULT().fontsize()) + .setNodesize(3 * RenderOptions.DEFAULT().nodesize()) + .setEdgesize(2 * RenderOptions.DEFAULT().edgesize()) + .setArrowsize(2 * RenderOptions.DEFAULT().arrowsize()) + .setFontsize(2 * RenderOptions.DEFAULT().fontsize()) .setTreeFormat(new RWCompositePatternTreeFormat()) .setNodeFormat(new RWCompositePatternNodeFormat()) .setCleanUpTemporaryFiles(true) @@ -89,8 +90,7 @@ public class SimpleRenderer { private final static Function GetRelativeOutputDir = // Path::getParent - p -> p.getParent().resolve("render") - ; + p -> p.getParent().resolve("render"); private static void render(final Path fileToRender) { if (FileUtils.isLineGraph(fileToRender)) { @@ -125,9 +125,10 @@ private static void render(final Path fileToRender) { * Expects one of the following argument configurations. * 1.) For rendering files: Exactly one argument pointing to a file or directory to render. * 2.) For rendering patches: Exactly three arguments. - * The first argument is the path to a local directory from which a patch should be analyzed. - * The second argument is a commit hash. - * The third argument is the file name of the patched file in the given commit. + * The first argument is the path to a local directory from which a patch should be analyzed. + * The second argument is a commit hash. + * The third argument is the file name of the patched file in the given commit. + * * @param args See above * @throws IOException when reading a file fails. */ diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java index 34f6da29..63ed6353 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java @@ -1,7 +1,7 @@ package org.variantsync.diffdetective.variation.diff.parse; import org.variantsync.diffdetective.feature.AnnotationParser; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; /** * Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s. diff --git a/src/test/java/CPPParserTest.java b/src/test/java/CPPParserTest.java index 77b6ed19..3756098b 100644 --- a/src/test/java/CPPParserTest.java +++ b/src/test/java/CPPParserTest.java @@ -1,135 +1,138 @@ -import org.variantsync.diffdetective.error.UnparseableFormulaException; -import org.variantsync.diffdetective.feature.CPPDiffLineFormulaExtractor; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor; + +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import java.util.List; - public class CPPParserTest { - private static record TestCase(String formula, String expected) {} - private static record ThrowingTestCase(String formula) {} + private static record TestCase(String formula, String expected) { + } + + private static record ThrowingTestCase(String formula) { + } private static List testCases() { return List.of( - new TestCase("#if A", "A"), - new TestCase("#ifdef A", "A"), - new TestCase("#ifndef A", "!(A)"), - new TestCase("#elif A", "A"), - - new TestCase("#if !A", "!A"), - new TestCase("#if A && B", "A&&B"), - new TestCase("#if A || B", "A||B"), - new TestCase("#if A && (B || C)", "A&&(B||C)"), - new TestCase("#if A && B || C", "A&&B||C"), - - new TestCase("#if 1 > -42", "1__GT____U_MINUS__42"), - new TestCase("#if 1 > +42", "1__GT____U_PLUS__42"), - new TestCase("#if 42 > A", "42__GT__A"), - new TestCase("#if 42 > ~A", "42__GT____U_TILDE__A"), - new TestCase("#if A + B > 42", "A__ADD__B__GT__42"), - new TestCase("#if A << B", "A__LSHIFT__B"), - new TestCase("#if A ? B : C", "A__THEN__B__COLON__C"), - new TestCase("#if A >= B && C > D", "A__GEQ__B&&C__GT__D"), - new TestCase("#if A * (B + C)", "A__MUL____LB__B__ADD__C__RB__"), - new TestCase("#if defined(A) && (B * 2) > C", "DEFINED___LB__A__RB__&&__LB__B__MUL__2__RB____GT__C"), - new TestCase("#if(STDC == 1) && (defined(LARGE) || defined(COMPACT))", "(STDC__EQ__1)&&(DEFINED___LB__LARGE__RB__||DEFINED___LB__COMPACT__RB__)"), - new TestCase("#if (('Z' - 'A') == 25)", "(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)"), - new TestCase("#if APR_CHARSET_EBCDIC && !(('Z' - 'A') == 25)", "APR_CHARSET_EBCDIC&&!(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)"), - new TestCase("# if ((GNUTLS_VERSION_MAJOR + (GNUTLS_VERSION_MINOR > 0 || GNUTLS_VERSION_PATCH >= 20)) > 3)", - "(__LB__GNUTLS_VERSION_MAJOR__ADD____LB__GNUTLS_VERSION_MINOR__GT__0__L_OR__GNUTLS_VERSION_PATCH__GEQ__20__RB____RB____GT__3)"), - - new TestCase("#if A && (B > C)", "A&&(B__GT__C)"), - new TestCase("#if (A && B) > C", "__LB__A__L_AND__B__RB____GT__C"), - new TestCase("#if C == (A || B)", "C__EQ____LB__A__L_OR__B__RB__"), - new TestCase("#if ((A && B) > C)", "(__LB__A__L_AND__B__RB____GT__C)"), - new TestCase("#if A && ((B + 1) > (C || D))", "A&&(__LB__B__ADD__1__RB____GT____LB__C__L_OR__D__RB__)"), - - new TestCase("#if __has_include", "HAS_INCLUDE_"), - new TestCase("#if defined __has_include", "DEFINED_HAS_INCLUDE_"), - new TestCase("#if __has_include()", "HAS_INCLUDE___LB____LT__nss3__DIV__nss__DOT__h__GT____RB__"), - new TestCase("#if __has_include()", "HAS_INCLUDE___LB____LT__nss__DOT__h__GT____RB__"), - new TestCase("#if __has_include(\"nss3/nss.h\")", "HAS_INCLUDE___LB____QUOTE__nss3__DIV__nss__DOT__h__QUOTE____RB__"), - new TestCase("#if __has_include(\"nss.h\")", "HAS_INCLUDE___LB____QUOTE__nss__DOT__h__QUOTE____RB__"), - - new TestCase("#if __has_attribute", "HAS_ATTRIBUTE_"), - new TestCase("#if defined __has_attribute", "DEFINED_HAS_ATTRIBUTE_"), - new TestCase("# if __has_attribute (nonnull)", "HAS_ATTRIBUTE___LB__nonnull__RB__"), - new TestCase("#if defined __has_attribute && __has_attribute (nonnull)", "DEFINED_HAS_ATTRIBUTE_&&HAS_ATTRIBUTE___LB__nonnull__RB__"), - - new TestCase("#if __has_cpp_attribute", "HAS_CPP_ATTRIBUTE_"), - new TestCase("#if defined __has_cpp_attribute", "DEFINED_HAS_CPP_ATTRIBUTE_"), - new TestCase("#if __has_cpp_attribute (nonnull)", "HAS_CPP_ATTRIBUTE___LB__nonnull__RB__"), - new TestCase("#if __has_cpp_attribute (nonnull) && A", "HAS_CPP_ATTRIBUTE___LB__nonnull__RB__&&A"), - - new TestCase("#if defined __has_c_attribute", "DEFINED_HAS_C_ATTRIBUTE_"), - new TestCase("#if __has_c_attribute", "HAS_C_ATTRIBUTE_"), - new TestCase("#if __has_c_attribute (nonnull)", "HAS_C_ATTRIBUTE___LB__nonnull__RB__"), - new TestCase("#if __has_c_attribute (nonnull) && A", "HAS_C_ATTRIBUTE___LB__nonnull__RB__&&A"), - - new TestCase("#if defined __has_builtin", "DEFINED_HAS_BUILTIN_"), - new TestCase("#if __has_builtin", "HAS_BUILTIN_"), - new TestCase("#if __has_builtin (__nonnull)", "HAS_BUILTIN___LB____nonnull__RB__"), - new TestCase("#if __has_builtin (nonnull) && A", "HAS_BUILTIN___LB__nonnull__RB__&&A"), - - new TestCase("#if A // Comment && B", "A"), - new TestCase("#if A /* Comment */ && B", "A&&B"), - new TestCase("#if A && B /* Multiline Comment", "A&&B"), - - new TestCase("#if A == B", "A__EQ__B"), - new TestCase("#if A == 1", "A__EQ__1"), - - new TestCase("#if defined A", "DEFINED_A"), - new TestCase("#if defined(A)", "DEFINED___LB__A__RB__"), - new TestCase("#if defined (A)", "DEFINED___LB__A__RB__"), - new TestCase("#if defined ( A )", "DEFINED___LB__A__RB__"), - new TestCase("#if (defined A)", "(DEFINED_A)"), - new TestCase("#if MACRO (A)", "MACRO___LB__A__RB__"), - new TestCase("#if MACRO (A, B)", "MACRO___LB__A__B__RB__"), - new TestCase("#if MACRO (A, B + C)", "MACRO___LB__A__B__ADD__C__RB__"), - new TestCase("#if MACRO (A, B) == 1", "MACRO___LB__A__B__RB____EQ__1"), - - new TestCase("#if ifndef", "ifndef"), - - new TestCase("#if __has_include_next()", "__HAS_INCLUDE_NEXT___LB____LT__some__SUB__header__DOT__h__GT____RB__"), - new TestCase("#if __is_target_arch(x86)", "__IS_TARGET_ARCH___LB__x86__RB__"), - new TestCase("#if A || (defined(NAME) && (NAME >= 199630))", "A||(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199630))"), - new TestCase("#if MACRO(part:part)", "MACRO___LB__part__COLON__part__RB__"), - new TestCase("#if MACRO(x=1)", "MACRO___LB__x__ASSIGN__1__RB__"), - new TestCase("#if A = 3", "A__ASSIGN__3"), - new TestCase("#if ' ' == 32", "__SQUOTE_____SQUOTE____EQ__32"), - new TestCase("#if (NAME<<1) > (1<= 199905) && (NAME < 1991011)) || (NAME >= 300000) || defined(NAME)", "(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199905)&&(NAME__LT__1991011))||(NAME__GEQ__300000)||DEFINED___LB__NAME__RB__"), - new TestCase("#if __has_warning(\"-Wa-warning\"_foo)", - "__HAS_WARNING___LB____QUOTE____SUB__Wa__SUB__warning__QUOTE_____foo__RB__") + new TestCase("#if A", "A"), + new TestCase("#ifdef A", "A"), + new TestCase("#ifndef A", "!(A)"), + new TestCase("#elif A", "A"), + + new TestCase("#if !A", "!A"), + new TestCase("#if A && B", "A&&B"), + new TestCase("#if A || B", "A||B"), + new TestCase("#if A && (B || C)", "A&&(B||C)"), + new TestCase("#if A && B || C", "A&&B||C"), + + new TestCase("#if 1 > -42", "1__GT____U_MINUS__42"), + new TestCase("#if 1 > +42", "1__GT____U_PLUS__42"), + new TestCase("#if 42 > A", "42__GT__A"), + new TestCase("#if 42 > ~A", "42__GT____U_TILDE__A"), + new TestCase("#if A + B > 42", "A__ADD__B__GT__42"), + new TestCase("#if A << B", "A__LSHIFT__B"), + new TestCase("#if A ? B : C", "A__THEN__B__COLON__C"), + new TestCase("#if A >= B && C > D", "A__GEQ__B&&C__GT__D"), + new TestCase("#if A * (B + C)", "A__MUL____LB__B__ADD__C__RB__"), + new TestCase("#if defined(A) && (B * 2) > C", "DEFINED___LB__A__RB__&&__LB__B__MUL__2__RB____GT__C"), + new TestCase("#if(STDC == 1) && (defined(LARGE) || defined(COMPACT))", "(STDC__EQ__1)&&(DEFINED___LB__LARGE__RB__||DEFINED___LB__COMPACT__RB__)"), + new TestCase("#if (('Z' - 'A') == 25)", "(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)"), + new TestCase("#if APR_CHARSET_EBCDIC && !(('Z' - 'A') == 25)", "APR_CHARSET_EBCDIC&&!(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)"), + new TestCase("# if ((GNUTLS_VERSION_MAJOR + (GNUTLS_VERSION_MINOR > 0 || GNUTLS_VERSION_PATCH >= 20)) > 3)", + "(__LB__GNUTLS_VERSION_MAJOR__ADD____LB__GNUTLS_VERSION_MINOR__GT__0__L_OR__GNUTLS_VERSION_PATCH__GEQ__20__RB____RB____GT__3)"), + + new TestCase("#if A && (B > C)", "A&&(B__GT__C)"), + new TestCase("#if (A && B) > C", "__LB__A__L_AND__B__RB____GT__C"), + new TestCase("#if C == (A || B)", "C__EQ____LB__A__L_OR__B__RB__"), + new TestCase("#if ((A && B) > C)", "(__LB__A__L_AND__B__RB____GT__C)"), + new TestCase("#if A && ((B + 1) > (C || D))", "A&&(__LB__B__ADD__1__RB____GT____LB__C__L_OR__D__RB__)"), + + new TestCase("#if __has_include", "HAS_INCLUDE_"), + new TestCase("#if defined __has_include", "DEFINED_HAS_INCLUDE_"), + new TestCase("#if __has_include()", "HAS_INCLUDE___LB____LT__nss3__DIV__nss__DOT__h__GT____RB__"), + new TestCase("#if __has_include()", "HAS_INCLUDE___LB____LT__nss__DOT__h__GT____RB__"), + new TestCase("#if __has_include(\"nss3/nss.h\")", "HAS_INCLUDE___LB____QUOTE__nss3__DIV__nss__DOT__h__QUOTE____RB__"), + new TestCase("#if __has_include(\"nss.h\")", "HAS_INCLUDE___LB____QUOTE__nss__DOT__h__QUOTE____RB__"), + + new TestCase("#if __has_attribute", "HAS_ATTRIBUTE_"), + new TestCase("#if defined __has_attribute", "DEFINED_HAS_ATTRIBUTE_"), + new TestCase("# if __has_attribute (nonnull)", "HAS_ATTRIBUTE___LB__nonnull__RB__"), + new TestCase("#if defined __has_attribute && __has_attribute (nonnull)", "DEFINED_HAS_ATTRIBUTE_&&HAS_ATTRIBUTE___LB__nonnull__RB__"), + + new TestCase("#if __has_cpp_attribute", "HAS_CPP_ATTRIBUTE_"), + new TestCase("#if defined __has_cpp_attribute", "DEFINED_HAS_CPP_ATTRIBUTE_"), + new TestCase("#if __has_cpp_attribute (nonnull)", "HAS_CPP_ATTRIBUTE___LB__nonnull__RB__"), + new TestCase("#if __has_cpp_attribute (nonnull) && A", "HAS_CPP_ATTRIBUTE___LB__nonnull__RB__&&A"), + + new TestCase("#if defined __has_c_attribute", "DEFINED_HAS_C_ATTRIBUTE_"), + new TestCase("#if __has_c_attribute", "HAS_C_ATTRIBUTE_"), + new TestCase("#if __has_c_attribute (nonnull)", "HAS_C_ATTRIBUTE___LB__nonnull__RB__"), + new TestCase("#if __has_c_attribute (nonnull) && A", "HAS_C_ATTRIBUTE___LB__nonnull__RB__&&A"), + + new TestCase("#if defined __has_builtin", "DEFINED_HAS_BUILTIN_"), + new TestCase("#if __has_builtin", "HAS_BUILTIN_"), + new TestCase("#if __has_builtin (__nonnull)", "HAS_BUILTIN___LB____nonnull__RB__"), + new TestCase("#if __has_builtin (nonnull) && A", "HAS_BUILTIN___LB__nonnull__RB__&&A"), + + new TestCase("#if A // Comment && B", "A"), + new TestCase("#if A /* Comment */ && B", "A&&B"), + new TestCase("#if A && B /* Multiline Comment", "A&&B"), + + new TestCase("#if A == B", "A__EQ__B"), + new TestCase("#if A == 1", "A__EQ__1"), + + new TestCase("#if defined A", "DEFINED_A"), + new TestCase("#if defined(A)", "DEFINED___LB__A__RB__"), + new TestCase("#if defined (A)", "DEFINED___LB__A__RB__"), + new TestCase("#if defined ( A )", "DEFINED___LB__A__RB__"), + new TestCase("#if (defined A)", "(DEFINED_A)"), + new TestCase("#if MACRO (A)", "MACRO___LB__A__RB__"), + new TestCase("#if MACRO (A, B)", "MACRO___LB__A__B__RB__"), + new TestCase("#if MACRO (A, B + C)", "MACRO___LB__A__B__ADD__C__RB__"), + new TestCase("#if MACRO (A, B) == 1", "MACRO___LB__A__B__RB____EQ__1"), + + new TestCase("#if ifndef", "ifndef"), + + new TestCase("#if __has_include_next()", "__HAS_INCLUDE_NEXT___LB____LT__some__SUB__header__DOT__h__GT____RB__"), + new TestCase("#if __is_target_arch(x86)", "__IS_TARGET_ARCH___LB__x86__RB__"), + new TestCase("#if A || (defined(NAME) && (NAME >= 199630))", "A||(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199630))"), + new TestCase("#if MACRO(part:part)", "MACRO___LB__part__COLON__part__RB__"), + new TestCase("#if MACRO(x=1)", "MACRO___LB__x__ASSIGN__1__RB__"), + new TestCase("#if A = 3", "A__ASSIGN__3"), + new TestCase("#if ' ' == 32", "__SQUOTE_____SQUOTE____EQ__32"), + new TestCase("#if (NAME<<1) > (1<= 199905) && (NAME < 1991011)) || (NAME >= 300000) || defined(NAME)", "(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199905)&&(NAME__LT__1991011))||(NAME__GEQ__300000)||DEFINED___LB__NAME__RB__"), + new TestCase("#if __has_warning(\"-Wa-warning\"_foo)", + "__HAS_WARNING___LB____QUOTE____SUB__Wa__SUB__warning__QUOTE_____foo__RB__") ); } private static List throwingTestCases() { return List.of( - // Invalid macro - new ThrowingTestCase(""), - new ThrowingTestCase("#"), - new ThrowingTestCase("ifdef A"), - new ThrowingTestCase("#error A"), - new ThrowingTestCase("#iferror A"), - - // Empty formula - new ThrowingTestCase("#ifdef"), - new ThrowingTestCase("#ifdef // Comment"), - new ThrowingTestCase("#ifdef /* Comment */") + // Invalid macro + new ThrowingTestCase(""), + new ThrowingTestCase("#"), + new ThrowingTestCase("ifdef A"), + new ThrowingTestCase("#error A"), + new ThrowingTestCase("#iferror A"), + + // Empty formula + new ThrowingTestCase("#ifdef"), + new ThrowingTestCase("#ifdef // Comment"), + new ThrowingTestCase("#ifdef /* Comment */") ); } private static List wontfixTestCases() { return List.of( - new TestCase("#if A == '1'", "A__EQ____TICK__1__TICK__"), - new TestCase("#if A && (B - (C || D))", "A&&(B__MINUS__LB__C__LOR__D__RB__)") + new TestCase("#if A == '1'", "A__EQ____TICK__1__TICK__"), + new TestCase("#if A && (B - (C || D))", "A&&(B__MINUS__LB__C__LOR__D__RB__)") ); } @@ -137,8 +140,8 @@ private static List wontfixTestCases() { @MethodSource("testCases") public void testCase(TestCase testCase) throws UnparseableFormulaException { assertEquals( - testCase.expected, - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + testCase.expected, + new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) ); } @@ -146,7 +149,7 @@ public void testCase(TestCase testCase) throws UnparseableFormulaException { @MethodSource("throwingTestCases") public void throwingTestCase(ThrowingTestCase testCase) { assertThrows(UnparseableFormulaException.class, () -> - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula) + new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula) ); } @@ -155,8 +158,8 @@ public void throwingTestCase(ThrowingTestCase testCase) { @MethodSource("wontfixTestCases") public void wontfixTestCase(TestCase testCase) throws UnparseableFormulaException { assertEquals( - testCase.expected, - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + testCase.expected, + new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) ); } diff --git a/src/test/java/TestMultiLineMacros.java b/src/test/java/TestMultiLineMacros.java index 2ebc48df..41004517 100644 --- a/src/test/java/TestMultiLineMacros.java +++ b/src/test/java/TestMultiLineMacros.java @@ -1,30 +1,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.tinylog.Logger; -import org.variantsync.diffdetective.variation.DiffLinesLabel; -import org.variantsync.diffdetective.variation.diff.VariationDiff; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; -import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; -import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; -import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExportOptions; -import org.variantsync.diffdetective.variation.diff.serialize.VariationDiffSerializeDebugData; -import org.variantsync.diffdetective.variation.diff.serialize.GraphFormat; -import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExport; -import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.DefaultEdgeLabelFormat; -import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.DebugDiffNodeFormat; -import org.variantsync.diffdetective.variation.diff.serialize.treeformat.CommitDiffVariationDiffLabelFormat; import org.variantsync.diffdetective.diff.result.DiffParseException; -import org.variantsync.diffdetective.util.IO; -import org.variantsync.diffdetective.util.StringUtils; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.io.BufferedReader; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.stream.Stream; public class TestMultiLineMacros { diff --git a/src/test/java/TreeDiffingTest.java b/src/test/java/TreeDiffingTest.java index b494dbf9..19f676da 100644 --- a/src/test/java/TreeDiffingTest.java +++ b/src/test/java/TreeDiffingTest.java @@ -1,15 +1,14 @@ import com.github.gumtreediff.matchers.Matcher; import com.github.gumtreediff.matchers.Matchers; - import org.apache.commons.io.IOUtils; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.diff.result.DiffParseException; -import org.variantsync.diffdetective.feature.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import org.variantsync.diffdetective.util.IO; import org.variantsync.diffdetective.variation.DiffLinesLabel; -import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff; import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; import org.variantsync.diffdetective.variation.diff.serialize.Format; @@ -21,19 +20,19 @@ import org.variantsync.diffdetective.variation.tree.VariationTree; import org.variantsync.diffdetective.variation.tree.source.LocalFileSource; -import static org.junit.jupiter.api.Assertions.fail; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.regex.Pattern; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.fail; import static org.variantsync.diffdetective.variation.diff.Time.BEFORE; public class TreeDiffingTest { private final static Path testDir = Constants.RESOURCE_DIR.resolve("tree-diffing"); private static Pattern expectedFileNameRegex = Pattern.compile("([^_]+)_([^_]+)_expected.lg"); + private static record TestCase(String basename, String matcherName, Matcher matcher) { public Path beforeEdit() { return testDir.resolve(String.format("%s.before", basename())); @@ -58,20 +57,20 @@ public Path visualisation() { private static Stream testCases() throws IOException { return Files - .list(testDir) - .mapMulti(((path, result) -> { - String filename = path.getFileName().toString(); - var filenameMatcher = expectedFileNameRegex.matcher(filename); - if (filenameMatcher.matches()) { - var treeMatcherName = filenameMatcher.group(2); - - result.accept(new TestCase( - filenameMatcher.group(1), - treeMatcherName, - Matchers.getInstance().getMatcher(treeMatcherName)) - ); - } - })); + .list(testDir) + .mapMulti(((path, result) -> { + String filename = path.getFileName().toString(); + var filenameMatcher = expectedFileNameRegex.matcher(filename); + if (filenameMatcher.matches()) { + var treeMatcherName = filenameMatcher.group(2); + + result.accept(new TestCase( + filenameMatcher.group(1), + treeMatcherName, + Matchers.getInstance().getMatcher(treeMatcherName)) + ); + } + })); } @ParameterizedTest @@ -84,7 +83,7 @@ public void testCase(TestCase testCase) throws IOException, DiffParseException { try (var output = IO.newBufferedOutputStream(testCase.actual())) { new LineGraphExporter<>(new Format<>(new FullNodeFormat(), new ChildOrderEdgeFormat<>())) - .exportVariationDiff(variationDiff, output); + .exportVariationDiff(variationDiff, output); } try ( @@ -97,16 +96,16 @@ public void testCase(TestCase testCase) throws IOException, DiffParseException { } else { // Keep output files if the test failed new TikzExporter<>(new Format<>(new FullNodeFormat(), new DefaultEdgeLabelFormat<>())) - .exportFullLatexExample(variationDiff, testCase.visualisation()); + .exportFullLatexExample(variationDiff, testCase.visualisation()); fail(String.format( - "The diff of %s and %s is not as expected. " + - "Expected the content of %s but got the content of %s. " + - "Note: A visualisation is available at %s", - testCase.beforeEdit(), - testCase.afterEdit(), - testCase.expected(), - testCase.actual(), - testCase.visualisation() + "The diff of %s and %s is not as expected. " + + "Expected the content of %s but got the content of %s. " + + "Note: A visualisation is available at %s", + testCase.beforeEdit(), + testCase.afterEdit(), + testCase.expected(), + testCase.actual(), + testCase.visualisation() )); } } @@ -115,14 +114,14 @@ public void testCase(TestCase testCase) throws IOException, DiffParseException { public VariationTree parseVariationTree(Path filename) throws IOException, DiffParseException { try (var file = Files.newBufferedReader(filename)) { return new VariationTree<>( - VariationDiffParser.createVariationTree( - file, - new VariationDiffParseOptions( - CPPAnnotationParser.Default, - false, - false) - ).getRoot().projection(BEFORE).toVariationTree(), - new LocalFileSource(filename) + VariationDiffParser.createVariationTree( + file, + new VariationDiffParseOptions( + CPPAnnotationParser.Default, + false, + false) + ).getRoot().projection(BEFORE).toVariationTree(), + new LocalFileSource(filename) ); } } From 0fadf873c792e4ab5b8c60bebfe5699b870b2c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Mon, 19 Feb 2024 19:51:22 +0100 Subject: [PATCH 05/41] fix: specify the correct matcher group id --- .../diffdetective/feature/cpp/CPPAnnotationParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java index ef010976..1bff406f 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java @@ -88,8 +88,9 @@ public Node parseCondition(String condition) { @Override public AnnotationType determineAnnotationType(String text) { var matcher = ANNOTATION.matcher(text); + int nameId = 1; if (matcher.find()) { - return AnnotationType.fromName(matcher.group(0)); + return AnnotationType.fromName(matcher.group(nameId)); } else { return AnnotationType.None; } From bc7747bd739ddfdc2c9904946c50826443a9fd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 09:46:51 +0100 Subject: [PATCH 06/41] todos: add todo about AnnotationType to NodeType conversion --- .../variantsync/diffdetective/variation/NodeType.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java index 78002756..80fd3304 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java @@ -17,12 +17,14 @@ public enum NodeType { ARTIFACT("artifact"); public final String name; + NodeType(String name) { this.name = name; } /** - * Returns true iff this node type represents a conditional feature annotation (i.e., if or elif). + * Returns true iff this node type represents a conditional feature annotation + * (i.e., if or elif). */ public boolean isConditionalAnnotation() { return this == IF || this == ELIF; @@ -37,8 +39,10 @@ public boolean isAnnotation() { /** * Creates a NodeType from its value names. + * * @see Enum#name() - * @param name a string that equals the name of one value of this enum (ignoring case) + * @param name a string that equals the name of one value of this enum (ignoring + * case) * @return The NodeType that has the given name */ public static NodeType fromName(final String name) { @@ -51,6 +55,8 @@ public static NodeType fromName(final String name) { throw new IllegalArgumentException("Given string \"" + name + "\" is not the name of a NodeType."); } + // TODO: fromAnnotationType constructor with switch case + /** * Returns the number of bits required for storing {@link ordinal}. */ From e853ff7472dc4ebf9541994dd84dd38ccf0af27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 10:38:27 +0100 Subject: [PATCH 07/41] docs: short addendum to docstrings of AnnotationParser --- .../variantsync/diffdetective/feature/AnnotationParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java index 0950dd4f..fdd6163b 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java @@ -5,7 +5,7 @@ public interface AnnotationParser { /** - * Determine the annotation type for the given piece of text. + * Determine the annotation type for the given piece of text (typically a line of source code). * * @param text The text of which the type is determined. * @return The annotation type of the piece of text. @@ -13,7 +13,7 @@ public interface AnnotationParser { AnnotationType determineAnnotationType(String text); /** - * Parse the condition of the given text containing an annotation. + * Parse the condition of the given text containing an annotation (typically a line of source code). * * @param text The text containing a conditional annotation * @return The formula of the condition in the given annotation. From 90d19d05d7799a67e604ccf8fcbeb498bd83f583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 10:55:33 +0100 Subject: [PATCH 08/41] refactor: move initialization of NodeType based on AnnotationType to NodeType.java --- .../diffdetective/variation/NodeType.java | 40 ++++++++++++++++--- .../diff/parse/VariationDiffParser.java | 7 +--- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java index 80fd3304..4a7a8679 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java @@ -1,7 +1,8 @@ package org.variantsync.diffdetective.variation; -import org.variantsync.diffdetective.variation.diff.DiffNode; // For Javadoc -import org.variantsync.diffdetective.variation.tree.VariationNode; // For Javadoc +import org.variantsync.diffdetective.feature.AnnotationType; +import org.variantsync.diffdetective.variation.diff.DiffNode; +import org.variantsync.diffdetective.variation.tree.VariationNode; /** * The type of nodes of a {@link DiffNode} and a {@link VariationNode}. @@ -39,11 +40,11 @@ public boolean isAnnotation() { /** * Creates a NodeType from its value names. - * - * @see Enum#name() + * * @param name a string that equals the name of one value of this enum (ignoring * case) * @return The NodeType that has the given name + * @see Enum#name() */ public static NodeType fromName(final String name) { for (NodeType candidate : values()) { @@ -55,10 +56,37 @@ public static NodeType fromName(final String name) { throw new IllegalArgumentException("Given string \"" + name + "\" is not the name of a NodeType."); } - // TODO: fromAnnotationType constructor with switch case + /** + * Creates a NodeType from an AnnotationType. + *

+ * All AnnotationType variants except for 'Endif' are supported. + * There is no valid representation for 'Endif' annotations. Thus, the method throws an IllegalArgumentException + * if it is given an 'Endif'. + *

+ * + * @param annotationType a variant of AnnotationType + * @return The NodeType that fits the given AnnotationType + */ + public static NodeType fromAnnotationType(final AnnotationType annotationType) { + switch (annotationType) { + case If -> { + return NodeType.IF; + } + case Elif -> { + return NodeType.ELIF; + } + case Else -> { + return NodeType.ELSE; + } + case None -> { + return NodeType.ARTIFACT; + } + default -> throw new IllegalArgumentException(annotationType + "has no NodeType counterpart"); + } + } /** - * Returns the number of bits required for storing {@link ordinal}. + * Returns the number of bits required for storing. */ public static int getRequiredBitCount() { return 3; diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index 8d26896b..b6a26393 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -325,12 +325,7 @@ private void parseLine( lastArtifact.setToLine(toLine); } else { try { - NodeType nodeType; - if (annotationType == AnnotationType.None) { - nodeType = NodeType.ARTIFACT; - } else { - nodeType = NodeType.fromName(annotationType.name); - } + NodeType nodeType = NodeType.fromAnnotationType(annotationType); DiffNode newNode = new DiffNode( diffType, From 92d66c1e61202ef003f80ee7479257a66e119472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 12:47:48 +0100 Subject: [PATCH 09/41] tests: add first test definitions for JavaPP parsing --- src/test/java/JPPParserTest.java | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/test/java/JPPParserTest.java diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java new file mode 100644 index 00000000..58702002 --- /dev/null +++ b/src/test/java/JPPParserTest.java @@ -0,0 +1,86 @@ +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.variantsync.diffdetective.error.UnparseableFormulaException; + +import java.util.List; + +// Test cases for a parser of https://www.slashdev.ca/javapp/ +public class JPPParserTest { + private record TestCase(String formula, String expected) { + } + + private record ThrowingTestCase(String formula) { + } + + private static List testCases() { + return List.of( + /// #if expression + // expression := | [!]defined(name) + // expression := operand == operand + new JPPParserTest.TestCase("//#if 1 == -42", "1__EQ____U_MINUS__42"), + // expression := operand != operand + new JPPParserTest.TestCase("// #if 1 != 0", "1__NEQ__0"), + // expression := operand <= operand + new JPPParserTest.TestCase("//#if -1 <= 0", "__U_MINUS__1__LEQ__0"), + // expression := operand < operand + new JPPParserTest.TestCase("//#if \"str\" < 0", "__QUOTE__str__QUOTE____LT__0"), + // expression := operand >= operand + new JPPParserTest.TestCase("// #if \"str\" >= \"str\"", "__QUOTE__str__QUOTE____GEQ____QUOTE__str__QUOTE__"), + // expression := operand > operand + new JPPParserTest.TestCase("// #if 1.2 > 0", "1__DOT__2__GT__0"), + // expression := defined(name) + new JPPParserTest.TestCase("//#if defined(property)", "DEFINED_property"), + // expression := !defined(name) + new JPPParserTest.TestCase("//#if !defined(property)", "__U_NOT__DEFINED_property"), + // operand := ${property} + new JPPParserTest.TestCase("//#if ${os_version} == 4.1", "os_version__EQ__4__DOT__1"), + + /// #if expression and expression + new JPPParserTest.TestCase("//#if 1 > 2 and defined( FEAT_A )", "1__GT__2&&DEFINED_FEAT_A"), + + /// #if expression or expression + new JPPParserTest.TestCase("//#if !defined(left) or defined(right)", "__U_NOT__DEFINED_left||DEFINED_right"), + + /// #if expression and expression or expression + new JPPParserTest.TestCase("//#if ${os_version} == 4.1 and 1 > -42 or defined(ALL)", "os_version__EQ__4__DOT__1&&1__GT____U_MINUS__42||DEFINED_ALL") + ); + } + + private static List throwingTestCases() { + return List.of( + // Invalid macro + new JPPParserTest.ThrowingTestCase(""), + new JPPParserTest.ThrowingTestCase("#"), + new JPPParserTest.ThrowingTestCase("ifdef A"), + new JPPParserTest.ThrowingTestCase("#error A"), + new JPPParserTest.ThrowingTestCase("#iferror A"), + + // Empty formula + new JPPParserTest.ThrowingTestCase("//#if"), + new JPPParserTest.ThrowingTestCase("#if defined()"), + new JPPParserTest.ThrowingTestCase("#if ${} > 0"), + + // incomplete expressions + new JPPParserTest.ThrowingTestCase("#if 1 >"), + new JPPParserTest.ThrowingTestCase("#if == 2"), + new JPPParserTest.ThrowingTestCase("#if ${version} > ") + ); + } + + @ParameterizedTest + @MethodSource("testCases") + public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException { +// assertEquals( +// testCase.expected, +// // TODO: +// ); + } + + @ParameterizedTest + @MethodSource("throwingTestCases") + public void throwingTestCase(JPPParserTest.ThrowingTestCase testCase) { + // assertThrows(UnparseableFormulaException.class, () -> //TODO + // ); + } + +} From 2d98af2c46f0d581907f8794a13ad9937b18a794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 13:22:54 +0100 Subject: [PATCH 10/41] feat: add initial ANTLR grammar for JPP --- .../feature/antlr/JPPExpression.g4 | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 diff --git a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 new file mode 100644 index 00000000..2c588967 --- /dev/null +++ b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 @@ -0,0 +1,320 @@ +grammar JPPExpression; +// A grammar for the JavaPreprocessor +// https://www.slashdev.ca/javapp/ + +jppExpression + : logicalOrExpression + ; + +logicalOrExpression + : logicalAndExpression (OR logicalAndExpression)* + ; + +logicalAndExpression + : expression (AND expression)* + ; + +expression + : definedExpression + | undefinedExpression + | comparisonExpression + ; + +comparisonExpression + : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? + ; + +operand + : propertyExpression + | Constant + | StringLiteral+ + | unaryOperator Constant + ; + +definedExpression + : 'defined' '(' Identifier ')' + ; + +undefinedExpression + : NOT 'defined' '(' Identifier ')' + ; + +propertyExpression + : '${' Identifier '}' + ; + +unaryOperator + : U_PLUS + | U_MINUS + ; + +U_PLUS : '+'; +U_MINUS : '-'; + +AND : 'and'; +OR : 'or'; +NOT : '!'; + +LT : '<'; +LEQ : '<='; +GT : '>'; +GEQ : '>='; + +EQ : '=='; +NEQ : '!='; + +DOT : '.'; + +Identifier + : ('\\')? ( IdentifierNondigit + | Digit + )+ + ; + +fragment +IdentifierNondigit + : Nondigit + | UniversalCharacterName + //| // other implementation-defined characters...y + ; + +fragment +Nondigit + : [a-zA-Z_] + ; + +fragment +Digit + : [0-9] + ; + +fragment +UniversalCharacterName + : '\\u' HexQuad + | '\\U' HexQuad HexQuad + ; + +fragment +HexQuad + : HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit + ; + +Constant + : IntegerConstant + | FloatingConstant + ; + +fragment +IntegerConstant + : DecimalConstant IntegerSuffix? + | OctalConstant IntegerSuffix? + | HexadecimalConstant IntegerSuffix? + | BinaryConstant + ; + +fragment +BinaryConstant + : '0' [bB] [0-1]+ + ; + +fragment +DecimalConstant + : NonzeroDigit Digit* + ; + +fragment +OctalConstant + : '0' OctalDigit* + ; + +fragment +HexadecimalConstant + : HexadecimalPrefix HexadecimalDigit+ + ; + +fragment +HexadecimalPrefix + : '0' [xX] + ; + +fragment +NonzeroDigit + : [1-9] + ; + +fragment +OctalDigit + : [0-7] + ; + +fragment +HexadecimalDigit + : [0-9a-fA-F] + ; + +fragment +IntegerSuffix + : UnsignedSuffix LongSuffix? + | UnsignedSuffix LongLongSuffix + | LongSuffix UnsignedSuffix? + | LongLongSuffix UnsignedSuffix? + ; + +fragment +UnsignedSuffix + : [uU] + ; + +fragment +LongSuffix + : [lL] + ; + +fragment +LongLongSuffix + : 'll' | 'LL' + ; + +fragment +FloatingConstant + : DecimalFloatingConstant + | HexadecimalFloatingConstant + ; + +fragment +DecimalFloatingConstant + : FractionalConstant ExponentPart? FloatingSuffix? + | DigitSequence ExponentPart FloatingSuffix? + ; + +fragment +HexadecimalFloatingConstant + : HexadecimalPrefix (HexadecimalFractionalConstant | HexadecimalDigitSequence) BinaryExponentPart FloatingSuffix? + ; + +fragment +FractionalConstant + : DigitSequence? '.' DigitSequence + | DigitSequence '.' + ; + +fragment +ExponentPart + : [eE] Sign? DigitSequence + ; + +fragment +Sign + : [+-] + ; + +DigitSequence + : Digit+ + ; + +fragment +HexadecimalFractionalConstant + : HexadecimalDigitSequence? '.' HexadecimalDigitSequence + | HexadecimalDigitSequence '.' + ; + +fragment +BinaryExponentPart + : [pP] Sign? DigitSequence + ; + +fragment +HexadecimalDigitSequence + : HexadecimalDigit+ + ; + +fragment +FloatingSuffix + : [flFL] + ; + + +fragment +CCharSequence + : CChar+ + ; + +fragment +CChar + : ~['\\\r\n] + | EscapeSequence + ; + +fragment +EscapeSequence + : SimpleEscapeSequence + | OctalEscapeSequence + | HexadecimalEscapeSequence + | UniversalCharacterName + ; + +fragment +SimpleEscapeSequence + : '\\' ['"?abfnrtv\\] + ; + +fragment +OctalEscapeSequence + : '\\' OctalDigit OctalDigit? OctalDigit? + ; + +fragment +HexadecimalEscapeSequence + : '\\x' HexadecimalDigit+ + ; + +StringLiteral + : EncodingPrefix? '"' SCharSequence? '"' + ; + +fragment +EncodingPrefix + : 'u8' + | 'u' + | 'U' + | 'L' + ; + +fragment +SCharSequence + : SChar+ + ; + +fragment +SChar + : ~["\\\r\n] + | EscapeSequence + | '\\\n' // Added line + | '\\\r\n' // Added line + ; + +Whitespace + : [ \t]+ -> channel(HIDDEN) + ; + +Newline + : ( '\r' '\n'? + | '\n' + ) + -> channel(HIDDEN) + ; + +BlockComment + : '/*' .*? '*/' + -> channel(HIDDEN) + ; + +OpenBlockComment + : '/*' ~[*/]* + -> channel(HIDDEN) + ; + +LineComment + : '//' ~[\r\n]* + -> channel(HIDDEN) + ; \ No newline at end of file From def9aa786a725e3a187b3b34ec92e1c16a8d02f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 16:33:21 +0100 Subject: [PATCH 11/41] refactor: change rule names of JPP grammar --- .../diffdetective/feature/antlr/JPPExpression.g4 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 index 2c588967..3d7e42a2 100644 --- a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 +++ b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 @@ -2,7 +2,7 @@ grammar JPPExpression; // A grammar for the JavaPreprocessor // https://www.slashdev.ca/javapp/ -jppExpression +expression : logicalOrExpression ; @@ -11,10 +11,10 @@ logicalOrExpression ; logicalAndExpression - : expression (AND expression)* + : primaryExpression (AND primaryExpression)* ; -expression +primaryExpression : definedExpression | undefinedExpression | comparisonExpression From f200c7a5a09345354a01259a842d4c67721f7ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 17:19:55 +0100 Subject: [PATCH 12/41] refactor: pull up interface of CPP diff line extraction and abstraction in preparation for JPP parsing --- .../MarlinCPPDiffLineFormulaExtractor.java | 2 +- .../feature/AbstractingFormulaExtractor.java | 89 ++++++++++++++++++ .../feature/DiffLineFormulaExtractor.java | 35 +++++++ .../feature/ParseErrorListener.java | 51 ++++++++++ .../cpp/CPPDiffLineFormulaExtractor.java | 93 ++++--------------- 5 files changed, 195 insertions(+), 75 deletions(-) create mode 100644 src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java index 8ff1d535..fa1fc3cc 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java @@ -16,7 +16,7 @@ public class MarlinCPPDiffLineFormulaExtractor extends CPPDiffLineFormulaExtract private static final Pattern DISABLED_PATTERN = Pattern.compile("DISABLED\\s*\\(([^)]*)\\)"); @Override - protected String resolveFeatureMacroFunctions(String formula) { + public String resolveFeatureMacroFunctions(String formula) { return replaceAll(ENABLED_PATTERN, "$1", replaceAll(DISABLED_PATTERN, "!($1)", diff --git a/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java new file mode 100644 index 00000000..ffe4528c --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java @@ -0,0 +1,89 @@ +package org.variantsync.diffdetective.feature; + +import org.tinylog.Logger; +import org.variantsync.diffdetective.error.UncheckedUnParseableFormulaException; +import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.cpp.AbstractingCExpressionVisitor; +import org.variantsync.diffdetective.feature.cpp.ControllingCExpressionVisitor; + +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AbstractingFormulaExtractor is an abstract class that extracts a formula from text containing a conditional annotation, + * and then abstracts the formula using the custom {@link #abstractFormula(String)} implementation of its subclass. + * The extraction of a formula is controlled by a {@link Pattern} with which an AbstractingFormulaExtractor is initialized. + * The given text might also be a line in a diff (i.e., preceeded by a '-' or '+'). + * + *

+ * For example, given the annotation "#if defined(A) || B()", the extractor should extract the formula + * "defined(A) || B". It would then hand this formula to the {@link #abstractFormula(String)} method for abstraction + * (e.g., to substitute the 'defined(A)' macro call with 'DEFINED_A'). + *

+ * @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß + */ +public abstract class AbstractingFormulaExtractor implements DiffLineFormulaExtractor { + private final Pattern annotationPattern; + + /** + * Initialize a new AbstractingFormulaExtractor object that uses the given Pattern to identify formulas in annotations. + * See {@link org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor} for an example of how such a pattern + * could look like. + * @param annotationPattern The pattern used for formula extraction + */ + public AbstractingFormulaExtractor(Pattern annotationPattern) { + this.annotationPattern = annotationPattern; + } + + /** + * Extracts the feature formula as a string from a piece of text (possibly within a diff) and abstracts it. + * + * @param text The text of which to extract the formula + * @return The extracted and abstracted formula + */ + @Override + public String extractFormula(final String text) throws UnparseableFormulaException { + final Matcher matcher = annotationPattern.matcher(text); + final Supplier couldNotExtractFormula = () -> + new UnparseableFormulaException("Could not extract formula from line \"" + text + "\"."); + + // Retrieve the formula from the macro line + String fm; + if (matcher.find()) { + if (matcher.group(3) != null) { + fm = matcher.group(3); + } else { + fm = matcher.group(4); + } + } else { + throw couldNotExtractFormula.get(); + } + + // abstract complex formulas (e.g., if they contain arithmetics or macro calls) + try { + fm = abstractFormula(fm); + } catch (UncheckedUnParseableFormulaException e) { + throw e.inner(); + } catch (Exception e) { + Logger.warn(e); + throw new UnparseableFormulaException(e); + } + + if (fm.isEmpty()) { + throw couldNotExtractFormula.get(); + } + + return fm; + } + + /** + * Abstract the given formula (e.g., by substituting parts of the formula with predefined String literals). + * See {@link org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor} for an example of how this could + * be done. + * + * @param formula that is to be abstracted + * @return the abstracted formula + */ + protected abstract String abstractFormula(String formula); +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java new file mode 100644 index 00000000..7000c7a3 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java @@ -0,0 +1,35 @@ +package org.variantsync.diffdetective.feature; + +import org.variantsync.diffdetective.error.UnparseableFormulaException; + +/** + * Extracts the expression from a C preprocessor statement. + * For example, given the annotation "#if defined(A) || B()", the extractor would extract + * "A || B". The extractor detects if, ifdef, ifndef and elif annotations. + * (Other annotations do not have expressions.) + * The given pre-processor statement might also a line in a diff (i.e., preceeded by a - or +). + * + * @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß + */ +public interface DiffLineFormulaExtractor { + /** + * Extracts the feature formula as a string from a macro line (possibly within a diff). + * + * @param line The line of which to get the feature mapping + * @return The feature mapping as a String of the given line + */ + String extractFormula(final String line) throws UnparseableFormulaException; + + /** + * Resolves any macros in the given formula that are relevant for feature annotations. + * For example, in {@link org.variantsync.diffdetective.datasets.predefined.MarlinCPPDiffLineFormulaExtractor Marlin}, + * feature annotations are given by the custom ENABLED and DISABLED macros, + * which have to be unwrapped. + * + * @param formula The formula whose feature macros to resolve. + * @return The parseable formula as string. The default implementation returns the input string. + */ + default String resolveFeatureMacroFunctions(String formula) { + return formula; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java b/src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java new file mode 100644 index 00000000..2a50916f --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java @@ -0,0 +1,51 @@ +package org.variantsync.diffdetective.feature; + +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.dfa.DFA; +import org.tinylog.Logger; +import org.variantsync.diffdetective.error.UncheckedUnParseableFormulaException; + +import java.util.BitSet; + +/** + * A ParseErrorListener listens to syntactical errors discovered by an ANTLR parser while parsing a text. Encountered + * errors are logged as warnings so that they can later be analyzed. + *

+ * Logged warning might indicate that the ANTLR grammar used for parsing is imprecise or incomplete. However, it might + * also simply be the case that the input text is indeed syntactically invalid. + *

+ * @author Alexander Schultheiß + */ +public class ParseErrorListener implements ANTLRErrorListener { + private final String formula; + + public ParseErrorListener(String formula) { + this.formula = formula; + } + + @Override + public void syntaxError(Recognizer recognizer, Object o, int i, int i1, String s, RecognitionException e) { + Logger.warn("syntax error: {} ; {}", s, e); + Logger.warn("formula: {}", formula); + throw new UncheckedUnParseableFormulaException(s, e); + } + + @Override + public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) { + // Do nothing + } + + @Override + public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) { + // Do nothing + } + + @Override + public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) { + // Do nothing + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java index 9a499af8..9d77c5c2 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java @@ -1,17 +1,13 @@ package org.variantsync.diffdetective.feature.cpp; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATNConfigSet; -import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTree; -import org.tinylog.Logger; -import org.variantsync.diffdetective.error.UncheckedUnParseableFormulaException; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.AbstractingFormulaExtractor; +import org.variantsync.diffdetective.feature.ParseErrorListener; import org.variantsync.diffdetective.feature.antlr.CExpressionLexer; import org.variantsync.diffdetective.feature.antlr.CExpressionParser; -import java.util.BitSet; -import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -22,24 +18,15 @@ * (Other annotations do not have expressions.) * The given pre-processor statement might also a line in a diff (i.e., preceeded by a - or +). * - * @author Paul Bittner, Sören Viegener, Benjamin Moosherr + * @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß */ -public class CPPDiffLineFormulaExtractor { +public class CPPDiffLineFormulaExtractor extends AbstractingFormulaExtractor { // ^[+-]?\s*#\s*(if|ifdef|ifndef|elif)(\s+(.*)|\((.*)\))$ private static final String CPP_ANNOTATION_REGEX = "^[+-]?\\s*#\\s*(if|ifdef|ifndef|elif)(\\s+(.*)|(\\(.*\\)))$"; - private static final Pattern CPP_ANNOTATION_REGEX_PATTERN = Pattern.compile(CPP_ANNOTATION_REGEX); + private static final Pattern CPP_ANNOTATION_PATTERN = Pattern.compile(CPP_ANNOTATION_REGEX); - /** - * Resolves any macros in the given formula that are relevant for feature annotations. - * For example, in {@link org.variantsync.diffdetective.datasets.predefined.MarlinCPPDiffLineFormulaExtractor Marlin}, - * feature annotations are given by the custom ENABLED and DISABLED macros, - * which have to be unwrapped. - * - * @param formula The formula whose feature macros to resolve. - * @return The parseable formula as string. The default implementation returns the input string. - */ - protected String resolveFeatureMacroFunctions(String formula) { - return formula; + public CPPDiffLineFormulaExtractor() { + super(CPP_ANNOTATION_PATTERN); } /** @@ -48,39 +35,14 @@ protected String resolveFeatureMacroFunctions(String formula) { * @param line The line of which to get the feature mapping * @return The feature mapping as a String of the given line */ + @Override public String extractFormula(final String line) throws UnparseableFormulaException { - final Matcher matcher = CPP_ANNOTATION_REGEX_PATTERN.matcher(line); - final Supplier couldNotExtractFormula = () -> - new UnparseableFormulaException("Could not extract formula from line \"" + line + "\"."); - - // Retrieve the formula from the macro line - String fm; - if (matcher.find()) { - if (matcher.group(3) != null) { - fm = matcher.group(3); - } else { - fm = matcher.group(4); - } - } else { - throw couldNotExtractFormula.get(); - } - - // abstract complex formulas (e.g., if they contain arithmetics or macro calls) - try { - fm = abstractFormula(fm); - } catch (UncheckedUnParseableFormulaException e) { - throw e.inner(); - } catch (Exception e) { - Logger.warn(e); - throw new UnparseableFormulaException(e); - } - - if (fm.isEmpty()) { - throw couldNotExtractFormula.get(); - } + // Delegate the formula extraction to AbstractingFormulaExtractor + String fm = super.extractFormula(line); // negate for ifndef - if ("ifndef".equals(matcher.group(1))) { + final Matcher matcher = CPP_ANNOTATION_PATTERN.matcher(line); + if (matcher.find() && "ifndef".equals(matcher.group(1))) { fm = "!(" + fm + ")"; } @@ -99,31 +61,14 @@ public String extractFormula(final String line) throws UnparseableFormulaExcepti * @param formula that is to be abstracted * @return the abstracted formula */ - private String abstractFormula(String formula) { + @Override + protected String abstractFormula(String formula) { CExpressionLexer lexer = new CExpressionLexer(CharStreams.fromString(formula)); CommonTokenStream tokens = new CommonTokenStream(lexer); - CExpressionParser parser = new CExpressionParser(tokens); - parser.addErrorListener(new ANTLRErrorListener() { - @Override - public void syntaxError(Recognizer recognizer, Object o, int i, int i1, String s, RecognitionException e) { - Logger.warn("syntax error: {} ; {}", s, e); - Logger.warn("formula: {}", formula); - throw new UncheckedUnParseableFormulaException(s, e); - } - - @Override - public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) { - } - @Override - public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) { - } + CExpressionParser parser = new CExpressionParser(tokens); + parser.addErrorListener(new ParseErrorListener(formula)); - @Override - public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) { - } - }); - ParseTree tree = parser.expression(); - return tree.accept(new ControllingCExpressionVisitor()).toString(); + return parser.expression().accept(new ControllingCExpressionVisitor()).toString(); } } From 54079467aa4687e157d71941a406d0ddf2591d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 18:42:49 +0100 Subject: [PATCH 13/41] refactor: reduce overhead of AnnotationParser implementations --- .../datasets/PatchDiffParseOptions.java | 4 +- .../datasets/predefined/Marlin.java | 7 +- .../feature/PreprocessorAnnotationParser.java | 99 +++++++++++++++++++ .../feature/PropositionalFormulaParser.java | 6 ++ .../feature/cpp/CPPAnnotationParser.java | 99 ------------------- .../jpp/JPPDiffLineFormulaExtractor.java | 50 ++++++++++ .../internal/SimpleRenderer.java | 4 +- .../diff/parse/VariationDiffParseOptions.java | 8 +- src/test/java/JPPParserTest.java | 17 ++-- src/test/java/TreeDiffingTest.java | 4 +- 10 files changed, 180 insertions(+), 118 deletions(-) create mode 100644 src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java delete mode 100644 src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java diff --git a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java index 0857343d..7d9be594 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java @@ -1,6 +1,6 @@ package org.variantsync.diffdetective.datasets; -import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; /** @@ -27,7 +27,7 @@ public enum DiffStoragePolicy { /** * Creates PatchDiffParseOptions with the given annotation parser. */ - public PatchDiffParseOptions withAnnotationParser(CPPAnnotationParser annotationParser) { + public PatchDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) { return new PatchDiffParseOptions( this.diffStoragePolicy(), this.variationDiffParseOptions().withAnnotationParser(annotationParser) diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java index 6d8cc1ec..57f28cd9 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java @@ -2,8 +2,8 @@ import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; import org.variantsync.diffdetective.datasets.Repository; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; import org.variantsync.diffdetective.feature.PropositionalFormulaParser; -import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import java.nio.file.Path; @@ -13,8 +13,9 @@ * @author Kevin Jedelhauser, Paul Maximilian Bittner */ public class Marlin { - public static final CPPAnnotationParser ANNOTATION_PARSER = - new CPPAnnotationParser( + public static final PreprocessorAnnotationParser ANNOTATION_PARSER = + new PreprocessorAnnotationParser( + PreprocessorAnnotationParser.CPP_PATTERN, PropositionalFormulaParser.Default, new MarlinCPPDiffLineFormulaExtractor() ); diff --git a/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java new file mode 100644 index 00000000..e122914b --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java @@ -0,0 +1,99 @@ +package org.variantsync.diffdetective.feature; + +import org.prop4j.Node; +import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor; +import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor; + +import java.util.regex.Pattern; + +/** + * A parser of preprocessor-like annotations. + * + * @author Paul Bittner, Alexander Schultheiß + */ +public class PreprocessorAnnotationParser implements AnnotationParser { + /** + * Matches the beginning or end of CPP conditional macros. + * It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is + * matched and only {@code "if"} is captured. + *

+ * Note that this pattern doesn't handle comments between {@code #} and the macro name. + */ + public final static Pattern CPP_PATTERN = + Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)"); + + /** + * Matches the beginning or end of JPP conditional macros. + * It doesn't match the whole macro name, for example for {@code //#if defined(x)} only {@code "//#if"} is + * matched and only {@code "if"} is captured. + *

+ */ + public final static Pattern JPP_PATTERN = + Pattern.compile("^[+-]?\\s*//\\s*#\\s*(if|elif|else|endif)"); + + /** + * Default parser for C preprocessor annotations. + * Created by invoking {@link #PreprocessorAnnotationParser(Pattern, PropositionalFormulaParser, DiffLineFormulaExtractor)}. + */ + public static final PreprocessorAnnotationParser CPPAnnotationParser = + new PreprocessorAnnotationParser(CPP_PATTERN, PropositionalFormulaParser.Default, new CPPDiffLineFormulaExtractor()); + + /** + * Default parser for JavaPP (Java PreProcessor) annotations. + * Created by invoking {@link #PreprocessorAnnotationParser(Pattern, PropositionalFormulaParser, DiffLineFormulaExtractor)}. + */ + public static final PreprocessorAnnotationParser JPPAnnotationParser = + new PreprocessorAnnotationParser(JPP_PATTERN, PropositionalFormulaParser.Default, new JPPDiffLineFormulaExtractor()); + + // Pattern that is used to identify the AnnotationType of a given annotation. + private final Pattern annotationPattern; + private final PropositionalFormulaParser formulaParser; + private final DiffLineFormulaExtractor extractor; + + /** + * Invokes {@link #PreprocessorAnnotationParser(Pattern, PropositionalFormulaParser, DiffLineFormulaExtractor)} with + * the {@link PropositionalFormulaParser#Default default formula parser} and a new {@link DiffLineFormulaExtractor}. + * + * @param annotationPattern Pattern that is used to identify the AnnotationType of a given annotation; {@link #CPP_PATTERN} provides an example + */ + public PreprocessorAnnotationParser(final Pattern annotationPattern, final DiffLineFormulaExtractor formulaExtractor) { + this(annotationPattern, PropositionalFormulaParser.Default, formulaExtractor); + } + + /** + * Creates a new preprocessor annotation parser. + * + * @param annotationPattern Pattern that is used to identify the AnnotationType of a given annotation; {@link #CPP_PATTERN} provides an example + * @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula f in #if f). + * @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. + */ + public PreprocessorAnnotationParser(final Pattern annotationPattern, final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) { + this.annotationPattern = annotationPattern; + this.formulaParser = formulaParser; + this.extractor = formulaExtractor; + } + + /** + * Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF). + * + * @param line The line of code of a preprocessor annotation. + * @return The formula of the macro in the given line. + * If no such formula could be parsed, returns a Literal with the line's condition as name. + * @throws UnparseableFormulaException when {@link DiffLineFormulaExtractor#extractFormula(String)} throws. + */ + public Node parseAnnotation(String line) throws UnparseableFormulaException { + return this.formulaParser.parse(extractor.extractFormula(line)); + } + + @Override + public AnnotationType determineAnnotationType(String text) { + var matcher = annotationPattern.matcher(text); + int nameId = 1; + if (matcher.find()) { + return AnnotationType.fromName(matcher.group(nameId)); + } else { + return AnnotationType.None; + } + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java b/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java index f24caad7..b470af7d 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java @@ -1,5 +1,6 @@ package org.variantsync.diffdetective.feature; +import org.prop4j.Literal; import org.prop4j.Node; import org.prop4j.NodeReader; import org.variantsync.diffdetective.util.fide.FixTrueFalse; @@ -36,6 +37,11 @@ public interface PropositionalFormulaParser { node = FixTrueFalse.EliminateTrueAndFalseInplace(node).get(); } + if (node == null) { +// Logger.warn("Could not parse expression '{}' to feature mapping. Using it as literal.", fmString); + node = new Literal(text); + } + return node; }; } diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java deleted file mode 100644 index 1bff406f..00000000 --- a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.variantsync.diffdetective.feature.cpp; - -import org.prop4j.Literal; -import org.prop4j.Node; -import org.variantsync.diffdetective.error.UnparseableFormulaException; -import org.variantsync.diffdetective.feature.AnnotationParser; -import org.variantsync.diffdetective.feature.AnnotationType; -import org.variantsync.diffdetective.feature.PropositionalFormulaParser; - -import java.util.regex.Pattern; - -/** - * A parser of C-preprocessor annotations. - * - * @author Paul Bittner - */ -public class CPPAnnotationParser implements AnnotationParser { - /** - * Default CPPAnnotationParser. Created by invoking {@link #CPPAnnotationParser()}. - */ - public static final CPPAnnotationParser Default = new CPPAnnotationParser(); - - /** - * Matches the beginning or end of CPP conditional macros. - * It doesn't match the whole macro name, for example for {@code #ifdef} only {@code "#if"} is - * matched and only {@code "if"} is captured. - *

- * Note that this pattern doesn't handle comments between {@code #} and the macro name. - */ - private final static Pattern ANNOTATION = - Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)"); - - private final PropositionalFormulaParser formulaParser; - private final CPPDiffLineFormulaExtractor extractor; - - /** - * Invokes {@link #CPPAnnotationParser(PropositionalFormulaParser, CPPDiffLineFormulaExtractor)} with - * the {@link PropositionalFormulaParser#Default default formula parser} and a new {@link CPPDiffLineFormulaExtractor}. - */ - public CPPAnnotationParser() { - this(PropositionalFormulaParser.Default, new CPPDiffLineFormulaExtractor()); - } - - /** - * Creates a new preprocessor annotation parser. - * - * @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula f in #if f). - * @param extractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. - */ - public CPPAnnotationParser(final PropositionalFormulaParser formulaParser, CPPDiffLineFormulaExtractor extractor) { - this.formulaParser = formulaParser; - this.extractor = extractor; - } - - /** - * Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF). - * - * @param line The line of code of a preprocessor annotation. - * @return The formula of the macro in the given line. - * If no such formula could be parsed, returns a Literal with the line's condition as name. - * @throws UnparseableFormulaException when {@link CPPDiffLineFormulaExtractor#extractFormula(String)} throws. - */ - public Node parseAnnotation(String line) throws UnparseableFormulaException { - return parseCondition(extractor.extractFormula(line)); - } - - /** - * Parses a condition of a preprocessor macro (i.e., IF, IFDEF, ELIF). - * The given input should not start with preprocessor annotations. - * If the input starts with a preprocessor annotation, use {@link #parseAnnotation} instead. - * The input should have been prepared by {@link CPPDiffLineFormulaExtractor}. - * - * @param condition The condition of a preprocessor annotation. - * @return The formula of the condition. - * If no such formula could be parsed, returns a Literal with the condition as name. - */ - public Node parseCondition(String condition) { - Node formula = formulaParser.parse(condition); - - if (formula == null) { -// Logger.warn("Could not parse expression '{}' to feature mapping. Using it as literal.", fmString); - formula = new Literal(condition); - } - - return formula; - } - - @Override - public AnnotationType determineAnnotationType(String text) { - var matcher = ANNOTATION.matcher(text); - int nameId = 1; - if (matcher.find()) { - return AnnotationType.fromName(matcher.group(nameId)); - } else { - return AnnotationType.None; - } - } - -} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java new file mode 100644 index 00000000..727d5cb8 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java @@ -0,0 +1,50 @@ +package org.variantsync.diffdetective.feature.jpp; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; +import org.variantsync.diffdetective.feature.AbstractingFormulaExtractor; +import org.variantsync.diffdetective.feature.ParseErrorListener; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionLexer; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; + +import java.util.regex.Pattern; + +/** + * Extracts the expression from a JavaPP (Java PreProcessor) statement . + * For example, given the annotation "//#if defined(A) || B()", the extractor would extract "A || B". + * The extractor detects if and elif annotations (other annotations do not have expressions). + * The given JPP statement might also be a line in a diff (i.e., preceeded by a - or +). + * + * @author Alexander Schultheiß + */ +public class JPPDiffLineFormulaExtractor extends AbstractingFormulaExtractor { + private static final String JPP_ANNOTATION_REGEX = "^[+-]?\\s*//\\s*#\\s*(if|elif)(\\s+(.*)|(\\(.*\\)))$"; + private static final Pattern JPP_ANNOTATION_PATTERN = Pattern.compile(JPP_ANNOTATION_REGEX); + + public JPPDiffLineFormulaExtractor() { + super(JPP_ANNOTATION_PATTERN); + } + + /** + * Abstract the given formula. + *

+ * First, the visitor uses ANTLR to parse the formula into a parse tree gives the tree to a {@link ControllingJPPExpressionVisitor}. + * The visitor traverses the tree starting from the root, searching for subtrees that must be abstracted. + * If such a subtree is found, the visitor calls an {@link AbstractingJPPExpressionVisitor} to abstract the part of + * the formula in the subtree. + *

+ * + * @param formula that is to be abstracted + * @return the abstracted formula + */ + @Override + protected String abstractFormula(String formula) { + JPPExpressionLexer lexer = new JPPExpressionLexer(CharStreams.fromString(formula)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + JPPExpressionParser parser = new JPPExpressionParser(tokens); + parser.addErrorListener(new ParseErrorListener(formula)); + ParseTree tree = parser.expression(); + return tree.accept(new ControllingJPPExpressionVisitor()).toString(); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java index ed8cb71b..e27983cc 100644 --- a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java +++ b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java @@ -5,7 +5,7 @@ import org.variantsync.diffdetective.datasets.Repository; import org.variantsync.diffdetective.diff.git.PatchDiff; import org.variantsync.diffdetective.diff.result.DiffParseException; -import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; import org.variantsync.diffdetective.mining.RWCompositePatternNodeFormat; import org.variantsync.diffdetective.mining.RWCompositePatternTreeFormat; import org.variantsync.diffdetective.mining.VariationDiffMiner; @@ -102,7 +102,7 @@ private static void render(final Path fileToRender) { try { t = VariationDiff.fromFile(fileToRender, new VariationDiffParseOptions( - CPPAnnotationParser.Default, collapseMultipleCodeLines, ignoreEmptyLines + PreprocessorAnnotationParser.CPPAnnotationParser, collapseMultipleCodeLines, ignoreEmptyLines )); } catch (IOException | DiffParseException e) { Logger.error(e, "Could not read given file '{}'", fileToRender); diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java index 63ed6353..6af0675c 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java @@ -1,7 +1,7 @@ package org.variantsync.diffdetective.variation.diff.parse; import org.variantsync.diffdetective.feature.AnnotationParser; -import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; /** * Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s. @@ -37,7 +37,7 @@ public VariationDiffParseOptions( /** * Creates VariationDiffParseOptions with the given annotation parser. */ - public VariationDiffParseOptions withAnnotationParser(CPPAnnotationParser annotationParser) { + public VariationDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) { return new VariationDiffParseOptions( annotationParser, this.collapseMultipleCodeLines(), @@ -47,10 +47,10 @@ public VariationDiffParseOptions withAnnotationParser(CPPAnnotationParser annota /** * Default value for VariationDiffParseOptions that does not remember parsed unix diffs - * and uses the default value for the parsing annotations ({@link CPPAnnotationParser#Default}). + * and uses the default value for the parsing annotations ({@link PreprocessorAnnotationParser#CPPAnnotationParser}). */ public static final VariationDiffParseOptions Default = new VariationDiffParseOptions( - CPPAnnotationParser.Default, + PreprocessorAnnotationParser.CPPAnnotationParser, false, false ); diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java index 58702002..76af531b 100644 --- a/src/test/java/JPPParserTest.java +++ b/src/test/java/JPPParserTest.java @@ -1,9 +1,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + // Test cases for a parser of https://www.slashdev.ca/javapp/ public class JPPParserTest { private record TestCase(String formula, String expected) { @@ -70,17 +74,18 @@ private static List throwingTestCases() { @ParameterizedTest @MethodSource("testCases") public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException { -// assertEquals( -// testCase.expected, -// // TODO: -// ); + assertEquals( + testCase.expected, + new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + ); } @ParameterizedTest @MethodSource("throwingTestCases") public void throwingTestCase(JPPParserTest.ThrowingTestCase testCase) { - // assertThrows(UnparseableFormulaException.class, () -> //TODO - // ); + assertThrows(UnparseableFormulaException.class, () -> + new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula) + ); } } diff --git a/src/test/java/TreeDiffingTest.java b/src/test/java/TreeDiffingTest.java index 19f676da..1bcf7c24 100644 --- a/src/test/java/TreeDiffingTest.java +++ b/src/test/java/TreeDiffingTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.diff.result.DiffParseException; -import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; import org.variantsync.diffdetective.util.IO; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; @@ -117,7 +117,7 @@ public VariationTree parseVariationTree(Path filename) throws IO VariationDiffParser.createVariationTree( file, new VariationDiffParseOptions( - CPPAnnotationParser.Default, + PreprocessorAnnotationParser.CPPAnnotationParser, false, false) ).getRoot().projection(BEFORE).toVariationTree(), From 5262889adee980becb94ec2056a7f5c412bd735b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 18:43:08 +0100 Subject: [PATCH 14/41] feat: add stubs for JPP expression abstraction --- .../jpp/AbstractingJPPExpressionVisitor.java | 117 ++++++++++++++++++ .../jpp/ControllingJPPExpressionVisitor.java | 59 +++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java create mode 100644 src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java new file mode 100644 index 00000000..ac907d16 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java @@ -0,0 +1,117 @@ +package org.variantsync.diffdetective.feature.jpp; + +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.variantsync.diffdetective.feature.antlr.CExpressionParser; +import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; + +public class AbstractingJPPExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { + @Override + public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { + return null; + } + + @Override + public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { + return null; + } + + @Override + public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { + return null; + } + + @Override + public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { + return null; + } + + @Override + public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { + return null; + } + + @Override + public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { + return null; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java new file mode 100644 index 00000000..b121f27a --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java @@ -0,0 +1,59 @@ +package org.variantsync.diffdetective.feature.jpp; + +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionVisitor; + +public class ControllingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { + private final AbstractingJPPExpressionVisitor abstractingVisitor = new AbstractingJPPExpressionVisitor(); + + @Override + public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { + return null; + } + + @Override + public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { + return null; + } + + @Override + public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { + return null; + } +} From a7a247d03248abae10000d5fcce1f08acb38dd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Tue, 20 Feb 2024 18:48:16 +0100 Subject: [PATCH 15/41] refactor: prepare JPPParserTest for more test cases --- src/test/java/JPPParserTest.java | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java index 76af531b..4ad49819 100644 --- a/src/test/java/JPPParserTest.java +++ b/src/test/java/JPPParserTest.java @@ -10,43 +10,43 @@ // Test cases for a parser of https://www.slashdev.ca/javapp/ public class JPPParserTest { - private record TestCase(String formula, String expected) { + private record TestCase(Input input, Expected expected) { } private record ThrowingTestCase(String formula) { } - private static List testCases() { + private static List> abstractionTests() { return List.of( /// #if expression // expression := | [!]defined(name) // expression := operand == operand - new JPPParserTest.TestCase("//#if 1 == -42", "1__EQ____U_MINUS__42"), + new JPPParserTest.TestCase<>("//#if 1 == -42", "1__EQ____U_MINUS__42"), // expression := operand != operand - new JPPParserTest.TestCase("// #if 1 != 0", "1__NEQ__0"), + new JPPParserTest.TestCase<>("// #if 1 != 0", "1__NEQ__0"), // expression := operand <= operand - new JPPParserTest.TestCase("//#if -1 <= 0", "__U_MINUS__1__LEQ__0"), + new JPPParserTest.TestCase<>("//#if -1 <= 0", "__U_MINUS__1__LEQ__0"), // expression := operand < operand - new JPPParserTest.TestCase("//#if \"str\" < 0", "__QUOTE__str__QUOTE____LT__0"), + new JPPParserTest.TestCase<>("//#if \"str\" < 0", "__QUOTE__str__QUOTE____LT__0"), // expression := operand >= operand - new JPPParserTest.TestCase("// #if \"str\" >= \"str\"", "__QUOTE__str__QUOTE____GEQ____QUOTE__str__QUOTE__"), + new JPPParserTest.TestCase<>("// #if \"str\" >= \"str\"", "__QUOTE__str__QUOTE____GEQ____QUOTE__str__QUOTE__"), // expression := operand > operand - new JPPParserTest.TestCase("// #if 1.2 > 0", "1__DOT__2__GT__0"), + new JPPParserTest.TestCase<>("// #if 1.2 > 0", "1__DOT__2__GT__0"), // expression := defined(name) - new JPPParserTest.TestCase("//#if defined(property)", "DEFINED_property"), + new JPPParserTest.TestCase<>("//#if defined(property)", "DEFINED_property"), // expression := !defined(name) - new JPPParserTest.TestCase("//#if !defined(property)", "__U_NOT__DEFINED_property"), + new JPPParserTest.TestCase<>("//#if !defined(property)", "__U_NOT__DEFINED_property"), // operand := ${property} - new JPPParserTest.TestCase("//#if ${os_version} == 4.1", "os_version__EQ__4__DOT__1"), + new JPPParserTest.TestCase<>("//#if ${os_version} == 4.1", "os_version__EQ__4__DOT__1"), /// #if expression and expression - new JPPParserTest.TestCase("//#if 1 > 2 and defined( FEAT_A )", "1__GT__2&&DEFINED_FEAT_A"), + new JPPParserTest.TestCase<>("//#if 1 > 2 and defined( FEAT_A )", "1__GT__2&&DEFINED_FEAT_A"), /// #if expression or expression - new JPPParserTest.TestCase("//#if !defined(left) or defined(right)", "__U_NOT__DEFINED_left||DEFINED_right"), + new JPPParserTest.TestCase<>("//#if !defined(left) or defined(right)", "__U_NOT__DEFINED_left||DEFINED_right"), /// #if expression and expression or expression - new JPPParserTest.TestCase("//#if ${os_version} == 4.1 and 1 > -42 or defined(ALL)", "os_version__EQ__4__DOT__1&&1__GT____U_MINUS__42||DEFINED_ALL") + new JPPParserTest.TestCase<>("//#if ${os_version} == 4.1 and 1 > -42 or defined(ALL)", "os_version__EQ__4__DOT__1&&1__GT____U_MINUS__42||DEFINED_ALL") ); } @@ -72,11 +72,11 @@ private static List throwingTestCases() { } @ParameterizedTest - @MethodSource("testCases") + @MethodSource("abstractionTests") public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException { assertEquals( testCase.expected, - new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + new JPPDiffLineFormulaExtractor().extractFormula((String) testCase.input()) ); } From cbbb3b4f8245e7e47a3fc4aa7f88c034a7cd2585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 09:18:29 +0100 Subject: [PATCH 16/41] docs: add comments describing the parser rules --- .../jpp/AbstractingJPPExpressionVisitor.java | 120 +++++++----------- .../jpp/ControllingJPPExpressionVisitor.java | 36 ++++++ 2 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java index ac907d16..c2114c0d 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java @@ -3,115 +3,93 @@ import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; import org.variantsync.diffdetective.feature.antlr.CExpressionParser; import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionVisitor; -public class AbstractingJPPExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { +public class AbstractingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { + // expression + // : logicalOrExpression + // ; @Override - public StringBuilder visitExpression(CExpressionParser.ExpressionContext ctx) { + public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) { return null; } + // logicalOrExpression + // : logicalAndExpression (OR logicalAndExpression)* + // ; @Override - public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { + public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { return null; } + // logicalAndExpression + // : primaryExpression (AND primaryExpression)* + // ; @Override - public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { + public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { return null; } + // primaryExpression + // : definedExpression + // | undefinedExpression + // | comparisonExpression + // ; @Override - public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { + public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { return null; } + // comparisonExpression + // : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? + // ; @Override - public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { + public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { return null; } + // operand + // : propertyExpression + // | Constant + // | StringLiteral+ + // | unaryOperator Constant + // ; @Override - public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { + public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { return null; } + // definedExpression + // : 'defined' '(' Identifier ')' + // ; @Override - public StringBuilder visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { + public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { return null; } + // undefinedExpression + // : NOT 'defined' '(' Identifier ')' + // ; @Override - public StringBuilder visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { + public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { return null; } + // propertyExpression + // : '${' Identifier '}' + // ; @Override - public StringBuilder visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { + public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { return null; } + // unaryOperator + // : U_PLUS + // | U_MINUS + // ; @Override - public StringBuilder visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { - return null; - } - - @Override - public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { - return null; - } - - @Override - public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { - return null; - } - - @Override - public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { + public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { return null; } } diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java index b121f27a..367058cc 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java @@ -7,51 +7,87 @@ public class ControllingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { private final AbstractingJPPExpressionVisitor abstractingVisitor = new AbstractingJPPExpressionVisitor(); + // expression + // : logicalOrExpression + // ; @Override public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) { return null; } + // logicalOrExpression + // : logicalAndExpression (OR logicalAndExpression)* + // ; @Override public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { return null; } + // logicalAndExpression + // : primaryExpression (AND primaryExpression)* + // ; @Override public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { return null; } + // primaryExpression + // : definedExpression + // | undefinedExpression + // | comparisonExpression + // ; @Override public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { return null; } + // comparisonExpression + // : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? + // ; @Override public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { return null; } + // operand + // : propertyExpression + // | Constant + // | StringLiteral+ + // | unaryOperator Constant + // ; @Override public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { return null; } + // definedExpression + // : 'defined' '(' Identifier ')' + // ; @Override public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { return null; } + // undefinedExpression + // : NOT 'defined' '(' Identifier ')' + // ; @Override public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { return null; } + // propertyExpression + // : '${' Identifier '}' + // ; @Override public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { return null; } + // unaryOperator + // : U_PLUS + // | U_MINUS + // ; @Override public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { return null; From d239664dc8c34a3ccddc07c4488bda25e4404644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 10:20:02 +0100 Subject: [PATCH 17/41] feat: first working version of JavaPP parsing --- .../feature/antlr/JPPExpression.g4 | 6 +- .../jpp/AbstractingJPPExpressionVisitor.java | 125 ++++++++++++++++-- .../jpp/ControllingJPPExpressionVisitor.java | 95 ------------- .../jpp/JPPDiffLineFormulaExtractor.java | 7 +- 4 files changed, 119 insertions(+), 114 deletions(-) delete mode 100644 src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java diff --git a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 index 3d7e42a2..ea3d6460 100644 --- a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 +++ b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 @@ -26,9 +26,9 @@ comparisonExpression operand : propertyExpression + | unaryOperator Constant | Constant | StringLiteral+ - | unaryOperator Constant ; definedExpression @@ -66,9 +66,9 @@ NEQ : '!='; DOT : '.'; Identifier - : ('\\')? ( IdentifierNondigit + : IdentifierNondigit ( IdentifierNondigit | Digit - )+ + )* ; fragment diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java index c2114c0d..af2bbe1f 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java @@ -1,18 +1,22 @@ package org.variantsync.diffdetective.feature.jpp; +import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; -import org.variantsync.diffdetective.feature.antlr.CExpressionParser; -import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.variantsync.diffdetective.feature.BooleanAbstraction; import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; import org.variantsync.diffdetective.feature.antlr.JPPExpressionVisitor; +import java.util.function.Function; + public class AbstractingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { // expression // : logicalOrExpression // ; @Override public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) { - return null; + return ctx.logicalOrExpression().accept(this); } // logicalOrExpression @@ -20,7 +24,8 @@ public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) // ; @Override public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { - return null; + return visitLogicalExpression(ctx, + childExpression -> childExpression instanceof JPPExpressionParser.LogicalAndExpressionContext); } // logicalAndExpression @@ -28,7 +33,8 @@ public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpre // ; @Override public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { - return null; + return visitLogicalExpression(ctx, + childExpression -> childExpression instanceof JPPExpressionParser.PrimaryExpressionContext); } // primaryExpression @@ -38,7 +44,16 @@ public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExp // ; @Override public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { - return null; + if (ctx.definedExpression() != null) { + return ctx.definedExpression().accept(this); + } + if (ctx.undefinedExpression() != null) { + return ctx.undefinedExpression().accept(this); + } + if (ctx.comparisonExpression() != null) { + return ctx.comparisonExpression().accept(this); + } + throw new IllegalStateException("Unreachable code"); } // comparisonExpression @@ -46,7 +61,7 @@ public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressio // ; @Override public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { - return null; + return visitExpression(ctx, childExpression -> childExpression instanceof JPPExpressionParser.OperandContext); } // operand @@ -57,7 +72,28 @@ public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExp // ; @Override public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { - return null; + // propertyExpression + if (ctx.propertyExpression() != null) { + return ctx.propertyExpression().accept(this); + } + // unaryOperator Constant + if (ctx.unaryOperator() != null) { + StringBuilder sb = ctx.unaryOperator().accept(this); + sb.append(BooleanAbstraction.abstractAll(ctx.Constant().getText().trim())); + return sb; + } + // Constant + if (ctx.Constant() != null) { + return new StringBuilder(BooleanAbstraction.abstractAll(ctx.Constant().getText().trim())); + } + // StringLiteral+ + if (!ctx.StringLiteral().isEmpty()) { + StringBuilder sb = new StringBuilder(); + ctx.StringLiteral().stream().map(ParseTree::getText).map(String::trim).map(BooleanAbstraction::abstractAll).forEach(sb::append); + return sb; + } + // Unreachable + throw new IllegalStateException("Unreachable code."); } // definedExpression @@ -65,7 +101,9 @@ public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { // ; @Override public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { - return null; + StringBuilder sb = new StringBuilder("DEFINED_"); + sb.append(ctx.Identifier().getText().trim()); + return sb; } // undefinedExpression @@ -73,7 +111,11 @@ public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressio // ; @Override public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { - return null; + StringBuilder sb = new StringBuilder(); + sb.append(BooleanAbstraction.U_NOT); + sb.append("DEFINED_"); + sb.append(ctx.Identifier().getText().trim()); + return sb; } // propertyExpression @@ -81,7 +123,7 @@ public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpre // ; @Override public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { - return null; + return new StringBuilder(ctx.Identifier().getText().trim()); } // unaryOperator @@ -90,6 +132,65 @@ public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpress // ; @Override public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { - return null; + switch (ctx.getText().trim()) { + case "+" -> { + return new StringBuilder(BooleanAbstraction.U_PLUS); + } + case "-" -> { + return new StringBuilder(BooleanAbstraction.U_MINUS); + } + } + throw new IllegalStateException("Unreachable code"); + } + + // logicalOrExpression + // : logicalAndExpression (OR logicalAndExpression)* + // ; + // logicalAndExpression + // : primaryExpression (AND primaryExpression)* + // ; + private StringBuilder visitLogicalExpression(ParserRuleContext expressionContext, Function instanceCheck) { + StringBuilder sb = new StringBuilder(); + for (ParseTree subtree : expressionContext.children) { + if (instanceCheck.apply(subtree)) { + // logicalAndExpression | InclusiveOrExpression + sb.append(subtree.accept(this)); + } else if (subtree instanceof TerminalNode terminal) { + // '&&' | '||' + switch (subtree.getText()) { + case "and" -> sb.append("&&"); + case "or" -> sb.append("||"); + default -> throw new IllegalStateException(); + } + } else { + // loop does not work as expected + throw new IllegalStateException(); + } + } + return sb; + } + + /** + * Abstract all child nodes in the parse tree. + * + * @param expressionContext The root of the subtree to abstract + * @param instanceCheck A check for expected child node types + * @return The abstracted formula of the subtree + */ + private StringBuilder visitExpression(ParserRuleContext expressionContext, Function instanceCheck) { + StringBuilder sb = new StringBuilder(); + for (ParseTree subtree : expressionContext.children) { + if (instanceCheck.apply(subtree)) { + // Some operand (i.e., a subtree) that we have to visit + sb.append(subtree.accept(this)); + } else if (subtree instanceof TerminalNode terminal) { + // Some operator (i.e., a leaf node) that requires direct abstraction + sb.append(BooleanAbstraction.abstractToken(terminal.getText().trim())); + } else { + // sanity check: loop does not work as expected + throw new IllegalStateException(); + } + } + return sb; } } diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java deleted file mode 100644 index 367058cc..00000000 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.variantsync.diffdetective.feature.jpp; - -import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; -import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; -import org.variantsync.diffdetective.feature.antlr.JPPExpressionVisitor; - -public class ControllingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { - private final AbstractingJPPExpressionVisitor abstractingVisitor = new AbstractingJPPExpressionVisitor(); - - // expression - // : logicalOrExpression - // ; - @Override - public StringBuilder visitExpression(JPPExpressionParser.ExpressionContext ctx) { - return null; - } - - // logicalOrExpression - // : logicalAndExpression (OR logicalAndExpression)* - // ; - @Override - public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { - return null; - } - - // logicalAndExpression - // : primaryExpression (AND primaryExpression)* - // ; - @Override - public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { - return null; - } - - // primaryExpression - // : definedExpression - // | undefinedExpression - // | comparisonExpression - // ; - @Override - public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { - return null; - } - - // comparisonExpression - // : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? - // ; - @Override - public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { - return null; - } - - // operand - // : propertyExpression - // | Constant - // | StringLiteral+ - // | unaryOperator Constant - // ; - @Override - public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { - return null; - } - - // definedExpression - // : 'defined' '(' Identifier ')' - // ; - @Override - public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { - return null; - } - - // undefinedExpression - // : NOT 'defined' '(' Identifier ')' - // ; - @Override - public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { - return null; - } - - // propertyExpression - // : '${' Identifier '}' - // ; - @Override - public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { - return null; - } - - // unaryOperator - // : U_PLUS - // | U_MINUS - // ; - @Override - public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { - return null; - } -} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java index 727d5cb8..6252dd07 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java @@ -29,10 +29,9 @@ public JPPDiffLineFormulaExtractor() { /** * Abstract the given formula. *

- * First, the visitor uses ANTLR to parse the formula into a parse tree gives the tree to a {@link ControllingJPPExpressionVisitor}. + * First, the visitor uses ANTLR to parse the formula into a parse tree gives the tree to a {@link AbstractingJPPExpressionVisitor}. * The visitor traverses the tree starting from the root, searching for subtrees that must be abstracted. - * If such a subtree is found, the visitor calls an {@link AbstractingJPPExpressionVisitor} to abstract the part of - * the formula in the subtree. + * If such a subtree is found, the visitor abstracts the part of the formula in the subtree. *

* * @param formula that is to be abstracted @@ -45,6 +44,6 @@ protected String abstractFormula(String formula) { JPPExpressionParser parser = new JPPExpressionParser(tokens); parser.addErrorListener(new ParseErrorListener(formula)); ParseTree tree = parser.expression(); - return tree.accept(new ControllingJPPExpressionVisitor()).toString(); + return tree.accept(new AbstractingJPPExpressionVisitor()).toString(); } } From 6d9e31524edecf9df4cf8f586843bfcbf768f252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 13:44:03 +0100 Subject: [PATCH 18/41] test: add test for parsing entire JPP diff --- src/test/java/JPPParserTest.java | 62 ++++++++++++++++++- src/test/resources/diffs/jpp/.gitignore | 2 + src/test/resources/diffs/jpp/basic_jpp.diff | 20 ++++++ .../resources/diffs/jpp/basic_jpp_expected.lg | 35 +++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/diffs/jpp/.gitignore create mode 100644 src/test/resources/diffs/jpp/basic_jpp.diff create mode 100644 src/test/resources/diffs/jpp/basic_jpp_expected.lg diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java index 4ad49819..2938989a 100644 --- a/src/test/java/JPPParserTest.java +++ b/src/test/java/JPPParserTest.java @@ -1,12 +1,28 @@ +import org.apache.commons.io.IOUtils; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; import org.variantsync.diffdetective.feature.jpp.JPPDiffLineFormulaExtractor; +import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; +import org.variantsync.diffdetective.variation.diff.serialize.Format; +import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExporter; +import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.ChildOrderEdgeFormat; +import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.FullNodeFormat; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.variantsync.diffdetective.util.Assert.fail; // Test cases for a parser of https://www.slashdev.ca/javapp/ public class JPPParserTest { @@ -71,12 +87,19 @@ private static List throwingTestCases() { ); } + private static List> fullDiffTests() { + final Path basePath = Path.of("src", "test", "resources", "diffs", "jpp"); + return List.of( + new JPPParserTest.TestCase<>(basePath.resolve("basic_jpp.diff"), basePath.resolve("basic_jpp_expected.lg")) + ); + } + @ParameterizedTest @MethodSource("abstractionTests") - public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException { + public void testCase(JPPParserTest.TestCase testCase) throws UnparseableFormulaException { assertEquals( testCase.expected, - new JPPDiffLineFormulaExtractor().extractFormula((String) testCase.input()) + new JPPDiffLineFormulaExtractor().extractFormula(testCase.input()) ); } @@ -88,4 +111,39 @@ public void throwingTestCase(JPPParserTest.ThrowingTestCase testCase) { ); } + @ParameterizedTest + @MethodSource("fullDiffTests") + public void fullDiffTestCase(JPPParserTest.TestCase testCase) throws IOException, DiffParseException { + VariationDiff variationDiff; + try (var inputFile = Files.newBufferedReader(testCase.input)) { + variationDiff = VariationDiffParser.createVariationDiff( + inputFile, + new VariationDiffParseOptions( + false, + false + ).withAnnotationParser(PreprocessorAnnotationParser.JPPAnnotationParser) + ); + } + + Path actualPath = testCase.input.getParent().resolve(testCase.input.getFileName() + "_actual"); + try (var output = IO.newBufferedOutputStream(actualPath)) { + new LineGraphExporter<>(new Format<>(new FullNodeFormat(), new ChildOrderEdgeFormat<>())) + .exportVariationDiff(variationDiff, output); + } + + try ( + var expectedFile = Files.newBufferedReader(testCase.expected); + var actualFile = Files.newBufferedReader(actualPath); + ) { + if (IOUtils.contentEqualsIgnoreEOL(expectedFile, actualFile)) { + // Delete output files if the test succeeded + Files.delete(actualPath); + } else { + // Keep output files if the test failed + fail("The VariationDiff in file " + testCase.input + " didn't parse correctly. " + + "Expected the content of " + testCase.expected + " but got the content of " + actualPath + ". "); + } + } + } + } diff --git a/src/test/resources/diffs/jpp/.gitignore b/src/test/resources/diffs/jpp/.gitignore new file mode 100644 index 00000000..6aa79ba5 --- /dev/null +++ b/src/test/resources/diffs/jpp/.gitignore @@ -0,0 +1,2 @@ +*_actual.lg +/tex/ diff --git a/src/test/resources/diffs/jpp/basic_jpp.diff b/src/test/resources/diffs/jpp/basic_jpp.diff new file mode 100644 index 00000000..69356bab --- /dev/null +++ b/src/test/resources/diffs/jpp/basic_jpp.diff @@ -0,0 +1,20 @@ ++package org.argouml.application; ++ ++import javax.swing.UIManager; ++//#if defined(LOGGING) ++import org.apache.log4j.BasicConfigurator; ++import org.apache.log4j.Level; ++import org.apache.log4j.Logger; ++//#endif ++import org.argouml.application.api.CommandLineInterface; ++import org.argouml.application.security.ArgoAwtExceptionHandler; ++//#if defined(COGNITIVE) ++//@#$LPS-COGNITIVE:GranularityType:Import ++import org.argouml.cognitive.AbstractCognitiveTranslator; ++import org.argouml.cognitive.ui.ToDoPane; ++//#endif ++import org.argouml.ui.cmd.InitUiCmdSubsystem; ++import org.argouml.ui.cmd.PrintManager; ++//#if defined(COGNITIVE) and defined(DEPLOYMENTDIAGRAM) ++import org.argouml.uml.diagram.activity.ui.InitActivityDiagram; ++//#endif \ No newline at end of file diff --git a/src/test/resources/diffs/jpp/basic_jpp_expected.lg b/src/test/resources/diffs/jpp/basic_jpp_expected.lg new file mode 100644 index 00000000..c47721aa --- /dev/null +++ b/src/test/resources/diffs/jpp/basic_jpp_expected.lg @@ -0,0 +1,35 @@ +v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True +v 131 ADD;ARTIFACT;(old: -1, diff: 1, new: 1);(old: -1, diff: 2, new: 2);;package org.argouml.application; +v 195 ADD;ARTIFACT;(old: -1, diff: 2, new: 2);(old: -1, diff: 3, new: 3);; +v 259 ADD;ARTIFACT;(old: -1, diff: 3, new: 3);(old: -1, diff: 4, new: 4);;import javax.swing.UIManager; +v 320 ADD;IF;(old: -1, diff: 4, new: 4);(old: -1, diff: 8, new: 8);DEFINED_LOGGING;//#if defined(LOGGING) +v 387 ADD;ARTIFACT;(old: -1, diff: 5, new: 5);(old: -1, diff: 6, new: 6);;import org.apache.log4j.BasicConfigurator; +v 451 ADD;ARTIFACT;(old: -1, diff: 6, new: 6);(old: -1, diff: 7, new: 7);;import org.apache.log4j.Level; +v 515 ADD;ARTIFACT;(old: -1, diff: 7, new: 7);(old: -1, diff: 8, new: 8);;import org.apache.log4j.Logger; +v 643 ADD;ARTIFACT;(old: -1, diff: 9, new: 9);(old: -1, diff: 10, new: 10);;import org.argouml.application.api.CommandLineInterface; +v 707 ADD;ARTIFACT;(old: -1, diff: 10, new: 10);(old: -1, diff: 11, new: 11);;import org.argouml.application.security.ArgoAwtExceptionHandler; +v 768 ADD;IF;(old: -1, diff: 11, new: 11);(old: -1, diff: 15, new: 15);DEFINED_COGNITIVE;//#if defined(COGNITIVE) +v 835 ADD;ARTIFACT;(old: -1, diff: 12, new: 12);(old: -1, diff: 13, new: 13);;//@#$LPS-COGNITIVE:GranularityType:Import +v 899 ADD;ARTIFACT;(old: -1, diff: 13, new: 13);(old: -1, diff: 14, new: 14);;import org.argouml.cognitive.AbstractCognitiveTranslator; +v 963 ADD;ARTIFACT;(old: -1, diff: 14, new: 14);(old: -1, diff: 15, new: 15);;import org.argouml.cognitive.ui.ToDoPane; +v 1091 ADD;ARTIFACT;(old: -1, diff: 16, new: 16);(old: -1, diff: 17, new: 17);;import org.argouml.ui.cmd.InitUiCmdSubsystem; +v 1155 ADD;ARTIFACT;(old: -1, diff: 17, new: 17);(old: -1, diff: 18, new: 18);;import org.argouml.ui.cmd.PrintManager; +v 1216 ADD;IF;(old: -1, diff: 18, new: 18);(old: -1, diff: 20, new: 20);DEFINED_COGNITIVE & DEFINED_DEPLOYMENTDIAGRAM;//#if defined(COGNITIVE) and defined(DEPLOYMENTDIAGRAM) +v 1283 ADD;ARTIFACT;(old: -1, diff: 19, new: 19);(old: -1, diff: 20, new: 20);;import org.argouml.uml.diagram.activity.ui.InitActivityDiagram; +e 131 16 a;-1,0 +e 195 16 a;-1,1 +e 259 16 a;-1,2 +e 320 16 a;-1,3 +e 387 320 a;-1,0 +e 451 320 a;-1,1 +e 515 320 a;-1,2 +e 643 16 a;-1,4 +e 707 16 a;-1,5 +e 768 16 a;-1,6 +e 835 768 a;-1,0 +e 899 768 a;-1,1 +e 963 768 a;-1,2 +e 1091 16 a;-1,7 +e 1155 16 a;-1,8 +e 1216 16 a;-1,9 +e 1283 1216 a;-1,0 From 569f6631bd486af445d1df891c901f6006f84778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 13:51:05 +0100 Subject: [PATCH 19/41] chore: increment DiffDetective version --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 4052eb59..1f2c3f03 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.variantsync diffdetective - 2.1.0 + 2.2.0 UTF-8 @@ -98,16 +98,16 @@ org.sat4j core 2.3.5 - - + + de.ovgu featureide.lib.fm 3.8.1 - - + + From 387c78f63daebbd6a11f24e527e19f82cb5b0bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 14:40:00 +0100 Subject: [PATCH 20/41] refactor: use the more general type --- .../diffdetective/datasets/PatchDiffParseOptions.java | 4 ++-- .../variation/diff/parse/VariationDiffParseOptions.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java index 7d9be594..10fbe15c 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/PatchDiffParseOptions.java @@ -1,6 +1,6 @@ package org.variantsync.diffdetective.datasets; -import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; +import org.variantsync.diffdetective.feature.AnnotationParser; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; /** @@ -27,7 +27,7 @@ public enum DiffStoragePolicy { /** * Creates PatchDiffParseOptions with the given annotation parser. */ - public PatchDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) { + public PatchDiffParseOptions withAnnotationParser(AnnotationParser annotationParser) { return new PatchDiffParseOptions( this.diffStoragePolicy(), this.variationDiffParseOptions().withAnnotationParser(annotationParser) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java index 6af0675c..1269e946 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParseOptions.java @@ -6,7 +6,7 @@ /** * Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s. * - * @param annotationParser A parser for parsing c preprocessor annotations. + * @param annotationParser A parser for parsing annotations. * @param collapseMultipleCodeLines Whether multiple consecutive code lines with the same diff * type should be collapsed into a single artifact node. * @param ignoreEmptyLines Whether to add {@code DiffNode}s for empty lines (regardless of their {@code DiffType}). @@ -37,7 +37,7 @@ public VariationDiffParseOptions( /** * Creates VariationDiffParseOptions with the given annotation parser. */ - public VariationDiffParseOptions withAnnotationParser(PreprocessorAnnotationParser annotationParser) { + public VariationDiffParseOptions withAnnotationParser(AnnotationParser annotationParser) { return new VariationDiffParseOptions( annotationParser, this.collapseMultipleCodeLines(), From f35636509c536bd0f878073deb0bc0fdd70b939a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 15:10:38 +0100 Subject: [PATCH 21/41] fix(docs): fix broken blocks in Docstring --- .../variation/diff/parse/VariationDiffParser.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index b6a26393..01863a8c 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -34,18 +34,26 @@ * Note: Weird line continuations and comments can cause misidentification of conditional macros. * The following examples are all correct according to the C11 standard: (comment end is marked by * {@code *\/}): + * + *

* * /* * #ifdef A * *\/ + * *

+ * * #ifdef /* * *\/ A * #endif + * *

+ * * # /**\/ ifdef * #endif + * *

+ * * # \ * ifdef * #endif From f91123b3d1a98364c930a758b923b1288dd1296a Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Wed, 21 Feb 2024 17:10:31 +0100 Subject: [PATCH 22/41] Update the version number in the remaining files These were missed in 569f6631bd486af445d1df891c901f6006f84778. --- README.md | 4 ++-- default.nix | 2 +- replication/esecfse22/Dockerfile | 2 +- replication/splc23-views/Dockerfile | 2 +- scripts/genUltimateResults.sh | 2 +- scripts/runValidation.sh | 2 +- scripts/runViewFeasibilityStudy.sh | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 581845f1..2b0fd7da 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,11 @@ To add DiffDetective as a dependency to your own project, add the following snip org.variantsync DiffDetective - 2.1.0 + 2.2.0 ``` -If you prefer to just use a jar file, you can find a jar file with all dependencies at `DiffDetective/target/diffdetective-2.1.0-jar-with-dependencies.jar` (again, the version number might be different). +If you prefer to just use a jar file, you can find a jar file with all dependencies at `DiffDetective/target/diffdetective-2.2.0-jar-with-dependencies.jar` (again, the version number might be different). You can (re-)produce this jar file by either running `mvn package` or `mvn install` within you local clone of DiffDetective. > Disclaimer: Setup tested with maven version 3.6.3. diff --git a/default.nix b/default.nix index 5e05140a..20664989 100644 --- a/default.nix +++ b/default.nix @@ -12,7 +12,7 @@ }: pkgs.stdenv.mkDerivation rec { pname = "DiffDetective"; - version = "2.1.0"; + version = "2.2.0"; src = with pkgs.lib.fileset; toSource { root = ./.; diff --git a/replication/esecfse22/Dockerfile b/replication/esecfse22/Dockerfile index be350805..61b12a77 100644 --- a/replication/esecfse22/Dockerfile +++ b/replication/esecfse22/Dockerfile @@ -32,7 +32,7 @@ WORKDIR /home/sherlock # Copy the compiled JAR file from the first stage into the second stage # Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH WORKDIR /home/sherlock/holmes -COPY --from=0 /home/user/target/diffdetective-2.1.0-jar-with-dependencies.jar ./DiffDetective.jar +COPY --from=0 /home/user/target/diffdetective-2.2.0-jar-with-dependencies.jar ./DiffDetective.jar WORKDIR /home/sherlock RUN mkdir results diff --git a/replication/splc23-views/Dockerfile b/replication/splc23-views/Dockerfile index 27ced085..5bdaa231 100644 --- a/replication/splc23-views/Dockerfile +++ b/replication/splc23-views/Dockerfile @@ -38,7 +38,7 @@ WORKDIR /home/sherlock # Copy the compiled JAR file from the first stage into the second stage # Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH WORKDIR /home/sherlock/holmes -COPY --from=0 /home/user/target/diffdetective-2.1.0-jar-with-dependencies.jar ./DiffDetective.jar +COPY --from=0 /home/user/target/diffdetective-2.2.0-jar-with-dependencies.jar ./DiffDetective.jar WORKDIR /home/sherlock # Copy the setup diff --git a/scripts/genUltimateResults.sh b/scripts/genUltimateResults.sh index d1548901..35fd6d7c 100755 --- a/scripts/genUltimateResults.sh +++ b/scripts/genUltimateResults.sh @@ -1,4 +1,4 @@ resultsdir=$1 -java -cp "target/diffdetective-2.1.0-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator $resultsdir $resultsdir +java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator $resultsdir $resultsdir echo "genUltimateResults.sh DONE" diff --git a/scripts/runValidation.sh b/scripts/runValidation.sh index 0160f042..db1638d6 100755 --- a/scripts/runValidation.sh +++ b/scripts/runValidation.sh @@ -1,2 +1,2 @@ -java -cp "target/diffdetective-2.1.0-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation +java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation echo "runValidation.sh DONE" diff --git a/scripts/runViewFeasibilityStudy.sh b/scripts/runViewFeasibilityStudy.sh index acb87597..3ff5993c 100755 --- a/scripts/runViewFeasibilityStudy.sh +++ b/scripts/runViewFeasibilityStudy.sh @@ -1,2 +1,2 @@ -java -cp "target/diffdetective-2.1.0-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" +java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" echo "runValidation.sh DONE" From f2d46b6994044ca93b99bc42d63e38717fb20764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 19:33:34 +0100 Subject: [PATCH 23/41] docs: add docstring for AnnotationParser --- .../diffdetective/feature/AnnotationParser.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java index fdd6163b..b6c21af6 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java @@ -3,6 +3,13 @@ import org.prop4j.Node; import org.variantsync.diffdetective.error.UnparseableFormulaException; +/** + * Interface for a parser that analyzes annotations in parsed text. The parser is responsible for determining the type + * of the annotation (see {@link AnnotationType}), and parsing the annotation into a {@link Node}. + *

+ * See {@link PreprocessorAnnotationParser} for an example of how an implementation of AnnotationParser could look like. + *

+ */ public interface AnnotationParser { /** * Determine the annotation type for the given piece of text (typically a line of source code). From 50c1d8a9551c992a876e22029ea4b2c5f57f095a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 19:41:48 +0100 Subject: [PATCH 24/41] docs: update documentation of DiffLineFormulaExtractor --- .../feature/DiffLineFormulaExtractor.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java index 7000c7a3..bfdcce3a 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java @@ -3,17 +3,20 @@ import org.variantsync.diffdetective.error.UnparseableFormulaException; /** - * Extracts the expression from a C preprocessor statement. - * For example, given the annotation "#if defined(A) || B()", the extractor would extract - * "A || B". The extractor detects if, ifdef, ifndef and elif annotations. - * (Other annotations do not have expressions.) - * The given pre-processor statement might also a line in a diff (i.e., preceeded by a - or +). + * Interface for extracting a formula from a line containing an annotation. + * The line might be preceded by a '-', '+', or ' '. + * For example, given the line "+#if defined(A) || B()", the extractor should extract "defined(A) || B". + * + *

+ * Further alterations of the extracted formula are allowed. For instance, the extracted formula might be abstracted + * (e.g., by simplifying the call to "defined(A)" leaving only the argument "A", or substituting it with "DEFINED_A"). + *

* * @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß */ public interface DiffLineFormulaExtractor { /** - * Extracts the feature formula as a string from a macro line (possibly within a diff). + * Extracts the feature formula as a string from a line (possibly within a diff). * * @param line The line of which to get the feature mapping * @return The feature mapping as a String of the given line From 16c6586f90e23d890c3b9762b22dde3ba9edb053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 19:48:44 +0100 Subject: [PATCH 25/41] refactor: let Marlin.java use a custom constructor method of PreprocessorAnnotationParser --- .../datasets/predefined/Marlin.java | 3 +-- .../feature/PreprocessorAnnotationParser.java | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java index 57f28cd9..afea9210 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java @@ -14,8 +14,7 @@ */ public class Marlin { public static final PreprocessorAnnotationParser ANNOTATION_PARSER = - new PreprocessorAnnotationParser( - PreprocessorAnnotationParser.CPP_PATTERN, + PreprocessorAnnotationParser.CreateCppAnnotationParser( PropositionalFormulaParser.Default, new MarlinCPPDiffLineFormulaExtractor() ); diff --git a/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java index e122914b..5828a61b 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java @@ -20,7 +20,7 @@ public class PreprocessorAnnotationParser implements AnnotationParser { *

* Note that this pattern doesn't handle comments between {@code #} and the macro name. */ - public final static Pattern CPP_PATTERN = + protected final static Pattern CPP_PATTERN = Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)"); /** @@ -29,7 +29,7 @@ public class PreprocessorAnnotationParser implements AnnotationParser { * matched and only {@code "if"} is captured. *

*/ - public final static Pattern JPP_PATTERN = + protected final static Pattern JPP_PATTERN = Pattern.compile("^[+-]?\\s*//\\s*#\\s*(if|elif|else|endif)"); /** @@ -74,6 +74,26 @@ public PreprocessorAnnotationParser(final Pattern annotationPattern, final Propo this.extractor = formulaExtractor; } + /** + * Creates a new preprocessor annotation parser for C preprocessor annotations. + * + * @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula f in #if f). + * @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. + */ + public static PreprocessorAnnotationParser CreateCppAnnotationParser(final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) { + return new PreprocessorAnnotationParser(CPP_PATTERN, formulaParser, formulaExtractor); + } + + /** + * Creates a new preprocessor annotation parser for JavaPP (Java PreProcessor) annotations. + * + * @param formulaParser Parser that is used to parse propositional formulas in conditional annotations (e.g., the formula f in #if f). + * @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser. + */ + public static PreprocessorAnnotationParser CreateJppAnnotationParser(final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) { + return new PreprocessorAnnotationParser(JPP_PATTERN, formulaParser, formulaExtractor); + } + /** * Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF). * From 987fe11260cd873efc2cc3990dc64bfda1f5c436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 19:49:51 +0100 Subject: [PATCH 26/41] docs: revise JPPDiffLineFormulaExtractor docstring --- .../diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java index 6252dd07..f760cd8e 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java @@ -12,7 +12,7 @@ /** * Extracts the expression from a JavaPP (Java PreProcessor) statement . - * For example, given the annotation "//#if defined(A) || B()", the extractor would extract "A || B". + * For example, given the annotation "//#if defined(A) || B()", the extractor would extract "DEFINED_A || B". * The extractor detects if and elif annotations (other annotations do not have expressions). * The given JPP statement might also be a line in a diff (i.e., preceeded by a - or +). * From 7a997620c9d3d743943826251cab071efebe483b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Wed, 21 Feb 2024 19:52:17 +0100 Subject: [PATCH 27/41] docs: add comment about Javadoc-related import --- .../java/org/variantsync/diffdetective/variation/NodeType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java index 4a7a8679..87cdd917 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java @@ -2,7 +2,7 @@ import org.variantsync.diffdetective.feature.AnnotationType; import org.variantsync.diffdetective.variation.diff.DiffNode; -import org.variantsync.diffdetective.variation.tree.VariationNode; +import org.variantsync.diffdetective.variation.tree.VariationNode; // For Javadoc /** * The type of nodes of a {@link DiffNode} and a {@link VariationNode}. From 7433fa747a9f4c52795065757e37d5371817ff58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Thu, 22 Feb 2024 07:29:27 +0100 Subject: [PATCH 28/41] refactor: use switch expression --- .../diffdetective/variation/NodeType.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java index 87cdd917..6726ba7b 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java @@ -68,21 +68,13 @@ public static NodeType fromName(final String name) { * @return The NodeType that fits the given AnnotationType */ public static NodeType fromAnnotationType(final AnnotationType annotationType) { - switch (annotationType) { - case If -> { - return NodeType.IF; - } - case Elif -> { - return NodeType.ELIF; - } - case Else -> { - return NodeType.ELSE; - } - case None -> { - return NodeType.ARTIFACT; - } + return switch (annotationType) { + case If -> NodeType.IF; + case Elif -> NodeType.ELIF; + case Else -> NodeType.ELSE; + case None -> NodeType.ARTIFACT; default -> throw new IllegalArgumentException(annotationType + "has no NodeType counterpart"); - } + }; } /** From e25fad47ca34f655486d24cc441488fd60ab9216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Schulthei=C3=9F?= Date: Thu, 22 Feb 2024 07:45:40 +0100 Subject: [PATCH 29/41] refactor: exchange default with actual variant to cause compile errors upon implementation changes --- .../java/org/variantsync/diffdetective/variation/NodeType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java index 6726ba7b..b60738c2 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/NodeType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/NodeType.java @@ -73,7 +73,7 @@ public static NodeType fromAnnotationType(final AnnotationType annotationType) { case Elif -> NodeType.ELIF; case Else -> NodeType.ELSE; case None -> NodeType.ARTIFACT; - default -> throw new IllegalArgumentException(annotationType + "has no NodeType counterpart"); + case Endif -> throw new IllegalArgumentException(annotationType + "has no NodeType counterpart"); }; } From 67e798cb4e5f9ccbb8747a5b676988e05f888000 Mon Sep 17 00:00:00 2001 From: Maximilian Glumann Date: Wed, 14 Feb 2024 13:40:17 +0100 Subject: [PATCH 30/41] added PropositionalFormulaParserTest --- .../java/PropositionalFormulaParserTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/test/java/PropositionalFormulaParserTest.java diff --git a/src/test/java/PropositionalFormulaParserTest.java b/src/test/java/PropositionalFormulaParserTest.java new file mode 100644 index 00000000..d1d5b93a --- /dev/null +++ b/src/test/java/PropositionalFormulaParserTest.java @@ -0,0 +1,74 @@ +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.prop4j.And; +import org.prop4j.Literal; +import org.prop4j.Node; +import org.prop4j.Or; +import org.variantsync.diffdetective.feature.PropositionalFormulaParser; + +import java.util.List; + +import static org.variantsync.diffdetective.util.Assert.assertEquals; + +public class PropositionalFormulaParserTest { + private record TestCase(String formula, Node expected) { + } + + /* + * These testCases are based on a subset of the CPPParserTest testCases. + * It is not necessary to keep all testCases from CPPParserTest as most of them result in a single but long Literal anyway. + * The idea is to ensure that the PropositionalFormulaParser is not confused by any symbols in the output of CPPDiffLineFormulaExtractor. + * It is not designed to extensively test the functionality of the PropositionalFormulaParser itself as this is expected to be tested by FeatureIDE already. + */ + private static List testCases() { + return List.of( + new TestCase("A", new Literal("A")), + new TestCase("!(A)", new Literal("A", false)), + new TestCase("!A", new Literal("A", false)), + + new TestCase("A&&B", new And(new Literal("A"), new Literal("B"))), + new TestCase("A||B", new Or(new Literal("A"), new Literal("B"))), + new TestCase("A&&(B||C)", new And(new Literal("A"), new Or(new Literal("B"), new Literal("C")))), + new TestCase("A&&B||C", new Or(new And(new Literal("A"), new Literal("B")), new Literal("C"))), + + new TestCase("A__GEQ__B&&C__GT__D", new And(new Literal("A__GEQ__B"), new Literal("C__GT__D"))), + new TestCase("DEFINED___LB__A__RB__&&__LB__B__MUL__2__RB____GT__C", new And(new Literal("DEFINED___LB__A__RB__"), new Literal("__LB__B__MUL__2__RB____GT__C"))), + new TestCase("(STDC__EQ__1)&&(DEFINED___LB__LARGE__RB__||DEFINED___LB__COMPACT__RB__)", new And(new Literal("STDC__EQ__1"), new Or(new Literal("DEFINED___LB__LARGE__RB__"), new Literal("DEFINED___LB__COMPACT__RB__")))), + new TestCase("APR_CHARSET_EBCDIC&&!(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)", new And(new Literal("APR_CHARSET_EBCDIC"), new Literal("__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25", false))), + new TestCase("A&&(B__GT__C)", new And(new Literal("A"), new Literal("B__GT__C"))), + new TestCase("A&&(__LB__B__ADD__1__RB____GT____LB__C__L_OR__D__RB__)", new And(new Literal("A"), new Literal("__LB__B__ADD__1__RB____GT____LB__C__L_OR__D__RB__"))), + new TestCase("DEFINED_HAS_ATTRIBUTE_&&HAS_ATTRIBUTE___LB__nonnull__RB__", new And(new Literal("DEFINED_HAS_ATTRIBUTE_"), new Literal("HAS_ATTRIBUTE___LB__nonnull__RB__"))), + new TestCase("HAS_BUILTIN___LB__nonnull__RB__&&A", new And(new Literal("HAS_BUILTIN___LB__nonnull__RB__"), new Literal("A"))), + new TestCase("A||(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199630))", new Or(new Literal("A"), new And(new Literal("DEFINED___LB__NAME__RB__"), new Literal("NAME__GEQ__199630")))), + new TestCase("(DEFINED___LB__NAME__RB__&&(NAME__GEQ__199905)&&(NAME__LT__1991011))||(NAME__GEQ__300000)||DEFINED___LB__NAME__RB__", new Or(new And(new Literal("DEFINED___LB__NAME__RB__"), new And(new Literal("NAME__GEQ__199905"), new Literal("NAME__LT__1991011"))), new Or(new Literal("NAME__GEQ__300000"), new Literal("DEFINED___LB__NAME__RB__")))), + new TestCase("1__GT____U_MINUS__42", new Literal("1__GT____U_MINUS__42")), + new TestCase("1__GT____U_PLUS__42", new Literal("1__GT____U_PLUS__42")), + new TestCase("42__GT____U_TILDE__A", new Literal("42__GT____U_TILDE__A")), + new TestCase("A__ADD__B__GT__42", new Literal("A__ADD__B__GT__42")), + new TestCase("A__LSHIFT__B", new Literal("A__LSHIFT__B")), + new TestCase("A__THEN__B__COLON__C", new Literal("A__THEN__B__COLON__C")), + new TestCase("A__MUL____LB__B__ADD__C__RB__", new Literal("A__MUL____LB__B__ADD__C__RB__")), + new TestCase("(__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25)", new Literal("__LB____SQUOTE__Z__SQUOTE____SUB____SQUOTE__A__SQUOTE____RB____EQ__25")), + new TestCase("(__LB__GNUTLS_VERSION_MAJOR__ADD____LB__GNUTLS_VERSION_MINOR__GT__0__L_OR__GNUTLS_VERSION_PATCH__GEQ__20__RB____RB____GT__3)", new Literal("__LB__GNUTLS_VERSION_MAJOR__ADD____LB__GNUTLS_VERSION_MINOR__GT__0__L_OR__GNUTLS_VERSION_PATCH__GEQ__20__RB____RB____GT__3")), + new TestCase("(__LB__A__L_AND__B__RB____GT__C)", new Literal("__LB__A__L_AND__B__RB____GT__C")), + new TestCase("A__EQ__B", new Literal("A__EQ__B")), + new TestCase("(DEFINED_A)", new Literal("DEFINED_A")), + new TestCase("MACRO___LB__A__B__RB____EQ__1", new Literal("MACRO___LB__A__B__RB____EQ__1")), + new TestCase("ifndef", new Literal("ifndef")), + new TestCase("__HAS_WARNING___LB____QUOTE____SUB__Wa__SUB__warning__QUOTE_____foo__RB__", new Literal("__HAS_WARNING___LB____QUOTE____SUB__Wa__SUB__warning__QUOTE_____foo__RB__")) + ); + } + + /* + * A testCase is evaluated using syntactic equivalence between expectation and result because deterministic results are expected. + * Therefore, modifications in FeatureIDE might break these tests although the result remains semantically equivalent. + */ + @ParameterizedTest + @MethodSource("testCases") + public void testCase(TestCase testCase) { + assertEquals( + testCase.expected, + PropositionalFormulaParser.Default.parse(testCase.formula) + ); + } +} \ No newline at end of file From 4ab8d211f017962d8db4faacb6867c6f4a9497ae Mon Sep 17 00:00:00 2001 From: Maximilian Glumann Date: Wed, 20 Mar 2024 18:13:58 +0100 Subject: [PATCH 31/41] clarified documentation comments --- .../java/PropositionalFormulaParserTest.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/test/java/PropositionalFormulaParserTest.java b/src/test/java/PropositionalFormulaParserTest.java index d1d5b93a..ee891f81 100644 --- a/src/test/java/PropositionalFormulaParserTest.java +++ b/src/test/java/PropositionalFormulaParserTest.java @@ -10,15 +10,20 @@ import static org.variantsync.diffdetective.util.Assert.assertEquals; +/** + * Class containing tests of the parsing behaviour for the default implementation of PropositionalFormulaParser. + * Goal: Special characters that occur in the output of a DiffLineFormulaExtractor must not confuse the parsing process of the PropositionalFormulaParser. + * It is not designed to extensively test the functionality of the PropositionalFormulaParser itself as this is expected to be done by FeatureIDE already. + * + * @author Maximilian Glumann + */ public class PropositionalFormulaParserTest { private record TestCase(String formula, Node expected) { } - /* - * These testCases are based on a subset of the CPPParserTest testCases. - * It is not necessary to keep all testCases from CPPParserTest as most of them result in a single but long Literal anyway. - * The idea is to ensure that the PropositionalFormulaParser is not confused by any symbols in the output of CPPDiffLineFormulaExtractor. - * It is not designed to extensively test the functionality of the PropositionalFormulaParser itself as this is expected to be tested by FeatureIDE already. + /** + * These test cases are based on a subset of the CPPParserTest test cases. + * It is not necessary to keep all test cases from CPPParserTest as most of them result in a single but long Literal anyway. */ private static List testCases() { return List.of( @@ -59,9 +64,13 @@ private static List testCases() { ); } - /* - * A testCase is evaluated using syntactic equivalence between expectation and result because deterministic results are expected. - * Therefore, modifications in FeatureIDE might break these tests although the result remains semantically equivalent. + /** + * Each test case compares the output of the default PropositionalFormularParser to the expected output. + * This comparison is performed using the equivalence defined by org.prop4j.Node from FeatureIDE. + * Therefore, nodes describing equivalent propositional formulas in different tree structures are not considered equal. + * As long as FeatureIDE produces a deterministic and consistent tree structure in its output, these tests will succeed. + * Because DiffDetective desires not only a correct but also a deterministic and consistent parser output, + * it is intended that these tests also break, if FeatureIDE changes its parsing behaviour in the future. */ @ParameterizedTest @MethodSource("testCases") From 58592f8a838c9c5586dc91d002cd257333e84869 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Mon, 26 Feb 2024 22:38:05 +0100 Subject: [PATCH 32/41] nix: Use stdenvNoCC to avoid the dependency on GCC --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index e6d9dd4f..d9479229 100644 --- a/default.nix +++ b/default.nix @@ -10,7 +10,7 @@ doCheck ? true, buildGitHubPages ? true, }: -pkgs.stdenv.mkDerivation rec { +pkgs.stdenvNoCC.mkDerivation rec { pname = "DiffDetective"; version = "2.2.0"; src = with pkgs.lib.fileset; From 22d97e5477d092a69d479b82261962a71d1cf65e Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Sun, 17 Mar 2024 17:27:02 +0100 Subject: [PATCH 33/41] Use the version in pom.xml as the single source of truth --- README.md | 2 +- default.nix | 6 +++++- pom.xml | 1 + replication/esecfse22/Dockerfile | 2 +- replication/splc23-views/Dockerfile | 2 +- scripts/genUltimateResults.sh | 2 +- scripts/runValidation.sh | 2 +- scripts/runViewFeasibilityStudy.sh | 2 +- scripts/version.sh | 6 ++++++ 9 files changed, 18 insertions(+), 7 deletions(-) create mode 100755 scripts/version.sh diff --git a/README.md b/README.md index 2b0fd7da..3ce8d2d2 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Next, build DiffDetective and install it on your system so that you can access i mvn install ``` -To add DiffDetective as a dependency to your own project, add the following snippet to the pom.xml of your Maven project, but make sure to pick the right version number. You can find the version number of DiffDetective at the top of the pom.xml file of DiffDetective. +To add DiffDetective as a dependency to your own project, add the following snippet to the pom.xml of your Maven project, but make sure to pick the right version number. The current version number can be obtained by running `scripts/version.sh` ```xml diff --git a/default.nix b/default.nix index d9479229..9e91d29b 100644 --- a/default.nix +++ b/default.nix @@ -12,7 +12,11 @@ }: pkgs.stdenvNoCC.mkDerivation rec { pname = "DiffDetective"; - version = "2.2.0"; + # The single source of truth for the version number is stored in `pom.xml`. + # Hence, this XML file needs to be parsed to extract the current version. + version = pkgs.lib.removeSuffix "\n" (pkgs.lib.readFile + (pkgs.runCommandLocal "DiffDetective-version" {} + "${pkgs.xq-xml}/bin/xq -x '/project/version' ${./pom.xml} > $out")); src = with pkgs.lib.fileset; toSource { root = ./.; diff --git a/pom.xml b/pom.xml index c2570176..5628799b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,7 @@ org.variantsync diffdetective + 2.2.0 diff --git a/replication/esecfse22/Dockerfile b/replication/esecfse22/Dockerfile index 61b12a77..50dc493b 100644 --- a/replication/esecfse22/Dockerfile +++ b/replication/esecfse22/Dockerfile @@ -32,7 +32,7 @@ WORKDIR /home/sherlock # Copy the compiled JAR file from the first stage into the second stage # Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH WORKDIR /home/sherlock/holmes -COPY --from=0 /home/user/target/diffdetective-2.2.0-jar-with-dependencies.jar ./DiffDetective.jar +COPY --from=0 /home/user/target/diffdetective-*-jar-with-dependencies.jar ./DiffDetective.jar WORKDIR /home/sherlock RUN mkdir results diff --git a/replication/splc23-views/Dockerfile b/replication/splc23-views/Dockerfile index 5bdaa231..dbb1a24b 100644 --- a/replication/splc23-views/Dockerfile +++ b/replication/splc23-views/Dockerfile @@ -38,7 +38,7 @@ WORKDIR /home/sherlock # Copy the compiled JAR file from the first stage into the second stage # Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH WORKDIR /home/sherlock/holmes -COPY --from=0 /home/user/target/diffdetective-2.2.0-jar-with-dependencies.jar ./DiffDetective.jar +COPY --from=0 /home/user/target/diffdetective-*-jar-with-dependencies.jar ./DiffDetective.jar WORKDIR /home/sherlock # Copy the setup diff --git a/scripts/genUltimateResults.sh b/scripts/genUltimateResults.sh index 35fd6d7c..e0d9fefa 100755 --- a/scripts/genUltimateResults.sh +++ b/scripts/genUltimateResults.sh @@ -1,4 +1,4 @@ resultsdir=$1 -java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator $resultsdir $resultsdir +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator $resultsdir $resultsdir echo "genUltimateResults.sh DONE" diff --git a/scripts/runValidation.sh b/scripts/runValidation.sh index db1638d6..57957668 100755 --- a/scripts/runValidation.sh +++ b/scripts/runValidation.sh @@ -1,2 +1,2 @@ -java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation echo "runValidation.sh DONE" diff --git a/scripts/runViewFeasibilityStudy.sh b/scripts/runViewFeasibilityStudy.sh index 3ff5993c..19721eeb 100755 --- a/scripts/runViewFeasibilityStudy.sh +++ b/scripts/runViewFeasibilityStudy.sh @@ -1,2 +1,2 @@ -java -cp "target/diffdetective-2.2.0-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" echo "runValidation.sh DONE" diff --git a/scripts/version.sh b/scripts/version.sh new file mode 100755 index 00000000..94c51d77 --- /dev/null +++ b/scripts/version.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# extracts the first version tag in pom.xml +sed -n '/\(.*\)<\/version.*/\1/; p; q}' pom.xml From ba621c545ee3758080346968fe35f76fca0788d2 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Sun, 17 Mar 2024 17:50:56 +0100 Subject: [PATCH 34/41] Make some scripts a little bit more robust --- scripts/genUltimateResults.sh | 8 ++++++-- scripts/runValidation.sh | 6 +++++- scripts/runViewFeasibilityStudy.sh | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/genUltimateResults.sh b/scripts/genUltimateResults.sh index e0d9fefa..a748d0e4 100755 --- a/scripts/genUltimateResults.sh +++ b/scripts/genUltimateResults.sh @@ -1,4 +1,8 @@ -resultsdir=$1 +#!/usr/bin/env bash -java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator $resultsdir $resultsdir +resultsdir="$1" + +cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1 + +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.tablegen.MiningResultAccumulator "$resultsdir" "$resultsdir" && echo "genUltimateResults.sh DONE" diff --git a/scripts/runValidation.sh b/scripts/runValidation.sh index 57957668..cebd8fcd 100755 --- a/scripts/runValidation.sh +++ b/scripts/runValidation.sh @@ -1,2 +1,6 @@ -java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation +#!/usr/bin/env bash + +cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1 + +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.validation.EditClassValidation && echo "runValidation.sh DONE" diff --git a/scripts/runViewFeasibilityStudy.sh b/scripts/runViewFeasibilityStudy.sh index 19721eeb..5f2f94d3 100755 --- a/scripts/runViewFeasibilityStudy.sh +++ b/scripts/runViewFeasibilityStudy.sh @@ -1,2 +1,6 @@ -java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" +#!/usr/bin/env bash + +cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1 + +java -cp "target/diffdetective-$(./scripts/version.sh)-jar-with-dependencies.jar" org.variantsync.diffdetective.experiments.views.Main "docs/datasets/views.md" && echo "runValidation.sh DONE" From 1ececbe84216ee57a9458c138b3f96cb14148e35 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Thu, 21 Mar 2024 19:26:18 +0100 Subject: [PATCH 35/41] Use reproducible time stamps for Maven build artifacts Unfortunately, JAR files store time stamps of the files inside them. These time stamps depend on the build time of these files hence breaking bit for bit reproducibility. By setting all time stamps to a predefined value and removing superfluous build artifacts this issue can be avoided. Conventionally, UNIX time stamp 1 signifies an intentionally set time stamp (in contrast to UNIX time stamp 0) which has no meaning and is easy to recognize. --- default.nix | 1 + pom.xml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/default.nix b/default.nix index e6d9dd4f..f6839855 100644 --- a/default.nix +++ b/default.nix @@ -67,6 +67,7 @@ pkgs.stdenv.mkDerivation rec { then '' mvn javadoc:javadoc JEKYLL_ENV=production PAGES_REPO_NWO=VariantSync/DiffDetective JEKYLL_BUILD_REVISION= github-pages build + rm -rf _site/target '' else "" } diff --git a/pom.xml b/pom.xml index c2570176..b518019d 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,8 @@ UTF-8 17 17 + + 1 From 63b5b0d364c0f3a1267b22c0dc57ccfeabcc3308 Mon Sep 17 00:00:00 2001 From: Paul Maximilian Bittner Date: Sat, 27 Apr 2024 12:33:03 +0200 Subject: [PATCH 36/41] VariationTree::fromFile(Path) --- .../diffdetective/variation/tree/VariationTree.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java index c53bfbc3..36cdff17 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java @@ -54,6 +54,14 @@ public VariationTree(VariationTreeNode root, VariationTreeSource source) { Assert.assertTrue(root.isRoot()); } + /** + * Same as {@link #fromFile(Path, VariationDiffParseOptions)} + * but with {@link VariationDiffParseOptions#Default} parse options. + */ + public static VariationTree fromFile(final Path path) throws IOException, DiffParseException { + return fromFile(path, VariationDiffParseOptions.Default); + } + /** * Same as {@link #fromFile(BufferedReader, VariationTreeSource, VariationDiffParseOptions)} * but registers {@code path} as source. From cc7380b8fea4490d51ae1e3af3d2a8e6d8a8463e Mon Sep 17 00:00:00 2001 From: Paul Maximilian Bittner Date: Sat, 27 Apr 2024 12:44:23 +0200 Subject: [PATCH 37/41] README: Add FSE'24 paper --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 581845f1..fa9f5c7b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Afterward, the [result](result) symlink points to the [Javadoc](result/share/git ## How to Get Started -For a demonstration on how to get started using the library, we have prepared a demo repository [here](https://github.com/VariantSync/DiffDetective-Demo). +For a demonstration on how to get started using the library, we have prepared a demo repository [here][demo]. You may clone it as a template and example for including the library into your own projects. Additionally, there is a screencast available on YouTube, guiding you through the demo's setup and source code: @@ -83,6 +83,16 @@ Additionally, there is a screencast available on YouTube, guiding you through th ## Publications +### Variability-Aware Differencing with DiffDetective (FSE 2024) + +[![Preprint](https://img.shields.io/badge/Preprint-Read-purple)](https://github.com/VariantSync/DiffDetective-Demo/blob/raw/Variability-Aware%20Differencing%20with%20DiffDetective.pdf) +[![Screencast](https://img.shields.io/badge/Screencast-Watch-purple)][screencast] +[![Demo Repository](https://img.shields.io/badge/Demo-Try-blue)][demo] + +> P. M. Bittner, A. Schultheiß, B. Moosherr, T. Kehrer, T. Thüm. _Variability-Aware Differencing with DiffDetective_. Demonstrations at International Conference on the Foundations of Software Engineering 2024, ACM, New York, NY, July 2024 + +This paper gives an overview of DiffDetective, its design, features, use-cases, and past case studies. We recommend reading this paper if you are interested in the design of DiffDetective or if you consider using it for your own projects or research. The paper is accompanied by a [demo project][demo] as well as a [screencast][screencast] (see `How to Get Started` above). + ### Classifying Edits to Variability in Source Code (ESEC/FSE 2022) [![Preprint](https://img.shields.io/badge/Preprint-Read-purple)](https://github.com/SoftVarE-Group/Papers/raw/main/2022/2022-ESECFSE-Bittner.pdf) @@ -156,3 +166,5 @@ DiffDetective was extended and used within bachelor's and master's theses: [documentation]: https://variantsync.github.io/DiffDetective/docs/javadoc [website]: https://variantsync.github.io/DiffDetective/ [forklg]: https://github.com/guethilu/DiffDetective +[demo]: https://github.com/VariantSync/DiffDetective-Demo +[screencast]: https://www.youtube.com/watch?v=q6ight5EDQY \ No newline at end of file From b459cac8a5e02d6fec946f6cd798b8c801dbed04 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Mon, 29 Apr 2024 07:53:05 +0200 Subject: [PATCH 38/41] nix: Conditionally remove the github-pages dependency If `buildGitHubPages` is `false`, the dependency shouldn't be included to reduce the build closure size. --- default.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/default.nix b/default.nix index 954b1677..93afba33 100644 --- a/default.nix +++ b/default.nix @@ -27,8 +27,10 @@ pkgs.stdenvNoCC.mkDerivation rec { maven makeWrapper graphviz - (ruby.withPackages (pkgs: with pkgs; [github-pages jekyll-theme-cayman])) - ]; + ] ++ pkgs.lib.optional buildGitHubPages (ruby.withPackages (pkgs: with pkgs; [ + github-pages + jekyll-theme-cayman + ])); mavenRepo = pkgs.stdenv.mkDerivation { pname = "${pname}-mavenRepo"; From 37ea216916fc10e77c179a54ae8aca0a543c8784 Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Mon, 29 Apr 2024 11:03:21 +0200 Subject: [PATCH 39/41] nix: Fix the PATH of graphviz --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 93afba33..98ab6c0c 100644 --- a/default.nix +++ b/default.nix @@ -97,7 +97,7 @@ pkgs.stdenvNoCC.mkDerivation rec { local jar="$out/share/java/DiffDetective/DiffDetective.jar" install -Dm644 "target/diffdetective-${version}-jar-with-dependencies.jar" "$jar" makeWrapper "${pkgs.jdk}/bin/java" "$out/bin/DiffDetective" --add-flags "-cp \"$jar\"" \ - --prefix PATH : "${pkgs.graphviz}" + --prefix PATH : "${pkgs.graphviz}/bin" ${ if buildGitHubPages From 4b984674b33307377b6282930798f34a177b5831 Mon Sep 17 00:00:00 2001 From: Paul Maximilian Bittner Date: Mon, 29 Apr 2024 19:09:17 +0200 Subject: [PATCH 40/41] fix: links to updated replication packages in README Previously, these links pointed to the README files of the replication packages, which made Gitlab download the READMEs instead of going to the subdirectory in the web browser. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4038464a..c3926f44 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ This paper gives an overview of DiffDetective, its design, features, use-cases, [![Paper](https://img.shields.io/badge/Paper-Read-purple)](https://dl.acm.org/doi/10.1145/3540250.3549108) [![Talk](https://img.shields.io/badge/Talk-Watch-purple)](https://www.youtube.com/watch?v=EnDx1AWxD24) [![Original Replication Package](https://img.shields.io/badge/Replication_Package-Original-blue)](https://github.com/VariantSync/DiffDetective/tree/esecfse22) -[![Updated Replication Package](https://img.shields.io/badge/Replication_Package-Updated-blue)](replication/esecfse22/README.md) +[![Updated Replication Package](https://img.shields.io/badge/Replication_Package-Updated-blue)](replication/esecfse22/) [![Artifact DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7110095.svg)](https://doi.org/10.5281/zenodo.7110095) > P. M. Bittner, C.Tinnes, A. Schultheiß, S. Viegener, T. Kehrer, T. Thüm. _Classifying Edits to Variability in Source Code_. In Proceedings of the 30th ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering (ESEC/FSE 2022), ACM, New York, NY, November 2022 @@ -120,7 +120,7 @@ The original replication package can be found on the [esecfse](https://github.co [![Preprint](https://img.shields.io/badge/Preprint-Read-purple)](https://github.com/SoftVarE-Group/Papers/raw/main/2023/2023-SPLC-Bittner.pdf) [![Paper](https://img.shields.io/badge/Paper-Read-purple)](https://dl.acm.org/doi/10.1145/3579027.3608985) [![Original Replication Package](https://img.shields.io/badge/Replication_Package-Original-blue)](https://github.com/VariantSync/DiffDetective/tree/splc23-views/replication/splc23-views) -[![Updated Replication Package](https://img.shields.io/badge/Replication_Package-Updated-blue)](replication/splc23-views/README.md) +[![Updated Replication Package](https://img.shields.io/badge/Replication_Package-Updated-blue)](replication/splc23-views/) [![Artifact DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8027920.svg)](https://doi.org/10.5281/zenodo.8027920) > P. M. Bittner, A. Schultheiß, S. Greiner, B. Moosherr, S. Krieter, C. Tinnes, T. Kehrer, T. Thüm. _Views on Edits to Variational Software_. In Proceedings of the 27th ACM International Systems and Software Product Line Conference (SPLC 2023), ACM, New York, NY, August 2023 From 80f834646500b704ea6d959b5a7cd517ecf17f1b Mon Sep 17 00:00:00 2001 From: Benjamin Moosherr Date: Mon, 29 Apr 2024 19:45:25 +0200 Subject: [PATCH 41/41] nix: Reduce the closure size by using a minimal JRE Apparently, there is no more canonical JRE because `pkgs.jre` is an alias for `pkgs.jdk`. However, nixpkgs provides `jre_minimal` which can be used to exclude unused modules. I created the list of used modules by running the ESEC/FSE 2024 demo and adding modules until it stopped complaining. --- default.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 98ab6c0c..235ed586 100644 --- a/default.nix +++ b/default.nix @@ -59,6 +59,10 @@ pkgs.stdenvNoCC.mkDerivation rec { outputHash = "sha256-Gimt6L54yyaX3BtdhQlVu1j4c4y++Mip0GzMl/IfzMc="; }; + jre-minimal = pkgs.callPackage (import "${sources.nixpkgs}/pkgs/development/compilers/openjdk/jre.nix") { + modules = ["java.base" "java.desktop"]; + }; + buildPhase = '' runHook preBuild @@ -96,7 +100,7 @@ pkgs.stdenvNoCC.mkDerivation rec { local jar="$out/share/java/DiffDetective/DiffDetective.jar" install -Dm644 "target/diffdetective-${version}-jar-with-dependencies.jar" "$jar" - makeWrapper "${pkgs.jdk}/bin/java" "$out/bin/DiffDetective" --add-flags "-cp \"$jar\"" \ + makeWrapper "${jre-minimal}/bin/java" "$out/bin/DiffDetective" --add-flags "-cp \"$jar\"" \ --prefix PATH : "${pkgs.graphviz}/bin" ${