/*
 *    (c)Copyright 1992-1997 Obvious Implementations Corp.  Redistribution and
 *    use is allowed under the terms of the DICE-LICENSE FILE,
 *    DICE-LICENSE.TXT.
 */
#include "vopts.h"

Prototype void do_command(char *string);
Prototype void read_file(int type);
Prototype void save_file(int type);
Prototype int get_filename(char *str);
Prototype void reset_options(int reshow);
Prototype void reset_group(struct G_OBJECT *object);
Prototype int match_opt(char *optstr,char **valstr,char *buf, int exact);
Prototype int set_option(struct G_OBJECT *object,char **argv, int exact);
Prototype int parse_options(char *string);
Prototype int get_option(struct G_OBJECT *object, char **buf, int *len);
Prototype int append_option(char *optstr1, char *optstr2, char **buf, int *len);

parse_options(char *string);

/***********************************************************************************
 * Procedure: do_command
 * Synopsis:  do_command(string);
 * Purpose:   Execute the command associated with a given string
 ***********************************************************************************/
void do_command(char *string)
{
   char *p;
   char buf[10];
   int len;
   int type;

   /* The following valid commands are allowed: */
   /*    READ  [ file | ? | ENV ]               */
   /*    SAVE  [ file | ? | ENV ]               */
   /*    RESET                                  */
   /*    QUIT                                   */
   /*    PARSE option                           */

   len = 0;
   while (*string == ' ') string++;

   p = string;
   while(((*p >= 'A') && (*p <= 'Z')) ||
         ((*p >= 'a') && (*p <= 'z')))
   {
      if (*p < 'Z') /* Upper case characters which occur first in ASCII */
         buf[len++] = *p;
      else
         buf[len++] = 'A' + (*p - 'a');
      if (len > 5) break;
      p++;
   }
   buf[len] = 0;
   while(*p == ' ') p++;

   if (!strcmp(buf, "READ"))
   {
      global.fileop = 0;
      type = get_filename(p);
      if (type == 0)            /* user aborted filename                */
         return;
      SetWindowTitles(global.window, global.title, global.wtitle);
      global.filetype = type;
      read_file(type);
   }
   else if (!strcmp(buf, "SAVE"))
   {
      global.fileop = 1;
      type = get_filename(p);
      if (type == 0)            /* user aborted filename                */
         return;
      if (request(0, TEXT_SAVEOK, global.filename, NULL) == 0)
         /* Chickened out...    */
         return;
      SetWindowTitles(global.window, global.title, global.wtitle);
      global.filetype = type;
      save_file(type);
   }
   else if (!strcmp(buf, "QUIT"))
   {
      global.done = 1;
   }
   else if (!strcmp(buf, "PARSE"))
   {
      char tbuf[MAX_STRING+1];
      strncpy(tbuf, p, MAX_STRING);
      set_gadgets(0);
      parse_options(tbuf);
      set_gadgets(1);
   }
   else if (!strcmp(buf, "RESET"))
   {
      reset_options(1);
   }
   else
   {
      /* We have an invalid option, ignore the command and let them know  */
      /* about the problem with the option.                               */
      request(1, TEXT_BADCMD, buf, NULL);
      return;
   }
}
/***********************************************************************************
 * Procedure: read_file
 * Synopsis:  read_file(type)
 * Purpose:   read file, extract options according to (type) and display
 ***********************************************************************************/
void read_file(int type)
{
   char buf[256];

   if (type == FILE_ENV)
   {
      strncpy(buf, getenv("DCCOPTS"), 255);
      reset_options(0);
      parse_options(buf);
   }
   else
   {
      FILE *fp;
      int cstate;

      fp = fopen(global.filename, "r");
      if (fp == NULL)
      {
         request(1, TEXT_BADFILE, global.filename, NULL);
         return;
      }

      reset_options(0);
      cstate = 1;
      while(cstate && (fgets(buf, 256, fp) != NULL))
      {
         int len;
         int extend;

         len = strlen(buf)-1;
         if (len < 2) continue;

         buf[len] = 0; /* Wipe out the \n */
         extend = 0;
         if (buf[len-1] == '\\')
         {
            extend = 1;
            buf[--len] = 0;
         }

         switch(type)
         {
            case FILE_C:
               /* Make sure it says #pragma DCCOPTS */
               if (!memcmp(buf, "#pragma DCCOPTS ", 16))
               {
                  /* We actually got it */
                  if (cstate == 3)
                  {
                     request(1, TEXT_EXTRAPRG, NULL, NULL);
                     cstate = 0;
                  }
                  else
                  {
                     if (parse_options(buf+16))
                        cstate = 0;
                     else
                        cstate = 2;
                  }
               }
               else
               {
                  if (cstate == 2) cstate = 3;
               }
               break;
            case FILE_OPTIONS:
               cstate = !parse_options(buf);
               break;
            case FILE_DMAKEFILE:
               if ((!memcmp(buf, "CFLAGS=", len=7)) ||
                   (len = 0, cstate == 2))
               {
                  cstate = 0;
                  if (extend) cstate = 2;
                  if (parse_options(buf+len))
                     cstate = 0;
               }
               break;
         }
      }
   global.nameoffile = 1; /* indicate that global.filename has been read in */
   fclose(fp);
   }
   set_gadgets(1);
}

/***********************************************************************************
 * Procedure: save_file
 * Synopsis:  save_file(type)
 * Purpose:   Write user selected options to file, formatted for type
 ***********************************************************************************/
