/*******************************************************************************
*
* MODULE: ctparse.c
*
********************************************************************************
*
* DESCRIPTION: Parser interface routines
*
********************************************************************************
*
* $Project: /Convert-Binary-C $
* $Author: mhx $
* $Date: 2006/01/04 23:23:20 +0100 $
* $Revision: 57 $
* $Source: /ctlib/ctparse.c $
*
********************************************************************************
*
* Copyright (c) 2002-2006 Marcus Holland-Moritz. All rights reserved.
* This program is free software; you can redistribute it and/or modify
* it under the same terms as Perl itself.
*
*******************************************************************************/

/*===== GLOBAL INCLUDES ======================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#include <assert.h>


/*===== LOCAL INCLUDES =======================================================*/

#include "ctparse.h"
#include "cterror.h"
#include "ctdebug.h"
#include "fileinfo.h"
#include "parser.h"

#include "util/memalloc.h"

#include "ucpp/cpp.h"

#ifdef MEM_DEBUG
#include "ucpp/mem.h" /* for report_leaks() */
#endif

#include "cppreent.h"


/*===== DEFINES ==============================================================*/

#if defined MSDOS || defined WIN32
#define SYSTEM_DIRECTORY_DELIMITER '\\'
#define IS_NON_SYSTEM_DIR_DELIM( c ) ( (c) == '/' )
#else
#define SYSTEM_DIRECTORY_DELIMITER '/'
#define IS_NON_SYSTEM_DIR_DELIM( c ) ( (c) == '\\' )
#endif

#define IS_ANY_DIRECTORY_DELIMITER( c ) ( (c) == '/' || (c) == '\\' )

#define BUFFER_NAME "[buffer]"

/*===== TYPEDEFS =============================================================*/

/*===== STATIC FUNCTION PROTOTYPES ===========================================*/

static char *get_path_name(const char *dir, const char *file);


/*===== EXTERNAL VARIABLES ===================================================*/

/*===== GLOBAL VARIABLES =====================================================*/

#ifndef UCPP_REENTRANT
CParseInfo *g_current_cpi;
#endif


/*===== STATIC VARIABLES =====================================================*/

/*===== STATIC FUNCTIONS =====================================================*/

/*******************************************************************************
*
*   ROUTINE: get_path_name
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

static char *get_path_name(const char *dir, const char *file)
{
  int dirlen = 0, filelen, append_delim = 0;
  char *buf, *b;

  if (dir != NULL)
  {
    dirlen = strlen(dir);
    if (!IS_ANY_DIRECTORY_DELIMITER(dir[dirlen-1]))
      append_delim = 1;
  }

  filelen = strlen(file);

  AllocF(char *, buf, dirlen + append_delim + filelen + 1);
  
  if (dir != NULL)
    strcpy(buf, dir);

  if (append_delim)
    buf[dirlen++] = SYSTEM_DIRECTORY_DELIMITER;

  strcpy(buf+dirlen, file);

  for (b = buf; *b; b++)
    if (IS_NON_SYSTEM_DIR_DELIM(*b))
      *b = SYSTEM_DIRECTORY_DELIMITER;

  return buf;
}


/*===== FUNCTIONS ============================================================*/

