WIP:StarBasicのコード(C++)を読もう!

まず、StarBasicのコードがどのように実行されているかを順番に示す。

  1. SbiTokenizerは、StarBasic中の各語(トークン)が何であるかという情報を割り当てる。(字句解析)。なお、次のトークンを切り出す際にはSbiScanner::NextSymの力を借りている。その際色々フラグを立てる(例:数値フラグbNumberが立っていればNUMBER)。
  2. SbiParserはそれぞれの語がどのような順番に並んでいるかという情報から、どの構文であるかを判断する(構文解析)、それと同時にこれまでに宣言した識別子と現在解析中の宣言文の識別子(SYMBOL)が同名でないかやその識別子のスコープを決定する。なお、SbiParser::Peek()は次に読み込むべきトークンの情報を内部の状態を変更せずに取得する。
  3. このとき(例:SbiParser::DoLoop())、SbiExpressionなどを通じて呼ばれるSbiCodeGenは、引数の数に応じたGenメソッドのオーバーロードを用いてアセンブリのコードのようなものを生成する。
  4. ここで生成したコードはSbiRuntimeに渡されSbiRuntime::Stepで処理される。(参考: SbiRuntime::aStep0, SbiRuntime::aStep1, SbiRuntime::aStep2、およびSbiOpCode)
  5. 一例として、関数ポインタの配列たるaStep1に存在するSbiRuntime::StepErrorは色々経由して、nErrorを設定する。
  6. エラーが発生した、つまりnErrorが0でないとき、エラーハンドラが何れの親ランタイムにも設定されていないならばSbiRuntime::Abortを実行する。
  7. コンパイルの問題であるときはSbiParserやSbiTokenizerを、実行時の処理が問題になっているときはSbiRuntimeを見る。

ここまでが前座。似たような追い方をして色々なコードを読んでみよう、という独立した話が以下続く。

65758:If ステートメント中にあるErrorで処理が止まらない

SbiTokenizerでの処理の際、関数名は識別子であり、それ以外はそのトークン用の列挙体メンバが用意されているのだが、トークンによっては同一文字列が識別子として使われることも、構文の一部として登場することもある。一例がErrorであり、前記の通りErrorステートメントのときは、指定された数値に対応したエラーを起こして処理を中断、Error関数のときはそれとは処理が異なり、与えられた引数に対応するエラーメッセージを返すものである。

どのようなトークンとして解釈するかは、SbiParser::Parseに入った直後のPeek()呼び出しの前後でフラグが切り替えられる

bool SbiParser::Parse()
{
    if( bAbort ) return false;

    EnableErrors();

    bErrorIsSymbol = false;
    Peek();
    bErrorIsSymbol = true;
SbiToken SbiTokenizer::Next()
{
        // (中略)
    
        else if( eCurTok >= DATATYPE1 && eCurTok <= DATATYPE2 && (bErrorIsSymbol || eCurTok != ERROR_) )
        {
            eCurTok = SYMBOL;
        }

ところが、SbiParser::Ifの中の条件文でSbiParse::Parse()が帰ってきた「後」にPeekしたトークン判別している。このときのErrorはSYMBOLだからError関数であって、エラー自体を発生させて止まるErrorステートメントとは別物だ。

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();
            std::unique_ptr<SbiExpression> pCond(new 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;
                }
            }

125627:仮引数と実引数の型が一致しないとき、ByValで、一致するときはByRef

関数の各実引数はpParamやrParamに、SbxVariableのインスタンスとして格納されている。なお、添字0番の領域は戻り値を格納するためのスペースである。

以下の条件のいずれかを満たした場合、ByValとして扱われる。

void SbiRuntime::SetParameters( SbxArray* pParams )
{
    refParams = new SbxArray;
    // for the return value
    refParams->Put32( pMeth, 0 );

    SbxInfo* pInfo = pMeth ? pMeth->GetInfo() : nullptr;
    sal_uInt32 nParamCount = pParams ? pParams->Count32() : 1;
    assert(nParamCount <= std::numeric_limits<sal_uInt16>::max());
    if( nParamCount > 1 )
    {
        for( sal_uInt32 i = 1 ; i < nParamCount ; i++ )
        {
            const SbxParamInfo* p = pInfo ? pInfo->GetParam( sal::static_int_cast<sal_uInt16>(i) ) : nullptr;

            // #111897 ParamArray
            // (ParamArrayに関する話なので中略)

            SbxVariable* v = pParams->Get32( i );
            // methods are always byval!
            bool bByVal = dynamic_cast<const SbxMethod>(v) != nullptr;
            SbxDataType t = v->GetType();
            bool bTargetTypeIsArray = false;
            if( p )
            {
                bByVal |= ( p->eType & SbxBYREF ) == 0;
                t = static_cast<SbxDatatype>( p->eType & 0x0FFF );

                if( !bByVal && t != SbxVARIANT &&
                    (!v->IsFixed() || static_cast<SbxDatatype>(v->GetType() & 0x0FFF ) != t) )
                {
                    bByVal = true;
                }

                bTargetTypeIsArray = (p->nUserData & PARAM_INFO_WITHBRACKETS) != 0;
            }
            
            
            if( bByVal )
            {
                if( bTargetTypeIsArray )
                {
                    t = SbxOBJECT;
                }
                SbxVariable* v2 = new SbxVariable( t );
                v2->SetFlag( SbxFlagBits::ReadWrite );
                *v2 = *v;
                refParams->Put32( v2, i );
            }
            else
            {
                if( t != SbxVARIANT && t != ( v->GetType() & 0x0FFF ) )
                {
                    if( p && (p->eType & SbxARRAY) )
                    {
                        Error( ERRCODE_BASIC_CONVERSION );
                    }
                    else
                    {
                        v->Convert( t );
                    }
                }
                refParams->Put32( v, i );
            }
            if( p )
            {
                refParams->PutAlias32( p->aName, i );
            }
        }
    }

    // ParamArray for missing parameter
    // (ParamArray「可変長引数」の話なので中略)
}

(FIXED)123025:Split関数の戻り値の配列をRedim Preserveで保持できない!(本項WIP)

StarBasicのコード中、定義でない場所で識別子が登場した場合、それは代入文か、関数・サブルーチンの呼び出しである。

// assignment or subroutine call

void SbiParser::Symbol( const KeywordSymbolInfo* pKeywordSymbolInfo )
{
    SbiExprMode eMode = bVBASupportOn ? EXPRMODE_STANDALONE : EXPRMODE_STANDARD;
    SbiExpression aVar( this, SbSYMBOL, eMode, pKeywordSymbolInfo );

    //(配列の代入の話なので中略)

    RecursiveMode eRecMode = ( bEQ ? PREVENT_CALL : FORCE_CALL );
    bool bSpecialMidHandling = false;
    SbiSymDef* pDef = aVar.GetRealVar();
    if( bEQ && pDef && pDef->GetScope() == SbRTL )
    {
    // (Mid関数やMid文は特殊な文法を採るため。中略)
    }
    // 関数呼び出し
    aVar.Gen( eRecMode );
    if( !bSpecialMidHandling )
    {
        if( !bEQ )
        {

            aGen.Gen( SbiOpcode::GET_ );
        }
        else
        {
            // so it must be an assignment!
            // (代入の話なので中略)            
            aGen.Gen( eOp );
        }
    }
}

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_ );
    }
}

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_ );
    }
}

// Output of an element
void SbiExprNode::Gen( SbiCodeGen& rGen, RecursiveMode eRecMode )
{
    sal_uInt16 nStringId;

    if( IsConstant() )
    {
        //(定数の話じゃないから中略)
    }
    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
    {
        //(演算子の話なので中略)
    }
}
// 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;
}
void SbiRuntime::StepRTL( sal_uInt32 nOp1, sal_uInt32 nOp2 )
{
    PushVar( FindElement( rBasic.pRtl.get(), nOp1, nOp2, ERRCODE_BASIC_PROC_UNDEFINED, false ) );
}    
SbiParser::SbiParser( StarBASIC* pb, SbModule* pm )
        : SbiTokenizer( pm->GetSource32(), pb ),
          aGlobals( aGblStrings, SbGLOBAL, this ),
          aPublics( aGblStrings, SbPUBLIC, this ),
          aRtlSyms( aGblStrings, SbRTL, this ),
          aGen( *pm, this, 1024 )
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(p) );
    return p;
}
StarBASIC::StarBASIC( StarBASIC* p, bool bIsDocBasic  )
    : SbxObject("StarBASIC"), bDocBasic( bIsDocBasic )
{
    SetParent( p );
    bNoRtl = bBreak = false;
    bVBAEnabled = false;

    if( !GetSbData()->nInst++ )
    {
        GetSbData()->pSbFac.reset( new SbiFactory );
        AddFactory( GetSbData()->pSbFac.get() );
        GetSbData()->pTypeFac = new SbTypeFactory;
        AddFactory( GetSbData()->pTypeFac );
        GetSbData()->pClassFac = new SbClassFactory;
        AddFactory( GetSbData()->pClassFac );
        GetSbData()->pOLEFac = new SbOLEFactory;
        AddFactory( GetSbData()->pOLEFac );
        GetSbData()->pFormFac = new SbFormFactory;
        AddFactory( GetSbData()->pFormFac );
        GetSbData()->pUnoFac.reset( new SbUnoFactory );
        AddFactory( GetSbData()->pUnoFac.get() );
    }
    pRtl = new SbiStdObject(RTLNAME, this );
    // Search via StarBasic is always global
    SetFlag( SbxFlagBits::GlobalSearch );
    pVBAGlobals = nullptr;
    bQuit = false;

    if( bDocBasic )
    {
        lclInsertDocBasicItem( *this );
    }
}

static Methods aMethods[] = {
// (中略)
{"Split",          SbxOBJECT,      3 | FUNCTION_, RTLNAME(Split),0         },
  { "expression",   SbxSTRING, 0,nullptr,0 },
  { "delimiter",    SbxSTRING, 0,nullptr,0 },
  { "count",        SbxLONG, 0,nullptr,0 },
// (中略)
}

SbxVariable* SbiStdObject::Find( const OUString& rName, SbxClassType t )
{
    // entered already?
    SbxVariable* pVar = SbxObject::Find( rName, t );
    if( !pVar )
    {
        // else search one
        sal_uInt16 nHash_ = SbxVariable::MakeHashCode( rName );
        Methods* p = aMethods;
        bool bFound = false;
        short nIndex = 0;
        sal_uInt16 nSrchMask = TYPEMASK_;
        switch( t )
        {
            case SbxClassType::Method:   nSrchMask = METHOD_; break;
            case SbxClassType::Property: nSrchMask = PROPERTY_; break;
            case SbxClassType::Object:   nSrchMask = OBJECT_; break;
            default: break;
        }
        while( p->nArgs != -1 )
        {
            if( ( p->nArgs & nSrchMask )
             && ( p->nHash == nHash_ )
             && ( rName.equalsIgnoreAsciiCaseAscii( p->pName ) ) )
            {
                bFound = true;
                if( p->nArgs & COMPTMASK_ )
                {
                    bool bCompatibility = false;
                    SbiInstance* pInst = GetSbData()->pInst;
                    if (pInst)
                    {
                        bCompatibility = pInst->IsCompatibility();
                    }
                    else
                    {
                        // No instance running => compiling a source on module level.
                        const SbModule* pModule = GetSbData()->pCompMod;
                        if (pModule)
                            bCompatibility = pModule->IsVBACompat();
                    }
                    if ((bCompatibility && (NORMONLY_ & p->nArgs)) || (!bCompatibility && (COMPATONLY_ & p->nArgs)))
                        bFound = false;
                }
                break;
            }
            nIndex += ( p->nArgs & ARGSMASK_ ) + 1;
            p = aMethods + nIndex;
        }

        if( bFound )
        {
            // isolate Args-fields:
            SbxFlagBits nAccess = static_cast<SbxFlagBits>(( p->nArgs & RWMASK_ ) >> 8);
            short nType   = ( p->nArgs & TYPEMASK_ );
            if( p->nArgs & CONST_ )
                nAccess |= SbxFlagBits::Const;
            OUString aName_ = OUString::createFromAscii( p->pName );
            SbxClassType eCT = SbxClassType::Object;
            if( nType & PROPERTY_ )
            {
                eCT = SbxClassType::Property;
            }
            else if( nType & METHOD_ )
            {
                eCT = SbxClassType::Method;
            }
            pVar = Make( aName_, eCT, p->eType, ( p->nArgs & FUNCTION_ ) == FUNCTION_ );
            pVar->SetUserData( nIndex + 1 );
            pVar->SetFlags( nAccess );
        }
    }
    return pVar;
}
// If a new object will be established, this object will be indexed,
// if an object of this name exists already.

SbxVariable* SbxObject::Make( const OUString& rName, SbxClassType ct, SbxDataType dt, bool bIsRuntimeFunction )
{
    // Is the object already available?
    SbxArray* pArray = nullptr;
    switch( ct )
    {
    case SbxClassType::Variable:
    case SbxClassType::Property: pArray = pProps.get();    break;
    case SbxClassType::Method:   pArray = pMethods.get();  break;
    case SbxClassType::Object:   pArray = pObjs.get();     break;
    default: SAL_WARN( "basic.sbx", "Invalid SBX-Class" ); break;
    }
    if( !pArray )
    {
        return nullptr;
    }
    // Collections may contain objects of the same name
    if( !( ct == SbxClassType::Object && dynamic_cast<const SbxCollection>( this ) != nullptr ) )
    {
        SbxVariable* pRes = pArray->Find( rName, ct );
        if( pRes )
        {
            return pRes;
        }
    }
    SbxVariable* pVar = nullptr;
    switch( ct )
    {
    case SbxClassType::Variable:
    case SbxClassType::Property:
        pVar = new SbxProperty( rName, dt );
        break;
    case SbxClassType::Method:
        pVar = new SbxMethod( rName, dt, bIsRuntimeFunction );
        break;
    case SbxClassType::Object:
        pVar = CreateObject( rName );
        break;
    default:
        break;
    }
    pVar->SetParent( this );
    pArray->Put32( pVar, pArray->Count32() );
    SetModified( true );
    // The object listen always
    StartListening(pVar->GetBroadcaster(), DuplicateHandling::Prevent);
    return pVar;
}  
void SbiRuntime::StepGET()
{
    SbxVariable* p = GetTOS();
    p->Broadcast( SfxHintId::BasicDataWanted );
}

// Perhaps some day one could cut the parameter 0.
// Then the copying will be dropped...

void SbxVariable::Broadcast( SfxHintId nHintId )
{
    if( mpBroadcaster && !IsSet( SbxFlagBits::NoBroadcast ) )
    {
        // Because the method could be called from outside, check the
        // rights here again
        if( nHintId == SfxHintId::BasicDataWanted )
        {
            if( !CanRead() )
            {
                return;
            }
        }
        if( nHintId == SfxHintId::BasicDataChanged )
        {
            if( !CanWrite() )
            {
                return;
            }
        }

        //fdo#86843 Add a ref during the following block to guard against
        //getting deleted before completing this method
        SbxVariableRef aBroadcastGuard(this);

        // Avoid further broadcasting
        std::unique_ptr<SfxBroadcaster> pSave = std::move(mpBroadcaster);
        SbxFlagBits nSaveFlags = GetFlags();
        SetFlag( SbxFlagBits::ReadWrite );
        if( mpPar.is() )
        {
            // Register this as element 0, but don't change over the parent!
            mpPar->GetRef32( 0 ) = this;
        }
        pSave->Broadcast( SbxHint( nHintId, this ) );
        mpBroadcaster = std::move(pSave);
        SetFlags( nSaveFlags );
    }
}

SfxBroadcaster& SbxVariable::GetBroadcaster()
{
    if( !mpBroadcaster )
    {
        mpBroadcaster.reset( new SfxBroadcaster );
    }
    return *mpBroadcaster;
}

