/*
 * This file contains various functions for dealing with macros.
 */
#include "../preproc/preproc.h"
#include "../preproc/ptoken.h"
#include "../preproc/pproto.h"

/*
 * Prototypes for static functions.
 */
static struct macro     **m_find      (char *mname);
static int            eq_id_lst   (struct id_lst *lst1, struct id_lst *lst2);
static int            eq_tok_lst  (struct tok_lst *lst1, struct tok_lst *lst2);
static int            parm_indx   (char *id, struct macro *m);
static void            cpy_str     (char *ldelim, char *image,
                                        char *rdelim, struct str_buf *sbuf);
static struct token      *stringize   (struct token *trigger,
                                        struct mac_expand *me);
static struct paste_lsts *paste_parse (struct token *t,
                                        struct mac_expand *me);
static int               *cpy_image   (struct token *t, int *s);

#define MacTblSz 149
#define MHash(x) (((unsigned int)(unsigned long)(x)) % MacTblSz)

static struct macro *m_table[MacTblSz];	/* hash table of macros */

int max_recurse;

/*
 * Some string to put in the string table:
 */
static char *line_mac = "__LINE__";
static char *file_mac = "__FILE__";
static char *date_mac = "__DATE__";
static char *time_mac = "__TIME__";
static char *rcrs_mac = "__RCRS__";
static char *defined = "defined";

/*
 * m_find - return return location of pointer to where macro belongs in
 *  macro table. If the macro is not in the table, the pointer at the
 *  location is NULL.
 */
static struct macro **m_find(char *mname)
   {
   struct macro **mpp;

   for (mpp = &m_table[MHash(mname)]; *mpp != NULL && (*mpp)->mname != mname;
      mpp = &(*mpp)->next)
      ;
   return mpp;
   }

/*
 * eq_id_lst - check to see if two identifier lists contain the same identifiers
 *  in the same order.
 */
static int eq_id_lst(struct id_lst *lst1, struct id_lst *lst2)
   {
   if (lst1 == lst2)
      return 1;
   if (lst1 == NULL || lst2 == NULL)
      return 0;
   if (lst1->id != lst2->id)
      return 0;
   return eq_id_lst(lst1->next, lst2->next);
   }

/*
 * eq_tok_lst - check to see if 2 token lists contain the same tokens
 *  in the same order. All white space tokens are considered equal.
 */
static int eq_tok_lst(struct tok_lst *lst1, struct tok_lst *lst2)
   {
   if (lst1 == lst2)
      return 1;
   if (lst1 == NULL || lst2 == NULL)
      return 0;
   if (lst1->t->tok_id != lst2->t->tok_id)
      return 0;
   if (lst1->t->tok_id != WhiteSpace && lst1->t->tok_id != PpDirEnd &&
        lst1->t->image != lst2->t->image)
      return 0;
   return eq_tok_lst(lst1->next, lst2->next);
   }

/*
 * init_macro - initialize this module, setting up standard macros.
 */
void init_macro()
   {
   int i;
   struct macro **mpp;
   struct token *t;
   time_t tv;
   char *s, *tval;
   static char *time_buf;
   static char *date_buf;
   static short first_time = 1;

   if (first_time) {
      first_time = 0;
      /*
       * Add names of standard macros to sting table.
       */
      line_mac = spec_str(line_mac);
      file_mac = spec_str(file_mac);
      date_mac = spec_str(date_mac);
      time_mac = spec_str(time_mac);
      rcrs_mac = spec_str(rcrs_mac);
      defined = spec_str(defined);
      }
   else {
      /*
       * Free macro definitions from the file processed.
       */
      for (i = 0; i < MacTblSz; ++i)
         free_m_lst(m_table[i]);
      }

   for (i = 0; i < MacTblSz; ++i)
      m_table[i] = NULL;

   /*
    * __LINE__ and __FILE__ are macros that require special processing
    *  when they are processed. Indicate that.
    */
   mpp = m_find(line_mac);
   *mpp = new_macro(line_mac, SpecMac, 0, NULL, NULL);

   mpp = m_find(file_mac);
   *mpp = new_macro(file_mac, SpecMac, 0, NULL, NULL);

   /*
    * __TIME__ and __DATE__ must be initialized to the current time and
    *  date.
    */
   time(&tv);
   tval = ctime(&tv);
   date_buf = alloc(12);
   time_buf = alloc(9);
   s = date_buf;
   for (i = 4; i <= 10; ++i)
      *s++ = tval[i];
   for (i = 20; i <= 23; ++i)
      *s++ = tval[i];
   *s = '\0';
   s = time_buf;
   for (i = 11; i <= 18; ++i)
      *s++ = tval[i];
   *s = '\0';
   date_buf = spec_str(date_buf);
   time_buf = spec_str(time_buf);

   t = new_token(StrLit, date_buf, "", 0);
   mpp = m_find(date_mac);
   *mpp = new_macro(date_mac, FixedMac, 0, NULL, new_t_lst(t));

   t = new_token(StrLit, time_buf, "", 0);
   mpp = m_find(time_mac);
   *mpp = new_macro(time_mac, FixedMac, 0, NULL, new_t_lst(t));

   /*
    * __RCRS__ is a special macro to indicate the allowance of
    *  recursive macros. It is not ANSI-standard. Initialize it
    *  to "1".
    */
   mpp = m_find(rcrs_mac);
   *mpp = new_macro(rcrs_mac, NoArgs, 0, NULL, new_t_lst(copy_t(one_tok)));
   max_recurse = 1;
   }

