#include "defs.h"

///1 "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 LEVEL_TEXT          0
#define LEVEL_COMMENT       1
#define LEVEL_BRACKET       2
#define LEVEL_KEYWORD       3
#define LEVEL_TEXKEYWORD    4
#define LEVEL_UNKNOWNKEY    5
#define LEVELS              6
///2
///1 "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);

///2
///1 "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: Pink_Texinfo 1.1 (" __COMMODORE_DATE__ ")";

    static struct ParserData parserData;

    // syntax elements understood by parser

    static UBYTE *levelNames[] = {
        "plain text",
        "comments",
        "braces {}",
        "keyword",
        "TeX keywords",
        "unknown keyword",
        NULL
      };

    static UBYTE *example[] = {
        "@c 1996 by P!\\K/Un0X",
        "bstegema@ix.urz.uni-heidelberg.de",
        "",
        "@node GoldED syntax highlighter",
        "This is a syntax highlighter for",
        "Texinfo.@*",
        "",
        "@tex",
        "$$ \\sqrt{\\sum_{i = 1}^n x_i} $$",
        "@end tex",
        "",
        "@shit happens ;-)",
        "",
        NULL
    };

    // color suggestions

    static ULONG levelColors[] = {
        MAKE_RGB4(0,  0,  0),
        MAKE_RGB4(15, 15, 0),
        MAKE_RGB4(0,  8, 15),
        MAKE_RGB4(15, 8,  0),
        MAKE_RGB4(15, 15, 0),
        MAKE_RGB4(15, 15, 0),
    };

    parserData.pd_Release  = SCANLIBVERSION;
    parserData.pd_Version  = 1;
    parserData.pd_Serial   = 0;
    parserData.pd_Info     = "Pink_Texinfo 1.1";
    parserData.pd_Levels   = LEVELS;
    parserData.pd_Names    = levelNames;
    parserData.pd_Colors   = levelColors;
    parserData.pd_Flags    = 0;
    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. We don't use a syntax cache, so there is nothing to free.

*/

LibCall void
FlushScanner(__D0 ULONG scanID)
{
    ;
}


/* ------------------------------- 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)

        return(ParseString(lineNode->Text, lineNode->Len, scanID));
    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. This scanner doesn't attach private data to
 lines (context scanners usally do in order to implement a syntax cache), so
 there is nothing to be freed.

*/

LibCall void
UnparseLines(__A0  struct LineNode *lineNode, __D0 ULONG lines)
{
    ;
}

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

 Called by the editor if lines are to be displayed. The scanner is encouraged to
 preparse the lines. This scanner doesn't attach private data to lines (context
 scanners usally do in order to implement a syntax cache), so there is nothing
 to do.

*/

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

///2
///1 "private"
struct KeyWord {
    char *key;
    ULONG level;
  };

