// const_eval.cc
// code for const_eval.h

#include "const_eval.h"     // this module
#include "cc_ast.h"         // C++ AST
#include "cc_env.h"         // Env
#include "stdconv.h"        // applyIntegralPromotions, etc.
#include "mtype.h"          // MType::getEnvironment
#include "template.h"       // STemplateArgument


// ----------------------- CValue ------------------------
STATICDEF CValue::Kind CValue::classify(SimpleTypeId t)
{
  switch (t) {
    case ST_CHAR:
    case ST_SIGNED_CHAR:
    case ST_SHORT_INT:
    case ST_WCHAR_T:
    case ST_INT:
    case ST_LONG_INT:
    case ST_LONG_LONG:
      return K_SIGNED;

    case ST_BOOL:
    case ST_UNSIGNED_CHAR:
    case ST_UNSIGNED_SHORT_INT:
    case ST_UNSIGNED_INT:
    case ST_UNSIGNED_LONG_INT:
    case ST_UNSIGNED_LONG_LONG:
      return K_UNSIGNED;

    case ST_FLOAT:
    case ST_DOUBLE:
    case ST_LONG_DOUBLE:
      return K_FLOAT;

    case ST_DEPENDENT:
      return K_DEPENDENT;

    default:
      return K_ERROR;
  }
}


void CValue::dup(CValue const &obj)
{
  type = obj.type;
  switch (classify(type)) {
    case K_SIGNED:    si = obj.si;     break;
    case K_UNSIGNED:  ui = obj.ui;     break;
    case K_FLOAT:     f = obj.f;       break;
    case K_ERROR:     why = obj.why;   break;
    case K_DEPENDENT:                  break;
  }
}


bool CValue::isZero() const
{
  switch (kind()) {
    default: // silence warning
    case K_SIGNED:    return si == 0;
    case K_UNSIGNED:  return ui == 0;
    case K_FLOAT:     return f == 0.0;
  }
}


bool CValue::isIntegral() const
{
  Kind k = kind();
  return k==K_SIGNED || k==K_UNSIGNED;
}

int CValue::getIntegralValue() const
{
  xassert(isIntegral());
  if (kind() == K_SIGNED) {
    return si;
  }
  else {
    return (int)ui;
  }
}


void CValue::setSigned(SimpleTypeId t, long v)
{
  type = t;
  xassert(isSigned());
  si = v;
}


void CValue::setUnsigned(SimpleTypeId t, unsigned long v)
{
  type = t;
  xassert(isUnsigned());
  ui = v;
}


void CValue::setFloat(SimpleTypeId t, float v)
{
  type = t;
  xassert(isFloat());
  f = v;
}


void CValue::setError(rostring w)
{
  type = ST_ERROR;
  why = new string(w);
}


void CValue::setDependent()
{
  type = ST_DEPENDENT;
  si = 0;
}


void CValue::performIntegralPromotions()
{
  convertToType(applyIntegralPromotions(type));
}

void CValue::convertToType(SimpleTypeId newType)
{
  Kind oldKind = classify(type);
  Kind newKind = classify(newType);
  if (newKind != oldKind) {
    // neither source nor dest should be sticky
    xassert(oldKind != K_ERROR && oldKind != K_DEPENDENT &&
            newKind != K_ERROR && newKind != K_DEPENDENT);

    xassert((unsigned)oldKind < 8u);    // for my representation trick

    // sort of like ML-style case analysis on a pair of values
    switch (newKind*8 + oldKind) {
      default:
        xfailure("unexpected type conversion scenario in const-eval");
        break;                   // silence warning

      case K_SIGNED*8 + K_UNSIGNED:
        si = (long)ui;           // convert unsigned to signed
        break;

      case K_SIGNED*8 + K_FLOAT:
        si = (long)f;
        break;

      case K_UNSIGNED*8 + K_SIGNED:
        ui = (unsigned long)si;
        break;

      case K_UNSIGNED*8 + K_FLOAT:
        ui = (unsigned long)f;
        break;

      case K_FLOAT*8 + K_SIGNED:
        f = (float)si;
        break;

      case K_FLOAT*8 + K_UNSIGNED:
        f = (float)ui;
        break;
    }
  }

  type = newType;

  // dmandelin@mozilla.com
  // Fixes Oink ticket #164, Mozilla bug #411178
  // Truncate value according to C++ semantics
  int reprSize = simpleTypeReprSize(type);
  // Mask for bits to be kept in truncation.
  long mask = (1L << 8*reprSize) - 1;
  if (mask) { // zero mask means no truncation required
    si &= mask;
    if (kind() == K_SIGNED) {
      long signTestMask = 1 << (8*reprSize-1);
      if (si & signTestMask) { si |= ~mask; }
    }
  }
}


