private ParseDelta enter() {
   return ParseDelta
       // Start the left.
       .builder(first)
       // Push this so that we can remember to do b if a passes.
       .push()
       .build();
 }
 private ParseDelta exitPass() {
   // Continue to the next entry.
   return ParseDelta.builder(second).build();
 }
/**
 * Executes A and then B on the remaining input.
 *
 * <p>This matches the minimal language {&forall;a&isin;A,%forall;b%isin;concatenation(a, b)}.
 */
public final class SeqCombinator extends BinaryCombinator {
  /** */
  public SeqCombinator(Supplier<NodeMetadata> mds, Combinator first, Combinator second) {
    super(mds, first, second);
  }

  @Override
  protected SeqCombinator unfold(
      NodeMetadata newMetadata,
      Function<ProdName, ProdName> renamer,
      Combinator newFirst,
      Combinator newSecond) {
    if (newMetadata.equals(this.md) && this.first == newFirst && this.second == newSecond) {
      return this;
    }
    return new SeqCombinator(Suppliers.ofInstance(newMetadata), newFirst, newSecond);
  }

  @Override
  public ParseDelta enter(Parse p) {
    return enter();
  }

  @Override
  public ParseDelta exit(Parse p, Success s) {
    switch (s) {
      case FAIL:
        return EXIT_FAIL;
      case PASS:
        return exitPass();
    }
    throw new AssertionError(s);
  }

  @Override
  public final ImmutableList<ParseDelta> epsilonTransition(
      TransitionType tt, Language lang, OutputContext ctx) {
    switch (tt) {
      case ENTER:
        return ImmutableList.of(enter());
      case EXIT_PASS:
        return ImmutableList.of(exitPass());
      case EXIT_FAIL:
        return ImmutableList.of(EXIT_FAIL);
    }
    throw new AssertionError(tt);
  }

  private ParseDelta enter() {
    return ParseDelta
        // Start the left.
        .builder(first)
        // Push this so that we can remember to do b if a passes.
        .push()
        .build();
  }

  private static final ParseDelta EXIT_FAIL = ParseDelta.fail().build();

  private ParseDelta exitPass() {
    // Continue to the next entry.
    return ParseDelta.builder(second).build();
  }

  @Override
  public Precedence precedence() {
    return Precedence.SEQUENCE;
  }

  @Override
  protected String getVizTypeClassName() {
    return "seq";
  }

  @Override
  protected void visualizeBody(DetailLevel lvl, VizOutput out) throws IOException {
    writeBinaryOperator(
        first,
        second,
        precedence(),
        Predicates.instanceOf(SeqCombinator.class),
        STRING_COALESCING_INFIXER,
        lvl,
        out);
  }

  @Override
  public Frequency consumesInput(Language lang) {
    Frequency ff = lang.lali.consumesInput(first);
    switch (ff) {
      case NEVER:
        return lang.lali.consumesInput(second);
      case ALWAYS:
        return ff;
      case SOMETIMES:
        Frequency sf = lang.lali.consumesInput(second);
        switch (sf) {
          case ALWAYS:
          case SOMETIMES:
            return sf;
          case NEVER:
            return Frequency.SOMETIMES;
        }
        throw new AssertionError(sf.name());
    }
    throw new AssertionError(ff.name());
  }

  @Override
  public ImmutableRangeSet<Integer> lookahead(Language lang) {
    Frequency ff = lang.lali.consumesInput(first);
    switch (ff) {
      case NEVER:
        return lang.lali.lookahead(second);
      case ALWAYS:
        return lang.lali.lookahead(first);
      case SOMETIMES:
        TreeRangeSet<Integer> r = TreeRangeSet.create();
        r.addAll(lang.lali.lookahead(first));
        r.addAll(lang.lali.lookahead(second));
        return ImmutableRangeSet.copyOf(r);
    }
    throw new AssertionError(ff.name());
  }

  @Override
  public boolean reachesWithoutConsuming(Combinator target, Language lang) {
    // Check whether target is reachable from any member.
    return super.reachesWithoutConsuming(target, lang)
        || first.reachesWithoutConsuming(target, lang)
        || (lang.lali.consumesInput(first) != Frequency.ALWAYS
            && second.reachesWithoutConsuming(target, lang));
  }

  /** Finds sequences of characters that */
  private static final Infixer STRING_COALESCING_INFIXER = new StringCoalescingInfixer();
}