/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     22/09/98
// RCS-ID:      $Id: cjparser.cpp,v 1.6 2001/11/18 12:25:11 GD Exp $
// Copyright:   (c) Aleskandars Gluchovas
// Licence:       wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
#  pragma implementation "acell.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "cjparser.h"

#if defined( wxUSE_TEMPLATE_STL )

    #include <map>

#else

    #include "wxstlac.h"

#endif


/***** Implementation for class SJParser *****/

// statics used by inline'ed C helper-functions
static char* _gSrcStart = 0;
static char* _gSrcEnd   = 0;
static char* _gLastSuppresedComment = 0;
static int   _gLineNo      = 0;

// FOR NOW:: comments queue is static
#define MAX_CQ_ENTRIES 128
static char* _gCommentsQueue[MAX_CQ_ENTRIES];
static int    _gCQSize = 0;

/***** keyword map related structures *****/

struct less_c_str
{
    inline bool operator()( char* x, char* y) const
    {     return ( strcmp( x,y ) < 0 );
    }
};

//WXSTL_MAP(CharPtrT,CharPtrT, LESS_THEN_FUNCTOR(CharPtrT));

#if defined( wxUSE_TEMPLATE_STL )

    typedef map< char*, char*, less_c_str > KeywordMapT;

#else

    typedef char* CharPtrT;
    typedef WXSTL_MAP( CharPtrT, CharPtrT ,less_c_str) KeywordMapT;

#endif

static KeywordMapT __gMultiLangMap;
static int         __gMapReady = 0;

static char* __gKeyWords[] =
{
    "public",
    "protected",
    "private",

    "class",
    "struct",
    "union",
    "enum",
    "interface",

    "package",
    "import",

    "typedef",
    "template",
    "friend",
    "const",
    "volatile",
    "mutable",
    "virtual",
    "inline",
    "static",
    "register",

    "final",
    "abstract",
    "native",

    "__stdcall",
    "extern",

    0
};

static void check_keyword_map()
{
    if ( !__gMapReady )
    {
        __gMapReady = 1;

        // "make sure" the address of the first member of non-polimorphic class
        // coinsides with the address of the instance

        char** keyword = __gKeyWords;

        while ( (*keyword) != 0 )
        {
            __gMultiLangMap.insert(
                KeywordMapT::value_type( *keyword, *keyword )
            );

            ++keyword;
        }
    }
}

/***** helper functions *****/

static inline void skip_to_eol( char*& cur )
{
    while( *(cur) != 10 && *cur != 13 && cur < _gSrcEnd) ++cur;
}

static inline void skip_eol( char*& cur )
{
    if ( *cur == 13 )

        cur += 2;
    else
        cur += 1;

    ++_gLineNo;
}

static inline bool skip_to_next_comment_in_the_line( char*& cur )
{
    do
    {
        while( cur < _gSrcEnd  &&
               *cur != 10 &&
               *cur != 13 &&
               *cur != '/'
             ) ++cur;

        if ( cur == _gSrcEnd ) return FALSE;

        if ( *cur == '/' )
        {
            if ( (*(cur+1) == '*') ||
                 (*(cur+1) == '/') ) return TRUE;
            else
            {
                ++cur;
                continue;
            }
        }

        return FALSE;

    } while(1);
}

inline static void store_line_no( int& toVar )
{
    toVar = _gLineNo;
}

inline static void restore_line_no( int storedLineNo )
{
    _gLineNo = storedLineNo;
}

inline static int get_line_no()
{
    return _gLineNo;
}

static void skip_to_prev_line( char*& cur )
{
    while( cur >= _gSrcStart  &&
           *cur != 10 &&
           *cur != 13
           ) --cur;

    // NOTE:: '\n' is 13,10 for DOS
    //        '\n' is 10 for UNIX

    // NOTE1: '\n' symbol is not used here,
    //        to provide possibility of loading
    //        file as binary

    --cur;
    if ( *cur == 10 )
    {
        ++cur;
        return;
    }

    if ( *cur == 13 ) --cur;

    while( cur >= _gSrcStart  &&
           *cur != 10 &&
           *cur != 13
           ) --cur;

    ++cur; // move to the first character in the line
}

static inline void skip_comments( char*& cur )
{
    ++cur; // skip '/' token

    if ( *cur != '/' && *cur != '*' ) return;

    // first, store position of the comment into the queue
    // (which further will be attached to the next context
    //  found)

    if ( cur-1 != _gLastSuppresedComment )
    {
        if ( _gCQSize == MAX_CQ_ENTRIES )
        {
            size_t i = MAX_CQ_ENTRIES-1;

            while( i != 0 )
            {
                _gCommentsQueue[i-1] = _gCommentsQueue[i];
                --i;
            }

            --_gCQSize ;
        }

        _gCommentsQueue[_gCQSize++] = cur-1;
    }

    // if signle-line comment, skip it now
    if ( *cur == '/' )
    {
        skip_to_eol( cur );
        skip_eol( cur );
        return;
    }

    size_t level = 1;

    // check for multiline comment (handle nested multiline comments!)

    int line_len = 0;

    ++cur;
    ++cur;
    do
    {
        // TBD:: check eof cond.

        // detect and remove vertical columns of '*''s

        while ( *cur != '/' && cur < _gSrcEnd )
        {
            switch (*cur)
            {
                case '*' :
                    {
                        if ( *(cur+1) != '/' )
                        {
                            if ( line_len == 1 )

                                *cur = ' ';
                        }

                        break;
                    }

                case 13 : line_len = 0; break;
                case 10 : { line_len = 0; ++_gLineNo; } break;

                default : ++line_len;
            }

            ++cur;
        }

        if ( cur >= _gSrcEnd  ) return;

        ++cur;

        if ( *(cur-2) == '*' )
        {
            --level;
            if ( level == 0 )
                break;
        }
        else
        if ( *cur == '*' )
        {
            ++cur;
            ++cur;

            ++level;
        }

    } while(1);
}

static inline void clear_commets_queue()
{
    _gCQSize = 0;
}