void save_file(int type)
{
#define MAXOPTSTR 512

   char buf[MAXOPTSTR];
   char *pbuf=buf;
   struct G_GROUP *group;
   int  ok, len = MAXOPTSTR - 1;
   int  skipping = 0;

   *buf = 0;    /* just in case no options are set                      */

   if (ok = get_option(global.objects, &pbuf, &len)) /* didn't over run buffer...*/
      for (group = global.groups;
           ok  && (group != NULL);
           group = (struct G_GROUP *)group->base.next)
         {
         if (!((type == FILE_ENV) && group->local))
            ok = get_option(group->objects, &pbuf, &len);
         }
   if (!ok)
      {
      ok = request(0, TEXT_BIGOPTS, NULL, NULL);
      if (!ok) return;
      }
#ifdef JGM_DBG
printf("Options: %s\n", buf);
#endif

   if (*buf == 0) return;       /* let's not try anything fancy         */

   if (type == FILE_ENV)
   {
      setenv("DCCOPTS", buf);
   }
   else
   {

      FILE *fpout, *fpin;

      if (global.nameoffile != 1)
      {
         /* didn't already read this file - does it exist? */
         fpin = fopen(global.filename, "r");
         /* yes I know lock is more efficient ... */
         if (fpin != NULL)
         {
            fclose(fpin); /* it don't have nothing we want */
            ok = request(0, TEXT_OVWRITEOK, global.filename, NULL);
            if (!ok) return;
         }
      }

      switch (type)
      {
         case FILE_OPTIONS:
         {
            char *p1 = buf, *p2;

            fpout = fopen(global.filename, "w");
            if (fpout == NULL)
            {
               request(1, TEXT_BADFILE, global.filename, NULL);
               return;
            }

            do
            {
               p2 = strchr(p1+1, '-');
               if (p2 != NULL)
                  p2[-1] = '\0';
               fputs(p1, fpout);
               fputc('\n', fpout);
               p1 = p2;
            }
            while (p1 != NULL);
            fclose(fpout);
            break;
         }
         case FILE_DMAKEFILE:
         {
            int werr = 1;       /* init to no error                     */
            char newname[MAX_FILENAME+11];
            char copybuf[256];

            strcpy(newname, global.filename);
            strcat(newname, ".vopt-temp");
            if ((fpin = fopen(newname, "r")) != NULL)
            {
               fclose(fpin);
               remove(newname);
            }
            if (rename(global.filename, newname) < 0)   /* error        */
            {
               request(1, TEXT_NORENAME, global.filename, NULL);
               return;
            }
            if ((fpin = fopen(newname, "r")) == NULL)
            {
               rename(newname, global.filename);
               request(1, TEXT_NOREAD, global.filename, NULL);
               return;
            }
            if ((fpout = fopen(global.filename, "w")) == NULL)
            {
               fclose(fpin);
               rename(newname, global.filename);
               request(1, TEXT_BADFILE, global.filename, NULL);
               return;
            }

            /* note that both fprintf() and fputs() return <0 for error */
            while ((fgets(copybuf, 256, fpin) != NULL) && (werr >= 0))
            {
               if (!memcmp(copybuf, "CFLAGS=", 7))
               {
                  werr = fprintf(fpout, "%s%s\n", "CFLAGS= ", buf);
                  skipping = 1;  /* might not turn out to be true... */                  
               }
               else 
                  if (!skipping)
                     werr = fputs(copybuf, fpout);
               if (skipping)
               {
                  len = strlen(copybuf);
                  if (copybuf[len-2] != '\\')
                     skipping = 0; /* finished (if we ever started) */
               }
            }
            fclose(fpout);
            fclose(fpin);
            if (werr < 0)       /* error writing file - don't rename    */
            {
               request(1, TEXT_NOWRITE, newname, NULL);
               remove(global.filename);
               rename(newname, global.filename);
               return;
            }
            remove(newname); /*delete backup copy of file */
            break;
         }
         case FILE_C:
         {
            int werr = 1;   /* init to no error                 */
            int cstate = 0; /* -1 = #pragma DCCOPTS
                                0 = ordinary line
                                1 = comment continuation
                            */
            char newname[MAX_FILENAME+11];
            char copybuf[256];

            strcpy(newname, global.filename);
            strcat(newname, ".vopt-temp");
            if ((fpin = fopen(newname, "r")) != NULL)
            {
               fclose(fpin);
               remove(newname);
            }
            if (rename(global.filename, newname) < 0)   /* error        */
            {
               request(1, TEXT_NORENAME, global.filename, NULL);
               return;
            }
            if ((fpin = fopen(newname, "r")) == NULL)
            {
               rename(newname, global.filename);
               request(1, TEXT_NOOVWRITE, global.filename, NULL);
               return;
            }
            if ((fpout = fopen(global.filename, "w")) == NULL)
            {
               fclose(fpin);
               rename(newname, global.filename);
               request(1, TEXT_BADFILE, global.filename, NULL);
               return;
            }

            werr = fprintf(fpout, "%s%s\n", "#pragma DCCOPTS ", buf);
            while ((fgets(copybuf, 256, fpin) != NULL) && (werr >= 0))
            {
               if (cstate == 0)
               {
                  if (!memcmp(copybuf, "#pragma DCCOPTS ", 16))
                     cstate = -1;
                  else
                  ;
               }
               /* else check for close comment                          */

               if (cstate >= 0)
                  werr = fputs(copybuf, fpout);
               else
                  cstate = 0;   /* next line is ordinary - not comment  */
            }

            fclose(fpout);
            fclose(fpin);
            if (werr < 0)       /* error writing file - don't rename    */
            {
               request(1, TEXT_NOOVWRITE, newname, NULL);
               remove(global.filename);
               rename(newname, global.filename);
               return;
            }
            remove(newname); /*delete backup copy of file */
            break;
         }
         default:
         printf("unimplemented file type %d\n", type);
      }
   }
}

/***********************************************************************************
 * Procedure: get_filename
 * Synopsis:  type = get_filename(str)
 * Purpose:   Parse out a file descriptor and return a type for that name
 ***********************************************************************************/