void CValue::applyUnary(UnaryOp op)
{
  if (isSticky()) { return; }

  switch (op) {
    default: xfailure("bad code");

    case UNY_PLUS:
      // 5.3.1p6
      performIntegralPromotions();
      break;

    case UNY_MINUS:
      // 5.3.1p7
      performIntegralPromotions();
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:    si = -si;   break;
        case K_UNSIGNED:  ui = -ui;   break;
        case K_FLOAT:     f = -f;     break;
      }
      break;

    case UNY_NOT:
      // 5.3.1p8
      ui = isZero()? 1u : 0u;
      type = ST_BOOL;
      break;

    case UNY_BITNOT:
      // 5.3.1p9
      performIntegralPromotions();
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:    si = ~si;   break;
        case K_UNSIGNED:  ui = ~ui;   break;
        case K_FLOAT:     setError("cannot apply ~ to float types"); break;
      }
  }
}


void CValue::applyUsualArithmeticConversions(CValue &other)
{
  SimpleTypeId finalType = usualArithmeticConversions(type, other.type);
  this->convertToType(finalType);
  other.convertToType(finalType);
}


void CValue::addOffset(int offset)
{
  if (isSigned()) {
    si += offset;
  }
  else if (isUnsigned()) {
    ui += (unsigned long)offset;
  }
  else if (isFloat()) {
    f += (float)offset;     // questionable...
  }
  else {
    // other cases: leave as-is
  }
}


