1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
24 #include <o3tl/float_int_conversion.hxx>
25 #include <tools/debug.hxx>
26 #include <tools/stream.hxx>
27 #include <sal/log.hxx>
29 #include <basic/sbx.hxx>
30 #include <sbunoobj.hxx>
31 #include "sbxconv.hxx"
32 #include <runtime.hxx>
35 ///////////////////////////// constructors
37 SbxValue::SbxValue() : SbxBase()
39 aData
.eType
= SbxEMPTY
;
42 SbxValue::SbxValue( SbxDataType t
) : SbxBase()
49 SetFlag( SbxFlagBits::Fixed
);
50 aData
.clear(SbxDataType( n
));
53 SbxValue::SbxValue( const SbxValue
& r
)
54 : SvRefBase( r
), SbxBase( r
)
58 SetError( ERRCODE_BASIC_PROP_WRITEONLY
);
60 aData
.eType
= SbxNULL
;
64 const_cast<SbxValue
*>(&r
)->Broadcast( SfxHintId::BasicDataWanted
);
66 // Copy pointer, increment references
71 aData
.pOUString
= new OUString( *aData
.pOUString
);
75 aData
.pObj
->AddFirstRef();
79 aData
.pDecimal
->addRef();
86 SbxValue
& SbxValue::operator=( const SbxValue
& r
)
91 SetError( ERRCODE_BASIC_PROP_READONLY
);
94 // string -> byte array
95 if( IsFixed() && (aData
.eType
== SbxOBJECT
)
96 && aData
.pObj
&& ( aData
.pObj
->GetType() == (SbxARRAY
| SbxBYTE
) )
97 && (r
.aData
.eType
== SbxSTRING
) )
99 OUString aStr
= r
.GetOUString();
100 SbxArray
* pArr
= StringToByteArray(aStr
);
104 // byte array -> string
105 if( r
.IsFixed() && (r
.aData
.eType
== SbxOBJECT
)
106 && r
.aData
.pObj
&& ( r
.aData
.pObj
->GetType() == (SbxARRAY
| SbxBYTE
) )
107 && (aData
.eType
== SbxSTRING
) )
109 SbxBase
* pObj
= r
.GetObject();
110 SbxArray
* pArr
= dynamic_cast<SbxArray
*>( pObj
);
113 OUString aStr
= ByteArrayToString( pArr
);
118 // Readout the content of the variables
121 // then the type has to match
122 aNew
.eType
= aData
.eType
;
123 else if( r
.IsFixed() )
124 // Source fixed: copy the type
125 aNew
.eType
= SbxDataType( r
.aData
.eType
& 0x0FFF );
127 // both variant: then don't care
128 aNew
.eType
= SbxVARIANT
;
136 SbxValue::~SbxValue()
138 SetFlag( SbxFlagBits::Write
);
142 void SbxValue::Clear()
144 switch( aData
.eType
)
151 delete aData
.pOUString
; aData
.pOUString
= nullptr;
156 if( aData
.pObj
!= this )
158 SAL_INFO("basic.sbx", "Not at Parent-Prop - otherwise CyclicRef");
159 SbxVariable
*pThisVar
= dynamic_cast<SbxVariable
*>( this );
160 bool bParentProp
= pThisVar
&& (pThisVar
->GetUserData() & 0xFFFF) == 5345;
162 aData
.pObj
->ReleaseRef();
164 aData
.pObj
= nullptr;
168 releaseDecimalPtr( aData
.pDecimal
);
171 aData
.pData
= nullptr; break;
175 aEmpty
.clear(GetType());
183 void SbxValue::Broadcast( SfxHintId
)
186 //////////////////////////// Readout data
188 // Detect the "right" variables. If it is an object, will be addressed either
189 // the object itself or its default property.
190 // If the variable contain a variable or an object, this will be
193 SbxValue
* SbxValue::TheRealValue( bool bObjInObjError
) const
195 SbxValue
* p
= const_cast<SbxValue
*>(this);
198 SbxDataType t
= SbxDataType( p
->aData
.eType
& 0x0FFF );
201 // The block contains an object or a variable
202 SbxObject
* pObj
= dynamic_cast<SbxObject
*>( p
->aData
.pObj
);
205 // Has the object a default property?
206 SbxVariable
* pDflt
= pObj
->GetDfltProperty();
208 // If this is an object and contains itself,
209 // we cannot access on it
210 // The old condition to set an error is not correct,
211 // because e.g. a regular variant variable with an object
212 // could be affected if another value should be assigned.
213 // Therefore with flag.
214 if( bObjInObjError
&& !pDflt
&&
215 static_cast<SbxValue
*>(pObj
)->aData
.eType
== SbxOBJECT
&&
216 static_cast<SbxValue
*>(pObj
)->aData
.pObj
== pObj
)
218 #if !HAVE_FEATURE_SCRIPTING
219 const bool bSuccess
= false;
221 bool bSuccess
= handleToStringForCOMObjects( pObj
, p
);
225 SetError( ERRCODE_BASIC_BAD_PROP_VALUE
);
233 // Did we have an array?
234 SbxArray
* pArray
= dynamic_cast<SbxArray
*>( p
->aData
.pObj
);
237 // When indicated get the parameter
238 SbxArray
* pPar
= nullptr;
239 SbxVariable
* pVar
= dynamic_cast<SbxVariable
*>( p
);
241 pPar
= pVar
->GetParameters();
244 // Did we have a dimensioned array?
245 SbxDimArray
* pDimArray
= dynamic_cast<SbxDimArray
*>( p
->aData
.pObj
);
247 p
= pDimArray
->Get( pPar
);
249 p
= pArray
->Get( pPar
->Get( 1 )->GetInteger() );
253 // Otherwise guess a SbxValue
254 SbxValue
* pVal
= dynamic_cast<SbxValue
*>( p
->aData
.pObj
);
266 bool SbxValue::Get( SbxValues
& rRes
) const
269 ErrCode eOld
= GetError();
270 if( eOld
!= ERRCODE_NONE
)
274 SetError( ERRCODE_BASIC_PROP_WRITEONLY
);
279 // If an object or a VARIANT is requested, don't search the real values
280 SbxValue
* p
= const_cast<SbxValue
*>(this);
281 if( rRes
.eType
!= SbxOBJECT
&& rRes
.eType
!= SbxVARIANT
)
282 p
= TheRealValue( true );
285 p
->Broadcast( SfxHintId::BasicDataWanted
);
291 case SbxVARIANT
: rRes
= p
->aData
; break;
292 case SbxINTEGER
: rRes
.nInteger
= ImpGetInteger( &p
->aData
); break;
293 case SbxLONG
: rRes
.nLong
= ImpGetLong( &p
->aData
); break;
294 case SbxSALINT64
: rRes
.nInt64
= ImpGetInt64( &p
->aData
); break;
295 case SbxSALUINT64
: rRes
.uInt64
= ImpGetUInt64( &p
->aData
); break;
296 case SbxSINGLE
: rRes
.nSingle
= ImpGetSingle( &p
->aData
); break;
297 case SbxDOUBLE
: rRes
.nDouble
= ImpGetDouble( &p
->aData
); break;
298 case SbxCURRENCY
:rRes
.nInt64
= ImpGetCurrency( &p
->aData
); break;
299 case SbxDECIMAL
: rRes
.pDecimal
= ImpGetDecimal( &p
->aData
); break;
300 case SbxDATE
: rRes
.nDouble
= ImpGetDate( &p
->aData
); break;
302 rRes
.nUShort
= sal::static_int_cast
< sal_uInt16
>(
303 ImpGetBool( &p
->aData
) );
305 case SbxCHAR
: rRes
.nChar
= ImpGetChar( &p
->aData
); break;
306 case SbxBYTE
: rRes
.nByte
= ImpGetByte( &p
->aData
); break;
307 case SbxUSHORT
: rRes
.nUShort
= ImpGetUShort( &p
->aData
); break;
308 case SbxULONG
: rRes
.nULong
= ImpGetULong( &p
->aData
); break;
310 case SbxSTRING
: p
->aPic
= ImpGetString( &p
->aData
);
311 rRes
.pOUString
= &p
->aPic
; break;
312 case SbxCoreSTRING
: p
->aPic
= ImpGetCoreString( &p
->aData
);
313 rRes
.pOUString
= &p
->aPic
; break;
315 rRes
.nInt
= static_cast<int>(ImpGetLong( &p
->aData
));
318 rRes
.nUInt
= static_cast<int>(ImpGetULong( &p
->aData
));
321 if( p
->aData
.eType
== SbxOBJECT
)
322 rRes
.pObj
= p
->aData
.pObj
;
325 SetError( ERRCODE_BASIC_NO_OBJECT
);
330 if( p
->aData
.eType
== rRes
.eType
)
334 SetError( ERRCODE_BASIC_CONVERSION
);
341 // Object contained itself
342 SbxDataType eTemp
= rRes
.eType
;
349 if( eOld
!= ERRCODE_NONE
)
355 const OUString
& SbxValue::GetCoreString() const
358 aRes
.eType
= SbxCoreSTRING
;
361 const_cast<SbxValue
*>(this)->aToolString
= *aRes
.pOUString
;
365 const_cast<SbxValue
*>(this)->aToolString
.clear();
370 OUString
SbxValue::GetOUString() const
374 aRes
.eType
= SbxSTRING
;
377 aResult
= *aRes
.pOUString
;
382 bool SbxValue::GetBool() const
385 aRes
.eType
= SbxBOOL
;
387 return aRes
.nUShort
!= 0;
390 #define GET( g, e, t, m ) \
391 t SbxValue::g() const { SbxValues aRes(e); Get( aRes ); return aRes.m; }
393 GET( GetByte
, SbxBYTE
, sal_uInt8
, nByte
)
394 GET( GetChar
, SbxCHAR
, sal_Unicode
, nChar
)
395 GET( GetCurrency
, SbxCURRENCY
, sal_Int64
, nInt64
)
396 GET( GetDate
, SbxDATE
, double, nDouble
)
397 GET( GetDouble
, SbxDOUBLE
, double, nDouble
)
398 GET( GetInteger
, SbxINTEGER
, sal_Int16
, nInteger
)
399 GET( GetLong
, SbxLONG
, sal_Int32
, nLong
)
400 GET( GetObject
, SbxOBJECT
, SbxBase
*, pObj
)
401 GET( GetSingle
, SbxSINGLE
, float, nSingle
)
402 GET( GetULong
, SbxULONG
, sal_uInt32
, nULong
)
403 GET( GetUShort
, SbxUSHORT
, sal_uInt16
, nUShort
)
404 GET( GetInt64
, SbxSALINT64
, sal_Int64
, nInt64
)
405 GET( GetUInt64
, SbxSALUINT64
, sal_uInt64
, uInt64
)
406 GET( GetDecimal
, SbxDECIMAL
, SbxDecimal
*, pDecimal
)
409 //////////////////////////// Write data
411 bool SbxValue::Put( const SbxValues
& rVal
)
414 ErrCode eOld
= GetError();
415 if( eOld
!= ERRCODE_NONE
)
418 SetError( ERRCODE_BASIC_PROP_READONLY
);
419 else if( rVal
.eType
& 0xF000 )
420 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
423 // If an object is requested, don't search the real values
425 if( rVal
.eType
!= SbxOBJECT
)
426 p
= TheRealValue( false ); // Don't allow an error here
430 SetError( ERRCODE_BASIC_PROP_READONLY
);
431 else if( p
->IsFixed() || p
->SetType( static_cast<SbxDataType
>( rVal
.eType
& 0x0FFF ) ) )
432 switch( rVal
.eType
& 0x0FFF )
437 case SbxINTEGER
: ImpPutInteger( &p
->aData
, rVal
.nInteger
); break;
438 case SbxLONG
: ImpPutLong( &p
->aData
, rVal
.nLong
); break;
439 case SbxSALINT64
: ImpPutInt64( &p
->aData
, rVal
.nInt64
); break;
440 case SbxSALUINT64
: ImpPutUInt64( &p
->aData
, rVal
.uInt64
); break;
441 case SbxSINGLE
: ImpPutSingle( &p
->aData
, rVal
.nSingle
); break;
442 case SbxDOUBLE
: ImpPutDouble( &p
->aData
, rVal
.nDouble
); break;
443 case SbxCURRENCY
: ImpPutCurrency( &p
->aData
, rVal
.nInt64
); break;
444 case SbxDECIMAL
: ImpPutDecimal( &p
->aData
, rVal
.pDecimal
); break;
445 case SbxDATE
: ImpPutDate( &p
->aData
, rVal
.nDouble
); break;
446 case SbxBOOL
: ImpPutBool( &p
->aData
, rVal
.nInteger
); break;
447 case SbxCHAR
: ImpPutChar( &p
->aData
, rVal
.nChar
); break;
448 case SbxBYTE
: ImpPutByte( &p
->aData
, rVal
.nByte
); break;
449 case SbxUSHORT
: ImpPutUShort( &p
->aData
, rVal
.nUShort
); break;
450 case SbxULONG
: ImpPutULong( &p
->aData
, rVal
.nULong
); break;
452 case SbxSTRING
: ImpPutString( &p
->aData
, rVal
.pOUString
); break;
454 ImpPutLong( &p
->aData
, static_cast<sal_Int32
>(rVal
.nInt
) );
457 ImpPutULong( &p
->aData
, static_cast<sal_uInt32
>(rVal
.nUInt
) );
460 if( !p
->IsFixed() || p
->aData
.eType
== SbxOBJECT
)
463 if( p
->aData
.eType
== SbxOBJECT
&& p
->aData
.pObj
== rVal
.pObj
)
466 // Delete only the value part!
467 p
->SbxValue::Clear();
470 p
->aData
.pObj
= rVal
.pObj
;
472 // if necessary increment Ref-Count
473 if( p
->aData
.pObj
&& p
->aData
.pObj
!= p
)
477 OSL_FAIL( "TheRealValue" );
479 SAL_INFO("basic.sbx", "Not at Parent-Prop - otherwise CyclicRef");
480 SbxVariable
*pThisVar
= dynamic_cast<SbxVariable
*>( this );
481 bool bParentProp
= pThisVar
&& (pThisVar
->GetUserData() & 0xFFFF) == 5345;
483 p
->aData
.pObj
->AddFirstRef();
487 SetError( ERRCODE_BASIC_CONVERSION
);
490 if( p
->aData
.eType
== rVal
.eType
)
494 SetError( ERRCODE_BASIC_CONVERSION
);
496 p
->aData
.eType
= SbxNULL
;
501 p
->SetModified( true );
502 p
->Broadcast( SfxHintId::BasicDataChanged
);
503 if( eOld
!= ERRCODE_NONE
)
513 // Method to execute a pretreatment of the strings at special types.
514 // In particular necessary for BASIC-IDE, so that
515 // the output in the Watch-Window can be written back with PutStringExt,
516 // if Float were declared with ',' as the decimal separator or BOOl
517 // explicit with "TRUE" or "FALSE".
518 // Implementation in ImpConvStringExt (SBXSCAN.CXX)
519 void SbxValue::PutStringExt( const OUString
& r
)
521 // Copy; if it is Unicode convert it immediately
524 // Identify the own type (not as in Put() with TheRealValue(),
525 // Objects are not handled anyway)
526 SbxDataType eTargetType
= SbxDataType( aData
.eType
& 0x0FFF );
528 // tinker a Source-Value
530 aRes
.eType
= SbxSTRING
;
532 // Only if really something was converted, take the copy,
533 // otherwise take the original (Unicode remains)
535 if( ImpConvStringExt( aStr
, eTargetType
) )
536 aRes
.pOUString
= &aStr
;
538 aRes
.pOUString
= const_cast<OUString
*>(&r
);
540 // #34939: For Strings which contain a number, and if this has a Num-Type,
541 // set a Fixed flag so that the type will not be changed
542 SbxFlagBits nFlags_
= GetFlags();
543 if( ( eTargetType
>= SbxINTEGER
&& eTargetType
<= SbxCURRENCY
) ||
544 ( eTargetType
>= SbxCHAR
&& eTargetType
<= SbxUINT
) ||
545 eTargetType
== SbxBOOL
)
549 if( aVal
.IsNumeric() )
550 SetFlag( SbxFlagBits::Fixed
);
554 bRet
= bool( !IsError() );
556 // If FIXED resulted in an error, set it back
557 // (UI-Action should not result in an error, but simply fail)
564 bool SbxValue::PutBool( bool b
)
567 aRes
.eType
= SbxBOOL
;
568 aRes
.nUShort
= sal::static_int_cast
< sal_uInt16
>(b
? SbxTRUE
: SbxFALSE
);
573 bool SbxValue::PutEmpty()
575 bool bRet
= SetType( SbxEMPTY
);
580 void SbxValue::PutNull()
582 bool bRet
= SetType( SbxNULL
);
588 // Special decimal methods
589 void SbxValue::PutDecimal( css::bridge::oleautomation::Decimal
const & rAutomationDec
)
592 aData
.pDecimal
= new SbxDecimal( rAutomationDec
);
593 aData
.pDecimal
->addRef();
594 aData
.eType
= SbxDECIMAL
;
597 void SbxValue::fillAutomationDecimal
598 ( css::bridge::oleautomation::Decimal
& rAutomationDec
) const
600 SbxDecimal
* pDecimal
= GetDecimal();
601 if( pDecimal
!= nullptr )
603 pDecimal
->fillAutomationDecimal( rAutomationDec
);
608 bool SbxValue::PutString( const OUString
& r
)
611 aRes
.eType
= SbxSTRING
;
612 aRes
.pOUString
= const_cast<OUString
*>(&r
);
618 #define PUT( p, e, t, m ) \
619 bool SbxValue::p( t n ) \
620 { SbxValues aRes(e); aRes.m = n; Put( aRes ); return !IsError(); }
622 void SbxValue::PutDate( double n
)
623 { SbxValues
aRes(SbxDATE
); aRes
.nDouble
= n
; Put( aRes
); }
624 void SbxValue::PutErr( sal_uInt16 n
)
625 { SbxValues
aRes(SbxERROR
); aRes
.nUShort
= n
; Put( aRes
); }
627 PUT( PutByte
, SbxBYTE
, sal_uInt8
, nByte
)
628 PUT( PutChar
, SbxCHAR
, sal_Unicode
, nChar
)
629 PUT( PutCurrency
, SbxCURRENCY
, sal_Int64
, nInt64
)
630 PUT( PutDouble
, SbxDOUBLE
, double, nDouble
)
631 PUT( PutInteger
, SbxINTEGER
, sal_Int16
, nInteger
)
632 PUT( PutLong
, SbxLONG
, sal_Int32
, nLong
)
633 PUT( PutObject
, SbxOBJECT
, SbxBase
*, pObj
)
634 PUT( PutSingle
, SbxSINGLE
, float, nSingle
)
635 PUT( PutULong
, SbxULONG
, sal_uInt32
, nULong
)
636 PUT( PutUShort
, SbxUSHORT
, sal_uInt16
, nUShort
)
637 PUT( PutInt64
, SbxSALINT64
, sal_Int64
, nInt64
)
638 PUT( PutUInt64
, SbxSALUINT64
, sal_uInt64
, uInt64
)
639 PUT( PutDecimal
, SbxDECIMAL
, SbxDecimal
*, pDecimal
)
641 ////////////////////////// Setting of the data type
643 bool SbxValue::IsFixed() const
645 return (GetFlags() & SbxFlagBits::Fixed
) || ((aData
.eType
& SbxBYREF
) != 0);
648 // A variable is numeric, if it is EMPTY or really numeric
649 // or if it contains a complete convertible String
651 // #41692, implement it for RTL and Basic-Core separately
652 bool SbxValue::IsNumeric() const
654 return ImpIsNumeric( /*bOnlyIntntl*/false );
657 bool SbxValue::IsNumericRTL() const
659 return ImpIsNumeric( /*bOnlyIntntl*/true );
662 bool SbxValue::ImpIsNumeric( bool bOnlyIntntl
) const
667 SetError( ERRCODE_BASIC_PROP_WRITEONLY
);
671 if( dynamic_cast<const SbxVariable
*>( this) != nullptr )
672 const_cast<SbxVariable
*>(static_cast<const SbxVariable
*>(this))->Broadcast( SfxHintId::BasicDataWanted
);
673 SbxDataType t
= GetType();
676 if( aData
.pOUString
)
678 OUString
s( *aData
.pOUString
);
682 if( ImpScan( s
, n
, t2
, &nLen
, bOnlyIntntl
) == ERRCODE_NONE
)
683 return nLen
== s
.getLength();
689 || ( t
>= SbxINTEGER
&& t
<= SbxCURRENCY
)
690 || ( t
>= SbxCHAR
&& t
<= SbxUINT
);
693 SbxDataType
SbxValue::GetType() const
695 return SbxDataType( aData
.eType
& 0x0FFF );
699 bool SbxValue::SetType( SbxDataType t
)
701 DBG_ASSERT( !( t
& 0xF000 ), "SetType of BYREF|ARRAY is forbidden!" );
702 if( ( t
== SbxEMPTY
&& aData
.eType
== SbxVOID
)
703 || ( aData
.eType
== SbxEMPTY
&& t
== SbxVOID
) )
705 if( ( t
& 0x0FFF ) == SbxVARIANT
)
707 // Try to set the data type to Variant
708 ResetFlag( SbxFlagBits::Fixed
);
711 SetError( ERRCODE_BASIC_CONVERSION
);
716 if( ( t
& 0x0FFF ) != ( aData
.eType
& 0x0FFF ) )
718 if( !CanWrite() || IsFixed() )
720 SetError( ERRCODE_BASIC_CONVERSION
);
725 // De-allocate potential objects
726 switch( aData
.eType
)
729 delete aData
.pOUString
;
732 if( aData
.pObj
&& aData
.pObj
!= this )
734 SAL_WARN("basic.sbx", "Not at Parent-Prop - otherwise CyclicRef");
735 SbxVariable
*pThisVar
= dynamic_cast<SbxVariable
*>( this );
736 sal_uInt32 nSlotId
= pThisVar
737 ? pThisVar
->GetUserData() & 0xFFFF
739 DBG_ASSERT( nSlotId
!= 5345 || pThisVar
->GetName() == "Parent",
740 "SID_PARENTOBJECT is not named 'Parent'" );
741 bool bParentProp
= nSlotId
== 5345;
743 aData
.pObj
->ReleaseRef();
754 bool SbxValue::Convert( SbxDataType eTo
)
756 eTo
= SbxDataType( eTo
& 0x0FFF );
757 if( ( aData
.eType
& 0x0FFF ) == eTo
)
761 if( eTo
== SbxVARIANT
)
763 // Trial to set the data type to Variant
764 ResetFlag( SbxFlagBits::Fixed
);
767 SetError( ERRCODE_BASIC_CONVERSION
);
773 // Converting from null doesn't work. Once null, always null!
774 if( aData
.eType
== SbxNULL
)
776 SetError( ERRCODE_BASIC_CONVERSION
);
780 // Conversion of the data:
785 // The data type could be converted. It ends here with fixed elements,
786 // because the data had not to be taken over
798 ////////////////////////////////// Calculating
800 bool SbxValue::Compute( SbxOperator eOp
, const SbxValue
& rOp
)
802 #if !HAVE_FEATURE_SCRIPTING
803 const bool bVBAInterop
= false;
805 bool bVBAInterop
= SbiRuntime::isVBAEnabled();
807 SbxDataType eThisType
= GetType();
808 SbxDataType eOpType
= rOp
.GetType();
809 ErrCode eOld
= GetError();
810 if( eOld
!= ERRCODE_NONE
)
813 SetError( ERRCODE_BASIC_PROP_READONLY
);
814 else if( !rOp
.CanRead() )
815 SetError( ERRCODE_BASIC_PROP_WRITEONLY
);
816 // Special rule 1: If one operand is null, the result is null
817 else if( eThisType
== SbxNULL
|| eOpType
== SbxNULL
)
822 bool bDecimal
= false;
823 if( bVBAInterop
&& ( ( eThisType
== SbxSTRING
&& eOpType
!= SbxSTRING
&& eOpType
!= SbxEMPTY
) ||
824 ( eThisType
!= SbxSTRING
&& eThisType
!= SbxEMPTY
&& eOpType
== SbxSTRING
) ) &&
825 ( eOp
== SbxMUL
|| eOp
== SbxDIV
|| eOp
== SbxPLUS
|| eOp
== SbxMINUS
) )
829 else if( eThisType
== SbxSTRING
|| eOp
== SbxCAT
|| ( bVBAInterop
&& ( eOpType
== SbxSTRING
) && ( eOp
== SbxPLUS
) ) )
831 if( eOp
== SbxCAT
|| eOp
== SbxPLUS
)
833 // From 1999-11-5, keep OUString in mind
834 aL
.eType
= aR
.eType
= SbxSTRING
;
836 // From 1999-12-8, #70399: Here call GetType() again, Get() can change the type!
837 if( rOp
.GetType() == SbxEMPTY
)
838 goto Lbl_OpIsEmpty
; // concatenate empty, *this stays lhs as result
841 // #30576: To begin with test, if the conversion worked
842 if( aL
.pOUString
!= nullptr && aR
.pOUString
!= nullptr )
844 // tdf#108039: catch possible bad_alloc
846 *aL
.pOUString
+= *aR
.pOUString
;
848 catch (const std::bad_alloc
&) {
849 SetError(ERRCODE_BASIC_MATH_OVERFLOW
);
853 else if( aL
.pOUString
== nullptr )
855 aL
.pOUString
= new OUString();
859 SetError( ERRCODE_BASIC_CONVERSION
);
861 else if( eOpType
== SbxSTRING
&& rOp
.IsFixed() )
862 { // Numeric: there is no String allowed on the right side
863 SetError( ERRCODE_BASIC_CONVERSION
);
864 // falls all the way out
866 else if( ( eOp
>= SbxIDIV
&& eOp
<= SbxNOT
) || eOp
== SbxMOD
)
868 if( GetType() == eOpType
)
870 if( GetType() == SbxSALUINT64
|| GetType() == SbxSALINT64
871 || GetType() == SbxCURRENCY
|| GetType() == SbxULONG
)
872 aL
.eType
= aR
.eType
= GetType();
873 else if ( bVBAInterop
&& eOpType
== SbxBOOL
)
874 aL
.eType
= aR
.eType
= SbxBOOL
;
876 aL
.eType
= aR
.eType
= SbxLONG
;
879 aL
.eType
= aR
.eType
= SbxLONG
;
881 if( rOp
.Get( aR
) ) // re-do Get after type assigns above
883 if( Get( aL
) ) switch( eOp
)
885 /* TODO: For SbxEMPTY operands with boolean operators use
886 * the VBA Nothing definition of Comparing Nullable Types?
887 * https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/nullable-value-types
889 /* TODO: it is unclear yet whether this also should be done
890 * for the non-bVBAInterop case or not, or at all, consider
891 * user defined spreadsheet functions where an empty cell
892 * is SbxEMPTY and usually is treated as 0 zero or "" empty
896 if( aL
.eType
== SbxCURRENCY
)
897 if( !aR
.nInt64
) SetError( ERRCODE_BASIC_ZERODIV
);
899 aL
.nInt64
/= aR
.nInt64
;
900 aL
.nInt64
*= CURRENCY_FACTOR
;
902 else if( aL
.eType
== SbxSALUINT64
)
903 if( !aR
.uInt64
) SetError( ERRCODE_BASIC_ZERODIV
);
904 else aL
.uInt64
/= aR
.uInt64
;
905 else if( aL
.eType
== SbxSALINT64
)
906 if( !aR
.nInt64
) SetError( ERRCODE_BASIC_ZERODIV
);
907 else aL
.nInt64
/= aR
.nInt64
;
908 else if( aL
.eType
== SbxLONG
)
909 if( !aR
.nLong
) SetError( ERRCODE_BASIC_ZERODIV
);
910 else aL
.nLong
/= aR
.nLong
;
912 if( !aR
.nULong
) SetError( ERRCODE_BASIC_ZERODIV
);
913 else aL
.nULong
/= aR
.nULong
;
916 if( aL
.eType
== SbxCURRENCY
|| aL
.eType
== SbxSALINT64
)
917 if( !aR
.nInt64
) SetError( ERRCODE_BASIC_ZERODIV
);
918 else aL
.nInt64
%= aR
.nInt64
;
919 else if( aL
.eType
== SbxSALUINT64
)
920 if( !aR
.uInt64
) SetError( ERRCODE_BASIC_ZERODIV
);
921 else aL
.uInt64
%= aR
.uInt64
;
922 else if( aL
.eType
== SbxLONG
)
923 if( !aR
.nLong
) SetError( ERRCODE_BASIC_ZERODIV
);
924 else aL
.nLong
%= aR
.nLong
;
926 if( !aR
.nULong
) SetError( ERRCODE_BASIC_ZERODIV
);
927 else aL
.nULong
%= aR
.nULong
;
930 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
931 aL
.nInt64
&= aR
.nInt64
;
933 aL
.nLong
&= aR
.nLong
;
936 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
937 aL
.nInt64
|= aR
.nInt64
;
939 aL
.nLong
|= aR
.nLong
;
942 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
943 aL
.nInt64
^= aR
.nInt64
;
945 aL
.nLong
^= aR
.nLong
;
948 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
949 aL
.nInt64
= (aL
.nInt64
& aR
.nInt64
) | (~aL
.nInt64
& ~aR
.nInt64
);
951 aL
.nLong
= (aL
.nLong
& aR
.nLong
) | (~aL
.nLong
& ~aR
.nLong
);
954 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
955 aL
.nInt64
= ~aL
.nInt64
| aR
.nInt64
;
957 aL
.nLong
= ~aL
.nLong
| aR
.nLong
;
960 if( aL
.eType
!= SbxLONG
&& aL
.eType
!= SbxULONG
)
962 if ( aL
.eType
!= SbxBOOL
)
963 aL
.nInt64
= ~aL
.nInt64
;
965 aL
.nLong
= ~aL
.nLong
;
968 aL
.nLong
= ~aL
.nLong
;
974 else if( ( GetType() == SbxDECIMAL
|| rOp
.GetType() == SbxDECIMAL
)
975 && ( eOp
== SbxMUL
|| eOp
== SbxDIV
|| eOp
== SbxPLUS
|| eOp
== SbxMINUS
|| eOp
== SbxNEG
) )
977 aL
.eType
= aR
.eType
= SbxDECIMAL
;
979 if( rOp
.Get( aR
) && Get( aL
) )
981 if( aL
.pDecimal
&& aR
.pDecimal
)
987 bOk
= ( *(aL
.pDecimal
) *= *(aR
.pDecimal
) );
990 if( aR
.pDecimal
->isZero() )
991 SetError( ERRCODE_BASIC_ZERODIV
);
993 bOk
= ( *(aL
.pDecimal
) /= *(aR
.pDecimal
) );
996 bOk
= ( *(aL
.pDecimal
) += *(aR
.pDecimal
) );
999 bOk
= ( *(aL
.pDecimal
) -= *(aR
.pDecimal
) );
1002 bOk
= ( aL
.pDecimal
->neg() );
1005 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1008 SetError( ERRCODE_BASIC_MATH_OVERFLOW
);
1012 SetError( ERRCODE_BASIC_CONVERSION
);
1016 else if( GetType() == SbxCURRENCY
|| rOp
.GetType() == SbxCURRENCY
)
1018 aL
.eType
= SbxCURRENCY
;
1019 aR
.eType
= SbxCURRENCY
;
1023 if( Get( aL
) ) switch( eOp
)
1027 // first overflow check: see if product will fit - test real value of product (hence 2 curr factors)
1028 double dTest
= static_cast<double>(aL
.nInt64
) * static_cast<double>(aR
.nInt64
) / double(CURRENCY_FACTOR_SQUARE
);
1029 if( dTest
< SbxMINCURR
|| SbxMAXCURR
< dTest
)
1031 aL
.nInt64
= SAL_MAX_INT64
;
1032 if( dTest
< SbxMINCURR
) aL
.nInt64
= SAL_MIN_INT64
;
1033 SetError( ERRCODE_BASIC_MATH_OVERFLOW
);
1036 // second overflow check: see if unscaled product overflows - if so use doubles
1037 dTest
= static_cast<double>(aL
.nInt64
) * static_cast<double>(aR
.nInt64
);
1038 if( !(o3tl::convertsToAtLeast(dTest
, SAL_MIN_INT64
)
1039 && o3tl::convertsToAtMost(dTest
, SAL_MAX_INT64
)))
1041 aL
.nInt64
= static_cast<sal_Int64
>( dTest
/ double(CURRENCY_FACTOR
) );
1044 // precise calc: multiply then scale back (move decimal pt)
1045 aL
.nInt64
*= aR
.nInt64
;
1046 aL
.nInt64
/= CURRENCY_FACTOR
;
1054 SetError( ERRCODE_BASIC_ZERODIV
);
1057 // first overflow check: see if quotient will fit - calc real value of quotient (curr factors cancel)
1058 double dTest
= static_cast<double>(aL
.nInt64
) / static_cast<double>(aR
.nInt64
);
1059 if( dTest
< SbxMINCURR
|| SbxMAXCURR
< dTest
)
1061 SetError( ERRCODE_BASIC_MATH_OVERFLOW
);
1064 // second overflow check: see if scaled dividend overflows - if so use doubles
1065 dTest
= static_cast<double>(aL
.nInt64
) * double(CURRENCY_FACTOR
);
1066 if( !(o3tl::convertsToAtLeast(dTest
, SAL_MIN_INT64
)
1067 && o3tl::convertsToAtMost(dTest
, SAL_MAX_INT64
)))
1069 aL
.nInt64
= static_cast<sal_Int64
>(dTest
/ static_cast<double>(aR
.nInt64
));
1072 // precise calc: scale (move decimal pt) then divide
1073 aL
.nInt64
*= CURRENCY_FACTOR
;
1074 aL
.nInt64
/= aR
.nInt64
;
1080 double dTest
= ( static_cast<double>(aL
.nInt64
) + static_cast<double>(aR
.nInt64
) ) / double(CURRENCY_FACTOR
);
1081 if( dTest
< SbxMINCURR
|| SbxMAXCURR
< dTest
)
1083 SetError( ERRCODE_BASIC_MATH_OVERFLOW
);
1086 aL
.nInt64
+= aR
.nInt64
;
1092 double dTest
= ( static_cast<double>(aL
.nInt64
) - static_cast<double>(aR
.nInt64
) ) / double(CURRENCY_FACTOR
);
1093 if( dTest
< SbxMINCURR
|| SbxMAXCURR
< dTest
)
1095 SetError( ERRCODE_BASIC_MATH_OVERFLOW
);
1098 aL
.nInt64
-= aR
.nInt64
;
1102 aL
.nInt64
= -aL
.nInt64
;
1105 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1111 { // other types and operators including Date, Double and Single
1112 aL
.eType
= aR
.eType
= SbxDOUBLE
;
1120 aL
.nDouble
= pow( aL
.nDouble
, aR
.nDouble
);
1123 aL
.nDouble
*= aR
.nDouble
; break;
1125 if( !aR
.nDouble
) SetError( ERRCODE_BASIC_ZERODIV
);
1126 else aL
.nDouble
/= aR
.nDouble
;
1129 aL
.nDouble
+= aR
.nDouble
; break;
1131 aL
.nDouble
-= aR
.nDouble
; break;
1133 aL
.nDouble
= -aL
.nDouble
; break;
1135 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1137 // Date with "+" or "-" needs special handling that
1138 // forces the Date type. If the operation is '+' the
1139 // result is always a Date, if '-' the result is only
1140 // a Date if one of lhs or rhs ( but not both ) is already
1142 if( GetType() == SbxDATE
|| rOp
.GetType() == SbxDATE
)
1144 if( eOp
== SbxPLUS
|| ( ( eOp
== SbxMINUS
) && ( GetType() != rOp
.GetType() ) ) )
1156 releaseDecimalPtr( aL
.pDecimal
);
1157 releaseDecimalPtr( aR
.pDecimal
);
1162 bool bRes
= !IsError();
1163 if( bRes
&& eOld
!= ERRCODE_NONE
)
1168 // The comparison routine deliver TRUE or FALSE.
1170 bool SbxValue::Compare( SbxOperator eOp
, const SbxValue
& rOp
) const
1172 #if !HAVE_FEATURE_SCRIPTING
1173 const bool bVBAInterop
= false;
1175 bool bVBAInterop
= SbiRuntime::isVBAEnabled();
1179 ErrCode eOld
= GetError();
1180 if( eOld
!= ERRCODE_NONE
)
1182 if( !CanRead() || !rOp
.CanRead() )
1183 SetError( ERRCODE_BASIC_PROP_WRITEONLY
);
1184 else if( GetType() == SbxNULL
&& rOp
.GetType() == SbxNULL
&& !bVBAInterop
)
1188 else if( GetType() == SbxEMPTY
&& rOp
.GetType() == SbxEMPTY
)
1189 bRes
= !bVBAInterop
|| ( eOp
== SbxEQ
);
1190 // Special rule 1: If an operand is null, the result is FALSE
1191 else if( GetType() == SbxNULL
|| rOp
.GetType() == SbxNULL
)
1193 // Special rule 2: If both are variant and one is numeric
1194 // and the other is a String, num is < str
1195 else if( !IsFixed() && !rOp
.IsFixed()
1196 && ( rOp
.GetType() == SbxSTRING
&& GetType() != SbxSTRING
&& IsNumeric() ) && !bVBAInterop
1198 bRes
= eOp
== SbxLT
|| eOp
== SbxLE
|| eOp
== SbxNE
;
1199 else if( !IsFixed() && !rOp
.IsFixed()
1200 && ( GetType() == SbxSTRING
&& rOp
.GetType() != SbxSTRING
&& rOp
.IsNumeric() )
1203 bRes
= eOp
== SbxGT
|| eOp
== SbxGE
|| eOp
== SbxNE
;
1207 // If one of the operands is a String,
1208 // a String comparing take place
1209 if( GetType() == SbxSTRING
|| rOp
.GetType() == SbxSTRING
)
1211 aL
.eType
= aR
.eType
= SbxSTRING
;
1212 if( Get( aL
) && rOp
.Get( aR
) ) switch( eOp
)
1215 bRes
= ( *aL
.pOUString
== *aR
.pOUString
); break;
1217 bRes
= ( *aL
.pOUString
!= *aR
.pOUString
); break;
1219 bRes
= ( *aL
.pOUString
< *aR
.pOUString
); break;
1221 bRes
= ( *aL
.pOUString
> *aR
.pOUString
); break;
1223 bRes
= ( *aL
.pOUString
<= *aR
.pOUString
); break;
1225 bRes
= ( *aL
.pOUString
>= *aR
.pOUString
); break;
1227 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1230 // From 1995-12-19: If SbxSINGLE participate, then convert to SINGLE,
1231 // otherwise it shows a numeric error
1232 else if( GetType() == SbxSINGLE
|| rOp
.GetType() == SbxSINGLE
)
1234 aL
.eType
= aR
.eType
= SbxSINGLE
;
1235 if( Get( aL
) && rOp
.Get( aR
) )
1239 bRes
= ( aL
.nSingle
== aR
.nSingle
); break;
1241 bRes
= ( aL
.nSingle
!= aR
.nSingle
); break;
1243 bRes
= ( aL
.nSingle
< aR
.nSingle
); break;
1245 bRes
= ( aL
.nSingle
> aR
.nSingle
); break;
1247 bRes
= ( aL
.nSingle
<= aR
.nSingle
); break;
1249 bRes
= ( aL
.nSingle
>= aR
.nSingle
); break;
1251 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1254 else if( GetType() == SbxDECIMAL
&& rOp
.GetType() == SbxDECIMAL
)
1256 aL
.eType
= aR
.eType
= SbxDECIMAL
;
1259 if( aL
.pDecimal
&& aR
.pDecimal
)
1261 SbxDecimal::CmpResult eRes
= compare( *aL
.pDecimal
, *aR
.pDecimal
);
1265 bRes
= ( eRes
== SbxDecimal::CmpResult::EQ
); break;
1267 bRes
= ( eRes
!= SbxDecimal::CmpResult::EQ
); break;
1269 bRes
= ( eRes
== SbxDecimal::CmpResult::LT
); break;
1271 bRes
= ( eRes
== SbxDecimal::CmpResult::GT
); break;
1273 bRes
= ( eRes
!= SbxDecimal::CmpResult::GT
); break;
1275 bRes
= ( eRes
!= SbxDecimal::CmpResult::LT
); break;
1277 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1282 SetError( ERRCODE_BASIC_CONVERSION
);
1284 releaseDecimalPtr( aL
.pDecimal
);
1285 releaseDecimalPtr( aR
.pDecimal
);
1287 // Everything else comparing on a SbxDOUBLE-Basis
1290 aL
.eType
= aR
.eType
= SbxDOUBLE
;
1291 bool bGetL
= Get( aL
);
1292 bool bGetR
= rOp
.Get( aR
);
1293 if( bGetL
&& bGetR
)
1297 bRes
= ( aL
.nDouble
== aR
.nDouble
); break;
1299 bRes
= ( aL
.nDouble
!= aR
.nDouble
); break;
1301 bRes
= ( aL
.nDouble
< aR
.nDouble
); break;
1303 bRes
= ( aL
.nDouble
> aR
.nDouble
); break;
1305 bRes
= ( aL
.nDouble
<= aR
.nDouble
); break;
1307 bRes
= ( aL
.nDouble
>= aR
.nDouble
); break;
1309 SetError( ERRCODE_BASIC_BAD_ARGUMENT
);
1311 // at least one value was got
1312 // if this is VBA then a conversion error for one
1313 // side will yield a false result of an equality test
1314 else if ( bGetR
|| bGetL
)
1316 if ( bVBAInterop
&& eOp
== SbxEQ
&& GetError() == ERRCODE_BASIC_CONVERSION
)
1326 if( eOld
!= ERRCODE_NONE
)
1331 ///////////////////////////// Reading/Writing
1333 bool SbxValue::LoadData( SvStream
& r
, sal_uInt16
)
1335 // #TODO see if these types are really dumped to any stream
1336 // more than likely this is functionality used in the binfilter alone
1339 r
.ReadUInt16( nType
);
1340 aData
.eType
= SbxDataType( nType
);
1345 r
.ReadInt16( aData
.nInteger
); break;
1347 r
.ReadInt32( aData
.nLong
); break;
1351 OUString aVal
= read_uInt16_lenPrefixed_uInt8s_ToOUString(r
,
1352 RTL_TEXTENCODING_ASCII_US
);
1355 if( ImpScan( aVal
, d
, t
, nullptr, false ) != ERRCODE_NONE
|| t
== SbxDOUBLE
)
1357 aData
.nSingle
= 0.0F
;
1360 aData
.nSingle
= static_cast<float>(d
);
1367 OUString aVal
= read_uInt16_lenPrefixed_uInt8s_ToOUString(r
,
1368 RTL_TEXTENCODING_ASCII_US
);
1370 if( ImpScan( aVal
, aData
.nDouble
, t
, nullptr, false ) != ERRCODE_NONE
)
1372 aData
.nDouble
= 0.0;
1378 r
.ReadInt64(aData
.nInt64
);
1381 r
.ReadUInt64( aData
.uInt64
);
1385 sal_uInt32 tmpHi
= 0;
1386 sal_uInt32 tmpLo
= 0;
1387 r
.ReadUInt32( tmpHi
).ReadUInt32( tmpLo
);
1388 aData
.nInt64
= (static_cast<sal_Int64
>(tmpHi
) << 32);
1389 aData
.nInt64
|= static_cast<sal_Int64
>(tmpLo
);
1394 OUString aVal
= read_uInt16_lenPrefixed_uInt8s_ToOUString(r
,
1395 RTL_TEXTENCODING_ASCII_US
);
1396 if( !aVal
.isEmpty() )
1397 aData
.pOUString
= new OUString( aVal
);
1399 aData
.pOUString
= nullptr; // JSM 1995-09-22
1404 r
.ReadUInt16( aData
.nUShort
); break;
1408 r
.ReadUChar( nMode
);
1412 aData
.pObj
= nullptr;
1415 aData
.pObj
= SbxBase::Load( r
);
1416 return ( aData
.pObj
!= nullptr );
1431 r
.ReadUChar( aData
.nByte
); break;
1433 r
.ReadUInt32( aData
.nULong
); break;
1438 // Match the Int on this system?
1439 if( n
> SAL_TYPES_SIZEOFINT
)
1441 r
.ReadInt32( aData
.nLong
);
1442 aData
.eType
= SbxLONG
;
1446 r
.ReadInt32( nInt
);
1455 // Match the UInt on this system?
1456 if( n
> SAL_TYPES_SIZEOFINT
)
1458 r
.ReadUInt32( aData
.nULong
);
1459 aData
.eType
= SbxULONG
;
1463 r
.ReadUInt32( nUInt
);
1464 aData
.nUInt
= nUInt
;
1473 r
.ReadInt32( aData
.nLong
);
1475 // #78919 For backwards compatibility
1480 aData
.clear(SbxNULL
);
1481 ResetFlag(SbxFlagBits::Fixed
);
1482 SAL_WARN( "basic.sbx", "Loaded a non-supported data type" );
1489 bool SbxValue::StoreData( SvStream
& r
) const
1491 sal_uInt16 nType
= sal::static_int_cast
< sal_uInt16
>(aData
.eType
);
1492 r
.WriteUInt16( nType
);
1493 switch( nType
& 0x0FFF )
1497 r
.WriteInt16( aData
.nInteger
); break;
1499 r
.WriteInt32( aData
.nLong
); break;
1501 // #49935: Save as double, otherwise an error during the read in
1502 const_cast<SbxValue
*>(this)->aData
.eType
= static_cast<SbxDataType
>( ( nType
& 0xF000 ) | SbxDOUBLE
);
1503 write_uInt16_lenPrefixed_uInt8s_FromOUString(r
, GetCoreString(), RTL_TEXTENCODING_ASCII_US
);
1504 const_cast<SbxValue
*>(this)->aData
.eType
= static_cast<SbxDataType
>(nType
);
1508 write_uInt16_lenPrefixed_uInt8s_FromOUString(r
, GetCoreString(), RTL_TEXTENCODING_ASCII_US
);
1512 // see comment in SbxValue::StoreData
1513 r
.WriteUInt64( aData
.uInt64
);
1517 sal_Int32 tmpHi
= ( (aData
.nInt64
>> 32) & 0xFFFFFFFF );
1518 sal_Int32 tmpLo
= static_cast<sal_Int32
>(aData
.nInt64
);
1519 r
.WriteInt32( tmpHi
).WriteInt32( tmpLo
);
1523 if( aData
.pOUString
)
1525 write_uInt16_lenPrefixed_uInt8s_FromOUString(r
, *aData
.pOUString
, RTL_TEXTENCODING_ASCII_US
);
1529 write_uInt16_lenPrefixed_uInt8s_FromOUString(r
, OUString(), RTL_TEXTENCODING_ASCII_US
);
1534 r
.WriteUInt16( aData
.nUShort
); break;
1536 // to save itself as Objectptr does not work!
1539 if( dynamic_cast<SbxValue
*>( aData
.pObj
) != this )
1542 return aData
.pObj
->Store( r
);
1552 char c
= sal::static_int_cast
< char >(aData
.nChar
);
1557 r
.WriteUChar( aData
.nByte
); break;
1559 r
.WriteUInt32( aData
.nULong
); break;
1562 r
.WriteUChar( SAL_TYPES_SIZEOFINT
).WriteInt32( aData
.nInt
);
1567 r
.WriteUChar( SAL_TYPES_SIZEOFINT
).WriteUInt32( aData
.nUInt
);
1575 r
.WriteInt32( aData
.nLong
);
1577 // #78919 For backwards compatibility
1582 SAL_WARN( "basic.sbx", "Saving a non-supported data type" );
1588 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */