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

Module: Program Transformation

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

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

#include <assert.h>

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

#include <ansi-c/c_types.h>

#include "goto_convert_class.h"
#include "remove_skip.h"
#include "destructor.h"

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

Function: goto_convertt::finish_gotos

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::finish_gotos()
{
  for(gotost::const_iterator it=targets.gotos.begin();
      it!=targets.gotos.end();
      it++)
  {
    goto_programt::instructiont &i=**it;
    
    if(i.code.get("statement")=="non-deterministic-goto")
    {
      const irept &destinations=i.code.find("destinations");

      i.make_goto();
      
      forall_irep(it, destinations.get_sub())
      {
        labelst::const_iterator l_it=
          targets.labels.find(it->id_string());

        if(l_it==targets.labels.end())
        {
          err_location(i.code);
          err << "goto label " << it->id_string() << " not found";
          throw 0;
        }
          
        i.targets.push_back(l_it->second);
      }
    }
    else if(i.is_start_thread())
    {
      const irep_idt &goto_label=i.code.get("destination");

      labelst::const_iterator l_it=
        targets.labels.find(goto_label);

      if(l_it==targets.labels.end())
      {
        err_location(i.code);
        err << "goto label " << goto_label << " not found";
        throw 0;
      }

      i.targets.push_back(l_it->second);
    }
    else if(i.code.get("statement")=="goto")
    {
      const irep_idt &goto_label=i.code.get("destination");

      labelst::const_iterator l_it=targets.labels.find(goto_label);

      if(l_it==targets.labels.end())
      {
        err_location(i.code);
        err << "goto label " << goto_label << " not found";
        throw 0;
      }

      i.targets.clear();
      i.targets.push_back(l_it->second);
    }
    else
    {
      err_location(i.code);
      throw "finish_gotos: unexpected goto";
    }
  }
  
  targets.gotos.clear();
}

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

Function: goto_convertt::goto_convert

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::goto_convert(codet &code, goto_programt &dest)
{
  goto_convert_rec(code, dest);
}

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

Function: goto_convertt::goto_convert_rec

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::goto_convert_rec(
  codet &code,
  goto_programt &dest)
{
  convert(code, dest);

  finish_gotos();
  remove_skip(dest);

  dest.update();
}

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

Function: goto_convertt::move

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::move(codet &code, goto_programt &dest)
{
  goto_programt::targett t=dest.add_instruction();
  t->code.swap(code);
  set_location(to_code(t->code), *t);
}

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

Function: goto_convert::convert_label

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_label(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "label statement expected to have one operand";
  }
    
  // grab the label
  const irep_idt &label=code.get("label");
  
  goto_programt tmp;

  // magic thread creation label?
  if(has_prefix(id2string(label), "CPROVER_ASYNC_"))
  {
    // this is like a START_THREAD
    codet tmp_code("start_thread");
    tmp_code.copy_to_operands(code.op0());
    convert(tmp_code, tmp);
  }
  else
    convert(to_code(code.op0()), tmp);

  // magic ERROR label?

  const std::string &error_label=options.get_option("error-label");

  goto_programt::targett target;

  if(error_label!="" && label==error_label)
  {
    goto_programt::targett t=dest.add_instruction(ASSERT);
    t->guard.make_false();
    set_location(code, *t);
    t->location.set("property", "error label");
    t->location.set("comment", "error label");
    t->location.set("user-provided", true);

    target=t;
    dest.destructive_append(tmp);
  }
  else
  {
    target=tmp.instructions.begin();
    dest.destructive_append(tmp);
  }

  if(!label.empty())
  {
    targets.labels.insert(std::pair<irep_idt, goto_programt::targett>
                          (label, target));
    target->labels.push_back(label);
  }

  // cases?

  const exprt::operandst &case_op=
    ((exprt &)code.find("case")).operands();

  if(!case_op.empty())
  {
    exprt::operandst &case_op_dest=targets.cases[target];
    
    case_op_dest.reserve(case_op_dest.size()+case_op.size());
    
    forall_expr(it, case_op)
      case_op_dest.push_back(*it);
  }

  // default?

  if(code.get_bool("default"))
    targets.set_default(target);
}

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

