/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <basic/sberrors.hxx>
#include <basic/sbstar.hxx>
#include <basic/sbx.hxx>
#include <sbunoobj.hxx>
#include <parser.hxx>
#include <sb.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/reflection/theCoreReflection.hpp>
#include <comphelper/processfactory.hxx>
#include <com/sun/star/uno/Exception.hpp>
#include <basic/codecompletecache.hxx>
#include <memory>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

// Declaration of a variable
// If there are errors it will be parsed up to the comma or the newline.
// Return-value: a new instance, which were inserted and then deleted.
// Array-Index were returned as SbiExprList

SbiSymDef* SbiParser::VarDecl( SbiExprListPtr* ppDim, bool bStatic, bool bConst )
{
    bool bWithEvents = false;
    if( Peek() == WITHEVENTS )
    {
        Next();
        bWithEvents = true;
    }
    if( !TestSymbol() ) return nullptr;
    SbxDataType t = eScanType;
    SbiSymDef* pDef = bConst ? new SbiConstDef( aSym ) : new SbiSymDef( aSym );
    SbiExprListPtr pDim;
    // Brackets?
    if( Peek() == LPAREN )
    {
        pDim = SbiExprList::ParseDimList( this );
        if( !pDim->GetDims() )
            pDef->SetWithBrackets();
    }
    pDef->SetType( t );
    if( bStatic )
        pDef->SetStatic();
    if( bWithEvents )
        pDef->SetWithEvents();
    TypeDecl( *pDef );
    if (ppDim)
        *ppDim = std::move(pDim);
    else if (pDim)
    {
        if(pDim->GetDims() )
            Error( ERRCODE_BASIC_EXPECTED, u"()"_ustr );
    }
    return pDef;
}

// Resolving of an AS-Type-Declaration
// The data type were inserted into the handed over variable

void SbiParser::TypeDecl( SbiSymDef& rDef, bool bAsNewAlreadyParsed )
{
    SbxDataType eType = rDef.GetType();
    if( !(bAsNewAlreadyParsed || Peek() == AS) )
        return;

    short nSize = 0;
    if( !bAsNewAlreadyParsed )
        Next();
    rDef.SetDefinedAs();
    SbiToken eTok = Next();
    if( !bAsNewAlreadyParsed && eTok == NEW )
    {
        rDef.SetNew();
        eTok = Next();
    }
    switch( eTok )
    {
        case ANY:
            if( rDef.IsNew() )
                Error( ERRCODE_BASIC_SYNTAX );
            eType = SbxVARIANT; break;
        case TINTEGER:
        case TLONG:
        case TSINGLE:
        case TDOUBLE:
        case TCURRENCY:
        case TDATE:
        case TSTRING:
        case TOBJECT:
        case ERROR_:
        case TBOOLEAN:
        case TVARIANT:
        case TBYTE:
            if( rDef.IsNew() )
                Error( ERRCODE_BASIC_SYNTAX );
            eType = (eTok==TBYTE) ? SbxBYTE : SbxDataType( eTok - TINTEGER + SbxINTEGER );
            if( eType == SbxSTRING )
            {
                // STRING*n ?
                if( Peek() == MUL )
                {       // fixed size!
                    Next();
                    SbiConstExpression aSize( this );
                    nSize = aSize.GetShortValue();
                    if( nSize < 0 || (bVBASupportOn && nSize <= 0) )
                        Error( ERRCODE_BASIC_OUT_OF_RANGE );
                    else
                        rDef.SetFixedStringLength( nSize );
                }
            }
            break;
        case SYMBOL: // can only be a TYPE or an object class!
            if( eScanType != SbxVARIANT )
                Error( ERRCODE_BASIC_SYNTAX );
            else
            {
                OUString aCompleteName = aSym;

                // #52709 DIM AS NEW for Uno with full-qualified name
                if( Peek() == DOT )
                {
                    OUString aDotStr( '.' );
                    while( Peek() == DOT )
                    {
                        aCompleteName += aDotStr;
                        Next();
                        SbiToken ePeekTok = Peek();
                        if( ePeekTok == SYMBOL || IsKwd( ePeekTok ) )
                        {
                            Next();
                            aCompleteName += aSym;
                        }
                        else
                        {
                            Next();
                            Error( ERRCODE_BASIC_UNEXPECTED, SYMBOL );
                            break;
                        }
                    }
                }
                else if( rEnumArray->Find( aCompleteName, SbxClassType::Object ) || ( IsVBASupportOn() && VBAConstantHelper::instance().isVBAConstantType( aCompleteName ) ) )
                {
                    eType = SbxLONG;
                    break;
                }

                // Take over in the string pool
                rDef.SetTypeId( aGblStrings.Add( aCompleteName ) );

                if( rDef.IsNew() && pProc == nullptr )
                    aRequiredTypes.push_back( aCompleteName );
            }
            eType = SbxOBJECT;
            break;
        case FIXSTRING: // new syntax for complex UNO types
            rDef.SetTypeId( aGblStrings.Add( aSym ) );
            eType = SbxOBJECT;
            break;
        default:
            Error( ERRCODE_BASIC_UNEXPECTED, eTok );
            Next();
    }
    // The variable could have been declared with a suffix
    if( rDef.GetType() != SbxVARIANT )
    {
        if( rDef.GetType() != eType )
            Error( ERRCODE_BASIC_VAR_DEFINED, rDef.GetName() );
        else if( eType == SbxSTRING && rDef.GetLen() != nSize )
            Error( ERRCODE_BASIC_VAR_DEFINED, rDef.GetName() );
    }
    rDef.SetType( eType );
    rDef.SetLen( nSize );
}

