/*
*  ChkTeX, resource file reader.
*  Copyright (C) 1995-96 Jens T. Berger Thielemann
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*  Contact the author at:
*              Jens Berger
*              Spektrumvn. 4
*              N-0666 Oslo
*              Norway
*              E-mail: <[email protected]>
*
*
*/

#include "ChkTeX.h"
#include "OpSys.h"
#include "Utility.h"
#include "Resource.h"

#define LNEMPTY(a) struct WordList a = {0, 1, {0}, {0}};
#define LIST(a)    struct WordList a = {0, 0, {0}, {0}};
#define LCASE(a)   LIST(a) LIST(a ## Case)
#define KEY(a,def) const char *a = def;

RESOURCE_INFO
#undef KEY
#undef LCASE
#undef LNEMPTY
#undef LIST
struct KeyWord
{
   const char *Name;
   const char **String;        /* Keyword = item */
   struct WordList *List,      /* Case-sensitive strings */
    *CaseList;                 /* Case-insensitive strings */
};

#define LNEMPTY LIST
#define LIST(name)       {#name, NULL, &name, NULL},
#define LCASE(name)      {#name, NULL, &name, &name ## Case},
#define KEY(name,def)    {#name, &name, NULL, NULL},

struct KeyWord Keys[] = {
   RESOURCE_INFO {NULL, NULL, NULL, NULL}
};

#undef KEY
#undef LCASE
#undef LNEMPTY
#undef LIST


/***************************** RESOURCE HANDLING **************************/

/* We don't include a trailing semicolon here, so that we can add it
* at the calling site, thereby preserving proper indentation.  Double
* semicolons are undesirable since they have been known to break some
* compilers. */
#define TOKENBITS(name) enum name { \
 BIT(Eof),      /* End-of-file */ \
 BIT(Open),     /* { */ \
 BIT(Close),    /* } */ \
 BIT(BrOpen),   /* [ */ \
 BIT(BrClose),  /* ] */ \
 BIT(Equal),    /* = */ \
 BIT(Word),     /* Keyword */ \
 BIT(Item)      /* List item */ \
}

#undef BIT
#define BIT BITDEF1
TOKENBITS(Token_BIT);
#undef BIT
#define BIT BITDEF2
TOKENBITS(Token);
static enum Token Expect;
static unsigned long RsrcLine;

static enum Token ReadWord(char *, FILE *,
                          char *(fgetsfun)(char *, int, FILE *));
static char MapChars(char **String);



/*
* Parses the contents of a resource file.
*
* Format:
* Keyword { item1 item2 ... } [ item1 item2 ... ]
* Keyword [ item1 item2 ... ] { item1 item2 ... }
* Keyword = { item1 item2 ...�}
* Keyword = [ item1 item2 ...�]
* Keyword = item
*
* Returns whether the attempt was a successful one.
*/