///1 "keywords"
struct KeyWord keywords[] = {
    { "@*", LEVEL_KEYWORD },
    { "@.", LEVEL_KEYWORD },
    { "@:", LEVEL_KEYWORD },
    { "@@", LEVEL_KEYWORD },
    { "@TeX", LEVEL_KEYWORD },
    { "@afourpaper", LEVEL_KEYWORD },
    { "@appendix", LEVEL_KEYWORD },
    { "@appendixsec", LEVEL_KEYWORD },
    { "@appendixsection", LEVEL_KEYWORD },
    { "@appendixsubsec", LEVEL_KEYWORD },
    { "@appendixsubsubsec", LEVEL_KEYWORD },
    { "@asis", LEVEL_KEYWORD },
    { "@author", LEVEL_KEYWORD },
    { "@b", LEVEL_KEYWORD },
    { "@br", LEVEL_KEYWORD },
    { "@bullet", LEVEL_KEYWORD },
    { "@bye", LEVEL_KEYWORD },
    { "@cartouche", LEVEL_KEYWORD },
    { "@center", LEVEL_KEYWORD },
    { "@chapheading", LEVEL_KEYWORD },
    { "@chapter", LEVEL_KEYWORD },
    { "@cindex", LEVEL_KEYWORD },
    { "@cite", LEVEL_KEYWORD },
    { "@clear", LEVEL_KEYWORD },
    { "@code", LEVEL_KEYWORD },
    { "@comment", LEVEL_KEYWORD },
    { "@contents", LEVEL_KEYWORD },
    { "@copyright", LEVEL_KEYWORD },
    { "@ctrl", LEVEL_KEYWORD },
    { "@defcodeindex", LEVEL_KEYWORD },
    { "@defcv", LEVEL_KEYWORD },
    { "@defcvx", LEVEL_KEYWORD },
    { "@deffn", LEVEL_KEYWORD },
    { "@deffnx", LEVEL_KEYWORD },
    { "@defindex", LEVEL_KEYWORD },
    { "@defivar", LEVEL_KEYWORD },
    { "@defivarx", LEVEL_KEYWORD },
    { "@defmac", LEVEL_KEYWORD },
    { "@defmacx", LEVEL_KEYWORD },
    { "@defmethod", LEVEL_KEYWORD },
    { "@defmethodx", LEVEL_KEYWORD },
    { "@defop", LEVEL_KEYWORD },
    { "@defopt", LEVEL_KEYWORD },
    { "@defoptx", LEVEL_KEYWORD },
    { "@defopx", LEVEL_KEYWORD },
    { "@defspec", LEVEL_KEYWORD },
    { "@defspecx", LEVEL_KEYWORD },
    { "@deftp", LEVEL_KEYWORD },
    { "@deftpx", LEVEL_KEYWORD },
    { "@deftypefn", LEVEL_KEYWORD },
    { "@deftypefnx", LEVEL_KEYWORD },
    { "@deftypefun", LEVEL_KEYWORD },
    { "@deftypefunx", LEVEL_KEYWORD },
    { "@deftypemethod", LEVEL_KEYWORD },
    { "@deftypemethodx", LEVEL_KEYWORD },
    { "@deftypevar", LEVEL_KEYWORD },
    { "@deftypevarx", LEVEL_KEYWORD },
    { "@deftypevr", LEVEL_KEYWORD },
    { "@deftypevrx", LEVEL_KEYWORD },
    { "@defun", LEVEL_KEYWORD },
    { "@defunx", LEVEL_KEYWORD },
    { "@defvar", LEVEL_KEYWORD },
    { "@defvarx", LEVEL_KEYWORD },
    { "@defvr", LEVEL_KEYWORD },
    { "@defvrx", LEVEL_KEYWORD },
    { "@dfn", LEVEL_KEYWORD },
    { "@display", LEVEL_KEYWORD },
    { "@dmn", LEVEL_KEYWORD },
    { "@dots", LEVEL_KEYWORD },
    { "@emph", LEVEL_KEYWORD },
    { "@end", LEVEL_KEYWORD },
    { "@enumerate", LEVEL_KEYWORD },
    { "@equiv", LEVEL_KEYWORD },
    { "@error", LEVEL_KEYWORD },
    { "@evenfooting", LEVEL_KEYWORD },
    { "@evenheading", LEVEL_KEYWORD },
    { "@example", LEVEL_KEYWORD },
    { "@exdent", LEVEL_KEYWORD },
    { "@expansion", LEVEL_KEYWORD },
    { "@file", LEVEL_KEYWORD },
    { "@finalout", LEVEL_KEYWORD },
    { "@findex", LEVEL_KEYWORD },
    { "@flushleft", LEVEL_KEYWORD },
    { "@flushright", LEVEL_KEYWORD },
    { "@footnote", LEVEL_KEYWORD },
    { "@footnotestyle", LEVEL_KEYWORD },
    { "@format", LEVEL_KEYWORD },
    { "@ftable", LEVEL_KEYWORD },
    { "@group", LEVEL_KEYWORD },
    { "@heading", LEVEL_KEYWORD },
    { "@headings", LEVEL_KEYWORD },
    { "@i", LEVEL_KEYWORD },
    { "@iappendix", LEVEL_KEYWORD },
    { "@iappendixsec", LEVEL_KEYWORD },
    { "@iappendixsection", LEVEL_KEYWORD },
    { "@iappendixsubsec", LEVEL_KEYWORD },
    { "@iappendixsubsubsec", LEVEL_KEYWORD },
    { "@ichapter", LEVEL_KEYWORD },
    { "@ifclear", LEVEL_KEYWORD },
    { "@ifinfo", LEVEL_KEYWORD },
    { "@ifset", LEVEL_KEYWORD },
    { "@iftex", LEVEL_KEYWORD },
    { "@ignore", LEVEL_KEYWORD },
    { "@include", LEVEL_KEYWORD },
    { "@infoappendix", LEVEL_KEYWORD },
    { "@infoappendixsec", LEVEL_KEYWORD },
    { "@infoappendixsubsec", LEVEL_KEYWORD },
    { "@infoappendixsubsubsec", LEVEL_KEYWORD },
    { "@infochapter", LEVEL_KEYWORD },
    { "@infoinclude", LEVEL_KEYWORD },
    { "@inforef", LEVEL_KEYWORD },
    { "@infosection", LEVEL_KEYWORD },
    { "@infosubsection", LEVEL_KEYWORD },
    { "@infosubsubsection", LEVEL_KEYWORD },
    { "@infotop", LEVEL_KEYWORD },
    { "@infounnumbered", LEVEL_KEYWORD },
    { "@infounnumberedsec", LEVEL_KEYWORD },
    { "@infounnumberedsubsec", LEVEL_KEYWORD },
    { "@infounnumberedsubsubsec", LEVEL_KEYWORD },
    { "@input", LEVEL_KEYWORD },
    { "@isection", LEVEL_KEYWORD },
    { "@isubsection", LEVEL_KEYWORD },
    { "@isubsubsection", LEVEL_KEYWORD },
    { "@item", LEVEL_KEYWORD },
    { "@itemize", LEVEL_KEYWORD },
    { "@itemx", LEVEL_KEYWORD },
    { "@iunnumbered", LEVEL_KEYWORD },
    { "@iunnumberedsec", LEVEL_KEYWORD },
    { "@iunnumberedsubsec", LEVEL_KEYWORD },
    { "@iunnumberedsubsubsec", LEVEL_KEYWORD },
    { "@kbd", LEVEL_KEYWORD },
    { "@key", LEVEL_KEYWORD },
    { "@kindex", LEVEL_KEYWORD },
    { "@lisp", LEVEL_KEYWORD },
    { "@lowersections", LEVEL_KEYWORD },
    { "@macro", LEVEL_KEYWORD },
    { "@majorheading", LEVEL_KEYWORD },
    { "@math", LEVEL_KEYWORD },
    { "@menu", LEVEL_KEYWORD },
    { "@minus", LEVEL_KEYWORD },
    { "@need", LEVEL_KEYWORD },
    { "@node", LEVEL_KEYWORD },
    { "@noindent", LEVEL_KEYWORD },
    { "@nwnode", LEVEL_KEYWORD },
    { "@oddfooting", LEVEL_KEYWORD },
    { "@oddheading", LEVEL_KEYWORD },
    { "@overfullrule", LEVEL_KEYWORD },
    { "@page", LEVEL_KEYWORD },
    { "@paragraphindent", LEVEL_KEYWORD },
    { "@pindex", LEVEL_KEYWORD },
    { "@point", LEVEL_KEYWORD },
    { "@print", LEVEL_KEYWORD },
    { "@printindex", LEVEL_KEYWORD },
    { "@pxref", LEVEL_KEYWORD },
    { "@quotation", LEVEL_KEYWORD },
    { "@r", LEVEL_KEYWORD },
    { "@raisesections", LEVEL_KEYWORD },
    { "@ref", LEVEL_KEYWORD },
    { "@refill", LEVEL_KEYWORD },
    { "@result", LEVEL_KEYWORD },
    { "@samp", LEVEL_KEYWORD },
    { "@sc", LEVEL_KEYWORD },
    { "@section", LEVEL_KEYWORD },
    { "@set", LEVEL_KEYWORD },
    { "@setchapternewpage", LEVEL_KEYWORD },
    { "@setchapterstyle", LEVEL_KEYWORD },
    { "@setfilename", LEVEL_KEYWORD },
    { "@settitle", LEVEL_KEYWORD },
    { "@shortcontents", LEVEL_KEYWORD },
    { "@shorttitlepage", LEVEL_KEYWORD },
    { "@smallbook", LEVEL_KEYWORD },
    { "@smallexample", LEVEL_KEYWORD },
    { "@smalllisp", LEVEL_KEYWORD },
    { "@sp", LEVEL_KEYWORD },
    { "@strong", LEVEL_KEYWORD },
    { "@subheading", LEVEL_KEYWORD },
    { "@subsection", LEVEL_KEYWORD },
    { "@subsubheading", LEVEL_KEYWORD },
    { "@subsubsection", LEVEL_KEYWORD },
    { "@subtitle", LEVEL_KEYWORD },
    { "@summarycontents", LEVEL_KEYWORD },
    { "@syncodeindex", LEVEL_KEYWORD },
    { "@synindex", LEVEL_KEYWORD },
    { "@t", LEVEL_KEYWORD },
    { "@table", LEVEL_KEYWORD },
    { "@tex", LEVEL_KEYWORD },
    { "@thischaptername", LEVEL_KEYWORD },
    { "@thispage", LEVEL_KEYWORD },
    { "@tindex", LEVEL_KEYWORD },
    { "@title", LEVEL_KEYWORD },
    { "@titlefont", LEVEL_KEYWORD },
    { "@titlepage", LEVEL_KEYWORD },
    { "@titlespec", LEVEL_KEYWORD },
    { "@today", LEVEL_KEYWORD },
    { "@top", LEVEL_KEYWORD },
    { "@unmacro", LEVEL_KEYWORD },
    { "@unnumbered", LEVEL_KEYWORD },
    { "@unnumberedsec", LEVEL_KEYWORD },
    { "@unnumberedsubsec", LEVEL_KEYWORD },
    { "@unnumberedsubsubsec", LEVEL_KEYWORD },
    { "@value", LEVEL_KEYWORD },
    { "@var", LEVEL_KEYWORD },
    { "@vindex", LEVEL_KEYWORD },
    { "@vskip", LEVEL_KEYWORD },
    { "@vtable", LEVEL_KEYWORD },
    { "@w", LEVEL_KEYWORD },
    { "@xref", LEVEL_KEYWORD }
    { "@{", LEVEL_KEYWORD },
    { "@|", LEVEL_KEYWORD },
    { "@}", LEVEL_KEYWORD },
  };