Function: goto_convertt::convert

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert(codet &code, goto_programt &dest)
{
  const irep_idt &statement=code.get_statement();

  dest.instructions.clear();

  if(statement=="block")
    convert_block(code, dest);
  else if(statement=="decl")
    convert_decl(code, dest);
  else if(statement=="expression")
    convert_expression(code, dest);
  else if(statement=="assign")
    convert_assign(code, dest);
  else if(statement=="assert")
    convert_assert(code, dest);
  else if(statement=="assume")
    convert_assume(code, dest);
  else if(statement=="functioncall")
    convert_functioncall(code, dest);
  else if(statement=="label")
    convert_label(code, dest);
  else if(statement=="for")
    convert_for(code, dest);
  else if(statement=="while")
    convert_while(code, dest);
  else if(statement=="dowhile")
    convert_dowhile(code, dest);
  else if(statement=="switch")
    convert_switch(code, dest);
  else if(statement=="break")
    convert_break(code, dest);
  else if(statement=="return")
    convert_return(code, dest);
  else if(statement=="continue")
    convert_continue(code, dest);
  else if(statement=="goto")
    convert_goto(code, dest);
  else if(statement=="skip")
    convert_skip(code, dest);
  else if(statement=="non-deterministic-goto")
    convert_non_deterministic_goto(code, dest);
  else if(statement=="ifthenelse")
    convert_ifthenelse(code, dest);
  else if(statement=="specc_notify")
    convert_specc_notify(code, dest);
  else if(statement=="specc_wait")
    convert_specc_wait(code, dest);
  else if(statement=="specc_par")
    convert_specc_par(code, dest);
  else if(statement=="start_thread")
    convert_start_thread(code, dest);
  else if(statement=="end_thread")
    convert_end_thread(code, dest);
  else if(statement=="sync")
    convert_sync(code, dest);
  else if(statement=="atomic_begin")
    convert_atomic_begin(code, dest);
  else if(statement=="atomic_end")
    convert_atomic_end(code, dest);
  else if(statement=="bp_enforce")
    convert_bp_enforce(code, dest);
  else if(statement=="bp_abortif")
    convert_bp_abortif(code, dest);
  else if(statement=="cpp_delete" ||
          statement=="cpp_delete[]")
    convert_cpp_delete(code, dest);
  else
    move(code, dest);

  if(dest.instructions.empty())
  {
    dest.add_instruction(SKIP);
    dest.instructions.back().code.make_nil();
  }
}

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

Function: goto_convertt::convert_block

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_block(codet &code, goto_programt &dest)
{
  std::list<irep_idt> locals;
  
  Forall_operands(it, code)
  {
    codet &code=to_code(*it);
    
    if(code.get_statement()=="decl")
    {
      const exprt &op0=code.op0();
      assert(op0.id()=="symbol");
      const irep_idt &identifier=op0.get("identifier");
      const symbolt &symbol=lookup(identifier);
      
      if(!symbol.static_lifetime &&
         symbol.type.id()!="code")
        locals.push_back(identifier);
    }
    
    goto_programt tmp;
    convert(code, tmp);

    for(tmp_symbolst::const_iterator
        it=tmp_symbols.begin();
        it!=tmp_symbols.end();
        it++)
      locals.push_back(*it);

    tmp_symbols.clear();

    if(!locals.empty())
      Forall_goto_program_instructions(i_it, tmp)
        i_it->add_local_variables(locals);

    dest.destructive_append(tmp);
  }

  // see if we need to call any destructors

  while(!locals.empty())
  {
    const symbolt &symbol=ns.lookup(locals.back());

    exprt destructor=get_destructor(ns, symbol.type);

    if(destructor.is_not_nil())
    {
      // add "this"
      exprt this_expr("address_of", pointer_typet());
      this_expr.type().subtype()=symbol.type;
      this_expr.copy_to_operands(symbol_expr(symbol));
      destructor.move_to_operands(this_expr);

      goto_programt tmp;
      convert(to_code(destructor), tmp);

      Forall_goto_program_instructions(i_it, tmp)
        i_it->add_local_variables(locals);

      dest.destructive_append(tmp);
    }
    
    locals.pop_back();
  }
}

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