// Here variables, arrays and structures were defined.
// DIM/PRIVATE/PUBLIC/GLOBAL

void SbiParser::Dim()
{
    DefVar( SbiOpcode::DIM_, pProc && bVBASupportOn && pProc->IsStatic() );
}

void SbiParser::DefVar( SbiOpcode eOp, bool bStatic )
{
    SbiSymPool* pOldPool = pPool;
    bool bSwitchPool = false;
    bool bPersistentGlobal = false;
    SbiToken eFirstTok = eCurTok;

    if( pProc && ( eCurTok == GLOBAL || eCurTok == PUBLIC || eCurTok == PRIVATE ) )
        Error( ERRCODE_BASIC_NOT_IN_SUBR, eCurTok );
    if( eCurTok == PUBLIC || eCurTok == GLOBAL )
    {
        bSwitchPool = true;     // at the right moment switch to the global pool
        if( eCurTok == GLOBAL )
            bPersistentGlobal = true;
    }
    // behavior in VBA is that a module scope variable's lifetime is
    // tied to the document. e.g. a module scope variable is global
    if(  GetBasic()->IsDocBasic() && bVBASupportOn && !pProc )
        bPersistentGlobal = true;
    // PRIVATE is a synonymous for DIM
    // _CONST_?
    bool bConst = false;
    if( eCurTok == CONST_ )
        bConst = true;
    else if( Peek() == CONST_ )
    {
        Next();
        bConst = true;
    }

    // #110004 It can also be a sub/function
    if( !bConst && (eCurTok == SUB || eCurTok == FUNCTION || eCurTok == PROPERTY ||
                    eCurTok == STATIC || eCurTok == ENUM || eCurTok == DECLARE || eCurTok == TYPE) )
    {
        // Next token is read here, because !bConst
        bool bPrivate = ( eFirstTok == PRIVATE );

        if( eCurTok == STATIC )
        {
            Next();
            DefStatic( bPrivate );
        }
        else if( eCurTok == SUB || eCurTok == FUNCTION || eCurTok == PROPERTY )
        {
            // End global chain if necessary (not done in
            // SbiParser::Parse() under these conditions
            if( bNewGblDefs && nGblChain == 0 )
            {
                nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 );
                bNewGblDefs = false;
            }
            Next();
            DefProc( false, bPrivate );
            return;
        }
        else if( eCurTok == ENUM )
        {
            Next();
            DefEnum( bPrivate );
            return;
        }
        else if( eCurTok == DECLARE )
        {
            Next();
            DefDeclare( bPrivate );
            return;
        }
        // #i109049
        else if( eCurTok == TYPE )
        {
            Next();
            DefType(); // TODO: Use bPrivate in DefType()
            return;
        }
    }

    // SHARED were ignored
    if( Peek() == SHARED ) Next();

    // PRESERVE only at REDIM
    if( Peek() == PRESERVE )
    {
        Next();
        if( eOp == SbiOpcode::REDIM_ )
            eOp = SbiOpcode::REDIMP_;
        else
            Error( ERRCODE_BASIC_UNEXPECTED, eCurTok );
    }
    SbiSymDef* pDef;
    SbiExprListPtr pDim;

    // #40689, Statics -> Module-Initialising, skip in Sub
    sal_uInt32 nEndOfStaticLbl = 0;
    if( !bVBASupportOn && bStatic )
    {
        nEndOfStaticLbl = aGen.Gen( SbiOpcode::JUMP_, 0 );
        aGen.Statement();   // catch up on static here
    }

    bool bDefined = false;
    while( ( pDef = VarDecl( &pDim, bStatic, bConst ) ) != nullptr )
    {
        /*fprintf(stderr, "Actual sub: \n");
        fprintf(stderr, "Symbol name: %s\n",OUStringToOString(pDef->GetName(),RTL_TEXTENCODING_UTF8).getStr());*/
        EnableErrors();
        // search variable:
        if( bSwitchPool )
            pPool = &aGlobals;
        SbiSymDef* pOld = pPool->Find( pDef->GetName() );
        // search also in the Runtime-Library
        bool bRtlSym = false;
        if( !pOld )
        {
            pOld = CheckRTLForSym( pDef->GetName(), SbxVARIANT );
            if( pOld )
                bRtlSym = true;
        }
        if( pOld && eOp != SbiOpcode::REDIM_ && eOp != SbiOpcode::REDIMP_ )
        {
            if( pDef->GetScope() == SbLOCAL )
                if (auto eOldScope = pOld->GetScope(); eOldScope != SbLOCAL && eOldScope != SbPARAM)
                    pOld = nullptr;
        }
        if( pOld )
        {
            bDefined = true;
            // always an error at a RTL-S
            if( !bRtlSym && (eOp == SbiOpcode::REDIM_ || eOp == SbiOpcode::REDIMP_) )
            {
                // compare the attributes at a REDIM
                SbxDataType eDefType;
                bool bError_ = false;
                if( pOld->IsStatic() )
                {
                    bError_ = true;
                }
                else if( pOld->GetType() != ( eDefType = pDef->GetType() ) )
                {
                    if( eDefType != SbxVARIANT || pDef->IsDefinedAs() )
                        bError_ = true;
                }
                if( bError_ )
                    Error( ERRCODE_BASIC_VAR_DEFINED, pDef->GetName() );
            }
            else
                Error( ERRCODE_BASIC_VAR_DEFINED, pDef->GetName() );
            delete pDef; pDef = pOld;
        }
        else
            pPool->Add( pDef );

        // #36374: Create the variable in front of the distinction IsNew()
        // Otherwise error at Dim Identifier As New Type and option explicit
        if( !bDefined && eOp != SbiOpcode::REDIM_ && eOp != SbiOpcode::REDIMP_
                      && ( !bConst || pDef->GetScope() == SbGLOBAL ) )
        {
            // Declare variable or global constant
            SbiOpcode eOp2;
            switch ( pDef->GetScope() )
            {
                case SbGLOBAL:  eOp2 = bPersistentGlobal ? SbiOpcode::GLOBAL_P_ : SbiOpcode::GLOBAL_;
                                goto global;
                case SbPUBLIC:  eOp2 = bPersistentGlobal ? SbiOpcode::PUBLIC_P_ : SbiOpcode::PUBLIC_;
                                // #40689, no own Opcode anymore
                                if( bVBASupportOn && bStatic )
                                {
                                    eOp2 = SbiOpcode::STATIC_;
                                    break;
                                }
                global:         aGen.BackChain( nGblChain );
                                nGblChain = 0;
                                bGblDefs = bNewGblDefs = true;
                                break;
                default:        eOp2 = SbiOpcode::LOCAL_;
            }
            sal_uInt32 nOpnd2 = sal::static_int_cast< sal_uInt16 >( pDef->GetType() );
            if( pDef->IsWithEvents() )
                nOpnd2 |= SBX_TYPE_WITH_EVENTS_FLAG;

            if( bCompatible && pDef->IsNew() )
                nOpnd2 |= SBX_TYPE_DIM_AS_NEW_FLAG;

            short nFixedStringLength = pDef->GetFixedStringLength();
            if( nFixedStringLength >= 0 )
                nOpnd2 |= (SBX_FIXED_LEN_STRING_FLAG + (sal_uInt32(nFixedStringLength) << 17));     // len = all bits above 0x10000

            if( pDim != nullptr && pDim->GetDims() > 0 )
                nOpnd2 |= SBX_TYPE_VAR_TO_DIM_FLAG;

            aGen.Gen( eOp2, pDef->GetId(), nOpnd2 );
        }

        // Initialising for self-defined data types
        // and per NEW created variable
        if( pDef->GetType() == SbxOBJECT
         && pDef->GetTypeId() )
        {
            if( !bCompatible && !pDef->IsNew() )
            {
                OUString aTypeName( aGblStrings.Find( pDef->GetTypeId() ) );
                if( rTypeArray->Find( aTypeName, SbxClassType::Object ) == nullptr )
                {
                    if( CodeCompleteOptions::IsExtendedTypeDeclaration() )
                    {
                        if(!IsUnoInterface(aTypeName))
                            Error( ERRCODE_BASIC_UNDEF_TYPE, aTypeName );
                    }
                    else
                        Error( ERRCODE_BASIC_UNDEF_TYPE, aTypeName );
                }
            }

            if( bConst )
            {
                Error( ERRCODE_BASIC_SYNTAX );
            }

            if( pDim )
            {
                if( eOp == SbiOpcode::REDIMP_ )
                {
                    SbiExpression aExpr( this, *pDef, nullptr );
                    aExpr.Gen();
                    aGen.Gen( SbiOpcode::REDIMP_ERASE_ );

                    pDef->SetDims( pDim->GetDims() );
                    SbiExpression aExpr2( this, *pDef, std::move(pDim) );
                    aExpr2.Gen();
                    aGen.Gen( SbiOpcode::DCREATE_REDIMP_, pDef->GetId(), pDef->GetTypeId() );
                }
                else
                {
                    // tdf#145371, tdf#136755 - only delete the variable beforehand REDIM
                    if (eOp == SbiOpcode::REDIM_)
                    {
                        SbiExpression aExpr(this, *pDef, nullptr);
                        aExpr.Gen();
                        aGen.Gen(bVBASupportOn ? SbiOpcode::ERASE_CLEAR_ : SbiOpcode::ERASE_);
                    }

                    pDef->SetDims( pDim->GetDims() );
                    SbiExpression aExpr2( this, *pDef, std::move(pDim) );
                    aExpr2.Gen();
                    aGen.Gen( SbiOpcode::DCREATE_, pDef->GetId(), pDef->GetTypeId() );
                }
            }
            else
            {
                SbiExpression aExpr( this, *pDef );
                aExpr.Gen();

                /* tdf#88442
                 * Don't initialize a
                 *      Global X as New SomeObjectType
                 * if it has already been initialized.
                 * This approach relies on JUMPT evaluating Object->NULL as being 'false'
                 * But the effect of this code is similar to inserting
                 *  If IsNull(YourGlobal)
                 *      Set YourGlobal = ' new obj
                 *  End If ' If IsNull(YourGlobal)
                 * Only for globals. For locals that check is skipped as it's unnecessary
                 */
                sal_uInt32 come_from = 0;
                if ( pDef->GetScope() == SbGLOBAL )
                {
                    come_from = aGen.Gen( SbiOpcode::JUMPT_, 0 );
                    aGen.Gen( SbiOpcode::FIND_, pDef->GetId(), pDef->GetTypeId() );
                }

                SbiOpcode eOp_ = pDef->IsNew() ? SbiOpcode::CREATE_ : SbiOpcode::TCREATE_;
                aGen.Gen( eOp_, pDef->GetId(), pDef->GetTypeId() );
                if ( bVBASupportOn )
                    aGen.Gen( SbiOpcode::VBASET_ );
                else
                    aGen.Gen( SbiOpcode::SET_ );

                if ( come_from )
                {
                    // See other tdf#88442 comment above where come_from is
                        // initialized. This is effectively 'inserting' the
                        // End If ' If IsNull(YourGlobal)
                    aGen.BackChain( come_from );
                }
            }
        }
        else
        {
            if( bConst )
            {
                // Definition of the constants
                if( pDim )
                {
                    Error( ERRCODE_BASIC_SYNTAX );
                }
                SbiExpression aVar( this, *pDef );
                if( !TestToken( EQ ) )
                    goto MyBreak;   // (see below)
                SbiConstExpression aExpr( this );
                if( !bDefined && aExpr.IsValid() )
                {
                    if( pDef->GetScope() == SbGLOBAL )
                    {
                        // Create code only for the global constant!
                        aVar.Gen();
                        aExpr.Gen();
                        aGen.Gen( SbiOpcode::PUTC_ );
                    }
                    SbiConstDef* pConst = pDef->GetConstDef();
                    if( aExpr.GetType() == SbxSTRING )
                        pConst->Set( aExpr.GetString() );
                    else
                        pConst->Set( aExpr.GetValue(), aExpr.GetType() );
                }
            }
            else if( pDim )
            {
                // Dimension the variable
                // Delete the var at REDIM beforehand
                if( eOp == SbiOpcode::REDIM_ )
                {
                    SbiExpression aExpr( this, *pDef, nullptr );
                    aExpr.Gen();
                    if ( bVBASupportOn )
                        // delete the array but
                        // clear the variable ( this
                        // allows the processing of
                        // the param to happen as normal without errors ( ordinary ERASE just clears the array )
                        aGen.Gen( SbiOpcode::ERASE_CLEAR_ );
                    else
                        aGen.Gen( SbiOpcode::ERASE_ );
                }
                else if( eOp == SbiOpcode::REDIMP_ )
                {
                    SbiExpression aExpr( this, *pDef, nullptr );
                    aExpr.Gen();
                    aGen.Gen( SbiOpcode::REDIMP_ERASE_ );
                }
                pDef->SetDims( pDim->GetDims() );
                if( bPersistentGlobal )
                    pDef->SetGlobal( true );
                SbiExpression aExpr( this, *pDef, std::move(pDim) );
                aExpr.Gen();
                pDef->SetGlobal( false );
                aGen.Gen( (eOp == SbiOpcode::STATIC_) ? SbiOpcode::DIM_ : eOp );
            }
        }
        if( !TestComma() )
            goto MyBreak;

        // Implementation of bSwitchPool (see above): pPool must not be set to &aGlobals
        // at the VarDecl-Call.
        // Apart from that the behavior should be absolutely identical,
        // i.e., pPool had to be reset always at the end of the loop.
        // also at a break
        pPool = pOldPool;
        continue;       // Skip MyBreak
    MyBreak:
        pPool = pOldPool;
        break;
    }

    // #40689, finalize the jump over statics declarations
    if( !bVBASupportOn && bStatic )
    {
        // maintain the global chain
        nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 );
        bGblDefs = bNewGblDefs = true;

        // Register for Sub a jump to the end of statics
        aGen.BackChain( nEndOfStaticLbl );
    }

}