static inline void skip_quoted_string( char*& cur )
{
    ++cur; // skip first quote '"'

    // check if quote wasn't prefixed
    if ( *(cur-2) == '\\' )
        return;

    do
    {
        while ( *cur != '"' && cur < _gSrcEnd )
        {
            if ( *cur == 10 ) ++_gLineNo;
            ++cur;
        }

        if ( cur >= _gSrcEnd ) return;

        ++cur; // skip the last quote

        // check if it wasn't prefixed

        if ( *(cur-2) != '\\' )
            break;

    } while (1);
}

// skips subsequent white space and comments
// (return false if the end of source code reached)

static inline bool get_next_token( char*& cur )
{
    for( ; cur < _gSrcEnd; ++cur )
    {
        switch( *(cur) )
        {
            case ' ' : continue;
            case '\t': continue;
            case 13  : continue;

            case 10  : { ++_gLineNo;continue; }

            case '/' : skip_comments( cur );
                       --cur;
                       continue;

            default : break;
        };

        break;
    }

    if ( cur >= _gSrcEnd )

        return FALSE;
    else
        return TRUE;
}

static inline void skip_preprocessor_dir( char*& cur )
{
    do
    {
        skip_to_eol(cur);

        if ( *(cur-1) != '\\' )
            break;

        if ( cur < _gSrcEnd )
            skip_eol( cur );
        else
            break;

    } while(1);
}

static void skip_token( char*& cur )
{
    if ( *cur == '"' )
    {
        skip_quoted_string( cur );
        return;
    }

    if ( *cur == ',' ||
         *cur == ';' ||
         *cur == ')' ||
         *cur == '('
       )
    {
        ++cur;
        return;
    }

    // special case of "!=", "<=", ... 2 character composite tokens
    if ( *cur == '<' ||
         *cur == '>' ||
         *cur == '=' ||
         *cur == '!'
       )
    {
        cur++;
        if ( *cur == '=' )
            cur++;

        return;
    }

    ++cur; // leading character is always skipped

    for( ; cur < _gSrcEnd ; ++cur )
    {
        switch ( *cur )
        {
            case ' ' : break;
            case '\t': break;
            case 13  : break;
            case 10  : break;
            case ',' : break;
            case ';' : break;
            case '<' : break;
            case '>' : break;

            // FIXME:: QUICK-HACK:: to treat scope resolution
            //         tokens are a part of the string - e.g. SomeSpace::SubName would
            //         become one token

            case ':' : if ( *(cur+1) == ':' )
                       {
                           ++cur;
                           continue;
                       }

                       break;
            case '=' : break;
            case '(' : break;
            case ')' : break;
            case '{' : break;
            case '}' : break;

            default : continue;
        };
        break;
    }
}

static inline size_t get_token_len( char* tok )
{
    char* start = tok;

    skip_token( tok );

    return size_t( tok - start );
}

// returns true, if given tokens are equel

static inline bool cmp_tokens( char* tok1, char* tok2 )
{
    // NOTE:: the case one token includes
    //        other in it's entirely is not handled

    size_t len = get_token_len( tok1 );

    // assuming that tokens are non-zero length

    do
    {
        if ( *(tok1++) != *(tok2++) )
            return FALSE;

        --len;

    } while ( --len );

    return TRUE;
}

static inline bool cmp_tokens_fast( char* tok1, char* tok2, size_t len )
{
    do
    {
        if ( *(tok1++) != *(tok2++) )
            return FALSE;

    } while ( --len );

    return TRUE;
}

static inline void skip_tempalate_statement( char*& cur )
{
    size_t level = 0;

    // go one level deeper
    while( *cur != '<' && cur < _gSrcEnd )
    {
        if (*cur == 10 ) ++_gLineNo;
        ++cur;
    }

    // FIXME:: template should be checked statement for
    //         comments inside of it

    do
    {
        if ( *cur == '<' )
            ++level;
        else
            --level;

        ++cur; // skip '<' or '>' token

        if ( level == 0 )
            return;

        while( *cur != '<' && *cur != '>' && cur < _gSrcEnd )
        {
            if (*cur == 10 ) ++_gLineNo;
            ++cur;
        }

    } while (1);
}

static inline void skip_statement( char*& cur )
{
    for( ; cur < _gSrcEnd; ++cur )

        switch (*cur)
        {
            case  ';' : ++cur; // skip statement-terminator token
                        return;

            case  '"' : skip_quoted_string(cur);
                        --cur;
                        continue;

            case  10  : ++_gLineNo;

                        continue;
            case  '/' : skip_comments( cur );
                        --cur;
                        continue;
            default : continue;
        }
}

// "reversed" versions of skip_token() and get_next_token()

static inline void skip_token_back( char*& cur )
{
    // FIXME:: now, when moving backwards, neither strings nor
    //         comment blocks are checked

    --cur; // skip to the trailing character

    if ( *cur == ',' ||
         *cur == ')' ||
         *cur == '('
       )
       return;


    for( ; cur < _gSrcEnd ; --cur )
    {
        switch ( *cur )
        {
            case ' ' : break;
            case '\t': break;
            case 13  : break;
            case 10  : break;
            case ',' : break;
            case '(' : break;

            default : continue;
        };

        break;
    }

    ++cur; // get to the leading character of the token
}

static inline void skip_next_token_back( char*& cur )
{
    --cur; // skip leading character of the current token

    if ( *cur == ',' ||
         *cur == ')' ||
         *cur == '('
       )
    {
       ++cur;
       return;
    }

    for( ; cur < _gSrcEnd; --cur )
    {
        switch ( *cur )
        {
            case ' ' : continue;
            case '\t': continue;
            case 13  : continue;
            case 10  : continue;
            case ',' : continue;
            case '(' : continue;

            default : break;
        };

        break;
    }

    ++cur; // position after the trailing charcter of the prev token
}

static string get_token_str( char* cur )
{
    return string( cur, get_token_len( cur ) );
}

// skips token or whole expression which may have
// nested  expressions between '(' ')' brackets.
//
// Upon return, the cursor points to the terminating bracket ')',
//
// Return value is the size of the block