int get_filename(char *str)
{
   char *p;
   int len;

   if (*str)
   {
      /* They actually specified a name.  All we need to do is replace the */
      /* current one.  If they specified a '?', then we need to prompt the */
      /* user with the file requester.  We will assume the ASL one for now */
      /* but should have a reasonable fall back one.                       */
      if (!strcmp(str, "?"))
      {

         if (global.freq) /* could be ASL or Arp */
         {
            int n;
            if (AslBase != NULL)
            {
/*
               struct TagItem taglist[2];
*/
               struct TagItem taglist[5];
#if 0
               taglist[0].ti_Tag = ASL_Hail;
               taglist[0].ti_Data = (ULONG)(v+1);
#endif
/* new */
               /* Pattern to match against project files */
               taglist[0].ti_Tag  = ASL_Pattern;
               taglist[0].ti_Data = (ULONG)"(#?FILE|#?DM#AK#?|#?.DICE)";

               taglist[1].ti_Tag  = ASL_File;
               taglist[1].ti_Data = (ULONG)"";

               taglist[2].ti_Tag  = ASL_Dir;
               taglist[2].ti_Data = (ULONG)"";


               taglist[3].ti_Tag = TAG_DONE;
   
               if (!AslRequest( (APTR)global.freq, taglist))
                  return(0);
            }
            else /* must be Arp since we have a requester... */
            {
               if (!ArpFileRequest(global.freq))
                  return (0);
            }   
            n = strlen(global.freq->rf_Dir);
            if (n > 255) n = 255;
            p = global.filename;
            memcpy(p, global.freq->rf_Dir, n);
            p += n;

            if ((n > 0) && (p[-1] != ':') && (p[-1] != '/'))
            {
               *p++ = '/';
               n++;
            }
            len = strlen(global.freq->rf_File);
            if ((n + len) > 255) len = 255-n;
            strncpy(p, global.freq->rf_File, len);
            p[len] = 0;

            global.nameoffile = 0; /* new name - possibly unsafe to write */
         }
         else
         {
            /* We don't have ASL.LIBRARY, try something else instead */
            set_fr_gadgets(global.filename);
            /* If user hits save button, we'll be back with the name */
            return 0;

         }
      }
      else
      {
         strncpy(global.filename, str, MAX_FILENAME);
      }
   }

   if (!stricmp(global.filename, "ENV"))
   {
      strcpy(global.title, global.text[TEXT_GLOBOPTS]);
      return(FILE_ENV);
   }

   len = strlen(global.filename);
   p = global.filename + strlen(global.filename);

   strcpy(global.title, global.text[TEXT_FILEOPTS]);
   strncat(global.title, global.filename, MAX_TITLE);
   global.title[MAX_TITLE] = 0;

   if (len < 2)
      return(FILE_OPTIONS);

   if ((len >= 7) && (!stricmp(p-7, "DCCOPTS")))
      return(FILE_OPTIONS);

   if ((len >= 9) && (!stricmp(p-9, "DMAKEFILE")))
      return(FILE_DMAKEFILE);

   if ((len >= 5) && (!stricmp(p-5, ".DICE")))
      return(FILE_DMAKEFILE);

   if (!stricmp(p-2, ".H"))
      return(FILE_C);

   if (!stricmp(p-2, ".C"))
      return(FILE_C);

   return(FILE_OPTIONS);
}

/***********************************************************************************
 * Procedure: reset_options
 * Synopsis:  reset_options(reshow)
 * Purpose:   Reset all the options to the default values
 ***********************************************************************************/
void reset_options(int reshow)
{
   struct G_GROUP *group;

   set_gadgets(0);

   reset_group(global.objects);
   for(group = global.groups; group != NULL;
       group = (struct G_GROUP *)group->base.next)
   {
      reset_group(group->objects);
   }
   if (reshow) set_gadgets(1);
}

/***********************************************************************************
 * Procedure: reset_group
 * Synopsis:  reset_group(object)
 * Purpose:   Reset all the options in a group to the default values
 ***********************************************************************************/