void CValue::applyBinary(BinaryOp op, CValue other)
{
  if (isSticky()) { return; }
  
  if (other.isSticky()) {
    *this = other;
    return;
  }

  switch (op) {
    default:
      setError(stringc << "cannot const-eval binary operator `" 
                       << toString(op) << "'");
      return;

    // ---- 5.6 ----
    case BIN_MULT:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si * other.si;    break;
        case K_UNSIGNED:   ui = ui * other.ui;    break;
        case K_FLOAT:      f = f * other.f;       break;
      }
      break;

    case BIN_DIV:
      applyUsualArithmeticConversions(other);
      if (other.isZero()) {
        setError("division by zero");
        return;
      }
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si / other.si;    break;
        case K_UNSIGNED:   ui = ui / other.ui;    break;
        case K_FLOAT:      f = f / other.f;       break;
      }
      break;

    case BIN_MOD:
      applyUsualArithmeticConversions(other);
      if (other.isZero()) {
        setError("mod by zero");
        return;
      }
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si % other.si;    break;
        case K_UNSIGNED:   ui = ui % other.ui;    break;
        case K_FLOAT:      setError("mod applied to floating-point args"); return;
      }
      break;

    // ---- 5.7 ----
    case BIN_PLUS:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si + other.si;    break;
        case K_UNSIGNED:   ui = ui + other.ui;    break;
        case K_FLOAT:      f = f + other.f;       break;
      }
      break;

    case BIN_MINUS:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si - other.si;    break;
        case K_UNSIGNED:   ui = ui - other.ui;    break;
        case K_FLOAT:      f = f - other.f;       break;
      }
      break;

    // ---- gcc <? and >? ----
    // guessing that <? and >? behave approximately like '+' in
    // terms of the types
    case BIN_MINIMUM:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case CValue::K_SIGNED:     si = ((si < other.si) ? si : other.si);   break;
        case CValue::K_UNSIGNED:   ui = ((ui < other.ui) ? ui : other.ui);   break;
        case CValue::K_FLOAT:      f = ((f < other.f) ? f : other.f);        break;
      }
      break;

    case BIN_MAXIMUM:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case CValue::K_SIGNED:     si = ((si > other.si) ? si : other.si);   break;
        case CValue::K_UNSIGNED:   ui = ((ui > other.ui) ? ui : other.ui);   break;
        case CValue::K_FLOAT:      f = ((f > other.f) ? f : other.f);        break;
      }
      break;

    // ---- 5.8 ----
    case BIN_LSHIFT:
    case BIN_RSHIFT: {
      this->performIntegralPromotions();
      other.performIntegralPromotions();

      if (kind() == K_FLOAT || other.kind() == K_FLOAT) {
        setError("cannot shift with float types");
        return;
      }

      // get shift amount
      int shamt = 0;
      switch (other.kind()) {
        default: // silence warning
        case K_SIGNED:     shamt = other.si;    break;
        case K_UNSIGNED:   shamt = other.ui;    break;
      }

      // apply it
      if (op == BIN_LSHIFT) {
        switch (kind()) {
          default: // silence warning
          case K_SIGNED:     si <<= shamt;    break;
          case K_UNSIGNED:   ui <<= shamt;    break;
        }
      }
      else {
        switch (kind()) {
          default: // silence warning
          case K_SIGNED:     si >>= shamt;    break;
          case K_UNSIGNED:   ui >>= shamt;    break;
        }
      }
      break;
    }

    // ---- 5.9 ----
    case BIN_LESS:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si < other.si;    break;
        case K_UNSIGNED:   ui = ui < other.ui;    break;
        case K_FLOAT:      ui = f < other.f;       break;
      }
      type = ST_BOOL;
      break;

    case BIN_GREATER:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si > other.si;    break;
        case K_UNSIGNED:   ui = ui > other.ui;    break;
        case K_FLOAT:      ui = f > other.f;       break;
      }
      type = ST_BOOL;
      break;

    case BIN_LESSEQ:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si <= other.si;    break;
        case K_UNSIGNED:   ui = ui <= other.ui;    break;
        case K_FLOAT:      ui = f <= other.f;       break;
      }
      type = ST_BOOL;
      break;

    case BIN_GREATEREQ:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si >= other.si;    break;
        case K_UNSIGNED:   ui = ui >= other.ui;    break;
        case K_FLOAT:      ui = f >= other.f;       break;
      }
      type = ST_BOOL;
      break;

    // ---- 5.10 ----
    case BIN_EQUAL:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si == other.si;    break;
        case K_UNSIGNED:   ui = ui == other.ui;    break;
        case K_FLOAT:      ui = f == other.f;       break;
      }
      type = ST_BOOL;
      break;

    case BIN_NOTEQUAL:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     ui = si != other.si;    break;
        case K_UNSIGNED:   ui = ui != other.ui;    break;
        case K_FLOAT:      ui = f != other.f;       break;
      }
      type = ST_BOOL;
      break;

    // ---- 5.11 ----
    case BIN_BITAND:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si & other.si;    break;
        case K_UNSIGNED:   ui = ui & other.ui;    break;
        case K_FLOAT:      setError("cannot apply bitand to float types"); break;
      }
      break;

    // ---- 5.12 ----
    case BIN_BITXOR:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si ^ other.si;    break;
        case K_UNSIGNED:   ui = ui ^ other.ui;    break;
        case K_FLOAT:      setError("cannot apply bitxor to float types"); break;
      }
      break;

    // ---- 5.13 ----
    case BIN_BITOR:
      applyUsualArithmeticConversions(other);
      switch (kind()) {
        default: // silence warning
        case K_SIGNED:     si = si | other.si;    break;
        case K_UNSIGNED:   ui = ui | other.ui;    break;
        case K_FLOAT:      setError("cannot apply bitor to float types"); break;
      }
      break;

    // ---- 5.14 ----
    case BIN_AND:
      // Note: short-circuit behavior is handled by the caller of
      // this function; by the time we get here, both args have
      // already been evaluated
      this->convertToType(ST_BOOL);
      other.convertToType(ST_BOOL);
      ui = ui && other.ui;
      break;

    // ---- 5.15 ----
    case BIN_OR:
      this->convertToType(ST_BOOL);
      other.convertToType(ST_BOOL);
      ui = ui || other.ui;
      break;
  }
}


