private Expr buildLookaheadWithAmountCondition(
     String symbol,
     GExpansion expansion,
     int amount,
     NodeList<FormalParameter> params,
     NodeList<Expr> args) {
   String matchMethodName = "match" + symbol + "_lookahead" + incrementCount(symbol);
   NodeList<Stmt> stmts =
       buildLookaheadWithAmountCondition(
           Collections.singletonList(expansion.location()), 0, amount, params, args);
   stmts = stmts.append(returnStmt().withExpr(FAILED_LOOKAHEAD));
   createMatchMethod(symbol, matchMethodName, expansion, stmts, emptyList());
   return binaryExpr(
       matchMethodCall(matchMethodName, literalExpr(0), emptyList()),
       BinaryOp.NotEqual,
       FAILED_LOOKAHEAD);
 }
  @Override
  protected ClassDecl contributeBody(
      ClassDecl decl, ImportManager importManager, TreeClassDescriptor[] arg) {
    importManager.addImports(
        listOf(
            importDecl(qualifiedName("org.jlato.internal.bu")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.coll")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.decl")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.expr")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.name")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.stmt")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.bu.type")).setOnDemand(true),
            importDecl(qualifiedName("org.jlato.internal.parser.Token")),
            importDecl(qualifiedName("org.jlato.internal.parser.TokenType")),
            importDecl(qualifiedName("org.jlato.tree.Problem.Severity")),
            importDecl(qualifiedName("org.jlato.parser.ParseException")),
            importDecl(qualifiedName("org.jlato.tree.expr.AssignOp")),
            importDecl(qualifiedName("org.jlato.tree.expr.BinaryOp")),
            importDecl(qualifiedName("org.jlato.tree.expr.UnaryOp")),
            importDecl(qualifiedName("org.jlato.tree.decl.ModifierKeyword")),
            importDecl(qualifiedName("org.jlato.tree.type.Primitive"))));

    List<GProduction> allProductions = productions.getAll();

    int memoizedProductionCount = MEMOIZE_ALL_MATCHES ? allProductions.size() : 0;
    if (!MEMOIZE_ALL_MATCHES) {
      for (GProduction production : allProductions) {
        if (production.memoizeMatches) memoizedProductionCount++;
      }
    }

    NodeList<MemberDecl> members = Trees.emptyList();
    members =
        members.append(
            memberDecl(
                    "protected int memoizedProductionCount() { return "
                        + memoizedProductionCount
                        + "; }")
                .build());

    for (GProduction production : allProductions) {
      if (excluded(production)) continue;

      parseMethods = parseMethods.append(parseMethod(importManager, production));
    }

    for (MethodDecl parseMethod : parseMethods) {
      members = members.append(parseMethod);

      String id = parseMethod.name().id();
      int indexOfUnderscore = id.indexOf('_');
      String symbol =
          id.substring("parse".length(), indexOfUnderscore == -1 ? id.length() : indexOfUnderscore);

      List<MethodDecl> methods = perSymbolMatchMethods.get(symbol);
      if (methods != null) {
        Collections.sort(methods, (m1, m2) -> m1.name().id().compareTo(m2.name().id()));
        members = members.appendAll(listOf(methods));
      }
    }

    return decl.withMembers(members);
  }
  private NodeList<Stmt> parseStatementsFor(
      String symbol, GExpansion expansion, NodeList<FormalParameter> hintParams, boolean optional) {
    NodeList<Stmt> stmts = emptyList();
    switch (expansion.kind) {
      case Sequence:
        stmts = stmts.appendAll(parseStatementsForChildren(symbol, expansion, hintParams, false));
        break;
      case ZeroOrOne:
        {
          if (expansion.children.size() == 1
              && expansion.children.get(0).kind == GExpansion.Kind.Choice) {
            stmts =
                stmts.appendAll(parseStatementsForChildren(symbol, expansion, hintParams, true));
          } else {
            stmts =
                stmts.append(
                    ifStmt(
                        matchCondition(
                            symbol,
                            expansion,
                            hintParams,
                            hintParams.map(p -> p.id().get().name())),
                        blockStmt()
                            .withStmts(
                                parseStatementsForChildren(symbol, expansion, hintParams, false))));
          }
          break;
        }
      case ZeroOrMore:
        {
          stmts =
              stmts.append(
                  whileStmt(
                      matchCondition(
                          symbol, expansion, hintParams, hintParams.map(p -> p.id().get().name())),
                      blockStmt()
                          .withStmts(
                              parseStatementsForChildren(symbol, expansion, hintParams, false))));
          break;
        }
      case OneOrMore:
        {
          stmts =
              stmts.append(
                  doStmt(
                      blockStmt()
                          .withStmts(
                              parseStatementsForChildren(symbol, expansion, hintParams, false)),
                      matchCondition(
                          symbol,
                          expansion,
                          hintParams,
                          hintParams.map(p -> p.id().get().name()))));
          break;
        }
      case Choice:
        {
          List<IfStmt> expansionsIfStmt =
              expansion
                  .children
                  .stream()
                  .map(
                      e ->
                          ifStmt(
                              matchCondition(
                                  symbol, e, hintParams, hintParams.map(p -> p.id().get().name())),
                              blockStmt()
                                  .withStmts(parseStatementsFor(symbol, e, hintParams, false))))
                  .collect(Collectors.toList());
          Collections.reverse(expansionsIfStmt);

          stmts =
              stmts.append(
                  listOf(expansionsIfStmt)
                      .foldRight(
                          (Stmt)
                              (optional
                                  ? null
                                  : blockStmt()
                                      .withStmts(
                                          listOf(
                                              throwStmt(
                                                  methodInvocationExpr(
                                                          name("produceParseException"))
                                                      .withArgs(
                                                          listOf(
                                                              firstTerminalsOf(expansion)
                                                                  .stream()
                                                                  .map(this::prefixedConstant)
                                                                  .collect(
                                                                      Collectors.toList()))))))),
                          (ifThenClause, elseClause) ->
                              optional && elseClause == null
                                  ? ifThenClause
                                  : ifThenClause.withElseStmt(elseClause)));
          break;
        }
      case NonTerminal:
        {
          Expr call =
              methodInvocationExpr(name("parse" + upperCaseFirst(expansion.symbol)))
                  .withArgs(expansion.hints.appendAll(expansion.arguments));
          stmts =
              stmts.append(
                  expressionStmt(
                      expansion.name == null
                          ? call
                          : assignExpr(name(expansion.name), AssignOp.Normal, call)));
          break;
        }
      case Terminal:
        {
          Expr argument = prefixedConstant(expansion.symbol);
          Expr call = methodInvocationExpr(name("parse")).withArgs(listOf(argument));
          stmts =
              stmts.append(
                  expressionStmt(
                      expansion.name == null
                          ? call
                          : assignExpr(name(expansion.name), AssignOp.Normal, call)));
          break;
        }
      case Action:
        {
          stmts = stmts.appendAll(expansion.action);
          break;
        }
      default:
    }
    return stmts;
  }