void reset_group(struct G_OBJECT *object)
{
#define object_check ((struct G_CHECK *)object)
#define object_list  ((struct G_LIST *)object)
#define object_str   ((struct G_STRING *)object)
#define object_cycle ((struct G_CYCLE *)object)

   while(object != NULL)
   {
      switch(object->class)
      {
         case CLASS_STRING:
            object_str->buf[0] = 0;
            break;
         case CLASS_CYCLE:
            {
               struct G_VALUE *val;

               val = object_cycle->curval = object_cycle->values;
               while(val != NULL)
               {
                  if (val->string)
                     val->string->buf[0] = 0;
                  val = val->next;
               }
            }
            break;
         case CLASS_LIST:
            {
               struct G_ENTRY *ent;
               ent = object_list->first;
               object_list->first = object_list->top = NULL;
               object_list->string = object_list->maxent = 0;
               while(ent != NULL)
               {
                  struct G_ENTRY *nextent;

                  nextent = (struct G_ENTRY *)ent->base.next;
                  free_mem(ent, sizeof(struct G_ENTRY));
                  ent = nextent;
               }
            }
            break;
         case CLASS_CHECK:
            object->state = 0;
            if (!object_check->option1[0]) object->state = 1;
            break;
         default:
            object->state = 0;
            break;
      }
      object = object->next;
   }
#undef object_list
#undef object_str
#undef object_cycle
#undef object_check
}
/***********************************************************************************
 * Procedure: match_opt
 * Synopsis:  rc = match_opt(optstr, argv, buf, exact)
 * Purpose:   Compare a string to see if it matches a given option
 *            If a successful match, return the number of argv entries used.
 *            If exact is specified, do not look for extension strings.
 ***********************************************************************************/
int match_opt(char *optstr,
              char **argv,
              char *buf,
              int exact
             )
{
   char *p;
   int len, olen;

   p = strchr(optstr, '%');
   if ((p == NULL) || (buf == NULL))
   {
      /* We have just a plain option.  For it to match, we must have */
      /* a complete match on the characters.                         */
      return(!strcmp(optstr, *argv));
   }

   if (exact) return 0; /* not looking for options strings this pass    */

   /* Not a plain one.  There are a couple of possibilities:       */
   /*  -opt%s       No space allowed after the option              */
   /*  -opt %s      Space after option is optional                 */
   /*  -opt  %s     Space after option is mandatory.               */
   olen = len = p - optstr;
   while(len && (optstr[len-1] == ' ')) len--;
   if (memcmp(optstr, *argv, len))
      return(0);

   /* We know that at least we match the start of the option.  Now we */
   /* Have to get the buffer filled in.                               */
   if ((*argv)[len]) /* We know that the parameter is with this option */
   {
      /* If the space is mandatory, we obviously didn't make a match  */
      if ((olen - len) > 1) return(0);
      strncpy(buf, argv[0]+len, MAX_STRING);
      return(1);
   }

   /* Parameter is with the next option */
   /* If the space is not allowed, we didn't get a match */
   if ((olen == len) || (argv[1] == NULL)) return(0);
   strncpy(buf, argv[1], MAX_STRING);
   return(2);
}

/***********************************************************************************
 * Procedure: append_option
 * Synopsis:  rc = append_option(optstr, buffer, length)
 * Purpose:   append option to string buffer, update count and pointer
 ***********************************************************************************/
int append_option(char *optstr1, char *optstr2, char **buf, int *len)
{
   int l;
   char *p, *optr;

   optr = optstr1;      /* unless we change it to fetch a string        */
   p = strchr(optstr1, '%');
   if ((p != NULL) && (optstr2 != NULL)) /* seems we have a string      */
   {
      l = p - optstr1;
      if (l >= *len) return 0;  /*no room to save it                    */
      if (optstr1[l-1] == ' ')
         l -= 1;
      memcpy(*buf, optstr1, l);
      *buf += l;
      *len -= l;
      **buf = '\0';     /* just in case next step fails...              */
      optr = optstr2;
   }

   /* now copy whatever is left and put a blank on the end              */
   l = strlen(optr);
   if (l >= *len) return 0;     /* no room to save it                   */
   strcpy(*buf, optr);
   *buf += l;
   strcpy(*buf, " ");
   *buf += 1;
   *len -= (l + 1);

   return 1;
}

