left <=> right is a tautology.
*/
public static boolean equivalent(final Node left, final Node right) {
- return isTautology(new Equals(left, right));
+ return isTautology(FormulaUtils.equivalent(left, right));
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/variantsync/diffdetective/datasets/CommitLister.java b/src/main/java/org/variantsync/diffdetective/datasets/CommitLister.java
new file mode 100644
index 000000000..07a066a24
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/datasets/CommitLister.java
@@ -0,0 +1,46 @@
+package org.variantsync.diffdetective.datasets;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.tinylog.Logger;
+
+/**
+ * A functional interface for listing the commits in a {@link Repository}.
+ * Apart from listing a fixed set of commits, this is mainly useful to configure a {@link org.eclipse.jgit.api.Git#log() Git log} call.
+ *
+ * This is mainly intended to be used as an argument for the {@code listCommits} argument of
+ * {@link Repository#Repository(RepositoryLocationType, Path, URI, String, Function
+ * Defaults to {@link CommitLister#TraverseHEAD}, {@link PatchDiffParseOptions#Default} and {@link DiffFilter#ALLOW_ALL}.
+ */
+ public Repository(
+ final RepositoryLocationType repoLocation,
+ final Path localPath,
+ final URI remote,
+ final String repositoryName) {
+ this(
+ repoLocation,
+ localPath,
+ remote,
+ repositoryName,
+ CommitLister.TraverseHEAD,
+ PatchDiffParseOptions.Default,
+ DiffFilter.ALLOW_ALL
+ );
+ }
+
+ /**
+ * Creates a repository from an existing directory.
+ *
+ * @param dirPath The path to the repo directory relative to {@code
+ * In contrast to {@link ControllingCExpressionVisitor},
+ * this class resolves the {@code ENABLED} and {@code DISABLED} macros
+ * that are used in Marlin to check for features being (de-)selected.
+ *
+ * @author Paul Bittner, Benjamin Moosherr
+ */
+public class MarlinControllingCExpressionVisitor extends ControllingCExpressionVisitor {
+ @Override
+ public Node visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) {
+ if (ctx.macroExpression() != null) {
+ TerminalNode name = ctx.macroExpression().Identifier();
+ List
- * The commits from the repository are first filtered using the given DiffFilter.
- * Then a CommitDiff is created for each commit.
- * File changes in each commit are filtered using the given DiffFilter.
- * Then a PatchDiff is created from each file change.
- * Finally, each patch is parsed to a VariationDiff.
+ * Then a {@link CommitDiff} is created for each commit.
+ * File changes in each commit are filtered using the {@link DiffFilter} of the {@link Repository#getDiffFilter() repository}.
+ * Then a {@link PatchDiff} is created from each file change.
+ * Finally, each patch is parsed to a {@link VariationDiff}.
*
* @author Soeren Viegener, Paul Maximilian Bittner
*/
@@ -56,111 +48,33 @@ public class GitDiffer {
private static final Pattern NO_NEWLINE_PATTERN = Pattern.compile(
"(" + StringUtils.LINEBREAK_REGEX.pattern() + ")(?m)\\\\ No newline at end of file$");
- private final Git git;
- private final DiffFilter diffFilter;
- private final PatchDiffParseOptions parseOptions;
-
- /**
- * Create a differ operating on the given repository.
- * @param repository The repository for whose history to obtain diffs.
- */
- public GitDiffer(final Repository repository) {
- this.git = repository.getGitRepo().run();
- this.diffFilter = repository.getDiffFilter();
- this.parseOptions = repository.getParseOptions();
- }
-
- /**
- * Returns all commits in the repository's history.
- */
- public Yield
+ * This honors the {@link Repository#getDiffFilter() diff filter}
+ * and the {@link Repository#getParseOptions() parser options} of the repository.
*
- * @param git The git repo which the commit stems from.
+ * @param repository The git repo which the commit stems from.
* @param currentCommit The commit from which to create a CommitDiff
- * @param parseOptions
* @return The CommitDiff of the given commit
*/
public static CommitDiffResult createCommitDiffFromFirstParent(
- Git git,
- DiffFilter diffFilter,
- RevCommit currentCommit,
- final PatchDiffParseOptions parseOptions) {
+ Repository repository,
+ RevCommit currentCommit) {
final RevCommit parent;
if (currentCommit.getParentCount() > 0) {
- try (var revWalk = new RevWalk(git.getRepository())) {
+ try (var revWalk = new RevWalk(repository.getGitRepo().getRepository())) {
parent = revWalk.parseCommit(currentCommit.getParent(0).getId());
} catch (IOException e) {
return CommitDiffResult.Failure(DiffError.JGIT_ERROR, "Could not parse parent commit of " + currentCommit.getId().getName() + "!");
@@ -169,22 +83,23 @@ public static CommitDiffResult createCommitDiffFromFirstParent(
parent = null;
}
- return createCommitDiff(git, diffFilter, parent, currentCommit, parseOptions);
+ return createCommitDiff(repository, parent, currentCommit);
}
/**
* Creates a CommitDiff that describes all changes made by the
* given childCommit to the given parentCommit.
+ *
+ * This honors the {@link Repository#getDiffFilter() diff filter}
+ * and the {@link Repository#getParseOptions() parser options} of the repository.
*
- * @param git The git repo which the commits stem from.
+ * @param repository The git repo which the commit stems from.
* @return The CommitDiff describing all changes between the two commits.
*/
public static CommitDiffResult createCommitDiff(
- Git git,
- DiffFilter diffFilter,
+ Repository repository,
RevCommit parentCommit,
- RevCommit childCommit,
- final PatchDiffParseOptions parseOptions) {
+ RevCommit childCommit) {
if (childCommit.getTree() == null) {
return CommitDiffResult.Failure(DiffError.JGIT_ERROR, "Could not obtain RevTree from child commit " + childCommit.getId());
}
@@ -195,7 +110,7 @@ public static CommitDiffResult createCommitDiff(
// get TreeParsers
final CanonicalTreeParser currentTreeParser = new CanonicalTreeParser();
final CanonicalTreeParser prevTreeParser = new CanonicalTreeParser();
- try (ObjectReader reader = git.getRepository().newObjectReader()) {
+ try (ObjectReader reader = repository.getGitRepo().getRepository().newObjectReader()) {
try {
currentTreeParser.reset(reader, childCommit.getTree());
if (parentCommit != null) {
@@ -214,9 +129,7 @@ public static CommitDiffResult createCommitDiff(
}
return getPatchDiffs(
- git,
- diffFilter,
- parseOptions,
+ repository,
parentTreeIterator,
currentTreeParser,
parentCommit,
@@ -225,43 +138,42 @@ public static CommitDiffResult createCommitDiff(
}
/**
- * The same as {@link GitDiffer#createCommitDiff(Git, DiffFilter, RevCommit, RevCommit, PatchDiffParseOptions)}
+ * The same as {@link #createCommitDiff(Repository, RevCommit, RevCommit)}
* but diffs the given commit against the current working tree.
*
- * @param git The git repo which the commit stems from
+ * @param repository The git repo which the commit stems from
* @param commit The commit which the working tree is compared with
- * @param parseOptions {@link PatchDiffParseOptions}
* @return The CommitDiff of the given commit
*/
public static CommitDiffResult createWorkingTreeDiff(
- Git git,
- DiffFilter diffFilter,
- RevCommit commit,
- final PatchDiffParseOptions parseOptions) {
+ Repository repository,
+ RevCommit commit) {
if (commit != null && commit.getTree() == null) {
return CommitDiffResult.Failure(DiffError.JGIT_ERROR, "Could not obtain RevTree from child commit " + commit.getId());
}
- // get TreeParsers
- final AbstractTreeIterator workingTreeIterator = new FileTreeIterator(git.getRepository());
+ // get TreeParsers
+ final AbstractTreeIterator workingTreeIterator = new FileTreeIterator(repository.getGitRepo().getRepository());
+
final AbstractTreeIterator prevTreeIterator;
if (commit == null) {
prevTreeIterator = new EmptyTreeIterator();
- } else try (ObjectReader reader = git.getRepository().newObjectReader()) {
+ } else try (ObjectReader reader = repository.getGitRepo().getRepository().newObjectReader()) {
prevTreeIterator = new CanonicalTreeParser(null, reader, commit.getTree());
} catch (IOException e) {
return CommitDiffResult.Failure(DiffError.JGIT_ERROR, e.toString());
}
- return getPatchDiffs(git, diffFilter, parseOptions, prevTreeIterator, workingTreeIterator, commit, commit);
+ return getPatchDiffs(repository, prevTreeIterator, workingTreeIterator, commit, commit);
}
-
+
/**
* Obtains the CommitDiff between two commit's trees.
+ *
+ * This honors the {@link Repository#getDiffFilter() diff filter}
+ * and the {@link Repository#getParseOptions() parser options} of the repository.
*
- * @param git The git repo which the commit stems from
- * @param diffFilter {@link DiffFilter}
- * @param parseOptions {@link PatchDiffParseOptions}
+ * @param repository The git repo which the commit stems from
* @param prevTreeParser The tree parser for parentCommit
* @param currentTreeParser The tree parser for childCommit or the working tree
* @param parentCommit The {@link RevCommit} for the parent commit
@@ -269,27 +181,25 @@ public static CommitDiffResult createWorkingTreeDiff(
* @return {@link CommitDiffResult}
*/
private static CommitDiffResult getPatchDiffs(
- Git git,
- DiffFilter diffFilter,
- final PatchDiffParseOptions parseOptions,
- AbstractTreeIterator prevTreeParser,
- AbstractTreeIterator currentTreeParser,
- RevCommit parentCommit,
- RevCommit childCommit) {
- final CommitDiff commitDiff = new CommitDiff(childCommit, parentCommit);
+ Repository repository,
+ AbstractTreeIterator prevTreeParser,
+ AbstractTreeIterator currentTreeParser,
+ RevCommit parentCommit,
+ RevCommit childCommit) {
+ final CommitDiff commitDiff = new CommitDiff(childCommit, parentCommit);
final List
- * 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').
- *
* See {@link PreprocessorAnnotationParser} for an example of how an implementation of AnnotationParser could look like.
*
- * 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);
- }
- }
-
- private static final List
- * Search for the first replacement that matches the entire text and apply it. This is the case, if the given text
- * corresponds to a single token (e.g., '&&', '||'). If no replacement for the entire text is found (e.g., if the token
- * has no replacement), all possible replacements are applied to abstract substrings of the token that require
- * abstraction.
- * The purpose of this method is to achieve a slight speedup for scenarios in which the text usually contains a single
- * token. For example, this is useful when abstracting individual tokens of an extracted preprocessor formula
- * in {@link AbstractingCExpressionVisitor}. In all other cases, directly calling {@link #abstractAll(String)} should
- * be preferred.
- *
- * 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").
- *
- * Note that this pattern doesn't handle comments between {@code #} and the macro name.
- */
- protected final static Pattern CPP_PATTERN =
- Pattern.compile("^[+-]?\\s*#\\s*(if|elif|else|endif)");
-
+public abstract class PreprocessorAnnotationParser implements AnnotationParser {
/**
- * 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.
+ * Pattern that is used to extract the {@link AnnotationType} and the associated {@link org.prop4j.Node formula}.
*
+ * The pattern needs to contain at least the two named capture groups {@code directive} and {@code formula}.
+ * The {@code directive} group must match a string that can be processed by {@link #parseAnnotationType}
+ * and whenever the resulting annotation type {@link AnnotationType#requiresFormula requires a formula},
+ * the capture group {@code formula} needs to match the formula that should be processed by {@link parseFormula}.
*/
- protected 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);
- }
+ protected final Pattern annotationPattern;
/**
* 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
+ * This method is only called if {@code directive} actually requires a formula as determined by {@link #parseAnnotationType}.
*
- * @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.
+ * @param directive as matched by the named capture group {@code directive} of {@link annotationPattern}
+ * @param formula as matched by the named capture group {@code formula} of {@link annotationPattern}
+ * @return the feature mapping
+ * @throws UnparseableFormulaException if {@code formula} is ill-formed.
*/
- 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;
- }
- }
+ protected abstract Node parseFormula(String directive, String formula) throws UnparseableFormulaException;
}
diff --git a/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java b/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java
deleted file mode 100644
index b470af7d3..000000000
--- a/src/main/java/org/variantsync/diffdetective/feature/PropositionalFormulaParser.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.variantsync.diffdetective.feature;
-
-import org.prop4j.Literal;
-import org.prop4j.Node;
-import org.prop4j.NodeReader;
-import org.variantsync.diffdetective.util.fide.FixTrueFalse;
-
-/**
- * A parser that parses propositional formula's from text to {@link Node}s.
- * @author Paul Bittner
- */
-@FunctionalInterface
-public interface PropositionalFormulaParser {
- /*
- * Parses a formula from a string.
- * @param text A propositional formula written as text.
- * @return The formula if parsing succeeded. Null if parsing failed somehow.
- */
- Node parse(String text);
-
- /**
- * Default parser that uses the {@link NodeReader} from FeatureIDE
- * and uses its {@link NodeReader#activateJavaSymbols() java symbols} to
- * match operators.
- */
- PropositionalFormulaParser Default = text -> {
- final NodeReader nodeReader = new NodeReader();
- nodeReader.activateJavaSymbols();
-
- Node node = nodeReader.stringToNode(text);
-
- // if parsing succeeded
- if (node != null) {
- // TODO: Is this our desired behaviour?
- // If so, should we document it by not using get here
- // and instead keeping the witness that this call happened?
- 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/AbstractingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java
index c317e4e20..3c4ad65ac 100644
--- a/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java
+++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/AbstractingCExpressionVisitor.java
@@ -1,192 +1,53 @@
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.RuleNode;
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}.
+ * Unparses the syntax tree into a string for use as a boolean abstraction.
+ * This visitor produces almost the same string as
+ * {@link org.antlr.v4.runtime.tree.ParseTree#getText()}
+ * i.e., the result is the string passed to the parser where all characters ignored by the parser
+ * are removed. Removed characters are mostly whitespace and comments, although the semantics of the
+ * strings are equivalent (e.g., spaces in strings literals are kept).
+ * The difference lies in some special cases:
+ *
+ *
- * 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.
- *
- * First, the visitor uses ANTLR to parse the formula into a parse tree gives the tree to a {@link ControllingCExpressionVisitor}.
- * 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.
- *
- * 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 abstracts the part of the formula in the subtree.
- * null if local.
- */
- private final URI remote;
+ /**
+ * The local path where the repository can be found or should be cloned to.
+ */
+ private final Path localPath;
+
+ /**
+ * The remote url of the repository. May be null if local.
+ */
+ private final URI remote;
+
+ /**
+ * The name of the repository. Used for debugging.
+ */
+ private final String repositoryName;
- /**
- * The name of the repository. Used for debugging.
- */
- private final String repositoryName;
+ /**
+ * A function that extracts the list of commits that are represented by this repository instance.
+ */
+ private CommitLister commitLister;
- /**
- * Filter determining which files and commits to consider for diffs.
- */
- private DiffFilter diffFilter;
+ /**
+ * Filter determining which files and commits to consider for diffs.
+ */
+ private DiffFilter diffFilter;
/**
* Options to configure parsing and memory consumption (e.g., by not keeping full diffs in memory).
*/
- private PatchDiffParseOptions parseOptions;
-
- private final Lazynull if local.
- * @param repositoryName Name of the cloned repository (null if local)
- * @param parseOptions Omit some debug data to save RAM.
- * @param diffFilter Filter determining which files and commits to consider for diffs.
- */
- public Repository(
- final RepositoryLocationType repoLocation,
- final Path localPath,
- final URI remote,
- final String repositoryName,
- final PatchDiffParseOptions parseOptions,
- final DiffFilter diffFilter) {
- this.repoLocation = repoLocation;
- this.localPath = localPath;
- this.remote = remote;
- this.repositoryName = repositoryName;
- this.parseOptions = parseOptions;
- this.diffFilter = diffFilter;
- }
-
- /**
- * Creates repository of the given source and with all other settings set to default values.
- * @see Repository
- */
- public Repository(
- final RepositoryLocationType repoLocation,
- final Path localPath,
- final URI remote,
- final String repositoryName) {
- this(repoLocation, localPath, remote, repositoryName,
- PatchDiffParseOptions.Default, DiffFilter.ALLOW_ALL);
- }
-
- /**
- * Creates a repository from an existing directory.
- *
- * @param dirPath The path to the repo directory relative to {@code null if local.
+ * @param repositoryName Name of the cloned repository (null if local)
+ * @param commitLister extracts the commits from {@link #getGitRepo()} that should be represented.
+ * @param parseOptions Omit some debug data to save RAM.
+ * @param diffFilter Filter determining which files and commits to consider for diffs.
+ */
+ public Repository(
+ final RepositoryLocationType repoLocation,
+ final Path localPath,
+ final URI remote,
+ final String repositoryName,
+ final CommitLister commitLister,
+ final PatchDiffParseOptions parseOptions,
+ final DiffFilter diffFilter) {
+ this.repoLocation = repoLocation;
+ this.localPath = localPath;
+ this.remote = remote;
+ this.repositoryName = repositoryName;
+ this.commitLister = commitLister;
+ this.parseOptions = parseOptions;
+ this.diffFilter = diffFilter;
+ }
+
+ /**
+ * Creates repository of the given source and with all other settings set to default values.
+ * @see Repository
+ * > split(List
==.
- */
- 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 <=.
- */
- public static final String LEQ = "__LEQ__";
- /**
- * Abstraction value for greater checks >.
- */
- public static final String GT = "__GT__";
- /**
- * Abstraction value for smaller checks <.
- */
- public static final String LT = "__LT__";
- /**
- * Abstraction value for subtractions -.
- */
- public static final String SUB = "__SUB__";
- /**
- * Abstraction value for additions +.
- */
- public static final String ADD = "__ADD__";
- /**
- * Abstraction value for multiplications *.
- */
- public static final String MUL = "__MUL__";
- /**
- * Abstraction value for divisions /.
- */
- public static final String DIV = "__DIV__";
- /**
- * Abstraction value for modulo %.
- */
- public static final String MOD = "__MOD__";
- /**
- * Abstraction value for bitwise left shift <<.
- */
- public static final String LSHIFT = "__LSHIFT__";
- /**
- * Abstraction value for bitwise right shift >>.
- */
- public static final String RSHIFT = "__RSHIFT__";
- /**
- * Abstraction value for bitwise not ~.
- */
- public static final String NOT = "__NOT__";
- /**
- * Abstraction value for bitwise and &.
- */
- public static final String AND = "__AND__";
- /**
- * Abstraction value for bitwise or |.
- */
- public static final String OR = "__OR__";
- /**
- * Abstraction value for bitwise xor ^.
- */
- public static final String XOR = "__XOR__";
- /**
- * 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.
- */
- public static final String COLON = "__COLON__";
- /**
- * Abstraction value for opening brackets (.
- */
- public static final String BRACKET_L = "__LB__";
- /**
- * Abstraction value for closing brackets ).
- */
- public static final String BRACKET_R = "__RB__";
- /**
- * Abstraction value for unary 'and' &.
- */
- public static final String U_AND = "__U_AND__";
- /**
- * Abstraction value for unary star *.
- */
- public static final String U_STAR = "__U_STAR__";
- /**
- * Abstraction value for unary plus +.
- */
- public static final String U_PLUS = "__U_PLUS__";
- /**
- * Abstraction value for unary minus -.
- */
- public static final String U_MINUS = "__U_MINUS__";
- /**
- * Abstraction value for unary tilde ~.
- */
- public static final String U_TILDE = "__U_TILDE__";
- /**
- * Abstraction value for unary not !.
- */
- public static final String U_NOT = "__U_NOT__";
- /**
- * Abstraction value for logical and &&.
- */
- public static final String L_AND = "__L_AND__";
- /**
- * Abstraction value for logical or ||.
- */
- public static final String L_OR = "__L_OR__";
- /**
- * Abstraction value for dots in paths ..
- */
- public static final String DOT = "__DOT__";
- /**
- * Abstraction value for quotation marks in paths ".
- */
- public static final String QUOTE = "__QUOTE__";
- /**
- * Abstraction value for single quotation marks '.
- */
- public static final String SQUOTE = "__SQUOTE__";
- /**
- * Abstraction value for assign operator =.
- */
- public static final String ASSIGN = "__ASSIGN__";
- /**
- * Abstraction value for star assign operator *=.
- */
- public static final String STAR_ASSIGN = "__STA___ASSIGN__";
- /**
- * Abstraction value for div assign operator /=.
- */
- public static final String DIV_ASSIGN = "__DIV___ASSIGN__";
- /**
- * Abstraction value for mod assign operator %=.
- */
- public static final String MOD_ASSIGN = "__MOD___ASSIGN__";
- /**
- * Abstraction value for plus assign operator +=.
- */
- public static final String PLUS_ASSIGN = "__PLU___ASSIGN__";
- /**
- * Abstraction value for minus assign operator -=.
- */
- public static final String MINUS_ASSIGN = "__MIN___ASSIGN__";
- /**
- * Abstraction value for left shift assign operator <<=.
- */
- public static final String LEFT_SHIFT_ASSIGN = "__LSH___ASSIGN__";
- /**
- * Abstraction value for right shift assign operator >>=.
- */
- public static final String RIGHT_SHIFT_ASSIGN = "__RSH___ASSIGN__";
- /**
- * Abstraction value for 'and' assign operator &=.
- */
- public static final String AND_ASSIGN = "__AND___ASSIGN__";
- /**
- * Abstraction value for xor assign operator ^=.
- */
- public static final String XOR_ASSIGN = "__XOR___ASSIGN__";
- /**
- * Abstraction value for 'or' assign operator |=.
- */
- public static final String OR_ASSIGN = "__OR___ASSIGN__";
- /**
- * Abstraction value for whitespace .
- */
- public static final String WHITESPACE = "_";
- /**
- * 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).
- * 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.
- */
- 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.
- */
- 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.
- */
- 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.
- */
- public static final String HAS_INCLUDE = "HAS_INCLUDE_";
- /**
- * 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) {
- /**
- * @param pattern the literal string to be replaced if it matches a whole word
- * @param replacement the replacement with special escape codes according to
- * {@link Matcher#replaceAll}
- */
- 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 but only on word
- * boundaries.
- * 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
index 2a50916fc..2f5e5a518 100644
--- a/src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java
+++ b/src/main/java/org/variantsync/diffdetective/feature/ParseErrorListener.java
@@ -7,7 +7,7 @@
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 org.variantsync.diffdetective.error.UncheckedUnparseableFormulaException;
import java.util.BitSet;
@@ -31,7 +31,7 @@ public ParseErrorListener(String formula) {
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);
+ throw new UncheckedUnparseableFormulaException(s, e);
}
@Override
diff --git a/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java
index 5828a61b6..002e95496 100644
--- a/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java
+++ b/src/main/java/org/variantsync/diffdetective/feature/PreprocessorAnnotationParser.java
@@ -2,118 +2,90 @@
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.Matcher;
import java.util.regex.Pattern;
/**
* A parser of preprocessor-like annotations.
*
* @author Paul Bittner, Alexander Schultheiß
+ * @see org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser
+ * @see org.variantsync.diffdetective.feature.jpp.JPPAnnotationParser
*/
-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.
- * f in #if f).
- * @param formulaExtractor An extractor that extracts the formula part of a preprocessor annotation that is then given to the formulaParser.
+ * @param annotationPattern pattern that identifies the {@link AnnotationType} and the associated {@link org.prop4j.Node formula} of an annotation
+ * @see #annotationPattern
*/
- public PreprocessorAnnotationParser(final Pattern annotationPattern, final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) {
+ public PreprocessorAnnotationParser(Pattern annotationPattern) {
this.annotationPattern = annotationPattern;
- this.formulaParser = formulaParser;
- 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);
+ @Override
+ public Annotation parseAnnotation(final String line) throws UnparseableFormulaException {
+ // Match the formula from the macro line
+ final Matcher matcher = annotationPattern.matcher(line);
+ if (!matcher.find()) {
+ return new Annotation(AnnotationType.None);
+ }
+ String directive = matcher.group("directive");
+ String formula = matcher.group("formula");
+ AnnotationType annotationType = parseAnnotationType(directive);
+
+ if (!annotationType.requiresFormula) {
+ return new Annotation(annotationType);
+ }
+
+ if (annotationType.requiresFormula && formula == null) {
+ throw new UnparseableFormulaException("Annotations of type " + annotationType.name + " require a formula but none was given");
+ }
+
+ return new Annotation(annotationType, parseFormula(directive, formula));
}
/**
- * 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.
+ * Converts the string captured by the named capture group {@code directive} of {@link #annotationPattern} into an {@link AnnotationType}.
*/
- public static PreprocessorAnnotationParser CreateJppAnnotationParser(final PropositionalFormulaParser formulaParser, DiffLineFormulaExtractor formulaExtractor) {
- return new PreprocessorAnnotationParser(JPP_PATTERN, formulaParser, formulaExtractor);
+ protected AnnotationType parseAnnotationType(String directive) {
+ if (directive.startsWith("if")) {
+ return AnnotationType.If;
+ } else if (directive.startsWith("elif")) {
+ return AnnotationType.Elif;
+ } else if (directive.equals("else")) {
+ return AnnotationType.Else;
+ } else if (directive.equals("endif")) {
+ return AnnotationType.Endif;
+ }
+
+ throw new IllegalArgumentException("The directive " + directive + " is not a valid conditional compilation directive");
}
/**
- * Parses the condition of the given line of source code that contains a preprocessor macro (i.e., IF, IFDEF, ELIF).
+ * Parses the feature formula of a preprocessor annotation line.
+ * It should abstract complex formulas (e.g., if they contain arithmetics or macro calls) as desired.
+ * For example, for the line {@code "#if A && B == C"},
+ * this method is should be called like {@code parseFormula("if", "A && B == C")}
+ * (the exact arguments are determined by {@link annotationPattern}
+ * and it should return something like {@code and(var("A"), var("B==C"))}.
+ *
+ *
*
- * t #).
- * @param inFile Path to the linegraph file that is currently parsed.
- * @param diffNodeList All nodes of the VariationDiff that is to be created. The nodes can be assumed to be complete and already connected.
- * @param options {@link LineGraphImportOptions}
- * @return {@link VariationDiff} generated from the given, already parsed parameters.
- */
- private static VariationDifft #).
+ * @param inFile Path to the linegraph file that is currently parsed.
+ * @param diffNodeList All nodes of the VariationDiff that is to be created. The nodes can be assumed to be complete and already connected.
+ * @param options {@link LineGraphImportOptions}
+ * @return {@link VariationDiff} generated from the given, already parsed parameters.
+ */
+ private static VariationDiff
"))
- + "\"";
- }
+ @Override
+ public String toLabel(final DiffNode extends L> node) {
+ return node.diffType + "_" + node.getNodeType() + "_\"" +
+ DiffNodeLabelPrettyfier.prettyPrintIfAnnotationOr(
+ node,
+ FileUtils.replaceLineEndings(node.getLabel().toString().trim().replaceAll("\t", " "), "
"))
+ + "\"";
+ }
}
diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java
index f55fce280..378497db9 100644
--- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java
+++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java
@@ -16,16 +16,16 @@
*/
@FunctionalInterface
public interface DiffNodeLabelFormat