// Here were Arrays redimensioned.

void SbiParser::ReDim()
{
    DefVar( SbiOpcode::REDIM_, pProc && bVBASupportOn && pProc->IsStatic() );
}

// ERASE array, ...

void SbiParser::Erase()
{
    while( !bAbort )
    {
        SbiExpression aExpr( this, SbLVALUE );
        aExpr.Gen();
        aGen.Gen( SbiOpcode::ERASE_ );
        if( !TestComma() ) break;
    }
}

// Declaration of a data type

void SbiParser::Type()
{
    DefType();
}

void SbiParser::DefType()
{
    // Read the new Token lesen. It had to be a symbol
    if (!TestSymbol())
        return;

    if (rTypeArray->Find(aSym,SbxClassType::Object))
    {
        Error( ERRCODE_BASIC_VAR_DEFINED, aSym );
        return;
    }

    SbxObject *pType = new SbxObject(aSym);

    bool bDone = false;

    while( !bDone && !IsEof() )
    {
        std::unique_ptr<SbiSymDef> pElem;
        SbiExprListPtr pDim;
        switch( Peek() )
        {
            case ENDTYPE :
                bDone = true;
                Next();
            break;

            case EOLN :
            case REM :
                Next();
            break;

            default:
                pElem.reset(VarDecl(&pDim, false, false));
                if( !pElem )
                    bDone = true;   // Error occurred
        }
        if( pElem )
        {
            SbxArray *pTypeMembers = pType->GetProperties();
            OUString aElemName = pElem->GetName();
            if( pTypeMembers->Find( aElemName, SbxClassType::DontCare) )
            {
                Error (ERRCODE_BASIC_VAR_DEFINED);
            }
            else
            {
                SbxDataType eElemType = pElem->GetType();
                SbxProperty *pTypeElem = new SbxProperty( aElemName, eElemType );
                if( pDim )
                {
                    SbxDimArray* pArray = new SbxDimArray( pElem->GetType() );
                    if ( pDim->GetSize() )
                    {
                        // Dimension the target array

                        for ( short i=0; i<pDim->GetSize();++i )
                        {
                            sal_Int32 lb = nBase;
                            SbiExprNode* pNode =  pDim->Get(i)->GetExprNode();
                            sal_Int32 ub = pNode->GetNumber();
                            if ( !pDim->Get( i )->IsBased() ) // each dim is low/up
                            {
                                if (  ++i >= pDim->GetSize() ) // trouble
                                    StarBASIC::FatalError( ERRCODE_BASIC_INTERNAL_ERROR );
                                pNode =  pDim->Get(i)->GetExprNode();
                                lb = ub;
                                ub = pNode->GetNumber();
                            }
                            else if ( !bCompatible )
                                ub += nBase;
                            pArray->AddDim(lb, ub);
                        }
                        pArray->setHasFixedSize( true );
                    }
                    else
                        pArray->unoAddDim(0, -1); // variant array
                    SbxFlagBits nSavFlags = pTypeElem->GetFlags();
                    // need to reset the FIXED flag
                    // when calling PutObject ( because the type will not match Object )
                    pTypeElem->ResetFlag( SbxFlagBits::Fixed );
                    pTypeElem->PutObject( pArray );
                    pTypeElem->SetFlags( nSavFlags );
                }
                // Nested user type?
                if( eElemType == SbxOBJECT )
                {
                    sal_uInt16 nElemTypeId = pElem->GetTypeId();
                    if( nElemTypeId != 0 )
                    {
                        OUString aTypeName( aGblStrings.Find( nElemTypeId ) );
                        SbxObject* pTypeObj = static_cast< SbxObject* >( rTypeArray->Find( aTypeName, SbxClassType::Object ) );
                        if( pTypeObj != nullptr )
                        {
                            SbxObjectRef pCloneObj = cloneTypeObjectImpl( *pTypeObj );
                            pTypeElem->PutObject( pCloneObj.get() );
                        }
                    }
                }
                pTypeMembers->Insert(pTypeElem, pTypeMembers->Count());
            }
        }
    }

    pType->Remove( u"Name"_ustr, SbxClassType::DontCare );
    pType->Remove( u"Parent"_ustr, SbxClassType::DontCare );

    rTypeArray->Insert(pType, rTypeArray->Count());
}


