/*******************************************************************************
*
* MODULE: member.c
*
********************************************************************************
*
* DESCRIPTION: C::B::C struct member utilities
*
********************************************************************************
*
* $Project: /Convert-Binary-C $
* $Author: mhx $
* $Date: 2006/01/05 00:49:59 +0100 $
* $Revision: 15 $
* $Source: /cbc/member.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 ======================================================*/

#define PERL_NO_GET_CONTEXT
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#include "ppport.h"


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

#include "cbc/member.h"
#include "cbc/util.h"


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

/* for fast index -> string conversion */
#define MAX_IXSTR 15


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

typedef enum { GMS_NONE, GMS_PAD, GMS_HIT_OFF, GMS_HIT } GMSRV;

typedef union {
  LinkedList list;
  int        count;
} AMSInfo;


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

static void get_ams_struct(pTHX_ Struct *pStruct, SV *name, int level, AMSInfo *info);
static void get_ams_type(pTHX_ TypeSpec *pTS, Declarator *pDecl, int dimension,
                         SV *name, int level, AMSInfo *info);

static GMSRV append_member_string_rec(pTHX_ const TypeSpec *pType, const Declarator *pDecl,
                                      int offset, SV *sv, GMSInfo *pInfo);
static GMSRV get_member_string_rec(pTHX_ const Struct *pStruct, int offset,
                                   int realoffset, SV *sv, GMSInfo *pInfo);

static int search_struct_member(Struct *pStruct, const char *elem,
                                StructDeclaration **ppSD, Declarator **ppD);


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

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

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

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

/*******************************************************************************
*
*   ROUTINE: get_ams_struct
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jul 2003
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

static void get_ams_struct(pTHX_ Struct *pStruct, SV *name, int level, AMSInfo *info)
{
  StructDeclaration *pStructDecl;
  Declarator        *pDecl;
  STRLEN             len;

  CT_DEBUG(MAIN, (XSCLASS "::get_ams_struct( pStruct=%p, name='%s', level=%d, info=%p )",
           pStruct, name ? SvPV_nolen(name) : "", level, info));

  if (name)
  {
    len = SvCUR(name);
    sv_catpvn_nomg(name, ".", 1);
  }

  LL_foreach(pStructDecl, pStruct->declarations)
  {
    if (pStructDecl->declarators)
    {
      LL_foreach(pDecl, pStructDecl->declarators)
      {
        /* skip unnamed bitfield members right here */
        if (pDecl->bitfield_flag && pDecl->identifier[0] == '\0')
          continue;

        if (name)
        {
          SvCUR_set(name, len+1);
          sv_catpvn_nomg(name, pDecl->identifier, CTT_IDLEN(pDecl));
        }

        get_ams_type(aTHX_ &pStructDecl->type, pDecl, 0, name, level+1, info);
      }
    }
    else
    {
      TypeSpec *pTS = &pStructDecl->type;
      FOLLOW_AND_CHECK_TSPTR(pTS);
      if (name)
        SvCUR_set(name, len);
      get_ams_struct(aTHX_ (Struct *) pTS->ptr, name, level+1, info);
    }
  }

  if (name)
    SvCUR_set(name, len);
}

