diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..69782c3e2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +# We let Git handle end_of_line such that this setting is effectively platform +# dependent but still normalized in the Git store. See .gitattributes + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.nix] +indent_size = 2 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index cca83e878..a94e1ea36 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -16,17 +16,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install Nix with cached packages - uses: rikhuijzer/cache-install@v.1.1.3 - with: - key: nix-${{ hashFiles('.github/workflows/maven.yml', 'default.nix', 'nix/**', 'pom.xml', 'local-maven-repo') }} - nix_file: nix/github-workflow-dependencies.nix + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v31 + # The dependencies could be cached if necessary. See PR #156 and da11a13c1451f5a52672fe494ac6e20116346865 for additional information. - name: Build run: nix-build - name: Upload Javadoc artifact if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }} - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: result/share/github-pages/DiffDetective @@ -50,7 +47,7 @@ jobs: steps: - name: Publish Javadoc to Github Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 # Kill in progress deployments because only the newest version is relevant # and concurrent deployments cause CI failures. diff --git a/README.md b/README.md index 656e4e0f5..1bc6aa74c 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ To add DiffDetective as a dependency to your own project, add the following snip org.variantsync DiffDetective - 2.2.0 + 2.3.0 ``` -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). +If you prefer to just use a jar file, you can find a jar file with all dependencies at `DiffDetective/target/diffdetective-2.3.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. @@ -68,7 +68,7 @@ nix build # Flake version ``` In case you are using Nix Flakes, you can skip cloning the repository as usual: `nix build github:VariantSync/DiffDetective#.` -Afterward, the [result](result) symlink points to the [Javadoc](result/share/github-pages/DiffDetective/docs/javadoc/index.html), the [DiffDetective jar](result/share/java/DiffDetective/DiffDetective.jar) and a simple [script](result/bin/DiffDetective) for executing a DiffDetective main class provided as argument (e.g., evaluations used in previous research, see below under 'Publications'). +Afterward, the [result](result) symlink points to the [Javadoc](result/share/github-pages/DiffDetective/docs/javadoc/index.html), a [DiffDetective jar for use as a library](result/share/java/DiffDetective.jar), a [self contained DiffDetective jar with all dependencies included](result/share/java/DiffDetective/DiffDetective-jar-with-dependencies.jar) and a simple [script](result/bin/DiffDetective) for executing a DiffDetective main class provided as argument (e.g., evaluations used in previous research, see below under 'Publications'). ## How to Get Started diff --git a/default.nix b/default.nix index 555854cfe..9503cc2f2 100644 --- a/default.nix +++ b/default.nix @@ -3,65 +3,111 @@ system ? builtins.currentSystem, pkgs ? import sources.nixpkgs { - overlays = []; + overlays = [ + (final: previous: { + defaultGemConfig = previous.defaultGemConfig // { + jekyll-github-metadata = attrs: { + dontBuild = false; + patches = [ + (final.fetchpatch { + url = "https://github.com/jekyll/github-metadata/commit/17cc5af5e1fd95d98d43676610cc8a47969350ab.patch"; + hash = "sha256-dUqvnYsjfG5xQIYS48B3xz0GLVYo2BrDAnYUafmDFKw="; + relative = "lib"; + stripLen = 1; + extraPrefix = "lib/jekyll-github-metadata/"; + }) + ]; + }; + }; + }) + ]; config = {}; inherit system; }, doCheck ? true, buildGitHubPages ? true, + dependenciesHash ? "sha256-OdagSk6jYCkkw/kPoOJlma9yEK7hMBcNkuxE6qt0ra8=", }: pkgs.stdenvNoCC.mkDerivation rec { pname = "DiffDetective"; # 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")); + (pkgs.runCommandLocal "DiffDetective-version" { + nativeBuildInputs = [pkgs.xq-xml]; + } "xq -x '/project/version' ${./pom.xml} > $out")); src = with pkgs.lib.fileset; toSource { root = ./.; fileset = gitTracked ./.; }; - nativeBuildInputs = with pkgs; [ - maven - makeWrapper - graphviz - ] ++ pkgs.lib.optional buildGitHubPages (ruby.withPackages (pkgs: with pkgs; [ - github-pages - jekyll-theme-cayman + nativeBuildInputs = [ + pkgs.maven + pkgs.makeWrapper + ] ++ pkgs.lib.optional buildGitHubPages (pkgs.ruby.withPackages (rubyPkgs: [ + rubyPkgs.github-pages + rubyPkgs.jekyll-theme-cayman ])); + nativeCheckInputs = [ + pkgs.graphviz + ]; + + # Maven needs to download necessary dependencies which is impure because it + # requires network access. Hence, we download all dependencies as a + # fixed-output derivation. This also serves as a nice cache. + # + # We use the hash of the input files to invalidate the maven cache whenever + # the input files change. This is purely for easing maintenance. In case a + # maintainer forgets (or simply doesn't know) to change the output hash to + # force a rebuild (and obtain the new, correct hash) this usually (without + # this naming hack) result in use of a stall cash, essentially behaving like + # an unreproducable derivation where a second build results in a different + # output. By changing the name of the fixed output derivation whenever + # `mavenRepoSrc` changes, we prevent this stall cache as the resulting store + # path will never exist (except when the cache is valid). + mavenRepoSrc = pkgs.lib.sourceByRegex ./. ["^pom.xml$" "^local-maven-repo(/.*)?$"]; + mavenRepoSrcName = with pkgs.lib; last (splitString "/" mavenRepoSrc.outPath); mavenRepo = pkgs.stdenv.mkDerivation { - pname = "${pname}-mavenRepo"; + pname = "${pname}-mavenRepo-${mavenRepoSrcName}"; inherit version; - src = pkgs.lib.sourceByRegex ./. ["^pom.xml$" "^local-maven-repo(/.*)?$"]; + src = mavenRepoSrc; - nativeBuildInputs = with pkgs; [maven]; + nativeBuildInputs = [pkgs.maven]; buildPhase = '' + runHook preBuild + mvn org.apache.maven.plugins:maven-dependency-plugin:3.6.0:go-offline -Dmaven.repo.local="$out" + + runHook postBuild ''; # keep only *.{pom,jar,sha1,nbm} and delete all ephemeral files with lastModified timestamps inside installPhase = '' + runHook preInstall + find "$out" -type f \ - \( -name \*.lastUpdated -or \ - -name resolver-status.properties -or \ - -name _remote.repositories \) \ + \( -not \( -name "*.pom" -o -name "*.jar" -o -name "*.sha1" -o -name "*.nbm" \) \ + -o -name "maven-metadata*" \) \ -delete + + runHook postInstall ''; dontFixup = true; dontConfigure = true; outputHashAlgo = "sha256"; outputHashMode = "recursive"; - outputHash = "sha256-TYZP4XhM3ExLNC3H/QLch6LMVQxbR1LECwubMZn+RXY="; + outputHash = dependenciesHash; }; - jre-minimal = pkgs.callPackage (import "${sources.nixpkgs}/pkgs/development/compilers/openjdk/jre.nix") { - modules = ["java.base" "java.desktop"]; - }; + # - `out` contains jars, an executable wrapper and optionally documentation + # (see `buildGitHubPages`) + # - `maven` contains a local maven repository with DiffDetective and all its + # build-time and run-time dependencies. + outputs = ["out" "maven"]; buildPhase = '' runHook preBuild @@ -76,7 +122,8 @@ pkgs.stdenvNoCC.mkDerivation rec { if buildGitHubPages then '' mvn javadoc:javadoc - JEKYLL_ENV=production PAGES_REPO_NWO=VariantSync/DiffDetective JEKYLL_BUILD_REVISION= github-pages build + mv target/reports/apidocs docs/javadoc + JEKYLL_ENV=production PAGES_REPO_NWO=VariantSync/DiffDetective JEKYLL_BUILD_REVISION= PAGES_DISABLE_NETWORK=1 github-pages build rm -rf _site/target '' else "" @@ -88,7 +135,7 @@ pkgs.stdenvNoCC.mkDerivation rec { inherit doCheck; checkPhase = '' - runHook postTest + runHook preTest mvn --offline -Dmaven.repo.local="$mavenRepo" test @@ -96,22 +143,38 @@ pkgs.stdenvNoCC.mkDerivation rec { ''; installPhase = '' - runHook postInstall + runHook preInstall - local jar="$out/share/java/DiffDetective/DiffDetective.jar" - install -Dm644 "target/diffdetective-${version}-jar-with-dependencies.jar" "$jar" - makeWrapper "${jre-minimal}/bin/java" "$out/bin/DiffDetective" --add-flags "-cp \"$jar\"" \ - --prefix PATH : "${pkgs.graphviz}/bin" + # install jars in "$out" + install -Dm644 "target/diffdetective-$version.jar" "$out/share/java/DiffDetective.jar" + local jar="$out/share/java/DiffDetective/DiffDetective-jar-with-dependencies.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.lib.makeBinPath [pkgs.graphviz]}" ${ if buildGitHubPages then '' + # install documentation in "$out" mkdir "$out/share/github-pages" cp -r _site "$out/share/github-pages/DiffDetective" '' else "" } + # install DiffDetective in "$maven" by creating a copy of "$mavenRepo" as base + cp -r "$mavenRepo" "$maven" + chmod u+w -R "$maven" + mvn --offline -Dmaven.repo.local="$maven" -Dmaven.test.skip=true install + + # keep only *.{pom,jar,sha1,nbm} and delete all ephemeral files with lastModified timestamps inside + find "$maven" -type f \ + \( -not \( -name "*.pom" -o -name "*.jar" -o -name "*.sha1" -o -name "*.nbm" \) \ + -o -name "maven-metadata*" \) \ + -delete + runHook postInstall ''; diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index c61cde92c..f69b5d2a0 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,9 +1,9 @@ #!/bin/sh if [ "$(id -u)" = "0" ]; then - # running on a developer laptop as root - fix-perms -r -u user -g user /home/user - exec gosu user "$@" + # running on a developer laptop as root + fix-perms -r -u user -g user /home/user + exec gosu user "$@" else - # running in production as a user - exec "$@" -fi \ No newline at end of file + # running in production as a user + exec "$@" +fi diff --git a/docker/fix-perms.sh b/docker/fix-perms.sh index 8c7159cc2..bdc0e55eb 100755 --- a/docker/fix-perms.sh +++ b/docker/fix-perms.sh @@ -1,12 +1,12 @@ # update the uid if [ -n "$opt_u" ]; then - OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:) - NEW_UID=$(stat -c "%u" "$1") - if [ "$OLD_UID" != "$NEW_UID" ]; then - echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID" - usermod -u "$NEW_UID" -o "$opt_u" - if [ -n "$opt_r" ]; then - find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \; + OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:) + NEW_UID=$(stat -c "%u" "$1") + if [ "$OLD_UID" != "$NEW_UID" ]; then + echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID" + usermod -u "$NEW_UID" -o "$opt_u" + if [ -n "$opt_r" ]; then + find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \; + fi fi - fi -fi \ No newline at end of file +fi diff --git a/docs/replication/create-forks-on-github.sh b/docs/replication/create-forks-on-github.sh index 3ad387d8e..7cac88519 100755 --- a/docs/replication/create-forks-on-github.sh +++ b/docs/replication/create-forks-on-github.sh @@ -10,22 +10,22 @@ DRY_RUN=n #DRY_RUN=n continue-with() { - echo - read -p "Do you want to continue with $1? [y/N] " answer - [ "$answer" == "y" ] + echo + read -p "Do you want to continue with $1? [y/N] " answer + [ "$answer" == "y" ] } run() { - echo - echo "\$ $*" - if [ "$DRY_RUN" = "n" ] - then - "$@" - fi + echo + echo "\$ $*" + if [ "$DRY_RUN" = "n" ] + then + "$@" + fi } repos() { - find "$PATH_TO_REPOSITORIES" -mindepth 1 -maxdepth 1 -type d "$@" + find "$PATH_TO_REPOSITORIES" -mindepth 1 -maxdepth 1 -type d "$@" } PATH_TO_REPOSITORIES="$(realpath "$PATH_TO_REPOSITORIES")" @@ -36,42 +36,42 @@ continue-with "these $(repos -print0 | tr -d -c '\0' | tr '\0' '\n' | wc -l) rep if gh auth status |& grep -q 'You are not logged into any GitHub hosts.' &>/dev/null then - run gh auth login || exit 1 - was_logged_in=0 + run gh auth login || exit 1 + was_logged_in=0 else - echo - gh auth status + echo + gh auth status - continue-with "this account" || - { - run gh auth logout && - run gh auth login || exit 1 - } - was_logged_in=1 + continue-with "this account" || + { + run gh auth logout && + run gh auth login || exit 1 + } + was_logged_in=1 fi repos -print0 | while IFS= read -d '' -r repository do - echo - run cd "$repository" - url="$(git remote get-url origin)" - if [[ "$url" =~ github.com ]] - then - echo "$repository is a github repo" - run gh repo fork --remote || echo "already forked" - run git push -f origin - else - echo "$repository is not a github repo" - run git remote rename origin upstream &>/dev/null - run gh repo create "DiffDetective/$(basename "$repository")" -d "Fork of $url" --push --public --source . - fi - echo "repo succesful" + echo + run cd "$repository" + url="$(git remote get-url origin)" + if [[ "$url" =~ github.com ]] + then + echo "$repository is a github repo" + run gh repo fork --remote || echo "already forked" + run git push -f origin + else + echo "$repository is not a github repo" + run git remote rename origin upstream &>/dev/null + run gh repo create "DiffDetective/$(basename "$repository")" -d "Fork of $url" --push --public --source . + fi + echo "repo succesful" done if [ "$was_logged_in" = "1" ] then - cat < 1: t = (t_max + t_min) // 2 found_patterns = run_approximate(lib_path, data_path + in_file, data_path + 'frequent_temp.cstring', t) - + if found_patterns >= target_count: - t_min = t + t_min = t else: t_max = t - + # Write found threshold to file with open(data_path + str(database_id) + '.threshold', 'w') as f: - f.write(str(t)) - - + f.write(str(t)) + + def get_db_size(file_name): with open(file_name) as f: return int(f.read()) - + def count_subgraphs(subgraph_file): ''' Assume a AIDS format. In this format, every graph is represented by 3 lines.''' i = None @@ -65,34 +65,34 @@ def count_subgraphs(subgraph_file): return ceil((i + 1) / 3) else: return 0 - + def run_approximate(lib_path, input_file, output_file, threshold): ''' We fix the maximum length to l, i.e., we are mining patterns at most l nodes large. ''' - + # Run HOPS approximate subgraph miner lwg_cmd_template = "'{lib_path}lwg' -t {threshold} -p 8 -e hops -i 5 '{input_file}' -o '{output_file}'" miner_cmd = lwg_cmd_template.format(lib_path=lib_path, input_file=input_file, output_file=output_file, threshold=threshold) - + p = subprocess.Popen(miner_cmd, shell=True) - + try: p.wait(30) # Should take at most 30 seconds except Exception as e: print(str(e)) - p.kill() - + p.kill() + # transform output file (so that we can easier count the number of patterns later cstring_cmd = "cat " + output_file + " | xargs -I {} bash -c \"echo '{}' | '" + lib_path + "cstring' -i -\" > " + output_file + ".tmp" - + subprocess.run(cstring_cmd, shell = True) #os.remove(output_file) nb_subgraphs = count_subgraphs(output_file+".tmp") #os.remove(output_file+".tmp") return nb_subgraphs - + if __name__ == "__main__": if len(sys.argv) == 4: - bisect_threshold(sys.argv[1], sys.argv[2], target_count = sys.argv[3]) + bisect_threshold(sys.argv[1], sys.argv[2], target_count = sys.argv[3]) else: print("Unexpected number of arguments. Run as python bisect_threshold_search.py [lib_path] [data_path] [target_count]. lwg and cstring tool binaries need to be located in the lib_path. Data directiory is expected to contain .lg line graph databases and .count files with the number of graphs in the corresponding database.") diff --git a/mining/compute_components.py b/mining/compute_components.py index 38c5ba365..46d7598d8 100644 --- a/mining/compute_components.py +++ b/mining/compute_components.py @@ -26,7 +26,7 @@ def connected_components(graph): def get_graph_components(graphs, filtered=False, filter_config=None): ''' Each of the given graphs is devided into its connected components. These components are then optionally filtered according to the filter_config. - + :return: a tuble of a list of all remaining graph components, an array with the number of components per input graph, and the total number of removed graphs ''' components = [] @@ -38,11 +38,11 @@ def get_graph_components(graphs, filtered=False, filter_config=None): nb_of_components_per_graph.append(len(new_components)) components += new_components if filtered: - + components, filtered = filter_too_large(*filter_too_many_similar_nodes(components, {}, filter_config.filter_too_many_similar_max_similar, filter_config.filter_too_many_similar_max_nodes), filter_config.filter_too_large_nb_nodes, filter_config.filter_too_large_nb_edges) - + #print("We have %d connected components in %d graphs. From these components %d have beend filtered." % (len(components) + sum(filtered.values()), len(graphs), filtered_total)) - + return components, nb_of_components_per_graph, filtered # Filters components with more than nb_nodes/nb_edges nodes/edges. Use -1 for infinity. @@ -52,8 +52,8 @@ def filter_too_large(components: list, filtered: dict, nb_nodes=18, nb_edges=40) for component in components: if not (nb_nodes != -1 and (component.number_of_nodes() > nb_nodes or component.number_of_edges() > nb_edges)): new_components.append(component) - - + + filtered["too_large"] = len(components)-len(new_components) #print("Filtered out %d components that are too large, i.e., more than %d nodes or %d edges" % (filtered["too_large"], nb_nodes, nb_edges)) return new_components, filtered @@ -68,7 +68,7 @@ def filter_too_many_similar_nodes(components: list, filtered: dict, max_similar= # if there are more than max_similar node labels with more than max_nodes if not (np.sum(np.array(list(labels.values())) > max_nodes) > max_similar): new_components.append(component) - + filtered["too_many_similar"] = len(components)-len(new_components) #print("Filtered out %d components with too many similar nodes, i.e., more than %d labels appeared more than %d times" % (filtered["too_many_similar"] , max_similar, max_nodes)) return new_components, filtered @@ -93,13 +93,13 @@ def get_components(input_folder, formatting=INPUT_FORMAT_NX, filtered=False): if formatting == INPUT_FORMAT_LG: graphs = import_tlv_folder(input_folder, parse_support=False) components, nb_of_components_per_diff, filtered_total = get_graph_components(graphs, filtered=filtered, filter_config = FilterConfig()) - - + + do_statistics(components) return components, nb_of_components_per_diff, filtered_total -def do_statistics(components): +def do_statistics(components): have_specialization = [component for component in components if has_node(component, 'c4')] have_generalization = [component for component in components if has_node(component, 'c5')] have_refactoring = [component for component in components if has_node(component, 'c7')] @@ -118,16 +118,16 @@ def do_statistics(components): def has_node(graph, label): return label in [node[1]['label'] for node in list(graph.nodes(data=True))] - + def main(input_folder, output_folder, dataset_name, max_components_per_file=200, formatting=INPUT_FORMAT_NX): # TODO doing this with streams and yield would be a nicer solution to the chunking. components, nb_of_components_per_diff, filtered = get_components(input_folder, formatting=formatting, filtered=True) for component in components: if not nx.is_directed_acyclic_graph(component): print(f"WARN: THERE ARE NON DAG GRAPHS IN THE INPUT: {component.name}") - + components_batched = [components[i*max_components_per_file:min((i+1)*max_components_per_file, len(components))] for i in range(ceil(len(components)/max_components_per_file))] - + # Create output folder if it doesn't exist yet os.makedirs(output_folder, exist_ok=True) @@ -143,7 +143,7 @@ def main(input_folder, output_folder, dataset_name, max_components_per_file=200, with open(output_folder + '/filter_stats.csv', 'w') as f: # ds name, all components, components after filtering, filtered too large, filtered too many similar - + f.write(dataset_name + "," + str(len(components) + filtered["too_large"] + filtered["too_many_similar"]) + "," + str(len(components)) + "," + str(filtered["too_large"])+ "," + str(filtered["too_many_similar"])) if __name__ == "__main__": @@ -151,7 +151,7 @@ def main(input_folder, output_folder, dataset_name, max_components_per_file=200, if len(sys.argv) == 5: main(sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])) elif len(sys.argv) == 6: - main(sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]), formatting = sys.argv[5]) + main(sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]), formatting = sys.argv[5]) else: print("Unexpected number of arguments. At least input path, output path, dataset name, as well as batch_size has to be provided. Optionally as a fourth argument put NX or LG indicating the input graph formatting.") end_time = time.time() diff --git a/mining/extract_subgraphs.sh b/mining/extract_subgraphs.sh index 42073be1d..00efc2e09 100755 --- a/mining/extract_subgraphs.sh +++ b/mining/extract_subgraphs.sh @@ -7,9 +7,10 @@ mkdir -p "$output_base/temp/" # Run for every dataset in input folder for dataset in "$input_base"/*/ ; do dataset_name="$(basename "$dataset")" - subgraphs_file="$dataset/mining_no_duplicates/results_no_duplicates.lg" + subgraphs_file="$dataset/mining_no_duplicates/results_no_duplicates.lg" if [ -e $subgraphs_file ] - then cp "$subgraphs_file" "$output_base/temp/$dataset_name.lg" + then + cp "$subgraphs_file" "$output_base/temp/$dataset_name.lg" fi done diff --git a/mining/isograph.py b/mining/isograph.py index 36203f4d2..d53eb54ba 100644 --- a/mining/isograph.py +++ b/mining/isograph.py @@ -21,52 +21,52 @@ def remove_duplicates(graphs: List[nx.DiGraph]) -> List[nx.DiGraph]: return list(set(graphs)) class IsoGraph(nx.DiGraph): - + def set_label(self, label_name: str): self.label_name = label_name - + def __eq__(self, other: nx.DiGraph): if not hasattr(self, 'label_name') or self.label_name is None: self.label_name = "label" - + nm = iso.categorical_node_match(self.label_name, "") em = iso.categorical_edge_match(self.label_name, "") return nx.is_isomorphic(self, other, node_match=nm, edge_match=em) - + def __hash__(self): ''' Computes a hash based on the Weisfeiler-Lehman test For a unique hash, something like the gSpan DFS code has to be used. This would be a complex operation. - + WL hash requires networkx version 2.6.2! ''' return int(nx.weisfeiler_lehman_graph_hash(self), base=16) - + def contains(self, subgraph_candidate): nm = iso.categorical_node_match(self.label_name, "") em = iso.categorical_edge_match(self.label_name, "") GM = iso.DiGraphMatcher(self, subgraph_candidate, node_match=nm, edge_match=em) #DiGM.subgraph_monomorphisms_iter( return GM.subgraph_is_monomorphic()#, GM.mapping - + def cut_root(self): ''' Removes the (single) root of the DiGraph and all outgoing edges. Fails if graph is not single-rooted. ''' root = self.get_roots() assert len(root) == 1 - + self.remove_nodes_from(root) return self - + def is_single_rooted(self) -> bool: return len(self.get_roots()) == 1 - + def get_roots(self) -> List[int]: ''' Computes all roots of a DiGraph. - + :return: a list of node_ids of the graph. ''' - # A root has in-degree = 0 + # A root has in-degree = 0 return [n for n,d in self.in_degree() if d==0] diff --git a/mining/parse_utils.py b/mining/parse_utils.py index f9c1a1ad9..67d4bfa2e 100644 --- a/mining/parse_utils.py +++ b/mining/parse_utils.py @@ -182,8 +182,8 @@ def convert_node_link_graph_to_subdue_python_graph(input_file, output_file): # Add edge the_file.write(']\n') - - + + ########################### Line Graph Parsers/Serializers ##################################################### # Export TLV (gSpan consumes TLV) def export_TLV(graph_db, path): @@ -199,7 +199,7 @@ def export_TLV(graph_db, path): label = "UNKNOWN_LABEL" else: label = data['label'] - + f.write("v " + str(node) + " " + label + '\n') edges = temp_graph.edges(data=True) for source, target, data in edges: @@ -213,14 +213,14 @@ def export_TLV(graph_db, path): def import_tlv_folder(folder_path, postfix='.lg', parse_support=True): ''' - See import_tlv. Just iterates over the folder and concats. + See import_tlv. Just iterates over the folder and concats. Doesn't take graph isomorphisms into account, i.e., isomorphic graphs in different files could appear as duplicates. ''' if parse_support: graphs = {} else: graphs = [] - + for file in os.listdir(folder_path): if file.endswith(postfix): new_graphs = import_tlv(os.path.join(folder_path, file), parse_support) @@ -234,7 +234,7 @@ def import_tlv(path, parse_support=True): ''' Parses the given file as line graph and (optionally) parses the support of the graph. There are multiple different formats to obtain the support from. It returns a dictionary of graphs with their support or a list of all graphs if no support parsing is desired. - + params: path, the path to the line graph file parse_support: true, if support should also be parsed returns: a dictionary dict(DiGraph, int) with the graphs and their suppor, if parse_support=True, else a list of graphs @@ -248,11 +248,11 @@ def import_tlv(path, parse_support=True): regex_header = r"t # (.*)" regex_node = r"v (\d+) (.+).*" regex_edge = r"e (\d+) (\d+) (.+).*" - + # Some file formats give the support directly, others list all the embeddings. We support both options. regex_support = r"Support: (\d+).*" regex_embedding = r"#=> ([^\s]+) .*" - + # if tlv header continue parsing match_header = re.match(regex_header, next_line) if match_header: @@ -268,7 +268,7 @@ def import_tlv(path, parse_support=True): support = None match_header = None - + while next_line and not match_header: match_node = re.match(regex_node, next_line) match_edge = re.match(regex_edge, next_line) @@ -285,7 +285,7 @@ def import_tlv(path, parse_support=True): next_line = graph_db.readline() if next_line: match_header = re.match(regex_header, next_line) - + if support_set is not None: graph.graph['embeddings'] = str(support_set) diff --git a/mining/pipeline.sh b/mining/pipeline.sh index 8e68d0fdd..7b8c6d1fe 100755 --- a/mining/pipeline.sh +++ b/mining/pipeline.sh @@ -3,10 +3,6 @@ # call as pipline.sh [input-path] [output-path] [lib-path] # e.g. ./pipeline.sh '/home/schwinnez/projects/Frequent Subgraph Mining/case studies/variability_patterns/dataset_1/diffgraphs/' '/home/schwinnez/projects/Frequent Subgraph Mining/case studies/variability_patterns/dataset_1/output/' './lib/' - - - - python_version=$(python3 --version) echo "Using python version $python_version" # TODO automatically make sure that python 3 is used @@ -27,42 +23,39 @@ mkdir -p "$2" run_dataset(){ - echo "Input: $1" - echo "Output: $2" - echo "Libs: $3" + echo "Input: $1" + echo "Output: $2" + echo "Libs: $3" - # Definition of output directories - data_set_name="$4" - input_path="$1" - output_filtered="$2filtered/" - output_mining="$2mining/" - output_mining_no_duplicates="$2mining_no_duplicates/" - lib_path="$3" - parsemis_path="$3parsemis.jar" + # Definition of output directories + data_set_name="$4" + input_path="$1" + output_filtered="$2filtered/" + output_mining="$2mining/" + output_mining_no_duplicates="$2mining_no_duplicates/" + lib_path="$3" + parsemis_path="$3parsemis.jar" - echo "Running dataset: $data_set_name" - mkdir -p "$2" + echo "Running dataset: $data_set_name" + mkdir -p "$2" - # Step 1 - Read graph databases, filter and chunk - Default filter config: Not larger than 15 nodes, 30 edges, no more than - python3 compute_components.py "$input_path" "$output_filtered" "$data_set_name" $batch_size LG + # Step 1 - Read graph databases, filter and chunk - Default filter config: Not larger than 15 nodes, 30 edges, no more than + python3 compute_components.py "$input_path" "$output_filtered" "$data_set_name" $batch_size LG - # Step 2 - Compute thresholds - Not better than fixed threshold for Linux dataset - #python bisect_threshold_search.py $lib_path $output_filtered $target_subtree_count_for_threshold_estimation + # Step 2 - Compute thresholds - Not better than fixed threshold for Linux dataset + #python bisect_threshold_search.py $lib_path $output_filtered $target_subtree_count_for_threshold_estimation - # Step 3 - Mining - python3 run_parsemis.py "$parsemis_path" "$output_filtered" "$output_mining" $threshold $min_size $max_size $timeout_mining + # Step 3 - Mining + python3 run_parsemis.py "$parsemis_path" "$output_filtered" "$output_mining" $threshold $min_size $max_size $timeout_mining - # Step 4 - Remove duplicates - python3 remove_duplicates.py "$output_mining" "$output_mining_no_duplicates" "$data_set_name" + # Step 4 - Remove duplicates + python3 remove_duplicates.py "$output_mining" "$output_mining_no_duplicates" "$data_set_name" } # Run for every dataset in input folder for input_folder in "$1"/*/ ; do - dataset="$(basename "$input_folder")" - output_base="$2/$batch_size-$threshold/$dataset/" - run_dataset "$input_folder" "$output_base" "$3" "$dataset" + dataset="$(basename "$input_folder")" + output_base="$2/$batch_size-$threshold/$dataset/" + run_dataset "$input_folder" "$output_base" "$3" "$dataset" done - - - diff --git a/mining/plot_utils.py b/mining/plot_utils.py index d321e169a..2e66d21bc 100644 --- a/mining/plot_utils.py +++ b/mining/plot_utils.py @@ -35,4 +35,4 @@ def plot_graph_dot(G, file_path, labels=True): plt.title(G.name) pos = graphviz_layout(G, prog='dot') nx.draw(G, pos, with_labels=labels, arrows=True) - plt.savefig(file_path) \ No newline at end of file + plt.savefig(file_path) diff --git a/mining/remove_duplicates.py b/mining/remove_duplicates.py index a6efa8ac0..4d8fdd28d 100644 --- a/mining/remove_duplicates.py +++ b/mining/remove_duplicates.py @@ -15,7 +15,7 @@ def main(subgraphs_path, results_dir, dataset, output_single=False): nb_pruned_subgraphs = len(subgraphs) removed_duplicates = nb_initial_subgraphs - nb_pruned_subgraphs print("Removed %d duplicates" % removed_duplicates) - + # Load graphs print("Writing subgraphs.") if output_single: @@ -23,17 +23,17 @@ def main(subgraphs_path, results_dir, dataset, output_single=False): export_TLV([subgraphs[i]], results_dir + '/' + str(i) + '.lg') else: export_TLV(subgraphs, results_dir + '/results_no_duplicates.lg') - + with open(results_dir + '/results.csv', 'w') as f: f.write(dataset + "," + str(nb_initial_subgraphs) +"," + str(nb_pruned_subgraphs) + "," + str(removed_duplicates)) - + if __name__ == "__main__": if len(sys.argv) < 4 or len(sys.argv) > 5: print("Three or four arguments expected: path to input graph database, path to output results, and optionally if every subgraph should be written to a single file. Run as python remove_duplicates.py [input_folder] [output_folder] [dataset_name] [True/False]") - + # Create output folder if it doesn't exist yet os.makedirs(sys.argv[2], exist_ok=True) - + start_time = time.time() if len(sys.argv) == 4: main(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/mining/run_parsemis.py b/mining/run_parsemis.py index fb2de2850..b5d7a8e06 100644 --- a/mining/run_parsemis.py +++ b/mining/run_parsemis.py @@ -14,9 +14,9 @@ def get_available_memory(fraction=0.8): with open('/proc/meminfo') as f: meminfo = f.read() matched = re.search(r'^MemTotal:\s+(\d+)', meminfo) - if matched: + if matched: mem_total_gig = int(matched.groups()[0])/(1024*1024) - + available_mem = math.ceil(mem_total_gig * fraction) return available_mem @@ -25,30 +25,30 @@ def run_parsemis(lib_path, in_folder, output_folder, threshold, min_size, max_si memory = str(get_available_memory()) + 'G' nb_threads = os.cpu_count() # Template for shell command shell command - parsemis_cmd_template = "java -Xmx{memory} -jar '{parsemis_path}' --graphFile='{in_file}' --outputFile='{out_file}' --minimumFrequency={threshold} --maximumNodeCount={max_size} --minimumNodeCount={min_size} --algorithm=dagma --storeEmbeddings=true --distribution=threads --threads={nb_threads} --swapFile='{swap_file}'" + parsemis_cmd_template = "java -Xmx{memory} -jar '{parsemis_path}' --graphFile='{in_file}' --outputFile='{out_file}' --minimumFrequency={threshold} --maximumNodeCount={max_size} --minimumNodeCount={min_size} --algorithm=dagma --storeEmbeddings=true --distribution=threads --threads={nb_threads} --swapFile='{swap_file}'" #--closeGraph=true --zaretsky=true --subdue=true --singleRooted=true print(f'Running Frequent Subgraph Mining for input folder: {in_folder}') # Currently only support for line graph for idx, in_file in enumerate([file_name for file_name in os.listdir(in_folder) if file_name.endswith('.lg')]): match_id = re.match(regex_file_id, in_file) - + if not match_id: assert False, "Filename for not formatted as expected." - - + + database_id = int(match_id.group(1)) - + # Read threshold from file (if no threshold is given) if threshold == 0: with open(in_folder + str(database_id) + ".threshold", 'r') as f: threshold = int(f.read()) - + parsemis_cmd = parsemis_cmd_template.format(parsemis_path=lib_path, in_file=in_folder + in_file, out_file=output_folder+'/results_' + str(database_id) + '.lg', swap_file=output_folder+'\swap.tmp', threshold=threshold, max_size=max_size, min_size=min_size, memory=memory, nb_threads=nb_threads ) # Run command (REQUIRES JAVA 8!!!) - + print(f'Running Frequent Subgraph Mining. Command: {parsemis_cmd}') - + try: p = subprocess.Popen(parsemis_cmd, shell=True, start_new_session=True) error_code = p.wait(timeout=timeout_seconds) @@ -58,9 +58,9 @@ def run_parsemis(lib_path, in_folder, output_folder, threshold, min_size, max_si os.killpg(os.getpgid(p.pid), signal.SIGKILL) with open(output_folder+'/results_'+ str(database_id) + '.err', 'w') as f: f.write(str(to)) - + continue - + if error_code != 0: print('Error. Got code:' + str(error_code)) with open(output_folder+'/results_'+ str(database_id) + '.err', 'w') as f: @@ -72,10 +72,10 @@ def run_parsemis(lib_path, in_folder, output_folder, threshold, min_size, max_si if __name__ == "__main__": if len(sys.argv) < 8: print("Call like python run_parsemis.py [lib_path] [in_folder] [out_folder] [threshold] [min_size] [max_size] [timeout_seconds]") - + # Create output folder if it doesn't exist yet os.makedirs(sys.argv[3], exist_ok=True) - + start_time = time.time() run_parsemis(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6], int(sys.argv[7])) end_time = time.time() diff --git a/mining/subgraph_statistics.py b/mining/subgraph_statistics.py index 5f01bb63a..5a962fc84 100644 --- a/mining/subgraph_statistics.py +++ b/mining/subgraph_statistics.py @@ -20,11 +20,11 @@ class LatticeNode(): '''Wraps IsoGraphs in a data structure for a lattice, i.e., links every graph to it's parents or childrens, respectively. Furthermore, the lattice nodes have pointers to graphs in a graph_database where they are contained in (i.e., their occurrences). ''' - + def __init__(self, graph: IsoGraph, label_name = 'label'): self.graph = graph self.graph.set_label(label_name) - + self.occurrences = [] self.parents = [] self.children = [] @@ -38,9 +38,9 @@ def __init__(self, graph: IsoGraph, label_name = 'label'): def __str__(self): return str(self.graph.name) - + __repr__ = __str__ - + class Lattice(): def __init__(self, nodes: List[LatticeNode]): @@ -50,10 +50,10 @@ def __init__(self, nodes: List[LatticeNode]): self.layers = dict() self._compute_layers() - # TODO this lattice generation is very complex -> Try to simplify and reduce cognitive overhead. + # TODO this lattice generation is very complex -> Try to simplify and reduce cognitive overhead. # TODO e.g., bidirectional parent-child relationship could be handled automatically,... There is also a huge duplication related to this. def _compute_lattice_dfs(self, child=None, current_path=[]): - ''' + ''' Efficient method to compute the subgraph lattice. Note that this method does not yet yield the right layer for every lattice node. To compute the layers, run _compute_layers. ''' @@ -63,10 +63,10 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): node.no_parent_candidates.add(node) reachable_nodes = self._compute_lattice_dfs(child=node, current_path = [node]) return - + if child.discovered: return - + for node in list(set(self.nodes)-child.no_parent_candidates): if not node.discovered : if node.graph.contains(child.graph): @@ -86,11 +86,11 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): if upper_parent in current_path[depth].reachable_nodes.keys(): current_path[depth].reachable_nodes[upper_parent] = max(length_to_node + node.reachable_nodes[upper_parent], current_path[depth].reachable_nodes[upper_parent]) else: - current_path[depth].reachable_nodes[upper_parent] = length_to_node + node.reachable_nodes[upper_parent] + current_path[depth].reachable_nodes[upper_parent] = length_to_node + node.reachable_nodes[upper_parent] else: # tracking this helps us speed up the lattice construction, since we can save some expensive subgraph monomorphism checks - child.no_parent_candidates.add(node) - else: + child.no_parent_candidates.add(node) + else: # check if we've discovered a longer path already (we want no shortcuts, always the full hiearchy in our child-pattern relationships) if not node in child.reachable_nodes.keys(): if node.graph.contains(child.graph): @@ -112,7 +112,7 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): # Handle parent-child relationship node.children.append(child) child.parents.append(node) - + # At last, we need to handle a situation in which short-cuts might still be present. This can happen when they have been created bevor the other "paths" have been completly discoverd. # We can see the shortcuts now, because a shortcut means that a parent's reachability (which is the longest path to the node) is > 1 parents_to_be_removed = [] @@ -120,14 +120,14 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): if child.reachable_nodes[parent] > 1: # Have to remember what we want to delete, because we can not remove while iterating over a list (mixing up iterator in python) parents_to_be_removed.append(parent) - parent.children.remove(child) + parent.children.remove(child) for parent in parents_to_be_removed: child.parents.remove(parent) - + # Node has finally been fully discovered child.discovered = True return child.reachable_nodes - + def _compute_layers(self): # Compute the max layer for each node (we need the maximum for bottom-up algos, to be sure in every new layer to know already all the children) for node in self.nodes: @@ -140,16 +140,16 @@ def _compute_layers(self): self.layers[node_layer] = [node] else: self.layers[node_layer].append(node) - - + + def to_graphml(self): nx_lattice = self.to_networkx(include_graphs = False) return "\n".join(generate_graphml(nx_lattice)) - + def to_networkx(self, include_graphs=True): '''Represents the lattice as a networkX graph''' nx_lattice = nx.DiGraph() - + # TODO deferring id resolution could speed up this graph generation (but should be fine with the lattice size we will face in practise) for i in range(len(self.nodes)): node = self.nodes[i] @@ -157,34 +157,34 @@ def to_networkx(self, include_graphs=True): nx_lattice.add_node(i, label = node.graph.name, graph = node.graph) else: nx_lattice.add_node(i, label = node.graph.name) - + for node in self.nodes: for child in node.children: nx_lattice.add_edge(self.nodes.index(node), self.nodes.index(child), label="subgraph") - + return nx_lattice - - - + + + # TODO only very simple printing of lattice. Proper plotting should be done in the future for debug purposes def describe(self, verbose=False): lines = [] for layer in sorted(self.layers.keys()): lines.append("Layer:\t %d; Nodes: \t %s" % (layer, "|".join([node.graph.name for node in self.layers[layer]]))) - + if verbose: lines += self._describe_verbose() - + return "\n".join(lines) - - + + def _describe_verbose(self): lines = [] for node in self.nodes: lines.append("Node:\t %s; Children: \t %s" %(node.graph.name, "|".join([child.graph.name for child in node.children]))) lines.append("Layer: %d; Reachable: %s" %(node.layer, node.reachable_nodes)) return lines - + def count_occurrences(self, graph_database: List[IsoGraph]): for layer in sorted(self.layers.keys()): for node in self.layers[layer]: @@ -192,12 +192,12 @@ def count_occurrences(self, graph_database: List[IsoGraph]): occurrence_candidates = list(set.intersection(*[set(child.occurrences) for child in node.children])) else: occurrence_candidates = range(len(graph_database)) - + for occurrence_candidate in occurrence_candidates: if graph_database[occurrence_candidate].contains(node.graph): node.occurrences.append(occurrence_candidate) - - + + class Statistics(): ''' Provides basic counting statistics for a set of subgraphs and a graph database. @@ -215,14 +215,14 @@ def __init__(self, graph_db: List[nx.DiGraph], subgraphs: List[nx.DiGraph], labe self.occurrences_references = {} self.compressions = {} self.lattice = lattice - + def _set_label_name(self): for graph in self.graph_db: graph.set_label(self.label_name) for graph in self.subgraphs: graph.set_label(self.label_name) - - + + def _name_subgraphs(self): ''' Simply given an identifier to graphs that don't have one yet. @@ -232,7 +232,7 @@ def _name_subgraphs(self): if self.override_names or graph.name is None or len(graph.name) == 0: graph.name=str(i) i+=1 - + def compute_occurrences_brute_force(self): ''' For a more efficient computation, see compute_occurrences_lattice_based. @@ -244,8 +244,8 @@ def compute_occurrences_brute_force(self): # todo count the occurrences instead of just "is there" if graph.contains(subgraph): occurrences+=1 - self.occurrences_transaction[subgraph.name] = occurrences - + self.occurrences_transaction[subgraph.name] = occurrences + def compute_occurrences_lattice_based(self): if self.lattice is None: # First create the lattice node for the subgraphs @@ -271,7 +271,7 @@ def write_as_csv(self, save_path, additional_tag): additional_tag: some additional tags to add, e.g., when later merging different results ''' os.makedirs(os.path.dirname(save_path), exist_ok=True) - + with open(save_path, 'w', newline='') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) @@ -309,13 +309,13 @@ def write_as_md(self, save_path, project_name): def main(graph_db_path: str, subgraphs_path:str, results_dir:str): subgraphs = import_tlv_folder(subgraphs_path, parse_support=False) - + #TODO REMOVE THIS AGAIN, THIS IS ONLY TO FIT TO THE TEST DATA #subgraphs = [graph.reverse() for graph in subgraphs] - + #TODO Workaround since a dummy root has been added by a previous steps subgraphs = [IsoGraph(graph).cut_root() for graph in subgraphs] - + # Get rid of clones nb_initial_subgraphs = len(subgraphs) print("Removing duplicates. This might take some time...") @@ -323,13 +323,13 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): nb_pruned_subgraphs = len(subgraphs) removed_duplicates = nb_initial_subgraphs - nb_pruned_subgraphs print("Removed %d duplicates" % removed_duplicates) - + print("Creating subgraph lattice for lattice-based counting...") # First create the lattice node for the subgraphs lattice_nodes = [LatticeNode(subgraph) for subgraph in subgraphs] # Create lattice (this might take some time) - lattice = Lattice(lattice_nodes) - + lattice = Lattice(lattice_nodes) + print("Exporting lattice.") nx_lattice = lattice.to_networkx() export_TLV([nx_lattice], results_dir + 'lattice.lg') @@ -337,7 +337,7 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): #plot_graph_dot(nx_lattice, results_dir + 'lattice_dot.png') with open(results_dir + 'lattice.graphml', 'w') as f: f.write(lattice.to_graphml()) - + # Write subgraphs without clones print("Writing subgraphs without occurrences.") export_TLV(subgraphs, results_dir + 'subgraph_candidates.lg') @@ -350,8 +350,8 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): graph_db = import_tlv_folder(graph_db_path+"/"+folder+"/", parse_support=False) compute_statistics(graph_db, subgraphs, lattice, results_dir + "/" + folder + "/", folder) -def get_url_for_project(project): - ''' +def get_url_for_project(project): + ''' Looks up the given project in a list of projects and returns the corresponding repository url. ''' # TODO project list could be cached @@ -379,7 +379,7 @@ def compute_statistics(graph_db, subgraphs, lattice, results_dir, dataset_name): stats.compute_occurrences_lattice_based() stop = time.time() print("Computing occurrences lattice based took %f seconds." % (stop-start)) - + # Write statistics to file print("Write occurrence statistics...") stats.write_as_csv(results_dir + 'occurrence_stats.csv', dataset_name) @@ -389,11 +389,11 @@ def compute_statistics(graph_db, subgraphs, lattice, results_dir, dataset_name): - + if __name__ == "__main__": if len(sys.argv) < 4: print("Three arguments expected: path to graph database folder, path to subgraph database folder, path to results directory") - + # Create output folder if it doesn't exist yet - os.makedirs(sys.argv[3], exist_ok=True) + os.makedirs(sys.argv[3], exist_ok=True) main(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/mining/subgraph_statistics_debug.py b/mining/subgraph_statistics_debug.py index 3953d13a0..3fbc8c36b 100644 --- a/mining/subgraph_statistics_debug.py +++ b/mining/subgraph_statistics_debug.py @@ -20,11 +20,11 @@ class LatticeNode(): '''Wraps IsoGraphs in a data structure for a lattice, i.e., links every graph to it's parents or childrens, respectively. Furthermore, the lattice nodes have pointers to graphs in a graph_database where they are contained in (i.e., their occurrences). ''' - + def __init__(self, graph: IsoGraph, label_name = 'label'): self.graph = graph self.graph.set_label(label_name) - + self.occurrences = [] self.parents = [] self.children = [] @@ -38,9 +38,9 @@ def __init__(self, graph: IsoGraph, label_name = 'label'): def __str__(self): return str(self.graph.name) - + __repr__ = __str__ - + class Lattice(): def __init__(self, nodes: List[LatticeNode]): @@ -50,10 +50,10 @@ def __init__(self, nodes: List[LatticeNode]): self.layers = dict() self._compute_layers() - # TODO this lattice generation is very complex -> Try to simplify and reduce cognitive overhead. + # TODO this lattice generation is very complex -> Try to simplify and reduce cognitive overhead. # TODO e.g., bidirectional parent-child relationship could be handled automatically,... There is also a huge duplication related to this. def _compute_lattice_dfs(self, child=None, current_path=[]): - ''' + ''' Efficient method to compute the subgraph lattice. Note that this method does not yet yield the right layer for every lattice node. To compute the layers, run _compute_layers. ''' @@ -63,10 +63,10 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): node.no_parent_candidates.add(node) reachable_nodes = self._compute_lattice_dfs(child=node, current_path = [node]) return - + if child.discovered: return - + for node in list(set(self.nodes)-child.no_parent_candidates): if not node.discovered : if node.graph.contains(child.graph): @@ -86,11 +86,11 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): if upper_parent in current_path[depth].reachable_nodes.keys(): current_path[depth].reachable_nodes[upper_parent] = max(length_to_node + node.reachable_nodes[upper_parent], current_path[depth].reachable_nodes[upper_parent]) else: - current_path[depth].reachable_nodes[upper_parent] = length_to_node + node.reachable_nodes[upper_parent] + current_path[depth].reachable_nodes[upper_parent] = length_to_node + node.reachable_nodes[upper_parent] else: # tracking this helps us speed up the lattice construction, since we can save some expensive subgraph monomorphism checks - child.no_parent_candidates.add(node) - else: + child.no_parent_candidates.add(node) + else: # check if we've discovered a longer path already (we want no shortcuts, always the full hiearchy in our child-pattern relationships) if not node in child.reachable_nodes.keys(): if node.graph.contains(child.graph): @@ -112,7 +112,7 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): # Handle parent-child relationship node.children.append(child) child.parents.append(node) - + # At last, we need to handle a situation in which short-cuts might still be present. This can happen when they have been created bevor the other "paths" have been completly discoverd. # We can see the shortcuts now, because a shortcut means that a parent's reachability (which is the longest path to the node) is > 1 parents_to_be_removed = [] @@ -120,14 +120,14 @@ def _compute_lattice_dfs(self, child=None, current_path=[]): if child.reachable_nodes[parent] > 1: # Have to remember what we want to delete, because we can not remove while iterating over a list (mixing up iterator in python) parents_to_be_removed.append(parent) - parent.children.remove(child) + parent.children.remove(child) for parent in parents_to_be_removed: child.parents.remove(parent) - + # Node has finally been fully discovered child.discovered = True return child.reachable_nodes - + def _compute_layers(self): # Compute the max layer for each node (we need the maximum for bottom-up algos, to be sure in every new layer to know already all the children) for node in self.nodes: @@ -140,16 +140,16 @@ def _compute_layers(self): self.layers[node_layer] = [node] else: self.layers[node_layer].append(node) - - + + def to_graphml(self): nx_lattice = self.to_networkx(include_graphs = False) return "\n".join(generate_graphml(nx_lattice)) - + def to_networkx(self, include_graphs=True): '''Represents the lattice as a networkX graph''' nx_lattice = nx.DiGraph() - + # TODO deferring id resolution could speed up this graph generation (but should be fine with the lattice size we will face in practise) for i in range(len(self.nodes)): node = self.nodes[i] @@ -157,34 +157,34 @@ def to_networkx(self, include_graphs=True): nx_lattice.add_node(i, label = node.graph.name, graph = node.graph) else: nx_lattice.add_node(i, label = node.graph.name) - + for node in self.nodes: for child in node.children: nx_lattice.add_edge(self.nodes.index(node), self.nodes.index(child), label="subgraph") - + return nx_lattice - - - + + + # TODO only very simple printing of lattice. Proper plotting should be done in the future for debug purposes def describe(self, verbose=False): lines = [] for layer in sorted(self.layers.keys()): lines.append("Layer:\t %d; Nodes: \t %s" % (layer, "|".join([node.graph.name for node in self.layers[layer]]))) - + if verbose: lines += self._describe_verbose() - + return "\n".join(lines) - - + + def _describe_verbose(self): lines = [] for node in self.nodes: lines.append("Node:\t %s; Children: \t %s" %(node.graph.name, "|".join([child.graph.name for child in node.children]))) lines.append("Layer: %d; Reachable: %s" %(node.layer, node.reachable_nodes)) return lines - + def count_occurrences(self, graph_database: List[IsoGraph]): for layer in sorted(self.layers.keys()): for node in self.layers[layer]: @@ -192,13 +192,13 @@ def count_occurrences(self, graph_database: List[IsoGraph]): occurrence_candidates = list(set.intersection(*[set(child.occurrences) for child in node.children])) else: occurrence_candidates = range(len(graph_database)) - + for occurrence_candidate in occurrence_candidates: if graph_database[occurrence_candidate].contains(node.graph): print(graph_database[occurrence_candidate].graph['embeddings']) node.occurrences.append(occurrence_candidate) - - + + class Statistics(): ''' Provides basic counting statistics for a set of subgraphs and a graph database. @@ -216,14 +216,14 @@ def __init__(self, graph_db: List[nx.DiGraph], subgraphs: List[nx.DiGraph], labe self.occurrences_references = {} self.compressions = {} self.lattice = lattice - + def _set_label_name(self): for graph in self.graph_db: graph.set_label(self.label_name) for graph in self.subgraphs: graph.set_label(self.label_name) - - + + def _name_subgraphs(self): ''' Simply given an identifier to graphs that don't have one yet. @@ -233,7 +233,7 @@ def _name_subgraphs(self): if self.override_names or graph.name is None or len(graph.name) == 0: graph.name=str(i) i+=1 - + def compute_occurrences_brute_force(self): ''' For a more efficient computation, see compute_occurrences_lattice_based. @@ -245,8 +245,8 @@ def compute_occurrences_brute_force(self): # todo count the occurrences instead of just "is there" if graph.contains(subgraph): occurrences+=1 - self.occurrences_transaction[subgraph.name] = occurrences - + self.occurrences_transaction[subgraph.name] = occurrences + def compute_occurrences_lattice_based(self): if self.lattice is None: # First create the lattice node for the subgraphs @@ -272,7 +272,7 @@ def write_as_csv(self, save_path, additional_tag): additional_tag: some additional tags to add, e.g., when later merging different results ''' os.makedirs(os.path.dirname(save_path), exist_ok=True) - + with open(save_path, 'w', newline='') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) @@ -310,13 +310,13 @@ def write_as_md(self, save_path, project_name): def main(graph_db_path: str, subgraphs_path:str, results_dir:str): subgraphs = import_tlv_folder(subgraphs_path, parse_support=False) - + #TODO REMOVE THIS AGAIN, THIS IS ONLY TO FIT TO THE TEST DATA #subgraphs = [graph.reverse() for graph in subgraphs] - + #TODO Workaround since a dummy root has been added by a previous steps subgraphs = [IsoGraph(graph).cut_root() for graph in subgraphs] - + # Get rid of clones nb_initial_subgraphs = len(subgraphs) print("Removing duplicates. This might take some time...") @@ -324,13 +324,13 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): nb_pruned_subgraphs = len(subgraphs) removed_duplicates = nb_initial_subgraphs - nb_pruned_subgraphs print("Removed %d duplicates" % removed_duplicates) - + print("Creating subgraph lattice for lattice-based counting...") # First create the lattice node for the subgraphs lattice_nodes = [LatticeNode(subgraph) for subgraph in subgraphs] # Create lattice (this might take some time) - lattice = Lattice(lattice_nodes) - + lattice = Lattice(lattice_nodes) + print("Exporting lattice.") nx_lattice = lattice.to_networkx() export_TLV([nx_lattice], results_dir + 'lattice.lg') @@ -338,7 +338,7 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): #plot_graph_dot(nx_lattice, results_dir + 'lattice_dot.png') with open(results_dir + 'lattice.graphml', 'w') as f: f.write(lattice.to_graphml()) - + # Write subgraphs without clones print("Writing subgraphs without occurrences.") export_TLV(subgraphs, results_dir + 'subgraph_candidates.lg') @@ -351,8 +351,8 @@ def main(graph_db_path: str, subgraphs_path:str, results_dir:str): graph_db = import_tlv_folder(graph_db_path+"/"+folder+"/mining/", parse_support=True) compute_statistics(graph_db, subgraphs, lattice, results_dir + "/" + folder + "/", folder) -def get_url_for_project(project): - ''' +def get_url_for_project(project): + ''' Looks up the given project in a list of projects and returns the corresponding repository url. ''' # TODO project list could be cached @@ -380,7 +380,7 @@ def compute_statistics(graph_db, subgraphs, lattice, results_dir, dataset_name): stats.compute_occurrences_lattice_based() stop = time.time() print("Computing occurrences lattice based took %f seconds." % (stop-start)) - + # Write statistics to file print("Write occurrence statistics...") stats.write_as_csv(results_dir + 'occurrence_stats.csv', dataset_name) @@ -390,11 +390,11 @@ def compute_statistics(graph_db, subgraphs, lattice, results_dir, dataset_name): - + if __name__ == "__main__": if len(sys.argv) < 4: print("Three arguments expected: path to graph database folder, path to subgraph database folder, path to results directory") - + # Create output folder if it doesn't exist yet - os.makedirs(sys.argv[3], exist_ok=True) + os.makedirs(sys.argv[3], exist_ok=True) main(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/nix/github-workflow-dependencies.nix b/nix/github-workflow-dependencies.nix deleted file mode 100644 index 72c9cc63e..000000000 --- a/nix/github-workflow-dependencies.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - sources ? import ./sources.nix, - system ? builtins.currentSystem, - pkgs ? - import sources.nixpkgs { - overlays = []; - config = {}; - inherit system; - }, -}: let - DiffDetective = import ../default.nix {}; -in - pkgs.mkShell { - inputsFrom = [DiffDetective]; - pkgs = [DiffDetective.mavenRepo]; - } diff --git a/nix/sources.json b/nix/sources.json index 94a365575..d65a61165 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,14 +1,14 @@ { "nixpkgs": { - "branch": "nixos-23.11", + "branch": "nixos-25.05", "description": "Nix Packages collection", "homepage": null, "owner": "NixOS", "repo": "nixpkgs", - "rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6", - "sha256": "1ww2vrgn8xrznssbd05hdlr3d4br6wbjlqprys1al8ahxkyl5syi", + "rev": "10d7f8d34e5eb9c0f9a0485186c1ca691d2c5922", + "sha256": "16ag6ac2szq60bm17cdj5ybg46gkvffkmq6a34wpx38v28r25ghx", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/6832d0d99649db3d65a0e15fa51471537b2c56a6.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/10d7f8d34e5eb9c0f9a0485186c1ca691d2c5922.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/pom.xml b/pom.xml index 6c623cad4..a598614bd 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.variantsync diffdetective - 2.2.0 + 2.3.0 UTF-8 @@ -20,13 +20,32 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 + + + enforce-maven + + enforce + + + + + 3.6.3 + + + + + + + org.apache.maven.plugins maven-javadoc-plugin - 3.4.1 + 3.11.2 - docs - javadoc private true @@ -34,13 +53,13 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.14.0 org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M8 + 3.5.3 **/*.java @@ -52,7 +71,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.4.2 + 3.7.1 package @@ -71,7 +90,7 @@ org.antlr antlr4-maven-plugin - 4.13.1 + 4.13.2 @@ -125,7 +144,7 @@ org.atteo.classindex classindex - 3.11 + 3.13 test @@ -138,60 +157,60 @@ org.eclipse.jgit org.eclipse.jgit - 6.7.0.202309050840-r + 7.2.1.202505142326-r org.apache.commons commons-lang3 - 3.12.0 + 3.17.0 commons-io commons-io - 2.14.0 + 2.19.0 org.tinylog tinylog-api - 2.6.1 + 2.7.0 org.tinylog tinylog-impl - 2.6.1 + 2.7.0 org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.0 org.junit.jupiter junit-jupiter-engine - 5.9.2 + 5.13.0 test org.junit.jupiter junit-jupiter - 5.9.2 + 5.13.0 test org.junit.platform junit-platform-launcher - 1.9.2 + 1.13.0 test org.apache.maven.surefire surefire-junit-platform - 3.0.0-M8 + 3.5.3 test @@ -199,20 +218,20 @@ org.slf4j slf4j-api - 2.0.5 + 2.0.17 org.slf4j slf4j-simple - 2.0.5 + 2.0.17 org.antlr antlr4 - 4.13.1 + 4.13.2 diff --git a/proofs/INSTALL.md b/proofs/INSTALL.md index 49ac9b154..c29d57614 100644 --- a/proofs/INSTALL.md +++ b/proofs/INSTALL.md @@ -27,7 +27,7 @@ stack run ## What Is There to See -Our example executes a simple test. It +Our example executes a simple test. It 1. constructs two variation trees (called `Kanto Starters` and `Johto Starters`) and prints them to terminal; 2. diffs both trees to a variation tree diff using our [`naiveDiff`](src/VariationDiff.hs) function, which is described in our appendix, and prints the diff to terminal; 3. creates both projections of the variation tree diff (before and after the edit) and prints both to terminal; diff --git a/proofs/src/Feature/Logic.hs b/proofs/src/Feature/Logic.hs index 2939464f7..39e65164b 100644 --- a/proofs/src/Feature/Logic.hs +++ b/proofs/src/Feature/Logic.hs @@ -18,7 +18,7 @@ class Composable f where class Comparable f where limplies :: f -> f -> Bool - + lequivalent :: f -> f -> Bool lequivalent a b = a `limplies` b && b `limplies` a diff --git a/proofs/src/Labels/MinimalLabels.hs b/proofs/src/Labels/MinimalLabels.hs index 2e50bb73f..92bbef56b 100644 --- a/proofs/src/Labels/MinimalLabels.hs +++ b/proofs/src/Labels/MinimalLabels.hs @@ -17,7 +17,7 @@ instance VTLabel MinimalLabels where featuremapping tree node@(VTNode _ label) = case label of Artifact _ -> fromJust $ featureMappingOfParent tree node Mapping f -> f - presencecondition tree node@(VTNode _ label) = case label of + presencecondition tree node@(VTNode _ label) = case label of Artifact _ -> parentPC Mapping f -> land [f, parentPC] where diff --git a/proofs/src/Labels/PaperLabels.hs b/proofs/src/Labels/PaperLabels.hs index 6f95383fa..4db6ab601 100644 --- a/proofs/src/Labels/PaperLabels.hs +++ b/proofs/src/Labels/PaperLabels.hs @@ -46,7 +46,7 @@ instance VTLabel PaperLabels where where parentPC = fromJust $ presenceConditionOfParent tree node getParent = fromJust . parent tree - + wellformednessConstraints = [isTree, allElsesBelowIfs, allMappingsHaveAtMostOneElse] instance Comparable f => Eq (PaperLabels f) where diff --git a/proofs/src/Labels/WithElif.hs b/proofs/src/Labels/WithElif.hs index 633346ddc..1779e248a 100644 --- a/proofs/src/Labels/WithElif.hs +++ b/proofs/src/Labels/WithElif.hs @@ -30,7 +30,7 @@ instance VTLabel WithElif where presencecondition tree (getParent (correspondingIf tree node))] Elif _ -> land [ featuremapping tree node, - presencecondition tree (getParent (correspondingIf tree node))] + presencecondition tree (getParent (correspondingIf tree node))] where parentPC = fromJust $ presenceConditionOfParent tree node getParent = fromJust . parent tree diff --git a/proofs/src/Time.hs b/proofs/src/Time.hs index 66754ae47..256e8b65a 100644 --- a/proofs/src/Time.hs +++ b/proofs/src/Time.hs @@ -6,7 +6,7 @@ data DiffType = ADD | REM | NON deriving (Eq, Show) always :: [Time] always = [BEFORE, AFTER] -abbreviate :: Time -> String +abbreviate :: Time -> String abbreviate BEFORE = "B" abbreviate AFTER = "A" @@ -20,8 +20,8 @@ existsAtTime BEFORE ADD = False existsAtTime AFTER REM = False existsAtTime _ _ = True -existsBefore :: DiffType -> Bool +existsBefore :: DiffType -> Bool existsBefore = existsAtTime BEFORE -existsAfter :: DiffType -> Bool +existsAfter :: DiffType -> Bool existsAfter = existsAtTime AFTER diff --git a/proofs/src/VariationTree.hs b/proofs/src/VariationTree.hs index ce29c42c5..8cfb100ec 100644 --- a/proofs/src/VariationTree.hs +++ b/proofs/src/VariationTree.hs @@ -19,7 +19,7 @@ type WellformednessConstraint l f = VariationTree l f -> Bool -- getReachableNodes :: VariationTree l f -> VTNode l f -> [VTNode l f] -- getReachableNodes tree node = --- let +-- let -- childrenMap = getAllChildrenLists tree -- childrenOf node = fromMaybe [] (Data.Map.lookup node childrenMap) -- in @@ -140,4 +140,4 @@ featureMappingOfParent tree = ofParent (featuremapping tree) tree presenceConditionOfParent :: VTLabel t => VariationTree t f -> VTNode t f -> Maybe f -presenceConditionOfParent tree = ofParent (presencecondition tree) tree \ No newline at end of file +presenceConditionOfParent tree = ofParent (presencecondition tree) tree diff --git a/replication/esecfse22/Dockerfile b/replication/esecfse22/Dockerfile index 50dc493b7..35687613c 100644 --- a/replication/esecfse22/Dockerfile +++ b/replication/esecfse22/Dockerfile @@ -54,4 +54,4 @@ RUN chmod +x fix-perms.sh ENTRYPOINT ["./entrypoint.sh", "./execute.sh"] # Set the user -USER sherlock \ No newline at end of file +USER sherlock diff --git a/replication/esecfse22/INSTALL.md b/replication/esecfse22/INSTALL.md index 66c7a9fd1..f6a5b2510 100644 --- a/replication/esecfse22/INSTALL.md +++ b/replication/esecfse22/INSTALL.md @@ -14,15 +14,15 @@ Then, start the docker deamon. ### 2. Open a Suitable Terminal ``` -# Windows Command Prompt: +# Windows Command Prompt: - Press 'Windows Key + R' on your keyboard - - Type in 'cmd' + - Type in 'cmd' - Click 'OK' or press 'Enter' on your keyboard - + # Windows PowerShell: - Open the search bar (Default: 'Windows Key') and search for 'PowerShell' - Start the PowerShell - + # Linux: - Press 'ctrl + alt + T' on your keyboard ``` @@ -39,16 +39,16 @@ cd DiffDetective/replication/esecfse22 ### 3. Build the Docker Container To build the Docker container you can run the `build` script corresponding to your operating system: ``` -# Windows: +# Windows: .\build.bat -# Linux/Mac (bash): +# Linux/Mac (bash): ./build.sh ``` ## 4. Verification & Replication ### Running the Replication or Verification -To execute the replication you can run the `execute` script corresponding to your operating system with `replication` as first argument. To execute the script you first have to navigate to the `esecfse22` directory, if you have not done so. +To execute the replication you can run the `execute` script corresponding to your operating system with `replication` as first argument. To execute the script you first have to navigate to the `esecfse22` directory, if you have not done so. ```shell cd DiffDetective/replication/esecfse22 ``` @@ -141,22 +141,22 @@ The input file must have the same format as the other dataset files (i.e., repos ## Troubleshooting ### 'Got permission denied while trying to connect to the Docker daemon socket' -`Problem:` This is a common problem under Linux, if the user trying to execute Docker commands does not have the permissions to do so. +`Problem:` This is a common problem under Linux, if the user trying to execute Docker commands does not have the permissions to do so. `Fix:` You can fix this problem by either following the [post-installation instructions](https://docs.docker.com/engine/install/linux-postinstall/), or by executing the scripts in the replication package with elevated permissions (i.e., `sudo`). ### 'Unable to find image 'replication-package:latest' locally' -`Problem:` The Docker container could not be found. This either means that the name of the container that was built does not fit the name of the container that is being executed (this only happens if you changed the provided scripts), or that the Docker container was not built yet. +`Problem:` The Docker container could not be found. This either means that the name of the container that was built does not fit the name of the container that is being executed (this only happens if you changed the provided scripts), or that the Docker container was not built yet. `Fix:` Follow the instructions described above in the section `Build the Docker Container`. ### No results after verification, or 'cannot create directory '../results/validation/current': Permission denied' `Problem:` This problem can occur due to how permissions are managed inside the Docker container. More specifically, it will appear, if Docker is executed with elevated permissions (i.e., `sudo`) and if there is no [results](results) directory because it was deleted manually. In this case, Docker will create the directory with elevated permissions, and the Docker user has no permissions to access the directory. -`Fix:` If there is a _results_ directory, delete it with elevated permission (e.g., `sudo rm -r results`). +`Fix:` If there is a _results_ directory, delete it with elevated permission (e.g., `sudo rm -r results`). Then, create a new _results_ directory without elevated permissions, or execute `git restore .` to restore the deleted directory. ### Failed to load class "org.slf4j.impl.StaticLoggerBinder" `Problem:` An operation within the initialization phase of the logger library we use (tinylog) failed. -`Fix:` Please ignore this warning. Tinylog will fall back onto a default implementation (`Defaulting to no-operation (NOP) logger implementation`) and logging will work as expected. \ No newline at end of file +`Fix:` Please ignore this warning. Tinylog will fall back onto a default implementation (`Defaulting to no-operation (NOP) logger implementation`) and logging will work as expected. diff --git a/replication/esecfse22/README.md b/replication/esecfse22/README.md index 51a150472..00d556bc8 100644 --- a/replication/esecfse22/README.md +++ b/replication/esecfse22/README.md @@ -21,7 +21,7 @@ This replication package consists of four parts: ## 1. DiffDetective DiffDetective is a java library and command-line tool to parse and classify edits to variability in git histories of preprocessor-based software product lines by creating [variation diffs][difftree_class] and operating on them. -We offer a [Docker](https://www.docker.com/) setup to easily __replicate__ the validation performed in our paper. +We offer a [Docker](https://www.docker.com/) setup to easily __replicate__ the validation performed in our paper. In the following, we provide a quickstart guide for running the replication. You can find detailed information on how to install Docker and build the container in the [INSTALL](INSTALL.md) file, including detailed descriptions of each step and troubleshooting advice. @@ -36,9 +36,9 @@ Start the docker deamon. Clone this repository. Open a terminal and navigate to the root directory of this repository. To build the Docker container you can run the `build` script corresponding to your operating system. -#### Windows: +#### Windows: `.\build.bat` -#### Linux/Mac (bash): +#### Linux/Mac (bash): `./build.sh` ### 1.2 Start the replication diff --git a/replication/esecfse22/REQUIREMENTS.md b/replication/esecfse22/REQUIREMENTS.md index f20870e4e..5430e2c8c 100644 --- a/replication/esecfse22/REQUIREMENTS.md +++ b/replication/esecfse22/REQUIREMENTS.md @@ -14,4 +14,4 @@ Docker will take care of all requirements and dependencies to replicate our vali The requirements to build our `proofs` Haskell library are documented in its respective [proofs/REQUIREMENTS.md](../../proofs/REQUIREMENTS.md) file. -[stack]: https://docs.haskellstack.org/en/stable/README/ \ No newline at end of file +[stack]: https://docs.haskellstack.org/en/stable/README/ diff --git a/replication/esecfse22/STATUS.md b/replication/esecfse22/STATUS.md index eaeac01c3..3ad0cb3da 100644 --- a/replication/esecfse22/STATUS.md +++ b/replication/esecfse22/STATUS.md @@ -2,7 +2,7 @@ ## Overview The artifact for the paper _Classifying Edits to Variability in Source Code_ consists of four parts: -1. **DiffDetective**: For our validation, we built DiffDetective, a java library and command-line tool to classify edits to variability in git histories of preprocessor-based software product lines. +1. **DiffDetective**: For our validation, we built DiffDetective, a java library and command-line tool to classify edits to variability in git histories of preprocessor-based software product lines. DiffDetective is the main artifact used to replicate the validation of our paper (see Section 5). DiffDetective is self-contained in that it does not require or depend on in-depth knowledge on the theoretical foundation of our work. Practitioners and researches are free to ignore the appendix as well as the haskell formalization and may use DiffDetective out-of-the-box. diff --git a/replication/esecfse22/docker/DOCKER.md b/replication/esecfse22/docker/DOCKER.md index 966ebc0d5..dee3de3e1 100644 --- a/replication/esecfse22/docker/DOCKER.md +++ b/replication/esecfse22/docker/DOCKER.md @@ -3,4 +3,4 @@ This directory contains the files that are required to run the Docker container. ## Execution -The [`execute.sh`](execute.sh) script can be adjusted to run the program that should be executed by the Docker container. \ No newline at end of file +The [`execute.sh`](execute.sh) script can be adjusted to run the program that should be executed by the Docker container. diff --git a/replication/esecfse22/docker/execute.sh b/replication/esecfse22/docker/execute.sh index b680e146c..b161fc130 100644 --- a/replication/esecfse22/docker/execute.sh +++ b/replication/esecfse22/docker/execute.sh @@ -1,31 +1,31 @@ #!/usr/bin/env bash if [ "$1" == '' ] || [ "$1" == '--help' ] || [ "$1" == '-help' ]; then - echo ">>>>>>>>> USAGE <<<<<<<<<<" - echo "Either fully run DiffDetective as presented in the paper (replication), do quick setup verification (verification), - or run DiffDetective on a custom dataset by providing the path to the dataset file." - echo "" - echo "-- Examples --" - echo "Run replication: './execute.sh replication'" - echo "Validate the setup: './execute.sh verification'" - echo "# See ./docs/datasets/esecfse22-verification.md for format details" - echo "Custom dataset: './execute.sh path/to/my_dataset.md'" - exit + echo ">>>>>>>>> USAGE <<<<<<<<<<" + echo "Either fully run DiffDetective as presented in the paper (replication), do quick setup verification (verification), + or run DiffDetective on a custom dataset by providing the path to the dataset file." + echo "" + echo "-- Examples --" + echo "Run replication: './execute.sh replication'" + echo "Validate the setup: './execute.sh verification'" + echo "# See ./docs/datasets/esecfse22-verification.md for format details" + echo "Custom dataset: './execute.sh path/to/my_dataset.md'" + exit fi cd /home/sherlock || exit - cd holmes || exit +cd holmes || exit if [ "$1" == 'replication' ]; then - echo "Running full replication. Depending on your system, this will require several hours or even a few days." - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation docs/datasets/esecfse22-replication.md + echo "Running full replication. Depending on your system, this will require several hours or even a few days." + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation docs/datasets/esecfse22-replication.md elif [ "$1" == 'verification' ]; then - echo "Running a short verification." - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation docs/datasets/esecfse22-verification.md + echo "Running a short verification." + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation docs/datasets/esecfse22-verification.md else - echo "" - echo "Running detection on a custom dataset with the input file $1" - echo "" - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation "$1" + echo "" + echo "Running detection on a custom dataset with the input file $1" + echo "" + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.esecfse22.EditClassValidation "$1" fi echo "Collecting results." cp -r results/* ../results/ diff --git a/replication/esecfse22/execute.sh b/replication/esecfse22/execute.sh index 54e70c0e8..cf6d53ccf 100755 --- a/replication/esecfse22/execute.sh +++ b/replication/esecfse22/execute.sh @@ -3,6 +3,6 @@ cd "$(dirname "${BASH_SOURCE[0]}")" || exit if [[ $# -gt 0 ]]; then -echo "Executing $1" + echo "Executing $1" fi docker run --rm -v "$(pwd)/results":"/home/sherlock/results" diff-detective "$@" diff --git a/replication/esecfse22/results/.gitignore b/replication/esecfse22/results/.gitignore index 86d0cb272..5e7d2734c 100644 --- a/replication/esecfse22/results/.gitignore +++ b/replication/esecfse22/results/.gitignore @@ -1,4 +1,4 @@ # Ignore everything in this directory * # Except this file -!.gitignore \ No newline at end of file +!.gitignore diff --git a/replication/esecfse22/stop-execution.bat b/replication/esecfse22/stop-execution.bat index 6dcf69016..5314b9ab4 100644 --- a/replication/esecfse22/stop-execution.bat +++ b/replication/esecfse22/stop-execution.bat @@ -1,3 +1,3 @@ @echo "Stopping all running simulations. This will take a moment..." @FOR /f "tokens=*" %%i IN ('docker ps -a -q --filter "ancestor=diff-detective"') DO docker stop %%i -@echo "...done." \ No newline at end of file +@echo "...done." diff --git a/replication/splc23-views/Dockerfile b/replication/splc23-views/Dockerfile index dbb1a24b3..3c0d9008d 100644 --- a/replication/splc23-views/Dockerfile +++ b/replication/splc23-views/Dockerfile @@ -61,4 +61,4 @@ RUN chmod +x fix-perms.sh ENTRYPOINT ["./entrypoint.sh", "./execute.sh"] # Set the user -USER sherlock \ No newline at end of file +USER sherlock diff --git a/replication/splc23-views/INSTALL.md b/replication/splc23-views/INSTALL.md index 236ac07f0..3ea3522e3 100644 --- a/replication/splc23-views/INSTALL.md +++ b/replication/splc23-views/INSTALL.md @@ -10,14 +10,14 @@ Start the docker deamon: - **On Linux**: Typically, the docker deamon runs automatically. Otherwise, run `sudo systemctl start docker`. - **On Windows**: Open the search bar using the 'Windows Key' and search for 'Docker' or 'Docker Desktop'. -More detailed instructions on starting the deamon are given [here](https://docs.docker.com/config/daemon/start/) on the docker website. +More detailed instructions on starting the deamon are given [here](https://docs.docker.com/config/daemon/start/) on the docker website. ### 2. Open a Suitable Terminal ``` # Windows PowerShell: - Open the search bar (Default: 'Windows Key') and search for 'PowerShell' - Start the PowerShell - + # Linux: - Press 'ctrl + alt + T' on your keyboard ``` @@ -42,9 +42,9 @@ cd replication/splc23-views ### 3. Build the Docker Container To build the Docker container you can run the `build` script corresponding to your operating system: ``` -# Windows: +# Windows: .\build.bat -# Linux/Mac (bash): +# Linux/Mac (bash): ./build.sh ``` @@ -116,19 +116,19 @@ The input file must have the same format as the other dataset files (i.e., repos ## Troubleshooting ### 'Got permission denied while trying to connect to the Docker daemon socket' -`Problem:` This is a common problem under Linux, if the user trying to execute Docker commands does not have the permissions to do so. +`Problem:` This is a common problem under Linux, if the user trying to execute Docker commands does not have the permissions to do so. `Fix:` You can fix this problem by either following the [post-installation instructions](https://docs.docker.com/engine/install/linux-postinstall/), or by executing the scripts in the replication package with elevated permissions (i.e., `sudo`). ### 'Unable to find image 'replication-package:latest' locally' -`Problem:` The Docker container could not be found. This either means that the name of the container that was built does not fit the name of the container that is being executed (this only happens if you changed the provided scripts), or that the Docker container was not built yet. +`Problem:` The Docker container could not be found. This either means that the name of the container that was built does not fit the name of the container that is being executed (this only happens if you changed the provided scripts), or that the Docker container was not built yet. `Fix:` Follow the instructions described above in the section `Build the Docker Container`. ### No results after verification, or 'cannot create directory '../results/views/current': Permission denied' `Problem:` This problem can occur due to how permissions are managed inside the Docker container. More specifically, it will appear, if Docker is executed with elevated permissions (i.e., `sudo`) and if there is no [results](results) directory because it was deleted manually. In this case, Docker will create the directory with elevated permissions, and the Docker user has no permissions to access the directory. -`Fix:` If there is a _results_ directory, delete it with elevated permission (e.g., `sudo rm -r results`). +`Fix:` If there is a _results_ directory, delete it with elevated permission (e.g., `sudo rm -r results`). Then, create a new _results_ directory without elevated permissions, or execute `git restore .` to restore the deleted directory. ### Failed to load class "org.slf4j.impl.StaticLoggerBinder" @@ -140,4 +140,4 @@ Then, create a new _results_ directory without elevated permissions, or execute `Problem:` The scripts `build.sh` and `execute.sh` were not run in bash. The scripts were probably run in shell explicitly (i.e., the `sh` command, for example via `sh build.sh`). -`Fix:` Run the scripts directly (i.e., `./build.sh` or `./execute.sh ...`) or via bash (i.e., `bash build.sh`). \ No newline at end of file +`Fix:` Run the scripts directly (i.e., `./build.sh` or `./execute.sh ...`) or via bash (i.e., `bash build.sh`). diff --git a/replication/splc23-views/README.md b/replication/splc23-views/README.md index ff181765a..c8d7848fa 100644 --- a/replication/splc23-views/README.md +++ b/replication/splc23-views/README.md @@ -46,4 +46,4 @@ We offer a [Docker](https://www.docker.com/) setup to replicate the feasibility [pkg-relevance]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/VariantSync/DiffDetective/splc23-views/docs/javadoc/org/variantsync/diffdetective/variation/tree/view/relevance/package-summary.html [pkg-feasibilityexperiment]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/VariantSync/DiffDetective/splc23-views/docs/javadoc/org/variantsync/diffdetective/experiments/views/package-summary.html [cls-diffview]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/VariantSync/DiffDetective/splc23-views/docs/javadoc/org/variantsync/diffdetective/variation/diff/view/DiffView.html -[cls-feasibilitymain]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/VariantSync/DiffDetective/splc23-views/docs/javadoc/org/variantsync/diffdetective/experiments/views/Main.html \ No newline at end of file +[cls-feasibilitymain]: https://htmlpreview.github.io/?https://raw.githubusercontent.com/VariantSync/DiffDetective/splc23-views/docs/javadoc/org/variantsync/diffdetective/experiments/views/Main.html diff --git a/replication/splc23-views/docker/DOCKER.md b/replication/splc23-views/docker/DOCKER.md index 966ebc0d5..dee3de3e1 100644 --- a/replication/splc23-views/docker/DOCKER.md +++ b/replication/splc23-views/docker/DOCKER.md @@ -3,4 +3,4 @@ This directory contains the files that are required to run the Docker container. ## Execution -The [`execute.sh`](execute.sh) script can be adjusted to run the program that should be executed by the Docker container. \ No newline at end of file +The [`execute.sh`](execute.sh) script can be adjusted to run the program that should be executed by the Docker container. diff --git a/replication/splc23-views/docker/execute.sh b/replication/splc23-views/docker/execute.sh index f864c6d22..d6f9bc1e1 100644 --- a/replication/splc23-views/docker/execute.sh +++ b/replication/splc23-views/docker/execute.sh @@ -1,31 +1,31 @@ #!/usr/bin/env bash if [ "$1" == '' ] || [ "$1" == '--help' ] || [ "$1" == '-help' ]; then - echo ">>>>>>>>> USAGE <<<<<<<<<<" - echo "Either fully run DiffDetective as presented in the paper (replication), do quick setup verification (verification), - or run DiffDetective on a custom dataset by providing the path to the dataset file." - echo "" - echo "-- Examples --" - echo "Run replication: './execute.sh replication'" - echo "Validate the setup: './execute.sh verification'" - echo "# See ./docs/datasets/esecfse22-verification.md for format details" - echo "Custom dataset: './execute.sh path/to/my_dataset.md'" - exit + echo ">>>>>>>>> USAGE <<<<<<<<<<" + echo "Either fully run DiffDetective as presented in the paper (replication), do quick setup verification (verification), + or run DiffDetective on a custom dataset by providing the path to the dataset file." + echo "" + echo "-- Examples --" + echo "Run replication: './execute.sh replication'" + echo "Validate the setup: './execute.sh verification'" + echo "# See ./docs/datasets/esecfse22-verification.md for format details" + echo "Custom dataset: './execute.sh path/to/my_dataset.md'" + exit fi cd /home/sherlock || exit - cd holmes || exit +cd holmes || exit if [ "$1" == 'replication' ]; then - echo "Running full replication. Depending on your system, this will require several hours or even a few days." - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main docs/datasets/esecfse22-replication.md + echo "Running full replication. Depending on your system, this will require several hours or even a few days." + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main docs/datasets/esecfse22-replication.md elif [ "$1" == 'verification' ]; then - echo "Running a short verification." - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main docs/datasets/esecfse22-verification.md + echo "Running a short verification." + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main docs/datasets/esecfse22-verification.md else - echo "" - echo "Running detection on a custom dataset with the input file $1" - echo "" - java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main "$1" + echo "" + echo "Running detection on a custom dataset with the input file $1" + echo "" + java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views.Main "$1" fi echo "Collecting results." cp -r results/* ../results/ diff --git a/replication/splc23-views/execute.sh b/replication/splc23-views/execute.sh index 68c3075c7..1558a6e6c 100755 --- a/replication/splc23-views/execute.sh +++ b/replication/splc23-views/execute.sh @@ -3,6 +3,6 @@ cd "$(dirname "${BASH_SOURCE[0]}")" || exit if [[ $# -gt 0 ]]; then -echo "Executing $1" + echo "Executing $1" fi -MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd)/results":"/home/sherlock/results" diff-detective-views "$@" \ No newline at end of file +MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd)/results":"/home/sherlock/results" diff-detective-views "$@" diff --git a/replication/splc23-views/stop-execution.bat b/replication/splc23-views/stop-execution.bat index 244d670b4..c4867cb20 100644 --- a/replication/splc23-views/stop-execution.bat +++ b/replication/splc23-views/stop-execution.bat @@ -1,3 +1,3 @@ @echo "Stopping all running replications. This will take a moment..." @FOR /f "tokens=*" %%i IN ('docker ps -a -q --filter "ancestor=diff-detective-views"') DO docker stop %%i -@echo "...done." \ No newline at end of file +@echo "...done." diff --git a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/CExpression.g4 b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/CExpression.g4 index 1d38df99d..36eca13fb 100644 --- a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/CExpression.g4 +++ b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/CExpression.g4 @@ -224,13 +224,13 @@ IntegerConstant : DecimalConstant IntegerSuffix? | OctalConstant IntegerSuffix? | HexadecimalConstant IntegerSuffix? - | BinaryConstant + | BinaryConstant ; fragment BinaryConstant - : '0' [bB] [0-1]+ - ; + : '0' [bB] [0-1]+ + ; fragment DecimalConstant @@ -420,7 +420,7 @@ SChar ; //MultiLineMacro: -// '#' (~[\n]*? '\\' '\r'? '\n')+ ~ [\n]+ -> channel (HIDDEN); +// '#' (~[\n]*? '\\' '\r'? '\n')+ ~ [\n]+ -> channel (HIDDEN); // //Directive: '#' ~ [\n]* -> channel (HIDDEN); @@ -442,7 +442,7 @@ Dollar */ AsmBlock : 'asm' ~'{'* '{' ~'}'* '}' - -> channel(HIDDEN) + -> channel(HIDDEN) ; Whitespace @@ -469,4 +469,4 @@ OpenBlockComment LineComment : '//' ~[\r\n]* -> channel(HIDDEN) - ; \ No newline at end of file + ; 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 ea3d64609..c443f1b7b 100644 --- a/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 +++ b/src/main/antlr4/org/variantsync/diffdetective/feature/antlr/JPPExpression.g4 @@ -109,13 +109,13 @@ IntegerConstant : DecimalConstant IntegerSuffix? | OctalConstant IntegerSuffix? | HexadecimalConstant IntegerSuffix? - | BinaryConstant + | BinaryConstant ; fragment BinaryConstant - : '0' [bB] [0-1]+ - ; + : '0' [bB] [0-1]+ + ; fragment DecimalConstant @@ -317,4 +317,4 @@ OpenBlockComment LineComment : '//' ~[\r\n]* -> channel(HIDDEN) - ; \ No newline at end of file + ; diff --git a/src/main/java/org/variantsync/diffdetective/AnalysisRunner.java b/src/main/java/org/variantsync/diffdetective/AnalysisRunner.java index 1f567343f..c3586420f 100644 --- a/src/main/java/org/variantsync/diffdetective/AnalysisRunner.java +++ b/src/main/java/org/variantsync/diffdetective/AnalysisRunner.java @@ -61,7 +61,7 @@ public record Options( * Creates options with the given parameters and uses default * values for all other parameters. * @see Options#Options(Path, Path, Path, Function, Function, boolean, boolean) - * @see Options#DEFAULT(String[]) + * @see Options#DEFAULT(String[]) */ public Options(Path repositoriesDirectory, Path outputDirectory, @@ -73,7 +73,7 @@ public Options(Path repositoriesDirectory, true, false); } - + public static Options DEFAULT(final String[] args) { final Path datasetsFile; if (args.length < 1) { @@ -132,14 +132,14 @@ public static void run(Options options, BiConsumer validation) if (options.preloadReposBeforeAnalysis()) { Logger.info("Preloading repositories:"); for (final Repository repo : repos) { - repo.getGitRepo().run(); + repo.preload(); } if (options.pullRepositoriesBeforeAnalysis()) { Logger.info("Updating repositories:"); for (final Repository repo : repos) { try { - Assert.assertTrue(repo.getGitRepo().run().pull().call().isSuccessful()); + Assert.assertTrue(repo.getGitRepo().pull().call().isSuccessful()); } catch (GitAPIException e) { Logger.error(e, "Failed to pull repository '{}'", repo.getRepositoryName()); } diff --git a/src/main/java/org/variantsync/diffdetective/analysis/Analysis.java b/src/main/java/org/variantsync/diffdetective/analysis/Analysis.java index 29232533d..d21cb5a7c 100644 --- a/src/main/java/org/variantsync/diffdetective/analysis/Analysis.java +++ b/src/main/java/org/variantsync/diffdetective/analysis/Analysis.java @@ -11,6 +11,7 @@ import org.apache.commons.lang3.function.FailableBiConsumer; import org.apache.commons.lang3.function.FailableBiFunction; +import org.eclipse.jgit.api.Git; import org.eclipse.jgit.revwalk.RevCommit; import org.tinylog.Logger; import org.variantsync.diffdetective.analysis.AnalysisResult.ResultKey; @@ -60,7 +61,7 @@ public class Analysis { protected final List hooks; protected final Repository repository; - protected GitDiffer differ; + protected Git git; protected RevCommit currentCommit; protected CommitDiff currentCommitDiff; protected PatchDiff currentPatch; @@ -307,8 +308,6 @@ public static void forEachRepository( * @param analysis the analysis to run */ public static AnalysisResult forSingleCommit(final String commitHash, final Analysis analysis) { - analysis.differ = new GitDiffer(analysis.getRepository()); - final Clock clock = new Clock(); // prepare tasks Logger.info(">>> Running Analysis on single commit {} in {}", commitHash, analysis.getRepository().getRepositoryName()); @@ -316,7 +315,7 @@ public static AnalysisResult forSingleCommit(final String commitHash, final Anal AnalysisResult result = null; try { - final RevCommit commit = analysis.differ.getCommit(commitHash); + final RevCommit commit = analysis.getRepository().getCommit(commitHash); analysis.processCommitBatch(List.of(commit)); result = analysis.getResult(); } catch (Exception e) { @@ -326,8 +325,8 @@ public static AnalysisResult forSingleCommit(final String commitHash, final Anal final double runtime = clock.getPassedSeconds(); Logger.info("<<< done in {}", Clock.printPassedSeconds(runtime)); - - result.get(TotalNumberOfCommitsResult.KEY).value++; + + result.get(TotalNumberOfCommitsResult.KEY).value = 1; exportMetadata(analysis.getOutputDir(), result); return result; @@ -397,7 +396,6 @@ public static AnalysisResult forEachCommit( final int nThreads ) { var analysis = analysisFactory.get(); - analysis.differ = new GitDiffer(analysis.getRepository()); analysis.result.append(RuntimeWithMultithreadingResult.KEY, new RuntimeWithMultithreadingResult()); final Clock clock = new Clock(); @@ -409,14 +407,18 @@ public static AnalysisResult forEachCommit( final Iterator> tasks = new MappedIterator<>( /// 1.) Retrieve COMMITS_TO_PROCESS_PER_THREAD commits from the differ and cluster them into one list. new ClusteredIterator<>( - analysis.differ.yieldRevCommitsAfter(numberOfTotalCommits), + analysis.getRepository().getDiffFilter().filter( + new MappedIterator<>( + analysis.getRepository().getCommits(), + numberOfTotalCommits + ) + ), commitsToProcessPerThread ), /// 2.) Create a MiningTask for the list of commits. This task will then be processed by one /// particular thread. commitList -> () -> { Analysis thisThreadsAnalysis = analysisFactory.get(); - thisThreadsAnalysis.differ = analysis.differ; thisThreadsAnalysis.processCommitBatch(commitList); return thisThreadsAnalysis.getResult(); } @@ -446,7 +448,7 @@ public static AnalysisResult forEachCommit( Logger.info("<<< done in {}", Clock.printPassedSeconds(runtime)); analysis.getResult().get(RuntimeWithMultithreadingResult.KEY).value = runtime; -// analysis.getResult().get(TotalNumberOfCommitsResult.KEY).value = numberOfTotalCommits.invocationCount().get(); + analysis.getResult().get(TotalNumberOfCommitsResult.KEY).value = numberOfTotalCommits.invocationCount().get(); exportMetadata(analysis.getOutputDir(), analysis.getResult()); return analysis.getResult(); @@ -469,10 +471,10 @@ public Analysis( this.hooks = hooks; this.repository = repository; this.outputDir = outputDir; - + this.result = new AnalysisResult(repository.getRepositoryName()); this.result.taskName = taskName; - + for (var hook : hooks) { hook.initializeResults(this); } @@ -519,7 +521,7 @@ protected void processCommitBatch(List commits) throws Exception { protected void processCommit() throws Exception { // parse the commit - final CommitDiffResult commitDiffResult = differ.createCommitDiff(currentCommit); + final CommitDiffResult commitDiffResult = GitDiffer.createCommitDiffFromFirstParent(repository, currentCommit); // report any errors that occurred and exit in case no VariationDiff could be parsed. getResult().reportDiffErrors(commitDiffResult.errors()); @@ -559,8 +561,6 @@ protected void processCommit() throws Exception { runReverseHook(patchHook, Hooks::endPatch); } } - - getResult().get(TotalNumberOfCommitsResult.KEY).value++; } protected void processPatch() throws Exception { diff --git a/src/main/java/org/variantsync/diffdetective/analysis/SimpleMetadata.java b/src/main/java/org/variantsync/diffdetective/analysis/SimpleMetadata.java index 095018094..df1017faa 100644 --- a/src/main/java/org/variantsync/diffdetective/analysis/SimpleMetadata.java +++ b/src/main/java/org/variantsync/diffdetective/analysis/SimpleMetadata.java @@ -13,7 +13,7 @@ * TODO: Move this implementation to Functjonal. * @param The type of the value to store (e.g., Integer when we want to count something). * @param The type of the subclass that derives this class. - * @author Paul Bittner + * @author Paul Bittner */ public abstract class SimpleMetadata> implements Metadata { public V value; diff --git a/src/main/java/org/variantsync/diffdetective/analysis/StatisticsAnalysis.java b/src/main/java/org/variantsync/diffdetective/analysis/StatisticsAnalysis.java index f90ea43e1..9efcb0e2d 100644 --- a/src/main/java/org/variantsync/diffdetective/analysis/StatisticsAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/analysis/StatisticsAnalysis.java @@ -21,7 +21,7 @@ public class StatisticsAnalysis implements Analysis.Hooks { /** * Invariant: - * {@link Result#emptyCommits} + {@link Result#failedCommits} + {@link Result#processedCommits} - {@link FilterAnalysis filteredCommits} = {@link Analysis.TotalNumberOfCommitsResult#value} + * {@link Result#emptyCommits} + {@link Result#failedCommits} + {@link Result#processedCommits} + {@link org.variantsync.diffdetective.diff.git.GitDiffer filteredCommits} = {@link Analysis.TotalNumberOfCommitsResult#value totalCommits} */ public static final class Result implements Metadata { /** diff --git a/src/main/java/org/variantsync/diffdetective/analysis/logic/SAT.java b/src/main/java/org/variantsync/diffdetective/analysis/logic/SAT.java index 5a792178a..438f2807f 100644 --- a/src/main/java/org/variantsync/diffdetective/analysis/logic/SAT.java +++ b/src/main/java/org/variantsync/diffdetective/analysis/logic/SAT.java @@ -9,6 +9,7 @@ import java.util.HashMap; import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; /** * Class with static functions for satisfiability solving, potentially with some optimizations. @@ -123,7 +124,7 @@ public static boolean implies(final Node left, final Node right) { /// = TAUT(!left || right) /// = !SAT(!(!left || right)) /// = !SAT(left && !right)) - return !isSatisfiable(new And(left, negate(right))); + return !isSatisfiable(and(left, negate(right))); } /** @@ -134,6 +135,6 @@ public static boolean implies(final Node left, final Node right) { * @return True iff 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>, PatchDiffParseOptions, DiffFilter)}. + */ +@FunctionalInterface +public interface CommitLister { + Iterator listCommits(Repository repository); + + /** + * List all commits reachable by the current {@code HEAD} of the repository. + */ + public static final CommitLister TraverseHEAD = + (Repository repository) -> { + try { + return repository.getGitRepo().log().call().iterator(); + } catch (GitAPIException e) { + Logger.warn("Could not get log for git repository {}", repository.getRepositoryName()); + throw new RuntimeException(e); + } + }; + + /** + * List all commits reachable from all branches of the repository. + */ + public static final CommitLister AllCommits = + (Repository repository) -> { + try { + return repository.getGitRepo().log().all().call().iterator(); + } catch (GitAPIException | IOException e) { + Logger.warn("Could not get log for git repository {}", repository.getRepositoryName()); + throw new RuntimeException(e); + } + }; +} diff --git a/src/main/java/org/variantsync/diffdetective/datasets/DatasetFactory.java b/src/main/java/org/variantsync/diffdetective/datasets/DatasetFactory.java index e52846698..589ea3175 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/DatasetFactory.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/DatasetFactory.java @@ -33,23 +33,6 @@ public class DatasetFactory { */ public static final String PHP = "PHP"; - /** - * Default value for diff filters. - * It disallows merge commits, only considers patches that modified files, - * and only allows source files of C/C++ projects ("h", "hpp", "c", "cpp"). - */ - public static final DiffFilter DEFAULT_DIFF_FILTER = - new DiffFilter.Builder() - .allowMerge(false) - .allowCommitsWithoutParents(false) - .allowedChangeTypes(DiffEntry.ChangeType.MODIFY) - .allowedFileExtensions("h", "hpp", "c", "cpp") - .build(); -// public static final DiffFilter PHP_DIFF_FILTER = -// new DiffFilter.Builder(DEFAULT_DIFF_FILTER) -//// .blockedPaths("ext/fileinfo/data_file.c") -// .build(); - private final Path cloneDirectory; /** @@ -69,10 +52,7 @@ public static DiffFilter getDefaultDiffFilterFor(final String repositoryName) { if (repositoryName.equalsIgnoreCase(MARLIN)) { return StanciulescuMarlin.DIFF_FILTER; } -// if (repositoryName.equalsIgnoreCase(PHP)) { -// return PHP_DIFF_FILTER; -// } - return DEFAULT_DIFF_FILTER; + return DiffFilter.DEFAULT_DIFF_FILTER; } /** @@ -124,7 +104,7 @@ public List createAll(final Collection datasets, if (preload) { Logger.info("Preloading repositories:"); for (final Repository repo : repos) { - repo.getGitRepo().run(); + repo.preload(); } } @@ -132,7 +112,7 @@ public List createAll(final Collection datasets, Logger.info("Pulling repositories:"); for (final Repository repo : repos) { try { - Assert.assertTrue(repo.getGitRepo().run().pull().call().isSuccessful()); + Assert.assertTrue(repo.getGitRepo().pull().call().isSuccessful()); } catch (GitAPIException e) { Logger.error(e, "Failed to pull repository '{}'", repo.getRepositoryName()); } diff --git a/src/main/java/org/variantsync/diffdetective/datasets/Repository.java b/src/main/java/org/variantsync/diffdetective/datasets/Repository.java index fafadbb42..c87e73a8b 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/Repository.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/Repository.java @@ -1,242 +1,285 @@ package org.variantsync.diffdetective.datasets; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; import org.tinylog.Logger; import org.variantsync.diffdetective.diff.git.DiffFilter; import org.variantsync.diffdetective.load.GitLoader; import org.variantsync.diffdetective.util.IO; import org.variantsync.functjonal.Lazy; +import java.io.IOException; import java.net.URI; import java.nio.file.Path; +import java.util.Iterator; import java.util.Optional; /** * Representation of git repositories used as datasets for DiffDetective. - * + * * @author Kevin Jedelhauser, Paul Maximilian Bittner */ public class Repository { public static final Path DIFFDETECTIVE_DEFAULT_REPOSITORIES_DIRECTORY = Path.of("repositories"); /** - * The location from where the input repository is read from. - */ - private final RepositoryLocationType repoLocation; - - /** - * The local path where the repository can be found or should be cloned to. - */ - private final Path localPath; + * The location from where the input repository is read from. + */ + private final RepositoryLocationType repoLocation; - /** - * The remote url of the repository. May be 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 Lazy git = Lazy.of(this::load); - - /** - * Creates a repository. - * - * @param repoLocation {@link RepositoryLocationType} From which location the repository is read from - * @param localPath The local path where the repository can be found or should be cloned to. - * @param remote The remote url of the repository. May be null 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 /repositories} - * @param repoName A name for the repository (currently not used) - * @return A repository from an existing directory - */ - public static Repository fromDirectory(Path dirPath, String repoName) { - return new Repository( - RepositoryLocationType.FROM_DIR, - dirPath, - null, - repoName); - } - - /** - * Creates a repository from a local zip file. - * - * @param filePath The path to the zip file (absolute or relative to {@code }). - * @param repoName A name for the repository (currently not used) - * @return A repository from a local zip file - */ - public static Repository fromZip(Path filePath, String repoName) { - return new Repository( - RepositoryLocationType.FROM_ZIP, - filePath, - null, - repoName); - } - - /** - * Creates a repository from a remote repository. - * - * @param localPath Path to clone the repository to. - * @param repoUri The address of the remote repository - * @param repoName Name of the folder, where the git repository is cloned to - * @return A repository from a remote location (e.g. Github repository) - */ - public static Repository fromRemote(Path localPath, URI repoUri, String repoName) { - return new Repository( - RepositoryLocationType.FROM_REMOTE, - localPath, - repoUri, - repoName); - } - - /** - * Creates a repository from a remote repository. - * - * @param localDir Directory to clone the repository to. - * @param repoUri The address of the remote repository - * @param repoName Name of the folder, where the git repository is cloned to - * @return A repository from a remote location (e.g. Github repository) - */ - public static Optional tryFromRemote(Path localDir, String repoUri, String repoName) { - return IO - .tryParseURI(repoUri) - .map(remote -> fromRemote(localDir.resolve(repoName), remote, repoName)); - } - - /** - * @return the location type indicating how this repository is stored. - */ - public RepositoryLocationType getRepoLocation() { - return repoLocation; - } - - /** - * The path to the repository on disk. - * The path points to the root directory of the repository if the repository is stored in a directory. - * The path points to a zip file if the repository is stored in a zip file. - * The path points to a (possibly not existing) directory to which the repository should be cloned to if the - * repository is stored on a remote server. - * @see Repository#getRepoLocation() - * @see RepositoryLocationType - * @return The path to the repository on disk. - */ - public Path getLocalPath() { - return localPath; - } - - /** - * URI of the origin of this repository (i.e., usually the location on a server where this repository was cloned from). - */ - public URI getRemoteURI() { - return remote; - } - - /** - * The name of this repository. Should be unique. - */ - public String getRepositoryName() { - return repositoryName; - } - - /** - * Set options for parsing parts of this repository's evolution history. - * @param parseOptions Options for parsing the evolution history. - * @return this - */ - public Repository setParseOptions(PatchDiffParseOptions parseOptions) { - this.parseOptions = parseOptions; + private PatchDiffParseOptions parseOptions; + + private final Lazy git = Lazy.of(this::load); + + /** + * Creates a repository. + * + * @param repoLocation {@link RepositoryLocationType} From which location the repository is read from + * @param localPath The local path where the repository can be found or should be cloned to. + * @param remote The remote url of the repository. May be 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 + *