// Declaration of Enum type

void SbiParser::Enum()
{
    DefEnum( false );
}

void SbiParser::DefEnum( bool bPrivate )
{
    // Read the new Token. It had to be a symbol
    if (!TestSymbol())
        return;

    OUString aEnumName = aSym;
    if( rEnumArray->Find(aEnumName,SbxClassType::Object) )
    {
        Error( ERRCODE_BASIC_VAR_DEFINED, aSym );
        return;
    }

    SbxObject *pEnum = new SbxObject( aEnumName );
    if( bPrivate )
    {
        pEnum->SetFlag( SbxFlagBits::Private );
    }
    SbiSymDef* pElem;
    bool bDone = false;

    // Starting with -1 to make first default value 0 after ++
    sal_Int32 nCurrentEnumValue = -1;
    while( !bDone && !IsEof() )
    {
        switch( Peek() )
        {
            case ENDENUM :
                pElem = nullptr;
                bDone = true;
                Next();
            break;

            case EOLN :
            case REM :
                pElem = nullptr;
                Next();
            break;

            default:
            {
                SbiExprListPtr pDim;
                pElem = VarDecl( &pDim, false, true );
                if( !pElem )
                {
                    bDone = true;   // Error occurred
                    break;
                }
                else if( pDim )
                {
                    Error( ERRCODE_BASIC_SYNTAX );
                    bDone = true;   // Error occurred
                    break;
                }

                SbiExpression aVar( this, *pElem );
                if( Peek() == EQ )
                {
                    Next();

                    SbiConstExpression aExpr( this );
                    if( aExpr.IsValid() )
                    {
                        SbxVariableRef xConvertVar = new SbxVariable();
                        if( aExpr.GetType() == SbxSTRING )
                            xConvertVar->PutString( aExpr.GetString() );
                        else
                            xConvertVar->PutDouble( aExpr.GetValue() );

                        nCurrentEnumValue = xConvertVar->GetLong();
                    }
                }
                else
                    nCurrentEnumValue++;

                SbiSymPool* pPoolToUse = bPrivate ? pPool : &aGlobals;

                SbiSymDef* pOld = pPoolToUse->Find( pElem->GetName() );
                if( pOld )
                {
                    Error( ERRCODE_BASIC_VAR_DEFINED, pElem->GetName() );
                    bDone = true;   // Error occurred
                    break;
                }

                pPool->Add( pElem );

                if( !bPrivate )
                {
                    aGen.BackChain( nGblChain );
                    nGblChain = 0;
                    bGblDefs = bNewGblDefs = true;
                    aGen.Gen(
                        SbiOpcode::GLOBAL_, pElem->GetId(),
                        sal::static_int_cast< sal_uInt16 >( pElem->GetType() ) );

                    aVar.Gen();
                    sal_uInt16 nStringId = aGen.GetParser()->aGblStrings.Add( nCurrentEnumValue, SbxLONG );
                    aGen.Gen( SbiOpcode::NUMBER_, nStringId );
                    aGen.Gen( SbiOpcode::PUTC_ );
                }

                SbiConstDef* pConst = pElem->GetConstDef();
                pConst->Set( nCurrentEnumValue, SbxLONG );
            }
        }
        if( pElem )
        {
            SbxArray *pEnumMembers = pEnum->GetProperties();
            SbxProperty *pEnumElem = new SbxProperty( pElem->GetName(), SbxLONG );
            pEnumElem->PutLong( nCurrentEnumValue );
            pEnumElem->ResetFlag( SbxFlagBits::Write );
            pEnumElem->SetFlag( SbxFlagBits::Const );
            pEnumMembers->Insert(pEnumElem, pEnumMembers->Count());
        }
    }

    pEnum->Remove( u"Name"_ustr, SbxClassType::DontCare );
    pEnum->Remove( u"Parent"_ustr, SbxClassType::DontCare );

    rEnumArray->Insert(pEnum, rEnumArray->Count());
}


