Bladeren bron

Merge branch 'master' into emf

Timur Sağlam 4 jaren geleden
bovenliggende
commit
42c86fc8a9
34 gewijzigde bestanden met toevoegingen van 2853 en 346 verwijderingen
  1. 1 1
      README.md
  2. 1 3
      jplag.frontend.cpp/src/main/javacc/CPP.jj
  3. 29 0
      jplag.frontend.golang/README.md
  4. 45 0
      jplag.frontend.golang/pom.xml
  5. 521 0
      jplag.frontend.golang/src/main/antlr4/de/jplag/golang/grammar/GoLexer.g4
  6. 380 0
      jplag.frontend.golang/src/main/antlr4/de/jplag/golang/grammar/GoParser.g4
  7. 59 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/GoParserAdapter.java
  8. 115 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/GoToken.java
  9. 96 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/GoTokenConstants.java
  10. 535 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/JPlagGoListener.java
  11. 49 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/Language.java
  12. 26 0
      jplag.frontend.golang/src/main/java/de/jplag/golang/grammar/GoParserBase.java
  13. 175 0
      jplag.frontend.golang/src/test/java/de/jplag/golang/GoFrontendTest.java
  14. 20 0
      jplag.frontend.golang/src/test/java/de/jplag/golang/GoTokenTestUtils.java
  15. 167 0
      jplag.frontend.golang/src/test/resources/de/jplag/golang/Complete.go
  16. 21 23
      jplag.frontend.text/src/main/antlr/text.g
  17. 5 2
      jplag.frontend.text/src/main/java/de/jplag/text/AntlrParserToken.java
  18. 18 9
      jplag.frontend.text/src/main/java/de/jplag/text/InputState.java
  19. 10 5
      jplag.frontend.text/src/main/java/de/jplag/text/Language.java
  20. 0 87
      jplag.frontend.text/src/main/java/de/jplag/text/Parser.java
  21. 84 0
      jplag.frontend.text/src/main/java/de/jplag/text/ParserAdapter.java
  22. 6 23
      jplag.frontend.text/src/main/java/de/jplag/text/TextToken.java
  23. 54 0
      jplag.frontend.text/src/test/java/jplag/text/TextFrontendTest.java
  24. 50 0
      jplag.frontend.text/src/test/resources/FutureJavaDoc.txt
  25. 5 1
      jplag/pom.xml
  26. 13 10
      jplag/src/main/java/de/jplag/JPlagComparison.java
  27. 17 1
      jplag/src/main/java/de/jplag/Submission.java
  28. 20 20
      jplag/src/main/java/de/jplag/SubmissionSetBuilder.java
  29. 3 2
      jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java
  30. 2 1
      jplag/src/main/java/de/jplag/options/LanguageOption.java
  31. 7 1
      jplag/src/test/java/de/jplag/special/TokenPrinterTest.java
  32. 10 3
      pom.xml
  33. 301 146
      report-viewer/package-lock.json
  34. 8 8
      report-viewer/package.json

+ 1 - 1
README.md

