/*
 * installer.parser -- Cached Installer script syntax parser for GoldEd.
 *
 * Copyright 1999 Thomas Aglassinger <agi@sbox.tu-graz.ac.at>
 * Freeware. Use at your own risk.
 */
#define DEBUG 0

#define PARSER_NAME     "Installer"
#define PARSER_VERSION  "1.0"
#define PARSER_VERSION_ID 1

#include "defs.h"

/// "Header stuff"

// Buffer handles are allocated for each text buffer to keep track of ressources:

struct BufferHandle {

    struct EditConfig     *bh_EditConfig;            // pointer to text data
    struct GlobalConfig   *bh_GlobalConfig;          // editor configuration
    struct SyntaxChunk    *bh_SyntaxStack;           // parser output
    struct RefreshRequest  bh_RefreshRequest;        // display refresh request
};

#define EMPTY_STACK ((struct SyntaxChunk *)~0)       // empty stack flag

// Syntax levels
#define SYNTAX_TEXT        0                        // normal text
#define SYNTAX_COMMENT     1                        // comment
#define SYNTAX_STRING      2                        // string
#define SYNTAX_BRACKET     3                        // brackets
#define SYNTAX_BAD         4                        // unterminated strings

///
/// "Prototype"

// library functions

Prototype LibCall struct ParserData     *MountScanner(void);
Prototype LibCall ULONG                  StartScanner(__A0 struct GlobalConfig *, __A1 struct EditConfig *, __D0 struct SyntaxChunk *);
Prototype LibCall ULONG                  CloseScanner(__D0 ULONG);
Prototype LibCall void                   FlushScanner(__D0 ULONG);
Prototype LibCall void                   SetupScanner(__A0 struct GlobalConfig  *);
Prototype LibCall struct RefreshRequest *BriefScanner(__D0 ULONG, __A0 struct ScannerNotify *);
Prototype LibCall struct SyntaxChunk    *ParseLine   (__D0 ULONG, __A0 struct LineNode *, __D1 ULONG);
Prototype LibCall void                   UnparseLines(__A0 struct LineNode *, __D0 ULONG);
Prototype LibCall void                   ParseSection(__D0 ULONG, __A0 struct LineNode *, __D1 ULONG);

// private functions

Prototype struct SyntaxChunk *ParseString(UBYTE *, UWORD, ULONG);
Prototype struct SyntaxChunk *DupStack(struct SyntaxChunk *);

///

/// "Debugging stuff"
#if DEBUG
#define D(x) x
#define bug kputs

ULONG debugFH = NULL;
#include <clib/dos_protos.h>

#define DEBUG 0

void kputs(char *text)
{
   if (debugFH == NULL) {
       debugFH = Open("con:9999//200/700/Debug/AUTO/INACTIVE", 1006);
   }

   Write(debugFH, text, strlen(text));
}

void kint(int number)
{
   char *digits3 = "1234";
   digits3[0] = (number / 1000) % 10 + '0';
   digits3[1] = (number / 100) % 10 + '0';
   digits3[2] = (number / 10) % 10 + '0';
   digits3[3] = (number / 1) % 10 + '0';

   kputs(digits3);
}

#else
#define D(x)
#define bug
#endif


///
/// "Library functions"

/* ------------------------------- MountScanner --------------------------------

 Called by the editor before first usage of a scanner. Return a description of
 our abilities.

*/

LibCall struct ParserData *
MountScanner()
{
    static UBYTE version[] = "$VER: " PARSER_NAME " " PARSER_VERSION " (" __COMMODORE_DATE__ ")";

    static struct ParserData parserData;

    // syntax elements understood by parser

    static UBYTE *levelNames[] = {

        "Standard text",
        "Comment",
        "String",
        "Bracket",
        "Syntax error",
        NULL
    };

    static UBYTE *example[] = {
        "; Simple Installer example.    ",
        "                               ",
        "(procedure P-hello             ",
        "(                              ",
        "   (message \"hello\")           ",
        "))                             ",
        "                               ",
        "(set #sepp \"missing quote)     ",
        "                               ",
        "(P-hello)                      ",
        "(exit)                         ",
        NULL
    };

    // color suggestions

    static ULONG levelColors[] = {

        MAKE_RGB4(0,  0,   0), /* SYNTAX_TEXT */
        MAKE_RGB4(0,  0,   0), /* SYNTAX_COMMENT */
        MAKE_RGB4(0,  0,   0), /* SYNTAX_STRING */
        MAKE_RGB4(15, 15, 15), /* SYNTAX_BRACKET */
        MAKE_RGB4(0,  0,   0), /* SYNTAX_ERROR */
    };

    parserData.pd_Release  = SCANLIBVERSION;
    parserData.pd_Version  = PARSER_VERSION_ID;
    parserData.pd_Serial   = 0;
    parserData.pd_Info     = PARSER_NAME " " PARSER_VERSION;
    parserData.pd_Levels   = (sizeof(levelNames) / sizeof(UBYTE *)) - 1;
    parserData.pd_Names    = levelNames;
    parserData.pd_Colors   = levelColors;
    parserData.pd_Flags    = SCPRF_SYNTAXCACHE;
    parserData.pd_Example  = example;

    return(&parserData);
}



/* ------------------------------- StartScanner --------------------------------

 Called by the editor after a new text buffer has been created. We allocate a
 buffer to hold text-specific data. The buffer address is returned as handle.

*/

LibCall ULONG
StartScanner(__A0 struct GlobalConfig *globalConfigPtr, __A1 struct EditConfig *editConfigPtr, __D0 struct SyntaxChunk *syntaxStack)
{
    struct BufferHandle *handle;

    if (handle = AllocVec(sizeof(struct BufferHandle), MEMF_PUBLIC | MEMF_CLEAR)) {

        handle->bh_GlobalConfig = globalConfigPtr;
        handle->bh_EditConfig   = editConfigPtr;
        handle->bh_SyntaxStack  = syntaxStack;
    }

    return((ULONG)handle);
}


/* ------------------------------- CloseScanner --------------------------------

 Called by the editor if a text buffer is about to be closed. Deallocate buffer
 specific 'global' data.

*/

LibCall ULONG
CloseScanner(__D0 ULONG scanID)
{
    if (scanID) {

        struct BufferHandle *handle = (struct BufferHandle *)scanID;

        FreeVec(handle);
    }

    return(0);
}


/* ------------------------------- FlushScanner --------------------------------

 Called by the editor in low memory situations: we are supposed to free as much
 memory as possible.

*/

LibCall void
FlushScanner(__D0 ULONG scanID)
{
    struct BufferHandle *handle;
    struct EditConfig   *config;
    struct LineNode     *lineNode;

    handle = (struct BufferHandle *)scanID;
    config = handle->bh_EditConfig;

    if (lineNode = config->TextNodes)
        UnparseLines(lineNode, config->Lines);
}


/* ------------------------------- SetupScanner --------------------------------

 Called by the editor if the user wants to change the scanner's configuration.
 We do not support user configuration (parserData.pd_Flags: SCPRF_CONFIGWIN flag
 unset).

*/

LibCall void
SetupScanner(__A0 globalConfigPtr)
{
    ;
}


/* ------------------------------- BriefScanner --------------------------------

 Called to notify a context scanner if lines have been added, deleted or
 modified. We aren't a context scanner (parserData.pd_Flags: SCPRF_CONTEXT
 flag unset), so we won't ever have to request additional display requests:
 the editor's built-in refresh of damage regions is sufficient.

*/

LibCall struct RefreshRequest *
BriefScanner(__D0 ULONG scanID, __A0 struct ScannerNotify *notify)
{
    return(NULL);
}


/* --------------------------------- ParseLine ---------------------------------

 Parse a line, build a syntax description

*/

LibCall struct SyntaxChunk *
ParseLine(__D0 ULONG scanID, __A0 struct LineNode *lineNode, __D1 ULONG line)
{
    if (IS_FOLD(lineNode))

        return(NULL);

    else if (lineNode->Len) {

        // line not yet parsed ?

        if (lineNode->UserData == NULL) {

            struct SyntaxChunk *syntaxStack = ParseString(lineNode->Text, lineNode->Len, scanID);

            if (syntaxStack == EMPTY_STACK)
                lineNode->UserData = EMPTY_STACK;
            else
                lineNode->UserData = DupStack(syntaxStack);
        }

        if (lineNode->UserData == EMPTY_STACK)
            return((struct SyntaxChunk *)NULL);
        else
            return((struct SyntaxChunk *)lineNode->UserData);
    }
    else
        return(NULL);
}

/* -------------------------------- UnparseLines -------------------------------

 Called by the editor if lines are to be deleted. We are supposed to free
 private data attached to the lines.

*/

LibCall void
UnparseLines(__A0  struct LineNode *lineNode, __D0 ULONG lines)
{
    while (lines--) {

        // free syntax cache

        if (lineNode->UserData) {

            if (lineNode->UserData != (APTR)EMPTY_STACK)
                FreeVec((APTR)lineNode->UserData);

            lineNode->UserData = NULL;
        }

        // free folded subblock

        if (IS_FOLD(lineNode)) {

            struct Fold *fold = (struct Fold *)lineNode->SpecialInfo;

            UnparseLines(fold->TextNodes, fold->Lines);
        }

        ++lineNode;
    }
}

/* -------------------------------- ParseSection -------------------------------

 Called by the editor if lines are to be displayed. The scanner is encouraged to
 preparse the lines.

*/