// Procedure-Declaration
// the first Token is already read in (SUB/FUNCTION)
// xxx Name [LIB "name"[ALIAS "name"]][(Parameter)][AS TYPE]

SbiProcDef* SbiParser::ProcDecl( bool bDecl )
{
    bool bFunc = ( eCurTok == FUNCTION );
    bool bProp = ( eCurTok == GET || eCurTok == SET || eCurTok == LET );
    if( !TestSymbol() ) return nullptr;
    OUString aName( aSym );
    SbxDataType eType = eScanType;
    SbiProcDef* pDef = new SbiProcDef( this, aName, true );
    pDef->SetType( eType );
    if( Peek() == CDECL_ )
    {
        Next(); pDef->SetCdecl(true);
    }
    if( Peek() == LIB )
    {
        Next();
        if( Next() == FIXSTRING )
        {
            pDef->GetLib() = aSym;
        }
        else
        {
            Error( ERRCODE_BASIC_SYNTAX );
        }
    }
    if( Peek() == ALIAS )
    {
        Next();
        if( Next() == FIXSTRING )
        {
            pDef->GetAlias() = aSym;
        }
        else
        {
            Error( ERRCODE_BASIC_SYNTAX );
        }
    }
    if( !bDecl )
    {
        // CDECL, LIB and ALIAS are invalid
        if( !pDef->GetLib().isEmpty() )
        {
            Error( ERRCODE_BASIC_UNEXPECTED, LIB );
        }
        if( !pDef->GetAlias().isEmpty() )
        {
            Error( ERRCODE_BASIC_UNEXPECTED, ALIAS );
        }
        if( pDef->IsCdecl() )
        {
            Error( ERRCODE_BASIC_UNEXPECTED, CDECL_ );
        }
        pDef->SetCdecl( false );
        pDef->GetLib().clear();
        pDef->GetAlias().clear();
    }
    else if( pDef->GetLib().isEmpty() )
    {
        // ALIAS and CDECL only together with LIB
        if( !pDef->GetAlias().isEmpty() )
        {
            Error( ERRCODE_BASIC_UNEXPECTED, ALIAS );
        }
        if( pDef->IsCdecl() )
        {
            Error( ERRCODE_BASIC_UNEXPECTED, CDECL_ );
        }
        pDef->SetCdecl( false );
        pDef->GetAlias().clear();
    }
    // Brackets?
    if( Peek() == LPAREN )
    {
        Next();
        if( Peek() == RPAREN )
        {
            Next();
        }
        else
        {
            for(;;)
            {
                bool bByVal = false;
                bool bOptional = false;
                bool bParamArray = false;
                while( Peek() == BYVAL || Peek() == BYREF || Peek() == OPTIONAL_ )
                {
                    if( Peek() == BYVAL )
                    {
                        bByVal = true;
                    }
                    else if ( Peek() == BYREF )
                    {
                        bByVal = false;
                    }
                    else if ( Peek() == OPTIONAL_ )
                    {
                        bOptional = true;
                    }
                    Next();
                }
                if( bCompatible && Peek() == PARAMARRAY )
                {
                    if( bByVal || bOptional )
                    {
                        Error( ERRCODE_BASIC_UNEXPECTED, PARAMARRAY );
                    }
                    Next();
                    bParamArray = true;
                }
                SbiSymDef* pPar = VarDecl( nullptr, false, false );
                if( !pPar )
                {
                    break;
                }
                if( bByVal )
                {
                    pPar->SetByVal(true);
                }
                if( bOptional )
                {
                    pPar->SetOptional();
                }
                if( bParamArray )
                {
                    pPar->SetParamArray();
                }
                if (SbiSymDef* pOldDef = pDef->GetParams().Find(pPar->GetName(), false))
                {
                    Error(ERRCODE_BASIC_VAR_DEFINED, pPar->GetName());
                    delete pPar;
                    pPar = pOldDef;
                }
                else
                    pDef->GetParams().Add( pPar );
                SbiToken eTok = Next();
                if( eTok != COMMA && eTok != RPAREN )
                {
                    bool bError2 = true;
                    if( bOptional && bCompatible && eTok == EQ )
                    {
                        {
                            SbiConstExpression aDefaultExpr(this);
                            SbxDataType eType2 = aDefaultExpr.GetType();

                            sal_uInt16 nStringId;
                            if (eType2 == SbxSTRING)
                            {
                                nStringId = aGblStrings.Add(aDefaultExpr.GetString());
                            }
                            else
                            {
                                nStringId = aGblStrings.Add(aDefaultExpr.GetValue(), eType2);
                            }
                            pPar->SetDefaultId(nStringId);
                        }

                        eTok = Next();
                        if( eTok == COMMA || eTok == RPAREN )
                        {
                            bError2 = false;
                        }
                    }
                    if( bError2 )
                    {
                        Error( ERRCODE_BASIC_EXPECTED, RPAREN );
                        break;
                    }
                }
                if( eTok == RPAREN )
                {
                    break;
                }
            }
        }
    }
    TypeDecl( *pDef );
    if( eType != SbxVARIANT && pDef->GetType() != eType )
    {
        Error( ERRCODE_BASIC_BAD_DECLARATION, aName );
    }
    if( pDef->GetType() == SbxVARIANT && !( bFunc || bProp ) )
    {
        pDef->SetType( SbxEMPTY );
    }
    return pDef;
}