// broadcast immediately

void SfxBroadcaster::Broadcast( const SfxHint &rHint )
{
    // notify all registered listeners exactly once
    for (size_t i = 0; i < mpImpl->m_Listeners.size(); ++i)
    {
        SfxListener *const pListener = mpImpl->m_Listeners[i];
        if (pListener)
            pListener->Notify( *this, rHint );
    }
}

void SbiStdObject::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )

{
    const SbxHint* pHint = dynamic_cast<const SbxHint*>(&rHint);
    if( pHint )
    {
        SbxVariable* pVar = pHint->GetVar();
        SbxArray* pPar_ = pVar->GetParameters();
        const sal_uInt16 nCallId = static_cast<sal_uInt16>(pVar->GetUserData());
        if( nCallId )
        {
            const SfxHintId t = pHint->GetId();
            if( t == SfxHintId::BasicInfoWanted )
                pVar->SetInfo( GetInfo( static_cast<short>(pVar->GetUserData()) ) );
            else
            {
                bool bWrite = false;
                if( t == SfxHintId::BasicDataChanged )
                    bWrite = true;
                if( t == SfxHintId::BasicDataWanted || bWrite )
                {
                    RtlCall p = aMethods[ nCallId-1 ].pFunc;
                    SbxArrayRef rPar( pPar_ );
                    if( !pPar_ )
                    {
                        rPar = pPar_ = new SbxArray;
                        pPar_->Put32( pVar, 0 );
                    }
                    p( static_cast<StarBASIC*>(GetParent()), *pPar_, bWrite );
                    return;
                }
            }
        }
        SbxObject::Notify( rBC, rHint );
    }
}

各種変数や各種一時変数はSbxVariableやSbxValueを使う。このコンストラクタに与えるのは宣言時の型であるが、これがSbxVARIANTの場合、StarBasicやVBAでは「なんでも入る変数」となる。このときはFixedフラグが設定されていない。

SbxValue::SbxValue( SbxDataType t ) : SbxBase()
{
    int n = t & 0x0FFF;

    if( n == SbxVARIANT )
        n = SbxEMPTY;
    else
        SetFlag( SbxFlagBits::Fixed );
    aData.clear(SbxDataType( n ));
}
// REDIM_COPY
// TOS  = Array-Variable, Reference to array is copied
//        Variable is cleared as in ERASE

void SbiRuntime::StepREDIMP_ERASE()
{
    SbxVariableRef refVar = PopVar();
    refRedim = refVar;
    SbxDataType eType = refVar->GetType();
    if( eType & SbxARRAY )
    {
        SbxBase* pElemObj = refVar->GetObject();
        SbxDimArray* pDimArray = dynamic_cast<SbxDimArray*>( pElemObj );
        if( pDimArray )
        {
            refRedimpArray = pDimArray;
        }

    }
    else if( refVar->IsFixed() )
    {
        refVar->Clear();
    }
    else
    {
        refVar->SetType( SbxEMPTY );
    }
}
void SbiRuntime::StepREDIMP()
{
    SbxVariableRef refVar = PopVar();
    DimImpl( refVar );

    // Now check, if we can copy from the old array
    if( refRedimpArray.is() )
    {
        if (SbxDimArray* pNewArray = dynamic_cast<SbxDimArray*>(refVar->GetObject()))
            implRestorePreservedArray(pNewArray, refRedimpArray);
    }
}
// Returns true when actually restored
static bool implRestorePreservedArray(SbxDimArray* pNewArray, SbxArrayRef& rrefRedimpArray, bool* pbWasError = nullptr)
{
    assert(pNewArray);
    bool bResult = false;
    if (pbWasError)
        *pbWasError = false;
    if (rrefRedimpArray)
    {
        SbxDimArray* pOldArray = static_cast<SbxDimArray*>(rrefRedimpArray.get());
        const sal_Int32 nDimsNew = pNewArray->GetDims32();
        const sal_Int32 nDimsOld = pOldArray->GetDims32();

        if (nDimsOld != nDimsNew)
        {
            StarBASIC::Error(ERRCODE_BASIC_OUT_OF_RANGE);
            if (pbWasError)
                *pbWasError = true;
        }
        else if (nDimsNew > 0)
        {
            // Store dims to use them for copying later
            std::unique_ptr<sal_Int32[]> pLowerBounds(new sal_Int32[nDimsNew]);
            std::unique_ptr<sal_Int32[]> pUpperBounds(new sal_Int32[nDimsNew]);
            std::unique_ptr<sal_Int32[]> pActualIndices(new sal_Int32[nDimsNew]);
            bool bNeedsPreallocation = true;

            // Compare bounds
            for (sal_Int32 i = 1; i <= nDimsNew; i++)
            {
                sal_Int32 lBoundNew, uBoundNew;
                sal_Int32 lBoundOld, uBoundOld;
                pNewArray->GetDim32(i, lBoundNew, uBoundNew);
                pOldArray->GetDim32(i, lBoundOld, uBoundOld);
                lBoundNew = std::max(lBoundNew, lBoundOld);
                uBoundNew = std::min(uBoundNew, uBoundOld);
                sal_Int32 j = i - 1;
                pActualIndices[j] = pLowerBounds[j] = lBoundNew;
                pUpperBounds[j] = uBoundNew;
                if (lBoundNew > uBoundNew) // No elements in the dimension -> no elements to restore
                    bNeedsPreallocation = false;
            }

            // Optimization: pre-allocate underlying container
            if (bNeedsPreallocation)
                pNewArray->Put32(nullptr, pUpperBounds.get());

            // Copy data from old array by going recursively through all dimensions
            // (It would be faster to work on the flat internal data array of an
            // SbyArray but this solution is clearer and easier)
            implCopyDimArray(pNewArray, pOldArray, nDimsNew - 1, 0, pActualIndices.get(),
                             pLowerBounds.get(), pUpperBounds.get());
            bResult = true;
        }

        rrefRedimpArray.clear();
    }
    return bResult;
}
// Helper function for StepREDIMP and StepDCREATE_IMPL / bRedimp = true
static void implCopyDimArray( SbxDimArray* pNewArray, SbxDimArray* pOldArray, sal_Int32 nMaxDimIndex,
    sal_Int32 nActualDim, sal_Int32* pActualIndices, sal_Int32* pLowerBounds, sal_Int32* pUpperBounds )
{
    sal_Int32& ri = pActualIndices[nActualDim];
    for( ri = pLowerBounds[nActualDim] ; ri <= pUpperBounds[nActualDim] ; ri++ )
    {
        if( nActualDim < nMaxDimIndex )
        {
            implCopyDimArray( pNewArray, pOldArray, nMaxDimIndex, nActualDim + 1,
                pActualIndices, pLowerBounds, pUpperBounds );
        }
        else
        {
            SbxVariable* pSource = pOldArray->Get32( pActualIndices );
            pNewArray->Put32(pSource, pActualIndices);
        }
    }
}