/*******************************************************************************
*
*   ROUTINE: parse_buffer
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

int parse_buffer(const char *filename, const Buffer *pBuf,
                 const CParseConfig *pCPC, CParseInfo *pCPI)
{
  int                rval;
  char              *file, *str;
  FILE              *infile;
  struct lexer_state lexer;
  ParserState       *pState;
#ifdef UCPP_REENTRANT
  struct CPP        *cpp;
#endif

  CT_DEBUG(CTLIB, ("ctparse::parse_buffer( %s, %p, %p, %p )",
           filename ? filename : BUFFER_NAME, pBuf, pCPI, pCPC));

#ifndef UCPP_REENTRANT
  g_current_cpi = pCPI;
#endif

  /*----------------------------------*/
  /* Initialize parse info structures */
  /*----------------------------------*/

  if (!pCPI->available)
  {
    assert(pCPI->enums == NULL);
    assert(pCPI->structs == NULL);
    assert(pCPI->typedef_lists == NULL);

    assert(pCPI->htEnumerators == NULL);
    assert(pCPI->htEnums == NULL);
    assert(pCPI->htStructs == NULL);
    assert(pCPI->htTypedefs == NULL);
    assert(pCPI->htFiles == NULL);

    CT_DEBUG(CTLIB, ("creating linked lists"));

    pCPI->enums         = LL_new();
    pCPI->structs       = LL_new();
    pCPI->typedef_lists = LL_new();

    pCPI->htEnumerators = HT_new_ex(5, HT_AUTOGROW);
    pCPI->htEnums       = HT_new_ex(4, HT_AUTOGROW);
    pCPI->htStructs     = HT_new_ex(4, HT_AUTOGROW);
    pCPI->htTypedefs    = HT_new_ex(4, HT_AUTOGROW);
    pCPI->htFiles       = HT_new_ex(3, HT_AUTOGROW);

    pCPI->errorStack    = LL_new();

    pCPI->available     = 1;
  }
  else if (pCPI->enums != NULL && pCPI->structs != NULL &&
           pCPI->typedef_lists != NULL)
  {
    CT_DEBUG(CTLIB, ("re-using linked lists"));
    pop_all_errors(pCPI);
  }
  else
    fatal_error("CParseInfo is inconsistent!");

  /* make sure we trigger update_parse_info() afterwards */
  pCPI->ready = 0;

  /*----------------------------*/
  /* Try to open the input file */
  /*----------------------------*/

  infile = NULL;

  if (filename != NULL)
  {
    file = get_path_name(NULL, filename);

    CT_DEBUG(CTLIB, ("Trying '%s'...", file));

    infile = fopen(file, "r");

    if (infile == NULL)
    {
      LL_foreach(str, pCPC->includes)
      {
        Free(file);

        file = get_path_name(str, filename);

        CT_DEBUG(CTLIB, ("Trying '%s'...", file));

        if((infile = fopen(file, "r")) != NULL)
          break;
      }

      if (infile == NULL)
      {
        Free(file);
        push_error(pCPI, "Cannot find input file '%s'", filename);
#ifndef UCPP_REENTRANT
        g_current_cpi = NULL;
#endif
        return 0;
      }
    }
  }

  /*-------------------------*/
  /* Set up the preprocessor */
  /*-------------------------*/

  CT_DEBUG(CTLIB, ("initializing preprocessor"));

#ifdef UCPP_REENTRANT
  cpp = new_cpp();
#endif

  init_cpp(aUCPP);

#ifdef UCPP_REENTRANT
  cpp->ucpp_ouch    = my_ucpp_ouch;
  cpp->ucpp_error   = my_ucpp_error;
  cpp->ucpp_warning = my_ucpp_warning;
  cpp->callback_arg = (void *) pCPI;
#endif

  r_no_special_macros = 0;
  r_emit_defines      = 0;
  r_emit_assertions   = 0;
  r_emit_dependencies = 0;

  init_tables( aUCPP_ 1 );

  CT_DEBUG(CTLIB, ("configuring preprocessor"));

  init_include_path(aUCPP_ NULL);

  if (filename != NULL)
  {
    set_init_filename(aUCPP_ file, 1);
    Free(file);
  }
  else
    set_init_filename(aUCPP_ BUFFER_NAME, 0);

  init_lexer_state(&lexer);
  init_lexer_mode(&lexer);

  lexer.flags |= HANDLE_ASSERTIONS
              |  HANDLE_PRAGMA
              |  LINE_NUM;

  if (pCPC->issue_warnings)
    lexer.flags |= WARN_STANDARD
                |  WARN_ANNOYING
                |  WARN_TRIGRAPHS
                |  WARN_TRIGRAPHS_MORE;

  if (pCPC->has_cpp_comments)
    lexer.flags |= CPLUSPLUS_COMMENTS;

  if (pCPC->has_macro_vaargs)
    lexer.flags |= MACRO_VAARG;

  if (infile != NULL)
  {
    lexer.input        = infile;
  }
  else
  {
    lexer.input        = NULL;
    lexer.input_string = (unsigned char *) pBuf->buffer;
    lexer.pbuf         = pBuf->pos;
    lexer.ebuf         = pBuf->length;
  }

  /* Add includes */

  LL_foreach(str, pCPC->includes)
  {
    CT_DEBUG(CTLIB, ("adding include path '%s'", str));
    add_incpath(aUCPP_ str);
  }

  /* Make defines */

  LL_foreach(str, pCPC->defines)
  {
    CT_DEBUG(CTLIB, ("defining macro '%s'", str));
    (void) define_macro(aUCPP_ &lexer, str);
  }

  /* Make assertions */

  LL_foreach(str, pCPC->assertions)
  {
    CT_DEBUG(CTLIB, ("making assertion '%s'", str));
    (void) make_assertion(aUCPP_ str);
  }

  enter_file(aUCPP_ &lexer, lexer.flags);

  /*---------------------*/
  /* Create the C parser */
  /*---------------------*/

  pState = c_parser_new(pCPC, pCPI, aUCPP_ &lexer);

  /*-----------------*/
  /* Parse the input */
  /*-----------------*/

  if (pCPC->disable_parser)
  {
    CT_DEBUG(CTLIB, ("parser is disabled, running only preprocessor"));
    rval = 0;
  }
  else
  {
    CT_DEBUG(CTLIB, ("entering parser"));
    rval = c_parser_run(pState);
    CT_DEBUG(CTLIB, ("c_parse() returned %d", rval));
  }

  /*-------------------------------*/
  /* Finish parsing (cleanup ucpp) */
  /*-------------------------------*/

  if (rval || pCPC->disable_parser)
    while (lex(aUCPP_ &lexer) < CPPERR_EOF);

  (void) check_cpp_errors(aUCPP_ &lexer);

  if (DEBUG_FLAG(PREPROC))
  {
#ifdef UCPP_REENTRANT
    cpp->
#endif
    emit_output = stderr;  /* the best we can get here... */
    print_defines(aUCPP);
  }

  free_lexer_state(&lexer);
  wipeout(aUCPP);