string CValue::asString() const
{
  switch (kind()) {
    case K_SIGNED:      return stringc << toString(type) << ": " << si;
    case K_UNSIGNED:    return stringc << toString(type) << ": " << ui;
    case K_FLOAT:       return stringc << toString(type) << ": " << f;
    case K_ERROR:       return stringc << "error: " << *why;
    default:    // silence warning
    case K_DEPENDENT:   return "dependent";
  }
}


// ----------------------- ConstEval ------------------------
ConstEval::ConstEval(Variable *d, MType *m)
  : dependentVar(d),
    map(m)
{}

ConstEval::~ConstEval()
{}


// external interface
CValue Expression::constEval(ConstEval &env) const
{
  #if 1       // production
    return iconstEval(env);
  #else       // debugging
    #warning debugging code is enabled
    CValue ret = iconstEval(env);
    cout << "const-eval of `" << exprToString() << "' is " << ret.asString() << endl;
    return ret;
  #endif
}


CValue Expression::iconstEval(ConstEval &env) const
{
  if (ambiguity) {
    // 2005-05-26: This used to be cause for an assertion failure, but
    // what can happen (in/c/dC0018.c) is that a user-error here or
    // higher up causes the tcheck to prune, thereby leaving an
    // ambiguity link.  What I will do is make this a user-error
    // condition also, under the premise that (due to disambiguation
    // selecting a different interpretation) the user will never see
    // it; but by adding the error, I ensure this interpretation will
    // not part of a successful parse.
    return CValue("ambiguous expr being const-eval'd (user should not see this)");
  }

  if (type->isError()) {
    // don't try to const-eval an expression that failed
    // to typecheck
    return CValue("failed to tcheck");
  }

  ASTSWITCHC(Expression, this) {
    // Handle this idiom for finding a member offset:
    // &((struct scsi_cmnd *)0)->b.q
    ASTCASEC(E_addrOf, eaddr)
      return eaddr->expr->constEvalAddr(env);

    ASTNEXTC(E_boolLit, b)
      CValue ret;
      ret.setBool(b->b);
      return ret;

    ASTNEXTC(E_intLit, i)
      CValue ret;
      SimpleTypeId id = type->asSimpleTypeC()->type;
      switch (CValue::classify(id)) {
        default: xfailure("unexpected intlit type");
        case CValue::K_SIGNED:     ret.setSigned(id, (long)i->i);              break;
        case CValue::K_UNSIGNED:   ret.setUnsigned(id, (unsigned long)i->i);   break;
      }
      return ret;

    ASTNEXTC(E_floatLit, f)
      CValue ret;
      SimpleTypeId id = type->asSimpleTypeC()->type;
      ret.setFloat(id, (float)f->d);
      return ret;

    ASTNEXTC(E_charLit, c)
      CValue ret;
      SimpleTypeId id = type->asSimpleTypeC()->type;
      switch (CValue::classify(id)) {
        default: xfailure("unexpected charlit type");
        case CValue::K_SIGNED:     ret.setSigned(id, (long)c->c);              break;
        case CValue::K_UNSIGNED:   ret.setUnsigned(id, (unsigned long)c->c);   break;
      }
      return ret;

    ASTNEXTC(E_variable, v)
      return env.evaluateE_variable(v);

    ASTNEXTC(E_constructor, c)
      if (type->isIntegerType()) {
        // allow it; should only be 1 arg, and that will be value
        return c->args->first()->constEval(env);
      }
      else {
        return CValue("can only const-eval E_constructors for integer types");
      }

    ASTNEXTC(E_sizeof, s)
      // 5.3.3p6: result is of type 'size_t'; most systems (including my
      // elsa/include/stddef.h header) make that the same as 'unsigned';
      // in any case, it must be an unsigned integer type (c99, 7.17p2)
      CValue ret;
      ret.setUnsigned(ST_UNSIGNED_INT, s->size);
      return ret;

    ASTNEXTC(E_unary, u)
      CValue ret = u->expr->constEval(env);
      ret.applyUnary(u->op);
      return ret;

    ASTNEXTC(E_binary, b)
      if (b->op == BIN_COMMA) {
        // avoid trying to eval the LHS
        return b->e2->constEval(env);
      }

      CValue v1 = b->e1->constEval(env);
      // dmandelin -- fixed bug with template params causing spurious error
      if (b->op == BIN_AND && v1.isZero() && !v1.isSticky()) {
        CValue ret;
        ret.setBool(false);   // short-circuit: propagate false
        return ret;
      }
      // dmandelin -- fixed bug with template params causing spurious error
      if (b->op == BIN_OR && !v1.isZero() && !v1.isSticky()) {
        CValue ret;
        ret.setBool(true);    // short-circuit: propagate false
        return ret;
      }

      CValue v2 = b->e2->constEval(env);

      v1.applyBinary(b->op, v2);
      return v1;

    ASTNEXTC(E_cast, c)
      return constEvalCast(env, c->ctype, c->expr);
      
    ASTNEXTC(E_keywordCast, c)
      if (c->key == CK_DYNAMIC) {
        return CValue("cannot const-eval a keyword_cast");
      }
      else {
        // assume the other three work like C casts
        return constEvalCast(env, c->ctype, c->expr);
      }

    ASTNEXTC(E_cond, c)
      CValue v = c->cond->constEval(env);
      if (v.isSticky()) {
        return v;
      }

      if (!v.isZero()) {
        return c->th->constEval(env);
      }
      else {
        return c->el->constEval(env);
      }

    ASTNEXTC(E_sizeofType, s)
      if (s->atype->getType()->isGeneralizedDependent()) {
        return CValue(ST_DEPENDENT);
      }
      CValue ret;
      ret.setUnsigned(ST_UNSIGNED_INT, s->size);
      return ret;

    ASTNEXTC(E_grouping, e)
      return e->expr->constEval(env);

    ASTDEFAULTC
      return extConstEval(env);

    ASTENDCASEC
  }
}