/***********************************************************************************
 * Procedure: get_option
 * Synopsis:  rc = get_option(object, buffer, length)
 * Purpose:   make a string of all options that are set
 ***********************************************************************************/
int get_option(struct G_OBJECT *object,
               char **buf,
               int *len
              )
{
#define object_list  ((struct G_LIST   *)object)
#define object_str   ((struct G_STRING *)object)
#define object_check ((struct G_CHECK  *)object)
#define object_cycle ((struct G_CYCLE  *)object)
   while (object != NULL)
   {
      switch (object->class)
      {
         case CLASS_CYCLE:
         {
            struct G_VALUE *val;

            val = object_cycle->curval;
            if (val->option[0] == 0) break;     /* option not set       */
            if
            (
                append_option(val->option,
                              val->string != NULL ? val->string->buf : NULL,
                              buf, len)
                == 0
            )
               return 0;        /* no room to save it                   */
            break;
         }
         case CLASS_CHECK:
         {
            char *p;
            p = object->state ? object_check->option1 : object_check->option0;
            if (*p)             /* do we gots a string?                 */
               if (append_option(p, NULL, buf, len) == 0)
                  return 0;     /* shucks, nowhere to put it            */
            break;
         }
         case CLASS_STRING:
         {
            if (object_str->buf[0] == 0) break; /* empty string         */
            if
            (
                append_option(object_str->option,
                              object_str->buf,
                              buf, len)
                == 0
            )
               return 0;        /* no room, buf is full...              */
            break;
         }
         case CLASS_LIST:
         {
            struct G_ENTRY *ent;

            ent = object_list->first;
            while (ent != NULL) /* empty list will fall through         */
            {
               if
               (append_option(object_list->option, ent->buf, buf, len) == 0)
                  return 0;     /* seem familiar?                       */
               ent = (struct G_ENTRY *)ent->base.next;
            }
            break;
         }
      }
      object = object->next;
   }
   return 1;
#undef object_check
#undef object_list
#undef object_str
#undef object_cycle
}

/***********************************************************************************
 * Procedure: set_option
 * Synopsis:  rc = set_option(object, optstr, exact)
 * Purpose:   Set an option based on a option string, allowing parms if not exact
 ***********************************************************************************/
int set_option(struct G_OBJECT *object,
               char **argv,
               int exact
              )
{
   int used;
#define object_list  ((struct G_LIST   *)object)
#define object_str   ((struct G_STRING *)object)
#define object_check ((struct G_CHECK  *)object)
#define object_cycle ((struct G_CYCLE  *)object)

   used = 0;

   while((used == 0) && (object != NULL))
   {
      switch(object->class)
      {
         case CLASS_STRING:
            used = match_opt(object_str->option, argv, object_str->buf, exact);
            break;
         case CLASS_CYCLE:
            {
               struct G_VALUE *val;

               for(val = object_cycle->values;
                   val != NULL;
                   val = (struct G_VALUE *)val->next)
               {
                  used = match_opt(val->option, argv,
                                   val->string ? val->string->buf : NULL,
                                   exact);
                  if (used)
                  {
                     object_cycle->curval = val;
                     break;
                  }
               }
            }
            break;
         case CLASS_CHECK:
            used = match_opt(object_check->option0, argv, NULL, exact);
            if (used)
            {
               object->state = 0;
            }
            else
            {
               used = match_opt(object_check->option1, argv, NULL, exact);
               if (used)
               {
                  object->state = 1;
               }
            }
            break;
         case CLASS_LIST:
            {
               char buf[MAX_STRING+1];

               used = match_opt(object_list->option, argv, buf, exact);
               if (used)
               {
                  struct G_ENTRY *ent, *prevent;

                  ent = get_mem(sizeof(struct G_ENTRY));
                  if (ent)
                  {
                     strcpy(ent->buf, buf);
                     if ((prevent = object_list->top) == NULL)
                        object_list->top = object_list->first = ent;
                     else
                     {
                        while(prevent->base.next)
                           prevent = (struct G_ENTRY *)prevent->base.next;
                        prevent->base.next = (struct G_OBJECT *)ent;
                        ent->base.prev = (struct G_OBJECT *)prevent;
                     }
                  }
               }
            }
            break;
         default:
            object->state = 0;
            break;
      }
      object = object->next;
   }

   return(used);
#undef object_check
#undef object_list
#undef object_str
#undef object_cycle
}

