/*
 *  ChkTeX v1.5, 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., 675 Mass Ave, Cambridge, MA 02139, 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) STRPTR a = def;

RESOURCE_INFO

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

struct KeyWord
{
    STRPTR  Name;
    STRPTR  *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 **************************/

#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 ULONG RsrcLine;

static enum Token ReadWord __PROTO((STRPTR, FILE *));
static TEXT MapChars       __PROTO((STRPTR *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.
 */

BOOL ReadRC(const STRPTR Filename)
{
    STRPTR      String;
    BOOL        Success = FALSE;
    FILE        *fh;
    enum Token  Token;
    ULONG       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;

    if((fh = fopen(Filename, "r")))
    {
        Success = TRUE;
        do
        {
            Token = ReadWord(ReadBuffer, fh);
            ifn(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:
                    ifn(*(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);

        fclose(fh);
    }
    else
        PrintPrgErr(pmRsrcOpen, Filename);

    return(Success);
}

/*
 * 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(STRPTR Buffer, FILE *fh)
{
    static
        STRPTR  String = NULL;
    static
        TEXT    StatBuf[BUFSIZ];
    enum Token Retval = FLG_Eof;

    UWORD       Chr;

    STRPTR      Ptr;
    BOOL        OnceMore = TRUE, Cont = TRUE;

    if(Buffer)
    {
        do
        {
            if(!(String && *String))
            {
                if(fgets(StatBuf, BUFSIZ-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:
                            ifn(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(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:
                                ifn(Chr = MapChars(&String))
                                    break;

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

                    ifn(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 TEXT MapChars(STRPTR *String)
{
    int    Chr, Tmp = 0;
    UWORD  Cnt;

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

    switch(tolower(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(Chr))
            {
                Chr = toupper(Chr);
                Tmp = (Tmp<<4) + Chr;

                if(isdigit(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(Chr = *((*String)++)))
                Tmp = (Tmp * 10) + Chr - '0';
            else
            {
                if(Chr)
                {
                    PrintPrgErr(pmNotPSDigit, Chr, "");
                    Tmp = 0;
                }
                break;
            }
        }
        break;
    default:
        PrintPrgErr(pmEscCode, ESCAPE, Chr);
    }
    return(Tmp);
}