static size_t skip_block( char*& cur )
{
    size_t level = 0; // nesting level

    char* start = cur;

    // NOTE:: assumed that block not necessarely starts
    //        with bracket rightaway

    if ( *cur == '(' )
    {
        ++level;
    }

    do
    {
        skip_token( cur );

        char* savedPos = cur;
        int tmpLnNo;
        store_line_no( tmpLnNo );

        get_next_token( cur );

        if ( cur >= _gSrcEnd ) return 0;

        if ( *cur == '(' )
        {
            ++level;
        }
        else
        if ( *cur == ')' )
        {
            if ( level == 0 )
            {
                cur = savedPos;
                restore_line_no( tmpLnNo );

                return size_t(cur-start);
            }

            --level;

            if ( level == 0 )
            {
                ++cur;

                // QUICK-HACK::to easily handle function prototypes ,
                // it works, besause theoretically there should
                // be no cast-expressions in non-implementation
                // scope (e.g. "time( (long*)(ptr+1) )" should not
                // appear in the declarations, thus it is most likelly
                // for the ")(" fragment to be within a function
                // prototype in the declarations scope

                if ( *cur == '(' )
                {
                    ++level;
                    continue;
                }

                else return size_t(cur-start);
            }
        }
        else
        {
            if ( level == 0 )
            {
                cur = savedPos;
                restore_line_no( tmpLnNo );

                return size_t(cur-start);
            }
        }

    } while(1);
}

// returns 0, if end of source reached
static inline bool skip_imp_block( char*& cur )
{
    while( *cur != '{' && cur < _gSrcEnd )
    {
        skip_token( cur );
        if ( !get_next_token( cur ) ) return FALSE;
    }

    while( *cur != '}' && cur < _gSrcEnd )
    {
        skip_token( cur );
        if ( !get_next_token( cur ) ) return FALSE;
    }

    ++cur;

    return TRUE;
}

static bool is_class_token( char*& cur )
{
    // FIXME:: the below mess should be cleaned in it's entirely

    if ( *cur == 'i' )
        if ( *(cur+1) == 'n' )

            return cmp_tokens_fast( cur, "interface", 9 );

    if ( *cur == 'c' )
        if ( *(cur+1) == 'l' )

            return cmp_tokens_fast( cur, "class", 5 );

    if ( *cur == 's' )
        if ( *(cur+1) == 't' )

            return cmp_tokens_fast( cur, "struct", 6 );

    if ( *cur == 'u' )
        if ( *(cur+1) == 'n' )

            return cmp_tokens_fast( cur, "union", 5 );

    return FALSE;
}

inline static bool is_forward_decl( char* cur )
{
    do
    {
        switch( *cur )
        {
            case ':' : return FALSE;
            case '{' : return FALSE;
            case '(' : return FALSE;

            case ';' : return TRUE;

            default : break;
        };

        ++cur;

    } while (cur < _gSrcEnd); // prevent running out of bounds

    return FALSE;
}

inline static bool is_function( char* cur, bool& isAMacro )
{
    isAMacro = FALSE;

    int tmpLnNo;
    store_line_no( tmpLnNo );

    // NOTE:: comments and quoted strings are not checked here

    // first,check for "single-line hanginging macros" like:
    // ___UNICODE
    //

    char* eol = cur;
    skip_to_eol( eol );

    skip_token( cur );
    get_next_token( cur );

    if ( cur > eol )
    {
        isAMacro = TRUE;
        restore_line_no( tmpLnNo );

        return TRUE;
    }

    // it's not a macro, go to the begining of arg. list

    do
    {
        // if bracket found, it's a function or a begining
        // of some macro
        if ( *cur == '(' )
        {
            restore_line_no( tmpLnNo );
            return TRUE;
        }

        // end of statement found without any brackets in it
        // - it cannot be a function

        if ( *cur == ';' )
        {
            restore_line_no( tmpLnNo );
            return FALSE;
        }

        ++cur;

    } while( cur < _gSrcEnd);

    isAMacro = 1;
    restore_line_no( tmpLnNo );

    return FALSE;
}

// upon return the cursor is positioned after the
// terminating curly brace

static inline void skip_scope_block( char*& cur )
{
    size_t level = 0;

    for( ; cur < _gSrcEnd ; ++cur )

        switch( *cur )
        {
            case '/' : skip_comments( cur );
                       --cur;
                       continue;
            case '"' : skip_quoted_string( cur );
                       --cur;
                       continue;

            case '{' : ++level;
                       continue;

            case '}'  :--level;
                       if ( level == 0 )
                       {
                           ++cur; // skip final closing curly brace
                           return;
                       }

            case 10 : ++_gLineNo; continue;

            default : continue;
        };
}

// moves tokens like '*' '**', '***', '&' from the name
// to the type

static void arrange_indirection_tokens_between( string& type,
                                                string& identifier )
{
    // TBD:: FIXME:: return value of operators !

    while ( identifier[0] == '*' ||
            identifier[0] == '&'
          )
    {
        type += identifier[0];
        identifier.erase(0,1);

        if ( !identifier.length() ) return;
    }
}


// the only function where multi-lang keyword map is accessed

static bool is_keyword( char* cur )
{
    size_t len = get_token_len( cur );

    // put a terminating zero after the given token
    char tmp = *(cur + len);
    *(cur+len) = '\0';

    KeywordMapT::iterator i;

    i = __gMultiLangMap.find( cur );

    // restore original character suppresed by terminating zero
    *(cur + len) = tmp;

    return i == __gMultiLangMap.end() ? false : true;
}

static inline void get_string_between( char* start, char* end,
                                       string* pStr )
{
    char saved = *end;

    *end  = '\0';
    *pStr = start;
    *end  = saved;
}

static char* set_comment_text( string& text, char* start )
{
    char* end = start;

    // to avoid poluting the queue with this comment
    _gLastSuppresedComment = start;

    skip_comments( end );

    if ( *(end-1) == '/' )
        end -= 2;

    start += 2;

    // skip multiple leading '/''s or '*''s
    while( *start == '/' && start < end ) ++start;
    while( *start == '*' && start < end ) ++start;

    get_string_between( start, end, &text );

    return end;
}