#ifdef UCPP_REENTRANT
  del_cpp(cpp);
#endif

#ifdef MEM_DEBUG
  report_leaks();
#endif

  /*----------------------*/
  /* Cleanup the C parser */
  /*----------------------*/

  c_parser_delete(pState);

  /* Invalidate the buffer name in the parsed files table */

  if (filename == NULL)
    ((FileInfo *) HT_get(pCPI->htFiles, BUFFER_NAME, 0, 0))->valid = 0;

#if !defined NDEBUG && defined CTLIB_DEBUGGING
  if (DEBUG_FLAG(HASH))
  {
    HT_dump(pCPI->htEnumerators);
    HT_dump(pCPI->htEnums);
    HT_dump(pCPI->htStructs);
    HT_dump(pCPI->htTypedefs);
    HT_dump(pCPI->htFiles);
  }
#endif

#ifndef UCPP_REENTRANT
  g_current_cpi = NULL;
#endif

  return rval ? 0 : 1;
}

/*******************************************************************************
*
*   ROUTINE: init_parse_info
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void init_parse_info(CParseInfo *pCPI)
{
  CT_DEBUG( CTLIB, ("ctparse::init_parse_info()") );

  if (pCPI)
  {
    pCPI->typedef_lists = NULL;
    pCPI->structs       = NULL;
    pCPI->enums         = NULL;

    pCPI->htEnumerators = NULL;
    pCPI->htEnums       = NULL;
    pCPI->htStructs     = NULL;
    pCPI->htTypedefs    = NULL;
    pCPI->htFiles       = NULL;

    pCPI->errorStack    = NULL;

    pCPI->available     = 0;
    pCPI->ready         = 0;
  }
}

/*******************************************************************************
*
*   ROUTINE: free_parse_info
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void free_parse_info(CParseInfo *pCPI)
{
  CT_DEBUG(CTLIB, ("ctparse::free_parse_info()"));

  if (pCPI)
  {
    if (pCPI->available)
    {
      LL_destroy(pCPI->enums,         (LLDestroyFunc) enumspec_delete);
      LL_destroy(pCPI->structs,       (LLDestroyFunc) struct_delete);
      LL_destroy(pCPI->typedef_lists, (LLDestroyFunc) typedef_list_delete);

      HT_destroy(pCPI->htEnumerators, NULL);
      HT_destroy(pCPI->htEnums,       NULL);
      HT_destroy(pCPI->htStructs,     NULL);
      HT_destroy(pCPI->htTypedefs,    NULL);

      HT_destroy(pCPI->htFiles,       (LLDestroyFunc) fileinfo_delete);

      if (pCPI->errorStack)
      {
        pop_all_errors(pCPI);
        LL_delete(pCPI->errorStack);
      }
    }

    init_parse_info(pCPI);  /* make sure everything is NULL'd */
  }
}

