ソースを参照

Merge branch 'master' into emf-fixes

Timur Sağlam 3 年 前
コミット
681a2c1334
27 ファイル変更3184 行追加8 行削除
  1. 5 1
      .gitignore
  2. 2 1
      README.md
  3. 5 0
      cli/pom.xml
  4. 1 1
      cli/src/test/java/de/jplag/cli/LanguageTest.java
  5. 6 4
      core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  6. 2 0
      core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java
  7. 13 0
      core/src/main/java/de/jplag/reporting/reportobject/model/Version.java
  8. 2 0
      core/src/test/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapperTest.java
  9. 1 0
      languages/pom.xml
  10. 25 0
      languages/swift/README.md
  11. 34 0
      languages/swift/pom.xml
  12. 412 0
      languages/swift/src/main/antlr4/de/jplag/swift/grammar/Swift5Lexer.g4
  13. 1305 0
      languages/swift/src/main/antlr4/de/jplag/swift/grammar/Swift5Parser.g4
  14. 477 0
      languages/swift/src/main/java/de/jplag/swift/JPlagSwiftListener.java
  15. 52 0
      languages/swift/src/main/java/de/jplag/swift/Language.java
  16. 80 0
      languages/swift/src/main/java/de/jplag/swift/SwiftParserAdapter.java
  17. 63 0
      languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java
  18. 360 0
      languages/swift/src/main/java/de/jplag/swift/grammar/SwiftSupport.java
  19. 156 0
      languages/swift/src/test/java/de/jplag/swift/SwiftFrontendTest.java
  20. 139 0
      languages/swift/src/test/resources/de/jplag/swift/Complete.swift
  21. 2 0
      languages/text/src/main/java/de/jplag/text/ParserAdapter.java
  22. 1 0
      report-viewer/package.json
  23. 1 1
      report-viewer/src/components/FilesContainer.vue
  24. 5 0
      report-viewer/src/model/Version.ts
  25. 13 0
      report-viewer/src/model/factories/ComparisonFactory.ts
  26. 21 0
      report-viewer/src/model/factories/OverviewFactory.ts
  27. 1 0
      report-viewer/vue.config.js

+ 5 - 1
.gitignore

@@ -5,7 +5,7 @@ target/
 
 *.class
 
-result
+
 
 # Mobile Tools for Java (J2ME)
 .mtj.tmp/
@@ -28,3 +28,7 @@ hs_err_pid*
 
 # ANTLR
 .antlr/
+
+# default result file name, should never be commited
+result/
+result.zip

+ 2 - 1
README.md

