diff --git a/docs/datasets/eugen-bachelor-thesis.md b/docs/datasets/eugen-bachelor-thesis.md new file mode 100644 index 000000000..52f415472 --- /dev/null +++ b/docs/datasets/eugen-bachelor-thesis.md @@ -0,0 +1,5 @@ +Project name | Domain | Source code available (\*\*y\*\*es/\*\*n\*\*o)? | Is it a git repository (\*\*y\*\*es/\*\*n\*\*o)? | Repository URL | Clone URL | Estimated number of commits +-------------------|-------------------------|-------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------|----------------------------------------------------|--------------------------------- +berkeley-db-libdb | database system | y | y | https://github.com/berkeleydb/libdb | https://github.com/berkeleydb/libdb.git | 7 +sylpheed | e-mail client | y | y | https://github.com/jan0sch/sylpheed | https://github.com/jan0sch/sylpheed.git | 2,682 +vim | text editor | y | y | https://github.com/vim/vim | https://github.com/vim/vim.git | 17,109 diff --git a/replication/thesis-es/Dockerfile b/replication/thesis-es/Dockerfile new file mode 100644 index 000000000..4520b8042 --- /dev/null +++ b/replication/thesis-es/Dockerfile @@ -0,0 +1,57 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:3.15 +# PACKAGE STAGE + +# Prepare the compile environment. JDK is automatically installed +RUN apk add maven + +# Create and navigate to a working directory +WORKDIR /home/user + +COPY local-maven-repo ./local-maven-repo + +# Copy the source code +COPY src ./src +# Copy the pom.xml if Maven is used +COPY pom.xml . +# Execute the maven package process +RUN mvn package || exit + +FROM alpine:3.15 + +# Create a user +RUN adduser --disabled-password --home /home/sherlock --gecos '' sherlock + +RUN apk add --no-cache --upgrade bash +RUN apk add --update openjdk17 + +# Change into the home directory +WORKDIR /home/sherlock + +# Copy the compiled JAR file from the first stage into the second stage +# Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH +WORKDIR /home/sherlock/holmes +COPY --from=0 /home/user/target/diffdetective-*-jar-with-dependencies.jar ./DiffDetective.jar +WORKDIR /home/sherlock +RUN mkdir results + +# Copy the setup +COPY docs holmes/docs + +# Copy the docker resources +COPY docker/* ./ +COPY replication/thesis-es/docker/* ./ +RUN mkdir DiffDetectiveMining + +# Adjust permissions +RUN chown sherlock:sherlock /home/sherlock -R +RUN chmod +x execute.sh +RUN chmod +x entrypoint.sh +RUN chmod +x fix-perms.sh + +# Set the entrypoint +ENTRYPOINT ["./entrypoint.sh", "./execute.sh"] + +# Set the user +USER sherlock diff --git a/replication/thesis-es/README.md b/replication/thesis-es/README.md new file mode 100644 index 000000000..dbac2923f --- /dev/null +++ b/replication/thesis-es/README.md @@ -0,0 +1,49 @@ +![Maven](https://github.com/VariantSync/DiffDetective/actions/workflows/maven.yml/badge.svg) +[![Documentation](https://img.shields.io/badge/Documentation-Read-purple)][documentation] +[![Install](https://img.shields.io/badge/Install-Instructions-blue)](INSTALL.md) +[![GitHubPages](https://img.shields.io/badge/GitHub%20Pages-online-blue.svg?style=flat)][website] +[![License](https://img.shields.io/badge/License-GNU%20LGPLv3-blue)](../../LICENSE.LGPL3) + +# Unparsing Experiment +This is an experiment for the bachelor thesis by Eugen Shulimov which tests the unparser for variation trees and diffs. + +### Prerequisite +All following commands assume that working directory of your terminal is the `thesis-es` directory. Please switch directories, if this is not the case: +```shell +cd DiffDetective/replication/thesis-es +``` + +### Build the Docker container +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: +`.\build.bat` +#### Linux/Mac (bash): +`./build.sh` + +### Start the experiment +To execute the experiment you can run the `execute`script corresponding to your operating system. + +#### Windows: +`.\execute.bat +#### Linux/Mac (bash): +`./execute.sh + +> If you want to stop the execution, you can call the provided script for stopping the container in a separate terminal. +> When restarted, the execution will continue processing by restarting at the last unfinished repository. +> #### Windows: +> `.\stop-execution.bat` +> #### Linux/Mac (bash): +> `./stop-execution.sh` + +You might see warnings or errors reported from SLF4J like `Failed to load class "org.slf4j.impl.StaticLoggerBinder"` which you can safely ignore. + +### View the results in the [results][resultsdir] directory +All raw results are stored in the [results][resultsdir] directory. + +[documentation]: https://variantsync.github.io/DiffDetective/docs/javadoc/ +[website]: https://variantsync.github.io/DiffDetective/ + +[resultsdir]: results diff --git a/replication/thesis-es/build.bat b/replication/thesis-es/build.bat new file mode 100644 index 000000000..73bc8d9ab --- /dev/null +++ b/replication/thesis-es/build.bat @@ -0,0 +1,19 @@ +@echo off +setlocal + +set "targetSubPath=thesis-es" + +rem Get the current directory +for %%A in ("%CD%") do set "currentDir=%%~nxA" + +rem Check if the current directory ends with the target sub-path + +if "%currentDir:~-9%"=="%targetSubPath%" ( + cd ..\.. + docker build -t diff-detective-unparse -f replication\thesis-es\Dockerfile . + @pause +) else ( + echo error: the script must be run from inside the thesis-es directory, i.e., DiffDetective\replication\%targetSubPath% +) +endlocal + diff --git a/replication/thesis-es/build.sh b/replication/thesis-es/build.sh new file mode 100644 index 000000000..19bb7b741 --- /dev/null +++ b/replication/thesis-es/build.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# We have to switch to the root directory of the project and build the Docker image from there, +# because Docker only allows access to the files in the current file system subtree (i.e., no access to ancestors). +# We have to do this to get access to 'src', 'docker', 'local-maven-repo', etc. +# For resiliency against different working directories during execution of this +# script we calculate the correct path using the special bash variable +# BASH_SOURCE. +cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit + +docker build -t diff-detective-unparse -f replication/thesis-es/Dockerfile . diff --git a/replication/thesis-es/docker/DOCKER.md b/replication/thesis-es/docker/DOCKER.md new file mode 100644 index 000000000..966ebc0d5 --- /dev/null +++ b/replication/thesis-es/docker/DOCKER.md @@ -0,0 +1,6 @@ +# Docker Files + +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 diff --git a/replication/thesis-es/docker/execute.sh b/replication/thesis-es/docker/execute.sh new file mode 100644 index 000000000..a4e2ce4dc --- /dev/null +++ b/replication/thesis-es/docker/execute.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +cd /home/sherlock/holmes || exit + +echo "Running the experiment." +java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.thesis-es.Main + +echo "Collecting results." +cp -r results/* ../results/ +echo "The results are located in the 'results' directory." + diff --git a/replication/thesis-es/execute.bat b/replication/thesis-es/execute.bat new file mode 100644 index 000000000..2467d7379 --- /dev/null +++ b/replication/thesis-es/execute.bat @@ -0,0 +1,15 @@ +@echo off +setlocal + +set "targetSubPath=thesis-es" + +rem Get the current directory +for %%A in ("%CD%") do set "currentDir=%%~nxA" + +rem Check if the current directory ends with the target sub-path +if "%currentDir:~-9%"=="%targetSubPath%" ( +docker run --rm -v "%cd%\results":"/home/sherlock/results" diff-detective-unparse %* +) else ( + echo error: the script must be run from inside the thesis-es directory, i.e., DiffDetective\replication\%targetSubPath% +) +endlocal diff --git a/replication/thesis-es/execute.sh b/replication/thesis-es/execute.sh new file mode 100644 index 000000000..c0b2fba9b --- /dev/null +++ b/replication/thesis-es/execute.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Assure that the script is only called from the folder cotaining this script +cd "$(dirname "${BASH_SOURCE[0]}")" || exit + +if [[ $# -gt 0 ]]; then +echo "Executing $1" +fi +docker run --rm -v "$(pwd)/results":"/home/sherlock/results" diff-detective-unparse "$@" diff --git a/replication/thesis-es/results/.gitignore b/replication/thesis-es/results/.gitignore new file mode 100644 index 000000000..86d0cb272 --- /dev/null +++ b/replication/thesis-es/results/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/replication/thesis-es/stop-execution.bat b/replication/thesis-es/stop-execution.bat new file mode 100644 index 000000000..009494d8f --- /dev/null +++ b/replication/thesis-es/stop-execution.bat @@ -0,0 +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-unparse"') DO docker stop %%i +@echo "...done." \ No newline at end of file diff --git a/replication/thesis-es/stop-execution.sh b/replication/thesis-es/stop-execution.sh new file mode 100644 index 000000000..0ebe6a83a --- /dev/null +++ b/replication/thesis-es/stop-execution.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +echo "Stopping Docker container. This will take a moment..." +docker stop "$(docker ps -a -q --filter "ancestor=diff-detective-unparse")" +echo "...done." diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/Main.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/Main.java new file mode 100644 index 000000000..089b21885 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/Main.java @@ -0,0 +1,185 @@ +package org.variantsync.diffdetective.experiments.thesis_es; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import org.variantsync.diffdetective.AnalysisRunner; +import org.variantsync.diffdetective.analysis.Analysis; +import org.variantsync.diffdetective.datasets.DatasetDescription; +import org.variantsync.diffdetective.datasets.DefaultDatasets; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions.DiffStoragePolicy; +import org.variantsync.diffdetective.datasets.Repository; +import org.variantsync.diffdetective.diff.git.DiffFilter; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; + +public class Main { + + public static void main(String[] args) throws IOException { + startAnalysis(); + evaluationAnalysis(Path.of("docs", "datasets", "eugen-bachelor-thesis.md")); + + } + + private static void startAnalysis() throws IOException { + final AnalysisRunner.Options analysisOptions = new AnalysisRunner.Options( + Paths.get("..", "DiffDetectiveReplicationDatasets"), + Paths.get("results", "thesis_es"), + Paths.get("docs", "datasets", "eugen-bachelor-thesis.md"), + repo -> new PatchDiffParseOptions( + DiffStoragePolicy.REMEMBER_FULL_DIFF, + VariationDiffParseOptions.Default), + repo -> new DiffFilter.Builder().allowMerge(true) + .allowedFileExtensions("c", "cpp").build(), + true, + false); + + AnalysisRunner.run(analysisOptions, extractionRunner()); + } + + protected static BiConsumer extractionRunner() { + return (repo, repoOutputDir) -> { + + final BiFunction AnalysisFactory = (r, out) -> new Analysis("Thesis Eugen Shulimov", + List.of(new UnparseAnalysis()), r, out); + + Analysis.forEachCommit(() -> AnalysisFactory.apply(repo, repoOutputDir)); + + }; + } + + /** + * Verarbeitet die Ergebnisse der Analyse um aus einzelnen + * Angaben eine gesamt Übersicht zu bekommen + * + * @param path Pfad zu der markdown datei, aus der die repositories für die + * Analyse stammen + * @throws IOException + */ + private static void evaluationAnalysis(Path path) throws IOException { + int count = 0; + int[] diffTest = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int[] diffSemEqTest = { 0, 0, 0, 0, }; + int[] treeTest = { 0, 0, 0, 0, 0, 0, 0, 0 }; + final List datasets = DefaultDatasets.loadDatasets(path); + + for (DatasetDescription description : datasets) { + Stream files = Files + .list(Path.of("results", "thesis_es", description.name())) + .filter(filename -> filename.getFileName().toString().endsWith(".thesis_es.csv")); + for (Path tempPath : files.toList()) { + String[] splitFileData = Files.readString(tempPath).split("\n"); + for (int j = 1; j < splitFileData.length; j++) { + String[] splitLineData = splitFileData[j].split(";"); + for (int i = 0; i < splitLineData.length; i++) { + splitLineData[i] = parseNumberStringToIntWithLengthGreaterOne(splitLineData[i]); + } + count = count + 1; + diffTest[0] = diffTest[0] + Integer.parseInt(splitLineData[8]); + diffTest[1] = diffTest[1] + Integer.parseInt(splitLineData[9]); + diffTest[2] = diffTest[2] + Integer.parseInt(splitLineData[10]); + diffTest[3] = diffTest[3] + Integer.parseInt(splitLineData[11]); + diffTest[4] = diffTest[4] + Integer.parseInt(splitLineData[12]); + diffTest[5] = diffTest[5] + Integer.parseInt(splitLineData[13]); + diffTest[6] = diffTest[6] + Integer.parseInt(splitLineData[14]); + diffTest[7] = diffTest[7] + Integer.parseInt(splitLineData[15]); + diffSemEqTest[0] = diffSemEqTest[0] + Integer.parseInt(splitLineData[16]); + diffSemEqTest[1] = diffSemEqTest[1] + Integer.parseInt(splitLineData[17]); + diffSemEqTest[2] = diffSemEqTest[2] + Integer.parseInt(splitLineData[18]); + diffSemEqTest[3] = diffSemEqTest[3] + Integer.parseInt(splitLineData[19]); + treeTest[0] = treeTest[0] + Integer.parseInt(splitLineData[20]); + treeTest[1] = treeTest[1] + Integer.parseInt(splitLineData[21]); + treeTest[2] = treeTest[2] + Integer.parseInt(splitLineData[22]); + treeTest[3] = treeTest[3] + Integer.parseInt(splitLineData[23]); + treeTest[4] = treeTest[4] + Integer.parseInt(splitLineData[24]); + treeTest[5] = treeTest[5] + Integer.parseInt(splitLineData[25]); + treeTest[6] = treeTest[6] + Integer.parseInt(splitLineData[26]); + treeTest[7] = treeTest[7] + Integer.parseInt(splitLineData[27]); + treeTest[0] = treeTest[0] + Integer.parseInt(splitLineData[28]); + treeTest[1] = treeTest[1] + Integer.parseInt(splitLineData[29]); + treeTest[2] = treeTest[2] + Integer.parseInt(splitLineData[30]); + treeTest[3] = treeTest[3] + Integer.parseInt(splitLineData[31]); + treeTest[4] = treeTest[4] + Integer.parseInt(splitLineData[32]); + treeTest[5] = treeTest[5] + Integer.parseInt(splitLineData[33]); + treeTest[6] = treeTest[6] + Integer.parseInt(splitLineData[34]); + treeTest[7] = treeTest[7] + Integer.parseInt(splitLineData[35]); + + } + } + } + List result = new ArrayList<>(); + result.add("Anzahl geprüfter Diffs : " + count + "\n"); + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine0 und EmptyLine0 : " + diffTest[0] + "\n"); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine0 und EmptyLine0 : " + diffTest[4] + + "\n"); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine0 und EmptyLine0 : " + diffSemEqTest[0] + "\n"); + result.add("Anzahl von Diffs mit MultiLine0 und EmptyLine0, welche keine Korrektheitskriterium erfühlt haben : " + + (count - diffSemEqTest[0]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine1 und EmptyLine0 : " + diffTest[1] + "\n"); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine1 und EmptyLine0 : " + diffTest[5] + + "\n"); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine1 und EmptyLine0 : " + diffSemEqTest[1] + "\n"); + result.add("Anzahl von Diffs mit MultiLine1 und EmptyLine0, welche keine Korrektheitskriterium erfühlt haben : " + + (count - diffSemEqTest[1]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine0 und EmptyLine1 : " + diffTest[2] + "\n"); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine0 und EmptyLine1 : " + diffTest[6] + + "\n"); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine0 und EmptyLine1 : " + diffSemEqTest[2] + "\n"); + result.add("Anzahl von Diffs mit MultiLine0 und EmptyLine1, welche keine Korrektheitskriterium erfühlt haben : " + + (count - diffSemEqTest[2]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine1 und EmptyLine1 : " + diffTest[3] + "\n"); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine1 und EmptyLine1 : " + diffTest[7] + + "\n"); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine1 und EmptyLine1 : " + diffSemEqTest[3] + "\n"); + result.add("Anzahl von Diffs mit MultiLine1 und EmptyLine1, welche keine Korrektheitskriterium erfühlt haben : " + + (count - diffSemEqTest[3]) + "\n"); + + result.add("-------------------------------------------------------------------------------------------"); + result.add("Anzahl geprüfter Trees : " + count * 2 + "\n"); + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine0 und EmptyLine0 : " + treeTest[0] + "\n"); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine0 und EmptyLine0 : " + treeTest[4] + + "\n"); + result.add("Anzahl von Trees mit MultiLine0 und EmptyLine0, welche keine Korrektheitskriterium erfühlt haben : " + + (2 * count - treeTest[4]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine1 und EmptyLine0 : " + treeTest[1] + "\n"); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine1 und EmptyLine0 : " + treeTest[5] + + "\n"); + result.add("Anzahl von Trees mit MultiLine1 und EmptyLine0, welche keine Korrektheitskriterium erfühlt haben : " + + (2 * count - treeTest[5]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine0 und EmptyLine1 : " + treeTest[2] + "\n"); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine0 und EmptyLine1 : " + treeTest[6] + + "\n"); + result.add("Anzahl von Trees mit MultiLine0 und EmptyLine1, welche keine Korrektheitskriterium erfühlt haben : " + + (2 * count - treeTest[6]) + "\n"); + + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine1 und EmptyLine1 : " + treeTest[3] + "\n"); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine1 und EmptyLine1 : " + treeTest[7] + + "\n"); + result.add("Anzahl von Trees mit MultiLine1 und EmptyLine1, welche keine Korrektheitskriterium erfühlt haben : " + + (2 * count - treeTest[7]) + "\n"); + + Files.write(Path.of("results", "thesis_es", "resultOfAnalysis.txt"), result); + + } + + private static String parseNumberStringToIntWithLengthGreaterOne(String string) { + string = string.trim(); + for (char c : string.toCharArray()) { + if (Character.isDigit(c)) { + return Character.toString(c); + } + } + return ""; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java new file mode 100644 index 000000000..ab18674fb --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java @@ -0,0 +1,324 @@ +package org.variantsync.diffdetective.experiments.thesis_es; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; +import org.variantsync.diffdetective.analysis.Analysis; +import org.variantsync.diffdetective.diff.git.PatchDiff; +import org.variantsync.diffdetective.util.CSV; +import org.variantsync.diffdetective.util.FileUtils; +import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.StringUtils; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.VariationUnparser; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; + +public class UnparseAnalysis implements Analysis.Hooks { + + public static final String CSV_EXTENSION = ".thesis_es.csv"; + + public static int count = 0; + + private StringBuilder csv; + + // for debugging + private int errorCount = 0; + + @Override + public void initializeResults(Analysis analysis) { + Analysis.Hooks.super.initializeResults(analysis); + + csv = new StringBuilder(); + csv.append(UnparseEvaluation.makeHeader(CSV.DEFAULT_CSV_DELIMITER)).append(StringUtils.LINEBREAK); + + errorCount = 0; + } + + @Override + public boolean analyzeVariationDiff(Analysis analysis) throws Exception { + PatchDiff patch = analysis.getCurrentPatch(); + String textDiff = patch.getDiff(); + try { + BufferedReader in = new BufferedReader(new StringReader(textDiff)); + String line = ""; + StringBuilder tempString = new StringBuilder(); + while ((line = in.readLine()) != null) { + tempString.append(line); + tempString.append("\n"); + } + textDiff = tempString.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + + String codeBefore = VariationUnparser.undiff(patch.getDiff(), Time.BEFORE); + String codeAfter = VariationUnparser.undiff(patch.getDiff(), Time.AFTER); + + boolean[][] diffTestAll = runTestsDiff(textDiff); + boolean[] treeBeforeTest = runTestsTree(codeBefore); + boolean[] treeAfterTest = runTestsTree(codeAfter); + boolean[] dataTests = runDataTest(textDiff, codeBefore, codeAfter); + int error = 1; + // Test das es überhaupt funktioniert + if (!(boolOr(diffTestAll[0]) || boolOr(diffTestAll[1]))) { + error = error * 2; + reportErrorToFile(analysis, "textDiff: \n" + textDiff); + } + if (!boolOr(treeBeforeTest)) { + error = error * 3; + reportErrorToFile(analysis, "treeBefore: \n" + codeBefore); + } + if (!boolOr(treeAfterTest)) { + error = error * 5; + reportErrorToFile(analysis, "treeAfter: \n" + codeAfter); + } + // Testen der implikationen + // für Diff + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine false + if (diffTestAll[0][0] && !diffTestAll[0][4]) { + reportErrorToFile(analysis, "diffTestAll[0][0] && !diffTestAll[0][4] " + textDiff); + } + // syntaktische Gleicheheit ohne Whitespace folgt semantische Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine false + if (diffTestAll[0][4] && !diffTestAll[1][0]) { + reportErrorToFile(analysis, "diffTestAll[0][4] && !diffTestAll[1][0] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine false + if (diffTestAll[0][1] && !diffTestAll[0][5]) { + reportErrorToFile(analysis, "diffTestAll[0][1] && !diffTestAll[0][5] " + textDiff); + } + // syntaktische Gleicheheit ohne Whitespace folgt semantische Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine false + if (diffTestAll[0][5] && !diffTestAll[1][1]) { + reportErrorToFile(analysis, "diffTestAll[0][5] && !diffTestAll[1][1] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine true + if (diffTestAll[0][2] && !diffTestAll[0][6]) { + reportErrorToFile(analysis, "diffTestAll[0][2] && !diffTestAll[0][6] " + textDiff); + } + // syntaktische Gleicheheit ohne Whitespace folgt semantische Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine true + if (diffTestAll[0][6] && !diffTestAll[1][2]) { + reportErrorToFile(analysis, "diffTestAll[0][6] && !diffTestAll[1][2] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine true + if (diffTestAll[0][3] && !diffTestAll[0][7]) { + reportErrorToFile(analysis, "diffTestAll[0][3] && !diffTestAll[0][7] " + textDiff); + } + // syntaktische Gleicheheit ohne Whitespace folgt semantische Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine true + if (diffTestAll[0][7] && !diffTestAll[1][3]) { + reportErrorToFile(analysis, "diffTestAll[0][7] && !diffTestAll[1][3] " + textDiff); + } + // für Trees + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine false + if (treeBeforeTest[0] && !treeBeforeTest[4]) { + reportErrorToFile(analysis, "treeBeforeTest[0] && !treeBeforeTest[4] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine false + if (treeBeforeTest[1] && !treeBeforeTest[5]) { + reportErrorToFile(analysis, "treeBeforeTest[1] && !treeBeforeTest[5] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine true + if (treeBeforeTest[2] && !treeBeforeTest[6]) { + reportErrorToFile(analysis, "treeBeforeTest[2] && !treeBeforeTest[6] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine true + if (treeBeforeTest[3] && !treeBeforeTest[7]) { + reportErrorToFile(analysis, "treeBeforeTest[3] && !treeBeforeTest[7] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine false + if (treeAfterTest[0] && !treeAfterTest[4]) { + reportErrorToFile(analysis, "treeAfterTest[0] && !treeAfterTest[4] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine false + if (treeAfterTest[1] && !treeAfterTest[5]) { + reportErrorToFile(analysis, "treeAfterTest[1] && !treeAfterTest[5] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine false und IgnoreEmptyLine true + if (treeAfterTest[2] && !treeAfterTest[6]) { + reportErrorToFile(analysis, "treeAfterTest[2] && !treeAfterTest[6] " + textDiff); + } + // syntaktische Gleicheheit folgt syntaktische ohne Whitespace Gleichheit + // für CollapseMultyLine true und IgnoreEmptyLine true + if (treeAfterTest[3] && !treeAfterTest[7]) { + reportErrorToFile(analysis, "treeAfterTest[3] && !treeAfterTest[7] " + textDiff); + } + + // Speichere ergebniss + final UnparseEvaluation ue = new UnparseEvaluation( + boolToInt(dataTests), + boolToInt(diffTestAll[0]), + boolToInt(diffTestAll[1]), + boolToInt(treeBeforeTest), + boolToInt(treeAfterTest)); + csv.append(ue.toCSV()).append(StringUtils.LINEBREAK); + return Analysis.Hooks.super.analyzeVariationDiff(analysis); + } + + @Override + public void endBatch(Analysis analysis) throws IOException { + IO.write( + FileUtils.addExtension(analysis.getOutputFile(), CSV_EXTENSION), + csv.toString()); + } + + private void reportErrorToFile(Analysis analysis, String errorMessage) throws IOException { + IO.write( + FileUtils.addExtension(analysis.getOutputFile().resolve("error" + errorCount), ".txt"), + errorMessage); + ++errorCount; + } + + public static String removeWhitespace(String string, boolean diff) { + if (string.isEmpty()) { + return ""; + } else { + StringBuilder result = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new StringReader(string)); + String line = ""; + while ((line = in.readLine()) != null) { + if (!line.replaceAll("\\s+", "").isEmpty()) { + String temp = line.trim(); + if (diff && !(temp.charAt(0) == '+' || temp.charAt(0) == '-')) { + temp = " " + temp; + } + result.append(temp); + result.append("\n"); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return result.toString(); + } + } + + public static String parseUnparseTree(String text, VariationDiffParseOptions option) { + String temp = "b"; + try { + VariationTree tree = VariationTree.fromText(text, VariationTreeSource.Unknown, option); + temp = VariationUnparser.unparseTree(tree); + } catch (Exception e) { + e.printStackTrace(); + } + return temp; + } + + public static String parseUnparseDiff(String textDiff, VariationDiffParseOptions option) { + String temp = "b"; + try { + VariationDiff diff = VariationDiff.fromDiff(textDiff, option); + temp = VariationUnparser.unparseDiff(diff); + } catch (Exception e) { + e.printStackTrace(); + } + return temp; + } + + public static VariationDiffParseOptions optionsSetter(int i) { + if (i == 0) { + return new VariationDiffParseOptions(false, false); + } else if (i == 1) { + return new VariationDiffParseOptions(true, false); + } else if (i == 2) { + return new VariationDiffParseOptions(false, true); + } else { + return new VariationDiffParseOptions(true, true); + } + } + + public static boolean equalsText(String text1, String text2, boolean whitespace, boolean diff) { + if (whitespace) { + return text1.equals(text2); + } else { + return removeWhitespace(text1, diff).equals(removeWhitespace(text2, diff)); + } + } + + public static boolean[][] runTestsDiff(String text) { + boolean[][] array = new boolean[2][8]; + for (int i = 0; i < 4; i++) { + String diff = parseUnparseDiff(text, optionsSetter(i)); + array[0][i] = equalsText(text, diff, true, true); + array[0][i + 4] = equalsText(text, diff, false, true); + array[1][i] = (equalsText(VariationUnparser.undiff(text, Time.BEFORE), + VariationUnparser.undiff(diff, Time.BEFORE), true, true) + && equalsText(VariationUnparser.undiff(text, Time.AFTER), + VariationUnparser.undiff(diff, Time.AFTER), true, true)) + || (equalsText(VariationUnparser.undiff(text, Time.BEFORE), + VariationUnparser.undiff(diff, Time.BEFORE), false, true) + && equalsText(VariationUnparser.undiff(text, Time.AFTER), + VariationUnparser.undiff(diff, Time.AFTER), false, true)); + array[1][i + 4] = false; + } + return array; + } + + public static boolean[] runTestsTree(String text) { + boolean[] array = new boolean[8]; + for (int i = 0; i < 4; i++) { + String temp = parseUnparseTree(text, optionsSetter(i)); + array[i] = equalsText(text, temp, true, false); + array[i + 4] = equalsText(text, temp, false, false); + } + return array; + } + + public static boolean[] runDataTest(String textDiff, String treeBefore, String treeAfter) throws IOException { + boolean[] array = new boolean[8]; + array[0] = JGitDiff.textDiff(treeBefore, treeAfter, SupportedAlgorithm.MYERS).equals(textDiff); + array[1] = removeWhitespace(JGitDiff.textDiff(treeBefore, treeAfter, SupportedAlgorithm.MYERS), true) + .equals(removeWhitespace(textDiff, true)); + array[2] = JGitDiff.textDiff(treeBefore, treeAfter, SupportedAlgorithm.HISTOGRAM).equals(textDiff); + array[3] = removeWhitespace(JGitDiff.textDiff(treeBefore, treeAfter, SupportedAlgorithm.HISTOGRAM), true) + .equals(removeWhitespace(textDiff, true)); + array[4] = treeBefore.equals(VariationUnparser.undiff(textDiff, Time.BEFORE)); + array[5] = removeWhitespace(treeBefore, false) + .equals(removeWhitespace(VariationUnparser.undiff(textDiff, Time.BEFORE), false)); + array[6] = treeAfter.equals(VariationUnparser.undiff(textDiff, Time.AFTER)); + array[7] = removeWhitespace(treeAfter, false) + .equals(removeWhitespace(VariationUnparser.undiff(textDiff, Time.AFTER), false)); + return array; + } + + public static int[] boolToInt(boolean[] array) { + int[] temp = new int[array.length]; + for (int i = 0; i < array.length; i++) { + if (array[i]) { + temp[i] = 1; + } else { + temp[i] = 0; + } + } + return temp; + } + + public static boolean boolOr(boolean[] array) { + for (boolean temp : array) { + if (temp) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseEvaluation.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseEvaluation.java new file mode 100644 index 000000000..ba0ff3ba5 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseEvaluation.java @@ -0,0 +1,95 @@ +package org.variantsync.diffdetective.experiments.thesis_es; + +import static org.variantsync.functjonal.Functjonal.intercalate; + +import org.variantsync.diffdetective.util.CSV; + +public record UnparseEvaluation( + int[] dataTest, + int[] diffTest, + int[] diffSemEqTest, + int[] treeBeforeTest, + int[] treeAfterTest) implements CSV { + + @Override + public String toCSV(String delimiter) { + return intercalate(delimiter, + dataTest[0], + dataTest[1], + dataTest[2], + dataTest[3], + dataTest[4], + dataTest[5], + dataTest[6], + dataTest[7], + diffTest[0], + diffTest[1], + diffTest[2], + diffTest[3], + diffTest[4], + diffTest[5], + diffTest[6], + diffTest[7], + diffSemEqTest[0], + diffSemEqTest[1], + diffSemEqTest[2], + diffSemEqTest[3], + treeBeforeTest[0], + treeBeforeTest[1], + treeBeforeTest[2], + treeBeforeTest[3], + treeBeforeTest[4], + treeBeforeTest[5], + treeBeforeTest[6], + treeBeforeTest[7], + treeAfterTest[0], + treeAfterTest[1], + treeAfterTest[2], + treeAfterTest[3], + treeAfterTest[4], + treeAfterTest[5], + treeAfterTest[6], + treeAfterTest[7]); + } + + public static String makeHeader(String delimiter) { + return intercalate(delimiter, + "diffFromTreesEqTextDiffMye", + "diffFromTreesEqTextDiffMyeWhite", + "diffFromTreesEqTextDiffHis", + "diffFromTreesEqTextDiffHisWhite", + "treeBefEqTreeBefDiff", + "treeBefEqTreeBefDiffWhite", + "treeAftEqTreeAftDiff", + "treeAftEqTreeAftDiffWhite", + "diffEqTestMultiL0EmptyL0", + "diffEqTestMultiL1EmptyL0", + "diffEqTestMultiL0EmptyL1", + "diffEqTestMultiL1EmptyL1", + "diffEqTestMultiL0EmptyL0White", + "diffEqTestMultiL1EmptyL0White", + "diffEqTestMultiL0EmptyL1White", + "diffEqTestMultiL1EmptyL1White", + "diffSemEqTestMultiL0EmptyL0", + "diffSemEqTestMultiL1EmptyL0", + "diffSemEqTestMultiL0EmptyL1", + "diffSemEqTestMultiL1EmptyL1", + "treeBeforeEqTestMultiL0EmptyL0", + "treeBeforeEqTestMultiL1EmptyL0", + "treeBeforeEqTestMultiL0EmptyL1", + "treeBeforeEqTestMultiL1EmptyL1", + "treeBeforeEqTestMultiL0EmptyL0White", + "treeBeforeEqTestMultiL1EmptyL0White", + "treeBeforeEqTestMultiL0EmptyL1White", + "treeBeforeEqTestMultiL1EmptyL1White", + "treeAfterEqTestMultiL0EmptyL0", + "treeAfterEqTestMultiL1EmptyL0", + "treeAfterEqTestMultiL0EmptyL1", + "treeAfterEqTestMultiL1EmptyL1", + "treeAfterEqTestMultiL0EmptyL0White", + "treeAfterEqTestMultiL1EmptyL0White", + "treeAfterEqTestMultiL0EmptyL1White", + "treeAfterEqTestMultiL1EmptyL1White"); + } + +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/DiffLinesLabel.java b/src/main/java/org/variantsync/diffdetective/variation/DiffLinesLabel.java index 1e1d86d3d..54a1f194e 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/DiffLinesLabel.java +++ b/src/main/java/org/variantsync/diffdetective/variation/DiffLinesLabel.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import org.variantsync.diffdetective.diff.text.DiffLineNumber; +import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.diff.VariationDiff; // For Javadoc @@ -18,6 +19,7 @@ */ public class DiffLinesLabel implements Label { private final List lines; + private List trailingLines; public record Line(String content, DiffLineNumber lineNumber) { public static Line withInvalidLineNumber(String content) { @@ -30,7 +32,15 @@ public DiffLinesLabel() { } public DiffLinesLabel(List lines) { + this(lines, new ArrayList<>()); + } + + public DiffLinesLabel(List lines, List trailingLines) { + Assert.assertNotNull(lines); + Assert.assertNotNull(trailingLines); + this.lines = lines; + this.trailingLines = trailingLines; } public static DiffLinesLabel withInvalidLineNumbers(List lines) { @@ -53,11 +63,25 @@ public List getDiffLines() { return lines; } + public void setDiffTrailingLines(List newLines) { + Assert.assertNotNull(newLines); + trailingLines = newLines; + } + + public List getDiffTrailingLines() { + return trailingLines; + } + @Override public List getLines() { return getDiffLines().stream().map(Line::content).toList(); } + @Override + public List getTrailingLines() { + return getDiffTrailingLines().stream().map(Line::content).toList(); + } + @Override public String toString() { return lines @@ -68,6 +92,15 @@ public String toString() { @Override public DiffLinesLabel clone() { - return new DiffLinesLabel(new ArrayList<>(lines)); + return new DiffLinesLabel(new ArrayList<>(lines), new ArrayList<>(trailingLines)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DiffLinesLabel that = (DiffLinesLabel) o; + return lines.equals(that.lines) && + trailingLines.equals(that.trailingLines); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/Label.java b/src/main/java/org/variantsync/diffdetective/variation/Label.java index 1c83c27f3..4c4bb6e94 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/Label.java +++ b/src/main/java/org/variantsync/diffdetective/variation/Label.java @@ -9,6 +9,18 @@ * Base interface for labels of {@link VariationTree}s and {@link VariationDiff}s. */ public interface Label { + /** + * Returns the lines which need to be printed before the children of a node. + * Most lines associated with a node should be included in this list, for example, {@code #if}s are stored here. + */ List getLines(); + /** + * Returns the lines which need to be printed after the children of a node. + * For example, {@code #endif}s are stored as trailing lines. + */ + List getTrailingLines(); + /** + * Creates a deep copy of this label. + */ Label clone(); } diff --git a/src/main/java/org/variantsync/diffdetective/variation/LinesLabel.java b/src/main/java/org/variantsync/diffdetective/variation/LinesLabel.java index 937d09712..c0b560212 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/LinesLabel.java +++ b/src/main/java/org/variantsync/diffdetective/variation/LinesLabel.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.StringUtils; /** @@ -12,23 +13,38 @@ */ public class LinesLabel implements Label { private final List lines; + private final List trailingLines; public LinesLabel() { - this(new ArrayList<>()); + this(new ArrayList<>(), new ArrayList<>()); } public LinesLabel(List lines) { + this(lines, new ArrayList<>()); + } + + public LinesLabel(List lines, List trailingLines) { + Assert.assertNotNull(lines); + Assert.assertNotNull(trailingLines); + this.lines = lines; + this.trailingLines = trailingLines; } public static LinesLabel ofCodeBlock(String codeBlock) { return new LinesLabel(Arrays.asList(StringUtils.LINEBREAK_REGEX.split(codeBlock, -1))); } + @Override public List getLines() { return lines; } + @Override + public List getTrailingLines() { + return trailingLines; + } + @Override public String toString() { return lines @@ -38,6 +54,15 @@ public String toString() { @Override public LinesLabel clone() { - return new LinesLabel(new ArrayList<>(lines)); + return new LinesLabel(new ArrayList<>(lines), new ArrayList<>(trailingLines)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LinesLabel that = (LinesLabel) o; + return lines.equals(that.lines) && + trailingLines.equals(that.trailingLines); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationLabel.java b/src/main/java/org/variantsync/diffdetective/variation/VariationLabel.java index 1709dfa95..341bae124 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/VariationLabel.java +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationLabel.java @@ -2,6 +2,7 @@ import java.util.List; +import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.variation.tree.HasNodeType; import org.variantsync.diffdetective.variation.tree.VariationTree; // For Javadoc import org.variantsync.functjonal.Cast; @@ -23,6 +24,9 @@ public class VariationLabel implements Label, HasNodeType { private L innerLabel; public VariationLabel(NodeType type, L innerLabel) { + Assert.assertNotNull(type); + Assert.assertNotNull(innerLabel); + this.type = type; this.innerLabel = innerLabel; } @@ -40,6 +44,11 @@ public List getLines() { return innerLabel.getLines(); } + @Override + public List getTrailingLines() { + return innerLabel.getTrailingLines(); + } + @Override public NodeType getNodeType() { return type; @@ -49,9 +58,18 @@ public NodeType getNodeType() { public VariationLabel clone() { return new VariationLabel(type, Cast.unchecked(innerLabel.clone())); } - + @Override public String toString() { - return innerLabel.toString(); + return innerLabel.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VariationLabel that = (VariationLabel) o; + return type.equals(that.type) && + innerLabel.equals(that.innerLabel); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java new file mode 100644 index 000000000..68d42e208 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -0,0 +1,57 @@ +package org.variantsync.diffdetective.variation; + +import java.io.IOException; +import java.util.stream.Collectors; + +import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; +import org.variantsync.diffdetective.variation.diff.DiffType; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; +import org.variantsync.diffdetective.variation.tree.VariationTree; + +public class VariationUnparser { + /** + * Unparse {@link VariationTree}s into a {@link String}. + * + * @param tree that is unparsed + * @return the unparsed variation tree + * @param the type of labels of the tree + */ + public static String unparseTree(VariationTree tree) { + return tree.unparse(); + } + + /** + * Unparse {@link VariationDiff}s into a {@link String}. + * + * @param diff that is unparsed + * @return the unparsed variation diff + * @param the type of labels of the tree + * @throws IOException + */ + public static String unparseDiff(VariationDiff diff) throws IOException { + String tree1 = unparseTree(diff.project(Time.BEFORE)); + String tree2 = unparseTree(diff.project(Time.AFTER)); + return JGitDiff.textDiff(tree1, tree2, SupportedAlgorithm.MYERS); + } + + /** + * Extract the state of the diffed text before or after {@code diff}. + * + * @param diff the diff from which the state is extracted + * @param time that the returned state represents + * @return the state before or after the diff + */ + public static String undiff(String diff, Time time) { + char excludedDiffSymbol = DiffType.thatExistsOnlyAt(time.other()).symbol; + + String result = diff + .lines() + .filter(line -> line.isEmpty() || line.charAt(0) != excludedDiffSymbol) + .map(line -> line.isEmpty() ? "" : line.substring(1)) + .collect(Collectors.joining("\n")); + + return result.isEmpty() ? "" : result + "\n"; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java index 4244fadfe..ee73be7da 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java @@ -90,11 +90,25 @@ public class DiffNode implements HasNodeType { public DiffNode(DiffType diffType, NodeType nodeType, DiffLineNumber fromLines, DiffLineNumber toLines, Node featureMapping, L label) { + this(diffType, fromLines, toLines, featureMapping, new VariationLabel<>(nodeType, label)); + } + + /** + * Creates a DiffNode with the given parameters. + * @param diffType The type of change made to this node. + * @param fromLines The starting line number of the corresponding text. + * @param toLines The ending line number of the corresponding text. + * @param featureMapping The formula stored in this node. Should be null for artifact nodes. + * @param label The label and type of this node. + */ + public DiffNode(DiffType diffType, + DiffLineNumber fromLines, DiffLineNumber toLines, + Node featureMapping, VariationLabel label) { children[BEFORE.ordinal()] = new ArrayList<>(); children[AFTER.ordinal()] = new ArrayList<>(); this.diffType = diffType; - this.label = new VariationLabel<>(nodeType, label); + this.label = label; this.from = fromLines; this.to = toLines; this.featureMapping = featureMapping; @@ -338,7 +352,6 @@ public List> removeChildren(Time time) { /** * Removes all children from the given node and adds them as children to this node at the respective times. - * The order of children is not stable because first all before children are transferred and then all after children. * The given node will have no children afterwards. * @param other The node whose children should be stolen. */ @@ -346,6 +359,66 @@ public void stealChildrenOf(final DiffNode other) { Time.forAll(time -> addChildren(other.removeChildren(time), time)); } + /** + * Splits an {@link isNon unmodified} node into a removed and an added node. + * Only one new node is created. Depending on {@code time}, {@code this} is changed into + * {@link DiffType#ADD} for {@link Time#BEFORE} or {@link DiffType#REM} for {@link Time#AFTER}. + * + * @param time decides which node is created + * @return the new node that exists at time {@code time}. + */ + public DiffNode split(Time time) { + Assert.assertTrue(isNon()); + + DiffType otherDiffType = DiffType.thatExistsOnlyAt(time); + var other = new DiffNode( + otherDiffType, + getFromLine().as(otherDiffType), + getToLine().as(otherDiffType), + getFormula(), + Cast.unchecked(label.clone()) + ); + + this.diffType = otherDiffType.inverse(); + this.from = this.from.as(this.diffType); + this.to = this.to.as(this.diffType); + + other.addChildren(this.removeChildren(time), time); + getParent(time).replaceChild(this, other, time); + + // Preserve the projection by changing its `backingNode` to `other`. + if (this.projections[time.ordinal()] != null) { + other.projections[time.ordinal()] = this.projections[time.ordinal()]; + this.projections[time.ordinal()] = null; + other.projections[time.ordinal()].backingNode = other; + } + + return other; + } + + /** + * Replaces a child of this node with another node. + *

