/* -*- 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 .
*/
&SbiRuntime::StepLIKE,
&SbiRuntime::StepIS, // load/save
&SbiRuntime::StepARGC, // establish new Argv
&SbiRuntime::StepARGV, // TOS ==> current Argv
&SbiRuntime::StepINPUT, // Input ==> TOS
&SbiRuntime::StepLINPUT, // Line Input ==> TOS
&SbiRuntime::StepGET, // touch TOS
&SbiRuntime::StepSET, // save object TOS ==> TOS-1
&SbiRuntime::StepPUT, // TOS ==> TOS-1
&SbiRuntime::StepPUTC, // TOS ==> TOS-1, then ReadOnly
&SbiRuntime::StepDIM, // DIM
&SbiRuntime::StepREDIM, // REDIM
&SbiRuntime::StepREDIMP, // REDIM PRESERVE
&SbiRuntime::StepERASE, // delete TOS // branch
&SbiRuntime::StepSTOP, // program end
&SbiRuntime::StepINITFOR, // initialize FOR-Variable
&SbiRuntime::StepNEXT, // increment FOR-Variable
&SbiRuntime::StepCASE, // beginning CASE
&SbiRuntime::StepENDCASE, // end CASE
&SbiRuntime::StepSTDERROR, // standard error handling
&SbiRuntime::StepNOERROR, // no error handling
&SbiRuntime::StepLEAVE, // leave UP // E/A
&SbiRuntime::StepCHANNEL, // TOS = channel number
&SbiRuntime::StepPRINT, // print TOS
&SbiRuntime::StepPRINTF, // print TOS in field
&SbiRuntime::StepWRITE, // write TOS
&SbiRuntime::StepRENAME, // Rename Tos+1 to Tos
&SbiRuntime::StepPROMPT, // define Input Prompt from TOS
&SbiRuntime::StepRESTART, // Set restart point
&SbiRuntime::StepCHANNEL0, // set E/A-channel 0
&SbiRuntime::StepEMPTY, // empty expression on stack
&SbiRuntime::StepERROR, // TOS = error code
&SbiRuntime::StepLSET, // save object TOS ==> TOS-1
&SbiRuntime::StepRSET, // save object TOS ==> TOS-1
&SbiRuntime::StepREDIMP_ERASE,// Copy array object for REDIMP
&SbiRuntime::StepINITFOREACH,// Init for each loop
&SbiRuntime::StepVBASET,// vba-like set statement
&SbiRuntime::StepERASE_CLEAR,// vba-like set statement
&SbiRuntime::StepARRAYACCESS,// access TOS as array
&SbiRuntime::StepBYVAL, // access TOS as array
};
const SbiRuntime::pStep1 SbiRuntime::aStep1[] = { // all opcodes with one operand
&SbiRuntime::StepLOADNC, // loading a numeric constant (+ID)
&SbiRuntime::StepLOADSC, // loading a string constant (+ID)
&SbiRuntime::StepLOADI, // Immediate Load (+value)
&SbiRuntime::StepARGN, // save a named Args in Argv (+StringID)
&SbiRuntime::StepPAD, // bring string to a definite length (+length) // branches
&SbiRuntime::StepJUMP, // jump (+Target)
&SbiRuntime::StepJUMPT, // evaluate TOS, conditional jump (+Target)
&SbiRuntime::StepJUMPF, // evaluate TOS, conditional jump (+Target)
&SbiRuntime::StepONJUMP, // evaluate TOS, jump into JUMP-table (+MaxVal)
&SbiRuntime::StepGOSUB, // UP-call (+Target)
&SbiRuntime::StepRETURN, // UP-return (+0 or Target)
&SbiRuntime::StepTESTFOR, // check FOR-variable, increment (+Endlabel)
&SbiRuntime::StepCASETO, // Tos+1 <= Case <= Tos), 2xremove (+Target)
&SbiRuntime::StepERRHDL, // error handler (+Offset)
&SbiRuntime::StepRESUME, // resume after errors (+0 or 1 or Label) // E/A
&SbiRuntime::StepCLOSE, // (+channel/0)
&SbiRuntime::StepPRCHAR, // (+char) // management
&SbiRuntime::StepSETCLASS, // check set + class names (+StringId)
&SbiRuntime::StepTESTCLASS, // Check TOS class (+StringId)
&SbiRuntime::StepLIB, // lib for declare-call (+StringId)
&SbiRuntime::StepBASED, // TOS is incremented by BASE, BASE is pushed before
&SbiRuntime::StepARGTYP, // convert last parameter in Argv (+Type)
&SbiRuntime::StepVBASETCLASS,// vba-like set statement
};
const SbiRuntime::pStep2 SbiRuntime::aStep2[] = {// all opcodes with two operands
&SbiRuntime::StepRTL, // load from RTL (+StringID+Typ)
&SbiRuntime::StepFIND, // load (+StringID+Typ)
&SbiRuntime::StepELEM, // load element (+StringID+Typ)
&SbiRuntime::StepPARAM, // Parameter (+Offset+Typ) // branches
&SbiRuntime::StepCALL, // Declare-Call (+StringID+Typ)
&SbiRuntime::StepCALLC, // CDecl-Declare-Call (+StringID+Typ)
&SbiRuntime::StepCASEIS, // Case-Test (+Test-Opcode+False-Target) // management
&SbiRuntime::StepSTMNT, // beginning of a statement (+Line+Col) // E/A
&SbiRuntime::StepOPEN, // (+StreamMode+Flags) // Objects
&SbiRuntime::StepLOCAL, // define local variable (+StringId+Typ)
&SbiRuntime::StepPUBLIC, // module global variable (+StringID+Typ)
&SbiRuntime::StepGLOBAL, // define global variable (+StringID+Typ)
&SbiRuntime::StepCREATE, // create object (+StringId+StringId)
&SbiRuntime::StepSTATIC, // static variable (+StringId+StringId)
&SbiRuntime::StepTCREATE, // user-defined objects (+StringId+StringId)
&SbiRuntime::StepDCREATE, // create object-array (+StringID+StringID)
&SbiRuntime::StepGLOBAL_P, // define global variable which is not overwritten // by the Basic on a restart (+StringID+Typ)
&SbiRuntime::StepFIND_G, // finds global variable with special treatment because of _GLOBAL_P
&SbiRuntime::StepDCREATE_REDIMP, // redimension object array (+StringID+StringID)
&SbiRuntime::StepFIND_CM, // Search inside a class module (CM) to enable global search in time
&SbiRuntime::StepPUBLIC_P, // Search inside a class module (CM) to enable global search in time
&SbiRuntime::StepFIND_STATIC, // Search inside a class module (CM) to enable global search in time
};
// 16.10.96: #31460 new concept for StepInto/Over/Out // The decision whether StepPoint shall be called is done with the help of // the CallLevel. It's stopped when the current CallLevel is <= nBreakCallLvl. // The current CallLevel can never be smaller than 1, as it's also incremented // during the call of a method (also main). Therefore a BreakCallLvl from 0 // means that the program isn't stopped at all. // (also have a look at: step2.cxx, SbiRuntime::StepSTMNT() )
// Several parser methods pass SvNumberFormatter::IsNumberFormat() a number // format index to parse against. Tell the formatter the proper date // evaluation order, which also determines the date acceptance patterns to // use if a format was passed. NF_EVALDATEFORMAT_FORMAT restricts to the // format's locale's date patterns/order (no init/system locale match // tried) and falls back to NF_EVALDATEFORMAT_INTL if no specific (i.e. 0) // (or an unknown) format index was passed.
pNumberFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT);
// the formatter's standard templates have only got a two-digit date // -> registering an own format
// HACK, because the numberformatter doesn't swap the place holders // for month, day and year according to the system setting. // Problem: Print Year(Date) under engl. BS // also have a look at: basic/source/sbx/sbxdate.cxx
// tdf#79426, tdf#125180 - adds the information about a missing parameter void SbiRuntime::SetIsMissing( SbxVariable* pVar )
{
SbxInfo* pInfo = pVar->GetInfo() ? pVar->GetInfo() : new SbxInfo();
pInfo->AddParam( pVar->GetName(), SbxMISSING, pVar->GetFlags() );
pVar->SetInfo( pInfo );
}
// tdf#79426, tdf#125180 - checks if a variable contains the information about a missing parameter bool SbiRuntime::IsMissing( SbxVariable* pVar, sal_uInt16 nIdx )
{ return pVar->GetInfo() && pVar->GetInfo()->GetParam( nIdx ) && pVar->GetInfo()->GetParam( nIdx )->eType & SbxMISSING;
}
// Construction of the parameter list. All ByRef-parameters are directly // taken over; copies of ByVal-parameters are created. If a particular // data type is requested, it is converted.
void SbiRuntime::SetParameters( SbxArray* pParams )
{
refParams = new SbxArray; // for the return value
refParams->Put(pMeth, 0);
// from 13.2.1997, new error handling: // ATTENTION: nError can be set already even if !nErrCode // since nError can now also be set from other RT-instances
if( nError )
{
SbxBase::ResetError();
}
// from 15.3.96: display errors only if BASIC is still active // (especially not after compiler errors at the runtime) if( nError && bRun )
{
ErrCode err = nError;
ClearExprStack();
nError = ERRCODE_NONE;
pInst->nErr = err;
pInst->nErl = nLine;
pErrCode = pCode;
pErrStmnt = pStmnt; // An error occurred in an error handler // force parent handler ( if there is one ) // to handle the error bool bLetParentHandleThis = false;
// in the error handler? so std-error if ( !bInError )
{
bInError = true;
if( !bError ) // On Error Resume Next
{
StepRESUME( 1 );
} elseif( pError ) // On Error Goto ...
{
pCode = pError;
} else
{
bLetParentHandleThis = true;
}
} else
{
bLetParentHandleThis = true;
pError = nullptr; //terminate the handler
} if ( bLetParentHandleThis )
{ // from 13.2.1997, new error handling: // consider superior error handlers
// Not correct for class module usage, remove for now //OSL_WARN_IF( pInst->pRun != this, "basic", "SbiRuntime::Error: can't propagate the error message details!" ); if ( pInst->pRun == this )
{
pInst->Error( _errCode, _details ); //OSL_WARN_IF( nError != _errCode, "basic", "SbiRuntime::Error: the instance is expected to propagate the error code back to me!" );
} else
{
nError = _errCode;
}
}
void SbiRuntime::FatalError( ErrCode n )
{
StepSTDERROR();
Error( n );
}
sal_Int32 SbiRuntime::translateErrorToVba( ErrCode nError, OUString& rMsg )
{ // If a message is defined use that ( in preference to // the defined one for the error ) NB #TODO // if there is an error defined it more than likely // is not the one you want ( some are the same though ) // we really need a new vba compatible error list // tdf#123144 - always translate an error number to a vba error message
StarBASIC::MakeErrorText( nError, rMsg );
rMsg = StarBASIC::GetErrorText(); // no num? most likely then it *is* really a vba err
sal_uInt16 nVBErrorCode = StarBASIC::GetVBErrorCode( nError );
sal_Int32 nVBAErrorNumber = ( nVBErrorCode == 0 ) ? sal_uInt32(nError) : nVBErrorCode; return nVBAErrorNumber;
}
// Stacks
// The expression-stack is available for the continuous evaluation // of expressions.
// Push of the for-stack. The stack has increment, end, begin and variable. // After the creation of the stack-element the stack's empty.
void SbiRuntime::PushFor()
{
SbiForStack* p = new SbiForStack;
p->eForType = ForType::To;
p->pNext = pForStk;
pForStk = p;
p->refInc = PopVar();
p->refEnd = PopVar(); if (isVBAEnabled())
{ // tdf#150458: only calculate these once, coercing to double // tdf#150460: shouldn't we do it in non-VBA / compat mode, too?
SbxVariableRef incCopy(new SbxVariable(SbxDOUBLE));
*incCopy = *p->refInc;
p->refInc = std::move(incCopy);
SbxVariableRef endCopy(new SbxVariable(SbxDOUBLE));
*endCopy = *p->refEnd;
p->refEnd = std::move(endCopy);
}
SbxVariableRef xBgn = PopVar();
p->refVar = PopVar(); // tdf#85371 - grant explicitly write access to the index variable // since it could be the name of a method itself used in the next statement.
ScopedWritableGuard aGuard(p->refVar, p->refVar.get() == pMeth);
*(p->refVar) = *xBgn;
nForLvl++;
}
void SbiRuntime::PushForEach()
{
SbiForStack* p = new SbiForStack; // Set default value in case of error which is ignored in Resume Next
p->eForType = ForType::EachArray;
p->pNext = pForStk;
pForStk = p;
SbxVariableRef xObjVar = PopVar();
SbxBase* pObj(nullptr); if (xObjVar)
{
SbxValues v(SbxVARIANT); // Here it may retrieve the value, and change the type from SbxEMPTY to SbxOBJECT
xObjVar->Get(v); if (v.eType == SbxOBJECT)
pObj = v.pObj;
}
if (SbxDimArray* pArray = dynamic_cast<SbxDimArray*>(pObj))
{
p->refEnd = reinterpret_cast<SbxVariable*>(pArray);
// tdf#144353 - do not compare a missing optional variable if ((p1->GetType() == SbxERROR && SbiRuntime::IsMissing(p1.get(), 1))
|| (p2->GetType() == SbxERROR && SbiRuntime::IsMissing(p2.get(), 1)))
{
SbxBase::SetError(ERRCODE_BASIC_NOT_OPTIONAL); return;
}
// Make sure objects with default params have // values ( and type ) set as appropriate
SbxDataType p1Type = p1->GetType();
SbxDataType p2Type = p2->GetType(); if ( p1Type == SbxEMPTY )
{
p1->Broadcast( SfxHintId::BasicDataWanted );
p1Type = p1->GetType();
} if ( p2Type == SbxEMPTY )
{
p2->Broadcast( SfxHintId::BasicDataWanted );
p2Type = p2->GetType();
} if ( p1Type == p2Type )
{ // if both sides are an object and have default props // then we need to use the default props // we don't need to worry if only one side ( lhs, rhs ) is an // object ( object side will get coerced to correct type in // Compare ) if ( p1Type == SbxOBJECT )
{
SbxVariable* pDflt = getDefaultProp( p1.get() ); if ( pDflt )
{
p1 = pDflt;
p1->Broadcast( SfxHintId::BasicDataWanted );
}
pDflt = getDefaultProp( p2.get() ); if ( pDflt )
{
p2 = pDflt;
p2->Broadcast( SfxHintId::BasicDataWanted );
}
}
} static SbxVariable* pTRUE = nullptr; static SbxVariable* pFALSE = nullptr; // why do this on non-windows ? // why do this at all ? // I dumbly follow the pattern :-/ if ( bVBAEnabled && ( p1->IsNull() || p2->IsNull() ) )
{ static SbxVariable* pNULL = []() {
SbxVariable* p = new SbxVariable;
p->PutNull();
p->AddFirstRef(); return p;
}();
PushVar( pNULL );
} elseif( p2->Compare( eOp, *p1 ) )
{ if( !pTRUE )
{
pTRUE = new SbxVariable;
pTRUE->PutBool( true );
pTRUE->AddFirstRef();
}
PushVar( pTRUE );
} else
{ if( !pFALSE )
{
pFALSE = new SbxVariable;
pFALSE->PutBool( false );
pFALSE->AddFirstRef();
}
PushVar( pFALSE );
}
}
// tdf#144353 - do not assign a missing optional variable to a property if (refVal->GetType() == SbxERROR && SbiRuntime::IsMissing(refVal.get(), 1))
{
SbxBase::SetError(ERRCODE_BASIC_NOT_OPTIONAL); returntrue;
}
if ( eValType != SbxOBJECT ) returnfalse; // we seem to be duplicating parts of SbxValue=operator, maybe we should just move this to // there :-/ not sure if for every '=' we would want struct handling if( eVarType != SbxOBJECT )
{ if ( refVar->IsFixed() ) returnfalse;
} // #115826: Exclude ProcedureProperties to avoid call to Property Get procedure elseif( dynamic_cast<const SbProcedureProperty*>( refVar.get() ) != nullptr ) returnfalse;
SbUnoObject* pUnoVal = dynamic_cast<SbUnoObject*>( xValObj.get() );
SbUnoStructRefObject* pUnoStructVal = dynamic_cast<SbUnoStructRefObject*>( xValObj.get() );
Any aAny; // make doubly sure value is either a Uno object or // a uno struct if ( pUnoVal || pUnoStructVal )
aAny = pUnoVal ? pUnoVal->getUnoAny() : pUnoStructVal->getUnoAny(); else returnfalse; if ( aAny.getValueTypeClass() != TypeClass_STRUCT ) returnfalse;
refVar->SetType( SbxOBJECT );
ErrCode eOldErr = SbxBase::GetError(); // There are some circumstances when calling GetObject // will trigger an error, we need to squash those here. // Alternatively it is possible that the same scenario // could overwrite and existing error. Let's prevent that
SbxObjectRef xVarObj = static_cast<SbxObject*>(refVar->GetObject()); if ( eOldErr != ERRCODE_NONE )
SbxBase::SetError( eOldErr ); else
SbxBase::ResetError();
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 );
}
// if left side arg is an object or variant and right handside isn't // either an object or a variant then try and see if a default // property exists. // to use e.g. Range{"A1") = 34 // could equate to Range("A1").Value = 34 if ( bVBAEnabled )
{ // yet more hacking at this, I feel we don't quite have the correct // heuristics for dealing with obj1 = obj2 ( where obj2 ( and maybe // obj1 ) has default member/property ) ) It seems that default props // aren't dealt with if the object is a member of some parent object bool bObjAssign = false; if ( refVar->GetType() == SbxEMPTY )
refVar->Broadcast( SfxHintId::BasicDataWanted ); if ( refVar->GetType() == SbxOBJECT )
{ if ( dynamic_cast<const SbxMethod *>(refVar.get()) != nullptr || ! refVar->GetParent() )
{
SbxVariable* pDflt = getDefaultProp( refVar.get() );
// VBA Dim As New behavior handling, save init object information struct DimAsNewRecoverItem
{
OUString m_aObjClass;
OUString m_aObjName;
SbxObject* m_pObjParent;
SbModule* m_pClassModule;
void SbiRuntime::StepSET_Impl( SbxVariableRef& refVal, SbxVariableRef& refVar, bool bHandleDefaultProp )
{ // #67733 types with array-flag are OK too
// Check var, !object is no error for sure if, only if type is fixed
SbxDataType eVarType = refVar->GetType(); if( !bHandleDefaultProp && eVarType != SbxOBJECT && !(eVarType & SbxARRAY) && refVar->IsFixed() )
{
Error( ERRCODE_BASIC_INVALID_USAGE_OBJECT ); return;
}
// Check value, !object is no error for sure if, only if type is fixed
SbxDataType eValType = refVal->GetType(); if( !bHandleDefaultProp && eValType != SbxOBJECT && !(eValType & SbxARRAY) && refVal->IsFixed() )
{
Error( ERRCODE_BASIC_INVALID_USAGE_OBJECT ); return;
}
// Getting in here causes problems with objects with default properties // if they are SbxEMPTY I guess if ( !bHandleDefaultProp || eValType == SbxOBJECT )
{ // activate GetObject for collections on refVal
SbxBase* pObjVarObj = refVal->GetObject(); if( pObjVarObj )
{
SbxVariableRef refObjVal = dynamic_cast<SbxObject*>( pObjVarObj );
// #52896 refVal can be invalid here, if uno-sequences - or more // general arrays - are assigned to variables that are declared // as an object! if( !refVal.is() )
{
Error( ERRCODE_BASIC_INVALID_USAGE_OBJECT );
} else
{ bool bFlagsChanged = false;
SbxFlagBits n = SbxFlagBits::NONE; if( refVar.get() == pMeth )
{
bFlagsChanged = true;
n = refVar->GetFlags();
refVar->SetFlag( SbxFlagBits::Write );
}
SbProcedureProperty* pProcProperty = dynamic_cast<SbProcedureProperty*>( refVar.get() ); if( pProcProperty )
{
pProcProperty->setSet( true );
} if ( bHandleDefaultProp )
{ // get default properties for lhs & rhs where necessary // SbxVariable* defaultProp = NULL; unused variable // LHS try determine if a default prop exists // again like in StepPUT (see there too ) we are tweaking the // heuristics again for when to assign an object reference or // use default members if they exist // #FIXME we really need to get to the bottom of this mess bool bObjAssign = false; if ( refVar->GetType() == SbxOBJECT )
{ if ( dynamic_cast<const SbxMethod *>(refVar.get()) != nullptr || ! refVar->GetParent() )
{
SbxVariable* pDflt = getDefaultProp( refVar.get() ); if ( pDflt )
{
refVar = pDflt;
}
} else
bObjAssign = true;
} // RHS only get a default prop is the rhs has one if ( refVal->GetType() == SbxOBJECT )
{ // check if lhs is a null object // if it is then use the object not the default property
SbxObject* pObj = dynamic_cast<SbxObject*>( refVar.get() );
// calling GetObject on a SbxEMPTY variable raises // object not set errors, make sure it's an Object if ( !pObj && refVar->GetType() == SbxOBJECT )
{
SbxBase* pObjVarObj = refVar->GetObject();
pObj = dynamic_cast<SbxObject*>( pObjVarObj );
}
SbxVariable* pDflt = nullptr; if ( pObj && !bObjAssign )
{ // lhs is either a valid object || or has a defaultProp
pDflt = getDefaultProp( refVal.get() );
} if ( pDflt )
{
refVal = pDflt;
}
}
}
// lhs is a property who's value is currently (Empty e.g. no broadcast yet) // in this case if there is a default prop involved the value of the // default property may in fact be void so the type will also be SbxEMPTY // in this case we do not want to call checkUnoStructCopy 'cause that will // cause an error also if ( !checkUnoStructCopy( bHandleDefaultProp, refVal, refVar ) )
{
*refVar = *refVal;
} if ( bDimAsNew )
{ if( dynamic_cast<const SbxObject*>( refVar.get() ) == nullptr )
{
SbxBase* pValObjBase = refVal->GetObject(); if( pValObjBase == nullptr )
{ if( xPrevVarObj.is() )
{ // Object is overwritten with NULL, instantiate init object
DimAsNewRecoverHash::iterator it = gaDimAsNewRecoverHash.find( refVar.get() ); if( it != gaDimAsNewRecoverHash.end() )
{ const DimAsNewRecoverItem& rItem = it->second; if( rItem.m_pClassModule != nullptr )
{
SbClassModuleObject* pNewObj = new SbClassModuleObject(*rItem.m_pClassModule);
pNewObj->SetName( rItem.m_aObjName );
pNewObj->SetParent( rItem.m_pObjParent );
refVar->PutObject( pNewObj );
} elseif( rItem.m_aObjClass.equalsIgnoreAsciiCase( pCollectionStr ) )
{
BasicCollection* pNewCollection = new BasicCollection( pCollectionStr );
pNewCollection->SetName( rItem.m_aObjName );
pNewCollection->SetParent( rItem.m_pObjParent );
refVar->PutObject( pNewCollection );
}
}
}
} else
{ // Does old value exist? bool bFirstInit = !xPrevVarObj.is(); if( bFirstInit )
{ // Store information to instantiate object later
SbxObject* pValObj = dynamic_cast<SbxObject*>( pValObjBase ); if( pValObj != nullptr )
{
OUString aObjClass = pValObj->GetClassName();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.