それを踏まえてSbRtl_Splitを見てみる。

void SbRtl_Split(StarBASIC *, SbxArray & rPar, bool)
{
    (中略)
    SbxDimArray* pArray = new SbxDimArray( SbxVARIANT );
    pArray->unoAddDim32( 0, nArraySize-1 );

    // insert parameter(s) into the array
    for(sal_Int32 i = 0 ; i < nArraySize ; i++ )
    {
        SbxVariableRef xVar = new SbxVariable( SbxVARIANT );
        xVar->PutString( vRet[i] );
        pArray->Put32( xVar.get(), &i );
    }

    // return array
    SbxVariableRef refVar = rPar.Get32(0);
    SbxFlagBits nFlags = refVar->GetFlags();
    refVar->ResetFlag( SbxFlagBits::Fixed );
    refVar->PutObject( pArray );
    refVar->SetFlags( nFlags );
    refVar->SetParameters( nullptr );
}

(FIXED)85371:関数名をループ変数に使うことができない

以下のコードを書いた時、アセンブリ言語的にはSbiOpCode::INITFOR_,SbiOpCode::NEXT_,SbiOpCode::JUMP_(範囲外になったとき)の3種類が出力されている。

For i = 1 to 10
' ほげふが
Next    
// 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();
}
    
void SbiRuntime::StepINITFOR()
{
    PushFor();
}

void SbiRuntime::StepINITFOREACH()
{
    PushForEach();
}

// increment FOR-variable

