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

Prototype int get_simple_token(char *buf);
Prototype int get_token(char *buf1, char *buf2, char *buf3);
Prototype struct G_OBJECT **newobj(struct G_OBJECT **objlist, int class, char *buf);
Prototype int parse_config(char *cfname);
Prototype int init_config(char *cfname);
Prototype void close_config(void);

#define MAX_STRTOK     128    /* The largest string we allow for a string token */
/*********************************************************************
 * TOKENS:
 *      TOK_EOF     End of file
 *                  '/''*' '*''/'
 *      TOK_STRING  "string"
 *      TOK_STRING  'string'
 *      TOK_BAR     BAR
 *      TOK_TEXT    TEXT   <string> <string>
 *      TOK_CONFIG  CONFIG <string> <string>
 *      TOK_SUBRTN  SUBRTN <string> <string>
 *      TOK_BUTTON  BUTTON <string> <string>
 *      TOK_CHECK   CHECK  <string> <string> <string>
 *      TOK_CYCLE   CYCLE  <string>
 *      TOK_ITEM    ITEM   <string> <string>
 *      TOK_LIST    LIST   <string> <string> <string>
 *      TOK_MENU    MENU   <string>
 *      TOK_STRING  STRING <string> <string>
 *      TOK_TITLE   TITLE  <string>
 *      TOK_VALUE   VALUE  <string> <string>
 */
#define TOK_ERROR  -1
#define TOK_EOF     0 /* Note ordering of ERROR/EOF is assumed for easy tests below */
#define TOK_STRTOK  1
#define TOK_TOKEN   2
#define TOK_BAR     3
#define TOK_BUTTON  4
#define TOK_CHECK   5
#define TOK_CYCLE   6
#define TOK_ITEM    7
#define TOK_LIST    8
#define TOK_MENU    9
#define TOK_STRING 10
#define TOK_TITLE  11
#define TOK_VALUE  12
#define TOK_TEXT   13
#define TOK_CONFIG 14
#define TOK_SUBRTN 15

struct TOK_LOOK {
   char toktype;   /* Value to return for this token                        */
   char token[6];  /* Note that we only allow 6 characters in a token       */
   char strings;   /* Number of strings that the token takes as an argument */
};

struct TOK_LOOK tokens[] = {
   { TOK_BAR,     "BAR   ", 0},
   { TOK_BUTTON,  "BUTTON", 2},
   { TOK_CHECK,   "CHECK ", 3},
   { TOK_CYCLE,   "CYCLE ", 1},
   { TOK_ITEM,    "ITEM  ", 3},
   { TOK_LIST,    "LIST  ", 2},
   { TOK_MENU,    "MENU  ", 1},
   { TOK_STRING,  "STRING", 2},
   { TOK_TITLE,   "TITLE ", 1},
   { TOK_VALUE,   "VALUE ", 2},
   { TOK_TEXT,    "TEXT  ", 2},
   { TOK_CONFIG,  "CONFIG", 2},
   { TOK_SUBRTN,  "SUBRTN", 2},
};
#define MAX_LOOK (sizeof(tokens)/sizeof(struct TOK_LOOK))

/*********************************************************************
 * Character classes:
 *  0  CL_EOF    - EOF
 *  1  CL_SLASH  - /
 *  2  CL_STAR   - *
 *  3  CL_DQUOTE - "
 *  4  CL_SQUOTE - '
 *  5  CL_ALPHA  - A-Z a-z
 *  6  CL_BLANK  - ' ' \t \n
 *  7  CL_OTHER  - Anything else
 */
#define CL_EOF     0
#define CL_SLASH   1
#define CL_STAR    2
#define CL_DQUOTE  3
#define CL_SQUOTE  4
#define CL_ALPHA   5
#define CL_BLANK   6
#define CL_OTHER   7
#define MAX_CL     8

/*********************************************************************
 * States:
 *  0  ST_SCN - SCAN   - Scanning - Looking for any character - skipping white space
 *  1  ST_GSL - GOTSL  - Found /  - Checking for a matching * to start a comment
 *  2  ST_CMT - CMT    - Comment  - Looking for a * to close a comment
 *  3  ST_GST - GOTST  - Found *  - Checking for a / to close a comment
 *  4  ST_DQT - DQUOTE - String " - Looking for a matching "
 *  5  ST_SQT - SQUOTE - String ' - Looking for a matching '
 *  6  ST_TOK - TOKEN  - Token    - Gathering a keyword token
 */
#define ST_SCN  0
#define ST_GSL  1
#define ST_CMT  2
#define ST_GST  3
#define ST_DQT  4
#define ST_SQT  5
#define ST_TOK  6
#define MAX_ST 7
#define MASK_ST 7