int ProcessRC(FILE *fh, const char *Filename,
             char *(fgetsfun)(char *, int, FILE *))
{
   const char *String = NULL;
   int Success = TRUE;
   enum Token Token;
   unsigned long Counter;

   struct KeyWord *CurWord = NULL;

   /* Interpret incoming words as ... */
   enum
   {
       whList,                 /* List elements */
       whCaseList,             /* Case insensitive list elements */
       whEqual,                /* Solo elements */
       whNone                  /* List items not accepted */
   } What = whNone;


   RsrcLine = 0;
   Expect = FLG_Word | FLG_Eof;
   do
   {
       Token = ReadWord(ReadBuffer, fh, fgetsfun);
       if (!(Expect & Token))
       {
           switch (Token)
           {
           case FLG_Item:
               String = "item";
               break;
           case FLG_Word:
               String = "word";
               break;
           case FLG_Equal:
               String = "`='";
               break;
           case FLG_Open:
               String = "`{'";
               break;
           case FLG_Close:
               String = "`}'";
               break;
           case FLG_BrOpen:
               String = "`['";
               break;
           case FLG_BrClose:
               String = "`]'";
               break;
           case FLG_Eof:
               String = "EOF";
               break;
           }
           PrintPrgErr(pmFaultFmt, Filename, RsrcLine, String);
           Success = FALSE;
           Token = FLG_Eof;
       }

       switch (Token)
       {
       case FLG_Word:
           for (Counter = 0; Keys[Counter].Name; Counter++)
           {
               if (!strcasecmp(ReadBuffer, Keys[Counter].Name))
               {
                   CurWord = &Keys[Counter];
                   Expect = (CurWord->List ? FLG_Open : 0) |
                       (CurWord->CaseList ? FLG_BrOpen : 0) | FLG_Equal;
                   break;
               }
           }
           if (!Keys[Counter].Name)
           {
               PrintPrgErr(pmKeyWord, ReadBuffer, Filename);
               Success = FALSE;
               Token = FLG_Eof;
           }
           break;
       case FLG_Item:
           switch (What)
           {
           case whEqual:
               if (!(*(CurWord->String) = strdup(ReadBuffer)))
               {
                   PrintPrgErr(pmStrDupErr);
                   Token = FLG_Eof;
                   Success = FALSE;
               }

               What = whNone;
               Expect = FLG_Word | FLG_Eof;
               break;
           case whCaseList:
               if (!InsertWord(ReadBuffer, CurWord->CaseList))
               {
                   Token = FLG_Eof;
                   Success = FALSE;
               }
               break;
           case whList:
               if (!InsertWord(ReadBuffer, CurWord->List))
               {
                   Token = FLG_Eof;
                   Success = FALSE;
               }
               break;
           case whNone:
               PrintPrgErr(pmAssert);
           }
           break;
       case FLG_Equal:
           What = whEqual;
           Expect = (CurWord->List ? FLG_Open : 0) |
               (CurWord->CaseList ? FLG_BrOpen : 0) |
               (CurWord->String ? FLG_Item : 0);
           break;
       case FLG_BrOpen:
           if (What == whEqual)
               ClearWord(CurWord->CaseList);
           What = whCaseList;
           Expect = FLG_Item | FLG_BrClose;
           break;
       case FLG_Open:
           if (What == whEqual)
               ClearWord(CurWord->List);
           What = whList;
           Expect = FLG_Item | FLG_Close;
           break;
       case FLG_BrClose:
       case FLG_Close:
           Expect = (CurWord->List ? FLG_Open : 0) |
               (CurWord->CaseList ? FLG_BrOpen : 0) | FLG_Equal | FLG_Word |
               FLG_Eof;
           What = whNone;
           break;
       case FLG_Eof:
           break;
       }
   } while (Token != FLG_Eof);
   return (Success);
}

/*
* Opens a file and passes to ProcessRC().
*/
int ReadRC(const char *Filename)
{
   int Success = FALSE;
   FILE *fh;

   if ((fh = fopen(Filename, "r")))
   {
       Success = ProcessRC(fh, Filename, &fgets);
       fclose(fh);
   }
   else
       PrintPrgErr(pmRsrcOpen, Filename);

   return (Success);
}

const char *FGETS_TMP = NULL;
char *fgets_from_string(char *out, int size, FILE *fh)
{
   char *res;
   if (FGETS_TMP == NULL)
       return NULL;
   res = strncpy(out, FGETS_TMP, size - 1);

   if (size - 1 < strlen(FGETS_TMP))
   {
       /* It wasn't all read, so null terminate it, and get ready for
        * next time. */
       res[size] = '\0';
       FGETS_TMP = FGETS_TMP + (size - 1);
   }
   else
   {
       /* We're done, so signal that */
       FGETS_TMP = NULL;
   }
   return res;
}

/*
* Opens a file and passes to ProcessRC().
*/
int ReadRCFromCmdLine(const char *CmdLineArg)
{
   if (!CmdLineArg)
       return FALSE;

   FGETS_TMP = CmdLineArg;
   return ProcessRC(NULL, "CommandLineArg", &fgets_from_string);
}

/*
* Reads a token from the `.chktexrc' file; if the token is
* FLG_Item or FLG_Word, Buffer will contain the plaintext of the
* token. If not, the contents are undefined.
*/