Function: goto_convertt::convert_sideeffect

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_sideeffect(
  exprt &expr,
  goto_programt &dest)
{
  const irep_idt &statement=expr.get("statement");

  if(statement=="postincrement" ||
     statement=="postdecrement" ||
     statement=="preincrement" ||
     statement=="predecrement")
  {
    if(expr.operands().size()!=1)
    {
      err_location(expr);
      err << statement << " takes one argument";
      throw 0;
    }

    exprt rhs;

    if(statement=="postincrement" ||
       statement=="preincrement")
      rhs.id("+");
    else
      rhs.id("-");
      
    const typet &op_type=ns.follow(expr.op0().type());
      
    if(op_type.id()=="bool")
    {
      rhs.copy_to_operands(expr.op0(), gen_one(int_type()));
      rhs.op0().make_typecast(int_type());
      rhs.type()=int_type();
      rhs.make_typecast(typet("bool"));
    }
    else if(op_type.id()=="c_enum" ||
            op_type.id()=="incomplete_c_enum")
    {
      rhs.copy_to_operands(expr.op0(), gen_one(int_type()));
      rhs.op0().make_typecast(int_type());
      rhs.type()=int_type();
      rhs.make_typecast(op_type);
    }
    else
    {
      typet constant_type;

      if(op_type.id()=="pointer")
        constant_type=index_type();
      else if(is_number(op_type))
        constant_type=op_type;
      else
      {
        err_location(expr);
        throw "no constant one of type "+op_type.to_string();
      }

      exprt constant=gen_one(constant_type);

      rhs.copy_to_operands(expr.op0());
      rhs.move_to_operands(constant);
      rhs.type()=expr.op0().type();
    }

    codet assignment("assign");
    assignment.copy_to_operands(expr.op0());
    assignment.move_to_operands(rhs);

    assignment.location()=expr.find_location();

    convert(assignment, dest);
  }
  else if(statement=="assign")
  {
    exprt tmp;
    tmp.swap(expr);
    tmp.id("code");
    convert(to_code(tmp), dest);
  }
  else if(statement=="assign+" ||
          statement=="assign-" ||
          statement=="assign*" ||
          statement=="assign_div" ||
          statement=="assign_mod" ||
          statement=="assign_shl" ||
          statement=="assign_ashr" ||
          statement=="assign_lshr" ||
          statement=="assign_bitand" ||
          statement=="assign_bitxor" ||
          statement=="assign_bitor")
  {
    if(expr.operands().size()!=2)
    {
      err_location(expr);
      err << statement << " takes two arguments";
      throw 0;
    }

    exprt rhs;

    if(statement=="assign+")
      rhs.id("+");
    else if(statement=="assign-")
      rhs.id("-");
    else if(statement=="assign*")
      rhs.id("*");
    else if(statement=="assign_div")
      rhs.id("/");
    else if(statement=="assign_mod")
      rhs.id("%");
    else if(statement=="assign_shl")
      rhs.id("shl");
    else if(statement=="assign_ashr")
      rhs.id("ashr");
    else if(statement=="assign_lshr")
      rhs.id("lshr");
    else if(statement=="assign_bitand")
      rhs.id("bitand");
    else if(statement=="assign_bitxor")
      rhs.id("bitxor");
    else if(statement=="assign_bitor")
      rhs.id("bitor");
    else
    {
      err_location(expr);
      err << statement << " not yet supproted";
      throw 0;
    }

    rhs.copy_to_operands(expr.op0(), expr.op1());
    rhs.type()=expr.op0().type();
    
    if(rhs.op0().type().id()=="bool")
    {
      rhs.op0().make_typecast(int_type());
      rhs.op1().make_typecast(int_type());
      rhs.type()=int_type();
      rhs.make_typecast(typet("bool"));
    }
    
    exprt lhs(expr.op0());
    
    codet assignment("assign");
    assignment.move_to_operands(lhs, rhs);
    
    convert(assignment, dest);
  }
  else if(statement=="cpp_delete" ||
          statement=="cpp_delete[]")
  {
    exprt tmp;
    tmp.swap(expr);
    tmp.id("code");
    convert(to_code(tmp), dest);
  }
  else if(statement=="functioncall")
  {
    exprt tmp;
    tmp.swap(expr);
    tmp.id("code");
    convert_functioncall(to_code(tmp), dest);
  }
  else
  {
    err_location(expr);
    err << "sideeffect " << statement << " not supported";
    throw 0;
  }
}

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

Function: goto_convertt::convert_expression

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_expression(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "expression statement takes one operand";
  }
  
  exprt &expr=code.op0();

  if(expr.id()=="sideeffect")
  {
    Forall_operands(it, expr)
      remove_sideeffects(*it, dest);

    goto_programt tmp;
    convert_sideeffect(expr, tmp);
    dest.destructive_append(tmp);
  }
  else
  {
    remove_sideeffects(expr, dest, false); // result not used

    if(expr.is_not_nil())
      move(code, dest);
  }
}

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

