The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*******************************************************************************
*
* MODULE: member.c
*
********************************************************************************
*
* DESCRIPTION: C::B::C compound member utilities
*
********************************************************************************
*
* Copyright (c) 2002-2015 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;

struct member_expr {
  enum {
    ST_MEMBER,
    ST_INDEX,
    ST_FINISH_INDEX,
    ST_SEARCH,
    ST_TERM
  } state;
  const char *p;
  unsigned startup : 1;
  unsigned has_dot : 1;
  char buf[1];
};


/*===== 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)
{
  ListIterator       sdi;
  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, sdi, pStruct->declarations)
  {
    if (pStructDecl->declarators)
    {
      ListIterator di;

      LL_foreach(pDecl, di, 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)
      {
        ListIterator ai;

        LL_foreach(pValue, ai, 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 compound 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)
{
  ListIterator       sdi;
  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, sdi, 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
      {
        ListIterator di;

        LL_foreach(pDecl, di, 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)
{
  ListIterator       sdi;
  StructDeclaration *pStructDecl;
  Declarator        *pDecl = NULL;
  int                offset;

  LL_foreach(pStructDecl, sdi, pStruct->declarations)
  {
    if (pStructDecl->declarators)
    {
      ListIterator di;

      LL_foreach(pDecl, di, 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: get_member
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Oct 2002
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

#define TRUNC_NAME                                                             \
        STMT_START {                                                           \
          strncpy(trunc, name, 20);                                            \
          if (strlen(name) > 20)                                               \
          {                                                                    \
            trunc[17] = trunc[18] = trunc[19] = '.';                           \
            trunc[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 {                                                           \
          assert(name != NULL);                                                \
          TRUNC_NAME;                                                          \
          (void) sprintf(err = errbuf,                                         \
                         "Cannot access member '%s' of " type " type", trunc); \
          goto error;                                                          \
        } STMT_END

int get_member(pTHX_ const MemberInfo *pMI, const char *member,
               MemberInfo *pMIout, unsigned gm_flags)
{
  unsigned           accept_dotless_member = gm_flags & CBC_GM_ACCEPT_DOTLESS_MEMBER;
  const unsigned     do_calc = (gm_flags & CBC_GM_NO_OFFSET_SIZE_CALC) == 0;
  const unsigned     reject_oobi = gm_flags & CBC_GM_REJECT_OUT_OF_BOUNDS_INDEX;
  const unsigned     reject_offset = gm_flags & CBC_GM_REJECT_OFFSET;
  const TypeSpec    *pType;
  int                size, level, t_off;
  int                offset;
  Struct            *pStruct;
  StructDeclaration *pSD;
  Declarator        *pDecl;
  char              *err, errbuf[128], trunc[32];
  MemberExprWalker   walker;
  const char        *name;

  CT_DEBUG(MAIN, ("get_member( member=\"%s\", accept_dotless_member=%d, do_calc=%d, reject_oobi=%d )",
                  member, accept_dotless_member, do_calc, reject_oobi));

  walker = member_expr_walker_new(aTHX_ member, 0);

  if (pMIout)
    pMIout->flags = 0;

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

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

  name   = NULL;
  err    = NULL;
  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 (;;)
  {
    struct me_walk_info mei;

    member_expr_walker_walk(aTHX_ walker, &mei);

    CT_DEBUG(MAIN, ("(offset=%d, level=%d, size=%d) %s (%d)", offset, level, size,
                     member_expr_walker_retval_string(mei.retval), (int) mei.retval));

    if (mei.retval == MERV_END)
    {
      break;
    }

    switch (mei.retval)
    {
      case MERV_ERR_INVALID_MEMBER_START:
        err = "Struct members must start with a character or an underscore";
        goto error;

      case MERV_ERR_INVALID_INDEX:
        err = "Array indices must be constant decimal values";
        goto error;

      case MERV_ERR_INVALID_CHAR:
        (void) sprintf(err = errbuf,
                       "Invalid character '%c' (0x%02X) in "
                       "compound member expression",
                       mei.u.invalid_char, (int) mei.u.invalid_char);
        goto error;

      case MERV_ERR_INDEX_NOT_TERMINATED:
        err = "Index operator not terminated correctly";
        goto error;

      case MERV_ERR_INCOMPLETE:
        err = "Incomplete compound member expression";
        goto error;

      case MERV_ERR_TERMINATED:
        fatal("member expression already terminated in get_member()");
        break;

      default:
        /* handled in next switch */
        break;
    }

    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;
    }

    switch (mei.retval)
    {
      case MERV_COMPOUND_MEMBER:
        {
          name = mei.u.compound_member.name;

          CT_DEBUG(MAIN, ("MEMBER: \"%s\"%s", name, mei.u.compound_member.has_dot ? " [dot]" : ""));

          if (!accept_dotless_member && !mei.u.compound_member.has_dot)
          {
            (void) sprintf(err = errbuf,
                           "Invalid character '%c' (0x%02X) in "
                           "compound member expression",
                           name[0], (int) name[0]);
            goto error;
          }

          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");

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

          if (t_off < 0)
          {
            TRUNC_NAME;
            (void) sprintf(err = errbuf, "Cannot find %s member '%s'",
                           pStruct->tflags & T_STRUCT ? "struct" : "union", trunc);
            goto error;
          }

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

          level = 0;
        }
        break;

      case MERV_ARRAY_INDEX:
        {
          int dim;

          if (pDecl == NULL || (level == 0 && pDecl->array_flag == 0))
          {
            if (name)
            {
              TRUNC_NAME;
              (void) sprintf(err = errbuf,
                             "Cannot use '%s' as an array", trunc);
            }
            else
              err = "Cannot use type as an array";

            goto error;
          }

          assert(pDecl->array_flag);

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

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

            pValue = (Value *) LL_get(pDecl->ext.array, level);
            index  = mei.u.array_index;

            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 (reject_oobi)
              {
                if (index < 0)
                {
                  (void) sprintf(err = errbuf,
                                 "Cannot use negative index %d into array",
                                 index);
                  goto error;
                }
                else 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++;
          }
        }
        break;

      case MERV_OFFSET:
        if (reject_offset)
        {
          err = "Cannot use offset in compound member expression";
          goto error;
        }
        if (do_calc)
        {
          offset += mei.u.offset;
        }
        break;

      default:
        /* all error cases have been handled above */
        fatal("unexpected retval (%d) in get_member()", (int) mei.retval);
        break;
    }

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

  error:
  member_expr_walker_delete(aTHX_ walker);

  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%lX], pDecl=%p[dim=%d], level=%d, offset=%d, size=%d, parent=%p",
                  pType->ptr, (unsigned long) pType->tflags, pDecl,
                  pDecl && pDecl->array_flag ? LL_count(pDecl->ext.array) : 0,
                  level, offset, size, pStruct));

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

  return 1;
}

