Просмотр исходного кода

Initial commit for Rust frontend

Robin Maisch 4 лет назад
Родитель
Сommit
ccc04b477c

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

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

+ 18 - 0
jplag.frontend.rust/src/main/antlr4/de/jplag/rust/grammar/README.md

@@ -0,0 +1,18 @@
+# Rust ANTLR 4 grammar
+
+This grammar is based on official language reference.
+
+Licensed under MIT
+
+Entry rule is `crate`.
+
+Last updated for rust v1.60.0
+
+## Maven build
+
+Install the parser into the local Maven repository with `mvn install`.
+
+## Known limitation
+
+- Only v2018+ stable feature is implemented.
+- Checks about isolated `\r` is not implemented. 

+ 341 - 0
jplag.frontend.rust/src/main/antlr4/de/jplag/rust/grammar/RustLexer.g4

@@ -0,0 +1,341 @@
+/*
+Copyright (c) 2010 The Rust Project Developers
+Copyright (c) 2020-2022 Student Main
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+lexer grammar RustLexer
+   ;
+
+options
+{
+   superClass = RustLexerBase;
+}
+
+// https://doc.rust-lang.org/reference/keywords.html strict
+KW_AS: 'as';
+KW_BREAK: 'break';
+KW_CONST: 'const';
+KW_CONTINUE: 'continue';
+KW_CRATE: 'crate';
+KW_ELSE: 'else';
+KW_ENUM: 'enum';
+KW_EXTERN: 'extern';
+KW_FALSE: 'false';
+KW_FN: 'fn';
+KW_FOR: 'for';
+KW_IF: 'if';
+KW_IMPL: 'impl';
+KW_IN: 'in';
+KW_LET: 'let';
+KW_LOOP: 'loop';
+KW_MATCH: 'match';
+KW_MOD: 'mod';
+KW_MOVE: 'move';
+KW_MUT: 'mut';
+KW_PUB: 'pub';
+KW_REF: 'ref';
+KW_RETURN: 'return';
+KW_SELFVALUE: 'self';
+KW_SELFTYPE: 'Self';
+KW_STATIC: 'static';
+KW_STRUCT: 'struct';
+KW_SUPER: 'super';
+KW_TRAIT: 'trait';
+KW_TRUE: 'true';
+KW_TYPE: 'type';
+KW_UNSAFE: 'unsafe';
+KW_USE: 'use';
+KW_WHERE: 'where';
+KW_WHILE: 'while';
+
+// 2018+
+KW_ASYNC: 'async';
+KW_AWAIT: 'await';
+KW_DYN: 'dyn';
+
+// reserved
+KW_ABSTRACT: 'abstract';
+KW_BECOME: 'become';
+KW_BOX: 'box';
+KW_DO: 'do';
+KW_FINAL: 'final';
+KW_MACRO: 'macro';
+KW_OVERRIDE: 'override';
+KW_PRIV: 'priv';
+KW_TYPEOF: 'typeof';
+KW_UNSIZED: 'unsized';
+KW_VIRTUAL: 'virtual';
+KW_YIELD: 'yield';
+
+// reserved 2018+
+KW_TRY: 'try';
+
+// weak
+KW_UNION: 'union';
+KW_STATICLIFETIME: '\'static';
+
+KW_MACRORULES: 'macro_rules';
+KW_UNDERLINELIFETIME: '\'_';
+KW_DOLLARCRATE: '$crate';
+
+// rule itself allow any identifier, but keyword has been matched before
+NON_KEYWORD_IDENTIFIER: XID_Start XID_Continue* | '_' XID_Continue+;
+
+// [\p{L}\p{Nl}\p{Other_ID_Start}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]
+fragment XID_Start
+   : [\p{L}\p{Nl}]
+   | UNICODE_OIDS
+   ;
+
+// [\p{ID_Start}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Other_ID_Continue}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]
+fragment XID_Continue
+   : XID_Start
+   | [\p{Mn}\p{Mc}\p{Nd}\p{Pc}]
+   | UNICODE_OIDC
+   ;
+
+fragment UNICODE_OIDS
+   : '\u1885'..'\u1886'
+   | '\u2118'
+   | '\u212e'
+   | '\u309b'..'\u309c'
+   ;
+
+fragment UNICODE_OIDC
+   : '\u00b7'
+   | '\u0387'
+   | '\u1369'..'\u1371'
+   | '\u19da'
+   ;
+
+RAW_IDENTIFIER: 'r#' NON_KEYWORD_IDENTIFIER;
+// comments https://doc.rust-lang.org/reference/comments.html
+LINE_COMMENT: ('//' (~[/!] | '//') ~[\r\n]* | '//') -> channel (HIDDEN);
+
+BLOCK_COMMENT
+   :
+   (
+      '/*'
+      (
+         ~[*!]
+         | '**'
+         | BLOCK_COMMENT_OR_DOC
+      )
+      (
+         BLOCK_COMMENT_OR_DOC
+         | ~[*]
+      )*? '*/'
+      | '/**/'
+      | '/***/'
+   ) -> channel (HIDDEN)
+   ;
+
+INNER_LINE_DOC: '//!' ~[\n\r]* -> channel (HIDDEN); // isolated cr
+
+INNER_BLOCK_DOC
+   : '/*!'
+   (
+      BLOCK_COMMENT_OR_DOC
+      | ~[*]
+   )*? '*/' -> channel (HIDDEN)
+   ;
+
+OUTER_LINE_DOC: '///' (~[/] ~[\n\r]*)? -> channel (HIDDEN); // isolated cr
+
+OUTER_BLOCK_DOC
+   : '/**'
+   (
+      ~[*]
+      | BLOCK_COMMENT_OR_DOC
+   )
+   (
+      BLOCK_COMMENT_OR_DOC
+      | ~[*]
+   )*? '*/' -> channel (HIDDEN)
+   ;
+
+BLOCK_COMMENT_OR_DOC
+   :
+   (
+      BLOCK_COMMENT
+      | INNER_BLOCK_DOC
+      | OUTER_BLOCK_DOC
+   ) -> channel (HIDDEN)
+   ;
+
+SHEBANG: {this.SOF()}? '\ufeff'? '#!' ~[\r\n]* -> channel(HIDDEN);
+
+//ISOLATED_CR
+// : '\r' {_input.LA(1)!='\n'}// not followed with \n ;
+
+// whitespace https://doc.rust-lang.org/reference/whitespace.html
+WHITESPACE: [\p{Zs}] -> channel(HIDDEN);
+NEWLINE: ('\r\n' | [\r\n]) -> channel(HIDDEN);
+
+// tokens char and string
+CHAR_LITERAL
+   : '\''
+   (
+      ~['\\\n\r\t]
+      | QUOTE_ESCAPE
+      | ASCII_ESCAPE
+      | UNICODE_ESCAPE
+   ) '\''
+   ;
+
+STRING_LITERAL
+   : '"'
+   (
+      ~["]
+      | QUOTE_ESCAPE
+      | ASCII_ESCAPE
+      | UNICODE_ESCAPE
+      | ESC_NEWLINE
+   )* '"'
+   ;
+
+RAW_STRING_LITERAL: 'r' RAW_STRING_CONTENT;
+
+fragment RAW_STRING_CONTENT: '#' RAW_STRING_CONTENT '#' | '"' .*? '"';
+
+BYTE_LITERAL: 'b\'' (. | QUOTE_ESCAPE | BYTE_ESCAPE) '\'';
+
+BYTE_STRING_LITERAL: 'b"' (~["] | QUOTE_ESCAPE | BYTE_ESCAPE)* '"';
+
+RAW_BYTE_STRING_LITERAL: 'br' RAW_STRING_CONTENT;
+
+fragment ASCII_ESCAPE: '\\x' OCT_DIGIT HEX_DIGIT | COMMON_ESCAPE;
+
+fragment BYTE_ESCAPE: '\\x' HEX_DIGIT HEX_DIGIT | COMMON_ESCAPE;
+
+fragment COMMON_ESCAPE: '\\' [nrt\\0];
+
+fragment UNICODE_ESCAPE
+   : '\\u{' HEX_DIGIT HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? '}'
+   ;
+
+fragment QUOTE_ESCAPE: '\\' ['"];
+
+fragment ESC_NEWLINE: '\\' '\n';
+
+// number
+
+INTEGER_LITERAL
+   :
+   (
+      DEC_LITERAL
+      | BIN_LITERAL
+      | OCT_LITERAL
+      | HEX_LITERAL
+   ) INTEGER_SUFFIX?
+   ;
+
+DEC_LITERAL: DEC_DIGIT (DEC_DIGIT | '_')*;
+
+HEX_LITERAL: '0x' '_'* HEX_DIGIT (HEX_DIGIT | '_')*;
+
+OCT_LITERAL: '0o' '_'* OCT_DIGIT (OCT_DIGIT | '_')*;
+
+BIN_LITERAL: '0b' '_'* [01] [01_]*;
+
+FLOAT_LITERAL
+   : {this.floatLiteralPossible()}? (DEC_LITERAL '.' {this.floatDotPossible()}?
+   | DEC_LITERAL
+   (
+      '.' DEC_LITERAL
+   )? FLOAT_EXPONENT? FLOAT_SUFFIX?)
+   ;
+
+fragment INTEGER_SUFFIX
+   : 'u8'
+   | 'u16'
+   | 'u32'
+   | 'u64'
+   | 'u128'
+   | 'usize'
+   | 'i8'
+   | 'i16'
+   | 'i32'
+   | 'i64'
+   | 'i128'
+   | 'isize'
+   ;
+
+fragment FLOAT_SUFFIX: 'f32' | 'f64';
+
+fragment FLOAT_EXPONENT: [eE] [+-]? '_'* DEC_LITERAL;
+
+fragment OCT_DIGIT: [0-7];
+
+fragment DEC_DIGIT: [0-9];
+
+fragment HEX_DIGIT: [0-9a-fA-F];
+
+// LIFETIME_TOKEN: '\'' IDENTIFIER_OR_KEYWORD | '\'_';
+
+LIFETIME_OR_LABEL: '\'' NON_KEYWORD_IDENTIFIER;
+
+PLUS: '+';
+MINUS: '-';
+STAR: '*';
+SLASH: '/';
+PERCENT: '%';
+CARET: '^';
+NOT: '!';
+AND: '&';
+OR: '|';
+ANDAND: '&&';
+OROR: '||';
+//SHL: '<<'; SHR: '>>'; removed to avoid confusion in type parameter
+PLUSEQ: '+=';
+MINUSEQ: '-=';
+STAREQ: '*=';
+SLASHEQ: '/=';
+PERCENTEQ: '%=';
+CARETEQ: '^=';
+ANDEQ: '&=';
+OREQ: '|=';
+SHLEQ: '<<=';
+SHREQ: '>>=';
+EQ: '=';
+EQEQ: '==';
+NE: '!=';
+GT: '>';
+LT: '<';
+GE: '>=';
+LE: '<=';
+AT: '@';
+UNDERSCORE: '_';
+DOT: '.';
+DOTDOT: '..';
+DOTDOTDOT: '...';
+DOTDOTEQ: '..=';
+COMMA: ',';
+SEMI: ';';
+COLON: ':';
+PATHSEP: '::';
+RARROW: '->';
+FATARROW: '=>';
+POUND: '#';
+DOLLAR: '$';
+QUESTION: '?';
+
+LCURLYBRACE: '{';
+RCURLYBRACE: '}';
+LSQUAREBRACKET: '[';
+RSQUAREBRACKET: ']';
+LPAREN: '(';
+RPAREN: ')';

+ 1091 - 0
jplag.frontend.rust/src/main/antlr4/de/jplag/rust/grammar/RustParser.g4

@@ -0,0 +1,1091 @@
+/*
+Copyright (c) 2010 The Rust Project Developers
+Copyright (c) 2020-2022 Student Main
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+parser grammar RustParser
+   ;
+
+options
+{
+   tokenVocab = RustLexer;
+   superClass = RustParserBase;
+}
+// entry point
+// 4
+crate
+   : innerAttribute* item* EOF
+   ;
+
+// 3
+macroInvocation
+   : simplePath '!' delimTokenTree
+   ;
+delimTokenTree
+   : '(' tokenTree* ')'
+   | '[' tokenTree* ']'
+   | '{' tokenTree* '}'
+   ;
+tokenTree
+   : tokenTreeToken+
+   | delimTokenTree
+   ;
+tokenTreeToken
+   : macroIdentifierLikeToken
+   | macroLiteralToken
+   | macroPunctuationToken
+   | macroRepOp
+   | '$'
+   ;
+
+macroInvocationSemi
+   : simplePath '!' '(' tokenTree* ')' ';'
+   | simplePath '!' '[' tokenTree* ']' ';'
+   | simplePath '!' '{' tokenTree* '}'
+   ;
+
+// 3.1
+macroRulesDefinition
+   : 'macro_rules' '!' identifier macroRulesDef
+   ;
+macroRulesDef
+   : '(' macroRules ')' ';'
+   | '[' macroRules ']' ';'
+   | '{' macroRules '}'
+   ;
+macroRules
+   : macroRule (';' macroRule)* ';'?
+   ;
+macroRule
+   : macroMatcher '=>' macroTranscriber
+   ;
+macroMatcher
+   : '(' macroMatch* ')'
+   | '[' macroMatch* ']'
+   | '{' macroMatch* '}'
+   ;
+macroMatch
+   : macroMatchToken+
+   | macroMatcher
+   | '$' (identifier | 'self') ':' macroFragSpec
+   | '$' '(' macroMatch+ ')' macroRepSep? macroRepOp
+   ;
+macroMatchToken
+   : macroIdentifierLikeToken
+   | macroLiteralToken
+   | macroPunctuationToken
+   | macroRepOp
+   ;
+macroFragSpec
+   : identifier // do validate here is wasting token
+   ;
+macroRepSep
+   : macroIdentifierLikeToken
+   | macroLiteralToken
+   | macroPunctuationToken
+   | '$'
+   ;
+macroRepOp
+   : '*'
+   | '+'
+   | '?'
+   ;
+macroTranscriber
+   : delimTokenTree
+   ;
+
+//configurationPredicate
+// : configurationOption | configurationAll | configurationAny | configurationNot ; configurationOption: identifier (
+// '=' (STRING_LITERAL | RAW_STRING_LITERAL))?; configurationAll: 'all' '(' configurationPredicateList? ')';
+// configurationAny: 'any' '(' configurationPredicateList? ')'; configurationNot: 'not' '(' configurationPredicate ')';
+
+//configurationPredicateList
+// : configurationPredicate (',' configurationPredicate)* ','? ; cfgAttribute: 'cfg' '(' configurationPredicate ')';
+// cfgAttrAttribute: 'cfg_attr' '(' configurationPredicate ',' cfgAttrs? ')'; cfgAttrs: attr (',' attr)* ','?;
+
+// 6
+item
+   : outerAttribute* (visItem | macroItem)
+   ;
+visItem
+   : visibility?
+   (
+      module
+      | externCrate
+      | useDeclaration
+      | function_
+      | typeAlias
+      | struct_
+      | enumeration
+      | union_
+      | constantItem
+      | staticItem
+      | trait_
+      | implementation
+      | externBlock
+   )
+   ;
+macroItem
+   : macroInvocationSemi
+   | macroRulesDefinition
+   ;
+
+// 6.1
+module
+   : 'unsafe'? 'mod' identifier (';' | '{' innerAttribute* item* '}')
+   ;
+
+// 6.2
+externCrate
+   : 'extern' 'crate' crateRef asClause? ';'
+   ;
+crateRef
+   : identifier
+   | 'self'
+   ;
+asClause
+   : 'as' (identifier | '_')
+   ;
+
+// 6.3
+useDeclaration
+   : 'use' useTree ';'
+   ;
+useTree
+   : (simplePath? '::')? ('*' | '{' ( useTree (',' useTree)* ','?)? '}')
+   | simplePath ('as' (identifier | '_'))?
+   ;
+
+// 6.4
+function_
+   : functionQualifiers 'fn' identifier genericParams? '(' functionParameters? ')' functionReturnType? whereClause?
+      (blockExpression | ';')
+   ;
+functionQualifiers
+   : 'const'? 'async'? 'unsafe'? ('extern' abi?)?
+   ;
+abi
+   : STRING_LITERAL
+   | RAW_STRING_LITERAL
+   ;
+functionParameters
+   : selfParam ','?
+   | (selfParam ',')? functionParam (',' functionParam)* ','?
+   ;
+selfParam
+   : outerAttribute* (shorthandSelf | typedSelf)
+   ;
+shorthandSelf
+   : ('&' lifetime?)? 'mut'? 'self'
+   ;
+typedSelf
+   : 'mut'? 'self' ':' type_
+   ;
+functionParam
+   : outerAttribute* (functionParamPattern | '...' | type_)
+   ;
+functionParamPattern
+   : pattern ':' (type_ | '...')
+   ;
+functionReturnType
+   : '->' type_
+   ;
+
+// 6.5
+typeAlias
+   : 'type' identifier genericParams? whereClause? ('=' type_)? ';'
+   ;
+
+// 6.6
+struct_
+   : structStruct
+   | tupleStruct
+   ;
+structStruct
+   : 'struct' identifier genericParams? whereClause? ('{' structFields? '}' | ';')
+   ;
+tupleStruct
+   : 'struct' identifier genericParams? '(' tupleFields? ')' whereClause? ';'
+   ;
+structFields
+   : structField (',' structField)* ','?
+   ;
+structField
+   : outerAttribute* visibility? identifier ':' type_
+   ;
+tupleFields
+   : tupleField (',' tupleField)* ','?
+   ;
+tupleField
+   : outerAttribute* visibility? type_
+   ;
+
+// 6.7
+enumeration
+   : 'enum' identifier genericParams? whereClause? '{' enumItems? '}'
+   ;
+enumItems
+   : enumItem (',' enumItem)* ','?
+   ;
+enumItem
+   : outerAttribute* visibility? identifier
+   (
+      enumItemTuple
+      | enumItemStruct
+      | enumItemDiscriminant
+   )?
+   ;
+enumItemTuple
+   : '(' tupleFields? ')'
+   ;
+enumItemStruct
+   : '{' structFields? '}'
+   ;
+enumItemDiscriminant
+   : '=' expression
+   ;
+
+// 6.8
+union_
+   : 'union' identifier genericParams? whereClause? '{' structFields '}'
+   ;
+
+// 6.9
+constantItem
+   : 'const' (identifier | '_') ':' type_ ('=' expression)? ';'
+   ;
+
+// 6.10
+staticItem
+   : 'static' 'mut'? identifier ':' type_ ('=' expression)? ';'
+   ;
+
+// 6.11
+trait_
+   : 'unsafe'? 'trait' identifier genericParams? (':' typeParamBounds?)? whereClause? '{' innerAttribute* associatedItem* '}'
+   ;
+
+// 6.12
+implementation
+   : inherentImpl
+   | traitImpl
+   ;
+inherentImpl
+   : 'impl' genericParams? type_ whereClause? '{' innerAttribute* associatedItem* '}'
+   ;
+traitImpl
+   : 'unsafe'? 'impl' genericParams? '!'? typePath 'for' type_ whereClause? '{' innerAttribute* associatedItem* '}'
+   ;
+
+// 6.13
+externBlock
+   : 'unsafe'? 'extern' abi? '{' innerAttribute* externalItem* '}'
+   ;
+externalItem
+   : outerAttribute*
+   (
+      macroInvocationSemi
+      | visibility? ( staticItem | function_)
+   )
+   ;
+
+// 6.14
+genericParams
+   : '<' ((genericParam ',')* genericParam ','? )?'>'
+   ;
+genericParam
+   : outerAttribute*
+   (
+      lifetimeParam
+      | typeParam
+      | constParam
+   );
+lifetimeParam
+   : outerAttribute? LIFETIME_OR_LABEL (':' lifetimeBounds)?
+   ;
+typeParam
+   : outerAttribute? identifier (':' typeParamBounds?)? ('=' type_)?
+   ;
+constParam
+   : 'const' identifier ':' type_
+   ;
+
+whereClause
+   : 'where' (whereClauseItem ',')* whereClauseItem?
+   ;
+whereClauseItem
+   : lifetimeWhereClauseItem
+   | typeBoundWhereClauseItem
+   ;
+lifetimeWhereClauseItem
+   : lifetime ':' lifetimeBounds
+   ;
+typeBoundWhereClauseItem
+   : forLifetimes? type_ ':' typeParamBounds?
+   ;
+forLifetimes
+   : 'for' genericParams
+   ;
+
+// 6.15
+associatedItem
+   : outerAttribute*
+   (
+      macroInvocationSemi
+      | visibility? ( typeAlias | constantItem | function_ )
+   )
+   ;
+
+// 7
+innerAttribute
+   : '#' '!' '[' attr ']'
+   ;
+outerAttribute
+   : '#' '[' attr ']'
+   ;
+attr
+   : simplePath attrInput?
+   ;
+attrInput
+   : delimTokenTree
+   | '=' literalExpression
+   ; // w/o suffix
+
+//metaItem
+// : simplePath ( '=' literalExpression //w | '(' metaSeq ')' )? ; metaSeq: metaItemInner (',' metaItemInner)* ','?;
+// metaItemInner: metaItem | literalExpression; // w
+
+//metaWord: identifier; metaNameValueStr: identifier '=' ( STRING_LITERAL | RAW_STRING_LITERAL); metaListPaths:
+// identifier '(' ( simplePath (',' simplePath)* ','?)? ')'; metaListIdents: identifier '(' ( identifier (','
+// identifier)* ','?)? ')'; metaListNameValueStr : identifier '(' (metaNameValueStr ( ',' metaNameValueStr)* ','?)? ')'
+// ;
+
+// 8
+statement
+   : ';'
+   | item
+   | letStatement
+   | expressionStatement
+   | macroInvocationSemi
+   ;
+
+letStatement
+   : outerAttribute* 'let' patternNoTopAlt (':' type_)? ('=' expression)? ';'
+   ;
+
+expressionStatement
+   : expression ';'
+   | expressionWithBlock ';'?
+   ;
+
+// 8.2
+expression
+   : outerAttribute+ expression                         # AttributedExpression // technical, remove left recursive
+   | literalExpression                                  # LiteralExpression_
+   | pathExpression                                     # PathExpression_
+   | expression '.' pathExprSegment '(' callParams? ')' # MethodCallExpression   // 8.2.10
+   | expression '.' identifier                          # FieldExpression  // 8.2.11
+   | expression '.' tupleIndex                          # TupleIndexingExpression   // 8.2.7
+   | expression '.' 'await'                             # AwaitExpression  // 8.2.18
+   | expression '(' callParams? ')'                     # CallExpression   // 8.2.9
+   | expression '[' expression ']'                      # IndexExpression  // 8.2.6
+   | expression '?'                                     # ErrorPropagationExpression   // 8.2.4
+   | ('&' | '&&') 'mut'? expression                     # BorrowExpression // 8.2.4
+   | '*' expression                                     # DereferenceExpression  // 8.2.4
+   | ('-' | '!') expression                             # NegationExpression  // 8.2.4
+   | expression 'as' typeNoBounds                       # TypeCastExpression  // 8.2.4
+   | expression ('*' | '/' | '%') expression            # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression ('+' | '-') expression                  # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression (shl | shr) expression                  # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression '&' expression                          # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression '^' expression                          # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression '|' expression                          # ArithmeticOrLogicalExpression   // 8.2.4
+   | expression comparisonOperator expression           # ComparisonExpression   // 8.2.4
+   | expression '&&' expression                         # LazyBooleanExpression  // 8.2.4
+   | expression '||' expression                         # LazyBooleanExpression  // 8.2.4
+   | expression '..' expression?                        # RangeExpression  // 8.2.14
+   | '..' expression?                                   # RangeExpression  // 8.2.14
+   | '..=' expression                                   # RangeExpression  // 8.2.14
+   | expression '..=' expression                        # RangeExpression  // 8.2.14
+   | expression '=' expression                          # AssignmentExpression   // 8.2.4
+   | expression compoundAssignOperator expression       # CompoundAssignmentExpression // 8.2.4
+   | 'continue' LIFETIME_OR_LABEL? expression?          # ContinueExpression  // 8.2.13
+   | 'break' LIFETIME_OR_LABEL? expression?             # BreakExpression  // 8.2.13
+   | 'return' expression?                               # ReturnExpression // 8.2.17
+   | '(' innerAttribute* expression ')'                 # GroupedExpression   // 8.2.5
+   | '[' innerAttribute* arrayElements? ']'             # ArrayExpression  // 8.2.6
+   | '(' innerAttribute* tupleElements? ')'             # TupleExpression  // 8.2.7
+   | structExpression                                   # StructExpression_   // 8.2.8
+   | enumerationVariantExpression                       # EnumerationVariantExpression_
+   | closureExpression                                  # ClosureExpression_  // 8.2.12
+   | expressionWithBlock                                # ExpressionWithBlock_
+   | macroInvocation                                    # MacroInvocationAsExpression
+   ;
+
+comparisonOperator
+   : '=='
+   | '!='
+   | '>'
+   | '<'
+   | '>='
+   | '<='
+   ;
+
+compoundAssignOperator
+   : '+='
+   | '-='
+   | '*='
+   | '/='
+   | '%='
+   | '&='
+   | '|='
+   | '^='
+   | '<<='
+   | '>>='
+   ;
+
+expressionWithBlock
+   : outerAttribute+ expressionWithBlock // technical
+   | blockExpression
+   | asyncBlockExpression
+   | unsafeBlockExpression
+   | loopExpression
+   | ifExpression
+   | ifLetExpression
+   | matchExpression
+   ;
+
+// 8.2.1
+literalExpression
+   : CHAR_LITERAL
+   | STRING_LITERAL
+   | RAW_STRING_LITERAL
+   | BYTE_LITERAL
+   | BYTE_STRING_LITERAL
+   | RAW_BYTE_STRING_LITERAL
+   | INTEGER_LITERAL
+   | FLOAT_LITERAL
+   | KW_TRUE
+   | KW_FALSE
+   ;
+
+// 8.2.2
+pathExpression
+   : pathInExpression
+   | qualifiedPathInExpression
+   ;
+
+// 8.2.3
+blockExpression
+   : '{' innerAttribute* statements? '}'
+   ;
+statements
+   : statement+ expression?
+   | expression
+   ;
+
+asyncBlockExpression
+   : 'async' 'move'? blockExpression
+   ;
+unsafeBlockExpression
+   : 'unsafe' blockExpression
+   ;
+
+// 8.2.6
+arrayElements
+   : expression (',' expression)* ','?
+   | expression ';' expression
+   ;
+
+// 8.2.7
+tupleElements
+   : (expression ',')+ expression?
+   ;
+tupleIndex
+   : INTEGER_LITERAL
+   ;
+
+// 8.2.8
+structExpression
+   : structExprStruct
+   | structExprTuple
+   | structExprUnit
+   ;
+structExprStruct
+   : pathInExpression '{' innerAttribute* (structExprFields | structBase)? '}'
+   ;
+structExprFields
+   : structExprField (',' structExprField)* (',' structBase | ','?)
+   ;
+// outerAttribute here is not in doc
+structExprField
+   : outerAttribute* (identifier | (identifier | tupleIndex) ':' expression)
+   ;
+structBase
+   : '..' expression
+   ;
+structExprTuple
+   : pathInExpression '(' innerAttribute* (expression ( ',' expression)* ','?)? ')'
+   ;
+structExprUnit
+   : pathInExpression
+   ;
+
+enumerationVariantExpression
+   : enumExprStruct
+   | enumExprTuple
+   | enumExprFieldless
+   ;
+enumExprStruct
+   : pathInExpression '{' enumExprFields? '}'
+   ;
+enumExprFields
+   : enumExprField (',' enumExprField)* ','?
+   ;
+enumExprField
+   : identifier
+   | (identifier | tupleIndex) ':' expression
+   ;
+enumExprTuple
+   : pathInExpression '(' (expression (',' expression)* ','?)? ')'
+   ;
+enumExprFieldless
+   : pathInExpression
+   ;
+
+// 8.2.9
+callParams
+   : expression (',' expression)* ','?
+   ;
+
+// 8.2.12
+closureExpression
+   : 'move'? ('||' | '|' closureParameters? '|')
+   (
+      expression
+      | '->' typeNoBounds blockExpression
+   )
+   ;
+closureParameters
+   : closureParam (',' closureParam)* ','?
+   ;
+closureParam
+   : outerAttribute* pattern (':' type_)?
+   ;
+
+// 8.2.13
+loopExpression
+   : loopLabel?
+   (
+      infiniteLoopExpression
+      | predicateLoopExpression
+      | predicatePatternLoopExpression
+      | iteratorLoopExpression
+   )
+   ;
+infiniteLoopExpression
+   : 'loop' blockExpression
+   ;
+predicateLoopExpression
+   : 'while' expression /*except structExpression*/ blockExpression
+   ;
+predicatePatternLoopExpression
+   : 'while' 'let' pattern '=' expression blockExpression
+   ;
+iteratorLoopExpression
+   : 'for' pattern 'in' expression blockExpression
+   ;
+loopLabel
+   : LIFETIME_OR_LABEL ':'
+   ;
+
+// 8.2.15
+ifExpression
+   : 'if' expression blockExpression
+   (
+      'else' (blockExpression | ifExpression | ifLetExpression)
+   )?
+   ;
+ifLetExpression
+   : 'if' 'let' pattern '=' expression blockExpression
+   (
+      'else' (blockExpression | ifExpression | ifLetExpression)
+   )?
+   ;
+
+// 8.2.16
+matchExpression
+   : 'match' expression '{' innerAttribute* matchArms? '}'
+   ;
+matchArms
+   : (matchArm '=>' matchArmExpression)* matchArm '=>' expression ','?
+   ;
+matchArmExpression
+   : expression ','
+   | expressionWithBlock ','?
+   ;
+matchArm
+   : outerAttribute* pattern matchArmGuard?
+   ;
+
+matchArmGuard
+   : 'if' expression
+   ;
+
+// 9
+pattern
+   : '|'? patternNoTopAlt ('|' patternNoTopAlt)*
+   ;
+
+patternNoTopAlt
+   : patternWithoutRange
+   | rangePattern
+   ;
+patternWithoutRange
+   : literalPattern
+   | identifierPattern
+   | wildcardPattern
+   | restPattern
+   | referencePattern
+   | structPattern
+   | tupleStructPattern
+   | tuplePattern
+   | groupedPattern
+   | slicePattern
+   | pathPattern
+   | macroInvocation
+   ;
+
+literalPattern
+   : KW_TRUE
+   | KW_FALSE
+   | CHAR_LITERAL
+   | BYTE_LITERAL
+   | STRING_LITERAL
+   | RAW_STRING_LITERAL
+   | BYTE_STRING_LITERAL
+   | RAW_BYTE_STRING_LITERAL
+   | '-'? INTEGER_LITERAL
+   | '-'? FLOAT_LITERAL
+   ;
+
+identifierPattern
+   : 'ref'? 'mut'? identifier ('@' pattern)?
+   ;
+wildcardPattern
+   : '_'
+   ;
+restPattern
+   : '..'
+   ;
+rangePattern
+   : rangePatternBound '..=' rangePatternBound  # InclusiveRangePattern
+   | rangePatternBound '..'                     # HalfOpenRangePattern
+   | rangePatternBound '...' rangePatternBound  # ObsoleteRangePattern
+   ;
+rangePatternBound
+   : CHAR_LITERAL
+   | BYTE_LITERAL
+   | '-'? INTEGER_LITERAL
+   | '-'? FLOAT_LITERAL
+   | pathPattern
+   ;
+referencePattern
+   : ('&' | '&&') 'mut'? patternWithoutRange
+   ;
+structPattern
+   : pathInExpression '{' structPatternElements? '}'
+   ;
+structPatternElements
+   : structPatternFields (',' structPatternEtCetera?)?
+   | structPatternEtCetera
+   ;
+structPatternFields
+   : structPatternField (',' structPatternField)*
+   ;
+structPatternField
+   : outerAttribute*
+   (
+      tupleIndex ':' pattern
+      | identifier ':' pattern
+      | 'ref'? 'mut'? identifier
+   )
+   ;
+structPatternEtCetera
+   : outerAttribute* '..'
+   ;
+tupleStructPattern
+   : pathInExpression '(' tupleStructItems? ')'
+   ;
+tupleStructItems
+   : pattern (',' pattern)* ','?
+   ;
+tuplePattern
+   : '(' tuplePatternItems? ')'
+   ;
+tuplePatternItems
+   : pattern ','
+   | restPattern
+   | pattern (',' pattern)+ ','?
+   ;
+groupedPattern
+   : '(' pattern ')'
+   ;
+slicePattern
+   : '[' slicePatternItems? ']'
+   ;
+slicePatternItems
+   : pattern (',' pattern)* ','?
+   ;
+pathPattern
+   : pathInExpression
+   | qualifiedPathInExpression
+   ;
+
+// 10.1
+type_
+   : typeNoBounds
+   | implTraitType
+   | traitObjectType
+   ;
+typeNoBounds
+   : parenthesizedType
+   | implTraitTypeOneBound
+   | traitObjectTypeOneBound
+   | typePath
+   | tupleType
+   | neverType
+   | rawPointerType
+   | referenceType
+   | arrayType
+   | sliceType
+   | inferredType
+   | qualifiedPathInType
+   | bareFunctionType
+   | macroInvocation
+   ;
+parenthesizedType
+   : '(' type_ ')'
+   ;
+
+// 10.1.4
+neverType
+   : '!'
+   ;
+
+// 10.1.5
+tupleType
+   : '(' ((type_ ',')+ type_?)? ')'
+   ;
+
+// 10.1.6
+arrayType
+   : '[' type_ ';' expression ']'
+   ;
+
+// 10.1.7
+sliceType
+   : '[' type_ ']'
+   ;
+
+// 10.1.13
+referenceType
+   : '&' lifetime? 'mut'? typeNoBounds
+   ;
+rawPointerType
+   : '*' ('mut' | 'const') typeNoBounds
+   ;
+
+// 10.1.14
+bareFunctionType
+   : forLifetimes? functionTypeQualifiers 'fn' '(' functionParametersMaybeNamedVariadic? ')' bareFunctionReturnType?
+   ;
+functionTypeQualifiers
+   : 'unsafe'? ('extern' abi?)?
+   ;
+bareFunctionReturnType
+   : '->' typeNoBounds
+   ;
+functionParametersMaybeNamedVariadic
+   : maybeNamedFunctionParameters
+   | maybeNamedFunctionParametersVariadic
+   ;
+maybeNamedFunctionParameters
+   : maybeNamedParam (',' maybeNamedParam)* ','?
+   ;
+maybeNamedParam
+   : outerAttribute* ((identifier | '_') ':')? type_
+   ;
+maybeNamedFunctionParametersVariadic
+   : (maybeNamedParam ',')* maybeNamedParam ',' outerAttribute* '...'
+   ;
+
+// 10.1.15
+traitObjectType
+   : 'dyn'? typeParamBounds
+   ;
+traitObjectTypeOneBound
+   : 'dyn'? traitBound
+   ;
+implTraitType
+   : 'impl' typeParamBounds
+   ;
+implTraitTypeOneBound
+   : 'impl' traitBound
+   ;
+
+// 10.1.18
+inferredType
+   : '_'
+   ;
+
+// 10.6
+typeParamBounds
+   : typeParamBound ('+' typeParamBound)* '+'?
+   ;
+typeParamBound
+   : lifetime
+   | traitBound
+   ;
+traitBound
+   : '?'? forLifetimes? typePath
+   | '(' '?'? forLifetimes? typePath ')'
+   ;
+lifetimeBounds
+   : (lifetime '+')* lifetime?
+   ;
+lifetime
+   : LIFETIME_OR_LABEL
+   | '\'static'
+   | '\'_'
+   ;
+
+// 12.4
+simplePath
+   : '::'? simplePathSegment ('::' simplePathSegment)*
+   ;
+simplePathSegment
+   : identifier
+   | 'super'
+   | 'self'
+   | 'crate'
+   | '$crate'
+   ;
+
+pathInExpression
+   : '::'? pathExprSegment ('::' pathExprSegment)*
+   ;
+pathExprSegment
+   : pathIdentSegment ('::' genericArgs)?
+   ;
+pathIdentSegment
+   : identifier
+   | 'super'
+   | 'self'
+   | 'Self'
+   | 'crate'
+   | '$crate'
+   ;
+
+//TODO: let x : T<_>=something;
+genericArgs
+   : '<' '>'
+   | '<' genericArgsLifetimes (',' genericArgsTypes)? (',' genericArgsBindings)? ','? '>'
+   | '<' genericArgsTypes (',' genericArgsBindings)? ','? '>'
+   | '<' (genericArg ',')* genericArg ','? '>'
+   ;
+genericArg
+   : lifetime
+   | type_
+   | genericArgsConst
+   | genericArgsBinding
+   ;
+genericArgsConst
+   : blockExpression
+   | '-'? literalExpression
+   | simplePathSegment
+   ;
+genericArgsLifetimes
+   : lifetime (',' lifetime)*
+   ;
+genericArgsTypes
+   : type_ (',' type_)*
+   ;
+genericArgsBindings
+   : genericArgsBinding (',' genericArgsBinding)*
+   ;
+genericArgsBinding
+   : identifier '=' type_
+   ;
+
+qualifiedPathInExpression
+   : qualifiedPathType ('::' pathExprSegment)+
+   ;
+qualifiedPathType
+   : '<' type_ ('as' typePath)? '>'
+   ;
+qualifiedPathInType
+   : qualifiedPathType ('::' typePathSegment)+
+   ;
+
+typePath
+   : '::'? typePathSegment ('::' typePathSegment)*
+   ;
+typePathSegment
+   : pathIdentSegment '::'? (genericArgs | typePathFn)?
+   ;
+typePathFn
+   : '(' typePathInputs? ')' ('->' type_)?
+   ;
+typePathInputs
+   : type_ (',' type_)* ','?
+   ;
+
+// 12.6
+visibility
+   : 'pub' ('(' ( 'crate' | 'self' | 'super' | 'in' simplePath) ')')?
+   ;
+
+// technical
+identifier
+   : NON_KEYWORD_IDENTIFIER
+   | RAW_IDENTIFIER
+   | 'macro_rules'
+   ;
+keyword
+   : KW_AS
+   | KW_BREAK
+   | KW_CONST
+   | KW_CONTINUE
+   | KW_CRATE
+   | KW_ELSE
+   | KW_ENUM
+   | KW_EXTERN
+   | KW_FALSE
+   | KW_FN
+   | KW_FOR
+   | KW_IF
+   | KW_IMPL
+   | KW_IN
+   | KW_LET
+   | KW_LOOP
+   | KW_MATCH
+   | KW_MOD
+   | KW_MOVE
+   | KW_MUT
+   | KW_PUB
+   | KW_REF
+   | KW_RETURN
+   | KW_SELFVALUE
+   | KW_SELFTYPE
+   | KW_STATIC
+   | KW_STRUCT
+   | KW_SUPER
+   | KW_TRAIT
+   | KW_TRUE
+   | KW_TYPE
+   | KW_UNSAFE
+   | KW_USE
+   | KW_WHERE
+   | KW_WHILE
+
+   // 2018+
+   | KW_ASYNC
+   | KW_AWAIT
+   | KW_DYN
+   // reserved
+   | KW_ABSTRACT
+   | KW_BECOME
+   | KW_BOX
+   | KW_DO
+   | KW_FINAL
+   | KW_MACRO
+   | KW_OVERRIDE
+   | KW_PRIV
+   | KW_TYPEOF
+   | KW_UNSIZED
+   | KW_VIRTUAL
+   | KW_YIELD
+   | KW_TRY
+   | KW_UNION
+   | KW_STATICLIFETIME
+   ;
+macroIdentifierLikeToken
+   : keyword
+   | identifier
+   | KW_MACRORULES
+   | KW_UNDERLINELIFETIME
+   | KW_DOLLARCRATE
+   | LIFETIME_OR_LABEL
+   ;
+macroLiteralToken
+   : literalExpression
+   ;
+// macroDelimiterToken: '{' | '}' | '[' | ']' | '(' | ')';
+macroPunctuationToken
+   : '-'
+   //| '+' | '*'
+   | '/'
+   | '%'
+   | '^'
+   | '!'
+   | '&'
+   | '|'
+   | '&&'
+   | '||'
+   // already covered by '<' and '>' in macro | shl | shr
+   | '+='
+   | '-='
+   | '*='
+   | '/='
+   | '%='
+   | '^='
+   | '&='
+   | '|='
+   | '<<='
+   | '>>='
+   | '='
+   | '=='
+   | '!='
+   | '>'
+   | '<'
+   | '>='
+   | '<='
+   | '@'
+   | '_'
+   | '.'
+   | '..'
+   | '...'
+   | '..='
+   | ','
+   | ';'
+   | ':'
+   | '::'
+   | '->'
+   | '=>'
+   | '#'
+   //| '$' | '?'
+   ;
+
+// LA can be removed, legal rust code still pass but the cost is `let c = a < < b` will pass... i hope antlr5 can add
+// some new syntax? dsl? for these stuff so i needn't write it in (at least) 5 language
+
+shl
+   : '<' {this.next('<')}? '<'
+   ;
+shr
+   : '>' {this.next('>')}? '>'
+   ;

+ 361 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/JplagRustListener.java

@@ -0,0 +1,361 @@
+package de.jplag.rust;
+
+import static de.jplag.rust.RustTokenConstants.*;
+
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.LinkedList;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.*;
+
+import de.jplag.rust.grammar.RustParser;
+import de.jplag.rust.grammar.RustParserBaseListener;
+
+public class JplagRustListener extends RustParserBaseListener implements ParseTreeListener {
+
+    private final RustParserAdapter parserAdapter;
+    private final Deque<RustBlockContext> blockContexts;
+
+    public JplagRustListener(RustParserAdapter parserAdapter) {
+        this.parserAdapter = parserAdapter;
+        this.blockContexts = new LinkedList<>();
+    }
+
+    private void transformToken(int targetType, Token token) {
+        parserAdapter.addToken(targetType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length());
+    }
+
+    private void transformToken(int targetType, Token start, Token end) {
+        parserAdapter.addToken(targetType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1);
+    }
+
+    private void enterBlockContext(RustBlockContext context) {
+        blockContexts.push(context);
+    }
+
+    private void expectAndLeave(RustBlockContext... contexts) {
+        RustBlockContext topContext = blockContexts.pop();
+        assert Arrays.stream(contexts).anyMatch(context -> context == topContext);
+    }
+
+    @Override
+    public void enterInnerAttribute(RustParser.InnerAttributeContext ctx) {
+        transformToken(INNER_ATTRIBUTE, ctx.getStart(), ctx.getStop());
+        super.enterInnerAttribute(ctx);
+    }
+
+    @Override
+    public void enterOuterAttribute(RustParser.OuterAttributeContext ctx) {
+        transformToken(OUTER_ATTRIBUTE, ctx.getStart(), ctx.getStop());
+        super.enterOuterAttribute(ctx);
+    }
+
+    @Override
+    public void enterUseDeclaration(RustParser.UseDeclarationContext ctx) {
+        transformToken(USE_DECLARATION, ctx.getStart());
+        super.enterUseDeclaration(ctx);
+    }
+
+    @Override
+    public void enterUseTree(RustParser.UseTreeContext ctx) {
+        enterBlockContext(RustBlockContext.USE_TREE);
+        super.enterUseTree(ctx);
+    }
+
+    @Override
+    public void exitUseTree(RustParser.UseTreeContext ctx) {
+        expectAndLeave(RustBlockContext.USE_TREE);
+        super.exitUseTree(ctx);
+    }
+
+    @Override
+    public void enterAttr(RustParser.AttrContext ctx) {
+        enterBlockContext(RustBlockContext.ATTRIBUTE_TREE);
+        super.enterAttr(ctx);
+    }
+
+    @Override
+    public void exitAttr(RustParser.AttrContext ctx) {
+        expectAndLeave(RustBlockContext.ATTRIBUTE_TREE);
+        super.exitAttr(ctx);
+    }
+
+    @Override
+    public void enterSimplePath(RustParser.SimplePathContext ctx) {
+        if (ctx.parent instanceof RustParser.UseTreeContext) {
+            if (ctx.parent.getChildCount() > 1 && ctx.parent.getChild(1).getText().equals("::")) {
+                // Not a leaf
+                return;
+            }
+
+            transformToken(USE_ITEM, ctx.getStart(), ctx.getStop());
+        }
+        super.enterSimplePath(ctx);
+    }
+
+    @Override
+    public void enterModule(RustParser.ModuleContext ctx) {
+        transformToken(MODULE, ctx.getStart());
+        enterBlockContext(RustBlockContext.MODULE_BODY);
+        super.enterModule(ctx);
+    }
+
+    @Override
+    public void enterStruct_(RustParser.Struct_Context ctx) {
+        transformToken(STRUCT, ctx.getStart());
+        enterBlockContext(RustBlockContext.STRUCT_BODY);
+        super.enterStruct_(ctx);
+    }
+
+    @Override
+    public void exitStruct_(RustParser.Struct_Context ctx) {
+        expectAndLeave(RustBlockContext.STRUCT_BODY);
+        super.exitStruct_(ctx);
+    }
+
+    @Override
+    public void enterUnion_(RustParser.Union_Context ctx) {
+        transformToken(UNION, ctx.getStart());
+        enterBlockContext(RustBlockContext.UNION_BODY);
+        super.enterUnion_(ctx);
+    }
+
+    @Override
+    public void exitUnion_(RustParser.Union_Context ctx) {
+        expectAndLeave(RustBlockContext.UNION_BODY);
+        super.exitUnion_(ctx);
+    }
+
+    @Override
+    public void enterTrait_(RustParser.Trait_Context ctx) {
+        transformToken(TRAIT, ctx.getStart());
+        enterBlockContext(RustBlockContext.TRAIT_BODY);
+        super.enterTrait_(ctx);
+    }
+
+    @Override
+    public void exitTrait_(RustParser.Trait_Context ctx) {
+        expectAndLeave(RustBlockContext.TRAIT_BODY);
+        super.exitTrait_(ctx);
+    }
+
+    @Override
+    public void enterImplementation(RustParser.ImplementationContext ctx) {
+        enterBlockContext(RustBlockContext.IMPL_BODY);
+        super.enterImplementation(ctx);
+    }
+
+    @Override
+    public void enterEnumeration(RustParser.EnumerationContext ctx) {
+        transformToken(ENUM, ctx.getStart());
+        enterBlockContext(RustBlockContext.ENUM_BODY);
+        super.enterEnumeration(ctx);
+    }
+
+    @Override
+    public void exitEnumeration(RustParser.EnumerationContext ctx) {
+        expectAndLeave(RustBlockContext.ENUM_BODY);
+        super.exitEnumeration(ctx);
+    }
+
+    @Override
+    public void enterMacroRulesDefinition(RustParser.MacroRulesDefinitionContext ctx) {
+        transformToken(MACRO_RULES_DEFINITION, ctx.getStart());
+        enterBlockContext(RustBlockContext.MACRO_RULES_DEFINITION_BODY);
+        super.enterMacroRulesDefinition(ctx);
+    }
+
+    @Override
+    public void exitMacroRulesDefinition(RustParser.MacroRulesDefinitionContext ctx) {
+        expectAndLeave(RustBlockContext.MACRO_RULES_DEFINITION_BODY);
+        super.exitMacroRulesDefinition(ctx);
+    }
+
+    @Override
+    public void enterMacroRule(RustParser.MacroRuleContext ctx) {
+        transformToken(MACRO_RULE, ctx.getStart());
+        enterBlockContext(RustBlockContext.MACRO_RULE_BODY);
+        super.enterMacroRule(ctx);
+    }
+
+    @Override
+    public void exitMacroRule(RustParser.MacroRuleContext ctx) {
+        expectAndLeave(RustBlockContext.MACRO_RULE_BODY);
+        super.exitMacroRule(ctx);
+    }
+
+    @Override
+    public void enterMacroInvocationSemi(RustParser.MacroInvocationSemiContext ctx) {
+        transformToken(MACRO_INVOCATION, ctx.getStart());
+        enterBlockContext(RustBlockContext.MACRO_INVOCATION_BODY);
+        super.enterMacroInvocationSemi(ctx);
+    }
+
+    @Override
+    public void exitMacroInvocationSemi(RustParser.MacroInvocationSemiContext ctx) {
+        expectAndLeave(RustBlockContext.MACRO_INVOCATION_BODY);
+        super.exitMacroInvocationSemi(ctx);
+    }
+
+    @Override
+    public void enterExternBlock(RustParser.ExternBlockContext ctx) {
+        enterBlockContext(RustBlockContext.EXTERN_BLOCK);
+        super.enterExternBlock(ctx);
+    }
+
+    @Override
+    public void exitExternBlock(RustParser.ExternBlockContext ctx) {
+        expectAndLeave(RustBlockContext.EXTERN_BLOCK);
+        super.exitExternBlock(ctx);
+    }
+
+    @Override
+    public void enterFunction_(RustParser.Function_Context ctx) {
+        Token fn = ((TerminalNodeImpl) ctx.getChild(1)).getSymbol();
+        transformToken(FUNCTION, fn);
+        enterBlockContext(RustBlockContext.FUNCTION_BODY);
+        super.enterFunction_(ctx);
+    }
+
+    @Override
+    public void exitFunction_(RustParser.Function_Context ctx) {
+        expectAndLeave(RustBlockContext.FUNCTION_BODY);
+        super.exitFunction_(ctx);
+    }
+
+    @Override
+    public void enterSelfParam(RustParser.SelfParamContext ctx) {
+        transformToken(FUNCTION_PARAMETER, ctx.getStart(), ctx.getStop());
+        super.enterSelfParam(ctx);
+    }
+
+    @Override
+    public void enterFunctionParam(RustParser.FunctionParamContext ctx) {
+        transformToken(FUNCTION_PARAMETER, ctx.getStart(), ctx.getStop());
+        super.enterFunctionParam(ctx);
+    }
+
+    @Override
+    public void enterGenericParam(RustParser.GenericParamContext ctx) {
+        transformToken(TYPE_PARAMETER, ctx.getStart(), ctx.getStop());
+        super.enterGenericParam(ctx);
+    }
+
+    @Override
+    public void enterExpressionWithBlock(RustParser.ExpressionWithBlockContext ctx) {
+        enterBlockContext(RustBlockContext.INNER_BLOCK);
+        super.enterExpressionWithBlock(ctx);
+    }
+
+    @Override
+    public void exitExpressionWithBlock(RustParser.ExpressionWithBlockContext ctx) {
+        expectAndLeave(RustBlockContext.INNER_BLOCK);
+        super.exitExpressionWithBlock(ctx);
+    }
+
+    @Override
+    public void enterCompoundAssignOperator(RustParser.CompoundAssignOperatorContext ctx) {
+        transformToken(ASSIGNMENT, ctx.getStart());
+        super.enterCompoundAssignOperator(ctx);
+    }
+
+    @Override
+    public void enterConstantItem(RustParser.ConstantItemContext ctx) {
+        transformToken(VARIABLE_DECLARATION, ctx.getStart());
+        super.enterConstantItem(ctx);
+    }
+
+    @Override
+    public void visitTerminal(TerminalNode node) {
+        final Token token = node.getSymbol();
+        switch (node.getText()) {
+            case "*" -> {
+                if (node.getParent() instanceof RustParser.UseTreeContext) {
+                    transformToken(USE_ITEM, token);
+                }
+            }
+            case "let" -> transformToken(VARIABLE_DECLARATION, token);
+            case "=" -> transformToken(ASSIGNMENT, token);
+            case "{" -> {
+                int startType = getCurrentContext().getStartType();
+                if (startType != NONE) {
+                    transformToken(startType, token);
+                }
+            }
+            case "}" -> {
+                int endType = getCurrentContext().getEndType();
+                if (endType != NONE) {
+                    transformToken(endType, token);
+                }
+            }
+            default -> {
+                // do nothing
+            }
+        }
+    }
+
+    private RustBlockContext getCurrentContext() {
+        return blockContexts.peek();
+    }
+
+    @Override
+    public void visitErrorNode(ErrorNode node) {
+
+    }
+
+    @Override
+    public void enterEveryRule(ParserRuleContext ctx) {
+
+    }
+
+    @Override
+    public void exitEveryRule(ParserRuleContext ctx) {
+
+    }
+
+    private RustParser.ExpressionContext getAttibutedSubTree(RustParser.ExpressionContext context) {
+        RustParser.ExpressionContext tree = context;
+        while (tree.getChild(0)instanceof RustParser.AttributedExpressionContext attrExpr) {
+            tree = attrExpr.children.stream().dropWhile(subTree -> subTree instanceof RustParser.OuterAttributeContext).findFirst()
+                    .map(subTree -> (RustParser.ExpressionContext) subTree).get();
+        }
+        return tree;
+    }
+
+    private enum RustBlockContext {
+        FUNCTION_BODY(FUNCTION_BODY_START, FUNCTION_BODY_END),
+        STRUCT_BODY(STRUCT_BODY_BEGIN, STRUCT_BODY_END),
+        IF_BODY(IF_BODY_START, IF_BODY_END),
+        INNER_BLOCK(INNER_BLOCK_START, INNER_BLOCK_END),
+        USE_TREE(NONE, NONE),
+        ATTRIBUTE_TREE(NONE, NONE),
+
+        TRAIT_BODY(TRAIT_BODY_START, TRAIT_BODY_END),
+        ENUM_BODY(ENUM_BODY_START, ENUM_BODY_END),
+        MACRO_RULES_DEFINITION_BODY(MACRO_RULES_DEFINITION_BODY_START, MACRO_RULES_DEFINITION_BODY_END),
+        MACRO_RULE_BODY(MACRO_RULE_BODY_START, MACRO_RULE_BODY_END),
+        MACRO_INVOCATION_BODY(MACRO_INVOCATION_BODY_START, MACRO_INVOCATION_BODY_END),
+        IMPL_BODY(IMPL_BODY_START, IMPL_BODY_END),
+        EXTERN_BLOCK(EXTERN_BLOCK_START, EXTERN_BLOCK_END),
+        MODULE_BODY(MODULE_START, MODULE_END),
+        UNION_BODY(UNION_BODY_START, UNION_BODY_END);
+
+        private final int startType;
+        private final int endType;
+
+        <T extends ParserRuleContext> RustBlockContext(int startType, int endType) {
+            this.startType = startType;
+            this.endType = endType;
+        }
+
+        public int getStartType() {
+            return startType;
+        }
+
+        public int getEndType() {
+            return endType;
+        }
+    }
+}

+ 70 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/Language.java

@@ -0,0 +1,70 @@
+package de.jplag.rust;
+
+import java.io.File;
+
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenList;
+
+public class Language implements de.jplag.Language {
+
+    public static final String[] FILE_EXTENSIONS = {".rs"};
+    public static final String NAME = "Rust frontend";
+    public static final String SHORT_NAME = "Rust";
+    public static final int MINIMUM_TOKEN_MATCH = 8;
+
+    private final RustParserAdapter parserAdapter;
+
+    public Language(ErrorConsumer consumer) {
+        this.parserAdapter = new RustParserAdapter(consumer);
+    }
+
+    @Override
+    public String[] suffixes() {
+        return FILE_EXTENSIONS;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    public int minimumTokenMatch() {
+        return MINIMUM_TOKEN_MATCH;
+    }
+
+    @Override
+    public TokenList parse(File directory, String[] files) {
+        return parserAdapter.parse(directory, files);
+    }
+
+    @Override
+    public boolean hasErrors() {
+        return parserAdapter.hasErrors();
+    }
+
+    @Override
+    public boolean supportsColumns() {
+        return true;
+    }
+
+    @Override
+    public boolean isPreformatted() {
+        return true;
+    }
+
+    @Override
+    public boolean usesIndex() {
+        return false;
+    }
+
+    @Override
+    public int numberOfTokens() {
+        return RustTokenConstants.NUMBER_DIFF_TOKENS;
+    }
+}

+ 89 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/RustParserAdapter.java

@@ -0,0 +1,89 @@
+package de.jplag.rust;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+
+import de.jplag.AbstractParser;
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenList;
+import de.jplag.rust.RustTokenConstants.*;
+import de.jplag.rust.grammar.RustLexer;
+import de.jplag.rust.grammar.RustParser;
+
+public class RustParserAdapter extends AbstractParser {
+
+    private String currentFile;
+    private TokenList tokens;
+
+    /**
+     * Creates the RustParserAdapter
+     * @param consumer the ErrorConsumer that parser errors are passed on to.
+     */
+    public RustParserAdapter(ErrorConsumer consumer) {
+        super(consumer);
+    }
+
+    /**
+     * Parsers a list of files into a single {@link TokenList}.
+     * @param directory the directory of the files.
+     * @param fileNames the file names of the files.
+     * @return a {@link TokenList} containing all tokens of all files.
+     */
+    public TokenList parse(File directory, String[] fileNames) {
+        tokens = new TokenList();
+        errors = 0;
+        for (String fileName : fileNames) {
+            if (!parseFile(directory, fileName)) {
+                errors++;
+            }
+            tokens.addToken(new RustToken(RustTokenConstants.FILE_END, fileName, -1, -1, -1));
+        }
+        return tokens;
+    }
+
+    private boolean parseFile(File directory, String fileName) {
+        File file = new File(directory, fileName);
+        try (FileInputStream inputStream = new FileInputStream(file)) {
+            currentFile = fileName;
+
+            // create a lexer, a parser and a buffer between them.
+            RustLexer lexer = new RustLexer(CharStreams.fromStream(inputStream));
+            CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+            RustParser parser = new RustParser(tokens);
+
+            // Create a tree walker and the entry context defined by the parser grammar
+            ParserRuleContext entryContext = parser.crate();
+            ParseTreeWalker treeWalker = new ParseTreeWalker();
+
+            // Walk over the parse tree:
+            for (int i = 0; i < entryContext.getChildCount(); i++) {
+                ParseTree parseTree = entryContext.getChild(i);
+                treeWalker.walk(new JplagRustListener(this), parseTree);
+            }
+        } catch (IOException exception) {
+            getErrorConsumer().addError("Parsing Error in '" + fileName + "':" + File.separator + exception);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Adds a new {@link de.jplag.Token} to the current {@link TokenList}.
+     * @param type the type of the new {@link de.jplag.Token}
+     * @param line the line of the Token in the current file
+     * @param start the start column of the Token in the line
+     * @param length the length of the Token
+     */
+    /* package-private */ void addToken(int type, int line, int start, int length) {
+        tokens.addToken(new RustToken(type, currentFile, line, start, length));
+
+    }
+}

+ 37 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/RustToken.java

@@ -0,0 +1,37 @@
+package de.jplag.rust;
+
+import static de.jplag.rust.RustTokenConstants.*;
+
+import de.jplag.Token;
+
+public class RustToken extends Token {
+    public RustToken(int type, String currentFile, int line, int start, int length) {
+        super(type, currentFile, line, start, length);
+    }
+
+    @Override
+    protected String type2string() {
+        return switch (type) {
+            case FILE_END -> "<EOF>";
+            case INNER_ATTRIBUTE -> "INNER_ATTR";
+            case OUTER_ATTRIBUTE -> "OUTER_ATTR";
+            case USE_DECLARATION -> "USE";
+            case USE_ITEM -> "USE_ITEM";
+            case STRUCT_BODY_BEGIN -> "STRUCT{";
+            case STRUCT_BODY_END -> "}STRUCT";
+            case FUNCTION -> "FUNCTION";
+            case TYPE_PARAMETER -> "<T>";
+            case FUNCTION_PARAMETER -> "PARAM";
+            case FUNCTION_BODY_START -> "FUNC{";
+            case FUNCTION_BODY_END -> "}FUNC";
+
+            case INNER_BLOCK_START -> "INNER{";
+            case INNER_BLOCK_END -> "}INNER";
+
+            case ASSIGNMENT -> "ASSIGN";
+            case VARIABLE_DECLARATION -> "VAR_DECL";
+
+            default -> "<UNKNOWN%d>".formatted(type);
+        };
+    }
+}

+ 76 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/RustTokenConstants.java

@@ -0,0 +1,76 @@
+package de.jplag.rust;
+
+import de.jplag.TokenConstants;
+
+public interface RustTokenConstants extends TokenConstants {
+    int NONE = -1;
+
+    // TOP LEVEL ELEMENTS
+
+    int INNER_ATTRIBUTE = 2;
+    int OUTER_ATTRIBUTE = 3;
+
+    int USE_DECLARATION = 4;
+    int USE_ITEM = 5;
+
+    int MODULE = 6;
+    int MODULE_START = 7;
+    int MODULE_END = 8;
+
+    int FUNCTION = 9;
+    int TYPE_PARAMETER = 10;
+    int FUNCTION_PARAMETER = 11;
+    int FUNCTION_BODY_START = 12;
+    int FUNCTION_BODY_END = 13;
+
+    int STRUCT = 14;
+    int STRUCT_BODY_BEGIN = 15;
+    int STRUCT_BODY_END = 16;
+
+    int STRUCT_FIELD = 17;
+
+    int UNION = 18;
+    int UNION_BODY_START = 19;
+    int UNION_BODY_END = 20;
+
+    int TRAIT = 21;
+    int TRAIT_BODY_START = 22;
+    int TRAIT_BODY_END = 23;
+
+    int IMPL = 24;
+    int IMPL_BODY_START = 25;
+    int IMPL_BODY_END = 26;
+
+    int ENUM = 27;
+    int ENUM_BODY_START = 28;
+    int ENUM_BODY_END = 29;
+    int ENUM_ITEM = 30;
+
+    int MACRO_RULES_DEFINITION = 31;
+    int MACRO_RULES_DEFINITION_BODY_START = 32;
+    int MACRO_RULES_DEFINITION_BODY_END = 33;
+    int MACRO_RULE = 34;
+    int MACRO_RULE_BODY_START = 35;
+    int MACRO_RULE_BODY_END = 36;
+
+    int MACRO_INVOCATION = 37;
+    int MACRO_INVOCATION_BODY_START = 38;
+    int MACRO_INVOCATION_BODY_END = 39;
+
+    int EXTERN_BLOCK = 40;
+    int EXTERN_BLOCK_START = 41;
+    int EXTERN_BLOCK_END = 42;
+
+    int IF_BODY_START = 43;
+    int IF_BODY_END = 44;
+
+    int INNER_BLOCK_START = 45;
+    int INNER_BLOCK_END = 46;
+
+    int ASSIGNMENT = 47;
+
+    int VARIABLE_DECLARATION = 48;
+
+    int NUMBER_DIFF_TOKENS = 49;
+
+}

+ 94 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/grammar/RustLexerBase.java

@@ -0,0 +1,94 @@
+package de.jplag.rust.grammar;
+
+import org.antlr.v4.runtime.*;
+
+public abstract class RustLexerBase extends Lexer {
+    public RustLexerBase(CharStream input) {
+        super(input);
+    }
+
+    Token lt1;
+    Token lt2;
+
+    @Override
+    public Token nextToken() {
+        Token next = super.nextToken();
+
+        if (next.getChannel() == Token.DEFAULT_CHANNEL) {
+            // Keep track of the last token on the default channel.
+            this.lt2 = this.lt1;
+            this.lt1 = next;
+        }
+
+        return next;
+    }
+
+    public boolean SOF() {
+        return _input.LA(-1) <= 0;
+    }
+
+    public boolean next(char expect) {
+        return _input.LA(1) == expect;
+    }
+
+    public boolean floatDotPossible() {
+        int next = _input.LA(1);
+        // only block . _ identifier after float
+        if (next == '.' || next == '_')
+            return false;
+        if (next == 'f') {
+            // 1.f32
+            if (_input.LA(2) == '3' && _input.LA(3) == '2')
+                return true;
+            // 1.f64
+            if (_input.LA(2) == '6' && _input.LA(3) == '4')
+                return true;
+            return false;
+        }
+        if (next >= 'a' && next <= 'z')
+            return false;
+        if (next >= 'A' && next <= 'Z')
+            return false;
+        return true;
+    }
+
+    public boolean floatLiteralPossible() {
+        if (this.lt1 == null || this.lt2 == null)
+            return true;
+        if (this.lt1.getType() != RustLexer.DOT)
+            return true;
+        switch (this.lt2.getType()) {
+            case RustLexer.CHAR_LITERAL:
+            case RustLexer.STRING_LITERAL:
+            case RustLexer.RAW_STRING_LITERAL:
+            case RustLexer.BYTE_LITERAL:
+            case RustLexer.BYTE_STRING_LITERAL:
+            case RustLexer.RAW_BYTE_STRING_LITERAL:
+            case RustLexer.INTEGER_LITERAL:
+            case RustLexer.DEC_LITERAL:
+            case RustLexer.HEX_LITERAL:
+            case RustLexer.OCT_LITERAL:
+            case RustLexer.BIN_LITERAL:
+
+            case RustLexer.KW_SUPER:
+            case RustLexer.KW_SELFVALUE:
+            case RustLexer.KW_SELFTYPE:
+            case RustLexer.KW_CRATE:
+            case RustLexer.KW_DOLLARCRATE:
+
+            case RustLexer.GT:
+            case RustLexer.RCURLYBRACE:
+            case RustLexer.RSQUAREBRACKET:
+            case RustLexer.RPAREN:
+
+            case RustLexer.KW_AWAIT:
+
+            case RustLexer.NON_KEYWORD_IDENTIFIER:
+            case RustLexer.RAW_IDENTIFIER:
+            case RustLexer.KW_MACRORULES:
+                return false;
+            default:
+                return true;
+        }
+    }
+}

+ 13 - 0
jplag.frontend.rust/src/main/java/de/jplag/rust/grammar/RustParserBase.java

@@ -0,0 +1,13 @@
+package de.jplag.rust.grammar;
+
+import org.antlr.v4.runtime.*;
+
+public abstract class RustParserBase extends Parser {
+    public RustParserBase(TokenStream input) {
+        super(input);
+    }
+
+    public boolean next(char expect) {
+        return _input.LA(1) == expect;
+    }
+}

+ 142 - 0
jplag.frontend.rust/src/test/java/de/jplag/rust/RustFrontendTest.java

@@ -0,0 +1,142 @@
+package de.jplag.rust;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+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.List;
+import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.Token;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+import de.jplag.TokenPrinter;
+import de.jplag.testutils.TestErrorConsumer;
+
+public class RustFrontendTest {
+
+    /**
+     * Regular expression for empty lines and single line comments.
+     */
+    private static final String RUST_EMPTY_OR_SINGLE_LINE_COMMENT = "\\s*(?://.*)?";
+    private static final String RUST_MULTILINE_COMMENT_BEGIN = "\\s*/\\*.*";
+    private static final String RUST_MULTILINE_COMMENT_END = ".*\\*/\\s*";
+
+    /**
+     * 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.rs";
+    public static final int NOT_SET = -1;
+    private static final String RUST_SHEBANG = "#!.*$";
+
+    private final Logger logger = LoggerFactory.getLogger("Rust frontend test");
+    private final String[] testFiles = new String[] {COMPLETE_TEST_FILE};
+    private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "rust").toFile();
+    private Language language;
+
+    @BeforeEach
+    void setup() {
+        TestErrorConsumer consumer = new TestErrorConsumer();
+        language = new Language(consumer);
+    }
+
+    @Test
+    void parseTestFiles() {
+        for (String fileName : testFiles) {
+            TokenList tokens = language.parse(testFileLocation, new String[] {fileName});
+            String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName));
+            logger.info(output);
+
+            testSourceCoverage(fileName, tokens);
+            if (fileName.equals(COMPLETE_TEST_FILE))
+                testTokenCoverage(tokens, fileName);
+        }
+    }
+
+    /**
+     * Confirms that the code is covered to a basic extent, i.e. each line of code contains at least one token.
+     * @param fileName a code sample file name
+     * @param tokens the TokenList generated from the sample
+     */
+    private void testSourceCoverage(String fileName, TokenList tokens) {
+        File testFile = new File(testFileLocation, fileName);
+
+        try {
+            List<String> lines = Files.readAllLines(testFile.toPath());
+            String emptyLineExpression = SINGLE_LINE_COMMENT();
+
+            // All lines that contain code
+            var codeLines = getCodeLines(lines);
+            // All lines that contain token
+            var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray();
+
+            if (codeLines.length > tokenLines.length) {
+                var diffLine = IntStream.range(0, codeLines.length)
+                        .dropWhile(lineIndex -> lineIndex < tokenLines.length && codeLines[lineIndex] == tokenLines[lineIndex]).findFirst();
+                diffLine.ifPresent(
+                        lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName)));
+            }
+            assertArrayEquals(codeLines, tokenLines);
+        } catch (IOException exception) {
+            logger.info("Error while reading test file %s".formatted(fileName), exception);
+            fail();
+        }
+    }
+
+    private int[] getCodeLines(List<String> lines) {
+        var state = new Object() {
+            boolean insideMultilineComment = false;
+
+        };
+
+        return IntStream.range(1, lines.size() + 1).sequential().filter(idx -> {
+            String line = lines.get(idx - 1);
+            if (line.matches(RUST_EMPTY_OR_SINGLE_LINE_COMMENT)) {
+                return false;
+            } else if (idx == 1 && line.matches(RUST_SHEBANG)) {
+                return false;
+            } else if (line.matches(RUST_MULTILINE_COMMENT_BEGIN)) {
+                state.insideMultilineComment = true;
+                return false;
+            } else if (line.matches(RUST_MULTILINE_COMMENT_END)) {
+                state.insideMultilineComment = false;
+                return false;
+            } else {
+                return !state.insideMultilineComment;
+            }
+        }).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(TokenList tokens, String fileName) {
+        var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().toArray();
+        // Exclude SEPARATOR_TOKEN, as it does not occur
+        var allTokens = IntStream.range(0, RustTokenConstants.NUMBER_DIFF_TOKENS).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray();
+
+        if (allTokens.length > foundTokens.length) {
+            var diffLine = IntStream.range(0, allTokens.length)
+                    .dropWhile(lineIndex -> lineIndex < foundTokens.length && allTokens[lineIndex] == foundTokens[lineIndex]).findFirst();
+            diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'."
+                    .formatted(new RustToken(allTokens[lineIdx], fileName, NOT_SET, NOT_SET, NOT_SET).type2string(), fileName)));
+        }
+        assertArrayEquals(allTokens, foundTokens);
+    }
+
+    private static String SINGLE_LINE_COMMENT() {
+        return RUST_EMPTY_OR_SINGLE_LINE_COMMENT;
+    }
+
+}

+ 1410 - 0
jplag.frontend.rust/src/test/resources/de/jplag/rust/complete.rs

@@ -0,0 +1,1410 @@
+#!/she-bang line
+// Source: https://github.com/antlr/grammars-v4/blob/7d9d9adb3c73f1775d62100766d155df8adcc4c9/rust/examples/intellijrust_test_allinone.rs
+//inner attributes
+#![crate_type = "lib"]
+#![crate_name = "rary"]
+
+fn main(){
+    #![crate_type = "lib"]
+    let y = &&& x;
+    y = &a & &b;
+    y = false == false && true
+}
+fn main1(){
+    #[foo]
+    #[bar]
+    let x = 1;
+
+    let x = #[foo] #[bar]1;
+    let _ = #[a] - #[b]-1;
+
+    #[foo]
+    #[bar]
+    {}
+}
+
+/* associated type defaults are unstable
+trait T {
+    type B;
+    type A = Self;
+}
+
+struct S;
+
+impl T for S {
+    type B = T;
+}
+*/
+
+async fn foo() {}
+async fn bar() {}
+
+trait T {
+    async fn foo();
+    async fn bar();
+}
+
+enum E {
+    #[cfg(test)] F(#[cfg(test)] i32)
+}
+
+#[empty_attr()]
+const T: i32 = 92;
+
+fn attrs_on_statements() {
+    #[cfg(test)]
+    let x = 92;
+
+    #[cfg(test)]
+    loop {}
+
+    #[cfg(test)]
+    x = 1 + 1;
+
+    S { #[foo] foo: 92 };
+}
+
+struct S<#[foo]'a, #[may_dangle] T> {}
+
+#[macro_export]
+macro_rules! give_me_struct {
+    ($name:ident) => {
+        #[allow(non_camel_case_types)]
+        struct $name;
+    }
+}
+
+#[cfg(not(test))]
+give_me_struct! {
+    hello_world
+}
+
+#[post("/", data = "<todo_form>")]
+fn string_value() {}
+
+const C: i32 = 0;
+
+#[cfg(attr(value = C))]
+fn const_value() {}
+
+#[py::class]
+fn path() {}
+
+#[cfg_attr(test, assert_instr(add_a.b))]
+fn custom_name() {}
+
+#[attr(foo::{bar, baz}, qwe)]
+fn arbitrary_token_tree() {}
+
+fn f1(#[attr1] #[attr2] pat: S) {}
+
+fn f2(#[attr] x: S) {}
+
+impl S {
+    fn f3(#[attr] self) {}
+
+    fn f4(#[attr] &self) {}
+
+    fn f5<'a>(#[attr] &mut self) {}
+
+    fn f6<'a>(#[attr] &'a self) {}
+
+    fn f7<'a>(#[attr] &'a mut self, #[attr] x: S, y: S) {}
+
+    fn f8(#[attr] self: Self) {}
+
+    fn f9(#[attr] self: S<Self>) {}
+}
+
+trait T { fn f10(#[attr] S); }
+
+extern "C" {
+    fn f11(#[attr] x: S, #[attr] ...);
+}
+
+// See stuff around `Restrictions::RESTRICTION_STMT_EXPR` in libsyntax
+
+pub fn foo(x: String) {
+    // These are not bit and, these are two statements.
+    { 1 }
+    *2;
+
+    { 1 }
+    &2;
+
+    loop {}
+    *x;
+
+    while true {}
+    &1;
+
+    loop {}
+    &mut x;
+
+    let foo = ();
+    {foo}
+    ();
+
+    // These are binary expressions
+    let _ = { 1 } * 2;
+    let _ = { 1 } & 2;
+    let _ = loop {} * 1;
+    2 & { 1 };
+
+    fn bar() {}
+    let _ = {bar}();
+}
+
+fn main3() {
+    let simple_block = {
+        123
+    };
+    /* labels on blocks are unstable
+    let block_with_label = 'block: {
+        if foo() { break 'block 1; }
+        if bar() { break 'block 2; }
+        3
+    };
+
+    match 123 {
+        1 => {},
+        2 => 'b: { break 'b; },
+        _ => {}
+    }*/
+}
+
+/// Does useful things
+/// Really useful
+fn documented_function() {
+    /// inner items can have docs too!
+    fn foo() { }
+}
+
+/// doc
+mod m {
+    //! This is module docs
+    //! It can span more the one line,
+    //! like this.
+    fn undocumented_function() {}
+
+    /// Does other things
+    fn documented_function() {}
+}
+
+/// Can mix doc comments and outer attributes
+#[cfg(test)]
+/// foo
+struct S {
+    /// Fields can have docs,
+    /// sometimes long ones.
+    field: f32
+}
+
+/// documentation
+// simple comments do not interfer with doc comments
+struct T (
+  /// Even for tuple structs!
+  i32
+);
+
+/// doc
+enum E {
+    /// doc
+    Foo,
+}
+
+enum ES {
+    /// doc
+    Foo {
+        /// field doc
+        field: usize
+    },
+}
+
+extern {
+    /// Doc
+    fn foo();
+
+    /// Doc
+    static errno: i32;
+}
+
+/// doc
+macro_rules! makro {
+    () => { };
+}
+
+////////////////////////////////
+// This is not a doc comment ///
+////////////////////////////////
+
+///
+///
+/// foo
+///
+///
+fn blanks() {}
+
+// A blank line after non-doc comment detaches it from item.
+
+// This multi-line
+// non-doc comment should be attached as well
+/// Blank lines after doc comments do not matter
+
+fn foo() {}
+
+
+/// Non-doc comments after a doc comment do not matter.
+// Like this one!
+fn bar() {}
+
+fn main4() {
+    if 1 < 2 {}
+    if let Some(x) = o {}
+    if let | Err(e) = r {}
+    if let V1(s) | V2(s) = value {}
+    if let | Cat(name) | Dog(name) | Parrot(name) = animal {}
+    // or-patterns syntax is experimental
+    // if let Ok(V1(s) | V2(s)) = value {}
+
+    while 1 < 2 {}
+    while let Some(x) = o {}
+    while let | Err(e) = r {}
+    while let V1(s) | V2(s) = value {}
+    while let | Cat(name) | Dog(name) | Parrot(name) = animal {}
+    // while let Ok(V1(s) | V2(s)) = value {}
+}
+
+/* const generics are unstable
+struct S<T, const N: i32, const M: &'static str>;
+fn foo<T, const N: i32, const M: &'static str>() {}
+fn main() { foo::<S<i32, 0, { x }>, -0, "">() }
+*/
+
+const FOO: i32 = 42;
+const _: i32 = 123;
+//simply not works
+//const NO_TYPE = 42;
+//static STATIC_NO_TYPE = 42;
+
+// Test that empty type parameter list (<>) is synonymous with
+// no type parameters at all
+
+struct S<>;
+trait T<> {}
+enum E<> { V }
+impl<> T<> for S<> {}
+impl T for E {}
+fn foo<>() {}
+fn bar() {}
+
+fn main() {
+    let _ = S;
+    let _ = S::<>;
+    let _ = E::V;
+    let _ = E::<>::V;
+    foo();
+    foo::<>();
+
+    // Test that we can supply <> to non generic things
+    bar::<>();
+    let _: i32<>;
+}
+
+fn foo() where for<> for<> T: T {}
+
+fn f() -> i32 {}
+
+fn test() -> u32 {
+
+    x :: y;         /* path-expr */
+    :: x :: y;
+    self :: x :: y;
+
+    x + y - z * 0;  /* binary */
+
+    x = y = z;      /* assignment + ; */
+
+    *x;             /* unary (+ ;) */
+    &x;
+    &mut x;
+
+    (x + y) * z;    /* parenthesized */
+
+    t = (0, 1, 2);  /* tuple */
+
+    t.a;            /* field */
+    t.0;
+    //t.0.0; //thanks god...
+
+    f.m();          /* method-invokation */
+
+    f();            /* call */
+    <T as Foo>::U::generic_method::<f64>();
+    S::<isize>::foo::<usize>();
+    let xs: Box<[()]> = Box::<[(); 0]>::new([]);
+
+    t = ();         /* unit */
+
+    [   0,          /* array */
+        1,
+        2,
+        [ 0 ; 1 ] ];
+    [];
+    [1,];
+    [1;2];
+
+    || {};          /* lambda */
+    |x| x;
+    |&x| x;
+    //box pattern syntax is experimental
+    //|box x| x;
+    //not work
+    //|x: i32| -> i32 92;
+    move |x: i32| {
+        x
+    };
+
+    |x: &mut i32| x = 92;
+
+    { }             /* block */
+
+    unsafe { 92 }
+
+    {
+        {92}.to_string()
+    }
+
+    //box 92;//box is experimental
+
+    let _ = 1 as i32 <= 1;
+    //type ascription is experimental
+    //let _ = 1: i32 <= 1;
+
+    const TEN: u32 = 10;
+    let _ = 1 as u32 + TEN;
+    //let _ = 1: u32 + TEN;
+    let _ = 1 as (i32);
+
+    //yield syntax is experimental
+    //|| { 0; yield 0; };
+
+    return (x = y)  /* return */
+            + 1
+}
+
+
+#[link(name = "objc")]
+extern {
+    fn foo(name: *const libc::c_uchar);
+    fn bar(a: i32,  ...) -> i32;
+
+    #[cfg(test)]
+    pub fn baz(b: i64, );
+
+    #[doc = "Hello"]
+    pub static X: i32;
+    //extern types are experimental
+    //pub type Y;
+}
+
+extern crate foo;
+#[macro_use] extern crate bar;
+extern crate spam as eggs;
+// should be annotated as error
+extern crate self;
+extern crate self as foo;
+
+extern fn baz() {}
+unsafe extern fn foo() {}
+unsafe extern "C" fn bar() {}
+
+
+fn add(x: i32, y: i32) -> i32 {
+    return x + y;
+}
+
+  fn mul(x: i32, y: i32) -> i32 {
+    x * y;
+}
+
+  fn id(x: i32,) -> i32 { x }
+
+  fn constant() -> i32 { 92 }
+
+  const        fn a() -> () { () }
+  const unsafe fn b() -> () { () }
+
+  fn diverging() -> ! { panic("! is a type") }
+  /*C-variadic functions are unstable
+  unsafe extern "C" fn ext_fn1(a: bool, ...) {}
+  unsafe extern "C" fn ext_fn2(a: bool, args: ...) {}
+  unsafe extern "C" fn ext_fn3(a: bool, ...,) {}
+  unsafe extern "C" fn ext_fn4(a: bool, args: ...,) {}
+  */
+
+  struct S;
+
+  trait A {
+      type B;
+  }
+
+  impl A for S {
+      type B = S;
+  }
+
+
+  trait T { }
+  trait P<X> { }
+
+
+  impl T  { }
+  impl (T) { }
+  impl T for S { }
+  // Syntactically invalid
+  //impl (T) for S { }
+
+  impl<U> P<U> { }
+  impl<U> (P<U>) { }
+  impl<U> P<U> for S { }
+  impl T for <S as A>::B { }
+
+  // Semantically invalid
+  impl (<S as A>::B) { }
+
+  impl<'a, T> Iterator for Iter<'a, T> + 'a {
+      type Item = &'a T;
+
+      foo!();
+  }
+
+  impl<T> GenVal<T> {
+      fn value(&self) -> &T {}
+      fn foo<A, B>(&mut self, a: i32, b: i32) -> &A {}
+  }
+/*specialization is unstable
+  impl<T: fmt::Display + ?Sized> ToString for T {
+      #[inline]
+      default fn to_string(&self) -> String { }
+      default fn a() {}
+      default fn b() {}
+      default const BAR: u32 = 81;
+      default type T = i32;
+      pub default fn c() {}
+      pub default const C1: i32 = 1;
+      pub default type T1 = i32;
+}
+
+default unsafe impl<T> const X for X {}
+*/
+mod m {
+    #    !    [ cfg ( test ) ]
+}
+
+fn main() {
+    {} // This should a stmt.
+    {} // And this one is an expr.
+}
+fn main() {
+    'label: while let Some(_) = Some(92) {}
+
+    let _  = loop { break 92 };
+    let _ = 'l: loop { break 'l 92 };
+
+    'll: loop {
+        break 'll;
+    }
+}
+
+//not work
+//peg! parser_definition(r#"
+//"#);
+
+macro_rules! vec {
+    ( $( $x:expr ),* ) => {
+        {
+            let mut temp_vec = Vec::new();
+            $(
+                temp_vec.push($x);
+            )*
+            temp_vec
+        }
+    };
+}
+
+macro_rules! comments {
+    () => {
+        /// doc comment
+        mod foo() {
+            /** doc comment 2 */
+            fn bar() {}
+        }
+    };
+}
+
+macro_rules! default {
+    ($ty: ty) => { /* ANYTHING */ };
+}
+
+macro_rules! foobar {
+    ($self: ident) => {  };
+}
+
+default!(String);
+
+thread_local!(static HANDLE: Handle = Handle(0));
+
+#[cfg(foo)]
+foo!();
+
+include!("path/to/rust/file.rs");
+const STR: &str = include_str!("foo.in");
+const BYTES: &[u8] = include_bytes!("data.data",);
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+std::include!("path/to/rust/file.rs");
+::std::include!("path/to/rust/file.rs");
+crate::foo! {}
+self::foo! {}
+super::foo! {}
+
+fn foo() {
+    #[cfg(foo)]
+    foo! {}
+    let a = 0; // needed to check that we parsed the call as a stmt
+
+    macro_rules! bar {
+        () => {};
+    }
+
+    let mut macro_rules = 0;
+    macro_rules += 1;
+
+    foo!() + foo!();
+
+
+    // -- vec macro ---
+    let v1 = vec![1, 2, 3];
+    let v2 = vec![1; 10];
+    let v: Vec<i32> = vec![];
+    let vv: Vec<i32> = std::vec![]; // fully qualified macro call
+    let vvv: Vec<i32> = std::vec /*comment*/ ![]; // fully qualified macro call with comment
+    vec!(Foo[]); // custom vec macro
+    // ----------------
+
+    // --- format macros ---
+    println!("{}", 92);
+    format!("{argument}", argument = "test");  // => "test"
+    format_args!("{name} {}", 1, name = 2);    // => "2 1"
+    format!["hello {}", "world!"];
+    format! {
+        "x = {}, y = {y}",
+        10, y = 30
+    }
+    panic!("division by zero");
+    unimplemented!("{} {} {}", 1, 2, 3);
+    todo!("it's too {epithet} to implement", epithet = "boring");
+    std::println!("{}", 92); // fully qualified macro call
+    std::println /*comment*/ !("{}", 92); // fully qualified macro call with comment
+    ::std::println!("{}", 92); // fully qualified macro call beginning with double colon
+    eprintln!(Foo[]); // custom format macro
+    // -------------------
+
+    // --- expr macros ---
+    /*deprecated
+    try!(bar());
+    try![bar()];
+    try! {
+        bar()
+    }*/
+    dbg!();
+    dbg!("Some text");
+    dbg!(123 + 567,);
+    std::dbg!(123); // fully qualified macro call
+    std::dbg /*comment*/ !(123); // fully qualified macro call with comment
+    dbg!(Foo[]); // custom expr macro
+    // ------------------
+
+    // --- log macros ---
+    error!();
+    debug!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
+    trace!(target: "smbc", "open_with {:?}", options);
+    log::warn!(target: "smbc", "open_with {:?}", options); // fully qualified macro call
+    log::info /*comment*/ !(target: "smbc", "open_with {:?}", options); // fully qualified macro call with comment
+    debug!(log, "debug values"; "x" => 1, "y" => -1); // custom log macro
+    // ------------------
+
+    // --- assert macros ---
+    let a = 42u32;
+    let b = 43u32;
+    assert!(a == b);
+    assert![a == b];
+    assert!{a == b};
+
+    assert_eq!(a, b, "Some text");
+    assert_ne!(a, b, "Some text");
+    assert!(a == b, "Some text");
+    assert!(a == b, "Text {} {} syntax", "with", "format");
+
+    assert!(a == b);
+    debug_assert!(a == b);
+    assert_eq!(a, b);
+    debug_assert_eq!(a, b);
+    assert_ne!(a, b);
+    debug_assert_ne!(a, b);
+    std::assert!(a == b); // fully qualified macro call
+    std::assert /*comment*/ !(a == b); // fully qualified macro call with comment
+    assert_eq!(Foo[]); // custom assert macro
+    // ---------------------
+
+    // --- concat macros
+    concat!("abc");
+    concat!("abc", "def");
+    concat!("abc", "def",);
+    std::concat!("abc", "def"); // fully qualified macro call
+    std::concat /*comment*/ !("abc", "def"); // fully qualified macro call with comment
+    concat!(Foo[]); // custom concat macro
+    // ------------------
+
+    // - env macros
+    env!("FOO");
+    env!("FOO",);
+    env!("FOO", "error message");
+    env!("FOO", "error message", );
+    std::env!("FOO"); // fully qualified macro call
+    std::env /*comment*/ !("FOO"); // fully qualified macro call with comment
+    env!(Foo[]); // custom env macro
+    // ------------------
+
+    // - asm macros
+    asm!("nop");
+    asm!("nop", "nop");
+    asm!("nop", options(pure, nomem, nostack));
+    asm!("nop", const 5, a = const 5);
+    asm!("nop", sym foo::bar, a = sym foo::bar, const 6);
+    asm!("nop", a = const A + 1);
+    asm!("nop", in(reg) x => y, out("eax") _);
+    asm!("nop", a = const 5, b = sym foo::bar, c = in(reg) _, d = out(reg) a => _);
+    std::asm!("nop"); // fully qualified macro call
+    std::asm /*comment*/ !("nop"); // fully qualified macro call with comment
+    // ------------------
+}
+fn main() {
+    match x {
+        _ => {}
+        _ => 1,
+        _ => unsafe { 1 }.to_string(),
+        _ => 92
+    };
+
+    match x {
+        | 0
+        | 1 => 0,
+        | _ => 42,
+    };
+}
+
+fn main() {
+    match () {
+        () => {}
+        () => {}
+    }
+}
+
+
+mod arith {
+
+    fn add(x: i32, y: i32) -> i32 {
+      return x + y;
+    }
+
+    fn mul(x: i32, y: i32) -> i32 {
+      x * y;
+    }
+
+}
+
+
+mod empty {
+
+}
+
+fn main() {
+    // Float literals
+    let _ = 1.0;
+    let _ = 1f32;
+    let _ = 1f64;
+    let _ = 1.0f64;
+    let _ = 1.0e92;
+    let _ = 1.0e92f32;
+    let _ = 1.;
+    let _ = 10e_6;
+    //not work
+    //let _ = 1f34;
+    //let _ = 1.0i98;
+    //shouldn't work
+    //let _ = 0.0.0;
+    let _ = 0f32.foo();
+
+    // Integer literals
+    let _ = 1234567890;
+    let _ = 1234567890i32;
+    let _ = 1_________;
+    let _ = 1_________i32;
+    let _ = 0x1234567890abcdef;
+    let _ = 0o1234567;
+    let _ = 0b10101011101010000111;
+    let _ = 0.foo();
+}
+
+fn moo() {
+    a || b || c;
+    5 | 3 == 2 || 4 | 2 | 0 == 4 || 1 | 0 == 1;
+}
+fn patterns() {
+    let S {..} = x;
+    let S {field} = x;
+    let S {field,} = x;
+    let S {field, ..} = x;
+    let T(field, ..) = x;
+    let T(.., field) = x;
+    let (x, .., y) = (1, 2, 3, 4, 5);
+    let [x, .., y] = [1, 2, 3, 4];
+    //let [ | x, .., | y] = [1, 2, 3, 4];
+    let &[x, ref y @ ..] = [1, 2, 3];
+    let [..] = [1, 2];
+
+    let ref a @ _ = value;
+
+    if let Some(x,) = Some(92) { }
+
+    let m!(x) = 92;
+
+    let <i32>::foo ... <i32>::bar = 92;
+    let Option::None = None;
+    /*or-patterns syntax is experimental
+    let Foo(x) | Bar(x) | Baz(x) = baz;
+    let | Foo(x) | Bar(x) | Baz(x) = baz;
+    let Some(Foo(x) | Bar(x) | Baz(x)) = baz;
+    //let Some(| Foo(x) | Bar(x) | Baz(x)) = baz;
+    let Some(Foo(x) | Bar(Ok(1 | 2)) | Baz(x)) = baz;
+    // https://github.com/rust-lang/rfcs/blob/master/text/2535-or-patterns.md#precedence
+    let i @ p | q = x;
+    let i @ (p | q) = x;
+    */
+    match 10 {
+        -100 => x,
+        X => x,
+        Q::T => x,
+        //exclusive range pattern syntax is experimental
+        //0..2 => x,
+        2...4 => x,
+        //V..=10 => x,
+        //W..20 => x,
+        //Y::Z..50 => x,
+        //Ok(Foo(x) | Bar(x) | Baz(x)) => x,
+        _ => x
+    };
+}
+
+fn single_bound<T: Bar>() {}
+
+fn parenthesized_bound<T: (Bar)>() {}
+
+struct QuestionBound<T: ?Sized>(Unique<T>);
+
+struct ParenthesizedQuestionBound<T: (?Sized)>(Unique<T>);
+
+fn multiple_bound<T: Bar + Baz>() {}
+
+fn parenthesized_multiple_bound<T: (Bar) + (Baz)>() {}
+
+fn lifetime_bound<'a, T:'a>() {}
+
+// ('a) syntactically invalid
+//fn parenthesized_lifetime_bound<'a, T: ('a)>() {}
+
+fn for_lifetime_bound<F>(f: F) where F: for<'a> Fn(&'a i32) {}
+
+fn parenthesized_for_lifetime_bound<F>(f: F) where F: (for<'a> Fn(&'a i32)) {}
+
+fn impl_bound() -> impl Bar {}
+
+fn parenthesized_impl_bound() -> impl (Bar) {}
+
+fn impl_multiple_bound() -> impl Bar + Baz {}
+
+fn parenthesized_impl_multiple_bound() -> impl (Bar) + (Baz) {}
+
+fn dyn_bound(b: &mut dyn Bar) {}
+
+fn parenthesized_dyn_bound(b: &mut dyn (Bar)) {}
+
+//fn dyn_multiple_bound(b: &mut dyn Bar + Baz) {}
+
+//fn parenthesized_dyn_multiple_bound(b: &mut dyn (Bar) + (Baz)) {}
+
+fn lifetime_bound_on_Fn_returning_reference<'b, F, Z: 'b>() where F: Fn() -> &'b Z + 'static {}
+//associated type bounds are unstable
+/*
+fn assoc_type_bounds1<T: Foo<Item: Bar>>(t: T) {}
+fn assoc_type_bounds2<T: Foo<Item: Bar+Baz>>(t: T) {}
+fn assoc_type_bounds3<T: Foo<Item1: Bar, Item2 = ()>>(t: T) {}
+fn assoc_type_bounds4<T: Foo<Item1 = (), Item2: Bar>>(t: T) {}
+fn assoc_type_bounds_in_args(t: &dyn Foo<Item: Bar>) {}
+*/
+fn main() {
+    let a = 1 + 2 * 3;
+    let b = *x == y;
+}
+fn main() {
+    r = 1..2;
+    r =  ..2;
+    r = 1.. ;
+    r =  .. ;
+    r = {1}..{2};
+    //r = 1...10;
+    //r = 1 ... 10;
+    //r = ... 10;
+    r = 1..=10;
+    r = 1 ..= 10;
+    r = ..= 10;
+    //r = 1..=;
+    //r = 1...;
+
+    for i in 0.. {
+        2
+    }
+}
+/*raw address of syntax is experimental
+fn main() {
+    let _ = &raw mut x;
+    let _ = &raw const x;
+    let _ = &raw;
+    let _ = &raw!();
+}*/
+/* TODO: fix << >> >>= <<= >= <=
+fn expressions() {
+    // expressions
+    1 >> 1;
+    x >>= 1;
+    x >= 1;
+    1 << 1;
+    x <<= 1;
+    x <= 1;
+
+    // generics
+    type T = Vec<Vec<_>>;
+    let x: V<_>= ();
+    let x: V<V<_>>= ();
+    x.collect::<Vec<Vec<_>>>();
+    type U = Vec<<i32 as F>::Q>;
+
+    i < <u32>::max_value();
+}*/
+
+struct S { f: i32 }
+struct S2 { foo: i32, bar: () }
+
+fn main() {
+    if if true { S {f:1}; true } else { S {f:1}; false } {
+        ()
+    } else {
+        ()
+    };
+
+    if {S {f:1}; let _ = S {f:1}; true} {()};
+
+    if { 1 } == 1 { 1; }
+    if unsafe { 0 } == 0 { 0; }
+
+    let (foo, bar) = (1, ());
+    let s2 = S2 { foo, bar };
+}
+
+struct S1;
+struct S2 {}
+struct S3 { field: f32  }
+struct S4 { field: f32, }
+struct S5 { #[foo] field: f32 }
+struct S6 { #[foo] field: f32, #[foo] field2: f32 }
+
+struct S10();
+struct S11(i32);
+struct S12(i32,);
+struct S13(i32,i32);
+struct S14(#[foo] i32);
+struct S15(#[foo] i32, #[foo] i32);
+
+#[repr(C)]
+union U {
+    i: i32,
+    f: f32,
+}
+
+fn foo() {
+    struct S1;
+    struct S2 {}
+    struct S3 { field: f32  }
+    struct S4 { field: f32, }
+
+    #[repr(C)]
+    union U {
+        i: i32,
+        f: f32,
+    }
+}
+
+trait Contains {
+    type A;
+    fn inner(&self) -> Self::A;
+    fn empty();
+    fn anon_param(i32);
+    fn self_type(x: Self, y: Vec<Self>) -> Self;
+}
+
+fn foo() {
+    trait Inner {};
+    unsafe trait UnsafeInner {};
+}
+
+trait bar<T> {
+    fn baz(&self,);
+}
+
+trait TrailingPlusIsOk: Clone+{}
+trait EmptyBoundsAreValid: {}
+
+fn main() {
+    "1".parse::<i32>()?;
+    {x}?;
+    x[y?]?;
+    x???;
+    Ok(true);
+    let question_should_bind_tighter = !x?;
+}
+fn main() {
+    a::<B<>>
+}
+type FunType = Fn(f64) -> f64;
+type FunType2 = FnOnce::(i32);
+
+type FunTypeVoid = Fn();
+
+type ColonColon = Vec::<[u8; 8]>;
+
+type Sum = Box<A + Copy>;
+
+type LifetimeSum = Box<'a + Copy>;
+
+type HrtbSum = &(for<'a> Trait1 + for<'b> Trait2);
+
+type FunSum = Box<Fn(f64, f64) -> f64 + Send + Sync>;
+type FunSum2 = Box<Fn() -> () + Send>;
+type FunRetDynTrait = Box<Fn() -> dyn Trait + Send>;
+
+type Shl = F<<i as B>::Q, T=bool>;
+type Shr = Vec<Vec<f64>>;
+
+type Path = io::Result<()>;
+
+type AssocType = Box<Iterator<Item=(Idx, T)> + 'a>;
+
+type GenericAssoc = Foo<T, U=i32>;
+
+type Trailing1 = Box<TypeA<'static,>>;
+
+type Trailing2<'a> = MyType<'a, (),>;
+
+type TrailingCommaInFn = unsafe extern "system" fn(x: i32,) -> ();
+
+fn foo<T>(xs: Vec<T>) -> impl Iterator<Item=impl FnOnce() -> T> + Clone {
+    xs.into_iter().map(|x| || x)
+}
+
+type DynTrait = dyn Trait;
+
+struct S<F>
+    where F: FnMut(&mut Self, &T) -> Result<(), <Self as Encoder>::Error>;
+
+struct EmptyWhere where {}
+
+fn bar() -> foo!() { let a: foo!() = 0 as foo!(); a }
+
+
+use self :: y :: { self   };
+use           :: { self   };
+use           :: { self , };
+use           :: {        };
+use              { y      };
+use              { y ,    };
+use              {        };
+use self  ::  y :: *;
+use self  ::  y as z;
+use self  ::  y as _;
+use self  ::  y;
+use crate  ::  y;
+
+// https://github.com/rust-lang/rfcs/blob/master/text/2128-use-nested-groups.md
+use a::{B, d::{self, *, g::H}};
+use ::{*, *};
+
+use foo::{bar, {baz, quux}};
+use {crate::foo, crate::bar, super::baz};
+
+struct S1;
+pub struct S2;
+pub(crate) struct S3;
+pub(self) struct S4;
+mod a {
+    pub (super) struct S5;
+    pub(in a) struct S6;
+    mod b {
+        pub(in super::super) struct S7;
+        // Syntactically invalid
+        //pub(a::b) struct S8;
+    }
+}
+//crate visibility modifier is experimental
+//crate struct S9;
+
+//struct S10(crate ::S1); // path `crate::S1`
+//struct S11(crate S1); // vis `crate`
+
+crate::macro1!();
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __diesel_column {
+    ($($table:ident)::*, $column_name:ident -> $Type:ty) => {
+        #[allow(non_camel_case_types, dead_code)]
+        #[derive(Debug, Clone, Copy)]
+        pub struct $column_name;
+
+        impl $crate::expression::Expression for $column_name {
+            type SqlType = $Type;
+        }
+
+        impl<DB> $crate::query_builder::QueryFragment<DB> for $column_name where
+            DB: $crate::backend::Backend,
+            <$($table)::* as QuerySource>::FromClause: QueryFragment<DB>,
+        {
+            fn to_sql(&self, out: &mut DB::QueryBuilder) -> $crate::query_builder::BuildQueryResult {
+                try!($($table)::*.from_clause().to_sql(out));
+                out.push_sql(".");
+                out.push_identifier(stringify!($column_name))
+            }
+
+            fn collect_binds(&self, _out: &mut DB::BindCollector) -> $crate::result::QueryResult<()> {
+                Ok(())
+            }
+
+            fn is_safe_to_cache_prepared(&self) -> bool {
+                true
+            }
+        }
+
+        impl_query_id!($column_name);
+
+        impl SelectableExpression<$($table)::*> for $column_name {
+        }
+
+    }
+}
+
+#[macro_export]
+macro_rules! table {
+    // Put `use` statements at the end because macro_rules! cannot figure out
+    // if `use` is an ident or not (hint: It's not)
+    (
+        use $($import:tt)::+; $($rest:tt)+
+    ) => {
+        table!($($rest)+ use $($import)::+;);
+    };
+
+    // Add the primary key if it's not present
+    (
+        $($table_name:ident).+ {$($body:tt)*}
+        $($imports:tt)*
+    ) => {
+        table! {
+            $($table_name).+ (id) {$($body)*} $($imports)*
+        }
+    };
+
+    // Add the schema name if it's not present
+    (
+        $name:ident $(($($pk:ident),+))* {$($body:tt)*}
+        $($imports:tt)*
+    ) => {
+        table! {
+            public . $name $(($($pk),+))* {$($body)*} $($imports)*
+        }
+    };
+
+    // Import `diesel::types::*` if no imports were given
+    (
+        $($table_name:ident).+ $(($($pk:ident),+))* {$($body:tt)*}
+    ) => {
+        table! {
+            $($table_name).+ $(($($pk),+))* {$($body)*}
+            use $crate::types::*;
+        }
+    };
+
+    // Terminal with single-column pk
+    (
+        $schema_name:ident . $name:ident ($pk:ident) $body:tt
+        $($imports:tt)+
+    ) => {
+        table_body! {
+            $schema_name . $name ($pk) $body $($imports)+
+        }
+    };
+
+    // Terminal with composite pk (add a trailing comma)
+    (
+        $schema_name:ident . $name:ident ($pk:ident, $($composite_pk:ident),+) $body:tt
+        $($imports:tt)+
+    ) => {
+        table_body! {
+            $schema_name . $name ($pk, $($composite_pk,)+) $body $($imports)+
+        }
+    };
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! table_body {
+    (
+        $schema_name:ident . $name:ident ($pk:ident) {
+            $($column_name:ident -> $Type:ty,)+
+        }
+        $(use $($import:tt)::+;)+
+    ) => {
+        table_body! {
+            schema_name = $schema_name,
+            table_name = $name,
+            primary_key_ty = columns::$pk,
+            primary_key_expr = columns::$pk,
+            columns = [$($column_name -> $Type,)+],
+            imports = ($($($import)::+),+),
+        }
+    };
+
+    (
+        $schema_name:ident . $name:ident ($($pk:ident,)+) {
+            $($column_name:ident -> $Type:ty,)+
+        }
+        $(use $($import:tt)::+;)+
+    ) => {
+        table_body! {
+            schema_name = $schema_name,
+            table_name = $name,
+            primary_key_ty = ($(columns::$pk,)+),
+            primary_key_expr = ($(columns::$pk,)+),
+            columns = [$($column_name -> $Type,)+],
+            imports = ($($($import)::+),+),
+        }
+    };
+
+    (
+        schema_name = $schema_name:ident,
+        table_name = $table_name:ident,
+        primary_key_ty = $primary_key_ty:ty,
+        primary_key_expr = $primary_key_expr:expr,
+        columns = [$($column_name:ident -> $column_ty:ty,)+],
+        imports = ($($($import:tt)::+),+),
+    ) => {
+        pub mod $table_name {
+            #![allow(dead_code)]
+            use $crate::{
+                QuerySource,
+                Table,
+            };
+            use $crate::associations::HasTable;
+            $(use $($import)::+;)+
+            __diesel_table_query_source_impl!(table, $schema_name, $table_name);
+
+            impl_query_id!(table);
+
+            pub mod columns {
+                use super::table;
+                use $crate::result::QueryResult;
+                $(use $($import)::+;)+
+
+                $(__diesel_column!(table, $column_name -> $column_ty);)+
+            }
+        }
+    }
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __diesel_table_query_source_impl {
+    ($table_struct:ident, public, $table_name:ident) => {
+        impl QuerySource for $table_struct {
+            type FromClause = Identifier<'static>;
+            type DefaultSelection = <Self as Table>::AllColumns;
+
+            fn from_clause(&self) -> Self::FromClause {
+                Identifier(stringify!($table_name))
+            }
+
+            fn default_selection(&self) -> Self::DefaultSelection {
+                Self::all_columns()
+            }
+        }
+    };
+
+    ($table_struct:ident, $schema_name:ident, $table_name:ident) => {
+        impl QuerySource for $table_struct {
+            type FromClause = $crate::query_builder::nodes::
+                InfixNode<'static, Identifier<'static>, Identifier<'static>>;
+            type DefaultSelection = <Self as Table>::AllColumns;
+
+            fn from_clause(&self) -> Self::FromClause {
+                $crate::query_builder::nodes::InfixNode::new(
+                    Identifier(stringify!($schema_name)),
+                    Identifier(stringify!($table_name)),
+                    ".",
+                )
+            }
+
+            fn default_selection(&self) -> Self::DefaultSelection {
+                Self::all_columns()
+            }
+        }
+    };
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! joinable {
+    ($child:ident -> $parent:ident ($source:ident)) => {
+        joinable_inner!($child::table => $parent::table : ($child::$source = $parent::table));
+        joinable_inner!($parent::table => $child::table : ($child::$source = $parent::table));
+    }
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! joinable_inner {
+    ($left_table:path => $right_table:path : ($foreign_key:path = $parent_table:path)) => {
+        joinable_inner!(
+            left_table_ty = $left_table,
+            right_table_ty = $right_table,
+            right_table_expr = $right_table,
+            foreign_key = $foreign_key,
+            primary_key_ty = <$parent_table as $crate::query_source::Table>::PrimaryKey,
+            primary_key_expr = $parent_table.primary_key(),
+        );
+    };
+
+    (
+        left_table_ty = $left_table_ty:ty,
+        right_table_ty = $right_table_ty:ty,
+        right_table_expr = $right_table_expr:expr,
+        foreign_key = $foreign_key:path,
+        primary_key_ty = $primary_key_ty:ty,
+        primary_key_expr = $primary_key_expr:expr,
+    ) => {
+        impl<JoinType> $crate::JoinTo<$right_table_ty, JoinType> for $left_table_ty {
+            type JoinClause = $crate::query_builder::nodes::Join<
+                <$left_table_ty as $crate::QuerySource>::FromClause,
+                <$right_table_ty as $crate::QuerySource>::FromClause,
+                $crate::expression::helper_types::Eq<
+                    $crate::expression::nullable::Nullable<$foreign_key>,
+                    $crate::expression::nullable::Nullable<$primary_key_ty>,
+                >,
+                JoinType,
+            >;
+        }
+    }
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! join_through {
+    ($parent:ident -> $through:ident -> $child:ident) => {
+        impl<JoinType: Copy> $crate::JoinTo<$child::table, JoinType> for $parent::table {
+            type JoinClause = <
+                <$parent::table as $crate::JoinTo<$through::table, JoinType>>::JoinClause
+                as $crate::query_builder::nodes::CombinedJoin<
+                    <$through::table as $crate::JoinTo<$child::table, JoinType>>::JoinClause,
+                >>::Output;
+
+            fn join_clause(&self, join_type: JoinType) -> Self::JoinClause {
+                use $crate::query_builder::nodes::CombinedJoin;
+                let parent_to_through = $crate::JoinTo::<$through::table, JoinType>
+                    ::join_clause(&$parent::table, join_type);
+                let through_to_child = $crate::JoinTo::<$child::table, JoinType>
+                    ::join_clause(&$through::table, join_type);
+                parent_to_through.combine_with(through_to_child)
+            }
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! debug_sql {
+    ($query:expr) => {{
+        use $crate::query_builder::{QueryFragment, QueryBuilder};
+        use $crate::query_builder::debug::DebugQueryBuilder;
+        let mut query_builder = DebugQueryBuilder::new();
+        QueryFragment::<$crate::backend::Debug>::to_sql(&$query, &mut query_builder).unwrap();
+        query_builder.finish()
+    }};
+}
+
+#[macro_export]
+macro_rules! print_sql {
+    ($query:expr) => {
+        println!("{}", &debug_sql!($query));
+    };
+}
+
+fn main() {
+    {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
+    ()
+    }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+}
+pub type T = A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<B>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;
+static i: () =
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+()
+)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+;
+
+static j:
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+i32
+)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+=
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+1
+)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+;
+
+static k:
+((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+(i32, )
+))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+=
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+1,
+)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+;
+
+static l:
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+i32,
+),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),)
+=
+(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+1,
+),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),)
+;
+
+fn main() {}

+ 1 - 0
pom.xml

@@ -50,6 +50,7 @@
         <module>jplag.frontend.java</module>
         <module>jplag.frontend.python-3</module>
         <module>jplag.frontend.rlang</module>
+        <module>jplag.frontend.rust</module>
         <module>jplag.frontend.scheme</module>
         <module>jplag.frontend.text</module>
         <module>jplag</module>