Function: goto_convertt::convert_decl

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_decl(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=1 &&
     code.operands().size()!=2)
  {
    err_location(code);
    throw "decl statement takes one or two operands";
  }
    
  const exprt &op0=code.op0();
    
  if(op0.id()!="symbol")
  {
    err_location(op0);
    throw "decl statement expects symbol as first operand";
  }

  const irep_idt &identifier=op0.get("identifier");
  
  const symbolt &symbol=lookup(identifier);
  
  if(symbol.static_lifetime ||
     symbol.type.id()=="code")
    return; // this is a SKIP!

  if(code.operands().size()==2)
  {
    goto_programt sideeffects;
    remove_sideeffects(code.op1(), sideeffects);
    dest.destructive_append(sideeffects);
  }

  move(code, dest);
}

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

Function: goto_convertt::convert_assign

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_assign(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=2)
  {
    err_location(code);
    throw "assignment statement takes two operands";
  }
  
  exprt &lhs=code.op0(),
        &rhs=code.op1();

  remove_sideeffects(lhs, dest);

  if(rhs.id()=="sideeffect" &&
     rhs.get("statement")=="functioncall")
  {
    Forall_operands(it, rhs)
      remove_sideeffects(*it, dest);

    do_functioncall(lhs, rhs, dest);
  }
  else if(rhs.id()=="sideeffect" &&
          (rhs.get("statement")=="cpp_new" ||
           rhs.get("statement")=="cpp_new[]"))
  {
    Forall_operands(it, rhs)
      remove_sideeffects(*it, dest);

    do_cpp_new(lhs, rhs, dest);
  }
  else
  {
    remove_sideeffects(rhs, dest);

    if(lhs.id()=="typecast")
    {
      assert(lhs.operands().size()==1);

      // move to rhs
      exprt tmp_rhs(lhs);
      tmp_rhs.op0()=rhs;
      rhs=tmp_rhs;

      // remove from lhs      
      exprt tmp(lhs.op0());
      lhs.swap(tmp);      
    }
  
    move(code, dest);
  }
}

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

Function: goto_convertt::convert_cpp_delete

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_cpp_delete(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "cpp_delete statement takes one operand";
  }
  
  remove_sideeffects(code.op0(), dest);
  move(code, dest);
}

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

Function: goto_convertt::convert_assert

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_assert(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "assert statement takes one operand";
  }
  
  exprt &op=code.op0();

  remove_sideeffects(op, dest);
  
  if(!options.get_bool_option("assertions"))
    return;

  goto_programt::targett t=dest.add_instruction(ASSERT);
  t->guard.swap(op);
  set_location(code, *t);
  t->location.set("property", "assertion");
  t->location.set("user-provided", true);
}

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

Function: goto_convertt::convert_skip

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_skip(codet &code, goto_programt &dest)
{
  goto_programt::targett t=dest.add_instruction(SKIP);
  set_location(code, *t);
  t->code.swap(code);
}

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

Function: goto_convertt::convert_assert

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_assume(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "assume statement takes one operand";
  }
  
  exprt &op=code.op0();

  remove_sideeffects(op, dest);

  goto_programt::targett t=dest.add_instruction(ASSUME);
  t->guard.swap(op);
  set_location(code, *t);
}

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

Function: goto_convertt::convert_for

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_for(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=4)
  {
    err_location(code);
    throw "for takes four operands";
  }
  
  // turn for(A; c; B) P;
  // into A; while(c) { P; B; }
  
  code_blockt block;
  
  if(code.op0().is_not_nil())
    block.move_to_operands(code.op0());
    
  code_blockt block2;
  
  block2.move_to_operands(code.op3());
  
  if(code.op2().is_not_nil())
    block2.move_to_operands(code.op2());
  
  codet while_loop("while");
  while_loop.operands().resize(2);
  
  if(code.op1().is_nil())
    while_loop.op0().make_true();
  else
    while_loop.op0().swap(code.op1());
    
  while_loop.op1().swap(block2);
  
  block.move_to_operands(while_loop);
  
  convert(block, dest);
}

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

