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

Module: C++ Language Type Checking

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

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

#include <assert.h>

#include <arith_tools.h>
#include <bitvector.h>
#include <config.h>
#include <expr_util.h>
#include <std_types.h>
#include <prefix.h>
#include <cprover_prefix.h>
#include <simplify_expr.h>
#include <base_type.h>

#include "c_types.h"
#include "c_typecast.h"
#include "c_typecheck_base.h"
#include "c_sizeof.h"

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

Function: c_typecheck_baset::typecheck_expr

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr(exprt &expr)
{
  if(expr.id()=="already_typechecked")
  {
    assert(expr.operands().size()==1);
    exprt tmp;
    tmp.swap(expr.op0());
    expr.swap(tmp);
    return;
  }

  // fist do sub-nodes
  typecheck_expr_operands(expr);  

  // now do case-split
  typecheck_expr_main(expr);
}
  
/*******************************************************************\

Function: c_typecheck_baset::typecheck_expr_main

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_main(exprt &expr)
{
  if(expr.id()=="sideeffect")
    typecheck_expr_sideeffect(expr);
  else if(expr.id()=="constant")
    typecheck_expr_constant(expr);
  else if(expr.id()=="infinit")
  {
    // ignore
  }
  else if(expr.id()=="symbol")
    typecheck_expr_symbol(expr);
  else if(expr.id()=="unary+" || expr.id()=="unary-" ||
          expr.id()=="bitnot")
    typecheck_expr_unary_arithmetic(expr);
  else if(expr.id()=="not")
    typecheck_expr_unary_boolean(expr);
  else if(expr.id()=="and" || expr.id()=="or")
    typecheck_expr_binary_boolean(expr);
  else if(expr.id()=="address_of")
    typecheck_expr_address_of(expr);
  else if(expr.id()=="dereference")
    typecheck_expr_dereference(expr);
  else if(expr.id()=="member")
    typecheck_expr_member(expr);
  else if(expr.id()=="ptrmember")
    typecheck_expr_ptrmember(expr);
  else if(expr.id()=="="  ||
          expr.id()=="notequal" ||
          expr.id()=="<"  ||
          expr.id()=="<=" ||
          expr.id()==">"  ||
          expr.id()==">=")
    typecheck_expr_rel(expr);
  else if(expr.id()=="index")
    typecheck_expr_index(expr);
  else if(expr.id()=="typecast")
    typecheck_expr_typecast(expr);
  else if(expr.id()=="sizeof")
    typecheck_expr_sizeof(expr);
  else if(expr.id()=="+" || expr.id()=="-" ||
          expr.id()=="*" || expr.id()=="/" ||
          expr.id()=="mod" ||
          expr.id()=="shl" || expr.id()=="shr" ||
          expr.id()=="bitand" || expr.id()=="bitxor" || expr.id()=="bitor")
    typecheck_expr_binary_arithmetic(expr);
  else if(expr.id()=="comma")
    typecheck_expr_comma(expr);
  else if(expr.id()=="if")
    typecheck_expr_trinary(expr);
  else if(expr.id()=="code")
    typecheck_code(to_code(expr));
  else if(expr.id()=="string-constant")
  {
    // already fine
  }
  else
  {
    err_location(expr);
    err << "unexpected expression: " << expr.pretty();
    throw 0;
  }
}

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

Function: c_typecheck_baset::typecheck_expr_comma

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_comma(exprt &expr)
{
  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects two operands";
    throw 0;
  }

  expr.type()=expr.op1().type();
  
  // make this an l-value if the last operand is one
  if(expr.op1().get_bool("#lvalue"))
    expr.set("#lvalue", true);
}

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

Function: c_typecheck_baset::typecheck_expr_operands

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_operands(exprt &expr)
{
  if(expr.id()=="sideeffect" &&
     expr.get("statement")=="functioncall")
  {
    // don't do first operand
    for(unsigned i=1; i<expr.operands().size(); i++)
      typecheck_expr(expr.operands()[i]);
  }
  else
  {
    Forall_operands(it, expr)
      typecheck_expr(*it);
  }
}

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

Function: c_typecheck_baset::typecheck_expr_symbol

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_symbol(exprt &expr)
{
  // adjust identifier, if needed
  replace_symbol(expr);

  const irep_idt &identifier=expr.get("identifier");
  
  // look it up
  symbolst::const_iterator s_it=context.symbols.find(identifier);
  
  if(s_it==context.symbols.end())
  {
    err_location(expr);
    err << "failed to find symbol `" << identifier << "'";
    throw 0;
  }
  
  // found it
  const symbolt &symbol=s_it->second;
  
  if(symbol.is_type)
  {
    err_location(expr);
    err << "did not expect a type symbol here, but got `"
        << symbol.display_name() << "'";
    throw 0;
  }
  
  // save location
  locationt location=expr.location();

  if(symbol.is_macro)
  {
    expr=symbol.value;

    // put it back
    expr.location()=location;
  }
  else if(has_prefix(id2string(identifier), CPROVER_PREFIX "constant_infinity"))
  {
    expr=exprt("infinity", symbol.type);

    // put it back
    expr.location()=location;
  }
  else
  {
    expr=symbol_expr(symbol);

    // put it back
    expr.location()=location;

    if(symbol.lvalue)
      expr.set("#lvalue", true);

    if(expr.type().id()=="code") // function designator
    { // special case: this is sugar for &f
      exprt tmp("address_of");
      tmp.set("#implicit", true);
      tmp.type().id("pointer");
      tmp.type().subtype()=expr.type();
      tmp.location()=expr.location();
      tmp.move_to_operands(expr);
      expr.swap(tmp);
    }
  }
}

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

Function: c_typecheck_baset::typecheck_sideeffect_statement_expression

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_sideeffect_statement_expression(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    err << "statement expression "
        << "expects one operand";
    throw 0;
  }

  codet &code=to_code(expr.op0());

  typecheck_code(code);

  assert(code.get("statement")=="block");

  // the type is the type of the last statement in the
  // block
  const codet &last=to_code(code.operands().back());

  if(last.get_statement()=="expression")
  {
    assert(last.operands().size()==1);
    expr.type()=last.op0().type();
  }
  else
    expr.type()=typet("empty");
}

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

Function: c_typecheck_baset::typecheck_expr_sizeof

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_sizeof(exprt &expr)
{
  typet type;

  if(expr.operands().size()==0)
  {
    type.swap((typet &)expr.add("sizeof-type"));
    typecheck_type(type);
  }
  else if(expr.operands().size()==1)
  {
    type.swap(expr.op0().type());
  }
  else
  {
    err_location(expr);
    err << "sizeof operator expects zero or one operand, "
           "but got " << expr.operands().size();
    throw 0;
  }

  #if 0
  typet type;

  if(expression.sizeofType!=NULL)
    convert(*expression.sizeofType, expression.location, type);
  else if(expression.expr!=NULL)
  {
    exprt expr;
    convert(*expression.expr, expr);

    // special case: ANSI-C 99 section 6.3.2.1 paragraph 4

    if(expr.id()=="address_of" &&
       expr.get_bool("#implicit") &&
       expr.operands().size()==1 &&
       expr.op0().id()=="symbol" &&
       expr.op0().type().id()=="code")
    {
      // undo implicit address_of
      exprt tmp;
      tmp.swap(expr.op0());
      expr.swap(tmp);
    }

    type=expr.type();
  }
  else
  {
    err << "sizeof without parameter: " << expr;
    throw 0;
  }
  #endif

  c_sizeoft c_sizeof(*this);

  exprt new_expr=c_sizeof(type);

  if(new_expr.is_nil())
  {
    err_location(expr);
    err << "type has no size: "
        << to_string(type);
    throw 0;
  }
  
  new_expr.swap(expr);
  
  expr.add("#c_sizeof_type")=type;
}

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

Function: c_typecheck_baset::typecheck_expr_typecast

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_typecast(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    error("typecast operator expects one operand");
    throw 0;
  }

  exprt &op=expr.op0();

  typecheck_type(expr.type());
  
  const typet expr_type=follow(expr.type());

  if(!is_number(expr_type) &&
     expr_type.id()!="bool" &&
     expr_type.id()!="pointer" &&
     expr_type.id()!="array" &&
     expr_type.id()!="empty" &&
     expr_type.id()!="c_enum" &&
     expr_type.id()!="incomplete_c_enum")
  {
    err_location(expr);
    err << "type cast is not of scalar or void type, but got `"
        << to_string(expr.type()) << "'";
    throw 0;
  }
  
  const typet op_type=follow(op.type());
  
  if(is_number(op_type) ||
     op_type.id()=="c_enum" ||
     op_type.id()=="incomplete_c_enum" ||
     op_type.id()=="bool" ||
     op_type.id()=="pointer")
  {
  }
  else if(op_type.id()=="array" ||
          op_type.id()=="incomplete_array")
  {
    op=gen_address_of(gen_index(op, gen_zero(index_type())));
  }
  else if(op_type.id()=="empty")
  {
    if(expr_type.id()!="empty")
    {
      err_location(expr);
      err << "type cast from void only permitted to void, but got `"
          << to_string(expr.type()) << "'";
      throw 0;
    }
  }
  else
  {
    err_location(expr);
    err << "type cast operand is not of scalar or void type, but is `"
        << to_string(op_type) << "'";
    throw 0;
  }

  // special case: NULL

  if(expr_type.id()=="pointer" &&
     op.is_zero())
  {
    // zero typecasted to a pointer is NULL
    expr.id("constant");
    expr.set("value", "NULL");
    expr.remove("operands");
    return;
  }
}

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

Function: c_typecheck_baset::make_index_type

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::make_index_type(exprt &expr)
{
  const typet &full_type=follow(expr.type());

  if(full_type.id()=="bool")
  {
    expr.make_typecast(index_type());
  }
  else if(full_type.id()=="unsignedbv")
  {
    unsigned width=bv_width(expr.type());
    
    if(width!=config.ansi_c.int_width)
      expr.make_typecast(uint_type());
  }
  else if(full_type.id()=="signedbv" ||
          full_type.id()=="c_enum" ||
          full_type.id()=="incomplete_c_enum")
  {
    if(full_type!=index_type())
      expr.make_typecast(index_type());
  }
  else
  {
    err_location(expr);
    err << "expected integer type, but got `"
        << to_string(full_type) << "'";
    throw 0;
  }
}

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

Function: c_typecheck_baset::typecheck_expr_index

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_index(exprt &expr)
{
  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects two operands";
    throw 0;
  }

  exprt &array_expr=expr.op0();
  exprt &index_expr=expr.op1();
  
  make_index_type(index_expr);
  
  const typet &final_array_type=follow(array_expr.type());

  if(final_array_type.id()=="array" ||
     final_array_type.id()=="incomplete_array")
  {
    if(array_expr.get_bool("#lvalue"))
      expr.set("#lvalue", true);
  }
  else if(final_array_type.id()=="pointer")
  {
    // p[i] is syntactic sugar for *(p+i)

    exprt addition("+", array_expr.type());
    addition.operands().swap(expr.operands());
    expr.move_to_operands(addition);    
    expr.id("dereference");
    expr.set("#lvalue", true);
  }
  else
  {
    err_location(expr);
    err << "operator [] must take array or pointer but got `"
        << to_string(array_expr.type()) << "'";
    throw 0;
  }

  expr.type()=array_expr.type().subtype();
}

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

Function: c_typecheck_baset::typecheck_expr_rel

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_rel(exprt &expr)
{
  expr.type()=typet("bool");

  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects two operands";
    throw 0;
  }

  exprt &op0=expr.op0();
  exprt &op1=expr.op1();

  const typet o_type0=op0.type();
  const typet o_type1=op1.type();

  if(expr.id()=="=" || expr.id()=="notequal")
  {
    if(follow(o_type0)==follow(o_type1))
    {
      const typet &final_type=follow(o_type0);
      if(final_type.id()!="array" &&
         final_type.id()!="incomplete_array" &&
         final_type.id()!="incomplete_struct")
        return; // no promotion necessary
    }
  }
  
  implicit_typecast_arithmetic(op0, op1);

  const typet &type0=op0.type();
  const typet &type1=op1.type();

  if(type0==type1)
  {
    if(is_number(type0))
      return;

    if(type0.id()=="pointer")
    {
      if(expr.id()=="=" || expr.id()=="notequal")
        return;

      if(expr.id()=="<=" || expr.id()=="<" ||
         expr.id()==">=" || expr.id()==">")
        return;
    }

    if(type0.id()=="string-constant")
    {
      if(expr.id()=="=" || expr.id()=="notequal")
        return;
    }
  }
  else
  {
    // pointer and zero
    if(type0.id()=="pointer" && op1.is_zero())
    {
      op1=exprt("constant", type0);
      op1.set("value", "NULL");
      return;
    }

    if(type1.id()=="pointer" && op0.is_zero())
    {
      op0=exprt("constant", type1);
      op0.set("value", "NULL");
      return;
    }
    
    // pointer and integer
    if(type0.id()=="pointer" && is_number(type1))
    {
      op1.make_typecast(type0);
      return;
    }

    if(type1.id()=="pointer" && is_number(type0))
    {
      op0.make_typecast(type1);
      return;
    }
    
    if(type0.id()=="pointer" && type1.id()=="pointer")
    {
      op1.make_typecast(type0);
      return;
    }
  }

  err_location(expr);
  err << "operator `" << expr.id_string()
      << "' not defined for types `"
      << to_string(o_type0) << "' and `"
      << to_string(o_type1) << "'";
  throw 0;
}

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

Function: c_typecheck_baset::typecheck_expr_ptrmember

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_ptrmember(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    error("ptrmember operator expects one operand");
    throw 0;
  }

  const typet &final_op0_type=follow(expr.op0().type());

  if(final_op0_type.id()!="pointer" &&
     final_op0_type.id()!="array")
  {
    err_location(expr);
    err << "ptrmember operator requires pointer type "
           "on left hand side, but got `"
        << to_string(expr.op0().type()) << "'";
    throw 0;
  }

  // turn x->y into (*x).y
  
  exprt deref("dereference");
  deref.move_to_operands(expr.op0());
  deref.location()=expr.location();
  
  typecheck_expr_dereference(deref);
  
  expr.op0().swap(deref);

  expr.id("member");
  typecheck_expr_member(expr);
}

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

Function: c_typecheck_baset::typecheck_expr_member

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_member(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    error("member operator expects one operand");
    throw 0;
  }

  exprt &op0=expr.op0();

  typet type=op0.type();

  follow_symbol(type);
  
  if(type.id()=="incomplete_struct")
  {
    err_location(expr);
    err << "member operator got incomplete structure type "
           "on left hand side";
    throw 0;
  }

  if(type.id()!="struct" &&
     type.id()!="union" &&
     type.id()!="class")
  {
    err_location(expr);
    err << "member operator requires structure type "
           "on left hand side but got `"
        << to_string(type) << "'";
    throw 0;
  }

  const irep_idt &component_name=
    expr.get("component_name");

  const irept &components=
    type.find("components");

  irept component;

  component.make_nil();

  forall_irep(it, components.get_sub())
    if(it->get("name")==component_name)
    {
      component=*it;
      break;
    }

  if(component.is_nil())
  {
    err_location(expr);
    err << "member `" << component_name
        << "' not found";
    throw 0;
  }

  const irep_idt &access=
    component.get("access");

  if(access=="private")
  {
    err_location(expr);
    err << "member `" << component_name
        << "' is " << access;
    throw 0;
  }

  expr.type()=(typet &)component.find("type");

  if(op0.get_bool("#lvalue"))
    expr.set("#lvalue", true);

  if(op0.get_bool("#constant"))
    expr.set("#constant", true);
  
  // copy method identifier
  const irep_idt &identifier=component.get("#identifier");

  if(identifier!="")
    expr.set("#identifier", identifier);
}

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

Function: c_typecheck_baset::typecheck_expr_trinary

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_trinary(exprt &expr)
{
  exprt::operandst &operands=expr.operands();

  if(operands.size()!=3)
  {
    err_location(expr);
    error("Boolean operator ?: expects three operands");
    throw 0;
  }

  const typet o_type0=expr.op0().type();
  const typet o_type1=expr.op1().type();
  const typet o_type2=expr.operands()[2].type();

  implicit_typecast_bool(expr.op0());
  implicit_typecast_arithmetic(operands[1], operands[2]);
  
  if(operands[1].type().id()=="pointer" &&
     operands[2].type().id()!="pointer")
    implicit_typecast(operands[2], operands[1].type());
  else if(operands[2].type().id()=="pointer" &&
          operands[1].type().id()!="pointer")
    implicit_typecast(operands[1], operands[2].type());

  if(operands[1].type().id()=="pointer" &&
     operands[2].type().id()=="pointer" &&
     operands[1].type()!=operands[2].type())
  {
    // make it void *
    expr.type()=typet("pointer");
    expr.type().subtype()=typet("empty");
    implicit_typecast(operands[1], expr.type());
    implicit_typecast(operands[2], expr.type());
  }

  if(operands[1].type()==operands[2].type())
  {
    expr.type()=operands[1].type();
    return;
  }

  err_location(expr);
  err << "operator ?: not defined for types `"
      << to_string(o_type0) << "' and `"
      << to_string(o_type1) << "'";
  throw 0;
}

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

Function: c_typecheck_baset::typecheck_expr_address_of

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_address_of(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    error("unary operator & expects one operand");
    throw 0;
  }

  exprt &op=expr.op0();
  
  // special case: address of function designator
  // ANSI-C 99 section 6.3.2.1 paragraph 4

  if(op.id()=="address_of" &&
     op.get_bool("#implicit") &&
     op.operands().size()==1 &&
     op.op0().id()=="symbol" &&
     op.op0().type().id()=="code")
  {
    // make the implicit address_of an explicit address_of
    exprt tmp;
    tmp.swap(op);
    tmp.set("#implicit", false);
    expr.swap(tmp);
    return;
  }

  expr.type()=typet("pointer");

  if(op.type().id()!="code" &&
     !op.get_bool("#lvalue"))
  {
    err_location(expr);
    err << "address_of error: `" << to_string(op)
        << "' not an lvalue";
    throw 0;
  }

  // turn &array into &(array[0])  
  if(op.type().id()=="array")
  {
    exprt index=gen_index(op, gen_zero(index_type()));
    index.location()=expr.location();
    op.swap(index);
  }

  expr.type().subtype()=op.type();
}

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

Function: c_typecheck_baset::typecheck_expr_dereference

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_dereference(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    err << "unary operator * expects one operand";
    throw 0;
  }

  exprt &op=expr.op0();
  
  const typet op_type=follow(op.type());

  if(op_type.id()=="array" ||
     op_type.id()=="incomplete_array")
  {
    // *a is the same as a[0]
    expr.id("index");
    expr.type()=op_type.subtype();
    expr.copy_to_operands(gen_zero(index_type()));
    assert(expr.operands().size()==2);
  }
  else if(op_type.id()=="pointer")
  {
    if(op_type.subtype().id()=="empty")
    {
      err_location(expr);
      error("operand of unary * is a void * pointer");
      throw 0;
    }

    expr.type()=op_type.subtype();
  }
  else
  {
    err_location(expr);
    err << "operand of unary * `" << to_string(op)
        << "' is not a pointer";
    throw 0;
  }

  expr.set("#lvalue", true);

  // if you dereference a pointer pointing to
  // a function, you get a pointer again
  // allowing ******...*p

  typecheck_expr_function_identifier(expr);
}
 
/*******************************************************************\

Function: c_typecheck_baset::typecheck_expr_function_identifier

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_function_identifier(exprt &expr)
{
  if(expr.type().id()=="code")
  {
    exprt tmp("address_of");
    tmp.set("#implicit", true);
    tmp.type().id("pointer");
    tmp.type().subtype()=expr.type();
    tmp.location()=expr.location();
    tmp.move_to_operands(expr);
    expr.swap(tmp);
  }
}

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

Function: c_typecheck_baset::typecheck_expr_sideeffect

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_sideeffect(exprt &expr)
{
  const irep_idt &statement=expr.get("statement");

  if(statement=="preincrement" ||
     statement=="predecrement" ||
     statement=="postincrement" ||
     statement=="postdecrement")
  {
    if(expr.operands().size()!=1)
    {
      err_location(expr);
      err << statement << "operator expects one operand";
    }

    const exprt &op0=expr.op0();
    const typet &type0=op0.type();
    const typet &final_type0=follow(type0);

    if(!op0.get_bool("#lvalue"))
    {
      err_location(op0);
      err << "prefix operator error: `" << to_string(op0)
          << "' not an lvalue";
      throw 0;
    }

    if(type0.get_bool("#constant"))
    {
      err_location(op0);
      err << "error: `" << to_string(op0)
          << "' is constant";
      throw 0;
    }

    if(is_number(final_type0) ||
       final_type0.id()=="bool" ||
       final_type0.id()=="c_enum" ||
       final_type0.id()=="incomplete_c_enum" ||
       final_type0.id()=="pointer")
    {
      expr.type()=type0;
    }
    else
    {
      err_location(expr);
      err << "operator `" << statement
          << "' not defined for type `"
          << to_string(type0) << "'";
      throw 0;
    }
  }
  else if(has_prefix(id2string(statement), "assign"))
    typecheck_sideeffect_assignment(expr);
  else if(statement=="functioncall")
    typecheck_sideeffect_functioncall(expr);
  else if(statement=="statement_expression")
    typecheck_sideeffect_statement_expression(expr);
  else
  {
    err_location(expr);
    err << "unknown sideeffect: " << statement;
    throw 0;
  }
}

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

Function: c_typecheck_baset::typecheck_sideeffect_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_sideeffect_functioncall(exprt &expr)
{
  if(expr.operands().size()<1)
  {
    err_location(expr);
    throw "expected at least one operand";
  }

  exprt &f_op=expr.op0();

  // f_op is not yet typechecked, in contrast to the other arguments
  // this is a big special case

  if(f_op.id()=="symbol")
  {
    replace_symbol(f_op);
    
    if(context.symbols.find(f_op.get("identifier"))==context.symbols.end())
    {
      // maybe this is an undeclared function
      // let's just add it
      const irep_idt &identifier=f_op.get("identifier");
      
      symbolt new_symbol;

      new_symbol.name=identifier;
      new_symbol.location=expr.location();
      new_symbol.type=code_typet();
      new_symbol.type.set("#incomplete", true);
      new_symbol.type.add("return_type")=int_type();
      // TODO: should add arguments

      symbolt *symbol_ptr;
      assert(!move_symbol(new_symbol, symbol_ptr));
      
      err_location(f_op);
      err << "function `" << identifier << "' is not declared";
      warning();
    }
  }
  
  // typecheck it now
  typecheck_expr(f_op);
  
  const typet f_op_type=follow(f_op.type());

  if(f_op_type.id()!="pointer")
  {
    err_location(f_op);
    err << "expected function/function pointer as argument but got `"
        << to_string(f_op_type) << "'";
    throw 0;
  }

  // do implicit dereference
  if(f_op.id()=="address_of" &&
     f_op.get_bool("#implicit") &&
     f_op.operands().size()==1)
  {
    exprt tmp;
    tmp.swap(f_op.op0());
    f_op.swap(tmp);
  }
  else
  {
    exprt tmp("dereference", f_op_type.subtype());
    tmp.set("#implicit", true);
    tmp.location()=f_op.location();
    tmp.move_to_operands(f_op);
    f_op.swap(tmp);
  }

  if(f_op.type().id()!="code")
  {
    err_location(f_op);
    throw "expected code as argument";
  }

  const code_typet &code_type=to_code_type(f_op.type());

  expr.type()=code_type.return_type();
  
  typecheck_functioncall_arguments(expr);
}

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

Function: c_typecheck_baset::typecheck_functioncall_arguments

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_functioncall_arguments(exprt &expr)
{
  exprt &f_op=expr.op0();
  const code_typet &code_type=to_code_type(f_op.type());
  const code_typet::argumentst &arguments=
    code_type.arguments();
  
  if(code_type.get_bool("#incomplete"))
  {
    // can't check...
    return;
  }
  
  // no. of arguments test

  if(code_type.has_ellipsis())
  {
    if(arguments.size()>
       expr.operands().size()-1)
    {
      err_location(expr);
      throw "not enough arguments";
    }
  }
  else if(arguments.size()!=
          expr.operands().size()-1)
  {
    err_location(expr);
    err << "wrong number of arguments: "
        << "expected " << arguments.size()
        << ", but got " << (expr.operands().size()-1);
    throw 0;
  }

  unsigned i=1;

  for(code_typet::argumentst::const_iterator
      it=arguments.begin();
      it!=arguments.end();
      it++)
  {
    exprt &op=expr.operands()[i];

    const typet &op_type=(const typet &)(*it).find("type");

    if(op_type.id()=="bool" &&
       op.id()=="sideeffect" &&
       op.get("statement")=="assign" &&
       op.type().id()!="bool")
    {
      err_location(expr);
      warning("assignment where Boolean argument is expected");
    }

    implicit_typecast(op, op_type);

    i++;
  }
}

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

Function: c_typecheck_baset::typecheck_expr_constant

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_constant(exprt &expr)
{
  const typet &type=expr.type();

  if(type.id()=="integer")
  {
    // save location
    locationt location=expr.location();
  
    mp_integer value=string2integer(expr.get_string("value"));
    const std::string &given_width=expr.type().get_string("#width");
    bool is_unsigned=expr.type().get_bool("#unsigned");
    bool is_hex_or_oct=expr.type().get_bool("#hex_or_oct");
    const std::string cformat=expr.get_string("#cformat");

    if(value<0)
      throw "unexpected value";

    unsigned min_width=config.ansi_c.int_width;

    if(given_width=="long")
      min_width=config.ansi_c.long_int_width;
    else if(given_width=="longlong")
      min_width=config.ansi_c.long_long_int_width;
    else if(given_width!="")
    {
      err_location(expr);
      err << "unkown width: " << given_width;
      throw 0;
    }

    #define FITS(width, signed) \
      ((signed?!is_unsigned:(is_unsigned || is_hex_or_oct)) && \
      (width>=min_width) && \
      (power(2, signed?width-1:width)>value))

    unsigned width;
    bool is_signed=false;

    if(FITS(config.ansi_c.int_width, true)) // int
    {
      width=config.ansi_c.int_width;
      is_signed=true;
    }
    else if(FITS(config.ansi_c.int_width, false)) // unsigned int
    {
      width=config.ansi_c.int_width;
    }
    else if(FITS(config.ansi_c.long_int_width, true)) // long int
    {
      width=config.ansi_c.long_int_width;
      is_signed=true;
    }
    else if(FITS(config.ansi_c.long_int_width, false)) // unsigned long int
    {
      width=config.ansi_c.long_int_width;
    }
    else if(FITS(config.ansi_c.long_long_int_width, true)) // long long int
    {
      width=config.ansi_c.long_long_int_width;
      is_signed=true;
    }
    else if(FITS(config.ansi_c.long_long_int_width, false)) // unsigned long long int
    {
      width=config.ansi_c.long_long_int_width;
    }
    else
    {
      err_location(expr);
      err << "constant " << value << " does not fit any scalar type";
      throw 0;
    }

    typet value_type(is_signed?"signedbv":"unsignedbv");
    value_type.set("width", width);

    expr=from_integer(value, value_type);
    expr.location()=location;

    if(!cformat.empty())
      expr.set("#cformat", cformat);
  }
  else if(type.id()=="array" ||
          type.id()=="incomplete_array")
  {
    Forall_operands(it, expr)
      typecheck_expr(*it);
  }
}

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

Function: c_typecheck_baset::typecheck_expr_unary_arithmetic

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_unary_arithmetic(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects one operand";
    throw 0;
  }

  exprt &operand=expr.op0();

  implicit_typecast_arithmetic(operand);

  if(is_number(operand.type()))
  {
    expr.type()=operand.type();
    return;
  }

  err_location(expr);
  err << "operator `" << expr.id_string()
      << "' not defined for type `"
      << to_string(operand.type()) << "'";
  throw 0;
}

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

Function: c_typecheck_baset::typecheck_expr_unary_boolean

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_unary_boolean(exprt &expr)
{
  if(expr.operands().size()!=1)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects one operand";
    throw 0;
  }

  exprt &operand=expr.op0();

  implicit_typecast_bool(operand);
  expr.type()=typet("bool");
}

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

Function: c_typecheck_baset::typecheck_expr_binary_arithmetic

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_binary_arithmetic(exprt &expr)
{
  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects two operands";
    throw 0;
  }

  exprt &op0=expr.op0();
  exprt &op1=expr.op1();

  const typet o_type0=op0.type();
  const typet o_type1=op1.type();
  
  if(expr.id()=="shl" || expr.id()=="shr")
  {
    implicit_typecast_arithmetic(op0);
  
    if(is_number(op0.type()) &&
       is_number(op1.type()))
    {
      expr.type()=op0.type();
    
      if(expr.id()=="shr") // shifting operation depends on types
      {
        const typet &op0_type=follow(op0.type());
      
        if(op0_type.id()=="unsignedbv")
        {
          expr.id("lshr");
          return;
        }
        else if(op0_type.id()=="signedbv")
        {
          expr.id("ashr");
          return;
        }
      }

      return;  
    }
  }
  else
  {
    implicit_typecast_arithmetic(op0, op1);
    
    const typet &type0=op0.type();
    const typet &type1=op1.type();
    
    if(expr.id()=="+" || expr.id()=="-" ||
       expr.id()=="*" || expr.id()=="/")
    {
      if(type0.id()=="pointer" || type1.id()=="pointer")
      {
        typecheck_expr_pointer_arithmetic(expr);
        return;
      }
      else if(type0==type1)
      {
        if(is_number(type0))
        {
          expr.type()=type0;
          return;
        }
      }
    }
    else if(expr.id()=="mod")
    {
      if(type0==type1)
      {
        if(type0.id()=="signedbv" || type0.id()=="unsignedbv")
        {
          expr.type()=type0;
          return;
        }
      }
    }
    else if(expr.id()=="bitand" || expr.id()=="bitxor" || expr.id()=="bitor")
    {
      if(type0==type1)
      {
        if(is_number(type0))
        {
          expr.type()=type0;
          return;
        }
      }
    }
  }

  err_location(expr);
  err << "operator `" << expr.id_string()
      << "' not defined for types `"
      << to_string(o_type0) << "' and `"
      << to_string(o_type1) << "'";
  throw 0;
}

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

Function: c_typecheck_baset::typecheck_expr_pointer_arithmetic

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_pointer_arithmetic(exprt &expr)
{
  exprt &op0=expr.op0();
  exprt &op1=expr.op1();

  const typet &type0=op0.type();
  const typet &type1=op1.type();

  if(expr.id()=="-" ||  
     (expr.id()=="sideeffect" && expr.get("statement")=="assign-"))
  {
    if(type0.id()=="pointer" &&
       type1.id()=="pointer")
    {
      typet pointer_diff_type;

      pointer_diff_type=typet("signedbv");
      pointer_diff_type.set("width", config.ansi_c.pointer_diff_width);

      expr.type()=pointer_diff_type;
      return;
    }

    if(type0.id()=="pointer")
    {
      make_index_type(op1);
      expr.type()=type0;
      return;
    }
  }
  else if(expr.id()=="+" ||  
          (expr.id()=="sideeffect" && expr.get("statement")=="assign+"))
  {
    exprt *pop, *intop;

    if(type0.id()=="pointer")
    {
      pop=&op0;
      intop=&op1;
    }
    else if(type1.id()=="pointer")
    {
      pop=&op1;
      intop=&op0;
    }
    else
      assert(false);

    make_index_type(*intop);
    expr.type()=pop->type();
    return;
  }

  err_location(expr);
  err << "operator `" << expr.id_string()
      << "' not defined for types `"
      << to_string(type0) << "' and `"
      << to_string(type1) << "'";
  throw 0;
}

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

Function: c_typecheck_baset::typecheck_expr_binary_boolean

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expr_binary_boolean(exprt &expr)
{
  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.id_string()
        << "' expects two operands";
    throw 0;
  }

  implicit_typecast_bool(expr.op0());
  implicit_typecast_bool(expr.op1());

  expr.type()=typet("bool");
}

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

Function: c_typecheck_baset::typecheck_sideeffect_assignment

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_sideeffect_assignment(exprt &expr)
{
  if(expr.operands().size()!=2)
  {
    err_location(expr);
    err << "operator `" << expr.get("statement")
        << "' expects two operands";
    throw 0;
  }

  const irep_idt &statement=expr.get("statement");

  exprt &op0=expr.op0();
  exprt &op1=expr.op1();
  
  // se if we have a typecast on the LHS
  if(op0.id()=="typecast")
  {
    assert(op0.operands().size()==1);

    // set #lvalue and #constant
    op0.set("#lvalue", op0.op0().get_bool("#lvalue"));
    op0.set("#constant", op0.op0().get_bool("#constant"));
  }

  const typet o_type0=op0.type();
  const typet o_type1=op1.type();

  const typet &type0=op0.type();
  const typet &final_type0=follow(type0);
  //const typet &type1=op1.type();

  expr.type()=type0;

  if(!op0.get_bool("#lvalue"))
  {
    err_location(expr);
    err << "assignment error: `" << to_string(op0)
        << "' not an lvalue";
    throw 0;
  }

  if(o_type0.get_bool("#constant"))
  {
    err_location(expr);
    err << "error: `" << to_string(op0)
        << "' is constant";
    throw 0;
  }

  if(statement=="assign")
  {
    implicit_typecast(op1, o_type0);
    return;
  }
  else if(statement=="assign_shl" ||
          statement=="assign_shr")
  {
    implicit_typecast_arithmetic(op1);

    if(is_number(op1.type()))
    {
      expr.type()=type0;
    
      if(statement=="assign_shl")
      {
        return;
      }
      else
      {
        if(type0.id()=="unsignedbv")
        {
          expr.set("statement", "assign_lshr");
          return;
        }
        else if(type0.id()=="signedbv")
        {
          expr.set("statement", "assign_ashr");
          return;
        }
      }
    }
  }
  else
  {
    if(final_type0.id()=="pointer" &&
       (statement=="assign-" || statement=="assign+"))
    {
      typecheck_expr_pointer_arithmetic(expr);
      return;
    }
    else if(final_type0.id()=="bool" ||
            final_type0.id()=="c_enum" ||
            final_type0.id()=="incomplete_c_enum")
    {
      implicit_typecast_arithmetic(op1);
      if(is_number(op1.type()))
        return;
    }
    else
    {
      implicit_typecast(op1, op0.type());
      if(is_number(op0.type()))
      {
        expr.type()=type0;
        return;
      }
    }
  }

  err_location(expr);
  err << "assignment `" << statement
      << "' not defined for types `" 
      << to_string(o_type0) << "' and `" 
      << to_string(o_type1) << "'";

  throw 0;
}

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

Function: c_typecheck_baset::make_constant

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::make_constant(exprt &expr)
{
  make_constant_rec(expr);
  base_type(expr, *this);
  simplify(expr);

  if(!expr.is_constant() &&
     expr.id()!="infinity")
  {
    err_location(expr.find_location());
    err << "expected constant expression, but got `"
        << to_string(expr) << "'";      
    throw 0;
  }
}

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

Function: c_typecheck_baset::make_constant_index

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::make_constant_index(exprt &expr)
{
  make_constant(expr);

  if(expr.id()!="infinity")
  {
    make_index_type(expr);
    simplify(expr);

    if(!expr.is_constant())
    {
      err_location(expr.find_location());
      throw "conversion to integer failed";
    }
  }
}

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

Function: c_typecheck_baset::make_constant_rec

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::make_constant_rec(exprt &expr)
{
}
