/**
   * 13.12.9 Runtime Semantics: CaseBlockEvaluation
   *
   * @param node the switch statement
   * @param type the switch statement type
   * @param lblExit the exit label
   * @param switchValue the variable which holds the switch value
   * @param mv the statement visitor
   * @return the completion value
   */
  private Completion CaseBlockEvaluation(
      SwitchStatement node,
      SwitchType type,
      Jump lblExit,
      Variable<?> switchValue,
      StatementVisitor mv) {
    List<SwitchClause> clauses = node.getClauses();
    Jump lblDefault = null;
    Jump[] labels = new Jump[clauses.size()];
    for (int i = 0, size = clauses.size(); i < size; ++i) {
      labels[i] = new Jump();
      if (clauses.get(i).isDefaultClause()) {
        assert lblDefault == null;
        lblDefault = labels[i];
      }
    }

    if (type == SwitchType.Int) {
      emitIntSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
    } else if (type == SwitchType.Char) {
      emitCharSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
    } else if (type == SwitchType.String) {
      emitStringSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
    } else if (type == SwitchType.Generic) {
      emitGenericSwitch(clauses, labels, lblDefault, lblExit, switchValue, mv);
    } else {
      assert type == SwitchType.Default;
      assert switchValue == null;
      // Directly jump to default clause; since switch clauses before default clause are not
      // emitted, jump instruction can be elided as well, so we directly fall into the default
      // clause.
    }

    Completion result = Completion.Normal, lastResult = Completion.Normal;
    if (type == SwitchType.Default) {
      Iterator<SwitchClause> iter = clauses.iterator();
      // skip leading clauses until default clause found
      while (iter.hasNext()) {
        SwitchClause switchClause = iter.next();
        if (switchClause.isDefaultClause()) {
          lastResult = switchClause.accept(this, mv);
          break;
        }
      }
      // handle clauses following default clause until abrupt completion
      while (iter.hasNext() && !lastResult.isAbrupt()) {
        lastResult = iter.next().accept(this, mv);
      }
      result = lastResult;
    } else {
      int index = 0;
      for (SwitchClause switchClause : clauses) {
        Jump caseLabel = labels[index++];
        if (caseLabel != null) {
          mv.mark(caseLabel);
        } else if (lastResult.isAbrupt()) {
          // Ignore unreachable targets
          continue;
        }
        Completion innerResult = switchClause.accept(this, mv);
        if (innerResult.isAbrupt()) {
          // not fall-thru
          result = result.isAbrupt() ? result.select(innerResult) : innerResult;
        }
        lastResult = innerResult;
      }
    }
    return result.normal(lblDefault == null || !lastResult.isAbrupt());
  }
  /** 13.12.11 Runtime Semantics: Evaluation */
  @Override
  public Completion visit(SwitchStatement node, StatementVisitor mv) {
    // stack -> switchValue
    ValType switchValueType = expression(node.getExpression(), mv);

    SwitchType type = SwitchType.of(node);
    boolean defaultOrReturn = false;
    if (type == SwitchType.Int) {
      if (!switchValueType.isNumeric() && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.Char) {
      if (switchValueType != ValType.String && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.String) {
      if (switchValueType != ValType.String && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.Generic) {
      mv.toBoxed(switchValueType);
      switchValueType = ValType.Any;
    } else {
      assert type == SwitchType.Default;
      defaultOrReturn = true;
    }

    final boolean defaultClausePresent = hasDefaultClause(node);
    if (defaultOrReturn) {
      // never true -> emit default switch or return
      mv.pop(switchValueType);
      if (defaultClausePresent) {
        type = SwitchType.Default;
      } else {
        return Completion.Normal;
      }
    }

    mv.enterVariableScope();
    Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);

    Variable<?> switchValue = null;
    if (type != SwitchType.Default) {
      switchValue = mv.newVariable("switchValue", switchValueType.toClass());
      mv.store(switchValue);
    }

    BlockScope scope = node.getScope();
    if (scope.isPresent()) {
      newDeclarativeEnvironment(scope, mv);
      codegen.blockInit(node, mv);
      pushLexicalEnvironment(mv);
    }

    Jump lblExit = new Jump();
    BreakLabel lblBreak = new BreakLabel();
    mv.enterScope(node);
    mv.enterBreakable(node, lblBreak);
    Completion result = CaseBlockEvaluation(node, type, lblExit, switchValue, mv);
    mv.exitBreakable(node);
    mv.exitScope();

    if (!defaultClausePresent) {
      mv.mark(lblExit);
    }
    if (scope.isPresent() && !result.isAbrupt()) {
      popLexicalEnvironment(mv);
    }
    if (lblBreak.isTarget()) {
      mv.mark(lblBreak);
      restoreEnvironment(savedEnv, mv);
    }
    mv.exitVariableScope();

    return result.normal(lblBreak.isTarget());
  }