Function: goto_convertt::convert_while

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_while(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=2)
  {
    err_location(code);
    throw "while takes two operands";
  }

  goto_programt sideeffects;
  remove_sideeffects(code.op0(), sideeffects);

  //    while(c) P;
  //--------------------
  // v: sideeffects in c <-- continue target
  // w: if(!c) goto z;
  // x: P;
  // y: goto v;
  // z: ;                <-- break target

  // save break/continue targets
  break_continue_targetst old_targets(targets);

  // do the v label
  goto_programt::targett v=sideeffects.instructions.begin();

  // do the w label
  goto_programt tmp_w;
  goto_programt::targett w=tmp_w.add_instruction();
  
  // do the z label
  goto_programt tmp_z;
  goto_programt::targett z=tmp_z.add_instruction();
  z->make_skip();

  // optimize the v label
  if(sideeffects.instructions.empty())
    v=w;

  // set the targets
  targets.set_break(z);
  targets.set_continue(v);

  // w: if(!c) goto z;
  w->make_goto(z);
  w->guard=code.op0();
  w->guard.make_not();
  set_location(code.op0(), *w);

  // do the x label
  goto_programt tmp_x;
  convert(to_code(code.op1()), tmp_x);

  // y: if(c) goto v;
  goto_programt tmp_y;
  goto_programt::targett y=tmp_y.add_instruction();
  y->make_goto(v);
  y->guard.make_true();
  set_location(code, *y);

  dest.destructive_append(sideeffects);
  dest.destructive_append(tmp_w);
  dest.destructive_append(tmp_x);
  dest.destructive_append(tmp_y);
  dest.destructive_append(tmp_z);

  // restore break/continue
  targets.restore(old_targets);
}

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

Function: goto_convertt::convert_dowhile

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_dowhile(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=2)
  {
    err_location(code);
    throw "dowhile takes two operands";
  }

  // save location  
  locationt condition_location=code.op0().find_location();

  goto_programt sideeffects;
  remove_sideeffects(code.op0(), sideeffects);  

  //    do P while(c);
  //--------------------
  // w: P;
  // x: sideeffects in c   <-- continue target
  // y: if(c) goto w;
  // z: ;                  <-- break target

  // save break/continue targets
  break_continue_targetst old_targets(targets);

  // do the y label
  goto_programt tmp_y;
  goto_programt::targett y=tmp_y.add_instruction();

  // do the z label
  goto_programt tmp_z;
  goto_programt::targett z=tmp_z.add_instruction();
  z->make_skip();

  // do the x label
  goto_programt::targett x;
  if(sideeffects.instructions.empty())
    x=y;
  else
    x=sideeffects.instructions.begin();

  // set the targets
  targets.set_break(z);
  targets.set_continue(x);

  // do the w label
  goto_programt tmp_w;
  convert(to_code(code.op1()), tmp_w);
  goto_programt::targett w=tmp_w.instructions.begin();

  // y: if(c) goto w;
  y->make_goto(w);
  y->guard=code.op0();
  y->location=condition_location;

  dest.destructive_append(tmp_w);
  dest.destructive_append(sideeffects);
  dest.destructive_append(tmp_y);
  dest.destructive_append(tmp_z);

  // restore break/continue targets
  targets.restore(old_targets);
}

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

Function: goto_convertt::case_guard

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::case_guard(const exprt &value,
                               exprt::operandst &case_op,
                               exprt &dest)
{
  dest=exprt("or", typet("bool"));
  dest.reserve_operands(case_op.size());

  Forall_expr(it, case_op)
  {
    exprt eq_expr("=", typet("bool"));
    eq_expr.reserve_operands(2);
    eq_expr.copy_to_operands(value);
    eq_expr.move_to_operands(*it);
    dest.move_to_operands(eq_expr);
  }

  assert(dest.operands().size()!=0);

  if(dest.operands().size()==1)
  {
    exprt tmp;
    tmp.swap(dest.op0());
    dest.swap(tmp);
  }
}

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

Function: goto_convertt::convert_switch

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_switch(codet &code, goto_programt &dest)
{
  // switch(v) {
  //   case x: Px;
  //   case y: Py;
  //   ...
  //   default: Pd;
  // }
  // --------------------
  // x: if(v==x) goto X;
  // y: if(v==y) goto Y;
  //    goto d;
  // X: Px;
  // Y: Py;
  // d: Pd;
  // z: ;

  if(code.operands().size()<2)
  {
    err_location(code);
    throw "switch takes at least two operands";
  }

  goto_programt sideeffects;
  remove_sideeffects(code.op0(), sideeffects);

  // save break/continue/default/cases targets
  break_continue_switch_targetst old_targets(targets);

  // do the z label
  goto_programt tmp_z;
  goto_programt::targett z=tmp_z.add_instruction();
  z->make_skip();

  // set the new targets -- continue stays as is
  targets.set_break(z);
  targets.set_default(z);
  targets.cases.clear();

  goto_programt tmp;

  Forall_operands(it, code)
    if(it!=code.operands().begin())
    {
      goto_programt t;
      convert(to_code(*it), t);
      tmp.destructive_append(t);
    }

  goto_programt tmp_cases;
 
  for(casest::iterator it=targets.cases.begin();
      it!=targets.cases.end();
      it++)
  {
    assert(!it->second.empty());
    
    locationt location=it->second.front().location();
  
    exprt guard_expr;
    case_guard(code.op0(), it->second, guard_expr);

    goto_programt::targett x=tmp_cases.add_instruction();
    assert(it->second.size()!=0);
    x->make_goto(it->first);
    x->guard.swap(guard_expr);
    x->location=location;
  }

  {
    goto_programt::targett d_jump=tmp_cases.add_instruction();
    d_jump->make_goto(targets.default_target);
    d_jump->location=targets.default_target->location;
  }

  dest.destructive_append(sideeffects);
  dest.destructive_append(tmp_cases);
  dest.destructive_append(tmp);
  dest.destructive_append(tmp_z);

  // restore old targets
  targets.restore(old_targets);
}

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