@@ -48,7 +48,7 @@ Usage: JPlag [ options ] [ <root-dir> ... ] [ -new <new-dir> ... ] [ -old <old-d
 
 
 named arguments:
 named arguments:
   -h, --help       show this help message and exit
   -h, --help       show this help message and exit
-  -l               {java,python3,cpp,csharp,rlang,kotlin,char,text,scheme,emf-metamodel,emf-metamodel-dynamic} Select the language to parse the submissions (default: java)
+  -l               {java,python3,cpp,csharp,golang,kotlin,rlang,char,text,scheme,emf-metamodel,emf-metamodel-dynamic} Select the language to parse the submissions (default: java)
   -bc BC           Path of the directory containing the base code (common framework used in all submissions)
   -bc BC           Path of the directory containing the base code (common framework used in all submissions)
   -v               {quiet,long} Verbosity of the logging (default: quiet)
   -v               {quiet,long} Verbosity of the logging (default: quiet)
   -d               Debug parser. Non-parsable files will be stored (default: false)
   -d               Debug parser. Non-parsable files will be stored (default: false)

+ 1 - 3
jplag.frontend.cpp/src/main/javacc/CPP.jj

@@ -19,10 +19,8 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.InputStream;
 
 
-import de.jplag.cpp.NewlineStream;
-
 public class CPPScanner implements CPPTokenConstants {
 public class CPPScanner implements CPPTokenConstants {
-    private static Scanner delegatingScanner;
+    private Scanner delegatingScanner;
 
 
     public static boolean scanFile(File dir, String fileName, Scanner delegatingScanner) {
     public static boolean scanFile(File dir, String fileName, Scanner delegatingScanner) {
         CPPScanner scanner;
         CPPScanner scanner;

+ 29 - 0
jplag.frontend.golang/README.md

@@ -0,0 +1,29 @@
+# JPlag Go language frontend
+
+The JPlag Go frontend allows the use of JPlag with submissions in Go. <br>
+It is based on the [Golang ANTLR4 grammar](https://github.com/antlr/grammars-v4/tree/master/golang), licensed under BSD-3.
+
+### Go specification compatibility
+
+The underlying grammar definition does not specify which version of Go it is built on. This is what we know:
+ - Number literal prefixes, a feature of go1.13, are included.
+ - Generics, a feature of go1.18, are _not_ included.
+ - Between go1.13 and go1.18, there were no changes to the syntax. So, the grammar should be fully compatible with go1.17, released in mid-2021.
+
+If the grammar is updated to a more recent<a href="#footnote-1"><sup>1</sup></a> syntax definition, this module should surely be updated as well.
+
+### Token Extraction
+
+The choice of tokens is intended to be similar to the Java or C# frontends. Specifically, among others, it includes a range of nesting structures (class and method declarations, control flow expressions) as well as variable declaration, object creation, assignment, and control flow altering keywords. <br>
+Blocks are distinguished by their context, i.e. there are separate `TokenConstants` for `if` blocks, `for` blocks, class bodies, method bodies, array constructors, and the like.
+
+More syntactic elements of Go may turn out to be helpful to include in the future, especially those that are newly introduced.
+
+### Usage
+
+To use the Go frontend, add the `-l golang` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.GO_LANG` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag).
+
+<br>
+
+#### Footnotes
+<section id="footnote-1"><sup>1 </sup>The grammar files are taken from grammar-v4, with the most recent modifiactions in <a href="https://github.com/antlr/grammars-v4/tree/51ecccf87b75e96177287367b96cfa99e9f304b8/golang">commit 51ecccf</a> from April 2022.</section>

+ 45 - 0
jplag.frontend.golang/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>de.jplag</groupId>
+        <artifactId>aggregator</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <artifactId>golang</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-testutils</artifactId>
+            <version>${revision}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr4-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>antlr4</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 521 - 0
jplag.frontend.golang/src/main/antlr4/de/jplag/golang/grammar/GoLexer.g4

@@ -0,0 +1,521 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2017 Sasa Coh, Michał Błotniak
+ Copyright (c) 2019 Ivan Kochurkin, kvanttt@gmail.com, Positive Technologies
+ Copyright (c) 2019 Dmitry Rassadin, flipparassa@gmail.com, Positive Technologies
+ Copyright (c) 2021 Martin Mirchev, mirchevmartin2203@gmail.com
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+    derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * A Go grammar for ANTLR 4 derived from the Go Language Specification
+ * https://golang.org/ref/spec
+ */
+
+lexer grammar GoLexer;
+
+// Keywords
+
+BREAK                  : 'break' -> mode(NLSEMI);
+DEFAULT                : 'default';
+FUNC                   : 'func';
+INTERFACE              : 'interface';
+SELECT                 : 'select';
+CASE                   : 'case';
+DEFER                  : 'defer';
+GO                     : 'go';
+MAP                    : 'map';
+STRUCT                 : 'struct';
+CHAN                   : 'chan';
+ELSE                   : 'else';
+GOTO                   : 'goto';
+PACKAGE                : 'package';
+SWITCH                 : 'switch';
+CONST                  : 'const';
+FALLTHROUGH            : 'fallthrough' -> mode(NLSEMI);
+IF                     : 'if';
+RANGE                  : 'range';
+TYPE                   : 'type';
+CONTINUE               : 'continue' -> mode(NLSEMI);
+FOR                    : 'for';
+IMPORT                 : 'import';
+RETURN                 : 'return' -> mode(NLSEMI);
+VAR                    : 'var';
+
+NIL_LIT                : 'nil' -> mode(NLSEMI);
+
+IDENTIFIER             : LETTER (LETTER | UNICODE_DIGIT)* -> mode(NLSEMI);
+
+// Punctuation
+
+L_PAREN                : '(';
+R_PAREN                : ')' -> mode(NLSEMI);
+L_CURLY                : '{';
+R_CURLY                : '}' -> mode(NLSEMI);
+L_BRACKET              : '[';
+R_BRACKET              : ']' -> mode(NLSEMI);
+ASSIGN                 : '=';
+COMMA                  : ',';
+SEMI                   : ';';
+COLON                  : ':';
+DOT                    : '.';
+PLUS_PLUS              : '++' -> mode(NLSEMI);
+MINUS_MINUS            : '--' -> mode(NLSEMI);
+DECLARE_ASSIGN         : ':=';
+ELLIPSIS               : '...';
+
+// Logical
+
+LOGICAL_OR             : '||';
+LOGICAL_AND            : '&&';
+
+// Relation operators
+
+EQUALS                 : '==';
+NOT_EQUALS             : '!=';
+LESS                   : '<';
+LESS_OR_EQUALS         : '<=';
+GREATER                : '>';
+GREATER_OR_EQUALS      : '>=';
+
+// Arithmetic operators
+
+OR                     : '|';
+DIV                    : '/';
+MOD                    : '%';
+LSHIFT                 : '<<';
+RSHIFT                 : '>>';
+BIT_CLEAR              : '&^';
+
+// Unary operators
+
+EXCLAMATION            : '!';
+
+// Mixed operators
+
+PLUS                   : '+';
+MINUS                  : '-';
+CARET                  : '^';
+STAR                   : '*';
+AMPERSAND              : '&';
+RECEIVE                : '<-';
+
+// Number literals
+
+DECIMAL_LIT            : ('0' | [1-9] ('_'? [0-9])*) -> mode(NLSEMI);
+BINARY_LIT             : '0' [bB] ('_'? BIN_DIGIT)+ -> mode(NLSEMI);
+OCTAL_LIT              : '0' [oO]? ('_'? OCTAL_DIGIT)+ -> mode(NLSEMI);
+HEX_LIT                : '0' [xX]  ('_'? HEX_DIGIT)+ -> mode(NLSEMI);
+
+
+FLOAT_LIT : (DECIMAL_FLOAT_LIT | HEX_FLOAT_LIT) -> mode(NLSEMI);
+
+DECIMAL_FLOAT_LIT      : DECIMALS ('.' DECIMALS? EXPONENT? | EXPONENT)
+                       | '.' DECIMALS EXPONENT?
+                       ;
+
+HEX_FLOAT_LIT          : '0' [xX] HEX_MANTISSA HEX_EXPONENT
+                       ;
+
+fragment HEX_MANTISSA  : ('_'? HEX_DIGIT)+ ('.' ( '_'? HEX_DIGIT )*)?
+                       | '.' HEX_DIGIT ('_'? HEX_DIGIT)*;
+
+fragment HEX_EXPONENT  : [pP] [+-]? DECIMALS;
+
+
+IMAGINARY_LIT          : (DECIMAL_LIT | BINARY_LIT |  OCTAL_LIT | HEX_LIT | FLOAT_LIT) 'i' -> mode(NLSEMI);
+
+// Rune literals
+
+fragment RUNE               : '\'' (UNICODE_VALUE | BYTE_VALUE) '\'';//: '\'' (~[\n\\] | ESCAPED_VALUE) '\'';
+
+RUNE_LIT                : RUNE -> mode(NLSEMI);
+
+
+
+BYTE_VALUE : OCTAL_BYTE_VALUE | HEX_BYTE_VALUE;
+
+OCTAL_BYTE_VALUE: '\\' OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT;
+
+HEX_BYTE_VALUE: '\\' 'x'  HEX_DIGIT HEX_DIGIT;
+
+LITTLE_U_VALUE: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT;
+
+BIG_U_VALUE: '\\' 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT;
+
+// String literals
+
+RAW_STRING_LIT         : '`' ~'`'*                      '`' -> mode(NLSEMI);
+INTERPRETED_STRING_LIT : '"' (~["\\] | ESCAPED_VALUE)*  '"' -> mode(NLSEMI);
+
+// Hidden tokens
+
+WS                     : [ \t]+             -> channel(HIDDEN);
+COMMENT                : '/*' .*? '*/'      -> channel(HIDDEN);
+TERMINATOR             : [\r\n]+            -> channel(HIDDEN);
+LINE_COMMENT           : '//' ~[\r\n]*      -> channel(HIDDEN);
+
+fragment UNICODE_VALUE: ~[\r\n'] | LITTLE_U_VALUE | BIG_U_VALUE | ESCAPED_VALUE;
+
+// Fragments
+
+fragment ESCAPED_VALUE
+    : '\\' ('u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
+           | 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
+           | [abfnrtv\\'"]
+           | OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT
+           | 'x' HEX_DIGIT HEX_DIGIT)
+    ;
+
+fragment DECIMALS
+    : [0-9] ('_'? [0-9])*
+    ;
+
+fragment OCTAL_DIGIT
+    : [0-7]
+    ;
+
+fragment HEX_DIGIT
+    : [0-9a-fA-F]
+    ;
+
+fragment BIN_DIGIT
+    : [01]
+    ;
+
+fragment EXPONENT
+    : [eE] [+-]? DECIMALS
+    ;
+
+fragment LETTER
+    : UNICODE_LETTER
+    | '_'
+    ;
+
+fragment UNICODE_DIGIT
+    : [\p{Nd}]
+
+    /*  [\u0030-\u0039]
+    | [\u0660-\u0669]
+    | [\u06F0-\u06F9]
+    | [\u0966-\u096F]
+    | [\u09E6-\u09EF]
+    | [\u0A66-\u0A6F]
+    | [\u0AE6-\u0AEF]
+    | [\u0B66-\u0B6F]
+    | [\u0BE7-\u0BEF]
+    | [\u0C66-\u0C6F]
+    | [\u0CE6-\u0CEF]
+    | [\u0D66-\u0D6F]
+    | [\u0E50-\u0E59]
+    | [\u0ED0-\u0ED9]
+    | [\u0F20-\u0F29]
+    | [\u1040-\u1049]
+    | [\u1369-\u1371]
+    | [\u17E0-\u17E9]
+    | [\u1810-\u1819]
+    | [\uFF10-\uFF19]*/
+    ;
+
+fragment UNICODE_LETTER
+    : [\p{L}]
+    /*  [\u0041-\u005A]
+    | [\u0061-\u007A]
+    | [\u00AA]
+    | [\u00B5]
+    | [\u00BA]
+    | [\u00C0-\u00D6]
+    | [\u00D8-\u00F6]
+    | [\u00F8-\u021F]
+    | [\u0222-\u0233]
+    | [\u0250-\u02AD]
+    | [\u02B0-\u02B8]
+    | [\u02BB-\u02C1]
+    | [\u02D0-\u02D1]
+    | [\u02E0-\u02E4]
+    | [\u02EE]
+    | [\u037A]
+    | [\u0386]
+    | [\u0388-\u038A]
+    | [\u038C]
+    | [\u038E-\u03A1]
+    | [\u03A3-\u03CE]
+    | [\u03D0-\u03D7]
+    | [\u03DA-\u03F3]
+    | [\u0400-\u0481]
+    | [\u048C-\u04C4]
+    | [\u04C7-\u04C8]
+    | [\u04CB-\u04CC]
+    | [\u04D0-\u04F5]
+    | [\u04F8-\u04F9]
+    | [\u0531-\u0556]
+    | [\u0559]
+    | [\u0561-\u0587]
+    | [\u05D0-\u05EA]
+    | [\u05F0-\u05F2]
+    | [\u0621-\u063A]
+    | [\u0640-\u064A]
+    | [\u0671-\u06D3]
+    | [\u06D5]
+    | [\u06E5-\u06E6]
+    | [\u06FA-\u06FC]
+    | [\u0710]
+    | [\u0712-\u072C]
+    | [\u0780-\u07A5]
+    | [\u0905-\u0939]
+    | [\u093D]
+    | [\u0950]
+    | [\u0958-\u0961]
+    | [\u0985-\u098C]
+    | [\u098F-\u0990]
+    | [\u0993-\u09A8]
+    | [\u09AA-\u09B0]
+    | [\u09B2]
+    | [\u09B6-\u09B9]
+    | [\u09DC-\u09DD]
+    | [\u09DF-\u09E1]
+    | [\u09F0-\u09F1]
+    | [\u0A05-\u0A0A]
+    | [\u0A0F-\u0A10]
+    | [\u0A13-\u0A28]
+    | [\u0A2A-\u0A30]
+    | [\u0A32-\u0A33]
+    | [\u0A35-\u0A36]
+    | [\u0A38-\u0A39]
+    | [\u0A59-\u0A5C]
+    | [\u0A5E]
+    | [\u0A72-\u0A74]
+    | [\u0A85-\u0A8B]
+    | [\u0A8D]
+    | [\u0A8F-\u0A91]
+    | [\u0A93-\u0AA8]
+    | [\u0AAA-\u0AB0]
+    | [\u0AB2-\u0AB3]
+    | [\u0AB5-\u0AB9]
+    | [\u0ABD]
+    | [\u0AD0]
+    | [\u0AE0]
+    | [\u0B05-\u0B0C]
+    | [\u0B0F-\u0B10]
+    | [\u0B13-\u0B28]
+    | [\u0B2A-\u0B30]
+    | [\u0B32-\u0B33]
+    | [\u0B36-\u0B39]
+    | [\u0B3D]
+    | [\u0B5C-\u0B5D]
+    | [\u0B5F-\u0B61]
+    | [\u0B85-\u0B8A]
+    | [\u0B8E-\u0B90]
+    | [\u0B92-\u0B95]
+    | [\u0B99-\u0B9A]
+    | [\u0B9C]
+    | [\u0B9E-\u0B9F]
+    | [\u0BA3-\u0BA4]
+    | [\u0BA8-\u0BAA]
+    | [\u0BAE-\u0BB5]
+    | [\u0BB7-\u0BB9]
+    | [\u0C05-\u0C0C]
+    | [\u0C0E-\u0C10]
+    | [\u0C12-\u0C28]
+    | [\u0C2A-\u0C33]
+    | [\u0C35-\u0C39]
+    | [\u0C60-\u0C61]
+    | [\u0C85-\u0C8C]
+    | [\u0C8E-\u0C90]
+    | [\u0C92-\u0CA8]
+    | [\u0CAA-\u0CB3]
+    | [\u0CB5-\u0CB9]
+    | [\u0CDE]
+    | [\u0CE0-\u0CE1]
+    | [\u0D05-\u0D0C]
+    | [\u0D0E-\u0D10]
+    | [\u0D12-\u0D28]
+    | [\u0D2A-\u0D39]
+    | [\u0D60-\u0D61]
+    | [\u0D85-\u0D96]
+    | [\u0D9A-\u0DB1]
+    | [\u0DB3-\u0DBB]
+    | [\u0DBD]
+    | [\u0DC0-\u0DC6]
+    | [\u0E01-\u0E30]
+    | [\u0E32-\u0E33]
+    | [\u0E40-\u0E46]
+    | [\u0E81-\u0E82]
+    | [\u0E84]
+    | [\u0E87-\u0E88]
+    | [\u0E8A]
+    | [\u0E8D]
+    | [\u0E94-\u0E97]
+    | [\u0E99-\u0E9F]
+    | [\u0EA1-\u0EA3]
+    | [\u0EA5]
+    | [\u0EA7]
+    | [\u0EAA-\u0EAB]
+    | [\u0EAD-\u0EB0]
+    | [\u0EB2-\u0EB3]
+    | [\u0EBD-\u0EC4]
+    | [\u0EC6]
+    | [\u0EDC-\u0EDD]
+    | [\u0F00]
+    | [\u0F40-\u0F6A]
+    | [\u0F88-\u0F8B]
+    | [\u1000-\u1021]
+    | [\u1023-\u1027]
+    | [\u1029-\u102A]
+    | [\u1050-\u1055]
+    | [\u10A0-\u10C5]
+    | [\u10D0-\u10F6]
+    | [\u1100-\u1159]
+    | [\u115F-\u11A2]
+    | [\u11A8-\u11F9]
+    | [\u1200-\u1206]
+    | [\u1208-\u1246]
+    | [\u1248]
+    | [\u124A-\u124D]
+    | [\u1250-\u1256]
+    | [\u1258]
+    | [\u125A-\u125D]
+    | [\u1260-\u1286]
+    | [\u1288]
+    | [\u128A-\u128D]
+    | [\u1290-\u12AE]
+    | [\u12B0]
+    | [\u12B2-\u12B5]
+    | [\u12B8-\u12BE]
+    | [\u12C0]
+    | [\u12C2-\u12C5]
+    | [\u12C8-\u12CE]
+    | [\u12D0-\u12D6]
+    | [\u12D8-\u12EE]
+    | [\u12F0-\u130E]
+    | [\u1310]
+    | [\u1312-\u1315]
+    | [\u1318-\u131E]
+    | [\u1320-\u1346]
+    | [\u1348-\u135A]
+    | [\u13A0-\u13B0]
+    | [\u13B1-\u13F4]
+    | [\u1401-\u1676]
+    | [\u1681-\u169A]
+    | [\u16A0-\u16EA]
+    | [\u1780-\u17B3]
+    | [\u1820-\u1877]
+    | [\u1880-\u18A8]
+    | [\u1E00-\u1E9B]
+    | [\u1EA0-\u1EE0]
+    | [\u1EE1-\u1EF9]
+    | [\u1F00-\u1F15]
+    | [\u1F18-\u1F1D]
+    | [\u1F20-\u1F39]
+    | [\u1F3A-\u1F45]
+    | [\u1F48-\u1F4D]
+    | [\u1F50-\u1F57]
+    | [\u1F59]
+    | [\u1F5B]
+    | [\u1F5D]
+    | [\u1F5F-\u1F7D]
+    | [\u1F80-\u1FB4]
+    | [\u1FB6-\u1FBC]
+    | [\u1FBE]
+    | [\u1FC2-\u1FC4]
+    | [\u1FC6-\u1FCC]
+    | [\u1FD0-\u1FD3]
+    | [\u1FD6-\u1FDB]
+    | [\u1FE0-\u1FEC]
+    | [\u1FF2-\u1FF4]
+    | [\u1FF6-\u1FFC]
+    | [\u207F]
+    | [\u2102]
+    | [\u2107]
+    | [\u210A-\u2113]
+    | [\u2115]
+    | [\u2119-\u211D]
+    | [\u2124]
+    | [\u2126]
+    | [\u2128]
+    | [\u212A-\u212D]
+    | [\u212F-\u2131]
+    | [\u2133-\u2139]
+    | [\u2160-\u2183]
+    | [\u3005-\u3007]
+    | [\u3021-\u3029]
+    | [\u3031-\u3035]
+    | [\u3038-\u303A]
+    | [\u3041-\u3094]
+    | [\u309D-\u309E]
+    | [\u30A1-\u30FA]
+    | [\u30FC-\u30FE]
+    | [\u3105-\u312C]
+    | [\u3131-\u318E]
+    | [\u31A0-\u31B7]
+    | [\u3400]
+    | [\u4DB5]
+    | [\u4E00]
+    | [\u9FA5]
+    | [\uA000-\uA48C]
+    | [\uAC00]
+    | [\uD7A3]
+    | [\uF900-\uFA2D]
+    | [\uFB00-\uFB06]
+    | [\uFB13-\uFB17]
+    | [\uFB1D]
+    | [\uFB1F-\uFB28]
+    | [\uFB2A-\uFB36]
+    | [\uFB38-\uFB3C]
+    | [\uFB3E]
+    | [\uFB40-\uFB41]
+    | [\uFB43-\uFB44]
+    | [\uFB46-\uFBB1]
+    | [\uFBD3-\uFD3D]
+    | [\uFD50-\uFD8F]
+    | [\uFD92-\uFDC7]
+    | [\uFDF0-\uFDFB]
+    | [\uFE70-\uFE72]
+    | [\uFE74]
+    | [\uFE76-\uFEFC]
+    | [\uFF21-\uFF3A]
+    | [\uFF41-\uFF5A]
+    | [\uFF66-\uFFBE]
+    | [\uFFC2-\uFFC7]
+    | [\uFFCA-\uFFCF]
+    | [\uFFD2-\uFFD7]
+    | [\uFFDA-\uFFDC]
+    */
+    ;
+
+
+mode NLSEMI;
+
+
+// Treat whitespace as normal
+WS_NLSEMI                     : [ \t]+             -> channel(HIDDEN);
+// Ignore any comments that only span one line
+COMMENT_NLSEMI                : '/*' ~[\r\n]*? '*/'      -> channel(HIDDEN);
+LINE_COMMENT_NLSEMI : '//' ~[\r\n]*      -> channel(HIDDEN);
+// Emit an EOS token for any newlines, semicolon, multiline comments or the EOF and 
+//return to normal lexing
+EOS:              ([\r\n]+ | ';' | '/*' .*? '*/' | EOF)            -> mode(DEFAULT_MODE);
+// Did not find an EOS, so go back to normal lexing
+OTHER: -> mode(DEFAULT_MODE), channel(HIDDEN);

+ 380 - 0
jplag.frontend.golang/src/main/antlr4/de/jplag/golang/grammar/GoParser.g4

@@ -0,0 +1,380 @@
+/*
+ [The "BSD licence"] Copyright (c) 2017 Sasa Coh, Michał Błotniak Copyright (c) 2019 Ivan Kochurkin,
+ kvanttt@gmail.com, Positive Technologies Copyright (c) 2019 Dmitry Rassadin,
+ flipparassa@gmail.com,Positive Technologies All rights reserved. Copyright (c) 2021 Martin Mirchev,
+ mirchevmartin2203@gmail.com
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted
+ provided that the following conditions are met: 1. Redistributions of source code must retain the
+ above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in
+ binary form must reproduce the above copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided with the distribution. 3. The name
+ of the author may not be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * A Go grammar for ANTLR 4 derived from the Go Language Specification https://golang.org/ref/spec
+ */
+
+parser grammar GoParser;
+
+options {
+	tokenVocab = GoLexer;
+	superClass = GoParserBase;
+}
+
+sourceFile:
+	packageClause eos (importDecl eos)* (
+		(functionDecl | methodDecl | declaration) eos
+	)* EOF;
+
+packageClause: PACKAGE packageName = IDENTIFIER;
+
+importDecl:
+	IMPORT (importSpec | L_PAREN (importSpec eos)* R_PAREN);
+
+importSpec: alias = (DOT | IDENTIFIER)? importPath;
+
+importPath: string_;
+
+declaration: constDecl | typeDecl | varDecl;
+
+constDecl: CONST (constSpec | L_PAREN (constSpec eos)* R_PAREN);
+
+constSpec: identifierList (type_? ASSIGN expressionList)?;
+
+identifierList: IDENTIFIER (COMMA IDENTIFIER)*;
+
+expressionList: expression (COMMA expression)*;
+
+typeDecl: TYPE (typeSpec | L_PAREN (typeSpec eos)* R_PAREN);
+
+typeSpec: IDENTIFIER ASSIGN? type_;
+
+// Function declarations
+
+functionDecl: FUNC IDENTIFIER (signature block?);
+
+methodDecl: FUNC receiver IDENTIFIER ( signature block?);
+
+receiver: parameters;
+
+varDecl: VAR (varSpec | L_PAREN (varSpec eos)* R_PAREN);
+
+varSpec:
+	identifierList (
+		type_ (ASSIGN expressionList)?
+		| ASSIGN expressionList
+	);
+
+block: L_CURLY statementList? R_CURLY;
+
+statementList: ((SEMI? | EOS? | {closingBracket()}?) statement eos)+;
+
+statement:
+	declaration
+	| labeledStmt
+	| simpleStmt
+	| goStmt
+	| returnStmt
+	| breakStmt
+	| continueStmt
+	| gotoStmt
+	| fallthroughStmt
+	| block
+	| ifStmt
+	| switchStmt
+	| selectStmt
+	| forStmt
+	| deferStmt;
+
+simpleStmt:
+	sendStmt
+	| incDecStmt
+	| assignment
+	| expressionStmt
+	| shortVarDecl;
+
+expressionStmt: expression;
+
+sendStmt: channel = expression RECEIVE expression;
+
+incDecStmt: expression (PLUS_PLUS | MINUS_MINUS);
+
+assignment: expressionList assign_op expressionList;
+
+assign_op: (
+		PLUS
+		| MINUS
+		| OR
+		| CARET
+		| STAR
+		| DIV
+		| MOD
+		| LSHIFT
+		| RSHIFT
+		| AMPERSAND
+		| BIT_CLEAR
+	)? ASSIGN;
+
+shortVarDecl: identifierList DECLARE_ASSIGN expressionList;
+
+emptyStmt: EOS | SEMI;
+
+labeledStmt: IDENTIFIER COLON statement?;
+
+returnStmt: RETURN expressionList?;
+
+breakStmt: BREAK IDENTIFIER?;
+
+continueStmt: CONTINUE IDENTIFIER?;
+
+gotoStmt: GOTO IDENTIFIER;
+
+fallthroughStmt: FALLTHROUGH;
+
+deferStmt: DEFER expression;
+
+ifStmt:
+	IF ( expression
+			| eos expression
+			| simpleStmt eos expression
+			) block (
+		ELSE (ifStmt | block)
+	)?;
+
+switchStmt: exprSwitchStmt | typeSwitchStmt;
+
+exprSwitchStmt:
+	SWITCH (expression?
+					| simpleStmt? eos expression?
+					) L_CURLY exprCaseClause* R_CURLY;
+
+exprCaseClause: exprSwitchCase COLON statementList?;
+
+exprSwitchCase: CASE expressionList | DEFAULT;
+
+typeSwitchStmt:
+	SWITCH ( typeSwitchGuard
+					| eos typeSwitchGuard
+					| simpleStmt eos typeSwitchGuard)
+					 L_CURLY typeCaseClause* R_CURLY;
+
+typeSwitchGuard: (IDENTIFIER DECLARE_ASSIGN)? primaryExpr DOT L_PAREN TYPE R_PAREN;
+
+typeCaseClause: typeSwitchCase COLON statementList?;
+
+typeSwitchCase: CASE typeList | DEFAULT;
+
+typeList: (type_ | NIL_LIT) (COMMA (type_ | NIL_LIT))*;
+
+selectStmt: SELECT L_CURLY commClause* R_CURLY;
+
+commClause: commCase COLON statementList?;
+
+commCase: CASE (sendStmt | recvStmt) | DEFAULT;
+
+recvStmt: (expressionList ASSIGN | identifierList DECLARE_ASSIGN)? recvExpr = expression;
+
+forStmt: FOR (expression? | forClause | rangeClause?) block;
+
+forClause:
+	initStmt = simpleStmt? eos expression? eos postStmt = simpleStmt?;
+
+rangeClause: (
+		expressionList ASSIGN
+		| identifierList DECLARE_ASSIGN
+	)? RANGE expression;
+
+goStmt: GO expression;
+
+type_: typeName | typeLit | L_PAREN type_ R_PAREN;
+
+typeName: qualifiedIdent | IDENTIFIER;
+
+typeLit:
+	arrayType
+	| structType
+	| pointerType
+	| functionType
+	| interfaceType
+	| sliceType
+	| mapType
+	| channelType;
+
+arrayType: L_BRACKET arrayLength R_BRACKET elementType;
+
+arrayLength: expression;
+
+elementType: type_;
+
+pointerType: STAR type_;
+
+interfaceType:
+	INTERFACE L_CURLY ((methodSpec | typeName) eos)* R_CURLY;
+
+sliceType: L_BRACKET R_BRACKET elementType;
+
+// It's possible to replace `type` with more restricted typeLit list and also pay attention to nil maps
+mapType: MAP L_BRACKET type_ R_BRACKET elementType;
+
+channelType: (CHAN | CHAN RECEIVE | RECEIVE CHAN) elementType;
+
+methodSpec:
+	IDENTIFIER parameters result
+	| IDENTIFIER parameters;
+
+functionType: FUNC signature;
+
+signature:
+	parameters result
+	| parameters;
+
+result: parameters | type_;
+
+parameters:
+	L_PAREN (parameterDecl (COMMA parameterDecl)* COMMA?)? R_PAREN;
+
+parameterDecl: identifierList? ELLIPSIS? type_;
+
+expression:
+	primaryExpr
+	| unary_op = (
+		PLUS
+		| MINUS
+		| EXCLAMATION
+		| CARET
+		| STAR
+		| AMPERSAND
+		| RECEIVE
+	) expression
+	| expression mul_op = (
+		STAR
+		| DIV
+		| MOD
+		| LSHIFT
+		| RSHIFT
+		| AMPERSAND
+		| BIT_CLEAR
+	) expression
+	| expression add_op = (PLUS | MINUS | OR | CARET) expression
+	| expression rel_op = (
+		EQUALS
+		| NOT_EQUALS
+		| LESS
+		| LESS_OR_EQUALS
+		| GREATER
+		| GREATER_OR_EQUALS
+	) expression
+	| expression LOGICAL_AND expression
+	| expression LOGICAL_OR expression;
+
+primaryExpr:
+	operand
+	| conversion
+	| methodExpr
+	| primaryExpr (
+		(DOT IDENTIFIER)
+		| index
+		| slice_
+		| typeAssertion
+		| arguments
+	);
+
+
+conversion: nonNamedType L_PAREN expression COMMA? R_PAREN;
+
+nonNamedType: typeLit | L_PAREN nonNamedType R_PAREN;
+
+operand: literal | operandName | L_PAREN expression R_PAREN;
+
+literal: basicLit | compositeLit | functionLit;
+
+basicLit:
+	NIL_LIT
+	| integer
+	| string_
+	| FLOAT_LIT;
+
+integer:
+	DECIMAL_LIT
+	| BINARY_LIT
+	| OCTAL_LIT
+	| HEX_LIT
+	| IMAGINARY_LIT
+	| RUNE_LIT;
+
+operandName: IDENTIFIER;
+
+qualifiedIdent: IDENTIFIER DOT IDENTIFIER;
+
+compositeLit: literalType literalValue;
+
+literalType:
+	structType
+	| arrayType
+	| L_BRACKET ELLIPSIS R_BRACKET elementType
+	| sliceType
+	| mapType
+	| typeName;
+
+literalValue: L_CURLY (elementList COMMA?)? R_CURLY;
+
+elementList: keyedElement (COMMA keyedElement)*;
+
+keyedElement: (key COLON)? element;
+
+key: expression | literalValue;
+
+element: expression | literalValue;
+
+structType: STRUCT L_CURLY (fieldDecl eos)* R_CURLY;
+
+fieldDecl: (
+		identifierList type_
+		| embeddedField
+	) tag = string_?;
+
+string_: RAW_STRING_LIT | INTERPRETED_STRING_LIT;
+
+embeddedField: STAR? typeName;
+
+functionLit: FUNC signature block; // function
+
+index: L_BRACKET expression R_BRACKET;
+
+slice_:
+	L_BRACKET (
+		expression? COLON expression?
+		| expression? COLON expression COLON expression
+	) R_BRACKET;
+
+typeAssertion: DOT L_PAREN type_ R_PAREN;
+
+arguments:
+	L_PAREN (
+		(expressionList | nonNamedType (COMMA expressionList)?) ELLIPSIS? COMMA?
+	)? R_PAREN;
+
+methodExpr: nonNamedType DOT IDENTIFIER;
+
+//receiverType: typeName | '(' ('*' typeName | receiverType) ')';
+
+receiverType: type_;
+
+eos:
+	SEMI
+	| EOF
+	| EOS
+	| {closingBracket()}?
+	;

+ 59 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/GoParserAdapter.java

@@ -0,0 +1,59 @@
+package de.jplag.golang;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+
+import de.jplag.AbstractParser;
+import de.jplag.TokenList;
+import de.jplag.golang.grammar.GoLexer;
+import de.jplag.golang.grammar.GoParser;
+
+public class GoParserAdapter extends AbstractParser {
+    private String currentFile;
+    private TokenList tokens;
+
+    public TokenList parse(File directory, String[] fileNames) {
+        tokens = new TokenList();
+        for (String file : fileNames) {
+            if (!parseFile(directory, file)) {
+                errors++;
+            }
+        }
+        return tokens;
+    }
+
+    private boolean parseFile(File directory, String fileName) {
+        File file = new File(directory, fileName);
+        try (FileInputStream inputStream = new FileInputStream(file)) {
+            currentFile = fileName;
+
+            GoLexer lexer = new GoLexer(CharStreams.fromStream(inputStream));
+            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+            GoParser parser = new GoParser(tokenStream);
+
+            ParserRuleContext entryContext = parser.sourceFile();
+            ParseTreeWalker treeWalker = new ParseTreeWalker();
+
+            JPlagGoListener listener = new JPlagGoListener(this);
+            for (int i = 0; i < entryContext.getChildCount(); i++) {
+                ParseTree parseTree = entryContext.getChild(i);
+                treeWalker.walk(listener, parseTree);
+            }
+        } catch (IOException exception) {
+            logger.error("Parsing Error in '%s':".formatted(fileName), exception);
+            return false;
+        }
+        return true;
+    }
+
+    public void addToken(int tokenType, int line, int column, int length) {
+        tokens.addToken(new GoToken(tokenType, currentFile, line, column, length));
+    }
+}

+ 115 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/GoToken.java

@@ -0,0 +1,115 @@
+package de.jplag.golang;
+
+import static de.jplag.golang.GoTokenConstants.*;
+
+import de.jplag.Token;
+
+public class GoToken extends Token {
+
+    public GoToken(int type, String file, int line, int column, int length) {
+        super(type, file, line, column, length);
+    }
+
+    @Override
+    protected String type2string() {
+        return switch (type) {
+            case FILE_END -> "<EOF>";
+            case SEPARATOR_TOKEN -> "---------";
+
+            case PACKAGE -> "PACKAGE";
+            case IMPORT_CLAUSE -> "IMPORT";
+            case IMPORT_CLAUSE_BEGIN -> "IMPORT(";
+            case IMPORT_CLAUSE_END -> ")IMPORT";
+            case IMPORT_DECLARATION -> "IMPORT_DECL";
+
+            case ARRAY_BODY_BEGIN -> "ARRAY{";
+            case ARRAY_BODY_END -> "}ARRAY";
+
+            case STRUCT_DECLARATION -> "STRUCT";
+            case STRUCT_BODY_BEGIN -> "STRUCT{";
+            case STRUCT_BODY_END -> "}STRUCT";
+
+            case INTERFACE_DECLARATION -> "INTERFACE";
+            case INTERFACE_BLOCK_BEGIN -> "INTERFACE{";
+            case INTERFACE_BLOCK_END -> "}INTERFACE";
+
+            case INTERFACE_METHOD -> "INTERFACE_METHOD";
+            case TYPE_CONSTRAINT -> "TYPE_CONSTRAINT";
+            case TYPE_ASSERTION -> "TYPE_ASSERTION";
+
+            case MAP_BODY_BEGIN -> "MAP{";
+            case MAP_BODY_END -> "}MAP";
+
+            case SLICE_BODY_BEGIN -> "SLICE{";
+            case SLICE_BODY_END -> "}SLICE";
+
+            case NAMED_TYPE_BODY_BEGIN -> "CTYPE{";
+            case NAMED_TYPE_BODY_END -> "}CTYPE";
+
+            case MEMBER_DECLARATION -> "FIELD";
+
+            // Functions and Methods
+
+            case FUNCTION_DECLARATION -> "FUNC";
+            case RECEIVER -> "RECEIVER";
+            case FUNCTION_PARAMETER -> "PARAM";
+            case FUNCTION_BODY_BEGIN -> "FUNC{";
+            case FUNCTION_BODY_END -> "}FUNC";
+
+            // Control flow statements
+
+            case IF_STATEMENT -> "IF";
+
+            case IF_BLOCK_BEGIN -> "IF{";
+            case IF_BLOCK_END -> "}IF";
+            case ELSE_BLOCK_BEGIN -> "ELSE{";
+            case ELSE_BLOCK_END -> "}ELSE";
+            case FOR_STATEMENT -> "FOR";
+            case FOR_BLOCK_BEGIN -> "FOR{";
+            case FOR_BLOCK_END -> "}FOR";
+            case SWITCH_STATEMENT -> "SWITCH";
+            case SWITCH_BLOCK_BEGIN -> "SWITCH{";
+            case SWITCH_BLOCK_END -> "}SWITCH";
+            case SWITCH_CASE -> "CASE";
+            case SELECT_STATEMENT -> "SELECT";
+            case SELECT_BLOCK_BEGIN -> "SELECT{";
+            case SELECT_BLOCK_END -> "}SELECT";
+            case CASE_BLOCK_BEGIN -> "CASE{";
+            case CASE_BLOCK_END -> "}CASE";
+
+            // Statements
+
+            case VARIABLE_DECLARATION -> "VAR_DECL";
+            case FUNCTION_LITERAL -> "FUNC_LIT";
+            case ASSIGNMENT -> "ASSIGN";
+            case SEND_STATEMENT -> "SEND";
+            case RECEIVE_STATEMENT -> "RECV";
+            case INVOCATION -> "INVOC";
+            case ARGUMENT -> "ARG";
+            case STATEMENT_BLOCK_BEGIN -> "INNER{";
+            case STATEMENT_BLOCK_END -> "}INNER";
+
+            // Object Creation
+
+            case ARRAY_ELEMENT -> "ARRAY_ELEM";
+            case MAP_ELEMENT -> "MAP_ELEM";
+            case SLICE_ELEMENT -> "SLICE_ELEM";
+            case NAMED_TYPE_ELEMENT -> "CTYPE_ELEM";
+            case ARRAY_CONSTRUCTOR -> "ARRAY()";
+            case SLICE_CONSTRUCTOR -> "SLICE()";
+            case MAP_CONSTRUCTOR -> "MAP()";
+            case NAMED_TYPE_CONSTRUCTOR -> "CTYPE()";
+
+            // Control Flow Keywords
+
+            case RETURN -> "RETURN";
+            case BREAK -> "BREAK";
+            case CONTINUE -> "CONTINUE";
+            case FALLTHROUGH -> "FALLTHROUGH";
+            case GOTO -> "GOTO";
+            case GO -> "GO";
+            case DEFER -> "DEFER";
+            default -> "<UNKNOWN%d>".formatted(type);
+        };
+    }
+}

+ 96 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/GoTokenConstants.java

@@ -0,0 +1,96 @@
+package de.jplag.golang;
+
+import de.jplag.TokenConstants;
+
+public interface GoTokenConstants extends TokenConstants {
+
+    // TOP LEVEL STRUCTURES
+
+    int PACKAGE = 2;
+    int IMPORT_CLAUSE = 3;
+    int IMPORT_CLAUSE_BEGIN = 4;
+    int IMPORT_CLAUSE_END = 5;
+    int IMPORT_DECLARATION = 6;
+    int ARRAY_BODY_BEGIN = 7;
+    int ARRAY_BODY_END = 8;
+    int STRUCT_DECLARATION = 9;
+    int STRUCT_BODY_BEGIN = 10;
+    int STRUCT_BODY_END = 11;
+    int INTERFACE_DECLARATION = 12;
+    int INTERFACE_BLOCK_BEGIN = 13;
+    int INTERFACE_BLOCK_END = 14;
+    int INTERFACE_METHOD = 15;
+    int TYPE_CONSTRAINT = 16;
+    int TYPE_ASSERTION = 17;
+    int MAP_BODY_BEGIN = 18;
+    int MAP_BODY_END = 19;
+    int SLICE_BODY_BEGIN = 20;
+    int SLICE_BODY_END = 21;
+    int NAMED_TYPE_BODY_BEGIN = 22;
+    int NAMED_TYPE_BODY_END = 23;
+    int MEMBER_DECLARATION = 24;
+
+    // FUNCTIONS AND METHODS
+
+    int FUNCTION_DECLARATION = 25;
+    int RECEIVER = 26;
+    int FUNCTION_PARAMETER = 27;
+    int FUNCTION_BODY_BEGIN = 28;
+    int FUNCTION_BODY_END = 29;
+
+    // CONTROL FLOW STATEMENTS
+
+    int IF_STATEMENT = 30;
+    int IF_BLOCK_BEGIN = 31;
+    int IF_BLOCK_END = 32;
+    int ELSE_BLOCK_BEGIN = 33;
+    int ELSE_BLOCK_END = 34;
+    int FOR_STATEMENT = 35;
+    int FOR_BLOCK_BEGIN = 36;
+    int FOR_BLOCK_END = 37;
+    int SWITCH_STATEMENT = 38;
+    int SWITCH_BLOCK_BEGIN = 39;
+    int SWITCH_BLOCK_END = 40;
+    int SWITCH_CASE = 41;
+    int SELECT_STATEMENT = 42;
+    int SELECT_BLOCK_BEGIN = 43;
+    int SELECT_BLOCK_END = 44;
+    int CASE_BLOCK_BEGIN = 45;
+    int CASE_BLOCK_END = 46;
+
+    // STATEMENTS
+
+    int VARIABLE_DECLARATION = 47;
+    int FUNCTION_LITERAL = 48;
+    int ASSIGNMENT = 49;
+    int SEND_STATEMENT = 50;
+    int RECEIVE_STATEMENT = 51;
+    int INVOCATION = 52;
+    int ARGUMENT = 53;
+    int STATEMENT_BLOCK_BEGIN = 54;
+    int STATEMENT_BLOCK_END = 55;
+
+    // OBJECT CREATION
+
+    int ARRAY_ELEMENT = 56;
+    int MAP_ELEMENT = 57;
+    int SLICE_ELEMENT = 58;
+    int NAMED_TYPE_ELEMENT = 59;
+    int ARRAY_CONSTRUCTOR = 60;
+    int MAP_CONSTRUCTOR = 61;
+    int SLICE_CONSTRUCTOR = 62;
+    int NAMED_TYPE_CONSTRUCTOR = 63;
+
+    // CONTROL FLOW KEYWORDS
+
+    int RETURN = 64;
+    int BREAK = 65;
+    int CONTINUE = 66;
+    int FALLTHROUGH = 67;
+    int GOTO = 68;
+    int GO = 69;
+    int DEFER = 70;
+
+    int NUM_DIFF_TOKENS = 71;
+
+}

+ 535 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/JPlagGoListener.java

@@ -0,0 +1,535 @@
+package de.jplag.golang;
+
+import static de.jplag.golang.GoTokenConstants.*;
+
+import java.util.*;
+
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import de.jplag.golang.grammar.GoParser;
+import de.jplag.golang.grammar.GoParserBaseListener;
+
+public class JPlagGoListener extends GoParserBaseListener {
+
+    public static final int NONE = -1;
+    private final GoParserAdapter parserAdapter;
+    private final Deque<GoBlockContext> blockContexts;
+
+    public JPlagGoListener(GoParserAdapter parserAdapter) {
+        this.parserAdapter = parserAdapter;
+        blockContexts = new LinkedList<>();
+    }
+
+    /**
+     * Passes a token of the given tokenType to the parserAdapter, representing the grammar's token given by token.
+     * @param tokenType the custom token type that occurred.
+     * @param token the corresponding grammar's token
+     */
+    private void transformToken(int tokenType, Token token) {
+        parserAdapter.addToken(tokenType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length());
+    }
+
+    /**
+     * Passes a token of the given tokenType to the parserAdapter, representing the current grammatical context given by
+     * start and end.
+     * @param tokenType the custom token type that occurred.
+     * @param start the first Token of the context
+     * @param end the last Token of the context
+     */
+    private void transformToken(int tokenType, Token start, Token end) {
+        parserAdapter.addToken(tokenType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1);
+    }
+
+    private void enterContext(GoBlockContext context) {
+        blockContexts.push(context);
+    }
+
+    private void expectAndLeave(GoBlockContext... contexts) {
+        GoBlockContext topContext = blockContexts.pop();
+        assert Arrays.stream(contexts).anyMatch(context -> context == topContext);
+    }
+
+    private GoBlockContext getCurrentContext() {
+        return blockContexts.peek();
+    }
+
+    /* TOP LEVEL STRUCTURES */
+
+    @Override
+    public void enterPackageClause(GoParser.PackageClauseContext context) {
+        transformToken(PACKAGE, context.getStart(), context.getStop());
+        super.enterPackageClause(context);
+    }
+
+    @Override
+    public void enterImportDecl(GoParser.ImportDeclContext context) {
+        transformToken(IMPORT_DECLARATION, context.getStart());
+
+        // if the children contain TerminalNodes, then it must be '(' and ')'
+        Optional<TerminalNode> listStart = context.children.stream().filter(TerminalNode.class::isInstance).map(TerminalNode.class::cast).findFirst();
+        listStart.ifPresent(lParenTree -> transformToken(IMPORT_CLAUSE_BEGIN, lParenTree.getSymbol()));
+
+        super.enterImportDecl(context);
+    }
+
+    @Override
+    public void exitImportDecl(GoParser.ImportDeclContext context) {
+        if (context.getStop().getText().equals(")")) {
+            transformToken(IMPORT_CLAUSE_END, context.getStop());
+        }
+        super.exitImportDecl(context);
+    }
+
+    @Override
+    public void enterImportSpec(GoParser.ImportSpecContext context) {
+        transformToken(IMPORT_CLAUSE, context.getStart(), context.getStop());
+        super.enterImportSpec(context);
+    }
+
+    /* INTERFACE */
+
+    @Override
+    public void enterInterfaceType(GoParser.InterfaceTypeContext context) {
+        transformToken(INTERFACE_DECLARATION, context.getStart());
+        enterContext(GoBlockContext.INTERFACE_BODY);
+        super.enterInterfaceType(context);
+    }
+
+    @Override
+    public void exitInterfaceType(GoParser.InterfaceTypeContext context) {
+        expectAndLeave(GoBlockContext.INTERFACE_BODY);
+        super.exitInterfaceType(context);
+    }
+
+    /* STRUCT */
+
+    @Override
+    public void enterStructType(GoParser.StructTypeContext context) {
+        transformToken(STRUCT_DECLARATION, context.getStart());
+        enterContext(GoBlockContext.STRUCT_BODY);
+        super.enterStructType(context);
+    }
+
+    @Override
+    public void exitStructType(GoParser.StructTypeContext context) {
+        expectAndLeave(GoBlockContext.STRUCT_BODY);
+        super.exitStructType(context);
+    }
+
+    @Override
+    public void enterFieldDecl(GoParser.FieldDeclContext context) {
+        transformToken(MEMBER_DECLARATION, context.getStart(), context.getStop());
+        super.enterFieldDecl(context);
+    }
+
+    /* FUNCTION */
+
+    @Override
+    public void enterFunctionDecl(GoParser.FunctionDeclContext context) {
+        transformToken(FUNCTION_DECLARATION, context.getStart());
+        enterContext(GoBlockContext.FUNCTION_BODY);
+        super.enterFunctionDecl(context);
+    }
+
+    @Override
+    public void exitFunctionDecl(GoParser.FunctionDeclContext context) {
+        expectAndLeave(GoBlockContext.FUNCTION_BODY);
+        super.exitFunctionDecl(context);
+    }
+
+    @Override
+    public void enterMethodDecl(GoParser.MethodDeclContext context) {
+        transformToken(FUNCTION_DECLARATION, context.getStart());
+        enterContext(GoBlockContext.FUNCTION_BODY);
+        super.enterMethodDecl(context);
+    }
+
+    @Override
+    public void exitMethodDecl(GoParser.MethodDeclContext context) {
+        expectAndLeave(GoBlockContext.FUNCTION_BODY);
+        super.exitMethodDecl(context);
+    }
+
+    @Override
+    public void enterParameterDecl(GoParser.ParameterDeclContext context) {
+        if (context.parent.parent instanceof GoParser.ReceiverContext) {
+            transformToken(RECEIVER, context.getStart(), context.getStop());
+        } else {
+            transformToken(FUNCTION_PARAMETER, context.getStart(), context.getStop());
+        }
+        super.enterParameterDecl(context);
+    }
+
+    /* CONTROL FLOW STATEMENTS */
+
+    @Override
+    public void enterIfStmt(GoParser.IfStmtContext context) {
+        transformToken(IF_STATEMENT, context.getStart());
+        enterContext(GoBlockContext.IF_BLOCK);
+        super.enterIfStmt(context);
+    }
+
+    @Override
+    public void exitIfStmt(GoParser.IfStmtContext context) {
+        expectAndLeave(GoBlockContext.IF_BLOCK, GoBlockContext.ELSE_BLOCK);
+        super.exitIfStmt(context);
+    }
+
+    @Override
+    public void enterForStmt(GoParser.ForStmtContext context) {
+        transformToken(FOR_STATEMENT, context.getStart());
+        enterContext(GoBlockContext.FOR_BLOCK);
+        super.enterForStmt(context);
+    }
+
+    @Override
+    public void exitForStmt(GoParser.ForStmtContext context) {
+        expectAndLeave(GoBlockContext.FOR_BLOCK);
+        super.exitForStmt(context);
+    }
+
+    @Override
+    public void enterSwitchStmt(GoParser.SwitchStmtContext context) {
+        transformToken(SWITCH_STATEMENT, context.getStart());
+        enterContext(GoBlockContext.SWITCH_BLOCK);
+        super.enterSwitchStmt(context);
+    }
+
+    @Override
+    public void exitSwitchStmt(GoParser.SwitchStmtContext context) {
+        expectAndLeave(GoBlockContext.SWITCH_BLOCK);
+        super.exitSwitchStmt(context);
+    }
+
+    @Override
+    public void enterExprCaseClause(GoParser.ExprCaseClauseContext context) {
+        transformToken(SWITCH_CASE, context.getStart());
+        var caseBlock = context.getChild(GoParser.StatementListContext.class, 0);
+        if (caseBlock != null) {
+            enterContext(GoBlockContext.CASE_BLOCK);
+            transformToken(CASE_BLOCK_BEGIN, caseBlock.getStart());
+        }
+        super.enterExprCaseClause(context);
+    }
+
+    @Override
+    public void exitExprCaseClause(GoParser.ExprCaseClauseContext context) {
+        if (getCurrentContext() == GoBlockContext.CASE_BLOCK) {
+            transformToken(CASE_BLOCK_END, context.getStop());
+            expectAndLeave(GoBlockContext.CASE_BLOCK);
+        }
+        super.exitExprCaseClause(context);
+    }
+
+    @Override
+    public void enterTypeCaseClause(GoParser.TypeCaseClauseContext context) {
+        transformToken(SWITCH_CASE, context.getStart());
+        var caseBlock = context.getChild(GoParser.StatementListContext.class, 0);
+        if (caseBlock != null) {
+            enterContext(GoBlockContext.CASE_BLOCK);
+            transformToken(CASE_BLOCK_BEGIN, caseBlock.getStart());
+        }
+        super.enterTypeCaseClause(context);
+    }
+
+    @Override
+    public void exitTypeCaseClause(GoParser.TypeCaseClauseContext context) {
+        if (getCurrentContext() == GoBlockContext.CASE_BLOCK) {
+            transformToken(CASE_BLOCK_END, context.getStop());
+            expectAndLeave(GoBlockContext.CASE_BLOCK);
+        }
+        super.exitTypeCaseClause(context);
+    }
+
+    @Override
+    public void enterSelectStmt(GoParser.SelectStmtContext context) {
+        transformToken(SELECT_STATEMENT, context.getStart());
+        enterContext(GoBlockContext.SELECT_CONTEXT);
+        super.enterSelectStmt(context);
+    }
+
+    @Override
+    public void exitSelectStmt(GoParser.SelectStmtContext context) {
+        expectAndLeave(GoBlockContext.SELECT_CONTEXT);
+        super.exitSelectStmt(context);
+    }
+
+    @Override
+    public void enterCommCase(GoParser.CommCaseContext context) {
+        transformToken(SWITCH_CASE, context.getStart());
+        var caseBlock = context.getChild(GoParser.StatementListContext.class, 0);
+        if (caseBlock != null) {
+            enterContext(GoBlockContext.CASE_BLOCK);
+            transformToken(CASE_BLOCK_BEGIN, caseBlock.getStart());
+        }
+        super.enterCommCase(context);
+    }
+
+    @Override
+    public void exitCommCase(GoParser.CommCaseContext context) {
+        if (getCurrentContext() == GoBlockContext.CASE_BLOCK) {
+            transformToken(CASE_BLOCK_END, context.getStop());
+            expectAndLeave(GoBlockContext.CASE_BLOCK);
+        }
+        super.exitCommCase(context);
+    }
+
+    /* STATEMENTS */
+
+    @Override
+    public void enterVarDecl(GoParser.VarDeclContext context) {
+        transformToken(VARIABLE_DECLARATION, context.getStart(), context.getStop());
+        super.enterVarDecl(context);
+    }
+
+    @Override
+    public void enterConstSpec(GoParser.ConstSpecContext context) {
+        transformToken(VARIABLE_DECLARATION, context.getStart());
+        super.enterConstSpec(context);
+    }
+
+    @Override
+    public void enterFunctionLit(GoParser.FunctionLitContext context) {
+        transformToken(FUNCTION_LITERAL, context.getStart());
+        enterContext(GoBlockContext.FUNCTION_BODY);
+        super.enterFunctionLit(context);
+    }
+
+    @Override
+    public void exitFunctionLit(GoParser.FunctionLitContext context) {
+        expectAndLeave(GoBlockContext.FUNCTION_BODY);
+        super.exitFunctionLit(context);
+    }
+
+    @Override
+    public void enterAssignment(GoParser.AssignmentContext context) {
+        transformToken(ASSIGNMENT, context.getStart(), context.getStop());
+        super.enterAssignment(context);
+    }
+
+    @Override
+    public void enterShortVarDecl(GoParser.ShortVarDeclContext context) {
+        transformToken(VARIABLE_DECLARATION, context.getStart());
+        transformToken(ASSIGNMENT, context.getStart());
+        super.enterShortVarDecl(context);
+    }
+
+    @Override
+    public void enterArguments(GoParser.ArgumentsContext context) {
+        transformToken(INVOCATION, context.getStart(), context.getStop());
+
+        // Arguments consist of ExpressionLists, which consist of Expressions
+        // Get all Expressions of all ExpressionLists in this ArgumentsContext
+        context.getRuleContexts(GoParser.ExpressionListContext.class).stream()
+                .flatMap(child -> child.getRuleContexts(GoParser.ExpressionContext.class).stream())
+                .forEachOrdered(arg -> transformToken(ARGUMENT, arg.getStart(), arg.getStop()));
+        super.enterArguments(context);
+    }
+
+    @Override
+    public void enterStatement(GoParser.StatementContext context) {
+        enterContext(GoBlockContext.STATEMENT_BLOCK);
+        super.enterStatement(context);
+    }
+
+    @Override
+    public void exitStatement(GoParser.StatementContext context) {
+        expectAndLeave(GoBlockContext.STATEMENT_BLOCK);
+        super.exitStatement(context);
+    }
+
+    /* OBJECT CREATION */
+
+    @Override
+    public void enterKeyedElement(GoParser.KeyedElementContext context) {
+        int tokenType = getCurrentContext().getElement();
+        transformToken(tokenType, context.getStart(), context.getStop());
+
+        super.enterKeyedElement(context);
+    }
+
+    @Override
+    public void enterArrayType(GoParser.ArrayTypeContext context) {
+        // otherwise, it is just a type expression
+        if (context.parent.parent instanceof GoParser.CompositeLitContext) {
+            enterContext(GoBlockContext.ARRAY_BODY);
+            transformToken(ARRAY_CONSTRUCTOR, context.getStart(), context.getStop());
+        }
+        super.enterArrayType(context);
+    }
+
+    @Override
+    public void enterSliceType(GoParser.SliceTypeContext context) {
+        // otherwise, it is just a type expression
+        if (context.parent.parent instanceof GoParser.CompositeLitContext) {
+            enterContext(GoBlockContext.SLICE_BODY);
+            transformToken(SLICE_CONSTRUCTOR, context.getStart(), context.getStop());
+        }
+        super.enterSliceType(context);
+    }
+
+    @Override
+    public void exitCompositeLit(GoParser.CompositeLitContext context) {
+        expectAndLeave(GoBlockContext.MAP_BODY, GoBlockContext.SLICE_BODY, GoBlockContext.ARRAY_BODY, GoBlockContext.NAMED_TYPE_BODY);
+        super.exitCompositeLit(context);
+    }
+
+    @Override
+    public void enterMapType(GoParser.MapTypeContext context) {
+        // otherwise, it is just a type expression
+        if (context.parent.parent instanceof GoParser.CompositeLitContext) {
+            enterContext(GoBlockContext.MAP_BODY);
+            transformToken(MAP_CONSTRUCTOR, context.getStart(), context.getStop());
+        }
+        super.enterMapType(context);
+    }
+
+    @Override
+    public void enterTypeName(GoParser.TypeNameContext context) {
+        if (context.parent.parent instanceof GoParser.CompositeLitContext) {
+            transformToken(NAMED_TYPE_CONSTRUCTOR, context.getStart());
+            enterContext(GoBlockContext.NAMED_TYPE_BODY);
+        } else if (context.parent instanceof GoParser.InterfaceTypeContext) {
+            transformToken(TYPE_CONSTRAINT, context.getStart(), context.getStop());
+        }
+        super.enterTypeName(context);
+    }
+
+    @Override
+    public void enterTypeAssertion(GoParser.TypeAssertionContext context) {
+        transformToken(TYPE_ASSERTION, context.getStart(), context.getStop());
+        super.enterTypeAssertion(context);
+    }
+
+    @Override
+    public void enterMethodSpec(GoParser.MethodSpecContext context) {
+        transformToken(INTERFACE_METHOD, context.getStart(), context.getStop());
+        super.enterMethodSpec(context);
+    }
+
+    /* CONTROL FLOW KEYWORDS */
+
+    @Override
+    public void enterReturnStmt(GoParser.ReturnStmtContext context) {
+        transformToken(RETURN, context.getStart(), context.getStop());
+        super.enterReturnStmt(context);
+    }
+
+    @Override
+    public void enterBreakStmt(GoParser.BreakStmtContext context) {
+        transformToken(BREAK, context.getStart(), context.getStop());
+        super.enterBreakStmt(context);
+    }
+
+    @Override
+    public void enterContinueStmt(GoParser.ContinueStmtContext context) {
+        transformToken(CONTINUE, context.getStart(), context.getStop());
+        super.enterContinueStmt(context);
+    }
+
+    @Override
+    public void enterFallthroughStmt(GoParser.FallthroughStmtContext context) {
+        transformToken(FALLTHROUGH, context.getStart(), context.getStop());
+        super.enterFallthroughStmt(context);
+    }
+
+    @Override
+    public void enterGotoStmt(GoParser.GotoStmtContext context) {
+        transformToken(GOTO, context.getStart(), context.getStop());
+        super.enterGotoStmt(context);
+    }
+
+    @Override
+    public void enterGoStmt(GoParser.GoStmtContext context) {
+        transformToken(GO, context.getStart(), context.getStop());
+        super.enterGoStmt(context);
+    }
+
+    @Override
+    public void enterDeferStmt(GoParser.DeferStmtContext context) {
+        transformToken(DEFER, context.getStart(), context.getStop());
+        super.enterDeferStmt(context);
+    }
+
+    @Override
+    public void enterSendStmt(GoParser.SendStmtContext ctx) {
+        transformToken(SEND_STATEMENT, ctx.getStart(), ctx.getStop());
+        super.enterSendStmt(ctx);
+    }
+
+    @Override
+    public void enterRecvStmt(GoParser.RecvStmtContext ctx) {
+        transformToken(RECEIVE_STATEMENT, ctx.getStart(), ctx.getStop());
+        super.enterRecvStmt(ctx);
+    }
+
+    @Override
+    public void visitTerminal(TerminalNode node) {
+        Token token = node.getSymbol();
+        switch (token.getText()) {
+            case "else" -> {
+                expectAndLeave(GoBlockContext.IF_BLOCK);
+                enterContext(GoBlockContext.ELSE_BLOCK);
+            }
+            case "{" -> transformToken(getCurrentContext().getBegin(), token);
+            case "}" -> transformToken(getCurrentContext().getEnd(), token);
+            case "<EOF>" -> {
+                if (node.getParent() instanceof GoParser.SourceFileContext) {
+                    transformToken(FILE_END, token);
+                }
+            }
+            default -> {
+                // do nothing.
+            }
+        }
+        super.visitTerminal(node);
+    }
+
+    /**
+     * This enumeration provides sets of information regarding different types of nesting structures in Go. Each element is
+     * a tuple of a token for the beginning of a block, the end of the block, and optionally, for the elements contained.
+     * <p>
+     * As the Go parser does not differentiate between different kinds of blocks, a stack of these GoBlockContexts is
+     * required to be able to assign the correct token types for each block.
+     */
+    private enum GoBlockContext {
+        ARRAY_BODY(ARRAY_BODY_BEGIN, ARRAY_BODY_END, ARRAY_ELEMENT),
+        STRUCT_BODY(STRUCT_BODY_BEGIN, STRUCT_BODY_END, MEMBER_DECLARATION),
+        MAP_BODY(MAP_BODY_BEGIN, MAP_BODY_END, MAP_ELEMENT),
+        SLICE_BODY(SLICE_BODY_BEGIN, SLICE_BODY_END, SLICE_ELEMENT),
+        NAMED_TYPE_BODY(NAMED_TYPE_BODY_BEGIN, NAMED_TYPE_BODY_END, NAMED_TYPE_ELEMENT),
+        FUNCTION_BODY(FUNCTION_BODY_BEGIN, FUNCTION_BODY_END, NONE),
+
+        IF_BLOCK(IF_BLOCK_BEGIN, IF_BLOCK_END, NONE),
+        ELSE_BLOCK(ELSE_BLOCK_BEGIN, ELSE_BLOCK_END, NONE),
+        FOR_BLOCK(FOR_BLOCK_BEGIN, FOR_BLOCK_END, NONE),
+        SWITCH_BLOCK(SWITCH_BLOCK_BEGIN, SWITCH_BLOCK_END, NONE),
+        SELECT_CONTEXT(SELECT_BLOCK_BEGIN, SELECT_BLOCK_END, NONE),
+        STATEMENT_BLOCK(STATEMENT_BLOCK_BEGIN, STATEMENT_BLOCK_END, NONE),
+        CASE_BLOCK(CASE_BLOCK_BEGIN, CASE_BLOCK_END, NONE),
+        INTERFACE_BODY(INTERFACE_BLOCK_BEGIN, INTERFACE_BLOCK_END, NONE);
+
+        private final int beginTokenType;
+        private final int endTokenType;
+        private final int elementTokenType;
+
+        GoBlockContext(int beginTokenType, int endTokenType, int elementTokenType) {
+            this.beginTokenType = beginTokenType;
+            this.endTokenType = endTokenType;
+            this.elementTokenType = elementTokenType;
+        }
+
+        int getBegin() {
+            return this.beginTokenType;
+        }
+
+        int getEnd() {
+            return this.endTokenType;
+        }
+
+        public int getElement() {
+            return this.elementTokenType;
+        }
+    }
+}

+ 49 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/Language.java

@@ -0,0 +1,49 @@
+package de.jplag.golang;
+
+import java.io.File;
+
+import de.jplag.TokenList;
+
+public class Language implements de.jplag.Language {
+
+    private static final String NAME = "Go Parser";
+    private static final String SHORT_NAME = "Go";
+    private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
+    private static final String[] FILE_EXTENSIONS = {".go"};
+    private final GoParserAdapter parserAdapter;
+
+    public Language() {
+        this.parserAdapter = new GoParserAdapter();
+    }
+
+    @Override
+    public String[] suffixes() {
+        return FILE_EXTENSIONS;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    public int minimumTokenMatch() {
+        return DEFAULT_MIN_TOKEN_MATCH;
+    }
+
+    @Override
+    public TokenList parse(File directory, String[] files) {
+        return parserAdapter.parse(directory, files);
+    }
+
+    @Override
+    public boolean hasErrors() {
+        return parserAdapter.hasErrors();
+    }
+
+}

+ 26 - 0
jplag.frontend.golang/src/main/java/de/jplag/golang/grammar/GoParserBase.java

@@ -0,0 +1,26 @@
+package de.jplag.golang.grammar;
+
+import org.antlr.v4.runtime.BufferedTokenStream;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.TokenStream;
+
+/**
+ * All parser methods that used in grammar (p, prev, notLineTerminator, etc.) should start with lower case char similar
+ * to parser rules. Taken from <a href="https://github.com/antlr/grammars-v4/tree/master/golang">the ANTLR4 Project
+ * grammars repository</a>. Licenced under BSD-3.
+ */
+public abstract class GoParserBase extends Parser {
+    protected GoParserBase(TokenStream input) {
+        super(input);
+    }
+
+    /**
+     * Returns true if the current Token is a closing bracket (")" or "}")
+     */
+    protected boolean closingBracket() {
+        BufferedTokenStream stream = (BufferedTokenStream) _input;
+        int prevTokenType = stream.LA(1);
+
+        return prevTokenType == GoParser.R_CURLY || prevTokenType == GoParser.R_PAREN;
+    }
+}

+ 175 - 0
jplag.frontend.golang/src/test/java/de/jplag/golang/GoFrontendTest.java

@@ -0,0 +1,175 @@
+package de.jplag.golang;
+
+import static de.jplag.golang.GoTokenTestUtils.getDummyToken;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.Token;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+import de.jplag.TokenPrinter;
+
+class GoFrontendTest {
+    /**
+     * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens.
+     */
+    private static final String COMPLETE_TEST_FILE = "Complete.go";
+
+    /**
+     * Regular expression that describes lines consisting only of whitespace and optionally a line comment.
+     */
+    private static final String EMPTY_OR_SINGLE_LINE_COMMENT = "\\s*(//.*|/\\*.*\\*/)?";
+
+    /**
+     * Regular expression that describes lines containing the start of a multiline comment and no code before it.
+     */
+    private static final String DELIMITED_COMMENT_START = "\\s*/\\*(?:(?!\\*/).)*$";
+
+    /**
+     * Regular expression that describes lines containing the end of a multiline comment and no more code after that.
+     */
+    private static final String DELIMITED_COMMENT_END = ".*\\*/\\s*$";
+
+    private final Logger logger = LoggerFactory.getLogger("GoLang frontend test");
+    private final String[] testFiles = new String[] {COMPLETE_TEST_FILE};
+    private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "golang").toFile();
+    private Language language;
+
+    @BeforeEach
+    void setup() {
+        language = new Language();
+    }
+
+    @Test
+    void parseTestFiles() {
+        for (String fileName : testFiles) {
+            TokenList tokens = language.parse(testFileLocation, new String[] {fileName});
+            String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName));
+            logger.info(output);
+
+            testSourceCoverage(fileName, tokens);
+            if (fileName.equals(COMPLETE_TEST_FILE)) {
+                testTokenCoverage(tokens, fileName);
+            }
+        }
+    }
+
+    /**
+     * Confirms that every type of GoToken has a Sting representation associated to it.
+     */
+    @Test
+    void testTokenToString() {
+        var missingTokens = IntStream.range(0, GoTokenConstants.NUM_DIFF_TOKENS).mapToObj(GoTokenTestUtils::getDummyToken)
+                .filter(token -> token.type2string().contains("UNKNOWN")).toList();
+
+        if (!missingTokens.isEmpty()) {
+            var typeList = missingTokens.stream().map(Token::getType).map(Object::toString).collect(Collectors.joining(", "));
+            fail("Found token types with no string representation: %s".formatted(typeList));
+        }
+
+    }
+
+    /**
+     * Confirms that the code is covered to a basic extent, i.e. each line of code contains at least one token.
+     * @param fileName a code sample file name
+     * @param tokens the TokenList generated from the sample
+     */
+    private void testSourceCoverage(String fileName, TokenList tokens) {
+        File testFile = new File(testFileLocation, fileName);
+
+        List<String> lines = null;
+        try {
+            lines = Files.readAllLines(testFile.toPath());
+        } catch (IOException exception) {
+            logger.info("Error while reading test file %s".formatted(fileName), exception);
+            fail();
+        }
+
+        // All lines that contain code
+        var codeLines = getCodeLines(lines);
+        // All lines that contain a token
+        var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().boxed().toList();
+
+        if (codeLines.size() > tokenLines.size()) {
+            List<Integer> missedLinesIndices = new ArrayList<>(codeLines);
+            missedLinesIndices.removeAll(tokenLines);
+            var missedLines = missedLinesIndices.stream().map(Object::toString).collect(Collectors.joining(", "));
+            if (!missedLines.isBlank()) {
+                fail("Found lines in file '%s' that are not represented in the token list. \n\tMissed lines: %s".formatted(fileName, missedLines));
+            }
+        }
+        OptionalInt differingLine = IntStream.range(0, codeLines.size())
+                .dropWhile(index -> Objects.equals(codeLines.get(index), tokenLines.get(index))).findAny();
+        differingLine.ifPresent(
+                i -> fail("Not all lines of code in '%s' are represented in tokens, starting with line %d.".formatted(fileName, codeLines.get(i))));
+    }
+
+    /**
+     * Gets the line numbers of lines containing actual code, omitting empty lines and comment lines.
+     * @param lines lines of a code file
+     * @return an array of the line numbers of code lines
+     */
+    private List<Integer> getCodeLines(List<String> lines) {
+        // This boxed boolean can be accessed from within the lambda method below
+        var state = new Object() {
+            boolean insideComment = false;
+        };
+
+        var codeLines = IntStream.rangeClosed(1, lines.size()).sequential().filter(idx -> {
+            String line = lines.get(idx - 1);
+            if (line.matches(EMPTY_OR_SINGLE_LINE_COMMENT)) {
+                return false;
+            } else if (line.matches(DELIMITED_COMMENT_START)) {
+                state.insideComment = true;
+                return false;
+            } else if (state.insideComment) {
+                // This fails if code follows after '*/'. If the code is formatted well, this should not happen.
+                if (line.matches(DELIMITED_COMMENT_END)) {
+                    state.insideComment = false;
+                }
+                return false;
+            }
+            return true;
+        });
+
+        return codeLines.boxed().toList();
+
+    }
+
+    /**
+     * Confirms that all Token types are 'reachable' with a complete code example.
+     * @param tokens TokenList which is supposed to contain all types of tokens
+     * @param fileName The file name of the complete code example
+     */
+    private void testTokenCoverage(TokenList tokens, String fileName) {
+        var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().boxed().toList();
+
+        // Exclude SEPARATOR_TOKEN, as it does not occur
+        var missingTokenTypes = IntStream.range(0, GoTokenConstants.NUM_DIFF_TOKENS).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).boxed()
+                .collect(Collectors.toList());
+        missingTokenTypes.removeAll(foundTokens);
+
+        if (!missingTokenTypes.isEmpty()) {
+            String missingTypesList = missingTokenTypes.stream().map(type -> getDummyToken(type).type2string()).collect(Collectors.joining(", "));
+            fail("Some Token types were not found in the complete code example '%s': %s.".formatted(fileName, missingTypesList));
+        }
+
+    }
+
+}

+ 20 - 0
jplag.frontend.golang/src/test/java/de/jplag/golang/GoTokenTestUtils.java

@@ -0,0 +1,20 @@
+package de.jplag.golang;
+
+public class GoTokenTestUtils {
+
+    private static final int NOT_SET = -1;
+    private static final String EMPTY_STRING = "";
+
+    private GoTokenTestUtils() {
+        // Utility class, should not be instantiated.
+    }
+
+    public static GoToken getDummyToken(int type) {
+        return new GoToken(type, EMPTY_STRING, NOT_SET, NOT_SET, NOT_SET);
+    }
+
+    public static GoToken getDummyToken(int type, String fileName) {
+        return new GoToken(type, fileName, NOT_SET, NOT_SET, NOT_SET);
+    }
+
+}

+ 167 - 0
jplag.frontend.golang/src/test/resources/de/jplag/golang/Complete.go

@@ -0,0 +1,167 @@
+package goExample
+
+import (
+	"fmt"
+	"strings"
+)
+
+type myStruct struct {
+	int number
+	string name
+
+	int	[]data
+}
+
+func (this myStruct) init(number int, name string, data []int) {
+	this.number = number
+	this.name = name
+	this.data = data
+}
+
+func (this myStruct) print() {
+	const (intro = "This is %s, myStruct number %d, which contains %d elements."; separator = "-------------------")
+
+	fmt.Printf(intro, this.name, this.number, len(this.data))
+	fmt.Println(separator)
+}
+
+func getNth(structs []myStruct, index i) myStruct {
+	if i < len(structs) {
+		return structs[i]
+	}
+
+	return nil
+}
+
+func printAll(structs []myStruct) {
+	for i := range structs {
+		var element = getNth(structs, i)
+		element.print()
+	}
+}
+
+func deferredFunction() {
+	fmt.println("This is printed even after panic.")
+}
+
+func panicExample() {
+	defer defferedFunction()
+	panic("This operation does not work!")
+}
+
+func switchExample(int dividend, int divisor) {
+	switch {
+		case divisor == 1: return dividend
+		case divisor == 0: panicExample()
+		case dividend == 0: return 0
+		default: return dividend / divisor
+	}
+}
+
+var isEven = map[int]bool{
+	0: true,
+	1: false,
+	2: true,
+	3: false,
+}
+
+func checkIsEven(value int) bool {
+	f := func(value int) bool {
+		if value >= 0 && value <= 3 {
+			return isEven[value]
+		} else {
+			return value % 2 == 0
+		}
+	}
+
+	return f(value)
+}
+
+func sliceExample() []int {
+	return []int{1, 2, 3}
+}
+
+func fallthroughExample(value int) {
+	switch {
+		case value > 10:
+			fmt.Println("Big number!")
+			fallthrough
+		case value > 100:
+			fmt.Println("Very big number!")
+	}
+}
+
+func createCType() {
+	var element = myStruct{
+		number: 1,
+		name: "myElement",
+		data: [5]int{1, 2, 3, 4, 5},
+	}
+
+}
+
+func createArray() *ast.File {
+	var array = [3]int { nil, nil, nil }
+	return array
+}
+
+func selectExample() int {
+	select {
+		case signal1 := <-ch1:
+			fmt.Println("Haha! ", signal1)
+			return signal1
+		case signal2 := <-ch2:
+			return signal2
+		default: return -1
+	}
+}
+
+func innerBlock() {
+	fmt.Println("Outer")
+	{
+		fmt.Println("Inner")
+	}
+}
+
+func continueExample() {
+	begin: for true {
+		if 1 > 0 {
+			continue;
+		}
+		break;
+	}
+	goto begin
+}
+
+func channelExample(ch chan<- int, value int) {
+	ch <- value
+}
+
+func goExample() {
+	ch := make(chan<- int)
+	go innerBlock(ch, 42)
+}
+
+type InterfaceExample interface {
+	f1(arg int) string
+	f2(arg string) int
+	MySuperType
+}
+
+type MySuperType interface {
+	superMethod()
+}
+
+type Implementation struct {}
+
+func (imp Implementation) f1(arg int) string {
+	return "A"
+}
+
+func (imp Implementation) f2(arg string) int {
+	return 1
+}
+
+func typeAssertion(InterfaceExample i) {
+	fmt.Println(i.(Implementation))
+}

+ 21 - 23
jplag.frontend.text/src/main/antlr/text.g

@@ -1,30 +1,29 @@
-header
-{
-
+header {
 package de.jplag.text;
 package de.jplag.text;
-
 }
 }
 
 
 // tell ANTLR that we want to generate Java source code
 // tell ANTLR that we want to generate Java source code
-options
-{
+options {
   language="Java";
   language="Java";
 }
 }
 
 
 class TextParser extends Parser;
 class TextParser extends Parser;
-options
-{
+options {
   k = 2;			  // two token lookahead
   k = 2;			  // two token lookahead
   //  exportVocab=Text;	          // Call its vocabulary "Text"
   //  exportVocab=Text;	          // Call its vocabulary "Text"
   codeGenMakeSwitchThreshold = 2; // Some optimizations
   codeGenMakeSwitchThreshold = 2; // Some optimizations
   codeGenBitsetTestThreshold = 3;
   codeGenBitsetTestThreshold = 3;
   defaultErrorHandler = true;
   defaultErrorHandler = true;
   buildAST = false;
   buildAST = false;
-  ASTLabelType = "ParserToken";
+  ASTLabelType = "AntlrParserToken";
 }
 }
 
 
 {
 {
-public de.jplag.text.Parser parser;
+    private de.jplag.text.ParserAdapter parser;
+
+    public void setParserAdapter(de.jplag.text.ParserAdapter adapter) {
+        this.parser = adapter;
+    }
 }
 }
 
 
 file : ( w:WORD { parser.add(w); } | PUNCTUATION | SPECIALS )* EOF ;
 file : ( w:WORD { parser.add(w); } | PUNCTUATION | SPECIALS )* EOF ;
@@ -35,22 +34,21 @@ file : ( w:WORD { parser.add(w); } | PUNCTUATION | SPECIALS )* EOF ;
 
 
 {
 {
 import de.jplag.text.InputState;
 import de.jplag.text.InputState;
-import de.jplag.text.ParserToken;
+import de.jplag.text.AntlrParserToken;
 }
 }
 
 
 class TextLexer extends Lexer;
 class TextLexer extends Lexer;
-options
-{
-  //  exportVocab=Text;        // call the vocabulary "Text"
-  testLiterals = false;    // don't automatically test for literals
-  k = 2;                   // two characters of lookahead
-  charVocabulary = '\u0000'..'\u00FF';
+options {
+    //  exportVocab=Text;    // call the vocabulary "Text"
+    testLiterals = false;    // don't automatically test for literals
+    k = 2;                   // two characters of lookahead
+    charVocabulary = '\u0000'..'\u00FF';
 }
 }
 
 
 {
 {
     public void newline() {
     public void newline() {
         super.newline();
         super.newline();
-        ((InputState) inputState).column = 1;
+        ((InputState) inputState).setColumnIndex(1);
     }
     }
 
 
     public void consume() throws antlr.CharStreamException {
     public void consume() throws antlr.CharStreamException {
@@ -58,16 +56,16 @@ options
             InputState state = (InputState) inputState;
             InputState state = (InputState) inputState;
             if (text.length() == 0) {
             if (text.length() == 0) {
                 // remember token start column
                 // remember token start column
-                state.tokenColumn = state.column;
+                state.setTokenColumnIndex(state.getColumnIndex());
             }
             }
-            state.column++;
+            state.setColumnIndex(state.getColumnIndex() + 1);
         }
         }
         super.consume();
         super.consume();
     }
     }
 
 
     protected Token makeToken(int t) {
     protected Token makeToken(int t) {
-        ParserToken token = (ParserToken) super.makeToken(t);
-        token.setColumn(((InputState) inputState).tokenColumn);
+        AntlrParserToken token = (AntlrParserToken) super.makeToken(t);
+        token.setColumn(((InputState) inputState).getTokenColumnIndex());
         return token;
         return token;
     }
     }
 }
 }
@@ -88,7 +86,7 @@ SPECIALS : ('#' | '$' | '%' | '&' | '+' | '<' | '=' | '*' |
 
 
 // Whitespace -- ignored
 // Whitespace -- ignored
 SPACE : ( ' '
 SPACE : ( ' '
-	  |	'\t' //{ ((InputState)inputState).column += 7; }
+	  |	'\t' //{ ((InputState)inputState).setColumnIndex((InputState)inputState).getColumnIndex() + 7); }
 	  |	'\f'
 	  |	'\f'
 	  |     '\240'
 	  |     '\240'
 	  |     ('\001' .. '\010')
 	  |     ('\001' .. '\010')

+ 5 - 2
jplag.frontend.text/src/main/java/de/jplag/text/ParserToken.java → jplag.frontend.text/src/main/java/de/jplag/text/AntlrParserToken.java

@@ -2,7 +2,10 @@ package de.jplag.text;
 
 
 import antlr.Token;
 import antlr.Token;
 
 
-public class ParserToken extends Token {
+/**
+ * Token of the ANTLR grammar, needs to be converted into a JPlag token by the parser adapter.
+ */
+public class AntlrParserToken extends Token {
     /**
     /**
      * This variable holds the line number of the current token.
      * This variable holds the line number of the current token.
      */
      */
@@ -23,7 +26,7 @@ public class ParserToken extends Token {
      */
      */
     private int id = -1;
     private int id = -1;
 
 
-    public ParserToken() {
+    public AntlrParserToken() {
         super();
         super();
     }
     }
 
 

+ 18 - 9
jplag.frontend.text/src/main/java/de/jplag/text/InputState.java

@@ -10,31 +10,40 @@ import antlr.LexerSharedInputState;
  * This object contains the data associated with an input stream of characters. Multiple lexers share a single
  * This object contains the data associated with an input stream of characters. Multiple lexers share a single
  * LexerSharedInputState to lex the same input stream.
  * LexerSharedInputState to lex the same input stream.
  */
  */
-@SuppressWarnings({"java:S1104", "java:S2387"})
 public class InputState extends LexerSharedInputState {
 public class InputState extends LexerSharedInputState {
-    public int column = 0;
-    public int tokenColumn = 0;
+    private int columnIndex = 1;
+    private int tokenColumnIndex = 1;
 
 
     public InputState(InputBuffer inputBuffer) {
     public InputState(InputBuffer inputBuffer) {
         super(inputBuffer);
         super(inputBuffer);
-        column = 1;
-        line = 1;
     }
     }
 
 
     public InputState(InputStream inputStream) {
     public InputState(InputStream inputStream) {
         super(inputStream);
         super(inputStream);
-        column = 1;
-        line = 1;
     }
     }
 
 
     public InputState(Reader inputReader) {
     public InputState(Reader inputReader) {
         super(inputReader);
         super(inputReader);
-        column = 1;
-        line = 1;
     }
     }
 
 
     @Override
     @Override
     public int getLine() {
     public int getLine() {
         return line;
         return line;
     }
     }
+
+    public int getColumnIndex() {
+        return columnIndex;
+    }
+
+    public void setColumnIndex(int columnIndex) {
+        this.columnIndex = columnIndex;
+    }
+
+    public int getTokenColumnIndex() {
+        return tokenColumnIndex;
+    }
+
+    public void setTokenColumnIndex(int tokenColumnIndex) {
+        this.tokenColumnIndex = tokenColumnIndex;
+    }
 }
 }

+ 10 - 5
jplag.frontend.text/src/main/java/de/jplag/text/Language.java

@@ -4,12 +4,17 @@ import java.io.File;
 
 
 import de.jplag.TokenList;
 import de.jplag.TokenList;
 
 
+/**
+ * Language class for parsing (natural language) text. This language module employs a primitive approach where
+ * individual words are interpreted as token types. Whitespace and special characters are ignored. This approach works,
+ * but there are better approaches for text plagiarism out there (based on NLP techniques).
+ */
 public class Language implements de.jplag.Language {
 public class Language implements de.jplag.Language {
 
 
-    private final Parser parser;
+    private final ParserAdapter parserAdapter;
 
 
     public Language() {
     public Language() {
-        parser = new Parser();
+        parserAdapter = new ParserAdapter();
     }
     }
 
 
     @Override
     @Override
@@ -19,7 +24,7 @@ public class Language implements de.jplag.Language {
 
 
     @Override
     @Override
     public String getName() {
     public String getName() {
-        return "Text Parser";
+        return "Text Parser (naive)";
     }
     }
 
 
     @Override
     @Override
@@ -34,12 +39,12 @@ public class Language implements de.jplag.Language {
 
 
     @Override
     @Override
     public TokenList parse(File dir, String[] files) {
     public TokenList parse(File dir, String[] files) {
-        return this.parser.parse(dir, files);
+        return parserAdapter.parse(dir, files);
     }
     }
 
 
     @Override
     @Override
     public boolean hasErrors() {
     public boolean hasErrors() {
-        return this.parser.hasErrors();
+        return parserAdapter.hasErrors();
     }
     }
 
 
     @Override
     @Override

+ 0 - 87
jplag.frontend.text/src/main/java/de/jplag/text/Parser.java

@@ -1,87 +0,0 @@
-package de.jplag.text;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.Hashtable;
-
-import antlr.Token;
-
-import de.jplag.AbstractParser;
-import de.jplag.TokenConstants;
-import de.jplag.TokenList;
-
-public class Parser extends AbstractParser {
-
-    protected Hashtable<String, Integer> table = new Hashtable<>();
-    protected int serial = 1; // 0 is FILE_END token
-
-    private TokenList tokens;
-
-    private String currentFile;
-
-    private boolean runOut = false;
-
-    /**
-     * Creates the parser.
-     */
-    public Parser() {
-        super();
-    }
-
-    public TokenList parse(File directory, String[] files) {
-        tokens = new TokenList();
-        errors = 0;
-        for (String file : files) {
-            logger.trace("Parsing file {}", file);
-            if (!parseFile(directory, file))
-                errors++;
-            tokens.addToken(new TextToken(TokenConstants.FILE_END, file, this));
-        }
-
-        TokenList tmp = tokens;
-        tokens = null;
-        return tmp;
-    }
-
-    public void add(Token token) {
-        ParserToken parserToken = (ParserToken) token;
-        tokens.addToken(new TextToken(token.getText(), currentFile, parserToken.getLine(), parserToken.getColumn(), parserToken.getLength(), this));
-    }
-
-    public void outOfSerials() {
-        if (runOut)
-            return;
-        runOut = true;
-        errors++;
-        logger.error("Out of serials!");
-    }
-
-    private boolean parseFile(File directory, String file) {
-        InputState inputState = null;
-        try {
-            FileInputStream inputStream = new FileInputStream(new File(directory, file));
-            currentFile = file;
-            // Create a scanner that reads from the input stream passed to us
-            inputState = new InputState(inputStream);
-            TextLexer lexer = new TextLexer(inputState);
-            lexer.setFilename(file);
-            lexer.setTokenObjectClass("de.jplag.text.ParserToken");
-
-            // Create a parser that reads from the scanner
-            TextParser parser = new TextParser(lexer);
-            parser.setFilename(file);
-            parser.parser = this;// Added by Emeric 26.01.05 BAD
-
-            // start parsing at the compilationUnit rule
-            parser.file();
-
-            // close file
-            inputStream.close();
-        } catch (Exception e) {
-            logger.error("Parsing Error in '" + file + "' (line " + (inputState != null ? "" + inputState.getLine() : "") + "):\n  " + e.getMessage(),
-                    e);
-            return false;
-        }
-        return true;
-    }
-}

+ 84 - 0
jplag.frontend.text/src/main/java/de/jplag/text/ParserAdapter.java

@@ -0,0 +1,84 @@
+package de.jplag.text;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import antlr.Token;
+
+import de.jplag.AbstractParser;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+
+public class ParserAdapter extends AbstractParser {
+
+    private final Map<String, Integer> tokenTypes = new HashMap<>();
+    private int tokenTypeIndex = 1; // 0 is FILE_END token, SEPARATOR is not used as there are no methods.
+
+    private TokenList tokens;
+    private String currentFile;
+
+    public TokenList parse(File directory, String[] files) {
+        tokens = new TokenList();
+        errors = 0;
+        for (String file : files) {
+            logger.trace("Parsing file {}", file);
+            if (!parseFile(directory, file)) {
+                errors++;
+            }
+            tokens.addToken(new TextToken(TokenConstants.FILE_END, file));
+        }
+        return tokens;
+    }
+
+    /**
+     * Converts a ANTLR token into a JPlag token and adds it to the token list.
+     * @param token is the ANTLR token to convert.
+     */
+    public void add(Token token) {
+        if (token instanceof AntlrParserToken parserToken) {
+            String text = token.getText();
+            int type = getTokenType(text);
+            tokens.addToken(new TextToken(text, type, currentFile, parserToken));
+        } else {
+            throw new IllegalArgumentException("Illegal token implementation: " + token);
+        }
+    }
+
+    private boolean parseFile(File directory, String file) {
+        InputState inputState = null;
+        try (FileInputStream inputStream = new FileInputStream(new File(directory, file))) {
+            currentFile = file;
+            // Create a scanner that reads from the input stream passed to us
+            inputState = new InputState(inputStream);
+            TextLexer lexer = new TextLexer(inputState);
+            lexer.setFilename(file);
+            lexer.setTokenObjectClass("de.jplag.text.AntlrParserToken");
+
+            // Create a parser that reads from the scanner
+            TextParser parser = new TextParser(lexer);
+            parser.setFilename(file);
+            parser.setParserAdapter(this);
+
+            // start parsing at the compilationUnit rule
+            parser.file();
+        } catch (Exception e) {
+            logger.error("Parsing Error in " + file + " (line " + (inputState != null ? "" + inputState.getLine() : "") + "):" + e.getMessage(), e);
+            return false;
+        }
+        return true;
+    }
+
+    private int getTokenType(String text) {
+        text = text.toLowerCase();
+        tokenTypes.computeIfAbsent(text, it -> {
+            if (tokenTypeIndex == Integer.MAX_VALUE) {
+                throw new IllegalStateException("Too many token types, should not happen!");
+            }
+            return ++tokenTypeIndex;
+        });
+        return tokenTypes.get(text);
+
+    }
+}

+ 6 - 23
jplag.frontend.text/src/main/java/de/jplag/text/TextToken.java

@@ -1,36 +1,19 @@
 package de.jplag.text;
 package de.jplag.text;
 
 
-import java.io.Serial;
-
 import de.jplag.Token;
 import de.jplag.Token;
 
 
 public class TextToken extends Token {
 public class TextToken extends Token {
-    @Serial
-    private static final long serialVersionUID = 4301179216570538972L;
-
-    private static int getSerial(String text, Parser parser) {
-        text = text.toLowerCase();
-        Integer serial = parser.table.get(text);
-        if (serial == null) {
-            serial = parser.serial;
-            if (parser.serial == Integer.MAX_VALUE)
-                parser.outOfSerials();
-            else
-                parser.serial++;
-            parser.table.put(text, serial);
-        }
-        return serial;
-    }
 
 
-    private String text;
+    private static final String NO_TEXT = "<unknown>";
+    private final String text;
 
 
-    public TextToken(int type, String file, Parser parser) {
+    public TextToken(int type, String file) {
         super(type, file, -1, -1, -1);
         super(type, file, -1, -1, -1);
+        this.text = NO_TEXT;
     }
     }
 
 
-    public TextToken(String text, String file, int line, int column, int length, Parser parser) {
-        super(-1, file, line, column, length);
-        this.type = getSerial(text, parser);
+    public TextToken(String text, int type, String file, AntlrParserToken parserToken) {
+        super(type, file, parserToken.getLine(), parserToken.getColumn(), parserToken.getLength());
         this.text = text.toLowerCase();
         this.text = text.toLowerCase();
     }
     }
 
 

+ 54 - 0
jplag.frontend.text/src/test/java/jplag/text/TextFrontendTest.java

@@ -0,0 +1,54 @@
+package jplag.text;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.StreamSupport;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.Token;
+import de.jplag.TokenList;
+import de.jplag.TokenPrinter;
+import de.jplag.text.Language;
+
+class TextFrontendTest {
+    private final Logger logger = LoggerFactory.getLogger("JPlag-Test");
+
+    private static final Path BASE_PATH = Path.of("src", "test", "resources");
+    private static final String TEST_SUBJECT = "FutureJavaDoc.txt";
+
+    private de.jplag.Language frontend;
+    private File baseDirectory;
+
+    @BeforeEach
+    public void setUp() {
+        frontend = new Language();
+        baseDirectory = BASE_PATH.toFile();
+        assertTrue(baseDirectory.exists(), "Could not find base directory!");
+    }
+
+    @Test
+    void testParsingJavaDoc() {
+        // Parse test input
+        String[] input = new String[] {TEST_SUBJECT};
+        TokenList result = frontend.parse(baseDirectory, input);
+        logger.info(TokenPrinter.printTokens(result, baseDirectory, Arrays.asList(input)));
+
+        // Compare parsed tokens:
+        Map<Integer, Token> tokenTypes = new HashMap<>();
+        StreamSupport.stream(result.allTokens().spliterator(), false).forEach(it -> tokenTypes.put(it.getType(), it));
+
+        assertEquals(293, result.size());
+        assertEquals(156, tokenTypes.values().size());
+    }
+
+}

+ 50 - 0
jplag.frontend.text/src/test/resources/FutureJavaDoc.txt

@@ -0,0 +1,50 @@
+/**
+ * A {@code Future} represents the result of an asynchronous
+ * computation.  Methods are provided to check if the computation is
+ * complete, to wait for its completion, and to retrieve the result of
+ * the computation.  The result can only be retrieved using method
+ * {@code get} when the computation has completed, blocking if
+ * necessary until it is ready.  Cancellation is performed by the
+ * {@code cancel} method.  Additional methods are provided to
+ * determine if the task completed normally or was cancelled. Once a
+ * computation has completed, the computation cannot be cancelled.
+ * If you would like to use a {@code Future} for the sake
+ * of cancellability but not provide a usable result, you can
+ * declare types of the form {@code Future<?>} and
+ * return {@code null} as a result of the underlying task.
+ *
+ * <p><b>Sample Usage</b> (Note that the following classes are all
+ * made-up.)
+ *
+ * <pre> {@code
+ * interface ArchiveSearcher { String search(String target); }
+ * class App {
+ *   ExecutorService executor = ...;
+ *   ArchiveSearcher searcher = ...;
+ *   void showSearch(String target) throws InterruptedException {
+ *     Callable<String> task = () -> searcher.search(target);
+ *     Future<String> future = executor.submit(task);
+ *     displayOtherThings(); // do other things while searching
+ *     try {
+ *       displayText(future.get()); // use future
+ *     } catch (ExecutionException ex) { cleanup(); return; }
+ *   }
+ * }}</pre>
+ *
+ * The {@link FutureTask} class is an implementation of {@code Future} that
+ * implements {@code Runnable}, and so may be executed by an {@code Executor}.
+ * For example, the above construction with {@code submit} could be replaced by:
+ * <pre> {@code
+ * FutureTask<String> future = new FutureTask<>(task);
+ * executor.execute(future);}</pre>
+ *
+ * <p>Memory consistency effects: Actions taken by the asynchronous computation
+ * <a href="package-summary.html#MemoryVisibility"> <i>happen-before</i></a>
+ * actions following the corresponding {@code Future.get()} in another thread.
+ *
+ * @see FutureTask
+ * @see Executor
+ * @since 1.5
+ * @author Doug Lea
+ * @param <V> The result type returned by this Future's {@code get} method
+ */

+ 5 - 1
jplag/pom.xml

@@ -47,12 +47,16 @@
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <groupId>de.jplag</groupId>
-            <artifactId>rlang</artifactId>
+            <artifactId>golang</artifactId>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <groupId>de.jplag</groupId>
             <artifactId>kotlin</artifactId>
             <artifactId>kotlin</artifactId>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>rlang</artifactId>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <groupId>de.jplag</groupId>
             <artifactId>scheme</artifactId>
             <artifactId>scheme</artifactId>

+ 13 - 10
jplag/src/main/java/de/jplag/JPlagComparison.java

@@ -1,13 +1,13 @@
 package de.jplag;
 package de.jplag;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.List;
+import java.util.Objects;
 
 
 /**
 /**
  * This method represents the whole result of a comparison between two submissions.
  * This method represents the whole result of a comparison between two submissions.
  */
  */
-public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME TS: contains a lot of code duplication
+public class JPlagComparison { // FIXME TS: contains a lot of code duplication
 
 
     private static final int ROUNDING_FACTOR = 10;
     private static final int ROUNDING_FACTOR = 10;
 
 
@@ -35,18 +35,21 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         matches.add(new Match(startOfFirst, startOfSecond, length));
         matches.add(new Match(startOfFirst, startOfSecond, length));
     }
     }
 
 
-    @Override
-    public int compare(JPlagComparison comparison1, JPlagComparison comparison2) {
-        return Float.compare(comparison2.similarity(), comparison1.similarity()); // comparison2 first!
-    }
-
-    // TODO DF: hashCode is not implemented!!
     @Override
     @Override
     public boolean equals(Object other) {
     public boolean equals(Object other) {
-        if (!(other instanceof JPlagComparison)) {
+        if (other == this) {
+            return true;
+        }
+        if (!(other instanceof JPlagComparison otherComparison)) {
             return false;
             return false;
         }
         }
-        return (compare(this, (JPlagComparison) other) == 0);
+        return firstSubmission.equals(otherComparison.getFirstSubmission()) && secondSubmission.equals(otherComparison.getSecondSubmission())
+                && matches.equals(otherComparison.matches);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(firstSubmission, secondSubmission, matches);
     }
     }
 
 
     /**
     /**

+ 17 - 1
jplag/src/main/java/de/jplag/Submission.java

@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.Objects;
 
 
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
@@ -75,10 +76,25 @@ public class Submission implements Comparable<Submission> {
 
 
     @Override
     @Override
     public int compareTo(Submission other) {
     public int compareTo(Submission other) {
-        // TODO DF: "equals(Object obj)" should be overridden along with the "compareTo(T obj)" method
         return name.compareTo(other.name);
         return name.compareTo(other.name);
     }
     }
 
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof Submission otherSubmission)) {
+            return false;
+        }
+        return otherSubmission.getName().equals(name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+
     /**
     /**
      * @return base code comparison
      * @return base code comparison
      */
      */

+ 20 - 20
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -130,27 +130,27 @@ public class SubmissionSetBuilder {
 
 
     private Optional<Submission> loadBaseCode(Set<File> submissionDirectories, Set<File> oldSubmissionDirectories,
     private Optional<Submission> loadBaseCode(Set<File> submissionDirectories, Set<File> oldSubmissionDirectories,
             Map<File, Submission> foundSubmissions) throws ExitException {
             Map<File, Submission> foundSubmissions) throws ExitException {
-        // Extract the basecode submission if necessary.
-        Optional<Submission> baseCodeSubmission = Optional.empty();
-        if (options.hasBaseCode()) {
-            String baseCodeName = options.getBaseCodeSubmissionName().orElseThrow();
-            Submission baseCode = loadBaseCodeAsPath(baseCodeName);
-            if (baseCode == null) {
-                int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
-                if (numberOfRootDirectories > 1) {
-                    throw new BasecodeException("The base code submission needs to be specified by path instead of by name!");
-                }
-
-                // There is one root directory, and the submissionDirectories variable has been checked to be non-empty.
-                // That set thus contains the one and only root directory.
-                File rootDirectory = submissionDirectories.iterator().next();
-
-                // Single root-directory, try the legacy way of specifying basecode.
-                baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
+        if (!options.hasBaseCode()) {
+            return Optional.empty();
+        }
+
+        String baseCodeName = options.getBaseCodeSubmissionName().orElseThrow();
+        Submission baseCode = loadBaseCodeAsPath(baseCodeName);
+        if (baseCode == null) {
+            int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
+            if (numberOfRootDirectories > 1) {
+                throw new BasecodeException("The base code submission needs to be specified by path instead of by name!");
             }
             }
-            // TODO DF: Here the method assumes that baseCode can be null. later, this is not assumed anymore
-            baseCodeSubmission = Optional.ofNullable(baseCode);
 
 
+            // There is one root directory, and the submissionDirectories variable has been checked to be non-empty.
+            // That set thus contains the one and only root directory.
+            File rootDirectory = submissionDirectories.iterator().next();
+
+            // Single root-directory, try the legacy way of specifying basecode.
+            baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
+        }
+
+        if (baseCode != null) {
             logger.info("Basecode directory \"{}\" will be used.", baseCode.getName());
             logger.info("Basecode directory \"{}\" will be used.", baseCode.getName());
 
 
             // Basecode may also be registered as a user submission. If so, remove the latter.
             // Basecode may also be registered as a user submission. If so, remove the latter.
@@ -159,7 +159,7 @@ public class SubmissionSetBuilder {
                 logger.info("Submission \"{}\" is the specified basecode, it will be skipped during comparison.", removed.getName());
                 logger.info("Submission \"{}\" is the specified basecode, it will be skipped during comparison.", removed.getName());
             }
             }
         }
         }
-        return baseCodeSubmission;
+        return Optional.ofNullable(baseCode);
     }
     }
 
 
     /**
     /**

+ 3 - 2
jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java

@@ -45,6 +45,9 @@ public class BayesianOptimization {
      * @param lengthScale width parameter for the matern kernel
      * @param lengthScale width parameter for the matern kernel
      */
      */
     public BayesianOptimization(RealVector minima, RealVector maxima, int initPoints, int maxEvaluations, double noise, RealVector lengthScale) {
     public BayesianOptimization(RealVector minima, RealVector maxima, int initPoints, int maxEvaluations, double noise, RealVector lengthScale) {
+        if (minima.getDimension() == 0) {
+            throw new IllegalArgumentException("explored parameters must at least have one dimension");
+        }
         if (minima.getDimension() != maxima.getDimension()) {
         if (minima.getDimension() != maxima.getDimension()) {
             throw new DimensionMismatchException(minima.getDimension(), maxima.getDimension());
             throw new DimensionMismatchException(minima.getDimension(), maxima.getDimension());
         }
         }
@@ -162,7 +165,6 @@ public class BayesianOptimization {
                 if (debug && logger.isDebugEnabled()) {
                 if (debug && logger.isDebugEnabled()) {
                     logger.debug(gpr.toString(minima, maxima, 100, 25, 0));
                     logger.debug(gpr.toString(minima, maxima, 100, 25, 0));
                 }
                 }
-                // TODO Check that best is not null here
                 coordinates = maxAcq(gpr, best.score, poiSampler, zeroAcquisitionsCounter);
                 coordinates = maxAcq(gpr, best.score, poiSampler, zeroAcquisitionsCounter);
                 testedCoordinates.add(coordinates);
                 testedCoordinates.add(coordinates);
             }
             }
@@ -173,7 +175,6 @@ public class BayesianOptimization {
                 best = result;
                 best = result;
             }
             }
         }
         }
-
         return best;
         return best;
     }
     }
 
 

+ 2 - 1
jplag/src/main/java/de/jplag/options/LanguageOption.java

@@ -13,8 +13,9 @@ public enum LanguageOption {
     PYTHON_3("python3", "de.jplag.python3.Language"),
     PYTHON_3("python3", "de.jplag.python3.Language"),
     C_CPP("cpp", "de.jplag.cpp.Language"),
     C_CPP("cpp", "de.jplag.cpp.Language"),
     C_SHARP("csharp", "de.jplag.csharp.Language"),
     C_SHARP("csharp", "de.jplag.csharp.Language"),
-    R_LANG("rlang", "de.jplag.rlang.Language"),
+    GO_LANG("golang", "de.jplag.golang.Language"),
     KOTLIN("kotlin", "de.jplag.kotlin.Language"),
     KOTLIN("kotlin", "de.jplag.kotlin.Language"),
+    R_LANG("rlang", "de.jplag.rlang.Language"),
     CHAR("char", "de.jplag.chars.Language"),
     CHAR("char", "de.jplag.chars.Language"),
     TEXT("text", "de.jplag.text.Language"),
     TEXT("text", "de.jplag.text.Language"),
     SCHEME("scheme", "de.jplag.scheme.Language"),
     SCHEME("scheme", "de.jplag.scheme.Language"),

+ 7 - 1
jplag/src/test/java/de/jplag/special/TokenPrinterTest.java

@@ -43,7 +43,7 @@ public class TokenPrinterTest extends TestBase {
         });
         });
     }
     }
 
 
-    @Disabled
+    @Disabled("Not a meaningful test, used for designing the token set")
     @Test
     @Test
     void printRLangFiles() {
     void printRLangFiles() {
         printSubmissions(options -> {
         printSubmissions(options -> {
@@ -52,6 +52,12 @@ public class TokenPrinterTest extends TestBase {
         });
         });
     }
     }
 
 
+    @Disabled("Not a meaningful test, used for designing the token set")
+    @Test
+    void printGoFiles() {
+        printSubmissions(options -> options.setLanguageOption(LanguageOption.GO_LANG));
+    }
+
     @Disabled("Not a meaningful test, used for designing the token set")
     @Disabled("Not a meaningful test, used for designing the token set")
     @Test
     @Test
     void printKotlinFiles() {
     void printKotlinFiles() {

+ 10 - 3
pom.xml

@@ -74,7 +74,7 @@
 
 
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
         <maven.compiler.target>17</maven.compiler.target>
-        <spotless.version>2.23.0</spotless.version>
+        <spotless.version>2.24.0</spotless.version>
         <slf4j.version>1.7.36</slf4j.version>
         <slf4j.version>1.7.36</slf4j.version>
 
 
         <antlr2.version>2.7.7</antlr2.version>
         <antlr2.version>2.7.7</antlr2.version>
@@ -154,12 +154,12 @@
             </dependency>
             </dependency>
             <dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
                 <groupId>de.jplag</groupId>
-                <artifactId>rlang</artifactId>
+                <artifactId>cpp</artifactId>
                 <version>${revision}</version>
                 <version>${revision}</version>
             </dependency>
             </dependency>
             <dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
                 <groupId>de.jplag</groupId>
-                <artifactId>cpp</artifactId>
+                <artifactId>golang</artifactId>
                 <version>${revision}</version>
                 <version>${revision}</version>
             </dependency>
             </dependency>
             <dependency>
             <dependency>
@@ -167,6 +167,11 @@
                 <artifactId>kotlin</artifactId>
                 <artifactId>kotlin</artifactId>
                 <version>${revision}</version>
                 <version>${revision}</version>
             </dependency>
             </dependency>
+            <dependency>
+                <groupId>de.jplag</groupId>
+                <artifactId>rlang</artifactId>
+                <version>${revision}</version>
+            </dependency>
             <dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
                 <groupId>de.jplag</groupId>
                 <artifactId>scheme</artifactId>
                 <artifactId>scheme</artifactId>
@@ -458,6 +463,7 @@
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.csharp-6</module>
                 <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.python-3</module>
                 <module>jplag.frontend.python-3</module>
@@ -481,6 +487,7 @@
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.csharp-6</module>
                 <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.python-3</module>
                 <module>jplag.frontend.python-3</module>

+ 301 - 146
report-viewer/package-lock.json

@@ -9,22 +9,22 @@
       "version": "0.1.0",
       "version": "0.1.0",
       "dependencies": {
       "dependencies": {
         "@highlightjs/vue-plugin": "^2.1.0",
         "@highlightjs/vue-plugin": "^2.1.0",
-        "chart.js": "^3.8.2",
-        "chartjs-plugin-datalabels": "^2.0.0",
-        "core-js": "^3.24.0",
+        "chart.js": "^3.9.0",
+        "chartjs-plugin-datalabels": "^2.1.0",
+        "core-js": "^3.24.1",
         "gitart-vue-dialog": "^2.4.0",
         "gitart-vue-dialog": "^2.4.0",
         "highlight.js": "^11.6.0",
         "highlight.js": "^11.6.0",
-        "jszip": "^3.10.0",
+        "jszip": "^3.10.1",
         "vue": "^3.2.37",
         "vue": "^3.2.37",
         "vue-chart-3": "^3.1.8",
         "vue-chart-3": "^3.1.8",
         "vue-draggable-next": "^2.1.1",
         "vue-draggable-next": "^2.1.1",
-        "vue-router": "^4.1.2",
+        "vue-router": "^4.1.3",
         "vue3-highlightjs": "^1.0.5",
         "vue3-highlightjs": "^1.0.5",
         "vuex": "^4.0.2"
         "vuex": "^4.0.2"
       },
       },
       "devDependencies": {
       "devDependencies": {
-        "@typescript-eslint/eslint-plugin": "^5.31.0",
-        "@typescript-eslint/parser": "^5.31.0",
+        "@typescript-eslint/eslint-plugin": "^5.32.0",
+        "@typescript-eslint/parser": "^5.32.0",
         "@vue/cli-plugin-babel": "~5.0.8",
         "@vue/cli-plugin-babel": "~5.0.8",
         "@vue/cli-plugin-eslint": "~5.0.8",
         "@vue/cli-plugin-eslint": "~5.0.8",
         "@vue/cli-plugin-router": "~5.0.8",
         "@vue/cli-plugin-router": "~5.0.8",
@@ -33,7 +33,7 @@
         "@vue/compiler-sfc": "^3.2.37",
         "@vue/compiler-sfc": "^3.2.37",
         "@vue/eslint-config-prettier": "^7.0.0",
         "@vue/eslint-config-prettier": "^7.0.0",
         "@vue/eslint-config-typescript": "^10.0.0",
         "@vue/eslint-config-typescript": "^10.0.0",
-        "eslint": "^8.20.0",
+        "eslint": "^8.21.0",
         "eslint-plugin-prettier": "^4.2.1",
         "eslint-plugin-prettier": "^4.2.1",
         "eslint-plugin-vue": "^8.7.1",
         "eslint-plugin-vue": "^8.7.1",
         "lint-staged": "^13.0.3",
         "lint-staged": "^13.0.3",
@@ -1770,9 +1770,9 @@
       }
       }
     },
     },
     "node_modules/@humanwhocodes/config-array": {
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.9.3",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz",
-      "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==",
+      "version": "0.10.4",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
+      "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
         "@humanwhocodes/object-schema": "^1.2.1",
         "@humanwhocodes/object-schema": "^1.2.1",
@@ -1783,6 +1783,16 @@
         "node": ">=10.10.0"
         "node": ">=10.10.0"
       }
       }
     },
     },
+    "node_modules/@humanwhocodes/gitignore-to-minimatch": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
+      "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
     "node_modules/@humanwhocodes/object-schema": {
     "node_modules/@humanwhocodes/object-schema": {
       "version": "1.2.1",
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
@@ -2221,14 +2231,14 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.31.0.tgz",
-      "integrity": "sha512-VKW4JPHzG5yhYQrQ1AzXgVgX8ZAJEvCz0QI6mLRX4tf7rnFfh5D8SKm0Pq6w5PyNfAWJk6sv313+nEt3ohWMBQ==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz",
+      "integrity": "sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/type-utils": "5.31.0",
-        "@typescript-eslint/utils": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/type-utils": "5.32.0",
+        "@typescript-eslint/utils": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
         "ignore": "^5.2.0",
@@ -2269,14 +2279,14 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/parser": {
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.31.0.tgz",
-      "integrity": "sha512-UStjQiZ9OFTFReTrN+iGrC6O/ko9LVDhreEK5S3edmXgR396JGq7CoX2TWIptqt/ESzU2iRKXAHfSF2WJFcWHw==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.32.0.tgz",
+      "integrity": "sha512-IxRtsehdGV9GFQ35IGm5oKKR2OGcazUoiNBxhRV160iF9FoyuXxjY+rIqs1gfnd+4eL98OjeGnMpE7RF/NBb3A==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/typescript-estree": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/typescript-estree": "5.32.0",
         "debug": "^4.3.4"
         "debug": "^4.3.4"
       },
       },
       "engines": {
       "engines": {
@@ -2296,13 +2306,13 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/scope-manager": {
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.31.0.tgz",
-      "integrity": "sha512-8jfEzBYDBG88rcXFxajdVavGxb5/XKXyvWgvD8Qix3EEJLCFIdVloJw+r9ww0wbyNLOTYyBsR+4ALNGdlalLLg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz",
+      "integrity": "sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/visitor-keys": "5.31.0"
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/visitor-keys": "5.32.0"
       },
       },
       "engines": {
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2313,12 +2323,12 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/type-utils": {
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.31.0.tgz",
-      "integrity": "sha512-7ZYqFbvEvYXFn9ax02GsPcEOmuWNg+14HIf4q+oUuLnMbpJ6eHAivCg7tZMVwzrIuzX3QCeAOqKoyMZCv5xe+w==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz",
+      "integrity": "sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/utils": "5.31.0",
+        "@typescript-eslint/utils": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
         "tsutils": "^3.21.0"
       },
       },
@@ -2339,9 +2349,9 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/types": {
     "node_modules/@typescript-eslint/types": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.31.0.tgz",
-      "integrity": "sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.32.0.tgz",
+      "integrity": "sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==",
       "dev": true,
       "dev": true,
       "engines": {
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2352,13 +2362,13 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/typescript-estree": {
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.31.0.tgz",
-      "integrity": "sha512-3S625TMcARX71wBc2qubHaoUwMEn+l9TCsaIzYI/ET31Xm2c9YQ+zhGgpydjorwQO9pLfR/6peTzS/0G3J/hDw==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz",
+      "integrity": "sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/visitor-keys": "5.31.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/visitor-keys": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
         "is-glob": "^4.0.3",
@@ -2394,15 +2404,15 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/utils": {
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.31.0.tgz",
-      "integrity": "sha512-kcVPdQS6VIpVTQ7QnGNKMFtdJdvnStkqS5LeALr4rcwx11G6OWb2HB17NMPnlRHvaZP38hL9iK8DdE9Fne7NYg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.32.0.tgz",
+      "integrity": "sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
         "@types/json-schema": "^7.0.9",
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/typescript-estree": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/typescript-estree": "5.32.0",
         "eslint-scope": "^5.1.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
         "eslint-utils": "^3.0.0"
       },
       },
@@ -2418,12 +2428,12 @@
       }
       }
     },
     },
     "node_modules/@typescript-eslint/visitor-keys": {
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.31.0.tgz",
-      "integrity": "sha512-ZK0jVxSjS4gnPirpVjXHz7mgdOsZUHzNYSfTw2yPa3agfbt9YfqaBiBZFSSxeBWnpWkzCxTfUpnzA3Vily/CSg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz",
+      "integrity": "sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "@typescript-eslint/types": "5.31.0",
+        "@typescript-eslint/types": "5.32.0",
         "eslint-visitor-keys": "^3.3.0"
         "eslint-visitor-keys": "^3.3.0"
       },
       },
       "engines": {
       "engines": {
@@ -3419,9 +3429,9 @@
       }
       }
     },
     },
     "node_modules/acorn": {
     "node_modules/acorn": {
-      "version": "8.7.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
-      "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
+      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
       "dev": true,
       "dev": true,
       "bin": {
       "bin": {
         "acorn": "bin/acorn"
         "acorn": "bin/acorn"
@@ -4118,14 +4128,14 @@
       }
       }
     },
     },
     "node_modules/chart.js": {
     "node_modules/chart.js": {
-      "version": "3.8.2",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz",
-      "integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ=="
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.0.tgz",
+      "integrity": "sha512-SHOC6eCTe3dXe6owLHnO56xTLBbVeMHskkAlgPPVkd6xa0mv3EPhH5azRLg1cucYFsp4UloH/4NjSG0oOIFJtw=="
     },
     },
     "node_modules/chartjs-plugin-datalabels": {
     "node_modules/chartjs-plugin-datalabels": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0.tgz",
-      "integrity": "sha512-WBsWihphzM0Y8fmQVm89+iy99mmgejmj5/jcsYqwxSioLRL/zqJ4Scv/eXq5ZqvG3TpojlGzZLeaOaSvDm7fwA==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.1.0.tgz",
+      "integrity": "sha512-WA6R4saSlY6mnyX78SkbSo2gGc+cj87lFi5zBrsjjYxE76JgXyxHa1OTodVCzRPoqeYJqSEOffeJ/897kRHR6w==",
       "peerDependencies": {
       "peerDependencies": {
         "chart.js": "^3.0.0"
         "chart.js": "^3.0.0"
       }
       }
@@ -4662,9 +4672,9 @@
       }
       }
     },
     },
     "node_modules/core-js": {
     "node_modules/core-js": {
-      "version": "3.24.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.0.tgz",
-      "integrity": "sha512-IeOyT8A6iK37Ep4kZDD423mpi6JfPRoPUdQwEWYiGolvn4o6j2diaRzNfDfpTdu3a5qMbrGUzKUpYpRY8jXCkQ==",
+      "version": "3.24.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
+      "integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==",
       "hasInstallScript": true,
       "hasInstallScript": true,
       "funding": {
       "funding": {
         "type": "opencollective",
         "type": "opencollective",
@@ -5593,13 +5603,14 @@
       }
       }
     },
     },
     "node_modules/eslint": {
     "node_modules/eslint": {
-      "version": "8.20.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.20.0.tgz",
-      "integrity": "sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==",
+      "version": "8.21.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz",
+      "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
         "@eslint/eslintrc": "^1.3.0",
         "@eslint/eslintrc": "^1.3.0",
-        "@humanwhocodes/config-array": "^0.9.2",
+        "@humanwhocodes/config-array": "^0.10.4",
+        "@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
         "ajv": "^6.10.0",
         "ajv": "^6.10.0",
         "chalk": "^4.0.0",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "cross-spawn": "^7.0.2",
@@ -5609,14 +5620,17 @@
         "eslint-scope": "^7.1.1",
         "eslint-scope": "^7.1.1",
         "eslint-utils": "^3.0.0",
         "eslint-utils": "^3.0.0",
         "eslint-visitor-keys": "^3.3.0",
         "eslint-visitor-keys": "^3.3.0",
-        "espree": "^9.3.2",
+        "espree": "^9.3.3",
         "esquery": "^1.4.0",
         "esquery": "^1.4.0",
         "esutils": "^2.0.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
         "fast-deep-equal": "^3.1.3",
         "file-entry-cache": "^6.0.1",
         "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
         "functional-red-black-tree": "^1.0.1",
         "functional-red-black-tree": "^1.0.1",
         "glob-parent": "^6.0.1",
         "glob-parent": "^6.0.1",
         "globals": "^13.15.0",
         "globals": "^13.15.0",
+        "globby": "^11.1.0",
+        "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.0.0",
         "import-fresh": "^3.0.0",
         "imurmurhash": "^0.1.4",
         "imurmurhash": "^0.1.4",
@@ -5900,6 +5914,22 @@
         "node": ">=4.0"
         "node": ">=4.0"
       }
       }
     },
     },