// The intent of this function is to provide a hook where extensions
// can handle the 'constEval' message for their own AST syntactic
// forms, by overriding this function.  The non-extension nodes use
// the switch statement above, which is more compact.
CValue Expression::extConstEval(ConstEval &env) const
{
  return CValue(stringc << kindName() << " is not constEval'able");
}


CValue ConstEval::evaluateVariable(Variable *var)
{
  if (var->isEnumerator()) {
    CValue ret;
    ret.setSigned(ST_INT, var->getEnumeratorValue());
    return ret;
  }

  if (var->type->isCVAtomicType() &&
      (var->type->asCVAtomicTypeC()->cv & CV_CONST) &&
      var->value) {
    // const variable
    return var->value->constEval(*this);
  }

  if (var->type->isGeneralizedDependent() &&
      var->value) {
    return CValue(ST_DEPENDENT);
  }

  if (var->isTemplateParam()) {
    return CValue(ST_DEPENDENT);
  }

  if (var == dependentVar) {
    // value-dependent expression
    return CValue(ST_DEPENDENT);
  }

  return CValue(stringc
    << "can't const-eval non-const variable `" << var->name << "'");
}


CValue ConstEval::evaluateE_variable(E_variable const *evar)
{
  // try to evaluate the variable directly
  CValue ret = evaluateVariable(evar->var);
  if (!ret.isDependent() || !map) {
    // either we succeeded, or we don't have a map with which
    // further attempts could be made
    return ret;
  }

  // hand this off to applyArgumentMap to deal with; this is where
  // const-eval dives back into the template mess, thus adding itself
  // to the giant ball of mutual dependencies despite my earlier
  // attempts to pull it off to the side...
  STemplateArgument sarg = map->getEnvironment()->
    applyArgumentMapToE_variable(*map, evar);

  // interpret the result
  switch (sarg.kind) {
    default:
      xfailure(stringb(
        "ConstEval::evaluateE_variable: applyArgumentMap return an "
        "unexpected STemplateArgument kind: " << toString(sarg.kind)));

    case STemplateArgument::STA_INT:
      // TODO: Hmmm... I'm sure there can be unsigned template
      // parameters, but applyArgumentMap just throws away the type of
      // the value.  Maybe STemplateArgument should be carrying around
      // a CValue rather than an 'int'?
      ret.setSigned(ST_INT, sarg.getInt());
      return ret;

    case STemplateArgument::STA_ENUMERATOR:
      return evaluateVariable(sarg.getEnumerator());

    case STemplateArgument::STA_DEPEXPR:
      ret.setDependent();
      return ret;
  }
}