Function: goto_convertt::convert_break

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_break(codet &code, goto_programt &dest)
{
  if(!targets.break_set)
  {
    err_location(code);
    throw "break without target";
  }

  goto_programt::targett t=dest.add_instruction();
  t->make_goto(targets.break_target);
  set_location(code, *t);
}

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

Function: goto_convertt::convert_return

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_return(codet &code, goto_programt &dest)
{
  if(!targets.return_set)
  {
    err_location(code);
    throw "return without target";
  }

  if(code.operands().size()!=0 &&
     code.operands().size()!=1)
  {
    err_location(code);
    throw "return takes none or one operand";
  }

  goto_programt sideeffects;

  if(code.operands().size()==1)
    remove_sideeffects(code.op0(), sideeffects);

  dest.destructive_append(sideeffects);

  if(targets.return_value)
  {
    if(code.operands().size()==0)
    {
      err_location(code);
      throw "function must return value";
    }
  }
  else
  {
    if(code.operands().size()!=0 &&
       code.op0().type().id()!="empty")
    {
      err_location(code);
      throw "function must not return value";
    }
  }

  goto_programt::targett t=dest.add_instruction();
  t->make_return();
  t->code.swap(code);
  set_location(to_code(t->code), *t);
}

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

Function: goto_convertt::convert_continue

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_continue(codet &code, goto_programt &dest)
{
  if(!targets.continue_set)
  {
    err_location(code);
    throw "continue without target";
  }

  goto_programt::targett t=dest.add_instruction();
  t->make_goto(targets.continue_target);
  set_location(code, *t);
}

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

Function: goto_convertt::convert_goto

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_goto(codet &code, goto_programt &dest)
{
  goto_programt::targett t=dest.add_instruction();
  t->make_goto();
  set_location(code, *t);
  t->code.swap(code);

  // remember it to do target later
  targets.gotos.insert(t);
}

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

Function: goto_convertt::convert_non_deterministic_goto

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_non_deterministic_goto(codet &code, goto_programt &dest)
{
  convert_goto(code, dest);
}

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

Function: goto_convertt::convert_specc_notify

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_specc_notify(codet &code, goto_programt &dest)
{
  #if 0
  goto_programt::targett t=dest.add_instruction(EVENT);

  forall_operands(it, code)
    convert_specc_event(*it, t->events);

  t->code.swap(code);
  set_location(code, *t);
  #endif

  move(code, dest);
}

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

Function: goto_convertt::convert_specc_event

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_specc_event(
  const exprt &op,
  std::set<irep_idt> &events)
{
  if(op.id()=="or" || op.id()=="and")
  {
    forall_operands(it, op)
      convert_specc_event(*it, events);
  }
  else if(op.id()=="specc_event")
  {
    irep_idt event=op.get("identifier");

    if(has_prefix(id2string(event), "specc::"))
      event=std::string(id2string(event), 7, std::string::npos);

    events.insert(event);
  }
  else
  {
    err_location(op);
    throw "convert_convert_event got "+op.id_string();
  }
}

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

Function: goto_convertt::convert_specc_wait

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_specc_wait(codet &code, goto_programt &dest)
{
  #if 0
  goto_programt::targett t=dest.add_instruction(WAIT);
  
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "specc_wait expects one operand";
  }

  const exprt &op=code.op0();

  if(op.id()=="or")
    t->or_semantics=true;

  convert_specc_event(op, t->events);

  t->code.swap(code);
  set_location(code, *t);
  #endif

  move(code, dest);
}

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