+    "node_modules/eslint/node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/eslint/node_modules/globals": {
     "node_modules/eslint/node_modules/globals": {
       "version": "13.15.0",
       "version": "13.15.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
       "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@@ -5924,6 +5954,51 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
+    "node_modules/eslint/node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint/node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint/node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/eslint/node_modules/path-key": {
     "node_modules/eslint/node_modules/path-key": {
       "version": "3.1.1",
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -5994,17 +6069,20 @@
       }
       }
     },
     },
     "node_modules/espree": {
     "node_modules/espree": {
-      "version": "9.3.2",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
-      "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
+      "version": "9.3.3",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz",
+      "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==",
       "dev": true,
       "dev": true,
       "dependencies": {
       "dependencies": {
-        "acorn": "^8.7.1",
+        "acorn": "^8.8.0",
         "acorn-jsx": "^5.3.2",
         "acorn-jsx": "^5.3.2",
         "eslint-visitor-keys": "^3.3.0"
         "eslint-visitor-keys": "^3.3.0"
       },
       },
       "engines": {
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
       }
       }
     },
     },
     "node_modules/esquery": {
     "node_modules/esquery": {
@@ -6767,6 +6845,12 @@
       "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
       "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/grapheme-splitter": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+      "dev": true
+    },
     "node_modules/gzip-size": {
     "node_modules/gzip-size": {
       "version": "6.0.0",
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@@ -7568,9 +7652,9 @@
       }
       }
     },
     },
     "node_modules/jszip": {
     "node_modules/jszip": {
-      "version": "3.10.0",
-      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz",
-      "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==",
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
       "dependencies": {
       "dependencies": {
         "lie": "~3.3.0",
         "lie": "~3.3.0",
         "pako": "~1.0.2",
         "pako": "~1.0.2",
@@ -11872,9 +11956,9 @@
       }
       }
     },
     },
     "node_modules/vue-router": {
     "node_modules/vue-router": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.2.tgz",
-      "integrity": "sha512-5BP1qXFncVRwgV/XnqzsKApdMjQPqWIpoUBdL1ynz8HyLxIX/UDAx7Ql2BjmA5CXT/p61JfZvkpiFWFpaqcfag==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.3.tgz",
+      "integrity": "sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==",
       "dependencies": {
       "dependencies": {
         "@vue/devtools-api": "^6.1.4"
         "@vue/devtools-api": "^6.1.4"
       },
       },
@@ -12611,6 +12695,18 @@
         "node": ">=10"
         "node": ">=10"
       }
       }
     },
     },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/yorkie": {
     "node_modules/yorkie": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz",