// DECLARE

void SbiParser::Declare()
{
    DefDeclare( false );
}

void SbiParser::DefDeclare( bool bPrivate )
{
    Next();
    if( eCurTok == PTRSAFE )
        Next();

    if( eCurTok != SUB && eCurTok != FUNCTION )
    {
      Error( ERRCODE_BASIC_UNEXPECTED, eCurTok );
    }
    else
    {
        bool bFunction = (eCurTok == FUNCTION);

        SbiProcDef* pDef = ProcDecl( true );
        if( pDef )
        {
            if( pDef->GetLib().isEmpty() )
            {
                Error( ERRCODE_BASIC_EXPECTED, LIB );
            }
            // Is it already there?
            SbiSymDef* pOld = aPublics.Find( pDef->GetName() );
            if( pOld )
            {
                SbiProcDef* p = pOld->GetProcDef();
                if( !p )
                {
                    // Declared as a variable
                    Error( ERRCODE_BASIC_BAD_DECLARATION, pDef->GetName() );
                    delete pDef;
                    pDef = nullptr;
                }
                else
                {
                    pDef->Match( p );
                }
            }
            else
            {
                aPublics.Add( pDef );
            }
            if ( pDef )
            {
                pDef->SetPublic( !bPrivate );

                // New declare handling
                if( !pDef->GetLib().isEmpty())
                {
                    if( bNewGblDefs && nGblChain == 0 )
                    {
                        nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 );
                        bNewGblDefs = false;
                    }

                    sal_uInt16 nSavLine = nLine;
                    aGen.Statement();
                    pDef->Define();
                    pDef->SetLine1( nSavLine );
                    pDef->SetLine2( nSavLine );

                    SbiSymPool& rPool = pDef->GetParams();
                    sal_uInt16 nParCount = rPool.GetSize();

                    SbxDataType eType = pDef->GetType();
                    if( bFunction )
                    {
                        aGen.Gen( SbiOpcode::PARAM_, 0, sal::static_int_cast< sal_uInt16 >( eType ) );
                    }
                    if( nParCount > 1 )
                    {
                        aGen.Gen( SbiOpcode::ARGC_ );

                        for( sal_uInt16 i = 1 ; i < nParCount ; ++i )
                        {
                            SbiSymDef* pParDef = rPool.Get( i );
                            SbxDataType eParType = pParDef->GetType();

                            aGen.Gen( SbiOpcode::PARAM_, i, sal::static_int_cast< sal_uInt16 >( eParType ) );
                            aGen.Gen( SbiOpcode::ARGV_ );

                            sal_uInt16 nTyp = sal::static_int_cast< sal_uInt16 >( pParDef->GetType() );
                            if( pParDef->IsByVal() )
                            {
                                // Reset to avoid additional byval in call to wrapper function
                                pParDef->SetByVal( false );
                                nTyp |= 0x8000;
                            }
                            aGen.Gen( SbiOpcode::ARGTYP_, nTyp );
                        }
                    }

                    aGen.Gen( SbiOpcode::LIB_, aGblStrings.Add( pDef->GetLib() ) );

                    SbiOpcode eOp = pDef->IsCdecl() ? SbiOpcode::CALLC_ : SbiOpcode::CALL_;
                    sal_uInt16 nId = pDef->GetId();
                    if( !pDef->GetAlias().isEmpty() )
                    {
                        nId = ( nId & 0x8000 ) | aGblStrings.Add( pDef->GetAlias() );
                    }
                    if( nParCount > 1 )
                    {
                        nId |= 0x8000;
                    }
                    aGen.Gen( eOp, nId, sal::static_int_cast< sal_uInt16 >( eType ) );

                    if( bFunction )
                    {
                        aGen.Gen( SbiOpcode::PUT_ );
                    }
                    aGen.Gen( SbiOpcode::LEAVE_ );
                }
            }
        }
    }
}