/***** Implementation for class CJSourceParser *****/

CJSourceParser::CJSourceParser( bool collectCommnets, bool collectMacros )
    : mpStart(0),
      mpEnd(0),
      mpCurCtx( 0 ),
      mCommentsOn( collectCommnets ),
      mMacrosOn  ( collectMacros )
{
    check_keyword_map();
}

spFile* CJSourceParser::Parse( char* start, char* end )
{
    // set up state variables
    mCurVis       = SP_VIS_PRIVATE;

    spFile* pTopCtx = new spFile();
    mpCurCtx        = pTopCtx;

    mIsVirtual    = 0;
    mIsTemplate   = 0;
    mNestingLevel = 0;

    cur = start;

    mpStart = start;
    mpEnd   = end;

    _gSrcEnd   = mpEnd; // let all the C-functions "smell" the end of file
    _gSrcStart = start;

    _gLineNo   = 0;

    clear_commets_queue();

    // main parsing loop

    do
    {
        if ( !get_next_token( cur ) )
            // end of source reached
            return pTopCtx;

        if ( memcmp( cur, "ScriptSection( const string&",
                     strlen( "ScriptSection( const string&" )
                   ) == 0
            )
        {
            int o = 0;
            ++o;
        }

        switch (*cur)
        {
            case '#' :
                {
                    AddMacroNode( cur );
                    continue;
                }

            case ':' :
                {
                    skip_token( cur );
                    continue;
                }

            case ';' :
                {
                    skip_token( cur );
                    continue;
                }

            case ')' :
                {
                    skip_token( cur );
                    continue;
                }

            case '=' :
                {
                    skip_token( cur );
                    continue;
                }

            default: break;
        }

        // 'const' is a part of the return type, not a keyword here
        if ( strncmp(cur, "const", 5) != 0 && is_keyword( cur ) )
        {
            // parses, token, if token identifies
            // the container context (e.g. class/namespace)
            // the corresponding context object is created
            // and set as current context

            ParseKeyword( cur );
            continue;
        }

        if ( *cur >= '0' && *cur <= '9' )
        {
            skip_token( cur );
            continue;
        }

        if ( *cur == '}' )
        {
            if ( mCurCtxType != SP_CTX_CLASS )
            {
                // FOR NOW:: disable the below assertion

                // DBG:: unexpected closing-bracket found
                //ASSERT(0);

                skip_token( cur ); // just skip it
                continue;
            }

            if ( mpCurCtx->GetType() == SP_CTX_CLASS )
            {
                int curOfs = ( (cur+1) - _gSrcStart );

                mpCurCtx->mContextLength = ( curOfs - mpCurCtx->mSrcOffset );
            }

            --mNestingLevel;

            // terminate operation/class/namespace context
            // TBD:: check if it's really this type of context

            wxASSERT( mpCurCtx );
            mpCurCtx = mpCurCtx->GetOutterContext();
            wxASSERT( mpCurCtx );

            if ( mNestingLevel == 0 )
            {

                mCurCtxType = SP_CTX_FILE;

                // not-nested class delclaration finished,
                // rest template flag in any case
                mIsTemplate = 0;
            }

            skip_token( cur );
            continue;
        }

        bool isAMacro = 0;

        if ( is_function( cur, isAMacro ) )
        {
            if ( isAMacro )
            {
                skip_token( cur );
                continue;
            }

            char* savedPos = cur;

            int tmpLnNo;
            store_line_no( tmpLnNo );

            isAMacro = FALSE;

            if ( !ParseNameAndRetVal( cur, isAMacro ) )
            {
                if ( !isAMacro )
                {
                    cur = savedPos;
                    SkipFunction( cur );
                }
                continue;
            }

            if ( !ParseArguments( cur ) )
            {
                // failure while parsing arguments,
                // remove enclosing operation context

                spContext* pFailed = mpCurCtx;
                mpCurCtx = mpCurCtx->GetOutterContext();
                mpCurCtx->RemoveChild( pFailed );

                skip_to_eol( cur );
                //cur = savedPos;
            }
            else
            {
                // otherwise, successfully close operation context:

                clear_commets_queue();

                SkipFunctionBody( cur );

                mpCurCtx = mpCurCtx->GetOutterContext();

                // DBG::
                wxASSERT( mpCurCtx );

            }
        }
        else // otherwise it's declaration of a variable;
        {
            // now, the cursor point to the end of statement (';' token)

            if ( mCurCtxType != SP_CTX_CLASS )
            {
                // non-class members are ignored

                skip_token( cur ); // skip the end of statement
                continue;
            }

            ParseMemberVar( cur );
        }

    } while( 1 );
}

void CJSourceParser::AttachComments( spContext& ctx, char* cur )
{
    if ( !mCommentsOn ) return;

    MCommentListT& lst = ctx.GetCommentList();

    char* prevComEnd = 0;

    int tmpLnNo;
    store_line_no( tmpLnNo );

    // attach comments which were found before the given context

    for( int i = 0; i != _gCQSize; ++i )
    {
        spComment* pComment = new spComment();
        lst.push_back( pComment );

        // find the end of comment
        char* start = _gCommentsQueue[i];

        pComment->mIsMultiline = ( *(start+1) == '*' );

        // first comment in the queue and multiline
        // comments are always treated as a begining
        // of the new paragraph in the comment text

        if ( i == 0 )

            pComment->mStartsPar = TRUE;
        else
        if ( pComment->mIsMultiline )

            pComment->mStartsPar = TRUE;
        else
        {
            // find out wheather there is a new-line
            // between to adjecent comments


            char* prevLine = start;
            skip_to_prev_line(prevLine);

            if ( prevLine >= prevComEnd )

                pComment->mStartsPar = TRUE;
            else
                pComment->mStartsPar = FALSE;
        }

        prevComEnd = set_comment_text( pComment->mText, start );
    }


    // attach comments which are at the end of the line
    // of the given context (if any)

    if ( skip_to_next_comment_in_the_line( cur ) )
    {
        spComment* pComment = new spComment();
        lst.push_back( pComment );

        set_comment_text( pComment->mText, cur );

        pComment->mStartsPar = 1;
        pComment->mIsMultiline = ( *(cur+1) == '*' );

        // mark this comment, so that it would not
        // get in the comments list of the next context
        _gLastSuppresedComment = cur;
    }

    restore_line_no( tmpLnNo );

    clear_commets_queue();
}

void CJSourceParser::AddMacroNode( char*& cur )
{
    char* start = cur;

    int lineNo = get_line_no();

    skip_preprocessor_dir( cur );

    int tmpLnNo;
    store_line_no( tmpLnNo );

    if ( !mMacrosOn ) return;

    spPreprocessorLine* pPL = new spPreprocessorLine();
    pPL->mSrcLineNo = lineNo;

    AttachComments( *pPL, cur );

    get_string_between( start, cur, &pPL->mLine );

    ++start; // skip '#'
    get_next_token( start );

    pPL->mDefType = SP_PREP_DEF_OTHER;

    // if we found a definition or redefinition,
    // determine the type exactly and assign
    // a name to the context

    if ( *start == 'd' )
    {
        if ( cmp_tokens_fast( start, "define", 6 ) )
        {
            char* tok = start+6;

            get_next_token( tok );

            pPL->mName = get_token_str( tok );

            skip_token( tok );
            get_next_token( tok);


            if ( tok > cur )
                pPL->mDefType = SP_PREP_DEF_DEFINE_SYMBOL;
            else
                pPL->mDefType = SP_PREP_DEF_REDEFINE_SYMBOL;
        }
    }
    else if ( *start == 'i' )
    {
        if ( cmp_tokens_fast( start, "include", 7 ) )
        {
            pPL->mDefType = SP_PREP_DEF_INCLUDE_FILE;
        }
        else if ( *++start == 'f' )
        {
            // either "#if" or "#ifdef"
            cur = start;
            skip_token( cur );
            get_next_token( cur );

            string condition = get_token_str( cur );

            // currently, everything except '0' is true
            if ( condition == "0" ) {
                // skip until the following else or enif
                while ( cur < _gSrcEnd ) {
                    skip_to_eol( cur );
                    skip_eol( cur );

                    get_next_token( cur );
                    if ( *cur++ == '#' && *cur == 'e' )
                        break;
                }
            }

            // TODO parse the condition...
        }
    }
    else if ( cmp_tokens_fast( start, "else", 4 ) )
    {
        // skip until "#endif"
        while ( cur < _gSrcEnd ) {
            skip_to_eol( cur );
            skip_eol( cur );

            get_next_token( cur );
            if ( *cur++ == '#' && cmp_tokens_fast( cur, "endif", 5 ) )
                break;
        }
    }

    mpCurCtx->AddMember( pPL );

    skip_to_eol( cur );
    skip_eol( cur );

    restore_line_no( tmpLnNo );

    clear_commets_queue();
}

void CJSourceParser::ParseKeyword( char*& cur )
{
    // analyze token, which identifies the begining of a new context

    if ( CheckVisibilty( cur ) )
    {
        skip_token( cur );
        return;
    }

    if ( is_class_token( cur ) )
    {
        if ( is_forward_decl( cur ) )
        {
            // forward declarations are ignored;
            skip_token( cur );
            return;
        }

        if ( mNestingLevel == 0 )
        {
            // change context form global class context
            mCurCtxType = SP_CTX_CLASS;
        }

        ++mNestingLevel;

        // add information about new class (name, inheritance, etc)
        AddClassNode( cur );

        // the default visiblity for class members is 'private'
        mCurVis = SP_VIS_PRIVATE;

        return;
    }

    size_t len = get_token_len( cur );

    if ( cmp_tokens_fast( cur, "typedef", len  ) )
    {
        skip_token(cur);
        get_next_token(cur);

        if ( cmp_tokens_fast( cur, "struct", len ) ||
             cmp_tokens_fast( cur, "union",  len ) ||
             cmp_tokens_fast( cur, "class",  len )
           )
        {
            if ( mNestingLevel == 0 )
            {
                // change context form global class context
                mCurCtxType = SP_CTX_CLASS;
            }

            ++mNestingLevel;

            // add information about new class (name, inheritance, etc)
            AddClassNode( cur );

            // the default visiblity for class members is 'private'
            mCurVis = SP_VIS_PRIVATE;

            return;

            // FOR NOW:: typedef struct, etc are also ignored
            //skip_scope_block( cur );
        }

        if ( cmp_tokens_fast( cur, "enum", len  ) )
        {
            AddEnumNode( cur );
            return;
        }

        AddTypeDefNode( cur );

        return;
    }

    if ( cmp_tokens_fast( cur, "enum", len ) )
    {
        AddEnumNode( cur );
        return;
    }

    if ( cmp_tokens_fast( cur, "extern", len ) )
    {
        // extern's are ignored (both extern "C" and extern vars)
        while ( *cur != '{' &&
                *cur != ';' )
        {
            skip_token( cur );
            get_next_token( cur );
        }
        return;

    }
    if ( cmp_tokens_fast( cur, "enum", len ) )
    {
        // enumeration blocks are ignored

        skip_scope_block( cur );

        get_next_token( cur );
        skip_token( cur ); // skip ';' token;
        return;
    }

    if ( cmp_tokens_fast( cur, "package", len  ) )
    {
        // packages are ignored
        skip_statement( cur );
        return;
    };

    if ( cmp_tokens_fast( cur, "import", len  ) )
    {
        // import statements are ignored
        skip_statement( cur );
        return;
    }

    if ( cmp_tokens_fast( cur, "virtual", len  ) )
    {
        // probably the virtual method is in front of us;
        mIsVirtual = 1;
        skip_token( cur );
        return;
    }

    if ( cmp_tokens_fast( cur, "template", len  ) )
    {
        mIsTemplate = 1;
        skip_tempalate_statement( cur );
        return;
    }

    if ( cmp_tokens_fast( cur, "friend", len  ) )
    {
        skip_statement( cur );
        return;
    }

    // ingnore "unsigificant" tokens (i.e. which do not
    // affect the current parsing context)

    skip_token( cur );
}