void SbiRuntime::StepNEXT()
{
    if( !pForStk )
    {
        StarBASIC::FatalError( ERRCODE_BASIC_INTERNAL_ERROR );
        return;
    }
    if( pForStk->eForType == ForType::To )
    {
        pForStk->refVar->Compute( SbxPLUS, *pForStk->refInc );
    }
}
void SbiRuntime::PushFor()
{
    SbiForStack* p = new SbiForStack;
    p->eForType = ForType::To;
    p->pNext = pForStk;
    pForStk = p;

    p->refInc = PopVar();
    p->refEnd = PopVar();
    SbxVariableRef xBgn = PopVar();
    p->refVar = PopVar();
    *(p->refVar) = *xBgn;
    nForLvl++;
}
bool SbxValue::Compute( SbxOperator eOp, const SbxValue& rOp )
{
#if !HAVE_FEATURE_SCRIPTING
    const bool bVBAInterop = false;
#else
    bool bVBAInterop =  SbiRuntime::isVBAEnabled();
#endif
    SbxDataType eThisType = GetType();
    SbxDataType eOpType = rOp.GetType();
    ErrCode eOld = GetError();
    if( eOld != ERRCODE_NONE )
        ResetError();
    if( !CanWrite() )
        SetError( ERRCODE_BASIC_PROP_READONLY );
    else if( !rOp.CanRead() )
        SetError( ERRCODE_BASIC_PROP_WRITEONLY );
    // Special rule 1: If one operand is null, the result is null
    else if( eThisType == SbxNULL || eOpType == SbxNULL )
        SetType( SbxNULL );
    else
    {

代入を担当する以下のコード、戻り値を担当するrefVar(SbxVariableを継承するSbxMethodが格納されていて通常の変数と同様に扱える)が現在走っているpMethodと等しいときのみSbxFlagBits::Writeを設定しており、それ以外のときにはSbxFlagBits::Readになっているため、前記のコードのCanWriteがエラーを返します。なので、少なくとも現状、For文の変数にメソッド名そのものを使うことはできません。


void SbiRuntime::StepPUT()
{
    SbxVariableRef refVal = PopVar();
    SbxVariableRef refVar = PopVar();
    // store on its own method (inside a function)?
    bool bFlagsChanged = false;
    SbxFlagBits n = SbxFlagBits::NONE;
    if( refVar.get() == pMeth )
    {
        bFlagsChanged = true;
        n = refVar->GetFlags();
        refVar->SetFlag( SbxFlagBits::Write );
    }

(FIXED)36737:String型やObject型の引数のデフォルト値が使用されない

次項と引用するコードが非常に近いのでそこで取り扱います。

(FIXED)79426:エラーオブジェクトを関数に渡して処理することができない

関数の引数には式の後にSbiOpCode::PARAM_が置かれ、これがSbiRuntime::StepPARAMによって処理される。

void SbiRuntime::StepPARAM( sal_uInt32 nOp1, sal_uInt32 nOp2 )
{
    sal_uInt16 i = static_cast<sal_uInt16>( nOp1 & 0x7FFF );
    SbxDataType t = static_cast<SbxDataType>(nOp2);
    SbxVariable* p;

    // #57915 solve missing in a cleaner way
    sal_uInt32 nParamCount = refParams->Count32();
    if( i >= nParamCount )
    {
        sal_uInt16 iLoop = i;
        while( iLoop >= nParamCount )
        {
            p = new SbxVariable();

            if( SbiRuntime::isVBAEnabled() &&
                (t == SbxOBJECT || t == SbxSTRING) )
            {
                if( t == SbxOBJECT )
                {
                    p->PutObject( nullptr );
                }
                else
                {
                    p->PutString( OUString() );
                }
            }
            else
            {
                p->PutErr( 448 );       // like in VB: Error-Code 448 (ERRCODE_BASIC_NAMED_NOT_FOUND)
            }
            refParams->Put32( p, iLoop );
            iLoop--;
        }
    }
    p = refParams->Get32( i );

    if( p->GetType() == SbxERROR && i )
    {
        // if there's a parameter missing, it can be OPTIONAL
        bool bOpt = false;
        if( pMeth )
        {
            SbxInfo* pInfo = pMeth->GetInfo();
            if ( pInfo )
            {
                const SbxParamInfo* pParam = pInfo->GetParam( i );
                if( pParam && ( pParam->nFlags & SbxFlagBits::Optional ) )
                {
                    // Default value?
                    sal_uInt16 nDefaultId = static_cast<sal_uInt16>(pParam->nUserData & 0x0ffff);
                    if( nDefaultId > 0 )
                    {
                        OUString aDefaultStr = pImg->GetString( nDefaultId );
                        p = new SbxVariable(pParam-> eType);
                        p->PutString( aDefaultStr );
                        refParams->Put32( p, i );
                    }
                    bOpt = true;
                }
            }
        }
        if( !bOpt )
        {
            Error( ERRCODE_BASIC_NOT_OPTIONAL );
        }
    }
    else if( t != SbxVARIANT && static_cast<SbxDataType>(p->GetType() & 0x0FFF ) != t )
    {
        SbxVariable* q = new SbxVariable( t );
        aRefSaved.emplace_back(q );
        *q = *p;
        p = q;
        if ( i )
        {
            refParams->Put32( p, i );
        }
    }
    SetupArgs( p, nOp1 );
    PushVar( CheckArray( p ) );
}

47214:複数の論理行を1行の物理行内に書くためにコロンを使うが、このあとにあるLINE INPUT文が認識されない

直前に取得したトークンが「なし(ファイル始端)」「コメントのためのREM」「行終端」「THEN」「ELSE」の何れかである場合は行の開始とみなす。

SbiToken SbiTokenizer::Next()
{
    //(中略)
    bool bStartOfLine = (eCurTok == NIL || eCurTok == REM || eCurTok == EOLN || eCurTok == THEN || eCurTok == ELSE); // single line If

取得したトークンがNAMEかLINEであるときは、そのトークンはSYMBOL、つまり識別子である。ということはNAMEステートメントやLINEステートメントの一部じゃない


if( !bStartOfLine && (tp->t == NAME || tp->t == LINE) )
{
        return eCurTok = SYMBOL;
}

じゃあ、コロンはSbiTokenizer/SbiParserではどういう扱いなのかというと、「Goto等の移動先のラベル」を明示するのに使われている。

123263:ある要素型の配列変数に別の要素型の配列が代入できてしまう。

配列変数を宣言したときにどのような処理が走るかを見ていこう。

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;
    }

    //(今回定義するのは変数だから中略)

    // 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 )
        {
            // (二重定義チェック、またはReDim, ReDim Preserve関連なので中略)
        }
        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() )
        {
            // (Dim x As New UserDifined_Type、みたいなヤツ。中略)
        }
        else
        {
            if( bConst )
            {
                // (定数の話なので中略)
                
            }
            else if( pDim )
            {
        // (RedimやReDim Preserveに絡む再設定)
                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 );
    }

}
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();

    // As 以下があるような場合。
    // つーか、型指定子よりもこっちの方が一般的だよね?
    // 型指定子とAs以下の型が矛盾しないかどうかのチェックも行うハズ。
    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 )
    {
        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!
                        // (固定長文字列の話。中略)
                    }
                }
                break;
            case SYMBOL: // can only be a TYPE or an object class!
                // (ユーザー定義型とかの話。中略)
                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 );
    }
}