/*
 * m_install - install a macro.
 */
void m_install(
   struct token *mname,	/* name of macro */
   int category,		/* # parms, or NoArgs if it is object-like macro */
   int multi_line,		/* flag indicating if this is a multi-line macro */
   struct id_lst *prmlst,	/* parameter list */
   struct tok_lst *body)	/* replacement list */
   {
   struct macro **mpp;
   char *s;

   if (mname->image == defined)
      errt1(mname, "'defined' may not be the subject of #define");

   /*
    * The special macro __RCRS__ may only be defined as a single integer
    *  token and must be an object-like macro.
    */
   if (mname->image == rcrs_mac) {
      if (body == NULL || body->t->tok_id != PpNumber || body->next != NULL)
         errt1(mname, "__RCRS__ must be a decimal integer");
      if (category != NoArgs)
         errt1(mname, "__RSCS__ may have no arguments");
      max_recurse = 0;
      for (s = body->t->image; *s != '\0'; ++s) {
         if (*s >= '0' && *s <= '9')
            max_recurse = max_recurse * 10 + (*s - '0');
         else
            errt1(mname, "__RCRS__ must be a decimal integer");
         }
      }

   mpp = m_find(mname->image);
   if (*mpp == NULL)
      *mpp = new_macro(mname->image, category, multi_line, prmlst, body);
    else {
      /*
       * The macro is already defined. Make sure it is identical (up to
       *  white space) to this definition.
       */
      if (!((*mpp)->category == category && eq_id_lst((*mpp)->prmlst, prmlst) &&
           eq_tok_lst((*mpp)->body, body)))
         errt2(mname, "invalid redefinition of macro ", mname->image);
      free_id_lst(prmlst);
      free_t_lst(body);
      }
   }

/*
 * m_delete - delete a macro.
 */
void m_delete(struct token *mname)
   {
   struct macro **mpp, *mp;

   if (mname->image == defined)
      errt1(mname, "'defined' may not be the subject of #undef");

   /*
    * Undefining __RCRS__ allows unlimited macro recursion (non-ANSI
    *  standard feature.
    */
   if (mname->image == rcrs_mac)
      max_recurse = -1;

   /*
    * Make sure undefining this macro is allowed, and free storage
    *  associate with it.
    */
   mpp = m_find(mname->image);
   if (*mpp != NULL) {
      mp = *mpp;
      if (mp->category == FixedMac || mp->category == SpecMac)
         errt2(mname, mname->image, " may not be the subject of #undef");
      *mpp = mp->next;
      free_m(mp);
      }
   }

/*
 * m_lookup - lookup a macro name. Return pointer to macro, if it is defined;
 *  return NULL, if it is not. This routine sets the definition for macros
 *  whose definitions various from place to place.
 */
struct macro *m_lookup(struct token *id)
   {
   struct macro *m;
   static char buf[20];

   m = *m_find(id->image);
   if (m != NULL && m->category == SpecMac) {
      if (m->mname == line_mac) {  /* __LINE___ */
         sprintf(buf, "%d", id->line);
         m->body = new_t_lst(new_token(PpNumber, buf, id->fname,
            id->line));
         }
      else if (m->mname == file_mac) /* __FILE__ */
         m->body = new_t_lst(new_token(StrLit, id->fname, id->fname,
            id->line));
      }
   return m;
   }

/*
 * parm_indx - see if a name is a paramter to the given macro.
 */
static int parm_indx(char *id, struct macro *m)
   {
   struct id_lst *idlst;
   int i;

   for (i = 0, idlst = m->prmlst; i < m->category; i++, idlst = idlst->next)
      if (id == idlst->id)
         return i;
   return -1;
   }

/*
 * cpy_str - copy a string into a string buffer, adding delimiters.
 */