/*********************************************************************
 * State Table Transitions:
 *
 *            Character Class
 *            0-EOF   1-/      2-*      3-"      4-'      5-A-Z      6-Blank  7-Other
 *   State
 *   0 SCAN    END    GOTSL   ERROR    DQUOTE   SQUOTE    >TOKEN     SCAN     ERROR
 *   1 GOTSL  ERROR   ERROR   CMT      ERROR    ERROR     ERROR      ERROR    ERROR
 *   2 CMT    ERROR   CMT     GOTST    CMT      CMT       CMT        CMT      CMT
 *   3 GOTST  ERROR   SCAN    CMT      CMT      CMT       CMT        CMT      CMT
 *   4 DQUOTE ERROR   >DQUOTE >DQUOTE  !SCAN    >DQUOTE   >DQUOTE    >DQUOTE  >DQUOTE
 *   5 SQUOTE ERROR   >SQUOTE >SQUOTE  >SQUOTE  !SCAN     >SQUOTE    >SQUOTE  >SQUOTE
 *   6 TOKEN  +END    +GOTSL  ERROR    +DQUOTE  +SQUOTE   >TOKEN     +SCAN    ERROR
 * Note: >     - AC_SAV - Means to append the current character and continue scan
 *       !     - AC_STR - Means to return the current token as a string.
 *       +     - AC_TOK - Means to return the current token as a keyword
 *       ERROR - AC_ERR - Indicates issuing an error
 *       END   - AC_END - Indicates returing the END token.
 */
#define AC_SKP (0<<5) /* Must be zero - the default to do nothing */
#define AC_SAV (1<<5)
#define AC_STR (2<<5)
#define AC_TOK (3<<5)
#define AC_ERR (4<<5)
#define AC_END (5<<5)
#define MASK_AC (7<<5)

char statetab[MAX_ST][MAX_CL] =
{
/* 0 ST_SCN  */
   { AC_END,        ST_GSL,         AC_ERR,         ST_DQT,
     ST_SQT,        AC_SAV|ST_TOK,  ST_SCN,         AC_ERR         },
/* 1 ST_GSL */
   { AC_ERR,        AC_ERR,         ST_CMT,         AC_ERR,
     AC_ERR,        AC_ERR,         AC_ERR,         AC_ERR         },
/* 2 ST_CMT   */
   { AC_ERR,        ST_CMT,         ST_GST,         ST_CMT,
     ST_CMT,        ST_CMT,         ST_CMT,         ST_CMT         },
/* 3 ST_GST */
   { AC_ERR,        ST_SCN,         ST_CMT,         ST_CMT,
     ST_CMT,        ST_CMT,         ST_CMT,         ST_CMT         },
/* 4 ST_DQT*/
   { AC_ERR,        AC_SAV|ST_DQT,  AC_SAV|ST_DQT,  AC_STR|ST_SCN,
     AC_SAV|ST_DQT, AC_SAV|ST_DQT,  AC_SAV|ST_DQT,  AC_SAV|ST_DQT  },
/* 5 ST_SQT*/
   { AC_ERR,        AC_SAV|ST_SQT,  AC_SAV|ST_SQT,  AC_SAV|ST_SQT,
     AC_STR|ST_SCN, AC_SAV|ST_SQT,  AC_SAV|ST_SQT,  AC_SAV|ST_SQT  },
/* 6 ST_TOK */
   { AC_TOK|ST_SCN, AC_TOK|ST_GSL,  AC_ERR,         AC_TOK|ST_DQT,
     AC_TOK|ST_SQT, AC_SAV|ST_TOK,  AC_TOK|ST_SCN,  AC_ERR         },
};

/*
 * Get a simple token from the file.
 * No parsing of semantics is done at this level.
 */
int get_simple_token(char *buf)
{
   int c;
   int pos;
   int class;
   int action;

   pos = 0;

   for(;;)
   {
      /* Get the next character from the input file and assign a character class */
      c = getc(global.fp);
      class = CL_OTHER;
      switch(c)
      {
         case EOF:  class = CL_EOF;    break;
         case '/':  class = CL_SLASH;  break;
         case '*':  class = CL_STAR;   break;
         case '"':  class = CL_DQUOTE; break;
         case '\'': class = CL_SQUOTE; break;
         case '\n': global.line++;
         case ' ':
         case '\t':  class = CL_BLANK;  break;
         default:
            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
               class = CL_ALPHA;
            break;
      }
      /* Run us through the state table to get an action and a new state */
      action = statetab[global.state][class];
      global.state = (action & MASK_ST);

      /* Perform the work for the action */
      switch(action & MASK_AC)
      {
         case AC_SAV: if (pos < MAX_STRTOK) /* make sure we don't overflow the buffer */
                      {
                         buf[pos++] = c;  /* Save the character */
                         break;
                      }
                      /* Else fall through to the error case */
         case AC_ERR: return(TOK_ERROR);
         case AC_STR: buf[pos] = 0;   /* Null terminate what we gathered */
                      return(TOK_STRTOK);
         case AC_TOK: buf[pos] = 0;
                      return(TOK_TOKEN);
         case AC_END: return(TOK_EOF);
      }
   }
}