/***********************************************************************************
 * Procedure: parse_options
 * Synopsis:  parse_options(string);
 * Purpose:   Parse an option and set the appropriate options in the global options
 *            data.  This subroutine destroys the string buffer.
 ***********************************************************************************/
#define MAX_BUF 100

int parse_options(char *string)
{
#define MAX_ARGS 80

   /* First we need to create an ARGV type array */
   /* We will assume no more than 80 arguments in a line */
   char *argv[MAX_ARGS];
   int argc;
   int state;
/*
 * Character Classes:
 *   SQ    '           Single Quote
 *   DQ    "           Double Quote
 *   BL    Space, Tab  White Space
 *   NL    Null        End of the string
 *   OT    Other       Anything else
 *        SQ   DQ   BL   NL   OT
 *    0    1    2    0    x    3
 *    1   >0   +1   +1   >x   +1
 *    2   +2   >0   +2   >x   +2
 *    3   +3   +3   >0   >x   +3
 */
#define SQ 0
#define DQ 1
#define BL 2
#define NL 3
#define OT 4
#define EXI 15
#define STATE_MASK 0x0f
#define ACTION_SHIFT  4
#define ACT_SKP 1
#define ACT_OUT 2
#define ACT_BEG 3
#define SKP (ACT_SKP << ACTION_SHIFT)
#define OUT (ACT_OUT << ACTION_SHIFT)
#define BEG (ACT_BEG << ACTION_SHIFT)

   static char class_tab[][5] = {
      { SKP|1, SKP|2,     0,       EXI, BEG|3 },
      { OUT|0,     1,     1,   OUT|EXI,     1 },
      {     2, OUT|0,     2,   OUT|EXI,     2 },
      {     3,     3, OUT|0,   OUT|EXI,     3 }
   };

   argc = 0;
   state = 0;
   while(state != EXI)
   {
      int class;
      int look;

      switch(*string)
      {
         case '\'': class = SQ; break;
         case '"' : class = DQ; break;
         case ' ' :
         case '\t': class = BL; break;
         case    0: class = NL; break;
         default:   class = OT; break;
      }
      look = class_tab[state][class];
      state = look & STATE_MASK;
      switch(look >> ACTION_SHIFT)
      {
         case ACT_SKP:
            argv[argc++] = string+1;
            break;
         case ACT_OUT:
            *string = 0;
            break;
         case ACT_BEG:
            argv[argc++] = string;
            break;
      }
      string++;
   }

   if (argc == 0) return(0);

   argv[argc] = NULL;

   {
      int i, used, exact;
      struct G_GROUP *group;

      for(i = 0; i < argc; i += used)
      {

         used = 0;      /* we've used what we used before       */
         /* do exact matches first to find things like -I0      */
         for (exact = 1; (exact >= 0) && (used == 0); exact -= 1)
         {
            used = set_option(global.objects, argv+i, exact);

            for(group = global.groups; (used == 0) && (group != NULL);
                group = (struct G_GROUP *)group->base.next)
            {
               used = set_option(group->objects, argv+i, exact);
            }
         }
         if ((!used) && (exact <= 0))
         {
            /* The option wasn't found, tell them in some way */
            if (!request(0, TEXT_BADOPT, argv[i], NULL))
               return(1);
            else
               used = 1; /* throw away the token and continue */
         }

      }
   }
   return(0);
}