/*******************************************************************************
*
*   ROUTINE: reset_parse_info
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void reset_parse_info(CParseInfo *pCPI)
{
  Struct *pStruct;
  TypedefList *pTDL;
  Typedef *pTD;

  CT_DEBUG(CTLIB, ("ctparse::reset_parse_info(): got %d struct(s)",
                   LL_count(pCPI->structs)));

  /* clear size and align fields */
  LL_foreach(pStruct, pCPI->structs)
  {
    CT_DEBUG(CTLIB, ("resetting struct '%s':", pStruct->identifier[0] ?
                     pStruct->identifier : "<no-identifier>"));

    pStruct->align = 0;
    pStruct->size  = 0;
  }

  LL_foreach(pTDL, pCPI->typedef_lists)
    LL_foreach(pTD, pTDL->typedefs)
    {
      pTD->pDecl->size      = -1;
      pTD->pDecl->item_size = -1;
    }

  pCPI->ready = 0;
}

/*******************************************************************************
*
*   ROUTINE: update_parse_info
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jan 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void update_parse_info(CParseInfo *pCPI, const CParseConfig *pCPC)
{
  Struct *pStruct;
  TypedefList *pTDL;
  Typedef *pTD;

  CT_DEBUG(CTLIB, ("ctparse::update_parse_info(): got %d struct(s)",
                   LL_count(pCPI->structs)));

  /* compute size and alignment */
  LL_foreach(pStruct, pCPI->structs)
  {
    CT_DEBUG(CTLIB, ("updating struct '%s':", pStruct->identifier[0] ?
                     pStruct->identifier : "<no-identifier>"));

    if (pStruct->align == 0)
      pCPC->layout_compound(&pCPC->layout, pStruct);
  }

  LL_foreach(pTDL, pCPI->typedef_lists)
    LL_foreach(pTD, pTDL->typedefs)
      if (pTD->pDecl->size < 0)
      {
        unsigned size, item_size;

        if (pCPC->get_type_info(&pCPC->layout, pTD->pType, pTD->pDecl,
                                "si", &size, &item_size) == GTI_NO_ERROR)
        {
          pTD->pDecl->size      = (int) size;
          pTD->pDecl->item_size = (int) item_size;
        }
      }

  pCPI->ready = 1;
}