bool CJSourceParser::ParseNameAndRetVal( char*& cur, bool& isAMacro )
{
    isAMacro = FALSE;

    // FOR NOW:: all functions in the global
    //           scope are ignored

    int lineNo = get_line_no();

    char* start = cur;

    bool isVirtual = false;
    while( *cur != '(' )
    {
        if ( get_token_str( cur ) == "virtual" )
            isVirtual = true;

        skip_token( cur );
        if ( !get_next_token( cur ) ) return FALSE;
    }

    char* bracketPos = cur;
    char* savedPos   = cur + 1;

    int tmpLnNo;
    store_line_no( tmpLnNo );

    // skip gap between function name and start of paramters list
    while ( *(cur-1) == ' ' )
        --cur;

    // check if it's not a macro, and let plugin handle it, if so

    if ( mpPlugin )
    {
        skip_token_back( cur );

        char* tmp = cur;

        if ( mpPlugin->CanUnderstandContext( tmp, _gSrcEnd, mpCurCtx ) )
        {
            cur = tmp;

            mpPlugin->ParseContext( _gSrcStart, cur, _gSrcEnd, mpCurCtx );

            isAMacro = TRUE;

            return FALSE;
        }
    }

    spOperation* pOp = new spOperation();

    pOp->mSrcLineNo    = lineNo;
    pOp->mSrcOffset    = int( start - _gSrcStart );
    pOp->mHeaderLength = int( bracketPos - start );
    if ( mpCurCtx->GetContextType() == SP_CTX_CLASS )
        pOp->mScope = mpCurCtx->mName;

    mpCurCtx->AddMember( pOp );
    pOp->mVisibility = mCurVis;
    pOp->mIsVirtual = isVirtual;

    // add comments about operation
    AttachComments( *pOp, cur );

    // go backwards to method name
    skip_token_back( cur );

    pOp->mName = get_token_str( cur );

    // checker whether it's not an operator
    char chFirst = *pOp->mName.c_str();
    if ( !isalpha(chFirst) && chFirst != '_' && chFirst != '~' ) {
        // skip 'operator'
        skip_next_token_back( cur );
        skip_token_back( cur );

        string lastToken = get_token_str( cur );
        if ( lastToken == "operator" ) {
            lastToken += pOp->mName;
            pOp->mName = lastToken;
        }
        else {
            // ok, it wasn't an operator after all
            skip_token( cur );
        }
    }
    else if ( pOp->mName == "operator" ) {
        skip_token( cur );
        get_next_token( cur );
        string oper = get_token_str( cur );

        pOp->mName += oper;
    }

    // go backwards to method return type
    skip_next_token_back( cur );

    if ( cur >= start )
    {
        string rettype = string( start, size_t( cur-start ) );
        rettype.Replace("WXDLLEXPORT ", ""); // FIXME just for now...
        pOp->mRetType = rettype;
    }

    arrange_indirection_tokens_between( pOp->mRetType, pOp->mName );

    cur = savedPos;
    restore_line_no( tmpLnNo );

    // now, enter operation context
    mpCurCtx = pOp;

    return TRUE;
}

bool CJSourceParser::ParseArguments( char*& cur )
{
    // DANGER-MACROS::

    // now cursor position is right after the first opening bracket
    // of the function declaration

    char* blocks    [16]; // used exclusivelly for iterative "lean out"
                          // of macros and misc. not-obviouse grammar
                          // (dirty,, but we cannot do it very nice,
                          //  we're not preprocessor-free C/C++ code)
    int   blockSizes[16];

    do
    {
        size_t blocksSkipped = 0;

        get_next_token( cur );

        bool first_blk = 1;

        while( *cur != ')' && *cur != ',' )
        {
            blocks[blocksSkipped] = cur;

            if ( first_blk )
            {
                char* prev = cur;
                skip_token( cur );

                blockSizes[blocksSkipped] = size_t(cur-prev);

                first_blk = 0;
            }
            else
                blockSizes[blocksSkipped] = skip_block( cur );

            get_next_token( cur );
            ++blocksSkipped;
        }


        if ( blocksSkipped == 1 )
        {
            // check if the empty arg. list stressed with "void" inside
            if ( cmp_tokens_fast( blocks[0] , "void", 4 ) )
            {
                cur++;  // skip ')'

                break;
            }

            // FIXME:: TBD:: K&R-style function declarations!

            // if only one block enclosed, than it's probably
            // some macro, there should be at least two blocks,
            // one for argument type and another for it's identifier
            return FALSE;
        }

        if ( blocksSkipped == 0 )
        {
            if ( *cur == 10 ) ++_gLineNo;
            ++cur; // skip ')'

            break; // function without paramters
        }

        // we should be in the operation context now
        spOperation* pOp = (spOperation*)mpCurCtx;

        spParameter* pPar = new spParameter();

        pOp->AddMember( pPar );
        // FOR NOW:: line number is not exact if argument list is mutiline
        pPar->mSrcLineNo = get_line_no();

        size_t nameBlock = blocksSkipped - 1;
        size_t typeBlock = nameBlock - 1;

        // check if default values present
        if ( *blocks[typeBlock] == '=' )
        {
            // expressions like "int = 5" are ignored,
            // since name for paramters is required
            if ( blocksSkipped == 3 )
            {
                if ( *cur == ')' )
                {
                    ++cur;
                    break;
                }
            else
                continue;
            }

            pPar->mInitVal = string( blocks[nameBlock], blockSizes[nameBlock] );

            nameBlock = nameBlock - 2; // skip '=' token and default value block
            typeBlock = nameBlock - 1;
        }

        // attach comments about the parameter
        AttachComments( *pPar, blocks[nameBlock] );

        // retrieve argument name
        pPar->mName = string( blocks[nameBlock], blockSizes[nameBlock] );

        // retreive argument type

        size_t len = blockSizes[ typeBlock ];
        len = size_t ( (blocks[ typeBlock ] + len) - blocks[ 0 ] );

        pPar->mType = string( blocks[0], len );

        arrange_indirection_tokens_between( pPar->mType, pPar->mName );

        if ( *cur == ')' )
        {
            ++cur;
            break;
        }

        ++cur; // skip comma
        get_next_token(cur);

    } while(1);

    // skip possible whitespace between ')' and following "const"
    while ( isspace(*cur) )
        cur++;

    // check if it was really a function not a macro,
    // if so, than it should be terminated with semicolon ';'
    // or opening implemenetaton bracket '{'

    char* tok = cur;

    int tmpLnNo;
    store_line_no( tmpLnNo );

    do
    {
        if ( *tok == '{' || *tok == ';' )
        {
            restore_line_no(tmpLnNo);
            return TRUE;
        }

        // check for unexpected tokens
        if ( *tok == '=' || *tok == '0' )
        {
            skip_token(tok);
            if ( !get_next_token(tok) ) return FALSE;
            continue;
        }

        if ( *tok == '}' ) return FALSE;

        // if initialization list found
        if ( *tok == ':' )
        {
            restore_line_no(tmpLnNo);
            return TRUE;
        }

        if ( cmp_tokens_fast( tok, "const", 5 ) )
        {
            ((spOperation*)mpCurCtx)->mIsConstant = true;

            skip_token(tok);
            if ( !get_next_token(tok) ) return FALSE;
            continue;
        }

        if ( CheckVisibilty( tok ) ) return FALSE;

        // if next context found
        if ( is_keyword( tok ) ) return FALSE;

        skip_token(tok);
        if ( !get_next_token(tok) ) return FALSE;

    } while(1);

    return TRUE;
}

