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

Module: SPEC-C Conversion

Author: Daniel Kroening, kroening@kroening.com

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

#include <std_types.h>

#include "ansi_c_convert.h"
#include "ansi_c_convert_type.h"
#include "ansi_c_declaration.h"

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

Function: ansi_c_convertt::convert

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert(ansi_c_parse_treet &ansi_c_parse_tree)
{
  for(std::list<ansi_c_declarationt>::iterator
      it=ansi_c_parse_tree.declarations.begin();
      it!=ansi_c_parse_tree.declarations.end();
      it++)
    convert_declaration(*it);
}

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

Function: ansi_c_convertt::convert_declaration

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert_declaration(ansi_c_declarationt &declaration)
{
  c_storage_spect c_storage_spec;

  convert_type(declaration.type(), c_storage_spec);

  declaration.set_is_inline(c_storage_spec.is_inline);
  declaration.set_is_static(c_storage_spec.is_static);
  declaration.set_is_extern(c_storage_spec.is_extern);
  declaration.set_is_register(c_storage_spec.is_register);

  // do not overwrite is_typedef -- it's done by the parser
  // typedefs are macros
  if(declaration.get_is_typedef())
    declaration.set_is_macro(true);

  // add language prefix
  declaration.set_name(language_prefix+id2string(declaration.get_name()));

  if(declaration.value().is_not_nil())
  {
    if(declaration.type().id()=="code")
      convert_code(to_code(declaration.value()));
    else
      convert_expr(declaration.value());
  }
}

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

Function: ansi_c_convertt::convert_expr

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert_expr(exprt &expr)
{
  Forall_operands(it, expr)
    convert_expr(*it);

  if(expr.id()=="symbol")
  {
    expr.set("identifier", final_id(expr.get("identifier")));
    expr.remove("#id_class");
    expr.remove("#base_name");
  }
  else if(expr.id()=="sizeof")
  {
    if(expr.operands().size()==0)
    {
      typet &type=static_cast<typet &>(expr.add("sizeof-type"));
      convert_type(type);
    }
  }
  else if(expr.id()=="typecast")
  {
    convert_type(expr.type());
  }
}

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

Function: ansi_c_convertt::convert_code

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert_code(codet &code)
{
  const irep_idt &statement=code.get_statement();

  if(statement=="expression")
  {
    assert(code.operands().size()==1);
    convert_expr(code.op0());
  }
  else if(statement=="decl")
  {
    assert(code.operands().size()==1 ||
           code.operands().size()==2);

    convert_expr(code.op0());

    if(code.operands().size()==2)
      convert_expr(code.op1());
  }
  else if(statement=="label")
  {
    assert(code.operands().size()==1);
    convert_code(to_code(code.op0()));

    if(code.find("case").is_not_nil())
      convert_expr(static_cast<exprt &>(code.add("case")));
  }
  else if(statement=="block" ||
          statement=="decl-block")
  {
    Forall_operands(it, code)
      convert_code(to_code(*it));
  }
  else if(statement=="ifthenelse")
  {
    assert(code.operands().size()==2 ||
           code.operands().size()==3);

    convert_expr(code.op0());
    convert_code(to_code(code.op1()));

    if(code.operands().size()==3)
      convert_code(to_code(code.op2()));
  }
  else if(statement=="while" ||
          statement=="dowhile")
  {
    assert(code.operands().size()==2);

    convert_expr(code.op0());
    convert_code(to_code(code.op1()));
  }
  else if(statement=="for")
  {
    assert(code.operands().size()==4);

    if(code.op0().is_not_nil())
      convert_code(to_code(code.op0()));

    if(code.op1().is_not_nil())
      convert_expr(code.op1());

    if(code.op2().is_not_nil())
    {
      convert_expr(code.op2());
      codet tmp("expression");
      tmp.move_to_operands(code.op2());
      code.op2().swap(tmp);
    }

    convert_code(to_code(code.op3()));
  }
  else if(statement=="switch")
  {
    assert(code.operands().size()==2);

    convert_expr(code.op0());
    convert_code(to_code(code.op1()));
  }
  else if(statement=="break")
  {
  }
  else if(statement=="goto")
  {
  }
  else if(statement=="continue")
  {
  }
  else if(statement=="return")
  {
    if(code.operands().size()==1)
      convert_expr(code.op0());
  }
  else if(statement=="decl")
  {
    assert(code.operands().size()==1 ||
           code.operands().size()==2);

    convert_type(code.op0().type());

    if(code.operands().size()==2)
      convert_expr(code.op1());
  }
  else if(statement=="skip")
  {
  }
  else if(statement=="asm")
  {
  }
  else
  {
    err_location(code);
    err << "unexpected statement during conversion: " << statement;
    throw 0;
  }
}

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

Function: ansi_c_convertt::convert_type

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert_type(typet &type)
{
  c_storage_spect c_storage_spec;
  convert_type(type, c_storage_spec);
}

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

Function: ansi_c_convertt::convert_type

  Inputs:

 Outputs:

 Purpose:

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