#undef TRUNC_NAME
#undef PROPAGATE_FLAGS
#undef CANNOT_ACCESS_MEMBER

/*******************************************************************************
*
*   ROUTINE: member_expr_walker_new
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Mar 2006
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

MemberExprWalker member_expr_walker_new(pTHX_ const char *expr, size_t len)
{
  MemberExprWalker me;

  assert(expr != NULL);

  if (len == 0)
    len = strlen(expr);

  Newc(0, me, offsetof(struct member_expr, buf) + len + 1, char, struct member_expr);

  me->state = ST_SEARCH;
  me->p = expr;
  me->startup = 1;
  me->has_dot = 0;

  CT_DEBUG(MAIN, ("(walk) created new walker @ %p", me));

  return me;
}

/*******************************************************************************
*
*   ROUTINE: member_expr_walker_retval_string
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Mar 2006
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

const char *member_expr_walker_retval_string(enum me_walk_rv retval)
{
  static const char *Sretval[] = {
    "MERV_COMPOUND_MEMBER",
    "MERV_ARRAY_INDEX",
    "MERV_OFFSET",
    "MERV_ERR_INVALID_MEMBER_START",
    "MERV_ERR_INVALID_INDEX",
    "MERV_ERR_INVALID_CHAR",
    "MERV_ERR_INDEX_NOT_TERMINATED",
    "MERV_ERR_INCOMPLETE",
    "MERV_ERR_TERMINATED",
    "MERV_END"
  };

  return Sretval[retval];
}

/*******************************************************************************
*
*   ROUTINE: member_expr_walker_walk
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Mar 2006
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void member_expr_walker_walk(pTHX_ MemberExprWalker me, struct me_walk_info *info)
{
#ifdef CBC_DEBUGGING
  static const char *Sstate[] = {
    "ST_MEMBER",
    "ST_INDEX",
    "ST_FINISH_INDEX",
    "ST_SEARCH",
    "ST_TERM"
  };
#endif
  const char *c, *ixstr;

  assert(me != NULL);

  if (me->state == ST_TERM)
  {
    info->retval = MERV_ERR_TERMINATED;
    return;
  }

  c = me->p;

  for (;;)
  {
    CT_DEBUG(MAIN, ("(walk) state = %s (%d) \"%s\"%s%s", Sstate[me->state], me->state, c,
                    me->startup ? " [startup]" : "", me->has_dot ? " [dot]" : ""));

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

    if (*c == '\0')
    {
      if (me->state != ST_SEARCH)
      {
        info->retval = MERV_ERR_INCOMPLETE;
        goto error;
      }

      info->retval = MERV_END;
      me->state = ST_TERM;

      return;
    }

    switch (me->state)
    {
      case ST_MEMBER:
        if(!(isALPHA(*c) || *c == '_'))
        {
          info->retval = MERV_ERR_INVALID_MEMBER_START;
          goto error;
        }
        else
        {
          char *e = &me->buf[0];
          do *e++ = *c++; while (isALNUM(*c) || *c == '_');
          *e = '\0';

          info->retval = MERV_COMPOUND_MEMBER;
          info->u.compound_member.name = &me->buf[0];
          info->u.compound_member.name_length = e - &me->buf[0];
          info->u.compound_member.has_dot = me->has_dot;

          goto found;
        }

      case ST_INDEX:
        ixstr = c;

        if (*c == '-' || *c == '+')
        {
          c++;
        }

        if (!isDIGIT(*c))
        {
          info->retval = MERV_ERR_INVALID_INDEX;
          goto error;
        }

        do c++; while (isDIGIT(*c));

        me->state = ST_FINISH_INDEX;
        break;

      case ST_FINISH_INDEX:
        if (*c++ != ']')
        {
          info->retval = MERV_ERR_INDEX_NOT_TERMINATED;
          goto error;
        }

        info->retval = MERV_ARRAY_INDEX;
        info->u.array_index = atoi(ixstr);

        goto found;

      case ST_SEARCH:
        {
          int inc_c = 1;

          switch (*c)
          {
            case '+':
              if (*(c+1) != '\0')
              {
                const char *p = c+1;

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

                /* only allowed at end of string */
                if (*p == '\0')
                {
                  info->retval = MERV_OFFSET;
                  info->u.offset = atoi(c+1);

                  c = p;

                  goto found;
                }
              }

              /* fall through */

            default:
              if (!me->startup || !(isALPHA(*c) || *c == '_'))
              {
                info->retval = MERV_ERR_INVALID_CHAR;
                info->u.invalid_char = *c;
                goto error;
              }

              inc_c = 0;

              /* fall through */

            case '.':
              me->has_dot = *c == '.';
              me->state = ST_MEMBER;
              break;

            case '[':
              me->state = ST_INDEX;
              break;
          }

          if (inc_c)
            c++;
        }
        break;

      default:
        fatal("invalid state (%d) in member_expr_walker_walk()", (int) me->state);
        break;
    }

    /* only accept dotless members at the very beginning */
    me->startup = 0;
  }

  error:

  /* no need to update me->p here */
  me->state = ST_TERM;

  return;

  found:

  me->p = c;
  me->state = ST_SEARCH;
}

/*******************************************************************************
*
*   ROUTINE: member_expr_walker_delete
*
*   WRITTEN BY: Marcus Holland-Moritz             ON: Mar 2006
*   CHANGED BY:                                   ON:
*
********************************************************************************
*
* DESCRIPTION:
*
*   ARGUMENTS:
*
*     RETURNS:
*
*******************************************************************************/

void member_expr_walker_delete(pTHX_ MemberExprWalker me)
{
  assert(me != NULL);

  CT_DEBUG(MAIN, ("(walk) delete walker @ %p", me));

  Safefree(me);
}