/*******************************************************************************
*
*   ROUTINE: get_ams_type
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jul 2003
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

static void get_ams_type(pTHX_ TypeSpec *pTS, Declarator *pDecl, int dimension,
                         SV *name, int level, AMSInfo *info)
{
  CT_DEBUG(MAIN, (XSCLASS "::get_ams_type( pTS=%p, pDecl=%p, dimension=%d, "
           "name='%s', level=%d, info=%p )", pTS, pDecl, dimension,
           name ? SvPV_nolen(name) : "", level, info));

  if (pDecl && pDecl->array_flag && dimension < LL_count(pDecl->ext.array))
  {
    Value *pValue = (Value *) LL_get(pDecl->ext.array, dimension);

    if ((pValue->flags & V_IS_UNDEF) == 0)
    {
      long i, ix, s = pValue->iv;
      STRLEN len;
      char ixstr[MAX_IXSTR+1];
      int  ixlen;

      if (name)
      {
        len = SvCUR(name);
        sv_catpvn_nomg(name, "[", 1);
        ixstr[MAX_IXSTR-1] = ']';
        ixstr[MAX_IXSTR]   = '\0';
      }

      for (i = 0; i < s; i++)
      {
        if (name)
        {
          SvCUR_set(name, len+1);

          for (ix = i, ixlen = 2; ixlen < MAX_IXSTR; ix /= 10, ixlen++)
          {
            ixstr[MAX_IXSTR-ixlen] = (char)('0'+(ix%10));
            if (ix < 10)
              break;
          }

          sv_catpvn_nomg(name, ixstr+MAX_IXSTR-ixlen, ixlen);
        }

        get_ams_type(aTHX_ pTS, pDecl, dimension+1, name, level+1, info);
      }

      if (name)
        SvCUR_set(name, len);
    }
  }
  else
  {
    if (pDecl && pDecl->pointer_flag)
      goto handle_basic;
    else if (pTS->tflags & T_TYPE)
    {
      Typedef *pTD = (Typedef *) pTS->ptr;
      get_ams_type(aTHX_ pTD->pType, pTD->pDecl, 0, name, level, info);
    }
    else if (pTS->tflags & T_COMPOUND)
    {
      Struct *pStruct = pTS->ptr;

      if (pStruct->declarations == NULL)
        WARN_UNDEF_STRUCT(pStruct);

      get_ams_struct(aTHX_ pStruct, name, level, info);
    }
    else
    {
handle_basic:
      if (name)
        LL_push(info->list, newSVsv(name));
      else
        info->count++;
    }
  }
}

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

static GMSRV append_member_string_rec(pTHX_ const TypeSpec *pType, const Declarator *pDecl,
                                      int offset, SV *sv, GMSInfo *pInfo)
{
  CT_DEBUG(MAIN, ("append_member_string_rec( off=%d, sv='%s' )", offset, SvPV_nolen(sv)));

  if (pDecl && pDecl->identifier[0] != '\0')
  {
    CT_DEBUG(MAIN, ("Appending identifier [%s]", pDecl->identifier));
    sv_catpvf(sv, ".%s", CONST_CHAR(pDecl->identifier));
  }

  if (pDecl == NULL && pType->tflags & T_TYPE)
  {
    Typedef *pTypedef = (Typedef *) pType->ptr;
    pDecl = pTypedef->pDecl;
    pType = pTypedef->pType;
  }

  if (pDecl != NULL)
  {
    if (pDecl->offset > 0)
      offset -= pDecl->offset;

    for(;;)
    {
      int index, size;
      Value *pValue;

      if (pDecl->size < 0)
        fatal("pDecl->size is not initialized in append_member_string_rec()");

      size = pDecl->size;

      if (pDecl->array_flag)
      {
        LL_foreach(pValue, pDecl->ext.array)
        {
          size /= pValue->iv;
          index = offset/size;
          CT_DEBUG(MAIN, ("Appending array size [%d]", index));
          sv_catpvf(sv, "[%d]", index);
          offset -= index*size;
        }
      }

      if (pDecl->pointer_flag || (pType->tflags & T_TYPE) == 0)
        break;

      do
      {
        Typedef *pTypedef = (Typedef *) pType->ptr;
        pDecl = pTypedef->pDecl;
        pType = pTypedef->pType;
      }
      while (!pDecl->pointer_flag &&
             pType->tflags & T_TYPE &&
             pDecl->array_flag == 0);
    }
  }

  if ((pDecl == NULL || !pDecl->pointer_flag) &&
      pType->tflags & T_COMPOUND)
    return get_member_string_rec(aTHX_ pType->ptr, offset, offset, sv, pInfo);

  if (offset > 0)
  {
    CT_DEBUG(MAIN, ("Appending type offset [+%d]", offset));
    sv_catpvf(sv, "+%d", offset);

    if (pInfo && pInfo->off)
      LL_push(pInfo->off, newSVsv(sv));

    return GMS_HIT_OFF;
  }

  if (pInfo && pInfo->hit)
    LL_push(pInfo->hit, newSVsv(sv));

  return GMS_HIT;
}

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

#define GMS_HANDLE_PAD_REGION                                                  \
        STMT_START {                                                           \
          CT_DEBUG(MAIN, ("Padding region found, exiting"));                   \
          sv_catpvf(sv, "+%d", realoffset);                                    \
          if (pInfo && pInfo->pad)                                             \
          {                                                                    \
            const char *str;                                                   \
            STRLEN      len;                                                   \
            str = SvPV(sv, len);                                               \
            if (HT_store(pInfo->htpad, str, len, 0, NULL))                     \
              LL_push(pInfo->pad, newSVsv(sv));                                \
          }                                                                    \
          return GMS_PAD;                                                      \
        } STMT_END

#define GMS_HANDLE_BEST_MEMBER                                                 \
        STMT_START {                                                           \
          if (rval > best)                                                     \
          {                                                                    \
            CT_DEBUG(MAIN, ("New member [%s] has better ranking (%d) than "    \
                            "old member [%s] (%d)", SvPV_nolen(tmpSV), rval,   \
                            bestSV ? SvPV_nolen(bestSV) : "", best));          \
                                                                               \
            best = rval;                                                       \
                                                                               \
            if (bestSV)                                                        \
            {                                                                  \
              SV *t;                                                           \
              t      = tmpSV;                                                  \
              tmpSV  = bestSV;                                                 \
              bestSV = t;                                                      \
            }                                                                  \
            else                                                               \
            {                                                                  \
              bestSV = tmpSV;                                                  \
              tmpSV  = NULL;                                                   \
            }                                                                  \
          }                                                                    \
                                                                               \
          if (best == GMS_HIT && pInfo == NULL)                                \
          {                                                                    \
            CT_DEBUG(MAIN, ("Hit struct member without offset"));              \
            goto handle_union_end;                                             \
          }                                                                    \
        } STMT_END

static GMSRV get_member_string_rec(pTHX_ const Struct *pStruct, int offset,
                                   int realoffset, SV *sv, GMSInfo *pInfo)
{
  StructDeclaration *pStructDecl;
  Declarator        *pDecl;
  SV                *tmpSV, *bestSV;
  GMSRV              best;
  int                isUnion;

  CT_DEBUG(MAIN, ("get_member_string_rec( off=%d, roff=%d, sv='%s' )",
                  offset, realoffset, SvPV_nolen(sv)));

  if (pStruct->declarations == NULL)
  {
    WARN_UNDEF_STRUCT(pStruct);
    return GMS_NONE;
  }

  if ((isUnion = pStruct->tflags & T_UNION) != 0)
  {
    best   = GMS_NONE;
    bestSV = NULL;
    tmpSV  = NULL;
  }

  LL_foreach(pStructDecl, pStruct->declarations)
  {
    CT_DEBUG(MAIN, ("Current StructDecl: offset=%d size=%d decl=%p",
             pStructDecl->offset, pStructDecl->size, pStructDecl->declarators));

    if (pStructDecl->offset > offset)
      GMS_HANDLE_PAD_REGION;

    if (pStructDecl->offset <= offset &&
        offset < pStructDecl->offset+pStructDecl->size)
    {
      CT_DEBUG(MAIN, ("Member possilbly within current StructDecl (%d <= %d < %d)",
               pStructDecl->offset, offset, pStructDecl->offset+pStructDecl->size));

      if (pStructDecl->declarators == NULL)
      {
        TypeSpec *pTS;

        CT_DEBUG(MAIN, ("Current StructDecl is an unnamed %s",
                 isUnion ? "union" : "struct"));

        pTS = &pStructDecl->type;
        FOLLOW_AND_CHECK_TSPTR(pTS);

        if (isUnion)
        {
          GMSRV rval;

          if (tmpSV == NULL)
            tmpSV = newSVsv(sv);
          else
            sv_setsv(tmpSV, sv);

          rval = get_member_string_rec(aTHX_ (Struct *) pTS->ptr, offset,
                                       realoffset, tmpSV, pInfo);

          GMS_HANDLE_BEST_MEMBER;
        }
        else /* not isUnion */
        {
          return get_member_string_rec(aTHX_ (Struct *) pTS->ptr,
                                       offset - pStructDecl->offset,
                                       realoffset, sv, pInfo);
        }
      }
      else
      {
        LL_foreach(pDecl, pStructDecl->declarators)
        {
          CT_DEBUG(MAIN, ("Current Declarator [%s]: offset=%d size=%d",
                          pDecl->identifier, pDecl->offset, pDecl->size));

          if (pDecl->offset > offset)
            GMS_HANDLE_PAD_REGION;

          if (pDecl->offset <= offset && offset < pDecl->offset+pDecl->size)
          {
            CT_DEBUG(MAIN, ("Member possibly within current Declarator [%s] "
                     "( %d <= %d < %d )", pDecl->identifier,
                     pDecl->offset, offset, pDecl->offset+pDecl->size));

            if (isUnion)
            {
              GMSRV rval;

              if (tmpSV == NULL)
                tmpSV = newSVsv(sv);
              else
                sv_setsv(tmpSV, sv);

              rval = append_member_string_rec(aTHX_ &pStructDecl->type, pDecl,
                                              offset, tmpSV, pInfo);

              GMS_HANDLE_BEST_MEMBER;
            }
            else /* not isUnion */
            {
              return append_member_string_rec(aTHX_ &pStructDecl->type, pDecl,
                                              offset, sv, pInfo);
            }
          }
        }
      }
    }
  }

  CT_DEBUG(MAIN, ("End of %s reached", isUnion ? "union" : "struct"));

  if (!isUnion || bestSV == NULL)
    GMS_HANDLE_PAD_REGION;