void ansi_c_convertt::convert_type(
  typet &type,
  c_storage_spect &c_storage_spec)
{
  ansi_c_convert_typet ansi_c_convert_type(get_message_handler());

  ansi_c_convert_type.read(type);
  ansi_c_convert_type.write(type);

  c_storage_spec=ansi_c_convert_type.c_storage_spec;

  if(type.id()=="pointer")
  {
    c_storage_spect sub_storage_spec;

    convert_type(type.subtype(), sub_storage_spec);
    c_storage_spec|=sub_storage_spec;
  }
  else if(type.id()=="c_bitfield")
  {
    convert_type(type.subtype());
    convert_expr(static_cast<exprt &>(type.add("size")));
  }
  else if(type.id()=="symbol")
  {
    irep_idt identifier=final_id(type.get("identifier"));
    type.set("identifier", identifier);
    type.remove("#id_class");
    type.remove("#base_name");
  }
  else if(type.id()=="ansi_c_event")
  {
    if(!ansi_c_convert_type.c_qualifiers.is_empty())
    {
      err_location(type);
      throw "no qualifiers permitted for event type";
    }
  }
  else if(type.id()=="code")
  {
    c_storage_spect sub_storage_spec;

    convert_type(type.subtype(), sub_storage_spec);
    c_storage_spec|=sub_storage_spec;

    code_typet &code_type=to_code_type(type);

    // change subtype to return_type
    code_type.return_type().swap(type.subtype());
    type.remove("subtype");

    // take care of argument types
    code_typet::argumentst &arguments=code_type.arguments();

    // see if we have an ellipsis
    if(!arguments.empty() &&
       arguments.back().id()=="ansi_c_ellipsis")
    {
      code_type.make_ellipsis();
      arguments.pop_back();
    }

    for(code_typet::argumentst::iterator
        it=arguments.begin();
        it!=arguments.end();
        it++)
    {
      if(it->id()=="declaration")
      {
        code_typet::argumentt argument;

        ansi_c_declarationt &declaration=
          to_ansi_c_declaration(*it);

        convert_type(declaration.type());

        irep_idt base_name=declaration.get_base_name();

        argument.type().swap(declaration.type());
        argument.set_base_name(base_name);
        argument.location()=declaration.location();

        argument.set_identifier(
          language_prefix+id2string(declaration.get_name()));

        it->swap(argument);
      }
      else if(it->id()=="ansi_c_ellipsis")
        throw "ellipsis only allowed as last argument";
      else
        throw "unexpected argument: "+it->id_string();
    }
  }
  else if(type.id()=="array")
  {
    array_typet &array_type=to_array_type(type);

    c_storage_spect sub_storage_spec;

    convert_type(array_type.subtype(), sub_storage_spec);
    c_storage_spec|=sub_storage_spec;

    convert_expr(array_type.size());
  }
  else if(type.id()=="incomplete_array")
  {
    c_storage_spect sub_storage_spec;

    convert_type(type.subtype(), sub_storage_spec);
    c_storage_spec|=sub_storage_spec;
  }
  else if(type.id()=="struct" ||
          type.id()=="union" ||
          type.id()=="ansi_c_interface" ||
          type.id()=="ansi_c_channel" ||
          type.id()=="ansi_c_behavior")
  {
    irept::subt &components=type.add("components").get_sub();

    Forall_irep(it, components)
    {
      // the arguments are now declarations
      ansi_c_declarationt &component=
        to_ansi_c_declaration((exprt &)*it);

      exprt new_component("component");

      new_component.location()=component.location();
      new_component.set("name", component.get_base_name());
      new_component.set("pretty_name", component.get_base_name());
      new_component.type().swap(component.type());

      convert_type(new_component.type());

      component.swap(new_component);
    }
  }
  else if(type.id()=="type_of")
  {
    if(type.get_bool("#is_expression"))
      convert_expr((exprt&)type.subtype());
    else
      convert_type(type.subtype());
  }
  else if(type.id()=="c_enum")
  {
  }
  else if(type.id()=="void")
  {
    type.id("empty");
  }
}

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

Function: ansi_c_convert

  Inputs:

 Outputs:

 Purpose:

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

bool ansi_c_convert(
  ansi_c_parse_treet &ansi_c_parse_tree,
  const std::string &module,
  message_handlert &message_handler)
{
  ansi_c_convertt ansi_c_convert(module, message_handler);

  try
  {
    ansi_c_convert.convert(ansi_c_parse_tree);
  }

  catch(int e)
  {
    ansi_c_convert.error();
  }

  catch(const char *e)
  {
    ansi_c_convert.error(e);
  }

  catch(const std::string &e)
  {
    ansi_c_convert.error(e);
  }

  return ansi_c_convert.get_error_found();
}

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

Function: ansi_c_convert

  Inputs:

 Outputs:

 Purpose:

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

bool ansi_c_convert(
  exprt &expr,
  const std::string &module,
  message_handlert &message_handler)
{
  ansi_c_convertt ansi_c_convert(module, message_handler);

  try
  {
    ansi_c_convert.convert_expr(expr);
  }

  catch(int e)
  {
    ansi_c_convert.error();
  }

  catch(const char *e)
  {
    ansi_c_convert.error(e);
  }

  catch(const std::string &e)
  {
    ansi_c_convert.error(e);
  }

  return ansi_c_convert.get_error_found();
}