@@ -13894,9 +13990,9 @@
       "requires": {}
       "requires": {}
     },
     },
     "@humanwhocodes/config-array": {
     "@humanwhocodes/config-array": {
-      "version": "0.9.3",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz",
-      "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==",
+      "version": "0.10.4",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
+      "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "@humanwhocodes/object-schema": "^1.2.1",
         "@humanwhocodes/object-schema": "^1.2.1",
@@ -13904,6 +14000,12 @@
         "minimatch": "^3.0.4"
         "minimatch": "^3.0.4"
       }
       }
     },
     },
+    "@humanwhocodes/gitignore-to-minimatch": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
+      "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
+      "dev": true
+    },
     "@humanwhocodes/object-schema": {
     "@humanwhocodes/object-schema": {
       "version": "1.2.1",
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
@@ -14296,14 +14398,14 @@
       }
       }
     },
     },
     "@typescript-eslint/eslint-plugin": {
     "@typescript-eslint/eslint-plugin": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.31.0.tgz",
-      "integrity": "sha512-VKW4JPHzG5yhYQrQ1AzXgVgX8ZAJEvCz0QI6mLRX4tf7rnFfh5D8SKm0Pq6w5PyNfAWJk6sv313+nEt3ohWMBQ==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz",
+      "integrity": "sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/type-utils": "5.31.0",
-        "@typescript-eslint/utils": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/type-utils": "5.32.0",
+        "@typescript-eslint/utils": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
         "ignore": "^5.2.0",
@@ -14324,52 +14426,52 @@
       }
       }
     },
     },
     "@typescript-eslint/parser": {
     "@typescript-eslint/parser": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.31.0.tgz",
-      "integrity": "sha512-UStjQiZ9OFTFReTrN+iGrC6O/ko9LVDhreEK5S3edmXgR396JGq7CoX2TWIptqt/ESzU2iRKXAHfSF2WJFcWHw==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.32.0.tgz",
+      "integrity": "sha512-IxRtsehdGV9GFQ35IGm5oKKR2OGcazUoiNBxhRV160iF9FoyuXxjY+rIqs1gfnd+4eL98OjeGnMpE7RF/NBb3A==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/typescript-estree": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/typescript-estree": "5.32.0",
         "debug": "^4.3.4"
         "debug": "^4.3.4"
       }
       }
     },
     },
     "@typescript-eslint/scope-manager": {
     "@typescript-eslint/scope-manager": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.31.0.tgz",
-      "integrity": "sha512-8jfEzBYDBG88rcXFxajdVavGxb5/XKXyvWgvD8Qix3EEJLCFIdVloJw+r9ww0wbyNLOTYyBsR+4ALNGdlalLLg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz",
+      "integrity": "sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/visitor-keys": "5.31.0"
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/visitor-keys": "5.32.0"
       }
       }
     },
     },
     "@typescript-eslint/type-utils": {
     "@typescript-eslint/type-utils": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.31.0.tgz",
-      "integrity": "sha512-7ZYqFbvEvYXFn9ax02GsPcEOmuWNg+14HIf4q+oUuLnMbpJ6eHAivCg7tZMVwzrIuzX3QCeAOqKoyMZCv5xe+w==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz",
+      "integrity": "sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/utils": "5.31.0",
+        "@typescript-eslint/utils": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
         "tsutils": "^3.21.0"
       }
       }
     },
     },
     "@typescript-eslint/types": {
     "@typescript-eslint/types": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.31.0.tgz",
-      "integrity": "sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.32.0.tgz",
+      "integrity": "sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ==",
       "dev": true
       "dev": true
     },
     },
     "@typescript-eslint/typescript-estree": {
     "@typescript-eslint/typescript-estree": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.31.0.tgz",
-      "integrity": "sha512-3S625TMcARX71wBc2qubHaoUwMEn+l9TCsaIzYI/ET31Xm2c9YQ+zhGgpydjorwQO9pLfR/6peTzS/0G3J/hDw==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz",
+      "integrity": "sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/visitor-keys": "5.31.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/visitor-keys": "5.32.0",
         "debug": "^4.3.4",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
         "is-glob": "^4.0.3",
@@ -14389,26 +14491,26 @@
       }
       }
     },
     },
     "@typescript-eslint/utils": {
     "@typescript-eslint/utils": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.31.0.tgz",
-      "integrity": "sha512-kcVPdQS6VIpVTQ7QnGNKMFtdJdvnStkqS5LeALr4rcwx11G6OWb2HB17NMPnlRHvaZP38hL9iK8DdE9Fne7NYg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.32.0.tgz",
+      "integrity": "sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "@types/json-schema": "^7.0.9",
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.31.0",
-        "@typescript-eslint/types": "5.31.0",
-        "@typescript-eslint/typescript-estree": "5.31.0",
+        "@typescript-eslint/scope-manager": "5.32.0",
+        "@typescript-eslint/types": "5.32.0",
+        "@typescript-eslint/typescript-estree": "5.32.0",
         "eslint-scope": "^5.1.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
         "eslint-utils": "^3.0.0"
       }
       }
     },
     },
     "@typescript-eslint/visitor-keys": {
     "@typescript-eslint/visitor-keys": {
-      "version": "5.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.31.0.tgz",
-      "integrity": "sha512-ZK0jVxSjS4gnPirpVjXHz7mgdOsZUHzNYSfTw2yPa3agfbt9YfqaBiBZFSSxeBWnpWkzCxTfUpnzA3Vily/CSg==",
+      "version": "5.32.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz",
+      "integrity": "sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "@typescript-eslint/types": "5.31.0",
+        "@typescript-eslint/types": "5.32.0",
         "eslint-visitor-keys": "^3.3.0"
         "eslint-visitor-keys": "^3.3.0"
       }
       }
     },
     },