/*
 * Get the next token and any associated strings.
 */
int get_token(char *buf1,
              char *buf2,
              char *buf3
             )
{
   int i;
   int j;
   int toktype;
   int len;
   char *strs[3];

   strs[0] = buf1;
   strs[1] = buf2;
   strs[2] = buf3;

   toktype = get_simple_token(buf1);
   if (toktype == TOK_EOF)   return(toktype);
   if (toktype != TOK_TOKEN) return(TOK_ERROR);

   /* Go through and uppercase the string */
   /* we KNOW that it consists of only upper/lower case letters */
   len = strlen(buf1);
   if (len > 6) return(TOK_ERROR);
   strcpy(buf1+len, "     "); /* Make sure we blank pad for the memcmp */
   for(i = 0; i < 6; i++)
      if (buf1[i] >= 'a') buf1[i] -= ('a'-'A');

   /* Now go through the table and look for a token */
   for (i = 0; i < MAX_LOOK; i++)
      if (!memcmp(buf1, tokens[i].token, 6)) break;
   if (i == MAX_LOOK) return(TOK_ERROR);

   /* We have a matching token, get any strings that it wants */
   for(j = 0; j < tokens[i].strings; j++)
   {
      toktype = get_simple_token(strs[j]);
      if (toktype != TOK_STRTOK) return(TOK_ERROR);
   }

   /* Everything checks out, let them know what type of token they got */
   return((int)tokens[i].toktype);
}

void init_group(struct G_OBJECT *object)
{
   while(object != NULL)
   {
      if (object->next) object->next->prev = object;
      if (object->class == CLASS_CYCLE)
      {
         struct G_VALUE *val;
         struct G_CYCLE *cyc;

         cyc = (struct G_CYCLE *)object;

         cyc->curval = cyc->values;
         for(val = cyc->curval; val; val = val->next)
         {
            if (cyc->base.next) cyc->base.next->prev = (struct G_OBJECT *)cyc;
         }
      }
      object = object->next;
   }
}
/*
 * Allocate an object structure to hold an entry
 */
static short sizetab[] = { 0,
                          sizeof(struct G_STRING),
                          sizeof(struct G_CYCLE),
                          sizeof(struct G_CHECK),
                          sizeof(struct G_LIST),
                          sizeof(struct G_BUTTON)
                         };

struct G_OBJECT **newobj(struct G_OBJECT **objlist,
                         int class,
                         char *buf
                        )
{
   struct G_OBJECT *obj;
   obj = get_mem(sizetab[class]);
   if (obj == NULL) return(NULL);

   if (objlist) *objlist = obj;
   obj->class = class;
   obj->title = savestr(buf);
   return(&obj->next);
}

/*
 * Parse the configuration file (name is cfname)
 * return:  0 if OK
 *          1 if syntax error in config file
 *          2 if unable to open config file
 */
