/*
 *                         OpenSplice DDS
 *
 *   This software and documentation are Copyright 2006 to TO_YEAR PrismTech
 *   Limited, its affiliated companies and licensors. All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
#include "cpp_malloc.h"
#include "symtbl.h"
#include "cpp_io.h"
#include "is.h"
#include "accum.h"
#include "unctrl.h"

#define DEBUG_EXPAND/**/

#ifdef DEBUG_EXPAND
extern int debugging;
#endif

extern int keep_comments;

static char **actuals;
static int *actlens;

static void read_actuals (DEF * d)
{
   int n;
   int i;
   int pc;
   char c;
   char last;
   char quote;
   int backslash;
   int comment;
   char *acc;

   n = d->nargs;
   actuals = (char **) os_malloc(n * sizeof(char *));
   check_os_malloc(actuals);
   actlens = (int *) os_malloc(n * sizeof(int));
   check_os_malloc(actlens);
   c = getnonspace();
   if (c != '(')
   {
      err_head();
      if (n == 0)
      {
         fprintf(stderr, "missing () on %s\n", d->name);
      }
      else
      {
         fprintf(stderr, "missing argument%s to %s\n", (n == 1) ? "" : "s", d->name);
      }
      for (i = 0;i < n;i++)
      {
         actuals[i] = copyofstr("");
         check_os_malloc(actuals[i]);
         actlens[i] = 0;
      }
      Push(c);
      return ;
   }
   if (n == 0)
   {
      c = getnonspace();
      if (c != ')')
      {
         err_head();
         fprintf(stderr, "unwanted argument to %s\n", d->name);
         Push(c);
      }
      return ;
   }
   i = 0;
   while (1)
   {
      pc = 0;
      quote = 0;
      backslash = 0;
      comment = 0;
      c = 0;
      acc = init_accum();
      while (1)
      {
         last = c;
         c = Get();
         accum_char(acc, c);
         if (comment)
         {
            if ((last == '*') && (c == '/'))
            {
               comment = 0;
            }
         }
         else
         {
            if (backslash)
            {
               backslash = 0;
            }
            else if (quote && (c == quote))
            {
               quote = 0;
            }
            else if (c == '\\')
            {
               backslash = 1;
            }
            else if (quote)
            {}
            else if ((last == '/') && (c == '*'))
            {
               comment = 1;
            }
            else if ((c == '\'') || (c == '"'))
            {
               quote = c;
            }
            else if (c == '(')
            {
               pc ++;
            }
            else if (c == ')')
            {
               if (pc > 0)
               {
                  pc --;
               }
               else
               {
                  accum_regret(acc);
                  break;
               }
            }
            else if ((c == ',') && (pc == 0))
            {
               accum_regret(acc);
               break;
            }
         }
      }
      if (i < n)
      {
         actuals[i] = accum_result(acc);
         actlens[i] = strlen(actuals[i]);
         i ++;
      }
      else if (i == n)
      {
         err_head();
         fprintf(stderr, "too many arguments to %s\n", d->name);
         i ++;
      }
      if (c == ')')
      {
         break;
      }
   }
   if (i < n)
   {
      err_head();
      fprintf(stderr, "too few arguments to %s\n", d->name);
      for (;i < n;i++)
      {
         actuals[i] = copyofstr("");
         check_os_malloc(actuals[i]);
         actlens[i] = 0;
      }
   }
}

extern void expand_def (DEF * d)
{
   unsigned char *cp;
   char *dp;
   char *ep;
   char *result;
   int ok;
   int incomm;
   char last;

   if (d->nargs >= 0)
   {
      read_actuals(d);
   }
#ifdef DEBUG_EXPAND
   if (debugging)
   {
      char *cp;
      outputs("~EXPAND:");
      outputs(d->name);
      if (d->nargs == 0)
      {
         outputs("()");
      }
      else if (d->nargs > 0)
      {
         int i;
         for (i = 0;i < d->nargs;i++)
         {
            char c = i ? ',' : '(';
            outputc(c);
            outputs(actuals[i]);
         }
         outputc(')');
      }
      outputs("-->");
      for (cp = (char *)d->repl;*cp;cp++)
      {
         outputs(unctrl(*cp));
      }
      outputc('~');
   }
#endif
   result = init_accum();
   for (cp = d->repl;*cp;cp++)
   {
      if (*cp & 0x80)
      {
         char *dp;
         int i;
         i = *cp & ~0x80;
         for (dp = actuals[i];*dp;dp++)
         {
            accum_char(result, *dp);
         }
      }
      else
      {
         accum_char(result, *cp);
      }
   }
   dp = accum_result(result);
#ifdef DEBUG_EXPAND

   if (debugging)
   {
      outputs("first:");
      for (ep = dp;*ep;ep++)
      {
         outputs(unctrl(*ep));
      }
      outputc('~');
   }
#endif
   result = init_accum();
   last = '\0';
   ok = 1;
   incomm = 0;
   for (ep = dp;*ep;ep++)
   {
      if (!incomm && (last == '/') && (*ep == '*'))
      {
         incomm = 1;
         if (!keep_comments || (strncmp(ep, "**/", 3) == 0))
         {
            accum_regret(result);
            ok = 0;
         }
      }
      if (ok)
      {
         accum_char(result, *ep);
      }
      if ((last == '*') && (*ep == '/'))
      {
         incomm = 0;
         ok = 1;
      }
      last = *ep;
   }
   os_free(dp);
   result = accum_result(result);
#ifdef DEBUG_EXPAND

   if (debugging)
   {
      outputs("/**/strip:");
      outputs(result);
      outputc('~');
   }
#endif
   for (dp = result + strlen (result) - 1; dp >= result; dp--)
   {
      if (*dp == '#' && (dp-1) >= result && *(dp-1) == '#')
      {
         /* Implement ## concatenation by skipping ## */

         dp--;
      }
      else
      {
         Push (*dp);
      }
   }
   os_free(result);
   if (d->nargs >= 0)
   {
      int i;
      for (i = 0;i < d->nargs;i++)
      {
         os_free(actuals[i]);
      }
      os_free((char *)actuals);
   }
}