@@ -15215,9 +15317,9 @@
       }
       }
     },
     },
     "acorn": {
     "acorn": {
-      "version": "8.7.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
-      "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
+      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
       "dev": true
       "dev": true
     },
     },
     "acorn-import-assertions": {
     "acorn-import-assertions": {
@@ -15716,14 +15818,14 @@
       }
       }
     },
     },
     "chart.js": {
     "chart.js": {
-      "version": "3.8.2",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz",
-      "integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ=="
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.0.tgz",
+      "integrity": "sha512-SHOC6eCTe3dXe6owLHnO56xTLBbVeMHskkAlgPPVkd6xa0mv3EPhH5azRLg1cucYFsp4UloH/4NjSG0oOIFJtw=="
     },
     },
     "chartjs-plugin-datalabels": {
     "chartjs-plugin-datalabels": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0.tgz",
-      "integrity": "sha512-WBsWihphzM0Y8fmQVm89+iy99mmgejmj5/jcsYqwxSioLRL/zqJ4Scv/eXq5ZqvG3TpojlGzZLeaOaSvDm7fwA==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.1.0.tgz",
+      "integrity": "sha512-WA6R4saSlY6mnyX78SkbSo2gGc+cj87lFi5zBrsjjYxE76JgXyxHa1OTodVCzRPoqeYJqSEOffeJ/897kRHR6w==",
       "requires": {}
       "requires": {}
     },
     },
     "chokidar": {
     "chokidar": {
@@ -16124,9 +16226,9 @@
       }
       }
     },
     },
     "core-js": {
     "core-js": {
-      "version": "3.24.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.0.tgz",
-      "integrity": "sha512-IeOyT8A6iK37Ep4kZDD423mpi6JfPRoPUdQwEWYiGolvn4o6j2diaRzNfDfpTdu3a5qMbrGUzKUpYpRY8jXCkQ=="
+      "version": "3.24.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
+      "integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg=="
     },
     },
     "core-js-compat": {
     "core-js-compat": {
       "version": "3.23.4",
       "version": "3.23.4",
@@ -16820,13 +16922,14 @@
       "dev": true
       "dev": true
     },
     },
     "eslint": {
     "eslint": {
-      "version": "8.20.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.20.0.tgz",
-      "integrity": "sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==",
+      "version": "8.21.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz",
+      "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
         "@eslint/eslintrc": "^1.3.0",
         "@eslint/eslintrc": "^1.3.0",
-        "@humanwhocodes/config-array": "^0.9.2",
+        "@humanwhocodes/config-array": "^0.10.4",
+        "@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
         "ajv": "^6.10.0",
         "ajv": "^6.10.0",
         "chalk": "^4.0.0",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "cross-spawn": "^7.0.2",
@@ -16836,14 +16939,17 @@
         "eslint-scope": "^7.1.1",
         "eslint-scope": "^7.1.1",
         "eslint-utils": "^3.0.0",
         "eslint-utils": "^3.0.0",
         "eslint-visitor-keys": "^3.3.0",
         "eslint-visitor-keys": "^3.3.0",
-        "espree": "^9.3.2",
+        "espree": "^9.3.3",
         "esquery": "^1.4.0",
         "esquery": "^1.4.0",
         "esutils": "^2.0.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
         "fast-deep-equal": "^3.1.3",
         "file-entry-cache": "^6.0.1",
         "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
         "functional-red-black-tree": "^1.0.1",
         "functional-red-black-tree": "^1.0.1",
         "glob-parent": "^6.0.1",
         "glob-parent": "^6.0.1",
         "globals": "^13.15.0",
         "globals": "^13.15.0",
+        "globby": "^11.1.0",
+        "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.0.0",
         "import-fresh": "^3.0.0",
         "imurmurhash": "^0.1.4",
         "imurmurhash": "^0.1.4",
@@ -16929,6 +17035,16 @@
           "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
           "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
           "dev": true
           "dev": true
         },
         },