+ * If {@code oldChild} is not a child of this node, nothing happens. + * + * @param oldChild the child that is removed + * @param newChild the child that is inserted + * @param time at which the child relation is changed + */ + public void replaceChild(DiffNode oldChild, DiffNode newChild, Time time) { + Assert.assertNull(newChild.getParent(time)); + Assert.assertTrue(newChild.getDiffType().existsAtTime(time)); + + for (ListIterator> it = children[time.ordinal()].listIterator(); it.hasNext(); ) { + if (it.next() == oldChild) { + it.set(newChild); + newChild.parents[time.ordinal()] = oldChild.parents[time.ordinal()]; + oldChild.parents[time.ordinal()] = null; + break; + } + } + } + /** * Returns the parent of this node before or after the edit. */ @@ -719,7 +792,7 @@ public static , L1 extends Label, L2 extends Labe for (var variationChildNode : variationNode.getChildren()) { var diffChildNode = unchanged(convert, variationChildNode); - Time.forAll(time -> diffNode.addChild(diffChildNode, time)); + diffChildNode.getDiffType().forAllTimesOfExistence(time -> diffNode.addChild(diffChildNode, time)); } return diffNode; @@ -750,7 +823,6 @@ public DiffNode deepCopy(HashMap, DiffNode> oldToNew) { public DiffNode shallowCopy() { return new DiffNode( getDiffType(), - getNodeType(), getFromLine(), getToLine(), getFormula(), @@ -787,7 +859,7 @@ private static boolean isSameAs(DiffNode a, DiffNode b, a.getNodeType().equals(b.getNodeType()) && a.getFromLine().equals(b.getFromLine()) && a.getToLine().equals(b.getToLine()) && - (a.getFormula() == null ? b.getFormula() == null : a.getFormula().equals(b.getFormula())) && + Objects.equals(a.getFormula(), b.getFormula()) && a.getLabel().equals(b.getLabel()) )) { return false; diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffType.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffType.java index 4adc558a5..233ffae89 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffType.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffType.java @@ -1,11 +1,10 @@ package org.variantsync.diffdetective.variation.diff; import org.apache.commons.lang3.function.FailableConsumer; -import org.tinylog.Logger; +import org.apache.commons.lang3.function.FailableRunnable; import java.util.Optional; import java.util.Set; -import java.util.function.Consumer; /** * Type of change made to an artifact (e.g., a line of text in a text-based diff). @@ -13,13 +12,13 @@ * These values correspond to the domain of the Delta function from our paper. */ public enum DiffType { - ADD("+"), - REM("-"), - NON(" "); + ADD('+'), + REM('-'), + NON(' '); - public final String symbol; + public final char symbol; - DiffType(String symbol) { + DiffType(char symbol) { this.symbol = symbol; } @@ -70,8 +69,9 @@ public static Optional thatExistsOnlyAtAll(final Set

+ * Note that this field is package private because {@link DiffNode} needs to change it, + * for example in {@link DiffNode#split}, + * to preserve the identity of this instance and prevent it from becoming invalid. + * Only {@link DiffNode} is intended to have access to this field. + */ + DiffNode backingNode; private Time time; /** diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java index 25496273d..f7508ee41 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java @@ -198,8 +198,18 @@ public static BadVDiff fromGood(VariationDiff d) { record EdgeToConstruct( VariationTreeNode child, DiffNode parent, - Time t - ) {} + Time t, + int index + ) { + public EdgeToConstruct( + VariationTreeNode child, + DiffNode parent, + Time t, + DiffNode originalChild + ) { + this(child, parent, t, parent.indexOfChild(originalChild, t)); + } + } final FromGoodNodeTranslation nodeTranslation = new FromGoodNodeTranslation<>(); @@ -259,7 +269,7 @@ record EdgeToConstruct( nodeTranslation.put(diffNode, time, self); - edgesToConstruct.add(new EdgeToConstruct<>(self, diffNode.getParent(time), time)); + edgesToConstruct.add(new EdgeToConstruct<>(self, diffNode.getParent(time), time, diffNode)); // further metadata to copy lines.put(self, dRange); @@ -283,16 +293,17 @@ record EdgeToConstruct( */ if (pbefore != null) { edgesToConstruct.add(new EdgeToConstruct<>( - self, pbefore, BEFORE + self, pbefore, BEFORE, diffNode )); } else if (pafter != null) { edgesToConstruct.add(new EdgeToConstruct<>( - self, pafter, AFTER + self, pafter, AFTER, diffNode )); } } }); + edgesToConstruct.sort(Comparator.comparingInt(EdgeToConstruct::index)); for (final EdgeToConstruct e : edgesToConstruct) { nodeTranslation.get(e.parent, e.t).addChild(e.child); } @@ -324,8 +335,18 @@ public VariationDiff toGood() { record EdgeToConstruct( DiffNode child, VariationTreeNode parent, - Time time - ) {} + Time time, + int index + ) { + public EdgeToConstruct( + DiffNode child, + VariationTreeNode parent, + Time time, + VariationTreeNode originalChild + ) { + this(child, parent, time, parent.indexOfChild(originalChild)); + } + } final List> edgesToConstruct = new ArrayList<>(); final Map, DiffNode> nodeTranslation = new HashMap<>(); @@ -351,7 +372,7 @@ record EdgeToConstruct( nodeTranslation.put(vtnode, vGood); coloring.get(vtnode).forAllTimesOfExistence( - t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, parent, t)) + t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, parent, t, vtnode)) ); } else { // v was cloned. @@ -369,14 +390,15 @@ record EdgeToConstruct( // invoke the callback for a single time: // BEFORE for REM and AFTER for ADD. vColor.forAllTimesOfExistence( - t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, parent, t)) + t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, parent, t, vtnode)) ); badBuddyColor.forAllTimesOfExistence( - t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, badBuddy.getParent(), t)) + t -> edgesToConstruct.add(new EdgeToConstruct<>(vGood, badBuddy.getParent(), t, badBuddy)) ); } }); + edgesToConstruct.sort(Comparator.comparingInt(EdgeToConstruct::index)); for (final EdgeToConstruct e : edgesToConstruct) { nodeTranslation.get(e.parent()).addChild(e.child(), e.time()); } 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 2df9c3bf3..41bb68f44 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 @@ -326,8 +326,8 @@ private void parseLine( // Do not create a node for ENDIF, but update the line numbers of the closed if-chain // and remove that if-chain from the relevant stacks. - diffType.forAllTimesOfExistence(beforeStack, afterStack, stack -> - popIfChain(stack, fromLine) + diffType.forAllTimesOfExistence(time -> + popIfChain(time, fromLine, line) ); } else if (options.collapseMultipleCodeLines() && annotation.type() == AnnotationType.None @@ -355,32 +355,49 @@ private void parseLine( } /** - * Pop {@code stack} until an IF node is popped. + * Pop the stack until an IF node is popped. * If there were ELSEs or ELIFs between an IF and an ENDIF, they were placed on the stack and * have to be popped now. The {@link DiffNode#getToLine() end line numbers} are adjusted * - * @param stack the stack which should be popped + * @param time which stack to pop the if chain (i.e., {@link beforeStack} or {@link afterStack}) * @param elseLineNumber the first line of the else which causes this IF to be popped + * @param line the line containing the endif * @throws DiffParseException if {@code stack} doesn't contain an IF node */ private void popIfChain( - Stack> stack, - DiffLineNumber elseLineNumber + Time time, + DiffLineNumber elseLineNumber, + LogicalLine line ) throws DiffParseException { + Stack> stack = time.match(beforeStack, afterStack); + DiffLineNumber previousLineNumber = elseLineNumber; do { DiffNode annotation = stack.peek(); + // Save endif + if (annotation.isIf()) { + var endIf = line.getLines(); + var otherEndIf = annotation.getLabel().getDiffTrailingLines(); + + // Split the node if two different endif lines are associated to one if node. + if (!otherEndIf.isEmpty() && !endIf.equals(otherEndIf)) { + annotation = annotation.split(time); + } + + annotation.getLabel().setDiffTrailingLines(endIf); + } + // Set the line number of now closed annotations to the beginning of the // following annotation. annotation.setToLine(new DiffLineNumber( Math.max(previousLineNumber.inDiff(), annotation.getToLine().inDiff()), - stack == beforeStack - ? previousLineNumber.beforeEdit() - : annotation.getToLine().beforeEdit(), - stack == afterStack - ? previousLineNumber.afterEdit() - : annotation.getToLine().afterEdit() + time.match( + previousLineNumber.beforeEdit(), + annotation.getToLine().beforeEdit()), + time.match( + annotation.getToLine().afterEdit(), + previousLineNumber.afterEdit()) )); previousLineNumber = annotation.getFromLine(); 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 cbeb3d811..ebf9e0238 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 @@ -114,9 +114,7 @@ public static VariationDiff naive(final Variat final Map, Projection> invCopyMemory = CollectionUtils.invert(copyMemory, HashMap::new); TreeView.treeInline(treeView.root(), v -> inView.test(t, invCopyMemory.get(v))); - final StringBuilder b = new StringBuilder(); - treeView.root().printSourceCode(b); - projectionViewText[i] = b.toString(); + projectionViewText[i] = treeView.unparse(); } return naive(d, rho, projectionViewText); diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java index d1622423f..c3e8d1f39 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java @@ -563,19 +563,19 @@ public void assertConsistency() { * *

This method assumes that all labels of this subtree represent source code lines. */ - public void printSourceCode(final StringBuilder output) { + public void unparse(final StringBuilder output) { for (final String line : getLabel().getLines()) { output.append(line); output.append(StringUtils.LINEBREAK); } for (final var child : getChildren()) { - child.printSourceCode(output); + child.unparse(output); } // Add #endif after macro - if (isIf() && !isRoot()) { - output.append("#endif"); + for (final String line : getLabel().getTrailingLines()) { + output.append(line); output.append(StringUtils.LINEBREAK); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java index 36cdff174..722744ed7 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java @@ -16,6 +16,8 @@ import java.io.BufferedReader; import java.io.IOException; +import java.io.StringReader; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -83,6 +85,7 @@ public static VariationTree fromFile( * Parses a {@code VariationTree} from source code with C preprocessor annotations. * * @param input the source code to be parsed + * @param source from where the variation tree was obtained * @param parseOptions {@link PatchDiffParseOptions} for the parsing process. * @return a new {@code VariationTree} representing {@code input} * @throws IOException if {@code input} throws {@code IOException} @@ -103,6 +106,28 @@ public static VariationTree fromFile( return new VariationTree<>(tree, source); } + /** + * Parses a {@code VariationTree} from source code with C preprocessor annotations. + * + * @param input the source code to be parsed + * @param source from where the variation tree was obtained + * @param parseOptions {@link PatchDiffParseOptions} for the parsing process. + * @return a new {@code VariationTree} representing {@code input} + * @throws DiffParseException if some preprocessor annotations can't be parsed + */ + public static VariationTree fromText( + final String input, + final VariationTreeSource source, + final VariationDiffParseOptions parseOptions + ) throws DiffParseException { + try { + return fromFile(new BufferedReader(new StringReader(input)), source, parseOptions); + } catch (IOException e) { + // Only thrown on programming errors because StringReader doesn't do IO. + throw new UncheckedIOException(e); + } + } + public static VariationTree fromProjection(final Projection projection, final VariationTreeSource source) { return fromVariationNode(projection, source); } @@ -182,6 +207,12 @@ public VariationTree deepCopy(final Map, VariationTreeNo return new VariationTree<>(root.deepCopy(oldToNew), this.source); } + public String unparse() { + var result = new StringBuilder(); + root().unparse(result); + return result.toString(); + } + @Override public String toString() { return "variation tree from " + source; diff --git a/src/test/java/BadVDiffTest.java b/src/test/java/BadVDiffTest.java index 3e7f4f7a5..34892f21a 100644 --- a/src/test/java/BadVDiffTest.java +++ b/src/test/java/BadVDiffTest.java @@ -1,4 +1,5 @@ import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.tinylog.Logger; @@ -8,6 +9,12 @@ import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.bad.BadVDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.serialize.GraphFormat; +import org.variantsync.diffdetective.variation.diff.serialize.LineGraphImport; +import org.variantsync.diffdetective.variation.diff.serialize.LineGraphImportOptions; +import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.ChildOrderEdgeFormat; +import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.FullNodeFormat; +import org.variantsync.diffdetective.variation.diff.serialize.treeformat.IndexedTreeFormat; import java.io.IOException; import java.nio.file.Path; @@ -39,4 +46,21 @@ public void toGood_after_fromGood_idempotency(String filename) throws IOExceptio Assertions.assertTrue(initialVDiff.isSameAs(goodDiff)); } + + @Test + public void childOrderIsPreserved() throws IOException { + final Path fileGraphFile = resDir.resolve("childOrder.lg"); + final var importOptions = new LineGraphImportOptions( + GraphFormat.VARIATION_DIFF, + new IndexedTreeFormat(), + new FullNodeFormat(), + new ChildOrderEdgeFormat<>() + ); + + final VariationDiff initialVDiff = LineGraphImport.fromFile(fileGraphFile, importOptions).get(0); + final var badDiff = BadVDiff.fromGood(initialVDiff); + final var goodDiff = badDiff.toGood(); + + Assertions.assertTrue(initialVDiff.isSameAs(goodDiff)); + } } diff --git a/src/test/java/ShowTest.java b/src/test/java/ShowTest.java new file mode 100644 index 000000000..004a3d1c0 --- /dev/null +++ b/src/test/java/ShowTest.java @@ -0,0 +1,25 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.bad.BadVDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; + +import org.variantsync.diffdetective.show.Show; +import org.variantsync.diffdetective.show.engine.GameEngine; + +import java.io.IOException; +import java.nio.file.Path; + +public class ShowTest { + @Disabled("GUI test, needs to be run manually") + @Test + public void toGood_after_fromGood_idempotency() throws IOException, DiffParseException { + final Path testfile = Constants.RESOURCE_DIR.resolve("badvdiff").resolve("1.diff"); + final VariationDiff vdiff = VariationDiff.fromFile(testfile, new VariationDiffParseOptions(false, false)); + final BadVDiff badDiff = BadVDiff.fromGood(vdiff); + + GameEngine.showAndAwaitAll(Show.baddiff(badDiff)); + } +} diff --git a/src/test/java/VariationUnparserTest.java b/src/test/java/VariationUnparserTest.java new file mode 100644 index 000000000..17755b25a --- /dev/null +++ b/src/test/java/VariationUnparserTest.java @@ -0,0 +1,79 @@ +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.VariationUnparser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.variantsync.diffdetective.experiments.thesis_es.UnparseAnalysis.removeWhitespace; + +public class VariationUnparserTest { + private final static Path unparserTestCaseDir = Constants.RESOURCE_DIR.resolve("unparser"); + private final static Path parserTestCaseDir = Constants.RESOURCE_DIR.resolve("diffs").resolve("parser"); + private final static String parserTestCaseSuffix = ".diff"; + + public static Stream treeTestCases() throws IOException { + return withParseOptions(Files.list(unparserTestCaseDir.resolve("trees"))); + } + + public static Stream diffTestCases() throws IOException { + return withParseOptions(Stream.concat( + Files.list(unparserTestCaseDir.resolve("diffs")), + Files.list(parserTestCaseDir) + .filter(filename -> filename.getFileName().toString().endsWith(parserTestCaseSuffix)))); + } + + private static Stream withParseOptions(Stream paths) { + // Build a Cartesian product of all paths and parse options. + return + paths.flatMap(path -> Stream.of( + Arguments.of(path, new VariationDiffParseOptions(false, false)), + Arguments.of(path, new VariationDiffParseOptions(false, true)), + Arguments.of(path, new VariationDiffParseOptions(true, false)), + Arguments.of(path, new VariationDiffParseOptions(true, true)))); + } + + @ParameterizedTest + @MethodSource("treeTestCases") + public void testTreeUnparse(Path testCasePath, VariationDiffParseOptions parseOptions) throws IOException, DiffParseException { + assertEqualTree( + Files.readString(testCasePath).replaceAll("\\r\\n", "\n"), + parseUnparseTree(testCasePath, parseOptions)); + } + + @ParameterizedTest + @MethodSource("diffTestCases") + public void testDiffUnparse(Path testCasePath, VariationDiffParseOptions parseOptions) throws IOException, DiffParseException { + assertEqualDiff( + Files.readString(testCasePath).replaceAll("\\r\\n", "\n"), + parseUnparseDiff(testCasePath, parseOptions)); + } + + private static String parseUnparseTree(Path path, VariationDiffParseOptions option) throws IOException, DiffParseException { + VariationTree tree = VariationTree.fromFile(path, option); + return VariationUnparser.unparseTree(tree); + } + + private static String parseUnparseDiff(Path path, VariationDiffParseOptions option) throws IOException, DiffParseException { + VariationDiff diff = VariationDiff.fromFile(path, option); + return VariationUnparser.unparseDiff(diff); + } + + private static void assertEqualDiff(String expected, String actual) { + assertEqualTree(VariationUnparser.undiff(expected, Time.BEFORE), VariationUnparser.undiff(actual, Time.BEFORE)); + assertEqualTree(VariationUnparser.undiff(expected, Time.AFTER), VariationUnparser.undiff(actual, Time.AFTER)); + } + + private static void assertEqualTree(String expected, String actual) { + assertEquals(removeWhitespace(expected, false), removeWhitespace(actual, false)); + } +} diff --git a/src/test/resources/badvdiff/childOrder.lg b/src/test/resources/badvdiff/childOrder.lg new file mode 100644 index 000000000..39cb4cdd3 --- /dev/null +++ b/src/test/resources/badvdiff/childOrder.lg @@ -0,0 +1,13 @@ +t # 1 +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;#if A +v 264 REM;IF;(old: 2, diff: 3, new: -1);(old: 4, diff: 6, new: -1);X;#if X +v 403 NON;ARTIFACT;(old: 3, diff: 5, new: 4);(old: 4, diff: 6, new: 5);;foo +v 192 ADD;IF;(old: -1, diff: 2, new: 1);(old: -1, diff: 7, new: 5);B;#if B +v 256 ADD;IF;(old: -1, diff: 3, new: 2);(old: -1, diff: 4, new: 3);X;#if X +e 136 16 b;0,-1 +e 264 136 b;0,-1 +e 403 264 b;0,-1 +e 192 16 a;-1,0 +e 256 192 a;-1,0 +e 403 192 a;-1,1 diff --git a/src/test/resources/diffs/parser/09_expected.lg b/src/test/resources/diffs/parser/09_expected.lg index 714b02ae3..5b3559d42 100644 --- a/src/test/resources/diffs/parser/09_expected.lg +++ b/src/test/resources/diffs/parser/09_expected.lg @@ -1,8 +1,11 @@ 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);defined(A);#ifdef A +v 136 REM;IF;(old: 1, diff: 1, new: -1);(old: 3, diff: 3, new: -1);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 -e 211 144 ba;0,0 +v 128 ADD;IF;(old: -1, diff: 1, new: 1);(old: -1, diff: 5, new: 4);defined(A);#ifdef A +e 136 16 b;0,-1 +e 211 136 b;0,-1 +e 211 128 a;-1,0 e 339 16 b;1,-1 -e 339 144 a;-1,1 +e 339 128 a;-1,1 +e 128 16 a;-1,0 diff --git a/src/test/resources/unparser/diffs/01.diff b/src/test/resources/unparser/diffs/01.diff new file mode 100644 index 000000000..471bf6f0f --- /dev/null +++ b/src/test/resources/unparser/diffs/01.diff @@ -0,0 +1,4 @@ + #if A + Code ++#endif A +-#endif B diff --git a/src/test/resources/unparser/diffs/02.diff b/src/test/resources/unparser/diffs/02.diff new file mode 100644 index 000000000..74c2ff65e --- /dev/null +++ b/src/test/resources/unparser/diffs/02.diff @@ -0,0 +1,7 @@ + #ifndef A + code +-#endif + +-#ifndef A + code + #endif // A diff --git a/src/test/resources/unparser/trees/01.txt b/src/test/resources/unparser/trees/01.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/test/resources/unparser/trees/02.txt b/src/test/resources/unparser/trees/02.txt new file mode 100644 index 000000000..4200a6eb6 --- /dev/null +++ b/src/test/resources/unparser/trees/02.txt @@ -0,0 +1,3 @@ +#ifdef C + boob() +#endif \ No newline at end of file diff --git a/src/test/resources/unparser/trees/03.txt b/src/test/resources/unparser/trees/03.txt new file mode 100644 index 000000000..1e1a94da3 --- /dev/null +++ b/src/test/resources/unparser/trees/03.txt @@ -0,0 +1,11 @@ +#ifdef A + foo(); + bar(); +#else + #ifdef B + baz(); + #endif +#endif +#ifdef C + boob() +#endif \ No newline at end of file diff --git a/src/test/resources/unparser/trees/04.txt b/src/test/resources/unparser/trees/04.txt new file mode 100644 index 000000000..21cb52f21 --- /dev/null +++ b/src/test/resources/unparser/trees/04.txt @@ -0,0 +1,8 @@ +#ifdef A + foo(); + bar(); +#endif + +#if B && (!A || C) + baz(); +#endif \ No newline at end of file diff --git a/src/test/resources/unparser/trees/05.txt b/src/test/resources/unparser/trees/05.txt new file mode 100644 index 000000000..3137d7b23 --- /dev/null +++ b/src/test/resources/unparser/trees/05.txt @@ -0,0 +1,5 @@ +#ifdef A + baz(); + foo(); +#endif +foo(); \ No newline at end of file diff --git a/src/test/resources/unparser/trees/06.txt b/src/test/resources/unparser/trees/06.txt new file mode 100644 index 000000000..b0f909a06 --- /dev/null +++ b/src/test/resources/unparser/trees/06.txt @@ -0,0 +1,5 @@ +#ifdef A + baz(); + foo(); +#endif + diff --git a/src/test/resources/unparser/trees/07.txt b/src/test/resources/unparser/trees/07.txt new file mode 100644 index 000000000..2a248d683 --- /dev/null +++ b/src/test/resources/unparser/trees/07.txt @@ -0,0 +1,9 @@ +#ifdef FEAT_GUI + if (gui.in_use) + gui_mch_set_foreground(); +#else +# ifdef MSWIN + win32_set_foreground(); +# endif +#endif +} diff --git a/src/test/resources/unparser/trees/08.txt b/src/test/resources/unparser/trees/08.txt new file mode 100644 index 000000000..2ba7d7d28 --- /dev/null +++ b/src/test/resources/unparser/trees/08.txt @@ -0,0 +1,11 @@ +#ifdef FEAT_GUI + if (gui.in_use) + { + gui_mch_set_foreground(); + return; + } +#endif +#if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) + win32_set_foreground(); +#endif +}