diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d08c4186e4..7f21a68184 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: with: servers: '[{ "id": "ossrh", "username": "jplag", "password": "${{ secrets.OSSRH_TOKEN }}" }]' - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.PGP_SECRET }} passphrase: ${{ secrets.PGP_PW }} diff --git a/.github/workflows/report-viewer-build-test.yml b/.github/workflows/report-viewer-build-test.yml index 1e51bfefb3..aa80c86dd7 100644 --- a/.github/workflows/report-viewer-build-test.yml +++ b/.github/workflows/report-viewer-build-test.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/.github/workflows/report-viewer-dev.yml b/.github/workflows/report-viewer-dev.yml index e6bf77f379..92fe974071 100644 --- a/.github/workflows/report-viewer-dev.yml +++ b/.github/workflows/report-viewer-dev.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/.github/workflows/report-viewer-e2e.yml b/.github/workflows/report-viewer-e2e.yml index b9ac6354cf..9e265b9108 100644 --- a/.github/workflows/report-viewer-e2e.yml +++ b/.github/workflows/report-viewer-e2e.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Test 🧪 working-directory: report-viewer diff --git a/.github/workflows/report-viewer-lint.yml b/.github/workflows/report-viewer-lint.yml index 768cdd6c56..924b274e7a 100644 --- a/.github/workflows/report-viewer-lint.yml +++ b/.github/workflows/report-viewer-lint.yml @@ -4,7 +4,7 @@ name: Report Viewer ESLint Workflow # Checks linting of report viewer on: workflow_dispatch: push: - path: + paths: - ".github/workflows/report-viewer-lint.yml" - "report-viewer/**" pull_request: @@ -35,7 +35,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Lint 🎨 working-directory: report-viewer diff --git a/.github/workflows/report-viewer-prettier.yml b/.github/workflows/report-viewer-prettier.yml index 2da72eec3c..e9933c24e0 100644 --- a/.github/workflows/report-viewer-prettier.yml +++ b/.github/workflows/report-viewer-prettier.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Check 🎨 working-directory: report-viewer diff --git a/.github/workflows/report-viewer-unit.yml b/.github/workflows/report-viewer-unit.yml index 1d3012e4e3..48f9082a7a 100644 --- a/.github/workflows/report-viewer-unit.yml +++ b/.github/workflows/report-viewer-unit.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Test 🧪 working-directory: report-viewer diff --git a/.github/workflows/report-viewer.yml b/.github/workflows/report-viewer.yml index eb36428282..624dde7c53 100644 --- a/.github/workflows/report-viewer.yml +++ b/.github/workflows/report-viewer.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/cli/src/main/java/de/jplag/cli/CLI.java b/cli/src/main/java/de/jplag/cli/CLI.java index 9507a9a804..3874cc3d55 100644 --- a/cli/src/main/java/de/jplag/cli/CLI.java +++ b/cli/src/main/java/de/jplag/cli/CLI.java @@ -171,7 +171,8 @@ public JPlagOptions buildOptionsFromArguments(ParseResult parseResult) throws Cl JPlagOptions jPlagOptions = new JPlagOptions(loadLanguage(parseResult), this.options.minTokenMatch, submissionDirectories, oldSubmissionDirectories, null, this.options.advanced.subdirectory, suffixes, this.options.advanced.exclusionFileName, JPlagOptions.DEFAULT_SIMILARITY_METRIC, this.options.advanced.similarityThreshold, this.options.shownComparisons, clusteringOptions, - this.options.advanced.debug, mergingOptions); + this.options.advanced.debug, mergingOptions, JPlagOptions.DEFAULT_PRE_PARSE_HOOK, JPlagOptions.DEFAULT_PARSE_HOOK, + JPlagOptions.DEFAULT_PRE_COMPARE_HOOK, JPlagOptions.DEFAULT_COMPARE_HOOK); String baseCodePath = this.options.baseCode; File baseCodeDirectory = baseCodePath == null ? null : new File(baseCodePath); diff --git a/cli/src/main/java/de/jplag/cli/CliOptions.java b/cli/src/main/java/de/jplag/cli/CliOptions.java index 573707b862..c55e71f3b1 100644 --- a/cli/src/main/java/de/jplag/cli/CliOptions.java +++ b/cli/src/main/java/de/jplag/cli/CliOptions.java @@ -53,7 +53,7 @@ public class CliOptions implements Runnable { "--result-directory"}, description = "Name of the directory in which the comparison results will be stored (default: result)%n") public String resultFolder = "results"; - @ArgGroup(heading = "Advanced%n") + @ArgGroup(heading = "Advanced%n", exclusive = false) public Advanced advanced = new Advanced(); @ArgGroup(validate = false, heading = "Clustering%n") diff --git a/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java b/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java new file mode 100644 index 0000000000..bc88af731c --- /dev/null +++ b/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java @@ -0,0 +1,23 @@ +package de.jplag.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +class AdvancedGroupTest extends CommandLineInterfaceTest { + private static final String SUFFIXES = ".sc,.scala"; + + private static final double SIMILARITY_THRESHOLD = 0.5; + + /** + * Verify that it is possible to set multiple options in the "advanced" options group. + */ + @Test + void testNotExclusive() throws CliException { + buildOptionsFromCLI(defaultArguments().suffixes(SUFFIXES).similarityThreshold(SIMILARITY_THRESHOLD)); + assertEquals(Arrays.stream(SUFFIXES.split(",")).toList(), options.fileSuffixes()); + assertEquals(0.5, options.similarityThreshold()); + } +} diff --git a/core/src/main/java/de/jplag/SubmissionSet.java b/core/src/main/java/de/jplag/SubmissionSet.java index c9fbc6f119..7d38eec118 100644 --- a/core/src/main/java/de/jplag/SubmissionSet.java +++ b/core/src/main/java/de/jplag/SubmissionSet.java @@ -135,6 +135,8 @@ private void parseBaseCodeSubmission(Submission baseCode) throws BasecodeExcepti * Parse all given submissions. */ private void parseSubmissions(List submissions) { + this.options.preParseHook().accept(submissions); + if (submissions.isEmpty()) { logger.warn("No submissions to parse!"); return; @@ -167,6 +169,8 @@ private void parseSubmissions(List submissions) { } else { logger.error("ERROR -> Submission {} removed", currentSubmissionName); } + + this.options.parseHook().accept(submission); } int validSubmissions = submissions.size() - errors - tooShort; diff --git a/core/src/main/java/de/jplag/options/JPlagOptions.java b/core/src/main/java/de/jplag/options/JPlagOptions.java index 44eea1d2f9..89015f7250 100644 --- a/core/src/main/java/de/jplag/options/JPlagOptions.java +++ b/core/src/main/java/de/jplag/options/JPlagOptions.java @@ -10,16 +10,20 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.jplag.JPlag; +import de.jplag.JPlagComparison; import de.jplag.Language; +import de.jplag.Submission; import de.jplag.clustering.ClusteringOptions; import de.jplag.exceptions.BasecodeException; import de.jplag.merging.MergingOptions; +import de.jplag.strategy.SubmissionTuple; import de.jplag.util.FileUtils; /** @@ -44,11 +48,21 @@ * set to {@link #SHOW_ALL_COMPARISONS} all comparisons will be shown. * @param clusteringOptions Clustering options * @param debugParser If true, submissions that cannot be parsed will be stored in a separate directory. + * @param mergingOptions Parameters for match merging. + * @param preParseHook Hook to be executed before any submission is parsed. The hook is called with the list of all + * {@link Submission} instances that will be parsed. The default hook does nothing. + * @param parseHook Hook to be executed after parsing a single submision. The hook is called with the {@link Submission} + * that has just been parsed. The default hook does nothing. + * @param preCompareHook Hook to be executed directly before performing submission comparisons. The hook is called with + * the list of all {@link SubmissionTuple} instances that will be compared. The default hook does nothing. + * @param compareHook Hook to be executed after comparing two submissions. The hook is called with the + * {@link JPlagComparison} result. The default hook does nothing. */ public record JPlagOptions(Language language, Integer minimumTokenMatch, Set submissionDirectories, Set oldSubmissionDirectories, File baseCodeSubmissionDirectory, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser, MergingOptions mergingOptions) { + boolean debugParser, MergingOptions mergingOptions, Consumer> preParseHook, Consumer parseHook, + Consumer> preCompareHook, Consumer compareHook) { public static final double DEFAULT_SIMILARITY_THRESHOLD = 0; public static final int DEFAULT_SHOWN_COMPARISONS = 100; @@ -59,15 +73,29 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, Set> DEFAULT_PRE_PARSE_HOOK = submissions -> { + }; + + public static final Consumer DEFAULT_PARSE_HOOK = submission -> { + }; + + public static final Consumer> DEFAULT_PRE_COMPARE_HOOK = comparisons -> { + }; + + public static final Consumer DEFAULT_COMPARE_HOOK = comparison -> { + }; + public JPlagOptions(Language language, Set submissionDirectories, Set oldSubmissionDirectories) { this(language, null, submissionDirectories, oldSubmissionDirectories, null, null, null, null, DEFAULT_SIMILARITY_METRIC, - DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), false, new MergingOptions()); + DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), false, new MergingOptions(), DEFAULT_PRE_PARSE_HOOK, + DEFAULT_PARSE_HOOK, DEFAULT_PRE_COMPARE_HOOK, DEFAULT_COMPARE_HOOK); } public JPlagOptions(Language language, Integer minimumTokenMatch, Set submissionDirectories, Set oldSubmissionDirectories, File baseCodeSubmissionDirectory, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser, MergingOptions mergingOptions) { + boolean debugParser, MergingOptions mergingOptions, Consumer> preParseHook, Consumer parseHook, + Consumer> preCompareHook, Consumer compareHook) { this.language = language; this.debugParser = debugParser; this.fileSuffixes = fileSuffixes == null || fileSuffixes.isEmpty() ? null : Collections.unmodifiableList(fileSuffixes); @@ -82,90 +110,118 @@ public JPlagOptions(Language language, Integer minimumTokenMatch, Set subm this.subdirectoryName = subdirectoryName; this.clusteringOptions = clusteringOptions; this.mergingOptions = mergingOptions; + this.preParseHook = preParseHook; + this.parseHook = parseHook; + this.preCompareHook = preCompareHook; + this.compareHook = compareHook; } public JPlagOptions withLanguageOption(Language language) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withDebugParser(boolean debugParser) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withFileSuffixes(List fileSuffixes) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withSimilarityThreshold(double similarityThreshold) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withMaximumNumberOfComparisons(int maximumNumberOfComparisons) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withSimilarityMetric(SimilarityMetric similarityMetric) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withMinimumTokenMatch(Integer minimumTokenMatch) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withExclusionFileName(String exclusionFileName) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withSubmissionDirectories(Set submissionDirectories) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withOldSubmissionDirectories(Set oldSubmissionDirectories) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withBaseCodeSubmissionDirectory(File baseCodeSubmissionDirectory) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withSubdirectoryName(String subdirectoryName) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withClusteringOptions(ClusteringOptions clusteringOptions) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public JPlagOptions withMergingOptions(MergingOptions mergingOptions) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); + } + + public JPlagOptions withPreParseHook(Consumer> preParseHook) { + return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, + subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); + } + + public JPlagOptions withParseHook(Consumer parseHook) { + return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, + subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); + } + + public JPlagOptions withPreCompareHook(Consumer> preCompareHook) { + return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, + subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); + } + + public JPlagOptions withCompareHook(Consumer compareHook) { + return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, + subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } public boolean hasBaseCode() { @@ -254,10 +310,12 @@ private Integer normalizeMinimumTokenMatch(Integer minimumTokenMatch) { public JPlagOptions(Language language, Integer minimumTokenMatch, File submissionDirectory, Set oldSubmissionDirectories, String baseCodeSubmissionName, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser, MergingOptions mergingOptions) throws BasecodeException { + boolean debugParser, MergingOptions mergingOptions, Consumer> preParseHook, Consumer parseHook, + Consumer> preCompareHook, Consumer compareHook) throws BasecodeException { this(language, minimumTokenMatch, Set.of(submissionDirectory), oldSubmissionDirectories, convertLegacyBaseCodeToFile(baseCodeSubmissionName, submissionDirectory), subdirectoryName, fileSuffixes, exclusionFileName, - similarityMetric, similarityThreshold, maximumNumberOfComparisons, clusteringOptions, debugParser, mergingOptions); + similarityMetric, similarityThreshold, maximumNumberOfComparisons, clusteringOptions, debugParser, mergingOptions, preParseHook, + parseHook, preCompareHook, compareHook); } /** @@ -280,7 +338,7 @@ public JPlagOptions withBaseCodeSubmissionName(String baseCodeSubmissionName) { try { return new JPlagOptions(language, minimumTokenMatch, submissionDirectory, oldSubmissionDirectories, baseCodeSubmissionName, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser, mergingOptions); + clusteringOptions, debugParser, mergingOptions, preParseHook, parseHook, preCompareHook, compareHook); } catch (BasecodeException e) { throw new IllegalArgumentException(e.getMessage(), e.getCause()); } diff --git a/core/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java b/core/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java index 19822ef412..e6279842b0 100644 --- a/core/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java +++ b/core/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java @@ -47,6 +47,7 @@ protected void compareSubmissionsToBaseCode(SubmissionSet submissionSet) { protected Optional compareSubmissions(Submission first, Submission second) { JPlagComparison comparison = greedyStringTiling.compare(first, second); logger.info("Comparing {}-{}: {}", first.getName(), second.getName(), comparison.similarity()); + this.options.compareHook().accept(comparison); if (options.similarityMetric().isAboveThreshold(comparison, options.similarityThreshold())) { return Optional.of(comparison); @@ -57,7 +58,7 @@ protected Optional compareSubmissions(Submission first, Submiss /** * @return a list of all submission tuples to be processed. */ - protected static List buildComparisonTuples(List submissions) { + protected List buildComparisonTuples(List submissions) { List tuples = new ArrayList<>(); List validSubmissions = submissions.stream().filter(s -> s.getTokenList() != null).toList(); @@ -70,6 +71,7 @@ protected static List buildComparisonTuples(List su } } } + this.options.preCompareHook().accept(tuples); return tuples; } } diff --git a/core/src/test/java/de/jplag/HooksTest.java b/core/src/test/java/de/jplag/HooksTest.java new file mode 100644 index 0000000000..522774a2f0 --- /dev/null +++ b/core/src/test/java/de/jplag/HooksTest.java @@ -0,0 +1,53 @@ +package de.jplag; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import de.jplag.exceptions.ExitException; + +class HooksTest extends TestBase { + private static final String ROOT = "NoDuplicate"; + private static final int SUBMISSION_COUNT = 3; + private static final int COMPARISON_COUNT = 3; + + @Test + void testPreParseHook() throws ExitException { + final AtomicInteger timesCalled = new AtomicInteger(0); + runJPlag(ROOT, it -> it.withPreParseHook(submissions -> { + timesCalled.incrementAndGet(); + assertEquals(SUBMISSION_COUNT, submissions.size()); + })); + assertEquals(1, timesCalled.get()); + } + + @Test + void testParseHook() throws ExitException { + final AtomicInteger totalSubmissions = new AtomicInteger(0); + runJPlag(ROOT, it -> it.withParseHook(submission -> { + totalSubmissions.incrementAndGet(); + })); + assertEquals(SUBMISSION_COUNT, totalSubmissions.get()); + } + + @Test + void testPreCompareHook() throws ExitException { + final AtomicInteger timesCalled = new AtomicInteger(0); + runJPlag(ROOT, it -> it.withPreCompareHook(comparisons -> { + timesCalled.incrementAndGet(); + assertEquals(COMPARISON_COUNT, comparisons.size()); + })); + assertEquals(1, timesCalled.get()); + } + + @Test + void testCompareHook() throws ExitException { + final AtomicInteger totalComparisons = new AtomicInteger(0); + runJPlag(ROOT, it -> it.withCompareHook(comparison -> { + totalComparisons.incrementAndGet(); + })); + assertEquals(COMPARISON_COUNT, totalComparisons.get()); + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java index b8a38cd280..3bb2f991f1 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java @@ -1,6 +1,5 @@ package de.jplag.antlr; -import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -8,211 +7,92 @@ import java.util.function.Predicate; import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeListener; import org.antlr.v4.runtime.tree.TerminalNode; -import de.jplag.TokenType; -import de.jplag.semantics.VariableRegistry; - /** - * Base class for Antlr listeners. You can use the create*Mapping functions to map antlr tokens to jplag tokens. - *