+ * 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 /repositories} + * @param repoName A name for the repository (currently not used) + * @return A repository from an existing directory + */ + public static Repository fromDirectory(Path dirPath, String repoName) { + return new Repository( + RepositoryLocationType.FROM_DIR, + dirPath, + null, + repoName); + } + + /** + * Creates a repository from a local zip file. + * + * @param filePath The path to the zip file (absolute or relative to {@code }). + * @param repoName A name for the repository (currently not used) + * @return A repository from a local zip file + */ + public static Repository fromZip(Path filePath, String repoName) { + return new Repository( + RepositoryLocationType.FROM_ZIP, + filePath, + null, + repoName); + } + + /** + * Creates a repository from a remote repository. + * + * @param localPath Path to clone the repository to. + * @param repoUri The address of the remote repository + * @param repoName Name of the folder, where the git repository is cloned to + * @return A repository from a remote location (e.g. Github repository) + */ + public static Repository fromRemote(Path localPath, URI repoUri, String repoName) { + return new Repository( + RepositoryLocationType.FROM_REMOTE, + localPath, + repoUri, + repoName); + } + + /** + * Creates a repository from a remote repository. + * + * @param localDir Directory to clone the repository to. + * @param repoUri The address of the remote repository + * @param repoName Name of the folder, where the git repository is cloned to + * @return A repository from a remote location (e.g. Github repository) + */ + public static Optional tryFromRemote(Path localDir, String repoUri, String repoName) { + return IO + .tryParseURI(repoUri) + .map(remote -> fromRemote(localDir.resolve(repoName), remote, repoName)); + } + + /** + * @return the location type indicating how this repository is stored. + */ + public RepositoryLocationType getRepoLocation() { + return repoLocation; + } + + /** + * The path to the repository on disk. + * The path points to the root directory of the repository if the repository is stored in a directory. + * The path points to a zip file if the repository is stored in a zip file. + * The path points to a (possibly not existing) directory to which the repository should be cloned to if the + * repository is stored on a remote server. + * @see Repository#getRepoLocation() + * @see RepositoryLocationType + * @return The path to the repository on disk. + */ + public Path getLocalPath() { + return localPath; + } + + /** + * URI of the origin of this repository (i.e., usually the location on a server where this repository was cloned from). + */ + public URI getRemoteURI() { + return remote; + } + + /** + * The name of this repository. Should be unique. + */ + public String getRepositoryName() { + return repositoryName; + } + + /** + * Set options for parsing parts of this repository's evolution history. + * @param parseOptions Options for parsing the evolution history. + * @return this + */ + public Repository setParseOptions(PatchDiffParseOptions parseOptions) { + this.parseOptions = parseOptions; return this; - } - - /** - * Set the diff filter for reading this repository. - * The diff filter decides which commits and files should be considered for analyses. - * @param filter Filter to apply when traversing this repository's commit history. - * @return this - */ - public Repository setDiffFilter(final DiffFilter filter) { - this.diffFilter = filter; + } + + /** + * Set the diff filter for reading this repository. + * The diff filter decides which commits and files should be considered for analyses. + * @param filter Filter to apply when traversing this repository's commit history. + * @return this + */ + public Repository setDiffFilter(final DiffFilter filter) { + this.diffFilter = filter; return this; - } - - /** - * The diff filter decides which commits and files should be considered for analyses. - */ - public DiffFilter getDiffFilter() { - return diffFilter; - } - - - /** - * Options that should be used when parsing the evolution history. - */ - public PatchDiffParseOptions getParseOptions() { - return parseOptions; - } - - /** - * Returns the internal jgit representation of this repository that allows to inspect the repositories history and content. - */ - public Lazy getGitRepo() { - return git; - } - - /** - * Loads this repository and returns a jgit representation to access it. - */ - private Git load() { - Logger.info("Loading git at {} ...", getLocalPath()); - return switch (getRepoLocation()) { - case FROM_DIR -> GitLoader.fromDirectory(getLocalPath()); - case FROM_ZIP -> GitLoader.fromZip(getLocalPath()); - case FROM_REMOTE -> GitLoader.fromRemote(getLocalPath(), getRemoteURI()); - default -> throw new UnsupportedOperationException("Unknown git repo source"); - }; - } -} \ No newline at end of file + } + + /** + * The diff filter decides which commits and files should be considered for analyses. + */ + public DiffFilter getDiffFilter() { + return diffFilter; + } + + + /** + * Options that should be used when parsing the evolution history. + */ + public PatchDiffParseOptions getParseOptions() { + return parseOptions; + } + + /** + * Returns the internal jgit representation of this repository that allows to inspect the repositories history and content. + */ + public Git getGitRepo() { + return git.run(); + } + + /** + * Prepares the Git repository (e.g., clones it if necessary). + */ + public void preload() { + getGitRepo(); + } + + /** + * Returns a single commit from the repository. + * Note that this commit may not be part of {@link #getCommits}. + */ + public RevCommit getCommit(String commitHash) throws IOException { + return getGitRepo().getRepository().parseCommit(ObjectId.fromString(commitHash)); + } + + /** + * Returns all commits in the repository's history. + */ + public Iterator getCommits() { + return commitLister.listCommits(this); + } + + /** + * Loads this repository and returns a jgit representation to access it. + */ + private Git load() { + Logger.info("Loading git at {} ...", getLocalPath()); + return switch (getRepoLocation()) { + case FROM_DIR -> GitLoader.fromDirectory(getLocalPath()); + case FROM_ZIP -> GitLoader.fromZip(getLocalPath()); + case FROM_REMOTE -> GitLoader.fromRemote(getLocalPath(), getRemoteURI()); + default -> throw new UnsupportedOperationException("Unknown git repo source"); + }; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/datasets/RepositoryLocationType.java b/src/main/java/org/variantsync/diffdetective/datasets/RepositoryLocationType.java index f0656cb0f..4e5dd5e44 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/RepositoryLocationType.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/RepositoryLocationType.java @@ -2,22 +2,22 @@ /** * Read the input from a local directory, a local zip file or a remote repository. - * + * * @author Kevin Jedelhauser */ public enum RepositoryLocationType { - /** - * Load repository from a local directory. - */ - FROM_DIR, - - /** - * Load repository from a local zip file. - */ - FROM_ZIP, - - /** - * Load repository from a remote location. - */ - FROM_REMOTE + /** + * Load repository from a local directory. + */ + FROM_DIR, + + /** + * Load repository from a local zip file. + */ + FROM_ZIP, + + /** + * Load repository from a remote location. + */ + FROM_REMOTE } 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 afea92104..9ee846ee3 100644 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/Marlin.java @@ -2,22 +2,19 @@ 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.AnnotationParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; import java.nio.file.Path; /** * Default repository for Marlin. * - * @author Kevin Jedelhauser, Paul Maximilian Bittner + * @author Kevin Jedelhauser, Paul Maximilian Bittner, Benjamin Moosherr */ public class Marlin { - public static final PreprocessorAnnotationParser ANNOTATION_PARSER = - PreprocessorAnnotationParser.CreateCppAnnotationParser( - PropositionalFormulaParser.Default, - new MarlinCPPDiffLineFormulaExtractor() - ); + public static final AnnotationParser ANNOTATION_PARSER = + new CPPAnnotationParser(new MarlinControllingCExpressionVisitor()); /** * Clones Marlin from Github. diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java deleted file mode 100644 index fa1fc3cc0..000000000 --- a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinCPPDiffLineFormulaExtractor.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.variantsync.diffdetective.datasets.predefined; - -import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor; - -import java.util.regex.Pattern; - -/** - * 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 { - private static final Pattern ENABLED_PATTERN = Pattern.compile("ENABLED\\s*\\(([^)]*)\\)"); - private static final Pattern DISABLED_PATTERN = Pattern.compile("DISABLED\\s*\\(([^)]*)\\)"); - - @Override - public String resolveFeatureMacroFunctions(String formula) { - return - replaceAll(ENABLED_PATTERN, "$1", - replaceAll(DISABLED_PATTERN, "!($1)", - super.resolveFeatureMacroFunctions(formula))); - } - - private String replaceAll(Pattern pattern, String replacement, String string) { - return pattern.matcher(string).replaceAll(replacement); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinControllingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinControllingCExpressionVisitor.java new file mode 100644 index 000000000..a7f370c77 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/datasets/predefined/MarlinControllingCExpressionVisitor.java @@ -0,0 +1,40 @@ +package org.variantsync.diffdetective.datasets.predefined; + +import org.antlr.v4.runtime.tree.TerminalNode; +import org.prop4j.Node; +import org.variantsync.diffdetective.feature.antlr.CExpressionParser; +import org.variantsync.diffdetective.feature.cpp.ControllingCExpressionVisitor; + +import java.util.List; + +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; + +/** + * Parses C preprocessor annotations in the marlin firmware. + *

+ * 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 arguments = ctx.macroExpression().argumentExpressionList().assignmentExpression(); + + if (arguments.size() == 1) { + if (name.getText().equals("ENABLED")) { + return abstractToLiteral(arguments.get(0)); + } + if (name.getText().equals("DISABLED")) { + return negate(abstractToLiteral(arguments.get(0))); + } + } + } + + return super.visitPrimaryExpression(ctx); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/CommitDiff.java b/src/main/java/org/variantsync/diffdetective/diff/git/CommitDiff.java index be92ba3b2..cc22540b3 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/CommitDiff.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/CommitDiff.java @@ -18,9 +18,9 @@ public class CommitDiff { public final static String INVALID_COMMIT_HASH = "none"; - /** - * A list of all {@link PatchDiff PatchDiffs} of a {@link CommitDiff}. - */ + /** + * A list of all {@link PatchDiff PatchDiffs} of a {@link CommitDiff}. + */ private final List patchDiffs; /** @@ -32,7 +32,7 @@ public class CommitDiff { * The hash of the current commit. */ private final String commitHash; - + final boolean merge; /** @@ -56,7 +56,7 @@ public CommitDiff(RevCommit commit, RevCommit parent) { /** * Add a {@link PatchDiff}. * The given diff should belong to the changes between the commits of this CommitDiff. - * + * * @param patchDiff The {@link PatchDiff PatchDiff} to be added */ public void addPatchDiff(PatchDiff patchDiff){ diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/DiffFilter.java b/src/main/java/org/variantsync/diffdetective/diff/git/DiffFilter.java index fe9c67b9c..eff2d5a97 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/DiffFilter.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/DiffFilter.java @@ -4,9 +4,11 @@ import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.revwalk.RevCommit; import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.functjonal.iteration.Yield; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; /** @@ -27,6 +29,19 @@ public class DiffFilter { .allowAllFileExtensions() .build(); + /** + * Default value for diff filters. + * It disallows merge commits, only considers patches that modified files, + * and only allows source files of C/C++ projects ("h", "hpp", "c", "cpp"). + */ + public static final DiffFilter DEFAULT_DIFF_FILTER = + new DiffFilter.Builder() + .allowMerge(false) + .allowCommitsWithoutParents(false) + .allowedChangeTypes(DiffEntry.ChangeType.MODIFY) + .allowedFileExtensions("h", "hpp", "c", "cpp") + .build(); + /** * A list of allowed file extensions for patches. * When this list is not empty all file extension that it does not contain will be filtered. @@ -317,6 +332,31 @@ public boolean filter(RevCommit commit) { ; } + /** + * Filters all undesired commits from the given list of commits. + * @param commitsIterator Commits to filter. + * @return All commits from the given set that should not be filtered out. + */ + public Iterator filter(final Iterator commitsIterator) { + return new Yield<>( + () -> { + while (commitsIterator.hasNext()) { + final RevCommit c = commitsIterator.next(); + // If this commit is filtered, go to the next one. + // filter returns true if we want to include the commit + // so if we do not want to filter it, we do not want to have it. Thus skip. + if (!filter(c)) { + continue; + } + + return c; + } + + return null; + } + ); + } + private boolean isAllowedPath(String filename) { return allowedPaths.stream().anyMatch(filename::matches); } diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java b/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java index 1e6ae5f45..a79f8936a 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java @@ -1,7 +1,6 @@ package org.variantsync.diffdetective.diff.git; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; @@ -13,7 +12,6 @@ import org.eclipse.jgit.treewalk.*; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.tinylog.Logger; -import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; import org.variantsync.diffdetective.datasets.Repository; import org.variantsync.diffdetective.diff.result.CommitDiffResult; import org.variantsync.diffdetective.diff.result.DiffError; @@ -23,28 +21,22 @@ import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; -import org.variantsync.functjonal.iteration.MappedIterator; -import org.variantsync.functjonal.iteration.SideEffectIterator; -import org.variantsync.functjonal.iteration.Yield; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * This class creates a GitDiff-object from a git repository (Git-object). + * This class provides utility functions for obtaining diffs from git {@link Repository repositories}. *

- * 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 yieldRevCommits() { - final Iterable commitsIterable; - try { - commitsIterable = git.log().call(); - } catch (GitAPIException e) { - Logger.warn("Could not get log for git repository {}", git.toString()); - return null; - } - - return yieldAllValidIn(commitsIterable.iterator()); - } - - /** - * The same as {@link GitDiffer#yieldRevCommits()} but applies the given function f to each commit - * before returning it. - * @param f A function to map over all commits before they can be accessed. - * Each returned commit was processed by f exactly once. - * @return All commits in the repository's history after applying the given function to each commit. - */ - public Yield yieldRevCommitsAfter(final Function f) { - Iterable commitsIterable; - try { - commitsIterable = git.log().call(); - } catch (GitAPIException e) { - Logger.warn("Could not get log for git repository {}", git.toString()); - return null; - } - - return yieldAllValidIn(new MappedIterator<>(commitsIterable.iterator(), f)); - } - - /** - * Filters all undesired commits from the given set of commits using the {@link DiffFilter} of - * this differs repository. - * @see GitDiffer#GitDiffer(Repository) - * @param commitsIterator Commits to filter. - * @return All commits from the given set that should not be filtered. - */ - private Yield yieldAllValidIn(final Iterator commitsIterator) { - return new Yield<>( - () -> { - while (commitsIterator.hasNext()) { - final RevCommit c = commitsIterator.next(); - // If this commit is filtered, go to the next one. - // filter returns true if we want to include the commit - // so if we do not want to filter it, we do not want to have it. Thus skip. - if (!diffFilter.filter(c)) { - continue; - } - - return c; - } - - return null; - } - ); - } - - public RevCommit getCommit(String commitHash) throws IOException { - return git.getRepository().parseCommit(ObjectId.fromString(commitHash)); - } - - public CommitDiffResult createCommitDiff(final String commitHash) throws IOException { - return createCommitDiff(getCommit(commitHash)); + private GitDiffer() { } - public CommitDiffResult createCommitDiff(final RevCommit revCommit) { - return createCommitDiffFromFirstParent(git, diffFilter, revCommit, parseOptions); + public static CommitDiffResult createCommitDiffFromFirstParent( + Repository repository, + String commitHash) throws IOException { + return createCommitDiffFromFirstParent(repository, repository.getCommit(commitHash)); } /** * Creates a CommitDiff from a given commit. * For this, the git diff is retrieved using JGit. * For each file in the diff, a PatchDiff is created. + *

+ * 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 errors = new ArrayList<>(); // get PatchDiffs try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); DiffFormatter diffFormatter = new DiffFormatter(outputStream)) { - diffFormatter.setRepository(git.getRepository()); + diffFormatter.setRepository(repository.getGitRepo().getRepository()); diffFormatter.setDetectRenames(true); diffFormatter.getRenameDetector().setRenameScore(50); List entries = diffFormatter.scan(prevTreeParser, currentTreeParser); for (DiffEntry diffEntry : entries) { - if (!diffFilter.filter(diffEntry)) { + if (!repository.getDiffFilter().filter(diffEntry)) { continue; } @@ -323,7 +233,7 @@ private static CommitDiffResult getPatchDiffs( yield hunkBeginAndRest[1]; } case RENAME, COPY, MODIFY -> { - final BufferedReader beforeFullFile = getBeforeFullFile(git, parentCommit, filename); + final BufferedReader beforeFullFile = getBeforeFullFile(repository, parentCommit, filename); yield getFullDiff(beforeFullFile, new BufferedReader(new StringReader(strippedDiff))); } }; @@ -344,11 +254,11 @@ private static CommitDiffResult getPatchDiffs( final VariationDiff variationDiff = VariationDiffParser.createVariationDiff( fullDiff, - parseOptions.variationDiffParseOptions() + repository.getParseOptions().variationDiffParseOptions() ); // not storing the full diff reduces memory usage by around 40-50% - final String diffToRemember = switch (parseOptions.diffStoragePolicy()) { + final String diffToRemember = switch (repository.getParseOptions().diffStoragePolicy()) { case DO_NOT_REMEMBER -> ""; case REMEMBER_DIFF -> gitDiff; case REMEMBER_FULL_DIFF -> fullDiff; @@ -459,11 +369,13 @@ public static String getFullDiff(BufferedReader beforeFile, BufferedReader gitDi /** * Gets the full content of a file before a commit. * - * @param commit The commit in which the file was changed + * @param repository The repository which contains {@code commit} + * @param commit The commit in which the file was changed * @param filename The name of the file * @return The full content of the file before the commit */ - public static BufferedReader getBeforeFullFile(Git git, RevCommit commit, String filename) throws IOException { + public static BufferedReader getBeforeFullFile(Repository repository, RevCommit commit, String filename) throws IOException { + Git git = repository.getGitRepo(); RevTree tree = commit.getTree(); try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) { @@ -481,11 +393,4 @@ public static BufferedReader getBeforeFullFile(Git git, RevCommit commit, String return new BufferedReader(new InputStreamReader(loader.openStream())); } } - - /** - * Returns the internal representation of this differs repository. - */ - public Git getJGitRepo() { - return git; - } } diff --git a/src/main/java/org/variantsync/diffdetective/error/UncheckedUnParseableFormulaException.java b/src/main/java/org/variantsync/diffdetective/error/UncheckedUnparseableFormulaException.java similarity index 75% rename from src/main/java/org/variantsync/diffdetective/error/UncheckedUnParseableFormulaException.java rename to src/main/java/org/variantsync/diffdetective/error/UncheckedUnparseableFormulaException.java index 1cbd0de44..6ea316223 100644 --- a/src/main/java/org/variantsync/diffdetective/error/UncheckedUnParseableFormulaException.java +++ b/src/main/java/org/variantsync/diffdetective/error/UncheckedUnparseableFormulaException.java @@ -3,10 +3,10 @@ /** * Runtime exception for cases in which a formula extracted from a diff cannot be parsed. */ -public class UncheckedUnParseableFormulaException extends RuntimeException { +public class UncheckedUnparseableFormulaException extends RuntimeException { final UnparseableFormulaException inner; - public UncheckedUnParseableFormulaException(String message, Exception e) { + public UncheckedUnparseableFormulaException(String message, Exception e) { super(message, e); inner = new UnparseableFormulaException(message, e); } diff --git a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java index 3d911c628..ebe8d1a11 100644 --- a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java +++ b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java @@ -100,7 +100,6 @@ public static List> split(List elements, Predicate split) { private boolean checkIfExample(Analysis analysis, String localDiff) { // Logger.info(localDiff); - final Repository currentRepo = analysis.getRepository(); final VariationDiff variationDiff = analysis.getCurrentVariationDiff(); final AnnotationParser annotationParser = analysis.getRepository().getParseOptions().variationDiffParseOptions().annotationParser(); diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java index 930fb3a71..75c04e6a3 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java @@ -384,7 +384,7 @@ private VariationDiff parseVariationTree(Analysis analysis, RevC */ new CharacterFilterReader( GitDiffer.getBeforeFullFile( - analysis.getRepository().getGitRepo().run(), + analysis.getRepository(), commit, analysis.getCurrentPatch().getFileName(AFTER)), 0xfeff)) // BOM, same as GitDiffer.BOM_PATTERN diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views/ViewAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/views/ViewAnalysis.java index 0ab7cd656..6c6087c76 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views/ViewAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views/ViewAnalysis.java @@ -181,7 +181,7 @@ else if (a.isConditionalAnnotation()) { return relevances; } - + /** * This is a convenience method for creating a random relevance predicate from a collection of * potential parameterisations. @@ -254,7 +254,7 @@ private List allConfigureRelevances(final List deselectedPCs) { return all; } - + /** * Creates a random {@link Configure} relevance predicate from the given list of * deselected presence conditions. @@ -297,7 +297,7 @@ private Configure randomConfigure(final List deselectedPCs) { return new Configure(winner); } - + /** * Generates a {@link Trace} relevance predicate for each feature in the given set of feature names. * @param features A set of feature names to trace. diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java b/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java index bce83717e..8211574bb 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java @@ -16,7 +16,7 @@ * @param relevance The relevance predicate from which the views were generated. * @param msNaive Milliseconds it took to generate the view with the {@link DiffView#naive(VariationDiff, Relevance) naive algorithm} * @param msOptimized Milliseconds it took to generate the view with the {@link DiffView#optimized(VariationDiff, Relevance) optimized algorithm} - * @param diffStatistics Various statistics on the variation diff of the analysed patch. + * @param diffStatistics Various statistics on the variation diff of the analysed patch. * @param viewStatistics The same statistics as for the original variation diff but for the produced view. */ public record ViewEvaluation( diff --git a/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java deleted file mode 100644 index ffe4528cc..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/AbstractingFormulaExtractor.java +++ /dev/null @@ -1,89 +0,0 @@ -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/Annotation.java b/src/main/java/org/variantsync/diffdetective/feature/Annotation.java new file mode 100644 index 000000000..f6409444b --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/Annotation.java @@ -0,0 +1,38 @@ +package org.variantsync.diffdetective.feature; + +import org.prop4j.Node; + +/** + * Represents a new annotation (i.e., a change in the presence condition). + * This includes, {@link AnnotationType#If adding to the presence condition}, + * {@link AnnotationType#Endif removing the most recent formula} + * and {@link AnnotationType others}. + * + * @param type the type of this annotation + * @param formula the formula associated to {@code type}. + * Non-null iff {@link AnnotationType#requiresFormula type.requiresFormula}. + */ +public record Annotation( + AnnotationType type, + Node formula +) { + public Annotation(AnnotationType type, Node formula) { + this.type = type; + this.formula = formula; + + if (type.requiresFormula && formula == null) { + throw new IllegalArgumentException("Annotations of type " + type.name + " but got null"); + } + if (!type.requiresFormula && formula != null) { + throw new IllegalArgumentException("Annotations of type " + type.name + " do not accept a formula but it was given " + formula); + } + } + + /** + * Equivalent to {@link #Annotation(AnnotationType, Node) Annotation(type, null)}. + * Hence, only useable iff {@link AnnotationType#requiresFormula !type.requiresFormula}. + */ + public Annotation(AnnotationType type) { + this(type, null); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java index b6c21af67..284516da5 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationParser.java @@ -1,31 +1,23 @@ package org.variantsync.diffdetective.feature; -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}. + * of the annotation (see {@link AnnotationType}), and parsing the annotation into a {@link org.prop4j.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). + * Parse the given line as an annotation. + * Note that {@code line} might also be a line in a diff (i.e., preceded by {@code -} or {@code +}). * - * @param text The text of which the type is determined. - * @return The annotation type of the piece of text. + * @param line that might contain an annotation + * @return the annotation type and the associated formula. + * If {@code line} doesn't contain an annotation, returns {@code Annotation(AnnotationType.NONE)}. + * @throws UnparseableFormulaException if an annotation is detected but it is malformed */ - AnnotationType determineAnnotationType(String text); - - /** - * 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. - * 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; + Annotation parseAnnotation(String line) throws UnparseableFormulaException; } diff --git a/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java b/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java index f66a12bca..05d2902ef 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java +++ b/src/main/java/org/variantsync/diffdetective/feature/AnnotationType.java @@ -9,34 +9,39 @@ 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"), + If("if", true), /** * 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"), + Elif("elif", true), /** * 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"), + Else("else", false), /** * The piece of text (e.g., "#endif") marks the end of an annotation (chain). */ - Endif("endif"), + Endif("endif", false), /** * The piece of text contains no annotation. This usually means that it contains an artifact. */ - None("NONE"); + None("NONE", false); public final String name; + /** + * Does this type of annotation require a formula? + */ + public final boolean requiresFormula; - AnnotationType(String name) { + AnnotationType(String name, boolean requiresFormula) { this.name = name; + this.requiresFormula = requiresFormula; } /** diff --git a/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java b/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java deleted file mode 100644 index 684af5bc2..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/BooleanAbstraction.java +++ /dev/null @@ -1,374 +0,0 @@ -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; - -/** - * Boolean abstraction for conditions in preprocessor macros. - * Boolean abstraction heuristically reduces expressions in - * 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() { - } - - /** - * 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 <=. - */ - 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. - *

- * 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 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 - ); - - /** - * Apply all possible abstraction replacements for substrings of the given formula. - * - * @param formula the formula to abstract - * @return a fully abstracted formula - */ - public static String abstractAll(String formula) { - for (var replacement : BooleanAbstraction.REPLACEMENTS) { - formula = replacement.applyTo(formula); - } - return formula; - } - - /** - *

- * 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. - *

- * - * @param text the text to abstract - * @return a fully abstracted text - */ - public static String abstractToken(String text) { - for (Replacement replacement : REPLACEMENTS) { - if (replacement.pattern.matcher(text).matches()) { - return replacement.applyTo(text); - } - } - return abstractAll(text); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java deleted file mode 100644 index bfdcce3af..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/DiffLineFormulaExtractor.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.variantsync.diffdetective.feature; - -import org.variantsync.diffdetective.error.UnparseableFormulaException; - -/** - * 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 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 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. - *

- * 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 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"))}. + *

+ * 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: + * + *

    + *
  • We handle the parse rule {@code Defined specialOperatorArgument?} specially and return + * {@code "defined(argument)"} instead of {@code "definedargument"}. This prevents clashes with + * macros called {@code "definedargument"} and makes the result consistent with the semantically + * equivalent rule {@code Defined ('(' specialOperatorArgument ')')}. + *
* - *

- * 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. - *

+ * @author Benjamin Moosherr */ -@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)* - // ; +public class AbstractingCExpressionVisitor extends AbstractParseTreeVisitor { @Override - public StringBuilder visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.ShiftExpressionContext); + public StringBuilder visitTerminal(TerminalNode node) { + return new StringBuilder(node.getSymbol().getText()); } - // equalityExpression - // : relationalExpression (('=='| '!=') relationalExpression)* - // ; @Override - public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.RelationalExpressionContext); + protected StringBuilder defaultResult() { + return new StringBuilder(); } - // andExpression - // : equalityExpression ( '&' equalityExpression)* - // ; @Override - public StringBuilder visitAndExpression(CExpressionParser.AndExpressionContext ctx) { - return visitExpression(ctx, childContext -> childContext instanceof CExpressionParser.EqualityExpressionContext); + protected StringBuilder aggregateResult(StringBuilder aggregate, StringBuilder nextResult) { + aggregate.append(nextResult); + return aggregate; } - // 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); + public StringBuilder visitChildren(RuleNode node) { + // Some rules need special cases + if (node instanceof CExpressionParser.SpecialOperatorContext ctx) { + return visitSpecialOperator(ctx); + } else { + return super.visitChildren(node); + } } // specialOperator @@ -198,145 +59,14 @@ public StringBuilder visitInclusiveOrExpression(CExpressionParser.InclusiveOrExp // | 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)); + if (ctx.getChildCount() == 2) { + StringBuilder sb = ctx.specialOperatorArgument().accept(this); + sb.insert(0, "defined("); + sb.append(")"); 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 super.visitChildren(ctx); } - return sb; } -} \ No newline at end of file +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java new file mode 100644 index 000000000..51de61754 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPAnnotationParser.java @@ -0,0 +1,80 @@ +package org.variantsync.diffdetective.feature.cpp; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.prop4j.Literal; +import org.prop4j.Node; +import org.prop4j.Not; +import org.tinylog.Logger; +import org.variantsync.diffdetective.error.UncheckedUnparseableFormulaException; +import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.ParseErrorListener; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; +import org.variantsync.diffdetective.feature.antlr.CExpressionLexer; +import org.variantsync.diffdetective.feature.antlr.CExpressionParser; + +import java.util.regex.Pattern; + +/** + * Parses a C preprocessor statement. + * For example, given the annotation {@code "#if defined(A) || B()"}, + * this class extracts the type {@link org.variantsync.diffdetective.feature.AnnotationType#If} with the formula {@code new Or(new Literal("A"), new Literal("B"))}. + * The extractor detects if, ifdef, ifndef, elif, elifdef, elifndef, else and endif annotations. + * All other annotations are considered source code. + * The given CPP statement might also be a line in a diff (i.e., preceded by a - or +). + * + * @author Paul Bittner, Sören Viegener, Benjamin Moosherr, Alexander Schultheiß + */ +public class CPPAnnotationParser extends PreprocessorAnnotationParser { + // Note that this pattern doesn't handle comments between {@code #} and the macro name. + private static final String CPP_ANNOTATION_REGEX = "^[+-]?\\s*#\\s*(?if|ifdef|ifndef|elif|elifdef|elifndef|else|endif)(?[\\s(].*)?$"; + private static final Pattern CPP_ANNOTATION_PATTERN = Pattern.compile(CPP_ANNOTATION_REGEX); + + private ParseTreeVisitor formulaVisitor; + + public CPPAnnotationParser(ParseTreeVisitor formulaVisitor) { + super(CPP_ANNOTATION_PATTERN); + this.formulaVisitor = formulaVisitor; + } + + public CPPAnnotationParser() { + this(new ControllingCExpressionVisitor()); + } + + @Override + public Node parseFormula(final String directive, final String formula) throws UnparseableFormulaException { + Node parsedFormula; + try { + CExpressionLexer lexer = new CExpressionLexer(CharStreams.fromString(formula)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + + CExpressionParser parser = new CExpressionParser(tokens); + parser.addErrorListener(new ParseErrorListener(formula)); + + parsedFormula = parser.expression().accept(formulaVisitor); + } catch (UncheckedUnparseableFormulaException e) { + throw e.inner(); + } catch (Exception e) { + Logger.warn(e); + throw new UnparseableFormulaException(e); + } + + // treat {@code #ifdef id}, {@code #ifndef id}, {@code #elifdef id} and {@code #elifndef id} + // like {@code defined(id)} and {@code !defined(id)} + if (directive.endsWith("def")) { + if (parsedFormula instanceof Literal literal) { + literal.var = String.format("defined(%s)", literal.var); + + // negate for ifndef + if (directive.endsWith("ndef")) { + literal.positive = false; + } + } else { + throw new UnparseableFormulaException("When using #ifdef, #ifndef, #elifdef or #elifndef, only literals are allowed. Hence, \"" + formula + "\" is disallowed."); + } + } + + return parsedFormula; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java deleted file mode 100644 index 9d77c5c23..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/cpp/CPPDiffLineFormulaExtractor.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.variantsync.diffdetective.feature.cpp; - -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.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 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 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_PATTERN = Pattern.compile(CPP_ANNOTATION_REGEX); - - public CPPDiffLineFormulaExtractor() { - super(CPP_ANNOTATION_PATTERN); - } - - /** - * 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 - */ - @Override - public String extractFormula(final String line) throws UnparseableFormulaException { - // Delegate the formula extraction to AbstractingFormulaExtractor - String fm = super.extractFormula(line); - - // negate for ifndef - final Matcher matcher = CPP_ANNOTATION_PATTERN.matcher(line); - if (matcher.find() && "ifndef".equals(matcher.group(1))) { - fm = "!(" + fm + ")"; - } - - return fm; - } - - /** - * Abstract the given formula. - *

- * 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. - *

- * - * @param formula that is to be abstracted - * @return the abstracted 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 ParseErrorListener(formula)); - - return parser.expression().accept(new ControllingCExpressionVisitor()).toString(); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java index 8bc9fed28..078480afe 100644 --- a/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java +++ b/src/main/java/org/variantsync/diffdetective/feature/cpp/ControllingCExpressionVisitor.java @@ -3,12 +3,18 @@ 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.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.prop4j.Node; import org.variantsync.diffdetective.feature.antlr.CExpressionParser; import org.variantsync.diffdetective.feature.antlr.CExpressionVisitor; +import org.variantsync.diffdetective.util.fide.FormulaUtils; +import java.util.List; import java.util.function.Function; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; + /** * 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. @@ -16,8 +22,8 @@ * 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 class ControllingCExpressionVisitor extends AbstractParseTreeVisitor implements CExpressionVisitor { + private final ParseTreeVisitor abstractingVisitor = new AbstractingCExpressionVisitor(); public ControllingCExpressionVisitor() { } @@ -26,15 +32,9 @@ public ControllingCExpressionVisitor() { // : 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); - } + public Node visitConditionalExpression(CExpressionParser.ConditionalExpressionContext ctx) { + // We have to abstract the expression if it is a ternary expression + return recurseOnSingleChild(ctx); } // primaryExpression @@ -47,53 +47,51 @@ public StringBuilder visitConditionalExpression(CExpressionParser.ConditionalExp // | specialOperator // ; @Override - public StringBuilder visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { + public Node visitPrimaryExpression(CExpressionParser.PrimaryExpressionContext ctx) { // macroExpression if (ctx.macroExpression() != null) { - return ctx.macroExpression().accept(abstractingVisitor); + return abstractToLiteral(ctx); } // Identifier if (ctx.Identifier() != null) { - // Terminal - return ctx.accept(abstractingVisitor); + return abstractToLiteral(ctx); } // Constant if (ctx.Constant() != null) { - // Terminal - return new StringBuilder(ctx.Constant().getText().trim()); + return abstractToLiteral(ctx); } // StringLiteral+ if (!ctx.StringLiteral().isEmpty()) { - return ctx.accept(abstractingVisitor); + return abstractToLiteral(ctx); } // '(' expression ')' if (ctx.expression() != null) { - StringBuilder sb = ctx.expression().accept(this); - sb.insert(0, "("); - sb.append(")"); - return sb; + return ctx.expression().accept(this); } // unaryOperator primaryExpression if (ctx.unaryOperator() != null) { - StringBuilder sb = ctx.unaryOperator().accept(this); - sb.append(ctx.primaryExpression().accept(this)); - return sb; + // Negation can be modeled in the formula. + // All other unary operators need to be abstracted. + if (ctx.unaryOperator().getText().equals("!")) { + return negate(ctx.primaryExpression().accept(this)); + } else { + return abstractToLiteral(ctx); + } } // specialOperator if (ctx.specialOperator() != null) { - return ctx.specialOperator().accept(abstractingVisitor); + return abstractToLiteral(ctx.specialOperator()); } - // Unreachable - throw new IllegalStateException("Unreachable code."); + throw new IllegalStateException("Unreachable code"); } // unaryOperator // : '&' | '*' | '+' | '-' | '~' | '!' // ; @Override - public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { - return new StringBuilder(ctx.getText()); + public Node visitUnaryOperator(CExpressionParser.UnaryOperatorContext ctx) { + throw new IllegalStateException("Unreachable code"); } @@ -101,96 +99,48 @@ public StringBuilder visitUnaryOperator(CExpressionParser.UnaryOperatorContext c // : 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); - } + public Node visitNamespaceExpression(CExpressionParser.NamespaceExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitMultiplicativeExpression(CExpressionParser.MultiplicativeExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitAdditiveExpression(CExpressionParser.AdditiveExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitShiftExpression(CExpressionParser.ShiftExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitRelationalExpression(CExpressionParser.RelationalExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitEqualityExpression(CExpressionParser.EqualityExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // specialOperator @@ -203,9 +153,9 @@ public StringBuilder visitEqualityExpression(CExpressionParser.EqualityExpressio // | Defined specialOperatorArgument? // ; @Override - public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { + public Node visitSpecialOperator(CExpressionParser.SpecialOperatorContext ctx) { // We have to abstract the special operator - return ctx.accept(abstractingVisitor); + return abstractToLiteral(ctx); } // specialOperatorArgument @@ -218,16 +168,16 @@ public StringBuilder visitSpecialOperator(CExpressionParser.SpecialOperatorConte // | Identifier // ; @Override - public StringBuilder visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { - return ctx.accept(abstractingVisitor); + public Node visitSpecialOperatorArgument(CExpressionParser.SpecialOperatorArgumentContext ctx) { + return abstractToLiteral(ctx); } // macroExpression // : Identifier '(' argumentExpressionList? ')' // ; @Override - public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { - return ctx.accept(abstractingVisitor); + public Node visitMacroExpression(CExpressionParser.MacroExpressionContext ctx) { + return abstractToLiteral(ctx); } // argumentExpressionList @@ -235,8 +185,8 @@ public StringBuilder visitMacroExpression(CExpressionParser.MacroExpressionConte // | assignmentExpression (assignmentExpression)* // ; @Override - public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { - return ctx.accept(abstractingVisitor); + public Node visitArgumentExpressionList(CExpressionParser.ArgumentExpressionListContext ctx) { + throw new IllegalStateException("Unreachable code"); } // assignmentExpression @@ -247,11 +197,11 @@ public StringBuilder visitArgumentExpressionList(CExpressionParser.ArgumentExpre // | primaryExpression assignmentOperator assignmentExpression // ; @Override - public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { + public Node visitAssignmentExpression(CExpressionParser.AssignmentExpressionContext ctx) { if (ctx.conditionalExpression() != null) { return ctx.conditionalExpression().accept(this); } else { - return ctx.accept(abstractingVisitor); + return abstractToLiteral(ctx); } } @@ -259,110 +209,99 @@ public StringBuilder visitAssignmentExpression(CExpressionParser.AssignmentExpre // : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' // ; @Override - public StringBuilder visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { - return ctx.accept(abstractingVisitor); + public Node visitAssignmentOperator(CExpressionParser.AssignmentOperatorContext ctx) { + throw new IllegalStateException("Unreachable code"); } // 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); - } + public Node visitExpression(CExpressionParser.ExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitAndExpression(CExpressionParser.AndExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitExclusiveOrExpression(CExpressionParser.ExclusiveOrExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // 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); - } + public Node visitInclusiveOrExpression(CExpressionParser.InclusiveOrExpressionContext ctx) { + return recurseOnSingleChild(ctx); } // logicalAndExpression // : logicalOperand ( '&&' logicalOperand)* // ; @Override - public StringBuilder visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { - return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalOperandContext); + public Node visitLogicalAndExpression(CExpressionParser.LogicalAndExpressionContext ctx) { + return visitLogicalExpression(ctx, FormulaUtils::and); } // logicalOrExpression // : logicalAndExpression ( '||' logicalAndExpression)* // ; @Override - public StringBuilder visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { - return visitLogicalExpression(ctx, childExpression -> childExpression instanceof CExpressionParser.LogicalAndExpressionContext); + public Node visitLogicalOrExpression(CExpressionParser.LogicalOrExpressionContext ctx) { + return visitLogicalExpression(ctx, FormulaUtils::or); } // logicalOperand // : inclusiveOrExpression // ; @Override - public StringBuilder visitLogicalOperand(CExpressionParser.LogicalOperandContext ctx) { + public Node 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(); + // logicalAndExpression + // : logicalOperand ( '&&' logicalOperand)* + // ; + // logicalOrExpression + // : logicalAndExpression ( '||' logicalAndExpression)* + // ; + protected Node visitLogicalExpression(ParserRuleContext expressionContext, Function newLogicNode) { + if (expressionContext.getChildCount() == 1) { + return expressionContext.getChild(0).accept(this); + } else { + List subtrees = expressionContext.children; + + // Skip every second node. These nodes are either "&&" or "||". + Node[] children = new Node[1 + (subtrees.size() - 1) / 2]; + for (int i = 0; i < children.length; ++i) { + children[i] = subtrees.get(2 * i).accept(this); } + + return newLogicNode.apply(children); + } + } + + protected Node recurseOnSingleChild(ParserRuleContext ctx) { + if (ctx.getChildCount() > 1) { + // We have to abstract the expression if there is more than one operand + return abstractToLiteral(ctx); + } else { + // There is exactly one child expression so we recurse + return ctx.getChild(0).accept(this); } - return sb; } -} \ No newline at end of file + + protected Node abstractToLiteral(ParserRuleContext ctx) { + return var(ctx.accept(abstractingVisitor).toString()); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java deleted file mode 100644 index af2bbe1fd..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/AbstractingJPPExpressionVisitor.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.variantsync.diffdetective.feature.jpp; - -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.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 ctx.logicalOrExpression().accept(this); - } - - // logicalOrExpression - // : logicalAndExpression (OR logicalAndExpression)* - // ; - @Override - public StringBuilder visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { - return visitLogicalExpression(ctx, - childExpression -> childExpression instanceof JPPExpressionParser.LogicalAndExpressionContext); - } - - // logicalAndExpression - // : primaryExpression (AND primaryExpression)* - // ; - @Override - public StringBuilder visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { - return visitLogicalExpression(ctx, - childExpression -> childExpression instanceof JPPExpressionParser.PrimaryExpressionContext); - } - - // primaryExpression - // : definedExpression - // | undefinedExpression - // | comparisonExpression - // ; - @Override - public StringBuilder visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { - 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 - // : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? - // ; - @Override - public StringBuilder visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { - return visitExpression(ctx, childExpression -> childExpression instanceof JPPExpressionParser.OperandContext); - } - - // operand - // : propertyExpression - // | Constant - // | StringLiteral+ - // | unaryOperator Constant - // ; - @Override - public StringBuilder visitOperand(JPPExpressionParser.OperandContext ctx) { - // 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 - // : 'defined' '(' Identifier ')' - // ; - @Override - public StringBuilder visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { - StringBuilder sb = new StringBuilder("DEFINED_"); - sb.append(ctx.Identifier().getText().trim()); - return sb; - } - - // undefinedExpression - // : NOT 'defined' '(' Identifier ')' - // ; - @Override - public StringBuilder visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { - StringBuilder sb = new StringBuilder(); - sb.append(BooleanAbstraction.U_NOT); - sb.append("DEFINED_"); - sb.append(ctx.Identifier().getText().trim()); - return sb; - } - - // propertyExpression - // : '${' Identifier '}' - // ; - @Override - public StringBuilder visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { - return new StringBuilder(ctx.Identifier().getText().trim()); - } - - // unaryOperator - // : U_PLUS - // | U_MINUS - // ; - @Override - public StringBuilder visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { - 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 new file mode 100644 index 000000000..ba8eb234c --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/ControllingJPPExpressionVisitor.java @@ -0,0 +1,140 @@ +package org.variantsync.diffdetective.feature.jpp; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.antlr.v4.runtime.tree.ParseTree; +import org.prop4j.Node; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionVisitor; +import org.variantsync.diffdetective.util.fide.FormulaUtils; + +import java.util.List; +import java.util.function.Function; + +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; + +/** + * Transform a parse tree into a {@link org.prop4j.Node formula}. + * Non-boolean sub-expressions are abstracted using + * {@link org.antlr.v4.runtime.tree.ParseTree#getText} + * which essentially removes all irrelevant whitespace. + */ +public class ControllingJPPExpressionVisitor extends AbstractParseTreeVisitor implements JPPExpressionVisitor { + // expression + // : logicalOrExpression + // ; + @Override + public Node visitExpression(JPPExpressionParser.ExpressionContext ctx) { + return ctx.logicalOrExpression().accept(this); + } + + // logicalOrExpression + // : logicalAndExpression (OR logicalAndExpression)* + // ; + @Override + public Node visitLogicalOrExpression(JPPExpressionParser.LogicalOrExpressionContext ctx) { + return visitLogicalExpression(ctx, FormulaUtils::or); + } + + // logicalAndExpression + // : primaryExpression (AND primaryExpression)* + // ; + @Override + public Node visitLogicalAndExpression(JPPExpressionParser.LogicalAndExpressionContext ctx) { + return visitLogicalExpression(ctx, FormulaUtils::and); + } + + // primaryExpression + // : definedExpression + // | undefinedExpression + // | comparisonExpression + // ; + @Override + public Node visitPrimaryExpression(JPPExpressionParser.PrimaryExpressionContext ctx) { + 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 + // : operand ((LT|GT|LEQ|GEQ|EQ|NEQ) operand)? + // ; + @Override + public Node visitComparisonExpression(JPPExpressionParser.ComparisonExpressionContext ctx) { + return var(ctx.getText()); + } + + // operand + // : propertyExpression + // | Constant + // | StringLiteral+ + // | unaryOperator Constant + // ; + @Override + public Node visitOperand(JPPExpressionParser.OperandContext ctx) { + return var(ctx.getText()); + } + + // definedExpression + // : 'defined' '(' Identifier ')' + // ; + @Override + public Node visitDefinedExpression(JPPExpressionParser.DefinedExpressionContext ctx) { + return var(String.format("defined(%s)", ctx.Identifier().getText())); + } + + // undefinedExpression + // : NOT 'defined' '(' Identifier ')' + // ; + @Override + public Node visitUndefinedExpression(JPPExpressionParser.UndefinedExpressionContext ctx) { + return negate(var(String.format("defined(%s)", ctx.Identifier().getText()))); + } + + // propertyExpression + // : '${' Identifier '}' + // ; + @Override + public Node visitPropertyExpression(JPPExpressionParser.PropertyExpressionContext ctx) { + throw new IllegalStateException("Unreachable code"); + } + + // unaryOperator + // : U_PLUS + // | U_MINUS + // ; + @Override + public Node visitUnaryOperator(JPPExpressionParser.UnaryOperatorContext ctx) { + throw new IllegalStateException("Unreachable code"); + } + + // logicalOrExpression + // : logicalAndExpression (OR logicalAndExpression)* + // ; + // logicalAndExpression + // : primaryExpression (AND primaryExpression)* + // ; + private Node visitLogicalExpression(ParserRuleContext expressionContext, Function newLogicNode) { + if (expressionContext.getChildCount() == 1) { + return expressionContext.getChild(0).accept(this); + } else { + List subtrees = expressionContext.children; + + // Skip every second node. These nodes are either "&&" or "||". + Node[] children = new Node[1 + (subtrees.size() - 1) / 2]; + for (int i = 0; i < children.length; ++i) { + children[i] = subtrees.get(2 * i).accept(this); + } + + return newLogicNode.apply(children); + } + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPAnnotationParser.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPAnnotationParser.java new file mode 100644 index 000000000..15b00e53c --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPAnnotationParser.java @@ -0,0 +1,51 @@ +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.prop4j.Node; +import org.tinylog.Logger; +import org.variantsync.diffdetective.error.UncheckedUnparseableFormulaException; +import org.variantsync.diffdetective.error.UnparseableFormulaException; +import org.variantsync.diffdetective.feature.ParseErrorListener; +import org.variantsync.diffdetective.feature.PreprocessorAnnotationParser; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionLexer; +import org.variantsync.diffdetective.feature.antlr.JPPExpressionParser; + +import java.util.regex.Pattern; + +/** + * Parses a JavaPP (Java PreProcessor) statement. + * For example, given the annotation {@code //#if defined(A) || B()}, + * the class would parse {@code new Or(new Literal("defined(A)"), new Literal("B()"))}. + * The parser detects if, elif, else and endif annotations. + * The given JPP statement might also be a line in a diff (i.e., preceded by a - or +). + * + * @author Alexander Schultheiß + */ +public class JPPAnnotationParser extends PreprocessorAnnotationParser { + private static final String JPP_ANNOTATION_REGEX = "^[+-]?\\s*//\\s*#\\s*(?if|elif|else|endif)(?[\\s(].*)?$"; + private static final Pattern JPP_ANNOTATION_PATTERN = Pattern.compile(JPP_ANNOTATION_REGEX); + + public JPPAnnotationParser() { + super(JPP_ANNOTATION_PATTERN); + } + + @Override + public Node parseFormula(final String directive, final String formula) throws UnparseableFormulaException { + // abstract complex formulas (e.g., if they contain arithmetics or macro calls) + try { + 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()); + } catch (UncheckedUnparseableFormulaException e) { + throw e.inner(); + } catch (Exception e) { + Logger.warn(e); + throw new UnparseableFormulaException(e); + } + } +} diff --git a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java b/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java deleted file mode 100644 index f760cd8ec..000000000 --- a/src/main/java/org/variantsync/diffdetective/feature/jpp/JPPDiffLineFormulaExtractor.java +++ /dev/null @@ -1,49 +0,0 @@ -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 "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 +). - * - * @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 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. - *

- * - * @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 AbstractingJPPExpressionVisitor()).toString(); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java b/src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java index e27983cc1..7737afedb 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.PreprocessorAnnotationParser; +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; @@ -102,7 +102,7 @@ private static void render(final Path fileToRender) { try { t = VariationDiff.fromFile(fileToRender, new VariationDiffParseOptions( - PreprocessorAnnotationParser.CPPAnnotationParser, collapseMultipleCodeLines, ignoreEmptyLines + new 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/internal/TextDiffToTikz.java b/src/main/java/org/variantsync/diffdetective/internal/TextDiffToTikz.java index 2bbccc934..a080e3c61 100644 --- a/src/main/java/org/variantsync/diffdetective/internal/TextDiffToTikz.java +++ b/src/main/java/org/variantsync/diffdetective/internal/TextDiffToTikz.java @@ -18,10 +18,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class TextDiffToTikz { +public record TextDiffToTikz(boolean collapseMultipleCodeLines, boolean ignoreEmptyLines) { public static String[] UNICODE_PROP_SYMBOLS = new String[]{"¬", "∧", "∨", "⇒", "⇔"}; /** @@ -40,6 +43,12 @@ public static void main(String[] args) throws IOException, DiffParseException { return; } + final Path fileToConvert = Path.of(args[0]); + if (!Files.exists(fileToConvert)) { + Logger.error("Path {} does not exist!", fileToConvert); + return; + } + final GraphvizExporter.LayoutAlgorithm layout; if (args.length < 2) { layout = GraphvizExporter.LayoutAlgorithm.DOT; @@ -47,30 +56,36 @@ public static void main(String[] args) throws IOException, DiffParseException { layout = GraphvizExporter.LayoutAlgorithm.valueOf(args[1].toUpperCase()); } - final Path fileToConvert = Path.of(args[0]); - if (!Files.exists(fileToConvert)) { - Logger.error("Path {} does not exist!", fileToConvert); - return; + // FIXME: Use a dedicated argument parser in the future. + boolean collapseMultipleCodeLines = false; + boolean ignoreEmptyLines = true; + List flags = new ArrayList<>(Arrays.asList(args).subList(2, args.length)); + if (flags.contains("-c") || flags.contains("--collapse-blocks")) { + collapseMultipleCodeLines = true; + } + if (flags.contains("-e") || flags.contains("--with-empty-lines")) { + ignoreEmptyLines = false; } + TextDiffToTikz me = new TextDiffToTikz(collapseMultipleCodeLines, ignoreEmptyLines); if (Files.isDirectory(fileToConvert)) { Logger.info("Processing directory " + fileToConvert); for (Path file : FileUtils.listAllFilesRecursively(fileToConvert)) { if (FileUtils.hasExtension(file, ".diff")) { - textDiff2Tikz(file, layout); + me.textDiff2Tikz(file, layout); } } } else { - textDiff2Tikz(fileToConvert, layout); + me.textDiff2Tikz(fileToConvert, layout); } } - public static void textDiff2Tikz(Path fileToConvert, GraphvizExporter.LayoutAlgorithm layout) throws IOException, DiffParseException { + public void textDiff2Tikz(Path fileToConvert, GraphvizExporter.LayoutAlgorithm layout) throws IOException, DiffParseException { Logger.info("Converting file " + fileToConvert); Logger.info("Using layout " + layout.getExecutableName()); final Path targetFile = fileToConvert.resolveSibling(fileToConvert.getFileName() + ".tikz"); - final VariationDiff d = VariationDiff.fromFile(fileToConvert, new VariationDiffParseOptions(true, true)); + final VariationDiff d = VariationDiff.fromFile(fileToConvert, new VariationDiffParseOptions(collapseMultipleCodeLines, ignoreEmptyLines)); final String tikz = exportAsTikz(d, layout); IO.write(targetFile, tikz); Logger.info("Wrote file " + targetFile); @@ -79,7 +94,7 @@ public static void textDiff2Tikz(Path fileToConvert, GraphvizExporter.LayoutAlgo public static String exportAsTikz(final VariationDiff variationDiff, GraphvizExporter.LayoutAlgorithm layout) throws IOException { // Export the test case var tikzOutput = new ByteArrayOutputStream(); - new TikzExporter(format).exportVariationDiff(variationDiff, layout, tikzOutput); + new TikzExporter<>(format).exportVariationDiff(variationDiff, layout, tikzOutput); return tikzOutput.toString(); } diff --git a/src/main/java/org/variantsync/diffdetective/metadata/EditClassCount.java b/src/main/java/org/variantsync/diffdetective/metadata/EditClassCount.java index b942329be..306441973 100644 --- a/src/main/java/org/variantsync/diffdetective/metadata/EditClassCount.java +++ b/src/main/java/org/variantsync/diffdetective/metadata/EditClassCount.java @@ -117,10 +117,10 @@ public void reportOccurrenceFor(final EditClass editClass, CommitDiff commit) { ); occurrences.get(editClass).increment(commit); } - + /** * Parses lines containing {@link EditClass edit classes} to {@link EditClassCount}. - * + * * @param lines Lines containing {@link EditClass edit classes} to be parsed * @return {@link EditClassCount} */ @@ -140,13 +140,13 @@ public static EditClassCount parse(final List lines, final String uuid) innerKeyValuePair = value.split(";"); total = Integer.parseInt(innerKeyValuePair[0].split("=")[1]); // total count commits = Integer.parseInt(innerKeyValuePair[1].split("=")[1]); - + // get edit class from key final String finalKey = key; EditClass editClass = ProposedEditClasses.Instance.fromName(key).orElseThrow( () -> new RuntimeException("Could not find EditClass with name " + finalKey) ); - + Occurrences occurence = new Occurrences(); occurence.totalAmount = total; @@ -154,11 +154,11 @@ public static EditClassCount parse(final List lines, final String uuid) for (int i = 0; i < commits; ++i) { occurence.uniqueCommits.add(uuid + i); } - + // add occurrence count.occurrences.put(editClass, occurence); } - + return count; } diff --git a/src/main/java/org/variantsync/diffdetective/metadata/ExplainedFilterSummary.java b/src/main/java/org/variantsync/diffdetective/metadata/ExplainedFilterSummary.java index e4f2e008c..f4ef14d4d 100644 --- a/src/main/java/org/variantsync/diffdetective/metadata/ExplainedFilterSummary.java +++ b/src/main/java/org/variantsync/diffdetective/metadata/ExplainedFilterSummary.java @@ -61,10 +61,10 @@ public ExplainedFilterSummary(final ExplainedFilter filter) { ) ); } - + /** * Parses lines containing {@link ExplainedFilter.Explanation Explanations} to {@link ExplainedFilterSummary}. - * + * * @param lines Lines containing {@link ExplainedFilter.Explanation Explanations} to be parsed * @return {@link ExplainedFilterSummary} */ @@ -78,10 +78,10 @@ public static ExplainedFilterSummary parse(final List lines) { key = keyValuePair[0]; key = key.substring(FILTERED_MESSAGE_BEGIN.length(), key.length() - FILTERED_MESSAGE_END.length()); value = Integer.parseInt(keyValuePair[1]); - + // create explanation ExplainedFilter.Explanation explanation = new ExplainedFilter.Explanation(value, key); - + // add explanation summary.explanations.put(key, explanation); } diff --git a/src/main/java/org/variantsync/diffdetective/mining/formats/DebugMiningDiffNodeFormat.java b/src/main/java/org/variantsync/diffdetective/mining/formats/DebugMiningDiffNodeFormat.java index 6df8edf85..f70140f7a 100644 --- a/src/main/java/org/variantsync/diffdetective/mining/formats/DebugMiningDiffNodeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/mining/formats/DebugMiningDiffNodeFormat.java @@ -15,14 +15,14 @@ * Annotation nodes are labeled with DIFFTYPE_NODETYPE (e.g., an added IF node gets the label ADD_IF). */ public class DebugMiningDiffNodeFormat implements MiningNodeFormat { - @Override - public String toLabel(final DiffNode node) { + @Override + public String toLabel(final DiffNode node) { if (node.isArtifact()) { return ProposedEditClasses.Instance.match(node).getName(); } else { return node.diffType + "_" + node.getNodeType(); } - } + } @Override public Pair fromEncodedTypes(String tag) { diff --git a/src/main/java/org/variantsync/diffdetective/show/engine/Texture.java b/src/main/java/org/variantsync/diffdetective/show/engine/Texture.java index 1838f320c..06f629233 100644 --- a/src/main/java/org/variantsync/diffdetective/show/engine/Texture.java +++ b/src/main/java/org/variantsync/diffdetective/show/engine/Texture.java @@ -783,4 +783,4 @@ public boolean inHeight(int y) { public boolean inWidth(int x) { return 0 <= x && x < getWidth(); } -} \ No newline at end of file +} diff --git a/src/main/java/org/variantsync/diffdetective/util/fide/FormulaUtils.java b/src/main/java/org/variantsync/diffdetective/util/fide/FormulaUtils.java index b03072c14..ece56aae0 100644 --- a/src/main/java/org/variantsync/diffdetective/util/fide/FormulaUtils.java +++ b/src/main/java/org/variantsync/diffdetective/util/fide/FormulaUtils.java @@ -1,9 +1,12 @@ package org.variantsync.diffdetective.util.fide; import org.prop4j.And; +import org.prop4j.Equals; +import org.prop4j.Implies; import org.prop4j.Literal; import org.prop4j.Node; import org.prop4j.Not; +import org.prop4j.Or; import org.variantsync.diffdetective.analysis.logic.SAT; import org.variantsync.diffdetective.util.Assert; import org.variantsync.functjonal.Cast; @@ -45,6 +48,18 @@ public static And and(Node... nodes) { return new And(nodes); } + public static Or or(Node... nodes) { + return new Or(nodes); + } + + public static Implies implies(Node a, Node b) { + return new Implies(a, b); + } + + public static Equals equivalent(Node a, Node b) { + return new Equals(a, b); + } + /** Recursively counts the number of instances of {@link Literal} in {@code formula}. */ public static int numberOfLiterals(final Node formula) { if (formula instanceof Literal) { diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java index 71e58a780..4a865e1f4 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java @@ -111,7 +111,7 @@ public static VariationDiff fromDiff(final String diff, final Va } catch (DiffParseException e) { Logger.error(""" Could not parse diff: - + {} """, diff); @@ -134,7 +134,7 @@ public static VariationDiff fromDiff(final String diff, final Va * encountered while trying to parse the {@link VariationDiff} */ public static Result, List> fromPatch(final PatchReference patchReference, final Repository repository) throws IOException { - final CommitDiffResult result = new GitDiffer(repository).createCommitDiff(patchReference.getCommitHash()); + final CommitDiffResult result = GitDiffer.createCommitDiffFromFirstParent(repository, patchReference.getCommitHash()); final Path changedFile = Path.of(patchReference.getFileName()); if (result.diff().isPresent()) { final CommitDiff commit = result.diff().get(); @@ -169,7 +169,7 @@ public static VariationDiff fromFiles( final Path afterFile, DiffAlgorithm.SupportedAlgorithm algorithm, VariationDiffParseOptions options) - throws IOException, DiffParseException + throws IOException, DiffParseException { try (BufferedReader b = Files.newBufferedReader(beforeFile); BufferedReader a = Files.newBufferedReader(afterFile) @@ -181,13 +181,13 @@ public static VariationDiff fromFiles( /** * Creates a variation diff from to line-based text inputs. * This method just forwards to: - * @see JGitDiff#diff(String, String, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) + * @see JGitDiff#diff(String, String, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) */ public static VariationDiff fromLines( String before, String after, DiffAlgorithm.SupportedAlgorithm algorithm, - VariationDiffParseOptions options) throws IOException, DiffParseException + VariationDiffParseOptions options) throws IOException, DiffParseException { return JGitDiff.diff(before, after, algorithm, options); } @@ -196,7 +196,7 @@ public static VariationDiff fromLines( * Create a {@link VariationDiff} by matching nodes between {@code before} and {@code after} with the * default GumTree matcher. * - * @see GumTreeDiff#diffUsingMatching(VariationTree, VariationTree) + * @see GumTreeDiff#diffUsingMatching(VariationTree, VariationTree) */ public static VariationDiff fromTrees(VariationTree before, VariationTree after) { return GumTreeDiff.diffUsingMatching(before, after); diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java index 625bbd72c..6d2920585 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java @@ -24,7 +24,7 @@ */ public final class JGitDiff { private final static Pattern NO_NEWLINE_AT_END_OF_FILE = Pattern.compile("\n\\\\ No newline at end of file"); - + private JGitDiff() {} /** @@ -47,7 +47,7 @@ public static String textDiff( return textDiff(IOUtils.toString(b), IOUtils.toString(a), algorithm); } } - + /** * Creates a text-based diff from two line-based text inputs. * Uses JGit to diff the two files using the specified {@code options}. @@ -112,10 +112,10 @@ Using our own formatter without diff headers (paired with a maximum context (?)) textDiff = NO_NEWLINE_AT_END_OF_FILE.matcher(textDiff).replaceAll(""); //textDiff = HUNK_HEADER_REGEX.matcher(textDiff).replaceAll(""); - + return textDiff; } - + /** * Creates a variation diff from to line-based text inputs. * Expects variability to be implemented via C preprocessor in those lines. 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 1269e9463..ba210cf7b 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.PreprocessorAnnotationParser; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; /** * Parse options that should be used when parsing {@link org.variantsync.diffdetective.variation.diff.VariationDiff}s. @@ -47,10 +47,10 @@ public VariationDiffParseOptions withAnnotationParser(AnnotationParser annotatio /** * Default value for VariationDiffParseOptions that does not remember parsed unix diffs - * and uses the default value for the parsing annotations ({@link PreprocessorAnnotationParser#CPPAnnotationParser}). + * and uses the default value for the parsing annotations ({@link CPPAnnotationParser}). */ public static final VariationDiffParseOptions Default = new VariationDiffParseOptions( - PreprocessorAnnotationParser.CPPAnnotationParser, + new CPPAnnotationParser(), false, false ); 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 01863a8c0..2df9c3bf3 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,6 +14,7 @@ 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.Annotation; import org.variantsync.diffdetective.feature.AnnotationType; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.variation.DiffLinesLabel; @@ -313,9 +314,14 @@ private void parseLine( // Is this line a conditional macro? // Note: The following line doesn't handle comments and line continuations correctly. - var annotationType = options.annotationParser().determineAnnotationType(line.toString()); + Annotation annotation; + try { + annotation = options.annotationParser().parseAnnotation(line.toString()); + } catch (UnparseableFormulaException e) { + throw DiffParseException.Unparseable(e, fromLine); + } - if (annotationType == AnnotationType.Endif) { + if (annotation.type() == AnnotationType.Endif) { lastArtifact = null; // Do not create a node for ENDIF, but update the line numbers of the closed if-chain @@ -324,7 +330,7 @@ private void parseLine( popIfChain(stack, fromLine) ); } else if (options.collapseMultipleCodeLines() - && annotationType == AnnotationType.None + && annotation.type() == AnnotationType.None && lastArtifact != null && lastArtifact.diffType.equals(diffType) && lastArtifact.getToLine().inDiff() == fromLine.inDiff()) { @@ -332,25 +338,19 @@ private void parseLine( lastArtifact.getLabel().addDiffLines(line.getLines()); lastArtifact.setToLine(toLine); } else { - try { - NodeType nodeType = NodeType.fromAnnotationType(annotationType); - - DiffNode newNode = new DiffNode( - diffType, - nodeType, - fromLine, - toLine, - nodeType == NodeType.ARTIFACT || nodeType == NodeType.ELSE - ? null - : options.annotationParser().parseAnnotation(line.toString()), - new DiffLinesLabel(line.getLines()) - ); - - addNode(newNode); - lastArtifact = newNode.isArtifact() ? newNode : null; - } catch (UnparseableFormulaException e) { - throw DiffParseException.Unparseable(e, fromLine); - } + NodeType nodeType = NodeType.fromAnnotationType(annotation.type()); + + DiffNode newNode = new DiffNode( + diffType, + nodeType, + fromLine, + toLine, + annotation.formula(), + new DiffLinesLabel(line.getLines()) + ); + + addNode(newNode); + lastArtifact = newNode.isArtifact() ? newNode : null; } } @@ -432,7 +432,7 @@ private void addNode(DiffNode newNode) throws DiffParseException * @throws IOException when an error occurred. */ public static CommitDiff parseCommit(Repository repo, String commitHash) throws IOException { - final Git git = repo.getGitRepo().run(); + final Git git = repo.getGitRepo(); Assert.assertNotNull(git); final RevWalk revWalk = new RevWalk(git.getRepository()); final RevCommit childCommit = revWalk.parseCommit(ObjectId.fromString(commitHash)); @@ -440,11 +440,9 @@ public static CommitDiff parseCommit(Repository repo, String commitHash) throws final CommitDiff commitDiff = GitDiffer.createCommitDiff( - git, - repo.getDiffFilter(), + repo, parentCommit, - childCommit, - repo.getParseOptions()) + childCommit) .diff().orElseThrow(); revWalk.close(); diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/render/RenderOptions.java b/src/main/java/org/variantsync/diffdetective/variation/diff/render/RenderOptions.java index f4977ec13..0c63a6307 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/render/RenderOptions.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/render/RenderOptions.java @@ -34,197 +34,197 @@ * @author Paul Bittner, Kevin Jedelhauser */ public record RenderOptions( - GraphFormat format, - VariationDiffLabelFormat treeFormat, - DiffNodeLabelFormat nodeFormat, - EdgeLabelFormat edgeFormat, - boolean cleanUpTemporaryFiles, - int dpi, - int nodesize, - double edgesize, - int arrowsize, - int fontsize, - boolean withlabels, - List extraArguments) + GraphFormat format, + VariationDiffLabelFormat treeFormat, + DiffNodeLabelFormat nodeFormat, + EdgeLabelFormat edgeFormat, + boolean cleanUpTemporaryFiles, + int dpi, + int nodesize, + double edgesize, + int arrowsize, + int fontsize, + boolean withlabels, + List extraArguments) { - /** - * Default options. - */ - public static RenderOptions DEFAULT() { - return new Builder().build(); - } - - /** - * Builder for {@link RenderOptions}. - */ - public static class Builder { - private GraphFormat format; - private VariationDiffLabelFormat treeParser; - private DiffNodeLabelFormat nodeParser; - private EdgeLabelFormat edgeParser; - private boolean cleanUpTemporaryFiles; - private int dpi; - private int nodesize; - private double edgesize; - private int arrowsize; - private int fontsize; - private boolean withlabels; - private List extraArguments; - - /** - * Creates a new builder with the default options for {@link RenderOptions}. - */ - public Builder() { - format = GraphFormat.VARIATION_DIFF; - treeParser = new CommitDiffVariationDiffLabelFormat(); - nodeParser = new DebugDiffNodeFormat<>(); - edgeParser = new DefaultEdgeLabelFormat<>(); - cleanUpTemporaryFiles = true; - dpi = 300; - nodesize = 700; - edgesize = 1.2; - arrowsize = 15; - fontsize = 5; - withlabels = true; - extraArguments = new ArrayList<>(); - } - - /** - * Complete the creation of {@link RenderOptions}. - * - * @return {@link RenderOptions} with this builder's configured settings. - */ - public RenderOptions build() { - return new RenderOptions<>( - format, - treeParser, - nodeParser, - edgeParser, - cleanUpTemporaryFiles, - dpi, - nodesize, - edgesize, - arrowsize, - fontsize, - withlabels, - extraArguments); - } - - /** - * @see RenderOptions#format - */ - public Builder setGraphFormat(GraphFormat format) { - this.format = format; - return this; - } - - /** - * @see RenderOptions#treeFormat - */ - public Builder setTreeFormat(VariationDiffLabelFormat treeFormat) { - this.treeParser = treeFormat; - return this; - } - - /** - * @see RenderOptions#nodeFormat - */ - public Builder setNodeFormat(DiffNodeLabelFormat nodeFormat) { - this.nodeParser = nodeFormat; - return this; - } - - /** - * @see RenderOptions#edgeFormat - */ - public Builder setEdgeFormat(EdgeLabelFormat edgeFormat) { - this.edgeParser = edgeFormat; - return this; - } - - /** - * @see RenderOptions#cleanUpTemporaryFiles - */ - public Builder setCleanUpTemporaryFiles(boolean cleanUpTemporaryFiles) { - this.cleanUpTemporaryFiles = cleanUpTemporaryFiles; - return this; - } - - /** - * @see RenderOptions#dpi - */ - public Builder setDpi(int dpi) { - this.dpi = dpi; - return this; - } - - /** - * @see RenderOptions#nodesize - */ - public Builder setNodesize(int nodesize) { - this.nodesize = nodesize; - return this; - } - - /** - * @see RenderOptions#edgesize - */ - public Builder setEdgesize(double edgesize) { - this.edgesize = edgesize; - return this; - } - - /** - * @see RenderOptions#arrowsize - */ - public Builder setArrowsize(int arrowsize) { - this.arrowsize = arrowsize; - return this; - } - - /** - * @see RenderOptions#fontsize - */ - public Builder setFontsize(int fontsize) { - this.fontsize = fontsize; - return this; - } - - /** - * @see RenderOptions#withlabels - */ - public Builder setWithlabels(boolean withlabels) { - this.withlabels = withlabels; - return this; - } - - /** - * Resets the extra arguments to the given list. - * @see RenderOptions#extraArguments - */ - public Builder setExtraArguments(List extraArguments) { - this.extraArguments = new ArrayList<>(extraArguments); - return this; - } - - /** - * Adds further arguments to the already set extra arguments. - * @see RenderOptions#extraArguments - */ - public Builder addExtraArguments(String... args) { - // add new list arguments to already existing arguments + /** + * Default options. + */ + public static RenderOptions DEFAULT() { + return new Builder().build(); + } + + /** + * Builder for {@link RenderOptions}. + */ + public static class Builder { + private GraphFormat format; + private VariationDiffLabelFormat treeParser; + private DiffNodeLabelFormat nodeParser; + private EdgeLabelFormat edgeParser; + private boolean cleanUpTemporaryFiles; + private int dpi; + private int nodesize; + private double edgesize; + private int arrowsize; + private int fontsize; + private boolean withlabels; + private List extraArguments; + + /** + * Creates a new builder with the default options for {@link RenderOptions}. + */ + public Builder() { + format = GraphFormat.VARIATION_DIFF; + treeParser = new CommitDiffVariationDiffLabelFormat(); + nodeParser = new DebugDiffNodeFormat<>(); + edgeParser = new DefaultEdgeLabelFormat<>(); + cleanUpTemporaryFiles = true; + dpi = 300; + nodesize = 700; + edgesize = 1.2; + arrowsize = 15; + fontsize = 5; + withlabels = true; + extraArguments = new ArrayList<>(); + } + + /** + * Complete the creation of {@link RenderOptions}. + * + * @return {@link RenderOptions} with this builder's configured settings. + */ + public RenderOptions build() { + return new RenderOptions<>( + format, + treeParser, + nodeParser, + edgeParser, + cleanUpTemporaryFiles, + dpi, + nodesize, + edgesize, + arrowsize, + fontsize, + withlabels, + extraArguments); + } + + /** + * @see RenderOptions#format + */ + public Builder setGraphFormat(GraphFormat format) { + this.format = format; + return this; + } + + /** + * @see RenderOptions#treeFormat + */ + public Builder setTreeFormat(VariationDiffLabelFormat treeFormat) { + this.treeParser = treeFormat; + return this; + } + + /** + * @see RenderOptions#nodeFormat + */ + public Builder setNodeFormat(DiffNodeLabelFormat nodeFormat) { + this.nodeParser = nodeFormat; + return this; + } + + /** + * @see RenderOptions#edgeFormat + */ + public Builder setEdgeFormat(EdgeLabelFormat edgeFormat) { + this.edgeParser = edgeFormat; + return this; + } + + /** + * @see RenderOptions#cleanUpTemporaryFiles + */ + public Builder setCleanUpTemporaryFiles(boolean cleanUpTemporaryFiles) { + this.cleanUpTemporaryFiles = cleanUpTemporaryFiles; + return this; + } + + /** + * @see RenderOptions#dpi + */ + public Builder setDpi(int dpi) { + this.dpi = dpi; + return this; + } + + /** + * @see RenderOptions#nodesize + */ + public Builder setNodesize(int nodesize) { + this.nodesize = nodesize; + return this; + } + + /** + * @see RenderOptions#edgesize + */ + public Builder setEdgesize(double edgesize) { + this.edgesize = edgesize; + return this; + } + + /** + * @see RenderOptions#arrowsize + */ + public Builder setArrowsize(int arrowsize) { + this.arrowsize = arrowsize; + return this; + } + + /** + * @see RenderOptions#fontsize + */ + public Builder setFontsize(int fontsize) { + this.fontsize = fontsize; + return this; + } + + /** + * @see RenderOptions#withlabels + */ + public Builder setWithlabels(boolean withlabels) { + this.withlabels = withlabels; + return this; + } + + /** + * Resets the extra arguments to the given list. + * @see RenderOptions#extraArguments + */ + public Builder setExtraArguments(List extraArguments) { + this.extraArguments = new ArrayList<>(extraArguments); + return this; + } + + /** + * Adds further arguments to the already set extra arguments. + * @see RenderOptions#extraArguments + */ + public Builder addExtraArguments(String... args) { + // add new list arguments to already existing arguments this.extraArguments.addAll(List.of(args)); - return this; - } - - } - - /** - * Converts this RenderOptions to options for linegraph export. - * Linegraph options are a subset of render options. - * @return Options for linegraph export consistent to this RenderOptions. - */ - public LineGraphExportOptions toLineGraphOptions() { - return new LineGraphExportOptions(format(), treeFormat(), nodeFormat(), edgeFormat()); - } + return this; + } + + } + + /** + * Converts this RenderOptions to options for linegraph export. + * Linegraph options are a subset of render options. + * @return Options for linegraph export consistent to this RenderOptions. + */ + public LineGraphExportOptions toLineGraphOptions() { + return new LineGraphExportOptions(format(), treeFormat(), nodeFormat(), edgeFormat()); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphConstants.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphConstants.java index cb141e3d3..11c112569 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphConstants.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphConstants.java @@ -7,45 +7,45 @@ * Constants that are related to line graph IO. */ public class LineGraphConstants { - - /** - * Declaration of a {@link VariationDiff} in a line graph. - */ - public static final String LG_TREE_HEADER = "t #"; - - /** - * Delimiter used in {@link VariationDiff VariationDiffs}. - */ - public static final String TREE_NAME_SEPARATOR = "$$$"; - - /** - * Delimiter used in {@link VariationDiff VariationDiffs} for regular expressions. - * {@link #TREE_NAME_SEPARATOR} - */ - public static final String TREE_NAME_SEPARATOR_REGEX = "\\$\\$\\$"; - - /** - * Declaration of a {@link DiffNode} in a line graph. - */ - public static final String LG_NODE = "v"; - - /** - * Declaration of a connection between two {@link DiffNode DiffNodes} in a line graph. - */ - public static final String LG_EDGE = "e"; - - /** - * An edge between two {@link DiffNode DiffNodes} that has not been altered. - */ - public final static String BEFORE_AND_AFTER_PARENT = "ba"; - - /** - * An edge between two {@link DiffNode DiffNodes} that exists after the edit only. - */ - public final static String AFTER_PARENT = "a"; - - /** - * An edge between two {@link DiffNode DiffNodes} that existed before the edit only. - */ - public final static String BEFORE_PARENT = "b"; + + /** + * Declaration of a {@link VariationDiff} in a line graph. + */ + public static final String LG_TREE_HEADER = "t #"; + + /** + * Delimiter used in {@link VariationDiff VariationDiffs}. + */ + public static final String TREE_NAME_SEPARATOR = "$$$"; + + /** + * Delimiter used in {@link VariationDiff VariationDiffs} for regular expressions. + * {@link #TREE_NAME_SEPARATOR} + */ + public static final String TREE_NAME_SEPARATOR_REGEX = "\\$\\$\\$"; + + /** + * Declaration of a {@link DiffNode} in a line graph. + */ + public static final String LG_NODE = "v"; + + /** + * Declaration of a connection between two {@link DiffNode DiffNodes} in a line graph. + */ + public static final String LG_EDGE = "e"; + + /** + * An edge between two {@link DiffNode DiffNodes} that has not been altered. + */ + public final static String BEFORE_AND_AFTER_PARENT = "ba"; + + /** + * An edge between two {@link DiffNode DiffNodes} that exists after the edit only. + */ + public final static String AFTER_PARENT = "a"; + + /** + * An edge between two {@link DiffNode DiffNodes} that existed before the edit only. + */ + public final static String BEFORE_PARENT = "b"; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java index 5e7e7064d..86686b6ed 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java @@ -29,10 +29,10 @@ public class LineGraphImport { /** * Import all VariationDiffs from the given linegraph file. * - * @param path Path to a linegraph file in which only VariationDiffs are stored. - * @param options Options for the import, such as hints for the used formats for node and edge labels. + * @param path Path to a linegraph file in which only VariationDiffs are stored. + * @param options Options for the import, such as hints for the used formats for node and edge labels. * @return All {@link VariationDiff VariationDiffs} contained in the linegraph file. - * @throws IOException when {@link LineGraphImport#fromLineGraph(BufferedReader, Path, LineGraphImportOptions)} throws. + * @throws IOException when {@link LineGraphImport#fromLineGraph(BufferedReader, Path, LineGraphImportOptions)} throws. */ public static List> fromFile(final Path path, final LineGraphImportOptions options) throws IOException { Assert.assertTrue(Files.isRegularFile(path)); @@ -41,102 +41,102 @@ public static List> fromFile(final Path path, fina return fromLineGraph(input, path, options); } } - - /** - * Import all VariationDiffs from the given linegraph file. - * - * @param lineGraph Reader that reads the linegraph file. - * @param originalFile Path to the file from which the lineGraph reader is reading. - * @param options Options for the import, such as hints for the used formats for node and edge labels. - * @return All {@link VariationDiff VariationDiffs} contained in the linegraph text. - */ - public static List> fromLineGraph(final BufferedReader lineGraph, final Path originalFile, final LineGraphImportOptions options) throws IOException { - // All VariationDiffs read from the line graph - List> variationDiffList = new ArrayList<>(); - - // All DiffNodes of one VariationDiff for determining the root node - List> diffNodeList = new ArrayList<>(); - - // A hash map of DiffNodes - // - HashMap> diffNodes = new HashMap<>(); - - // The previously read VariationDiff - String previousVariationDiffLine = ""; - - // Read the entire line graph - String ln; - while ((ln = lineGraph.readLine()) != null) { - if (ln.startsWith(LineGraphConstants.LG_TREE_HEADER)) { - // the line represents a VariationDiff - - if (!diffNodeList.isEmpty()) { - VariationDiff curVariationDiff = parseVariationDiff(previousVariationDiffLine, originalFile, diffNodeList, options); // parse to VariationDiff - variationDiffList.add(curVariationDiff); // add newly computed VariationDiff to the list of all VariationDiffs - - // Remove all DiffNodes from list - diffNodeList.clear(); - diffNodes.clear(); - } - previousVariationDiffLine = ln; - } else if (ln.startsWith(LineGraphConstants.LG_NODE)) { - // the line represents a DiffNode - - // parse node from input line - final Pair> idAndNode = options.nodeFormat().fromLineGraphLine(ln); - - // add DiffNode to lists of current VariationDiff - diffNodeList.add(idAndNode.second()); - diffNodes.put(idAndNode.first(), idAndNode.second()); - - } else if (ln.startsWith(LineGraphConstants.LG_EDGE)) { - // the line represent a connection with two DiffNodes - options.edgeFormat().connect(ln, diffNodes); - } else if (!ln.isBlank()) { - // ignore blank lines and throw an exception otherwise - String errorMessage = String.format( - "Line graph syntax error. Expects: \"%s\" (VariationDiff), \"%s\" (DiffNode), \"%s\" (edge) or a blank space (delimiter). Faulty input: \"%s\".", - LineGraphConstants.LG_TREE_HEADER, - LineGraphConstants.LG_NODE, - LineGraphConstants.LG_EDGE, - ln); - throw new IllegalArgumentException(errorMessage); - } - } - - if (!diffNodeList.isEmpty()) { - VariationDiff curVariationDiff = parseVariationDiff(previousVariationDiffLine, originalFile, diffNodeList, options); // parse to VariationDiff - variationDiffList.add(curVariationDiff); // add newly computed VariationDiff to the list of all VariationDiffs - } - - // return all computed VariationDiffs. - return variationDiffList; - } - - /** - * Generates a {@link VariationDiff} from the given, already parsed parameters. - * - * @param lineGraph The header line in the linegraph that describes the VariationDiff (starting with 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 VariationDiff parseVariationDiff(final String lineGraph, final Path inFile, final List> diffNodeList, final LineGraphImportOptions options) { - VariationDiffSource variationDiffSource = options.treeFormat().fromLineGraphLine(lineGraph); - - if (variationDiffSource == null || VariationDiffSource.Unknown.equals(variationDiffSource)) { - variationDiffSource = new LineGraphFileSource( - lineGraph, - inFile - ); - } - - // Handle trees and graphs differently - if (options.graphFormat() == GraphFormat.DIFFGRAPH) { - return DiffGraph.fromNodes(diffNodeList, variationDiffSource); - } else if (options.graphFormat() == GraphFormat.VARIATION_DIFF) { - // If you should interpret the input data as VariationDiffs, always expect a root to be present. Parse all nodes (v) to a list of nodes. Search for the root. Assert that there is exactly one root. + + /** + * Import all VariationDiffs from the given linegraph file. + * + * @param lineGraph Reader that reads the linegraph file. + * @param originalFile Path to the file from which the lineGraph reader is reading. + * @param options Options for the import, such as hints for the used formats for node and edge labels. + * @return All {@link VariationDiff VariationDiffs} contained in the linegraph text. + */ + public static List> fromLineGraph(final BufferedReader lineGraph, final Path originalFile, final LineGraphImportOptions options) throws IOException { + // All VariationDiffs read from the line graph + List> variationDiffList = new ArrayList<>(); + + // All DiffNodes of one VariationDiff for determining the root node + List> diffNodeList = new ArrayList<>(); + + // A hash map of DiffNodes + // + HashMap> diffNodes = new HashMap<>(); + + // The previously read VariationDiff + String previousVariationDiffLine = ""; + + // Read the entire line graph + String ln; + while ((ln = lineGraph.readLine()) != null) { + if (ln.startsWith(LineGraphConstants.LG_TREE_HEADER)) { + // the line represents a VariationDiff + + if (!diffNodeList.isEmpty()) { + VariationDiff curVariationDiff = parseVariationDiff(previousVariationDiffLine, originalFile, diffNodeList, options); // parse to VariationDiff + variationDiffList.add(curVariationDiff); // add newly computed VariationDiff to the list of all VariationDiffs + + // Remove all DiffNodes from list + diffNodeList.clear(); + diffNodes.clear(); + } + previousVariationDiffLine = ln; + } else if (ln.startsWith(LineGraphConstants.LG_NODE)) { + // the line represents a DiffNode + + // parse node from input line + final Pair> idAndNode = options.nodeFormat().fromLineGraphLine(ln); + + // add DiffNode to lists of current VariationDiff + diffNodeList.add(idAndNode.second()); + diffNodes.put(idAndNode.first(), idAndNode.second()); + + } else if (ln.startsWith(LineGraphConstants.LG_EDGE)) { + // the line represent a connection with two DiffNodes + options.edgeFormat().connect(ln, diffNodes); + } else if (!ln.isBlank()) { + // ignore blank lines and throw an exception otherwise + String errorMessage = String.format( + "Line graph syntax error. Expects: \"%s\" (VariationDiff), \"%s\" (DiffNode), \"%s\" (edge) or a blank space (delimiter). Faulty input: \"%s\".", + LineGraphConstants.LG_TREE_HEADER, + LineGraphConstants.LG_NODE, + LineGraphConstants.LG_EDGE, + ln); + throw new IllegalArgumentException(errorMessage); + } + } + + if (!diffNodeList.isEmpty()) { + VariationDiff curVariationDiff = parseVariationDiff(previousVariationDiffLine, originalFile, diffNodeList, options); // parse to VariationDiff + variationDiffList.add(curVariationDiff); // add newly computed VariationDiff to the list of all VariationDiffs + } + + // return all computed VariationDiffs. + return variationDiffList; + } + + /** + * Generates a {@link VariationDiff} from the given, already parsed parameters. + * + * @param lineGraph The header line in the linegraph that describes the VariationDiff (starting with 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 VariationDiff parseVariationDiff(final String lineGraph, final Path inFile, final List> diffNodeList, final LineGraphImportOptions options) { + VariationDiffSource variationDiffSource = options.treeFormat().fromLineGraphLine(lineGraph); + + if (variationDiffSource == null || VariationDiffSource.Unknown.equals(variationDiffSource)) { + variationDiffSource = new LineGraphFileSource( + lineGraph, + inFile + ); + } + + // Handle trees and graphs differently + if (options.graphFormat() == GraphFormat.DIFFGRAPH) { + return DiffGraph.fromNodes(diffNodeList, variationDiffSource); + } else if (options.graphFormat() == GraphFormat.VARIATION_DIFF) { + // If you should interpret the input data as VariationDiffs, always expect a root to be present. Parse all nodes (v) to a list of nodes. Search for the root. Assert that there is exactly one root. DiffNode root = null; for (final DiffNode v : diffNodeList) { if (v.isRoot()) { @@ -158,9 +158,9 @@ private static VariationDiff parseVariationDiff(final String lin // countRootTypes.merge(root.getNodeType(), 1, Integer::sum); - return new VariationDiff<>(root, variationDiffSource); - } else { - throw new RuntimeException("Unsupported GraphFormat"); - } - } + return new VariationDiff<>(root, variationDiffSource); + } else { + throw new RuntimeException("Unsupported GraphFormat"); + } + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DebugDiffNodeFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DebugDiffNodeFormat.java index 02a53f7fe..2d3b38df7 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DebugDiffNodeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DebugDiffNodeFormat.java @@ -9,12 +9,12 @@ * @author Paul Bittner, Kevin Jedelhauser */ public class DebugDiffNodeFormat implements DiffNodeLabelFormat { - @Override - public String toLabel(final DiffNode node) { - return node.diffType + "_" + node.getNodeType() + "_\"" + - DiffNodeLabelPrettyfier.prettyPrintIfAnnotationOr( - node, - FileUtils.replaceLineEndings(node.getLabel().toString().trim().replaceAll("\t", " "), "
")) - + "\""; - } + @Override + public String toLabel(final DiffNode 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 extends LinegraphFormat { - /** - * Converts a label of line graph into a {@link DiffNode}. - * - * @param lineGraphNodeLabel A string containing the label of the {@link DiffNode} - * @param nodeId The id of the {@link DiffNode} - * @return The corresponding {@link DiffNode} - */ - default DiffNode fromLabelAndId(final String lineGraphNodeLabel, final int nodeId) { - return DiffNode.fromID(nodeId, lineGraphNodeLabel); - } + /** + * Converts a label of line graph into a {@link DiffNode}. + * + * @param lineGraphNodeLabel A string containing the label of the {@link DiffNode} + * @param nodeId The id of the {@link DiffNode} + * @return The corresponding {@link DiffNode} + */ + default DiffNode fromLabelAndId(final String lineGraphNodeLabel, final int nodeId) { + return DiffNode.fromID(nodeId, lineGraphNodeLabel); + } /** * Converts a {@link DiffNode} into a label suitable for exporting. diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LabelOnlyDiffNodeFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LabelOnlyDiffNodeFormat.java index 848843133..947d8782b 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LabelOnlyDiffNodeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LabelOnlyDiffNodeFormat.java @@ -8,8 +8,8 @@ * @author Paul Bittner, Kevin Jedelhauser */ public class LabelOnlyDiffNodeFormat implements DiffNodeLabelFormat { - @Override - public String toLabel(final DiffNode node) { - return node.getLabel().toString(); - } + @Override + public String toLabel(final DiffNode node) { + return node.getLabel().toString(); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LineNumberFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LineNumberFormat.java index 20496bd5b..ddfcc6b26 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LineNumberFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/LineNumberFormat.java @@ -7,8 +7,8 @@ * Labels nodes using their line number in the source diff. */ public class LineNumberFormat implements DiffNodeLabelFormat { - @Override - public String toLabel(final DiffNode node) { - return String.valueOf(node.getFromLine().inDiff()); - } + @Override + public String toLabel(final DiffNode node) { + return String.valueOf(node.getFromLine().inDiff()); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/MappingsDiffNodeFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/MappingsDiffNodeFormat.java index 765f31eac..58aefa470 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/MappingsDiffNodeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/MappingsDiffNodeFormat.java @@ -11,8 +11,8 @@ * @author Paul Bittner, Kevin Jedelhauser */ public class MappingsDiffNodeFormat implements DiffNodeLabelFormat { - @Override - public String toLabel(final DiffNode node) { - return node.diffType + "_" + node.getNodeType() + "_\"" + DiffNodeLabelPrettyfier.prettyPrintIfAnnotationOr(node, "") + "\""; - } + @Override + public String toLabel(final DiffNode node) { + return node.diffType + "_" + node.getNodeType() + "_\"" + DiffNodeLabelPrettyfier.prettyPrintIfAnnotationOr(node, "") + "\""; + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/TypeDiffNodeFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/TypeDiffNodeFormat.java index a554367c4..55ef70e11 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/TypeDiffNodeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/TypeDiffNodeFormat.java @@ -8,8 +8,8 @@ * @author Paul Bittner, Kevin Jedelhauser */ public class TypeDiffNodeFormat implements DiffNodeLabelFormat { - @Override - public String toLabel(final DiffNode node) { - return node.diffType + "_" + node.getNodeType(); - } + @Override + public String toLabel(final DiffNode node) { + return node.diffType + "_" + node.getNodeType(); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java index fe47748ef..c4d19637e 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java @@ -25,7 +25,7 @@ public VariationDiffSource fromLabel(final String label) { Path filePath = Paths.get(commit[0]); String commitHash = commit[1]; return new CommitDiffVariationDiffSource(filePath, commitHash); - } catch (InvalidPathException e) { + } catch (InvalidPathException e) { throw new RuntimeException("Syntax error. The path cannot be read: " + commit[0]); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java index 7ffbf18c4..f9c5d5ebb 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java @@ -9,39 +9,39 @@ * @author Paul Bittner, Kevin Jedelhauser */ public interface VariationDiffLabelFormat extends LinegraphFormat { - /** - * Converts a label of line graph into a {@link VariationDiffSource}. - * - * @param label A string containing the label of the {@link VariationDiffSource} - * @return The {@link VariationDiffSource} descibed by this label. - */ - VariationDiffSource fromLabel(final String label); - - /** - * Converts a {@link VariationDiffSource} label of line graph. - * - * @param variationDiffSource The {@link VariationDiffSource} to be converted - * @return The corresponding line graph line - */ - String toLabel(final VariationDiffSource variationDiffSource); - - /** + /** + * Converts a label of line graph into a {@link VariationDiffSource}. + * + * @param label A string containing the label of the {@link VariationDiffSource} + * @return The {@link VariationDiffSource} descibed by this label. + */ + VariationDiffSource fromLabel(final String label); + + /** + * Converts a {@link VariationDiffSource} label of line graph. + * + * @param variationDiffSource The {@link VariationDiffSource} to be converted + * @return The corresponding line graph line + */ + String toLabel(final VariationDiffSource variationDiffSource); + + /** * Converts a line describing a graph (starting with "t # ") in line graph format into a {@link VariationDiffSource}. - * + * * @param lineGraphLine A line from a line graph file starting with "t #" - * @return The {@link VariationDiffSource} descibed by the label of this line. - */ - default VariationDiffSource fromLineGraphLine(final String lineGraphLine) { - return fromLabel(lineGraphLine.substring((LineGraphConstants.LG_TREE_HEADER + " ").length())); - } - - /** - * Prepends the {@link LineGraphConstants#LG_TREE_HEADER tree declaration} to a label and return an entire line graph line. - * - * @param variationDiffSource The {@link VariationDiffSource} to be converted - * @return The entire line graph line of a {@link VariationDiffSource}. - */ + * @return The {@link VariationDiffSource} descibed by the label of this line. + */ + default VariationDiffSource fromLineGraphLine(final String lineGraphLine) { + return fromLabel(lineGraphLine.substring((LineGraphConstants.LG_TREE_HEADER + " ").length())); + } + + /** + * Prepends the {@link LineGraphConstants#LG_TREE_HEADER tree declaration} to a label and return an entire line graph line. + * + * @param variationDiffSource The {@link VariationDiffSource} to be converted + * @return The entire line graph line of a {@link VariationDiffSource}. + */ default String toLineGraphLine(final VariationDiffSource variationDiffSource) { - return LineGraphConstants.LG_TREE_HEADER + " " + toLabel(variationDiffSource); - } + return LineGraphConstants.LG_TREE_HEADER + " " + toLabel(variationDiffSource); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java index 911977982..7b12106b7 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java @@ -8,32 +8,32 @@ * Describes that a VariationDiff was created from a patch in a {@link CommitDiff}. */ public class CommitDiffVariationDiffSource implements VariationDiffSource { - private final Path fileName; - private final String commitHash; + private final Path fileName; + private final String commitHash; - /** - * Create a source that refers to changes to the given file in the given commit. - * @param fileName Name of the modified file from whose changes the VariationDiff was parsed. - * @param commitHash Hash of the commit in which the edit occurred. - */ - public CommitDiffVariationDiffSource(final Path fileName, final String commitHash) { - this.fileName = fileName; - this.commitHash = commitHash; - } + /** + * Create a source that refers to changes to the given file in the given commit. + * @param fileName Name of the modified file from whose changes the VariationDiff was parsed. + * @param commitHash Hash of the commit in which the edit occurred. + */ + public CommitDiffVariationDiffSource(final Path fileName, final String commitHash) { + this.fileName = fileName; + this.commitHash = commitHash; + } - /** - * Returns the name of the modified file from whose changes the VariationDiff was parsed. - */ - public Path getFileName() { - return fileName; - } + /** + * Returns the name of the modified file from whose changes the VariationDiff was parsed. + */ + public Path getFileName() { + return fileName; + } - /** - * Returns the hash of the commit in which the edit occurred. - */ - public String getCommitHash() { - return commitHash; - } + /** + * Returns the hash of the commit in which the edit occurred. + */ + public String getCommitHash() { + return commitHash; + } @Override public String toString() { diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/traverse/VariationDiffVisitor.java b/src/main/java/org/variantsync/diffdetective/variation/diff/traverse/VariationDiffVisitor.java index 32e189f24..caaff592c 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/traverse/VariationDiffVisitor.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/traverse/VariationDiffVisitor.java @@ -13,13 +13,13 @@ */ @FunctionalInterface public interface VariationDiffVisitor { - /** - * Invoked by a traversal when a node is visited. - * The traversal might be continued by invoking respective methods on the given traversal object again. - * However, any node that was already visited, will not be visited again. - * @param traversal The current traversal. May be instructed on how to continue traversal. - * @param subtree The node that is currently visited. - * @see VariationDiffTraversal - */ - void visit(final VariationDiffTraversal traversal, final DiffNode subtree); + /** + * Invoked by a traversal when a node is visited. + * The traversal might be continued by invoking respective methods on the given traversal object again. + * However, any node that was already visited, will not be visited again. + * @param traversal The current traversal. May be instructed on how to continue traversal. + * @param subtree The node that is currently visited. + * @see VariationDiffTraversal + */ + void visit(final VariationDiffTraversal traversal, final DiffNode subtree); } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java b/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java index 0c16f8f76..cbeb3d811 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java @@ -48,7 +48,7 @@ public static BiPredicate> computeWhenNode return (t, p) -> V.get(t).contains(p); } - + /** * This method is not intended to be used directly and exists for optimization purposes only. * Instead, consider using {@link #naive(VariationDiff, Relevance)}. @@ -81,7 +81,7 @@ private static VariationDiff naive(final Varia return view; } - + /** * This method is not intended to be used directly and exists for optimization purposes only. * Instead, consider using {@link #naive(VariationDiff, Relevance)}. @@ -92,7 +92,7 @@ private static VariationDiff naive(final Varia * the translation of the given relevance predicate to a relevance on a variation diff. * This additional parameter is assumed to be created from the given relevance predicate * in terms of {@link #computeWhenNodesAreRelevant(VariationDiff, Relevance)}. - * + * * @param inView {@link #computeWhenNodesAreRelevant(VariationDiff, Relevance)} for the given variation diff d * and relevance predicate rho. * @throws IOException When the text-based diffing fails because of an IO error. @@ -137,7 +137,7 @@ public static VariationDiff naive(final Variat } /** - * An alternative algorithm for generating of views on variation diffs based on + * An alternative algorithm for generating of views on variation diffs based on * (1) removing cycles in the variation diff, * (2) interpreting the resulting acyclic variation diff as a colored variation tree, * (3) creating a view on the variation tree, diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/TreeView.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/TreeView.java index 4d306bf30..5d8cb96c7 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/TreeView.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/TreeView.java @@ -14,7 +14,7 @@ /** * This class groups the implementations for functions that generate views on variation trees, - * as described in Chapter 3 of our SPLC'23 paper - Views on Edits to Variational Software. + * as described in Chapter 3 of our SPLC'23 paper - Views on Edits to Variational Software. */ public final class TreeView { /** diff --git a/src/main/resources/tikz_header.tex b/src/main/resources/tikz_header.tex index f9bffb886..a7df65b2d 100644 --- a/src/main/resources/tikz_header.tex +++ b/src/main/resources/tikz_header.tex @@ -39,23 +39,23 @@ %\tikzexternalize \tikzset{ - text centered, - annotation/.style = {draw=vtdAnnotationBorderColor, circle, inner sep=0pt, minimum size=6.5mm, font=\footnotesize, line width=.4mm, fill=black!5}, - artifact/.style = {draw=vtdCodeColor, circle, inner sep=0pt, minimum size=6.5mm, font=\footnotesize, line width=.4mm, fill=black!5}, - non/.style = {}, - add/.style = {fill=diffAddColour}, - rem/.style = {fill=diffRemColour, dashed}, - before/.style = {color=\vtdEdgeBeforeColor}, - after/.style = {color=\vtdEdgeAfterColor}, - unchanged/.style = {}, - vtdarrow/.style = {black, {Latex[length=1.5mm,width=1.5mm]}-, line width = 0.35mm}, - vtddashed/.style = {black, dash pattern=on 2pt off 2pt, line width = 0.35mm}, - node distance = {9.5mm}, - object/.style = {draw, thick}, - large/.style = {node distance = 13mm, minimum size=9.5mm}, - textbox/.style = {draw, rectangle, rounded corners=0.4mm, fill=white, inner sep=2pt, font=\tiny, align=center}, - largertextbox/.style = {font=\small}, - vtdcloud/.style = {cloud, draw, minimum width=22mm, minimum height=15mm} + text centered, + annotation/.style = {draw=vtdAnnotationBorderColor, circle, inner sep=0pt, minimum size=6.5mm, font=\footnotesize, line width=.4mm, fill=black!5}, + artifact/.style = {draw=vtdCodeColor, circle, inner sep=0pt, minimum size=6.5mm, font=\footnotesize, line width=.4mm, fill=black!5}, + non/.style = {}, + add/.style = {fill=diffAddColour}, + rem/.style = {fill=diffRemColour, dashed}, + before/.style = {color=\vtdEdgeBeforeColor}, + after/.style = {color=\vtdEdgeAfterColor}, + unchanged/.style = {}, + vtdarrow/.style = {black, {Latex[length=1.5mm,width=1.5mm]}-, line width = 0.35mm}, + vtddashed/.style = {black, dash pattern=on 2pt off 2pt, line width = 0.35mm}, + node distance = {9.5mm}, + object/.style = {draw, thick}, + large/.style = {node distance = 13mm, minimum size=9.5mm}, + textbox/.style = {draw, rectangle, rounded corners=0.4mm, fill=white, inner sep=2pt, font=\tiny, align=center}, + largertextbox/.style = {font=\small}, + vtdcloud/.style = {cloud, draw, minimum width=22mm, minimum height=15mm} } \begin{document} diff --git a/src/test/java/CPPParserTest.java b/src/test/java/CPPParserTest.java index 3756098bd..b19be1355 100644 --- a/src/test/java/CPPParserTest.java +++ b/src/test/java/CPPParserTest.java @@ -1,16 +1,30 @@ -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.prop4j.Node; +import org.variantsync.diffdetective.datasets.predefined.MarlinControllingCExpressionVisitor; import org.variantsync.diffdetective.error.UnparseableFormulaException; -import org.variantsync.diffdetective.feature.cpp.CPPDiffLineFormulaExtractor; +import org.variantsync.diffdetective.feature.Annotation; +import org.variantsync.diffdetective.feature.AnnotationType; +import org.variantsync.diffdetective.feature.cpp.CPPAnnotationParser; 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.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.or; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; public class CPPParserTest { - private static record TestCase(String formula, String expected) { + private static record TestCase(String formula, Node expectedFormula, AnnotationType expectedType) { + public TestCase(String formula, Node expectedFormula) { + this(formula, expectedFormula, AnnotationType.If); + } + + public Annotation expectedAnnotation() { + return new Annotation(expectedType, expectedFormula); + } } private static record ThrowingTestCase(String formula) { @@ -18,110 +32,139 @@ 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)"), + // ignored directives + new TestCase("ifdef A", null, AnnotationType.None), + new TestCase("", null, AnnotationType.None), + new TestCase("#", null, AnnotationType.None), + new TestCase("#error A", null, AnnotationType.None), + new TestCase("#iferror A", null, AnnotationType.None), + + new TestCase("#if A", var("A")), + new TestCase("#ifdef A", var("defined(A)")), + new TestCase("#ifndef A", negate(var("defined(A)"))), + new TestCase("#elifdef A", var("defined(A)"), AnnotationType.Elif), + new TestCase("#elifndef A", negate(var("defined(A)")), AnnotationType.Elif), + new TestCase("#elif A", var("A"), AnnotationType.Elif), + new TestCase("#else", null, AnnotationType.Else), + new TestCase("#endif", null, AnnotationType.Endif), + + new TestCase("#if !A", negate(var("A"))), + new TestCase("#if A && B", and(var("A"), var("B"))), + new TestCase("#if A || B", or(var("A"), var("B"))), + new TestCase("#if A && (B || C)", and(var("A"), or(var("B"), var("C")))), + new TestCase("#if A && B || C", or(and(var("A"), var("B")), var("C"))), + + new TestCase("#if 1 > -42", var("1>-42")), + new TestCase("#if 1 > +42", var("1>+42")), + new TestCase("#if 42 > A", var("42>A")), + new TestCase("#if 42 > ~A", var("42>~A")), + new TestCase("#if A + B > 42", var("A+B>42")), + new TestCase("#if A << B", var("A<= B && C > D", and(var("A>=B"), var("C>D"))), + new TestCase("#if A * (B + C)", var("A*(B+C)")), + new TestCase("#if defined(A) && (B * 2) > C", and(var("defined(A)"), var("(B*2)>C"))), + new TestCase("#if(STDC == 1) && (defined(LARGE) || defined(COMPACT))", and(var("STDC==1"), or(var("defined(LARGE)"), var("defined(COMPACT)")))), + new TestCase("#if (('Z' - 'A') == 25)", var("('Z'-'A')==25")), + new TestCase("#if APR_CHARSET_EBCDIC && !(('Z' - 'A') == 25)", and(var("APR_CHARSET_EBCDIC"), negate(var("('Z'-'A')==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__") + var("(GNUTLS_VERSION_MAJOR+(GNUTLS_VERSION_MINOR>0||GNUTLS_VERSION_PATCH>=20))>3")), + + new TestCase("#if A && (B > C)", and(var("A"), var("B>C"))), + new TestCase("#if (A && B) > C", var("(A&&B)>C")), + new TestCase("#if C == (A || B)", var("C==(A||B)")), + new TestCase("#if ((A && B) > C)", var("(A&&B)>C")), + new TestCase("#if A && ((B + 1) > (C || D))", and(var("A"), var("(B+1)>(C||D)"))), + + new TestCase("#if __has_include", var("__has_include")), + new TestCase("#if defined __has_include", var("defined(__has_include)")), + new TestCase("#if __has_include()", var("__has_include()")), + new TestCase("#if __has_include()", var("__has_include()")), + new TestCase("#if __has_include(\"nss3/nss.h\")", var("__has_include(\"nss3/nss.h\")")), + new TestCase("#if __has_include(\"nss.h\")", var("__has_include(\"nss.h\")")), + + new TestCase("#if __has_attribute", var("__has_attribute")), + new TestCase("#if defined __has_attribute", var("defined(__has_attribute)")), + new TestCase("# if __has_attribute (nonnull)", var("__has_attribute(nonnull)")), + new TestCase("#if defined __has_attribute && __has_attribute (nonnull)", and(var("defined(__has_attribute)"), var("__has_attribute(nonnull)"))), + + new TestCase("#if __has_cpp_attribute", var("__has_cpp_attribute")), + new TestCase("#if defined __has_cpp_attribute", var("defined(__has_cpp_attribute)")), + new TestCase("#if __has_cpp_attribute (nonnull)", var("__has_cpp_attribute(nonnull)")), + new TestCase("#if __has_cpp_attribute (nonnull) && A", and(var("__has_cpp_attribute(nonnull)"), var("A"))), + + new TestCase("#if defined __has_c_attribute", var("defined(__has_c_attribute)")), + new TestCase("#if __has_c_attribute", var("__has_c_attribute")), + new TestCase("#if __has_c_attribute (nonnull)", var("__has_c_attribute(nonnull)")), + new TestCase("#if __has_c_attribute (nonnull) && A", and(var("__has_c_attribute(nonnull)"), var("A"))), + + new TestCase("#if defined __has_builtin", var("defined(__has_builtin)")), + new TestCase("#if __has_builtin", var("__has_builtin")), + new TestCase("#if __has_builtin (__nonnull)", var("__has_builtin(__nonnull)")), + new TestCase("#if __has_builtin (nonnull) && A", and(var("__has_builtin(nonnull)"), var("A"))), + + new TestCase("#if A // Comment && B", var("A")), + new TestCase("#if A /* Comment */ && B", and(var("A"), var("B"))), + new TestCase("#if A && B /* Multiline Comment", and(var("A"), var("B"))), + + new TestCase("#if A == B", var("A==B")), + new TestCase("#if A == 1", var("A==1")), + + new TestCase("#if defined A", var("defined(A)")), + new TestCase("#if defined(A)", var("defined(A)")), + new TestCase("#if defined (A)", var("defined(A)")), + new TestCase("#if defined ( A )", var("defined(A)")), + new TestCase("#if (defined A)", var("defined(A)")), + new TestCase("#if MACRO (A)", var("MACRO(A)")), + new TestCase("#if MACRO (A, B)", var("MACRO(A,B)")), + new TestCase("#if MACRO (A, B + C)", var("MACRO(A,B+C)")), + new TestCase("#if MACRO (A, B) == 1", var("MACRO(A,B)==1")), + + new TestCase("#if ifndef", var("ifndef")), + + new TestCase("#if __has_include_next()", var("__has_include_next()")), + new TestCase("#if __is_target_arch(x86)", var("__is_target_arch(x86)")), + new TestCase("#if A || (defined(NAME) && (NAME >= 199630))", or(var("A"), and(var("defined(NAME)"), var("NAME>=199630")))), + new TestCase("#if MACRO(part:part)", var("MACRO(part:part)")), + new TestCase("#if MACRO(x=1)", var("MACRO(x=1)")), + new TestCase("#if A = 3", var("A=3")), + new TestCase("#if ' ' == 32", var("' '==32")), + new TestCase("#if (NAME<<1) > (1<(1<= 199905) && (NAME < 1991011)) || (NAME >= 300000) || defined(NAME)", + or(and(var("defined(NAME)"), var("NAME>=199905"), var("NAME<1991011")), var("NAME>=300000"), var("defined(NAME)"))), + new TestCase("#if __has_warning(\"-Wa-warning\"_foo)", var("__has_warning(\"-Wa-warning\"_foo)")), + + new TestCase("#if A && (B - (C || D))", and(var("A"), var("B-(C||D)"))), + new TestCase("#if A == '1'", var("A=='1'")) ); } - private static List throwingTestCases() { + private static List nonMarlinTestCases() { return List.of( - // Invalid macro - new ThrowingTestCase(""), - new ThrowingTestCase("#"), - new ThrowingTestCase("ifdef A"), - new ThrowingTestCase("#error A"), - new ThrowingTestCase("#iferror A"), + new TestCase("#if ENABLED(A)", var("ENABLED(A)")), + new TestCase("#if DISABLED(A)", var("DISABLED(A)")), + new TestCase("#if ENABLED(FEATURE_A) && DISABLED(FEATURE_B)", and(var("ENABLED(FEATURE_A)"), var("DISABLED(FEATURE_B)"))), + new TestCase("#if ENABLED(A, B)", var("ENABLED(A,B)")), + new TestCase("#if OTHER(A, B)", var("OTHER(A,B)")), + new TestCase("#if A", var("A")) + ); + } + private static List marlinTestCases() { + return List.of( + new TestCase("#if ENABLED(A)", var("A")), + new TestCase("#if DISABLED(A)", negate(var("A"))), + new TestCase("#if ENABLED(FEATURE_A) && DISABLED(FEATURE_B)", and(var("FEATURE_A"), negate(var("FEATURE_B")))), + new TestCase("#if ENABLED(A, B)", var("ENABLED(A,B)")), + new TestCase("#if OTHER(A, B)", var("OTHER(A,B)")), + new TestCase("#if A", var("A")) + ); + } + + private static List throwingTestCases() { + return List.of( // Empty formula new ThrowingTestCase("#ifdef"), new ThrowingTestCase("#ifdef // Comment"), @@ -129,38 +172,30 @@ private static List throwingTestCases() { ); } - 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__)") + @ParameterizedTest + @MethodSource({"testCases", "nonMarlinTestCases"}) + public void testCase(TestCase testCase) throws UnparseableFormulaException { + assertEquals( + testCase.expectedAnnotation(), + new CPPAnnotationParser().parseAnnotation(testCase.formula()) ); } @ParameterizedTest - @MethodSource("testCases") - public void testCase(TestCase testCase) throws UnparseableFormulaException { + @MethodSource({"testCases", "marlinTestCases"}) + public void marlinTestCase(TestCase testCase) throws UnparseableFormulaException { assertEquals( - testCase.expected, - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + testCase.expectedAnnotation(), + new CPPAnnotationParser(new MarlinControllingCExpressionVisitor()).parseAnnotation(testCase.formula()) ); } + @ParameterizedTest @MethodSource("throwingTestCases") public void throwingTestCase(ThrowingTestCase testCase) { assertThrows(UnparseableFormulaException.class, () -> - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula) - ); - } - - @Disabled("WONTFIX") - @ParameterizedTest - @MethodSource("wontfixTestCases") - public void wontfixTestCase(TestCase testCase) throws UnparseableFormulaException { - assertEquals( - testCase.expected, - new CPPDiffLineFormulaExtractor().extractFormula(testCase.formula()) + new CPPAnnotationParser().parseAnnotation(testCase.formula) ); } - } diff --git a/src/test/java/FeatureIDETest.java b/src/test/java/FeatureIDETest.java index 1db9dffbf..d1ef53623 100644 --- a/src/test/java/FeatureIDETest.java +++ b/src/test/java/FeatureIDETest.java @@ -1,11 +1,17 @@ import de.ovgu.featureide.fm.core.editing.NodeCreator; import org.junit.jupiter.api.Test; -import org.prop4j.*; +import org.prop4j.False; +import org.prop4j.Literal; +import org.prop4j.Node; +import org.prop4j.True; import org.variantsync.diffdetective.analysis.logic.SAT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.or; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; import java.util.HashMap; import java.util.Map; @@ -37,8 +43,8 @@ public void falseIsContradiction() { @Test public void trueAndA_Equals_A() { final Node tru = createTrue(); - final Node a = new Literal("A"); - final Node trueAndA = new And(tru, a); + final Node a = var("A"); + final Node trueAndA = and(tru, a); assertTrue(SAT.equivalent(trueAndA, a)); } @@ -47,15 +53,15 @@ public void trueAndA_Equals_A() { */ @Test public void A_Equals_A() { - final Node a = new Literal("A"); + final Node a = var("A"); assertTrue(SAT.equivalent(a, a)); } @Test public void falseOrA_Equals_A() { final Node no = createFalse(); - final Node a = new Literal("A"); - final Node noOrA = new Or(no, a); + final Node a = var("A"); + final Node noOrA = or(no, a); assertTrue(SAT.equivalent(noOrA, a)); } @@ -67,7 +73,7 @@ public void atomString() { // assume the following does not crash createTrue().toString(); createFalse().toString(); - new And(createFalse(), createTrue()).toString(); + and(createFalse(), createTrue()).toString(); } @Test @@ -79,17 +85,24 @@ public void atomValuesEqual() { @Test public void noAssignmentOfAtomsNecessary() { final Map emptyAssignment = new HashMap<>(); - Node formula = new And(createFalse(), createTrue()); + Node formula = and(createFalse(), createTrue()); formula.getValue(emptyAssignment); } // @Test // public void ontest() { // final Node tru = createTrue(); -// final Node a = new Literal("A"); -// final Node trueAndA = new And(tru, a); -// final Node eq = new Equals(trueAndA, a); +// final Node a = var("A"); +// final Node trueAndA = and(tru, a); +// final Node eq = equivalent(trueAndA, a); // System.out.println(eq); // System.out.println(FixTrueFalse.On(eq)); // } + + @Test + public void testWeirdVariableNames() { + final Node node = var("A@#$%^&*( )}{]`~]}\\|,./<>?`[)(_"); + assertTrue(SAT.isSatisfiable(node)); + } + } diff --git a/src/test/java/FixTrueFalseTest.java b/src/test/java/FixTrueFalseTest.java index 976b36dec..bb450c048 100644 --- a/src/test/java/FixTrueFalseTest.java +++ b/src/test/java/FixTrueFalseTest.java @@ -1,6 +1,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.prop4j.*; +import org.prop4j.Literal; +import org.prop4j.Node; import org.variantsync.diffdetective.util.fide.FixTrueFalse; import java.util.List; @@ -8,7 +9,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.variantsync.diffdetective.util.fide.FixTrueFalse.False; import static org.variantsync.diffdetective.util.fide.FixTrueFalse.True; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.equivalent; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.implies; import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.or; public class FixTrueFalseTest { private record TestCase(Node formula, Node expectedResult) {} @@ -16,34 +21,34 @@ private record TestCase(Node formula, Node expectedResult) {} private final static Literal A = new Literal("A"); private final static Literal B = new Literal("B"); private final static Literal C = new Literal("C"); - private final static Node SomeIrreducible = new And(A, new Implies(A, B)); + private final static Node SomeIrreducible = and(A, implies(A, B)); public static List testCases() { return List.of( - new TestCase(new And(True, A), A), - new TestCase(new Or(False, A), A), - new TestCase(new And(False, A), False), - new TestCase(new Or(True, A), True), + new TestCase(and(True, A), A), + new TestCase(or(False, A), A), + new TestCase(and(False, A), False), + new TestCase(or(True, A), True), - new TestCase(new Implies(False, A), True), - new TestCase(new Implies(A, False), negate(A)), - new TestCase(new Implies(True, A), A), - new TestCase(new Implies(A, True), True), + new TestCase(implies(False, A), True), + new TestCase(implies(A, False), negate(A)), + new TestCase(implies(True, A), A), + new TestCase(implies(A, True), True), - new TestCase(new Equals(A, True), A), - new TestCase(new Equals(True, A), A), - new TestCase(new Equals(A, False), negate(A)), - new TestCase(new Equals(False, A), negate(A)), + new TestCase(equivalent(A, True), A), + new TestCase(equivalent(True, A), A), + new TestCase(equivalent(A, False), negate(A)), + new TestCase(equivalent(False, A), negate(A)), new TestCase( - new Equals( - new Or( - new And(False, True, A), + equivalent( + or( + and(False, True, A), SomeIrreducible ), - new Implies( - new Or(False, C), - new Not(False) + implies( + or(False, C), + negate(False) ) ), SomeIrreducible) diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java index 2938989a8..a2ac2dc8f 100644 --- a/src/test/java/JPPParserTest.java +++ b/src/test/java/JPPParserTest.java @@ -1,10 +1,12 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.prop4j.Node; 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.feature.Annotation; +import org.variantsync.diffdetective.feature.AnnotationType; +import org.variantsync.diffdetective.feature.jpp.JPPAnnotationParser; import org.variantsync.diffdetective.util.IO; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; @@ -23,6 +25,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.variantsync.diffdetective.util.Assert.fail; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.or; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; // Test cases for a parser of https://www.slashdev.ca/javapp/ public class JPPParserTest { @@ -32,58 +38,66 @@ private record TestCase(Input input, Expected expected) { private record ThrowingTestCase(String formula) { } - private static List> abstractionTests() { + private static TestCase abstrationTestCase(String input, Node expected) { + return new TestCase<>(input, new Annotation(AnnotationType.If, expected)); + } + + private static List> abstractionTests() { return List.of( + // source code lines + new TestCase<>("", new Annotation(AnnotationType.None)), + new TestCase<>("if (A) {", new Annotation(AnnotationType.None)), + new TestCase<>("#", new Annotation(AnnotationType.None)), + new TestCase<>("ifdef A", new Annotation(AnnotationType.None)), + new TestCase<>("#error A", new Annotation(AnnotationType.None)), + new TestCase<>("#iferror A", new Annotation(AnnotationType.None)), + /// #if expression // expression := | [!]defined(name) // expression := operand == operand - new JPPParserTest.TestCase<>("//#if 1 == -42", "1__EQ____U_MINUS__42"), + abstrationTestCase("//#if 1 == -42", var("1==-42")), // expression := operand != operand - new JPPParserTest.TestCase<>("// #if 1 != 0", "1__NEQ__0"), + abstrationTestCase("// #if 1 != 0", var("1!=0")), // expression := operand <= operand - new JPPParserTest.TestCase<>("//#if -1 <= 0", "__U_MINUS__1__LEQ__0"), + abstrationTestCase("//#if -1 <= 0", var("-1<=0")), // expression := operand < operand - new JPPParserTest.TestCase<>("//#if \"str\" < 0", "__QUOTE__str__QUOTE____LT__0"), + abstrationTestCase("//#if \"str\" < 0", var("\"str\"<0")), // expression := operand >= operand - new JPPParserTest.TestCase<>("// #if \"str\" >= \"str\"", "__QUOTE__str__QUOTE____GEQ____QUOTE__str__QUOTE__"), + abstrationTestCase("// #if \"str\" >= \"str\"", var("\"str\">=\"str\"")), // expression := operand > operand - new JPPParserTest.TestCase<>("// #if 1.2 > 0", "1__DOT__2__GT__0"), + abstrationTestCase("// #if 1.2 > 0", var("1.2>0")), // expression := defined(name) - new JPPParserTest.TestCase<>("//#if defined(property)", "DEFINED_property"), + abstrationTestCase("//#if defined(property)", var("defined(property)")), // expression := !defined(name) - new JPPParserTest.TestCase<>("//#if !defined(property)", "__U_NOT__DEFINED_property"), + abstrationTestCase("//#if !defined(property)", negate(var("defined(property)"))), // operand := ${property} - new JPPParserTest.TestCase<>("//#if ${os_version} == 4.1", "os_version__EQ__4__DOT__1"), + abstrationTestCase("//#if ${os_version} == 4.1", var("${os_version}==4.1")), /// #if expression and expression - new JPPParserTest.TestCase<>("//#if 1 > 2 and defined( FEAT_A )", "1__GT__2&&DEFINED_FEAT_A"), + abstrationTestCase("//#if 1 > 2 and defined( FEAT_A )", and(var("1>2"), var("defined(FEAT_A)"))), /// #if expression or expression - new JPPParserTest.TestCase<>("//#if !defined(left) or defined(right)", "__U_NOT__DEFINED_left||DEFINED_right"), + abstrationTestCase("//#if !defined(left) or defined(right)", or(negate(var("defined(left)")), var("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") + abstrationTestCase("//#if ${os_version} == 4.1 and 1 > -42 or defined(ALL)", or(and(var("${os_version}==4.1"), var("1>-42")), var("defined(ALL)"))), + + /// #if "string with whitespace" + abstrationTestCase("//#if ${ test } == \"a b\"", var("${test}==\"a b\"")) ); } 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"), + 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} > ") + new JPPParserTest.ThrowingTestCase("//#if 1 >"), + new JPPParserTest.ThrowingTestCase("//#if == 2"), + new JPPParserTest.ThrowingTestCase("//#if ${version} > ") ); } @@ -96,10 +110,10 @@ private static List> fullDiffTests() { @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(testCase.input()) + new JPPAnnotationParser().parseAnnotation(testCase.input()) ); } @@ -107,7 +121,7 @@ public void testCase(JPPParserTest.TestCase testCase) throws Unp @MethodSource("throwingTestCases") public void throwingTestCase(JPPParserTest.ThrowingTestCase testCase) { assertThrows(UnparseableFormulaException.class, () -> - new JPPDiffLineFormulaExtractor().extractFormula(testCase.formula) + new JPPAnnotationParser().parseAnnotation(testCase.formula) ); } @@ -121,7 +135,7 @@ public void fullDiffTestCase(JPPParserTest.TestCase testCase) throws new VariationDiffParseOptions( false, false - ).withAnnotationParser(PreprocessorAnnotationParser.JPPAnnotationParser) + ).withAnnotationParser(new JPPAnnotationParser()) ); } diff --git a/src/test/java/LineGraphTest.java b/src/test/java/LineGraphTest.java index af2aa8779..2c79e3dbd 100644 --- a/src/test/java/LineGraphTest.java +++ b/src/test/java/LineGraphTest.java @@ -23,7 +23,7 @@ * For testing the import of a line graph. */ public class LineGraphTest { - private final static LineGraphImportOptions IMPORT_OPTIONS = new LineGraphImportOptions<>( + private final static LineGraphImportOptions IMPORT_OPTIONS = new LineGraphImportOptions<>( GraphFormat.VARIATION_DIFF, new CommitDiffVariationDiffLabelFormat(), new LabelOnlyDiffNodeFormat<>(), @@ -67,15 +67,15 @@ public void idempotentReadWrite(Path testFile) throws IOException { } } - /** - * Check consistency of {@link VariationDiff VariationDiffs}. - * - * @param treeList {@link VariationDiff} list - */ - private static void assertConsistencyForAll(final List> treeList) { + /** + * Check consistency of {@link VariationDiff VariationDiffs}. + * + * @param treeList {@link VariationDiff} list + */ + private static void assertConsistencyForAll(final List> treeList) { // for (final VariationDiff t : treeList) { // VariationDiffRenderer.WithinDiffDetective().render(t, t.getSource().toString(), Path.of("error"), PatchDiffRenderer.ErrorVariationDiffRenderOptions); // } - treeList.forEach(VariationDiff::assertConsistency); - } + treeList.forEach(VariationDiff::assertConsistency); + } } diff --git a/src/test/java/PCTest.java b/src/test/java/PCTest.java index 7944dcdef..361a9b53f 100644 --- a/src/test/java/PCTest.java +++ b/src/test/java/PCTest.java @@ -1,7 +1,5 @@ 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.tinylog.Logger; import org.variantsync.diffdetective.analysis.logic.SAT; @@ -16,16 +14,18 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; import static org.variantsync.diffdetective.variation.diff.Time.AFTER; import static org.variantsync.diffdetective.variation.diff.Time.BEFORE; -import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; public class PCTest { - private static final Literal A = new Literal("A"); - private static final Literal B = new Literal("B"); - private static final Literal C = new Literal("C"); - private static final Literal D = new Literal("D"); - private static final Literal E = new Literal("E"); + private static final Node A = var("A"); + private static final Node B = var("B"); + private static final Node C = var("C"); + private static final Node D = var("D"); + private static final Node E = var("E"); record ExpectedPC(Node before, Node after) {} record TestCase(Path file, Map expectedResult) { @Override @@ -38,27 +38,27 @@ public String toString() { private final static TestCase a = new TestCase( Path.of("a.diff"), Map.of( - "1", new ExpectedPC(A, new And(A, B)), - "2", new ExpectedPC(A, new And(A, C, negate(B))), - "3", new ExpectedPC(new And(A, D, E), new And(A, D)), + "1", new ExpectedPC(A, and(A, B)), + "2", new ExpectedPC(A, and(A, C, negate(B))), + "3", new ExpectedPC(and(A, D, E), and(A, D)), "4", new ExpectedPC(A, A) )); private final static TestCase elif = new TestCase( Path.of("elif.diff"), Map.of( "1", new ExpectedPC(A, A), - "2", new ExpectedPC(new And(negate(A), B), new And(negate(A), B)), - "3", new ExpectedPC(new And(negate(A), negate(B), C), new And(negate(A), B)), - "4", new ExpectedPC(new And(negate(A), negate(B), C), new And(negate(A), negate(B), D)), - "5", new ExpectedPC(new And(negate(A), negate(B), negate(C)), new And(negate(A), negate(B), negate(D))) + "2", new ExpectedPC(and(negate(A), B), and(negate(A), B)), + "3", new ExpectedPC(and(negate(A), negate(B), C), and(negate(A), B)), + "4", new ExpectedPC(and(negate(A), negate(B), C), and(negate(A), negate(B), D)), + "5", new ExpectedPC(and(negate(A), negate(B), negate(C)), and(negate(A), negate(B), negate(D))) )); private final static TestCase elze = new TestCase( Path.of("else.diff"), Map.of( - "1", new ExpectedPC(A, new And(A, B)), - "2", new ExpectedPC(new And(negate(A), C), new And(A, negate(B), C)), - "3", new ExpectedPC(new And(negate(A), C), new And(A, negate(B), negate(C))), - "4", new ExpectedPC(new And(negate(A), negate(C)), negate(A)) + "1", new ExpectedPC(A, and(A, B)), + "2", new ExpectedPC(and(negate(A), C), and(A, negate(B), C)), + "3", new ExpectedPC(and(negate(A), C), and(A, negate(B), negate(C))), + "4", new ExpectedPC(and(negate(A), negate(C)), negate(A)) )); private static String errorAt(final String node, String time, Node is, Node should) { diff --git a/src/test/java/PrintWorkingTreeDiff.java b/src/test/java/PrintWorkingTreeDiff.java index ec0aec1ad..4ed697f7c 100644 --- a/src/test/java/PrintWorkingTreeDiff.java +++ b/src/test/java/PrintWorkingTreeDiff.java @@ -19,35 +19,33 @@ */ @Disabled public class PrintWorkingTreeDiff { - - @Test - public void testWorkingTreeDiff() throws IOException, NoHeadException, GitAPIException { - String repoName = "test_repo"; - - // Retrieve repository - final String repo_path = "repositories/" + repoName; - final Repository repository = Repository.fromZip(Paths.get(repo_path + ".zip"), repoName); // remove ".zip" when using fromDirectory() - repository.setParseOptions(repository.getParseOptions().withDiffStoragePolicy(DiffStoragePolicy.REMEMBER_FULL_DIFF)); - - final GitDiffer differ = new GitDiffer(repository); - - // Retrieve latest commit - // Alternatively, replace with desired RevCommit - RevCommit latestCommit = differ.getJGitRepo().log().setMaxCount(1).call().iterator().next(); - - // Extract CommitDiff - CommitDiffResult commitDiffResult = GitDiffer.createWorkingTreeDiff(differ.getJGitRepo(), repository.getDiffFilter(), latestCommit, repository.getParseOptions()); - CommitDiff commitDiff = commitDiffResult.diff().orElseThrow(); - - // Save diff output - String diffOutput = ""; - for (PatchDiff patchDiff : commitDiff.getPatchDiffs()) { - diffOutput += patchDiff.getDiff(); - } - - // Check whether diffs match - Path fileForVerification = Path.of("src", "test", "resources", repoName + ".txt"); - TestUtils.assertEqualToFile(fileForVerification, diffOutput); - - } + + @Test + public void testWorkingTreeDiff() throws IOException, NoHeadException, GitAPIException { + String repoName = "test_repo"; + + // Retrieve repository + final String repo_path = "repositories/" + repoName; + final Repository repository = Repository.fromZip(Paths.get(repo_path + ".zip"), repoName); // remove ".zip" when using fromDirectory() + repository.setParseOptions(repository.getParseOptions().withDiffStoragePolicy(DiffStoragePolicy.REMEMBER_FULL_DIFF)); + + // Retrieve latest commit + // Alternatively, replace with desired RevCommit + RevCommit latestCommit = repository.getGitRepo().log().setMaxCount(1).call().iterator().next(); + + // Extract CommitDiff + CommitDiffResult commitDiffResult = GitDiffer.createWorkingTreeDiff(repository, latestCommit); + CommitDiff commitDiff = commitDiffResult.diff().orElseThrow(); + + // Save diff output + String diffOutput = ""; + for (PatchDiff patchDiff : commitDiff.getPatchDiffs()) { + diffOutput += patchDiff.getDiff(); + } + + // Check whether diffs match + Path fileForVerification = Path.of("src", "test", "resources", repoName + ".txt"); + TestUtils.assertEqualToFile(fileForVerification, diffOutput); + + } } diff --git a/src/test/java/PropositionalFormulaParserTest.java b/src/test/java/PropositionalFormulaParserTest.java deleted file mode 100644 index ee891f811..000000000 --- a/src/test/java/PropositionalFormulaParserTest.java +++ /dev/null @@ -1,83 +0,0 @@ -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; - -/** - * 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 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( - 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__")) - ); - } - - /** - * 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") - public void testCase(TestCase testCase) { - assertEquals( - testCase.expected, - PropositionalFormulaParser.Default.parse(testCase.formula) - ); - } -} \ No newline at end of file diff --git a/src/test/java/SATTest.java b/src/test/java/SATTest.java index 26075c25a..549069cb3 100644 --- a/src/test/java/SATTest.java +++ b/src/test/java/SATTest.java @@ -1,6 +1,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.prop4j.*; +import org.prop4j.Node; import org.variantsync.diffdetective.analysis.logic.SAT; import org.variantsync.diffdetective.analysis.logic.Tseytin; import org.variantsync.diffdetective.util.fide.FixTrueFalse; @@ -10,20 +10,25 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.and; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.equivalent; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.implies; import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.or; +import static org.variantsync.diffdetective.util.fide.FormulaUtils.var; public class SATTest { - private static final Literal A = new Literal("A"); - private static final Literal B = new Literal("B"); - private static final Literal C = new Literal("C"); - private static final Literal D = new Literal("D"); + private static final Node A = var("A"); + private static final Node B = var("B"); + private static final Node C = var("C"); + private static final Node D = var("D"); public static List tautologyTestCases() { return List.of( FixTrueFalse.True, negate(FixTrueFalse.False), - new Or(A, negate(A)), - new Implies(new And(A, new Implies(A, B)), B) // modus ponens + or(A, negate(A)), + implies(and(A, implies(A, B)), B) // modus ponens ); } @@ -31,7 +36,7 @@ public static List contradictoryTestCases() { return List.of( FixTrueFalse.False, negate(FixTrueFalse.True), - new And(A, negate(A)) + and(A, negate(A)) ); } @@ -39,12 +44,12 @@ public static List satisfiableTestCases() { var satisfiableTestCases = new ArrayList<>(List.of( A, negate(A), - negate(new And(A, B)), - new And(A, B), - new Or(A, B), - new Implies(A, B), - new Equals(A, B), - new And(A, B, new Or(negate(C), new Implies(D, A))) + negate(and(A, B)), + and(A, B), + or(A, B), + implies(A, B), + equivalent(A, B), + and(A, B, or(negate(C), implies(D, A))) )); satisfiableTestCases.addAll(tautologyTestCases()); return satisfiableTestCases; diff --git a/src/test/java/TreeDiffingTest.java b/src/test/java/TreeDiffingTest.java index 1bcf7c246..c4c0dad7f 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.PreprocessorAnnotationParser; +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.VariationDiff; @@ -117,7 +117,7 @@ public VariationTree parseVariationTree(Path filename) throws IO VariationDiffParser.createVariationTree( file, new VariationDiffParseOptions( - PreprocessorAnnotationParser.CPPAnnotationParser, + new CPPAnnotationParser(), false, false) ).getRoot().projection(BEFORE).toVariationTree(), diff --git a/src/test/java/ViewTest.java b/src/test/java/ViewTest.java index f033d66b2..595ce6dd1 100644 --- a/src/test/java/ViewTest.java +++ b/src/test/java/ViewTest.java @@ -105,10 +105,10 @@ void test(String filename) throws IOException, DiffParseException { ); } -// showViews(initialVDiff, new VariantQuery(new And(new Literal("X")))); -// showViews(initialVDiff, new VariantQuery(new And(new Literal("Y")))); -// showViews(initialVDiff, new VariantQuery(new And(negate(new Literal("X"))))); -// showViews(initialVDiff, new VariantQuery(new And(negate(new Literal("Y"))))); +// showViews(initialVDiff, new VariantQuery(and(var("X")))); +// showViews(initialVDiff, new VariantQuery(and(var("Y")))); +// showViews(initialVDiff, new VariantQuery(and(negate(var("X"))))); +// showViews(initialVDiff, new VariantQuery(and(negate(var("Y"))))); // showViews(initialVDiff, new FeatureQuery("X")); // showViews(initialVDiff, new FeatureQuery("Y")); // showViews(initialVDiff, new ArtifactQuery("y")); @@ -145,7 +145,7 @@ void inspectRunningExample(String filename) throws IOException, DiffParseExcepti // Figure 3 final Configure configureExample1 = new Configure( - and(featureRing, /* FM = */ negate(new And(featureDoubleLink, featureRing))) + and(featureRing, /* FM = */ negate(and(featureDoubleLink, featureRing))) ); GameEngine.showAndAwaitAll( Show.tree(TreeView.tree(b, configureExample1), "Figure 3: view_{tree}(Figure 1, " + configureExample1 + ")") diff --git a/src/test/resources/diffs/differ/a032346092e47048becb36a7cb183b4739547370.lg b/src/test/resources/diffs/differ/a032346092e47048becb36a7cb183b4739547370.lg index 042ba288a..d151296bd 100644 --- a/src/test/resources/diffs/differ/a032346092e47048becb36a7cb183b4739547370.lg +++ b/src/test/resources/diffs/differ/a032346092e47048becb36a7cb183b4739547370.lg @@ -2,10 +2,10 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True v 147 NON;ARTIFACT;(old: 1, diff: 1, new: 1);(old: 2, diff: 2, new: 2);; v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 3, new: 3);; v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 4, new: 4);; -v 336 NON;IF;(old: 4, diff: 4, new: 4);(old: 33, diff: 33, new: 33);-_DB_STL_RESOURCE_MANAGER_H__;#ifndef _DB_STL_RESOURCE_MANAGER_H__ +v 336 NON;IF;(old: 4, diff: 4, new: 4);(old: 33, diff: 33, new: 33);-defined(_DB_STL_RESOURCE_MANAGER_H__);#ifndef _DB_STL_RESOURCE_MANAGER_H__ v 403 NON;ARTIFACT;(old: 5, diff: 5, new: 5);(old: 6, diff: 6, new: 6);;#include v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 7, new: 7);; -v 528 NON;IF;(old: 7, diff: 7, new: 7);(old: 9, diff: 9, new: 9);-DEFINED___LB__MESSAGE__RB__;#if !defined (MESSAGE) +v 528 NON;IF;(old: 7, diff: 7, new: 7);(old: 9, diff: 9, new: 9);-defined(MESSAGE);#if !defined (MESSAGE) v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 9, new: 9);; #define MESSAGE "You wish!" v 657 NON;ELSE;(old: 9, diff: 9, new: 9);(old: 11, diff: 11, new: 11);;#else v 723 NON;ARTIFACT;(old: 10, diff: 10, new: 10);(old: 11, diff: 11, new: 11);; // do nothing @@ -13,14 +13,14 @@ v 851 NON;ARTIFACT;(old: 12, diff: 12, new: 12);(old: 13, diff: 13, new: 13);; v 915 NON;ARTIFACT;(old: 13, diff: 13, new: 13);(old: 14, diff: 14, new: 14);;int main(void) { v 979 NON;ARTIFACT;(old: 14, diff: 14, new: 14);(old: 15, diff: 15, new: 15);; printf("Here is the message: %s\n", MESSAGE); v 1043 NON;ARTIFACT;(old: 15, diff: 15, new: 15);(old: 16, diff: 16, new: 16);; -v 1104 NON;IF;(old: 16, diff: 16, new: 16);(old: 21, diff: 21, new: 21);(-DEFINED___LB__HAVE_GETHOSTBYNAME_R__RB__ | -DEFINED___LB__HAVE_GETHOSTBYADDR_R__RB__ | -DEFINED___LB__HAVE_GETPWUID_R__RB__ | -DEFINED___LB__HAVE_GETGRGID_R__RB__) & (-DEFINED___LB__HAVE_MTSAFE_GETHOSTBYNAME__RB__ | -DEFINED___LB__HAVE_MTSAFE_GETHOSTBYADDR__RB__);#if (!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R) || \; !defined(HAVE_GETPWUID_R) || !defined(HAVE_GETGRGID_R)) && \; (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR)) +v 1104 NON;IF;(old: 16, diff: 16, new: 16);(old: 21, diff: 21, new: 21);(-defined(HAVE_GETHOSTBYNAME_R) | -defined(HAVE_GETHOSTBYADDR_R) | -defined(HAVE_GETPWUID_R) | -defined(HAVE_GETGRGID_R)) & (-defined(HAVE_MTSAFE_GETHOSTBYNAME) | -defined(HAVE_MTSAFE_GETHOSTBYADDR));#if (!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R) || \; !defined(HAVE_GETPWUID_R) || !defined(HAVE_GETGRGID_R)) && \; (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR)) v 1299 NON;ARTIFACT;(old: 19, diff: 19, new: 19);(old: 20, diff: 20, new: 20);; blabla v 1363 NON;ARTIFACT;(old: 20, diff: 20, new: 20);(old: 21, diff: 21, new: 21);; blabla v 1619 NON;ARTIFACT;(old: 24, diff: 24, new: 24);(old: 25, diff: 25, new: 25);; -v 1680 NON;IF;(old: 25, diff: 25, new: 25);(old: 28, diff: 28, new: 28);(-DEFINED___LB__HAVE_GETHOSTBYNAME_R__RB__ | -DEFINED___LB__HAVE_GETHOSTBYADDR_R__RB__) & (-DEFINED___LB__HAVE_MTSAFE_GETHOSTBYNAME__RB__ | -DEFINED___LB__HAVE_MTSAFE_GETHOSTBYADDR__RB__);#if (!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R)) && \; (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR)) +v 1680 NON;IF;(old: 25, diff: 25, new: 25);(old: 28, diff: 28, new: 28);(-defined(HAVE_GETHOSTBYNAME_R) | -defined(HAVE_GETHOSTBYADDR_R)) & (-defined(HAVE_MTSAFE_GETHOSTBYNAME) | -defined(HAVE_MTSAFE_GETHOSTBYADDR));#if (!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R)) && \; (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR)) v 1811 NON;ARTIFACT;(old: 27, diff: 27, new: 27);(old: 28, diff: 28, new: 28);;hello v 1939 NON;ARTIFACT;(old: 29, diff: 29, new: 29);(old: 30, diff: 30, new: 30);;} -v 2000 NON;IF;(old: 30, diff: 30, new: 30);(old: 32, diff: 32, new: 32);DEFINED___LB__MESSAGE__RB__;#if defined (MESSAGE) +v 2000 NON;IF;(old: 30, diff: 30, new: 30);(old: 32, diff: 32, new: 32);defined(MESSAGE);#if defined (MESSAGE) v 2067 NON;ARTIFACT;(old: 31, diff: 31, new: 31);(old: 32, diff: 32, new: 32);; #define MESSAGE "You wish!" e 147 16 ba;0,0 e 211 16 ba;1,1 diff --git a/src/test/resources/diffs/jpp/basic_jpp_expected.lg b/src/test/resources/diffs/jpp/basic_jpp_expected.lg index c47721aa1..c6f8d5924 100644 --- a/src/test/resources/diffs/jpp/basic_jpp_expected.lg +++ b/src/test/resources/diffs/jpp/basic_jpp_expected.lg @@ -2,19 +2,19 @@ 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 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 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 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 diff --git a/src/test/resources/diffs/linenumbers/deleteMLM.txt b/src/test/resources/diffs/linenumbers/deleteMLM.txt index 2c2f1b530..ffb5f60f4 100644 --- a/src/test/resources/diffs/linenumbers/deleteMLM.txt +++ b/src/test/resources/diffs/linenumbers/deleteMLM.txt @@ -1,4 +1,4 @@ --#ifdef d10 && \ +-#if d10 && \ - d11 - d12 -#endif \ No newline at end of file diff --git a/src/test/resources/diffs/linenumbers/lineno1.txt b/src/test/resources/diffs/linenumbers/lineno1.txt index 8ed6f163f..1164e30f2 100644 --- a/src/test/resources/diffs/linenumbers/lineno1.txt +++ b/src/test/resources/diffs/linenumbers/lineno1.txt @@ -10,7 +10,7 @@ b8a9 +#endif b9a11 --#ifdef d10 && \ +-#if d10 && \ - d11 - d12 -#endif \ No newline at end of file diff --git a/src/test/resources/diffs/parser/01_expected.lg b/src/test/resources/diffs/parser/01_expected.lg index fe4c0766d..53af723d1 100644 --- a/src/test/resources/diffs/parser/01_expected.lg +++ b/src/test/resources/diffs/parser/01_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 3, new: -1);A;#ifdef \; A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 3, new: -1);defined(A);#ifdef \; A v 195 ADD;ARTIFACT;(old: -1, diff: 2, new: 1);(old: -1, diff: 3, new: 2);; A e 136 16 b;0,-1 e 195 16 a;-1,0 diff --git a/src/test/resources/diffs/parser/04_expected.lg b/src/test/resources/diffs/parser/04_expected.lg index d1b25b758..b04538603 100644 --- a/src/test/resources/diffs/parser/04_expected.lg +++ b/src/test/resources/diffs/parser/04_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 4, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 4, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 4, diff: 4, new: 4);; a \; b e 144 16 ba;0,0 e 211 144 ba;0,0 diff --git a/src/test/resources/diffs/parser/05_expected.lg b/src/test/resources/diffs/parser/05_expected.lg index d1b25b758..b04538603 100644 --- a/src/test/resources/diffs/parser/05_expected.lg +++ b/src/test/resources/diffs/parser/05_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 4, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 4, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 4, diff: 4, new: 4);; a \; b e 144 16 ba;0,0 e 211 144 ba;0,0 diff --git a/src/test/resources/diffs/parser/08_expected.lg b/src/test/resources/diffs/parser/08_expected.lg index 33bd5894a..6d28b028d 100644 --- a/src/test/resources/diffs/parser/08_expected.lg +++ b/src/test/resources/diffs/parser/08_expected.lg @@ -1,7 +1,7 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 4, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 4, new: -1);defined(A);#ifdef A v 275 NON;ARTIFACT;(old: 2, diff: 3, new: 2);(old: 3, diff: 4, new: 3);;Code -v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 4, new: 3);B;#ifdef B +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 4, new: 3);defined(B);#ifdef B e 136 16 b;0,-1 e 275 136 b;0,-1 e 275 192 a;-1,0 diff --git a/src/test/resources/diffs/parser/09_expected.lg b/src/test/resources/diffs/parser/09_expected.lg index 6303ac6d3..714b02ae3 100644 --- a/src/test/resources/diffs/parser/09_expected.lg +++ b/src/test/resources/diffs/parser/09_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 5, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 5, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 3, new: 3);;Code 1 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 3);(old: 5, diff: 5, new: 4);;Code 2 e 144 16 ba;0,0 diff --git a/src/test/resources/diffs/parser/10_expected.lg b/src/test/resources/diffs/parser/10_expected.lg index 53aa5fe5f..23e1643c1 100644 --- a/src/test/resources/diffs/parser/10_expected.lg +++ b/src/test/resources/diffs/parser/10_expected.lg @@ -1,7 +1,7 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 200 REM;IF;(old: 1, diff: 2, new: -1);(old: 3, diff: 4, new: -1);A;#ifdef A +v 200 REM;IF;(old: 1, diff: 2, new: -1);(old: 3, diff: 4, new: -1);defined(A);#ifdef A v 275 NON;ARTIFACT;(old: 2, diff: 3, new: 2);(old: 3, diff: 4, new: 3);;Code -v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 5, new: 3);B;#ifdef B +v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 5, new: 3);defined(B);#ifdef B e 200 16 b;0,-1 e 275 200 b;0,-1 e 275 128 a;-1,0 diff --git a/src/test/resources/diffs/parser/11_expected.lg b/src/test/resources/diffs/parser/11_expected.lg index e83b8b6fd..886bc794f 100644 --- a/src/test/resources/diffs/parser/11_expected.lg +++ b/src/test/resources/diffs/parser/11_expected.lg @@ -1,7 +1,7 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 4, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 4, new: -1);defined(A);#ifdef A v 275 NON;ARTIFACT;(old: 2, diff: 3, new: 2);(old: 3, diff: 4, new: 3);;Code -v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 5, new: 3);B;#ifdef B +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 5, new: 3);defined(B);#ifdef B e 136 16 b;0,-1 e 275 136 b;0,-1 e 275 192 a;-1,0 diff --git a/src/test/resources/diffs/parser/12_expected.lg b/src/test/resources/diffs/parser/12_expected.lg index 6a1964001..2b0891083 100644 --- a/src/test/resources/diffs/parser/12_expected.lg +++ b/src/test/resources/diffs/parser/12_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 3, new: 3);DEFINED___LB__A__RB__;#if defined(A) +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 3, new: 3);defined(A);#if defined(A) v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 3, new: 3);;Code e 144 16 ba;0,0 e 211 144 ba;0,0 diff --git a/src/test/resources/diffs/parser/13_expected.lg b/src/test/resources/diffs/parser/13_expected.lg index 506a930db..698366f25 100644 --- a/src/test/resources/diffs/parser/13_expected.lg +++ b/src/test/resources/diffs/parser/13_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 2, new: 2);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 2, new: 2);defined(A);#ifdef A v 209 NON;ELSE;(old: 2, diff: 2, new: 2);(old: 3, diff: 3, new: 3);;#else // B e 144 16 ba;0,0 e 209 144 ba;0,0 diff --git a/src/test/resources/diffs/parser/14_expected.lg b/src/test/resources/diffs/parser/14_expected.lg index ecd900447..7cf93e82e 100644 --- a/src/test/resources/diffs/parser/14_expected.lg +++ b/src/test/resources/diffs/parser/14_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 3, new: 2);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 3, new: 2);defined(A);#ifdef A v 201 REM;ELSE;(old: 2, diff: 2, new: -1);(old: 3, diff: 3, new: -1);;#else // B e 144 16 ba;0,0 e 201 144 b;0,-1 diff --git a/src/test/resources/diffs/parser/15_expected.lg b/src/test/resources/diffs/parser/15_expected.lg index cd9d6784f..6f14d5f89 100644 --- a/src/test/resources/diffs/parser/15_expected.lg +++ b/src/test/resources/diffs/parser/15_expected.lg @@ -1,5 +1,5 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 4, new: 3);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 2, diff: 4, new: 3);defined(A);#ifdef A v 202 REM;ELIF;(old: 2, diff: 2, new: -1);(old: 4, diff: 4, new: -1);B;#elif \; B v 259 ADD;ARTIFACT;(old: -1, diff: 3, new: 2);(old: -1, diff: 4, new: 3);; B e 144 16 ba;0,0 diff --git a/src/test/resources/diffs/parser/16_expected.lg b/src/test/resources/diffs/parser/16_expected.lg index 4a0746b6b..f351f9f7a 100644 --- a/src/test/resources/diffs/parser/16_expected.lg +++ b/src/test/resources/diffs/parser/16_expected.lg @@ -1,11 +1,11 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 7, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 7, new: -1);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 1);(old: 3, diff: 3, new: 2);;a v 339 NON;ARTIFACT;(old: 3, diff: 4, new: 3);(old: 4, diff: 5, new: 4);;b v 467 NON;ARTIFACT;(old: 4, diff: 6, new: 5);(old: 5, diff: 7, new: 6);;c v 529 NON;ELSE;(old: 5, diff: 7, new: 6);(old: 7, diff: 10, new: 8);;#else D v 595 NON;ARTIFACT;(old: 6, diff: 8, new: 7);(old: 7, diff: 9, new: 8);;d -v 256 ADD;IF;(old: -1, diff: 3, new: 2);(old: -1, diff: 5, new: 4);B;#ifdef B +v 256 ADD;IF;(old: -1, diff: 3, new: 2);(old: -1, diff: 5, new: 4);defined(B);#ifdef B v 386 ADD;ELIF;(old: -1, diff: 5, new: 4);(old: -1, diff: 7, new: 6);C;#elif C e 136 16 b;0,-1 e 211 136 b;0,-1 diff --git a/src/test/resources/diffs/parser/17_expected.lg b/src/test/resources/diffs/parser/17_expected.lg index 718641f8a..0476ef4d0 100644 --- a/src/test/resources/diffs/parser/17_expected.lg +++ b/src/test/resources/diffs/parser/17_expected.lg @@ -1,7 +1,7 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 2, diff: 3, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 2, diff: 3, new: -1);defined(A);#ifdef A v 265 REM;ELSE;(old: 2, diff: 3, new: -1);(old: 3, diff: 5, new: -1);;#else -v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 4, new: 3);B;#ifdef B #\;#else +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 4, new: 3);defined(B);#ifdef B #\;#else e 136 16 b;0,-1 e 265 136 b;0,-1 e 192 16 a;-1,0 diff --git a/src/test/resources/multilinemacros/mldiff1_expected.lg b/src/test/resources/multilinemacros/mldiff1_expected.lg index 327fb093e..10ef7f3a1 100644 --- a/src/test/resources/multilinemacros/mldiff1_expected.lg +++ b/src/test/resources/multilinemacros/mldiff1_expected.lg @@ -1,11 +1,11 @@ v 16 NON;IF;(old: -1, diff: -1, new: -1);(old: -1, diff: -1, new: -1);True v 147 NON;ARTIFACT;(old: 1, diff: 1, new: 1);(old: 2, diff: 2, new: 2);;code0 -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 5, diff: 5, new: 5);-(DEFINED___LB____FreeBSD____RB__ | DEFINED___LB____OpenBSD____RB__ | DEFINED___LB____NetBSD____RB__ | DEFINED___LB____NEWLINE____RB__);#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \; || defined(__NEWLINE__)) +v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 5, diff: 5, new: 5);-(defined(__FreeBSD__) | defined(__OpenBSD__) | defined(__NetBSD__) | defined(__NEWLINE__));#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \; || defined(__NEWLINE__)) v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 5, new: 5);; foo(); v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 7, new: 7);;code1 v 659 NON;ARTIFACT;(old: 7, diff: 9, new: 9);(old: 8, diff: 10, new: 10);; bar(); v 787 NON;ARTIFACT;(old: 8, diff: 11, new: 11);(old: 9, diff: 12, new: 12);;code2 -v 840 REM;IF;(old: 9, diff: 12, new: -1);(old: 13, diff: 17, new: -1);DEFINED___UCLIBC__ & REMOVED_FEATURE & lol;# if defined __UCLIBC__ && ( \; REMOVED_FEATURE \; ) && lol +v 840 REM;IF;(old: 9, diff: 12, new: -1);(old: 13, diff: 17, new: -1);defined(__UCLIBC__) & REMOVED_FEATURE & lol;# if defined __UCLIBC__ && ( \; REMOVED_FEATURE \; ) && lol v 1107 NON;ARTIFACT;(old: 12, diff: 16, new: 15);(old: 13, diff: 17, new: 16);; baz(); v 1235 NON;ARTIFACT;(old: 14, diff: 18, new: 17);(old: 15, diff: 19, new: 18);;code3 v 1288 REM;IF;(old: 15, diff: 19, new: -1);(old: 18, diff: 24, new: -1);A & REMOVED_FEATURE;# if A && ( \; REMOVED_FEATURE) @@ -14,10 +14,10 @@ v 1683 NON;ARTIFACT;(old: 19, diff: 25, new: 23);(old: 20, diff: 26, new: 24);;c v 1736 REM;IF;(old: 20, diff: 26, new: -1);(old: 23, diff: 30, new: -1);X & Y | W;#if (X && Y) \; || W v 1939 NON;ARTIFACT;(old: 22, diff: 29, new: 27);(old: 23, diff: 30, new: 28);; dog(); v 2067 NON;ARTIFACT;(old: 24, diff: 31, new: 29);(old: 25, diff: 32, new: 30);;code5 -v 512 ADD;IF;(old: -1, diff: 7, new: 7);(old: -1, diff: 10, new: 10);ENABLE_FEATURE_LESS_DASHCMD & ENABLE_FEATURE_LESS_LINENUMS | DEFINED___LB____NEWLINE____RB__;#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \; || defined(__NEWLINE__) -v 832 ADD;IF;(old: -1, diff: 12, new: 12);(old: -1, diff: 17, new: 16);DEFINED___UCLIBC__ & ADDED_FEATURE & lol;# if defined __UCLIBC__ && ( \; ADDED_FEATURE \; ) && lol +v 512 ADD;IF;(old: -1, diff: 7, new: 7);(old: -1, diff: 10, new: 10);ENABLE_FEATURE_LESS_DASHCMD & ENABLE_FEATURE_LESS_LINENUMS | defined(__NEWLINE__);#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \; || defined(__NEWLINE__) +v 832 ADD;IF;(old: -1, diff: 12, new: 12);(old: -1, diff: 17, new: 16);defined(__UCLIBC__) & ADDED_FEATURE & lol;# if defined __UCLIBC__ && ( \; ADDED_FEATURE \; ) && lol v 1280 ADD;IF;(old: -1, diff: 19, new: 18);(old: -1, diff: 24, new: 22);A & (ADDED_FEATURE1 | ADDED_FEATURE2) & lol;# if A && ( \; ADDED_FEATURE1 || ADDED_FEATURE2 \; ) && lol -v 1728 ADD;IF;(old: -1, diff: 26, new: 24);(old: -1, diff: 30, new: 28);X & Y | DEFINED___LB__Z__RB__ | W;#if (X && Y) \; || defined(Z) \; || W +v 1728 ADD;IF;(old: -1, diff: 26, new: 24);(old: -1, diff: 30, new: 28);X & Y | defined(Z) | W;#if (X && Y) \; || defined(Z) \; || W e 147 16 ba;0,0 e 208 16 ba;1,1 e 339 208 ba;0,0 diff --git a/src/test/resources/serialize/expected.tex b/src/test/resources/serialize/expected.tex index e4bdc0aab..5b0722ffc 100644 --- a/src/test/resources/serialize/expected.tex +++ b/src/test/resources/serialize/expected.tex @@ -1,35 +1,35 @@ \begin{tikzpicture} - \coordinate (144) at (1.3,0.25); - \coordinate (592) at (1.3,1.25); - \coordinate (8275) at (0.39719,3.25); - \coordinate (899) at (1.8139,2.25); - \coordinate (3200) at (2.8139,2.25); - \coordinate (3283) at (2.8139,3.25); - \coordinate (8192) at (0.78608,2.25); + \coordinate (144) at (1.3,0.25); + \coordinate (592) at (1.3,1.25); + \coordinate (8275) at (0.39719,3.25); + \coordinate (899) at (1.8139,2.25); + \coordinate (3200) at (2.8139,2.25); + \coordinate (3283) at (2.8139,3.25); + \coordinate (8192) at (0.78608,2.25); - \node[annotation, non] (node_144) at (144) {}; - \node[annotation, non] (node_592) at (592) {}; - \node[artifact, non] (node_8275) at (8275) {}; - \node[artifact, add] (node_899) at (899) {}; - \node[annotation, add] (node_3200) at (3200) {}; - \node[artifact, non] (node_3283) at (3283) {}; - \node[annotation, add] (node_8192) at (8192) {}; + \node[annotation, non] (node_144) at (144) {}; + \node[annotation, non] (node_592) at (592) {}; + \node[artifact, non] (node_8275) at (8275) {}; + \node[artifact, add] (node_899) at (899) {}; + \node[annotation, add] (node_3200) at (3200) {}; + \node[artifact, non] (node_3283) at (3283) {}; + \node[annotation, add] (node_8192) at (8192) {}; - \draw[vtdarrow] - (node_592) edge[always] (node_144) - (node_8275) edge[before] (node_592) - (node_8275) edge[after] (node_8192) - (node_899) edge[after] (node_592) - (node_3200) edge[after] (node_592) - (node_3283) edge[after] (node_3200) - (node_8192) edge[after] (node_592) + \draw[vtdarrow] + (node_592) edge[always] (node_144) + (node_8275) edge[before] (node_592) + (node_8275) edge[after] (node_8192) + (node_899) edge[after] (node_592) + (node_3200) edge[after] (node_592) + (node_3283) edge[after] (node_3200) + (node_8192) edge[after] (node_592) - ; - \node[textbox] at (144) {\verb|1|}; - \node[textbox] at (592) {\verb|8|}; - \node[textbox] at (8275) {\verb|128|}; - \node[textbox] at (899) {\verb|13|}; - \node[textbox] at (3200) {\verb|49|}; - \node[textbox] at (3283) {\verb|50|}; - \node[textbox] at (8192) {\verb|127|}; + ; + \node[textbox] at (144) {\verb|1|}; + \node[textbox] at (592) {\verb|8|}; + \node[textbox] at (8275) {\verb|128|}; + \node[textbox] at (899) {\verb|13|}; + \node[textbox] at (3200) {\verb|49|}; + \node[textbox] at (3283) {\verb|50|}; + \node[textbox] at (8192) {\verb|127|}; \end{tikzpicture} diff --git a/src/test/resources/tree-diffing/01_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/01_gumtree-hybrid_expected.lg index 66d80bd77..7ae3cefa8 100644 --- a/src/test/resources/tree-diffing/01_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/01_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 1, new: 3);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 1, new: 3);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line e 144 80 ba;0,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/01_gumtree_expected.lg b/src/test/resources/tree-diffing/01_gumtree_expected.lg index 66d80bd77..7ae3cefa8 100644 --- a/src/test/resources/tree-diffing/01_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/01_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 1, new: 3);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 3, diff: 1, new: 3);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line e 144 80 ba;0,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/02_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/02_gumtree-hybrid_expected.lg index 3af3f0397..0650f459f 100644 --- a/src/test/resources/tree-diffing/02_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/02_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 1, new: 3);A;#ifdef A +v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 1, new: 3);defined(A);#ifdef A v 195 ADD;ARTIFACT;(old: -1, diff: 2, new: 2);(old: -1, diff: 2, new: 3);;Line e 128 80 a;-1,0 e 195 128 a;-1,0 diff --git a/src/test/resources/tree-diffing/02_gumtree_expected.lg b/src/test/resources/tree-diffing/02_gumtree_expected.lg index 3af3f0397..0650f459f 100644 --- a/src/test/resources/tree-diffing/02_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/02_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 1, new: 3);A;#ifdef A +v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 1, new: 3);defined(A);#ifdef A v 195 ADD;ARTIFACT;(old: -1, diff: 2, new: 2);(old: -1, diff: 2, new: 3);;Line e 128 80 a;-1,0 e 195 128 a;-1,0 diff --git a/src/test/resources/tree-diffing/03_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/03_gumtree-hybrid_expected.lg index ddd1454a7..a76efb445 100644 --- a/src/test/resources/tree-diffing/03_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/03_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);defined(A);#ifdef A v 203 REM;ARTIFACT;(old: 2, diff: 2, new: -1);(old: 3, diff: 2, new: -1);;Line e 136 80 b;0,-1 e 203 136 b;0,-1 diff --git a/src/test/resources/tree-diffing/03_gumtree_expected.lg b/src/test/resources/tree-diffing/03_gumtree_expected.lg index ddd1454a7..a76efb445 100644 --- a/src/test/resources/tree-diffing/03_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/03_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);defined(A);#ifdef A v 203 REM;ARTIFACT;(old: 2, diff: 2, new: -1);(old: 3, diff: 2, new: -1);;Line e 136 80 b;0,-1 e 203 136 b;0,-1 diff --git a/src/test/resources/tree-diffing/04_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/04_gumtree-hybrid_expected.lg index e069a93e2..840767659 100644 --- a/src/test/resources/tree-diffing/04_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/04_gumtree-hybrid_expected.lg @@ -1,6 +1,6 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True v 147 NON;ARTIFACT;(old: 1, diff: 1, new: 1);(old: 2, diff: 1, new: 2);;Line -v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 2, new: 3);A;#ifdef A +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 2, new: 3);defined(A);#ifdef A e 147 80 b;0,-1 e 147 192 a;-1,0 e 192 80 a;-1,0 diff --git a/src/test/resources/tree-diffing/04_gumtree_expected.lg b/src/test/resources/tree-diffing/04_gumtree_expected.lg index e069a93e2..840767659 100644 --- a/src/test/resources/tree-diffing/04_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/04_gumtree_expected.lg @@ -1,6 +1,6 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True v 147 NON;ARTIFACT;(old: 1, diff: 1, new: 1);(old: 2, diff: 1, new: 2);;Line -v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 2, new: 3);A;#ifdef A +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 2, new: 3);defined(A);#ifdef A e 147 80 b;0,-1 e 147 192 a;-1,0 e 192 80 a;-1,0 diff --git a/src/test/resources/tree-diffing/05_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/05_gumtree-hybrid_expected.lg index 0fd90b730..1b1f5340d 100644 --- a/src/test/resources/tree-diffing/05_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/05_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line e 136 80 b;0,-1 e 211 136 b;0,-1 diff --git a/src/test/resources/tree-diffing/05_gumtree_expected.lg b/src/test/resources/tree-diffing/05_gumtree_expected.lg index 0fd90b730..1b1f5340d 100644 --- a/src/test/resources/tree-diffing/05_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/05_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);A;#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 1, new: -1);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line e 136 80 b;0,-1 e 211 136 b;0,-1 diff --git a/src/test/resources/tree-diffing/06_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/06_gumtree-hybrid_expected.lg index c742e642d..ed9ff5794 100644 --- a/src/test/resources/tree-diffing/06_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/06_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line A v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line B v 322 ADD;ELIF;(old: -1, diff: 4, new: 3);(old: -1, diff: 4, new: 5);B;#elif B diff --git a/src/test/resources/tree-diffing/06_gumtree_expected.lg b/src/test/resources/tree-diffing/06_gumtree_expected.lg index c742e642d..ed9ff5794 100644 --- a/src/test/resources/tree-diffing/06_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/06_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line A v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line B v 322 ADD;ELIF;(old: -1, diff: 4, new: 3);(old: -1, diff: 4, new: 5);B;#elif B diff --git a/src/test/resources/tree-diffing/07_change-distiller-theta_expected.lg b/src/test/resources/tree-diffing/07_change-distiller-theta_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_change-distiller-theta_expected.lg +++ b/src/test/resources/tree-diffing/07_change-distiller-theta_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_change-distiller_expected.lg b/src/test/resources/tree-diffing/07_change-distiller_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_change-distiller_expected.lg +++ b/src/test/resources/tree-diffing/07_change-distiller_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_classic-gumtree-theta_expected.lg b/src/test/resources/tree-diffing/07_classic-gumtree-theta_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_classic-gumtree-theta_expected.lg +++ b/src/test/resources/tree-diffing/07_classic-gumtree-theta_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-hybrid-id_expected.lg b/src/test/resources/tree-diffing/07_gumtree-hybrid-id_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-hybrid-id_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-hybrid-id_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/07_gumtree-hybrid_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-hybrid_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-partition-id_expected.lg b/src/test/resources/tree-diffing/07_gumtree-partition-id_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-partition-id_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-partition-id_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-simple-id-theta_expected.lg b/src/test/resources/tree-diffing/07_gumtree-simple-id-theta_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-simple-id-theta_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-simple-id-theta_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-simple-id_expected.lg b/src/test/resources/tree-diffing/07_gumtree-simple-id_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-simple-id_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-simple-id_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree-simple_expected.lg b/src/test/resources/tree-diffing/07_gumtree-simple_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree-simple_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree-simple_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_gumtree_expected.lg b/src/test/resources/tree-diffing/07_gumtree_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/07_gumtree_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_longestCommonSequence_expected.lg b/src/test/resources/tree-diffing/07_longestCommonSequence_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_longestCommonSequence_expected.lg +++ b/src/test/resources/tree-diffing/07_longestCommonSequence_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_theta_expected.lg b/src/test/resources/tree-diffing/07_theta_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_theta_expected.lg +++ b/src/test/resources/tree-diffing/07_theta_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/07_xy_expected.lg b/src/test/resources/tree-diffing/07_xy_expected.lg index 7f71a3cb6..aa4fcbe2b 100644 --- a/src/test/resources/tree-diffing/07_xy_expected.lg +++ b/src/test/resources/tree-diffing/07_xy_expected.lg @@ -1,9 +1,9 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);CONFIG_A;#ifdef CONFIG_A -v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);CONFIG_B;#ifdef CONFIG_B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(CONFIG_A);#ifdef CONFIG_A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(CONFIG_B);#ifdef CONFIG_B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);CONFIG_B;#ifdef CONFIG_B -v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);CONFIG_A;#ifdef CONFIG_A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(CONFIG_B);#ifdef CONFIG_B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(CONFIG_A);#ifdef CONFIG_A e 136 80 b;0,-1 e 200 136 b;0,-1 e 275 200 b;0,-1 diff --git a/src/test/resources/tree-diffing/08_change-distiller-theta_expected.lg b/src/test/resources/tree-diffing/08_change-distiller-theta_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_change-distiller-theta_expected.lg +++ b/src/test/resources/tree-diffing/08_change-distiller-theta_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_change-distiller_expected.lg b/src/test/resources/tree-diffing/08_change-distiller_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_change-distiller_expected.lg +++ b/src/test/resources/tree-diffing/08_change-distiller_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_classic-gumtree-theta_expected.lg b/src/test/resources/tree-diffing/08_classic-gumtree-theta_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_classic-gumtree-theta_expected.lg +++ b/src/test/resources/tree-diffing/08_classic-gumtree-theta_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-hybrid-id_expected.lg b/src/test/resources/tree-diffing/08_gumtree-hybrid-id_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-hybrid-id_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-hybrid-id_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/08_gumtree-hybrid_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-hybrid_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-partition-id_expected.lg b/src/test/resources/tree-diffing/08_gumtree-partition-id_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-partition-id_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-partition-id_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-simple-id-theta_expected.lg b/src/test/resources/tree-diffing/08_gumtree-simple-id-theta_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-simple-id-theta_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-simple-id-theta_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-simple-id_expected.lg b/src/test/resources/tree-diffing/08_gumtree-simple-id_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-simple-id_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-simple-id_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree-simple_expected.lg b/src/test/resources/tree-diffing/08_gumtree-simple_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree-simple_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree-simple_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_gumtree_expected.lg b/src/test/resources/tree-diffing/08_gumtree_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/08_gumtree_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_longestCommonSequence_expected.lg b/src/test/resources/tree-diffing/08_longestCommonSequence_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_longestCommonSequence_expected.lg +++ b/src/test/resources/tree-diffing/08_longestCommonSequence_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_theta_expected.lg b/src/test/resources/tree-diffing/08_theta_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_theta_expected.lg +++ b/src/test/resources/tree-diffing/08_theta_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/08_xy_expected.lg b/src/test/resources/tree-diffing/08_xy_expected.lg index 0dd6a03d2..3303e4528 100644 --- a/src/test/resources/tree-diffing/08_xy_expected.lg +++ b/src/test/resources/tree-diffing/08_xy_expected.lg @@ -1,11 +1,12 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);A;#ifdef A -v 208 NON;IF;(old: 2, diff: 2, new: 2);(old: 4, diff: 2, new: 4);B;#ifdef B +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 5, diff: 1, new: -1);defined(A);#ifdef A +v 200 REM;IF;(old: 2, diff: 2, new: -1);(old: 4, diff: 2, new: -1);defined(B);#ifdef B v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line -v 320 ADD;IF;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 4);A;#ifdef A +v 320 ADD;IF;(old: -1, diff: 4, new: 1);(old: -1, diff: 4, new: 5);defined(B);#ifdef B +v 384 ADD;IF;(old: -1, diff: 5, new: 2);(old: -1, diff: 5, new: 4);defined(A);#ifdef A e 136 80 b;0,-1 -e 208 136 b;0,-1 -e 208 80 a;-1,0 -e 275 208 b;0,-1 -e 275 320 a;-1,0 -e 320 208 a;-1,0 +e 200 136 b;0,-1 +e 275 200 b;0,-1 +e 275 384 a;-1,0 +e 320 80 a;-1,0 +e 384 320 a;-1,0 diff --git a/src/test/resources/tree-diffing/09_change-distiller-theta_expected.lg b/src/test/resources/tree-diffing/09_change-distiller-theta_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_change-distiller-theta_expected.lg +++ b/src/test/resources/tree-diffing/09_change-distiller-theta_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_change-distiller_expected.lg b/src/test/resources/tree-diffing/09_change-distiller_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_change-distiller_expected.lg +++ b/src/test/resources/tree-diffing/09_change-distiller_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_classic-gumtree-theta_expected.lg b/src/test/resources/tree-diffing/09_classic-gumtree-theta_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_classic-gumtree-theta_expected.lg +++ b/src/test/resources/tree-diffing/09_classic-gumtree-theta_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-hybrid-id_expected.lg b/src/test/resources/tree-diffing/09_gumtree-hybrid-id_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-hybrid-id_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-hybrid-id_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/09_gumtree-hybrid_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-hybrid_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-partition-id_expected.lg b/src/test/resources/tree-diffing/09_gumtree-partition-id_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-partition-id_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-partition-id_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-simple-id-theta_expected.lg b/src/test/resources/tree-diffing/09_gumtree-simple-id-theta_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-simple-id-theta_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-simple-id-theta_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-simple-id_expected.lg b/src/test/resources/tree-diffing/09_gumtree-simple-id_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-simple-id_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-simple-id_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree-simple_expected.lg b/src/test/resources/tree-diffing/09_gumtree-simple_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree-simple_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree-simple_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_gumtree_expected.lg b/src/test/resources/tree-diffing/09_gumtree_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/09_gumtree_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_longestCommonSequence_expected.lg b/src/test/resources/tree-diffing/09_longestCommonSequence_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_longestCommonSequence_expected.lg +++ b/src/test/resources/tree-diffing/09_longestCommonSequence_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_theta_expected.lg b/src/test/resources/tree-diffing/09_theta_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_theta_expected.lg +++ b/src/test/resources/tree-diffing/09_theta_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/09_xy_expected.lg b/src/test/resources/tree-diffing/09_xy_expected.lg index 83e705cc7..30efe1ca9 100644 --- a/src/test/resources/tree-diffing/09_xy_expected.lg +++ b/src/test/resources/tree-diffing/09_xy_expected.lg @@ -1,13 +1,13 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 10, diff: 1, new: 10);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;Line 1 v 275 NON;ARTIFACT;(old: 3, diff: 3, new: 3);(old: 4, diff: 3, new: 4);;Line 2 v 339 NON;ARTIFACT;(old: 4, diff: 4, new: 4);(old: 5, diff: 4, new: 5);;Line 3 -v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);B;#ifdef B +v 392 REM;IF;(old: 5, diff: 5, new: -1);(old: 9, diff: 5, new: -1);defined(B);#ifdef B v 467 NON;ARTIFACT;(old: 6, diff: 6, new: 6);(old: 7, diff: 6, new: 7);;Line 4 v 531 NON;ARTIFACT;(old: 7, diff: 7, new: 7);(old: 8, diff: 7, new: 8);;Line 5 v 595 NON;ARTIFACT;(old: 8, diff: 8, new: 8);(old: 9, diff: 8, new: 9);;Line 6 -v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);B;#ifdef B +v 640 ADD;IF;(old: -1, diff: 9, new: 1);(old: -1, diff: 9, new: 10);defined(B);#ifdef B e 144 80 b;0,-1 e 144 640 a;-1,0 e 211 144 ba;0,0 diff --git a/src/test/resources/tree-diffing/10_change-distiller_expected.lg b/src/test/resources/tree-diffing/10_change-distiller_expected.lg index e6eea8ff1..bb645cdad 100644 --- a/src/test/resources/tree-diffing/10_change-distiller_expected.lg +++ b/src/test/resources/tree-diffing/10_change-distiller_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;456 v 267 REM;ARTIFACT;(old: 3, diff: 3, new: -1);(old: 4, diff: 3, new: -1);;123 v 323 ADD;ARTIFACT;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 3);;123 diff --git a/src/test/resources/tree-diffing/10_gumtree-hybrid_expected.lg b/src/test/resources/tree-diffing/10_gumtree-hybrid_expected.lg index e6eea8ff1..bb645cdad 100644 --- a/src/test/resources/tree-diffing/10_gumtree-hybrid_expected.lg +++ b/src/test/resources/tree-diffing/10_gumtree-hybrid_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;456 v 267 REM;ARTIFACT;(old: 3, diff: 3, new: -1);(old: 4, diff: 3, new: -1);;123 v 323 ADD;ARTIFACT;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 3);;123 diff --git a/src/test/resources/tree-diffing/10_gumtree_expected.lg b/src/test/resources/tree-diffing/10_gumtree_expected.lg index e6eea8ff1..bb645cdad 100644 --- a/src/test/resources/tree-diffing/10_gumtree_expected.lg +++ b/src/test/resources/tree-diffing/10_gumtree_expected.lg @@ -1,5 +1,5 @@ v 80 NON;IF;(old: -1, diff: 0, new: -1);(old: -1, diff: 0, new: -1);True -v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);A;#ifdef A +v 144 NON;IF;(old: 1, diff: 1, new: 1);(old: 4, diff: 1, new: 4);defined(A);#ifdef A v 211 NON;ARTIFACT;(old: 2, diff: 2, new: 2);(old: 3, diff: 2, new: 3);;456 v 267 REM;ARTIFACT;(old: 3, diff: 3, new: -1);(old: 4, diff: 3, new: -1);;123 v 323 ADD;ARTIFACT;(old: -1, diff: 4, new: 2);(old: -1, diff: 4, new: 3);;123