docs: function defineRules($, t) {
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Block
    $.RULE("block", () => {
      $.CONSUME(t.LCurly);
      $.OPTION(() => {
        $.SUBRULE($.blockStatements);
      });
      $.CONSUME(t.RCurly);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-BlockStatements
    $.RULE("blockStatements", () => {
      $.SUBRULE($.blockStatement);
      $.MANY(() => {
        $.SUBRULE2($.blockStatement);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-BlockStatement
    $.RULE("blockStatement", () => {
      const isLocalVariableDeclaration = this.BACKTRACK_LOOKAHEAD(
        $.isLocalVariableDeclaration
      );
      $.OR([
        {
          GATE: () => isLocalVariableDeclaration,
          ALT: () => $.SUBRULE($.localVariableDeclarationStatement)
        },
        {
          ALT: () => $.SUBRULE($.classDeclaration)
        },
        { ALT: () => $.SUBRULE($.statement) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-LocalVariableDeclaration
    $.RULE("localVariableDeclarationStatement", () => {
      $.SUBRULE($.localVariableDeclaration);
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-LocalVariableDeclaration
    $.RULE("localVariableDeclaration", () => {
      $.MANY(() => {
        $.SUBRULE($.variableModifier);
      });
      $.SUBRULE($.localVariableType);
      $.SUBRULE($.variableDeclaratorList);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-LocalVariableType
    $.RULE("localVariableType", () => {
      $.OR([
        { ALT: () => $.SUBRULE($.unannType) },
        { ALT: () => $.CONSUME(t.Var) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Statement
    $.RULE("statement", () => {
      $.OR([
        { ALT: () => $.SUBRULE($.statementWithoutTrailingSubstatement) },
        { ALT: () => $.SUBRULE($.labeledStatement) },
        // Spec deviation: combined "IfThenStatement" and "IfThenElseStatement"
        { ALT: () => $.SUBRULE($.ifStatement) },
        { ALT: () => $.SUBRULE($.whileStatement) },
        { ALT: () => $.SUBRULE($.forStatement) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-StatementWithoutTrailingSubstatement
    $.RULE("statementWithoutTrailingSubstatement", () => {
      $.OR([
        { ALT: () => $.SUBRULE($.block) },
        { ALT: () => $.SUBRULE($.emptyStatement) },
        { ALT: () => $.SUBRULE($.expressionStatement) },
        { ALT: () => $.SUBRULE($.assertStatement) },
        { ALT: () => $.SUBRULE($.switchStatement) },
        { ALT: () => $.SUBRULE($.doStatement) },
        { ALT: () => $.SUBRULE($.breakStatement) },
        { ALT: () => $.SUBRULE($.continueStatement) },
        { ALT: () => $.SUBRULE($.returnStatement) },
        { ALT: () => $.SUBRULE($.synchronizedStatement) },
        { ALT: () => $.SUBRULE($.throwStatement) },
        { ALT: () => $.SUBRULE($.tryStatement) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-EmptyStatement
    $.RULE("emptyStatement", () => {
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-LabeledStatement
    $.RULE("labeledStatement", () => {
      $.CONSUME(t.Identifier);
      $.CONSUME(t.Colon);
      $.SUBRULE($.statement);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ExpressionStatement
    $.RULE("expressionStatement", () => {
      $.SUBRULE($.statementExpression);
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-StatementExpression
    $.RULE("statementExpression", () => {
      // Spec deviation: The many alternatives here were replaced with
      //                 the "expression" rule as it contains them all,
      //                 and distinguishing between the alternatives cannot be done
      //                 using a fixed lookahead.
      // TODO: verify the resulting expression is one of the valid alternatives?
      $.SUBRULE($.expression);
    });
  
    // Spec deviation: combined "IfThenStatement" and "IfThenElseStatement"
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-IfThenStatement
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-IfThenElseStatement
    $.RULE("ifStatement", () => {
      $.CONSUME(t.If);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.statement);
      $.OPTION(() => {
        $.CONSUME(t.Else);
        $.SUBRULE2($.statement);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-AssertStatement
    $.RULE("assertStatement", () => {
      $.CONSUME(t.Assert);
      $.SUBRULE($.expression);
      $.OPTION(() => {
        $.CONSUME(t.Colon);
        $.SUBRULE2($.expression);
      });
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-SwitchStatement
    $.RULE("switchStatement", () => {
      $.CONSUME(t.Switch);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.switchBlock);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-SwitchBlock
    $.RULE("switchBlock", () => {
      $.CONSUME(t.LCurly);
      $.MANY(() => {
        // Spec Deviation: refactored "switchBlock" for easy post-processing
        //                 each case and block together in the same rule.
        $.SUBRULE($.switchCase);
      });
      $.CONSUME(t.RCurly);
    });
  
    $.RULE("switchCase", () => {
      $.SUBRULE($.switchLabel);
      $.OPTION(() => {
        $.SUBRULE($.blockStatements);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-SwitchLabel
    $.RULE("switchLabel", () => {
      $.OR([
        {
          ALT: () => {
            $.CONSUME(t.Case);
            $.SUBRULE($.constantExpression);
            $.CONSUME(t.Colon);
          }
        },
        // SPEC Deviation: the variant with "enumConstantName" was removed
        // as it can be matched by the "constantExpression" variant
        // the distinction is semantic not syntactic.
        {
          ALT: () => {
            $.CONSUME(t.Default);
            $.CONSUME3(t.Colon);
          }
        }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-EnumConstantName
    $.RULE("enumConstantName", () => {
      $.CONSUME(t.Identifier);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-WhileStatement
    $.RULE("whileStatement", () => {
      $.CONSUME(t.While);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.statement);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-DoStatement
    $.RULE("doStatement", () => {
      $.CONSUME(t.Do);
      $.SUBRULE($.statement);
      $.CONSUME(t.While);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.CONSUME(t.Semicolon);
    });
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ForStatement
    $.RULE("forStatement", () => {
      $.OR([
        {
          GATE: () => this.BACKTRACK_LOOKAHEAD($.isBasicForStatement),
          ALT: () => $.SUBRULE($.basicForStatement)
        },
        { ALT: () => $.SUBRULE($.enhancedForStatement) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-BasicForStatement
    $.RULE("basicForStatement", () => {
      $.CONSUME(t.For);
      $.CONSUME(t.LBrace);
      $.OPTION(() => {
        $.SUBRULE($.forInit);
      });
      $.CONSUME(t.Semicolon);
      $.OPTION2(() => {
        $.SUBRULE($.expression);
      });
      $.CONSUME2(t.Semicolon);
      $.OPTION3(() => {
        $.SUBRULE($.forUpdate);
      });
      $.CONSUME(t.RBrace);
      $.SUBRULE($.statement);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ForInit
    $.RULE("forInit", () => {
      $.OR([
        {
          GATE: () => $.BACKTRACK_LOOKAHEAD($.isLocalVariableDeclaration),
          ALT: () => $.SUBRULE($.localVariableDeclaration)
        },
        { ALT: () => $.SUBRULE($.statementExpressionList) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ForUpdate
    $.RULE("forUpdate", () => {
      $.SUBRULE($.statementExpressionList);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-StatementExpressionList
    $.RULE("statementExpressionList", () => {
      $.SUBRULE($.statementExpression);
      $.MANY(() => {
        $.CONSUME(t.Comma);
        $.SUBRULE2($.statementExpression);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-EnhancedForStatement
    $.RULE("enhancedForStatement", () => {
      $.CONSUME(t.For);
      $.CONSUME(t.LBrace);
      $.MANY(() => {
        $.SUBRULE($.variableModifier);
      });
      $.SUBRULE($.localVariableType);
      $.SUBRULE($.variableDeclaratorId);
      $.CONSUME(t.Colon);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.statement);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-BreakStatement
    $.RULE("breakStatement", () => {
      $.CONSUME(t.Break);
      $.OPTION(() => {
        $.CONSUME(t.Identifier);
      });
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ContinueStatement
    $.RULE("continueStatement", () => {
      $.CONSUME(t.Continue);
      $.OPTION(() => {
        $.CONSUME(t.Identifier);
      });
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ReturnStatement
    $.RULE("returnStatement", () => {
      $.CONSUME(t.Return);
      $.OPTION(() => {
        $.SUBRULE($.expression);
      });
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ThrowStatement
    $.RULE("throwStatement", () => {
      $.CONSUME(t.Throw);
      $.SUBRULE($.expression);
      $.CONSUME(t.Semicolon);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-SynchronizedStatement
    $.RULE("synchronizedStatement", () => {
      $.CONSUME(t.Synchronized);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.expression);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.block);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-TryStatement
    $.RULE("tryStatement", () => {
      $.OR([
        {
          ALT: () => {
            $.CONSUME(t.Try);
            $.SUBRULE($.block);
            $.OR2([
              {
                ALT: () => {
                  $.SUBRULE($.catches);
                  $.OPTION(() => {
                    $.SUBRULE($.finally);
                  });
                }
              },
              { ALT: () => $.SUBRULE2($.finally) }
            ]);
          }
        },
        { ALT: () => $.SUBRULE($.tryWithResourcesStatement) }
      ]);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Catches
    $.RULE("catches", () => {
      $.SUBRULE($.catchClause);
      $.MANY(() => {
        $.SUBRULE2($.catchClause);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-CatchClause
    $.RULE("catchClause", () => {
      $.CONSUME(t.Catch);
      $.CONSUME(t.LBrace);
      $.SUBRULE($.catchFormalParameter);
      $.CONSUME(t.RBrace);
      $.SUBRULE($.block);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-CatchFormalParameter
    $.RULE("catchFormalParameter", () => {
      $.MANY(() => {
        $.SUBRULE($.variableModifier);
      });
      $.SUBRULE($.catchType);
      $.SUBRULE($.variableDeclaratorId);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-CatchType
    $.RULE("catchType", () => {
      $.SUBRULE($.unannClassType);
      $.MANY(() => {
        $.CONSUME(t.Or);
        $.SUBRULE2($.classType);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Finally
    $.RULE("finally", () => {
      $.CONSUME(t.Finally);
      $.SUBRULE($.block);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-TryWithResourcesStatement
    $.RULE("tryWithResourcesStatement", () => {
      $.CONSUME(t.Try);
      $.SUBRULE($.resourceSpecification);
      $.SUBRULE($.block);
      $.OPTION(() => {
        $.SUBRULE($.catches);
      });
      $.OPTION2(() => {
        $.SUBRULE($.finally);
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ResourceSpecification
    $.RULE("resourceSpecification", () => {
      $.CONSUME(t.LBrace);
      $.SUBRULE($.resourceList);
      $.OPTION(() => {
        $.CONSUME(t.Semicolon);
      });
      $.CONSUME(t.RBrace);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-ResourceList
    $.RULE("resourceList", () => {
      $.SUBRULE($.resource);
      $.MANY({
        GATE: () => tokenMatcher($.LA(2).tokenType, t.RBrace) === false,
        DEF: () => {
          $.CONSUME(t.Semicolon);
          $.SUBRULE2($.resource);
        }
      });
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Resource
    $.RULE("resource", () => {
      $.OR([
        {
          GATE: $.BACKTRACK($.resourceInit),
          // Spec Deviation: extracted this alternative to "resourceInit"
          //                 to enable backtracking.
          ALT: () => $.SUBRULE($.resourceInit)
        },
        { ALT: () => $.SUBRULE($.variableAccess) }
      ]);
    });
  
    // Spec Deviation: extracted from "resource"
    $.RULE("resourceInit", () => {
      $.MANY(() => {
        $.SUBRULE($.variableModifier);
      });
      $.SUBRULE($.localVariableType);
      $.CONSUME(t.Identifier);
      $.CONSUME(t.Equals);
      $.SUBRULE($.expression);
    });
  
    // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-VariableAccess
    $.RULE("variableAccess", () => {
      // Spec Deviation: both "expressionName" and "fieldAccess" can be parsed
      //                 by the "primary" rule
      // TODO: verify that the primary is a fieldAccess or an expressionName.
      $.SUBRULE($.primary);
    });
  
    // ------------------------------------
    // Special optimized backtracking rules.
    // ------------------------------------
    $.RULE("isBasicForStatement", () => {
      $.CONSUME(t.For);
      $.CONSUME(t.LBrace);
      $.OPTION(() => {
        $.SUBRULE($.forInit);
      });
      $.CONSUME(t.Semicolon);
      // consuming the first semiColon distinguishes between
      // "basic" and "enhanced" for statements
      return true;
    });
  
    $.RULE("isLocalVariableDeclaration", () => {
      $.MANY(() => {
        $.SUBRULE($.variableModifier);
      });
      $.SUBRULE($.localVariableType);
      $.SUBRULE($.variableDeclaratorId);
  
      const nextTokenType = this.LA(1).tokenType;
      switch (nextTokenType) {
        // Int x;
        case t.Semicolon:
        // Int x, y, z;
        case t.Comma:
        // Int x = 5;
        case t.Equals:
          return true;
        default:
          return false;
      }
    });
  }