- * You should create a constructor matching one of the constructors and create your mapping after calling super. + * Base class for Antlr listeners. This is a quasi-static class that is only created once per language. Use by calling + * the visit methods in the overwritten constructor. */ -@SuppressWarnings("unused") -public class AbstractAntlrListener implements ParseTreeListener { - private final List> startMappings; - private final List> endMappings; - - private final List terminalMapping; - - private final TokenCollector collector; - private final File currentFile; - - private VariableRegistry variableRegistry; +public abstract class AbstractAntlrListener { + private final List> contextVisitors; + private final List terminalVisitors; /** * New instance - * @param collector The token collector - * @param currentFile The currently processed file - * @param extractsSemantics If true, the listener will extract semantics along with every token */ - public AbstractAntlrListener(TokenCollector collector, File currentFile, boolean extractsSemantics) { - this.collector = collector; - this.currentFile = currentFile; - - this.startMappings = new ArrayList<>(); - this.endMappings = new ArrayList<>(); - - this.terminalMapping = new ArrayList<>(); - - if (extractsSemantics) { - this.variableRegistry = new VariableRegistry(); - } + protected AbstractAntlrListener() { + contextVisitors = new ArrayList<>(); + terminalVisitors = new ArrayList<>(); } /** - * Creates a new AbstractAntlrListener, that does not collect semantics information - * @param collector The collector, obtained by the parser - * @param currentFile The current file, obtained by the parser - */ - public AbstractAntlrListener(TokenCollector collector, File currentFile) { - this(collector, currentFile, false); - } - - @Override - public void visitTerminal(TerminalNode terminalNode) { - this.terminalMapping.stream().filter(mapping -> mapping.matches(terminalNode.getSymbol())) - .forEach(mapping -> mapping.createToken(terminalNode.getSymbol(), variableRegistry)); - } - - @Override - public void visitErrorNode(ErrorNode errorNode) { - // does nothing, because we do not handle error nodes right now. - } - - @Override - public void enterEveryRule(ParserRuleContext rule) { - this.startMappings.stream().filter(mapping -> mapping.matches(rule)).forEach(mapping -> mapping.createToken(rule, variableRegistry)); - } - - @Override - public void exitEveryRule(ParserRuleContext rule) { - this.endMappings.stream().filter(mapping -> mapping.matches(rule)).forEach(mapping -> mapping.createToken(rule, variableRegistry)); - } - - /** - * Creates a mapping using the start token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - protected ContextTokenBuilder mapEnter(Class antlrType, TokenType jplagType) { - return this.mapEnter(antlrType, jplagType, it -> true); - } - - /** - * Creates a mapping using the start token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapEnter(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.START); - this.startMappings.add((ContextTokenBuilder) builder); - return builder; - } - - /** - * Creates a mapping using the stop token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - protected ContextTokenBuilder mapExit(Class antlrType, TokenType jplagType) { - return this.mapExit(antlrType, jplagType, it -> true); - } - - /** - * Creates a mapping using the stop token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given node. + * @param antlrType The antlr type of the node. + * @param condition An additional condition for the visit. + * @return A visitor for the node. + * @param The class of the node. */ @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapExit(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.STOP); - this.endMappings.add((ContextTokenBuilder) builder); - return builder; + public ContextVisitor visit(Class antlrType, Predicate condition) { + Predicate typeCheck = rule -> rule.getClass() == antlrType; + ContextVisitor visitor = new ContextVisitor<>(typeCheck.and(condition)); + contextVisitors.add((ContextVisitor) visitor); + return visitor; } /** - * Creates a mapping using the beginning of the start token as the start location and the distance from the start to the - * stop token as the length - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given node. + * @param antlrType The antlr type of the node. + * @return A visitor for the node. + * @param The class of the node. */ - protected ContextTokenBuilder mapRange(Class antlrType, TokenType jplagType) { - return this.mapRange(antlrType, jplagType, it -> true); + public ContextVisitor visit(Class antlrType) { + return visit(antlrType, ignore -> true); } /** - * Creates a mapping using the beginning of the start token as the start location and the distance from the start to the - * stop token as the length - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given terminal. + * @param terminalType The type of the terminal. + * @param condition An additional condition for the visit. + * @return A visitor for the node. */ - @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapRange(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.RANGE); - this.startMappings.add((ContextTokenBuilder) builder); - return builder; + public TerminalVisitor visit(int terminalType, Predicate condition) { + Predicate typeCheck = rule -> rule.getType() == terminalType; + TerminalVisitor visitor = new TerminalVisitor(typeCheck.and(condition)); + terminalVisitors.add(visitor); + return visitor; } /** - * Creates a start mapping from antlrType to startType and a stop mapping from antlrType to stopType. - * @param antlrType The antlr token type - * @param startType The token type for the start mapping - * @param stopType The token type for the stop mapping - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given terminal. + * @param terminalType The type of the terminal. + * @return A visitor for the node. */ - protected RangeBuilder mapEnterExit(Class antlrType, TokenType startType, TokenType stopType) { - return mapEnterExit(antlrType, startType, stopType, it -> true); + public TerminalVisitor visit(int terminalType) { + return visit(terminalType, ignore -> true); } /** - * Creates a start mapping from antlrType to startType and a stop mapping from antlrType to stopType. - * @param antlrType The antlr token type - * @param startType The token type for the start mapping - * @param stopType The token type for the stop mapping - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Called by {@link InternalListener#visitTerminal(TerminalNode)} as part of antlr framework. */ - protected RangeBuilder mapEnterExit(Class antlrType, TokenType startType, TokenType stopType, - Predicate condition) { - ContextTokenBuilder start = this.mapEnter(antlrType, startType, condition); - ContextTokenBuilder end = this.mapExit(antlrType, stopType, condition); - return new RangeBuilder<>(start, end); + void visitTerminal(HandlerData data) { + this.terminalVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.enter(data)); } /** - * Creates a mapping for terminal tokens - * @param terminalType The type of the terminal node - * @param jplagType The jplag token type - * @return The builder for the token + * Called by {@link InternalListener#enterEveryRule(ParserRuleContext)} as part of antlr framework. */ - protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType) { - return this.mapTerminal(terminalType, jplagType, it -> true); + void enterEveryRule(HandlerData data) { + this.contextVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.enter(data)); } /** - * Creates a mapping for terminal tokens - * @param terminalType The type of the terminal node - * @param jplagType The jplag token type - * @param condition The condition under which the mapping applies - * @return The builder for the token + * Called by {@link InternalListener#exitEveryRule(ParserRuleContext)} as part of antlr framework. */ - protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType, Predicate condition) { - TerminalTokenBuilder builder = new TerminalTokenBuilder(jplagType, token -> token.getType() == terminalType && condition.test(token), - this.collector, this.currentFile); - this.terminalMapping.add(builder); - return builder; + void exitEveryRule(HandlerData data) { + this.contextVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.exit(data)); } /** @@ -224,7 +104,7 @@ protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType * @return an ancestor of the specified type, or null if not found. */ @SafeVarargs - protected final T getAncestor(ParserRuleContext context, Class ancestor, + protected static T getAncestor(ParserRuleContext context, Class ancestor, Class... stops) { ParserRuleContext currentContext = context; Set> forbidden = Set.of(stops); @@ -251,7 +131,7 @@ protected final T getAncestor(ParserRuleContext co * @see #getAncestor(ParserRuleContext, Class, Class[]) */ @SafeVarargs - protected final boolean hasAncestor(ParserRuleContext context, Class parent, + protected static boolean hasAncestor(ParserRuleContext context, Class parent, Class... stops) { return getAncestor(context, parent, stops) != null; } @@ -263,13 +143,17 @@ protected final boolean hasAncestor(ParserRuleContext context, Class the type to search for. * @return the first appearance of an element of the given type in the subtree, or null if no such element exists. */ - protected final T getDescendant(ParserRuleContext context, Class descendant) { + protected static T getDescendant(ParserRuleContext context, Class descendant) { // simple iterative bfs ArrayDeque queue = new ArrayDeque<>(); queue.add(context); while (!queue.isEmpty()) { ParserRuleContext next = queue.removeFirst(); - for (ParseTree tree : next.children) { + var children = next.children; + if (children == null) { + continue; + } + for (ParseTree tree : children) { if (tree.getClass() == descendant) { return descendant.cast(tree); } @@ -280,10 +164,4 @@ protected final T getDescendant(ParserRuleContext } return null; } - - private ContextTokenBuilder initTypeBuilder(Class antlrType, TokenType jplagType, Predicate condition, - ContextTokenBuilderType type) { - return new ContextTokenBuilder<>(jplagType, rule -> rule.getClass() == antlrType && condition.test(antlrType.cast(rule)), this.collector, - this.currentFile, type); - } } diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java index c5cdc478f9..1644ccf7b2 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java @@ -25,6 +25,24 @@ * @param The type of the antlr parser */ public abstract class AbstractAntlrParserAdapter extends AbstractParser { + + private final boolean extractsSemantics; + + /** + * New instance + * @param extractsSemantics If true, the listener will extract semantics along with every token + */ + protected AbstractAntlrParserAdapter(boolean extractsSemantics) { + this.extractsSemantics = extractsSemantics; + } + + /** + * New instance + */ + protected AbstractAntlrParserAdapter() { + this(false); + } + /** * Parsers the set of files * @param files The files @@ -32,33 +50,29 @@ public abstract class AbstractAntlrParserAdapter extends Abstr * @throws ParsingException If anything goes wrong */ public List parse(Set files) throws ParsingException { - TokenCollector collector = new TokenCollector(); - + TokenCollector collector = new TokenCollector(extractsSemantics); for (File file : files) { parseFile(file, collector); } - return collector.getTokens(); } private void parseFile(File file, TokenCollector collector) throws ParsingException { + collector.enterFile(file); try (Reader reader = FileUtils.openFileReader(file)) { Lexer lexer = this.createLexer(CharStreams.fromReader(reader)); CommonTokenStream tokenStream = new CommonTokenStream(lexer); T parser = this.createParser(tokenStream); - ParserRuleContext entryContext = this.getEntryContext(parser); ParseTreeWalker treeWalker = new ParseTreeWalker(); - - AbstractAntlrListener listener = this.createListener(collector, file); + InternalListener listener = new InternalListener(this.getListener(), collector); for (ParseTree child : entryContext.children) { treeWalker.walk(listener, child); } - - collector.addToken(Token.fileEnd(file)); } catch (IOException exception) { throw new ParsingException(file, exception.getMessage(), exception); } + collector.addFileEndToken(); } /** @@ -83,10 +97,7 @@ private void parseFile(File file, TokenCollector collector) throws ParsingExcept protected abstract ParserRuleContext getEntryContext(T parser); /** - * Creates the listener - * @param collector The token collector - * @param currentFile The current file - * @return The parser + * @return The listener. Should be created once statically since it never changes. */ - protected abstract AbstractAntlrListener createListener(TokenCollector collector, File currentFile); + protected abstract AbstractAntlrListener getListener(); } diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java new file mode 100644 index 0000000000..cad061e190 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java @@ -0,0 +1,122 @@ +package de.jplag.antlr; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.*; + +import org.antlr.v4.runtime.Token; + +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; + +/** + * The abstract visitor. + * @param The type of the visited entity. + */ +public abstract class AbstractVisitor { + private final Predicate condition; + private final List>> entryHandlers; + private TokenType entryTokenType; + private Function entrySemantics; + + /** + * @param condition The condition for the visit. + */ + AbstractVisitor(Predicate condition) { + this.condition = condition; + this.entryHandlers = new ArrayList<>(); + } + + /** + * Add an action the visitor runs upon entering the entity. + * @param handler The action, takes the entity and the variable registry as parameter. + * @return Self + */ + public AbstractVisitor onEnter(BiConsumer handler) { + entryHandlers.add(handlerData -> handler.accept(handlerData.entity(), handlerData.variableRegistry())); + return this; + } + + /** + * Add an action the visitor runs upon entering the entity. + * @param handler The action, takes the entity as parameter. + * @return Self + */ + public AbstractVisitor onEnter(Consumer handler) { + entryHandlers.add(handlerData -> handler.accept(handlerData.entity())); + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering the entity. Should only be invoked once per visitor. + * @param tokenType The type of the token. + * @return Self + */ + public AbstractVisitor mapEnter(TokenType tokenType) { + entryTokenType = tokenType; + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering the entity. Should only be invoked once per visitor. + * Alias for {@link #mapEnter(TokenType)}. + * @param tokenType The type of the token. + * @return Self + */ + public AbstractVisitor map(TokenType tokenType) { + mapEnter(tokenType); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics. + * @param semanticsSupplier A function that takes the entity and returns the semantics. + * @return Self + */ + public AbstractVisitor withSemantics(Function semanticsSupplier) { + this.entrySemantics = semanticsSupplier; + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics. + * @param semanticsSupplier A function that returns the semantics. + * @return Self + */ + public AbstractVisitor withSemantics(Supplier semanticsSupplier) { + this.entrySemantics = ignore -> semanticsSupplier.get(); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics of type control. + * @return Self + */ + public AbstractVisitor withControlSemantics() { + withSemantics(CodeSemantics::createControl); + return this; + } + + /** + * @param entity The entity to check. + * @return Whether to visit the entity. + */ + boolean matches(T entity) { + return this.condition.test(entity); + } + + /** + * Enter a given entity, injecting the needed dependencies. + */ + void enter(HandlerData data) { + addToken(data, entryTokenType, entrySemantics, this::extractEnterToken); + entryHandlers.forEach(handler -> handler.accept(data)); + } + + void addToken(HandlerData data, TokenType tokenType, Function semantics, Function extractToken) { + data.collector().addToken(tokenType, semantics, data.entity(), extractToken, data.variableRegistry()); + } + + abstract Token extractEnterToken(T entity); +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java deleted file mode 100644 index c48ffc678f..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java +++ /dev/null @@ -1,53 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.antlr.v4.runtime.ParserRuleContext; - -import de.jplag.TokenType; -import de.jplag.semantics.VariableScope; - -/** - * Builds tokens for {@link ParserRuleContext}s. - * @param The type of context - */ -public class ContextTokenBuilder extends TokenBuilder { - private final ContextTokenBuilderType type; - - ContextTokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file, ContextTokenBuilderType type) { - super(tokenType, condition, collector, file); - this.type = type; - } - - /** - * Adds this builder as a variable to the variable registry - * @param scope The scope of the variable - * @param mutable true, if the variable is mutable - * @param nameGetter The getter for the name, from the current {@link ParserRuleContext} - * @return Self - */ - public ContextTokenBuilder addAsVariable(VariableScope scope, boolean mutable, Function nameGetter) { - addSemanticsHandler((registry, rule) -> registry.registerVariable(nameGetter.apply(rule), scope, mutable)); - return this; - } - - @Override - protected org.antlr.v4.runtime.Token getAntlrToken(T antlrContent) { - if (this.type != ContextTokenBuilderType.STOP) { - return antlrContent.getStart(); - } else { - return antlrContent.getStop(); - } - } - - @Override - protected int getLength(T antlrContent) { - if (this.type != ContextTokenBuilderType.RANGE) { - return super.getLength(antlrContent); - } else { - return antlrContent.getStop().getStopIndex() - antlrContent.getStart().getStartIndex() + 1; - } - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java deleted file mode 100644 index ab7d9231c9..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.jplag.antlr; - -/** - * The types of context token builder. Either start, stop or range. Should only be used internally. - */ -enum ContextTokenBuilderType { - START, - STOP, - RANGE -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java new file mode 100644 index 0000000000..92dd4ae792 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java @@ -0,0 +1,139 @@ +package de.jplag.antlr; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.*; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; + +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; + +/** + * The visitor for nodes, or contexts. + * @param The antlr type of the node. + */ +public class ContextVisitor extends AbstractVisitor { + private final List>> exitHandlers; + private TokenType exitToken; + private Function exitSemantics; + + ContextVisitor(Predicate condition) { + super(condition); + this.exitHandlers = new ArrayList<>(); + } + + /** + * Add an action the visitor runs upon exiting the entity. + * @param handler The action, takes the entity and the variable registry as parameter. + * @return Self + */ + public AbstractVisitor onExit(BiConsumer handler) { + exitHandlers.add(handlerData -> handler.accept(handlerData.entity(), handlerData.variableRegistry())); + return this; + } + + /** + * Add an action the visitor runs upon exiting the entity. + * @param handler The action, takes the entity as parameter. + * @return Self + */ + public AbstractVisitor onExit(Consumer handler) { + exitHandlers.add(handlerData -> handler.accept(handlerData.entity())); + return this; + } + + /** + * Tell the visitor that it should generate a token upon exiting the entity. Should only be invoked once per visitor. + * @param tokenType The type of the token. + * @return Self + */ + public ContextVisitor mapExit(TokenType tokenType) { + exitToken = tokenType; + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering and one upon exiting the entity. Should only be + * invoked once per visitor. + * @param enterTokenType The type of the token generated on enter. + * @param exitTokenType The type of the token generated on exit. + * @return Self + */ + public ContextVisitor mapEnterExit(TokenType enterTokenType, TokenType exitTokenType) { + mapEnter(enterTokenType); + mapExit(exitTokenType); + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering and one upon exiting the entity. Should only be + * invoked once per visitor. Alias for {@link #mapEnterExit(TokenType, TokenType)}. + * @param enterTokenType The type of the token generated on enter. + * @param exitTokenType The type of the token generated on exit. + * @return Self + */ + public ContextVisitor map(TokenType enterTokenType, TokenType exitTokenType) { + mapEnterExit(enterTokenType, exitTokenType); + return this; + } + + @Override + public ContextVisitor withSemantics(Function semantics) { + super.withSemantics(semantics); + this.exitSemantics = semantics; + return this; + } + + @Override + public ContextVisitor withSemantics(Supplier semantics) { + super.withSemantics(semantics); + this.exitSemantics = ignore -> semantics.get(); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics of type loop begin, + * same for the exit and loop end. + * @return Self + */ + public ContextVisitor withLoopSemantics() { + super.withSemantics(CodeSemantics::createLoopBegin); + this.exitSemantics = ignore -> CodeSemantics.createLoopEnd(); + return this; + } + + /** + * Tell the visitor that the entity represents a local scope. + * @return Self + */ + public ContextVisitor addLocalScope() { + onEnter((ignore, variableRegistry) -> variableRegistry.enterLocalScope()); + onExit((ignore, variableRegistry) -> variableRegistry.exitLocalScope()); + return this; + } + + /** + * Tell the visitor that the entity represents a class scope. + * @return Self + */ + public ContextVisitor addClassScope() { + onEnter((ignore, variableRegistry) -> variableRegistry.enterClass()); + onExit((ignore, variableRegistry) -> variableRegistry.exitClass()); + return this; + } + + /** + * Exit a given entity, injecting the needed dependencies. + */ + void exit(HandlerData data) { + addToken(data, exitToken, exitSemantics, ParserRuleContext::getStop); + exitHandlers.forEach(handler -> handler.accept(data)); + } + + Token extractEnterToken(T entity) { + return entity.getStart(); + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java b/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java new file mode 100644 index 0000000000..6c1bb40953 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java @@ -0,0 +1,9 @@ +package de.jplag.antlr; + +import de.jplag.semantics.VariableRegistry; + +/** + * Holds the data passed to the (quasi-static) listeners. + */ +record HandlerData(T entity, VariableRegistry variableRegistry, TokenCollector collector) { +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java new file mode 100644 index 0000000000..39178e0fb2 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java @@ -0,0 +1,48 @@ +package de.jplag.antlr; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.TerminalNode; + +import de.jplag.semantics.VariableRegistry; + +/** + * Internal listener that implements pre-existing antlr methods that are called automatically. This listener is created + * for every file. + */ +class InternalListener implements ParseTreeListener { + private final AbstractAntlrListener listener; + private final TokenCollector collector; + protected final VariableRegistry variableRegistry; + + InternalListener(AbstractAntlrListener listener, TokenCollector collector) { + this.listener = listener; + this.collector = collector; + this.variableRegistry = new VariableRegistry(); + } + + @Override + public void visitTerminal(TerminalNode terminalNode) { + listener.visitTerminal(getHandlerData(terminalNode.getSymbol())); + } + + @Override + public void enterEveryRule(ParserRuleContext rule) { + listener.enterEveryRule(getHandlerData(rule)); + } + + @Override + public void exitEveryRule(ParserRuleContext rule) { + listener.exitEveryRule(getHandlerData(rule)); + } + + @Override + public void visitErrorNode(ErrorNode errorNode) { + // does nothing, because we do not handle error nodes right now. + } + + private HandlerData getHandlerData(T entity) { + return new HandlerData<>(entity, variableRegistry, collector); + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java deleted file mode 100644 index 70422ee29a..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.jplag.antlr; - -/** - * Exception type used internally within the antlr utils. Has to be a {@link RuntimeException}, because it is thrown - * within the antlr listener methods. Should not be thrown outside the antlr utils. - */ -public class InternalListenerException extends RuntimeException { - /** - * New instance - * @param message The message of the exception - */ - public InternalListenerException(String message) { - super(message); - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java deleted file mode 100644 index ea84cf3bd4..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java +++ /dev/null @@ -1,112 +0,0 @@ -package de.jplag.antlr; - -import java.util.function.Consumer; -import java.util.function.Function; - -import org.antlr.v4.runtime.ParserRuleContext; - -import de.jplag.semantics.CodeSemantics; -import de.jplag.semantics.VariableRegistry; -import de.jplag.semantics.VariableScope; - -/** - * Builder for semantics on range mappings - * @param The type of rule - */ -@SuppressWarnings("unused") -public class RangeBuilder { - private final ContextTokenBuilder start; - private final ContextTokenBuilder end; - - /** - * New instance - * @param start The builder for the start token - * @param end The builder for the end token - */ - RangeBuilder(ContextTokenBuilder start, ContextTokenBuilder end) { - this.start = start; - this.end = end; - } - - /** - * Adds a class context to the variable registry - * @return Self - */ - public RangeBuilder addClassContext() { - this.start.addSemanticsHandler(VariableRegistry::enterClass); - this.end.addSemanticsHandler(VariableRegistry::exitClass); - return this; - } - - /** - * Adds a local scope to the variable registry - * @return Self - */ - public RangeBuilder addLocalScope() { - this.start.addSemanticsHandler(VariableRegistry::enterLocalScope); - this.end.addSemanticsHandler(VariableRegistry::exitLocalScope); - return this; - } - - /** - * Adds a semantics handler to the start token builder - * @param handler The handler - * @return Self - */ - public RangeBuilder addStartSemanticHandler(Consumer handler) { - this.start.addSemanticsHandler(handler); - return this; - } - - /** - * Adds a semantic handler to the end token builder - * @param handler The handler - * @return Self - */ - public RangeBuilder addEndSemanticHandler(Consumer handler) { - this.end.addSemanticsHandler(handler); - return this; - } - - /** - * Adds the start token as a variable when it is extracted - * @param scope The scope for the variable - * @param mutable true if the variable is mutable - * @param nameGetter The getter for the name - * @return Self - */ - public RangeBuilder addAsVariableOnStart(VariableScope scope, boolean mutable, Function nameGetter) { - this.start.addAsVariable(scope, mutable, nameGetter); - return this; - } - - /** - * Sets the given semantic for the start token - * @param semantics The semantic - * @return Self - */ - public RangeBuilder withStartSemantics(CodeSemantics semantics) { - this.start.withSemantics(semantics); - return this; - } - - /** - * Sets the given semantic for the end token - * @param semantics The semantic - * @return Self - */ - public RangeBuilder withEndSemantics(CodeSemantics semantics) { - this.end.withSemantics(semantics); - return this; - } - - /** - * Sets a control semantics for both tokens - * @return Self - */ - public RangeBuilder withControlSemantics() { - this.start.withControlSemantics(); - this.end.withControlSemantics(); - return this; - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java deleted file mode 100644 index 3f074e6adb..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.function.Predicate; - -import org.antlr.v4.runtime.Token; - -import de.jplag.TokenType; - -/** - * Builds tokens from terminal antlr nodes - */ -public class TerminalTokenBuilder extends TokenBuilder { - /** - * New instance - * @param tokenType The token type - * @param condition The condition - * @param collector The token collector for the listener - * @param file The file the listener is for - */ - TerminalTokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file) { - super(tokenType, condition, collector, file); - } - - @Override - protected Token getAntlrToken(Token antlrContent) { - return antlrContent; - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java new file mode 100644 index 0000000000..170f5d6279 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java @@ -0,0 +1,19 @@ +package de.jplag.antlr; + +import java.util.function.Predicate; + +import org.antlr.v4.runtime.Token; + +/** + * The visitor for terminals. + */ +public class TerminalVisitor extends AbstractVisitor { + + TerminalVisitor(Predicate condition) { + super(condition); + } + + Token extractEnterToken(Token token) { + return token; + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java deleted file mode 100644 index 08e393ef76..0000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java +++ /dev/null @@ -1,140 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.function.*; -import java.util.logging.Logger; - -import de.jplag.Token; -import de.jplag.TokenType; -import de.jplag.semantics.CodeSemantics; -import de.jplag.semantics.VariableRegistry; - -/** - * Handles the extraction of tokens. Contains information on the appropriate antlr types, the conditions under which the - * token should be extracted and semantics information. - * @param The antlr type being mapped - */ -public abstract class TokenBuilder { - private static final Logger logger = Logger.getLogger(TokenBuilder.class.getName()); - private static final String UNEXPECTED_SEMANTICS = "The listener %s indicates, it does not extract semantics. But the token (%s) has semantics information"; - private static final String MISSING_SEMANTICS = "Tokens should contain semantics, but none were supplied"; - - private final Predicate condition; - protected final TokenType tokenType; - - private Function semanticsSupplier = null; - private final List> semanticsHandler; - - protected final TokenCollector tokenCollector; - protected final File file; - - /** - * New instance - * @param tokenType The token type - * @param condition The condition - * @param collector The token collector for the listener - * @param file The file the listener is for - */ - TokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file) { - this.condition = condition; - this.tokenType = tokenType; - this.tokenCollector = collector; - this.file = file; - - this.semanticsHandler = new ArrayList<>(); - } - - /** - * Checks if the token should be extracted for this node. - * @param value The node to check - * @return true, if the token should be extracted - */ - boolean matches(T value) { - return this.condition.test(value); - } - - /** - * Sets the given semantics for the token - * @param semantics The semantics - * @return Self - */ - public TokenBuilder withSemantics(CodeSemantics semantics) { - this.semanticsSupplier = ignore -> semantics; - return this; - } - - /** - * Uses the given function to build the token semantics from the antlr node - * @param function The function - * @return Self - */ - public TokenBuilder withSemantics(Function function) { - this.semanticsSupplier = function; - return this; - } - - /** - * Sets control semantics for the token - * @return Self - */ - public TokenBuilder withControlSemantics() { - withSemantics(CodeSemantics.createControl()); - return this; - } - - /** - * Adds a semantics handler to this builder. This can be used to perform additional operation like calling methods in - * the {@link de.jplag.semantics.VariableRegistry}. - * @param handler The handler function - * @return Self - */ - public TokenBuilder addSemanticsHandler(Consumer handler) { - this.semanticsHandler.add((semantics, rule) -> handler.accept(semantics)); - return this; - } - - /** - * Adds a semantics handler, that can perform additional operations required for semantics using the Semantics context - * objects and the antlr node. - * @param handler The handler - * @return Self - */ - public TokenBuilder addSemanticsHandler(BiConsumer handler) { - this.semanticsHandler.add(handler); - return this; - } - - void createToken(T antlrContent, VariableRegistry semantics) { - org.antlr.v4.runtime.Token antlrToken = getAntlrToken(antlrContent); - - int line = antlrToken.getLine(); - int column = antlrToken.getCharPositionInLine() + 1; - int length = getLength(antlrContent); - - Token token; - if (semantics != null) { - if (semanticsSupplier == null) { - throw new IllegalStateException(MISSING_SEMANTICS); - } - - this.semanticsHandler.forEach(it -> it.accept(semantics, antlrContent)); - token = new Token(this.tokenType, this.file, line, column, length, semanticsSupplier.apply(antlrContent)); - } else { - if (semanticsSupplier != null) { - logger.warning(() -> String.format(UNEXPECTED_SEMANTICS, this.getClass().getName(), this.tokenType.getDescription())); - } - - token = new Token(this.tokenType, this.file, line, column, length); - } - - this.tokenCollector.addToken(token); - } - - protected abstract org.antlr.v4.runtime.Token getAntlrToken(T antlrContent); - - protected int getLength(T antlrContent) { - return getAntlrToken(antlrContent).getText().length(); - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java index 30ab0ce78e..7a284d0151 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java @@ -1,36 +1,80 @@ package de.jplag.antlr; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; +import java.util.logging.Logger; import de.jplag.Token; +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; /** * Collects the tokens during parsing. */ public class TokenCollector { + private static final Logger logger = Logger.getLogger(TokenCollector.class.getName()); private final List collected; + private final boolean extractsSemantics; + private File file; /** - * New instance + * @param extractsSemantics If semantics are extracted */ - public TokenCollector() { + TokenCollector(boolean extractsSemantics) { this.collected = new ArrayList<>(); - } - - /** - * Adds a token to the collector - * @param token The token to add - */ - public void addToken(Token token) { - this.collected.add(token); + this.extractsSemantics = extractsSemantics; } /** * @return All collected tokens */ - public List getTokens() { + List getTokens() { return Collections.unmodifiableList(this.collected); } + + void addToken(TokenType jplagType, Function semanticsSupplier, T entity, + Function extractToken, VariableRegistry variableRegistry) { + if (jplagType == null) { + if (semanticsSupplier != null) { + logger.warning("Received semantics, but no token type, so no token was generated and the semantics discarded"); + } + return; + } + org.antlr.v4.runtime.Token antlrToken = extractToken.apply(entity); + int line = antlrToken.getLine(); + int column = antlrToken.getCharPositionInLine() + 1; + int length = antlrToken.getText().length(); + Token token; + if (extractsSemantics) { + if (semanticsSupplier == null) { + throw new IllegalStateException(String.format("Expected semantics bud did not receive any for token %s", jplagType.getDescription())); + } + CodeSemantics semantics = semanticsSupplier.apply(entity); + token = new Token(jplagType, this.file, line, column, length, semantics); + variableRegistry.updateSemantics(semantics); + } else { + if (semanticsSupplier != null) { + logger.warning(() -> String.format("Received semantics for token %s despite not expecting any", jplagType.getDescription())); + } + token = new Token(jplagType, this.file, line, column, length); + } + addToken(token); + } + + void enterFile(File newFile) { + this.file = newFile; + } + + void addFileEndToken() { + addToken(extractsSemantics ? Token.semanticFileEnd(file) : Token.fileEnd(file)); + // don't need to update semantics because variable registry is new for every file + } + + private void addToken(Token token) { + this.collected.add(token); + } } diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java index 49d30b023b..9073ad9541 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java @@ -2,34 +2,20 @@ import static de.jplag.antlr.testLanguage.TestTokenType.*; -import java.io.File; - -import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TestParser; +import de.jplag.antlr.*; import de.jplag.antlr.TestParser.*; -import de.jplag.antlr.TokenCollector; import de.jplag.semantics.CodeSemantics; import de.jplag.semantics.VariableScope; -public class TestListener extends AbstractAntlrListener { - /** - * New instance - * @param collector The token collector - * @param currentFile The currently processed file - */ - public TestListener(TokenCollector collector, File currentFile) { - super(collector, currentFile, true); - - mapEnter(VarDefContext.class, VARDEF).addAsVariable(VariableScope.FILE, false, rule -> rule.VAR_NAME().getText()) - .withSemantics(CodeSemantics.createKeep()); +class TestListener extends AbstractAntlrListener { - mapRange(CalcExpressionContext.class, ADDITION, rule -> rule.operator() != null && rule.operator().PLUS() != null).withControlSemantics(); - mapRange(OperatorContext.class, SUBTRACTION, rule -> rule.MINUS() != null).withControlSemantics(); - mapEnterExit(SubExpressionContext.class, SUB_EXPRESSION_BEGIN, SUB_EXPRESSION_END) - // .addEndSemanticHandler(registry -> registry.addAllNonLocalVariablesAsReads()) - // does not work here, because there is no class context. Is still here as an example. - .addLocalScope().withControlSemantics(); - mapTerminal(TestParser.NUMBER, NUMBER).withSemantics(CodeSemantics.createKeep()); - mapEnter(VarRefContext.class, VARREF).withSemantics(CodeSemantics.createKeep()); + TestListener() { + visit(VarDefContext.class).map(VARDEF).withSemantics(CodeSemantics::createKeep) + .onEnter((rule, variableRegistry) -> variableRegistry.registerVariable(rule.VAR_NAME().getText(), VariableScope.FILE, false)); + visit(CalcExpressionContext.class, rule -> rule.operator() != null && rule.operator().PLUS() != null).map(ADDITION).withControlSemantics(); + visit(OperatorContext.class, rule -> rule.MINUS() != null).map(SUBTRACTION).withControlSemantics(); + visit(SubExpressionContext.class).map(SUB_EXPRESSION_BEGIN, SUB_EXPRESSION_END).addLocalScope().withControlSemantics(); + visit(TestParser.NUMBER).map(NUMBER).withSemantics(CodeSemantics::createKeep); + visit(VarDefContext.class).map(VARDEF).withSemantics(CodeSemantics::createKeep); } } diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java index 5b292e18dc..c2873a167d 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java @@ -1,7 +1,5 @@ package de.jplag.antlr.testLanguage; -import java.io.File; - import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; @@ -10,6 +8,8 @@ import de.jplag.antlr.*; public class TestParserAdapter extends AbstractAntlrParserAdapter { + private static final TestListener listener = new TestListener(); + @Override protected Lexer createLexer(CharStream input) { return new TestLexer(input); @@ -26,7 +26,7 @@ protected ParserRuleContext getEntryContext(TestParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new TestListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java index 90ef46b3bc..6b2a68c496 100644 --- a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java +++ b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java @@ -2,10 +2,7 @@ import static de.jplag.cpp2.CPPTokenType.*; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; import de.jplag.cpp2.grammar.CPP14Parser; import de.jplag.cpp2.grammar.CPP14Parser.*; @@ -15,59 +12,53 @@ *

* Those cases are covered by {@link SimpleTypeSpecifierContext} and {@link SimpleDeclarationContext} */ -public class CPPListener extends AbstractAntlrListener { - /** - * New instance - * @param collector The token collector the token will be added to - * @param currentFile The currently processed file - */ - public CPPListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); +class CPPListener extends AbstractAntlrListener { - mapEnterExit(ClassSpecifierContext.class, UNION_BEGIN, UNION_END, rule -> rule.classHead().Union() != null); - mapEnterExit(ClassSpecifierContext.class, CLASS_BEGIN, CLASS_END, - rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Class() != null); - mapEnterExit(ClassSpecifierContext.class, STRUCT_BEGIN, STRUCT_END, - rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Struct() != null); - mapEnterExit(EnumSpecifierContext.class, ENUM_BEGIN, ENUM_END); + CPPListener() { + visit(ClassSpecifierContext.class, rule -> rule.classHead().Union() != null).map(UNION_BEGIN, UNION_END); + visit(ClassSpecifierContext.class, rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Class() != null) + .map(CLASS_BEGIN, CLASS_END); + visit(ClassSpecifierContext.class, rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Struct() != null) + .map(STRUCT_BEGIN, STRUCT_END); + visit(EnumSpecifierContext.class).map(ENUM_BEGIN, ENUM_END); - mapEnterExit(FunctionDefinitionContext.class, FUNCTION_BEGIN, FUNCTION_END); + visit(FunctionDefinitionContext.class).map(FUNCTION_BEGIN, FUNCTION_END); - mapEnterExit(IterationStatementContext.class, DO_BEGIN, DO_END, rule -> rule.Do() != null); - mapEnterExit(IterationStatementContext.class, FOR_BEGIN, FOR_END, rule -> rule.For() != null); - mapEnterExit(IterationStatementContext.class, WHILE_BEGIN, WHILE_END, rule -> rule.While() != null && rule.Do() == null); + visit(IterationStatementContext.class, rule -> rule.Do() != null).map(DO_BEGIN, DO_END); + visit(IterationStatementContext.class, rule -> rule.For() != null).map(FOR_BEGIN, FOR_END); + visit(IterationStatementContext.class, rule -> rule.While() != null && rule.Do() == null).map(WHILE_BEGIN, WHILE_END); - mapEnterExit(SelectionStatementContext.class, SWITCH_BEGIN, SWITCH_END, rule -> rule.Switch() != null); - mapEnterExit(SelectionStatementContext.class, IF_BEGIN, IF_END, rule -> rule.If() != null); - mapTerminal(CPP14Parser.Else, ELSE); + visit(SelectionStatementContext.class, rule -> rule.Switch() != null).map(SWITCH_BEGIN, SWITCH_END); + visit(SelectionStatementContext.class, rule -> rule.If() != null).map(IF_BEGIN, IF_END); + visit(CPP14Parser.Else).map(ELSE); - mapEnter(LabeledStatementContext.class, CASE, rule -> rule.Case() != null); - mapEnter(LabeledStatementContext.class, DEFAULT, rule -> rule.Default() != null); + visit(LabeledStatementContext.class, rule -> rule.Case() != null).map(CASE); + visit(LabeledStatementContext.class, rule -> rule.Default() != null).map(DEFAULT); - mapEnter(TryBlockContext.class, TRY); - mapEnterExit(HandlerContext.class, CATCH_BEGIN, CATCH_END); + visit(TryBlockContext.class).map(TRY); + visit(HandlerContext.class).map(CATCH_BEGIN, CATCH_END); - mapEnter(JumpStatementContext.class, BREAK, rule -> rule.Break() != null); - mapEnter(JumpStatementContext.class, CONTINUE, rule -> rule.Continue() != null); - mapEnter(JumpStatementContext.class, GOTO, rule -> rule.Goto() != null); - mapEnter(JumpStatementContext.class, RETURN, rule -> rule.Return() != null); + visit(JumpStatementContext.class, rule -> rule.Break() != null).map(BREAK); + visit(JumpStatementContext.class, rule -> rule.Continue() != null).map(CONTINUE); + visit(JumpStatementContext.class, rule -> rule.Goto() != null).map(GOTO); + visit(JumpStatementContext.class, rule -> rule.Return() != null).map(RETURN); - mapEnter(ThrowExpressionContext.class, THROW); + visit(ThrowExpressionContext.class).map(THROW); - mapEnter(NewExpressionContext.class, NEWCLASS, rule -> rule.newInitializer() != null); - mapEnter(NewExpressionContext.class, NEWARRAY, rule -> rule.newInitializer() == null); + visit(NewExpressionContext.class, rule -> rule.newInitializer() != null).map(NEWCLASS); + visit(NewExpressionContext.class, rule -> rule.newInitializer() == null).map(NEWARRAY); - mapEnter(TemplateDeclarationContext.class, GENERIC); + visit(TemplateDeclarationContext.class).map(GENERIC); - mapEnter(AssignmentOperatorContext.class, ASSIGN); - mapEnter(BraceOrEqualInitializerContext.class, ASSIGN, rule -> rule.Assign() != null); - mapEnter(UnaryExpressionContext.class, ASSIGN, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null); + visit(AssignmentOperatorContext.class).map(ASSIGN); + visit(BraceOrEqualInitializerContext.class, rule -> rule.Assign() != null).map(ASSIGN); + visit(UnaryExpressionContext.class, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null).map(ASSIGN); - mapEnter(StaticAssertDeclarationContext.class, STATIC_ASSERT); - mapEnter(EnumeratorDefinitionContext.class, VARDEF); - mapEnterExit(BracedInitListContext.class, BRACED_INIT_BEGIN, BRACED_INIT_END); + visit(StaticAssertDeclarationContext.class).map(STATIC_ASSERT); + visit(EnumeratorDefinitionContext.class).map(VARDEF); + visit(BracedInitListContext.class).map(BRACED_INIT_BEGIN, BRACED_INIT_END); - mapEnter(SimpleTypeSpecifierContext.class, VARDEF, rule -> { + visit(SimpleTypeSpecifierContext.class, rule -> { if (hasAncestor(rule, MemberdeclarationContext.class, FunctionDefinitionContext.class)) { return true; } @@ -80,23 +71,23 @@ public CPPListener(TokenCollector collector, File currentFile) { } return false; - }); + }).map(VARDEF); - mapEnter(SimpleDeclarationContext.class, APPLY, rule -> { + visit(SimpleDeclarationContext.class, rule -> { if (!hasAncestor(rule, FunctionBodyContext.class)) { return false; } NoPointerDeclaratorContext noPointerDecl = getDescendant(rule, NoPointerDeclaratorContext.class); return noPointerInFunctionCallContext(noPointerDecl); - }); + }).map(APPLY); - mapEnter(InitDeclaratorContext.class, APPLY, rule -> rule.initializer() != null && rule.initializer().LeftParen() != null); - mapEnter(ParameterDeclarationContext.class, VARDEF); - mapEnter(ConditionalExpressionContext.class, QUESTIONMARK, rule -> rule.Question() != null); + visit(InitDeclaratorContext.class, rule -> rule.initializer() != null && rule.initializer().LeftParen() != null).map(APPLY); + visit(ParameterDeclarationContext.class).map(VARDEF); + visit(ConditionalExpressionContext.class, rule -> rule.Question() != null).map(QUESTIONMARK); - mapEnter(PostfixExpressionContext.class, APPLY, rule -> rule.LeftParen() != null); - mapEnter(PostfixExpressionContext.class, ASSIGN, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null); + visit(PostfixExpressionContext.class, rule -> rule.LeftParen() != null).map(APPLY); + visit(PostfixExpressionContext.class, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null).map(ASSIGN); } /** diff --git a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java index fc73e2b11e..c876023eea 100644 --- a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java +++ b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java @@ -1,7 +1,5 @@ package de.jplag.cpp2; -import java.io.File; - import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; @@ -10,7 +8,6 @@ import de.jplag.AbstractParser; import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.cpp2.grammar.CPP14Lexer; import de.jplag.cpp2.grammar.CPP14Parser; @@ -18,6 +15,8 @@ * The adapter between {@link AbstractParser} and the ANTLR based parser of this language module. */ public class CPPParserAdapter extends AbstractAntlrParserAdapter { + private static final CPPListener listener = new CPPListener(); + @Override protected Lexer createLexer(CharStream input) { return new CPP14Lexer(input); @@ -34,7 +33,7 @@ protected ParserRuleContext getEntryContext(CPP14Parser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new CPPListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java index bb308df615..19a475ffa7 100644 --- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java +++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java @@ -93,59 +93,55 @@ import static de.jplag.kotlin.grammar.KotlinParser.WhenExpressionContext; import static de.jplag.kotlin.grammar.KotlinParser.WhileExpressionContext; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; import de.jplag.kotlin.grammar.KotlinParser; -public class KotlinListener extends AbstractAntlrListener { - public KotlinListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); +class KotlinListener extends AbstractAntlrListener { - this.mapRange(PackageHeaderContext.class, PACKAGE); - this.mapRange(ImportHeaderContext.class, IMPORT); - this.mapEnter(ClassDeclarationContext.class, CLASS_DECLARATION); - this.mapRange(ObjectDeclarationContext.class, OBJECT_DECLARATION); - this.mapRange(CompanionObjectContext.class, COMPANION_DECLARATION); - this.mapRange(TypeParameterContext.class, TYPE_PARAMETER); - this.mapRange(PrimaryConstructorContext.class, CONSTRUCTOR); - this.mapRange(ClassParameterContext.class, PROPERTY_DECLARATION); - this.mapEnterExit(ClassBodyContext.class, CLASS_BODY_BEGIN, CLASS_BODY_END); - this.mapEnterExit(EnumClassBodyContext.class, ENUM_CLASS_BODY_BEGIN, ENUM_CLASS_BODY_END); - this.mapEnter(EnumEntryContext.class, ENUM_ENTRY); - this.mapRange(SecondaryConstructorContext.class, CONSTRUCTOR); - this.mapEnter(PropertyDeclarationContext.class, PROPERTY_DECLARATION); - this.mapEnter(AnonymousInitializerContext.class, INITIALIZER); - this.mapEnterExit(InitBlockContext.class, INITIALIZER_BODY_START, INITIALIZER_BODY_END); - this.mapEnter(FunctionDeclarationContext.class, FUNCTION); - this.mapEnter(GetterContext.class, GETTER); - this.mapEnter(SetterContext.class, SETTER); - this.mapRange(FunctionValueParameterContext.class, FUNCTION_PARAMETER); - this.mapEnterExit(FunctionBodyContext.class, FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); - this.mapEnterExit(FunctionLiteralContext.class, FUNCTION_LITERAL_BEGIN, FUNCTION_LITERAL_END); - this.mapEnterExit(ForExpressionContext.class, FOR_EXPRESSION_BEGIN, FOR_EXPRESSION_END); - this.mapEnterExit(IfExpressionContext.class, IF_EXPRESSION_BEGIN, IF_EXPRESSION_END); - this.mapEnterExit(WhileExpressionContext.class, WHILE_EXPRESSION_START, WHILE_EXPRESSION_END); - this.mapEnterExit(DoWhileExpressionContext.class, DO_WHILE_EXPRESSION_START, DO_WHILE_EXPRESSION_END); - this.mapEnter(TryExpressionContext.class, TRY_EXPRESSION); - this.mapEnterExit(TryBodyContext.class, TRY_BODY_START, TRY_BODY_END); - this.mapEnter(CatchStatementContext.class, CATCH); - this.mapEnterExit(CatchBodyContext.class, CATCH_BODY_START, CATCH_BODY_END); - this.mapEnter(FinallyStatementContext.class, FINALLY); - this.mapEnterExit(FinallyBodyContext.class, FINALLY_BODY_START, FINALLY_BODY_END); - this.mapEnterExit(WhenExpressionContext.class, WHEN_EXPRESSION_START, WHEN_EXPRESSION_END); - this.mapEnter(WhenConditionContext.class, WHEN_CONDITION); - this.mapEnterExit(ControlStructureBodyContext.class, CONTROL_STRUCTURE_BODY_START, CONTROL_STRUCTURE_BODY_END); - this.mapEnter(VariableDeclarationContext.class, VARIABLE_DECLARATION); - this.mapRange(ConstructorInvocationContext.class, CREATE_OBJECT); - this.mapRange(CallSuffixContext.class, FUNCTION_INVOCATION); - this.mapEnter(AssignmentOperatorContext.class, ASSIGNMENT); + KotlinListener() { + visit(PackageHeaderContext.class).map(PACKAGE); + visit(ImportHeaderContext.class).map(IMPORT); + visit(ClassDeclarationContext.class).map(CLASS_DECLARATION); + visit(ObjectDeclarationContext.class).map(OBJECT_DECLARATION); + visit(CompanionObjectContext.class).map(COMPANION_DECLARATION); + visit(TypeParameterContext.class).map(TYPE_PARAMETER); + visit(PrimaryConstructorContext.class).map(CONSTRUCTOR); + visit(ClassParameterContext.class).map(PROPERTY_DECLARATION); + visit(ClassBodyContext.class).map(CLASS_BODY_BEGIN, CLASS_BODY_END); + visit(EnumClassBodyContext.class).map(ENUM_CLASS_BODY_BEGIN, ENUM_CLASS_BODY_END); + visit(EnumEntryContext.class).map(ENUM_ENTRY); + visit(SecondaryConstructorContext.class).map(CONSTRUCTOR); + visit(PropertyDeclarationContext.class).map(PROPERTY_DECLARATION); + visit(AnonymousInitializerContext.class).map(INITIALIZER); + visit(InitBlockContext.class).map(INITIALIZER_BODY_START, INITIALIZER_BODY_END); + visit(FunctionDeclarationContext.class).map(FUNCTION); + visit(GetterContext.class).map(GETTER); + visit(SetterContext.class).map(SETTER); + visit(FunctionValueParameterContext.class).map(FUNCTION_PARAMETER); + visit(FunctionBodyContext.class).map(FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); + visit(FunctionLiteralContext.class).map(FUNCTION_LITERAL_BEGIN, FUNCTION_LITERAL_END); + visit(ForExpressionContext.class).map(FOR_EXPRESSION_BEGIN, FOR_EXPRESSION_END); + visit(IfExpressionContext.class).map(IF_EXPRESSION_BEGIN, IF_EXPRESSION_END); + visit(WhileExpressionContext.class).map(WHILE_EXPRESSION_START, WHILE_EXPRESSION_END); + visit(DoWhileExpressionContext.class).map(DO_WHILE_EXPRESSION_START, DO_WHILE_EXPRESSION_END); + visit(TryExpressionContext.class).map(TRY_EXPRESSION); + visit(TryBodyContext.class).map(TRY_BODY_START, TRY_BODY_END); + visit(CatchStatementContext.class).map(CATCH); + visit(CatchBodyContext.class).map(CATCH_BODY_START, CATCH_BODY_END); + visit(FinallyStatementContext.class).map(FINALLY); + visit(FinallyBodyContext.class).map(FINALLY_BODY_START, FINALLY_BODY_END); + visit(WhenExpressionContext.class).map(WHEN_EXPRESSION_START, WHEN_EXPRESSION_END); + visit(WhenConditionContext.class).map(WHEN_CONDITION); + visit(ControlStructureBodyContext.class).map(CONTROL_STRUCTURE_BODY_START, CONTROL_STRUCTURE_BODY_END); + visit(VariableDeclarationContext.class).map(VARIABLE_DECLARATION); + visit(ConstructorInvocationContext.class).map(CREATE_OBJECT); + visit(CallSuffixContext.class).map(FUNCTION_INVOCATION); + visit(AssignmentOperatorContext.class).map(ASSIGNMENT); - this.mapTerminal(KotlinParser.THROW, THROW); - this.mapTerminal(KotlinParser.RETURN, RETURN); - this.mapTerminal(KotlinParser.CONTINUE, CONTINUE); - this.mapTerminal(KotlinParser.BREAK, BREAK); - this.mapTerminal(KotlinParser.BREAK_AT, BREAK); + visit(KotlinParser.THROW).map(THROW); + visit(KotlinParser.RETURN).map(RETURN); + visit(KotlinParser.CONTINUE).map(CONTINUE); + visit(KotlinParser.BREAK).map(BREAK); + visit(KotlinParser.BREAK_AT).map(BREAK); } } diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java index 49537918d5..0fed85032b 100644 --- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java +++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java @@ -1,16 +1,15 @@ package de.jplag.kotlin; -import java.io.File; - import org.antlr.v4.runtime.*; import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.kotlin.grammar.KotlinLexer; import de.jplag.kotlin.grammar.KotlinParser; public class KotlinParserAdapter extends AbstractAntlrParserAdapter { + private static final KotlinListener listener = new KotlinListener(); + @Override protected Lexer createLexer(CharStream input) { return new KotlinLexer(input); @@ -27,7 +26,7 @@ protected ParserRuleContext getEntryContext(KotlinParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new KotlinListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java index ed0efc65f8..24e70a9ca5 100644 --- a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java @@ -110,133 +110,123 @@ import static de.jplag.llvmir.grammar.LLVMIRParser.ZExtExprContext; import static de.jplag.llvmir.grammar.LLVMIRParser.ZExtInstContext; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; /** * Extracts tokens from the ANTLR parse tree. The token abstraction includes nesting tokens for functions and basic * blocks and separate tokens for different elements. These include binary and bitwise instructions, memory operations, * terminator instructions, conversions, global variables, type definitions, constants, and others. */ -public class LLVMIRListener extends AbstractAntlrListener { - - /** - * New instance - * @param collector The token collector the token will be added to - * @param currentFile The currently processed file - */ - public LLVMIRListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); +class LLVMIRListener extends AbstractAntlrListener { - this.mapEnter(SourceFilenameContext.class, FILENAME); - this.mapRange(ModuleAsmContext.class, ASSEMBLY); - this.mapEnter(TypeDefContext.class, TYPE_DEFINITION); - this.mapRange(GlobalDeclContext.class, GLOBAL_VARIABLE); - this.mapRange(GlobalDefContext.class, GLOBAL_VARIABLE); - this.mapRange(FuncDeclContext.class, FUNCTION_DECLARATION); - this.mapRange(FuncDefContext.class, FUNCTION_DEFINITION); - this.mapEnterExit(FuncBodyContext.class, FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); - this.mapEnterExit(BasicBlockContext.class, BASIC_BLOCK_BEGIN, BASIC_BLOCK_END); - this.mapRange(RetTermContext.class, RETURN); - this.mapRange(BrTermContext.class, BRANCH); - this.mapRange(CondBrTermContext.class, CONDITIONAL_BRANCH); - this.mapRange(SwitchTermContext.class, SWITCH); - this.mapRange(IndirectBrTermContext.class, BRANCH); - this.mapRange(ResumeTermContext.class, RESUME); - this.mapRange(CatchRetTermContext.class, CATCH_RETURN); - this.mapRange(CleanupRetTermContext.class, CLEAN_UP_RETURN); - this.mapRange(InvokeTermContext.class, INVOKE); - this.mapRange(CallBrTermContext.class, CALL_BRANCH); - this.mapRange(CatchSwitchTermContext.class, CATCH_SWITCH); - this.mapRange(Case_Context.class, CASE); - this.mapRange(StructConstContext.class, STRUCTURE); - this.mapRange(ArrayConstContext.class, ARRAY); - this.mapRange(VectorConstContext.class, VECTOR); - this.mapRange(InlineAsmContext.class, ASSEMBLY); - this.mapRange(BitCastExprContext.class, BITCAST); - this.mapRange(GetElementPtrExprContext.class, GET_ELEMENT_POINTER); - this.mapEnter(AddrSpaceCastExprContext.class, CONVERSION); - this.mapEnter(IntToPtrExprContext.class, CONVERSION); - this.mapRange(ICmpExprContext.class, COMPARISON); - this.mapRange(FCmpExprContext.class, COMPARISON); - this.mapRange(SelectExprContext.class, SELECT); - this.mapEnter(TruncExprContext.class, CONVERSION); - this.mapEnter(ZExtExprContext.class, CONVERSION); - this.mapEnter(SExtExprContext.class, CONVERSION); - this.mapEnter(FpTruncExprContext.class, CONVERSION); - this.mapEnter(FpExtExprContext.class, CONVERSION); - this.mapEnter(FpToUiExprContext.class, CONVERSION); - this.mapEnter(FpToSiExprContext.class, CONVERSION); - this.mapEnter(UiToFpExprContext.class, CONVERSION); - this.mapEnter(SiToFpExprContext.class, CONVERSION); - this.mapEnter(PtrToIntExprContext.class, CONVERSION); - this.mapEnter(ExtractElementExprContext.class, EXTRACT_ELEMENT); - this.mapEnter(InsertElementExprContext.class, INSERT_ELEMENT); - this.mapEnter(ShuffleVectorExprContext.class, SHUFFLE_VECTOR); - this.mapRange(ShlExprContext.class, SHIFT); - this.mapRange(LShrExprContext.class, SHIFT); - this.mapRange(AShrExprContext.class, SHIFT); - this.mapRange(AndExprContext.class, AND); - this.mapRange(OrExprContext.class, OR); - this.mapRange(XorExprContext.class, XOR); - this.mapRange(AddExprContext.class, ADDITION); - this.mapRange(SubExprContext.class, SUBTRACTION); - this.mapRange(MulExprContext.class, MULTIPLICATION); - this.mapRange(StoreInstContext.class, STORE); - this.mapRange(FenceInstContext.class, FENCE); - this.mapRange(AddInstContext.class, ADDITION); - this.mapRange(FAddInstContext.class, ADDITION); - this.mapRange(SubInstContext.class, SUBTRACTION); - this.mapRange(FSubInstContext.class, SUBTRACTION); - this.mapRange(MulInstContext.class, MULTIPLICATION); - this.mapRange(FMulInstContext.class, MULTIPLICATION); - this.mapRange(UDivInstContext.class, DIVISION); - this.mapRange(SDivInstContext.class, DIVISION); - this.mapRange(FDivInstContext.class, DIVISION); - this.mapRange(URemInstContext.class, REMAINDER); - this.mapRange(SRemInstContext.class, REMAINDER); - this.mapRange(FRemInstContext.class, REMAINDER); - this.mapRange(ShlInstContext.class, SHIFT); - this.mapRange(LShrInstContext.class, SHIFT); - this.mapRange(AShrInstContext.class, SHIFT); - this.mapRange(AndInstContext.class, AND); - this.mapRange(OrInstContext.class, OR); - this.mapRange(XorInstContext.class, XOR); - this.mapEnter(ExtractElementInstContext.class, EXTRACT_ELEMENT); - this.mapEnter(InsertElementInstContext.class, INSERT_ELEMENT); - this.mapEnter(ShuffleVectorInstContext.class, SHUFFLE_VECTOR); - this.mapRange(ExtractValueInstContext.class, EXTRACT_VALUE); - this.mapRange(InsertValueInstContext.class, INSERT_VALUE); - this.mapRange(AllocaInstContext.class, ALLOCATION); - this.mapRange(LoadInstContext.class, LOAD); - this.mapRange(CmpXchgInstContext.class, COMPARE_EXCHANGE); - this.mapRange(AtomicRMWInstContext.class, ATOMIC_READ_MODIFY_WRITE); - this.mapRange(GetElementPtrInstContext.class, GET_ELEMENT_POINTER); - this.mapEnter(TruncInstContext.class, CONVERSION); - this.mapEnter(ZExtInstContext.class, CONVERSION); - this.mapEnter(SExtInstContext.class, CONVERSION); - this.mapEnter(FpTruncInstContext.class, CONVERSION); - this.mapEnter(FpExtInstContext.class, CONVERSION); - this.mapEnter(FpToUiInstContext.class, CONVERSION); - this.mapEnter(FpToSiInstContext.class, CONVERSION); - this.mapEnter(UiToFpInstContext.class, CONVERSION); - this.mapEnter(SiToFpInstContext.class, CONVERSION); - this.mapEnter(PtrToIntInstContext.class, CONVERSION); - this.mapEnter(IntToPtrInstContext.class, CONVERSION); - this.mapRange(BitCastInstContext.class, BITCAST); - this.mapEnter(AddrSpaceCastInstContext.class, CONVERSION); - this.mapRange(ICmpInstContext.class, COMPARISON); - this.mapRange(FCmpInstContext.class, COMPARISON); - this.mapRange(PhiInstContext.class, PHI); - this.mapRange(SelectInstContext.class, SELECT); - this.mapRange(CallInstContext.class, CALL); - this.mapRange(VaargInstContext.class, VARIABLE_ARGUMENT); - this.mapRange(LandingPadInstContext.class, LANDING_PAD); - this.mapRange(CatchPadInstContext.class, CATCH_PAD); - this.mapRange(CleanupPadInstContext.class, CLEAN_UP_PAD); - this.mapRange(ClauseContext.class, CLAUSE); - this.mapRange(AtomicOrderingContext.class, ATOMIC_ORDERING); + LLVMIRListener() { + visit(SourceFilenameContext.class).map(FILENAME); + visit(ModuleAsmContext.class).map(ASSEMBLY); + visit(TypeDefContext.class).map(TYPE_DEFINITION); + visit(GlobalDeclContext.class).map(GLOBAL_VARIABLE); + visit(GlobalDefContext.class).map(GLOBAL_VARIABLE); + visit(FuncDeclContext.class).map(FUNCTION_DECLARATION); + visit(FuncDefContext.class).map(FUNCTION_DEFINITION); + visit(FuncBodyContext.class).map(FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); + visit(BasicBlockContext.class).map(BASIC_BLOCK_BEGIN, BASIC_BLOCK_END); + visit(RetTermContext.class).map(RETURN); + visit(BrTermContext.class).map(BRANCH); + visit(CondBrTermContext.class).map(CONDITIONAL_BRANCH); + visit(SwitchTermContext.class).map(SWITCH); + visit(IndirectBrTermContext.class).map(BRANCH); + visit(ResumeTermContext.class).map(RESUME); + visit(CatchRetTermContext.class).map(CATCH_RETURN); + visit(CleanupRetTermContext.class).map(CLEAN_UP_RETURN); + visit(InvokeTermContext.class).map(INVOKE); + visit(CallBrTermContext.class).map(CALL_BRANCH); + visit(CatchSwitchTermContext.class).map(CATCH_SWITCH); + visit(Case_Context.class).map(CASE); + visit(StructConstContext.class).map(STRUCTURE); + visit(ArrayConstContext.class).map(ARRAY); + visit(VectorConstContext.class).map(VECTOR); + visit(InlineAsmContext.class).map(ASSEMBLY); + visit(BitCastExprContext.class).map(BITCAST); + visit(GetElementPtrExprContext.class).map(GET_ELEMENT_POINTER); + visit(AddrSpaceCastExprContext.class).map(CONVERSION); + visit(IntToPtrExprContext.class).map(CONVERSION); + visit(ICmpExprContext.class).map(COMPARISON); + visit(FCmpExprContext.class).map(COMPARISON); + visit(SelectExprContext.class).map(SELECT); + visit(TruncExprContext.class).map(CONVERSION); + visit(ZExtExprContext.class).map(CONVERSION); + visit(SExtExprContext.class).map(CONVERSION); + visit(FpTruncExprContext.class).map(CONVERSION); + visit(FpExtExprContext.class).map(CONVERSION); + visit(FpToUiExprContext.class).map(CONVERSION); + visit(FpToSiExprContext.class).map(CONVERSION); + visit(UiToFpExprContext.class).map(CONVERSION); + visit(SiToFpExprContext.class).map(CONVERSION); + visit(PtrToIntExprContext.class).map(CONVERSION); + visit(ExtractElementExprContext.class).map(EXTRACT_ELEMENT); + visit(InsertElementExprContext.class).map(INSERT_ELEMENT); + visit(ShuffleVectorExprContext.class).map(SHUFFLE_VECTOR); + visit(ShlExprContext.class).map(SHIFT); + visit(LShrExprContext.class).map(SHIFT); + visit(AShrExprContext.class).map(SHIFT); + visit(AndExprContext.class).map(AND); + visit(OrExprContext.class).map(OR); + visit(XorExprContext.class).map(XOR); + visit(AddExprContext.class).map(ADDITION); + visit(SubExprContext.class).map(SUBTRACTION); + visit(MulExprContext.class).map(MULTIPLICATION); + visit(StoreInstContext.class).map(STORE); + visit(FenceInstContext.class).map(FENCE); + visit(AddInstContext.class).map(ADDITION); + visit(FAddInstContext.class).map(ADDITION); + visit(SubInstContext.class).map(SUBTRACTION); + visit(FSubInstContext.class).map(SUBTRACTION); + visit(MulInstContext.class).map(MULTIPLICATION); + visit(FMulInstContext.class).map(MULTIPLICATION); + visit(UDivInstContext.class).map(DIVISION); + visit(SDivInstContext.class).map(DIVISION); + visit(FDivInstContext.class).map(DIVISION); + visit(URemInstContext.class).map(REMAINDER); + visit(SRemInstContext.class).map(REMAINDER); + visit(FRemInstContext.class).map(REMAINDER); + visit(ShlInstContext.class).map(SHIFT); + visit(LShrInstContext.class).map(SHIFT); + visit(AShrInstContext.class).map(SHIFT); + visit(AndInstContext.class).map(AND); + visit(OrInstContext.class).map(OR); + visit(XorInstContext.class).map(XOR); + visit(ExtractElementInstContext.class).map(EXTRACT_ELEMENT); + visit(InsertElementInstContext.class).map(INSERT_ELEMENT); + visit(ShuffleVectorInstContext.class).map(SHUFFLE_VECTOR); + visit(ExtractValueInstContext.class).map(EXTRACT_VALUE); + visit(InsertValueInstContext.class).map(INSERT_VALUE); + visit(AllocaInstContext.class).map(ALLOCATION); + visit(LoadInstContext.class).map(LOAD); + visit(CmpXchgInstContext.class).map(COMPARE_EXCHANGE); + visit(AtomicRMWInstContext.class).map(ATOMIC_READ_MODIFY_WRITE); + visit(GetElementPtrInstContext.class).map(GET_ELEMENT_POINTER); + visit(TruncInstContext.class).map(CONVERSION); + visit(ZExtInstContext.class).map(CONVERSION); + visit(SExtInstContext.class).map(CONVERSION); + visit(FpTruncInstContext.class).map(CONVERSION); + visit(FpExtInstContext.class).map(CONVERSION); + visit(FpToUiInstContext.class).map(CONVERSION); + visit(FpToSiInstContext.class).map(CONVERSION); + visit(UiToFpInstContext.class).map(CONVERSION); + visit(SiToFpInstContext.class).map(CONVERSION); + visit(PtrToIntInstContext.class).map(CONVERSION); + visit(IntToPtrInstContext.class).map(CONVERSION); + visit(BitCastInstContext.class).map(BITCAST); + visit(AddrSpaceCastInstContext.class).map(CONVERSION); + visit(ICmpInstContext.class).map(COMPARISON); + visit(FCmpInstContext.class).map(COMPARISON); + visit(PhiInstContext.class).map(PHI); + visit(SelectInstContext.class).map(SELECT); + visit(CallInstContext.class).map(CALL); + visit(VaargInstContext.class).map(VARIABLE_ARGUMENT); + visit(LandingPadInstContext.class).map(LANDING_PAD); + visit(CatchPadInstContext.class).map(CATCH_PAD); + visit(CleanupPadInstContext.class).map(CLEAN_UP_PAD); + visit(ClauseContext.class).map(CLAUSE); + visit(AtomicOrderingContext.class).map(ATOMIC_ORDERING); } } diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java index 0d96e301ec..edbe941484 100644 --- a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java @@ -1,13 +1,10 @@ package de.jplag.llvmir; -import java.io.File; - import org.antlr.v4.runtime.*; import de.jplag.AbstractParser; import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.llvmir.grammar.LLVMIRLexer; import de.jplag.llvmir.grammar.LLVMIRParser; @@ -15,6 +12,8 @@ * The adapter between {@link AbstractParser} and the ANTLR based parser of this language module. */ public class LLVMIRParserAdapter extends AbstractAntlrParserAdapter { + private static final LLVMIRListener listener = new LLVMIRListener(); + @Override protected Lexer createLexer(CharStream input) { return new LLVMIRLexer(input); @@ -31,7 +30,7 @@ protected ParserRuleContext getEntryContext(LLVMIRParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new LLVMIRListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/scala/pom.xml b/languages/scala/pom.xml index 67138fb224..8eb11b59a8 100644 --- a/languages/scala/pom.xml +++ b/languages/scala/pom.xml @@ -10,7 +10,7 @@ scala - 2.13.11 + 2.13.12 2.13 @@ -25,7 +25,7 @@ org.scalameta scalameta_${scala.compat.version} - 4.8.9 + 4.8.11 diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java index a71b3dc848..31dc213780 100644 --- a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java @@ -76,76 +76,71 @@ import static de.jplag.typescript.grammar.TypeScriptParser.VariableDeclarationContext; import static de.jplag.typescript.grammar.TypeScriptParser.WhileStatementContext; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; /** * This class is responsible for mapping parsed TypeScript to the internal Token structure */ public class TypeScriptListener extends AbstractAntlrListener { - public TypeScriptListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); - - this.mapRange(ImportStatementContext.class, IMPORT); - this.mapTerminal(Export, EXPORT); - this.mapEnterExit(NamespaceDeclarationContext.class, NAMESPACE_BEGIN, NAMESPACE_END); + public TypeScriptListener() { + visit(ImportStatementContext.class).map(IMPORT); + visit(Export).map(EXPORT); + visit(NamespaceDeclarationContext.class).map(NAMESPACE_BEGIN, NAMESPACE_END); - this.mapEnterExit(ClassDeclarationContext.class, CLASS_BEGIN, CLASS_END); - this.mapEnterExit(GetterSetterDeclarationExpressionContext.class, METHOD_BEGIN, METHOD_END); - this.mapRange(PropertyDeclarationExpressionContext.class, DECLARATION); - this.mapRange(PropertyDeclarationExpressionContext.class, ASSIGNMENT, it -> it.initializer() != null); - this.mapRange(PropertySetterContext.class, ASSIGNMENT); - this.mapRange(PropertySignaturContext.class, DECLARATION); + visit(ClassDeclarationContext.class).map(CLASS_BEGIN, CLASS_END); + visit(GetterSetterDeclarationExpressionContext.class).map(METHOD_BEGIN, METHOD_END); + visit(PropertyDeclarationExpressionContext.class).map(DECLARATION); + visit(PropertyDeclarationExpressionContext.class, it -> it.initializer() != null).map(ASSIGNMENT); + visit(PropertySetterContext.class).map(ASSIGNMENT); + visit(PropertySignaturContext.class).map(DECLARATION); - this.mapEnterExit(InterfaceDeclarationContext.class, INTERFACE_BEGIN, INTERFACE_END); - this.mapEnterExit(ConstructorDeclarationContext.class, CONSTRUCTOR_BEGIN, CONSTRUCTOR_END); + visit(InterfaceDeclarationContext.class).map(INTERFACE_BEGIN, INTERFACE_END); + visit(ConstructorDeclarationContext.class).map(CONSTRUCTOR_BEGIN, CONSTRUCTOR_END); - this.mapEnterExit(EnumDeclarationContext.class, ENUM_BEGIN, ENUM_END); - this.mapRange(EnumMemberContext.class, ENUM_MEMBER); + visit(EnumDeclarationContext.class).map(ENUM_BEGIN, ENUM_END); + visit(EnumMemberContext.class).map(ENUM_MEMBER); - this.mapRange(VariableDeclarationContext.class, DECLARATION); - this.mapRange(VariableDeclarationContext.class, ASSIGNMENT, it -> it.Assign() != null); + visit(VariableDeclarationContext.class).map(DECLARATION); + visit(VariableDeclarationContext.class, it -> it.Assign() != null).map(ASSIGNMENT); - this.mapEnterExit(IfStatementContext.class, IF_BEGIN, IF_END); - this.mapTerminal(Else, IF_BEGIN); + visit(IfStatementContext.class).map(IF_BEGIN, IF_END); + visit(Else).map(IF_BEGIN); - this.mapEnterExit(SwitchStatementContext.class, SWITCH_BEGIN, SWITCH_END); - this.mapRange(CaseClauseContext.class, SWITCH_CASE); - this.mapRange(DefaultClauseContext.class, SWITCH_CASE); + visit(SwitchStatementContext.class).map(SWITCH_BEGIN, SWITCH_END); + visit(CaseClauseContext.class).map(SWITCH_CASE); + visit(DefaultClauseContext.class).map(SWITCH_CASE); - this.mapEnterExit(MethodDeclarationExpressionContext.class, METHOD_BEGIN, METHOD_END); + visit(MethodDeclarationExpressionContext.class).map(METHOD_BEGIN, METHOD_END); - this.mapRange(FunctionDeclarationContext.class, DECLARATION); - this.mapRange(FunctionDeclarationContext.class, ASSIGNMENT); - this.mapEnterExit(FunctionDeclarationContext.class, METHOD_BEGIN, METHOD_END); + visit(FunctionDeclarationContext.class).map(DECLARATION); + visit(FunctionDeclarationContext.class).map(ASSIGNMENT); + visit(FunctionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); - this.mapEnterExit(ArrowFunctionDeclarationContext.class, METHOD_BEGIN, METHOD_END); - this.mapEnterExit(FunctionExpressionDeclarationContext.class, METHOD_BEGIN, METHOD_END); + visit(ArrowFunctionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); + visit(FunctionExpressionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); - this.mapEnterExit(WhileStatementContext.class, WHILE_BEGIN, WHILE_END); - this.mapEnterExit(ForStatementContext.class, FOR_BEGIN, FOR_END); - this.mapEnterExit(ForVarStatementContext.class, FOR_BEGIN, FOR_END); - this.mapEnterExit(ForInStatementContext.class, FOR_BEGIN, FOR_END); + visit(WhileStatementContext.class).map(WHILE_BEGIN, WHILE_END); + visit(ForStatementContext.class).map(FOR_BEGIN, FOR_END); + visit(ForVarStatementContext.class).map(FOR_BEGIN, FOR_END); + visit(ForInStatementContext.class).map(FOR_BEGIN, FOR_END); - this.mapRange(TryStatementContext.class, TRY_BEGIN); - this.mapEnterExit(CatchProductionContext.class, CATCH_BEGIN, CATCH_END); - this.mapEnterExit(FinallyProductionContext.class, FINALLY_BEGIN, FINALLY_END); + visit(TryStatementContext.class).map(TRY_BEGIN); + visit(CatchProductionContext.class).map(CATCH_BEGIN, CATCH_END); + visit(FinallyProductionContext.class).map(FINALLY_BEGIN, FINALLY_END); - this.mapRange(BreakStatementContext.class, BREAK); - this.mapRange(ReturnStatementContext.class, RETURN); - this.mapRange(ContinueStatementContext.class, CONTINUE); - this.mapRange(ThrowStatementContext.class, THROW); + visit(BreakStatementContext.class).map(BREAK); + visit(ReturnStatementContext.class).map(RETURN); + visit(ContinueStatementContext.class).map(CONTINUE); + visit(ThrowStatementContext.class).map(THROW); - this.mapRange(AssignmentExpressionContext.class, ASSIGNMENT); - this.mapRange(PostDecreaseExpressionContext.class, ASSIGNMENT); - this.mapRange(PreDecreaseExpressionContext.class, ASSIGNMENT); - this.mapRange(PostIncrementExpressionContext.class, ASSIGNMENT); - this.mapRange(PreIncrementExpressionContext.class, ASSIGNMENT); + visit(AssignmentExpressionContext.class).map(ASSIGNMENT); + visit(PostDecreaseExpressionContext.class).map(ASSIGNMENT); + visit(PreDecreaseExpressionContext.class).map(ASSIGNMENT); + visit(PostIncrementExpressionContext.class).map(ASSIGNMENT); + visit(PreIncrementExpressionContext.class).map(ASSIGNMENT); - this.mapRange(ArgumentsContext.class, FUNCTION_CALL); + visit(ArgumentsContext.class).map(FUNCTION_CALL); } } diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java index 2f7586d1c9..837512053f 100644 --- a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java @@ -1,7 +1,5 @@ package de.jplag.typescript; -import java.io.File; - import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; @@ -9,7 +7,6 @@ import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.typescript.grammar.TypeScriptLexer; import de.jplag.typescript.grammar.TypeScriptParser; @@ -17,7 +14,7 @@ * The Antlr adapter used for the TypeScript language module */ public class TypeScriptParserAdapter extends AbstractAntlrParserAdapter { - + private static final TypeScriptListener listener = new TypeScriptListener(); private final boolean useStrictDefault; /** @@ -46,7 +43,7 @@ protected ParserRuleContext getEntryContext(TypeScriptParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new TypeScriptListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/pom.xml b/pom.xml index 801156d6db..968ba8cca7 100644 --- a/pom.xml +++ b/pom.xml @@ -80,9 +80,9 @@ 2.7.7 4.13.1 - 2.34.0 - 2.28.0 - 2.18.0 + 2.35.0 + 2.29.0 + 2.35.0 1.0.0 @@ -117,7 +117,7 @@ edu.stanford.nlp stanford-corenlp - 4.5.4 + 4.5.5 @@ -330,7 +330,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.0 attach-javadocs diff --git a/report-viewer/package-lock.json b/report-viewer/package-lock.json index 67d41a1efd..3cd65c76e6 100644 --- a/report-viewer/package-lock.json +++ b/report-viewer/package-lock.json @@ -21,34 +21,34 @@ "vue": "^3.3.4", "vue-chartjs": "^5.2.0", "vue-draggable-next": "^2.2.1", - "vue-router": "^4.2.4", + "vue-router": "^4.2.5", "vue-virtual-scroller": "^2.0.0-beta.8" }, "devDependencies": { - "@playwright/test": "^1.37.1", - "@rushstack/eslint-patch": "^1.3.3", - "@types/jsdom": "^21.1.2", - "@types/node": "^20.5.9", + "@playwright/test": "^1.38.1", + "@rushstack/eslint-patch": "^1.4.0", + "@types/jsdom": "^21.1.3", + "@types/node": "^18.18.0", "@vitejs/plugin-vue": "^4.3.4", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.3", "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", "autoprefixer": "^10.4.15", - "eslint": "^8.48.0", + "eslint": "^8.50.0", "eslint-plugin-vue": "^9.17.0", "husky": "^8.0.0", "jsdom": "^22.1.0", "lint-staged": "^14.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.29", + "postcss": "^8.4.30", "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", "vite": "^4.4.9", - "vitest": "^0.34.3", - "vue-tsc": "^1.8.8" + "vitest": "^0.34.4", + "vue-tsc": "^1.8.15" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -483,9 +483,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", - "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -546,9 +546,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -716,28 +716,24 @@ "dev": true }, "node_modules/@playwright/test": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz", - "integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.37.1" + "playwright": "1.38.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz", - "integrity": "sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz", + "integrity": "sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -771,9 +767,9 @@ } }, "node_modules/@types/jsdom": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.2.tgz", - "integrity": "sha512-bGj+7TaCkOwkJfx7HtS9p22Ij0A2aKMuz8a1+owpkxa1wU/HUBy/WAXhdv90uDdVI9rSjGvUrXmLSeA9VP3JeA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.3.tgz", + "integrity": "sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==", "dev": true, "dependencies": { "@types/node": "*", @@ -788,9 +784,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.5.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", - "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "version": "18.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.0.tgz", + "integrity": "sha512-3xA4X31gHT1F1l38ATDIL9GpRLdwVhnEFC8Uikv5ZLlXATwrCYyPq7ZWHxzxc3J/30SUiwiYT+bQe0/XvKlWbw==", "dev": true }, "node_modules/@types/semver": { @@ -1007,13 +1003,13 @@ } }, "node_modules/@vitest/expect": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.3.tgz", - "integrity": "sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.4.tgz", + "integrity": "sha512-XlMKX8HyYUqB8dsY8Xxrc64J2Qs9pKMt2Z8vFTL4mBWXJsg4yoALHzJfDWi8h5nkO4Zua4zjqtapQ/IluVkSnA==", "dev": true, "dependencies": { - "@vitest/spy": "0.34.3", - "@vitest/utils": "0.34.3", + "@vitest/spy": "0.34.4", + "@vitest/utils": "0.34.4", "chai": "^4.3.7" }, "funding": { @@ -1021,12 +1017,12 @@ } }, "node_modules/@vitest/runner": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.3.tgz", - "integrity": "sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.4.tgz", + "integrity": "sha512-hwwdB1StERqUls8oV8YcpmTIpVeJMe4WgYuDongVzixl5hlYLT2G8afhcdADeDeqCaAmZcSgLTLtqkjPQF7x+w==", "dev": true, "dependencies": { - "@vitest/utils": "0.34.3", + "@vitest/utils": "0.34.4", "p-limit": "^4.0.0", "pathe": "^1.1.1" }, @@ -1062,9 +1058,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.3.tgz", - "integrity": "sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.4.tgz", + "integrity": "sha512-GCsh4coc3YUSL/o+BPUo7lHQbzpdttTxL6f4q0jRx2qVGoYz/cyTRDJHbnwks6TILi6560bVWoBpYC10PuTLHw==", "dev": true, "dependencies": { "magic-string": "^0.30.1", @@ -1076,9 +1072,9 @@ } }, "node_modules/@vitest/spy": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.3.tgz", - "integrity": "sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.4.tgz", + "integrity": "sha512-PNU+fd7DUPgA3Ya924b1qKuQkonAW6hL7YUjkON3wmBwSTIlhOSpy04SJ0NrRsEbrXgMMj6Morh04BMf8k+w0g==", "dev": true, "dependencies": { "tinyspy": "^2.1.1" @@ -1088,9 +1084,9 @@ } }, "node_modules/@vitest/utils": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.3.tgz", - "integrity": "sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.4.tgz", + "integrity": "sha512-yR2+5CHhp/K4ySY0Qtd+CAL9f5Yh1aXrKfAT42bq6CtlGPh92jIDDDSg7ydlRow1CP+dys4TrOrbELOyNInHSg==", "dev": true, "dependencies": { "diff-sequences": "^29.4.3", @@ -1102,30 +1098,30 @@ } }, "node_modules/@volar/language-core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.0.tgz", - "integrity": "sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz", + "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==", "dev": true, "dependencies": { - "@volar/source-map": "1.10.0" + "@volar/source-map": "1.10.1" } }, "node_modules/@volar/source-map": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.0.tgz", - "integrity": "sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz", + "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==", "dev": true, "dependencies": { "muggle-string": "^0.3.1" } }, "node_modules/@volar/typescript": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.0.tgz", - "integrity": "sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz", + "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==", "dev": true, "dependencies": { - "@volar/language-core": "1.10.0" + "@volar/language-core": "1.10.1" } }, "node_modules/@vue/compiler-core": { @@ -1218,9 +1214,9 @@ } }, "node_modules/@vue/language-core": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.8.tgz", - "integrity": "sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.15.tgz", + "integrity": "sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==", "dev": true, "dependencies": { "@volar/language-core": "~1.10.0", @@ -1347,13 +1343,13 @@ "dev": true }, "node_modules/@vue/typescript": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.8.tgz", - "integrity": "sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.15.tgz", + "integrity": "sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==", "dev": true, "dependencies": { "@volar/typescript": "~1.10.0", - "@vue/language-core": "1.8.8" + "@vue/language-core": "1.8.15" } }, "node_modules/abab": { @@ -2414,16 +2410,16 @@ } }, "node_modules/eslint": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", - "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.48.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", @@ -4146,9 +4142,9 @@ "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==" }, "node_modules/mlly": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.1.tgz", - "integrity": "sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", "dev": true, "dependencies": { "acorn": "^8.10.0", @@ -4833,10 +4829,28 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/playwright-core": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", - "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -4846,9 +4860,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", "funding": [ { "type": "opencollective", @@ -5084,9 +5098,9 @@ } }, "node_modules/pretty-format": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", - "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -6422,9 +6436,9 @@ } }, "node_modules/vite-node": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.3.tgz", - "integrity": "sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.4.tgz", + "integrity": "sha512-ho8HtiLc+nsmbwZMw8SlghESEE3KxJNp04F/jPUCLVvaURwt0d+r9LxEqCX5hvrrOQ0GSyxbYr5ZfRYhQ0yVKQ==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -6445,19 +6459,19 @@ } }, "node_modules/vitest": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.3.tgz", - "integrity": "sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.4.tgz", + "integrity": "sha512-SE/laOsB6995QlbSE6BtkpXDeVNLJc1u2LHRG/OpnN4RsRzM3GQm4nm3PQCK5OBtrsUqnhzLdnT7se3aeNGdlw==", "dev": true, "dependencies": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.34.3", - "@vitest/runner": "0.34.3", - "@vitest/snapshot": "0.34.3", - "@vitest/spy": "0.34.3", - "@vitest/utils": "0.34.3", + "@vitest/expect": "0.34.4", + "@vitest/runner": "0.34.4", + "@vitest/snapshot": "0.34.4", + "@vitest/spy": "0.34.4", + "@vitest/utils": "0.34.4", "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -6471,8 +6485,8 @@ "strip-literal": "^1.0.1", "tinybench": "^2.5.0", "tinypool": "^0.7.0", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.34.3", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.4", "why-is-node-running": "^2.2.2" }, "bin": { @@ -6620,9 +6634,9 @@ } }, "node_modules/vue-router": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", - "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", + "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", "dependencies": { "@vue/devtools-api": "^6.5.0" }, @@ -6644,13 +6658,13 @@ } }, "node_modules/vue-tsc": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.8.tgz", - "integrity": "sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.15.tgz", + "integrity": "sha512-4DoB3LUj7IToLmggoCxRiFG+QU5lem0nv03m1ocqugXA9rSVoTOEoYYaP8vu8b99Eh+/cCVdYOeIAQ+RsgUYUw==", "dev": true, "dependencies": { - "@vue/language-core": "1.8.8", - "@vue/typescript": "1.8.8", + "@vue/language-core": "1.8.15", + "@vue/typescript": "1.8.15", "semver": "^7.3.8" }, "bin": { diff --git a/report-viewer/package.json b/report-viewer/package.json index 119db51d15..b553013722 100644 --- a/report-viewer/package.json +++ b/report-viewer/package.json @@ -30,33 +30,33 @@ "vue": "^3.3.4", "vue-chartjs": "^5.2.0", "vue-draggable-next": "^2.2.1", - "vue-router": "^4.2.4", + "vue-router": "^4.2.5", "vue-virtual-scroller": "^2.0.0-beta.8" }, "devDependencies": { - "@playwright/test": "^1.37.1", - "@rushstack/eslint-patch": "^1.3.3", - "@types/jsdom": "^21.1.2", - "@types/node": "^20.5.9", + "@playwright/test": "^1.38.1", + "@rushstack/eslint-patch": "^1.4.0", + "@types/jsdom": "^21.1.3", + "@types/node": "^18.18.0", "@vitejs/plugin-vue": "^4.3.4", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.3", "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", "autoprefixer": "^10.4.15", - "eslint": "^8.48.0", + "eslint": "^8.50.0", "eslint-plugin-vue": "^9.17.0", "husky": "^8.0.0", "jsdom": "^22.1.0", "lint-staged": "^14.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.29", + "postcss": "^8.4.30", "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", "vite": "^4.4.9", - "vitest": "^0.34.3", - "vue-tsc": "^1.8.8" + "vitest": "^0.34.4", + "vue-tsc": "^1.8.15" } } diff --git a/report-viewer/src/App.vue b/report-viewer/src/App.vue index 4750912183..094cf305b1 100644 --- a/report-viewer/src/App.vue +++ b/report-viewer/src/App.vue @@ -4,6 +4,7 @@ class="max-w-screen max-h-fit min-h-screen bg-backgorund-light text-black dark:bg-backgorund-dark dark:text-amber-50" > + + + + + + @@ -26,26 +34,30 @@

Distribution of Comparisons:

Options:

- - + @@ -55,11 +67,22 @@

Top Comparisons:

- + + + + +
-