"use strict";

const { isRecognitionException, tokenMatcher } = require("chevrotain");

function defineRules($, t) {
  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassDeclaration
  $.RULE("classDeclaration", () => {
    // Spec Deviation: extracted common "{classModifier}" prefix
    //      extraction is safe because there are no other references to
    //      "normalClassDeclaration" and "enumDeclaration"
    $.MANY(() => {
      $.SUBRULE($.classModifier);
    });
    $.OR([
      { ALT: () => $.SUBRULE($.normalClassDeclaration) },
      { ALT: () => $.SUBRULE($.enumDeclaration) },
      { ALT: () => $.SUBRULE($.recordDeclaration) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-NormalClassDeclaration
  $.RULE("normalClassDeclaration", () => {
    // Spec Deviation: extracted common "{classModifier}" to "classDeclaration"
    $.CONSUME(t.Class);
    $.SUBRULE($.typeIdentifier);
    $.OPTION(() => {
      $.SUBRULE($.typeParameters);
    });
    $.OPTION2(() => {
      $.SUBRULE($.superclass);
    });
    $.OPTION3(() => {
      $.SUBRULE($.superinterfaces);
    });
    $.OPTION4(() => {
      $.SUBRULE($.classPermits);
    });
    $.SUBRULE($.classBody);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassModifier
  $.RULE("classModifier", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.annotation) },
      { ALT: () => $.CONSUME(t.Public) },
      { ALT: () => $.CONSUME(t.Protected) },
      { ALT: () => $.CONSUME(t.Private) },
      { ALT: () => $.CONSUME(t.Abstract) },
      { ALT: () => $.CONSUME(t.Static) },
      { ALT: () => $.CONSUME(t.Final) },
      { ALT: () => $.CONSUME(t.Sealed) },
      { ALT: () => $.CONSUME(t.NonSealed) },
      { ALT: () => $.CONSUME(t.Strictfp) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-TypeParameters
  $.RULE("typeParameters", () => {
    $.CONSUME(t.Less);
    $.SUBRULE($.typeParameterList);
    $.CONSUME(t.Greater);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-TypeParameterList
  $.RULE("typeParameterList", () => {
    $.SUBRULE($.typeParameter);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.typeParameter);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassExtends
  $.RULE("superclass", () => {
    $.CONSUME(t.Extends);
    $.SUBRULE($.classType);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassImplements
  $.RULE("superinterfaces", () => {
    $.CONSUME(t.Implements);
    $.SUBRULE($.interfaceTypeList);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-InterfaceTypeList
  $.RULE("interfaceTypeList", () => {
    $.SUBRULE($.interfaceType);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.interfaceType);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/preview/specs/sealed-classes-jls.html
  $.RULE("classPermits", () => {
    $.CONSUME(t.Permits);
    $.SUBRULE($.typeName);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.typeName);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassBody
  $.RULE("classBody", () => {
    $.CONSUME(t.LCurly);
    $.MANY(() => {
      $.SUBRULE($.classBodyDeclaration);
    });
    $.CONSUME(t.RCurly);
  });

  const classBodyTypes = {
    unknown: 0,
    fieldDeclaration: 1,
    methodDeclaration: 2,
    classDeclaration: 3,
    interfaceDeclaration: 4,
    semiColon: 5,
    instanceInitializer: 6,
    staticInitializer: 7,
    constructorDeclaration: 8
  };

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassBodyDeclaration
  $.RULE("classBodyDeclaration", () => {
    const nextRuleType = $.BACKTRACK_LOOKAHEAD(
      $.identifyClassBodyDeclarationType
    );

    $.OR([
      {
        GATE: () =>
          nextRuleType >= classBodyTypes.fieldDeclaration &&
          nextRuleType <= classBodyTypes.semiColon,
        ALT: () =>
          $.SUBRULE($.classMemberDeclaration, {
            ARGS: [nextRuleType]
          })
      },
      // no gate needed for the initializers because these are LL(1) rules.
      { ALT: () => $.SUBRULE($.instanceInitializer) },
      { ALT: () => $.SUBRULE($.staticInitializer) },
      {
        GATE: () =>
          tokenMatcher(nextRuleType, classBodyTypes.constructorDeclaration),
        ALT: () => $.SUBRULE($.constructorDeclaration)
      }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassMemberDeclaration
  $.RULE("classMemberDeclaration", nextRuleType => {
    $.OR([
      {
        GATE: () => nextRuleType === classBodyTypes.fieldDeclaration,
        ALT: () => $.SUBRULE($.fieldDeclaration)
      },
      {
        GATE: () => nextRuleType === classBodyTypes.methodDeclaration,
        ALT: () => $.SUBRULE($.methodDeclaration)
      },
      {
        GATE: () => nextRuleType === classBodyTypes.classDeclaration,
        ALT: () => $.SUBRULE($.classDeclaration)
      },
      {
        GATE: () => nextRuleType === classBodyTypes.interfaceDeclaration,
        ALT: () => $.SUBRULE($.interfaceDeclaration)
      },
      {
        // No GATE is needed as this is LL(1)
        ALT: () => $.CONSUME(t.Semicolon)
      }
    ]);
  });

  // // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-FieldDeclaration
  $.RULE("fieldDeclaration", () => {
    $.MANY(() => {
      $.SUBRULE($.fieldModifier);
    });
    $.SUBRULE($.unannType);
    $.SUBRULE($.variableDeclaratorList);
    $.CONSUME(t.Semicolon);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-FieldModifier
  $.RULE("fieldModifier", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.annotation) },
      { ALT: () => $.CONSUME(t.Public) },
      { ALT: () => $.CONSUME(t.Protected) },
      { ALT: () => $.CONSUME(t.Private) },
      { ALT: () => $.CONSUME(t.Static) },
      { ALT: () => $.CONSUME(t.Final) },
      { ALT: () => $.CONSUME(t.Transient) },
      { ALT: () => $.CONSUME(t.Volatile) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableDeclaratorList
  $.RULE("variableDeclaratorList", () => {
    $.SUBRULE($.variableDeclarator);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.variableDeclarator);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableDeclarator
  $.RULE("variableDeclarator", () => {
    $.SUBRULE($.variableDeclaratorId);
    $.OPTION(() => {
      $.CONSUME(t.Equals);
      $.SUBRULE($.variableInitializer);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableDeclaratorId
  $.RULE("variableDeclaratorId", () => {
    $.CONSUME(t.Identifier);
    $.OPTION(() => {
      $.SUBRULE($.dims);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableInitializer
  $.RULE("variableInitializer", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.expression) },
      { ALT: () => $.SUBRULE($.arrayInitializer) }
    ]);
  });

  // // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-UnannType
  $.RULE("unannType", () => {
    $.OR([
      // Spec Deviation: The array type "dims" suffix was extracted to this rule
      // to avoid backtracking for performance reasons.
      { ALT: () => $.SUBRULE($.unannPrimitiveTypeWithOptionalDimsSuffix) },
      { ALT: () => $.SUBRULE($.unannReferenceType) }
    ]);
  });

  $.RULE("unannPrimitiveTypeWithOptionalDimsSuffix", () => {
    $.SUBRULE($.unannPrimitiveType);
    $.OPTION({
      GATE: () => this.BACKTRACK_LOOKAHEAD($.isDims),
      DEF: () => $.SUBRULE2($.dims)
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-UnannPrimitiveType
  $.RULE("unannPrimitiveType", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.numericType) },
      { ALT: () => $.CONSUME(t.Boolean) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-UnannReferenceType
  $.RULE("unannReferenceType", () => {
    $.SUBRULE($.unannClassOrInterfaceType);
    $.OPTION({
      GATE: () => this.BACKTRACK_LOOKAHEAD($.isDims),
      DEF: () => $.SUBRULE2($.dims)
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-UnannClassType
  $.RULE("unannClassOrInterfaceType", () => {
    // Spec Deviation: The spec says: "UnannClassType  | UnannInterfaceType" but "UnannInterfaceType"
    //                 is not mentioned in the parser because it is identical to "UnannClassType"
    //                 The distinction is **semantic** not syntactic.
    $.SUBRULE($.unannClassType);
  });

  $.RULE("unannClassType", () => {
    // Spec Deviation: Refactored left recursion and alternation to iterations
    $.CONSUME(t.Identifier);
    $.OPTION(() => {
      $.SUBRULE($.typeArguments);
    });
    $.MANY2(() => {
      $.CONSUME(t.Dot);
      $.MANY3(() => {
        $.SUBRULE2($.annotation);
      });
      // TODO: Semantic Check: This Identifier cannot be "var"
      $.CONSUME2(t.Identifier);
      $.OPTION2(() => {
        $.SUBRULE2($.typeArguments);
      });
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-UnannInterfaceType
  $.RULE("unannInterfaceType", () => {
    $.SUBRULE($.unannClassType);
  });

  $.RULE("unannTypeVariable", () => {
    // TODO: Semantic Check: This Identifier cannot be "var"
    // TODO: or define as token type?
    $.CONSUME(t.Identifier);
  });

  // // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-MethodDeclaration
  $.RULE("methodDeclaration", () => {
    $.MANY(() => {
      $.SUBRULE($.methodModifier);
    });
    $.SUBRULE($.methodHeader);
    $.SUBRULE($.methodBody);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-MethodModifier
  $.RULE("methodModifier", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.annotation) },
      { ALT: () => $.CONSUME(t.Public) },
      { ALT: () => $.CONSUME(t.Protected) },
      { ALT: () => $.CONSUME(t.Private) },
      { ALT: () => $.CONSUME(t.Abstract) },
      { ALT: () => $.CONSUME(t.Static) },
      { ALT: () => $.CONSUME(t.Final) },
      { ALT: () => $.CONSUME(t.Synchronized) },
      { ALT: () => $.CONSUME(t.Native) },
      { ALT: () => $.CONSUME(t.Strictfp) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-MethodHeader
  $.RULE("methodHeader", () => {
    // Spec Deviation: extracted common prefix from both alternatives
    $.OPTION(() => {
      $.SUBRULE($.typeParameters);
      $.MANY(() => {
        $.SUBRULE($.annotation);
      });
    });
    $.SUBRULE($.result);
    $.SUBRULE($.methodDeclarator);
    $.OPTION2(() => {
      $.SUBRULE($.throws);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-Result
  $.RULE("result", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.unannType) },
      { ALT: () => $.CONSUME(t.Void) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-MethodDeclarator
  $.RULE("methodDeclarator", () => {
    $.CONSUME(t.Identifier);
    $.CONSUME(t.LBrace);
    $.OPTION(() => {
      $.SUBRULE($.formalParameterList);
    });
    $.CONSUME(t.RBrace);
    $.OPTION2(() => {
      $.SUBRULE($.dims);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ReceiverParameter
  $.RULE("receiverParameter", () => {
    $.MANY(() => {
      $.SUBRULE($.annotation);
    });
    $.SUBRULE($.unannType);
    $.OPTION(() => {
      $.CONSUME(t.Identifier);
      $.CONSUME(t.Dot);
    });
    $.CONSUME(t.This);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-FormalParameterList
  $.RULE("formalParameterList", () => {
    $.SUBRULE($.formalParameter);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.formalParameter);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-FormalParameter
  $.RULE("formalParameter", () => {
    $.OR([
      // Spec Deviation: extracted to "variableParaRegularParameter"
      {
        GATE: $.BACKTRACK($.variableParaRegularParameter),
        ALT: () => $.SUBRULE($.variableParaRegularParameter)
      },
      { ALT: () => $.SUBRULE($.variableArityParameter) }
    ]);
  });

  // Spec Deviation: extracted from "formalParameter"
  $.RULE("variableParaRegularParameter", () => {
    $.MANY(() => {
      $.SUBRULE($.variableModifier);
    });
    $.SUBRULE($.unannType);
    $.SUBRULE($.variableDeclaratorId);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableArityParameter
  $.RULE("variableArityParameter", () => {
    $.MANY(() => {
      $.SUBRULE($.variableModifier);
    });
    $.SUBRULE($.unannType);
    $.MANY2(() => {
      $.SUBRULE($.annotation);
    });
    $.CONSUME(t.DotDotDot);
    $.CONSUME(t.Identifier);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableModifier
  $.RULE("variableModifier", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.annotation) },
      { ALT: () => $.CONSUME(t.Final) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-Throws
  $.RULE("throws", () => {
    $.CONSUME(t.Throws);
    $.SUBRULE($.exceptionTypeList);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ExceptionTypeList
  $.RULE("exceptionTypeList", () => {
    $.SUBRULE($.exceptionType);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.exceptionType);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ExceptionType
  $.RULE("exceptionType", () => {
    // Spec Deviation: "typeVariable" alternative is missing because
    //                 it is contained in classType.
    $.SUBRULE($.classType);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-MethodBody
  $.RULE("methodBody", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.block) },
      { ALT: () => $.CONSUME(t.Semicolon) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-InstanceInitializer
  $.RULE("instanceInitializer", () => {
    $.SUBRULE($.block);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-StaticInitializer
  $.RULE("staticInitializer", () => {
    $.CONSUME(t.Static);
    $.SUBRULE($.block);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ConstructorDeclaration
  $.RULE("constructorDeclaration", () => {
    $.MANY(() => {
      $.SUBRULE($.constructorModifier);
    });
    $.SUBRULE($.constructorDeclarator);
    $.OPTION(() => {
      $.SUBRULE($.throws);
    });
    $.SUBRULE($.constructorBody);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ConstructorModifier
  $.RULE("constructorModifier", () => {
    $.OR([
      { ALT: () => $.SUBRULE($.annotation) },
      { ALT: () => $.CONSUME(t.Public) },
      { ALT: () => $.CONSUME(t.Protected) },
      { ALT: () => $.CONSUME(t.Private) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ConstructorDeclarator
  $.RULE("constructorDeclarator", () => {
    $.OPTION(() => {
      $.SUBRULE($.typeParameters);
    });
    $.SUBRULE($.simpleTypeName);
    $.CONSUME(t.LBrace);
    $.OPTION2({
      // a "formalParameterList" and a "receiverParameter"
      // cannot be distinguished using fixed lookahead.
      GATE: $.BACKTRACK($.receiverParameter),
      DEF: () => {
        $.SUBRULE($.receiverParameter);
        $.CONSUME(t.Comma);
      }
    });
    $.OPTION3(() => {
      $.SUBRULE($.formalParameterList);
    });
    $.CONSUME(t.RBrace);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-SimpleTypeName
  $.RULE("simpleTypeName", () => {
    // TODO: implement: Identifier but not var
    $.CONSUME(t.Identifier);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ConstructorBody
  $.RULE("constructorBody", () => {
    $.CONSUME(t.LCurly);
    $.OPTION({
      GATE: $.BACKTRACK($.explicitConstructorInvocation),
      DEF: () => {
        $.SUBRULE($.explicitConstructorInvocation);
      }
    });
    $.OPTION2(() => {
      $.SUBRULE($.blockStatements);
    });
    $.CONSUME(t.RCurly);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ExplicitConstructorInvocation
  $.RULE("explicitConstructorInvocation", () => {
    // Spec Deviation: split into two separate sub rules.
    $.OR([
      {
        ALT: () => $.SUBRULE($.unqualifiedExplicitConstructorInvocation)
      },
      { ALT: () => $.SUBRULE($.qualifiedExplicitConstructorInvocation) }
    ]);
  });

  $.RULE("unqualifiedExplicitConstructorInvocation", () => {
    $.OPTION(() => {
      $.SUBRULE($.typeArguments);
    });
    $.OR([
      {
        ALT: () => $.CONSUME(t.This)
      },
      {
        ALT: () => $.CONSUME(t.Super)
      }
    ]);
    $.CONSUME(t.LBrace);
    $.OPTION2(() => {
      $.SUBRULE($.argumentList);
    });
    $.CONSUME(t.RBrace);
    $.CONSUME(t.Semicolon);
  });

  $.RULE("qualifiedExplicitConstructorInvocation", () => {
    // Spec Deviation: According to the spec the prefix may be a "primary' as well,
    //                 however, most primary variants don't make sense here
    // TODO: discover which primary forms could be valid here
    //       and handle only those specific cases.
    //       It is best if we avoid referencing "primary" rule from
    //       outside the expressions rules as the expressions rules are not aligned
    //       to the spec style, so we want the smallest possible "external api"
    //       for the expressions rules.
    $.SUBRULE($.expressionName);
    $.CONSUME(t.Dot);
    $.OPTION(() => {
      $.SUBRULE($.typeArguments);
    });
    $.CONSUME(t.Super);
    $.CONSUME(t.LBrace);
    $.OPTION2(() => {
      $.SUBRULE($.argumentList);
    });
    $.CONSUME(t.RBrace);
    $.CONSUME(t.Semicolon);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumDeclaration
  $.RULE("enumDeclaration", () => {
    $.MANY(() => {
      $.SUBRULE($.classModifier);
    });
    $.CONSUME(t.Enum);
    $.SUBRULE($.typeIdentifier);
    $.OPTION(() => {
      $.SUBRULE($.superinterfaces);
    });
    $.SUBRULE($.enumBody);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumBody
  $.RULE("enumBody", () => {
    $.CONSUME(t.LCurly);
    $.OPTION(() => {
      $.SUBRULE($.enumConstantList);
    });
    $.OPTION2(() => {
      $.CONSUME(t.Comma);
    });
    $.OPTION3(() => {
      $.SUBRULE($.enumBodyDeclarations);
    });
    $.CONSUME(t.RCurly);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumConstantList
  $.RULE("enumConstantList", () => {
    $.SUBRULE($.enumConstant);
    $.MANY({
      GATE: () => {
        const nextToken = $.LA(2);
        return (
          tokenMatcher(nextToken, t.Identifier) || tokenMatcher(nextToken, t.At)
        );
      },
      DEF: () => {
        $.CONSUME(t.Comma);
        $.SUBRULE2($.enumConstant);
      }
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumConstant
  $.RULE("enumConstant", () => {
    $.MANY(() => {
      $.SUBRULE($.enumConstantModifier);
    });
    $.CONSUME(t.Identifier);
    $.OPTION(() => {
      $.CONSUME(t.LBrace);
      $.OPTION2(() => {
        $.SUBRULE($.argumentList);
      });
      $.CONSUME(t.RBrace);
    });
    $.OPTION3(() => {
      $.SUBRULE($.classBody);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumConstantModifier
  $.RULE("enumConstantModifier", () => {
    $.SUBRULE($.annotation);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-EnumBodyDeclarations
  $.RULE("enumBodyDeclarations", () => {
    $.CONSUME(t.Semicolon);
    $.MANY(() => {
      $.SUBRULE($.classBodyDeclaration);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordHeader
  $.RULE("recordDeclaration", () => {
    $.CONSUME(t.Record);
    $.SUBRULE($.typeIdentifier);
    $.OPTION(() => {
      $.SUBRULE($.typeParameters);
    });
    $.SUBRULE($.recordHeader);
    $.OPTION2(() => {
      $.SUBRULE($.superinterfaces);
    });
    $.SUBRULE($.recordBody);
  });

  $.RULE("recordHeader", () => {
    $.CONSUME(t.LBrace);
    $.OPTION(() => {
      $.SUBRULE($.recordComponentList);
    });
    $.CONSUME(t.RBrace);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordComponentList
  $.RULE("recordComponentList", () => {
    $.SUBRULE($.recordComponent);
    $.MANY(() => {
      $.CONSUME(t.Comma);
      $.SUBRULE2($.recordComponent);
    });
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordComponent
  $.RULE("recordComponent", () => {
    // Spec Deviation: extracted common "{recordComponentModifier} unannType" prefix
    //      extraction is safe because there are no other references to
    //      "variableArityRecordComponent"
    $.MANY(() => {
      $.SUBRULE($.recordComponentModifier);
    });
    $.SUBRULE($.unannType);
    $.OR([
      { ALT: () => $.CONSUME(t.Identifier) },
      { ALT: () => $.SUBRULE($.variableArityRecordComponent) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableArityRecordComponent
  // Spec Deviation: common "{recordComponentModifier} unannType" prefix was extracted in "recordComponent"
  $.RULE("variableArityRecordComponent", () => {
    $.MANY(() => {
      $.SUBRULE($.annotation);
    });
    $.CONSUME(t.DotDotDot);
    $.CONSUME(t.Identifier);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordComponentModifier
  $.RULE("recordComponentModifier", () => {
    $.SUBRULE($.annotation);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordBody
  $.RULE("recordBody", () => {
    $.CONSUME(t.LCurly);
    $.MANY(() => {
      $.SUBRULE($.recordBodyDeclaration);
    });
    $.CONSUME(t.RCurly);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-RecordBodyDeclaration
  $.RULE("recordBodyDeclaration", () => {
    $.OR([
      {
        GATE: () => this.BACKTRACK_LOOKAHEAD($.isCompactConstructorDeclaration),
        ALT: () => $.SUBRULE($.compactConstructorDeclaration)
      },
      { ALT: () => $.SUBRULE($.classBodyDeclaration) }
    ]);
  });

  // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-CompactConstructorDeclaration
  $.RULE("compactConstructorDeclaration", () => {
    $.MANY(() => {
      $.SUBRULE($.constructorModifier);
    });
    $.SUBRULE($.simpleTypeName);
    $.SUBRULE($.constructorBody);
  });

  $.RULE("isClassDeclaration", () => {
    let isEmptyTypeDeclaration = false;

    if (
      $.OPTION(() => {
        $.CONSUME(t.Semicolon);
      })
    ) {
      // an empty "TypeDeclaration"
      isEmptyTypeDeclaration = true;
    }

    try {
      // The {classModifier} is a super grammar of the "interfaceModifier"
      // So we must parse all the "{classModifier}" before we can distinguish
      // between the alternatives.
      $.MANY({
        GATE: () =>
          (tokenMatcher($.LA(1).tokenType, t.At) &&
            tokenMatcher($.LA(2).tokenType, t.Interface)) === false,
        DEF: () => {
          $.SUBRULE($.classModifier);
        }
      });
    } catch (e) {
      if (isRecognitionException(e)) {
        // TODO: add original syntax error?
        throw "Cannot Identify if the <TypeDeclaration> is a <ClassDeclaration> or an <InterfaceDeclaration>";
      } else {
        throw e;
      }
    }

    if (isEmptyTypeDeclaration) {
      return false;
    }

    const nextTokenType = this.LA(1).tokenType;
    return (
      tokenMatcher(nextTokenType, t.Class) ||
      tokenMatcher(nextTokenType, t.Enum) ||
      (tokenMatcher(nextTokenType, t.Record) &&
        tokenMatcher(this.LA(2).tokenType, t.Identifier))
    );
  });

  $.RULE("identifyClassBodyDeclarationType", () => {
    try {
      let nextTokenType = this.LA(1).tokenType;
      let nextNextTokenType = this.LA(2).tokenType;

      switch (nextTokenType) {
        case t.Semicolon:
          return classBodyTypes.semiColon;
        case t.LCurly:
          return classBodyTypes.instanceInitializer;
        case t.Static:
          switch (nextNextTokenType) {
            case t.LCurly:
              return classBodyTypes.staticInitializer;
          }
      }

      // We have to look beyond the modifiers to distinguish between the declaration types.
      $.MANY({
        GATE: () =>
          (tokenMatcher($.LA(1).tokenType, t.At) &&
            tokenMatcher($.LA(2).tokenType, t.Interface)) === false,
        DEF: () => {
          // This alternation includes all possible modifiers for all types of "ClassBodyDeclaration"
          // Certain combinations are syntactically invalid, this is **not** checked here,
          // Invalid combinations will cause a descriptive parsing error message to be
          // Created inside the relevant parsing rules **after** this lookahead
          // analysis.
          $.OR([
            {
              GATE: () =>
                (tokenMatcher($.LA(1).tokenType, t.At) &&
                  tokenMatcher($.LA(2).tokenType, t.Interface)) === false,
              ALT: () => $.SUBRULE($.annotation)
            },
            { ALT: () => $.CONSUME(t.Public) },
            { ALT: () => $.CONSUME(t.Protected) },
            { ALT: () => $.CONSUME(t.Private) },
            { ALT: () => $.CONSUME(t.Abstract) },
            { ALT: () => $.CONSUME(t.Static) },
            { ALT: () => $.CONSUME(t.Final) },
            { ALT: () => $.CONSUME(t.Transient) },
            { ALT: () => $.CONSUME(t.Volatile) },
            { ALT: () => $.CONSUME(t.Synchronized) },
            { ALT: () => $.CONSUME(t.Native) },
            { ALT: () => $.CONSUME(t.Strictfp) }
          ]);
        }
      });

      nextTokenType = this.LA(1).tokenType;
      nextNextTokenType = this.LA(2).tokenType;
      if (
        tokenMatcher(nextTokenType, t.Identifier) &&
        tokenMatcher(nextNextTokenType, t.LBrace)
      ) {
        return classBodyTypes.constructorDeclaration;
      }

      if (
        tokenMatcher(nextTokenType, t.Class) ||
        tokenMatcher(nextTokenType, t.Enum) ||
        tokenMatcher(nextTokenType, t.Record)
      ) {
        return classBodyTypes.classDeclaration;
      }

      if (
        tokenMatcher(nextTokenType, t.Interface) ||
        tokenMatcher(nextTokenType, t.At)
      ) {
        return classBodyTypes.interfaceDeclaration;
      }

      if (tokenMatcher(nextTokenType, t.Void)) {
        // method with result type "void"
        return classBodyTypes.methodDeclaration;
      }

      // Type Arguments common prefix
      if (tokenMatcher(nextTokenType, t.Less)) {
        this.SUBRULE($.typeParameters);
        const nextTokenType = this.LA(1).tokenType;
        const nextNextTokenType = this.LA(2).tokenType;
        // "<T> foo(" -> constructor
        if (
          tokenMatcher(nextTokenType, t.Identifier) &&
          tokenMatcher(nextNextTokenType, t.LBrace)
        ) {
          return classBodyTypes.constructorDeclaration;
        }
        // typeParameters can only appear in method or constructor
        // declarations, so if it is not a constructor it must be a method
        return classBodyTypes.methodDeclaration;
      }

      // Only field or method declarations may be valid at this point.
      // All other alternatives should have been attempted.
      // **both** start with "unannType"
      this.SUBRULE($.unannType);

      const nextToken = this.LA(1);
      nextNextTokenType = this.LA(2).tokenType;
      // "foo(..." --> look like method start
      if (
        tokenMatcher(nextToken, t.Identifier) &&
        tokenMatcher(nextNextTokenType, t.LBrace)
      ) {
        return classBodyTypes.methodDeclaration;
      }

      // a valid field
      // TODO: because we use token categories we should use tokenMatcher everywhere.
      if (tokenMatcher(nextToken, t.Identifier)) {
        return classBodyTypes.fieldDeclaration;
      }

      return classBodyTypes.unknown;
    } catch (e) {
      // TODO: add info from the original error
      throw Error("Cannot Identify the type of a <classBodyDeclaration>");
    }
  });

  $.RULE("isDims", () => {
    $.MANY($.annotation);
    return (
      tokenMatcher(this.LA(1).tokenType, t.LSquare) &&
      tokenMatcher(this.LA(2).tokenType, t.RSquare)
    );
  });

  $.RULE("isCompactConstructorDeclaration", () => {
    $.MANY($.constructorModifier);
    $.SUBRULE($.simpleTypeName);
    $.CONSUME(t.LCurly);
  });
}

module.exports = {
  defineRules
};
