/* -----------------------------------------------------------------------------

 TeX scanner ©1995 Markus Aretz

 GoldED syntax parser (uses a syntax cache). Dice:

 DMAKE

 -------------------------------------------------------------------------------

*/

#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 SyntaxInfo     *bh_SyntaxInfo;            // parser output
    struct RefreshRequest  bh_RefreshRequest;        // display refresh request
};

// known TeX keywords are described by syntax structures:

struct Keyword {

    UBYTE *Pattern;                                  // known pattern (e.g. "UWORD")
    UBYTE *Next;                                     // valid range for next non-space char  (or NULL)
    UBYTE *Required;                                 // this char has to be on the same line (or NULL)
    UWORD  Level;                                    // syntax level
};

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

// recognized syntax levels

#define SYNTAX_COMMENT          1
#define SYNTAX_ENVIRONMENT      2
#define SYNTAX_RESERVED         3
#define SYNTAX_UNKNWNCMDS       4
#define SYNTAX_NUMBER           5
#define SYNTAX_TEXTFORMULA      6
#define SYNTAX_FUNCTION         7
#define SYNTAX_OPERATOR         8
#define SYNTAX_BRACKET          9
#define SYNTAX_ARROW            10
#define SYNTAX_FONTATTR         11
#define SYNTAX_DEFINE           12
#define SYNTAX_DECLARE          13


#define UPPER(a) ((a) & 95)                          // simple uppercase conversion

// syntax infos (or EMPTY_STACK) are attached to parsed lines

struct SyntaxInfo {

    UWORD              Flags;                        // comment flags
    struct SyntaxChunk SyntaxChunk;                  // syntax chunks
};

// these flag bits describe the line's comment properties

#define COMMENT_NONE      (0)                        // no comment in this line
#define COMMENT_SIMPLE    (1L<<0)                    // there is a comment in this line
#define COMMENT_START     (1L<<1)                    // line is start of multi-line comment
#define COMMENT_BODY      (1L<<2)                    // line is body  of multi-line comment
#define COMMENT_END       (1L<<3)                    // line is end   of multi-line comment

// macros to access SyntaxInfo members

#define GET_FLAGS(node)    (((struct SyntaxInfo *)(node)->UserData)->Flags)
#define GET_CHUNK(node)   (&((struct SyntaxInfo *)(node)->UserData)->SyntaxChunk)

///
/// "Globals"

struct Keyword *Hash      [256];
BOOL            IsOperator[256];
BOOL            IsLetter  [256];

struct Keyword Reserved[] = {

    //  known_word next_char  on_same_line level

    // uppercase


    // lowercase