///2

#define ARRAYSIZE   256

UWORD GetLevel(char *key, UWORD len) {
    char buf[32];
    strncpy(buf, key, (len > 31) ? 31 : len);
    buf[(len > 31) ? 31 : len] = 0;

    WORD offset = ARRAYSIZE / 2;
    WORD skip = ARRAYSIZE / 2;

    struct KeyWord *mid;
    WORD c;
    while ( skip > 0 ) {
        if ( offset < sizeof(keywords) / sizeof(struct KeyWord) ) {
            mid = &keywords[offset];
            c = strcmp(mid->key, buf);
          }
        else {
            c = 1;
          }

        if ( c == 0 )
            return(mid->level);
        else if ( c > 0 ) {
            offset -= skip;
            if ( offset < 0 )
                offset = 0;
          }
        else {
            offset += skip;
          }

        skip = skip >> 1;
      }

    if ( offset < sizeof(keywords) / sizeof(struct KeyWord) ) {
        mid = &keywords[offset];
        c = strcmp(mid->key, buf);
      }
    else {
        c = 1;
      }

    if ( c == 0 )
        return(mid->level);

    if ( *key == '@' )
        return(LEVEL_UNKNOWNKEY);
    else
        return(LEVEL_TEXT);
  }

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

 Parse a string, build a syntax description. This is a simple example only:
 C++-Comments (// ....) are highlighted. Return EMPTY_STACK in case there is
 nothing to highlight.

*/

struct SyntaxChunk *ParseString(UBYTE *text, UWORD len, ULONG scanID) {
    struct SyntaxChunk *syntaxStack = ((struct BufferHandle *)scanID)->bh_SyntaxStack;
    WORD indent;

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

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

    if ( len > 0 ) {
        UWORD first = indent;

        UWORD level = LEVEL_TEXT;
        UWORD startcol = indent;
        UWORD endcol = indent;
        UWORD pushlevel = -1;
        UWORD pushstart = 0;
        UWORD pushend = 0;

        char *scan = text;
        char *end = text + len;
        while ( scan < end ) {
            switch ( *scan ) {
                case '{' :
                case '}' : {
                    if ( level != LEVEL_BRACKET ) {
                        pushlevel = level;
                        pushstart = startcol;
                        pushend = endcol;

                        startcol = indent;
                      }

                    level = LEVEL_BRACKET;
                    endcol = indent;

                    scan++;
                    indent++;
                    break;
                  }

                case '\\' : {
                    char *eon = scan + 1;
                    if ( (*eon == '\\') ||
                         (*eon == '|') ||
                         (*eon == '[') ||
                         (*eon == ']') ||
                         (*eon == '(') ||
                         (*eon == ')') )
                        eon++;
                    else {
                        while ( (eon < end) &&
                                (((*eon >= 'a') && (*eon <= 'z')) ||
                                 ((*eon >= 'A') && (*eon <= 'Z'))) )
                            eon++;
                      }

                    if ( level != LEVEL_TEXKEYWORD ) {
                        pushlevel = level;
                        pushstart = startcol;
                        pushend = endcol;

                        startcol = indent;
                      }

                    level = LEVEL_TEXKEYWORD;
                    endcol = indent + (eon - scan) - 1;

                    indent += eon - scan;
                    scan = eon;
                    break;
                  }

                case '@' : {
                    char *eon = scan + 1;
                    if ( (*eon == '.') ||
                         (*eon == ':') ||
                         (*eon == '*') ||
                         (*eon == '@') ||
                         (*eon == '{') ||
                         (*eon == '|') ||
                         (*eon == '}') )
                        eon++;
                    else {
                        while ( (eon < end) &&
                                (((*eon >= 'a') && (*eon <= 'z')) ||
                                 ((*eon >= 'A') && (*eon <= 'Z'))) )
                            eon++;
                      }

                    if ( ((eon - scan == 2) && (scan[1] == 'c')) ||
                         ((eon - scan == 8) && (scan[1] == 'c') && (scan[2] == 'o') && (scan[3] == 'm') && (scan[4] == 'm') && (scan[5] == 'e') && (scan[6] == 'n') && (scan[7] == 't')) ) {
                        if ( level != LEVEL_COMMENT ) {
                            pushlevel = level;
                            pushstart = startcol;
                            pushend = endcol;

                            startcol = indent;
                          }

                        level = LEVEL_COMMENT;
                        endcol = first + len - 1;

                        scan = end;
                        indent = first + len;
                      }
                    else {
                        UWORD type = GetLevel(scan, eon - scan);

                        if ( level != type ) {
                            pushlevel = level;
                            pushstart = startcol;
                            pushend = endcol;

                            startcol = indent;
                          }

                        level = type;
                        endcol = indent + (eon - scan) - 1;

                        indent += eon - scan;
                        scan = eon;
                      }
                    break;
                  }

                default : {
                    if ( level != LEVEL_TEXT ) {
                        pushlevel = level;
                        pushstart = startcol;
                        pushend = endcol;

                        startcol = indent;
                      }

                    level = LEVEL_TEXT;
                    endcol = indent;

                    indent++;
                    scan++;
                    break;
                  }
              }

            if ( pushlevel != -1 ) {
                if ( pushlevel != LEVEL_TEXT ) {
                    syntaxStack->sc_Start = pushstart;
                    syntaxStack->sc_End   = pushend;
                    syntaxStack->sc_Level = pushlevel;
                    syntaxStack++;
                  }

                pushlevel = -1;
                pushstart = 0;
                pushend = 0;
              }
          }

        if ( level != LEVEL_TEXT ) {
            syntaxStack->sc_Start = startcol;
            syntaxStack->sc_End   = endcol;
            syntaxStack->sc_Level = level;
            syntaxStack++;
          }
      } // if ( len > 0 )

    if ( syntaxStack != ((struct BufferHandle *)scanID)->bh_SyntaxStack ) {
        syntaxStack->sc_Start = FALSE;
        syntaxStack->sc_End   = FALSE;
        syntaxStack->sc_Level = FALSE;

        return(((struct BufferHandle *)scanID)->bh_SyntaxStack);
      }

    return(NULL);
  }
///2