LibCall void
ParseSection(__D0 ULONG scanID, __A0  struct LineNode *lineNode, __D1 ULONG lines)
{
    while (lines--) {

        // fold headers have to be ignored

        if (IS_FOLD(lineNode) == FALSE) {

            // line not yet parsed ?

            if (lineNode->Len)
                if (lineNode->UserData == NULL)
                    lineNode->UserData = DupStack(ParseString(lineNode->Text, lineNode->Len, scanID));
        }

        ++lineNode;
    }
}

///
/// "private"

/* -------------------------------- ParseString --------------------------------

 Parse a string, build a syntax description. Return EMPTY_STACK in case there
 is nothing to highlight.

*/

struct SyntaxChunk *
ParseString(UBYTE *text, UWORD len, ULONG scanID)
{
#define ADD_ELEMENT(start,end,level)           \
{                                              \
    syntaxStack[element].sc_Start = (start);   \
    syntaxStack[element].sc_End   = (end);     \
    syntaxStack[element].sc_Level = (level);   \
    element += 1;                              \
    D(bug("    elem: "));                      \
    D(kint(start));                            \
    D(bug(" "));                               \
    D(kint(end));                              \
    D(bug(" "));                               \
    D(kint(level));                            \
    D(bug("\n"));                              \
}

    UBYTE *lineStart = text;
    UWORD lenStart = len;

    if (len) {

        struct SyntaxChunk *syntaxStack = ((struct BufferHandle *)scanID)->bh_SyntaxStack;

        UWORD  element, indent;
        UWORD  stringStart, stringEnd;
        UBYTE  inString = 0;
        // if in name, this is the first character of the name, otherwise 0
        BOOL   inComment = FALSE;
        BOOL   afterBackslash = FALSE;

        // leading and trailing spaces have to be ignored

        for (indent = 0; (len && (*text == 32)); --len, ++indent)
            ++text;

        while (len && (text[len - 1] == 32))
            --len;

        // no syntax elements found so far

        element = 0;

        for (inComment = FALSE; len >= 1; ++text, ++indent, --len) {

            if (inString) {

                if (afterBackslash) {

                    // skip escaped character
                    afterBackslash = FALSE;

                }
                else if (*text == inString) {

                    // end of string detected

                    D(bug("  String end: "));

                    stringEnd = indent - 1;
                    ADD_ELEMENT(stringStart, stringEnd, SYNTAX_STRING);
                    inString = 0;
                    afterBackslash = FALSE;

                    D(kint(stringStart));
                    D(bug(" - "));
                    D(kint(stringEnd));
                    D(bug("\n"));

                }
                else if (*text == '\\') {

                    afterBackslash = TRUE;
                }
            }
            else if (*text == 34) {

                D(bug("  String start\n"));

                // start of string detected
                inString = *text;
                afterBackslash = FALSE;
                stringStart = indent + 1;

            }
            else if (*text == ';') {

                // comment until end of line
                ADD_ELEMENT(indent, indent + len - 1, SYNTAX_COMMENT);
                break;
            }
            else if ((*text == '(') || (*text == ')')){

                // bracket
                ADD_ELEMENT(indent, indent, SYNTAX_BRACKET);
            }
        }

        // go back one char so the last char of the line is addressed
        text -= 1; indent -= 1; len += 1;

        // unterminated string or name? highlight rest of line

        if (inString) {

            stringEnd = indent + len - 1;

            ADD_ELEMENT(stringStart, stringEnd, SYNTAX_BAD);
        }

        if (element) {
            // terminate syntax stack
            ADD_ELEMENT(FALSE, FALSE, FALSE);
            return(syntaxStack);
        }
        else
            return(EMPTY_STACK);

    }
    else
        return(EMPTY_STACK);
}

/* --------------------------------- DupStack ----------------------------------

 Duplicate syntax stack (to be FreeVec'ed). Return NULL in case of failure.

*/

struct SyntaxChunk *
DupStack(syntaxStack)

struct SyntaxChunk *syntaxStack;
{
    if (syntaxStack && (syntaxStack != EMPTY_STACK)) {

        struct SyntaxChunk *chunk;
        UWORD               elements;

        // determine stack size

        for (elements = 0, chunk = syntaxStack; chunk->sc_Level; ++chunk)
            ++elements;

        // create copy of syntax stack (to be attached to a text line by the caller)

        if (elements) {

            ULONG size = (++elements) * sizeof(struct SyntaxChunk);

            chunk = syntaxStack;

            if (syntaxStack = AllocVec(size, MEMF_PUBLIC))
                movmem(chunk, syntaxStack, size);
        }
        else
            syntaxStack = EMPTY_STACK;
    }

    return(syntaxStack);
}


///