/*******************************************************************************
*
*   ROUTINE: clone_parse_info
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Oct 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

#define PTR_NOT_FOUND(ptr)                                                     \
          fatal_error("FATAL: pointer " #ptr " (%p) not found! (%s:%d)\n",     \
                      ptr, __FILE__, __LINE__)

#define REMAP_PTR(what, target)                                                \
        do {                                                                   \
          if (target != NULL)                                                  \
          {                                                                    \
            void *ptr = HT_get(ptrmap, (const char *) &target,                 \
                               sizeof(void *), 0);                             \
                                                                               \
            CT_DEBUG(CTLIB, (#what ": %p => %p", target, ptr));                \
                                                                               \
            if (ptr)                                                           \
              target = ptr;                                                    \
            else                                                               \
              PTR_NOT_FOUND((void *) target);                                  \
          }                                                                    \
        } while (0)

void clone_parse_info(CParseInfo *pDest, const CParseInfo *pSrc)
{
  HashTable      ptrmap;
  EnumSpecifier *pES;
  Struct        *pStruct;
  TypedefList   *pTDL;

  CT_DEBUG(CTLIB, ("ctparse::clone_parse_info()"));

  if (!pSrc->available)
    return;  /* don't clone empty objects */

  assert(pSrc->enums != NULL);
  assert(pSrc->structs != NULL);
  assert(pSrc->typedef_lists != NULL);

  assert(pSrc->htEnumerators != NULL);
  assert(pSrc->htEnums != NULL);
  assert(pSrc->htStructs != NULL);
  assert(pSrc->htTypedefs != NULL);
  assert(pSrc->htFiles != NULL);

  ptrmap = HT_new_ex(3, HT_AUTOGROW);

  pDest->enums         = LL_new();
  pDest->structs       = LL_new();
  pDest->typedef_lists = LL_new();
  pDest->htEnumerators = HT_new_ex(HT_size(pSrc->htEnumerators), HT_AUTOGROW);
  pDest->htEnums       = HT_new_ex(HT_size(pSrc->htEnums), HT_AUTOGROW);
  pDest->htStructs     = HT_new_ex(HT_size(pSrc->htStructs), HT_AUTOGROW);
  pDest->htTypedefs    = HT_new_ex(HT_size(pSrc->htTypedefs), HT_AUTOGROW);
  pDest->errorStack    = LL_new();
  pDest->available     = pSrc->available;
  pDest->ready         = pSrc->ready;

  CT_DEBUG(CTLIB, ("cloning enums"));

  LL_foreach(pES, pSrc->enums)
  {
    Enumerator    *pEnum;
    EnumSpecifier *pClone = enumspec_clone(pES);

    CT_DEBUG(CTLIB, ("storing pointer to map: %p <=> %p", pES, pClone));
    HT_store(ptrmap, (const char *) &pES, sizeof(pES), 0, pClone);
    LL_push(pDest->enums, pClone);

    if (pClone->identifier[0])
      HT_store(pDest->htEnums, pClone->identifier, 0, 0, pClone);

    LL_foreach(pEnum, pClone->enumerators)
      HT_store(pDest->htEnumerators, pEnum->identifier, 0, 0, pEnum);
  }

  CT_DEBUG(CTLIB, ("cloning structs"));

  LL_foreach(pStruct, pSrc->structs)
  {
    Struct *pClone = struct_clone(pStruct);

    CT_DEBUG(CTLIB, ("storing pointer to map: %p <=> %p", pStruct, pClone));
    HT_store(ptrmap, (const char *) &pStruct, sizeof(pStruct), 0, pClone);
    LL_push(pDest->structs, pClone);

    if (pClone->identifier[0])
      HT_store(pDest->htStructs, pClone->identifier, 0, 0, pClone);
  }

  CT_DEBUG(CTLIB, ("cloning typedefs"));

  LL_foreach(pTDL, pSrc->typedef_lists)
  {
    TypedefList *pClone = typedef_list_clone(pTDL);
    Typedef *pOld, *pNew;

    LL_reset(pTDL->typedefs);
    LL_reset(pClone->typedefs);

    while ((pOld = LL_next(pTDL->typedefs))   != NULL &&
           (pNew = LL_next(pClone->typedefs)) != NULL)
    {
      CT_DEBUG(CTLIB, ("storing pointer to map: %p <=> %p", pOld, pNew));
      HT_store(ptrmap, (const char *) &pOld, sizeof(pOld), 0, pNew);
      HT_store(pDest->htTypedefs, pNew->pDecl->identifier, 0, 0, pNew);
    }

    LL_push(pDest->typedef_lists, pClone);
  }

  CT_DEBUG(CTLIB, ("cloning file information"));

  {
    void *pOld, *pNew;

    pDest->htFiles = HT_clone(pSrc->htFiles, (HTCloneFunc) fileinfo_clone);

    HT_reset(pSrc->htFiles);
    HT_reset(pDest->htFiles);

    while (HT_next(pSrc->htFiles, NULL, NULL, &pOld) &&
           HT_next(pDest->htFiles, NULL, NULL, &pNew))
    {
      CT_DEBUG(CTLIB, ("storing pointer to map: %p <=> %p", pOld, pNew));
      HT_store(ptrmap, (const char *) &pOld, sizeof(pOld), 0, pNew);
    }
  }

  CT_DEBUG(CTLIB, ("remapping pointers for enums"));

  LL_foreach(pES, pDest->enums)
    REMAP_PTR(EnumSpec, pES->context.pFI);

  CT_DEBUG(CTLIB, ("remapping pointers for structs"));

  LL_foreach(pStruct, pDest->structs)
  {
    StructDeclaration *pStructDecl;

    CT_DEBUG(CTLIB, ("remapping pointers for struct @ %p ('%s')",
                     pStruct, pStruct->identifier));

    LL_foreach(pStructDecl, pStruct->declarations)
      REMAP_PTR(StructDecl, pStructDecl->type.ptr);

    REMAP_PTR(Struct, pStruct->context.pFI);
  }

  CT_DEBUG(CTLIB, ("remapping pointers for typedef lists"));

  LL_foreach(pTDL, pDest->typedef_lists)
    REMAP_PTR(TypedefList, pTDL->type.ptr);

  HT_destroy(ptrmap, NULL);
}

#undef REMAP_PTR
#undef PTR_NOT_FOUND