    { "\\addtocounter",     NULL,       NULL,      SYNTAX_DECLARE },
    { "\\addtolength",      NULL,       NULL,      SYNTAX_DEFINE  },
    { "\\arccos",           NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\arcsin",           NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\arctan",           NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\arg",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\author",           "{",        NULL,      SYNTAX_DEFINE  },
    { "\\begin",            "{",        NULL,      SYNTAX_RESERVED},
    { "\\bf",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\bibitem",          "{[",       NULL,      SYNTAX_RESERVED},
    { "\\bigskip",          NULL,       NULL,      SYNTAX_RESERVED},
    { "\\bigskipamount",    NULL,       NULL,      SYNTAX_RESERVED},
    { "\\cal",              NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\caption",          "{[",       NULL,      SYNTAX_RESERVED},
    { "\\cdot",             NULL,       NULL,      SYNTAX_RESERVED},
    { "\\cdots",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\centerline",       "{",        NULL,      SYNTAX_RESERVED},
    { "\\chapter",          "*{[",      "{",       SYNTAX_RESERVED},
    { "\\cos",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\cosh",             NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\cot",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\coth",             NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\csc",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\date",             "{",        NULL,      SYNTAX_DEFINE  },
    { "\\def",              NULL,       NULL,      SYNTAX_RESERVED},
    { "\\deg",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\det",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\dim",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\documentclass",    "{[",       NULL,      SYNTAX_DECLARE },
    { "\\documentstyle",    "{[",       NULL,      SYNTAX_DECLARE },
    { "\\em",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\end",              "{",        NULL,      SYNTAX_RESERVED},
    { "\\evensidemargin",   NULL,       NULL,      SYNTAX_RESERVED},
    { "\\exp",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\footnote",         "{[",       "{",       SYNTAX_RESERVED},
    { "\\footnotemark",     "{[",       "{",       SYNTAX_RESERVED},
    { "\\footnotesize",     NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\frac",             "{",        "}",       SYNTAX_RESERVED},
    { "\\gcd",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\hfill",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\hline",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\hom",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\huge",             NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\Huge",             NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\hyphenation",      "{",        NULL,      SYNTAX_RESERVED},
    { "\\inf",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\include",          "{",        NULL,      SYNTAX_RESERVED},
    { "\\includeonly",      "{",        NULL,      SYNTAX_RESERVED},
    { "\\input",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\it",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\item",             NULL,       NULL,      SYNTAX_RESERVED},
    { "\\itemindent",       NULL,       NULL,      SYNTAX_DECLARE },
    { "\\itemsep",          NULL,       NULL,      SYNTAX_DECLARE },
    { "\\ker",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\label",            "{",        NULL,      SYNTAX_RESERVED},
    { "\\LARGE",            NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\large",            NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\Large",            NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\ldots",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\lg",               NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\lim",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\liminf",           NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\limsup",           NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\ln",               NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\log",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\makeglossary",     NULL,       NULL,      SYNTAX_RESERVED},
    { "\\makeindex",        NULL,       NULL,      SYNTAX_RESERVED},
    { "\\makelabel",        NULL,       NULL,      SYNTAX_RESERVED},
    { "\\makelabels",       NULL,       NULL,      SYNTAX_RESERVED},
    { "\\maketitle",        NULL,       NULL,      SYNTAX_RESERVED},
    { "\\max",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\mbox",             "{",        NULL,      SYNTAX_RESERVED},
    { "\\min",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\mit",              NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\newcommand",       "{",        "\\",      SYNTAX_DEFINE  },
    { "\\newcounter",       "{",        NULL,      SYNTAX_DECLARE },
    { "\\newpage",          NULL,       NULL,      SYNTAX_RESERVED},
    { "\\normalsize",       NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\oddsidemargin",    NULL,       NULL,      SYNTAX_RESERVED},
    { "\\pagebreak",        NULL,       NULL,      SYNTAX_RESERVED},
    { "\\pagenumbering",    "{",        NULL,      SYNTAX_DECLARE },
    { "\\pageref",          "{",        NULL,      SYNTAX_DECLARE },
    { "\\pagestyle",        "{",        NULL,      SYNTAX_DECLARE },
    { "\\par",              NULL,       NULL,      SYNTAX_RESERVED},
    { "\\paragraph",        "*{[",      "{",       SYNTAX_RESERVED},
    { "\\parbox",           "[{",       NULL,      SYNTAX_RESERVED},
    { "\\parindent",        NULL,       NULL,      SYNTAX_DECLARE },
    { "\\Pr",               NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\ref",              "{",        NULL,      SYNTAX_RESERVED},
    { "\\renewcommand",     "{",        "\\",      SYNTAX_DEFINE  },
    { "\\rm",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\sc",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\scriptsize",       NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\sec",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\section",          "[{",       "{",       SYNTAX_RESERVED},
    { "\\setcounter",       NULL,       NULL,      SYNTAX_DEFINE  },
    { "\\setlength",        NULL,       NULL,      SYNTAX_DEFINE  },
    { "\\settowidth",       NULL,       NULL,      SYNTAX_DEFINE  },
    { "\\sf",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\sin",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\sinh",             NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\sl",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\small",            NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\subsection",       "[{",       "{",       SYNTAX_RESERVED},
    { "\\subsubsection",    "[{",       "{",       SYNTAX_RESERVED},
    { "\\sup",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\tan",              NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\tanh",             NULL,       NULL,      SYNTAX_FUNCTION},
    { "\\textheight",       NULL,       NULL,      SYNTAX_RESERVED},
    { "\\textwidth",        NULL,       NULL,      SYNTAX_RESERVED},
    { "\\tiny",             NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\title",            "{",        NULL,      SYNTAX_DEFINE  },
    { "\\tt",               NULL,       NULL,      SYNTAX_FONTATTR},
    { "\\typein",           "{[",       NULL,      SYNTAX_RESERVED},
    { "\\typeout",          "{",        NULL,      SYNTAX_RESERVED},
    { "\\unitlength",       NULL,       NULL,      SYNTAX_RESERVED},
    { "\\usepackage",       "{[",       NULL,      SYNTAX_DECLARE},
    { "\\vdots",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\vline",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\voffset",          NULL,       NULL,      SYNTAX_RESERVED},
    { "\\vskip",            NULL,       NULL,      SYNTAX_RESERVED},
    { "\\vspace",           "*{",       NULL,      SYNTAX_RESERVED},
    { "abstract",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "appendix",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "array",              "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "center",             "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "description",        "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "displaymath",        "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "document",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "empty",              "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "enumerate",          "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "eqnarray",           "*}",       "}",       SYNTAX_ENVIRONMENT},
    { "equation",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "figure",             "*}",       "}",       SYNTAX_ENVIRONMENT},
    { "flushleft",          "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "flushright",         "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "fussypar",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "headings",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "itemize",            "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "letter",             "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "list",               "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "math",               "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "minipage",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "myheadings",         "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "note",               "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "plain",              "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "picture",            "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "quotation",          "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "quote",              "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "samepage",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "sloppypar",          "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "tabbing",            "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "table",              "*}",       "}",       SYNTAX_ENVIRONMENT},
    { "tabular",            "*}",       "}",       SYNTAX_ENVIRONMENT},
    { "thebibliography",    "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "theindex",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "theorem",            "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "titlepage",          "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "trivlist",           "}",        NULL,      SYNTAX_ENVIRONMENT},
    { "verbatim"            ,"}*",      "}",       SYNTAX_ENVIRONMENT},
    {  NULL,                NULL,       NULL,      0}
};

///
/// "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 void                           PrepareHash(void);
Prototype struct SyntaxChunk            *ParseString(UBYTE *, UWORD, ULONG);
Prototype struct SyntaxInfo             *DupInfo(struct SyntaxInfo *);
Prototype struct Keyword                *IsAReservedWord(UBYTE *, UWORD);
Prototype struct Keyword                *IsAReservedOperator(UBYTE *, UWORD);
Prototype BOOL                           strnchr(UBYTE *, UWORD, UWORD);
Prototype BOOL                           CheckEnvironment(UBYTE *, UWORD, UWORD, struct Keyword *);
Prototype struct LineNode               *FindContextStart(ULONG, struct LineNode *);
Prototype void                           ParseContext(struct LineNode *, ULONG, ULONG);
Prototype struct SyntaxInfo             *ParseContextLine(UBYTE *, UWORD, ULONG, ULONG);
Prototype BOOL                           BadSuccessor(ULONG, ULONG);
Prototype struct RefreshRequest         *VerifyContext(ULONG, ULONG, ULONG);

///
/// "Library functions"

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

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

*/

LibCall struct ParserData *
MountScanner()
{
    static struct ParserData parserData;

    // syntax elements understood by parser

    static UBYTE *levelNames[] = {

        "Standard Text",
        "TeX - Kommentar",
        "Umgebung",
        "Reservierter Befehl",
        "unbekannter Befehl",
        "Zahl",
        "Textformel",
        "Funktion",
        "Double operators",
        "{}",
        "Pfeil- und Zeigersymbole",
        "Schriftarten",
        "Definition",
        "Erklärung",

        NULL
    };

    // color suggestions

    static ULONG *levelColors[] = {

        MAKE_RGB4(0,  0,   0),                  // black
        MAKE_RGB4(15, 15,  0),                  //
        MAKE_RGB4(0,  15, 15),                  //
        MAKE_RGB4(15,  0,  0),                  // red
        MAKE_RGB4(0,  15,  0),                  // green
        MAKE_RGB4(0,   0, 15),                  // blue
        MAKE_RGB4(15, 15, 15),                  // white
        MAKE_RGB4(15,  0, 15),                  //
        MAKE_RGB4(0,   0,  0),                  // black
        MAKE_RGB4(0,   0,  0),                  // black
        MAKE_RGB4(15, 15, 15),                  // white
        MAKE_RGB4(15, 15, 15),                  // white
        MAKE_RGB4(15, 15, 15),                  // white
        MAKE_RGB4(15, 15, 15),                  // white
    };

    parserData.pd_Release  = SCANLIBVERSION;
    parserData.pd_Version  = 1;
    parserData.pd_Serial   = 0;
    parserData.pd_Info     = "Warp LaTeX 0.3. ©'95 M.Aretz";
    parserData.pd_Levels   = 14;
    parserData.pd_Names    = levelNames;
    parserData.pd_Colors   = levelColors;
    parserData.pd_Flags    = SCPRF_SYNTAXCACHE | SCPRF_CONTEXT;
    parserData.pd_Reserved = 0;

    PrepareHash();

    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_SyntaxInfo   = (struct SyntaxInfo *)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)
{
    return();
}


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

 Called to notify a context scanner if lines have been added, deleted or
 modified. We are supposed to return an additional display request or NULL
 (the editor will refresh the damaged region only if we return NULL). Damaged
 lines have already been unparsed by the editor. This is what we have to do:

 a) lines have been deleted or modified

    Check whether the syntax scheme of the remaining lines is still valid (it
    will be invalid if say a comment start has been deleted but the comment
    end has not been deleted). Reparse the lines and return a refresh request
    if not.

 b) lines have been inserted

    Check whether the syntax sheme of the new lines affects the existing lines.
    Reparse all lines starting at the insertion point and return a refresh
    request if not.

 c) lines have been (un)folded

    This scanner assumes that folding doesn't affect syntax highlighting for the
    sake of simplicity. This isn't necessarily a valid assumption: we will run
    into touble if the user folds parts of a comment only (e.g. the first line).

*/

LibCall struct RefreshRequest *
BriefScanner(__D0 ULONG scanID, __A0 struct ScannerNotify *notify)
{
    struct EditConfig *config = ((struct BufferHandle *)scanID)->bh_EditConfig;

    switch (notify->sn_Class) {

        case SCANNER_NOTIFY_MODIFIED:

            ULONG line = notify->sn_Line;

            if (notify->sn_Removed > notify->sn_Lines) {

                // lines have been deleted

                return(VerifyContext(scanID, line, 0));
            }
            else if (notify->sn_Lines)

                // lines have been modified or inserted

                return(VerifyContext(scanID, line, notify->sn_Lines));
            else
                return(NULL);

            break;

        default:

            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 (lineNode->Fold)

        return(NULL);

    else if (lineNode->Len) {

        // not yet preparsed ? preparse a couple of lines

        if (lineNode->UserData == EMPTY_STACK)

            return(NULL);

        else if (lineNode->UserData)

            return(GET_CHUNK(lineNode));

        else {

            ULONG lines = ((struct BufferHandle *)scanID)->bh_EditConfig->Lines - line + 1;

            if (lines > 50)
                lines = 50;

            ParseSection(scanID, lineNode, lines);

            if (lineNode->UserData == EMPTY_STACK)
                return(NULL);
            else
                return(GET_CHUNK(lineNode));
        }
    }
    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(lineNode->UserData);

            lineNode->UserData = NULL;
        }

        // free folded subblock

        if (lineNode->Fold)
            UnparseLines(lineNode->Fold->TextNodes, lineNode->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)
{
    struct LineNode *firstNode;

    if (firstNode = FindContextStart(scanID, lineNode))
        ParseContext(firstNode, lines + (lineNode - firstNode), scanID);
}

///
/// "private"

/* -------------------------------- PrepareHash --------------------------------

 Prepare reserved keyword hashtable (used to find keyword description if ascii
 value of first letter is known) and prepare IsOperator array.

*/

void
PrepareHash()
{
    struct Keyword *keyword;
    UWORD           ascii;

    memset(Hash, 0, sizeof(Hash));

    keyword = Reserved;

    while (keyword->Pattern) {

        UWORD ascii = *keyword->Pattern;

        Hash[ascii] = keyword;

        while (keyword->Pattern && (*keyword->Pattern == ascii))
            ++keyword;
    }

    memset(IsOperator, FALSE, sizeof(IsOperator));

    IsOperator['#'] = TRUE;
    IsOperator['&'] = TRUE;
    IsOperator['_'] = TRUE;
    IsOperator['{'] = TRUE;
    IsOperator['}'] = TRUE;
    IsOperator['-'] = TRUE;
    IsOperator['+'] = TRUE;
    IsOperator['/'] = TRUE;
    IsOperator['*'] = TRUE;

    memset(IsLetter, FALSE, sizeof(IsLetter));

    for (ascii = 0; ascii <= 255; ++ascii)
        IsLetter[ascii] = ((ascii >= 'A') && (ascii <= 'Z')) || ((ascii >= 'a') && (ascii <= 'z')) || ascii == '\\';
}




/* ------------------------------ IsAReservedWord ------------------------------

 Decide whether word of length <len> is a reserved LaTeX keyword

*/

struct Keyword *
IsAReservedWord(text, len)

UBYTE *text;
UWORD  len;
{
    struct Keyword *keyword;

    if (keyword = Hash[*text]) {

        while (keyword->Pattern) {

            if (*keyword->Pattern > *text)

                break;

            else if (keyword->Pattern[len] || (keyword->Pattern[1] != text[1]) || memcmp(text, keyword->Pattern, len))

                ++keyword;
            else
                return(keyword);
        }

        return(NULL);
    }
    else
        return(NULL);
}


/* ------------------------------ IsAReservedOperator --------------------------

 Decide whether word of length <len> is a reserved C 'double' operator

*/

struct Keyword *
IsAReservedOperator(text, len)

UBYTE *text;
UWORD  len;
{
    static struct Keyword reserved[] = {

    //  known word    next char   on same line

        { "\#",         NULL,        NULL,      8} ,
        { "\$",         NULL,        NULL,      8} ,
        { "\&",         NULL,        NULL,      8} ,
        { "\_",         NULL,        NULL,      8} ,
        { "\{",         NULL,        NULL,      8} ,
        { "\}",         NULL,        NULL,      8} ,
        { "--",         NULL,        NULL,      8} ,
        { "---",         NULL,        NULL,      8} ,
        { "->",         NULL,        NULL,     10} ,
        { "/=",         NULL,        NULL,      8} ,
        { "::",         NULL,        NULL,      8} ,
        { "<<",         NULL,        NULL,      8} ,
        { "==",         NULL,        NULL,      8} ,
        { ">=",         NULL,        NULL,      8} ,
        { ">>",         NULL,        NULL,      8} ,
        { "|=",         NULL,        NULL,      8} ,
        {  NULL,        NULL,        NULL,      0}
    };

    struct Keyword *keyword;

    for (keyword = reserved; keyword->Pattern; ++keyword) {

        if (*keyword->Pattern > *text)

            break;

        else if ((*keyword->Pattern == *text) && (keyword->Pattern[1] == text[1]))

            return(keyword);
    }

    return(NULL);
}



/* ----------------------------- CheckEnvironment ------------------------------

 Check whether keyword environment complies with keyword.

*/

BOOL
CheckEnvironment(text, len, wordLen, keyword)

struct Keyword *keyword;
UBYTE          *text;
UWORD           len, wordLen;
{
    // move to first non-space character after recognized keyword

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

    // check whether first non-space character is valid

    if ((keyword->Next == NULL) || strchr(keyword->Next, *text)) {

        // check whether required character is used on the same line

        if ((keyword->Required == NULL) || strnchr(text, len, *keyword->Required)) {

            return(TRUE);
        }
        else
            return(FALSE);
    }
    else
        return(FALSE);
}


/* ---------------------------------- strnchr ----------------------------------

 strchr replacement (doesn't require 0-terminated string). Return TRUE if the
 character is found within <text>.

*/

BOOL
strnchr(text, len, character)

UBYTE *text;
UWORD  character, len;
{
    while (len--)
        if (*text++ == character)
            return(TRUE);

    return(FALSE);
}


/* --------------------------------- DupInfo -----------------------------------

 Duplicate syntax info (to be FreeVec'ed)

*/

struct SyntaxInfo *
DupInfo(syntaxInfo)

struct SyntaxInfo *syntaxInfo;
{
    struct SyntaxChunk *chunk;
    struct SyntaxInfo  *info;
    ULONG               size;
    UWORD               elements;

    // determine stack size

    for (elements = 0, chunk = &syntaxInfo->SyntaxChunk; chunk->sc_Level; ++chunk)
        ++elements;

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

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

    if (info = AllocVec(size + sizeof(UWORD), MEMF_PUBLIC)) {

        movmem(syntaxInfo, info, size);

        return(info);
    }
    else
        return(NULL);
}


/*  ----------------------------- FindContextStart ------------------------------

 Search backwards until a parsed line or a context free line (fold or start of
 file) is found. Return line node. This node may be used as starting point for
 parsing lines.

*/

struct LineNode *
FindContextStart(scanID, lineNode)

ULONG            scanID;
struct LineNode *lineNode;
{
    ULONG line = lineNode - ((struct BufferHandle *)scanID)->bh_EditConfig-> TextNodes;

    while (line--) {

        // found a fold or a parsed line ?

        if (lineNode->Fold || lineNode->UserData)
            break;

        --lineNode;
    }

    return(lineNode);
}


/* ------------------------------- ParseContext --------------------------------

 Preparse lines. The first line is expected to be either a context free line
 (start of file or a fold header) or to be a parsed line.

*/

void
ParseContext(lineNode, lines, scanID)

struct LineNode *lineNode;
ULONG            lines, scanID;
{
    ULONG mode;

    for (mode = COMMENT_NONE; lines--; ++lineNode) {

        if (lineNode->Fold)

            mode = COMMENT_NONE;

        else {

            if (lineNode->UserData == EMPTY_STACK)

                mode = COMMENT_NONE;

            else if (lineNode->UserData)

                mode = GET_FLAGS(lineNode);

            else {

                struct SyntaxInfo *syntaxInfo = ParseContextLine(lineNode->Text, lineNode->Len, mode, scanID);

                if (syntaxInfo == EMPTY_STACK) {

                    lineNode->UserData = EMPTY_STACK;

                    mode = COMMENT_NONE;
                }
                else if (syntaxInfo) {

                    lineNode->UserData = DupInfo(syntaxInfo);

                    mode = syntaxInfo->Flags;
                }
                else
                    mode = COMMENT_NONE;
            }
        }
    }
}


/* ----------------------------- ParseContextLine ------------------------------

 Parse a string, build a syntax description. Return EMPTY_STACK in case there is
 nothing to highlight. Check whether there are comments. The status of the last
 line is passed in by the caller, this function will return the status (flag
 bits) of the new line.

*/

struct SyntaxInfo *
ParseContextLine(text, len, last, scanID)

UBYTE *text;
UWORD  len;
ULONG  last, scanID;
{
    if (len) {

        struct SyntaxInfo  *syntaxInfo;
        struct SyntaxChunk *syntaxStack;
        struct Keyword     *keyword;

        BOOL   inString, inComment, anyText, innerComment;
        UBYTE *next;
        UWORD  indent, startString, startComment, element, lastChar, wordLen;
        ULONG  status;

        syntaxInfo = ((struct BufferHandle *)scanID)->bh_SyntaxInfo;

        // leading spaces have to be ignored

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

        // trailing spaces have to be ignored

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

        // preset comment status based on status of previous line

        if (last & (COMMENT_START | COMMENT_BODY)) {

            status    = COMMENT_BODY;
            inComment = TRUE;
        }
        else {

            status    = COMMENT_NONE;
            inComment = FALSE;
        }

        startComment = 0;
        innerComment = FALSE;

        inString = FALSE;
        anyText  = FALSE;
        lastChar = 32;

        syntaxStack = &syntaxInfo->SyntaxChunk;

        for (element = 0; len; ++text, ++indent, --len) {

            if (*text != 32) {

                if (inComment) {

                    // end of comment detected ?

                    if ((*text == '*') && (text[1] == '/') && (len > 1)) {

                        // end of a multi-line comment or end of inner line comment ?

                        if (innerComment)

                            status |= COMMENT_SIMPLE;

                        else {

                            status |=  COMMENT_END;
                            status &= ~COMMENT_BODY;
                        }

                        syntaxStack[element].sc_Level = SYNTAX_COMMENT;
                        syntaxStack[element].sc_Start = startComment;
                        syntaxStack[element].sc_End   = indent + 1;

                        ++element;

                        inComment = FALSE;

                        ++text;
                        ++indent;
                        --len;
                    }
                }
                else if ((*text == 36) && ((inString == FALSE) || (*(text - 1) != 92))) {

                    if (inString = !inString)

                        startString = indent;

                    else if (indent > startString) {

                        // end of string detected

                        syntaxStack[element].sc_Level = SYNTAX_TEXTFORMULA;
                        syntaxStack[element].sc_Start = startString;
                        syntaxStack[element].sc_End   = indent;

                        ++element;
                    }
                }
                else if (inString == FALSE) {

                    if (IsLetter[*text]) {

                        wordLen = 1;

                        while (IsLetter[text[wordLen]] && (wordLen < len) && (text[wordLen] != '\\'))
                            ++wordLen;

                        // reserved word detected ?

                        if (Hash[*text]) {

                            if ((lastChar != '\\')) {

                                if (keyword = IsAReservedWord(text, wordLen)) {

                                    // environment to be checked ?

                                    if (keyword->Next || keyword->Required) {

                                        if (CheckEnvironment(text, len, wordLen, keyword)) {

                                            syntaxStack[element].sc_Level = keyword->Level;
                                            syntaxStack[element].sc_Start = indent;
                                            syntaxStack[element].sc_End   = indent + wordLen - 1;

                                            ++element;
                                        }
                                    }
                                    else {

                                        syntaxStack[element].sc_Level = keyword->Level;
                                        syntaxStack[element].sc_Start = indent;
                                        syntaxStack[element].sc_End   = indent + wordLen - 1;

                                        ++element;
                                    }
                                }
                            }
                        }

                        // move to next section (consider end-of-loop action)

                        --wordLen;

                        text   += wordLen;
                        indent += wordLen;
                        len    -= wordLen;
                    }
                    else if ((*text >= '0') && (*text <= '9')) {

                        // number detected

                        BOOL isHex = (*text == '0') && (text[1] == 'x');

                        for (next = text + (wordLen = 1); (((*next <= '9') && (*next >= '0')) || (isHex && ((*next == 'x') || ((UPPER(*next) >= 'A') && (UPPER(*next) <= 'F'))))) && (wordLen < len); ++next)
                            ++wordLen;

                        // ignore numbers attached to letters (e.g. __A0)

                        if (IsLetter[lastChar] == FALSE) {

                            syntaxStack[element].sc_Level = SYNTAX_NUMBER;
                            syntaxStack[element].sc_Start = indent;
                            syntaxStack[element].sc_End   = indent + wordLen - 1;

                            ++element;
                        }

                        // move to next section (consider end-of-loop action)

                        --wordLen;

                        text   += wordLen;
                        indent += wordLen;
                        len    -= wordLen;
                    }
                    else if ((*text == '%') && (*(text - 1) != '\\')) {

                        // TeX comment detected (indicated by %)

                        syntaxStack[element].sc_Level = SYNTAX_COMMENT;
                        syntaxStack[element].sc_Start = indent;
                        syntaxStack[element].sc_End   = indent + len - 1;

                        ++element;

                        // terminate parsing (rest of line is a comment)

                        break;
                    }
                    else if ((*text == '/') && (text[1] == '*') && (len > 1)) {

                        // start of comment detected

                        inComment    = TRUE;
                        innerComment = TRUE;
                        startComment = indent;

                        ++text;
                        ++indent;
                        --len;
                    }
                    else if (IsOperator[*text]) {

                        wordLen = 1;

                        while (IsOperator[text[wordLen]] && (wordLen < len))
                            ++wordLen;

                        // reserved operator detected ?

                        if (keyword = IsAReservedOperator(text, wordLen)) {

                            syntaxStack[element].sc_Level = keyword->Level;
                            syntaxStack[element].sc_Start = indent;
                            syntaxStack[element].sc_End   = indent + wordLen - 1;

                            ++element;
                        }

                        // move to next section (consider end-of-loop action)

                        --wordLen;

                        text   += wordLen;
                        indent += wordLen;
                        len    -= wordLen;
                    }
                    else if ((*text == '{') || (*text == '}')) {

                        // bracket detected

                        syntaxStack[element].sc_Level = SYNTAX_BRACKET;
                        syntaxStack[element].sc_Start = indent;
                        syntaxStack[element].sc_End   = indent;

                        ++element;
                    }
                    else if (*text == '\\') {

                        // only spaces detected so far ?

                        if (anyText == FALSE) {

                            // any LaTeX command

                            wordLen = 1;

                            for (wordLen = 1; (wordLen < len) && (text[wordLen] != 32) && (text[wordLen] != '\\') && (text[wordLen] != '{') && (text[wordLen] != '['); ++wordLen)
                                if ((text[wordLen] == '/') && (wordLen + 1 < len) && (text[wordLen + 1] == '/'))
                                    break;

                            syntaxStack[element].sc_Level = SYNTAX_UNKNWNCMDS;
                            syntaxStack[element].sc_Start = indent;
                            syntaxStack[element].sc_End   = indent + wordLen - 1;

                            // move to end of reserved word (consider end-of-loop action)

                            --wordLen;

                            text   += wordLen;
                            indent += wordLen;
                            len    -= wordLen;

                            ++element;
                        }
                    }
                }

                anyText = TRUE;
            }

            lastChar = *text;
        }

        // comment not closed ?

        if (inComment) {

            // continuation of multi-line comment or new (inner line) comment ?

            if (innerComment)
                status |= COMMENT_START;
            else
                status |= COMMENT_BODY;

            syntaxStack[element].sc_Level = SYNTAX_COMMENT;
            syntaxStack[element].sc_Start = startComment;
            syntaxStack[element].sc_End   = indent - 1;

            ++element;
        }

        if (element) {

            // terminate syntax stack

            syntaxStack[element].sc_Start = FALSE;
            syntaxStack[element].sc_End   = FALSE;
            syntaxStack[element].sc_Level = FALSE;

            syntaxInfo->Flags = status;

            return(syntaxInfo);
        }
        else
            return(EMPTY_STACK);
    }
    else if (last) {

        if (last & (COMMENT_START | COMMENT_BODY)) {

            struct SyntaxInfo *syntaxInfo = ((struct BufferHandle *)scanID)->bh_SyntaxInfo;

            syntaxInfo->SyntaxChunk.sc_Start = FALSE;
            syntaxInfo->SyntaxChunk.sc_End   = FALSE;
            syntaxInfo->SyntaxChunk.sc_Level = FALSE;

            syntaxInfo->Flags = COMMENT_BODY;

            return(syntaxInfo);
        }
        else
            return(EMPTY_STACK);
    }
    else
        return(EMPTY_STACK);
}


/* ------------------------------- VerifyContext -------------------------------

 Ensure that syntax information of a specified range of lines ("damage region")
 is valid and consistent with the existing text. Return a refresh request if
 not. The damage area is expected to have been unparsed already. This is what we
 have to do: we preparse existing lines before the damage area if belonging to
 the syntax context of the damage area (ie. all lines affecting highlighting of
 the first line in the damage area). The damage area is parsed, too. Parsed
 lines after the damage area are reparsed if highlighting is found to be
 inconsistent with the line(s) before. Reparsing continues until the end of the
 file respectively until no more inconsistencies are found.

*/

struct RefreshRequest *
VerifyContext(scanID, line, lines)

ULONG scanID, line, lines;
{
    struct EditConfig *config;
    struct LineNode   *lineNode, *lastNode;
    struct SyntaxInfo *syntaxInfo;

    ULONG last, new, refreshStart, refresh = FALSE;

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

    lineNode = config->TextNodes + line;
    lastNode = config->TextNodes + line + lines - 1;

    // preparse from context start until end of damage area

    ParseSection(scanID, lineNode, lines);

    // get syntax flags of last line in damage area

    if (lastNode->Fold || (lastNode->UserData == EMPTY_STACK) || (lastNode->UserData == NULL))
        last = COMMENT_NONE;
    else
        last = GET_FLAGS(lastNode);

    // continue parsing until no more inconsistencies are found (until context end)

    refreshStart = (line += lines);

    for (lineNode = lastNode + 1; line < config->Lines; ++line, ++lineNode, ++refresh) {

        if (lineNode->Fold)

            // folds terminate parsing (context end)

            break;

        else if (lineNode->UserData == NULL) {

            // line not yet parsed

            syntaxInfo = ParseContextLine(lineNode->Text, lineNode->Len, last, scanID);

            if (syntaxInfo == EMPTY_STACK)

                lineNode->UserData = EMPTY_STACK;

            else if (syntaxInfo)

                lineNode->UserData = DupInfo(syntaxInfo);
        }
        else {

            // check whether highlighting of this line is consistent with previous line

            new = (lineNode->UserData == EMPTY_STACK) ? COMMENT_NONE : GET_FLAGS(lineNode);

            if (BadSuccessor(last, new)) {

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

                syntaxInfo = ParseContextLine(lineNode->Text, lineNode->Len, last, scanID);

                if (syntaxInfo == EMPTY_STACK)

                    lineNode->UserData = EMPTY_STACK;

                else if (syntaxInfo)

                    lineNode->UserData = DupInfo(syntaxInfo);
            }
            else
                break;
        }

        if (lineNode->Fold || (lineNode->UserData == EMPTY_STACK) || (lineNode->UserData == NULL))
            last = COMMENT_NONE;
        else
            last = GET_FLAGS(lineNode);
    }

    if (refresh) {

        struct RefreshRequest *refreshRequest = &((struct BufferHandle *)scanID)->bh_RefreshRequest;

        refreshRequest->rr_Line  = refreshStart;
        refreshRequest->rr_Lines = refresh;

        return(refreshRequest);
    }
    else
        return(NULL);
}


/* ------------------------------- BadSuccessor --------------------------------

 Return TRUE if syntax information of two adjacent lines is inconsistent.

*/

BOOL
BadSuccessor(pred, succ)

ULONG pred, succ;
{
    if (succ & (COMMENT_BODY | COMMENT_END)) {

        // comment body/end without start ?

        if ((pred & (COMMENT_START | COMMENT_BODY)) == FALSE)

            return(TRUE);
    }
    else if (pred & COMMENT_START) {

        // comment start without body/end ?

        if ((succ & (COMMENT_BODY | COMMENT_END)) == FALSE)

            return(TRUE);

        // comment inside comment ?

        if (succ & COMMENT_START)

            return(TRUE);
    }
    else if (pred & COMMENT_BODY) {

        // body line without end ?

        if ((succ & (COMMENT_BODY | COMMENT_END)) == FALSE)

            return(TRUE);
    }

    return(FALSE);
}             

///