@@ -27,6 +27,7 @@ In the following, a list of all supported languages with their supported languag
 | [Rust](https://www.rust-lang.org/)                               |  1.60.0 | rust                  | beta | ANTLR 4 |
 | [Scala](https://www.scala-lang.org)                              |  2.13.8 | scala                 | beta | Scalameta |
 | [Scheme](http://www.scheme-reports.org)                          |       ? | scheme                | unknown | JavaCC |
+| [Swift](https://www.swift.org)                                   |     5.4 | swift                 | beta | ANTLR 4 |
 | [EMF Metamodel](https://www.eclipse.org/modeling/emf/)           |  2.25.0 | emf                   | alpha | EMF |
 | Text (naive)                                                     |       - | text                  | legacy | CoreNLP |
 
@@ -65,7 +66,7 @@ named arguments:
   -h, --help             show this help message and exit
   -new NEW [NEW ...]     Root-directory with submissions to check for plagiarism (same as the root directory)
   -old OLD [OLD ...]     Root-directory with prior submissions to compare against
-  -l {cpp,csharp,emf,go,java,kotlin,python3,rlang,scala,scheme,text}
+  -l {cpp,csharp,emf,go,java,kotlin,python3,rlang,scala,scheme,swift,text}
                          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)

+ 5 - 0
cli/pom.xml

@@ -75,6 +75,11 @@
             <artifactId>scheme</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>swift</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <artifactId>emf-metamodel</artifactId>

+ 1 - 1
cli/src/test/java/de/jplag/cli/LanguageTest.java

@@ -31,7 +31,7 @@ class LanguageTest extends CommandLineInterfaceTest {
     @Test
     void testLoading() {
         var languages = LanguageLoader.getAllAvailableLanguages();
-        Assertions.assertEquals(11, languages.size(), "Loaded Languages: " + languages.keySet());
+        Assertions.assertEquals(12, languages.size(), "Loaded Languages: " + languages.keySet());
     }
 
     @Test

+ 6 - 4
core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -27,10 +27,10 @@ import de.jplag.Language;
 import de.jplag.Submission;
 import de.jplag.reporting.jsonfactory.ComparisonReportWriter;
 import de.jplag.reporting.jsonfactory.ToDiskWriter;
-import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
 import de.jplag.reporting.reportobject.mapper.MetricMapper;
 import de.jplag.reporting.reportobject.model.Metric;
 import de.jplag.reporting.reportobject.model.OverviewReport;
+import de.jplag.reporting.reportobject.model.Version;
 
 /**
  * Factory class, responsible for converting a JPlagResult object to Overview and Comparison DTO classes and writing it
@@ -42,6 +42,9 @@ public class ReportObjectFactory {
     private static final ToDiskWriter fileWriter = new ToDiskWriter();
     public static final String OVERVIEW_FILE_NAME = "overview.json";
     public static final String SUBMISSIONS_FOLDER = "submissions";
+
+    // TODO: This shall be moved to a better visible and upgradable position. Shall be fixed in a future version.
+    public static final Version REPORT_VIEWER_VERSION = new Version(4, 0, 0);
     private Map<String, String> submissionNameToIdMap;
     private Function<Submission, String> submissionToIdFunction;
     private Map<String, Map<String, String>> submissionNameToNameToComparisonFileName;
@@ -139,9 +142,8 @@ public class ReportObjectFactory {
         folders.addAll(result.getOptions().oldSubmissionDirectories());
 
         String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().baseCodeSubmissionDirectory().getName() : "";
-        ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper(submissionToIdFunction);
 
-        OverviewReport overviewReport = new OverviewReport(folders.stream().map(File::getPath).toList(), // submissionFolderPath
+        OverviewReport overviewReport = new OverviewReport(REPORT_VIEWER_VERSION, folders.stream().map(File::getPath).toList(), // submissionFolderPath
                 baseCodePath, // baseCodeFolderPath
                 result.getOptions().language().getName(), // language
                 result.getOptions().fileSuffixes(), // fileExtensions
@@ -153,7 +155,7 @@ public class ReportObjectFactory {
                 getDate(),// dateOfExecution
                 result.getDuration(), // executionTime
                 getMetrics(result),// metrics
-                clusteringResultMapper.map(result)); // clusters
+                List.of()); // clusters (deactivated for now)
 
         fileWriter.saveAsJSON(overviewReport, path, OVERVIEW_FILE_NAME);
 

+ 2 - 0
core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java

@@ -8,6 +8,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public record OverviewReport(
 
+        @JsonProperty("jplag_version") Version version,
+
         @JsonProperty("submission_folder_path") List<String> submissionFolderPath,
 
         @JsonProperty("base_code_folder_path") String baseCodeFolderPath,

+ 13 - 0
core/src/main/java/de/jplag/reporting/reportobject/model/Version.java

@@ -0,0 +1,13 @@
+package de.jplag.reporting.reportobject.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * This record represents a version number according to <a href="https://semver.org/">https://semver.org/</a>. This
+ * version number is used to compare generated result.zip files with the version of the report-viewer.
+ * @param major MAJOR version when you make incompatible API changes
+ * @param minor MINOR version when you add functionality in a backwards compatible manner
+ * @param patch PATCH version when you make backwards compatible bug fixes
+ */
+public record Version(@JsonProperty("major") int major, @JsonProperty("minor") int minor, @JsonProperty("patch") int patch) {
+}

+ 2 - 0
core/src/test/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapperTest.java

@@ -7,6 +7,7 @@ import static org.mockito.Mockito.when;
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import de.jplag.JPlagResult;
@@ -18,6 +19,7 @@ public class ClusteringResultMapperTest {
     private final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper(Submission::getName);
 
     @Test
+    @Disabled("Skipped for now because cluster serialization is deactivated.")
     public void test() {
         // given
         JPlagResult resultMock = mock(JPlagResult.class);

+ 1 - 0
languages/pom.xml

@@ -21,6 +21,7 @@
         <module>rust</module>
         <module>scala</module>
         <module>scheme</module>
+        <module>swift</module>
         <module>text</module>
     </modules>
     <dependencies>

+ 25 - 0
languages/swift/README.md

@@ -0,0 +1,25 @@
+# JPlag Swift language frontend
+
+The JPlag Swift frontend allows the use of JPlag with submissions in Swift. <br>
+It is based on the [Swift ANTLR4 grammar](https://github.com/antlr/grammars-v4/tree/master/swift/swift5), licensed under the Apache 2.0.
+
+### Swift specification compatibility
+
+The underlying grammar definition is based on Swift 5.4.
+
+If there are any major updates or fixes to the grammar<a href="#footnote-1"><sup>1</sup></a>, they should surely be applied to this module as well. 
+
+
+### Token Extraction
+
+The choice of tokens is intended to be similar to the Java or C# frontends. It includes a range of nesting structures (class, method, control flow expressions) as well as variable declaration, object creation, assignment, and control flow altering keywords.
+
+### Usage
+
+To use the Swift frontend, add the `-l swift` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.SWIFT` 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 modification in <a href="https://github.com/antlr/grammars-v4/tree/d9af329091f9888ffde70aa5cfe84f583b98751a/swift/swift5">commit d9af329</a> from Sep 2022.
+They received some minor modifications to support identifying the end of do-blocks.</section>

+ 34 - 0
languages/swift/pom.xml

@@ -0,0 +1,34 @@
+<?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>languages</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <artifactId>swift</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+        </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>

+ 412 - 0
languages/swift/src/main/antlr4/de/jplag/swift/grammar/Swift5Lexer.g4

@@ -0,0 +1,412 @@
+lexer grammar Swift5Lexer;
+@lexer::header {
+	import java.util.Stack;
+}
+@lexer::members {
+	Stack<Integer> parenthesis = new Stack<Integer>();
+
+	@Override
+	public void reset(){
+		super.reset();
+		parenthesis.clear();
+	}
+}
+
+AS: 'as';
+ALPHA: 'alpha';
+BREAK: 'break';
+CASE: 'case';
+CATCH: 'catch';
+CLASS: 'class';
+CONTINUE: 'continue';
+DEFAULT: 'default';
+DEFER: 'defer';
+DO: 'do';
+GUARD: 'guard';
+ELSE: 'else';
+ENUM: 'enum';
+FOR: 'for';
+FALLTHROUGH: 'fallthrough';
+FUNC: 'func';
+IN: 'in';
+IF: 'if';
+IMPORT: 'import';
+INTERNAL: 'internal';
+FINAL: 'final';
+OPEN: 'open';
+PRIVATE: 'private';
+PUBLIC: 'public';
+WHERE: 'where';
+WHILE: 'while';
+LET: 'let';
+VAR: 'var';
+PROTOCOL: 'protocol';
+GET: 'get';
+SET: 'set';
+WILL_SET: 'willSet';
+DID_SET: 'didSet';
+REPEAT: 'repeat';
+SWITCH: 'switch';
+STRUCT: 'struct';
+RETURN: 'return';
+THROW: 'throw';
+THROWS: 'throws';
+RETHROWS: 'rethrows';
+INDIRECT: 'indirect';
+INIT: 'init';
+DEINIT: 'deinit';
+ASSOCIATED_TYPE: 'associatedtype';
+EXTENSION: 'extension';
+SUBSCRIPT: 'subscript';
+PREFIX: 'prefix';
+INFIX: 'infix';
+LEFT: 'left';
+RIGHT: 'right';
+NONE: 'none';
+PRECEDENCE_GROUP: 'precedencegroup';
+HIGHER_THAN: 'higherThan';
+LOWER_THAN: 'lowerThan';
+ASSIGNMENT: 'assignment';
+ASSOCIATIVITY: 'associativity';
+POSTFIX: 'postfix';
+OPERATOR: 'operator';
+TYPEALIAS: 'typealias';
+OS: 'os';
+ARCH: 'arch';
+SWIFT: 'swift';
+COMPILER: 'compiler';
+CAN_IMPORT: 'canImport';
+TARGET_ENVIRONMENT: 'targetEnvironment';
+CONVENIENCE: 'convenience';
+DYNAMIC: 'dynamic';
+LAZY: 'lazy';
+OPTIONAL: 'optional';
+OVERRIDE: 'override';
+REQUIRED: 'required';
+STATIC: 'static';
+WEAK: 'weak';
+UNOWNED: 'unowned';
+SAFE: 'safe';
+UNSAFE: 'unsafe';
+MUTATING: 'mutating';
+NONMUTATING: 'nonmutating';
+FILE_PRIVATE: 'fileprivate';
+IS: 'is';
+TRY: 'try';
+SUPER: 'super';
+ANY: 'Any';
+FALSE: 'false';
+RED: 'red';
+BLUE: 'blue';
+GREEN: 'green';
+RESOURCE_NAME: 'resourceName';
+TRUE: 'true';
+NIL: 'nil';
+INOUT: 'inout';
+SOME: 'some';
+TYPE: 'Type';
+PRECEDENCE: 'precedence';
+SELF: 'self';
+SELF_BIG: 'Self';
+
+MAC_OS: 'macOS';
+I_OS: 'iOS';
+OSX: 'OSX';
+WATCH_OS: 'watchOS';
+TV_OS: 'tvOS';
+LINUX: 'Linux';
+WINDOWS: 'Windows';
+
+I386: 'i386';
+X86_64: 'x86_64';
+ARM: 'arm';
+ARM64: 'arm64';
+
+SIMULATOR: 'simulator';
+MAC_CATALYST: 'macCatalyst';
+
+I_OS_APPLICATION_EXTENSION: 'iOSApplicationExtension';
+MAC_CATALYST_APPLICATION_EXTENSION:
+	'macCatalystApplicationExtension';
+MAC_OS_APPLICATION_EXTENSION: 'macOSApplicationExtension';
+SOURCE_LOCATION: '#sourceLocation';
+
+FILE: 'file';
+LINE: 'line';
+ERROR: '#error';
+WARNING: '#warning';
+AVAILABLE: '#available';
+
+HASH_IF: '#if';
+HASH_ELSEIF: '#elseif';
+HASH_ELSE: '#else';
+HASH_ENDIF: '#endif';
+HASH_FILE: '#file';
+HASH_FILE_ID: '#fileID';
+HASH_FILE_PATH: '#filePath';
+HASH_LINE: '#line';
+HASH_COLUMN: '#column';
+HASH_FUNCTION: '#function';
+HASH_DSO_HANDLE: '#dsohandle';
+HASH_SELECTOR: '#selector';
+HASH_KEYPATH: '#keyPath';
+HASH_COLOR_LITERAL: '#colorLiteral';
+HASH_FILE_LITERAL: '#fileLiteral';
+HASH_IMAGE_LITERAL: '#imageLiteral';
+GETTER: 'getter';
+SETTER: 'setter';
+
+Identifier:
+	Identifier_head Identifier_characters?
+	| Implicit_parameter_name
+	| Property_wrapper_projection;
+
+fragment Identifier_head:
+	[a-zA-Z]
+	| '_'
+	| '\u00A8'
+	| '\u00AA'
+	| '\u00AD'
+	| '\u00AF'
+	| [\u00B2-\u00B5]
+	| [\u00B7-\u00BA]
+	| [\u00BC-\u00BE]
+	| [\u00C0-\u00D6]
+	| [\u00D8-\u00F6]
+	| [\u00F8-\u00FF]
+	| [\u0100-\u02FF]
+	| [\u0370-\u167F]
+	| [\u1681-\u180D]
+	| [\u180F-\u1DBF]
+	| [\u1E00-\u1FFF]
+	| [\u200B-\u200D]
+	| [\u202A-\u202E]
+	| [\u203F-\u2040]
+	| '\u2054'
+	| [\u2060-\u206F]
+	| [\u2070-\u20CF]
+	| [\u2100-\u218F]
+	| [\u2460-\u24FF]
+	| [\u2776-\u2793]
+	| [\u2C00-\u2DFF]
+	| [\u2E80-\u2FFF]
+	| [\u3004-\u3007]
+	| [\u3021-\u302F]
+	| [\u3031-\u303F]
+	| [\u3040-\uD7FF]
+	| [\uF900-\uFD3D]
+	| [\uFD40-\uFDCF]
+	| [\uFDF0-\uFE1F]
+	| [\uFE30-\uFE44]
+	| [\uFE47-\uFFFD]
+	| [\u{10000}-\u{1FFFD}]
+	| [\u{20000}-\u{2FFFD}]
+	| [\u{30000}-\u{3FFFD}]
+	| [\u{40000}-\u{4FFFD}]
+	| [\u{50000}-\u{5FFFD}]
+	| [\u{60000}-\u{6FFFD}]
+	| [\u{70000}-\u{7FFFD}]
+	| [\u{80000}-\u{8FFFD}]
+	| [\u{90000}-\u{9FFFD}]
+	| [\u{A0000}-\u{AFFFD}]
+	| [\u{B0000}-\u{BFFFD}]
+	| [\u{C0000}-\u{CFFFD}]
+	| [\u{D0000}-\u{DFFFD}]
+	| [\u{E0000}-\u{EFFFD}];
+
+fragment Identifier_character:
+	[0-9]
+	| [\u0300-\u036F]
+	| [\u1DC0-\u1DFF]
+	| [\u20D0-\u20FF]
+	| [\uFE20-\uFE2F]
+	| Identifier_head;
+
+fragment Identifier_characters: Identifier_character+;
+
+fragment Implicit_parameter_name: '$' Decimal_digits;
+
+fragment Property_wrapper_projection: '$' Identifier_characters;
+
+DOT: '.';
+LCURLY: '{';
+LPAREN:
+	'(' { if(!parenthesis.isEmpty()) parenthesis.push(parenthesis.pop()+1);};
+LBRACK: '[';
+RCURLY: '}';
+RPAREN:
+	')' { if(!parenthesis.isEmpty()) 
+		{
+			parenthesis.push(parenthesis.pop()-1); 
+			if(parenthesis.peek() == 0) 
+			{ 
+				parenthesis.pop();
+				popMode();
+			}
+		}
+		};
+RBRACK: ']';
+COMMA: ',';
+COLON: ':';
+SEMI: ';';
+LT: '<';
+GT: '>';
+UNDERSCORE: '_';
+BANG: '!';
+QUESTION: '?';
+AT: '@';
+AND: '&';
+SUB: '-';
+EQUAL: '=';
+OR: '|';
+DIV: '/';
+ADD: '+';
+MUL: '*';
+MOD: '%';
+CARET: '^';
+TILDE: '~';
+HASH: '#';
+BACKTICK: '`';
+DOLLAR: '$';
+BACKSLASH: '\\';
+
+Operator_head_other: // valid operator chars not used by Swift itself
+	[\u00A1-\u00A7]
+	| [\u00A9\u00AB]
+	| [\u00AC\u00AE]
+	| [\u00B0-\u00B1\u00B6\u00BB\u00BF\u00D7\u00F7]
+	| [\u2016-\u2017\u2020-\u2027]
+	| [\u2030-\u203E]
+	| [\u2041-\u2053]
+	| [\u2055-\u205E]
+	| [\u2190-\u23FF]
+	| [\u2500-\u2775]
+	| [\u2794-\u2BFF]
+	| [\u2E00-\u2E7F]
+	| [\u3001-\u3003]
+	| [\u3008-\u3020\u3030];
+
+Operator_following_character:
+	[\u0300-\u036F]
+	| [\u1DC0-\u1DFF]
+	| [\u20D0-\u20FF]
+	| [\uFE00-\uFE0F]
+	| [\uFE20-\uFE2F]
+	| [\u{E0100}-\u{E01EF}];
+
+Binary_literal: '0b' Binary_digit Binary_literal_characters?;
+fragment Binary_digit: [01];
+fragment Binary_literal_character: Binary_digit | '_';
+fragment Binary_literal_characters: Binary_literal_character+;
+
+Octal_literal: '0o' Octal_digit Octal_literal_characters?;
+fragment Octal_digit: [0-7];
+fragment Octal_literal_character: Octal_digit | '_';
+fragment Octal_literal_characters: Octal_literal_character+;
+
+Decimal_digits: Decimal_digit+;
+Decimal_literal: Decimal_digit Decimal_literal_characters?;
+fragment Decimal_digit: [0-9];
+fragment Decimal_literal_character: Decimal_digit | '_';
+fragment Decimal_literal_characters: Decimal_literal_character+;
+
+Hexadecimal_literal:
+	'0x' Hexadecimal_digit Hexadecimal_literal_characters?;
+fragment Hexadecimal_digit: [0-9a-fA-F];
+fragment Hexadecimal_literal_character: Hexadecimal_digit | '_';
+fragment Hexadecimal_literal_characters:
+	Hexadecimal_literal_character+;
+
+// Floating-Point Literals
+Floating_point_literal:
+	Decimal_literal Decimal_fraction? Decimal_exponent?
+	| Hexadecimal_literal Hexadecimal_fraction? Hexadecimal_exponent;
+fragment Decimal_fraction: '.' Decimal_literal;
+fragment Decimal_exponent:
+	Floating_point_e Sign? Decimal_literal;
+fragment Hexadecimal_fraction:
+	'.' Hexadecimal_digit Hexadecimal_literal_characters?;
+fragment Hexadecimal_exponent:
+	Floating_point_p Sign? Decimal_literal;
+fragment Floating_point_e: [eE];
+fragment Floating_point_p: [pP];
+fragment Sign: [+-];
+
+WS: [ \n\r\t\u000B\u000C\u0000]+ -> channel(HIDDEN);
+
+HASHBANG: '#!' .*? [\r\n]+ -> channel(HIDDEN);
+
+Block_comment:
+	'/*' (Block_comment | .)*? '*/' -> channel(HIDDEN);
+
+Line_comment: '//' .*? ('\n' | EOF) -> channel(HIDDEN);
+
+Multi_line_extended_string_open:
+	'#'+ '"""' -> pushMode(MultiLineExtended);
+
+Single_line_extended_string_open:
+	'#'+ '"' -> pushMode(SingleLineExtended);
+
+Multi_line_string_open: '"""' -> pushMode(MultiLine);
+
+Single_line_string_open: '"' -> pushMode(SingleLine);
+
+mode SingleLine;
+
+Interpolataion_single_line:
+	'\\(' { parenthesis.push(1);} -> pushMode(DEFAULT_MODE);
+
+Single_line_string_close: '"' -> popMode;
+
+Quoted_single_line_text: Quoted_text;
+
+mode MultiLine;
+
+Interpolataion_multi_line:
+	'\\(' {parenthesis.push(1); } -> pushMode(DEFAULT_MODE);
+
+Multi_line_string_close: '"""' -> popMode;
+
+Quoted_multi_line_text: Multiline_quoted_text;
+
+mode SingleLineExtended;
+
+Single_line_extended_string_close: '"' '#'+ -> popMode;
+
+Quoted_single_line_extended_text: ~[\r\n"]+;
+
+mode MultiLineExtended;
+
+Multi_line_extended_string_close: '"""' '#'+ -> popMode;
+
+Quoted_multi_line_extended_text: ~["]+ | '"' '"'?;
+
+fragment Quoted_text: Quoted_text_item+;
+
+fragment Quoted_text_item: Escaped_character | ~["\n\r\\];
+
+fragment Multiline_quoted_text:
+	Escaped_character
+	| ~[\\"]+
+	| '"' '"'?
+	| Escaped_newline;
+
+fragment Escape_sequence: '\\' '#'*;
+
+fragment Escaped_character:
+	Escape_sequence (
+		[0\\tnr"'\u201c]
+		| 'u' '{' Unicode_scalar_digits '}'
+	);
+
+//Between one and eight hexadecimal digits
+fragment Unicode_scalar_digits:
+	Hexadecimal_digit Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit?
+		Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit?;
+
+fragment Escaped_newline:
+	Escape_sequence Inline_spaces? Line_break;
+
+fragment Inline_spaces: [\u0009\u0020];
+
+fragment Line_break: [\u000A\u000D]| '\u000D' '\u000A';

+ 1305 - 0
languages/swift/src/main/antlr4/de/jplag/swift/grammar/Swift5Parser.g4

@@ -0,0 +1,1305 @@
+parser grammar Swift5Parser;
+
+options {
+	tokenVocab = Swift5Lexer;
+}
+
+top_level: statements? EOF;
+
+// Statements
+statement:
+	(
+		loop_statement
+		| declaration
+		| branch_statement
+		| labeled_statement
+		| control_transfer_statement
+		| defer_statement
+		| do_statement
+		| expression
+	) SEMI?
+	| compiler_control_statement;
+
+statements
+	locals[int indexBefore = -1]: (
+		{SwiftSupport.isSeparatedStatement(_input, $indexBefore)}? statement {$indexBefore = _input.index();
+			}
+	)+;
+
+// Loop Statements
+loop_statement:
+	for_in_statement
+	| while_statement
+	| repeat_while_statement;
+
+// For-In Statement
+for_in_statement:
+	FOR CASE? pattern IN expression where_clause? code_block;
+
+// While Statement
+while_statement: WHILE condition_list code_block;
+
+condition_list: condition (COMMA condition)*;
+
+condition:
+	availability_condition
+	| expression
+	| case_condition
+	| optional_binding_condition;
+
+case_condition: CASE pattern initializer;
+
+optional_binding_condition: (LET | VAR) pattern initializer;
+
+// Repeat-While Statement
+repeat_while_statement: REPEAT code_block WHILE expression;
+
+// Branch Statements
+branch_statement:
+	if_statement
+	| guard_statement
+	| switch_statement;
+
+// If Statement
+if_statement: IF condition_list code_block else_clause?;
+else_clause: ELSE code_block | ELSE if_statement;
+
+// Guard Statement
+guard_statement: GUARD condition_list ELSE code_block;
+
+// Switch Statement
+switch_statement: SWITCH expression LCURLY switch_cases? RCURLY;
+switch_cases: switch_case switch_cases?;
+switch_case: (case_label | default_label) statements
+	| conditional_switch_case;
+case_label: attributes? CASE case_item_list COLON;
+case_item_list:
+	pattern where_clause? (COMMA pattern where_clause?)*;
+default_label: attributes? DEFAULT COLON;
+where_clause: WHERE where_expression;
+where_expression: expression;
+conditional_switch_case:
+	switch_if_directive_clause switch_elseif_directive_clauses? switch_else_directive_clause?
+		HASH_ENDIF;
+switch_if_directive_clause:
+	HASH_IF compilation_condition switch_cases?;
+switch_elseif_directive_clauses:
+	elseif_directive_clause switch_elseif_directive_clauses?;
+switch_elseif_directive_clause:
+	HASH_ELSEIF compilation_condition switch_cases?;
+switch_else_directive_clause: HASH_ELSE switch_cases?;
+
+// Labeled Statement
+labeled_statement:
+	statement_label (
+		loop_statement
+		| if_statement
+		| switch_statement
+		| do_statement
+	);
+
+statement_label: label_name COLON;
+label_name: identifier;
+
+// Control Transfer Statements
+control_transfer_statement:
+	break_statement
+	| continue_statement
+	| fallthrough_statement
+	| return_statement
+	| throw_statement;
+
+// Break Statement
+break_statement: BREAK label_name?;
+
+// Continue Statement
+continue_statement: CONTINUE label_name?;
+
+// Fallthrough Statement
+fallthrough_statement: FALLTHROUGH;
+
+// Return Statement
+return_statement: RETURN expression?;
+
+// Throw Statement
+throw_statement: THROW expression;
+
+// Defer Statement
+defer_statement: DEFER code_block;
+
+// Do Statement
+do_statement: DO do_block catch_clauses?;
+do_block: code_block;
+catch_clauses: catch_clause+;
+catch_clause: CATCH catch_pattern_list? code_block;
+catch_pattern_list:
+	catch_pattern (catch_pattern COMMA catch_pattern)*;
+catch_pattern: pattern where_clause?;
+
+// Compiler Control Statements
+compiler_control_statement:
+	conditional_compilation_block
+	| line_control_statement
+	| diagnostic_statement;
+
+// Conditional Compilation Block
+conditional_compilation_block:
+	if_directive_clause elseif_directive_clauses? else_directive_clause? HASH_ENDIF;
+
+if_directive_clause: HASH_IF compilation_condition statements?;
+
+elseif_directive_clauses: elseif_directive_clause+;
+elseif_directive_clause:
+	HASH_ELSEIF compilation_condition statements?;
+else_directive_clause: HASH_ELSE statements?;
+
+compilation_condition:
+	platform_condition
+	| identifier
+	| boolean_literal
+	| LPAREN compilation_condition RPAREN
+	| BANG compilation_condition
+	| compilation_condition (
+		compilation_condition_AND
+		| compilation_condition_OR
+	) compilation_condition;
+
+platform_condition:
+	OS LPAREN operating_system RPAREN
+	| ARCH LPAREN architecture RPAREN
+	| (SWIFT | COMPILER) LPAREN (
+		compilation_condition_GE
+		| compilation_condition_L
+	) swift_version RPAREN
+	| CAN_IMPORT LPAREN module_name RPAREN
+	| TARGET_ENVIRONMENT LPAREN environment RPAREN;
+
+swift_version: Decimal_digits swift_version_continuation?;
+swift_version_continuation:
+	DOT Decimal_digits swift_version_continuation?;
+
+operating_system:
+	MAC_OS
+	| I_OS
+	| OSX
+	| WATCH_OS
+	| TV_OS
+	| LINUX
+	| WINDOWS;
+architecture: I386 | X86_64 | ARM | ARM64;
+
+module_name: identifier (DOT identifier)*;
+environment: SIMULATOR | MAC_CATALYST;
+
+// Line Control Statement
+line_control_statement:
+	SOURCE_LOCATION LPAREN (
+		FILE COLON file_name COMMA LINE COLON line_number
+	)? RPAREN;
+
+line_number:
+	Decimal_literal; // TODO: A decimal integer greater than zero
+file_name: static_string_literal;
+
+// Compile-Time Diagnostic Statement
+diagnostic_statement: (ERROR | WARNING) LPAREN diagnostic_message RPAREN;
+
+diagnostic_message: static_string_literal;
+
+// Availability Condition
+availability_condition:
+	AVAILABLE LPAREN availability_arguments RPAREN;
+
+availability_arguments:
+	availability_argument (COMMA availability_argument)*;
+
+availability_argument: platform_name platform_version | MUL;
+
+platform_name:
+	I_OS
+	| OSX
+	| I_OS_APPLICATION_EXTENSION
+	| MAC_OS
+	| MAC_OS_APPLICATION_EXTENSION
+	| MAC_CATALYST
+	| MAC_CATALYST_APPLICATION_EXTENSION
+	| WATCH_OS
+	| TV_OS;
+
+platform_version:
+	Decimal_literal
+	| Decimal_digits
+	| Floating_point_literal (DOT Decimal_digits)?;
+
+// Generic Parameter Clause
+generic_parameter_clause: LT generic_parameter_list GT;
+generic_parameter_list:
+	generic_parameter (COMMA generic_parameter)*;
+generic_parameter:
+	type_name (
+		COLON (type_identifier | protocol_composition_type)
+	)?;
+
+generic_where_clause: WHERE requirement_list;
+requirement_list: requirement (COMMA requirement)*;
+requirement: conformance_requirement | same_type_requirement;
+
+conformance_requirement:
+	type_identifier COLON (
+		type_identifier
+		| protocol_composition_type
+	);
+same_type_requirement:
+	type_identifier same_type_equals (type_identifier | type);
+
+// Generic Argument Clause
+generic_argument_clause: LT generic_argument_list GT;
+generic_argument_list:
+	generic_argument (COMMA generic_argument)*;
+generic_argument: type;
+
+// Declarations
+declaration:
+	(
+		import_declaration
+		| constant_declaration
+		| variable_declaration
+		| typealias_declaration
+		| function_declaration
+		| enum_declaration
+		| struct_declaration
+		| class_declaration
+		| protocol_declaration
+		| initializer_declaration
+		| deinitializer_declaration
+		| extension_declaration
+		| subscript_declaration
+		| operator_declaration
+		| precedence_group_declaration
+	) SEMI?;
+
+declarations: declaration+;
+
+// Top-Level Code
+top_level_declaration: statements?;
+
+// Code Blocks
+code_block: LCURLY statements? RCURLY;
+
+// Import Declaration
+import_declaration: attributes? IMPORT import_kind? import_path;
+import_kind:
+	TYPEALIAS
+	| STRUCT
+	| CLASS
+	| ENUM
+	| PROTOCOL
+	| LET
+	| VAR
+	| FUNC;
+import_path:
+	import_path_identifier (DOT import_path_identifier)*;
+import_path_identifier: identifier | operator;
+
+// Constant Declaration
+constant_declaration:
+	attributes? declaration_modifiers? LET pattern_initializer_list;
+pattern_initializer_list:
+	pattern_initializer (COMMA pattern_initializer)*;
+pattern_initializer: pattern initializer?;
+initializer: EQUAL expression;
+
+// Variable Declaration
+variable_declaration:
+	variable_declaration_head (
+		variable_name (
+			initializer willSet_didSet_block
+			| type_annotation (
+			    initializer? willSet_didSet_block
+				| getter_setter_block // contains code_block
+				| getter_setter_keyword_block
+			)
+		)
+		| pattern_initializer_list
+	);
+
+variable_declaration_head:
+	attributes? declaration_modifiers? VAR;
+variable_name: identifier;
+
+getter_setter_block:
+	LCURLY (
+		getter_clause setter_clause?
+		| setter_clause getter_clause
+	) RCURLY
+	| code_block;
+getter_clause: attributes? mutation_modifier? GET code_block?;
+setter_clause:
+	attributes? mutation_modifier? SET setter_name? code_block?;
+setter_name: LPAREN identifier RPAREN;
+
+getter_setter_keyword_block:
+	LCURLY (
+		getter_keyword_clause setter_keyword_clause?
+		| setter_keyword_clause getter_keyword_clause
+	) RCURLY;
+getter_keyword_clause: attributes? mutation_modifier? GET;
+setter_keyword_clause: attributes? mutation_modifier? SET;
+
+willSet_didSet_block:
+	LCURLY (
+		willSet_clause didSet_clause?
+		| didSet_clause willSet_clause?
+	) RCURLY;
+willSet_clause: attributes? WILL_SET setter_name? code_block;
+didSet_clause: attributes? DID_SET setter_name? code_block;
+
+// Type Alias Declaration
+typealias_declaration:
+	attributes? access_level_modifier? TYPEALIAS typealias_name generic_parameter_clause?
+		typealias_assignment;
+typealias_name: identifier;
+typealias_assignment: EQUAL type;
+
+// Function Declaration
+function_declaration:
+	function_head function_name generic_parameter_clause? function_signature generic_where_clause?
+		function_body?;
+
+function_head: attributes? declaration_modifiers? FUNC;
+
+function_name: identifier | operator;
+
+function_signature:
+	parameter_clause (THROWS? | RETHROWS) function_result?;
+
+function_result: arrow_operator attributes? type;
+
+function_body: code_block;
+
+parameter_clause: LPAREN parameter_list? RPAREN;
+parameter_list: parameter (COMMA parameter)*;
+
+parameter:
+	attributes? external_parameter_name? local_parameter_name type_annotation (
+		default_argument_clause?
+		| range_operator
+	);
+external_parameter_name: identifier;
+local_parameter_name: identifier;
+default_argument_clause: EQUAL expression;
+
+// Enumeration Declaration
+enum_declaration:
+	attributes? access_level_modifier? (
+		union_style_enum
+		| raw_value_style_enum
+	);
+
+union_style_enum:
+	INDIRECT? ENUM enum_name generic_parameter_clause? type_inheritance_clause? generic_where_clause
+		? LCURLY union_style_enum_members? RCURLY;
+
+union_style_enum_members: union_style_enum_member+;
+
+union_style_enum_member:
+	declaration
+	| union_style_enum_case_clause
+	| compiler_control_statement;
+
+union_style_enum_case_clause:
+	attributes? INDIRECT? CASE union_style_enum_case_list;
+
+union_style_enum_case_list:
+	union_style_enum_case (COMMA union_style_enum_case)*;
+
+union_style_enum_case:
+	opaque_type
+	| enum_case_name (tuple_type | LPAREN type RPAREN)?;
+
+enum_name: identifier;
+
+enum_case_name: identifier;
+
+raw_value_style_enum:
+	ENUM enum_name generic_parameter_clause? type_inheritance_clause generic_where_clause? LCURLY
+		raw_value_style_enum_members RCURLY;
+
+raw_value_style_enum_members: raw_value_style_enum_member+;
+
+raw_value_style_enum_member:
+	declaration
+	| raw_value_style_enum_case_clause
+	| compiler_control_statement;
+
+raw_value_style_enum_case_clause:
+	attributes? CASE raw_value_style_enum_case_list;
+
+raw_value_style_enum_case_list:
+	raw_value_style_enum_case (COMMA raw_value_style_enum_case)*;
+
+raw_value_style_enum_case: enum_case_name raw_value_assignment?;
+
+raw_value_assignment: EQUAL raw_value_literal;
+
+raw_value_literal:
+	numeric_literal
+	| static_string_literal
+	| boolean_literal;
+
+// Structure Declaration
+struct_declaration:
+	attributes? access_level_modifier? STRUCT struct_name generic_parameter_clause?
+		type_inheritance_clause? generic_where_clause? struct_body;
+struct_name: identifier;
+struct_body: LCURLY struct_members RCURLY;
+struct_members: struct_member*;
+struct_member: declaration | compiler_control_statement;
+
+// Class Declaration
+class_declaration:
+	attributes? (
+		access_level_modifier? FINAL?
+		| FINAL access_level_modifier?
+	) CLASS class_name generic_parameter_clause? type_inheritance_clause? generic_where_clause?
+		class_body;
+class_name: identifier;
+class_body: LCURLY class_members RCURLY;
+class_members: class_member*;
+class_member: declaration | compiler_control_statement;
+
+// Protocol Declaration
+protocol_declaration:
+	attributes? access_level_modifier? PROTOCOL protocol_name (
+		COLON CLASS
+		| type_inheritance_clause
+	)? generic_where_clause? protocol_body;
+protocol_name: identifier;
+protocol_body: LCURLY protocol_members RCURLY;
+protocol_members: protocol_member*;
+
+protocol_member:
+	protocol_member_declaration
+	| compiler_control_statement;
+
+protocol_member_declaration:
+	protocol_property_declaration
+	| protocol_method_declaration
+	| protocol_initializer_declaration
+	| protocol_subscript_declaration
+	| protocol_associated_type_declaration
+	| typealias_declaration;
+
+// Protocol Property Declaration
+protocol_property_declaration:
+	variable_declaration_head variable_name type_annotation getter_setter_keyword_block;
+
+// Protocol Method Declaration
+protocol_method_declaration:
+	function_head function_name generic_parameter_clause? function_signature generic_where_clause?;
+
+// Protocol Initializer Declaration
+protocol_initializer_declaration:
+	initializer_head generic_parameter_clause? parameter_clause (
+		THROWS?
+		| RETHROWS
+	) generic_where_clause?;
+
+// Protocol Subscript Declaration
+protocol_subscript_declaration:
+	subscript_head subscript_result generic_where_clause? getter_setter_keyword_block;
+
+// Protocol Associated Type Declaration
+protocol_associated_type_declaration:
+	attributes? access_level_modifier? ASSOCIATED_TYPE typealias_name type_inheritance_clause?
+		typealias_assignment? generic_where_clause?;
+
+// Initializer Declaration
+initializer_declaration:
+	initializer_head generic_parameter_clause? parameter_clause (
+		THROWS
+		| RETHROWS
+	)? generic_where_clause? initializer_body;
+
+initializer_head:
+	attributes? declaration_modifiers? INIT (QUESTION | BANG)?;
+
+initializer_body: code_block;
+
+// Deinitializer Declaration
+deinitializer_declaration: attributes? DEINIT code_block;
+
+// Extension Declaration
+extension_declaration:
+	attributes? access_level_modifier? EXTENSION type_identifier type_inheritance_clause?
+		generic_where_clause? extension_body;
+extension_body: LCURLY extension_members RCURLY;
+extension_members: extension_member*;
+extension_member: declaration | compiler_control_statement;
+
+// Subscript Declaration
+subscript_declaration:
+	subscript_head subscript_result generic_where_clause? (
+		code_block
+		| getter_setter_block
+		| getter_setter_keyword_block
+	);
+
+subscript_head:
+	attributes? declaration_modifiers? SUBSCRIPT generic_parameter_clause? parameter_clause;
+subscript_result: arrow_operator attributes? type;
+
+// Operator Declaration
+operator_declaration:
+	prefix_operator_declaration
+	| postfix_operator_declaration
+	| infix_operator_declaration;
+
+prefix_operator_declaration: PREFIX OPERATOR operator;
+postfix_operator_declaration: POSTFIX OPERATOR operator;
+infix_operator_declaration:
+	INFIX OPERATOR operator infix_operator_group?;
+
+infix_operator_group: COLON precedence_group_name;
+
+// Precedence Group Declaration
+precedence_group_declaration:
+	PRECEDENCE_GROUP precedence_group_name LCURLY precedence_group_attributes? RCURLY;
+precedence_group_attributes: precedence_group_attribute+;
+
+precedence_group_attribute:
+	precedence_group_relation
+	| precedence_group_assignment
+	| precedence_group_associativity;
+
+precedence_group_relation:
+	(HIGHER_THAN | LOWER_THAN) COLON precedence_group_names;
+
+precedence_group_assignment: ASSIGNMENT COLON boolean_literal;
+
+precedence_group_associativity:
+	ASSOCIATIVITY COLON (LEFT | RIGHT | NONE);
+
+precedence_group_names:
+	precedence_group_name (COMMA precedence_group_name)*;
+precedence_group_name: identifier (DOT identifier)*;
+
+// Declaration Modifiers
+declaration_modifier:
+	CLASS
+	| CONVENIENCE
+	| DYNAMIC
+	| FINAL
+	| INFIX
+	| LAZY
+	| OPTIONAL
+	| OVERRIDE
+	| POSTFIX
+	| PREFIX
+	| REQUIRED
+	| STATIC
+	| UNOWNED (LPAREN (SAFE | UNSAFE) RPAREN)?
+	| WEAK
+	| access_level_modifier
+	| mutation_modifier;
+
+declaration_modifiers: declaration_modifier+;
+
+access_level_modifier:
+	(PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (
+		LPAREN SET RPAREN
+	)?;
+
+mutation_modifier: MUTATING | NONMUTATING;
+
+// Patterns
+
+//The following sets of rules are mutually left-recursive [pattern, Type-Casting], to avoid this they were integrated into the same rule.
+pattern:
+	(wildcard_pattern | identifier_pattern | tuple_pattern) type_annotation?
+	| value_binding_pattern
+	| enum_case_pattern
+	| optional_pattern
+	| IS type
+	| pattern AS type
+	| expression_pattern;
+
+// Wildcard Pattern
+wildcard_pattern: UNDERSCORE;
+
+// identifier Pattern
+identifier_pattern: identifier;
+
+// Value-Binding Pattern
+value_binding_pattern: VAR pattern | LET pattern;
+
+// Tuple Pattern
+tuple_pattern: LPAREN tuple_pattern_element_list? RPAREN;
+tuple_pattern_element_list:
+	tuple_pattern_element (COMMA tuple_pattern_element)*;
+tuple_pattern_element: (identifier COLON)? pattern;
+
+// Enumeration Case Pattern
+enum_case_pattern:
+	type_identifier? DOT enum_case_name tuple_pattern?;
+
+// Optional Pattern
+optional_pattern: identifier_pattern QUESTION;
+
+// Expression Pattern
+expression_pattern: expression;
+
+// Attributes
+attribute: AT attribute_name attribute_argument_clause?;
+attribute_name: identifier (DOT identifier)*;
+attribute_argument_clause: LPAREN balanced_tokens? RPAREN;
+attributes: attribute+;
+balanced_tokens: balanced_token+;
+
+balanced_token:
+	LPAREN balanced_tokens? RPAREN
+	| LBRACK balanced_tokens? RBRACK
+	| LCURLY balanced_tokens? RCURLY
+	//Any identifier, keyword, literal, or operator Any punctuation except (, ), [, ], {, or }
+	| identifier
+	| keyword
+	| literal
+	| operator
+	| balanced_token_punctuation;
+
+balanced_token_punctuation:
+	(
+		DOT
+		| COMMA
+		| COLON
+		| SEMI
+		| EQUAL
+		| AT
+		| HASH
+		| BACKTICK
+		| QUESTION
+	)
+	| arrow_operator
+	| {SwiftSupport.isPrefixOp(_input)}? AND
+	| {SwiftSupport.isPostfixOp(_input)}? BANG;
+
+// Expressions
+expression: try_operator? prefix_expression binary_expressions?;
+
+expression_list: expression (COMMA expression)*;
+
+// Prefix Expressions
+prefix_expression:
+	prefix_operator? postfix_expression
+	| in_out_expression;
+
+in_out_expression: AND identifier;
+
+// Try Operator
+try_operator: TRY (QUESTION | BANG)?;
+
+// Binary Expressions
+binary_expression:
+	binary_operator prefix_expression
+	| (assignment_operator | conditional_operator) try_operator? prefix_expression
+	| type_casting_operator;
+
+binary_expressions: binary_expression+;
+
+// Conditional Operator
+conditional_operator: QUESTION expression COLON;
+
+// Ternary Conditional Operator
+type_casting_operator: (IS | AS ( QUESTION | BANG)?) type;
+
+// Primary Expressions
+primary_expression:
+	unqualified_name generic_argument_clause?
+	| array_type
+	| dictionary_type
+	| literal_expression
+	| self_expression
+	| superclass_expression
+	| closure_expression
+	| parenthesized_operator
+	| parenthesized_expression
+	| tuple_expression
+	| implicit_member_expression
+	| wildcard_expression
+	| key_path_expression
+	| selector_expression
+	| key_path_string_expression;
+
+unqualified_name: identifier (LPAREN argument_names RPAREN)?;
+
+// Literal Expression
+literal_expression:
+	literal
+	| array_literal
+	| dictionary_literal
+	| playground_literal
+	| HASH_FILE
+	| HASH_FILE_ID
+	| HASH_FILE_PATH
+	| HASH_LINE
+	| HASH_COLUMN
+	| HASH_FUNCTION
+	| HASH_DSO_HANDLE;
+
+array_literal: LBRACK array_literal_items? RBRACK;
+
+array_literal_items:
+	array_literal_item (COMMA array_literal_item)* COMMA?;
+
+array_literal_item: expression;
+
+dictionary_literal:
+	LBRACK (dictionary_literal_items | COLON) RBRACK;
+
+dictionary_literal_items:
+	dictionary_literal_item (COMMA dictionary_literal_item)* COMMA?;
+
+dictionary_literal_item: expression COLON expression;
+
+playground_literal:
+	HASH_COLOR_LITERAL LPAREN RED COLON expression COMMA GREEN COLON expression COMMA BLUE COLON
+		expression COMMA ALPHA COLON expression RPAREN
+	| HASH_FILE_LITERAL LPAREN RESOURCE_NAME COLON expression RPAREN
+	| HASH_IMAGE_LITERAL LPAREN RESOURCE_NAME COLON expression RPAREN;
+
+// Self Expression
+self_expression:
+	SELF												# self_pure_expression
+	| SELF DOT identifier								# self_method_expression
+	| SELF LBRACK function_call_argument_list RBRACK	# self_subscript_expression
+	| SELF DOT INIT										# self_initializer_expression;
+
+// Superclass Expression
+superclass_expression:
+	SUPER DOT identifier								# superclass_method_expression
+	| SUPER LBRACK function_call_argument_list RBRACK	# superclass_subscript_expression
+	| SUPER DOT INIT									# superclass_initializer_expression;
+
+// Capture Lists
+closure_expression:
+	LCURLY closure_signature? statements? RCURLY;
+
+closure_signature:
+	capture_list? closure_parameter_clause THROWS? function_result? IN
+	| capture_list IN;
+
+closure_parameter_clause:
+	LPAREN closure_parameter_list? RPAREN
+	| identifier_list;
+
+closure_parameter_list:
+	closure_parameter (COMMA closure_parameter)*;
+
+closure_parameter:
+	closure_parameter_name = identifier (
+		type_annotation range_operator?
+	)?;
+
+capture_list: LBRACK capture_list_items RBRACK;
+
+capture_list_items:
+	capture_list_item (COMMA capture_list_item)*;
+
+capture_list_item:
+	capture_specifier? (
+		identifier EQUAL? expression
+		| self_expression
+	);
+
+capture_specifier:
+	WEAK
+	| UNOWNED (LPAREN (SAFE | UNSAFE) RPAREN)?;
+
+// Implicit Member Expression
+implicit_member_expression:
+	DOT (identifier | keyword) (DOT postfix_expression)?;
+// let a: MyType = .default; static let `default` = MyType()
+
+// Parenthesized Expression
+parenthesized_operator: LPAREN operator RPAREN;
+
+// Parenthesized Expression
+parenthesized_expression: LPAREN expression RPAREN;
+
+// Tuple Expression
+tuple_expression:
+	LPAREN RPAREN
+	| LPAREN tuple_element COMMA tuple_element_list RPAREN;
+
+tuple_element_list: tuple_element (COMMA tuple_element)*;
+
+tuple_element: (identifier COLON)? expression;
+
+// Wildcard Expression
+wildcard_expression: UNDERSCORE;
+
+// Key-Path Expression
+key_path_expression: BACKSLASH type? DOT key_path_components;
+key_path_components:
+	key_path_component (DOT key_path_component)*;
+key_path_component:
+	identifier key_path_postfixes?
+	| key_path_postfixes;
+key_path_postfixes: key_path_postfix+;
+key_path_postfix:
+	QUESTION
+	| BANG
+	| SELF
+	| LBRACK function_call_argument_list RBRACK;
+
+// Selector Expression
+selector_expression:
+	HASH_SELECTOR LPAREN ((GETTER | SETTER) COLON)? expression RPAREN;
+
+// Key-Path String Expression
+key_path_string_expression:
+	HASH_KEYPATH LPAREN expression RPAREN;
+
+// Postfix Expressions
+
+//The following sets of rules are mutually left-recursive [postfix_expression,
+// function_call_expression, initializer_expression, explicit_member_expression,
+// postfix_self_expression, subscript_expression, forced_value_expression,
+// optional_chaining_expression], to avoid this the rule inner_postfix_expression was implemented.
+/*postfix_expression:
+ postfix_expression postfix_operator | function_call_expression | initializer_expression |
+ explicit_member_expression | postfix_self_expression | subscript_expression |
+ forced_value_expression | optional_chaining_expression | primary_expression ;
+ */
+postfix_expression:
+	primary_expression (
+		function_call_suffix
+		| initializer_suffix
+		| explicit_member_suffix
+		| postfix_self_suffix
+		| subscript_suffix
+		| forced_value_suffix
+		| optional_chaining_suffix
+	)* postfix_operator*?;
+
+function_call_suffix:
+	function_call_argument_clause? trailing_closures
+	| function_call_argument_clause;
+
+initializer_suffix: DOT INIT (LPAREN argument_names RPAREN)?;
+
+explicit_member_suffix:
+	DOT (
+		Decimal_digits
+		| identifier (
+			generic_argument_clause
+			| LPAREN argument_names RPAREN
+		)?
+	);
+
+postfix_self_suffix: DOT SELF;
+
+subscript_suffix: LBRACK function_call_argument_list RBRACK;
+
+forced_value_suffix: {!SwiftSupport.isBinaryOp(_input)}? BANG;
+optional_chaining_suffix:
+	{!SwiftSupport.isBinaryOp(_input)}? QUESTION;
+
+// Function Call Expression
+
+function_call_argument_clause:
+	LPAREN function_call_argument_list? RPAREN;
+
+function_call_argument_list:
+	function_call_argument (COMMA function_call_argument)*;
+
+function_call_argument:
+	argument_name? (
+		identifier /*optimization */
+		| expression
+		| operator
+	);
+
+trailing_closures:
+	closure_expression labeled_trailing_closures?;
+
+labeled_trailing_closures: labeled_trailing_closure+;
+
+labeled_trailing_closure: identifier COLON closure_expression;
+
+argument_names: argument_name+;
+argument_name: identifier COLON;
+
+// Types
+
+// The following sets of rules are mutually left-recursive [type, optional_type, implicitly_unwrapped_optional_type, metatype_type], to avoid this they were integrated into the same rule.
+type:
+	function_type
+	| array_type
+	| dictionary_type
+	| protocol_composition_type
+	| type_identifier
+	| tuple_type
+	| opaque_type
+	| type (
+		{!SwiftSupport.isBinaryOp(_input)}? QUESTION //optional_type
+		| {!SwiftSupport.isBinaryOp(_input)}? BANG //implicitly_unwrapped_optional_type
+		| DOT TYPE
+		| DOT PROTOCOL
+	) //metatype_type
+	| any_type
+	| self_type
+	| LPAREN type RPAREN;
+
+// Type Annotation
+type_annotation: COLON attributes? INOUT? type;
+
+// Type identifier
+type_identifier:
+	type_name generic_argument_clause? (DOT type_identifier)?;
+
+type_name: identifier;
+
+// Tuple Type
+tuple_type: LPAREN tuple_type_element_list? RPAREN;
+tuple_type_element_list:
+	tuple_type_element (COMMA tuple_type_element)*;
+tuple_type_element:
+	(element_name type_annotation | type) (EQUAL expression)? /* assigning value */;
+element_name:
+	identifier+; // TODO understand a specific thing - _
+
+// Function Type
+function_type:
+	attributes? function_type_argument_clause THROWS? arrow_operator type;
+
+function_type_argument_clause:
+	LPAREN (function_type_argument_list range_operator?)? RPAREN;
+
+function_type_argument_list:
+	function_type_argument (COMMA function_type_argument)*;
+
+function_type_argument:
+	attributes? INOUT? type
+	| argument_label type_annotation;
+
+argument_label:
+	identifier+; // TODO Understand a specific thing - _
+
+// Array Type
+array_type: LBRACK type RBRACK;
+
+// Dictionary Type
+dictionary_type: LBRACK type COLON type RBRACK;
+
+// Protocol Composition Type
+protocol_composition_type:
+	type_identifier (AND type_identifier)* trailing_composition_and?;
+
+trailing_composition_and:
+	{!SwiftSupport.isBinaryOp(_input)}? AND;
+
+// Opaque Type
+opaque_type: SOME type;
+
+// Any Type
+any_type: ANY;
+
+// Self Type
+self_type: SELF_BIG;
+
+// Type Inheritance Clause
+type_inheritance_clause: COLON type_inheritance_list;
+
+type_inheritance_list: type_identifier (COMMA type_identifier)*;
+
+// Identifiers
+identifier:
+	(
+		LINUX
+		| WINDOWS
+		| ALPHA
+		| ARCH
+		| ARM
+		| ARM64
+		| ASSIGNMENT
+		| BLUE
+		| CAN_IMPORT
+		| COMPILER
+		| FILE
+		| GREEN
+		| HIGHER_THAN
+		| I386
+		| I_OS
+		| OSX
+		| I_OS_APPLICATION_EXTENSION
+		| LINE
+		| LOWER_THAN
+		| MAC_CATALYST
+		| MAC_CATALYST_APPLICATION_EXTENSION
+		| MAC_OS
+		| MAC_OS_APPLICATION_EXTENSION
+		| OS
+		| PRECEDENCE_GROUP
+		| RED
+		| RESOURCE_NAME
+		| SAFE
+		| SIMULATOR
+		| SOME
+		| SWIFT
+		| TARGET_ENVIRONMENT
+		| TV_OS
+		| UNSAFE
+		| WATCH_OS
+		| X86_64
+
+		// Keywords reserved in particular contexts
+		| ASSOCIATIVITY
+		| CONVENIENCE
+		| DYNAMIC
+		| DID_SET
+		| FINAL
+		| GET
+		| INFIX
+		| INDIRECT
+		| LAZY
+		| LEFT
+		| MUTATING
+		| NONE
+		| NONMUTATING
+		| OPTIONAL
+		| OVERRIDE
+		| POSTFIX
+		| PRECEDENCE
+		| PREFIX
+		| PROTOCOL
+		| REQUIRED
+		| RIGHT
+		| SET
+		| TYPE
+		| UNOWNED
+		| WEAK
+		| WILL_SET
+		| IN
+		| FOR
+		| GUARD
+		| WHERE
+		| DEFAULT
+		| INTERNAL
+		| PRIVATE
+		| PUBLIC
+		| OPEN
+		| AS
+		| PREFIX
+		| POSTFIX
+		| WHILE
+		| SELF
+		| SELF_BIG
+		| SET
+		| CLASS
+		| GETTER
+		| SETTER
+		| OPERATOR
+		| DO
+		| CATCH
+	)
+	| Identifier
+	| BACKTICK (keyword | Identifier | DOLLAR) BACKTICK;
+
+identifier_list: identifier (COMMA identifier)*;
+
+// Keywords and Punctuation
+keyword:
+	// Keywords used in declarations
+	ASSOCIATED_TYPE
+	| CLASS
+	| DEINIT
+	| ENUM
+	| EXTENSION
+	| FILE_PRIVATE
+	| FUNC
+	| IMPORT
+	| INIT
+	| INOUT
+	| INTERNAL
+	| LET
+	| OPEN
+	| OPERATOR
+	| PRIVATE
+	| PROTOCOL
+	| PUBLIC
+	| RETHROWS
+	| STATIC
+	| STRUCT
+	| SUBSCRIPT
+	| TYPEALIAS
+	| VAR
+	// Keywords used in statements
+	| BREAK
+	| CASE
+	| CONTINUE
+	| DEFAULT
+	| DEFER
+	| DO
+	| ELSE
+	| FALLTHROUGH
+	| FOR
+	| GUARD
+	| IF
+	| IN
+	| REPEAT
+	| RETURN
+	| SWITCH
+	| WHERE
+	| WHILE
+	// Keywords used in expressions and types
+	| AS
+	| ANY
+	| CATCH
+	| FALSE
+	| IS
+	| NIL
+	| SUPER
+	| SELF
+	| SELF_BIG
+	| THROW
+	| THROWS
+	| TRUE
+	| TRY
+	// Keywords used in patterns
+	| UNDERSCORE
+	// Keywords that begin with a number sign (#)
+	| AVAILABLE
+	| HASH_COLOR_LITERAL
+	| HASH_COLUMN
+	| HASH_ELSE
+	| HASH_ELSEIF
+	| HASH_ENDIF
+	| ERROR
+	| HASH_FILE
+	| HASH_FILE_ID
+	| HASH_FILE_LITERAL
+	| HASH_FILE_PATH
+	| HASH_FUNCTION
+	| HASH_IF
+	| HASH_IMAGE_LITERAL
+	| HASH_LINE
+	| HASH_SELECTOR
+	| SOURCE_LOCATION
+	| WARNING;
+
+// Operators
+
+// Assignment Operator
+assignment_operator: {SwiftSupport.isBinaryOp(_input)}? EQUAL;
+
+negate_prefix_operator: {SwiftSupport.isPrefixOp(_input)}? SUB;
+
+compilation_condition_AND:
+	{SwiftSupport.isOperator(_input,"&&")}? AND AND;
+compilation_condition_OR:
+	{SwiftSupport.isOperator(_input,"||")}? OR OR;
+compilation_condition_GE:
+	{SwiftSupport.isOperator(_input,">=")}? GT EQUAL;
+compilation_condition_L: {SwiftSupport.isOperator(_input,"<")}? LT;
+arrow_operator: {SwiftSupport.isOperator(_input,"->")}? SUB GT;
+range_operator: {SwiftSupport.isOperator(_input,"...")}? DOT DOT DOT;
+same_type_equals:
+	{SwiftSupport.isOperator(_input,"==")}? EQUAL EQUAL;
+
+binary_operator: {SwiftSupport.isBinaryOp(_input)}? operator;
+
+prefix_operator: {SwiftSupport.isPrefixOp(_input)}? operator;
+
+postfix_operator: {SwiftSupport.isPostfixOp(_input)}? operator;
+
+operator:
+	operator_head operator_characters?
+	| dot_operator_head dot_operator_characters;
+
+operator_head: (
+		DIV
+		| EQUAL
+		| SUB
+		| ADD
+		| BANG
+		| MUL
+		| MOD
+		| AND
+		| OR
+		| LT
+		| GT
+		| CARET
+		| TILDE
+		| QUESTION
+	) // wrapping in (..) makes it a fast set comparison
+	| Operator_head_other;
+
+operator_character:
+	operator_head
+	| Operator_following_character;
+
+operator_characters: (
+		{_input.get(_input.index()-1).getType()!=WS}? operator_character
+	)+;
+
+dot_operator_head: DOT;
+dot_operator_character: DOT | operator_character;
+dot_operator_characters: (
+		{_input.get(_input.index()-1).getType()!=WS}? dot_operator_character
+	)+;
+
+// Literals
+literal:
+	numeric_literal
+	| string_literal
+	| boolean_literal
+	| nil_literal;
+
+numeric_literal:
+	negate_prefix_operator? integer_literal
+	| negate_prefix_operator? Floating_point_literal;
+
+boolean_literal: TRUE | FALSE;
+
+nil_literal: NIL;
+
+// Integer Literals
+integer_literal:
+	Decimal_digits
+	| Decimal_literal
+	| Binary_literal
+	| Octal_literal
+	| Hexadecimal_literal;
+
+// String Literals
+string_literal:
+	extended_string_literal
+	| interpolated_string_literal
+	| static_string_literal;
+
+extended_string_literal:
+	Multi_line_extended_string_open Quoted_multi_line_extended_text+
+		Multi_line_extended_string_close
+	| Single_line_extended_string_open Quoted_single_line_extended_text+
+		Single_line_extended_string_close;
+
+static_string_literal:
+	Single_line_string_open Quoted_single_line_text* Single_line_string_close
+	| Multi_line_string_open Quoted_multi_line_text* Multi_line_string_close;
+
+interpolated_string_literal:
+	Single_line_string_open (
+		Quoted_single_line_text
+		| Interpolataion_single_line (
+			expression
+			| tuple_element COMMA tuple_element_list
+		) RPAREN
+	)* Single_line_string_close
+	| Multi_line_string_open (
+		Quoted_multi_line_text
+		| Interpolataion_multi_line (
+			expression
+			| tuple_element COMMA tuple_element_list
+		) RPAREN
+	)* Multi_line_string_close;

+ 477 - 0
languages/swift/src/main/java/de/jplag/swift/JPlagSwiftListener.java

@@ -0,0 +1,477 @@
+package de.jplag.swift;
+
+import static de.jplag.swift.SwiftTokenType.*;
+
+import org.antlr.v4.runtime.Token;
+
+import de.jplag.swift.grammar.Swift5Parser.*;
+import de.jplag.swift.grammar.Swift5ParserBaseListener;
+
+public class JPlagSwiftListener extends Swift5ParserBaseListener {
+    private final SwiftParserAdapter parserAdapter;
+
+    public JPlagSwiftListener(SwiftParserAdapter parserAdapter) {
+        this.parserAdapter = parserAdapter;
+    }
+
+    /**
+     * 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(SwiftTokenType 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(SwiftTokenType tokenType, Token start, Token end) {
+        parserAdapter.addToken(tokenType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1);
+    }
+
+    @Override
+    public void enterImport_declaration(Import_declarationContext context) {
+        transformToken(IMPORT, context.getStart(), context.getStop());
+        super.enterImport_declaration(context);
+    }
+
+    @Override
+    public void enterClass_declaration(Class_declarationContext context) {
+        transformToken(CLASS_DECLARATION, context.getStart(), context.getStop());
+        super.enterClass_declaration(context);
+    }
+
+    @Override
+    public void enterClass_body(Class_bodyContext context) {
+        transformToken(CLASS_BODY_BEGIN, context.getStart());
+        super.enterClass_body(context);
+    }
+
+    @Override
+    public void exitClass_body(Class_bodyContext context) {
+        transformToken(CLASS_BODY_END, context.getStop());
+        super.exitClass_body(context);
+    }
+
+    @Override
+    public void enterStruct_declaration(Struct_declarationContext context) {
+        transformToken(STRUCT_DECLARATION, context.getStart(), context.getStop());
+        super.enterStruct_declaration(context);
+    }
+
+    @Override
+    public void enterStruct_body(Struct_bodyContext context) {
+        transformToken(STRUCT_BODY_BEGIN, context.getStart());
+        super.enterStruct_body(context);
+    }
+
+    @Override
+    public void exitStruct_body(Struct_bodyContext context) {
+        transformToken(STRUCT_BODY_END, context.getStop());
+        super.exitStruct_body(context);
+    }
+
+    @Override
+    public void enterEnum_name(Enum_nameContext context) {
+        transformToken(ENUM_DECLARATION, context.getStart(), context.getStop());
+        super.enterEnum_name(context);
+    }
+
+    @Override
+    public void enterRaw_value_style_enum_members(Raw_value_style_enum_membersContext context) {
+        transformToken(ENUM_BODY_BEGIN, context.getStart());
+        super.enterRaw_value_style_enum_members(context);
+    }
+
+    @Override
+    public void exitRaw_value_style_enum(Raw_value_style_enumContext context) {
+        transformToken(ENUM_BODY_END, context.getStop());
+        super.exitRaw_value_style_enum(context);
+    }
+
+    @Override
+    public void enterRaw_value_style_enum_case(Raw_value_style_enum_caseContext context) {
+        transformToken(ENUM_LITERAL, context.getStart(), context.getStop());
+        super.enterRaw_value_style_enum_case(context);
+    }
+
+    @Override
+    public void enterUnion_style_enum_members(Union_style_enum_membersContext context) {
+        transformToken(ENUM_BODY_BEGIN, context.getStart());
+        super.enterUnion_style_enum_members(context);
+    }
+
+    @Override
+    public void exitUnion_style_enum(Union_style_enumContext context) {
+        transformToken(ENUM_BODY_END, context.getStop());
+        super.exitUnion_style_enum(context);
+    }
+
+    @Override
+    public void enterUnion_style_enum_case(Union_style_enum_caseContext context) {
+        transformToken(ENUM_LITERAL, context.getStart(), context.getStop());
+        super.enterUnion_style_enum_case(context);
+    }
+
+    @Override
+    public void enterProtocol_declaration(Protocol_declarationContext context) {
+        transformToken(PROTOCOL_DECLARATION, context.getStart(), context.getStop());
+        super.enterProtocol_declaration(context);
+    }
+
+    @Override
+    public void enterProtocol_body(Protocol_bodyContext context) {
+        transformToken(PROTOCOL_BODY_BEGIN, context.getStart());
+        super.enterProtocol_body(context);
+    }
+
+    @Override
+    public void exitProtocol_body(Protocol_bodyContext context) {
+        transformToken(PROTOCOL_BODY_END, context.getStop());
+        super.exitProtocol_body(context);
+    }
+
+    @Override
+    public void enterVariable_declaration(Variable_declarationContext context) {
+        transformToken(PROPERTY_DECLARATION, context.getStart(), context.getStop());
+        super.enterVariable_declaration(context);
+    }
+
+    @Override
+    public void enterProtocol_property_declaration(Protocol_property_declarationContext context) {
+        transformToken(PROPERTY_DECLARATION, context.getStart(), context.getStop());
+        super.enterProtocol_property_declaration(context);
+    }
+
+    @Override
+    public void enterConstant_declaration(Constant_declarationContext context) {
+        transformToken(PROPERTY_DECLARATION, context.getStart(), context.getStop());
+        super.enterConstant_declaration(context);
+    }
+
+    @Override
+    public void enterGetter_clause(Getter_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_BEGIN, context.getStart());
+        super.enterGetter_clause(context);
+    }
+
+    @Override
+    public void exitGetter_clause(Getter_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_END, context.getStop());
+        super.exitGetter_clause(context);
+    }
+
+    @Override
+    public void enterSetter_clause(Setter_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_BEGIN, context.getStart());
+        super.enterSetter_clause(context);
+    }
+
+    @Override
+    public void exitSetter_clause(Setter_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_END, context.getStop());
+        super.exitSetter_clause(context);
+    }
+
+    @Override
+    public void enterWillSet_clause(WillSet_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_BEGIN, context.getStart());
+        super.enterWillSet_clause(context);
+    }
+
+    @Override
+    public void enterGetter_setter_block(Getter_setter_blockContext context) {
+        if (isComputedReadOnlyVariableGetterContext(context)) {
+            transformToken(PROPERTY_ACCESSOR_BEGIN, context.getStart());
+        }
+        super.enterGetter_setter_block(context);
+    }
+
+    @Override
+    public void exitGetter_setter_block(Getter_setter_blockContext context) {
+        if (isComputedReadOnlyVariableGetterContext(context)) {
+            transformToken(PROPERTY_ACCESSOR_END, context.getStop());
+        }
+        super.exitGetter_setter_block(context);
+    }
+
+    /**
+     * Indicates whether the given context encodes a computed read-only variable getter. An example of this is
+     * <code>var example: Int { return 1 }</code>.
+     * @param context
+     * @return
+     */
+    private boolean isComputedReadOnlyVariableGetterContext(Getter_setter_blockContext context) {
+        return context.getChildCount() == 1 && context.getChild(0) instanceof Code_blockContext;
+    }
+
+    @Override
+    public void exitWillSet_clause(WillSet_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_END, context.getStop());
+        super.exitWillSet_clause(context);
+    }
+
+    @Override
+    public void enterDidSet_clause(DidSet_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_BEGIN, context.getStart());
+        super.enterDidSet_clause(context);
+    }
+
+    @Override
+    public void exitDidSet_clause(DidSet_clauseContext context) {
+        transformToken(PROPERTY_ACCESSOR_END, context.getStop());
+        super.exitDidSet_clause(context);
+    }
+
+    @Override
+    public void enterInitializer_declaration(Initializer_declarationContext context) {
+        transformToken(FUNCTION, context.getStart(), context.getStop());
+        super.enterInitializer_declaration(context);
+    }
+
+    @Override
+    public void enterProtocol_initializer_declaration(Protocol_initializer_declarationContext context) {
+        transformToken(FUNCTION, context.getStart(), context.getStop());
+        super.enterProtocol_initializer_declaration(context);
+    }
+
+    @Override
+    public void enterInitializer_body(Initializer_bodyContext context) {
+        transformToken(FUNCTION_BODY_BEGIN, context.getStart());
+        super.enterInitializer_body(context);
+    }
+
+    @Override
+    public void exitInitializer_body(Initializer_bodyContext context) {
+        transformToken(FUNCTION_BODY_END, context.getStop());
+        super.exitInitializer_body(context);
+    }
+
+    @Override
+    public void enterFunction_name(Function_nameContext context) {
+        transformToken(FUNCTION, context.getStart(), context.getStop());
+        super.enterFunction_name(context);
+    }
+
+    @Override
+    public void enterParameter(ParameterContext context) {
+        transformToken(FUNCTION_PARAMETER, context.getStart(), context.getStop());
+        super.enterParameter(context);
+    }
+
+    @Override
+    public void enterFunction_result(Function_resultContext context) {
+        transformToken(FUNCTION_PARAMETER, context.getStart(), context.getStop());
+        super.enterFunction_result(context);
+    }
+
+    @Override
+    public void enterFunction_body(Function_bodyContext context) {
+        transformToken(FUNCTION_BODY_BEGIN, context.getStart());
+        super.enterFunction_body(context);
+    }
+
+    @Override
+    public void exitFunction_body(Function_bodyContext context) {
+        transformToken(FUNCTION_BODY_END, context.getStop());
+        super.exitFunction_body(context);
+    }
+
+    @Override
+    public void enterClosure_expression(Closure_expressionContext context) {
+        transformToken(CLOSURE_BODY_BEGIN, context.getStart());
+        super.enterClosure_expression(context);
+    }
+
+    @Override
+    public void exitClosure_expression(Closure_expressionContext context) {
+        transformToken(CLOSURE_BODY_END, context.getStop());
+        super.exitClosure_expression(context);
+    }
+
+    @Override
+    public void enterFor_in_statement(For_in_statementContext context) {
+        transformToken(FOR_BODY_BEGIN, context.getStart());
+        super.enterFor_in_statement(context);
+    }
+
+    @Override
+    public void exitFor_in_statement(For_in_statementContext context) {
+        transformToken(FOR_BODY_END, context.getStop());
+        super.exitFor_in_statement(context);
+    }
+
+    @Override
+    public void enterIf_statement(If_statementContext context) {
+        transformToken(IF_BODY_BEGIN, context.getStart());
+        super.enterIf_statement(context);
+    }
+
+    @Override
+    public void exitIf_statement(If_statementContext context) {
+        transformToken(IF_BODY_END, context.getStop());
+        super.exitIf_statement(context);
+    }
+
+    @Override
+    public void enterElse_clause(Else_clauseContext context) {
+        transformToken(IF_BODY_END, context.getStart());
+        // Check that current context is not `else if` but rather `else`
+        if (!(context.getChild(1) instanceof If_statementContext)) {
+            transformToken(IF_BODY_BEGIN, context.getStart());
+        }
+        super.enterElse_clause(context);
+    }
+
+    @Override
+    public void enterGuard_statement(Guard_statementContext context) {
+        transformToken(IF_BODY_BEGIN, context.getStart());
+        super.enterGuard_statement(context);
+    }
+
+    @Override
+    public void exitGuard_statement(Guard_statementContext context) {
+        transformToken(IF_BODY_END, context.getStop());
+        super.exitGuard_statement(context);
+    }
+
+    @Override
+    public void enterSwitch_statement(Switch_statementContext context) {
+        transformToken(SWITCH_BODY_BEGIN, context.getStart());
+        super.enterSwitch_statement(context);
+    }
+
+    @Override
+    public void exitSwitch_statement(Switch_statementContext context) {
+        transformToken(SWITCH_BODY_END, context.getStop());
+        super.exitSwitch_statement(context);
+    }
+
+    @Override
+    public void enterSwitch_case(Switch_caseContext context) {
+        transformToken(SWITCH_CASE, context.getStart());
+        super.enterSwitch_case(context);
+    }
+
+    @Override
+    public void enterWhile_statement(While_statementContext context) {
+        transformToken(WHILE_BODY_BEGIN, context.getStart());
+        super.enterWhile_statement(context);
+    }
+
+    @Override
+    public void exitWhile_statement(While_statementContext context) {
+        transformToken(WHILE_BODY_END, context.getStop());
+        super.exitWhile_statement(context);
+    }
+
+    @Override
+    public void enterRepeat_while_statement(Repeat_while_statementContext context) {
+        transformToken(REPEAT_WHILE_BODY_BEGIN, context.getStart());
+        super.enterRepeat_while_statement(context);
+    }
+
+    @Override
+    public void exitRepeat_while_statement(Repeat_while_statementContext context) {
+        transformToken(REPEAT_WHILE_BODY_END, context.getStop());
+        super.exitRepeat_while_statement(context);
+    }
+
+    @Override
+    public void enterDefer_statement(Defer_statementContext context) {
+        transformToken(DEFER_BODY_BEGIN, context.getStart());
+        super.enterDefer_statement(context);
+    }
+
+    @Override
+    public void exitDefer_statement(Defer_statementContext context) {
+        transformToken(DEFER_BODY_END, context.getStop());
+        super.exitDefer_statement(context);
+    }
+
+    @Override
+    public void enterDo_block(Do_blockContext context) {
+        transformToken(DO_TRY_BODY_BEGIN, context.getStart());
+        super.enterDo_block(context);
+    }
+
+    @Override
+    public void exitDo_block(Do_blockContext context) {
+        transformToken(DO_TRY_BODY_END, context.getStop());
+        super.exitDo_block(context);
+    }
+
+    @Override
+    public void enterCatch_clause(Catch_clauseContext context) {
+        transformToken(CATCH_BODY_BEGIN, context.getStart());
+        super.enterCatch_clause(context);
+    }
+
+    @Override
+    public void exitCatch_clause(Catch_clauseContext context) {
+        transformToken(CATCH_BODY_END, context.getStop());
+        super.exitCatch_clause(context);
+    }
+
+    @Override
+    public void enterThrow_statement(Throw_statementContext context) {
+        transformToken(THROW, context.getStart(), context.getStop());
+        super.enterThrow_statement(context);
+    }
+
+    @Override
+    public void enterReturn_statement(Return_statementContext context) {
+        transformToken(RETURN, context.getStart(), context.getStop());
+        super.enterReturn_statement(context);
+    }
+
+    @Override
+    public void enterContinue_statement(Continue_statementContext context) {
+        transformToken(CONTINUE, context.getStart(), context.getStop());
+        super.enterContinue_statement(context);
+    }
+
+    @Override
+    public void enterBreak_statement(Break_statementContext context) {
+        transformToken(BREAK, context.getStart(), context.getStop());
+        super.enterBreak_statement(context);
+    }
+
+    @Override
+    public void enterFallthrough_statement(Fallthrough_statementContext context) {
+        transformToken(FALLTHROUGH, context.getStart(), context.getStop());
+        super.enterFallthrough_statement(context);
+    }
+
+    @Override
+    public void enterInitializer(InitializerContext context) {
+        transformToken(ASSIGNMENT, context.getStart(), context.getStop());
+        super.enterInitializer(context);
+    }
+
+    @Override
+    public void enterRaw_value_assignment(Raw_value_assignmentContext context) {
+        transformToken(ASSIGNMENT, context.getStart(), context.getStop());
+        super.enterRaw_value_assignment(context);
+    }
+
+    @Override
+    public void enterBinary_operator(Binary_operatorContext context) {
+        String operator = context.getText();
+        if (operator.contains("=") && !operator.contains("==")) {
+            transformToken(ASSIGNMENT, context.getStart(), context.getStop());
+        }
+        super.enterBinary_operator(context);
+    }
+
+    @Override
+    public void enterFunction_call_suffix(Function_call_suffixContext context) {
+        transformToken(FUNCTION_CALL, context.getStart(), context.getStop());
+        super.enterFunction_call_suffix(context);
+    }
+}

+ 52 - 0
languages/swift/src/main/java/de/jplag/swift/Language.java

@@ -0,0 +1,52 @@
+package de.jplag.swift;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+import org.kohsuke.MetaInfServices;
+
+import de.jplag.ParsingException;
+import de.jplag.Token;
+
+/**
+ * This represents the Swift language as a language supported by JPlag.
+ */
+@MetaInfServices(de.jplag.Language.class)
+public class Language implements de.jplag.Language {
+
+    private static final String NAME = "Swift Parser";
+    private static final String IDENTIFIER = "swift";
+    private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
+    private static final String[] FILE_EXTENSIONS = {".swift"};
+    private final SwiftParserAdapter parserAdapter;
+
+    public Language() {
+        this.parserAdapter = new SwiftParserAdapter();
+    }
+
+    @Override
+    public String[] suffixes() {
+        return FILE_EXTENSIONS;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getIdentifier() {
+        return IDENTIFIER;
+    }
+
+    @Override
+    public int minimumTokenMatch() {
+        return DEFAULT_MIN_TOKEN_MATCH;
+    }
+
+    @Override
+    public List<Token> parse(Set<File> files) throws ParsingException {
+        return parserAdapter.parse(files);
+    }
+}

+ 80 - 0
languages/swift/src/main/java/de/jplag/swift/SwiftParserAdapter.java

@@ -0,0 +1,80 @@
+package de.jplag.swift;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+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.ParsingException;
+import de.jplag.Token;
+import de.jplag.swift.grammar.Swift5Lexer;
+import de.jplag.swift.grammar.Swift5Parser;
+
+public class SwiftParserAdapter extends AbstractParser {
+
+    public static final int NOT_SET = -1;
+    private File currentFile;
+    private List<Token> tokens;
+
+    /**
+     * Creates the SwiftParserAdapter
+     */
+    public SwiftParserAdapter() {
+        super();
+    }
+
+    /**
+     * Parsers a set of files into a single list of {@link Token}s.
+     * @param files the set of files.
+     * @return a list containing all tokens of all files.
+     */
+    public List<Token> parse(Set<File> files) throws ParsingException {
+        tokens = new ArrayList<>();
+        for (File file : files) {
+            parse(file);
+            tokens.add(Token.fileEnd(file));
+        }
+        return tokens;
+    }
+
+    private void parse(File file) throws ParsingException {
+        try (FileInputStream inputStream = new FileInputStream(file)) {
+            currentFile = file;
+
+            Swift5Lexer lexer = new Swift5Lexer(CharStreams.fromStream(inputStream));
+            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+            Swift5Parser parser = new Swift5Parser(tokenStream);
+
+            ParserRuleContext entryContext = parser.top_level();
+            ParseTreeWalker treeWalker = new ParseTreeWalker();
+
+            JPlagSwiftListener listener = new JPlagSwiftListener(this);
+            for (int i = 0; i < entryContext.getChildCount(); i++) {
+                ParseTree parseTree = entryContext.getChild(i);
+                treeWalker.walk(listener, parseTree);
+            }
+        } catch (IOException exception) {
+            throw new ParsingException(file, exception.getMessage(), exception);
+        }
+    }
+
+    /**
+     * Adds a new {@link Token} to the current token list.
+     * @param tokenType the type of the new {@link Token}
+     * @param line the line of the Token in the current file
+     * @param column the start column of the Token in the line
+     * @param length the length of the Token
+     */
+    /* package-private */ void addToken(SwiftTokenType tokenType, int line, int column, int length) {
+        tokens.add(new Token(tokenType, currentFile, line, column, length));
+    }
+}

+ 63 - 0
languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java

@@ -0,0 +1,63 @@
+package de.jplag.swift;
+
+import de.jplag.TokenType;
+
+public enum SwiftTokenType implements TokenType {
+    IMPORT("IMPORT"),
+    CLASS_DECLARATION("CLASS"),
+    STRUCT_DECLARATION("STRUCT"),
+    ENUM_DECLARATION("ENUM"),
+    PROTOCOL_DECLARATION("PROTOCOL"),
+    CLASS_BODY_BEGIN("CLASS{"),
+    CLASS_BODY_END("}CLASS"),
+    STRUCT_BODY_BEGIN("STRUCT{"),
+    STRUCT_BODY_END("}STRUCT"),
+    ENUM_BODY_BEGIN("ENUM{"),
+    ENUM_BODY_END("}ENUM"),
+    ENUM_LITERAL("ENUM CASE"),
+    PROTOCOL_BODY_BEGIN("PROTOCOL{"),
+    PROTOCOL_BODY_END("}PROTOCOL"),
+    PROPERTY_DECLARATION("PROPERTY"),
+    PROPERTY_ACCESSOR_BEGIN("ACCESSOR{"),
+    PROPERTY_ACCESSOR_END("}ACCESSOR"),
+    FUNCTION("FUNCTION"),
+    FUNCTION_PARAMETER("PARAMETER"),
+    FUNCTION_BODY_BEGIN("FUNCTION{"),
+    FUNCTION_BODY_END("}FUNCTION"),
+    CLOSURE_BODY_BEGIN("CLOSURE{"),
+    CLOSURE_BODY_END("}CLOSURE"),
+    FOR_BODY_BEGIN("FOR{"),
+    FOR_BODY_END("}FOR"),
+    IF_BODY_BEGIN("IF{"),
+    IF_BODY_END("}IF"),
+    SWITCH_BODY_BEGIN("SWITCH{"),
+    SWITCH_BODY_END("}SWITCH"),
+    SWITCH_CASE("CASE"),
+    WHILE_BODY_BEGIN("WHILE{"),
+    WHILE_BODY_END("}WHILE"),
+    REPEAT_WHILE_BODY_BEGIN("REPEAT{"),
+    REPEAT_WHILE_BODY_END("}REPEAT"),
+    DEFER_BODY_BEGIN("DEFER{"),
+    DEFER_BODY_END("}DEFER"),
+    DO_TRY_BODY_BEGIN("DO{"),
+    DO_TRY_BODY_END("}DO"),
+    CATCH_BODY_BEGIN("CATCH{"),
+    CATCH_BODY_END("}CATCH"),
+    THROW("THROW"),
+    RETURN("RETURN"),
+    CONTINUE("CONTINUE"),
+    BREAK("BREAK"),
+    FALLTHROUGH("FALLTHROUGH"),
+    ASSIGNMENT("ASSIGN"),
+    FUNCTION_CALL("CALL");
+
+    private final String description;
+
+    public String getDescription() {
+        return description;
+    }
+
+    SwiftTokenType(String description) {
+        this.description = description;
+    }
+}

+ 360 - 0
languages/swift/src/main/java/de/jplag/swift/grammar/SwiftSupport.java

@@ -0,0 +1,360 @@
+package de.jplag.swift.grammar;
+
+import java.util.BitSet;
+
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.misc.Interval;
+
+// Taken from https://github.com/antlr/grammars-v4/tree/master/swift/swift5 Commit
+// 2f6c19cc742c60541227b19c45ac2acc844d9b1a
+public class SwiftSupport {
+    /*
+     * TODO There is one caveat to the rules above. If the ! or ? predefined operator has no whitespace on the left, it is
+     * treated as a postfix operator, regardless of whether it has whitespace on the right. To use the ? as the
+     * optional-chaining operator, it must not have whitespace on the left. To use it in the ternary conditional (? :)
+     * operator, it must have whitespace around both sides.
+     */
+
+    /*
+     * operator-head : / = - + ! * % < > & | ^ ~ ? | [\u00A1-\u00A7] | [\u00A9\u00AB] | [\u00AC\u00AE] |
+     * [\u00B0-\u00B1\u00B6\u00BB\u00BF\u00D7\u00F7] | [\u2016-\u2017\u2020-\u2027] | [\u2030-\u203E] | [\u2041-\u2053] |
+     * [\u2055-\u205E] | [\u2190-\u23FF] | [\u2500-\u2775] | [\u2794-\u2BFF] | [\u2E00-\u2E7F] | [\u3001-\u3003] |
+     * [\u3008-\u3030] ;
+     */
+    public static final BitSet operatorHead = new BitSet(0x10000);
+    public static final BitSet operatorCharacter;
+
+    public static final BitSet leftWS = new BitSet(255);
+    public static final BitSet rightWS = new BitSet(255);
+
+    static {
+        // operator-head → / =­ -­ +­ !­ *­ %­ <­ >­ &­ |­ ^­ ~­ ?­
+        operatorHead.set('/');
+        operatorHead.set('=');
+        operatorHead.set('-');
+        operatorHead.set('+');
+        operatorHead.set('!');
+        operatorHead.set('*');
+        operatorHead.set('%');
+        operatorHead.set('&');
+        operatorHead.set('|');
+        operatorHead.set('<');
+        operatorHead.set('>');
+        operatorHead.set('^');
+        operatorHead.set('~');
+        operatorHead.set('?');
+
+        // operator-head → U+00A1–U+00A7
+        operatorHead.set(0x00A1, 0x00A7 + 1);
+
+        // operator-head → U+00A9 or U+00AB
+        operatorHead.set(0x00A9);
+        operatorHead.set(0x00AB);
+
+        // operator-head → U+00AC or U+00AE
+        operatorHead.set(0x00AC);
+        operatorHead.set(0x00AE);
+
+        // operator-head → U+00B0–U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7
+        operatorHead.set(0x00B0, 0x00B1 + 1);
+        operatorHead.set(0x00B6);
+        operatorHead.set(0x00BB);
+        operatorHead.set(0x00BF);
+        operatorHead.set(0x00D7);
+        operatorHead.set(0x00F7);
+
+        // operator-head → U+2016–U+2017 or U+2020–U+2027
+        operatorHead.set(0x2016, 0x2017 + 1);
+        operatorHead.set(0x2020, 0x2027 + 1);
+
+        // operator-head → U+2030–U+203E
+        operatorHead.set(0x2030, 0x203E + 1);
+
+        // operator-head → U+2041–U+2053
+        operatorHead.set(0x2041, 0x2053 + 1);
+
+        // operator-head → U+2055–U+205E
+        operatorHead.set(0x2055, 0x205E + 1);
+
+        // operator-head → U+2190–U+23FF
+        operatorHead.set(0x2190, 0x23FF + 1);
+
+        // operator-head → U+2500–U+2775
+        operatorHead.set(0x2500, 0x2775 + 1);
+
+        // operator-head → U+2794–U+2BFF
+        operatorHead.set(0x2794, 0x2BFF + 1);
+
+        // operator-head → U+2E00–U+2E7F
+        operatorHead.set(0x2E00, 0x2E7F + 1);
+
+        // operator-head → U+3001–U+3003
+        operatorHead.set(0x3001, 0x3003 + 1);
+
+        // operator-head → U+3008–U+3030
+        operatorHead.set(0x3008, 0x3020 + 1);
+
+        operatorHead.set(0x3030);
+
+        // operator-character → operator-head­
+        operatorCharacter = (BitSet) operatorHead.clone();
+
+        // operator-character → U+0300–U+036F
+        operatorCharacter.set(0x0300, 0x036F + 1);
+        // operator-character → U+1DC0–U+1DFF
+        operatorCharacter.set(0x1DC0, 0x1DFF + 1);
+        // operator-character → U+20D0–U+20FF
+        operatorCharacter.set(0x20D0, 0x20FF + 1);
+        // operator-character → U+FE00–U+FE0F
+        operatorCharacter.set(0xFE00, 0xFE0F + 1);
+        // operator-character → U+FE20–U+FE2F
+        operatorCharacter.set(0xFE20, 0xFE2F + 1);
+        operatorCharacter.set(0xE0100, 0xE01EF + 1);
+
+        // operator-character → U+E0100–U+E01EF
+        // Java works with 16-bit unicode chars. However, it can work for targets in other languages, e.g. in Swift
+        // operatorCharacter.set(0xE0100,0xE01EF+1);
+
+        leftWS.set(Swift5Parser.WS);
+        leftWS.set(Swift5Parser.LPAREN);
+        leftWS.set(Swift5Parser.Interpolataion_multi_line);
+        leftWS.set(Swift5Parser.Interpolataion_single_line);
+        leftWS.set(Swift5Parser.LBRACK);
+        leftWS.set(Swift5Parser.LCURLY);
+        leftWS.set(Swift5Parser.COMMA);
+        leftWS.set(Swift5Parser.COLON);
+        leftWS.set(Swift5Parser.SEMI);
+
+        rightWS.set(Swift5Parser.WS);
+        rightWS.set(Swift5Parser.RPAREN);
+        rightWS.set(Swift5Parser.RBRACK);
+        rightWS.set(Swift5Parser.RCURLY);
+        rightWS.set(Swift5Parser.COMMA);
+        rightWS.set(Swift5Parser.COLON);
+        rightWS.set(Swift5Parser.SEMI);
+        rightWS.set(Swift5Parser.Line_comment);
+        rightWS.set(Swift5Parser.Block_comment);
+    }
+
+    private static boolean isCharacterFromSet(Token token, BitSet bitSet) {
+        if (token.getType() == Token.EOF) {
+            return false;
+        } else {
+            String text = token.getText();
+            int codepoint = text.codePointAt(0);
+            if (Character.charCount(codepoint) != text.length()) {
+                // not a single character
+                return false;
+            } else {
+                return bitSet.get(codepoint);
+            }
+        }
+    }
+
+    public static boolean isOperatorHead(Token token) {
+        return isCharacterFromSet(token, operatorHead);
+    }
+
+    public static boolean isOperatorCharacter(Token token) {
+        return isCharacterFromSet(token, operatorCharacter);
+    }
+
+    public static boolean isOpNext(TokenStream tokens) {
+        int start = tokens.index();
+        Token lt = tokens.get(start);
+        int stop = getLastOpTokenIndex(tokens);
+        return stop != -1;
+        // System.out.printf("isOpNext: i=%d t='%s'", start, lt.getText());
+        // System.out.printf(", op='%s'\n", tokens.getText(Interval.of(start,stop)));
+    }
+
+    /**
+     * Find stop token index of next operator; return -1 if not operator.
+     */
+    public static int getLastOpTokenIndex(TokenStream tokens) {
+        SwiftSupport.fillUp(tokens);
+        int currentTokenIndex = tokens.index(); // current on-channel lookahead token index
+        Token currentToken = tokens.get(currentTokenIndex);
+
+        // System.out.println("getLastOpTokenIndex: "+currentToken.getText());
+
+        // operator → dot-operator-head­ dot-operator-characters
+        if (currentToken.getType() == Swift5Parser.DOT && tokens.get(currentTokenIndex + 1).getType() == Swift5Parser.DOT) {
+            // System.out.println("DOT");
+
+            // dot-operator
+            currentTokenIndex += 2; // point at token after ".."
+            currentToken = tokens.get(currentTokenIndex);
+
+            // dot-operator-character → .­ | operator-character­
+            while (currentToken.getType() == Swift5Parser.DOT || isOperatorCharacter(currentToken)) {
+                // System.out.println("DOT");
+                currentTokenIndex++;
+                currentToken = tokens.get(currentTokenIndex);
+            }
+
+            // System.out.println("result: "+(currentTokenIndex - 1));
+            return currentTokenIndex - 1;
+        }
+
+        // operator → operator-head­ operator-characters­?
+
+        if (isOperatorHead(currentToken)) {
+            // System.out.println("isOperatorHead");
+
+            currentToken = tokens.get(currentTokenIndex);
+            while (isOperatorCharacter(currentToken)) {
+                // System.out.println("isOperatorCharacter");
+                currentTokenIndex++;
+                currentToken = tokens.get(currentTokenIndex);
+            }
+            // System.out.println("result: "+(currentTokenIndex - 1));
+            return currentTokenIndex - 1;
+        } else {
+            // System.out.println("result: "+(-1));
+            return -1;
+        }
+    }
+
+    /**
+     * "If an operator has whitespace around both sides or around neither side, it is treated as a binary operator. As an
+     * example, the + operator in a+b and a + b is treated as a binary operator."
+     */
+    public static boolean isBinaryOp(TokenStream tokens) {
+        SwiftSupport.fillUp(tokens);
+        int stop = getLastOpTokenIndex(tokens);
+        if (stop == -1)
+            return false;
+
+        int start = tokens.index();
+        Token currentToken = tokens.get(start);
+        Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens
+        Token nextToken = tokens.get(stop + 1);
+        boolean prevIsWS = isLeftOperatorWS(prevToken);
+        boolean nextIsWS = isRightOperatorWS(nextToken);
+        // String text = tokens.getText(Interval.of(start, stop));
+        // System.out.println("isBinaryOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result);
+        if (prevIsWS) {
+            return nextIsWS;
+        } else {
+            if (currentToken.getType() == Swift5Lexer.BANG || currentToken.getType() == Swift5Lexer.QUESTION) {
+                return false;
+            } else {
+                if (!nextIsWS)
+                    return nextToken.getType() != Swift5Lexer.DOT;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * "If an operator has whitespace on the left side only, it is treated as a prefix unary operator. As an example, the ++
+     * operator in a ++b is treated as a prefix unary operator."
+     */
+    public static boolean isPrefixOp(TokenStream tokens) {
+        SwiftSupport.fillUp(tokens);
+        int stop = getLastOpTokenIndex(tokens);
+        if (stop == -1)
+            return false;
+
+        int start = tokens.index();
+        Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens
+        Token nextToken = tokens.get(stop + 1);
+        boolean prevIsWS = isLeftOperatorWS(prevToken);
+        boolean nextIsWS = isRightOperatorWS(nextToken);
+        // String text = tokens.getText(Interval.of(start, stop));
+        // System.out.println("isPrefixOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result);
+        return prevIsWS && !nextIsWS;
+    }
+
+    /**
+     * "If an operator has whitespace on the right side only, it is treated as a postfix unary operator. As an example, the
+     * ++ operator in a++ b is treated as a postfix unary operator."
+     * <p>
+     * "If an operator has no whitespace on the left but is followed immediately by a dot (.), it is treated as a postfix
+     * unary operator. As an example, the ++ operator in a++.b is treated as a postfix unary operator (a++ .b rather than a
+     * ++ .b)."
+     */
+    public static boolean isPostfixOp(TokenStream tokens) {
+        SwiftSupport.fillUp(tokens);
+        int stop = getLastOpTokenIndex(tokens);
+        if (stop == -1)
+            return false;
+
+        int start = tokens.index();
+        Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens
+        Token nextToken = tokens.get(stop + 1);
+        boolean prevIsWS = isLeftOperatorWS(prevToken);
+        boolean nextIsWS = isRightOperatorWS(nextToken);
+        // String text = tokens.getText(Interval.of(start, stop));
+        // System.out.println("isPostfixOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result);
+        return !prevIsWS && nextIsWS || !prevIsWS && nextToken.getType() == Swift5Parser.DOT;
+    }
+
+    public static boolean isOperator(TokenStream tokens, String op) {
+        SwiftSupport.fillUp(tokens);
+        int stop = getLastOpTokenIndex(tokens);
+        if (stop == -1)
+            return false;
+
+        int start = tokens.index();
+        String text = tokens.getText(Interval.of(start, stop));
+        // System.out.println("text: '"+text+"', op: '"+op+"', text.equals(op): '"+text.equals(op)+"'");
+
+        // for (int i = 0; i <= stop; i++) {
+        // System.out.println("token["+i+"] = '"+tokens.getText(Interval.of(i, i))+"'");
+        // }
+
+        return text.equals(op);
+    }
+
+    public static boolean isLeftOperatorWS(Token t) {
+        return leftWS.get(t.getType());
+    }
+
+    public static boolean isRightOperatorWS(Token t) {
+        return rightWS.get(t.getType()) || t.getType() == Token.EOF;
+    }
+
+    public static boolean isSeparatedStatement(TokenStream tokens, int indexOfPreviousStatement) {
+        SwiftSupport.fillUp(tokens);
+        // System.out.println("------");
+        // System.out.println("indexOfPreviousStatement: " + indexOfPreviousStatement);
+
+        int indexFrom = indexOfPreviousStatement - 1;
+        int indexTo = tokens.index() - 1;
+
+        if (indexFrom >= 0) {
+            // Stupid check for new line and semicolon, can be optimized
+            while (indexFrom >= 0 && tokens.get(indexFrom).getChannel() == Token.HIDDEN_CHANNEL) {
+                indexFrom--;
+            }
+
+            // System.out.println("from: '" + tokens.getText(Interval.of(indexFrom, indexFrom))+"', "+tokens.get(indexFrom));
+            // System.out.println("to: '" + tokens.getText(Interval.of(indexTo, indexTo))+"', "+tokens.get(indexTo));
+            // System.out.println("in_between: '" + tokens.getText(Interval.of(indexFrom, indexTo)));
+
+            // for (int i = previousIndex; i < currentIndex; i++)
+            for (int i = indexTo; i >= indexFrom; i--) {
+                String t = tokens.get(i).getText();
+                if (t.contains("\n") || t.contains(";")) {
+                    return true;
+                }
+            }
+            return false;
+            // String text = tokens.getText(Interval.of(indexFrom, indexTo));
+            // return text.contains("\n") || text.contains(";");
+        } else {
+            return true;
+        }
+    }
+
+    public static void fillUp(TokenStream tokens) {
+        for (int jj = 1;; ++jj) {
+            int t = tokens.LA(jj);
+            if (t == -1)
+                break;
+        }
+    }
+}

+ 156 - 0
languages/swift/src/test/java/de/jplag/swift/SwiftFrontendTest.java

@@ -0,0 +1,156 @@
+package de.jplag.swift;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+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.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.ParsingException;
+import de.jplag.SharedTokenType;
+import de.jplag.Token;
+import de.jplag.TokenPrinter;
+
+class SwiftFrontendTest {
+
+    /**
+     * 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.swift";
+
+    /**
+     * Regular expression that describes lines consisting only of whitespace and optionally a line comment.
+     */
+    private static final String EMPTY_OR_SINGLE_LINE_COMMENT = "\\s*(//.*|/\\*.*\\*/)?";
+
+    /**
+     * Lines containing this tag are expected to not contain any token.
+     */
+    private static final String NO_TOKEN_ANNOTATION = "//NO TOKEN";
+
+    /**
+     * 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("Swift frontend test");
+    private final String[] testFiles = new String[] {COMPLETE_TEST_FILE};
+    private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "swift").toFile();
+    private Language language;
+
+    @BeforeEach
+    void setup() {
+        language = new Language();
+    }
+
+    @Test
+    void parseTestFiles() throws ParsingException {
+        for (String fileName : testFiles) {
+            List<Token> tokens = language.parse(Set.of(new File(testFileLocation, fileName)));
+            String output = TokenPrinter.printTokens(tokens, testFileLocation, Optional.empty());
+            logger.info(output);
+
+            testSourceCoverage(fileName, tokens);
+            if (fileName.equals(COMPLETE_TEST_FILE)) {
+                testTokenCoverage(tokens, fileName);
+            }
+
+        }
+    }
+
+    /**
+     * 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 list of Tokens generated from the sample
+     */
+    private void testSourceCoverage(String fileName, List<Token> tokens) {
+        File testFile = new File(testFileLocation, fileName);
+
+        try {
+            List<String> lines = Files.readAllLines(testFile.toPath());
+
+            // All lines that contain code
+            var codeLines = getCodeLines(lines);
+            // All lines that contain token
+            var tokenLines = tokens.stream().mapToInt(Token::getLine).filter(line -> line != Token.NO_VALUE).distinct().toArray();
+
+            if (codeLines.length > tokenLines.length) {
+                var diffLine = IntStream.range(0, codeLines.length)
+                        .dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst();
+                assertTrue(diffLine.isEmpty(),
+                        "Line %d of file '%s' is not represented in the token list.".formatted(codeLines[diffLine.getAsInt()], fileName));
+            }
+            assertArrayEquals(codeLines, tokenLines);
+        } catch (IOException exception) {
+            logger.info("Error while reading test file %s".formatted(fileName), exception);
+            fail();
+        }
+    }
+
+    /**
+     * 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 int[] 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) || line.contains(NO_TOKEN_ANNOTATION)) {
+                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.toArray();
+
+    }
+
+    /**
+     * 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(List<Token> tokens, String fileName) {
+        var annotatedTokens = tokens.stream().map(Token::getType).collect(Collectors.toSet());
+        assertTrue(annotatedTokens.contains(SharedTokenType.FILE_END));
+        var annotatedSwiftTokens = annotatedTokens.stream().filter(SwiftTokenType.class::isInstance).collect(Collectors.toSet());
+        var allSwiftTokens = SwiftTokenType.values();
+        var missingSwiftTokens = Arrays.stream(allSwiftTokens).filter(token -> !annotatedSwiftTokens.contains(token)).toList();
+        assertTrue(missingSwiftTokens.isEmpty(), "The following Swift tokens are missing in the code example '%s':\n".formatted(fileName)
+                + String.join("\n", missingSwiftTokens.stream().map(SwiftTokenType::getDescription).toList()));
+    }
+
+}

+ 139 - 0
languages/swift/src/test/resources/de/jplag/swift/Complete.swift

@@ -0,0 +1,139 @@
+import Foundation
+
+private let fileConstant = 0
+
+public class MyClass {
+    public static let classConstant = 0.0
+    
+    private let x: Int
+
+    public var name: String = "" {
+        willSet {
+            precondition(!name.isEmpty, "name must not be empty")
+        }
+        didSet {
+            nameLength = name.count
+        }
+    } //NO TOKEN
+
+    public var optionalName: String? {
+        didSet {
+            nameLength = name.count
+        }
+    } //NO TOKEN
+
+    private var nameLength = 0
+    private let a = "A"
+
+    public init(name: String) {
+        self.name = name
+        self.x = 0
+    }
+
+    func computeIteratively(max: Int) -> Int {
+        var sum = 0
+        for i in x..<max {
+            if i == 0 {
+                continue
+            }
+            sum += i
+        }
+
+        var i = x
+        while i < max {
+            sum -= i
+        }
+
+        i = x
+        repeat {
+            sum *= i
+        } while i < max
+
+        switch max {
+        case 0:
+            sum = 0
+        case 1:
+            break
+        case 2:
+            fallthrough
+        default:
+            sum /= 2
+        }
+
+        sum += (x..<max).reduce(0, +)
+        return sum
+    }
+
+    func textLength() -> (String) -> Int {
+        let closure: (String) -> Int = { text in
+            return text.count
+        }
+        return closure
+    }
+}
+
+private struct MyStruct {
+    var fullName: String
+    var nameComponents: [String] { fullName.components(separatedBy: " ") }
+    var settableNameComponents: [String] {
+        get { nameComponents }
+        set { fullName = newValue.joined(separator: " ") }
+    } //NO TOKEN
+
+    func hasMultipleComponents() -> Bool {
+        defer {
+            print("later")
+        }
+        if nameComponents.count > 1 {
+            return true
+        } else if settableNameComponents.count > 1 {
+            return true
+        } else {
+            return false
+        }
+    }
+}
+
+enum MyEnum: String {
+    case one = "ONE"
+
+    var value: Int {
+        switch self {
+        case .one: return 1
+        }
+    }
+
+    func throwAny() throws {
+        throw NSError(domain: "error", code: -1)
+    }
+
+    func catchSome() throws {
+        let _ = try? throwAny()
+        do {
+            try throwAny()
+        }
+        catch let error {
+            print(error)
+        }
+        let _ = try! throwAny()
+        do {
+            try throwAny()
+        }
+    }
+}
+
+enum ListEnum {
+    case a, b, c
+}
+
+indirect enum Tree<T> {
+    case leaf(value: T)
+    case node(value: T, children: [Tree])
+}
+
+protocol MyProtocol {
+    var property: String { get set }
+
+    init(property: String)
+    func doSomething(_ x: Int, y: Int)
+}

+ 2 - 0
languages/text/src/main/java/de/jplag/text/ParserAdapter.java

@@ -15,6 +15,7 @@ import de.jplag.Token;
 import edu.stanford.nlp.ling.CoreLabel;
 import edu.stanford.nlp.pipeline.CoreDocument;
 import edu.stanford.nlp.pipeline.StanfordCoreNLP;
+import edu.stanford.nlp.util.logging.RedwoodConfiguration;
 
 public class ParserAdapter extends AbstractParser {
 
@@ -33,6 +34,7 @@ public class ParserAdapter extends AbstractParser {
     private int currentLineBreakIndex;
 
     public ParserAdapter() {
+        RedwoodConfiguration.errorLevel().apply();
         Properties properties = new Properties();
         properties.put(ANNOTATORS_KEY, ANNOTATORS_VALUE);
         this.pipeline = new StanfordCoreNLP(properties);

+ 1 - 0
report-viewer/package.json

@@ -4,6 +4,7 @@
   "private": true,
   "scripts": {
     "serve": "vue-cli-service serve",
+    "dev": "vue-cli-service serve",
     "build": "vue-cli-service build",
     "lint": "vue-cli-service lint"
   },

+ 1 - 1
report-viewer/src/components/FilesContainer.vue

@@ -58,7 +58,7 @@ export default defineComponent({
      * Matche of submission.
      */
     matches: {
-     type: Map<string,MatchInSingleFile>,
+     type: Map<string,MatchInSingleFile[]>,
       required: true,
     },
   },

+ 5 - 0
report-viewer/src/model/Version.ts

@@ -0,0 +1,5 @@
+export type Version = {
+    major: number,
+    minor: number,
+    patch: number
+}

+ 13 - 0
report-viewer/src/model/factories/ComparisonFactory.ts

@@ -21,6 +21,13 @@ export class ComparisonFactory {
     );
 
     const matches = json.matches as Array<Record<string, unknown>>;
+    
+    // TODO: Quick fix for issue #658, requires deeper insights to be resolved completely
+    matches.forEach((match: Record<string, unknown>) => {
+      match["file1"] = this.removePathFromFileName(match["file1"] as string ?? "");
+      match["file2"] = this.removePathFromFileName(match["file2"] as string ?? "");
+    })
+
     const colors = this.generateColorsForMatches(matches.length);
     const coloredMatches = matches.map((match, index) =>
       this.mapMatch(match, colors[index])
@@ -71,9 +78,11 @@ export class ComparisonFactory {
     matches.forEach((val) => {
       const name =
         index === 1 ? (val.firstFile as string) : (val.secondFile as string);
+
       if (!acc.get(name)) {
         acc.set(name, []);
       }
+
       if (index === 1) {
         const newVal: MatchInSingleFile = {
           start: val.startInFirst as number,
@@ -99,6 +108,10 @@ export class ComparisonFactory {
     return acc;
   }
 
+  private static removePathFromFileName(filePath: string): string {
+    return  filePath.substring(filePath.lastIndexOf("\\") + 1).substring(filePath.lastIndexOf("/") + 1);
+  }
+
   private static generateColorsForMatches(num: number): Array<string> {
     const colors = [];
     const hueDelta = Math.trunc(360 / num);

+ 21 - 0
report-viewer/src/model/factories/OverviewFactory.ts

@@ -3,10 +3,23 @@ import { Metric } from "../Metric";
 import { ComparisonListElement } from "../ComparisonListElement";
 import { Cluster } from "@/model/Cluster";
 import store from "@/store/store";
+import { Version } from "../Version";
 
 export class OverviewFactory {
 
+  // TODO: Find a better more visible position to store this
+  static reportViewerVersion: Version = {major: 4, minor: 0, patch: 0};
+
   static getOverview(json: Record<string, unknown>): Overview {
+    const versionField = json.jplag_version as Record<string, number>;
+    const jplagVersion: Version = {
+      major: versionField.major,
+      minor: versionField.minor,
+      patch: versionField.patch
+    }
+
+    OverviewFactory.compareVersions(jplagVersion, this.reportViewerVersion);
+
     const submissionFolder = json.submission_folder_path as Array<string>;
     const baseCodeFolder = "";
     const language = json.language as string;
@@ -68,6 +81,14 @@ export class OverviewFactory {
     );
   }
 
+  static compareVersions(jsonVersion: Version, reportViewerVersion: Version) {
+    if(jsonVersion.major !== reportViewerVersion.major ||
+       jsonVersion.minor !== reportViewerVersion.minor ||
+       jsonVersion.patch !== reportViewerVersion.patch) {
+        console.warn("The result's version tag does not fit the report viewer's version. Trying to read it anyhow but be careful.")
+       }
+  }
+
   private static  saveSubmissionsToComparisonNameMap(json: Record<string, unknown>){
     const submissionIdsToComparisonName =
     json.submission_ids_to_comparison_file_name as Map<

+ 1 - 0
report-viewer/vue.config.js

@@ -11,5 +11,6 @@ module.exports = defineConfig({
         chunks: "all",
       },
     },
+    devtool: 'source-map'
   },
 });