こういうコードになっている。つまり、Dim x(1) As Integerのx、例えばVB.NETなんかだとIntegerの配列なのだけれども、StarBasicではあくまでInteger型に括弧が付いたものなんだよ。式上、識別子の後に「括弧がついているか」もしくは「GetDims()の戻り値はどうか」、それは配列なのかを気にしなきゃいけなくなるので個人的にはStarBasicの設計で一番モヤっとしているところ。

余談:これが、個人的にあまり好きじゃなかったから、個人的なビルドの方には括弧を処理しているときに配列だと判断して、TypeDeclが処理する際に、rDefが配列だという情報も一緒に型として持たせているわけだ(笑)。

少し先の話になるけれど、exprtree.cxxを読むときは、「SbxOBJECTとSbxARRAYが別フラグだ!」ってことを非常に強く意識して欲しい。「SbxOBJECTとSbxARRAYが別フラグだ!」「SbxOBJECTとSbxARRAYが別フラグだ!」大事なことだから3回繰り返した。

// 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 );
    // (今回は標準ライブラリ内関数の呼び出しではないので中略)
    aVar.Gen( eRecMode );
    if( !bSpecialMidHandling )
    {
        if( !bEQ )
        {
            // (関数呼び出し。中略)
        }
        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 SbiRuntime::StepPUT()
{
    SbxVariableRef refVal = PopVar();
    SbxVariableRef refVar = PopVar();
    // store on its own method (inside a function)?
    bool bFlagsChanged = false;
    SbxFlagBits n = SbxFlagBits::NONE;
    if( refVar.get() == pMeth )
    {
        bFlagsChanged = true;
        n = refVar->GetFlags();
        refVar->SetFlag( SbxFlagBits::Write );
    }

    // (デフォルトプロパティ絡みのごちゃごちゃ。基本的にVBAにしか関係ない話なので中略)

    //ええっ!条件これだけ??
    if ( !checkUnoStructCopy( bVBAEnabled, refVal, refVar ) )
        *refVar = *refVal;

    if( bFlagsChanged )
        refVar->SetFlags( n );
}
    

refVarが代入される側の変数で、refValが代入される値なのだが、この型チェックがガバガバすぎるだろう、と考えて型チェックを突っ込んだ。

もうひとつ重要なこと言わないといけない。C言語などでは、配列の添字は角括弧で、配列の引数は丸括弧だが、StarBasic含むBasic系言語は両方とも丸括弧

だから識別子の後に括弧が来た場合、それがどちらなのか考えないといけない。とりあえず、関数でないかどうかをチェックし、その後に出てくる、括弧つき識別子を配列として扱っている。ただし、デフォルトプロパティがある場合を除く。

std::unique_ptr SbiExpression::Term( const KeywordSymbolInfo* pKeywordSymbolInfo )
{
    // (中略)
    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;
        }
    }
    // (中略)
}    

それと、式の値がSbxOBJECTだったときは、ドットを置くことでメンバを参照できる。

ちなみに、VBAの場合はデフォルトプロパティが絡むからもっとややこしくなる。

とりあえず、Term()は文中に登場する最初の識別子の処理で、ObjTerm()が!や.でつながった後の識別子に対する処理。

もう一つ、自分が不満に思っていたのが identifier1(1).identifier2のようなコードで、identifier1が配列、という可能性は十分考えられたのに、今までの処理の流れと自分のパッチを当てる前の修正前コードでは、 identifier1が配列の要素、それにくっついている括弧となってしまっていた。自分は、これを補正して、identifier1を配列、その後の添字で初めてSbxOBJECTの可能性が生まれる、としたかった。

もちろんこの値はSbxOBJECTとは限らず、SbxINTEGERとかかもしれないし、(SbxARRAY | SbxINTEGER)かもしれない。

自分の修正前後の比較を置いておきたい。

52601:LIKEの処理

If Not "X" Like "Y" Then

上記コードがコンパイルエラーに成る原因をSbiParserから追っていく。

void SbiParser::If()
{
    sal_uInt32 nEndLbl;
    SbiToken eTok = NIL;
    // ignore end-tokens
    SbiExpression aCond( this );
    aCond.Gen();
    TestToken( THEN );

まず、SbiParser::IfはIfの次に式が来て、その後にTHENというトークンが来るとしている。

SbiExpressionはそのコンストラクタで第一引数にSbiParserをとり、SbiExpression::Booleanを呼び出す。

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;
}

SbiExpression::Booleanは任意の個数のSbiExpression::Like()を呼び出すが、複数回実行されるときは、その間に論理演算子が来る。

SbiExpression::Like()は内部でそれがVBA向けかStarBasic向けかによって異なる処理を呼び出す。いずれの場合も、Like()から呼び出されるComp()またはVBA_Not()以下再帰を追っていった地点でNotが登場する。

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;
}

メソッド呼び出しの深い順から、SbiExprNodeを作っていって上部につなげているので、Not "X" Like "Y"はVBASupport 1でもVBASupport 0でも((Not "X") Like "Y")として処理されるが、Not "X"は引数の型が期待どおりでないため、エラーとなる。

