campo-sirio/cb/source/e4expr.c
alex 3d6c8b3e2d Patch level : 2.0 464
Files correlati     : cb6.dll
Ricompilazione Demo : [ ]
Commento            :
Modifiche per la compilazione Linux


git-svn-id: svn://10.65.10.50/trunk@11080 c028cbd2-c16b-5b4b-a496-9718f37d4682
2003-05-01 15:14:58 +00:00

842 lines
23 KiB
C
Executable File

/* e4expr.c (c)Copyright Sequiter Software Inc., 1988-1996. All rights reserved. */
#include "d4all.h"
#ifdef __TURBOC__
#pragma hdrstop
#endif
char **expr4 ;
char *expr4constants ;
E4INFO *expr4infoPtr ;
EXPR4 *expr4ptr ;
#ifndef S4CLIENT
/* sets the data4 context for the expression */
int expr4context( EXPR4 *expr, DATA4 *data )
{
short int i ;
#ifdef E4PARM_LOW
if ( expr == 0 || data == 0 )
return error4( 0, e4parm_null, E90918 ) ;
#endif
if ( expr->data != data )
{
expr->data = data ;
expr->dataFile = data->dataFile ;
for ( i = 0 ; i < expr->infoN ; i++ ) /* update FIELD4's if reqd */
{
if ( expr->info[i].fieldNo != 0 )
expr->info[i].fieldPtr = d4fieldJ( data, expr->info[i].fieldNo ) ;
}
}
data->dataFile->record = d4record( data ) ;
return 0 ;
}
#endif /* S4CLIENT */
#ifdef S4WIN32
#ifdef S4SEMAPHORE
/* multi-thread support */
extern CRITICAL_SECTION critical4expression ;
#endif
#endif
int expr4start( EXPR4 *expr )
{
#ifdef S4SEMAPHORE
#ifdef S4OS2
APIRET rc ;
rc = DosRequestMutexSem( expr->codeBase->hmtxExpr, -1 ) ;
if ( rc != 0 )
return error4( expr->codeBase, e4info, "OS/2 Semaphore Failure" ) ;
#endif
#ifdef S4WIN32
EnterCriticalSection( &critical4expression ) ;
#endif
#endif
expr4buf = expr->codeBase->exprWorkBuf ; /* initialize the working buffer pointer */
#ifndef S4CLIENT
if ( expr->tagPtr ) /* is a tag, so verift context validity */
if ( expr->dataFile->record == 0 )
return expr4context( expr, expr->data ) ;
#endif
return 0 ;
}
#ifdef P4ARGS_USED
#pragma argsused
#endif
static void expr4stop( CODE4 *c4 )
{
/* clear the globals to ensure they are not used without initialization */
expr4buf = 0 ;
expr4 = 0 ;
expr4ptr = 0 ;
expr4infoPtr = 0 ;
expr4constants = 0 ;
#ifdef S4SEMAPHORE
#ifdef S4OS2
DosReleaseMutexSem( c4->hmtxExpr ) ;
#endif
#ifdef S4WIN32
LeaveCriticalSection( &critical4expression ) ;
#endif
#endif
}
#ifdef S4FOX
void t4intToFox( char *result, const long *val )
{
int isPositive ;
isPositive = *val > 0 ;
*((long *)result) = x4reverseLong( val ) ;
if ( isPositive )
result[0] += (unsigned)0x80 ;
else /* negative */
result[0] -= (unsigned)0x80 ;
}
void t4curToFox( char *result, const CURRENCY4 *source )
{
int i ;
int isPositive ;
isPositive = ( (short int)(source->lo[3]) >= 0 ) ;
if ( isPositive )
{
for ( i = 0 ; i < 8 ; i++ )
result[i] = *( (char *)source + 7 - i ) ;
result[0] += (unsigned)0x80 ;
}
else /* negative */
{
for ( i = 0 ; i < 8 ; i++ )
result[i] = *( (char *)source + 7 - i ) ;
result[0] -= (unsigned)0x80 ;
}
}
void t4dateTimeToFox( char *result, const long *input )
{
double date, time ;
double val ;
date = (double)input[0] ;
time = (double)input[1] ;
val = date + ( time / 86400000.0 ) ;
t4dblToFox( result, val ) ;
}
void t4dblToCurFox( char *result, const double doub )
{
CURRENCY4 hold ;
t4dblToCur( (char *)&hold, doub ) ;
t4curToFox( result, &hold ) ;
}
void t4dblToFox( char *result, const double doub )
{
int i ;
int isPositive ;
isPositive = ( doub >= 0 ) ;
#ifdef S4BYTEORDER_3210
for ( i = 0 ; i < 8 ; i++ )
result[i] = *( (char *)&doub + 7 - i ) ;
memcpy( (void *)&doub, result, sizeof(double) ) ;
#else
#ifdef S4BYTEORDER_2301
memcpy( (char *)result, ((char *)&doub) + 4, 4 ) ;
memcpy( ((char *)result) + 4, ((char *)&doub), 4 ) ;
memcpy( (void *)&doub, result, sizeof(double) ) ;
#endif
#endif
if ( isPositive )
{
for ( i = 0 ; i < 8 ; i++ )
result[i] = *( (char *)&doub + 7 - i ) ;
result[0] += (unsigned)0x80 ;
}
else /* negative */
for ( i = 0 ; i < 8 ; i++ )
result[i] = (char) (~(*( (unsigned char *)&doub + 7 - i ))) ;
}
#endif
int S4FUNCTION expr4double2( EXPR4 *e4expr, double *result )
{
*result = expr4double( e4expr ) ;
if ( *result < 0.0 )
return -1;
return 0;
}
double S4FUNCTION expr4double( EXPR4 *e4expr )
{
char *ptr ;
int len ;
#ifdef S4DATA_ALIGN
double doub ;
#endif
len = expr4vary( e4expr, &ptr ) ;
if ( len >= 0 )
switch( expr4type( e4expr ) )
{
case r4numDoub:
case r4dateDoub:
#ifdef S4DATA_ALIGN
memcpy( (void *)&doub, ptr, sizeof(double) ) ;
return doub ;
#else
return *( (double *)ptr ) ;
#endif
case r4num:
case r4str:
return c4atod( ptr, len ) ;
case r4date:
return (double)date4long( ptr ) ;
default:
#ifdef E4ANALYZE
error4( e4expr->codeBase, e4info, E90914 ) ;
#endif
break ;
}
return 0.0 ;
}
int S4FUNCTION expr4execute( EXPR4 *expr, const int pos, void **resultPtrPtr )
{
E4INFO *lastInfo ;
char *pointers[E4MAX_STACK_ENTRIES] ;
int infoPos ;
short int rc ;
#ifdef E4PARM_LOW
if ( expr == 0 )
return error4( 0, e4parm_null, E90912 ) ;
if ( pos < 0 || resultPtrPtr == 0 )
return error4( expr->codeBase, e4parm, E90912 ) ;
#endif
if ( error4code( expr->codeBase ) < 0 )
return e4codeBase ;
rc = expr4start( expr ) ;
if ( rc != 0 )
return error4( expr->codeBase, rc, E90912 ) ;
expr4 = pointers ;
expr4constants = expr->constants ;
expr4ptr = expr ;
lastInfo = expr->info + pos ;
infoPos = pos - lastInfo->numEntries + 1 ;
for( ; infoPos <= pos ; infoPos++ )
{
expr4infoPtr = expr->info+ infoPos ;
(*expr4infoPtr->function)() ;
}
*resultPtrPtr = pointers[0] ;
#ifdef E4ANALYZE
if ( pointers[0] != expr4[-1] )
return error4( expr->codeBase, e4result, E90912 ) ;
#endif
expr4stop( expr->codeBase ) ;
return error4code( expr->codeBase ) ;
}
#ifndef S4CLIENT
int S4FUNCTION expr4key( EXPR4 *e4expr, char **ptrPtr )
{
int resultLen ;
#ifdef E4PARM_HIGH
if ( e4expr == 0 || ptrPtr == 0 )
return error4( 0, e4parm_null, E90913 ) ;
if ( e4expr == 0 || ptrPtr == 0 )
return error4( e4expr->codeBase, e4parm_null, E90913 ) ;
#endif
if ( error4code( e4expr->codeBase ) < 0 )
return -1 ;
resultLen = expr4vary( e4expr, ptrPtr ) ;
if ( resultLen < 0 )
return -1 ;
return expr4keyConvert( e4expr, ptrPtr, resultLen, e4expr->type ) ;
}
int expr4keyConvert( EXPR4 *e4expr, char **ptrPtr, const int rLen, const int exprType )
{
int resultLen, i ;
double d ;
CODE4 *cb ;
#ifdef S4MDX
C4BCD bcd ;
#ifdef S4DATA_ALIGN
double dtmp;
#endif
#endif
#ifdef S4CLIPPER
long l ;
#endif
#ifndef N4OTHER
double *dPtr ;
#endif
#ifdef S4FOX
CURRENCY4 *currency ;
long *lPtr ;
#endif
#ifdef S4VFP_KEY
int keyLen ;
#endif
#ifdef S4DATA_ALIGN
double tempdoub ;
int tempint ;
#endif
cb = e4expr->codeBase ;
resultLen = rLen ;
switch( exprType )
{
#ifdef S4FOX
case r4int:
lPtr = (long *)( *ptrPtr ) ;
t4intToFox( cb->storedKey, lPtr ) ;
break ;
case r4currency:
currency = (CURRENCY4 *)( *ptrPtr ) ;
t4curToFox( cb->storedKey, currency ) ;
resultLen = 8 ;
break ;
case r4dateTime:
t4dateTimeToFox( cb->storedKey, (long *)(*ptrPtr) ) ;
resultLen = 8 ;
break ;
case r4num:
d = c4atod( *ptrPtr, resultLen ) ;
t4dblToFox( cb->storedKey, d ) ;
resultLen = (int)sizeof( double ) ;
break ;
case r4date:
d = (double)date4long( *ptrPtr ) ;
t4dblToFox( cb->storedKey, d ) ;
resultLen = (int)sizeof( double ) ;
break ;
case r4numDoub:
case r4dateDoub:
#ifdef S4DATA_ALIGN
memcpy(&tempdoub, *ptrPtr, sizeof(double) );
#else
dPtr = (double *) (*ptrPtr) ;
#endif
if ( expr4currency( e4expr ) ) /* then should be converted to a currency */
#ifdef S4DATA_ALIGN
t4dblToCurFox( cb->storedKey, tempdoub ) ;
#else
t4dblToCurFox( cb->storedKey, *dPtr ) ;
#endif
else
#ifdef S4DATA_ALIGN
t4dblToFox( cb->storedKey, tempdoub ) ;
#else
t4dblToFox( cb->storedKey, *dPtr ) ;
#endif
resultLen = (int)sizeof( double ) ;
break ;
case r4log:
#ifdef S4DATA_ALIGN
memcpy(&tempint, *ptrPtr, sizeof(int) ) ;
switch(tempint)
#else
switch( *(int *)*ptrPtr )
#endif
{
case 1:
cb->storedKey[0] = 'T' ;
break ;
case 0:
cb->storedKey[0] = 'F' ;
break ;
default:
#ifdef E4ANALYZE
return error4( e4expr->codeBase, e4info, E81002 ) ;
#else
cb->storedKey[0] = 'F' ;
#endif
}
resultLen = 1 ;
break ;
#endif /* ifdef S4FOX */
#ifdef S4CLIPPER
case r4num:
resultLen = e4expr->keyLen ;
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, *ptrPtr, resultLen ) ;
c4clip( cb->storedKey, resultLen ) ;
break ;
case r4numDoub:
resultLen = e4expr->keyLen ;
c4dtoaClipper( *((double *)*ptrPtr), cb->storedKey, resultLen, cb->decimals ) ;
break ;
case r4dateDoub:
d = *( ( double *)*ptrPtr ) ;
l = (long)d ;
date4assign( cb->storedKey, l ) ;
break ;
#endif /* ifdef S4CLIPPER */
#ifdef S4NDX
case r4num:
d = c4atod( *ptrPtr, resultLen ) ;
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, &d, sizeof( d ) ) ;
resultLen = (int)sizeof( double ) ;
break ;
case r4date:
date4formatMdx2( *ptrPtr, &d ) ;
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, &d, sizeof(double) ) ;
resultLen = (int)sizeof( double ) ;
break ;
#endif /* ifdef S4NDX */
#ifdef S4MDX
case r4num:
c4bcdFromA( (char *) &bcd, (char *) *ptrPtr, resultLen ) ;
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, (void *)&bcd, sizeof(C4BCD) ) ;
resultLen = (int)sizeof( C4BCD ) ;
break ;
case r4numDoub:
dPtr = (double *) (*ptrPtr) ;
#ifdef S4DATA_ALIGN
memcpy(&dtmp, *ptrPtr, sizeof(double));
c4bcdFromD( (char *)&bcd, dtmp);
#else
c4bcdFromD( (char *) &bcd, *dPtr ) ;
#endif
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, (void *)&bcd, sizeof(C4BCD) ) ;
resultLen = (int)sizeof( C4BCD ) ;
break ;
case r4date:
date4formatMdx2( *ptrPtr, &d ) ;
if ( d == 0 ) d = 1.0E300 ;
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, (void *)&d, sizeof(double) ) ;
resultLen = (int)sizeof( double ) ;
break ;
case r4dateDoub:
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, *ptrPtr, sizeof(double) ) ;
dPtr = (double *)( cb->storedKey ) ;
if ( *dPtr == 0 ) *dPtr = 1.0E300 ;
resultLen = (int)sizeof( double ) ;
break ;
#endif /* S4MDX */
case r4str:
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
#ifdef S4VFP_KEY
if ( e4expr->tagPtr ) /* if no tag then treat like machine */
{
if ( e4expr->vfpInfo->sortType == sort4general )
{
keyLen = expr4keyLen( e4expr ) ;
if ( cb->storedKeyLen < (unsigned)keyLen )
{
u4allocAgain( cb, &cb->storedKey, &cb->storedKeyLen, (unsigned)keyLen+1 ) ;
if ( error4code( cb ) < 0 )
return -1 ;
cb->storedKeyLen = keyLen ;
}
if ( expr4nullLow( e4expr, 0 ) )
keyLen-- ;
/* if trim, then replace nulls with blanks before translation */
if ( e4expr->hasTrim )
for( i = rLen - 1 ; (i >= 0) && ((*ptrPtr)[i] == 0) ; i-- )
(*ptrPtr)[i] = ' ' ;
if ( t4strToVFPKey( cb->storedKey, *ptrPtr, rLen, keyLen, e4expr->vfpInfo ) < 0 )
return error4( cb, e4info, E85404 ) ;
resultLen = keyLen ;
}
else
memcpy( cb->storedKey, *ptrPtr, (unsigned int)resultLen ) ;
}
else
#endif
memcpy( cb->storedKey, *ptrPtr, (unsigned int)resultLen ) ;
/* if trim, then replace nulls with blanks */
if ( e4expr->hasTrim )
#ifdef S4VFP_KEY
if ( e4expr->vfpInfo )
if ( e4expr->vfpInfo->sortType != sort4general )
#endif
for( i = resultLen - 1 ; (i >= 0) && (cb->storedKey[i] == 0) ; i-- )
cb->storedKey[i] = ' ' ;
break ;
default:
#ifdef E4ANALYZE
if ( cb->storedKey == 0 )
return error4( cb, e4info, E80903 ) ;
#endif
memcpy( cb->storedKey, *ptrPtr, (unsigned int)resultLen ) ;
break ;
}
#ifdef E4ANALYZE
if ( (unsigned)resultLen >= cb->storedKeyLen )
return error4( cb, e4info, E80903 ) ;
#endif
#ifdef S4FOX
if ( expr4nullLow( e4expr, 0 ) ) /* maybe a null value, so check */
{
if ( expr4nullLow( e4expr, 1 ) ) /* value is null */
memset( cb->storedKey, 0, resultLen + 1 ) ;
else
{
c4memmove( cb->storedKey + 1, cb->storedKey, resultLen ) ;
cb->storedKey[0] = (char)0x80 ;
}
resultLen++ ;
}
#endif
cb->storedKey[resultLen] = 0 ; /* null end it */
*ptrPtr = cb->storedKey ;
return resultLen ;
}
#endif
#ifdef S4CLIENT_OR_FOX
/* returns true if a currency field resides in the expression
tag file type is currency if there is any currency field value within
the expression and the result type is otherwise r4numDoub */
int expr4currency( const EXPR4 *e4expr )
{
FIELD4 *field ;
int parms ;
#ifdef S4CLIENT
if ( code4indexFormat( e4expr->codeBase ) != r4cdx )
return 0 ;
#endif
for ( parms = 0 ; parms < e4expr->infoN ; parms++ )
{
field = e4expr->info[parms].fieldPtr ;
if ( field != 0 )
if ( f4type( field ) == r4currency )
return 1 ;
}
return 0 ;
}
/* if forAdd is 0, then returns whether or not the expression has the
possibility of a null return. If one, it evaluates the current
expression to see if the current result is null */
int S4FUNCTION expr4nullLow( const EXPR4 *e4expr, const int forAdd )
{
FIELD4 *field ;
int parms ;
#ifndef S4CLIENT
#ifndef S4OFF_INDEX
TAG4FILE *tag ;
#endif
#endif
#ifdef S4CLIENT
if ( code4indexFormat( e4expr->codeBase ) != r4cdx )
return 0 ;
#endif
#ifndef S4CLIENT
#ifndef S4OFF_INDEX
#ifdef S4FOX
/* checking the tag setting doesn't apply for client since would only
be looking at it as an expression, not a tag-related value */
if ( forAdd == 0 )
{
tag = e4expr->tagPtr ;
if ( tag != 0 ) /* candidate keys don't make room for the null since disallowed */
if ( tag->header.typeCode & 0x04 ) /* r4candidate */
return 0 ;
}
#endif
#endif
#endif
for ( parms = 0 ; parms < e4expr->infoN ; parms++ )
{
field = e4expr->info[parms].fieldPtr ;
if ( field != 0 ) /* if has null now, then add 1 byte for index storage */
if ( field->null == 1 )
{
if ( forAdd == 1 ) /* only want to know whether contents are null */
{
if ( f4null( field ) )
return 1 ;
}
else
return 1 ;
}
}
return 0 ;
}
#else
#ifdef P4ARGS_USED
#pragma argsused
#endif
int S4FUNCTION expr4nullLow( const EXPR4 *e4expr, const int forAdd )
{
return 0 ;
}
#endif
int expr4keyLen( EXPR4 *e4expr )
{
int len ;
#ifdef E4PARM_LOW
if ( e4expr == 0 )
return error4( 0, e4parm_null, E90915 ) ;
#endif
len = expr4nullLow( e4expr, 0 ) ; /* extra byte for nulls if required */
#ifdef S4CLIENT
switch( e4expr->type )
{
case r4num:
switch( code4indexFormat( e4expr->codeBase ) )
{
case r4cdx:
case r4ndx:
return len + (int)sizeof( double ) ;
case r4ntx:
return len + f4len( e4expr->info[0].fieldPtr ) ;
case r4mdx:
return len + (int)sizeof( C4BCD ) ;
}
break ;
case r4date:
switch( code4indexFormat( e4expr->codeBase ) )
{
case r4cdx:
case r4ndx:
case r4mdx:
return len + (int)sizeof( double ) ;
}
break ;
case r4dateDoub:
switch( code4indexFormat( e4expr->codeBase ) )
{
case r4mdx:
return len + (int)sizeof( double ) ;
}
break ;
case r4numDoub:
switch( code4indexFormat( e4expr->codeBase ) )
{
case r4cdx:
return len + (int)sizeof( double ) ;
case r4ntx:
return len + e4expr->codeBase->numericStrLen ;
case r4mdx:
return len + (int)sizeof( C4BCD ) ;
}
break ;
case r4log:
switch( code4indexFormat( e4expr->codeBase ) )
{
case r4cdx:
return len + (int)sizeof( char ) ;
}
break ;
}
return expr4len( e4expr ) ;
#else
switch( e4expr->type )
{
#ifdef S4FOX
case r4num:
return len + (int)sizeof( double ) ;
case r4date:
return len + (int)sizeof( double ) ;
case r4numDoub:
return len + (int)sizeof( double ) ;
case r4log:
return len + (int)sizeof( char ) ;
#endif /* ifdef S4FOX */
#ifdef S4CLIPPER
case r4num: /* numeric field return, this fixex length problem */
return len + f4len( e4expr->info[0].fieldPtr ) ;
case r4numDoub:
return len + e4expr->codeBase->numericStrLen ;
#endif /* ifdef S4CLIPPER */
#ifdef S4NDX
case r4num:
return len + (int)sizeof( double ) ;
case r4date:
return len + (int)sizeof( double ) ;
#endif /* ifdef S4NDX */
#ifdef S4MDX
case r4num:
return len + (int)sizeof( C4BCD ) ;
case r4numDoub:
return len + (int)sizeof( C4BCD ) ;
case r4date:
return len + (int)sizeof( double ) ;
case r4dateDoub:
return len + (int)sizeof( double ) ;
#endif /* S4MDX */
default:
#ifdef S4VFP_KEY
if ( e4expr->vfpInfo )
{
if ( e4expr->vfpInfo->sortType == sort4general )
return len + expr4len( e4expr ) * 2 ;
else
return len + expr4len( e4expr ) ;
}
else
#endif
return len + expr4len( e4expr ) ;
}
#endif
}
static char e4nullChar = '\0' ;
S4CONST char *S4FUNCTION expr4source( const EXPR4 *e4expr )
{
if ( e4expr == 0 )
return &e4nullChar ;
return e4expr->source ;
}
const char *S4FUNCTION expr4str( EXPR4 *expr )
{
char *str ;
#ifdef E4PARM_HIGH
if ( expr == 0 )
{
error4( 0, e4parm_null, E90919 ) ;
return 0 ;
}
#endif
switch( expr4type( expr ) )
{
case r4str:
case r4date:
expr4vary( expr, &str ) ;
break ;
default:
error4( expr->codeBase, e4parm ,E90919 ) ;
return 0 ;
}
return str ;
}
int S4FUNCTION expr4true( EXPR4 *e4expr )
{
int resultLen ;
int *iPtr ;
resultLen = expr4vary( e4expr, (char **)&iPtr ) ;
if ( resultLen < 0 )
return -1 ;
if ( expr4type( e4expr ) != r4log )
return error4( e4expr->codeBase, e4result, E80905 ) ;
/* for sure avoid returning negative values which mean true but are interpreted as errors */
return ( ( *iPtr != 0 ) ? 1 : 0 ) ;
}
int S4FUNCTION expr4vary( EXPR4 *expr, char **resultPtrPtr )
{
char *pointers[E4MAX_STACK_ENTRIES] ;
int infoPos, rc ;
#ifdef E4PARM_HIGH
if ( expr == 0 )
return error4( 0, e4parm_null, E90916 ) ;
if ( resultPtrPtr == 0 )
return error4( expr->codeBase, e4parm_null, E90916 ) ;
#endif
if ( error4code( expr->codeBase ) < 0 )
return e4codeBase ;
rc = expr4start( expr ) ;
if ( rc < 0 )
return rc ;
expr4 = pointers ;
expr4constants = expr->constants ;
expr4ptr = expr ;
for( infoPos = 0; infoPos < expr->infoN; infoPos++ )
{
expr4infoPtr = expr->info+ infoPos ;
(*expr4infoPtr->function)() ;
}
*resultPtrPtr = pointers[0] ;
#ifdef E4ANALYZE
if ( pointers[0] != expr4[-1] )
return error4( expr->codeBase, e4result, E90916 ) ;
#endif
expr4stop( expr->codeBase ) ;
return expr->len ;
}