int parse_config(char *cfname)
{
   static char buf1[MAX_STRTOK+2], buf2[MAX_STRTOK+2], buf3[MAX_STRTOK+2];
   int toktype;
   int menupos;
   int buttonpos;
   int strpos;
   struct G_OBJECT **objlist;
   struct G_VALUE  **valent;

   objlist = &global.objects;
   valent = NULL;

   menupos = buttonpos = 0;
   if (init_config(cfname)) return(2);

   while((toktype = get_token(buf1, buf2, buf3)) > TOK_EOF)
   {
      switch(toktype)
      {
         case TOK_TEXT:   strpos = atoi(buf1) - 1;
                          if ((strpos < 0) || (strpos >= NUM_TEXT)) goto error;
                          global.text[strpos] = savestr(buf2);
                          break;
         case TOK_CONFIG: strpos = atoi(buf1) - 1;
                          if ((strpos < 0) || (strpos >= NUM_CONFIG)) goto error;
                          global.text[CONFIG_BASE+strpos] = savestr(buf2);
                          break;
         case TOK_SUBRTN: strpos = atoi(buf1) - 1;
                          if ((strpos < 0) || (strpos >= NUM_SUBRTN)) goto error;
                          global.text[SUBRTN_BASE+strpos] = savestr(buf2);
                          break;
         case TOK_BAR:    global.menuitem[menupos].nm_Type   = MENU_ITEM;
                          global.menuitem[menupos].nm_Label  = NM_BARLABEL;
                          if (menupos >= MAX_MENU) goto error;
                          menupos++;
                          break;
         case TOK_MENU:   global.menuitem[menupos].nm_Type   = MENU_MENU;
                          global.menuitem[menupos].nm_Label  = savestr(buf1);
                          if (menupos >= MAX_MENU) goto error;
                          menupos++;
                          break;
         case TOK_ITEM:   global.menuitem[menupos].nm_Type   = MENU_ITEM;
                          global.menuitem[menupos].nm_Label  = savestr(buf1);
                          if (buf2[0])
                             global.menuitem[menupos].nm_CommKey = savestr(buf2);
                          global.menuitem[menupos].nm_UserData = savestr(buf3);
                          if (menupos >= MAX_MENU) goto error;
                          menupos++;
                          break;
         case TOK_BUTTON: if (buttonpos >= MAX_BUTTON) goto error;
                          global.button[buttonpos].base.title = savestr(buf1);
                          global.button[buttonpos++].command    = savestr(buf2);
                          break;
         case TOK_TITLE:  buf1[MAX_TITLE-1] = 0;
                          strcpy(global.title2, buf1);
                          break;
         case TOK_STRING: objlist = newobj(objlist, CLASS_STRING, buf1);
                          if (!objlist) goto error;
                          ((struct G_STRING *)objlist)->option = savestr(buf2);
                          valent = NULL;
                          break;
         case TOK_CHECK:  objlist = newobj(objlist, CLASS_CHECK, buf1);
                          if (!objlist) goto error;
                          ((struct G_CHECK *)objlist)->option0 = savestr(buf2);
                          ((struct G_CHECK *)objlist)->option1 = savestr(buf3);
                          valent = NULL;
                          break;
         case TOK_LIST:   objlist = newobj(objlist, CLASS_LIST, buf1);
                          if (!objlist) goto error;
                          ((struct G_LIST *)objlist)->option = savestr(buf2);
                          if (!stricmp(buf2, "Files"))
                             global.filelist = (struct G_LIST *)objlist;
                          valent = NULL;
                          break;
         case TOK_CYCLE:  objlist = newobj(objlist, CLASS_CYCLE, buf1);
                          if (!objlist) goto error;
                          valent = &((struct G_CYCLE *)objlist)->values;
                          break;
         case TOK_VALUE:  if (valent == NULL) goto error;
                          *valent = get_mem(sizeof(struct G_VALUE));
                          if (!*valent) goto error;
                          (*valent)->next = NULL;
                          (*valent)->title = savestr(buf1);
                          (*valent)->option = savestr(buf2);
                          (*valent)->string = NULL;
                          if (strchr(buf2, '%'))
                          {
                             struct G_STRING *gstr;

                             gstr = get_mem(sizeof(struct G_STRING));
                             if (!gstr) goto error;
                             gstr->base.class = CLASS_STRING;
                             (*valent)->string = gstr;
                          }
                          valent = &((*valent)->next);
                          break;
      }
   }
   global.menuitem[menupos].nm_Type = MENU_END;

   if (toktype == TOK_ERROR)
   {
error:
      sprintf(global.title, "ERROR- %s line %d", cfname, global.line);
      global.objects = NULL; /* memory will get cleaned up on exit */
      /* global.menuitem gets cleared in vmake.c to handle case of */
      /* failure to open config file as well.                      */
      return(1);
   }
   close_config();

   /* Now we need to go through the groups and sanitize the pointers as well */
   /* as initialize all the default states                                   */
   init_group(global.objects);
   return(0);
}

/*
 * Open the configuration file and initialize any global data
 */
int init_config(char *cfname)
{
   struct Process *mytask;
   APTR saveptr;

   global.state = ST_SCN;
   global.line  = 1;
   mytask = (struct Process *)FindTask(NULL);
   saveptr = mytask->pr_WindowPtr;
   mytask->pr_WindowPtr = (APTR)-1;
   global.fp = fopen(cfname, "r");
   mytask->pr_WindowPtr = saveptr;
   if (global.fp == NULL)
   {
      sprintf(global.title, "ERROR- No %s", cfname);
      return(1);
   }
   return(0);
}

/*
 * Close the configuration file and clean up anything else necessary
 */
void close_config()
{
   if (global.fp)
      fclose(global.fp);
   global.fp = NULL;
}