Function: goto_convertt::convert_specc_par

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_specc_par(codet &code, goto_programt &dest)
{
  #if 0
  goto_programt::targett notify=dest.add_instruction(EVENT);
  goto_programt::targett wait=dest.add_instruction(WAIT);

  wait->or_semantics=false;
  set_location(code, *notify);

  if(code.operands().size()==0)
  {
    err_location(code);
    throw "specc_par expects at least one argument";
  }

  // create the threads
  Forall_operands(it, code)
  {
    unsigned thread_nr=threads.size();

    notify->events.insert("start_thread"+i2string(thread_nr));
    wait->events.insert("end_thread"+i2string(thread_nr));

    threads.push_back(goto_threadt());

    goto_threadt &thread=threads.back();

    goto_programt::targett t_wait=
      thread.goto_program.add_instruction(WAIT);

    set_location(code, *t_wait);
    t_wait->events.insert("start_thread"+i2string(thread_nr));

    // fresh targets
    targetst old_targets;
    targets.swap(old_targets);

    goto_programt tmp;
    goto_convert_rec(*it, tmp);
    targets.swap(old_targets);

    thread.goto_program.destructive_append(tmp);

    goto_programt::targett t_notify=
      thread.goto_program.add_instruction(EVENT);

    t_notify->events.insert("end_thread"+i2string(thread_nr));

    // add a loop, for the case that the par statement is also
    // in a loop

    goto_programt::targett t_goto=
      thread.goto_program.add_instruction();

    t_goto->make_goto(t_wait);

    thread.goto_program.update();
  }

  notify->code.swap(code);
  #endif

  move(code, dest);
}

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

Function: goto_convertt::convert_start_thread

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_start_thread(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "start_thread expects one operand";
  }

  goto_programt::targett start_thread=
    dest.add_instruction(START_THREAD);

  set_location(code, *start_thread);
  
  // see if op0 is an unconditional goto
  
  if(code.op0().get("statement")=="goto")
  {
    start_thread->code.set("destination",
      code.op0().get("destination"));
      
    // remember it to do target later
    targets.gotos.insert(start_thread);
  }
  else
  {
    goto_programt tmp;
    convert(to_code(code.op0()), tmp);
    tmp.add_instruction(END_THREAD);

    dest.destructive_append(tmp);

    goto_programt tmp2;
    start_thread->targets.push_back(tmp2.add_instruction(SKIP));
    dest.destructive_append(tmp2);
  }
}

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

Function: goto_convertt::convert_end_thread

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_end_thread(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=0)
  {
    err_location(code);
    throw "end_thread expects no operands";
  }

  move(code, dest);
  dest.instructions.back().type=END_THREAD;
}

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

Function: goto_convertt::convert_sync

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_sync(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=0)
  {
    err_location(code);
    throw "sync expects no operands";
  }

  move(code, dest);
  dest.instructions.back().type=SYNC;
  dest.instructions.back().event=
    dest.instructions.back().code.get("event");
}

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

Function: goto_convertt::convert_atomic_begin

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_atomic_begin(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=0)
  {
    err_location(code);
    throw "atomic_begin expects no operands";
  }

  move(code, dest);
  dest.instructions.back().type=ATOMIC_BEGIN;
}

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

Function: goto_convertt::convert_atomic_end

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_atomic_end(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=0)
  {
    err_location(code);
    throw "atomic_end expects no operands";
  }

  move(code, dest);
  dest.instructions.back().type=ATOMIC_END;
}

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

Function: goto_convertt::convert_bp_enforce

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_bp_enforce(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=2)
  {
    err_location(code);
    error("bp_enfroce expects two arguments");
    throw 0;
  }
    
  // do an assume
  exprt &op=code.op0();

  remove_sideeffects(op, dest);

  goto_programt::targett t=dest.add_instruction(ASSUME);
  t->guard=op;
  set_location(code, *t);

  // change the assignments
  
  goto_programt tmp;
  convert(to_code(code.op1()), tmp);
  
  if(!op.is_true())
  {
    exprt constraint(op);
    make_next_state(constraint);
  
    Forall_goto_program_instructions(it, tmp)
    {
      if(it->is_other())
      {
        if(it->code.get("statement")=="assign")
        {
          // add constrain
          codet constrain("bp_constrain");
          constrain.reserve_operands(2);
          constrain.move_to_operands(it->code);
          constrain.copy_to_operands(constraint);
          it->code.swap(constrain);
        }
        else if(it->code.get("statement")=="bp_constrain")
        {
          // add to constraint
          assert(it->code.operands().size()==2);
          it->code.op1()=
            gen_and(it->code.op1(), constraint);
        }
      }
    }
  }
  
  dest.destructive_append(tmp);
}

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