// TODO: This function is basically a stub; it does not compute
// the right result in almost any circumstance.
CValue Expression::constEvalAddr(ConstEval &env) const
{
  // FIX: I'm sure there are cases missing from this
  ASTSWITCHC(Expression, this) {
    // These two are dereferences, so they recurse back to constEval().
    ASTCASEC(E_deref, e)
      return e->ptr->constEval(env);

    ASTNEXTC(E_arrow, e)
      return e->obj->constEval(env);

    ASTNEXTC(E_fieldAcc, e) {
      // 2006-06-03: Trying to make this work for in/c/k0014.c.
      CValue val = e->obj->constEvalAddr(env);
      CompoundType *ct = e->obj->type->asRval()->ifCompoundType();
      if (!ct) {
        // probably something weird like a template
      }
      else {
        val.addOffset(ct->getDataMemberOffset(e->field));
      }
      return val;
    }

    // These just recurse on constEvalAddr().
    ASTNEXTC(E_cast, e)
      return e->expr->constEvalAddr(env);

    ASTNEXTC(E_keywordCast, e)
      // FIX: haven't really thought about these carefully
      switch (e->key) {
        default:
          xfailure("bad CastKeyword");

        case CK_DYNAMIC:
          return CValue("can't const-eval dynamic_cast");

        case CK_STATIC:
        case CK_REINTERPRET:
        case CK_CONST:
          return e->expr->constEvalAddr(env);
      }
      break;

    ASTNEXTC(E_grouping, e)
      return e->expr->constEvalAddr(env);

    ASTDEFAULTC
      return CValue("unhandled case in constEvalAddr");

    ASTENDCASEC
  }
}


CValue Expression::constEvalCast(ConstEval &env, ASTTypeId const *ctype,
                                 Expression const *expr) const
{
  CValue ret = expr->constEval(env);
  if (ret.isSticky()) {
    return ret;
  }

  Type *t = ctype->getType();
  if (t->isSimpleType()) {
    ret.convertToType(t->asSimpleTypeC()->type);
  }
  else if (t->isEnumType() ||
           t->isPointer()) {        // for Linux kernel
    ret.convertToType(ST_INT);
  }
  else {
    // TODO: this is probably not the right rule..
    return CValue(stringc
      << "in constant expression, can only cast to arithmetic or pointer types, not `"
      << t->toString() << "'");
  }

  return ret;
}


// EOF