void SbiParser::Attribute()
{
    // TODO: Need to implement the method as an attributed object.
    while( Next() != EQ )
    {
        if( Next() != DOT)
        {
            break;
        }
    }

    if( eCurTok != EQ )
    {
        Error( ERRCODE_BASIC_SYNTAX );
    }
    else
    {
        SbiExpression aValue( this );
    }
    // Don't generate any code - just discard it.
}

// Call of a SUB or a FUNCTION

void SbiParser::Call()
{
    SbiExpression aVar( this, SbSYMBOL );
    aVar.Gen( FORCE_CALL );
    aGen.Gen( SbiOpcode::GET_ );
}

// SUB/FUNCTION

void SbiParser::SubFunc()
{
    DefProc( false, false );
}

// Read in of a procedure

void SbiParser::DefProc( bool bStatic, bool bPrivate )
{
    sal_uInt16 l1 = nLine;
    bool bSub = ( eCurTok == SUB );
    bool bProperty = ( eCurTok == PROPERTY );
    PropertyMode ePropertyMode = PropertyMode::NONE;
    if( bProperty )
    {
        Next();
        if( eCurTok == GET )
        {
            ePropertyMode = PropertyMode::Get;
        }
        else if( eCurTok == LET )
        {
            ePropertyMode = PropertyMode::Let;
        }
        else if( eCurTok == SET )
        {
            ePropertyMode = PropertyMode::Set;
        }
        else
        {
            Error( ERRCODE_BASIC_EXPECTED, u"Get or Let or Set"_ustr );
        }
    }

    SbiToken eExit = eCurTok;
    SbiProcDef* pDef = ProcDecl( false );
    if( !pDef )
    {
        return;
    }
    pDef->setPropertyMode( ePropertyMode );

    // Is the Proc already declared?
    SbiSymDef* pOld = aPublics.Find( pDef->GetName() );
    if( pOld )
    {
        pProc = pOld->GetProcDef();
        if( !pProc )
        {
            // Declared as a variable
            Error( ERRCODE_BASIC_BAD_DECLARATION, pDef->GetName() );
            delete pDef;
            return;
        }
        // #100027: Multiple declaration -> Error
        // #112787: Not for setup, REMOVE for 8
        else if( pProc->IsUsedForProcDecl() )
        {
            PropertyMode ePropMode = pDef->getPropertyMode();
            if( ePropMode == PropertyMode::NONE || ePropMode == pProc->getPropertyMode() )
            {
                Error( ERRCODE_BASIC_PROC_DEFINED, pDef->GetName() );
                delete pDef;
                return;
            }
        }

        pDef->Match( pProc );
    }
    else
    {
        aPublics.Add( pDef );
    }
    assert(pDef);
    pProc = pDef;
    pProc->SetPublic( !bPrivate );

    // Now we set the search hierarchy for symbols as well as the
    // current procedure.
    aPublics.SetProcId( pProc->GetId() );
    pProc->GetParams().SetParent( &aPublics );
    if( bStatic )
    {
        if ( bVBASupportOn )
        {
            pProc->SetStatic();
        }
        else
        {
            Error( ERRCODE_BASIC_NOT_IMPLEMENTED ); // STATIC SUB ...
        }
    }
    else
    {
        pProc->SetStatic( false );
    }
    // Normal case: Local variable->parameter->global variable
    pProc->GetLocals().SetParent( &pProc->GetParams() );
    pPool = &pProc->GetLocals();

    pProc->Define();
    OpenBlock( eExit );
    StmntBlock( bSub ? ENDSUB : (bProperty ? ENDPROPERTY : ENDFUNC) );
    sal_uInt16 l2 = nLine;
    pProc->SetLine1( l1 );
    pProc->SetLine2( l2 );
    pPool = &aPublics;
    aPublics.SetProcId( 0 );
    // Open labels?
    pProc->GetLabels().CheckRefs();
    CloseBlock();
    aGen.Gen( SbiOpcode::LEAVE_ );
    pProc = nullptr;
}

// STATIC variable|procedure

void SbiParser::Static()
{
    DefStatic( false );
}

void SbiParser::DefStatic( bool bPrivate )
{
    SbiSymPool* p;

    switch( Peek() )
    {
    case SUB:
    case FUNCTION:
    case PROPERTY:
        // End global chain if necessary (not done in
        // SbiParser::Parse() under these conditions
        if( bNewGblDefs && nGblChain == 0 )
        {
            nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 );
            bNewGblDefs = false;
        }
        Next();
        DefProc( true, bPrivate );
        break;
    default:
        if( !pProc )
        {
            Error( ERRCODE_BASIC_NOT_IN_SUBR );
        }
        // Reset the Pool, so that STATIC-Declarations go into the
        // global Pool
        p = pPool;
        pPool = &aPublics;
        DefVar( SbiOpcode::STATIC_, true );
        pPool = p;
        break;
    }
}

bool SbiParser::IsUnoInterface(const OUString& sTypeName)
{
    try
    {
        return css::reflection::theCoreReflection::get(
            comphelper::getProcessComponentContext())->forName(sTypeName).is();
    }
    catch(const Exception&)
    {
        OSL_FAIL("Could not create reflection.CoreReflection.");
    }
    return false;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