void CJSourceParser::ParseMemberVar( char*& cur )
{
    MMemberListT& members = mpCurCtx->GetMembers();

    bool firstMember = 1;

    size_t first = 0;

    string type;

    // jump to the end of statement
    // and start collecting same-type varibles
    // back-to-front towards the type identifier

    skip_statement( cur );
    char* savedPos = cur;

    int tmpLnNo;
    store_line_no( tmpLnNo );

    --cur; // rewind back to ';'

    do
    {
        spAttribute* pAttr = new spAttribute();
        // FOR NOW:: line not is not exact, if member declaration is multiline
        pAttr->mSrcLineNo = get_line_no();

        mpCurCtx->AddMember( pAttr );
        pAttr->mVisibility = mCurVis;

        pAttr->mIsConstant = 0;

        if ( firstMember )
        {
            firstMember = 0;
            first = members.size() - 1;;
        }

        skip_token_back( cur );

        // attach comments about the attribute
        AttachComments( *pAttr, cur );

        pAttr->mName = get_token_str( cur );

        // guessing that this going to be variable type
        skip_next_token_back( cur );
        skip_token_back( cur );

        pAttr->mType = get_token_str( cur );

        // if comma, than variable list continues
        // otherwise the variable type reached - stop

        if ( *cur == '=' )
        {
            // yes, we've mistaken, it was not a identifier,
            // but it's default value
            pAttr->mInitVal =
                pAttr->mName;

            // skip default value and '=' symbol
            skip_next_token_back( cur );
            skip_token_back( cur );

            pAttr->mName = get_token_str( cur );

            skip_next_token_back( cur );
            skip_token_back( cur );
        }

        if ( *cur != ',' )
        {
            type = get_token_str( cur );
            break;
        }

    } while(1);

    first = 0;

    // set up types for all collected (same-type) attributes;
    while ( first != members.size() - 1 )
    {
        spAttribute* pAttr = members[first++]->CastToAttribute();
        if ( !pAttr )
            continue;

        if ( !pAttr->mType )
            pAttr->mType = type;
        pAttr->mVisibility = mCurVis;

        if ( !!pAttr->mName )
            arrange_indirection_tokens_between( pAttr->mType, pAttr->mName );
    }

    cur = savedPos;
    restore_line_no( tmpLnNo );

    clear_commets_queue();


}

void CJSourceParser::SkipFunction( char*& cur )
{
    while ( *cur != '(' && cur < _gSrcEnd )
    {
        if (*cur == 10 ) ++_gLineNo;
        ++cur;
    }

    skip_next_token_back( cur ); // go back and skip function identifier
    skip_token_back( cur );      // go back and skip return type

    skip_block( cur );           // now, go ahead and skip whole declaration

    SkipFunctionBody( cur );

}

void CJSourceParser::SkipFunctionBody( char*& cur )
{
    // FIXME:: check for comments and quoted stirngs here

    bool hasDefinition = FALSE;

    while( *cur != '{' && *cur != ';' )
    {
        if (*cur == 10 ) ++_gLineNo;
        ++cur;
    }

    if ( *cur == ';' )
    {
        ++cur;
    }
    else
    {
        hasDefinition = TRUE;

        skip_scope_block( cur ); // skip the whole imp.
    }

    if ( mpCurCtx->GetType() == SP_CTX_OPERATION )
    {
        spOperation& op = *((spOperation*)mpCurCtx);

        int curOfs = int ( cur - _gSrcStart );

        op.mContextLength = curOfs - mpCurCtx->mSrcOffset;

        op.mHasDefinition = hasDefinition;

        // separate scope resolution token from the name of operation

        for( size_t i = 0; i != op.mName.length(); ++i )
        {
            if ( op.mName[i] == ':' && op.mName[i+1] == ':' )
            {
                string unscoped( op.mName, i+2, op.mName.length() - ( i + 2 ) );

                op.mScope = string( op.mName, 0, i );

                op.mName = unscoped;

                break;
            }
        }
    }
}

bool CJSourceParser::CheckVisibilty( char*& cur )
{
    size_t len = get_token_len( cur );

    if ( cmp_tokens_fast( cur, "public:", len ) )
    {
        mCurVis = SP_VIS_PUBLIC;
        return TRUE;
    }

    if ( cmp_tokens_fast( cur, "protected:", len ) )
    {
        mCurVis = SP_VIS_PROTECTED;
        return TRUE;
    }

    if ( cmp_tokens_fast( cur, "private:", len ) )
    {
        mCurVis = SP_VIS_PRIVATE;
        return TRUE;
    }

    return FALSE;
}

