/*
 *  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: <jensthi@ifi.uio.no>
 *
 *
 */

#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);
}