handle_union_end:

  if (!isUnion)
    fatal("not a union!");

  if (bestSV == NULL)
    fatal("bestSV not set!");

  sv_setsv(sv, bestSV);

  SvREFCNT_dec(bestSV);

  if (tmpSV)
    SvREFCNT_dec(tmpSV);

  return best;
}

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

static int search_struct_member(Struct *pStruct, const char *elem,
                                StructDeclaration **ppSD, Declarator **ppD)
{
  StructDeclaration *pStructDecl;
  Declarator        *pDecl = NULL;
  int                offset;

  LL_foreach(pStructDecl, pStruct->declarations)
  {
    if (pStructDecl->declarators)
    {
      LL_foreach(pDecl, pStructDecl->declarators)
      {
        if (strEQ(pDecl->identifier, elem))
          break;
      }

      if (pDecl)
      {
        offset = pDecl->offset;
        break;
      }
    }
    else
    {
      TypeSpec *pTS = &pStructDecl->type;

      FOLLOW_AND_CHECK_TSPTR(pTS);

      offset  = pStructDecl->offset;
      offset += search_struct_member((Struct *) pTS->ptr, elem, &pStructDecl, &pDecl);

      if (pDecl)
        break;
    }
  }

  *ppSD = pStructDecl;
  *ppD  = pDecl;

  if (pDecl)
    return offset < 0 ? 0 : offset;

  return -1;
}


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