void CJSourceParser::AddClassNode( char*& cur )
{
    char* ctxStart = cur;

    string classkeyword = get_token_str( cur );

    skip_token( cur ); // skip 'class' keyword
    if ( !get_next_token( cur ) ) return;

    // in C++
    if ( *cur == ':' )
    {
        skip_token( cur );
        get_next_token( cur );
    }

    // by default all class members are private
    mCurVis = SP_VIS_PRIVATE;

    spClass* pClass = new spClass();
    if ( classkeyword == "class" )
        pClass->mClassSubType = SP_CLTYPE_CLASS;
    else if ( classkeyword == "struct" ) {
        pClass->mClassSubType = SP_CLTYPE_STRUCTURE;

        mCurVis = SP_VIS_PUBLIC;
    }
    else if ( classkeyword == "union" ) {
        pClass->mClassSubType = SP_CLTYPE_UNION;

        mCurVis = SP_VIS_PUBLIC;
    }
    else if ( classkeyword == "interface" )
        pClass->mClassSubType = SP_CLTYPE_INTERFACE;
    else {
        pClass->mClassSubType = SP_CLTYPE_INVALID;

        wxFAIL_MSG("unknown class keyword");
    }

    mpCurCtx->AddMember( pClass );

    // attach comments about the class
    AttachComments( *pClass, cur );

    pClass->mSrcLineNo = get_line_no();

    pClass->mSrcOffset = int( ctxStart - _gSrcStart );

    char* nameTok = cur;
    pClass->mName = get_token_str( cur );

    bool isDerived = 0;

    // DANGER-MACROS::

    do
    {
        skip_token( cur );
        if ( !get_next_token( cur ) ) return;

        if ( *cur == ':' )
        {
            isDerived = 1;

            char* tok = cur;

            int tmpLn;
            store_line_no( tmpLn );

            skip_next_token_back( tok );
            skip_token_back( tok );

            restore_line_no( tmpLn );

            // class name should precend ':' colon, thus
            // the one which was captured before was
            // proablty something else (like __dllexport MyClass : ... )

            if ( nameTok != tok )
            {
                pClass->mName = get_token_str( tok );
            }

        }

        if ( *cur == '{' )
            break;

        if ( *cur == ',' )
            continue;

        size_t len = get_token_len( cur );

        // skip neglectable C++ modifieres
        if ( cmp_tokens_fast( cur, "public", len ) )
            continue;

        if ( cmp_tokens_fast( cur, "protected", len ) )
            continue;

        if ( cmp_tokens_fast( cur, "private", len ) )
            continue;

        if ( cmp_tokens_fast( cur, "virtual", len ) )
            continue;

        // skip neglectable JAVA modifieres

        if ( cmp_tokens_fast( cur, "extends", len ) )
        {
            isDerived = 1;
            continue;
        }

        if ( cmp_tokens_fast( cur, "implements", len ) )
        {
            isDerived = 1;
            continue;
        }

        // all we need to know is superclass or interface

        char* tok = cur;
        int tmpLn;
        store_line_no( tmpLn );

        skip_token(tok);
        get_next_token(tok);

        restore_line_no( tmpLn );

        if ( *tok != ':' && *cur != ':' )

            pClass->mSuperClassNames.push_back( string( cur, len ) );

    } while(1);

    if ( !isDerived )
    {
        int tmpLn;
        store_line_no( tmpLn );

        while ( pClass->mSuperClassNames.size() )

            pClass->mSuperClassNames.erase( &pClass->mSuperClassNames[0] );

        char* tok = cur;

        // some non-obviouse token was following "class" keyword -
        // we've confused it with class name - thus now we're reverting this mistake

        skip_next_token_back( tok );
        skip_token_back( tok );

        pClass->mName = get_token_str( tok );

        restore_line_no( tmpLn );
    }


    ++cur; // skip opening curly brace

    pClass->mHeaderLength = ( cur - ctxStart );

    // now, enter the class context
    mpCurCtx = pClass;

    clear_commets_queue();
}

void CJSourceParser::AddEnumNode( char*& cur )
{
    // now the cursor is at "enum" keyword
    char* start = cur;

    spEnumeration* pEnum = new spEnumeration();
    mpCurCtx->AddMember( pEnum );

    pEnum->mSrcLineNo = get_line_no();


    AttachComments( *pEnum, cur );

    skip_token( cur );
    if ( !get_next_token( cur ) ) return;

    // check if enumeration has got it's identifier
    if ( *cur != '{' )
    {
        pEnum->mName = get_token_str( cur );
    }

    if ( !skip_imp_block( cur ) ) return;

    get_string_between( start, cur, &pEnum->mEnumContent );

    if ( get_next_token(cur) )
    {
        // check if the identifier if after the {...} block
        if ( *cur != ';' )

            pEnum->mName = get_token_str( cur );
    }

    clear_commets_queue();
}

void CJSourceParser::AddTypeDefNode( char*& cur )
{
    // now the cursor at the token next to "typedef" keyword

    if ( !get_next_token(cur) ) return;

    char* start = cur;

    spTypeDef* pTDef = new spTypeDef();
    mpCurCtx->AddMember( pTDef );

    pTDef->mSrcLineNo = get_line_no();

    AttachComments( *pTDef, cur );

    skip_statement( cur );

    int tmpLnNo;
    store_line_no( tmpLnNo );

    char* tok = cur-1;
    skip_next_token_back( tok );

    char* nameEnd = tok;

    skip_token_back( tok );

    char* nameStart = tok;

    skip_next_token_back( tok );

    char* typeEnd = tok;

    // check if it's function prototype
    if ( *nameStart == ')' )
    {
        typeEnd = nameStart+1;

        // skip argument list
        while ( *nameStart != '(' ) --nameStart;

        // skip to function type definition
        while ( *nameStart != ')' ) --nameStart;

        skip_next_token_back( nameStart );

        nameEnd = nameStart;

        skip_token_back( nameStart );

        if ( *nameStart == '*' ) ++nameStart;
    }

    get_string_between( start, typeEnd, &pTDef->mOriginalType );

    get_string_between( nameStart, nameEnd, &pTDef->mName );

    clear_commets_queue();

    restore_line_no( tmpLnNo );
}