static void cpy_str(char *ldelim, char *image, char *rdelim,
   struct str_buf *sbuf)
   {
   register char *s;

   for (s = ldelim; *s != '\0'; ++s)
      AppChar(*sbuf, *s);

   for (s = image; *s != '\0'; ++s) {
      if (*s == '\\' || *s == '"')
         AppChar(*sbuf, '\\');
      AppChar(*sbuf, *s);
      }

   for (s = rdelim; *s != '\0'; ++s)
      AppChar(*sbuf, *s);
   }

/*
 * stringize - create a stringized version of a token.
 */
static struct token *stringize(struct token *trigger, struct mac_expand *me)
   {
   register struct token *t;
   struct tok_lst *arg;
   struct str_buf *sbuf;
   char *s;
   int indx;

   /*
    * Get the next token from the macro body. It must be a macro parameter;
    *  retrieve the raw tokens for the corresponding argument.
    */
   if (me->rest_bdy == NULL)
      errt1(trigger, "the # operator must have an argument");
   t = me->rest_bdy->t;
   me->rest_bdy = me->rest_bdy->next;
   if (t->tok_id == Identifier)
      indx = parm_indx(t->image, me->m);
   else
      indx = -1;
   if (indx == -1)
      errt1(t, "the # operator may only be applied to a macro argument");
   arg = me->args[indx];

   /*
    * Copy the images for the argument tokens into a string buffer. Note
    *  that the images of string and character literals lack quotes; these
    *  must be escaped in the stringized value.
    */
   sbuf = get_sbuf();
   while (arg != NULL) {
      t = arg->t;
      if (t->tok_id == WhiteSpace)
         AppChar(*sbuf, ' ');
      else if (t->tok_id == StrLit)
         cpy_str("\\\"", t->image, "\\\"", sbuf);
      else if (t->tok_id == LStrLit)
         cpy_str("L\\\"", t->image, "\\\"", sbuf);
      else if (t->tok_id == CharConst)
         cpy_str("'", t->image, "'", sbuf);
      else if (t->tok_id == LCharConst)
         cpy_str("L'", t->image, "'", sbuf);
      else
         for (s = t->image; *s != '\0'; ++s)
            AppChar(*sbuf, *s);
      arg = arg->next;
      }

   /*
    * Created the token for the stringized argument.
    */
   t = new_token(StrLit, str_install(sbuf), trigger->fname, trigger->line);
   t->flag |= trigger->flag & LineChk;
   rel_sbuf(sbuf);
   return t;
   }

/*
 * paste_parse - parse an expression involving token pasting operators (and
 *  stringizing operators). Return a list of token lists. Each token list
 *  is from a token pasting operand, with operands that are macro parameters
 *  replaced by their corresponding argument (this is why a list of tokens
 *  is needed for each operand). Any needed stringizing is done as the list
 *  is created.
 */
static struct paste_lsts *paste_parse(struct token *t, struct mac_expand *me)
   {
   struct token *t1;
   struct token *trigger = NULL;
   struct tok_lst *lst;
   struct paste_lsts *plst;
   int indx;

   if (me->rest_bdy == NULL || me->rest_bdy->t->tok_id != PpPaste)
      plst = NULL;  /* we have reached the end of the pasting expression */
   else {
      /*
       * The next token is a pasting operator. Copy it an move on to the
       *  operand.
       */
      trigger = copy_t(me->rest_bdy->t);
      me->rest_bdy = me->rest_bdy->next;
      if (me->rest_bdy == NULL)
         errt1(t, "the ## operator must not appear at the end of a macro");
      t1 = me->rest_bdy->t;
      me->rest_bdy = me->rest_bdy->next;

      /*
       * See if the operand is a stringizing operation.
       */
      if (t1->tok_id == '#')
         t1 = stringize(t1, me);
      else
         t1 = copy_t(t1);
      plst = paste_parse(t1, me); /* get any further token pasting */
      }

   /*
    * If the operand is a macro parameter, replace it by the corresponding
    *  argument, otherwise make the operand into a 1-element token list.
    */
   indx = -1;
   if (t->tok_id == Identifier)
      indx = parm_indx(t->image, me->m);
   if (indx == -1)
      lst = new_t_lst(t);
   else {
      lst = me->args[indx];
      free_t(t);
      }

   /*
    * Ignore emtpy arguments when constructing the pasting list.
    */
   if (lst == NULL)
      return plst;
   else
      return new_plsts(trigger, lst, plst);
   }

/*
 * cpy_image - copy the image of a token into a character buffer adding
 *  delimiters if it is a string or character literal.  s can contain EOF.
 */