/*******************************************************************************
*
*   ROUTINE: get_all_member_strings
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Jul 2003
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

int get_all_member_strings(pTHX_ MemberInfo *pMI, LinkedList list)
{
  AMSInfo info;

  if (list)
    info.list = list;
  else
    info.count = 0;

  get_ams_type(aTHX_ &pMI->type, pMI->pDecl, pMI->level,
               list ? sv_2mortal(newSVpvn("", 0)) : NULL, 0, &info);

  return list ? LL_count(list) : info.count;
}

/*******************************************************************************
*
*   ROUTINE: get_member_string
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Apr 2003
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

SV *get_member_string(pTHX_ const MemberInfo *pMI, int offset, GMSInfo *pInfo)
{
  GMSRV rval;
  SV *sv;
  int dim;

  CT_DEBUG(MAIN, ("get_member_string( off=%d )", offset));

  if (pInfo)
    pInfo->htpad = HT_new(4);

  sv = newSVpvn("", 0);

  /* handle array remainder here */
  if (pMI->pDecl && pMI->pDecl->array_flag &&
      pMI->level < (dim = LL_count(pMI->pDecl->ext.array)))
  {
    int i, index, size = pMI->size;

    for (i = pMI->level; i < dim; i++)
    {
      size /= ((Value *) LL_get(pMI->pDecl->ext.array, i))->iv;
      index = offset / size;
      sv_catpvf(sv, "[%d]", index);
      offset -= index*size;
    }
  }

  rval = append_member_string_rec(aTHX_ &pMI->type, NULL, offset, sv, pInfo);

  if (pInfo)
    HT_destroy(pInfo->htpad, NULL);

  if (rval == GMS_NONE)
  {
    SvREFCNT_dec(sv);
    sv = newSV(0);
  }

  return sv_2mortal(sv);
}

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

#define TRUNC_ELEM                                                             \
        STMT_START {                                                           \
          if (strlen(elem) > 20)                                               \
          {                                                                    \
            elem[17] = elem[18] = elem[19] = '.';                              \
            elem[20] = '\0';                                                   \
          }                                                                    \
        } STMT_END

#define PROPAGATE_FLAGS(from)                                                  \
        STMT_START {                                                           \
          if (pMIout)                                                          \
            pMIout->flags |= (from) & (T_HASBITFIELD | T_UNSAFE_VAL);          \
        } STMT_END

#define CANNOT_ACCESS_MEMBER(type)                                             \
        STMT_START {                                                           \
          (void) strcpy(elem, c);                                              \
          TRUNC_ELEM;                                                          \
          (void) sprintf(err = errbuf,                                         \
                         "Cannot access member '%s%s' of " type " type",       \
                         dot, c);                                              \
          goto error;                                                          \
        } STMT_END

