text: #include <cassert>
  #include <iostream>
  #include <sstream>
  
  #include <FPG/AST.h>
  #include <FPG/Error.h>
  
  
  namespace AST {
  
  void
  requireConvertibleTypes( Type *target, Type *source, const Location& location ) {
      bool b = true;
      try {
          b = source->isConvertible( target );
      } catch( Warning &w ) {
          std::cerr << "warning: " << location.toString() << " " << w.msg << std::endl;
      }
      if( !b )
          throw TypeError( "type `" + source->name() + "` is not convertible to type `" + target->name() + "`", location );
  }
  
  Type*
  Node::getType() {
      if( type )
          return type;
      else
          return type = computeType();
  }
  
  Type*
  LiteralExpression::computeType() {
    return ( val.type == Concrete_value::INTCONST ) ? type_int : type_float;
  }
  
  Type*
  IdentifierExpression::computeType() {
      return var->type;
  }
  
  Type*
  UnaryExpression::computeType() {
      Type *base_type = e->getType();
      switch( kind ) {
      case REF:
          if( base_type == type_float_bound )
              return type_float_bound_ptr;
          else
              throw TypeError("ref operation only applicable to float bounds");
      case DEREF:
          if( base_type == type_float_bound_ptr )
              return type_float_bound;
          else
              throw TypeError("deref operation only applicable to float bound ptr");
      default:
          return base_type;
      }
  }
  
  Type*
  BinaryExpression::computeType() {
  #define TYPE_ERROR       throw TypeError(error_prefix + operators[kind]        , location);
  #define TYPE_ERROR2(msg) throw TypeError(error_prefix + operators[kind]  + " : " + msg , location);
      Type *t1 = e1->getType();
      Type *t2 = e2->getType();
      const std::string error_prefix("invalid operands of types `" + t1->name() + "` and `" + t2->name() + "` to binary operator ");
  
      bool is_int1 =   is_int(t1),
           is_int2 =   is_int(t2);
  
      switch( kind ) {
          case ADD:
          case SUB:
          case MUL:
          case DIV:
              if( is_int1 && is_int2 )
                  return type_int;
              else
                  return type_float;
          case MOD:
          case XOR:
              if( is_int1 && is_int2 )
                  return type_int;
              else
                  TYPE_ERROR
          case AND:
          case OR :
              if( is_int1 && is_int2 )
                  return type_int;
              else
                  TYPE_ERROR
          case EQ :
          case NEQ:
          case GEQ:
          case LEQ:
          case LE :
          case GR :
              // keep in sync with cases ADD...DIV
              return type_int;
          default:
              throw InternalError("bla",location);
      }
  #undef TYPE_ERROR
  #undef TYPE_ERROR2
  }
  
  Type*
  ConditionalExpression::computeType() {
      Type* t0 = cond->getType();
      Type* t1 = e1->getType();
      Type* t2 = e2->getType();
      requireConvertibleTypes( type_int, t0, location );
  
      if( t1 == type_void || t2 == type_void )
          throw TypeError("cond-exp cannot return values of type void", location);
  
      if( is_int(t1) && is_int(t2) )
          return type_int;
      else
          return type_float;
  }
  
  Type*
  AssignmentExpression::computeType() {
      Type *lhs = e1->getType();
      Type *rhs = e2->getType();
  
      requireConvertibleTypes( lhs, rhs, location );
      return lhs;
  }
  
  Type*
  UnaryFunction::computeType() {
      requireConvertibleTypes( type_float, e->getType() );
      switch( kind ) {
      case XABS:
      case XSQRT: return type_float;
      case XSIGN: return type_int;
      default: ;
      }
      return type_void;
  }
  
  // TODO: implement a seperate type check. while transforming the AST, type correctness is violated --> checking disabled temporarily
  Type*
  FunctionCall::computeType() {
  #if 0
      const int arg_count = exp_list->size();
      const int param_count = fun_type->parameters.size();
  
      // check if number of formal parameters matches number of actual parameters
      if( arg_count != param_count ) {
          std::stringstream s;
          s << "function `" << fun_type->id << "` requires " << param_count << " arguments. however, only " << arg_count << "supplied" << std::endl;
          throw TypeError( s.str(), location );
      }
  
      FOREACH_PARAMETER_ARGUMENT_PAIR {
          Variable *v = *param_iter;
          Type *lhs = v->type;
          Type *rhs = (*arg_iter)->getType();
          requireConvertibleTypes( lhs, rhs, location );
      }
  #endif
      return fun_type->getReturnType();
  }
  
  Type*
  ConditionalStatement::computeType() {
      Type *cond_type = cond->getType();
      requireConvertibleTypes( type_int, cond_type, location );
      then_branch->getType();
      if( else_branch )
          else_branch->getType();
      return type_void;
  }
  
  Type*
  Return::computeType() {
      Type *t;
      if( e )
          t = e->getType();
      else
          t = type_void;
      if( return_type )
          requireConvertibleTypes( return_type, t, location );
      return t;
  }
  
  
  Type*
  StatementList::computeType() {
      FOREACH_STATEMENT {
          Statement *stmt = *it;
          assert( stmt != nullptr );
          stmt->getType();
      }
      return type_void;
  }
  
  
  Type*
  FunctionDefinition::computeType() {
      return_type = type->return_type;
      body->getType();
      return_type = nullptr;
      return type_void;
  }
  
  
  Type*
  TranslationUnit::computeType() {
      global_statements->getType();
      FOREACH_FUNCTION
          (*it)->getType();
      return type_void;
  }
  
  }
  