Errata

  • 関数呼び出しの場所が間違っていた。GET_じゃない。そのまえにSbiExpressionが存在していてそこでFIND_やFIND_G_やFIND_CM_が行われる。自分でも書いてて何か変だと思ってたんだ
  • 内容的には正しいが、不適切な以下の文章を削除してここに持ってきた。

    StarBasicにおける関数はどのように管理されているのかを見ていきましょう。

    StarBasicにおける関数定義はFunction 識別子(識別子 [As 識別子][,識別子 [As 識別子]]+) As 識別子という構文です。サブルーチンのときはFunctionの代わりにSubとなり、戻り値の型が消えます。

    Functionというトークンにあたると、SbiParser::SubFuncが呼ばれます。

    { FUNCTION, &SbiParser::SubFunc,    Y, N, }, // 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 );
        // (プロパティではないので中略)
        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 )
        {
            //(既に同名の識別子が使われていた場合のエラー処理。中略)
        }
        else
        {
            aPublics.Add( pDef );
        }
    
    
    // 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 );
        // (外部DLLの関数の呼び出し関連処理。それと引数リスト。中略)
        TypeDecl( *pDef ); // 戻り値の型の処理
        (中略)
        return pDef;
    }
    

    関数の定義(関数名含む)はパブリック変数等の定義を格納しているaPublicsに追加されている。

  • この下りも不要

    実際の処理はSbiRuntimeで行われる。

    void SbiRuntime::StepFIND( sal_uInt32 nOp1, sal_uInt32 nOp2 )
    {
        // 第一引数はモジュール
        StepFIND_Impl( pMod, nOp1, nOp2, ERRCODE_BASIC_PROC_UNDEFINED );
    }
    void SbiRuntime::StepFIND_Impl( SbxObject* pObj, sal_uInt32 nOp1, sal_uInt32 nOp2,
                                    ErrCode nNotFound, bool bStatic )
    {
        if( !refLocals.is() )
        {
            refLocals = new SbxArray;
        }
        // ローカル、つまりモジュールの中から探す
        // 探したものをスタックに突っ込んでいる
        PushVar( FindElement( pObj, nOp1, nOp2, nNotFound, true/*bLocal*/, bStatic ) );
    }
    SbxVariable* SbiRuntime::FindElement( SbxObject* pObj, sal_uInt32 nOp1, sal_uInt32 nOp2,
                                          ErrCode nNotFound, bool bLocal, bool bStatic )
    {
        bool bIsVBAInterOp = SbiRuntime::isVBAEnabled();
        if( bIsVBAInterOp )
        {
            // (VBAの話。中略)
        }
    
        SbxVariable* pElem = nullptr;
        if( !pObj )
        {
            Error( ERRCODE_BASIC_NO_OBJECT );
            pElem = new SbxVariable;
        }
        else
        {
            bool bFatalError = false;
            SbxDataType t = static_cast<SbxDataType>(nOp2);
            OUString aName( pImg->GetString( static_cast<short>( nOp1 & 0x7FFF ) ) );
            // Hacky capture of Evaluate [] syntax
            // this should be tackled I feel at the pcode level
        // (角括弧の評価の話。中略)
            if( bLocal )
            {
                if ( bStatic && pMeth )
                {
                    pElem = pMeth->GetStatics()->Find( aName, SbxClassType::DontCare );
                }
    
                if ( !pElem )
                {
                    pElem = refLocals->Find( aName, SbxClassType::DontCare );
                }
            }
            if( !pElem )
            {
                // (メソッド・関数が見つからなかったときの話。中略)
            }
            // #39108 Args can already be deleted!
            if( !bFatalError )
            {
                SetupArgs( pElem, nOp1 );
            }
            // because a particular call-type is requested
            if (SbxMethod* pMethod = dynamic_cast<SbxMethod*>(pElem))
            {
                // shall the type be converted?
                SbxDataType t2 = pElem->GetType();
                bool bSet = false;
                if( (pElem->GetFlags() & SbxFlagBits::Fixed) == SbxFlagBits::NONE )
                {
                    if( t != SbxVARIANT && t != t2 &&
                        t >= SbxINTEGER && t <= SbxSTRING )
                    {
                        pElem->SetType( t );
                        bSet = true;
                    }
                }
                // assign pElem to a Ref, to delete a temp-var if applicable
                SbxVariableRef refTemp = pElem;
    
                // remove potential rests of the last call of the SbxMethod
                // free Write before, so that there's no error
                SbxFlagBits nSavFlags = pElem->GetFlags();
                pElem->SetFlag( SbxFlagBits::ReadWrite | SbxFlagBits::NoBroadcast );
                pElem->SbxValue::Clear();
                pElem->SetFlags( nSavFlags );
    
                // don't touch before setting, as e. g. LEFT()
                // has to know the difference between Left$() and Left()
    
                // because the methods' parameters are cut away in PopVar()
                SbxVariable* pNew = new SbxMethod(*pMethod);
                //OLD: SbxVariable* pNew = new SbxVariable( *pElem );
    
                pElem->SetParameters(nullptr);
                pNew->SetFlag( SbxFlagBits::ReadWrite );
    
                if( bSet )
                {
                    pElem->SetType( t2 );
                }
                pElem = pNew;
            }
            // consider index-access for UnoObjects
            // definitely we want this for VBA where properties are often
            // collections ( which need index access ), but lets only do
            // this if we actually have params following
            else if( bVBAEnabled && dynamic_cast<const SbUnoProperty*>( pElem) != nullptr && pElem->GetParameters() )
            {
                SbxVariableRef refTemp = pElem;
    
                // dissolve the notify while copying variable
                SbxVariable* pNew = new SbxVariable( *pElem );
                pElem->SetParameters( nullptr );
                pElem = pNew;
            }
        }
        return CheckArray( pElem );
    }
    
    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;
        }
    }