int get_member(pTHX_ const MemberInfo *pMI, const char *member,
               MemberInfo *pMIout, unsigned gm_flags)
{
  int                accept_dotless_member = gm_flags & CBC_GM_ACCEPT_DOTLESS_MEMBER;
  const int          do_calc = (gm_flags & CBC_GM_NO_OFFSET_SIZE_CALC) == 0;
  const TypeSpec    *pType;
  const char        *c, *ixstr, *dot;
  char              *e, *elem;
  int                size, level, t_off, inc_c;
  UV                 offset;
  Struct            *pStruct;
  StructDeclaration *pSD;
  Declarator        *pDecl;
  char              *err, errbuf[128];

  enum {
    ST_MEMBER,
    ST_INDEX,
    ST_FINISH_INDEX,
    ST_SEARCH
  }                  state;

#ifdef CBC_DEBUGGING
  static const char *Sstate[] = {
    "ST_MEMBER",
    "ST_INDEX",
    "ST_FINISH_INDEX",
    "ST_SEARCH"
  };
#endif

  Newz(0, elem, strlen(member)+1, char);

  if (pMIout)
    pMIout->flags = 0;

  pType = &pMI->type;
  pDecl = pMI->pDecl;

  if (pDecl == NULL && pType->tflags & T_TYPE)
  {
    Typedef *pTypedef = (Typedef *) pType->ptr;
    pDecl = pTypedef->pDecl;
    pType = pTypedef->pType;
  }

  err    = NULL;
  c      = member;
  state  = ST_SEARCH;
  offset = 0;
  level  = pMI->level;
  size   = do_calc ? -1 : 0;

  if (do_calc && pDecl)
  {
    int i;

    size = pDecl->size;

    if (level > 0)
    {
      assert(pDecl->array_flag);

      if (size < 0)
        fatal("pDecl->size is not initialized in get_member()");

      for (i = 0; i < level; i++)
        size /= ((Value *) LL_get(pDecl->ext.array, i))->iv;
    }
  }

  for (;;)
  {
    CT_DEBUG(MAIN, ("state = %s (%d) \"%s\" (offset=%"UVuf", level=%d, size=%d)",
                    Sstate[state], state, c, offset, level, size));

    while (*c && isSPACE(*c))
      c++;

    if (*c == '\0')
      break;

    switch (state)
    {
      case ST_MEMBER:
        if(!(isALPHA(*c) || *c == '_'))
        {
          err = "Struct members must start with a character or an underscore";
          goto error;
        }

        e = elem;
        do *e++ = *c++; while (*c && (isALNUM(*c) || *c == '_'));
        *e = '\0';

        CT_DEBUG(MAIN, ("MEMBER: \"%s\"", elem));

        t_off = search_struct_member(pStruct, elem, &pSD, &pDecl);
        pType = &pSD->type;

        if (t_off < 0)
        {
          TRUNC_ELEM;
          (void) sprintf(err = errbuf, "Cannot find struct member '%s'", elem);
          goto error;
        }

        if (do_calc)
        {
          size    = pDecl->size;
          offset += t_off;
        }

        level = 0;

        state = ST_SEARCH;
        break;

      case ST_INDEX:
        if (!isDIGIT(*c))
        {
          err = "Array indices must be constant decimal values";
          goto error;
        }

        ixstr = c++;
        while (*c && isDIGIT(*c))
          c++;

        state = ST_FINISH_INDEX;
        break;

      case ST_FINISH_INDEX:
        if (*c++ != ']')
        {
          err = "Index operator not terminated correctly";
          goto error;
        }
        else
        {
          int dim;

          assert(pDecl->array_flag);

          dim = LL_count(pDecl->ext.array);

          if (level >= dim)
          {
            TRUNC_ELEM;
            (void) sprintf(err = errbuf,
                           "Cannot use '%s' as a %d-dimensional array",
                           elem, level+1);
            goto error;
          }
          else
          {
            Value *pValue;
            int index;

            pValue = (Value *) LL_get(pDecl->ext.array, level);
            index  = atoi(ixstr);

            CT_DEBUG(MAIN, ("INDEX: \"%d\"", index));

            if (pValue->flags & V_IS_UNDEF)
            {
              if (do_calc)
              {
                size = pDecl->item_size;

                if (size <= 0)
                  fatal("pDecl->item_size is not initialized in get_member()");

                while (dim-- > level+1)
                  size *= ((Value *) LL_get(pDecl->ext.array, dim))->iv;
              }
            }
            else
            {
              dim = pValue->iv;

              if (index >= dim)
              {
                (void) sprintf(err = errbuf,
                               "Cannot use index %d into array of size %d",
                               index, dim );
                goto error;
              }

              if (do_calc)
              {
                if (size < 0)
                  fatal("size is not initialized in get_member()");

                size /= dim;
              }
            }

            if (do_calc)
            {
              if (size < 0)
                fatal("size is not initialized in get_member()");

              offset += index * size;
            }

            level++;
          }
        }

        state = ST_SEARCH;
        break;

      case ST_SEARCH:
        CT_DEBUG(MAIN, ("SEARCH: level=%d, dim=%d", level,
                 pDecl && pDecl->array_flag ? LL_count(pDecl->ext.array) : 0));

        PROPAGATE_FLAGS(pType->tflags);

        if (pDecl && !pDecl->pointer_flag && pType->tflags & T_TYPE &&
            level == (pDecl->array_flag ? LL_count(pDecl->ext.array) : 0))
        {
          do
          {
            Typedef *pTypedef = (Typedef *) pType->ptr;
            pDecl = pTypedef->pDecl;
            pType = pTypedef->pType;
          }
          while (!pDecl->pointer_flag &&
                 pType->tflags & T_TYPE &&
                 pDecl->array_flag == 0);

          if (do_calc)
          {
            size = pDecl->size;
          }

          level = 0;
        }

        inc_c = 1;
        dot   = "";

        switch (*c)
        {
          case '+':
            /*
               Handle the special case that we have a member returned
               by the 'member' method with appended "+digits".
               If this sequence is found at the end of the string,
               simply ignore it.
            */
            if (*(c+1) != '\0')
            {
              const char *p = c+1;

              while (*p && isDIGIT(*p))
                p++;

              if (*p == '\0')
              {
                /* quit */
                if (do_calc)
                {
                  offset += atoi(c+1);
                }

                c = p;
                inc_c = 0;
                break;
              }
            }

            /* fall through */

          default:
            if (!accept_dotless_member || !(isALPHA(*c) || *c == '_'))
            {
              (void) sprintf(err = errbuf,
                             "Invalid character '%c' (0x%02X) in "
                             "struct member expression",
                             *c, (int) *c);
              goto error;
            }

            inc_c = 0;
            dot   = ".";

            /* fall through */

          case '.':
            if (pDecl && pDecl->array_flag && level < LL_count(pDecl->ext.array))
              CANNOT_ACCESS_MEMBER("array");
            else if (pDecl && pDecl->pointer_flag)
              CANNOT_ACCESS_MEMBER("pointer");
            else if (pType->tflags & T_COMPOUND)
            {
              pStruct = (Struct *) pType->ptr;
              PROPAGATE_FLAGS( pStruct->tflags );
            }
            else
              CANNOT_ACCESS_MEMBER("non-compound");

            state = ST_MEMBER;
            break;

          case '[':
            if (pDecl == NULL || (level == 0 && pDecl->array_flag == 0))
            {
              if (elem[0] != '\0')
              {
                TRUNC_ELEM;
                (void) sprintf(err = errbuf,
                               "Cannot use '%s' as an array", elem);
              }
              else
                err = "Cannot use type as an array";

              goto error;
            }

            state = ST_INDEX;
            break;
        }

        if (inc_c)
          c++;

        break;
    }

    /* only accept dotless members at the very beginning */
    accept_dotless_member = 0;
  }

  if (state != ST_SEARCH)
  {
    err = "Incomplete struct member expression";
    goto error;
  }

  error:
  Safefree(elem);

  if (err != NULL)
  {
    if (gm_flags & CBC_GM_DONT_CROAK)
      return 0;
    Perl_croak(aTHX_ "%s", err);
  }

  CT_DEBUG(MAIN, ("FINISHED: typespec=[ptr=%p, flags=0x%X], pDecl=%p[dim=%d], level=%d, offset=%d, size=%d",
                  pType->ptr, pType->tflags, pDecl,
                  pDecl && pDecl->array_flag ? LL_count(pDecl->ext.array) : 0,
                  level, offset, size));

  if (pMIout)
  {
    pMIout->type   = *pType;
    pMIout->pDecl  = pDecl;
    pMIout->level  = level;
    pMIout->offset = offset;
    pMIout->size   = (unsigned) size;
  }

  return 1;
}