static int *cpy_image(struct token *t, int *s)
   {
   register char *s1;

   switch (t->tok_id) {
      case StrLit:
         *s++ = '"';
         break;
      case LStrLit:
         *s++ = 'L';
         *s++ = '"';
         break;
      case CharConst:
         *s++ = '\'';
         break;
      case LCharConst:
         *s++ = 'L';
         *s++ = '\'';
         break;
      }

   s1 = t->image;
   while (*s1 != '\0')
      *s++ = *s1++;

   switch (t->tok_id) {
      case StrLit:
      case LStrLit:
         *s++ = '"';
         break;
      case CharConst:
      case LCharConst:
         *s++ = '\'';
         break;
      }

   return s;
   }

/*
 * paste - return the next token from a source which pastes tokens. The
 *   source may represent a series of token pasting operators.
 */
struct token *paste()
   {
   struct token *t;
   struct token *t1;
   struct token *trigger;
   struct paste_lsts *plst;
   union src_ref ref;
   int i;
   int *s;

   plst = src_stack->u.plsts;

   /*
    * If the next token of the current list is not the one to be pasted,
    *  just return it.
    */
   t = copy_t(plst->tlst->t);
   plst->tlst = plst->tlst->next;
   if (plst->tlst != NULL)
      return t;

   /*
    * We have the last token from the current list. If there is another
    *  list, this token must be pasted to the first token of that list.
    *  Make the next list the current one and get its first token.
    */
   trigger = plst->trigger;
   plst = plst->next;
   free_plsts(src_stack->u.plsts);
   src_stack->u.plsts = plst;
   if (plst == NULL) {
      pop_src();
      return t;
      }
   t1 = next_tok();

   /*
    * Paste tokens by creating a character source with the images of the
    *  two tokens concatenated.
    */
   ref.cs = new_cs(trigger->fname, NULL,
      (int)strlen(t->image) + (int)strlen(t1->image) + 7);
   push_src(CharSrc, &ref);
   s = cpy_image(t, ref.cs->char_buf);
   s = cpy_image(t1, s);
   *s = EOF;

   /*
    * Treat all characters of the new source as if they come from the
    *  location of the token pasting.
    */
   for (i = 0; i < (s - ref.cs->char_buf + 1); ++i)
      *(ref.cs->line_buf) = trigger->line;
   ref.cs->last_char = s;
   ref.cs->dir_state = Reset;
   first_char = ref.cs->char_buf;
   next_char = first_char;
   last_char = ref.cs->last_char;

   return next_tok(); /* first token from pasted images */
   }

/*
 * mac_tok - return the next token from a source which is a macro.
 */
struct token *mac_tok()
   {
   struct mac_expand *me;
   register struct token *t, *t1;
   struct paste_lsts *plst;
   union src_ref ref;
   int line_check;
   int indx;
   int line;
   char *fname;

   me = src_stack->u.me; /* macro, current position, and arguments */

   /*
    * Get the next token from the macro body.
    */
   if (me->rest_bdy == NULL)
      return NULL;
   t = me->rest_bdy->t;
   me->rest_bdy = me->rest_bdy->next;

   /*
    * If this token is a stringizing operator, try stringizing the next
    *  token.
    */
   if (t->tok_id == '#')
      t = stringize(t, me);
   else
      t = copy_t(t);

   if (me->rest_bdy != NULL && me->rest_bdy->t->tok_id == PpPaste) {
      /*
       * We have found token pasting. If there is a series of such operators,
       *  make them all into one token pasting source and push it on
       *  the source stack.
       */
      if (t->flag & LineChk) {
         line_check = 1;
         line = t->line;
         fname = t->fname;
         }
      else
         line_check = 0;
      plst = paste_parse(t, me);
      if (plst != NULL) {
         ref.plsts = plst;
         push_src(PasteLsts, &ref);
         }
      t1 = next_tok();
      if (line_check && !(t1->flag & LineChk)) {
         t1->flag |= LineChk;
         t1->line = line;
         t1->fname = fname;
         }
      return t1;
      }
   else if (t->tok_id == Identifier &&
     (indx = parm_indx(t->image, me->m)) != -1) {
      /*
       * We have found a parameter. Push a token source for the corresponding
       *  argument, that is, replace the parameter with its definition.
       */
      ref.tlst = me->exp_args[indx];
      push_src(TokLst, &ref);
      if (t->flag & LineChk) {
         line = t->line;
         fname = t->fname;
         t1 = next_tok();
         if (!(t1->flag & LineChk)) {
            /*
             * The parameter name token is significant with respect to
             *  outputting #line directives but the first argument token
             *  is not. Pretend the argument has the same line number as the
             *  parameter name.
             */
            t1->flag |= LineChk;
            t1->line = line;
            t1->fname = fname;
            }
         free_t(t);
         return t1;
         }
      else {
         free_t(t);
         return next_tok();
         }
      }
   else {
      /*
       * This is an ordinary token, nothing further is needed here.
       */
      return t;
      }
   }
