diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /basic/source/comp | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/4%7.4.7.tar.xz libreoffice-upstream/4%7.4.7.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'basic/source/comp')
-rw-r--r-- | basic/source/comp/basiccharclass.cxx | 58 | ||||
-rw-r--r-- | basic/source/comp/buffer.cxx | 95 | ||||
-rw-r--r-- | basic/source/comp/codegen.cxx | 585 | ||||
-rw-r--r-- | basic/source/comp/dim.cxx | 1363 | ||||
-rw-r--r-- | basic/source/comp/exprgen.cxx | 281 | ||||
-rw-r--r-- | basic/source/comp/exprnode.cxx | 480 | ||||
-rw-r--r-- | basic/source/comp/exprtree.cxx | 1123 | ||||
-rw-r--r-- | basic/source/comp/io.cxx | 309 | ||||
-rw-r--r-- | basic/source/comp/loops.cxx | 572 | ||||
-rw-r--r-- | basic/source/comp/parser.cxx | 889 | ||||
-rw-r--r-- | basic/source/comp/sbcomp.cxx | 85 | ||||
-rw-r--r-- | basic/source/comp/scanner.cxx | 717 | ||||
-rw-r--r-- | basic/source/comp/symtbl.cxx | 534 | ||||
-rw-r--r-- | basic/source/comp/token.cxx | 571 |
14 files changed, 7662 insertions, 0 deletions
diff --git a/basic/source/comp/basiccharclass.cxx b/basic/source/comp/basiccharclass.cxx new file mode 100644 index 000000000..c06bd8bb6 --- /dev/null +++ b/basic/source/comp/basiccharclass.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <basiccharclass.hxx> + +#include <unotools/charclass.hxx> +#include <rtl/character.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +bool BasicCharClass::isLetter( sal_Unicode c ) +{ + //All characters from C0 to FF are letters except D7 and F7 + return (c < 0xFF ? + ( (c >= 0xC0) && (c != 0xD7) && (c != 0xF7) ) : + BasicCharClass::isLetterUnicode( c )); +} + +bool BasicCharClass::isLetterUnicode( sal_Unicode c ) +{ + static CharClass* pCharClass = new CharClass( Application::GetSettings().GetLanguageTag() ); + // can we get pCharClass to accept a sal_Unicode instead of this waste? + return pCharClass->isLetter( OUString(c), 0 ); +} + +bool BasicCharClass::isAlpha( sal_Unicode c, bool bCompatible ) +{ + return rtl::isAsciiAlpha(c) + || (bCompatible && BasicCharClass::isLetter( c )); +} + +bool BasicCharClass::isAlphaNumeric( sal_Unicode c, bool bCompatible ) +{ + return rtl::isAsciiDigit( c ) || BasicCharClass::isAlpha( c, bCompatible ); +} + +bool BasicCharClass::isWhitespace( sal_Unicode c ) +{ + return (c == ' ') || (c == '\t') || (c == '\f'); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/buffer.cxx b/basic/source/comp/buffer.cxx new file mode 100644 index 000000000..ff6b5173f --- /dev/null +++ b/basic/source/comp/buffer.cxx @@ -0,0 +1,95 @@ +/* -*- 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 <buffer.hxx> +#include <basic/sberrors.hxx> + +namespace +{ +const sal_uInt32 UP_LIMIT=0xFFFFFF00; + +template <class I, typename T> void write(I it, T n) +{ + *it = static_cast<sal_uInt8>(n & 0xFF); + // coverity[stray_semicolon : FALSE] - coverity parse error + if constexpr (sizeof(n) > 1) + { + for (std::size_t i = 1; i < sizeof(n); ++i) + { + n >>= 8; + *++it = static_cast<sal_uInt8>(n & 0xFF); + } + } +} +} + +template <typename T> void SbiBuffer::append(T n) +{ + if (m_aErrCode) + return; + if ((m_aBuf.size() + sizeof(n)) > UP_LIMIT) + { + m_aErrCode = ERRCODE_BASIC_PROG_TOO_LARGE; + m_aBuf.clear(); + return; + } + m_aBuf.reserve(m_aBuf.size() + sizeof(n)); + write(std::back_inserter(m_aBuf), n); +} + +void SbiBuffer::operator+=(sal_Int8 n) { append(n); } +void SbiBuffer::operator+=(sal_Int16 n) { append(n); } +void SbiBuffer::operator+=(sal_uInt8 n) { append(n); } +void SbiBuffer::operator+=(sal_uInt16 n) { append(n); } +void SbiBuffer::operator+=(sal_uInt32 n) { append(n); } +void SbiBuffer::operator+=(sal_Int32 n) { append(n); } + +// Patch of a Location + +void SbiBuffer::Patch( sal_uInt32 off, sal_uInt32 val ) +{ + if (m_aErrCode) + return; + if ((off + sizeof(sal_uInt32)) <= GetSize()) + write(m_aBuf.begin() + off, val); +} + +// Forward References upon label and procedures +// establish a linkage. The beginning of the linkage is at the passed parameter, +// the end of the linkage is 0. + +void SbiBuffer::Chain( sal_uInt32 off ) +{ + if (m_aErrCode) + return; + for (sal_uInt32 i = off; i;) + { + if ((i + sizeof(sal_uInt32)) > GetSize()) + { + m_aErrCode = ERRCODE_BASIC_INTERNAL_ERROR; + m_sErrMsg = "BACKCHAIN"; + break; + } + auto ip = m_aBuf.begin() + i; + i = ip[0] | (ip[1] << 8) | (ip[2] << 16) | (ip[3] << 24); + write(ip, GetSize()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/codegen.cxx b/basic/source/comp/codegen.cxx new file mode 100644 index 000000000..9f2f4960b --- /dev/null +++ b/basic/source/comp/codegen.cxx @@ -0,0 +1,585 @@ +/* -*- 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/sbx.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbmod.hxx> +#include <image.hxx> +#include <codegen.hxx> +#include <parser.hxx> +#include <sbintern.hxx> +#include <cstddef> +#include <limits> +#include <algorithm> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/script/ModuleType.hpp> + +// nInc is the increment size of the buffers + +SbiCodeGen::SbiCodeGen(SbModule& r, SbiParser* p) + : pParser(p) + , rMod(r) + , nLine(0) + , nCol(0) + , nForLevel(0) + , bStmnt(false) +{ +} + +sal_uInt32 SbiCodeGen::GetPC() const +{ + return aCode.GetSize(); +} + +// memorize the statement + +void SbiCodeGen::Statement() +{ + if( pParser->IsCodeCompleting() ) + return; + + bStmnt = true; + + nLine = pParser->GetLine(); + nCol = pParser->GetCol1(); + + // #29955 Store the information of the for-loop-layer + // in the upper Byte of the column + nCol = (nCol & 0xff) + 0x100 * nForLevel; +} + +// Mark the beginning of a statement + +void SbiCodeGen::GenStmnt() +{ + if( pParser->IsCodeCompleting() ) + return; + + if( bStmnt ) + { + bStmnt = false; + Gen( SbiOpcode::STMNT_, nLine, nCol ); + } +} + +// The Gen-Routines return the offset of the 1. operand, +// so that jumps can sink their backchain there. + +sal_uInt32 SbiCodeGen::Gen( SbiOpcode eOpcode ) +{ + if( pParser->IsCodeCompleting() ) + return 0; + +#ifdef DBG_UTIL + if( eOpcode < SbiOpcode::SbOP0_START || eOpcode > SbiOpcode::SbOP0_END ) + pParser->Error( ERRCODE_BASIC_INTERNAL_ERROR, "OPCODE1" ); +#endif + GenStmnt(); + aCode += static_cast<sal_uInt8>(eOpcode); + return GetPC(); +} + +sal_uInt32 SbiCodeGen::Gen( SbiOpcode eOpcode, sal_uInt32 nOpnd ) +{ + if( pParser->IsCodeCompleting() ) + return 0; + +#ifdef DBG_UTIL + if( eOpcode < SbiOpcode::SbOP1_START || eOpcode > SbiOpcode::SbOP1_END ) + pParser->Error( ERRCODE_BASIC_INTERNAL_ERROR, "OPCODE2" ); +#endif + GenStmnt(); + aCode += static_cast<sal_uInt8>(eOpcode); + sal_uInt32 n = GetPC(); + aCode += nOpnd; + return n; +} + +sal_uInt32 SbiCodeGen::Gen( SbiOpcode eOpcode, sal_uInt32 nOpnd1, sal_uInt32 nOpnd2 ) +{ + if( pParser->IsCodeCompleting() ) + return 0; + +#ifdef DBG_UTIL + if( eOpcode < SbiOpcode::SbOP2_START || eOpcode > SbiOpcode::SbOP2_END ) + pParser->Error( ERRCODE_BASIC_INTERNAL_ERROR, "OPCODE3" ); +#endif + GenStmnt(); + aCode += static_cast<sal_uInt8>(eOpcode); + sal_uInt32 n = GetPC(); + aCode += nOpnd1; + aCode += nOpnd2; + return n; +} + +// Storing of the created image in the module + +void SbiCodeGen::Save() +{ + if( pParser->IsCodeCompleting() ) + return; + + std::unique_ptr<SbiImage> p(new SbiImage); + rMod.StartDefinitions(); + // OPTION BASE-Value: + p->nDimBase = pParser->nBase; + // OPTION take over the EXPLICIT-Flag + if( pParser->bExplicit ) + p->SetFlag( SbiImageFlags::EXPLICIT ); + + int nIfaceCount = 0; + if( rMod.mnType == css::script::ModuleType::CLASS ) + { + rMod.bIsProxyModule = true; + p->SetFlag( SbiImageFlags::CLASSMODULE ); + GetSbData()->pClassFac->AddClassModule( &rMod ); + + nIfaceCount = pParser->aIfaceVector.size(); + if( !rMod.pClassData ) + rMod.pClassData.reset(new SbClassData); + if( nIfaceCount ) + { + for( int i = 0 ; i < nIfaceCount ; i++ ) + { + const OUString& rIfaceName = pParser->aIfaceVector[i]; + SbxVariable* pIfaceVar = new SbxVariable( SbxVARIANT ); + pIfaceVar->SetName( rIfaceName ); + SbxArray* pIfaces = rMod.pClassData->mxIfaces.get(); + pIfaces->Insert( pIfaceVar, pIfaces->Count() ); + } + } + + rMod.pClassData->maRequiredTypes = pParser->aRequiredTypes; + } + else + { + GetSbData()->pClassFac->RemoveClassModule( &rMod ); + // Only a ClassModule can revert to Normal + if ( rMod.mnType == css::script::ModuleType::CLASS ) + { + rMod.mnType = css::script::ModuleType::NORMAL; + } + rMod.bIsProxyModule = false; + } + + // GlobalCode-Flag + if( pParser->HasGlobalCode() ) + { + p->SetFlag( SbiImageFlags::INITCODE ); + } + // The entry points: + for( SbiSymDef* pDef = pParser->aPublics.First(); pDef; + pDef = pParser->aPublics.Next() ) + { + SbiProcDef* pProc = pDef->GetProcDef(); + if( pProc && pProc->IsDefined() ) + { + OUString aProcName = pProc->GetName(); + OUStringBuffer aIfaceProcName; + OUString aIfaceName; + sal_uInt16 nPassCount = 1; + if( nIfaceCount ) + { + int nPropPrefixFound = aProcName.indexOf("Property "); + std::u16string_view aPureProcName = aProcName; + std::u16string_view aPropPrefix; + if( nPropPrefixFound == 0 ) + { + aPropPrefix = aProcName.subView( 0, 13 ); // 13 == Len( "Property ?et " ) + aPureProcName = aProcName.subView( 13 ); + } + for( int i = 0 ; i < nIfaceCount ; i++ ) + { + const OUString& rIfaceName = pParser->aIfaceVector[i]; + bool bFound = o3tl::starts_with(aPureProcName, rIfaceName ); + if( bFound && aPureProcName[rIfaceName.getLength()] == '_' ) + { + if( nPropPrefixFound == 0 ) + { + aIfaceProcName.append(aPropPrefix); + } + aIfaceProcName.append(aPureProcName.substr(rIfaceName.getLength() + 1) ); + aIfaceName = rIfaceName; + nPassCount = 2; + break; + } + } + } + SbMethod* pMeth = nullptr; + for( sal_uInt16 nPass = 0 ; nPass < nPassCount ; nPass++ ) + { + if( nPass == 1 ) + { + aProcName = aIfaceProcName.toString(); + } + PropertyMode ePropMode = pProc->getPropertyMode(); + if( ePropMode != PropertyMode::NONE ) + { + SbxDataType ePropType = SbxEMPTY; + switch( ePropMode ) + { + case PropertyMode::Get: + ePropType = pProc->GetType(); + break; + case PropertyMode::Let: + { + // type == type of first parameter + ePropType = SbxVARIANT; // Default + SbiSymPool* pPool = &pProc->GetParams(); + if( pPool->GetSize() > 1 ) + { + SbiSymDef* pPar = pPool->Get( 1 ); + if( pPar ) + { + ePropType = pPar->GetType(); + } + } + break; + } + case PropertyMode::Set: + ePropType = SbxOBJECT; + break; + default: + OSL_FAIL("Illegal PropertyMode"); + break; + } + OUString aPropName = pProc->GetPropName(); + if( nPass == 1 ) + { + aPropName = aPropName.copy( aIfaceName.getLength() + 1 ); + } + rMod.GetProcedureProperty( aPropName, ePropType ); + } + if( nPass == 1 ) + { + rMod.GetIfaceMapperMethod( aProcName, pMeth ); + } + else + { + pMeth = rMod.GetMethod( aProcName, pProc->GetType() ); + + if( !pProc->IsPublic() ) + { + pMeth->SetFlag( SbxFlagBits::Private ); + } + // Declare? -> Hidden + if( !pProc->GetLib().isEmpty()) + { + pMeth->SetFlag( SbxFlagBits::Hidden ); + } + pMeth->nStart = pProc->GetAddr(); + pMeth->nLine1 = pProc->GetLine1(); + pMeth->nLine2 = pProc->GetLine2(); + // The parameter: + SbxInfo* pInfo = pMeth->GetInfo(); + OUString aHelpFile, aComment; + sal_uInt32 nHelpId = 0; + if( pInfo ) + { + // Rescue the additional data + aHelpFile = pInfo->GetHelpFile(); + aComment = pInfo->GetComment(); + nHelpId = pInfo->GetHelpId(); + } + // And reestablish the parameter list + pInfo = new SbxInfo( aHelpFile, nHelpId ); + pInfo->SetComment( aComment ); + SbiSymPool* pPool = &pProc->GetParams(); + // The first element is always the value of the function! + for( sal_uInt16 i = 1; i < pPool->GetSize(); i++ ) + { + SbiSymDef* pPar = pPool->Get( i ); + SbxDataType t = pPar->GetType(); + if( !pPar->IsByVal() ) + { + t = static_cast<SbxDataType>( t | SbxBYREF ); + } + if( pPar->GetDims() ) + { + t = static_cast<SbxDataType>( t | SbxARRAY ); + } + // #33677 hand-over an Optional-Info + SbxFlagBits nFlags = SbxFlagBits::Read; + if( pPar->IsOptional() ) + { + nFlags |= SbxFlagBits::Optional; + } + pInfo->AddParam( pPar->GetName(), t, nFlags ); + + sal_uInt32 nUserData = 0; + sal_uInt16 nDefaultId = pPar->GetDefaultId(); + if( nDefaultId ) + { + nUserData |= nDefaultId; + } + if( pPar->IsParamArray() ) + { + nUserData |= PARAM_INFO_PARAMARRAY; + } + if( pPar->IsWithBrackets() ) + { + nUserData |= PARAM_INFO_WITHBRACKETS; + } + SbxParamInfo* pParam = nullptr; + if( nUserData ) + { + pParam = const_cast<SbxParamInfo*>(pInfo->GetParam( i )); + } + if( pParam ) + { + pParam->nUserData = nUserData; + } + } + pMeth->SetInfo( pInfo ); + } + } // for( iPass... + } + } + if (aCode.GetErrCode()) + { + pParser->Error(aCode.GetErrCode(), aCode.GetErrMessage()); + } + // The code + p->AddCode(aCode.GetBuffer()); + + // The global StringPool. 0 is not occupied. + SbiStringPool* pPool = &pParser->aGblStrings; + sal_uInt16 nSize = pPool->GetSize(); + p->MakeStrings( nSize ); + sal_uInt32 i; + for( i = 1; i <= nSize; i++ ) + { + p->AddString( pPool->Find( i ) ); + } + // Insert types + sal_uInt32 nCount = pParser->rTypeArray->Count(); + for (i = 0; i < nCount; i++) + { + p->AddType(static_cast<SbxObject *>(pParser->rTypeArray->Get(i))); + } + // Insert enum objects + nCount = pParser->rEnumArray->Count(); + for (i = 0; i < nCount; i++) + { + p->AddEnum(static_cast<SbxObject *>(pParser->rEnumArray->Get(i))); + } + if( !p->IsError() ) + { + rMod.pImage = std::move(p); + } + rMod.EndDefinitions(); +} + +namespace { + +template < class T > +class PCodeVisitor +{ +public: + virtual ~PCodeVisitor(); + + virtual void start( const sal_uInt8* pStart ) = 0; + virtual void processOpCode0( SbiOpcode eOp ) = 0; + virtual void processOpCode1( SbiOpcode eOp, T nOp1 ) = 0; + virtual void processOpCode2( SbiOpcode eOp, T nOp1, T nOp2 ) = 0; + virtual bool processParams() = 0; +}; + +} + +template <class T> PCodeVisitor< T >::~PCodeVisitor() +{} + +namespace { + +template <class T> +class PCodeBufferWalker +{ +private: + T m_nBytes; + const sal_uInt8* m_pCode; + static T readParam( sal_uInt8 const *& pCode ) + { + T nOp1=0; + for ( std::size_t i=0; i<sizeof( T ); ++i ) + nOp1 |= *pCode++ << ( i * 8); + return nOp1; + } +public: + PCodeBufferWalker( const sal_uInt8* pCode, T nBytes ): m_nBytes( nBytes ), m_pCode( pCode ) + { + } + void visitBuffer( PCodeVisitor< T >& visitor ) + { + const sal_uInt8* pCode = m_pCode; + if ( !pCode ) + return; + const sal_uInt8* pEnd = pCode + m_nBytes; + visitor.start( m_pCode ); + T nOp1 = 0, nOp2 = 0; + for( ; pCode < pEnd; ) + { + SbiOpcode eOp = static_cast<SbiOpcode>(*pCode++); + + if ( eOp <= SbiOpcode::SbOP0_END ) + visitor.processOpCode0( eOp ); + else if( eOp >= SbiOpcode::SbOP1_START && eOp <= SbiOpcode::SbOP1_END ) + { + if ( visitor.processParams() ) + nOp1 = readParam( pCode ); + else + pCode += sizeof( T ); + visitor.processOpCode1( eOp, nOp1 ); + } + else if( eOp >= SbiOpcode::SbOP2_START && eOp <= SbiOpcode::SbOP2_END ) + { + if ( visitor.processParams() ) + { + nOp1 = readParam( pCode ); + nOp2 = readParam( pCode ); + } + else + pCode += ( sizeof( T ) * 2 ); + visitor.processOpCode2( eOp, nOp1, nOp2 ); + } + } + } +}; + +template < class T, class S > +class OffSetAccumulator : public PCodeVisitor< T > +{ + T m_nNumOp0; + T m_nNumSingleParams; + T m_nNumDoubleParams; +public: + + OffSetAccumulator() : m_nNumOp0(0), m_nNumSingleParams(0), m_nNumDoubleParams(0){} + virtual void start( const sal_uInt8* /*pStart*/ ) override {} + virtual void processOpCode0( SbiOpcode /*eOp*/ ) override { ++m_nNumOp0; } + virtual void processOpCode1( SbiOpcode /*eOp*/, T /*nOp1*/ ) override { ++m_nNumSingleParams; } + virtual void processOpCode2( SbiOpcode /*eOp*/, T /*nOp1*/, T /*nOp2*/ ) override { ++m_nNumDoubleParams; } + S offset() + { + typedef decltype(T(1) + S(1)) larger_t; // type capable to hold both value ranges of T and S + T result = 0 ; + static const S max = std::numeric_limits< S >::max(); + result = m_nNumOp0 + ( ( sizeof(S) + 1 ) * m_nNumSingleParams ) + ( (( sizeof(S) * 2 )+ 1 ) * m_nNumDoubleParams ); + return std::min<larger_t>(max, result); + } + virtual bool processParams() override { return false; } +}; + + +template < class T, class S > +class BufferTransformer : public PCodeVisitor< T > +{ + const sal_uInt8* m_pStart; + SbiBuffer m_ConvertedBuf; +public: + BufferTransformer():m_pStart(nullptr) {} + virtual void start( const sal_uInt8* pStart ) override { m_pStart = pStart; } + virtual void processOpCode0( SbiOpcode eOp ) override + { + m_ConvertedBuf += static_cast<sal_uInt8>(eOp); + } + virtual void processOpCode1( SbiOpcode eOp, T nOp1 ) override + { + m_ConvertedBuf += static_cast<sal_uInt8>(eOp); + switch( eOp ) + { + case SbiOpcode::JUMP_: + case SbiOpcode::JUMPT_: + case SbiOpcode::JUMPF_: + case SbiOpcode::GOSUB_: + case SbiOpcode::CASEIS_: + case SbiOpcode::RETURN_: + case SbiOpcode::ERRHDL_: + case SbiOpcode::TESTFOR_: + nOp1 = static_cast<T>( convertBufferOffSet(m_pStart, nOp1) ); + break; + case SbiOpcode::RESUME_: + if ( nOp1 > 1 ) + nOp1 = static_cast<T>( convertBufferOffSet(m_pStart, nOp1) ); + break; + default: + break; + + } + m_ConvertedBuf += static_cast<S>(nOp1); + } + virtual void processOpCode2( SbiOpcode eOp, T nOp1, T nOp2 ) override + { + m_ConvertedBuf += static_cast<sal_uInt8>(eOp); + if ( eOp == SbiOpcode::CASEIS_ && nOp1 ) + nOp1 = static_cast<T>( convertBufferOffSet(m_pStart, nOp1) ); + m_ConvertedBuf += static_cast<S>(nOp1); + m_ConvertedBuf += static_cast<S>(nOp2); + + } + virtual bool processParams() override { return true; } + // yeuch, careful here, you can only call + // GetBuffer on the returned SbiBuffer once, also + // you (as the caller) get to own the memory + SbiBuffer& buffer() + { + return m_ConvertedBuf; + } + static S convertBufferOffSet( const sal_uInt8* pStart, T nOp1 ) + { + PCodeBufferWalker< T > aBuff( pStart, nOp1); + OffSetAccumulator< T, S > aVisitor; + aBuff.visitBuffer( aVisitor ); + return aVisitor.offset(); + } +}; + +} + +sal_uInt32 +SbiCodeGen::calcNewOffSet( sal_uInt8 const * pCode, sal_uInt16 nOffset ) +{ + return BufferTransformer< sal_uInt16, sal_uInt32 >::convertBufferOffSet( pCode, nOffset ); +} + +sal_uInt16 +SbiCodeGen::calcLegacyOffSet( sal_uInt8 const * pCode, sal_uInt32 nOffset ) +{ + return BufferTransformer< sal_uInt32, sal_uInt16 >::convertBufferOffSet( pCode, nOffset ); +} + +template <class T, class S> +void +PCodeBuffConvertor<T,S>::convert() +{ + PCodeBufferWalker< T > aBuf( m_pStart, m_nSize ); + BufferTransformer< T, S > aTrnsfrmer; + aBuf.visitBuffer( aTrnsfrmer ); + // TODO: handle buffer errors + m_aCnvtdBuf = aTrnsfrmer.buffer().GetBuffer(); +} + +template class PCodeBuffConvertor< sal_uInt16, sal_uInt32 >; +template class PCodeBuffConvertor< sal_uInt32, sal_uInt16 >; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/dim.cxx b/basic/source/comp/dim.cxx new file mode 100644 index 000000000..cbc25b015 --- /dev/null +++ b/basic/source/comp/dim.cxx @@ -0,0 +1,1363 @@ +/* -*- 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 && pDim ) + { + if(pDim->GetDims() ) + Error( ERRCODE_BASIC_EXPECTED, "()" ); + } + else if( ppDim ) + *ppDim = std::move(pDim); + 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( "Name", SbxClassType::DontCare ); + pType->Remove( "Parent", 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( "Name", SbxClassType::DontCare ); + pEnum->Remove( "Parent", 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 ) + { + auto pDefaultExpr = std::make_unique<SbiConstExpression>(this); + SbxDataType eType2 = pDefaultExpr->GetType(); + + sal_uInt16 nStringId; + if( eType2 == SbxSTRING ) + { + nStringId = aGblStrings.Add( pDefaultExpr->GetString() ); + } + else + { + nStringId = aGblStrings.Add( pDefaultExpr->GetValue(), eType2 ); + } + pPar->SetDefaultId( nStringId ); + pDefaultExpr.reset(); + + 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, "Get or Let or Set" ); + } + } + + 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: */ diff --git a/basic/source/comp/exprgen.cxx b/basic/source/comp/exprgen.cxx new file mode 100644 index 000000000..76f1ab776 --- /dev/null +++ b/basic/source/comp/exprgen.cxx @@ -0,0 +1,281 @@ +/* -*- 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 <codegen.hxx> +#include <expr.hxx> +#include <parser.hxx> + +// Transform table for token operators and opcodes + +namespace { + +struct OpTable { + SbiToken eTok; // Token + SbiOpcode eOp; // Opcode +}; + +} + +const OpTable aOpTable [] = { + { EXPON,SbiOpcode::EXP_ }, + { MUL, SbiOpcode::MUL_ }, + { DIV, SbiOpcode::DIV_ }, + { IDIV, SbiOpcode::IDIV_ }, + { MOD, SbiOpcode::MOD_ }, + { PLUS, SbiOpcode::PLUS_ }, + { MINUS,SbiOpcode::MINUS_ }, + { EQ, SbiOpcode::EQ_ }, + { NE, SbiOpcode::NE_ }, + { LE, SbiOpcode::LE_ }, + { GE, SbiOpcode::GE_ }, + { LT, SbiOpcode::LT_ }, + { GT, SbiOpcode::GT_ }, + { AND, SbiOpcode::AND_ }, + { OR, SbiOpcode::OR_ }, + { XOR, SbiOpcode::XOR_ }, + { EQV, SbiOpcode::EQV_ }, + { IMP, SbiOpcode::IMP_ }, + { NOT, SbiOpcode::NOT_ }, + { NEG, SbiOpcode::NEG_ }, + { CAT, SbiOpcode::CAT_ }, + { LIKE, SbiOpcode::LIKE_ }, + { IS, SbiOpcode::IS_ }, + { NIL, SbiOpcode::NOP_ }}; + +// Output of an element +void SbiExprNode::Gen( SbiCodeGen& rGen, RecursiveMode eRecMode ) +{ + sal_uInt16 nStringId; + + if( IsConstant() ) + { + switch( GetType() ) + { + case SbxEMPTY: + rGen.Gen( SbiOpcode::EMPTY_ ); + break; + case SbxSTRING: + nStringId = rGen.GetParser()->aGblStrings.Add( aStrVal ); + rGen.Gen( SbiOpcode::SCONST_, nStringId ); + break; + default: + // tdf#131296 - generate SbiOpcode::NUMBER_ instead of SbiOpcode::CONST_ + // for SbxINTEGER and SbxLONG including their numeric value and its data type, + // which will be restored in SbiRuntime::StepLOADNC. + nStringId = rGen.GetParser()->aGblStrings.Add( nVal, eType ); + rGen.Gen( SbiOpcode::NUMBER_, nStringId ); + break; + } + } + else if( IsOperand() ) + { + SbiExprNode* pWithParent_ = nullptr; + SbiOpcode eOp; + if( aVar.pDef->GetScope() == SbPARAM ) + { + eOp = SbiOpcode::PARAM_; + if( aVar.pDef->GetPos() == 0 ) + { + bool bTreatFunctionAsParam = true; + if( eRecMode == FORCE_CALL ) + { + bTreatFunctionAsParam = false; + } + else if( eRecMode == UNDEFINED ) + { + if( aVar.pPar && aVar.pPar->IsBracket() ) + { + bTreatFunctionAsParam = false; + } + } + if( !bTreatFunctionAsParam ) + { + eOp = aVar.pDef->IsGlobal() ? SbiOpcode::FIND_G_ : SbiOpcode::FIND_; + } + } + } + // special treatment for WITH + else if( (pWithParent_ = pWithParent) != nullptr ) + { + eOp = SbiOpcode::ELEM_; // .-Term in WITH + } + else + { + eOp = ( aVar.pDef->GetScope() == SbRTL ) ? SbiOpcode::RTL_ : + (aVar.pDef->IsGlobal() ? SbiOpcode::FIND_G_ : SbiOpcode::FIND_); + } + + if( eOp == SbiOpcode::FIND_ ) + { + + SbiProcDef* pProc = aVar.pDef->GetProcDef(); + if ( rGen.GetParser()->bClassModule ) + { + eOp = SbiOpcode::FIND_CM_; + } + else if ( aVar.pDef->IsStatic() || (pProc && pProc->IsStatic()) ) + { + eOp = SbiOpcode::FIND_STATIC_; + } + } + for( SbiExprNode* p = this; p; p = p->aVar.pNext ) + { + if( p == this && pWithParent_ != nullptr ) + { + pWithParent_->Gen(rGen); + } + p->GenElement( rGen, eOp ); + eOp = SbiOpcode::ELEM_; + } + } + else if( eNodeType == SbxTYPEOF ) + { + pLeft->Gen(rGen); + rGen.Gen( SbiOpcode::TESTCLASS_, nTypeStrId ); + } + else if( eNodeType == SbxNEW ) + { + rGen.Gen( SbiOpcode::CREATE_, 0, nTypeStrId ); + } + else + { + pLeft->Gen(rGen); + if( pRight ) + { + pRight->Gen(rGen); + } + for( const OpTable* p = aOpTable; p->eTok != NIL; p++ ) + { + if( p->eTok == eTok ) + { + rGen.Gen( p->eOp ); break; + } + } + } +} + +// Output of an operand element + +void SbiExprNode::GenElement( SbiCodeGen& rGen, SbiOpcode eOp ) +{ +#ifdef DBG_UTIL + if ((eOp < SbiOpcode::RTL_ || eOp > SbiOpcode::CALLC_) && eOp != SbiOpcode::FIND_G_ && eOp != SbiOpcode::FIND_CM_ && eOp != SbiOpcode::FIND_STATIC_) + rGen.GetParser()->Error( ERRCODE_BASIC_INTERNAL_ERROR, "Opcode" ); +#endif + SbiSymDef* pDef = aVar.pDef; + // The ID is either the position or the String-ID + // If the bit Bit 0x8000 is set, the variable have + // a parameter list. + sal_uInt16 nId = ( eOp == SbiOpcode::PARAM_ ) ? pDef->GetPos() : pDef->GetId(); + // Build a parameter list + if( aVar.pPar && aVar.pPar->GetSize() ) + { + nId |= 0x8000; + aVar.pPar->Gen(rGen); + } + + rGen.Gen( eOp, nId, sal::static_int_cast< sal_uInt16 >( GetType() ) ); + + if( aVar.pvMorePar ) + { + for( auto& pExprList: *aVar.pvMorePar ) + { + pExprList->Gen(rGen); + rGen.Gen( SbiOpcode::ARRAYACCESS_ ); + } + } +} + +// Create an Argv-Table +// The first element remain available for return value etc. +// See as well SbiProcDef::SbiProcDef() in symtbl.cxx + +void SbiExprList::Gen(SbiCodeGen& rGen) +{ + if( aData.empty() ) + return; + + rGen.Gen( SbiOpcode::ARGC_ ); + // Type adjustment at DECLARE + + for( auto& pExpr: aData ) + { + pExpr->Gen(); + if( !pExpr->GetName().isEmpty() ) + { + // named arg + sal_uInt16 nSid = rGen.GetParser()->aGblStrings.Add( pExpr->GetName() ); + rGen.Gen( SbiOpcode::ARGN_, nSid ); + + /* TODO: Check after Declare concept change + // From 1996-01-10: Type adjustment at named -> search suitable parameter + if( pProc ) + { + // For the present: trigger an error + pParser->Error( ERRCODE_BASIC_NO_NAMED_ARGS ); + + // Later, if Named Args at DECLARE is possible + //for( sal_uInt16 i = 1 ; i < nParAnz ; i++ ) + //{ + // SbiSymDef* pDef = pPool->Get( i ); + // const String& rName = pDef->GetName(); + // if( rName.Len() ) + // { + // if( pExpr->GetName().ICompare( rName ) + // == COMPARE_EQUAL ) + // { + // pParser->aGen.Gen( ARGTYP_, pDef->GetType() ); + // break; + // } + // } + //} + } + */ + } + else + { + rGen.Gen( SbiOpcode::ARGV_ ); + } + } +} + +void SbiExpression::Gen( RecursiveMode eRecMode ) +{ + // special treatment for WITH + // If pExpr == .-term in With, approximately Gen for Basis-Object + pExpr->Gen( pParser->aGen, eRecMode ); + if( bByVal ) + { + pParser->aGen.Gen( SbiOpcode::BYVAL_ ); + } + if( bBased ) + { + sal_uInt16 uBase = pParser->nBase; + if( pParser->IsCompatible() ) + { + uBase |= 0x8000; // #109275 Flag compatibility + } + pParser->aGen.Gen( SbiOpcode::BASED_, uBase ); + pParser->aGen.Gen( SbiOpcode::ARGV_ ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/exprnode.cxx b/basic/source/comp/exprnode.cxx new file mode 100644 index 000000000..ade1d5832 --- /dev/null +++ b/basic/source/comp/exprnode.cxx @@ -0,0 +1,480 @@ +/* -*- 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 <cmath> + +#include <o3tl/temporary.hxx> +#include <parser.hxx> +#include <expr.hxx> +#include <tools/long.hxx> + +#include <basic/sberrors.hxx> + +#include <rtl/math.hxx> +#include <utility> + +SbiExprNode::SbiExprNode( std::unique_ptr<SbiExprNode> l, SbiToken t, std::unique_ptr<SbiExprNode> r ) : + pLeft(std::move(l)), + pRight(std::move(r)), + pWithParent(nullptr), + eNodeType(SbxNODE), + eType(SbxVARIANT), // Nodes are always Variant + eTok(t), + bError(false) +{ +} + +SbiExprNode::SbiExprNode( double n, SbxDataType t ): + nVal(n), + pWithParent(nullptr), + eNodeType(SbxNUMVAL), + eType(t), + eTok(NIL), + bError(false) +{ +} + +SbiExprNode::SbiExprNode( OUString aVal ): + aStrVal(std::move(aVal)), + pWithParent(nullptr), + eNodeType(SbxSTRVAL), + eType(SbxSTRING), + eTok(NIL), + bError(false) +{ +} + +SbiExprNode::SbiExprNode( const SbiSymDef& r, SbxDataType t, SbiExprListPtr l ) : + pWithParent(nullptr), + eNodeType(SbxVARVAL), + eTok(NIL), + bError(false) +{ + eType = ( t == SbxVARIANT ) ? r.GetType() : t; + aVar.pDef = const_cast<SbiSymDef*>(&r); + aVar.pPar = l.release(); + aVar.pvMorePar = nullptr; + aVar.pNext= nullptr; +} + +// #120061 TypeOf +SbiExprNode::SbiExprNode( std::unique_ptr<SbiExprNode> l, sal_uInt16 nId ) : + nTypeStrId(nId), + pLeft(std::move(l)), + pWithParent(nullptr), + eNodeType(SbxTYPEOF), + eType(SbxBOOL), + eTok(NIL), + bError(false) +{ +} + +// new <type> +SbiExprNode::SbiExprNode( sal_uInt16 nId ) : + nTypeStrId(nId), + pWithParent(nullptr), + eNodeType(SbxNEW), + eType(SbxOBJECT), + eTok(NIL), + bError(false) +{ +} + +SbiExprNode::SbiExprNode() : + pWithParent(nullptr), + eNodeType(SbxDUMMY), + eType(SbxVARIANT), + eTok(NIL), + bError(false) +{ +} + +SbiExprNode::~SbiExprNode() +{ + if( IsVariable() ) + { + delete aVar.pPar; + delete aVar.pNext; + delete aVar.pvMorePar; + } +} + +SbiSymDef* SbiExprNode::GetVar() +{ + if( eNodeType == SbxVARVAL ) + return aVar.pDef; + else + return nullptr; +} + +SbiSymDef* SbiExprNode::GetRealVar() +{ + SbiExprNode* p = GetRealNode(); + if( p ) + return p->GetVar(); + else + return nullptr; +} + +// From 1995-12-18 +SbiExprNode* SbiExprNode::GetRealNode() +{ + if( eNodeType == SbxVARVAL ) + { + SbiExprNode* p = this; + while( p->aVar.pNext ) + p = p->aVar.pNext; + return p; + } + else + return nullptr; +} + +// This method transform the type, if it fits into the Integer range + +void SbiExprNode::ConvertToIntConstIfPossible() +{ + if( eNodeType == SbxNUMVAL ) + { + if( eType >= SbxINTEGER && eType <= SbxDOUBLE ) + { + if( nVal >= SbxMININT && nVal <= SbxMAXINT && modf( nVal, &o3tl::temporary(double()) ) == 0 ) + { + eType = SbxINTEGER; + } + } + } +} + +bool SbiExprNode::IsNumber() const +{ + return eNodeType == SbxNUMVAL; +} + +bool SbiExprNode::IsVariable() const +{ + return eNodeType == SbxVARVAL; +} + +bool SbiExprNode::IsLvalue() const +{ + return IsVariable(); +} + +// Adjustment of a tree: +// 1. Constant Folding +// 2. Type-Adjustment +// 3. Conversion of the operands into Strings +// 4. Lifting of the composite- and error-bits + +void SbiExprNode::Optimize(SbiParser* pParser) +{ + FoldConstants(pParser); + CollectBits(); +} + +// Lifting of the error-bits + +void SbiExprNode::CollectBits() +{ + if( pLeft ) + { + pLeft->CollectBits(); + bError = bError || pLeft->bError; + } + if( pRight ) + { + pRight->CollectBits(); + bError = bError || pRight->bError; + } +} + +// If a twig can be converted, True will be returned. In this case +// the result is in the left twig. +void SbiExprNode::FoldConstants(SbiParser* pParser) +{ + if( IsOperand() || eTok == LIKE ) return; + + if (pLeft && !pRight) + FoldConstantsUnaryNode(pParser); + else if (pLeft && pRight) + FoldConstantsBinaryNode(pParser); + + if( eNodeType == SbxNUMVAL ) + { + // Potentially convolve in INTEGER (because of better opcode)? + if( eType == SbxSINGLE || eType == SbxDOUBLE ) + { + if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG + && !modf( nVal, &o3tl::temporary(double()) ) ) + eType = SbxLONG; + } + if( eType == SbxLONG && nVal >= SbxMININT && nVal <= SbxMAXINT ) + eType = SbxINTEGER; + } +} + +void SbiExprNode::FoldConstantsBinaryNode(SbiParser* pParser) +{ + pLeft->FoldConstants(pParser); + pRight->FoldConstants(pParser); + if( !(pLeft->IsConstant() && pRight->IsConstant() + && pLeft->eNodeType == pRight->eNodeType) ) + return; + + CollectBits(); + if( eTok == CAT ) + // CAT affiliate also two numbers! + eType = SbxSTRING; + if( pLeft->eType == SbxSTRING ) + // No Type Mismatch! + eType = SbxSTRING; + if( eType == SbxSTRING ) + { + OUString rl( pLeft->GetString() ); + OUString rr( pRight->GetString() ); + pLeft.reset(); + pRight.reset(); + if( eTok == PLUS || eTok == CAT ) + { + eTok = CAT; + // Linking: + aStrVal = rl; + aStrVal += rr; + eType = SbxSTRING; + eNodeType = SbxSTRVAL; + } + else + { + eType = SbxBOOL; + eNodeType = SbxNUMVAL; + int eRes = rr.compareTo( rl ); + switch( eTok ) + { + case EQ: + nVal = ( eRes == 0 ) ? SbxTRUE : SbxFALSE; + break; + case NE: + nVal = ( eRes != 0 ) ? SbxTRUE : SbxFALSE; + break; + case LT: + nVal = ( eRes > 0 ) ? SbxTRUE : SbxFALSE; + break; + case GT: + nVal = ( eRes < 0 ) ? SbxTRUE : SbxFALSE; + break; + case LE: + nVal = ( eRes >= 0 ) ? SbxTRUE : SbxFALSE; + break; + case GE: + nVal = ( eRes <= 0 ) ? SbxTRUE : SbxFALSE; + break; + default: + pParser->Error( ERRCODE_BASIC_CONVERSION ); + bError = true; + break; + } + } + } + else + { + double nl = pLeft->nVal; + double nr = pRight->nVal; + // tdf#141201, tdf#147089 - round MOD/IDIV literals to Integer values + if (eTok == MOD || eTok == IDIV) + { + nl = rtl::math::round(nl); + nr = rtl::math::round(nr); + } + tools::Long ll = 0, lr = 0; + if( ( eTok >= AND && eTok <= IMP ) + || eTok == IDIV || eTok == MOD ) + { + // Integer operations + bool bErr = false; + if( nl > SbxMAXLNG ) + { + bErr = true; + nl = SbxMAXLNG; + } + else if( nl < SbxMINLNG ) + { + bErr = true; + nl = SbxMINLNG; + } + if( nr > SbxMAXLNG ) + { + bErr = true; + nr = SbxMAXLNG; + } + else if( nr < SbxMINLNG ) + { + bErr = true; + nr = SbxMINLNG; + } + ll = static_cast<tools::Long>(nl); lr = static_cast<tools::Long>(nr); + if( bErr ) + { + pParser->Error( ERRCODE_BASIC_MATH_OVERFLOW ); + bError = true; + } + } + bool bBothInt = ( pLeft->eType < SbxSINGLE + && pRight->eType < SbxSINGLE ); + pLeft.reset(); + pRight.reset(); + nVal = 0; + eType = SbxDOUBLE; + eNodeType = SbxNUMVAL; + bool bCheckType = false; + switch( eTok ) + { + case EXPON: + nVal = pow( nl, nr ); break; + case MUL: + bCheckType = true; + nVal = nl * nr; break; + case DIV: + if( !nr ) + { + pParser->Error( ERRCODE_BASIC_ZERODIV ); nVal = HUGE_VAL; + bError = true; + } else nVal = nl / nr; + break; + case PLUS: + bCheckType = true; + nVal = nl + nr; break; + case MINUS: + bCheckType = true; + nVal = nl - nr; break; + case EQ: + nVal = ( nl == nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case NE: + nVal = ( nl != nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case LT: + nVal = ( nl < nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case GT: + nVal = ( nl > nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case LE: + nVal = ( nl <= nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case GE: + nVal = ( nl >= nr ) ? SbxTRUE : SbxFALSE; + eType = SbxBOOL; break; + case IDIV: + if( !lr ) + { + pParser->Error( ERRCODE_BASIC_ZERODIV ); nVal = HUGE_VAL; + bError = true; + } else nVal = ll / lr; + eType = SbxLONG; break; + case MOD: + if( !lr ) + { + pParser->Error( ERRCODE_BASIC_ZERODIV ); nVal = HUGE_VAL; + bError = true; + } else nVal = ll - lr * (ll/lr); + eType = SbxLONG; break; + case AND: + nVal = static_cast<double>( ll & lr ); eType = SbxLONG; break; + case OR: + nVal = static_cast<double>( ll | lr ); eType = SbxLONG; break; + case XOR: + nVal = static_cast<double>( ll ^ lr ); eType = SbxLONG; break; + case EQV: + nVal = static_cast<double>( ~ll ^ lr ); eType = SbxLONG; break; + case IMP: + nVal = static_cast<double>( ~ll | lr ); eType = SbxLONG; break; + default: break; + } + + if( !std::isfinite( nVal ) ) + pParser->Error( ERRCODE_BASIC_MATH_OVERFLOW ); + + // Recover the data type to kill rounding error + if( bCheckType && bBothInt + && nVal >= SbxMINLNG && nVal <= SbxMAXLNG ) + { + // Decimal place away + tools::Long n = static_cast<tools::Long>(nVal); + nVal = n; + eType = ( n >= SbxMININT && n <= SbxMAXINT ) + ? SbxINTEGER : SbxLONG; + } + } + +} +void SbiExprNode::FoldConstantsUnaryNode(SbiParser* pParser) +{ + pLeft->FoldConstants(pParser); + if (pLeft->IsNumber()) + { + nVal = pLeft->nVal; + pLeft.reset(); + eType = SbxDOUBLE; + eNodeType = SbxNUMVAL; + switch( eTok ) + { + case NEG: + nVal = -nVal; break; + case NOT: { + // Integer operation! + bool bErr = false; + if( nVal > SbxMAXLNG ) + { + bErr = true; + nVal = SbxMAXLNG; + } + else if( nVal < SbxMINLNG ) + { + bErr = true; + nVal = SbxMINLNG; + } + if( bErr ) + { + pParser->Error( ERRCODE_BASIC_MATH_OVERFLOW ); + bError = true; + } + nVal = static_cast<double>(~static_cast<tools::Long>(nVal)); + eType = SbxLONG; + } break; + default: break; + } + } + if( eNodeType == SbxNUMVAL ) + { + // Potentially convolve in INTEGER (because of better opcode)? + if( eType == SbxSINGLE || eType == SbxDOUBLE ) + { + if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG + && !modf( nVal, &o3tl::temporary(double()) ) ) + eType = SbxLONG; + } + if( eType == SbxLONG && nVal >= SbxMININT && nVal <= SbxMAXINT ) + eType = SbxINTEGER; + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/exprtree.cxx b/basic/source/comp/exprtree.cxx new file mode 100644 index 000000000..989f1c633 --- /dev/null +++ b/basic/source/comp/exprtree.cxx @@ -0,0 +1,1123 @@ +/* -*- 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 <memory> +#include <parser.hxx> +#include <basic/sberrors.hxx> +#include <basic/sbmod.hxx> +#include <comphelper/SetFlagContextHelper.hxx> +#include <expr.hxx> + +SbiExpression::SbiExpression( SbiParser* p, SbiExprType t, + SbiExprMode eMode, const KeywordSymbolInfo* pKeywordSymbolInfo ) : + pParser(p), + eCurExpr(t), + m_eMode(eMode) +{ + pExpr = (t != SbSTDEXPR ) ? Term( pKeywordSymbolInfo ) : Boolean(); + if( t != SbSYMBOL ) + { + pExpr->Optimize(pParser); + } + if( t == SbLVALUE && !pExpr->IsLvalue() ) + { + p->Error( ERRCODE_BASIC_LVALUE_EXPECTED ); + } + if( t == SbOPERAND && !IsVariable() ) + { + p->Error( ERRCODE_BASIC_VAR_EXPECTED ); + } +} + +SbiExpression::SbiExpression( SbiParser* p, double n, SbxDataType t ) : + pParser(p), + eCurExpr(SbOPERAND), + m_eMode(EXPRMODE_STANDARD) +{ + pExpr = std::make_unique<SbiExprNode>( n, t ); + pExpr->Optimize(pParser); +} + +SbiExpression::SbiExpression( SbiParser* p, const SbiSymDef& r, SbiExprListPtr pPar ) : + pParser(p), + eCurExpr(SbOPERAND), + m_eMode(EXPRMODE_STANDARD) +{ + pExpr = std::make_unique<SbiExprNode>( r, SbxVARIANT, std::move(pPar) ); +} + +SbiExpression::~SbiExpression() { } + +// reading in a complete identifier +// an identifier has the following form: +// name[(Parameter)][.Name[(parameter)]]... +// structure elements are coupled via the element pNext, +// so that they're not in the tree. + +// Are there parameters without brackets following? This may be a number, +// a string, a symbol or also a comma (if the 1st parameter is missing) + +static bool DoParametersFollow( const SbiParser* p, SbiExprType eCurExpr, SbiToken eTok ) +{ + if( eTok == LPAREN ) + { + return true; + } + // but only if similar to CALL! + if( !p->WhiteSpace() || eCurExpr != SbSYMBOL ) + { + return false; + } + if ( eTok == NUMBER || eTok == MINUS || eTok == FIXSTRING || + eTok == SYMBOL || eTok == COMMA || eTok == DOT || eTok == NOT || eTok == BYVAL ) + { + return true; + } + else // check for default params with reserved names ( e.g. names of tokens ) + { + SbiTokenizer tokens( *static_cast<const SbiTokenizer*>(p) ); + // Urk the Next() / Peek() semantics are... weird + tokens.Next(); + if ( tokens.Peek() == ASSIGN ) + { + return true; + } + } + return false; +} + +// definition of a new symbol + +static SbiSymDef* AddSym ( SbiToken eTok, SbiSymPool& rPool, SbiExprType eCurExpr, + const OUString& rName, SbxDataType eType, const SbiExprList* pPar ) +{ + SbiSymDef* pDef; + // A= is not a procedure + bool bHasType = ( eTok == EQ || eTok == DOT ); + if( ( !bHasType && eCurExpr == SbSYMBOL ) || pPar ) + { + // so this is a procedure + // the correct pool should be found out, as + // procs must always get into a public pool + SbiSymPool* pPool = &rPool; + if( pPool->GetScope() != SbPUBLIC ) + { + pPool = &rPool.GetParser()->aPublics; + } + SbiProcDef* pProc = pPool->AddProc( rName ); + + // special treatment for Colls like Documents(1) + if( eCurExpr == SbSTDEXPR ) + { + bHasType = true; + } + pDef = pProc; + pDef->SetType( bHasType ? eType : SbxEMPTY ); + if( pPar ) + { + // generate dummy parameters + for( sal_Int32 n = 1; n <= pPar->GetSize(); n++ ) + { + OUString aPar = "PAR" + OUString::number( n ); + pProc->GetParams().AddSym( aPar ); + } + } + } + else + { + // or a normal symbol + pDef = rPool.AddSym( rName ); + pDef->SetType( eType ); + } + return pDef; +} + +// currently even keywords are allowed (because of Dflt properties of the same name) + +std::unique_ptr<SbiExprNode> SbiExpression::Term( const KeywordSymbolInfo* pKeywordSymbolInfo ) +{ + if( pParser->Peek() == DOT ) + { + SbiExprNode* pWithVar = pParser->GetWithVar(); + // #26608: get to the node-chain's end to pass the correct object + SbiSymDef* pDef = pWithVar ? pWithVar->GetRealVar() : nullptr; + std::unique_ptr<SbiExprNode> pNd; + if( !pDef ) + { + pParser->Next(); + } + else + { + pNd = ObjTerm( *pDef ); + if( pNd ) + { + pNd->SetWithParent( pWithVar ); + } + } + if( !pNd ) + { + pParser->Error( ERRCODE_BASIC_UNEXPECTED, DOT ); + pNd = std::make_unique<SbiExprNode>( 1.0, SbxDOUBLE ); + } + return pNd; + } + + SbiToken eTok = (pKeywordSymbolInfo == nullptr) ? pParser->Next() : SYMBOL; + // memorize the parsing's begin + pParser->LockColumn(); + OUString aSym( (pKeywordSymbolInfo == nullptr) ? pParser->GetSym() : pKeywordSymbolInfo->m_aKeywordSymbol ); + SbxDataType eType = (pKeywordSymbolInfo == nullptr) ? pParser->GetType() : pKeywordSymbolInfo->m_eSbxDataType; + SbiExprListPtr pPar; + std::unique_ptr<SbiExprListVector> pvMoreParLcl; + // are there parameters following? + SbiToken eNextTok = pParser->Peek(); + // is it a known parameter? + // create a string constant then, which will be recognized + // in the SbiParameters-ctor and is continued to be handled + if( eNextTok == ASSIGN ) + { + pParser->UnlockColumn(); + return std::make_unique<SbiExprNode>( aSym ); + } + // no keywords allowed from here on! + if( SbiTokenizer::IsKwd( eTok ) + && (!pParser->IsCompatible() || eTok != INPUT) ) + { + pParser->Error( ERRCODE_BASIC_SYNTAX ); + bError = true; + } + + eTok = eNextTok; + if( DoParametersFollow( pParser, eCurExpr, eTok ) ) + { + bool bStandaloneExpression = (m_eMode == EXPRMODE_STANDALONE); + pPar = SbiExprList::ParseParameters( pParser, bStandaloneExpression ); + bError = bError || !pPar->IsValid(); + if( !bError ) + bBracket = pPar->IsBracket(); + eTok = pParser->Peek(); + + // i75443 check for additional sets of parameters + while( eTok == LPAREN ) + { + if( pvMoreParLcl == nullptr ) + { + pvMoreParLcl.reset(new SbiExprListVector); + } + SbiExprListPtr pAddPar = SbiExprList::ParseParameters( pParser ); + bError = bError || !pAddPar->IsValid(); + pvMoreParLcl->push_back( std::move(pAddPar) ); + eTok = pParser->Peek(); + } + } + // It might be an object part, if . or ! is following. + // In case of . the variable must already be defined; + // it's an object, if pDef is NULL after the search. + bool bObj = ( ( eTok == DOT || eTok == EXCLAM ) + && !pParser->WhiteSpace() ); + if( bObj ) + { + bBracket = false; // Now the bracket for the first term is obsolete + if( eType == SbxVARIANT ) + { + eType = SbxOBJECT; + } + else + { + // Name%. really does not work! + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + } + // Search: + SbiSymDef* pDef = pParser->pPool->Find( aSym ); + if( !pDef ) + { + // Part of the Runtime-Library? + // from 31.3.1996: swapped out to parser-method + // (is also needed in SbiParser::DefVar() in DIM.CXX) + pDef = pParser->CheckRTLForSym( aSym, eType ); + + // #i109184: Check if symbol is or later will be defined inside module + SbModule& rMod = pParser->aGen.GetModule(); + if( rMod.FindMethod( aSym, SbxClassType::DontCare ) ) + { + pDef = nullptr; + } + } + if( !pDef ) + { + if( bObj ) + { + eType = SbxOBJECT; + } + pDef = AddSym( eTok, *pParser->pPool, eCurExpr, aSym, eType, pPar.get() ); + // Looks like this is a local ( but undefined variable ) + // if it is in a static procedure then make this Symbol + // static + if ( !bObj && pParser->pProc && pParser->pProc->IsStatic() ) + { + pDef->SetStatic(); + } + } + else + { + + SbiConstDef* pConst = pDef->GetConstDef(); + if( pConst ) + { + pPar = nullptr; + pvMoreParLcl.reset(); + if( pConst->GetType() == SbxSTRING ) + { + return std::make_unique<SbiExprNode>( pConst->GetString() ); + } + else + { + return std::make_unique<SbiExprNode>( pConst->GetValue(), pConst->GetType() ); + } + } + + // 0 parameters come up to () + if( pDef->GetDims() ) + { + if( pPar && pPar->GetSize() && pPar->GetSize() != pDef->GetDims() ) + { + pParser->Error( ERRCODE_BASIC_WRONG_DIMS ); + } + } + if( pDef->IsDefinedAs() ) + { + SbxDataType eDefType = pDef->GetType(); + // #119187 Only error if types conflict + if( eType >= SbxINTEGER && eType <= SbxSTRING && eType != eDefType ) + { + // How? Define with AS first and take a Suffix then? + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + else if ( eType == SbxVARIANT ) + { + // if there's nothing named, take the type of the entry, + // but only if the var hasn't been defined with AS XXX + // so that we catch n% = 5 : print n + eType = eDefType; + } + } + // checking type of variables: + // is there named anything different in the scanner? + // That's OK for methods! + if( eType != SbxVARIANT && // Variant takes everything + eType != pDef->GetType() && + !pDef->GetProcDef() ) + { + // maybe pDef describes an object that so far has only been + // recognized as SbxVARIANT - then change type of pDef + // from 16.12.95 (similar cases possible perhaps?!?) + if( eType == SbxOBJECT && pDef->GetType() == SbxVARIANT ) + { + pDef->SetType( SbxOBJECT ); + } + else + { + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + } + } + auto pNd = std::make_unique<SbiExprNode>( *pDef, eType ); + if( !pPar ) + { + pPar = SbiExprList::ParseParameters( pParser,false,false ); + } + pNd->aVar.pPar = pPar.release(); + pNd->aVar.pvMorePar = pvMoreParLcl.release(); + if( bObj ) + { + // from 8.1.95: Object may also be of the type SbxVARIANT + if( pDef->GetType() == SbxVARIANT ) + pDef->SetType( SbxOBJECT ); + // if we scan something with point, + // the type must be SbxOBJECT + if( pDef->GetType() != SbxOBJECT && pDef->GetType() != SbxVARIANT ) + { + // defer error until runtime if in vba mode + if ( !pParser->IsVBASupportOn() ) + { + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + } + if( !bError ) + { + pNd->aVar.pNext = ObjTerm( *pDef ).release(); + } + } + + pParser->UnlockColumn(); + return pNd; +} + +// construction of an object term. A term of this kind is part +// of an expression that begins with an object variable. + +std::unique_ptr<SbiExprNode> SbiExpression::ObjTerm( SbiSymDef& rObj ) +{ + pParser->Next(); + SbiToken eTok = pParser->Next(); + if( eTok != SYMBOL && !SbiTokenizer::IsKwd( eTok ) && !SbiTokenizer::IsExtra( eTok ) ) + { + // #66745 Some operators can also be allowed + // as identifiers, important for StarOne + if( eTok != MOD && eTok != NOT && eTok != AND && eTok != OR && + eTok != XOR && eTok != EQV && eTok != IMP && eTok != IS ) + { + pParser->Error( ERRCODE_BASIC_VAR_EXPECTED ); + bError = true; + } + } + + if( bError ) + { + return nullptr; + } + OUString aSym( pParser->GetSym() ); + SbxDataType eType = pParser->GetType(); + SbiExprListPtr pPar; + SbiExprListVector* pvMoreParLcl = nullptr; + eTok = pParser->Peek(); + + if( DoParametersFollow( pParser, eCurExpr, eTok ) ) + { + pPar = SbiExprList::ParseParameters( pParser, false/*bStandaloneExpression*/ ); + bError = bError || !pPar->IsValid(); + eTok = pParser->Peek(); + + // i109624 check for additional sets of parameters + while( eTok == LPAREN ) + { + if( pvMoreParLcl == nullptr ) + { + pvMoreParLcl = new SbiExprListVector; + } + SbiExprListPtr pAddPar = SbiExprList::ParseParameters( pParser ); + bError = bError || !pPar->IsValid(); + pvMoreParLcl->push_back( std::move(pAddPar) ); + eTok = pParser->Peek(); + } + } + bool bObj = ( ( eTok == DOT || eTok == EXCLAM ) && !pParser->WhiteSpace() ); + if( bObj ) + { + if( eType == SbxVARIANT ) + { + eType = SbxOBJECT; + } + else + { + // Name%. does really not work! + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + } + + // an object's symbol pool is always PUBLIC + SbiSymPool& rPool = rObj.GetPool(); + rPool.SetScope( SbPUBLIC ); + SbiSymDef* pDef = rPool.Find( aSym ); + if( !pDef ) + { + pDef = AddSym( eTok, rPool, eCurExpr, aSym, eType, pPar.get() ); + pDef->SetType( eType ); + } + + auto pNd = std::make_unique<SbiExprNode>( *pDef, eType ); + pNd->aVar.pPar = pPar.release(); + pNd->aVar.pvMorePar = pvMoreParLcl; + if( bObj ) + { + if( pDef->GetType() == SbxVARIANT ) + { + pDef->SetType( SbxOBJECT ); + } + if( pDef->GetType() != SbxOBJECT ) + { + pParser->Error( ERRCODE_BASIC_BAD_DECLARATION, aSym ); + bError = true; + } + if( !bError ) + { + pNd->aVar.pNext = ObjTerm( *pDef ).release(); + pNd->eType = eType; + } + } + return pNd; +} + +// an operand can be: +// constant +// scalar variable +// structure elements +// array elements +// functions +// bracketed expressions + +std::unique_ptr<SbiExprNode> SbiExpression::Operand( bool bUsedForTypeOf ) +{ + std::unique_ptr<SbiExprNode> pRes; + + // test operand: + switch( SbiToken eTok = pParser->Peek() ) + { + case SYMBOL: + pRes = Term(); + // process something like "IF Not r Is Nothing Then .." + if( !bUsedForTypeOf && pParser->IsVBASupportOn() && pParser->Peek() == IS ) + { + eTok = pParser->Next(); + pRes = std::make_unique<SbiExprNode>( std::move(pRes), eTok, Like() ); + } + break; + case DOT: // .with + pRes = Term(); break; + case NOT: + pRes = VBA_Not(); + break; + case NUMBER: + pParser->Next(); + pRes = std::make_unique<SbiExprNode>( pParser->GetDbl(), pParser->GetType() ); + break; + case FIXSTRING: + pParser->Next(); + pRes = std::make_unique<SbiExprNode>( pParser->GetSym() ); break; + case LPAREN: + pParser->Next(); + if( nParenLevel == 0 && m_eMode == EXPRMODE_LPAREN_PENDING && pParser->Peek() == RPAREN ) + { + m_eMode = EXPRMODE_EMPTY_PAREN; + pRes = std::make_unique<SbiExprNode>(); // Dummy node + pParser->Next(); + break; + } + nParenLevel++; + pRes = Boolean(); + if( pParser->Peek() != RPAREN ) + { + // If there was a LPARAM, it does not belong to the expression + if( nParenLevel == 1 && m_eMode == EXPRMODE_LPAREN_PENDING ) + { + m_eMode = EXPRMODE_LPAREN_NOT_NEEDED; + } + else + { + pParser->Error( ERRCODE_BASIC_BAD_BRACKETS ); + } + } + else + { + pParser->Next(); + if( nParenLevel == 1 && m_eMode == EXPRMODE_LPAREN_PENDING ) + { + SbiToken eTokAfterRParen = pParser->Peek(); + if( eTokAfterRParen == EQ || eTokAfterRParen == LPAREN || eTokAfterRParen == DOT ) + { + m_eMode = EXPRMODE_ARRAY_OR_OBJECT; + } + else + { + m_eMode = EXPRMODE_STANDARD; + } + } + } + nParenLevel--; + break; + default: + // keywords here are OK at the moment! + if( SbiTokenizer::IsKwd( eTok ) ) + { + pRes = Term(); + } + else + { + pParser->Next(); + pRes = std::make_unique<SbiExprNode>( 1.0, SbxDOUBLE ); + pParser->Error( ERRCODE_BASIC_UNEXPECTED, eTok ); + } + break; + } + return pRes; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Unary() +{ + std::unique_ptr<SbiExprNode> pNd; + SbiToken eTok = pParser->Peek(); + switch( eTok ) + { + case MINUS: + eTok = NEG; + pParser->Next(); + pNd = std::make_unique<SbiExprNode>( Unary(), eTok, nullptr ); + break; + case NOT: + if( pParser->IsVBASupportOn() ) + { + pNd = Operand(); + } + else + { + pParser->Next(); + pNd = std::make_unique<SbiExprNode>( Unary(), eTok, nullptr ); + } + break; + case PLUS: + pParser->Next(); + pNd = Unary(); + break; + case TYPEOF: + { + pParser->Next(); + std::unique_ptr<SbiExprNode> pObjNode = Operand( true/*bUsedForTypeOf*/ ); + pParser->TestToken( IS ); + SbiSymDef* pTypeDef = new SbiSymDef( OUString() ); + pParser->TypeDecl( *pTypeDef, true ); + pNd = std::make_unique<SbiExprNode>( std::move(pObjNode), pTypeDef->GetTypeId() ); + break; + } + case NEW: + { + pParser->Next(); + SbiSymDef* pTypeDef = new SbiSymDef( OUString() ); + pParser->TypeDecl( *pTypeDef, true ); + pNd = std::make_unique<SbiExprNode>( pTypeDef->GetTypeId() ); + break; + } + default: + pNd = Operand(); + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Exp() +{ + std::unique_ptr<SbiExprNode> pNd = Unary(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + while( pParser->Peek() == EXPON ) + { + SbiToken eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Unary() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::MulDiv() +{ + std::unique_ptr<SbiExprNode> pNd = Exp(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + for( ;; ) + { + SbiToken eTok = pParser->Peek(); + if( eTok != MUL && eTok != DIV ) + { + break; + } + eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Exp() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::IntDiv() +{ + std::unique_ptr<SbiExprNode> pNd = MulDiv(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + while( pParser->Peek() == IDIV ) + { + SbiToken eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, MulDiv() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Mod() +{ + std::unique_ptr<SbiExprNode> pNd = IntDiv(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + while( pParser->Peek() == MOD ) + { + SbiToken eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, IntDiv() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::AddSub() +{ + std::unique_ptr<SbiExprNode> pNd = Mod(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + for( ;; ) + { + SbiToken eTok = pParser->Peek(); + if( eTok != PLUS && eTok != MINUS ) + { + break; + } + eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Mod() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Cat() +{ + std::unique_ptr<SbiExprNode> pNd = AddSub(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + for( ;; ) + { + SbiToken eTok = pParser->Peek(); + if( eTok != CAT ) + { + break; + } + eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, AddSub() ); + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Comp() +{ + std::unique_ptr<SbiExprNode> pNd = Cat(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + for( ;; ) + { + SbiToken eTok = pParser->Peek(); + if( m_eMode == EXPRMODE_ARRAY_OR_OBJECT ) + { + break; + } + if( eTok != EQ && eTok != NE && eTok != LT && + eTok != GT && eTok != LE && eTok != GE ) + { + break; + } + eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Cat() ); + } + } + return pNd; +} + + +std::unique_ptr<SbiExprNode> SbiExpression::VBA_Not() +{ + std::unique_ptr<SbiExprNode> pNd; + + SbiToken eTok = pParser->Peek(); + if( eTok == NOT ) + { + pParser->Next(); + pNd = std::make_unique<SbiExprNode>( VBA_Not(), eTok, nullptr ); + } + else + { + pNd = Comp(); + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Like() +{ + std::unique_ptr<SbiExprNode> pNd = pParser->IsVBASupportOn() ? VBA_Not() : Comp(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + short nCount = 0; + while( pParser->Peek() == LIKE ) + { + SbiToken eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Comp() ); + nCount++; + } + // multiple operands in a row does not work + if( nCount > 1 && !pParser->IsVBASupportOn() ) + { + pParser->Error( ERRCODE_BASIC_SYNTAX ); + bError = true; + } + } + return pNd; +} + +std::unique_ptr<SbiExprNode> SbiExpression::Boolean() +{ + std::unique_ptr<SbiExprNode> pNd = Like(); + if( m_eMode != EXPRMODE_EMPTY_PAREN ) + { + for( ;; ) + { + SbiToken eTok = pParser->Peek(); + if( (eTok != AND) && (eTok != OR) && + (eTok != XOR) && (eTok != EQV) && + (eTok != IMP) && (eTok != IS) ) + { + break; + } + eTok = pParser->Next(); + pNd = std::make_unique<SbiExprNode>( std::move(pNd), eTok, Like() ); + } + } + return pNd; +} + +SbiConstExpression::SbiConstExpression( SbiParser* p ) : SbiExpression( p ) +{ + if( pExpr->IsConstant() ) + { + eType = pExpr->GetType(); + if( pExpr->IsNumber() ) + { + nVal = pExpr->nVal; + } + else + { + nVal = 0; + aVal = pExpr->aStrVal; + } + } + else + { + // #40204 special treatment for sal_Bool-constants + bool bIsBool = false; + if( pExpr->eNodeType == SbxVARVAL ) + { + SbiSymDef* pVarDef = pExpr->GetVar(); + + bool bBoolVal = false; + if( pVarDef->GetName().equalsIgnoreAsciiCase( "true" ) ) + { + bIsBool = true; + bBoolVal = true; + } + else if( pVarDef->GetName().equalsIgnoreAsciiCase( "false" ) ) + //else if( pVarDef->GetName().ICompare( "false" ) == COMPARE_EQUAL ) + { + bIsBool = true; + bBoolVal = false; + } + + if( bIsBool ) + { + pExpr = std::make_unique<SbiExprNode>( (bBoolVal ? SbxTRUE : SbxFALSE), SbxINTEGER ); + eType = pExpr->GetType(); + nVal = pExpr->nVal; + } + } + + if( !bIsBool ) + { + pParser->Error( ERRCODE_BASIC_SYNTAX ); + eType = SbxDOUBLE; + nVal = 0; + } + } +} + +short SbiConstExpression::GetShortValue() +{ + if( eType == SbxSTRING ) + { + SbxVariableRef refConv = new SbxVariable; + refConv->PutString( aVal ); + return refConv->GetInteger(); + } + else + { + double n = nVal; + if( n > 0 ) + { + n += .5; + } + else + { + n -= .5; + } + if( n > SbxMAXINT ) + { + n = SbxMAXINT; + pParser->Error( ERRCODE_BASIC_OUT_OF_RANGE ); + } + else if( n < SbxMININT ) + { + n = SbxMININT; + pParser->Error( ERRCODE_BASIC_OUT_OF_RANGE ); + } + + return static_cast<short>(n); + } +} + + +SbiExprList::SbiExprList( ) +{ + nDim = 0; + bError = false; + bBracket = false; +} + +SbiExprList::~SbiExprList() {} + +SbiExpression* SbiExprList::Get( size_t n ) +{ + return aData[n].get(); +} + +void SbiExprList::addExpression( std::unique_ptr<SbiExpression>&& pExpr ) +{ + aData.push_back(std::move(pExpr)); +} + +// the parameter list is completely parsed +// "procedurename()" is OK +// it's a function without parameters then +// i. e. you give an array as procedure parameter + +// #i79918/#i80532: bConst has never been set to true +// -> reused as bStandaloneExpression +//SbiParameters::SbiParameters( SbiParser* p, sal_Bool bConst, sal_Bool bPar) : +SbiExprListPtr SbiExprList::ParseParameters( SbiParser* pParser, bool bStandaloneExpression, bool bPar) +{ + auto pExprList = std::make_unique<SbiExprList>(); + if( !bPar ) + { + return pExprList; + } + + SbiToken eTok = pParser->Peek(); + + bool bAssumeExprLParenMode = false; + bool bAssumeArrayMode = false; + if( eTok == LPAREN ) + { + if( bStandaloneExpression ) + { + bAssumeExprLParenMode = true; + } + else + { + pExprList->bBracket = true; + pParser->Next(); + eTok = pParser->Peek(); + } + } + + + if( ( pExprList->bBracket && eTok == RPAREN ) || SbiTokenizer::IsEoln( eTok ) ) + { + if( eTok == RPAREN ) + { + pParser->Next(); + } + return pExprList; + } + // read in parameter table and lay down in correct order! + while( !pExprList->bError ) + { + std::unique_ptr<SbiExpression> pExpr; + // missing argument + if( eTok == COMMA ) + { + pExpr = std::make_unique<SbiExpression>( pParser, 0, SbxEMPTY ); + } + // named arguments: either .name= or name:= + else + { + bool bByVal = false; + if( eTok == BYVAL ) + { + bByVal = true; + pParser->Next(); + eTok = pParser->Peek(); + } + + if( bAssumeExprLParenMode ) + { + pExpr = std::make_unique<SbiExpression>( pParser, SbSTDEXPR, EXPRMODE_LPAREN_PENDING ); + bAssumeExprLParenMode = false; + + SbiExprMode eModeAfter = pExpr->m_eMode; + if( eModeAfter == EXPRMODE_LPAREN_NOT_NEEDED ) + { + pExprList->bBracket = true; + } + else if( eModeAfter == EXPRMODE_ARRAY_OR_OBJECT ) + { + // Expression "looks" like an array assignment + // a(...)[(...)] = ? or a(...).b(...) + // RPAREN is already parsed + pExprList->bBracket = true; + bAssumeArrayMode = true; + eTok = NIL; + } + else if( eModeAfter == EXPRMODE_EMPTY_PAREN ) + { + pExprList->bBracket = true; + return pExprList; + } + } + else + { + pExpr = std::make_unique<SbiExpression>( pParser ); + } + if( bByVal && pExpr->IsLvalue() ) + { + pExpr->SetByVal(); + } + if( !bAssumeArrayMode ) + { + OUString aName; + if( pParser->Peek() == ASSIGN ) + { + // VBA mode: name:= + // SbiExpression::Term() has made as string out of it + aName = pExpr->GetString(); + pParser->Next(); + pExpr = std::make_unique<SbiExpression>( pParser ); + } + pExpr->GetName() = aName; + } + } + pExprList->bError = pExprList->bError || !pExpr->IsValid(); + pExprList->aData.push_back(std::move(pExpr)); + if( bAssumeArrayMode ) + { + break; + } + // next element? + eTok = pParser->Peek(); + if( eTok != COMMA ) + { + if( ( pExprList->bBracket && eTok == RPAREN ) || SbiTokenizer::IsEoln( eTok ) ) + { + // tdf#80731 + if (SbiTokenizer::IsEoln(eTok) && pExprList->bBracket) + { + // tdf#106529: only fail here in strict mode (i.e. when compiled from IDE), and + // allow legacy code with missing closing parenthesis when started e.g. from + // extensions and event handlers + if (comphelper::IsContextFlagActive("BasicStrict")) + { + pParser->Error(ERRCODE_BASIC_EXPECTED, RPAREN); + pExprList->bError = true; + } + } + break; + } + pParser->Error( pExprList->bBracket ? ERRCODE_BASIC_BAD_BRACKETS : ERRCODE_BASIC_EXPECTED, COMMA ); + pExprList->bError = true; + } + else + { + pParser->Next(); + eTok = pParser->Peek(); + if( ( pExprList->bBracket && eTok == RPAREN ) || SbiTokenizer::IsEoln( eTok ) ) + { + break; + } + } + } + // closing bracket + if( eTok == RPAREN ) + { + pParser->Next(); + pParser->Peek(); + if( !pExprList->bBracket ) + { + pParser->Error( ERRCODE_BASIC_BAD_BRACKETS ); + pExprList->bError = true; + } + } + pExprList->nDim = pExprList->GetSize(); + return pExprList; +} + +// A list of array dimensions is parsed. + +SbiExprListPtr SbiExprList::ParseDimList( SbiParser* pParser ) +{ + auto pExprList = std::make_unique<SbiExprList>(); + + if( pParser->Next() != LPAREN ) + { + pParser->Error( ERRCODE_BASIC_EXPECTED, LPAREN ); + pExprList->bError = true; return pExprList; + } + + if( pParser->Peek() != RPAREN ) + { + SbiToken eTok; + for( ;; ) + { + auto pExpr1 = std::make_unique<SbiExpression>( pParser ); + eTok = pParser->Next(); + if( eTok == TO ) + { + auto pExpr2 = std::make_unique<SbiExpression>( pParser ); + pExpr1->ConvertToIntConstIfPossible(); + pExpr2->ConvertToIntConstIfPossible(); + eTok = pParser->Next(); + pExprList->bError = pExprList->bError || !pExpr1->IsValid() || !pExpr2->IsValid(); + pExprList->aData.push_back(std::move(pExpr1)); + pExprList->aData.push_back(std::move(pExpr2)); + } + else + { + pExpr1->SetBased(); + pExpr1->ConvertToIntConstIfPossible(); + pExprList->bError = pExprList->bError || !pExpr1->IsValid(); + pExprList->aData.push_back(std::move(pExpr1)); + } + pExprList->nDim++; + if( eTok == RPAREN ) break; + if( eTok != COMMA ) + { + pParser->Error( ERRCODE_BASIC_BAD_BRACKETS ); + pParser->Next(); + break; + } + } + } + else pParser->Next(); + return pExprList; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/io.cxx b/basic/source/comp/io.cxx new file mode 100644 index 000000000..9e91413fd --- /dev/null +++ b/basic/source/comp/io.cxx @@ -0,0 +1,309 @@ +/* -*- 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 <parser.hxx> +#include <iosys.hxx> +#include <memory> + +// test if there's an I/O channel + +bool SbiParser::Channel( bool bAlways ) +{ + bool bRes = false; + Peek(); + if( IsHash() ) + { + SbiExpression aExpr( this ); + while( Peek() == COMMA || Peek() == SEMICOLON ) + Next(); + aExpr.Gen(); + aGen.Gen( SbiOpcode::CHANNEL_ ); + bRes = true; + } + else if( bAlways ) + Error( ERRCODE_BASIC_EXPECTED, "#" ); + return bRes; +} + +// it's tried that at object variables the Default- +// Property is addressed for PRINT and WRITE + +void SbiParser::Print() +{ + bool bChan = Channel(); + + while( !bAbort ) + { + if( !IsEoln( Peek() ) ) + { + auto pExpr = std::make_unique<SbiExpression>(this); + pExpr->Gen(); + pExpr.reset(); + Peek(); + aGen.Gen( eCurTok == COMMA ? SbiOpcode::PRINTF_ : SbiOpcode::BPRINT_ ); + } + if( eCurTok == COMMA || eCurTok == SEMICOLON ) + { + Next(); + if( IsEoln( Peek() ) ) break; + } + else + { + aGen.Gen( SbiOpcode::PRCHAR_, '\n' ); + break; + } + } + if( bChan ) + aGen.Gen( SbiOpcode::CHAN0_ ); +} + +// WRITE #chan, expr, ... + +void SbiParser::Write() +{ + bool bChan = Channel(); + + while( !bAbort ) + { + auto pExpr = std::make_unique<SbiExpression>(this); + pExpr->Gen(); + pExpr.reset(); + aGen.Gen( SbiOpcode::BWRITE_ ); + if( Peek() == COMMA ) + { + aGen.Gen( SbiOpcode::PRCHAR_, ',' ); + Next(); + if( IsEoln( Peek() ) ) break; + } + else + { + aGen.Gen( SbiOpcode::PRCHAR_, '\n' ); + break; + } + } + if( bChan ) + aGen.Gen( SbiOpcode::CHAN0_ ); +} + + +// #i92642 Handle LINE keyword outside ::Next() +void SbiParser::Line() +{ + // #i92642: Special handling to allow name as symbol + if( Peek() == INPUT ) + { + Next(); + LineInput(); + } + else + { + aGen.Statement(); + + KeywordSymbolInfo aInfo; + aInfo.m_aKeywordSymbol = "line"; + aInfo.m_eSbxDataType = GetType(); + + Symbol( &aInfo ); + } +} + + +// LINE INPUT [prompt], var$ + +void SbiParser::LineInput() +{ + Channel( true ); + auto pExpr = std::make_unique<SbiExpression>( this, SbOPERAND ); + if( !pExpr->IsVariable() ) + Error( ERRCODE_BASIC_VAR_EXPECTED ); + if( pExpr->GetType() != SbxVARIANT && pExpr->GetType() != SbxSTRING ) + Error( ERRCODE_BASIC_CONVERSION ); + pExpr->Gen(); + aGen.Gen( SbiOpcode::LINPUT_ ); + pExpr.reset(); + aGen.Gen( SbiOpcode::CHAN0_ ); // ResetChannel() not in StepLINPUT() anymore +} + +// INPUT + +void SbiParser::Input() +{ + aGen.Gen( SbiOpcode::RESTART_ ); + Channel( true ); + auto pExpr = std::make_unique<SbiExpression>( this, SbOPERAND ); + while( !bAbort ) + { + if( !pExpr->IsVariable() ) + Error( ERRCODE_BASIC_VAR_EXPECTED ); + pExpr->Gen(); + aGen.Gen( SbiOpcode::INPUT_ ); + if( Peek() == COMMA ) + { + Next(); + pExpr.reset(new SbiExpression( this, SbOPERAND )); + } + else break; + } + pExpr.reset(); + aGen.Gen( SbiOpcode::CHAN0_ ); +} + +// OPEN stringexpr FOR mode ACCESS access mode AS Channel [Len=n] + +void SbiParser::Open() +{ + bInStatement = true; + SbiExpression aFileName( this ); + SbiToken eTok; + TestToken( FOR ); + StreamMode nMode = StreamMode::NONE; + SbiStreamFlags nFlags = SbiStreamFlags::NONE; + switch( Next() ) + { + case INPUT: + nMode = StreamMode::READ; nFlags |= SbiStreamFlags::Input; break; + case OUTPUT: + nMode = StreamMode::WRITE | StreamMode::TRUNC; nFlags |= SbiStreamFlags::Output; break; + case APPEND: + nMode = StreamMode::WRITE; nFlags |= SbiStreamFlags::Append; break; + case RANDOM: + nMode = StreamMode::READ | StreamMode::WRITE; nFlags |= SbiStreamFlags::Random; break; + case BINARY: + nMode = StreamMode::READ | StreamMode::WRITE; nFlags |= SbiStreamFlags::Binary; break; + default: + Error( ERRCODE_BASIC_SYNTAX ); + } + if( Peek() == ACCESS ) + { + Next(); + eTok = Next(); + // influence only READ,WRITE-Flags in nMode + nMode &= ~StreamMode(StreamMode::READ | StreamMode::WRITE); // delete + if( eTok == READ ) + { + if( Peek() == WRITE ) + { + Next(); + nMode |= StreamMode::READ | StreamMode::WRITE; + } + else + nMode |= StreamMode::READ; + } + else if( eTok == WRITE ) + nMode |= StreamMode::WRITE; + else + Error( ERRCODE_BASIC_SYNTAX ); + } + switch( Peek() ) + { + case SHARED: + Next(); nMode |= StreamMode::SHARE_DENYNONE; break; + case LOCK: + Next(); + eTok = Next(); + if( eTok == READ ) + { + if( Peek() == WRITE ) + { + Next(); + nMode |= StreamMode::SHARE_DENYALL; + } + else nMode |= StreamMode::SHARE_DENYREAD; + } + else if( eTok == WRITE ) + nMode |= StreamMode::SHARE_DENYWRITE; + else + Error( ERRCODE_BASIC_SYNTAX ); + break; + default: break; + } + TestToken( AS ); + // channel number + auto pChan = std::make_unique<SbiExpression>( this ); + std::unique_ptr<SbiExpression> pLen; + if( Peek() == SYMBOL ) + { + Next(); + if( aSym.equalsIgnoreAsciiCase("LEN") ) + { + TestToken( EQ ); + pLen.reset(new SbiExpression( this )); + } + } + if( !pLen ) pLen.reset(new SbiExpression( this, 128, SbxINTEGER )); + // the stack for the OPEN command looks as follows: + // block length + // channel number + // file name + pLen->Gen(); + pChan->Gen(); + aFileName.Gen(); + aGen.Gen( SbiOpcode::OPEN_, static_cast<sal_uInt32>(nMode), static_cast<sal_uInt32>(nFlags) ); + bInStatement = false; +} + +// NAME file AS file + +void SbiParser::Name() +{ + // #i92642: Special handling to allow name as symbol + if( Peek() == EQ ) + { + aGen.Statement(); + + KeywordSymbolInfo aInfo; + aInfo.m_aKeywordSymbol = "name"; + aInfo.m_eSbxDataType = GetType(); + + Symbol( &aInfo ); + return; + } + SbiExpression aExpr1( this ); + TestToken( AS ); + SbiExpression aExpr2( this ); + aExpr1.Gen(); + aExpr2.Gen(); + aGen.Gen( SbiOpcode::RENAME_ ); +} + +// CLOSE [n,...] + +void SbiParser::Close() +{ + Peek(); + if( IsEoln( eCurTok ) ) + aGen.Gen( SbiOpcode::CLOSE_, 0 ); + else + for( ;; ) + { + SbiExpression aExpr( this ); + while( Peek() == COMMA || Peek() == SEMICOLON ) + Next(); + aExpr.Gen(); + aGen.Gen( SbiOpcode::CHANNEL_ ); + aGen.Gen( SbiOpcode::CLOSE_, 1 ); + + if( IsEoln( Peek() ) ) + break; + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/loops.cxx b/basic/source/comp/loops.cxx new file mode 100644 index 000000000..07aac4494 --- /dev/null +++ b/basic/source/comp/loops.cxx @@ -0,0 +1,572 @@ +/* -*- 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 <parser.hxx> +#include <memory> + +#include <basic/sberrors.hxx> + +// Single-line IF and Multiline IF + +void SbiParser::If() +{ + sal_uInt32 nEndLbl; + SbiToken eTok = NIL; + // ignore end-tokens + SbiExpression aCond( this ); + aCond.Gen(); + TestToken( THEN ); + if( IsEoln( Next() ) ) + { + // At the end of each block a jump to ENDIF must be inserted, + // so that the condition is not evaluated again at ELSEIF. + // The table collects all jump points. +#define JMP_TABLE_SIZE 100 + sal_uInt32 pnJmpToEndLbl[JMP_TABLE_SIZE]; // 100 ELSEIFs allowed + sal_uInt16 iJmp = 0; // current table index + + // multiline IF + nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 ); + eTok = Peek(); + while( !( eTok == ELSEIF || eTok == ELSE || eTok == ENDIF ) && + !bAbort && Parse() ) + { + eTok = Peek(); + if( IsEof() ) + { + Error( ERRCODE_BASIC_BAD_BLOCK, IF ); bAbort = true; return; + } + } + while( eTok == ELSEIF ) + { + // jump to ENDIF in case of a successful IF/ELSEIF + if( iJmp >= JMP_TABLE_SIZE ) + { + Error( ERRCODE_BASIC_PROG_TOO_LARGE ); bAbort = true; return; + } + pnJmpToEndLbl[iJmp++] = aGen.Gen( SbiOpcode::JUMP_, 0 ); + + Next(); + aGen.BackChain( nEndLbl ); + + aGen.Statement(); + auto pCond = std::make_unique<SbiExpression>( this ); + pCond->Gen(); + nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 ); + pCond.reset(); + TestToken( THEN ); + eTok = Peek(); + while( !( eTok == ELSEIF || eTok == ELSE || eTok == ENDIF ) && + !bAbort && Parse() ) + { + eTok = Peek(); + if( IsEof() ) + { + Error( ERRCODE_BASIC_BAD_BLOCK, ELSEIF ); bAbort = true; return; + } + } + } + if( eTok == ELSE ) + { + Next(); + sal_uInt32 nElseLbl = nEndLbl; + nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 ); + aGen.BackChain( nElseLbl ); + + aGen.Statement(); + StmntBlock( ENDIF ); + } + else if( eTok == ENDIF ) + Next(); + + + while( iJmp > 0 ) + { + iJmp--; + aGen.BackChain( pnJmpToEndLbl[iJmp] ); + } + } + else + { + // single line IF + bSingleLineIf = true; + nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 ); + Push( eCurTok ); + // tdf#128263: update push positions to correctly restore in Next() + nPLine = nLine; + nPCol1 = nCol1; + nPCol2 = nCol2; + + while( !bAbort ) + { + if( !Parse() ) break; + eTok = Peek(); + if( eTok == ELSE || eTok == EOLN || eTok == REM ) + break; + } + if( eTok == ELSE ) + { + Next(); + sal_uInt32 nElseLbl = nEndLbl; + nEndLbl = aGen.Gen( SbiOpcode::JUMP_, 0 ); + aGen.BackChain( nElseLbl ); + while( !bAbort ) + { + if( !Parse() ) break; + eTok = Peek(); + if( eTok == EOLN || eTok == REM ) + break; + } + } + bSingleLineIf = false; + } + aGen.BackChain( nEndLbl ); +} + +// ELSE/ELSEIF/ENDIF without IF + +void SbiParser::NoIf() +{ + Error( ERRCODE_BASIC_NO_IF ); + StmntBlock( ENDIF ); +} + +// DO WHILE...LOOP +// DO ... LOOP WHILE + +void SbiParser::DoLoop() +{ + sal_uInt32 nStartLbl = aGen.GetPC(); + OpenBlock( DO ); + SbiToken eTok = Next(); + if( IsEoln( eTok ) ) + { + // DO ... LOOP [WHILE|UNTIL expr] + StmntBlock( LOOP ); + eTok = Next(); + if( eTok == UNTIL || eTok == WHILE ) + { + SbiExpression aExpr( this ); + aExpr.Gen(); + aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPF_ : SbiOpcode::JUMPT_, nStartLbl ); + } else + if (eTok == EOLN || eTok == REM) + aGen.Gen (SbiOpcode::JUMP_, nStartLbl); + else + Error( ERRCODE_BASIC_EXPECTED, WHILE ); + } + else + { + // DO [WHILE|UNTIL expr] ... LOOP + if( eTok == UNTIL || eTok == WHILE ) + { + SbiExpression aCond( this ); + aCond.Gen(); + } + sal_uInt32 nEndLbl = aGen.Gen( eTok == UNTIL ? SbiOpcode::JUMPT_ : SbiOpcode::JUMPF_, 0 ); + StmntBlock( LOOP ); + TestEoln(); + aGen.Gen( SbiOpcode::JUMP_, nStartLbl ); + aGen.BackChain( nEndLbl ); + } + CloseBlock(); +} + +// WHILE ... WEND + +void SbiParser::While() +{ + SbiExpression aCond( this ); + sal_uInt32 nStartLbl = aGen.GetPC(); + aCond.Gen(); + sal_uInt32 nEndLbl = aGen.Gen( SbiOpcode::JUMPF_, 0 ); + StmntBlock( WEND ); + aGen.Gen( SbiOpcode::JUMP_, nStartLbl ); + aGen.BackChain( nEndLbl ); +} + +// FOR var = expr TO expr STEP + +void SbiParser::For() +{ + bool bForEach = ( Peek() == EACH ); + if( bForEach ) + Next(); + SbiExpression aLvalue( this, SbOPERAND ); + aLvalue.Gen(); // variable on the Stack + + if( bForEach ) + { + TestToken( IN_ ); + SbiExpression aCollExpr( this, SbOPERAND ); + aCollExpr.Gen(); // Collection var to for stack + TestEoln(); + aGen.Gen( SbiOpcode::INITFOREACH_ ); + } + else + { + TestToken( EQ ); + SbiExpression aStartExpr( this ); + aStartExpr.Gen(); + TestToken( TO ); + SbiExpression aStopExpr( this ); + aStopExpr.Gen(); + if( Peek() == STEP ) + { + Next(); + SbiExpression aStepExpr( this ); + aStepExpr.Gen(); + } + else + { + SbiExpression aOne( this, 1, SbxINTEGER ); + aOne.Gen(); + } + TestEoln(); + // The stack has all 4 elements now: variable, start, end, increment + // bind start value + aGen.Gen( SbiOpcode::INITFOR_ ); + } + + sal_uInt32 nLoop = aGen.GetPC(); + // do tests, maybe free the stack + sal_uInt32 nEndTarget = aGen.Gen( SbiOpcode::TESTFOR_, 0 ); + OpenBlock( FOR ); + StmntBlock( NEXT ); + aGen.Gen( SbiOpcode::NEXT_ ); + aGen.Gen( SbiOpcode::JUMP_, nLoop ); + // are there variables after NEXT? + if( Peek() == SYMBOL ) + { + SbiExpression aVar( this, SbOPERAND ); + if( aVar.GetRealVar() != aLvalue.GetRealVar() ) + Error( ERRCODE_BASIC_EXPECTED, aLvalue.GetRealVar()->GetName() ); + } + aGen.BackChain( nEndTarget ); + CloseBlock(); +} + +// WITH .. END WITH + +void SbiParser::With() +{ + SbiExpression aVar( this, SbOPERAND ); + + SbiExprNode *pNode = aVar.GetExprNode()->GetRealNode(); + if (!pNode) + return; + SbiSymDef* pDef = pNode->GetVar(); + // Variant, from 27.6.1997, #41090: empty -> must be Object + if( pDef->GetType() == SbxVARIANT || pDef->GetType() == SbxEMPTY ) + pDef->SetType( SbxOBJECT ); + else if( pDef->GetType() != SbxOBJECT ) + Error( ERRCODE_BASIC_NEEDS_OBJECT ); + + + pNode->SetType( SbxOBJECT ); + + OpenBlock( NIL, aVar.GetExprNode() ); + StmntBlock( ENDWITH ); + CloseBlock(); +} + +// LOOP/NEXT/WEND without construct + +void SbiParser::BadBlock() +{ + if( eEndTok ) + Error( ERRCODE_BASIC_BAD_BLOCK, eEndTok ); + else + Error( ERRCODE_BASIC_BAD_BLOCK, "Loop/Next/Wend" ); +} + +// On expr Goto/Gosub n,n,n... + +void SbiParser::OnGoto() +{ + SbiExpression aCond( this ); + aCond.Gen(); + sal_uInt32 nLabelsTarget = aGen.Gen( SbiOpcode::ONJUMP_, 0 ); + SbiToken eTok = Next(); + if( eTok != GOTO && eTok != GOSUB ) + { + Error( ERRCODE_BASIC_EXPECTED, "GoTo/GoSub" ); + eTok = GOTO; + } + + sal_uInt32 nLbl = 0; + do + { + Next(); // get label + if( MayBeLabel() ) + { + sal_uInt32 nOff = pProc->GetLabels().Reference( aSym ); + aGen.Gen( SbiOpcode::JUMP_, nOff ); + nLbl++; + } + else Error( ERRCODE_BASIC_LABEL_EXPECTED ); + } + while( !bAbort && TestComma() ); + if( eTok == GOSUB ) + nLbl |= 0x8000; + aGen.Patch( nLabelsTarget, nLbl ); +} + +// GOTO/GOSUB + +void SbiParser::Goto() +{ + SbiOpcode eOp = eCurTok == GOTO ? SbiOpcode::JUMP_ : SbiOpcode::GOSUB_; + Next(); + if( MayBeLabel() ) + { + sal_uInt32 nOff = pProc->GetLabels().Reference( aSym ); + aGen.Gen( eOp, nOff ); + } + else Error( ERRCODE_BASIC_LABEL_EXPECTED ); +} + +// RETURN [label] + +void SbiParser::Return() +{ + Next(); + if( MayBeLabel() ) + { + sal_uInt32 nOff = pProc->GetLabels().Reference( aSym ); + aGen.Gen( SbiOpcode::RETURN_, nOff ); + } + else aGen.Gen( SbiOpcode::RETURN_, 0 ); +} + +// SELECT CASE + +void SbiParser::Select() +{ + TestToken( CASE ); + SbiExpression aCase( this ); + SbiToken eTok = NIL; + aCase.Gen(); + aGen.Gen( SbiOpcode::CASE_ ); + TestEoln(); + sal_uInt32 nNextTarget = 0; + sal_uInt32 nDoneTarget = 0; + bool bElse = false; + + while( !bAbort ) + { + eTok = Next(); + if( eTok == CASE ) + { + if( nNextTarget ) + { + aGen.BackChain( nNextTarget ); + nNextTarget = 0; + } + aGen.Statement(); + + bool bDone = false; + sal_uInt32 nTrueTarget = 0; + if( Peek() == ELSE ) + { + // CASE ELSE + Next(); + bElse = true; + } + else while( !bDone ) + { + if( bElse ) + Error( ERRCODE_BASIC_SYNTAX ); + SbiToken eTok2 = Peek(); + if( eTok2 == IS || ( eTok2 >= EQ && eTok2 <= GE ) ) + { // CASE [IS] operator expr + if( eTok2 == IS ) + Next(); + eTok2 = Peek(); + if( eTok2 < EQ || eTok2 > GE ) + Error( ERRCODE_BASIC_SYNTAX ); + else Next(); + SbiExpression aCompare( this ); + aCompare.Gen(); + nTrueTarget = aGen.Gen( + SbiOpcode::CASEIS_, nTrueTarget, + sal::static_int_cast< sal_uInt16 >( + SbxEQ + ( eTok2 - EQ ) ) ); + } + else + { // CASE expr | expr TO expr + SbiExpression aCase1( this ); + aCase1.Gen(); + if( Peek() == TO ) + { + // CASE a TO b + Next(); + SbiExpression aCase2( this ); + aCase2.Gen(); + nTrueTarget = aGen.Gen( SbiOpcode::CASETO_, nTrueTarget ); + } + else + // CASE a + nTrueTarget = aGen.Gen( SbiOpcode::CASEIS_, nTrueTarget, SbxEQ ); + + } + if( Peek() == COMMA ) Next(); + else + { + TestEoln(); + bDone = true; + } + } + + if( !bElse ) + { + nNextTarget = aGen.Gen( SbiOpcode::JUMP_, nNextTarget ); + aGen.BackChain( nTrueTarget ); + } + // build the statement body + while( !bAbort ) + { + eTok = Peek(); + if( eTok == CASE || eTok == ENDSELECT ) + break; + if( !Parse() ) goto done; + eTok = Peek(); + if( eTok == CASE || eTok == ENDSELECT ) + break; + } + if( !bElse ) + nDoneTarget = aGen.Gen( SbiOpcode::JUMP_, nDoneTarget ); + } + else if( !IsEoln( eTok ) ) + break; + } +done: + if( eTok != ENDSELECT ) + Error( ERRCODE_BASIC_EXPECTED, ENDSELECT ); + if( nNextTarget ) + aGen.BackChain( nNextTarget ); + aGen.BackChain( nDoneTarget ); + aGen.Gen( SbiOpcode::ENDCASE_ ); +} + +// ON Error/Variable + +void SbiParser::On() +{ + SbiToken eTok = Peek(); + OUString aString = SbiTokenizer::Symbol(eTok); + if (aString.equalsIgnoreAsciiCase("ERROR")) + { + eTok = ERROR_; // Error comes as SYMBOL + } + if( eTok != ERROR_ && eTok != LOCAL ) + { + OnGoto(); + } + else + { + if( eTok == LOCAL ) + { + Next(); + } + Next (); // no more TestToken, as there'd be an error otherwise + + Next(); // get token after error + if( eCurTok == GOTO ) + { + // ON ERROR GOTO label|0 + Next(); + bool bError_ = false; + if( MayBeLabel() ) + { + if( eCurTok == NUMBER && !nVal ) + { + aGen.Gen( SbiOpcode::STDERROR_ ); + } + else + { + sal_uInt32 nOff = pProc->GetLabels().Reference( aSym ); + aGen.Gen( SbiOpcode::ERRHDL_, nOff ); + } + } + else if( eCurTok == MINUS ) + { + Next(); + if( eCurTok == NUMBER && nVal == 1 ) + { + aGen.Gen( SbiOpcode::STDERROR_ ); + } + else + { + bError_ = true; + } + } + if( bError_ ) + { + Error( ERRCODE_BASIC_LABEL_EXPECTED ); + } + } + else if( eCurTok == RESUME ) + { + TestToken( NEXT ); + aGen.Gen( SbiOpcode::NOERROR_ ); + } + else Error( ERRCODE_BASIC_EXPECTED, "GoTo/Resume" ); + } +} + +// RESUME [0]|NEXT|label + +void SbiParser::Resume() +{ + sal_uInt32 nLbl; + + switch( Next() ) + { + case EOS: + case EOLN: + aGen.Gen( SbiOpcode::RESUME_, 0 ); + break; + case NEXT: + aGen.Gen( SbiOpcode::RESUME_, 1 ); + Next(); + break; + case NUMBER: + if( !nVal ) + { + aGen.Gen( SbiOpcode::RESUME_, 0 ); + break; + } + [[fallthrough]]; + case SYMBOL: + if( MayBeLabel() ) + { + nLbl = pProc->GetLabels().Reference( aSym ); + aGen.Gen( SbiOpcode::RESUME_, nLbl ); + Next(); + break; + } + [[fallthrough]]; + default: + Error( ERRCODE_BASIC_LABEL_EXPECTED ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/parser.cxx b/basic/source/comp/parser.cxx new file mode 100644 index 000000000..777db4071 --- /dev/null +++ b/basic/source/comp/parser.cxx @@ -0,0 +1,889 @@ +/* -*- 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/sbxmeth.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbstar.hxx> +#include <basic/sbx.hxx> +#include <parser.hxx> +#include <com/sun/star/script/ModuleType.hpp> +#include <rtl/character.hxx> + +struct SbiParseStack { // "Stack" for statement-blocks + SbiParseStack* pNext; // Chain + SbiExprNode* pWithVar; + SbiToken eExitTok; + sal_uInt32 nChain; // JUMP-Chain +}; + +namespace { + +struct SbiStatement { + SbiToken eTok; + void( SbiParser::*Func )(); + bool bMain; // true: OK outside the SUB + bool bSubr; // true: OK inside the SUB +}; + +} + +#define Y true +#define N false + +const SbiStatement StmntTable [] = { +{ ATTRIBUTE, &SbiParser::Attribute, Y, Y, }, // ATTRIBUTE +{ CALL, &SbiParser::Call, N, Y, }, // CALL +{ CLOSE, &SbiParser::Close, N, Y, }, // CLOSE +{ CONST_, &SbiParser::Dim, Y, Y, }, // CONST +{ DECLARE, &SbiParser::Declare, Y, N, }, // DECLARE +{ DEFBOOL, &SbiParser::DefXXX, Y, N, }, // DEFBOOL +{ DEFCUR, &SbiParser::DefXXX, Y, N, }, // DEFCUR +{ DEFDATE, &SbiParser::DefXXX, Y, N, }, // DEFDATE +{ DEFDBL, &SbiParser::DefXXX, Y, N, }, // DEFDBL +{ DEFERR, &SbiParser::DefXXX, Y, N, }, // DEFERR +{ DEFINT, &SbiParser::DefXXX, Y, N, }, // DEFINT +{ DEFLNG, &SbiParser::DefXXX, Y, N, }, // DEFLNG +{ DEFOBJ, &SbiParser::DefXXX, Y, N, }, // DEFOBJ +{ DEFSNG, &SbiParser::DefXXX, Y, N, }, // DEFSNG +{ DEFSTR, &SbiParser::DefXXX, Y, N, }, // DEFSTR +{ DEFVAR, &SbiParser::DefXXX, Y, N, }, // DEFVAR +{ DIM, &SbiParser::Dim, Y, Y, }, // DIM +{ DO, &SbiParser::DoLoop, N, Y, }, // DO +{ ELSE, &SbiParser::NoIf, N, Y, }, // ELSE +{ ELSEIF, &SbiParser::NoIf, N, Y, }, // ELSEIF +{ ENDIF, &SbiParser::NoIf, N, Y, }, // ENDIF +{ END, &SbiParser::Stop, N, Y, }, // END +{ ENUM, &SbiParser::Enum, Y, N, }, // TYPE +{ ERASE, &SbiParser::Erase, N, Y, }, // ERASE +{ ERROR_, &SbiParser::ErrorStmnt, N, Y, }, // ERROR +{ EXIT, &SbiParser::Exit, N, Y, }, // EXIT +{ FOR, &SbiParser::For, N, Y, }, // FOR +{ FUNCTION, &SbiParser::SubFunc, Y, N, }, // FUNCTION +{ GOSUB, &SbiParser::Goto, N, Y, }, // GOSUB +{ GLOBAL, &SbiParser::Dim, Y, N, }, // GLOBAL +{ GOTO, &SbiParser::Goto, N, Y, }, // GOTO +{ IF, &SbiParser::If, N, Y, }, // IF +{ IMPLEMENTS, &SbiParser::Implements, Y, N, }, // IMPLEMENTS +{ INPUT, &SbiParser::Input, N, Y, }, // INPUT +{ LET, &SbiParser::Assign, N, Y, }, // LET +{ LINE, &SbiParser::Line, N, Y, }, // LINE, -> LINE INPUT (#i92642) +{ LINEINPUT,&SbiParser::LineInput, N, Y, }, // LINE INPUT +{ LOOP, &SbiParser::BadBlock, N, Y, }, // LOOP +{ LSET, &SbiParser::LSet, N, Y, }, // LSET +{ NAME, &SbiParser::Name, N, Y, }, // NAME +{ NEXT, &SbiParser::BadBlock, N, Y, }, // NEXT +{ ON, &SbiParser::On, N, Y, }, // ON +{ OPEN, &SbiParser::Open, N, Y, }, // OPEN +{ OPTION, &SbiParser::Option, Y, N, }, // OPTION +{ PRINT, &SbiParser::Print, N, Y, }, // PRINT +{ PRIVATE, &SbiParser::Dim, Y, N, }, // PRIVATE +{ PROPERTY, &SbiParser::SubFunc, Y, N, }, // FUNCTION +{ PUBLIC, &SbiParser::Dim, Y, N, }, // PUBLIC +{ REDIM, &SbiParser::ReDim, N, Y, }, // DIM +{ RESUME, &SbiParser::Resume, N, Y, }, // RESUME +{ RETURN, &SbiParser::Return, N, Y, }, // RETURN +{ RSET, &SbiParser::RSet, N, Y, }, // RSET +{ SELECT, &SbiParser::Select, N, Y, }, // SELECT +{ SET, &SbiParser::Set, N, Y, }, // SET +{ STATIC, &SbiParser::Static, Y, Y, }, // STATIC +{ STOP, &SbiParser::Stop, N, Y, }, // STOP +{ SUB, &SbiParser::SubFunc, Y, N, }, // SUB +{ TYPE, &SbiParser::Type, Y, N, }, // TYPE +{ UNTIL, &SbiParser::BadBlock, N, Y, }, // UNTIL +{ WHILE, &SbiParser::While, N, Y, }, // WHILE +{ WEND, &SbiParser::BadBlock, N, Y, }, // WEND +{ WITH, &SbiParser::With, N, Y, }, // WITH +{ WRITE, &SbiParser::Write, N, Y, }, // WRITE + +{ NIL, nullptr, N, N } +}; + +SbiParser::SbiParser( StarBASIC* pb, SbModule* pm ) + : SbiTokenizer( pm->GetSource32(), pb ), + pStack(nullptr), + pProc(nullptr), + pWithVar(nullptr), + eEndTok(NIL), + bGblDefs(false), + bNewGblDefs(false), + bSingleLineIf(false), + bCodeCompleting(false), + aGlobals( aGblStrings, SbGLOBAL, this ), + aPublics( aGblStrings, SbPUBLIC, this ), + aRtlSyms( aGblStrings, SbRTL, this ), + aGen( *pm, this ), + nBase(0), + bExplicit(false) +{ + bClassModule = ( pm->GetModuleType() == css::script::ModuleType::CLASS ); + pPool = &aPublics; + for(SbxDataType & eDefType : eDefTypes) + eDefType = SbxVARIANT; // no explicit default type + + aPublics.SetParent( &aGlobals ); + aGlobals.SetParent( &aRtlSyms ); + + + nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 ); + + rTypeArray = new SbxArray; // array for user defined types + rEnumArray = new SbxArray; // array for Enum types + bVBASupportOn = pm->IsVBACompat(); + if ( bVBASupportOn ) + EnableCompatibility(); + +} + +SbiParser::~SbiParser() { } + +// part of the runtime-library? +SbiSymDef* SbiParser::CheckRTLForSym(const OUString& rSym, SbxDataType eType) +{ + SbxVariable* pVar = GetBasic()->GetRtl()->Find(rSym, SbxClassType::DontCare); + if (!pVar) + return nullptr; + + if (SbxMethod* pMethod = dynamic_cast<SbxMethod*>(pVar)) + { + SbiProcDef* pProc_ = aRtlSyms.AddProc( rSym ); + if (pMethod->IsRuntimeFunction()) + { + pProc_->SetType( pMethod->GetRuntimeFunctionReturnType() ); + } + else + { + pProc_->SetType( pVar->GetType() ); + } + return pProc_; + } + + + SbiSymDef* pDef = aRtlSyms.AddSym(rSym); + pDef->SetType(eType); + return pDef; +} + +// close global chain + +bool SbiParser::HasGlobalCode() +{ + if( bGblDefs && nGblChain ) + { + aGen.BackChain( nGblChain ); + aGen.Gen( SbiOpcode::LEAVE_ ); + nGblChain = 0; + } + return bGblDefs; +} + +void SbiParser::OpenBlock( SbiToken eTok, SbiExprNode* pVar ) +{ + SbiParseStack* p = new SbiParseStack; + p->eExitTok = eTok; + p->nChain = 0; + p->pWithVar = pWithVar; + p->pNext = pStack; + pStack = p; + pWithVar = pVar; + + // #29955 service the for-loop level + if( eTok == FOR ) + aGen.IncForLevel(); +} + +void SbiParser::CloseBlock() +{ + if( !pStack ) + return; + + SbiParseStack* p = pStack; + + // #29955 service the for-loop level + if( p->eExitTok == FOR ) + aGen.DecForLevel(); + + aGen.BackChain( p->nChain ); + pStack = p->pNext; + pWithVar = p->pWithVar; + delete p; +} + +// EXIT ... + +void SbiParser::Exit() +{ + SbiToken eTok = Next(); + for( SbiParseStack* p = pStack; p; p = p->pNext ) + { + SbiToken eExitTok = p->eExitTok; + if( eTok == eExitTok || + (eTok == PROPERTY && (eExitTok == GET || eExitTok == LET) ) ) // #i109051 + { + p->nChain = aGen.Gen( SbiOpcode::JUMP_, p->nChain ); + return; + } + } + if( pStack ) + Error( ERRCODE_BASIC_EXPECTED, pStack->eExitTok ); + else + Error( ERRCODE_BASIC_BAD_EXIT ); +} + +bool SbiParser::TestSymbol() +{ + Peek(); + if( eCurTok == SYMBOL ) + { + Next(); return true; + } + Error( ERRCODE_BASIC_SYMBOL_EXPECTED ); + return false; +} + + +bool SbiParser::TestToken( SbiToken t ) +{ + if( Peek() == t ) + { + Next(); return true; + } + else + { + Error( ERRCODE_BASIC_EXPECTED, t ); + return false; + } +} + + +bool SbiParser::TestComma() +{ + SbiToken eTok = Peek(); + if( IsEoln( eTok ) ) + { + Next(); + return false; + } + else if( eTok != COMMA ) + { + Error( ERRCODE_BASIC_EXPECTED, COMMA ); + return false; + } + Next(); + return true; +} + + +void SbiParser::TestEoln() +{ + if( !IsEoln( Next() ) ) + { + Error( ERRCODE_BASIC_EXPECTED, EOLN ); + while( !IsEoln( Next() ) ) {} + } +} + + +void SbiParser::StmntBlock( SbiToken eEnd ) +{ + SbiToken xe = eEndTok; + eEndTok = eEnd; + while( !bAbort && Parse() ) {} + eEndTok = xe; + if( IsEof() ) + { + Error( ERRCODE_BASIC_BAD_BLOCK, eEnd ); + bAbort = true; + } +} + +void SbiParser::SetCodeCompleting( bool b ) +{ + bCodeCompleting = b; +} + + +bool SbiParser::Parse() +{ + if( bAbort ) return false; + + EnableErrors(); + + bErrorIsSymbol = false; + Peek(); + bErrorIsSymbol = true; + + if( IsEof() ) + { + // AB #33133: If no sub has been created before, + // the global chain must be closed here! + // AB #40689: Due to the new static-handling there + // can be another nGblChain, so ask for it before. + if( bNewGblDefs && nGblChain == 0 ) + nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 ); + return false; + } + + + if( IsEoln( eCurTok ) ) + { + Next(); return true; + } + + if( !bSingleLineIf && MayBeLabel( true ) ) + { + // is a label + if( !pProc ) + Error( ERRCODE_BASIC_NOT_IN_MAIN, aSym ); + else + pProc->GetLabels().Define( aSym ); + Next(); Peek(); + + if( IsEoln( eCurTok ) ) + { + Next(); return true; + } + } + + // end of parsing? + if( eCurTok == eEndTok || + ( bVBASupportOn && // #i109075 + (eCurTok == ENDFUNC || eCurTok == ENDPROPERTY || eCurTok == ENDSUB) && + (eEndTok == ENDFUNC || eEndTok == ENDPROPERTY || eEndTok == ENDSUB) ) ) + { + Next(); + if( eCurTok != NIL ) + aGen.Statement(); + return false; + } + + // comment? + if( eCurTok == REM ) + { + Next(); return true; + } + + // In vba it's possible to do Error.foobar ( even if it results in + // a runtime error + if ( eCurTok == ERROR_ && IsVBASupportOn() ) // we probably need to define a subset of keywords where this madness applies e.g. if ( IsVBASupportOn() && SymbolCanBeRedined( eCurTok ) ) + { + SbiTokenizer tokens( *this ); + tokens.Next(); + if ( tokens.Peek() == DOT ) + { + eCurTok = SYMBOL; + ePush = eCurTok; + } + } + // if there's a symbol, it's either a variable (LET) + // or a SUB-procedure (CALL without brackets) + // DOT for assignments in the WITH-block: .A=5 + if( eCurTok == SYMBOL || eCurTok == DOT ) + { + if( !pProc ) + Error( ERRCODE_BASIC_EXPECTED, SUB ); + else + { + // for correct line and column... + Next(); + Push( eCurTok ); + aGen.Statement(); + Symbol(nullptr); + } + } + else + { + Next(); + + // statement parsers + + const SbiStatement* p; + for( p = StmntTable; p->eTok != NIL; p++ ) + if( p->eTok == eCurTok ) + break; + if( p->eTok != NIL ) + { + if( !pProc && !p->bMain ) + Error( ERRCODE_BASIC_NOT_IN_MAIN, eCurTok ); + else if( pProc && !p->bSubr ) + Error( ERRCODE_BASIC_NOT_IN_SUBR, eCurTok ); + else + { + // AB #41606/#40689: Due to the new static-handling there + // can be another nGblChain, so ask for it before. + if( bNewGblDefs && nGblChain == 0 && + ( eCurTok == SUB || eCurTok == FUNCTION || eCurTok == PROPERTY ) ) + { + nGblChain = aGen.Gen( SbiOpcode::JUMP_, 0 ); + bNewGblDefs = false; + } + // statement-opcode at the beginning of a sub, too, please + if( ( p->bSubr && (eCurTok != STATIC || Peek() == SUB || Peek() == FUNCTION ) ) || + eCurTok == SUB || eCurTok == FUNCTION ) + aGen.Statement(); + (this->*( p->Func ) )(); + ErrCode nSbxErr = SbxBase::GetError(); + if( nSbxErr ) + { + SbxBase::ResetError(); + Error( nSbxErr ); + } + } + } + else + Error( ERRCODE_BASIC_UNEXPECTED, eCurTok ); + } + + // test for the statement's end - + // might also be an ELSE, as there must not necessary be a : before the ELSE! + + if( !IsEos() ) + { + Peek(); + if( !IsEos() && eCurTok != ELSE ) + { + // if the parsing has been aborted, jump over to the ":" + Error( ERRCODE_BASIC_UNEXPECTED, eCurTok ); + while( !IsEos() ) Next(); + } + } + // The parser aborts at the end, the + // next token has not been fetched yet! + return true; +} + + +SbiExprNode* SbiParser::GetWithVar() +{ + if( pWithVar ) + return pWithVar; + + SbiParseStack* p = pStack; + while( p ) + { + // LoopVar can at the moment only be for with + if( p->pWithVar ) + return p->pWithVar; + p = p->pNext; + } + return nullptr; +} + + +// assignment or subroutine call + +void SbiParser::Symbol( const KeywordSymbolInfo* pKeywordSymbolInfo ) +{ + SbiExprMode eMode = bVBASupportOn ? EXPRMODE_STANDALONE : EXPRMODE_STANDARD; + SbiExpression aVar( this, SbSYMBOL, eMode, pKeywordSymbolInfo ); + + bool bEQ = ( Peek() == EQ ); + if( !bEQ && bVBASupportOn && aVar.IsBracket() ) + Error( ERRCODE_BASIC_EXPECTED, "=" ); + + RecursiveMode eRecMode = ( bEQ ? PREVENT_CALL : FORCE_CALL ); + bool bSpecialMidHandling = false; + SbiSymDef* pDef = aVar.GetRealVar(); + if( bEQ && pDef && pDef->GetScope() == SbRTL ) + { + OUString aRtlName = pDef->GetName(); + if( aRtlName.equalsIgnoreAsciiCase("Mid") ) + { + SbiExprNode* pExprNode = aVar.GetExprNode(); + if( pExprNode && pExprNode->GetNodeType() == SbxVARVAL ) + { + SbiExprList* pPar = pExprNode->GetParameters(); + short nParCount = pPar ? pPar->GetSize() : 0; + if( nParCount == 2 || nParCount == 3 ) + { + if( nParCount == 2 ) + pPar->addExpression( std::make_unique<SbiExpression>( this, -1, SbxLONG ) ); + + TestToken( EQ ); + pPar->addExpression( std::make_unique<SbiExpression>( this ) ); + + bSpecialMidHandling = true; + } + } + } + } + aVar.Gen( eRecMode ); + if( bSpecialMidHandling ) + return; + + if( !bEQ ) + { + aGen.Gen( SbiOpcode::GET_ ); + } + else + { + // so it must be an assignment! + if( !aVar.IsLvalue() ) + Error( ERRCODE_BASIC_LVALUE_EXPECTED ); + TestToken( EQ ); + SbiExpression aExpr( this ); + aExpr.Gen(); + SbiOpcode eOp = SbiOpcode::PUT_; + if( pDef ) + { + if( pDef->GetConstDef() ) + Error( ERRCODE_BASIC_DUPLICATE_DEF, pDef->GetName() ); + if( pDef->GetType() == SbxOBJECT ) + { + eOp = SbiOpcode::SET_; + if( pDef->GetTypeId() ) + { + aGen.Gen( SbiOpcode::SETCLASS_, pDef->GetTypeId() ); + return; + } + } + } + aGen.Gen( eOp ); + } +} + + +void SbiParser::Assign() +{ + SbiExpression aLvalue( this, SbLVALUE ); + TestToken( EQ ); + SbiExpression aExpr( this ); + aLvalue.Gen(); + aExpr.Gen(); + sal_uInt16 nLen = 0; + SbiSymDef* pDef = aLvalue.GetRealVar(); + { + if( pDef->GetConstDef() ) + Error( ERRCODE_BASIC_DUPLICATE_DEF, pDef->GetName() ); + nLen = aLvalue.GetRealVar()->GetLen(); + } + if( nLen ) + aGen.Gen( SbiOpcode::PAD_, nLen ); + aGen.Gen( SbiOpcode::PUT_ ); +} + +// assignments of an object-variable + +void SbiParser::Set() +{ + SbiExpression aLvalue( this, SbLVALUE ); + SbxDataType eType = aLvalue.GetType(); + if( eType != SbxOBJECT && eType != SbxEMPTY && eType != SbxVARIANT ) + Error( ERRCODE_BASIC_INVALID_OBJECT ); + TestToken( EQ ); + SbiSymDef* pDef = aLvalue.GetRealVar(); + if( pDef->GetConstDef() ) + Error( ERRCODE_BASIC_DUPLICATE_DEF, pDef->GetName() ); + + SbiToken eTok = Peek(); + if( eTok == NEW ) + { + Next(); + auto pTypeDef = std::make_unique<SbiSymDef>( OUString() ); + TypeDecl( *pTypeDef, true ); + + aLvalue.Gen(); + aGen.Gen( SbiOpcode::CREATE_, pDef->GetId(), pTypeDef->GetTypeId() ); + aGen.Gen( SbiOpcode::SETCLASS_, pDef->GetTypeId() ); + } + else + { + SbiExpression aExpr( this ); + aLvalue.Gen(); + aExpr.Gen(); + // It's a good idea to distinguish between + // set something = another & + // something = another + // ( it's necessary for vba objects where set is object + // specific and also doesn't involve processing default params ) + if( pDef->GetTypeId() ) + { + if ( bVBASupportOn ) + aGen.Gen( SbiOpcode::VBASETCLASS_, pDef->GetTypeId() ); + else + aGen.Gen( SbiOpcode::SETCLASS_, pDef->GetTypeId() ); + } + else + { + if ( bVBASupportOn ) + aGen.Gen( SbiOpcode::VBASET_ ); + else + aGen.Gen( SbiOpcode::SET_ ); + } + } +} + +// JSM 07.10.95 +void SbiParser::LSet() +{ + SbiExpression aLvalue( this, SbLVALUE ); + if( aLvalue.GetType() != SbxSTRING ) + { + Error( ERRCODE_BASIC_INVALID_OBJECT ); + } + TestToken( EQ ); + SbiSymDef* pDef = aLvalue.GetRealVar(); + if( pDef && pDef->GetConstDef() ) + { + Error( ERRCODE_BASIC_DUPLICATE_DEF, pDef->GetName() ); + } + SbiExpression aExpr( this ); + aLvalue.Gen(); + aExpr.Gen(); + aGen.Gen( SbiOpcode::LSET_ ); +} + +// JSM 07.10.95 +void SbiParser::RSet() +{ + SbiExpression aLvalue( this, SbLVALUE ); + if( aLvalue.GetType() != SbxSTRING ) + { + Error( ERRCODE_BASIC_INVALID_OBJECT ); + } + TestToken( EQ ); + SbiSymDef* pDef = aLvalue.GetRealVar(); + if( pDef && pDef->GetConstDef() ) + Error( ERRCODE_BASIC_DUPLICATE_DEF, pDef->GetName() ); + SbiExpression aExpr( this ); + aLvalue.Gen(); + aExpr.Gen(); + aGen.Gen( SbiOpcode::RSET_ ); +} + +// DEFINT, DEFLNG, DEFSNG, DEFDBL, DEFSTR and so on + +void SbiParser::DefXXX() +{ + sal_Unicode ch1, ch2; + SbxDataType t = SbxDataType( eCurTok - DEFINT + SbxINTEGER ); + + while( !bAbort ) + { + if( Next() != SYMBOL ) break; + ch1 = rtl::toAsciiUpperCase(aSym[0]); + ch2 = 0; + if( Peek() == MINUS ) + { + Next(); + if( Next() != SYMBOL ) Error( ERRCODE_BASIC_SYMBOL_EXPECTED ); + else + { + ch2 = rtl::toAsciiUpperCase(aSym[0]); + if( ch2 < ch1 ) + { + Error( ERRCODE_BASIC_SYNTAX ); + ch2 = 0; + } + } + } + if (!ch2) ch2 = ch1; + ch1 -= 'A'; ch2 -= 'A'; + for (; ch1 <= ch2; ch1++) eDefTypes[ ch1 ] = t; + if( !TestComma() ) break; + } +} + +// STOP/SYSTEM + +void SbiParser::Stop() +{ + aGen.Gen( SbiOpcode::STOP_ ); + Peek(); // #35694: only Peek(), so that EOL is recognized in Single-Line-If +} + +// IMPLEMENTS + +void SbiParser::Implements() +{ + if( !bClassModule ) + { + Error( ERRCODE_BASIC_UNEXPECTED, IMPLEMENTS ); + return; + } + + Peek(); + if( eCurTok != SYMBOL ) + { + Error( ERRCODE_BASIC_SYMBOL_EXPECTED ); + return; + } + + OUString aImplementedIface = aSym; + Next(); + if( Peek() == DOT ) + { + OUString aDotStr( '.' ); + while( Peek() == DOT ) + { + aImplementedIface += aDotStr; + Next(); + SbiToken ePeekTok = Peek(); + if( ePeekTok == SYMBOL || IsKwd( ePeekTok ) ) + { + Next(); + aImplementedIface += aSym; + } + else + { + Next(); + Error( ERRCODE_BASIC_SYMBOL_EXPECTED ); + break; + } + } + } + aIfaceVector.push_back( aImplementedIface ); +} + +void SbiParser::EnableCompatibility() +{ + if( !bCompatible ) + AddConstants(); + bCompatible = true; +} + +// OPTION + +void SbiParser::Option() +{ + switch( Next() ) + { + case BASIC_EXPLICIT: + bExplicit = true; break; + case BASE: + if( Next() == NUMBER && ( nVal == 0 || nVal == 1 ) ) + { + nBase = static_cast<short>(nVal); + break; + } + Error( ERRCODE_BASIC_EXPECTED, "0/1" ); + break; + case PRIVATE: + { + OUString aString = SbiTokenizer::Symbol(Next()); + if( !aString.equalsIgnoreAsciiCase("Module") ) + { + Error( ERRCODE_BASIC_EXPECTED, "Module" ); + } + break; + } + case COMPARE: + { + SbiToken eTok = Next(); + if( eTok == BINARY ) + { + } + else if( eTok == SYMBOL && GetSym().equalsIgnoreAsciiCase("text") ) + { + } + else + { + Error( ERRCODE_BASIC_EXPECTED, "Text/Binary" ); + } + break; + } + case COMPATIBLE: + EnableCompatibility(); + break; + + case CLASSMODULE: + bClassModule = true; + aGen.GetModule().SetModuleType( css::script::ModuleType::CLASS ); + break; + case VBASUPPORT: // Option VBASupport used to override the module mode ( in fact this must reset the mode + if( Next() == NUMBER ) + { + if ( nVal == 1 || nVal == 0 ) + { + bVBASupportOn = ( nVal == 1 ); + if ( bVBASupportOn ) + { + EnableCompatibility(); + } + // if the module setting is different + // reset it to what the Option tells us + if ( bVBASupportOn != aGen.GetModule().IsVBACompat() ) + { + aGen.GetModule().SetVBACompat( bVBASupportOn ); + } + break; + } + } + Error( ERRCODE_BASIC_EXPECTED, "0/1" ); + break; + default: + Error( ERRCODE_BASIC_BAD_OPTION, eCurTok ); + } +} + +static void addStringConst( SbiSymPool& rPool, const OUString& pSym, const OUString& rStr ) +{ + SbiConstDef* pConst = new SbiConstDef( pSym ); + pConst->SetType( SbxSTRING ); + pConst->Set( rStr ); + rPool.Add( pConst ); +} + +static void addNumericConst(SbiSymPool& rPool, const OUString& pSym, double nVal) +{ + SbiConstDef* pConst = new SbiConstDef(pSym); + pConst->Set(nVal, SbxDOUBLE); + rPool.Add(pConst); +} + +void SbiParser::AddConstants() +{ + // tdf#131563 - add vba color constants + // See https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/color-constants + addNumericConst(aPublics, "vbBlack", 0x0); + addNumericConst(aPublics, "vbRed", 0xFF); + addNumericConst(aPublics, "vbGreen", 0xFF00); + addNumericConst(aPublics, "vbYellow", 0xFFFF); + addNumericConst(aPublics, "vbBlue", 0xFF0000); + addNumericConst(aPublics, "vbMagenta", 0xFF00FF); + addNumericConst(aPublics, "vbCyan", 0xFFFF00); + addNumericConst(aPublics, "vbWhite", 0xFFFFFF); + + // #113063 Create constant RTL symbols + addStringConst( aPublics, "vbCr", "\x0D" ); + addStringConst( aPublics, "vbCrLf", "\x0D\x0A" ); + addStringConst( aPublics, "vbFormFeed", "\x0C" ); + addStringConst( aPublics, "vbLf", "\x0A" ); +#ifdef _WIN32 + addStringConst( aPublics, "vbNewLine", "\x0D\x0A" ); +#else + addStringConst( aPublics, "vbNewLine", "\x0A" ); +#endif + addStringConst( aPublics, "vbNullString", "" ); + addStringConst( aPublics, "vbTab", "\x09" ); + addStringConst( aPublics, "vbVerticalTab", "\x0B" ); + + addStringConst( aPublics, "vbNullChar", OUString(u'\0') ); +} + +// ERROR n + +void SbiParser::ErrorStmnt() +{ + SbiExpression aPar( this ); + aPar.Gen(); + aGen.Gen( SbiOpcode::ERROR_ ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/sbcomp.cxx b/basic/source/comp/sbcomp.cxx new file mode 100644 index 000000000..1a6a52b22 --- /dev/null +++ b/basic/source/comp/sbcomp.cxx @@ -0,0 +1,85 @@ +/* -*- 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/sbmeth.hxx> +#include <basic/sbstar.hxx> +#include <basic/sbx.hxx> +#include <parser.hxx> +#include <image.hxx> +#include <sbintern.hxx> +#include <sbobjmod.hxx> +#include <memory> + +// This routine is defined here, so that the +// compiler can be loaded as a discrete segment. + +bool SbModule::Compile() +{ + if( pImage ) + return true; + StarBASIC* pBasic = dynamic_cast<StarBASIC*>( GetParent() ); + if( !pBasic ) + return false; + SbxBase::ResetError(); + + SbModule* pOld = GetSbData()->pCompMod; + GetSbData()->pCompMod = this; + + auto pParser = std::make_unique<SbiParser>( pBasic, this ); + while( pParser->Parse() ) {} + if( !pParser->GetErrors() ) + pParser->aGen.Save(); + pParser.reset(); + // for the disassembler + if( pImage ) + pImage->aOUSource = aOUSource; + + GetSbData()->pCompMod = pOld; + + // compiling a module, the module-global + // variables of all modules become invalid + bool bRet = IsCompiled(); + if( bRet ) + { + if( dynamic_cast<const SbObjModule*>( this) == nullptr ) + pBasic->ClearAllModuleVars(); + RemoveVars(); // remove 'this' Modules variables + // clear all method statics + for (sal_uInt32 i = 0; i < pMethods->Count(); i++) + { + SbMethod* p = dynamic_cast<SbMethod*>(pMethods->Get(i)); + if( p ) + p->ClearStatics(); + } + + // #i31510 Init other libs only if Basic isn't running + if( GetSbData()->pInst == nullptr ) + { + SbxObject* pParent_ = pBasic->GetParent(); + if( pParent_ ) + pBasic = dynamic_cast<StarBASIC*>( pParent_ ); + if( pBasic ) + pBasic->ClearAllModuleVars(); + } + } + + return bRet; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/scanner.cxx b/basic/source/comp/scanner.cxx new file mode 100644 index 000000000..b5a923969 --- /dev/null +++ b/basic/source/comp/scanner.cxx @@ -0,0 +1,717 @@ +/* -*- 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 <basiccharclass.hxx> +#include <scanner.hxx> +#include <sbintern.hxx> +#include <runtime.hxx> + +#include <basic/sberrors.hxx> +#include <i18nlangtag/lang.h> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <rtl/character.hxx> +#include <o3tl/string_view.hxx> +#include <utility> + +SbiScanner::SbiScanner(OUString _aBuf, StarBASIC* p) + : aBuf(std::move(_aBuf)) + , nLineIdx(-1) + , nSaveLineIdx(-1) + , pBasic(p) + , eScanType(SbxVARIANT) + , nVal(0) + , nSavedCol1(0) + , nCol(0) + , nErrors(0) + , nColLock(0) + , nBufPos(0) + , nLine(0) + , nCol1(0) + , nCol2(0) + , bSymbol(false) + , bNumber(false) + , bSpaces(false) + , bAbort(false) + , bHash(true) + , bError(false) + , bCompatible(false) + , bVBASupportOn(false) + , bPrevLineExtentsComment(false) + , bClosingUnderscore(false) + , bLineEndsWithWhitespace(false) + , bInStatement(false) +{ +} + +void SbiScanner::LockColumn() +{ + if( !nColLock++ ) + nSavedCol1 = nCol1; +} + +void SbiScanner::UnlockColumn() +{ + if( nColLock ) + nColLock--; +} + +void SbiScanner::GenError( ErrCode code ) +{ + if( GetSbData()->bBlockCompilerError ) + { + bAbort = true; + return; + } + if( !bError ) + { + bool bRes = true; + // report only one error per statement + bError = true; + if( pBasic ) + { + // in case of EXPECTED or UNEXPECTED it always refers + // to the last token, so take the Col1 over + sal_Int32 nc = nColLock ? nSavedCol1 : nCol1; + if ( code.anyOf( + ERRCODE_BASIC_EXPECTED, + ERRCODE_BASIC_UNEXPECTED, + ERRCODE_BASIC_SYMBOL_EXPECTED, + ERRCODE_BASIC_LABEL_EXPECTED) ) + { + nc = nCol1; + if( nc > nCol2 ) nCol2 = nc; + } + bRes = pBasic->CError( code, aError, nLine, nc, nCol2 ); + } + bAbort = bAbort || !bRes || ( code == ERRCODE_BASIC_NO_MEMORY || code == ERRCODE_BASIC_PROG_TOO_LARGE ); + } + nErrors++; +} + + +// used by SbiTokenizer::MayBeLabel() to detect a label +bool SbiScanner::DoesColonFollow() +{ + if(nCol < aLine.getLength() && aLine[nCol] == ':') + { + ++nLineIdx; ++nCol; + return true; + } + else + return false; +} + +// test for legal suffix +static SbxDataType GetSuffixType( sal_Unicode c ) +{ + switch (c) + { + case '%': + return SbxINTEGER; + case '&': + return SbxLONG; + case '!': + return SbxSINGLE; + case '#': + return SbxDOUBLE; + case '@': + return SbxCURRENCY; + case '$': + return SbxSTRING; + default: + return SbxVARIANT; + } +} + +// reading the next symbol into the variables aSym, nVal and eType +// return value is sal_False at EOF or errors +#define BUF_SIZE 80 + +void SbiScanner::scanAlphanumeric() +{ + sal_Int32 n = nCol; + while(nCol < aLine.getLength() && (BasicCharClass::isAlphaNumeric(aLine[nCol], bCompatible) || aLine[nCol] == '_')) + { + ++nLineIdx; + ++nCol; + } + aSym = aLine.copy(n, nCol - n); +} + +void SbiScanner::scanGoto() +{ + sal_Int32 n = nCol; + while(n < aLine.getLength() && BasicCharClass::isWhitespace(aLine[n])) + ++n; + + if(n + 1 < aLine.getLength()) + { + std::u16string_view aTemp = aLine.subView(n, 2); + if(o3tl::equalsIgnoreAsciiCase(aTemp, u"to")) + { + aSym = "goto"; + nLineIdx += n + 2 - nCol; + nCol = n + 2; + } + } +} + +bool SbiScanner::readLine() +{ + if(nBufPos >= aBuf.getLength()) + return false; + + sal_Int32 n = nBufPos; + sal_Int32 nLen = aBuf.getLength(); + + while(n < nLen && aBuf[n] != '\r' && aBuf[n] != '\n') + ++n; + + // Trim trailing whitespace + sal_Int32 nEnd = n; + while(nBufPos < nEnd && BasicCharClass::isWhitespace(aBuf[nEnd - 1])) + --nEnd; + + // tdf#149402 - check if line ends with a whitespace + bLineEndsWithWhitespace = (n > nEnd); + aLine = aBuf.copy(nBufPos, nEnd - nBufPos); + + // Fast-forward past the line ending + if(n + 1 < nLen && aBuf[n] == '\r' && aBuf[n + 1] == '\n') + n += 2; + else if(n < nLen) + ++n; + + nBufPos = n; + nLineIdx = 0; + + ++nLine; + nCol = nCol1 = nCol2 = 0; + nColLock = 0; + + return true; +} + +bool SbiScanner::NextSym() +{ + // memorize for the EOLN-case + sal_Int32 nOldLine = nLine; + sal_Int32 nOldCol1 = nCol1; + sal_Int32 nOldCol2 = nCol2; + sal_Unicode buf[ BUF_SIZE ], *p = buf; + + eScanType = SbxVARIANT; + aSym.clear(); + bHash = bSymbol = bNumber = bSpaces = false; + bool bCompilerDirective = false; + + // read in line? + if (nLineIdx == -1) + { + if(!readLine()) + return false; + + nOldLine = nLine; + nOldCol1 = nOldCol2 = 0; + } + + const sal_Int32 nLineIdxScanStart = nLineIdx; + + if(nCol < aLine.getLength() && BasicCharClass::isWhitespace(aLine[nCol])) + { + bSpaces = true; + while(nCol < aLine.getLength() && BasicCharClass::isWhitespace(aLine[nCol])) + { + ++nLineIdx; + ++nCol; + } + } + + nCol1 = nCol; + + // only blank line? + if(nCol >= aLine.getLength()) + goto eoln; + + if( bPrevLineExtentsComment ) + goto PrevLineCommentLbl; + + if(nCol < aLine.getLength() && aLine[nCol] == '#') + { + sal_Int32 nLineTempIdx = nLineIdx; + do + { + nLineTempIdx++; + } while (nLineTempIdx < aLine.getLength() && !BasicCharClass::isWhitespace(aLine[nLineTempIdx]) + && aLine[nLineTempIdx] != '#' && aLine[nLineTempIdx] != ','); + // leave it if it is a date literal - it will be handled later + if (nLineTempIdx >= aLine.getLength() || aLine[nLineTempIdx] != '#') + { + ++nLineIdx; + ++nCol; + //ignore compiler directives (# is first non-space character) + if (nOldCol2 == 0) + bCompilerDirective = true; + else + bHash = true; + } + } + + // copy character if symbol + if(nCol < aLine.getLength() && (BasicCharClass::isAlpha(aLine[nCol], bCompatible) || aLine[nCol] == '_')) + { + // if there's nothing behind '_' , it's the end of a line! + if(nCol + 1 == aLine.getLength() && aLine[nCol] == '_') + { + // Note that nCol is not incremented here... + ++nLineIdx; + goto eoln; + } + + bSymbol = true; + + scanAlphanumeric(); + + // Special handling for "go to" + if(nCol < aLine.getLength() && bCompatible && aSym.equalsIgnoreAsciiCase("go")) + scanGoto(); + + // tdf#125637 - check for closing underscore + if (nCol == aLine.getLength() && aLine[nCol - 1] == '_') + { + bClosingUnderscore = true; + } + // type recognition? + // don't test the exclamation mark + // if there's a symbol behind it + else if((nCol >= aLine.getLength() || aLine[nCol] != '!') || + (nCol + 1 >= aLine.getLength() || !BasicCharClass::isAlpha(aLine[nCol + 1], bCompatible))) + { + if(nCol < aLine.getLength()) + { + SbxDataType t(GetSuffixType(aLine[nCol])); + if( t != SbxVARIANT ) + { + eScanType = t; + ++nLineIdx; + ++nCol; + } + } + } + } + + // read in and convert if number + else if((nCol < aLine.getLength() && rtl::isAsciiDigit(aLine[nCol])) || + (nCol + 1 < aLine.getLength() && aLine[nCol] == '.' && rtl::isAsciiDigit(aLine[nCol + 1]))) + { + short exp = 0; + short dec = 0; + eScanType = SbxDOUBLE; + bool bScanError = false; + bool bBufOverflow = false; + // All this because of 'D' or 'd' floating point type, sigh... + while(!bScanError && nCol < aLine.getLength() && strchr("0123456789.DEde", aLine[nCol])) + { + // from 4.1.1996: buffer full? -> go on scanning empty + if( (p-buf) == (BUF_SIZE-1) ) + { + bBufOverflow = true; + ++nLineIdx; + ++nCol; + continue; + } + // point or exponent? + if(aLine[nCol] == '.') + { + if( ++dec > 1 ) + bScanError = true; + else + *p++ = '.'; + } + else if(strchr("DdEe", aLine[nCol])) + { + if (++exp > 1) + bScanError = true; + else + { + *p++ = 'E'; + if (nCol + 1 < aLine.getLength() && (aLine[nCol+1] == '+' || aLine[nCol+1] == '-')) + { + ++nLineIdx; + ++nCol; + if( (p-buf) == (BUF_SIZE-1) ) + { + bBufOverflow = true; + continue; + } + *p++ = aLine[nCol]; + } + } + } + else + { + *p++ = aLine[nCol]; + } + ++nLineIdx; + ++nCol; + } + *p = 0; + aSym = p; bNumber = true; + + // For bad characters, scan and parse errors generate only one error. + ErrCode nError = ERRCODE_NONE; + if (bScanError) + { + --nLineIdx; + --nCol; + aError = OUString( aLine[nCol]); + nError = ERRCODE_BASIC_BAD_CHAR_IN_NUMBER; + } + + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + const sal_Unicode* pParseEnd = buf; + nVal = rtl_math_uStringToDouble( buf, buf+(p-buf), '.', ',', &eStatus, &pParseEnd ); + if (pParseEnd != buf+(p-buf)) + { + // e.g. "12e" or "12e+", or with bScanError "12d"+"E". + sal_Int32 nChars = buf+(p-buf) - pParseEnd; + nLineIdx -= nChars; + nCol -= nChars; + // For bScanError, nLineIdx and nCol were already decremented, just + // add that character to the parse end. + if (bScanError) + ++nChars; + // Copy error position from original string, not the buffer + // replacement where "12dE" => "12EE". + aError = aLine.copy( nCol, nChars); + nError = ERRCODE_BASIC_BAD_CHAR_IN_NUMBER; + } + else if (eStatus != rtl_math_ConversionStatus_Ok) + { + // Keep the scan error and character at position, if any. + if (!nError) + nError = ERRCODE_BASIC_MATH_OVERFLOW; + } + + if (nError) + GenError( nError ); + + if( !dec && !exp ) + { + if( nVal >= SbxMININT && nVal <= SbxMAXINT ) + eScanType = SbxINTEGER; + else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG ) + eScanType = SbxLONG; + } + + if( bBufOverflow ) + GenError( ERRCODE_BASIC_MATH_OVERFLOW ); + + // type recognition? + if( nCol < aLine.getLength() ) + { + SbxDataType t(GetSuffixType(aLine[nCol])); + if( t != SbxVARIANT ) + { + eScanType = t; + ++nLineIdx; + ++nCol; + } + // tdf#130476 - don't allow String trailing data type character with numbers + if ( t == SbxSTRING ) + { + GenError( ERRCODE_BASIC_SYNTAX ); + } + } + } + + // Hex/octal number? Read in and convert: + else if(aLine.getLength() - nCol > 1 && aLine[nCol] == '&') + { + ++nLineIdx; ++nCol; + sal_Unicode base = 16; + sal_Unicode xch = aLine[nCol]; + ++nLineIdx; ++nCol; + switch( rtl::toAsciiUpperCase( xch ) ) + { + case 'O': + base = 8; + break; + case 'H': + break; + default : + // treated as an operator + --nLineIdx; --nCol; nCol1 = nCol-1; + aSym = "&"; + return true; + } + bNumber = true; + // Hex literals are signed Integers ( as defined by basic + // e.g. -2,147,483,648 through 2,147,483,647 (signed) + sal_uInt64 lu = 0; + bool bOverflow = false; + while(nCol < aLine.getLength() && BasicCharClass::isAlphaNumeric(aLine[nCol], false)) + { + sal_Unicode ch = rtl::toAsciiUpperCase(aLine[nCol]); + ++nLineIdx; ++nCol; + if( ((base == 16 ) && rtl::isAsciiHexDigit( ch ) ) || + ((base == 8) && rtl::isAsciiOctalDigit( ch ))) + { + int i = ch - '0'; + if( i > 9 ) i -= 7; + lu = ( lu * base ) + i; + if( lu > SAL_MAX_UINT32 ) + { + bOverflow = true; + } + } + else + { + aError = OUString(ch); + GenError( ERRCODE_BASIC_BAD_CHAR_IN_NUMBER ); + } + } + + // tdf#130476 - take into account trailing data type characters + if( nCol < aLine.getLength() ) + { + SbxDataType t(GetSuffixType(aLine[nCol])); + if( t != SbxVARIANT ) + { + eScanType = t; + ++nLineIdx; + ++nCol; + } + // tdf#130476 - don't allow String trailing data type character with numbers + if ( t == SbxSTRING ) + { + GenError( ERRCODE_BASIC_SYNTAX ); + } + } + + // tdf#130476 - take into account trailing data type characters + switch ( eScanType ) + { + case SbxINTEGER: + nVal = static_cast<double>( static_cast<sal_Int16>(lu) ); + if ( lu > SbxMAXUINT ) + { + bOverflow = true; + } + break; + case SbxLONG: nVal = static_cast<double>( static_cast<sal_Int32>(lu) ); break; + case SbxVARIANT: + { + // tdf#62326 - If the value of the hex string without explicit type character lies within + // the range of 0x8000 (SbxMAXINT + 1) and 0xFFFF (SbxMAXUINT) inclusive, cast the value + // to 16 bit in order to get signed integers, e.g., SbxMININT through SbxMAXINT + sal_Int32 ls = (lu > SbxMAXINT && lu <= SbxMAXUINT) ? static_cast<sal_Int16>(lu) : static_cast<sal_Int32>(lu); + eScanType = ( ls >= SbxMININT && ls <= SbxMAXINT ) ? SbxINTEGER : SbxLONG; + nVal = static_cast<double>(ls); + break; + } + default: + nVal = static_cast<double>(lu); + break; + } + if( bOverflow ) + GenError( ERRCODE_BASIC_MATH_OVERFLOW ); + } + + // Strings: + else if (nLineIdx < aLine.getLength() && (aLine[nLineIdx] == '"' || aLine[nLineIdx] == '[')) + { + sal_Unicode cSep = aLine[nLineIdx]; + if( cSep == '[' ) + { + bSymbol = true; + cSep = ']'; + } + sal_Int32 n = nCol + 1; + while (nLineIdx < aLine.getLength()) + { + do + { + nLineIdx++; + nCol++; + } + while (nLineIdx < aLine.getLength() && (aLine[nLineIdx] != cSep)); + if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == cSep) + { + nLineIdx++; nCol++; + if (nLineIdx >= aLine.getLength() || aLine[nLineIdx] != cSep || cSep == ']') + { + // If VBA Interop then doesn't eat the [] chars + if ( cSep == ']' && bVBASupportOn ) + aSym = aLine.copy( n - 1, nCol - n + 1); + else + aSym = aLine.copy( n, nCol - n - 1 ); + // get out duplicate string delimiters + OUStringBuffer aSymBuf(aSym.getLength()); + for ( sal_Int32 i = 0, len = aSym.getLength(); i < len; ++i ) + { + aSymBuf.append( aSym[i] ); + if ( aSym[i] == cSep && ( i+1 < len ) && aSym[i+1] == cSep ) + ++i; + } + aSym = aSymBuf.makeStringAndClear(); + if( cSep != ']' ) + eScanType = SbxSTRING; + break; + } + } + else + { + aError = OUString(cSep); + GenError( ERRCODE_BASIC_EXPECTED ); + } + } + } + + // Date: + else if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == '#') + { + sal_Int32 n = nCol + 1; + do + { + nLineIdx++; + nCol++; + } + while (nLineIdx < aLine.getLength() && (aLine[nLineIdx] != '#')); + if (nLineIdx < aLine.getLength() && aLine[nLineIdx] == '#') + { + nLineIdx++; nCol++; + aSym = aLine.copy( n, nCol - n - 1 ); + + // parse date literal + std::shared_ptr<SvNumberFormatter> pFormatter; + if (GetSbData()->pInst) + { + pFormatter = GetSbData()->pInst->GetNumberFormatter(); + } + else + { + sal_uInt32 nDummy; + pFormatter = SbiInstance::PrepareNumberFormatter( nDummy, nDummy, nDummy ); + } + sal_uInt32 nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US); + bool bSuccess = pFormatter->IsNumberFormat(aSym, nIndex, nVal); + if( bSuccess ) + { + SvNumFormatType nType_ = pFormatter->GetType(nIndex); + if( !(nType_ & SvNumFormatType::DATE) ) + bSuccess = false; + } + + if (!bSuccess) + GenError( ERRCODE_BASIC_CONVERSION ); + + bNumber = true; + eScanType = SbxDOUBLE; + } + else + { + aError = OUString('#'); + GenError( ERRCODE_BASIC_EXPECTED ); + } + } + // invalid characters: + else if (nLineIdx < aLine.getLength() && aLine[nLineIdx] >= 0x7F) + { + GenError( ERRCODE_BASIC_SYNTAX ); nLineIdx++; nCol++; + } + // other groups: + else + { + sal_Int32 n = 1; + auto nChar = nLineIdx < aLine.getLength() ? aLine[nLineIdx] : 0; + ++nLineIdx; + if (nLineIdx < aLine.getLength()) + { + switch (nChar) + { + case '<': if( aLine[nLineIdx] == '>' || aLine[nLineIdx] == '=' ) n = 2; break; + case '>': if( aLine[nLineIdx] == '=' ) n = 2; break; + case ':': if( aLine[nLineIdx] == '=' ) n = 2; break; + } + } + aSym = aLine.copy(nCol, std::min(n, aLine.getLength() - nCol)); + nLineIdx += n-1; nCol = nCol + n; + } + + nCol2 = nCol-1; + +PrevLineCommentLbl: + + if( bPrevLineExtentsComment || (eScanType != SbxSTRING && + ( bCompilerDirective || + aSym.startsWith("'") || + aSym.equalsIgnoreAsciiCase( "REM" ) ) ) ) + { + bPrevLineExtentsComment = false; + aSym = "REM"; + sal_Int32 nLen = aLine.getLength() - nLineIdx; + // tdf#149402 - don't extend comment if line ends in a whitespace (asicCharClass::isWhitespace) + if (bCompatible && !bLineEndsWithWhitespace && aLine[nLineIdx + nLen - 1] == '_' + && aLine[nLineIdx + nLen - 2] == ' ') + bPrevLineExtentsComment = true; + nCol2 = nCol2 + nLen; + nLineIdx = -1; + } + + if (nLineIdx == nLineIdxScanStart) + { + GenError( ERRCODE_BASIC_SYMBOL_EXPECTED ); + return false; + } + + return true; + + +eoln: + if (nCol && aLine[--nLineIdx] == '_' && !bClosingUnderscore) + { + nLineIdx = -1; + bool bRes = NextSym(); + if( aSym.startsWith(".") ) + { + // object _ + // .Method + // ^^^ <- spaces is legal in MSO VBA + bSpaces = false; + } + return bRes; + } + else + { + nLineIdx = -1; + nLine = nOldLine; + nCol1 = nOldCol1; + nCol2 = nOldCol2; + aSym = "\n"; + nColLock = 0; + bClosingUnderscore = false; + // tdf#149157 - break multiline continuation in a comment after a new line + bPrevLineExtentsComment = false; + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/symtbl.cxx b/basic/source/comp/symtbl.cxx new file mode 100644 index 000000000..6caa3b2ed --- /dev/null +++ b/basic/source/comp/symtbl.cxx @@ -0,0 +1,534 @@ +/* -*- 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 <memory> +#include <parser.hxx> + +#include <osl/diagnose.h> + +#include <stdio.h> +#include <rtl/character.hxx> +#include <basic/sberrors.hxx> +#include <utility> + +// All symbol names are laid down int the symbol-pool's stringpool, so that +// all symbols are handled in the same case. On saving the code-image, the +// global stringpool with the respective symbols is also saved. +// The local stringpool holds all the symbols that don't move to the image +// (labels, constant names etc.). + +SbiStringPool::SbiStringPool( ) +{} + +SbiStringPool::~SbiStringPool() +{} + +OUString SbiStringPool::Find( sal_uInt32 n ) const +{ + if( n == 0 || n > aData.size() ) + return OUString(); + else + return aData[n - 1]; +} + +short SbiStringPool::Add( const OUString& rVal ) +{ + sal_uInt32 n = aData.size(); + for( sal_uInt32 i = 0; i < n; ++i ) + { + OUString& p = aData[i]; + if( p == rVal ) + return i+1; + } + + aData.push_back(rVal); + return static_cast<short>(++n); +} + +short SbiStringPool::Add(double n, SbxDataType t) +{ + size_t size = 0; + const size_t aBufLength = 40; + char buf[aBufLength]{}; + + // tdf#143707 - add the type character after the null termination of the string in order to + // keep compatibility. After the type character has been added, the buffer contains the value + // of the double n, the string termination symbol, and the type character. + switch( t ) + { + // tdf#142460 - properly handle boolean values in string pool + case SbxBOOL: + size = snprintf(buf, sizeof(buf), "%d", static_cast<short>(n)) + 1; + buf[size++] = 'b'; + break; + // tdf#131296 - store numeric value including its type character + // See GetSuffixType in basic/source/comp/scanner.cxx for type characters + case SbxINTEGER: + size = snprintf(buf, sizeof(buf), "%d", static_cast<short>(n)) + 1; + buf[size++] = '%'; + break; + case SbxLONG: + size = snprintf(buf, sizeof(buf), "%" SAL_PRIdINT32, static_cast<sal_Int32>(n)) + 1; + buf[size++] = '&'; + break; + case SbxSINGLE: + size = snprintf(buf, sizeof(buf), "%.6g", static_cast<float>(n)) + 1; + buf[size++] = '!'; + break; + case SbxDOUBLE: + size = snprintf(buf, sizeof(buf), "%.16g", n) + 1; + buf[size++] = '#'; + break; + case SbxCURRENCY: + size = snprintf(buf, sizeof(buf), "%.16g", n) + 1; + buf[size++] = '@'; + break; + default: assert(false); break; // should not happen + } + + // tdf#143707 - add the content of the buffer to the string pool including its calculated length + return Add(OUString::fromUtf8(std::string_view(buf, size))); +} + +SbiSymPool::SbiSymPool( SbiStringPool& r, SbiSymScope s, SbiParser* pP ) : + rStrings(r), + pParent(nullptr), + pParser(pP), + eScope(s), + nProcId(0), + nCur(0) +{ +} + +SbiSymPool::~SbiSymPool() +{} + + +SbiSymDef* SbiSymPool::First() +{ + nCur = sal_uInt16(-1); + return Next(); +} + +SbiSymDef* SbiSymPool::Next() +{ + if (m_Data.size() <= ++nCur) + return nullptr; + else + return m_Data[ nCur ].get(); +} + + +SbiSymDef* SbiSymPool::AddSym( const OUString& rName ) +{ + SbiSymDef* p = new SbiSymDef( rName ); + p->nPos = m_Data.size(); + p->nId = rStrings.Add( rName ); + p->nProcId = nProcId; + p->pIn = this; + m_Data.insert( m_Data.begin() + p->nPos, std::unique_ptr<SbiSymDef>(p) ); + return p; +} + +SbiProcDef* SbiSymPool::AddProc( const OUString& rName ) +{ + SbiProcDef* p = new SbiProcDef( pParser, rName ); + p->nPos = m_Data.size(); + p->nId = rStrings.Add( rName ); + // procs are always local + p->nProcId = 0; + p->pIn = this; + m_Data.insert( m_Data.begin() + p->nPos, std::unique_ptr<SbiProcDef>(p) ); + return p; +} + +// adding an externally constructed symbol definition + +void SbiSymPool::Add( SbiSymDef* pDef ) +{ + if( !(pDef && pDef->pIn != this) ) + return; + + if( pDef->pIn ) + { +#ifdef DBG_UTIL + + pParser->Error( ERRCODE_BASIC_INTERNAL_ERROR, "Dbl Pool" ); +#endif + return; + } + + pDef->nPos = m_Data.size(); + if( !pDef->nId ) + { + // A unique name must be created in the string pool + // for static variables (Form ProcName:VarName) + OUString aName( pDef->aName ); + if( pDef->IsStatic() ) + { + aName = pParser->aGblStrings.Find( nProcId ) + + ":" + + pDef->aName; + } + pDef->nId = rStrings.Add( aName ); + } + + if( !pDef->GetProcDef() ) + { + pDef->nProcId = nProcId; + } + pDef->pIn = this; + m_Data.insert( m_Data.begin() + pDef->nPos, std::unique_ptr<SbiSymDef>(pDef) ); +} + + +SbiSymDef* SbiSymPool::Find( const OUString& rName, bool bSearchInParents ) +{ + sal_uInt16 nCount = m_Data.size(); + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + SbiSymDef &r = *m_Data[ nCount - i - 1 ]; + if( ( !r.nProcId || ( r.nProcId == nProcId)) && + ( r.aName.equalsIgnoreAsciiCase(rName))) + { + return &r; + } + } + if( bSearchInParents && pParent ) + { + return pParent->Find( rName ); + } + else + { + return nullptr; + } +} + + +// find via position (from 0) + +SbiSymDef* SbiSymPool::Get( sal_uInt16 n ) +{ + if (m_Data.size() <= n) + { + return nullptr; + } + else + { + return m_Data[ n ].get(); + } +} + +sal_uInt32 SbiSymPool::Define( const OUString& rName ) +{ + SbiSymDef* p = Find( rName ); + if( p ) + { + if( p->IsDefined() ) + { + pParser->Error( ERRCODE_BASIC_LABEL_DEFINED, rName ); + } + } + else + { + p = AddSym( rName ); + } + return p->Define(); +} + +sal_uInt32 SbiSymPool::Reference( const OUString& rName ) +{ + SbiSymDef* p = Find( rName ); + if( !p ) + { + p = AddSym( rName ); + } + // to be sure + pParser->aGen.GenStmnt(); + return p->Reference(); +} + + +void SbiSymPool::CheckRefs() +{ + for (std::unique_ptr<SbiSymDef> & r : m_Data) + { + if( !r->IsDefined() ) + { + pParser->Error( ERRCODE_BASIC_UNDEF_LABEL, r->GetName() ); + } + } +} + +SbiSymDef::SbiSymDef( OUString _aName ) : + aName(std::move(_aName)), + eType(SbxEMPTY), + pIn(nullptr), + nLen(0), + nDims(0), + nId(0), + nTypeId(0), + nProcId(0), + nPos(0), + nChain(0), + bNew(false), + bChained(false), + bByVal(false), + bOpt(false), + bStatic(false), + bAs(false), + bGlobal(false), + bParamArray(false), + bWithEvents(false), + bWithBrackets(false), + nDefaultId(0), + nFixedStringLength(-1) +{ +} + +SbiSymDef::~SbiSymDef() +{ +} + +SbiProcDef* SbiSymDef::GetProcDef() +{ + return nullptr; +} + +SbiConstDef* SbiSymDef::GetConstDef() +{ + return nullptr; +} + + +const OUString& SbiSymDef::GetName() +{ + if( pIn ) + { + aName = pIn->rStrings.Find( nId ); + } + return aName; +} + + +void SbiSymDef::SetType( SbxDataType t ) +{ + if( t == SbxVARIANT && pIn ) + { + //See if there have been any deftype statements to set the default type + //of a variable based on its starting letter + sal_Unicode cu = aName[0]; + if( cu < 256 ) + { + unsigned char ch = static_cast<unsigned char>(cu); + if( ch == '_' ) + { + ch = 'Z'; + } + int ch2 = rtl::toAsciiUpperCase( ch ); + int nIndex = ch2 - 'A'; + if (nIndex >= 0 && nIndex < N_DEF_TYPES) + t = pIn->pParser->eDefTypes[nIndex]; + } + } + eType = t; +} + +// construct a backchain, if not yet defined +// the value that shall be stored as an operand is returned + +sal_uInt32 SbiSymDef::Reference() +{ + if( !bChained ) + { + sal_uInt32 n = nChain; + nChain = pIn->pParser->aGen.GetOffset(); + return n; + } + else return nChain; +} + + +sal_uInt32 SbiSymDef::Define() +{ + sal_uInt32 n = pIn->pParser->aGen.GetPC(); + pIn->pParser->aGen.GenStmnt(); + if( nChain ) + { + pIn->pParser->aGen.BackChain( nChain ); + } + nChain = n; + bChained = true; + return nChain; +} + +// A symbol definition may have its own pool. This is the case +// for objects and procedures (local variable) + +SbiSymPool& SbiSymDef::GetPool() +{ + if( !pPool ) + { + pPool = std::make_unique<SbiSymPool>( pIn->pParser->aGblStrings, SbLOCAL, pIn->pParser );// is dumped + } + return *pPool; +} + +SbiSymScope SbiSymDef::GetScope() const +{ + return pIn ? pIn->GetScope() : SbLOCAL; +} + + +// The procedure definition has three pools: +// 1) aParams: is filled by the definition. Contains the +// parameters' names, like they're used inside the body. +// The first element is the return value. +// 2) pPool: all local variables +// 3) aLabels: labels + +SbiProcDef::SbiProcDef( SbiParser* pParser, const OUString& rName, + bool bProcDecl ) + : SbiSymDef( rName ) + , aParams( pParser->aGblStrings, SbPARAM, pParser ) // is dumped + , aLabels( pParser->aLclStrings, SbLOCAL, pParser ) // is not dumped + , mbProcDecl( bProcDecl ) +{ + aParams.SetParent( &pParser->aPublics ); + pPool = std::make_unique<SbiSymPool>( pParser->aGblStrings, SbLOCAL, pParser ); + pPool->SetParent( &aParams ); + nLine1 = + nLine2 = 0; + mePropMode = PropertyMode::NONE; + bPublic = true; + bCdecl = false; + bStatic = false; + // For return values the first element of the parameter + // list is always defined with name and type of the proc + aParams.AddSym( aName ); +} + +SbiProcDef::~SbiProcDef() +{} + +SbiProcDef* SbiProcDef::GetProcDef() +{ + return this; +} + +void SbiProcDef::SetType( SbxDataType t ) +{ + SbiSymDef::SetType( t ); + aParams.Get( 0 )->SetType( eType ); +} + +// match with a forward-declaration +// if the match is OK, pOld is replaced by this in the pool +// pOld is deleted in any case! + +void SbiProcDef::Match( SbiProcDef* pOld ) +{ + SbiSymDef *pn=nullptr; + // parameter 0 is the function name + sal_uInt16 i; + for( i = 1; i < aParams.GetSize(); i++ ) + { + SbiSymDef* po = pOld->aParams.Get( i ); + pn = aParams.Get( i ); + // no type matching - that is done during running + // but is it maybe called with too little parameters? + if( !po && !pn->IsOptional() && !pn->IsParamArray() ) + { + break; + } + pOld->aParams.Next(); + } + + if( pn && i < aParams.GetSize() && pOld->pIn ) + { + // mark the whole line + pOld->pIn->GetParser()->SetCol1( 0 ); + pOld->pIn->GetParser()->Error( ERRCODE_BASIC_BAD_DECLARATION, aName ); + } + + if( !pIn && pOld->pIn ) + { + // Replace old entry with the new one + nPos = pOld->nPos; + nId = pOld->nId; + pIn = pOld->pIn; + + // don't delete pOld twice, if it's stored in m_Data + if (pOld == pIn->m_Data[nPos].get()) + pOld = nullptr; + pIn->m_Data[nPos].reset(this); + } + delete pOld; +} + +void SbiProcDef::setPropertyMode( PropertyMode ePropMode ) +{ + mePropMode = ePropMode; + if( mePropMode == PropertyMode::NONE ) + return; + + // Prop name = original scanned procedure name + maPropName = aName; + + // CompleteProcName includes "Property xxx " + // to avoid conflicts with other symbols + OUString aCompleteProcName = "Property "; + switch( mePropMode ) + { + case PropertyMode::Get: aCompleteProcName += "Get "; break; + case PropertyMode::Let: aCompleteProcName += "Let "; break; + case PropertyMode::Set: aCompleteProcName += "Set "; break; + case PropertyMode::NONE: OSL_FAIL( "Illegal PropertyMode PropertyMode::NONE" ); break; + } + aCompleteProcName += aName; + aName = aCompleteProcName; +} + + +SbiConstDef::SbiConstDef( const OUString& rName ) + : SbiSymDef( rName ) +{ + nVal = 0; eType = SbxINTEGER; +} + +void SbiConstDef::Set( double n, SbxDataType t ) +{ + aVal.clear(); nVal = n; eType = t; +} + +void SbiConstDef::Set( const OUString& n ) +{ + aVal = n; nVal = 0; eType = SbxSTRING; +} + +SbiConstDef::~SbiConstDef() +{} + +SbiConstDef* SbiConstDef::GetConstDef() +{ + return this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basic/source/comp/token.cxx b/basic/source/comp/token.cxx new file mode 100644 index 000000000..a060b3fd9 --- /dev/null +++ b/basic/source/comp/token.cxx @@ -0,0 +1,571 @@ +/* -*- 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 <array> + +#include <basic/sberrors.hxx> +#include <sal/macros.h> +#include <basiccharclass.hxx> +#include <token.hxx> + +namespace { + +struct TokenTable { SbiToken t; const char *s; }; + +} + +const TokenTable aTokTable_Basic [] = { + { CAT, "&" }, + { MUL, "*" }, + { PLUS, "+" }, + { MINUS, "-" }, + { DIV, "/" }, + { EOS, ":" }, + { ASSIGN, ":=" }, + { LT, "<" }, + { LE, "<=" }, + { NE, "<>" }, + { EQ, "=" }, + { GT, ">" }, + { GE, ">=" }, + { ACCESS, "Access" }, + { ALIAS, "Alias" }, + { AND, "And" }, + { ANY, "Any" }, + { APPEND, "Append" }, + { AS, "As" }, + { ATTRIBUTE,"Attribute" }, + { BASE, "Base" }, + { BINARY, "Binary" }, + { TBOOLEAN, "Boolean" }, + { BYREF, "ByRef", }, + { TBYTE, "Byte", }, + { BYVAL, "ByVal", }, + { CALL, "Call" }, + { CASE, "Case" }, + { CDECL_, "Cdecl" }, + { CLASSMODULE, "ClassModule" }, + { CLOSE, "Close" }, + { COMPARE, "Compare" }, + { COMPATIBLE,"Compatible" }, + { CONST_, "Const" }, + { TCURRENCY,"Currency" }, + { TDATE, "Date" }, + { DECLARE, "Declare" }, + { DEFBOOL, "DefBool" }, + { DEFCUR, "DefCur" }, + { DEFDATE, "DefDate" }, + { DEFDBL, "DefDbl" }, + { DEFERR, "DefErr" }, + { DEFINT, "DefInt" }, + { DEFLNG, "DefLng" }, + { DEFOBJ, "DefObj" }, + { DEFSNG, "DefSng" }, + { DEFSTR, "DefStr" }, + { DEFVAR, "DefVar" }, + { DIM, "Dim" }, + { DO, "Do" }, + { TDOUBLE, "Double" }, + { EACH, "Each" }, + { ELSE, "Else" }, + { ELSEIF, "ElseIf" }, + { END, "End" }, + { ENDENUM, "End Enum" }, + { ENDFUNC, "End Function" }, + { ENDIF, "End If" }, + { ENDPROPERTY, "End Property" }, + { ENDSELECT,"End Select" }, + { ENDSUB, "End Sub" }, + { ENDTYPE, "End Type" }, + { ENDIF, "EndIf" }, + { ENUM, "Enum" }, + { EQV, "Eqv" }, + { ERASE, "Erase" }, + { ERROR_, "Error" }, + { EXIT, "Exit" }, + { BASIC_EXPLICIT, "Explicit" }, + { FOR, "For" }, + { FUNCTION, "Function" }, + { GET, "Get" }, + { GLOBAL, "Global" }, + { GOSUB, "GoSub" }, + { GOTO, "GoTo" }, + { IF, "If" }, + { IMP, "Imp" }, + { IMPLEMENTS, "Implements" }, + { IN_, "In" }, + { INPUT, "Input" }, // also INPUT # + { TINTEGER, "Integer" }, + { IS, "Is" }, + { LET, "Let" }, + { LIB, "Lib" }, + { LIKE, "Like" }, + { LINE, "Line" }, + { LINEINPUT,"Line Input" }, + { LOCAL, "Local" }, + { LOCK, "Lock" }, + { TLONG, "Long" }, + { LOOP, "Loop" }, + { LPRINT, "LPrint" }, + { LSET, "LSet" }, // JSM + { MOD, "Mod" }, + { NAME, "Name" }, + { NEW, "New" }, + { NEXT, "Next" }, + { NOT, "Not" }, + { TOBJECT, "Object" }, + { ON, "On" }, + { OPEN, "Open" }, + { OPTION, "Option" }, + { OPTIONAL_, "Optional" }, + { OR, "Or" }, + { OUTPUT, "Output" }, + { PARAMARRAY, "ParamArray" }, + { PRESERVE, "Preserve" }, + { PRINT, "Print" }, + { PRIVATE, "Private" }, + { PROPERTY, "Property" }, + { PTRSAFE, "PtrSafe" }, + { PUBLIC, "Public" }, + { RANDOM, "Random" }, + { READ, "Read" }, + { REDIM, "ReDim" }, + { REM, "Rem" }, + { RESUME, "Resume" }, + { RETURN, "Return" }, + { RSET, "RSet" }, // JSM + { SELECT, "Select" }, + { SET, "Set" }, + { SHARED, "Shared" }, + { TSINGLE, "Single" }, + { STATIC, "Static" }, + { STEP, "Step" }, + { STOP, "Stop" }, + { TSTRING, "String" }, + { SUB, "Sub" }, + { STOP, "System" }, + { TEXT, "Text" }, + { THEN, "Then" }, + { TO, "To", }, + { TYPE, "Type" }, + { TYPEOF, "TypeOf" }, + { UNTIL, "Until" }, + { TVARIANT, "Variant" }, + { VBASUPPORT, "VbaSupport" }, + { WEND, "Wend" }, + { WHILE, "While" }, + { WITH, "With" }, + { WITHEVENTS, "WithEvents" }, + { WRITE, "Write" }, // also WRITE # + { XOR, "Xor" }, +}; + +namespace { + +// #i109076 +class TokenLabelInfo +{ + std::array<bool,VBASUPPORT+1> m_pTokenCanBeLabelTab; + +public: + TokenLabelInfo(); + + bool canTokenBeLabel( SbiToken eTok ) + { return m_pTokenCanBeLabelTab[eTok]; } +}; + +} + +// #i109076 +TokenLabelInfo::TokenLabelInfo() +{ + m_pTokenCanBeLabelTab.fill(false); + + // Token accepted as label by VBA + static const SbiToken eLabelToken[] = { ACCESS, ALIAS, APPEND, BASE, BINARY, CLASSMODULE, + COMPARE, COMPATIBLE, DEFERR, ERROR_, BASIC_EXPLICIT, LIB, LINE, LPRINT, NAME, + TOBJECT, OUTPUT, PROPERTY, RANDOM, READ, STEP, STOP, TEXT, VBASUPPORT }; + for( SbiToken eTok : eLabelToken ) + { + m_pTokenCanBeLabelTab[eTok] = true; + } +} + + +SbiTokenizer::SbiTokenizer( const OUString& rSrc, StarBASIC* pb ) + : SbiScanner(rSrc, pb) + , eCurTok(NIL) + , ePush(NIL) + , nPLine(0) + , nPCol1(0) + , nPCol2(0) + , bEof(false) + , bEos(true) + , bAs(false) + , bErrorIsSymbol(true) +{ +} + +void SbiTokenizer::Push( SbiToken t ) +{ + if( ePush != NIL ) + Error( ERRCODE_BASIC_INTERNAL_ERROR, "PUSH" ); + else ePush = t; +} + +void SbiTokenizer::Error( ErrCode code, const OUString &aMsg ) +{ + aError = aMsg; + Error( code ); +} + +void SbiTokenizer::Error( ErrCode code, SbiToken tok ) +{ + aError = Symbol( tok ); + Error( code ); +} + +// reading in the next token without absorbing it + +SbiToken SbiTokenizer::Peek() +{ + if( ePush == NIL ) + { + sal_Int32 nOldLine = nLine; + sal_Int32 nOldCol1 = nCol1; + sal_Int32 nOldCol2 = nCol2; + ePush = Next(); + nPLine = nLine; nLine = nOldLine; + nPCol1 = nCol1; nCol1 = nOldCol1; + nPCol2 = nCol2; nCol2 = nOldCol2; + } + eCurTok = ePush; + return eCurTok; +} + +// For decompilation. Numbers and symbols return an empty string. + +const OUString& SbiTokenizer::Symbol( SbiToken t ) +{ + // character token? + if( t < FIRSTKWD ) + { + aSym = OUString(sal::static_int_cast<sal_Unicode>(t)); + return aSym; + } + switch( t ) + { + case NEG : + aSym = "-"; + return aSym; + case EOS : + aSym = ":/CRLF"; + return aSym; + case EOLN : + aSym = "CRLF"; + return aSym; + default: + break; + } + for( auto& rTok : aTokTable_Basic ) + { + if( rTok.t == t ) + { + aSym = OStringToOUString(rTok.s, RTL_TEXTENCODING_ASCII_US); + return aSym; + } + } + const sal_Unicode *p = aSym.getStr(); + if (*p <= ' ') + { + aSym = "???"; + } + return aSym; +} + +// Reading in the next token and put it down. +// Tokens that don't appear in the token table +// are directly returned as a character. +// Some words are treated in a special way. + +SbiToken SbiTokenizer::Next() +{ + if (bEof) + { + return EOLN; + } + // have read in one already? + if( ePush != NIL ) + { + eCurTok = ePush; + ePush = NIL; + nLine = nPLine; + nCol1 = nPCol1; + nCol2 = nPCol2; + bEos = IsEoln( eCurTok ); + return eCurTok; + } + const TokenTable *tp; + + if( !NextSym() ) + { + bEof = bEos = true; + eCurTok = EOLN; + return eCurTok; + } + + if( aSym.startsWith("\n") ) + { + bEos = true; + eCurTok = EOLN; + return eCurTok; + } + bEos = false; + + if( bNumber ) + { + eCurTok = NUMBER; + return eCurTok; + } + else if( ( eScanType == SbxDATE || eScanType == SbxSTRING ) && !bSymbol ) + { + eCurTok = FIXSTRING; + return eCurTok; + } + else if( aSym.isEmpty() ) + { + //something went wrong + bEof = bEos = true; + eCurTok = EOLN; + return eCurTok; + } + // Special cases of characters that are between "Z" and "a". ICompare() + // evaluates the position of these characters in different ways. + else if( aSym[0] == '^' ) + { + eCurTok = EXPON; + return eCurTok; + } + else if( aSym[0] == '\\' ) + { + eCurTok = IDIV; + return eCurTok; + } + else + { + if( eScanType != SbxVARIANT ) + { + eCurTok = SYMBOL; + return eCurTok; + } + // valid token? + short lb = 0; + short ub = std::size(aTokTable_Basic)-1; + short delta; + do + { + delta = (ub - lb) >> 1; + tp = &aTokTable_Basic[ lb + delta ]; + sal_Int32 res = aSym.compareToIgnoreAsciiCaseAscii( tp->s ); + + if( res == 0 ) + { + goto special; + } + if( res < 0 ) + { + if ((ub - lb) == 2) + { + ub = lb; + } + else + { + ub = ub - delta; + } + } + else + { + if ((ub -lb) == 2) + { + lb = ub; + } + else + { + lb = lb + delta; + } + } + } + while( delta ); + // Symbol? if not >= token + sal_Unicode ch = aSym[0]; + if( !BasicCharClass::isAlpha( ch, bCompatible ) && !bSymbol ) + { + eCurTok = static_cast<SbiToken>(ch & 0x00FF); + return eCurTok; + } + eCurTok = SYMBOL; + return eCurTok; + } +special: + // #i92642 + bool bStartOfLine = (eCurTok == NIL || eCurTok == REM || eCurTok == EOLN || + eCurTok == THEN || eCurTok == ELSE); // single line If + if( !bStartOfLine && (tp->t == NAME || tp->t == LINE) ) + { + eCurTok = SYMBOL; + return eCurTok; + } + else if( tp->t == TEXT ) + { + eCurTok = SYMBOL; + return eCurTok; + } + // maybe we can expand this for other statements that have parameters + // that are keywords ( and those keywords are only used within such + // statements ) + // what's happening here is that if we come across 'append' ( and we are + // not in the middle of parsing a special statement ( like 'Open') + // we just treat keyword 'append' as a normal 'SYMBOL'. + // Also we accept Dim APPEND + else if ( ( !bInStatement || eCurTok == DIM ) && tp->t == APPEND ) + { + eCurTok = SYMBOL; + return eCurTok; + } + // #i92642: Special LINE token handling -> SbiParser::Line() + + // END IF, CASE, SUB, DEF, FUNCTION, TYPE, CLASS, WITH + if( tp->t == END ) + { + // from 15.3.96, special treatment for END, at Peek() the current + // time is lost, so memorize everything and restore after + sal_Int32 nOldLine = nLine; + sal_Int32 nOldCol = nCol; + sal_Int32 nOldCol1 = nCol1; + sal_Int32 nOldCol2 = nCol2; + OUString aOldSym = aSym; + SaveLine(); // save pLine in the scanner + + eCurTok = Peek(); + switch( eCurTok ) + { + case IF: Next(); eCurTok = ENDIF; break; + case SELECT: Next(); eCurTok = ENDSELECT; break; + case SUB: Next(); eCurTok = ENDSUB; break; + case FUNCTION: Next(); eCurTok = ENDFUNC; break; + case PROPERTY: Next(); eCurTok = ENDPROPERTY; break; + case TYPE: Next(); eCurTok = ENDTYPE; break; + case ENUM: Next(); eCurTok = ENDENUM; break; + case WITH: Next(); eCurTok = ENDWITH; break; + default : eCurTok = END; break; + } + nCol1 = nOldCol1; + if( eCurTok == END ) + { + // reset everything so that token is read completely newly after END + ePush = NIL; + nLine = nOldLine; + nCol = nOldCol; + nCol2 = nOldCol2; + aSym = aOldSym; + RestoreLine(); + } + return eCurTok; + } + // are data types keywords? + // there is ERROR(), DATA(), STRING() etc. + eCurTok = tp->t; + // AS: data types are keywords + if( tp->t == AS ) + { + bAs = true; + } + else + { + if( bAs ) + { + bAs = false; + } + else if( eCurTok >= DATATYPE1 && eCurTok <= DATATYPE2 && (bErrorIsSymbol || eCurTok != ERROR_) ) + { + eCurTok = SYMBOL; + } + } + + // CLASSMODULE, PROPERTY, GET, ENUM token only visible in compatible mode + SbiToken eTok = tp->t; + if( bCompatible ) + { + // #129904 Suppress system + if( eTok == STOP && aSym.equalsIgnoreAsciiCase("system") ) + { + eCurTok = SYMBOL; + } + if( eTok == GET && bStartOfLine ) + { + eCurTok = SYMBOL; + } + } + else + { + if( eTok == CLASSMODULE || + eTok == IMPLEMENTS || + eTok == PARAMARRAY || + eTok == ENUM || + eTok == PROPERTY || + eTok == GET || + eTok == TYPEOF ) + { + eCurTok = SYMBOL; + } + } + + bEos = IsEoln( eCurTok ); + return eCurTok; +} + +bool SbiTokenizer::MayBeLabel( bool bNeedsColon ) +{ + static TokenLabelInfo gaStaticTokenLabelInfo; + + if( eCurTok == SYMBOL || gaStaticTokenLabelInfo.canTokenBeLabel( eCurTok ) ) + { + return !bNeedsColon || DoesColonFollow(); + } + else + { + return ( eCurTok == NUMBER + && eScanType == SbxINTEGER + && nVal >= 0 ); + } +} + + +OUString SbiTokenizer::GetKeywordCase( const OUString& sKeyword ) +{ + for( auto& rTok : aTokTable_Basic ) + { + if( sKeyword.equalsIgnoreAsciiCaseAscii(rTok.s) ) + return OStringToOUString(rTok.s, RTL_TEXTENCODING_ASCII_US); + } + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |