Explorar o código

Merge pull request #500 from robinmaisch/golang

Add Golang frontend
Timur Sağlam %!s(int64=4) %!d(string=hai) anos
pai
achega
1ae04f2d77

+ 1 - 1
README.md

@@ -48,7 +48,7 @@ Usage: JPlag [ options ] [ <root-dir> ... ] [ -new <new-dir> ... ] [ -old <old-d
 
 named arguments:
   -h, --help       show this help message and exit
-  -l               {java,python3,cpp,csharp,rlang,kotlin,char,text,scheme} Select the language to parse the submissions (default: java)
+  -l               {java,python3,cpp,csharp,golang,kotlin,rlang,char,text,scheme} 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)
   -v               {quiet,long} Verbosity of the logging (default: quiet)
   -d               Debug parser. Non-parsable files will be stored (default: false)

+ 0 - 4
jplag.cli/pom.xml

@@ -12,10 +12,6 @@
             <groupId>de.jplag</groupId>
             <artifactId>jplag</artifactId>
         </dependency>
-        <dependency>
-            <groupId>de.jplag</groupId>
-            <artifactId>jplag</artifactId>
-        </dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <artifactId>frontend-utils</artifactId>

+ 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))
+}

+ 5 - 1
jplag/pom.xml

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

+ 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"),
     C_CPP("cpp", "de.jplag.cpp.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"),
+    R_LANG("rlang", "de.jplag.rlang.Language"),
     CHAR("char", "de.jplag.chars.Language"),
     TEXT("text", "de.jplag.text.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
     void printRLangFiles() {
         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")
     @Test
     void printKotlinFiles() {

+ 9 - 2
pom.xml

@@ -154,12 +154,12 @@
             </dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
-                <artifactId>rlang</artifactId>
+                <artifactId>cpp</artifactId>
                 <version>${revision}</version>
             </dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
-                <artifactId>cpp</artifactId>
+                <artifactId>golang</artifactId>
                 <version>${revision}</version>
             </dependency>
             <dependency>
@@ -167,6 +167,11 @@
                 <artifactId>kotlin</artifactId>
                 <version>${revision}</version>
             </dependency>
+            <dependency>
+                <groupId>de.jplag</groupId>
+                <artifactId>rlang</artifactId>
+                <version>${revision}</version>
+            </dependency>
             <dependency>
                 <groupId>de.jplag</groupId>
                 <artifactId>scheme</artifactId>
@@ -448,6 +453,7 @@
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.python-3</module>
@@ -469,6 +475,7 @@
                 <module>jplag.frontend.chars</module>
                 <module>jplag.frontend.cpp</module>
                 <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
                 <module>jplag.frontend.java</module>
                 <module>jplag.frontend.kotlin</module>
                 <module>jplag.frontend.python-3</module>