private Expr createMatchMethodAndCallFor(
      String symbol,
      String namePrefix,
      GExpansion expansion,
      Expr outerLookahead,
      NodeList<FormalParameter> params,
      NodeList<Expr> args,
      boolean memoize) {
    switch (expansion.kind) {
      case LookAhead:
        Expr semanticLookahead = expansion.semanticLookahead;
        if (semanticLookahead != null) {
          if (semanticLookahead.equals(methodInvocationExpr(name("isLambda")))) {
            semanticLookahead =
                methodInvocationExpr(name("isLambda")).withArgs(listOf(outerLookahead));
          } else if (semanticLookahead.equals(methodInvocationExpr(name("isCast")))) {
            semanticLookahead =
                methodInvocationExpr(name("isCast")).withArgs(listOf(outerLookahead));
          }
          return conditionalExpr(semanticLookahead, outerLookahead, FAILED_LOOKAHEAD);
        }
        return null;
      case Sequence:
        {
          int index = memoize ? memoizationIndex++ : -1;

          NodeList<Stmt> stmts = emptyList();
          int count = 0;
          for (GExpansion child : expansion.children) {
            Expr childCall =
                createMatchMethodAndCallFor(
                    symbol,
                    namePrefix + "_" + ++count,
                    child,
                    LOOKAHEAD,
                    params,
                    params.map(p -> p.id().get().name()),
                    false);
            if (childCall == null) continue;
            stmts = stmts.append(expressionStmt(assignExpr(LOOKAHEAD, AssignOp.Normal, childCall)));
            stmts =
                stmts.append(
                    ifStmt(
                        binaryExpr(LOOKAHEAD, BinaryOp.Equal, FAILED_LOOKAHEAD),
                        memoize
                            ? stmt("return memoizeMatch(initialLookahead, " + index + ", -1);")
                                .build()
                            : stmt("return -1;").build()));
          }

          if (memoize) {
            stmts =
                stmts.prependAll(
                    listOf(
                        stmt("int initialLookahead = lookahead;").build(),
                        stmt("int memoizedMatch = memoizedMatch(initialLookahead, " + index + ");")
                            .build(),
                        stmt("if (memoizedMatch > -2) return memoizedMatch;").build()));
          }

          stmts =
              stmts.append(
                  memoize
                      ? stmt("return memoizeMatch(initialLookahead, " + index + ", lookahead);")
                          .build()
                      : stmt("return lookahead;").build());

          createMatchMethod(symbol, namePrefix, expansion, stmts, params);

          return matchMethodCall(namePrefix, outerLookahead, args);
        }
      case Choice:
        {
          NodeList<Stmt> stmts = emptyList();
          stmts =
              stmts.append(
                  expressionStmt(
                      variableDeclarationExpr(
                          localVariableDecl(primitiveType(Primitive.Int))
                              .withVariables(
                                  listOf(
                                      variableDeclarator(variableDeclaratorId(LOOKAHEAD_NEW)))))));
          int count = 0;
          for (GExpansion child : expansion.children) {
            Expr childCall =
                createMatchMethodAndCallFor(
                    symbol,
                    namePrefix + "_" + ++count,
                    child,
                    LOOKAHEAD,
                    params,
                    params.map(p -> p.id().get().name()),
                    false);
            if (childCall == null) continue;
            stmts =
                stmts.append(expressionStmt(assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall)));
            stmts =
                stmts.append(
                    ifStmt(
                        binaryExpr(LOOKAHEAD_NEW, BinaryOp.NotEqual, FAILED_LOOKAHEAD),
                        returnStmt().withExpr(LOOKAHEAD_NEW)));
          }
          stmts = stmts.append(returnStmt().withExpr(FAILED_LOOKAHEAD));
          createMatchMethod(symbol, namePrefix, expansion, stmts, params);

          return matchMethodCall(namePrefix, outerLookahead, args);
        }
      case ZeroOrOne:
        {
          NodeList<Stmt> stmts = emptyList();
          stmts =
              stmts.append(
                  expressionStmt(
                      variableDeclarationExpr(
                          localVariableDecl(primitiveType(Primitive.Int))
                              .withVariables(
                                  listOf(
                                      variableDeclarator(variableDeclaratorId(LOOKAHEAD_NEW)))))));

          Expr childCall =
              createMatchMethodAndCallFor(
                  symbol,
                  namePrefix + "_" + 1,
                  GExpansion.sequence(expansion.children),
                  LOOKAHEAD,
                  params,
                  params.map(p -> p.id().get().name()),
                  false);
          stmts =
              stmts.append(expressionStmt(assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall)));
          stmts =
              stmts.append(
                  ifStmt(
                      binaryExpr(LOOKAHEAD_NEW, BinaryOp.NotEqual, FAILED_LOOKAHEAD),
                      returnStmt().withExpr(LOOKAHEAD_NEW)));

          stmts = stmts.append(returnStmt().withExpr(LOOKAHEAD));
          createMatchMethod(symbol, namePrefix, expansion, stmts, params);

          return matchMethodCall(namePrefix, outerLookahead, args);
        }
      case ZeroOrMore:
        {
          NodeList<Stmt> stmts = emptyList();
          stmts =
              stmts.append(
                  expressionStmt(
                      variableDeclarationExpr(
                          localVariableDecl(primitiveType(Primitive.Int))
                              .withVariables(
                                  listOf(
                                      variableDeclarator(variableDeclaratorId(LOOKAHEAD_NEW)))))));

          Expr childCall =
              createMatchMethodAndCallFor(
                  symbol,
                  namePrefix + "_" + 1,
                  GExpansion.sequence(expansion.children),
                  LOOKAHEAD,
                  params,
                  params.map(p -> p.id().get().name()),
                  false);
          stmts =
              stmts.append(expressionStmt(assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall)));
          stmts =
              stmts.append(
                  whileStmt(
                      binaryExpr(LOOKAHEAD_NEW, BinaryOp.NotEqual, FAILED_LOOKAHEAD),
                      blockStmt()
                          .withStmts(
                              listOf(
                                  expressionStmt(
                                      assignExpr(LOOKAHEAD, AssignOp.Normal, LOOKAHEAD_NEW)),
                                  expressionStmt(
                                      assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall))))));
          stmts = stmts.append(returnStmt().withExpr(LOOKAHEAD));
          createMatchMethod(symbol, namePrefix, expansion, stmts, params);

          return matchMethodCall(namePrefix, outerLookahead, args);
        }
      case OneOrMore:
        {
          NodeList<Stmt> stmts = emptyList();
          stmts =
              stmts.append(
                  expressionStmt(
                      variableDeclarationExpr(
                          localVariableDecl(primitiveType(Primitive.Int))
                              .withVariables(
                                  listOf(
                                      variableDeclarator(variableDeclaratorId(LOOKAHEAD_NEW)))))));

          Expr childCall =
              createMatchMethodAndCallFor(
                  symbol,
                  namePrefix + "_" + 1,
                  GExpansion.sequence(expansion.children),
                  LOOKAHEAD,
                  params,
                  params.map(p -> p.id().get().name()),
                  false);
          stmts =
              stmts.append(expressionStmt(assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall)));
          stmts =
              stmts.append(
                  ifStmt(
                      binaryExpr(LOOKAHEAD_NEW, BinaryOp.Equal, FAILED_LOOKAHEAD),
                      returnStmt().withExpr(FAILED_LOOKAHEAD)));
          stmts =
              stmts.append(
                  whileStmt(
                      binaryExpr(LOOKAHEAD_NEW, BinaryOp.NotEqual, FAILED_LOOKAHEAD),
                      blockStmt()
                          .withStmts(
                              listOf(
                                  expressionStmt(
                                      assignExpr(LOOKAHEAD, AssignOp.Normal, LOOKAHEAD_NEW)),
                                  expressionStmt(
                                      assignExpr(LOOKAHEAD_NEW, AssignOp.Normal, childCall))))));
          stmts = stmts.append(returnStmt().withExpr(LOOKAHEAD));
          createMatchMethod(symbol, namePrefix, expansion, stmts, params);

          return matchMethodCall(namePrefix, outerLookahead, args);
        }
      case NonTerminal:
        {
          return createMatchMethodAndCallFor(expansion.symbol, outerLookahead, expansion.hints);
        }
      case Terminal:
        {
          return matchCall(expansion.symbol, LOOKAHEAD);
        }
      case Action:
        {
          return null;
        }
      default:
    }
    return null;
  }
  private Expr matchCondition(
      String symbol, GExpansion expansion, NodeList<FormalParameter> params, NodeList<Expr> args) {
    if (expansion.children != null && !expansion.children.isEmpty()) {
      GExpansion firstChild = expansion.children.get(0);
      if (firstChild.kind == GExpansion.Kind.LookAhead) {
        Expr lookaheadCondition = null;

        Expr semanticLookaheadCondition = firstChild.semanticLookahead;
        int amount = firstChild.amount;
        List<GExpansion> children = firstChild.children;
        boolean negativeLookahead = firstChild.negativeLookahead;

        if (semanticLookaheadCondition != null) {
          if (semanticLookaheadCondition.equals(methodInvocationExpr(name("isLambda")))) {
            semanticLookaheadCondition =
                methodInvocationExpr(name("isLambda")).withArgs(listOf(literalExpr(0)));
          } else if (semanticLookaheadCondition.equals(methodInvocationExpr(name("isCast")))) {
            semanticLookaheadCondition =
                methodInvocationExpr(name("isCast")).withArgs(listOf(literalExpr(0)));
          }
          lookaheadCondition =
              negativeLookahead
                  ? unaryExpr(UnaryOp.Not, semanticLookaheadCondition)
                  : semanticLookaheadCondition;
        }
        if (amount != -1) {
          Expr amountLookaheadCondition =
              buildLookaheadWithAmountCondition(symbol, expansion, amount, params, args);
          lookaheadCondition =
              lookaheadCondition == null
                  ? amountLookaheadCondition
                  : binaryExpr(lookaheadCondition, BinaryOp.And, amountLookaheadCondition);
        }
        if (children != null) {
          String matchMethodName = "match" + symbol + "_lookahead" + incrementCount(symbol);
          Expr call =
              createMatchMethodAndCallFor(
                  symbol,
                  matchMethodName,
                  GExpansion.sequence(children),
                  literalExpr(0),
                  params,
                  args,
                  false);
          Expr descriptiveLookaheadCondition =
              binaryExpr(
                  call, negativeLookahead ? BinaryOp.Equal : BinaryOp.NotEqual, literalExpr(-1));

          lookaheadCondition =
              lookaheadCondition == null
                  ? descriptiveLookaheadCondition
                  : binaryExpr(
                      lookaheadCondition,
                      negativeLookahead ? BinaryOp.Or : BinaryOp.And,
                      descriptiveLookaheadCondition);
        }

        return lookaheadCondition;
      }
    }
    return binaryExpr(
        matchCall(firstTerminalsOf(expansion), literalExpr(0)),
        BinaryOp.NotEqual,
        FAILED_LOOKAHEAD);
  }