/*******************************************************************\

Module: ANSI-C Conversion / Type Checking

Author: Daniel Kroening, kroening@cs.cmu.edu

\*******************************************************************/

#include <std_types.h>
#include <prefix.h>

#include "c_typecheck_base.h"
#include "expr2c.h"

/*******************************************************************\

Function: c_typecheck_baset::to_string

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

std::string c_typecheck_baset::to_string(const exprt &expr)
{ 
  return expr2c(expr, *this);
}

/*******************************************************************\

Function: c_typecheck_baset::to_string

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

std::string c_typecheck_baset::to_string(const typet &type)
{ 
  return type2c(type, *this);
}

/*******************************************************************\

Function: c_typecheck_baset::move_symbol

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool c_typecheck_baset::move_symbol(symbolt &symbol, symbolt *&new_symbol)
{
  symbol.mode=mode;
  symbol.module=module;
  return context.move(symbol, new_symbol);
}

/*******************************************************************\

Function: c_typecheck_baset::typecheck_symbol

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void c_typecheck_baset::typecheck_symbol(symbolt &symbol)
{
  // set a few flags
  symbol.lvalue=!symbol.is_type && !symbol.is_macro;
    
  std::string prefix="c::";
  std::string root_name=prefix+id2string(symbol.base_name);
  std::string new_name=id2string(symbol.name);
  
  if(symbol.name==root_name)
  {
    if(symbol.file_local)
      new_name=prefix+module+"::"+id2string(symbol.base_name);
  }
  else if(symbol.is_type &&
          has_prefix(id2string(symbol.name), prefix+"tag-#anon"))
  {
    // anon-tags are file-local
    new_name=prefix+module+"::tag-"+id2string(symbol.base_name);
  }
  else
  {    
    if(symbol.is_extern)
    {
      // variables mared as "extern" go into the global namespace
      // and have static lifetime
      new_name=root_name;
      symbol.static_lifetime=true;
    }
    else if(symbol.type.id()=="code")
    {
      // functions go into the global namespace
      new_name=root_name;
    }
  }
  
  if(symbol.name!=new_name)
  {
    id_replace_map[symbol.name]=new_name;
    symbol.name=new_name;
  }
  
  // set the pretty name
  if(symbol.is_type &&
     (symbol.type.id()=="struct" ||
      symbol.type.id()=="incomplete_struct"))
  {
    symbol.pretty_name="struct "+id2string(symbol.base_name);
  }
  else if(symbol.is_type &&
          (symbol.type.id()=="union" ||
           symbol.type.id()=="incomplete_union"))
  {
    symbol.pretty_name="union "+id2string(symbol.base_name);
  }
  else if(symbol.is_type &&
          (symbol.type.id()=="c_enum" ||
           symbol.type.id()=="incomplete_c_enum"))
  {
    symbol.pretty_name="enum "+id2string(symbol.base_name);
  }
  else
  {
    // just strip the c::
    symbol.pretty_name=
      std::string(new_name, prefix.size(), std::string::npos);
  }
  
  // see if we have it already
  symbolst::iterator old_it=context.symbols.find(symbol.name);
  
  if(old_it==context.symbols.end())
  {
    // just put into context
    symbolt *new_symbol;
    assert(!move_symbol(symbol, new_symbol));
    
    typecheck_new_symbol(*new_symbol);
  }    
  else
    typecheck_symbol_redefinition(old_it->second, symbol);
}

/*******************************************************************\

Function: c_typecheck_baset::typecheck_new_symbol

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void c_typecheck_baset::typecheck_new_symbol(symbolt &symbol)
{
  // check the type
  typecheck_type(symbol.type);
  
  if(symbol.is_actual)
    adjust_function_argument(symbol.type);

  // check initializer, if needed

  if(symbol.type.id()=="code")
  {
    if(symbol.value.is_not_nil())
      typecheck_function_body(symbol);
    else
    {
      // we don't need the identifiers
      code_typet &code_type=to_code_type(symbol.type);
      for(code_typet::argumentst::iterator
          it=code_type.arguments().begin();
          it!=code_type.arguments().end();
          it++)
        it->set_identifier("");
    }
  }
  else
  {
    // check the initializer
    do_initializer(symbol);
  }
}

/*******************************************************************\

Function: c_typecheck_baset::typecheck_symbol_redefinition

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void c_typecheck_baset::typecheck_symbol_redefinition(
  symbolt &old_symbol,
  symbolt &new_symbol)
{
  // check the type
  typecheck_type(new_symbol.type);
  
  const typet &final_old=follow(old_symbol.type);
  const typet &final_new=follow(new_symbol.type);
  
  // K&R stuff?
  if(old_symbol.type.id()=="KnR")
  {
    // check the type
    if(final_new.id()=="code")
    {
      err_location(new_symbol.location);
      throw "function type not allowed for K&R function argument";
    }
    
    // fix up old symbol -- we now got the type
    old_symbol.type=new_symbol.type;
  }
  else if(old_symbol.is_type)
  {
    // see if we had s.th. incomplete before
    if(old_symbol.type.id()=="incomplete_struct" ||
       old_symbol.type.id()=="incomplete_union" ||
       old_symbol.type.id()=="incomplete_c_enum")
    {
      // new one complete?
      if("incomplete_"+new_symbol.type.id_string()==old_symbol.type.id_string())
      {
        // overwrite location
        old_symbol.location=new_symbol.location;
        
        // move body
        old_symbol.type.swap(new_symbol.type);
      }
      else if(new_symbol.type.id()==old_symbol.type.id())
        return;
      else
      {
        err_location(new_symbol.location);
        err << "error: conflicting defintion of type symbol `"
            << new_symbol.display_name()
            << "'";
        throw 0;
      }
    }
    else
    {
      // see if new one is just a tag
      if(new_symbol.type.id()=="incomplete_struct" ||
         new_symbol.type.id()=="incomplete_union" ||
         new_symbol.type.id()=="incomplete_c_enum")
      {
        if("incomplete_"+old_symbol.type.id_string()==new_symbol.type.id_string())
        {
          // just ignore silently  
        }
        else
        {
          // arg! new tag type
          err_location(new_symbol.location);
          err << "error: conflicting defintion of tag symbol `"
              << new_symbol.display_name()
              << "'";
          throw 0;
        }
      }
      else
      {
        // see if it changed
        if(new_symbol.type!=old_symbol.type)
        {
          err_location(new_symbol.location);
          err << "error: type symbol `" << new_symbol.display_name()
              << "' defined twice:" << std::endl;
          err << "Original: " << to_string(old_symbol.type) << std::endl;
          err << "     New: " << to_string(new_symbol.type);
          throw 0;
        }
      }
    }
  }
  else
  {
    if(final_old!=final_new)
    {
      if(old_symbol.type.id()=="array" &&
         new_symbol.type.id()=="incomplete_array" &&
         old_symbol.type.subtype()==new_symbol.type.subtype())
      {
        // this is ok
        new_symbol.type=old_symbol.type;
      }
      else if((final_old.id()=="incomplete_c_enum" ||
               final_old.id()=="c_enum") &&
              (final_new.id()=="incomplete_c_enum" ||
               final_new.id()=="c_enum"))
      {
        // this is ok for now
      }
      else
      {
        err_location(new_symbol.location);
        err << "error: symbol `" << new_symbol.display_name()
            << "' defined twice with different types:" << std::endl;
        err << "Original: " << to_string(old_symbol.type) << std::endl;
        err << "     New: " << to_string(new_symbol.type);
        throw 0;
      }
    }
    
    // do value
    if(old_symbol.type.id()=="code")
    {
      if(new_symbol.value.is_not_nil())
      {      
        if(old_symbol.value.is_not_nil())
        {
          err_location(new_symbol.location);
          err << "function `" << new_symbol.display_name()
              << "' defined twice";
          error();
        }
        else
        {
          typecheck_function_body(new_symbol);
        
          // overwrite location
          old_symbol.location=new_symbol.location;
        
          // move body
          old_symbol.value.swap(new_symbol.value);

          // overwrite type (because of argument names)
          old_symbol.type=new_symbol.type;
        }
      }
    }
    else
    {
      // initializer
      do_initializer(new_symbol);
      
      if(new_symbol.value.is_not_nil())
      {
        // see if we already have one
        if(old_symbol.value.is_not_nil())
        {
          if(new_symbol.value.get_bool("#zero_initializer"))
          {
            // do nothing
          }
          else if(old_symbol.value.get_bool("#zero_initializer"))
          {
            old_symbol.value=new_symbol.value;
            old_symbol.type=new_symbol.type;
          }
          else
          {
            if(new_symbol.is_macro &&
               (final_new.id()=="incomplete_c_enum" ||
                final_new.id()=="c_enum") &&
                old_symbol.value.is_constant() &&
                new_symbol.value.is_constant() &&
                old_symbol.value.get("value")==new_symbol.value.get("value"))
            {
              // ignore
            }
            else
            {
              err_location(new_symbol.value);
              err << "symbol `" << new_symbol.display_name()
                  << "' already has an initial value";
              warning();
            }
          }
        }
        else
        {
          old_symbol.value=new_symbol.value;
          old_symbol.type=new_symbol.type;
        }
      }
    }
  }
}

/*******************************************************************\

Function: c_typecheck_baset::typecheck_function_body

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void c_typecheck_baset::typecheck_function_body(symbolt &symbol)
{
  const code_typet &code_type=to_code_type(symbol.type);
    
  assert(symbol.value.is_not_nil());

  // fix type
  symbol.value.type()=code_type;
    
  // set return type
  return_type=code_type.return_type();
  
  typecheck_code(to_code(symbol.value));
  
  if(symbol.name=="c::main")
    add_argc_argv(symbol);
}
