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

Module: C++ Language Type Checking

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

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

#include <i2string.h>
#include <expr_util.h>

#include "c_typecheck_base.h"

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

Function: c_typecheck_baset::init

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::start_typecheck_code()
{
  case_is_allowed=break_is_allowed=continue_is_allowed=false;
}

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

Function: c_typecheck_baset::typecheck_code

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_code(codet &code)
{
  if(code.id()!="code")
    throw "expected code, got "+code.pretty();

  code.type()=typet("code");

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

  if(statement=="expression")
    typecheck_expression(code);
  else if(statement=="label")
    typecheck_label(code);
  else if(statement=="block")
    typecheck_block(code);
  else if(statement=="ifthenelse")
    typecheck_ifthenelse(code);
  else if(statement=="while" ||
          statement=="dowhile")
    typecheck_while(code);
  else if(statement=="for")
    typecheck_for(code);
  else if(statement=="switch")
    typecheck_switch(code);
  else if(statement=="break")
    typecheck_break(code);
  else if(statement=="goto")
    typecheck_goto(code);
  else if(statement=="continue")
    typecheck_continue(code);
  else if(statement=="return")
    typecheck_return(code);
  else if(statement=="decl")
    typecheck_decl(code);
  else if(statement=="decl-block")
    typecheck_decl_block(code);
  else if(statement=="assign")
    typecheck_assign(code);
  else if(statement=="skip")
  {
  }
  else if(statement=="asm")
    typecheck_asm(code);
  else if(statement=="start_thread")
    typecheck_start_thread(code);
  else
  {
    err_location(code);
    err << "unexpected statement: " << statement;
    throw 0;
  }
}

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

Function: c_typecheck_baset::typecheck_asm

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_asm(codet &code)
{
}

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

Function: c_typecheck_baset::typecheck_assign

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_assign(codet &code)
{
  if(code.operands().size()!=2)
    throw "assignment statement expected to have two operands";

  typecheck_expr(code.op0());
  typecheck_expr(code.op1());

  implicit_typecast(code.op1(), code.op0().type());
}

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

Function: c_typecheck_baset::typecheck_decl_block

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_decl_block(codet &code)
{
  Forall_operands(it, code)
    typecheck_code(to_code(*it));
}

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

Function: c_typecheck_baset::typecheck_block

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_block(codet &code)
{
  Forall_operands(it, code)
    typecheck_code(to_code(*it));

  // do decl-blocks

  exprt new_ops;
  new_ops.operands().reserve(code.operands().size());

  Forall_operands(it1, code)
  {
    if(it1->is_nil()) continue;

    codet &code_op=to_code(*it1);
  
    if(code_op.get_statement()=="decl-block")
    {
      Forall_operands(it2, code_op)
        if(it2->is_not_nil())
          new_ops.move_to_operands(*it2);
    }
    else if(code_op.get_statement()=="label")
    {
      // these may be nested
      codet *code_ptr=&code_op;
      
      while(code_ptr->get_statement()=="label")
      {
        assert(code_ptr->operands().size()==1);
        code_ptr=&to_code(code_ptr->op0());
      }
      
      codet &label_op=*code_ptr;

      // move declaration out of label
      if(label_op.get_statement()=="decl-block")
      {
        codet tmp;
        tmp.swap(label_op);
        label_op=codet("skip");
        
        new_ops.move_to_operands(code_op);

        Forall_operands(it2, tmp)
          if(it2->is_not_nil())
            new_ops.move_to_operands(*it2);
      }
      else
        new_ops.move_to_operands(code_op);
    }
    else
      new_ops.move_to_operands(code_op);
  }

  code.operands().swap(new_ops.operands());
}

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

Function: c_typecheck_baset::typecheck_break

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_break(codet &code)
{
  if(!break_is_allowed)
  {
    err_location(code);
    throw "break not allowed here";
  }
}

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

Function: c_typecheck_baset::typecheck_continue

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_continue(codet &code)
{
  if(!continue_is_allowed)
  {
    err_location(code);
    throw "continue not allowed here";
  }
}

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

Function: c_typecheck_baset::typecheck_decl

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_decl(codet &code)
{
  if(code.operands().size()!=1 &&
     code.operands().size()!=2)
  {
    err_location(code);
    throw "decl expected to have one or two arguments";
  }

  // op0 must be symbol
  if(code.op0().id()!="symbol")
  {
    err_location(code);
    throw "decl expected to have symbol as first operand";
  }

  // replace if needed
  replace_symbol(code.op0());

  // look it up
  const irep_idt &identifier=code.op0().get("identifier");

  symbolst::iterator s_it=context.symbols.find(identifier);

  if(s_it==context.symbols.end())
  {
    err_location(code);
    throw "failed to find decl symbol in context";
  }

  symbolt &symbol=s_it->second;

  // see if it's a typedef
  // or a function
  // or static
  if(symbol.is_type ||
     symbol.type.id()=="code" ||
     symbol.static_lifetime)
  {
    locationt location=code.location();
    code=code_skipt();
    code.location()=location;
    return;
  }

  code.op0().type()=symbol.type;

  // check the initializer, if any
  if(code.operands().size()==2)
  {
    typecheck_expr(code.op1());
    do_initializer(code.op1(), code.op0().type(), false);

    // adjust type in case it's fixed by the initializer
    symbol.type=code.op0().type();
  }

  const typet &type=follow(code.op0().type());

  // this must not be an incomplete type
  if(type.id()=="incomplete_struct" ||
     type.id()=="incomplete_array")
  {
    err_location(code);
    throw "incomplete type not permitted here";
  }
}

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