static enum Token ReadWord(char *Buffer, FILE *fh,
                          char *(fgetsfun)(char *, int, FILE *))
{
   static char *String = NULL;
   static char StatBuf[BUFFER_SIZE];
   enum Token Retval = FLG_Eof;

   unsigned short Chr;

   char *Ptr;
   int OnceMore = TRUE, Cont = TRUE;

   if (Buffer)
   {
       do
       {
           if (!(String && *String))
           {
               if ((fgetsfun)(StatBuf, BUFFER_SIZE - 1, fh))
                   String = strip(StatBuf, STRP_RGT);
               RsrcLine++;
           }

           Ptr = Buffer;
           if (String && (String = strip(String, STRP_LFT)))
           {
               switch (Chr = *String)
               {
               case 0:
               case CMNT:
                   String = NULL;
                   break;
               case QUOTE:    /* Quoted argument */
                   Cont = TRUE;
                   String++;

                   while (Cont)
                   {
                       switch (Chr = *String++)
                       {
                       case 0:
                       case QUOTE:
                           Cont = FALSE;
                           break;
                       case ESCAPE:
                           if (!(Chr = MapChars(&String)))
                               break;

                           /* FALLTHRU */
                       default:
                           *Ptr++ = Chr;
                       }
                   }
                   *Ptr = 0;
                   Retval = FLG_Item;
                   OnceMore = FALSE;
                   break;

#define DONEKEY (FLG_Open | FLG_Equal | FLG_BrOpen)
#define DONELIST (FLG_Close | FLG_BrClose)
#define TOKEN(c, ctxt, tk) case c: if(Expect & (ctxt)) Retval = tk; LAST(token)

                   LOOP(token,
                        TOKEN('{', DONEKEY, FLG_Open);
                        TOKEN('[', DONEKEY, FLG_BrOpen);
                        TOKEN('=', DONEKEY, FLG_Equal);
                        TOKEN(']', DONELIST, FLG_BrClose);
                        TOKEN('}', DONELIST, FLG_Close);
                       )
                   if (Retval != FLG_Eof)
                   {
                       OnceMore = FALSE;
                       String++;
                       break;
                   }

                   /* FALLTHRU */

               default:       /* Non-quoted argument */
                   OnceMore = FALSE;
                   if (Expect & FLG_Word)
                   {
                       while (Cont)
                       {
                           Chr = *String++;
                           if (isalpha((unsigned char)Chr))
                               *Ptr++ = Chr;
                           else
                               Cont = FALSE;
                       }
                       String--;
                       Retval = FLG_Word;
                   }
                   else        /* Expect & FLG_Item */
                   {
                       while (Cont)
                       {
                           switch (Chr = *String++)
                           {
                           case CMNT:
                           case 0:
                               String = NULL;
                               Cont = FALSE;
                               break;
                           case ESCAPE:
                               if (!(Chr = MapChars(&String)))
                                   break;

                               *Ptr++ = Chr;
                               break;
                           default:
                               if (!isspace((unsigned char)Chr))
                                   *Ptr++ = Chr;
                               else
                                   Cont = FALSE;
                           }
                       }
                       Retval = FLG_Item;
                   }

                   if (!(Buffer[0]))
                   {
                       PrintPrgErr(pmEmptyToken);
                       if (*String)
                           String++;
                   }
                   *Ptr = 0;
                   break;
               }
           }
           else
               OnceMore = FALSE;
       }
       while (OnceMore);
   }
   return (Retval);
}



/*
* Translates escape codes. Give it a pointer to the char after the
* escape char, and we'll return what it maps to.
*/

#define MAP(a,b)        case a: Tmp = b; break;

static char MapChars(char **String)
{
   int Chr, Tmp = 0;
   unsigned short Cnt;

   Chr = *((char *) (*String)++);

   switch (tolower((unsigned char)Chr))
   {
       MAP(QUOTE, QUOTE);
       MAP(ESCAPE, ESCAPE);
       MAP(CMNT, CMNT);
       MAP('n', '\n');
       MAP('r', '\r');
       MAP('b', '\b');
       MAP('t', '\t');
       MAP('f', '\f');
       MAP('{', '{');
       MAP('}', '}');
       MAP('[', '[');
       MAP(']', ']');
       MAP('=', '=');
       MAP(' ', ' ');
   case 'x':
       Tmp = 0;

       for (Cnt = 0; Cnt < 2; Cnt++)
       {
           Chr = *((*String)++);
           if (isxdigit((unsigned char)Chr))
           {
               Chr = toupper((unsigned char)Chr);
               Tmp = (Tmp << 4) + Chr;

               if (isdigit((unsigned char)Chr))
                   Tmp -= '0';
               else
                   Tmp -= 'A' - 10;
           }
           else
           {
               if (Chr)
               {
                   PrintPrgErr(pmNotPSDigit, Chr, "hex");
                   Tmp = 0;
               }
               break;
           }
       }
       break;
   case '0':
   case '1':
   case '2':
   case '3':
   case '4':
   case '5':
   case '6':
   case '7':

       Tmp = Chr - '0';

       for (Cnt = 0; Cnt < 2; Cnt++)
       {
           Chr = *((*String)++);
           if (within('0', Chr, '7'))
               Tmp = (Tmp * 8) + Chr - '0';
           else
           {
               if (Chr)
               {
                   PrintPrgErr(pmNotPSDigit, Chr, "octal");
                   Tmp = 0;
               }
               break;
           }
       }
       break;
   case 'd':
       for (Cnt = 0; Cnt < 3; Cnt++)
       {
           if (isdigit((unsigned char)(Chr = *((*String)++))))
               Tmp = (Tmp * 10) + Chr - '0';
           else
           {
               if (Chr)
               {
                   PrintPrgErr(pmNotPSDigit, Chr, "");
                   Tmp = 0;
               }
               break;
           }
       }
       break;
   default:
       PrintPrgErr(pmEscCode, ESCAPE, Chr);
   }
   return (Tmp);
}