+        "find-up": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+          "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^6.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
         "globals": {
         "globals": {
           "version": "13.15.0",
           "version": "13.15.0",
           "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
           "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@@ -16944,6 +17060,33 @@
           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true
           "dev": true
         },
         },
+        "locate-path": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+          "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^5.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+          "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+          "dev": true,
+          "requires": {
+            "yocto-queue": "^0.1.0"
+          }
+        },
+        "p-locate": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+          "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^3.0.2"
+          }
+        },
         "path-key": {
         "path-key": {
           "version": "3.1.1",
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
           "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -17092,12 +17235,12 @@
       }
       }
     },
     },
     "espree": {
     "espree": {
-      "version": "9.3.2",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
-      "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
+      "version": "9.3.3",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz",
+      "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==",
       "dev": true,
       "dev": true,
       "requires": {
       "requires": {
-        "acorn": "^8.7.1",
+        "acorn": "^8.8.0",
         "acorn-jsx": "^5.3.2",
         "acorn-jsx": "^5.3.2",
         "eslint-visitor-keys": "^3.3.0"
         "eslint-visitor-keys": "^3.3.0"
       }
       }
@@ -17673,6 +17816,12 @@
       "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
       "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
       "dev": true
       "dev": true
     },
     },
+    "grapheme-splitter": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+      "dev": true
+    },
     "gzip-size": {
     "gzip-size": {
       "version": "6.0.0",
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@@ -18243,9 +18392,9 @@
       }
       }
     },
     },
     "jszip": {
     "jszip": {
-      "version": "3.10.0",
-      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz",
-      "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==",
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
       "requires": {
       "requires": {
         "lie": "~3.3.0",
         "lie": "~3.3.0",
         "pako": "~1.0.2",
         "pako": "~1.0.2",
@@ -21429,9 +21578,9 @@
       }
       }
     },
     },
     "vue-router": {
     "vue-router": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.2.tgz",
-      "integrity": "sha512-5BP1qXFncVRwgV/XnqzsKApdMjQPqWIpoUBdL1ynz8HyLxIX/UDAx7Ql2BjmA5CXT/p61JfZvkpiFWFpaqcfag==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.3.tgz",
+      "integrity": "sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==",
       "requires": {
       "requires": {
         "@vue/devtools-api": "^6.1.4"
         "@vue/devtools-api": "^6.1.4"
       }
       }
@@ -21976,6 +22125,12 @@
       "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
       "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
       "dev": true
       "dev": true
     },
     },
+    "yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true
+    },
     "yorkie": {
     "yorkie": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz",

+ 8 - 8
report-viewer/package.json

@@ -9,22 +9,22 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@highlightjs/vue-plugin": "^2.1.0",
     "@highlightjs/vue-plugin": "^2.1.0",
-    "chart.js": "^3.8.2",
-    "chartjs-plugin-datalabels": "^2.0.0",
-    "core-js": "^3.24.0",
+    "chart.js": "^3.9.0",
+    "chartjs-plugin-datalabels": "^2.1.0",
+    "core-js": "^3.24.1",
     "gitart-vue-dialog": "^2.4.0",
     "gitart-vue-dialog": "^2.4.0",
     "highlight.js": "^11.6.0",
     "highlight.js": "^11.6.0",
-    "jszip": "^3.10.0",
+    "jszip": "^3.10.1",
     "vue": "^3.2.37",
     "vue": "^3.2.37",
     "vue-chart-3": "^3.1.8",
     "vue-chart-3": "^3.1.8",
     "vue-draggable-next": "^2.1.1",
     "vue-draggable-next": "^2.1.1",
-    "vue-router": "^4.1.2",
+    "vue-router": "^4.1.3",
     "vue3-highlightjs": "^1.0.5",
     "vue3-highlightjs": "^1.0.5",
     "vuex": "^4.0.2"
     "vuex": "^4.0.2"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^5.31.0",
-    "@typescript-eslint/parser": "^5.31.0",
+    "@typescript-eslint/eslint-plugin": "^5.32.0",
+    "@typescript-eslint/parser": "^5.32.0",
     "@vue/cli-plugin-babel": "~5.0.8",
     "@vue/cli-plugin-babel": "~5.0.8",
     "@vue/cli-plugin-eslint": "~5.0.8",
     "@vue/cli-plugin-eslint": "~5.0.8",
     "@vue/cli-plugin-router": "~5.0.8",
     "@vue/cli-plugin-router": "~5.0.8",
@@ -33,7 +33,7 @@
     "@vue/compiler-sfc": "^3.2.37",
     "@vue/compiler-sfc": "^3.2.37",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/eslint-config-typescript": "^10.0.0",
     "@vue/eslint-config-typescript": "^10.0.0",
-    "eslint": "^8.20.0",
+    "eslint": "^8.21.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^8.7.1",
     "eslint-plugin-vue": "^8.7.1",
     "lint-staged": "^13.0.3",
     "lint-staged": "^13.0.3",