Function: c_typecheck_baset::typecheck_expression

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_expression(codet &code)
{
  if(code.operands().size()!=1)
    throw "expression statement expected to have one operand";

  exprt &op=code.op0();
  typecheck_expr(op);

  // pull pure assignment statements up

  if(op.id()=="sideeffect" &&
     op.get("statement")=="assign")
  {
    exprt::operandst operands;
    operands.swap(op.operands());
    code.set("statement", "assign");
    code.operands().swap(operands);
  }
}

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

Function: c_typecheck_baset::typecheck_for

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_for(codet &code)
{
  if(code.operands().size()!=4)
    throw "for expected to have four operands";

  // the "for" statement has an implicit block around it,
  // since code.op0() may contain declarations
  //
  // we therefore transform
  //
  //   for(a;b;c) d;
  //
  // to
  //
  //   { a; for(;b;c) d; }

  if(code.op0().is_nil())
  {
    if(code.op1().is_nil())
      code.op1().make_true();
    else
    {
      typecheck_expr(code.op1());
      implicit_typecast_bool(code.op1());
    }

    if(code.op2().is_not_nil())
      typecheck_code(to_code(code.op2()));

    if(code.op3().is_not_nil())
    {
      // save & set flags
      bool old_break_is_allowed(break_is_allowed);
      bool old_continue_is_allowed(continue_is_allowed);

      break_is_allowed=continue_is_allowed=true;

      typecheck_code(to_code(code.op3()));

      // restore flags
      break_is_allowed=old_break_is_allowed;
      continue_is_allowed=old_continue_is_allowed;
    }
  }
  else
  {
    code_blockt code_block;
    code_block.location()=code.location();

    code_block.reserve_operands(2);
    code_block.move_to_operands(code.op0());
    code.op0().make_nil();
    code_block.move_to_operands(code);
    code.swap(code_block);
    typecheck_code(code); // recursive call
  }
}

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

Function: c_typecheck_baset::typecheck_label

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_label(codet &code)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "label expected to have one operand";
  }

  typecheck_code(to_code(code.op0()));

  if(code.get_bool("default"))
  {
    if(!case_is_allowed)
    {
      err_location(code);
      throw "did not expect default label here";
    }
  }

  if(code.find("case").is_not_nil())
  {
    if(!case_is_allowed)
    {
      err_location(code);
      throw "did not expect `case' here";
    }

    exprt &case_expr=static_cast<exprt &>(code.add("case"));

    Forall_operands(it, case_expr)
    {
      typecheck_expr(*it);
      implicit_typecast(*it, switch_op_type);
    }
  }
}

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

Function: c_typecheck_baset::typecheck_goto

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_goto(codet &code)
{
}

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

Function: c_typecheck_baset::typecheck_ifthenelse

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_ifthenelse(codet &code)
{
  if(code.operands().size()!=2 &&
     code.operands().size()!=3)
    throw "ifthenelse expected to have two or three operands";

  exprt &cond=code.op0();

  typecheck_expr(cond);

  if(cond.id()=="sideeffect" &&
     cond.get("statement")=="assign")
  {
    err_location(cond);
    warning("warning: assignment in if condition");
  }

  implicit_typecast_bool(cond);

  typecheck_code(to_code(code.op1()));

  if(code.operands().size()==3 &&
     !code.op2().is_nil())
    typecheck_code(to_code(code.op2()));
}

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

Function: c_typecheck_baset::typecheck_start_thread

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_start_thread(codet &code)
{
  if(code.operands().size()!=1)
    throw "start_thread expected to have one operand";

  typecheck_code(to_code(code.op0()));
}

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

Function: c_typecheck_baset::typecheck_return

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_return(codet &code)
{
  if(code.operands().size()==0)
  {
    if(return_type.id()!="empty")
    {
      err_location(code);
      throw "function expected to return a value";
    }
  }
  else if(code.operands().size()==1)
  {
    typecheck_expr(code.op0());

    if(return_type.id()=="empty")
    {
      if(code.op0().type().id()!="empty")
      {
        err_location(code);
        throw "function not expected to return a value";
      }
    }
    else
      implicit_typecast(code.op0(), return_type);
  }
  else
  {
    err_location(code);
    throw "return expected to have 0 or 1 operands";
  }
}

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

Function: c_typecheck_baset::typecheck_switch

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_switch(codet &code)
{
  if(code.operands().size()!=2)
    throw "switch expects two operands";

  typecheck_expr(code.op0());

  // this needs to be promoted
  implicit_typecast_arithmetic(code.op0());

  // save & set flags

  bool old_case_is_allowed(case_is_allowed);
  bool old_break_is_allowed(break_is_allowed);
  typet old_switch_op_type(switch_op_type);

  switch_op_type=code.op0().type();
  break_is_allowed=case_is_allowed=true;

  typecheck_code(to_code(code.op1()));

  // restore flags
  case_is_allowed=old_case_is_allowed;
  break_is_allowed=old_break_is_allowed;
  switch_op_type=old_switch_op_type;
}

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

Function: c_typecheck_baset::typecheck_while

  Inputs:

 Outputs:

 Purpose:

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

void c_typecheck_baset::typecheck_while(codet &code)
{
  if(code.operands().size()!=2)
    throw "while expected to have two operands";

  typecheck_expr(code.op0());
  implicit_typecast_bool(code.op0());

  // save & set flags
  bool old_break_is_allowed(break_is_allowed);
  bool old_continue_is_allowed(continue_is_allowed);

  break_is_allowed=continue_is_allowed=true;

  typecheck_code(to_code(code.op1()));

  // restore flags
  break_is_allowed=old_break_is_allowed;
  continue_is_allowed=old_continue_is_allowed;
}