Function: goto_convertt::convert_bp_abortif

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_bp_abortif(
  codet &code,
  goto_programt &dest)
{
  if(code.operands().size()!=1)
  {
    err_location(code);
    throw "bp_abortif expects one argument";
  }
    
  // do an assert
  exprt &op=code.op0();

  remove_sideeffects(op, dest);
  
  op.make_not();

  goto_programt::targett t=dest.add_instruction(ASSERT);
  t->guard.swap(op);
  set_location(code, *t);
}

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

Function: goto_convertt::convert_ifthenelse

  Inputs:

 Outputs:

 Purpose:

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

void goto_convertt::convert_ifthenelse(codet &code, goto_programt &dest)
{
  if(code.operands().size()!=2 &&
     code.operands().size()!=3)
  {
    err_location(code);
    throw "ifthenelse takes two or three operands";
  }
  
  bool has_else=
    code.operands().size()==3 &&
    !code.op2().is_nil();

  goto_programt sideeffects;
  remove_sideeffects(code.op0(), sideeffects);

  // convert 'then'-branch
  goto_programt tmp_op1;
  convert(to_code(code.op1()), tmp_op1);
  
  // do guarded gotos directly
  if(!has_else &&
     tmp_op1.instructions.size()==1 &&
     tmp_op1.instructions.back().is_goto() &&
     tmp_op1.instructions.back().guard.is_true())
  {
    dest.destructive_append(sideeffects);
    dest.destructive_append(tmp_op1);
    dest.instructions.back().guard=code.op0();
    return;
  }

  //    if(c) P;
  //--------------------
  // v: if(!c) goto z;
  // w: P;
  // z: ;

  //    if(c) P; else Q;
  //--------------------
  // v: if(!c) goto y;
  // w: P;
  // x: goto z;
  // y: Q;
  // z: ;

  // do the v label
  goto_programt tmp_v;
  goto_programt::targett v=tmp_v.add_instruction();

  // do the x label
  goto_programt tmp_x;
  goto_programt::targett x=tmp_x.add_instruction();

  // do the z label
  goto_programt tmp_z;
  goto_programt::targett z=tmp_z.add_instruction();
  z->make_skip();

  // y: Q;
  goto_programt tmp_y;
  goto_programt::targett y;
  if(has_else)
  {
    convert(to_code(code.op2()), tmp_y);
    y=tmp_y.instructions.begin();
  }

  // v: if(!c) goto z/y;
  v->make_goto(has_else?y:z);
  v->guard=code.op0();
  v->guard.make_not();
  set_location(code, *v);

  // w: P;
  goto_programt tmp_w;
  tmp_w.swap(tmp_op1);

  // x: goto z;
  x->make_goto(z);

  dest.destructive_append(sideeffects);  
  dest.destructive_append(tmp_v);
  dest.destructive_append(tmp_w);

  if(has_else)
  {
    dest.destructive_append(tmp_x);
    dest.destructive_append(tmp_y);
  }

  dest.destructive_append(tmp_z);
}

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

Function: goto_convertt::get_string_constant

  Inputs:

 Outputs:

 Purpose:

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

const std::string &goto_convertt::get_string_constant(
  const exprt &expr)
{
  if(expr.id()!="address_of" ||
     expr.operands().size()!=1 ||
     expr.op0().id()!="index" ||
     expr.op0().operands().size()!=2 ||
     expr.op0().op0().id()!="string-constant")
  {
    err_location(expr);
    err << "expected string constant, but got: "
        << expr.pretty() << std::endl;
    throw 0;
  }

  return expr.op0().op0().get_string("value");
}

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

Function: goto_convertt::new_tmp_symbol

  Inputs:

 Outputs:

 Purpose:

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

symbolt &goto_convertt::new_tmp_symbol(const typet &type)
{
  symbolt new_symbol;

  new_symbol.base_name="tmp$"+i2string(++temporary_counter);
  new_symbol.name=tmp_symbol_prefix+id2string(new_symbol.base_name);
  new_symbol.lvalue=true;
  new_symbol.type=type;
  
  symbolt *symbol_ptr;
  assert(!context.move(new_symbol, symbol_ptr));
  
  tmp_symbols.push_back(symbol_ptr->name);

  return *symbol_ptr;  
}
