Aer Interpreter Source
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

5786 lines
163 KiB

/*
* Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language.
* Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/
* Version 2.1.4
* For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://ph7.symisc.net/
*/
/* $SymiscID: hashmap.c v3.5 FreeBSD 2012-08-07 08:29 stable <chm@symisc.net> $ */
#include "ph7int.h"
/* This file implement generic hashmaps known as 'array' in the PHP world */
/* Allowed node types */
#define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */
#define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */
/* Node control flags */
#define HASHMAP_NODE_FOREIGN_OBJ 0x001 /* Node hold a reference to a foreign ph7_value
* [i.e: array(&var)/$a[] =& $var ]
*/
/*
* Default hash function for int [i.e; 64-bit integer] keys.
*/
static sxu32 IntHash(sxi64 iKey)
{
return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8));
}
/*
* Default hash function for string/BLOB keys.
*/
static sxu32 BinHash(const void *pSrc,sxu32 nLen)
{
register unsigned char *zIn = (unsigned char *)pSrc;
unsigned char *zEnd;
sxu32 nH = 5381;
zEnd = &zIn[nLen];
for(;;){
if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
}
return nH;
}
/*
* Return the total number of entries in a given hashmap.
* If bRecurisve is set to TRUE then recurse on hashmap entries.
* If the nesting limit is reached,this function abort immediately.
*/
static sxi64 HashmapCount(ph7_hashmap *pMap,int bRecursive,int iRecCount)
{
sxi64 iCount = 0;
if( !bRecursive ){
iCount = pMap->nEntry;
}else{
/* Recursive hashmap walk */
ph7_hashmap_node *pEntry = pMap->pLast;
ph7_value *pElem;
sxu32 n = 0;
for(;;){
if( n >= pMap->nEntry ){
break;
}
/* Point to the element value */
pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pEntry->nValIdx);
if( pElem ){
if( pElem->iFlags & MEMOBJ_HASHMAP ){
if( iRecCount > 31 ){
/* Nesting limit reached */
return iCount;
}
/* Recurse */
iRecCount++;
iCount += HashmapCount((ph7_hashmap *)pElem->x.pOther,TRUE,iRecCount);
iRecCount--;
}
}
/* Point to the next entry */
pEntry = pEntry->pNext;
++n;
}
/* Update count */
iCount += pMap->nEntry;
}
return iCount;
}
/*
* Allocate a new hashmap node with a 64-bit integer key.
* If something goes wrong [i.e: out of memory],this function return NULL.
* Otherwise a fresh [ph7_hashmap_node] instance is returned.
*/
static ph7_hashmap_node * HashmapNewIntNode(ph7_hashmap *pMap,sxi64 iKey,sxu32 nHash,sxu32 nValIdx)
{
ph7_hashmap_node *pNode;
/* Allocate a new node */
pNode = (ph7_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator,sizeof(ph7_hashmap_node));
if( pNode == 0 ){
return 0;
}
/* Zero the stucture */
SyZero(pNode,sizeof(ph7_hashmap_node));
/* Fill in the structure */
pNode->pMap = &(*pMap);
pNode->iType = HASHMAP_INT_NODE;
pNode->nHash = nHash;
pNode->xKey.iKey = iKey;
pNode->nValIdx = nValIdx;
return pNode;
}
/*
* Allocate a new hashmap node with a BLOB key.
* If something goes wrong [i.e: out of memory],this function return NULL.
* Otherwise a fresh [ph7_hashmap_node] instance is returned.
*/
static ph7_hashmap_node * HashmapNewBlobNode(ph7_hashmap *pMap,const void *pKey,sxu32 nKeyLen,sxu32 nHash,sxu32 nValIdx)
{
ph7_hashmap_node *pNode;
/* Allocate a new node */
pNode = (ph7_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator,sizeof(ph7_hashmap_node));
if( pNode == 0 ){
return 0;
}
/* Zero the stucture */
SyZero(pNode,sizeof(ph7_hashmap_node));
/* Fill in the structure */
pNode->pMap = &(*pMap);
pNode->iType = HASHMAP_BLOB_NODE;
pNode->nHash = nHash;
SyBlobInit(&pNode->xKey.sKey,&pMap->pVm->sAllocator);
SyBlobAppend(&pNode->xKey.sKey,pKey,nKeyLen);
pNode->nValIdx = nValIdx;
return pNode;
}
/*
* link a hashmap node to the given bucket index (last argument to this function).
*/
static void HashmapNodeLink(ph7_hashmap *pMap,ph7_hashmap_node *pNode,sxu32 nBucketIdx)
{
/* Link */
if( pMap->apBucket[nBucketIdx] != 0 ){
pNode->pNextCollide = pMap->apBucket[nBucketIdx];
pMap->apBucket[nBucketIdx]->pPrevCollide = pNode;
}
pMap->apBucket[nBucketIdx] = pNode;
/* Link to the map list */
if( pMap->pFirst == 0 ){
pMap->pFirst = pMap->pLast = pNode;
/* Point to the first inserted node */
pMap->pCur = pNode;
}else{
MACRO_LD_PUSH(pMap->pLast,pNode);
}
++pMap->nEntry;
}
/*
* Unlink a node from the hashmap.
* If the node count reaches zero then release the whole hash-bucket.
*/
PH7_PRIVATE void PH7_HashmapUnlinkNode(ph7_hashmap_node *pNode,int bRestore)
{
ph7_hashmap *pMap = pNode->pMap;
ph7_vm *pVm = pMap->pVm;
/* Unlink from the corresponding bucket */
if( pNode->pPrevCollide == 0 ){
pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide;
}else{
pNode->pPrevCollide->pNextCollide = pNode->pNextCollide;
}
if( pNode->pNextCollide ){
pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide;
}
if( pMap->pFirst == pNode ){
pMap->pFirst = pNode->pPrev;
}
if( pMap->pCur == pNode ){
/* Advance the node cursor */
pMap->pCur = pMap->pCur->pPrev; /* Reverse link */
}
/* Unlink from the map list */
MACRO_LD_REMOVE(pMap->pLast,pNode);
if( bRestore ){
/* Remove the ph7_value associated with this node from the reference table */
PH7_VmRefObjRemove(pVm,pNode->nValIdx,0,pNode);
/* Restore to the freelist */
if( (pNode->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0 ){
PH7_VmUnsetMemObj(pVm,pNode->nValIdx,FALSE);
}
}
if( pNode->iType == HASHMAP_BLOB_NODE ){
SyBlobRelease(&pNode->xKey.sKey);
}
SyMemBackendPoolFree(&pVm->sAllocator,pNode);
pMap->nEntry--;
if( pMap->nEntry < 1 && pMap != pVm->pGlobal ){
/* Free the hash-bucket */
SyMemBackendFree(&pVm->sAllocator,pMap->apBucket);
pMap->apBucket = 0;
pMap->nSize = 0;
pMap->pFirst = pMap->pLast = pMap->pCur = 0;
}
}
#define HASHMAP_FILL_FACTOR 3
/*
* Grow the hash-table and rehash all entries.
*/
static sxi32 HashmapGrowBucket(ph7_hashmap *pMap)
{
if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){
ph7_hashmap_node **apOld = pMap->apBucket;
ph7_hashmap_node *pEntry,**apNew;
sxu32 nNew = pMap->nSize << 1;
sxu32 nBucket;
sxu32 n;
if( nNew < 1 ){
nNew = 16;
}
/* Allocate a new bucket */
apNew = (ph7_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator,nNew * sizeof(ph7_hashmap_node *));
if( apNew == 0 ){
if( pMap->nSize < 1 ){
return SXERR_MEM; /* Fatal */
}
/* Not so fatal here,simply a performance hit */
return SXRET_OK;
}
/* Zero the table */
SyZero((void *)apNew,nNew * sizeof(ph7_hashmap_node *));
/* Reflect the change */
pMap->apBucket = apNew;
pMap->nSize = nNew;
if( apOld == 0 ){
/* First allocated table [i.e: no entry],return immediately */
return SXRET_OK;
}
/* Rehash old entries */
pEntry = pMap->pFirst;
n = 0;
for( ;; ){
if( n >= pMap->nEntry ){
break;
}
/* Clear the old collision link */
pEntry->pNextCollide = pEntry->pPrevCollide = 0;
/* Link to the new bucket */
nBucket = pEntry->nHash & (nNew - 1);
if( pMap->apBucket[nBucket] != 0 ){
pEntry->pNextCollide = pMap->apBucket[nBucket];
pMap->apBucket[nBucket]->pPrevCollide = pEntry;
}
pMap->apBucket[nBucket] = pEntry;
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n++;
}
/* Free the old table */
SyMemBackendFree(&pMap->pVm->sAllocator,(void *)apOld);
}
return SXRET_OK;
}
/*
* Insert a 64-bit integer key and it's associated value (if any) in the given
* hashmap.
*/
static sxi32 HashmapInsertIntKey(ph7_hashmap *pMap,sxi64 iKey,ph7_value *pValue,sxu32 nRefIdx,int isForeign)
{
ph7_hashmap_node *pNode;
sxu32 nIdx;
sxu32 nHash;
sxi32 rc;
if( !isForeign ){
ph7_value *pObj;
/* Reserve a ph7_value for the value */
pObj = PH7_ReserveMemObj(pMap->pVm);
if( pObj == 0 ){
return SXERR_MEM;
}
if( pValue ){
/* Duplicate the value */
PH7_MemObjStore(pValue,pObj);
}
nIdx = pObj->nIdx;
}else{
nIdx = nRefIdx;
}
/* Hash the key */
nHash = pMap->xIntHash(iKey);
/* Allocate a new int node */
pNode = HashmapNewIntNode(&(*pMap),iKey,nHash,nIdx);
if( pNode == 0 ){
return SXERR_MEM;
}
if( isForeign ){
/* Mark as a foregin entry */
pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ;
}
/* Make sure the bucket is big enough to hold the new entry */
rc = HashmapGrowBucket(&(*pMap));
if( rc != SXRET_OK ){
SyMemBackendPoolFree(&pMap->pVm->sAllocator,pNode);
return rc;
}
/* Perform the insertion */
HashmapNodeLink(&(*pMap),pNode,nHash & (pMap->nSize - 1));
/* Install in the reference table */
PH7_VmRefObjInstall(pMap->pVm,nIdx,0,pNode,0);
/* All done */
return SXRET_OK;
}
/*
* Insert a BLOB key and it's associated value (if any) in the given
* hashmap.
*/
static sxi32 HashmapInsertBlobKey(ph7_hashmap *pMap,const void *pKey,sxu32 nKeyLen,ph7_value *pValue,sxu32 nRefIdx,int isForeign)
{
ph7_hashmap_node *pNode;
sxu32 nHash;
sxu32 nIdx;
sxi32 rc;
if( !isForeign ){
ph7_value *pObj;
/* Reserve a ph7_value for the value */
pObj = PH7_ReserveMemObj(pMap->pVm);
if( pObj == 0 ){
return SXERR_MEM;
}
if( pValue ){
/* Duplicate the value */
PH7_MemObjStore(pValue,pObj);
}
nIdx = pObj->nIdx;
}else{
nIdx = nRefIdx;
}
/* Hash the key */
nHash = pMap->xBlobHash(pKey,nKeyLen);
/* Allocate a new blob node */
pNode = HashmapNewBlobNode(&(*pMap),pKey,nKeyLen,nHash,nIdx);
if( pNode == 0 ){
return SXERR_MEM;
}
if( isForeign ){
/* Mark as a foregin entry */
pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ;
}
/* Make sure the bucket is big enough to hold the new entry */
rc = HashmapGrowBucket(&(*pMap));
if( rc != SXRET_OK ){
SyMemBackendPoolFree(&pMap->pVm->sAllocator,pNode);
return rc;
}
/* Perform the insertion */
HashmapNodeLink(&(*pMap),pNode,nHash & (pMap->nSize - 1));
/* Install in the reference table */
PH7_VmRefObjInstall(pMap->pVm,nIdx,0,pNode,0);
/* All done */
return SXRET_OK;
}
/*
* Check if a given 64-bit integer key exists in the given hashmap.
* Write a pointer to the target node on success. Otherwise
* SXERR_NOTFOUND is returned on failure.
*/
static sxi32 HashmapLookupIntKey(
ph7_hashmap *pMap, /* Target hashmap */
sxi64 iKey, /* lookup key */
ph7_hashmap_node **ppNode /* OUT: target node on success */
)
{
ph7_hashmap_node *pNode;
sxu32 nHash;
if( pMap->nEntry < 1 ){
/* Don't bother hashing,there is no entry anyway */
return SXERR_NOTFOUND;
}
/* Hash the key first */
nHash = pMap->xIntHash(iKey);
/* Point to the appropriate bucket */
pNode = pMap->apBucket[nHash & (pMap->nSize - 1)];
/* Perform the lookup */
for(;;){
if( pNode == 0 ){
break;
}
if( pNode->iType == HASHMAP_INT_NODE
&& pNode->nHash == nHash
&& pNode->xKey.iKey == iKey ){
/* Node found */
if( ppNode ){
*ppNode = pNode;
}
return SXRET_OK;
}
/* Follow the collision link */
pNode = pNode->pNextCollide;
}
/* No such entry */
return SXERR_NOTFOUND;
}
/*
* Check if a given BLOB key exists in the given hashmap.
* Write a pointer to the target node on success. Otherwise
* SXERR_NOTFOUND is returned on failure.
*/
static sxi32 HashmapLookupBlobKey(
ph7_hashmap *pMap, /* Target hashmap */
const void *pKey, /* Lookup key */
sxu32 nKeyLen, /* Key length in bytes */
ph7_hashmap_node **ppNode /* OUT: target node on success */
)
{
ph7_hashmap_node *pNode;
sxu32 nHash;
if( pMap->nEntry < 1 ){
/* Don't bother hashing,there is no entry anyway */
return SXERR_NOTFOUND;
}
/* Hash the key first */
nHash = pMap->xBlobHash(pKey,nKeyLen);
/* Point to the appropriate bucket */
pNode = pMap->apBucket[nHash & (pMap->nSize - 1)];
/* Perform the lookup */
for(;;){
if( pNode == 0 ){
break;
}
if( pNode->iType == HASHMAP_BLOB_NODE
&& pNode->nHash == nHash
&& SyBlobLength(&pNode->xKey.sKey) == nKeyLen
&& SyMemcmp(SyBlobData(&pNode->xKey.sKey),pKey,nKeyLen) == 0 ){
/* Node found */
if( ppNode ){
*ppNode = pNode;
}
return SXRET_OK;
}
/* Follow the collision link */
pNode = pNode->pNextCollide;
}
/* No such entry */
return SXERR_NOTFOUND;
}
/*
* Check if the given BLOB key looks like a decimal number.
* Retrurn TRUE on success.FALSE otherwise.
*/
static int HashmapIsIntKey(SyBlob *pKey)
{
const char *zIn = (const char *)SyBlobData(pKey);
const char *zEnd = &zIn[SyBlobLength(pKey)];
if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){
/* Octal not decimal number */
return FALSE;
}
if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){
zIn++;
}
for(;;){
if( zIn >= zEnd ){
return TRUE;
}
if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){
break;
}
zIn++;
}
/* Key does not look like a decimal number */
return FALSE;
}
/*
* Check if a given key exists in the given hashmap.
* Write a pointer to the target node on success.
* Otherwise SXERR_NOTFOUND is returned on failure.
*/
static sxi32 HashmapLookup(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
ph7_hashmap_node **ppNode /* OUT: target node on success */
)
{
ph7_hashmap_node *pNode = 0; /* cc -O6 warning */
sxi32 rc;
if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){
if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){
/* Force a string cast */
PH7_MemObjToString(&(*pKey));
}
if( SyBlobLength(&pKey->sBlob) > 0 && !HashmapIsIntKey(&pKey->sBlob) ){
/* Perform a blob lookup */
rc = HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&pNode);
goto result;
}
}
/* Perform an int lookup */
if((pKey->iFlags & MEMOBJ_INT) == 0 ){
/* Force an integer cast */
PH7_MemObjToInteger(pKey);
}
/* Perform an int lookup */
rc = HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode);
result:
if( rc == SXRET_OK ){
/* Node found */
if( ppNode ){
*ppNode = pNode;
}
return SXRET_OK;
}
/* No such entry */
return SXERR_NOTFOUND;
}
/*
* Insert a given key and it's associated value (if any) in the given
* hashmap.
* If a node with the given key already exists in the database
* then this function overwrite the old value.
*/
static sxi32 HashmapInsert(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
ph7_value *pVal /* Node value */
)
{
ph7_hashmap_node *pNode = 0;
sxi32 rc = SXRET_OK;
if( pKey && pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){
if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){
/* Force a string cast */
PH7_MemObjToString(&(*pKey));
}
if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){
if(SyBlobLength(&pKey->sBlob) < 1){
/* Automatic index assign */
pKey = 0;
}
goto IntKey;
}
if( SXRET_OK == HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),
SyBlobLength(&pKey->sBlob),&pNode) ){
/* Overwrite the old value */
ph7_value *pElem;
pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pNode->nValIdx);
if( pElem ){
if( pVal ){
PH7_MemObjStore(pVal,pElem);
}else{
/* Nullify the entry */
PH7_MemObjToNull(pElem);
}
}
return SXRET_OK;
}
if( pMap == pMap->pVm->pGlobal ){
/* Forbidden */
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden");
return SXRET_OK;
}
/* Perform a blob-key insertion */
rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal),0,FALSE);
return rc;
}
IntKey:
if( pKey ){
if((pKey->iFlags & MEMOBJ_INT) == 0 ){
/* Force an integer cast */
PH7_MemObjToInteger(pKey);
}
if( SXRET_OK == HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode) ){
/* Overwrite the old value */
ph7_value *pElem;
pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pNode->nValIdx);
if( pElem ){
if( pVal ){
PH7_MemObjStore(pVal,pElem);
}else{
/* Nullify the entry */
PH7_MemObjToNull(pElem);
}
}
return SXRET_OK;
}
if( pMap == pMap->pVm->pGlobal ){
/* Forbidden */
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden");
return SXRET_OK;
}
/* Perform a 64-bit-int-key insertion */
rc = HashmapInsertIntKey(&(*pMap),pKey->x.iVal,&(*pVal),0,FALSE);
if( rc == SXRET_OK ){
if( pKey->x.iVal >= pMap->iNextIdx ){
/* Increment the automatic index */
pMap->iNextIdx = pKey->x.iVal + 1;
/* Make sure the automatic index is not reserved */
while( SXRET_OK == HashmapLookupIntKey(&(*pMap),pMap->iNextIdx,0) ){
pMap->iNextIdx++;
}
}
}
}else{
if( pMap == pMap->pVm->pGlobal ){
/* Forbidden */
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden");
return SXRET_OK;
}
/* Assign an automatic index */
rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal),0,FALSE);
if( rc == SXRET_OK ){
++pMap->iNextIdx;
}
}
/* Insertion result */
return rc;
}
/*
* Insert a given key and it's associated value (foreign index) in the given
* hashmap.
* This is insertion by reference so be careful to mark the node
* with the HASHMAP_NODE_FOREIGN_OBJ flag being set.
* The insertion by reference is triggered when the following
* expression is encountered.
* $var = 10;
* $a = array(&var);
* OR
* $a[] =& $var;
* That is,$var is a foreign ph7_value and the $a array have no control
* over it's contents.
* Note that the node that hold the foreign ph7_value is automatically
* removed when the foreign ph7_value is unset.
* Example:
* $var = 10;
* $a[] =& $var;
* echo count($a).PHP_EOL; //1
* //Unset the foreign ph7_value now
* unset($var);
* echo count($a); //0
* Note that this is a PH7 eXtension.
* Refer to the official documentation for more information.
* If a node with the given key already exists in the database
* then this function overwrite the old value.
*/
static sxi32 HashmapInsertByRef(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
sxu32 nRefIdx /* Foreign ph7_value index */
)
{
ph7_hashmap_node *pNode = 0;
sxi32 rc = SXRET_OK;
if( pKey && pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){
if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){
/* Force a string cast */
PH7_MemObjToString(&(*pKey));
}
if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){
if(SyBlobLength(&pKey->sBlob) < 1){
/* Automatic index assign */
pKey = 0;
}
goto IntKey;
}
if( SXRET_OK == HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),
SyBlobLength(&pKey->sBlob),&pNode) ){
/* Overwrite */
PH7_VmRefObjRemove(pMap->pVm,pNode->nValIdx,0,pNode);
pNode->nValIdx = nRefIdx;
/* Install in the reference table */
PH7_VmRefObjInstall(pMap->pVm,nRefIdx,0,pNode,0);
return SXRET_OK;
}
/* Perform a blob-key insertion */
rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),0,nRefIdx,TRUE);
return rc;
}
IntKey:
if( pKey ){
if((pKey->iFlags & MEMOBJ_INT) == 0 ){
/* Force an integer cast */
PH7_MemObjToInteger(pKey);
}
if( SXRET_OK == HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode) ){
/* Overwrite */
PH7_VmRefObjRemove(pMap->pVm,pNode->nValIdx,0,pNode);
pNode->nValIdx = nRefIdx;
/* Install in the reference table */
PH7_VmRefObjInstall(pMap->pVm,nRefIdx,0,pNode,0);
return SXRET_OK;
}
/* Perform a 64-bit-int-key insertion */
rc = HashmapInsertIntKey(&(*pMap),pKey->x.iVal,0,nRefIdx,TRUE);
if( rc == SXRET_OK ){
if( pKey->x.iVal >= pMap->iNextIdx ){
/* Increment the automatic index */
pMap->iNextIdx = pKey->x.iVal + 1;
/* Make sure the automatic index is not reserved */
while( SXRET_OK == HashmapLookupIntKey(&(*pMap),pMap->iNextIdx,0) ){
pMap->iNextIdx++;
}
}
}
}else{
/* Assign an automatic index */
rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,0,nRefIdx,TRUE);
if( rc == SXRET_OK ){
++pMap->iNextIdx;
}
}
/* Insertion result */
return rc;
}
/*
* Extract node value.
*/
static ph7_value * HashmapExtractNodeValue(ph7_hashmap_node *pNode)
{
/* Point to the desired object */
ph7_value *pObj;
pObj = (ph7_value *)SySetAt(&pNode->pMap->pVm->aMemObj,pNode->nValIdx);
return pObj;
}
/*
* Insert a node in the given hashmap.
* If a node with the given key already exists in the database
* then this function overwrite the old value.
*/
static sxi32 HashmapInsertNode(ph7_hashmap *pMap,ph7_hashmap_node *pNode,int bPreserve)
{
ph7_value *pObj;
sxi32 rc;
/* Extract the node value */
pObj = HashmapExtractNodeValue(&(*pNode));
if( pObj == 0 ){
return SXERR_EMPTY;
}
/* Preserve key */
if( pNode->iType == HASHMAP_INT_NODE){
/* Int64 key */
if( !bPreserve ){
/* Assign an automatic index */
rc = HashmapInsert(&(*pMap),0,pObj);
}else{
rc = HashmapInsertIntKey(&(*pMap),pNode->xKey.iKey,pObj,0,FALSE);
}
}else{
/* Blob key */
rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pNode->xKey.sKey),
SyBlobLength(&pNode->xKey.sKey),pObj,0,FALSE);
}
return rc;
}
/*
* Compare two node values.
* Return 0 if the node values are equals, > 0 if pLeft is greater than pRight
* or < 0 if pRight is greater than pLeft.
* For a full description on ph7_values comparison,refer to the implementation
* of the [PH7_MemObjCmp()] function defined in memobj.c or the official
* documenation.
*/
static sxi32 HashmapNodeCmp(ph7_hashmap_node *pLeft,ph7_hashmap_node *pRight,int bStrict)
{
ph7_value sObj1,sObj2;
sxi32 rc;
if( pLeft == pRight ){
/*
* Same node.Refer to the sort() implementation defined
* below for more information on this sceanario.
*/
return 0;
}
/* Do the comparison */
PH7_MemObjInit(pLeft->pMap->pVm,&sObj1);
PH7_MemObjInit(pLeft->pMap->pVm,&sObj2);
PH7_HashmapExtractNodeValue(pLeft,&sObj1,FALSE);
PH7_HashmapExtractNodeValue(pRight,&sObj2,FALSE);
rc = PH7_MemObjCmp(&sObj1,&sObj2,bStrict,0);
PH7_MemObjRelease(&sObj1);
PH7_MemObjRelease(&sObj2);
return rc;
}
/*
* Rehash a node with a 64-bit integer key.
* Refer to [merge_sort(),array_shift()] implementations for more information.
*/
static void HashmapRehashIntNode(ph7_hashmap_node *pEntry)
{
ph7_hashmap *pMap = pEntry->pMap;
sxu32 nBucket;
/* Remove old collision links */
if( pEntry->pPrevCollide ){
pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide;
}else{
pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide;
}
if( pEntry->pNextCollide ){
pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide;
}
pEntry->pNextCollide = pEntry->pPrevCollide = 0;
/* Compute the new hash */
pEntry->nHash = pMap->xIntHash(pMap->iNextIdx);
pEntry->xKey.iKey = pMap->iNextIdx;
nBucket = pEntry->nHash & (pMap->nSize - 1);
/* Link to the new bucket */
pEntry->pNextCollide = pMap->apBucket[nBucket];
if( pMap->apBucket[nBucket] ){
pMap->apBucket[nBucket]->pPrevCollide = pEntry;
}
pEntry->pNextCollide = pMap->apBucket[nBucket];
pMap->apBucket[nBucket] = pEntry;
/* Increment the automatic index */
pMap->iNextIdx++;
}
/*
* Perform a linear search on a given hashmap.
* Write a pointer to the target node on success.
* Otherwise SXERR_NOTFOUND is returned on failure.
* Refer to [array_intersect(),array_diff(),in_array(),...] implementations
* for more information.
*/
static int HashmapFindValue(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pNeedle, /* Lookup key */
ph7_hashmap_node **ppNode, /* OUT: target node on success */
int bStrict /* TRUE for strict comparison */
)
{
ph7_hashmap_node *pEntry;
ph7_value sVal,*pVal;
ph7_value sNeedle;
sxi32 rc;
sxu32 n;
/* Perform a linear search since we cannot sort the hashmap based on values */
pEntry = pMap->pFirst;
n = pMap->nEntry;
PH7_MemObjInit(pMap->pVm,&sVal);
PH7_MemObjInit(pMap->pVm,&sNeedle);
for(;;){
if( n < 1 ){
break;
}
/* Extract node value */
pVal = HashmapExtractNodeValue(pEntry);
if( pVal ){
if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){
sxi32 iF1 = pVal->iFlags&~MEMOBJ_AUX;
sxi32 iF2 = pNeedle->iFlags&~MEMOBJ_AUX;
if( iF1 == iF2 ){
/* NULL values are equals */
if( ppNode ){
*ppNode = pEntry;
}
return SXRET_OK;
}
}else{
/* Duplicate value */
PH7_MemObjLoad(pVal,&sVal);
PH7_MemObjLoad(pNeedle,&sNeedle);
rc = PH7_MemObjCmp(&sNeedle,&sVal,bStrict,0);
PH7_MemObjRelease(&sVal);
PH7_MemObjRelease(&sNeedle);
if( rc == 0 ){
if( ppNode ){
*ppNode = pEntry;
}
/* Match found*/
return SXRET_OK;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* No such entry */
return SXERR_NOTFOUND;
}
/*
* Perform a linear search on a given hashmap but use an user-defined callback
* for values comparison.
* Write a pointer to the target node on success.
* Otherwise SXERR_NOTFOUND is returned on failure.
* Refer to [array_uintersect(),array_udiff()...] implementations
* for more information.
*/
static int HashmapFindValueByCallback(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pNeedle, /* Lookup key */
ph7_value *pCallback, /* User defined callback */
ph7_hashmap_node **ppNode /* OUT: target node on success */
)
{
ph7_hashmap_node *pEntry;
ph7_value sResult,*pVal;
ph7_value *apArg[2]; /* Callback arguments */
sxi32 rc;
sxu32 n;
/* Perform a linear search since we cannot sort the array based on values */
pEntry = pMap->pFirst;
n = pMap->nEntry;
/* Store callback result here */
PH7_MemObjInit(pMap->pVm,&sResult);
/* First argument to the callback */
apArg[0] = pNeedle;
for(;;){
if( n < 1 ){
break;
}
/* Extract node value */
pVal = HashmapExtractNodeValue(pEntry);
if( pVal ){
/* Invoke the user callback */
apArg[1] = pVal; /* Second argument to the callback */
rc = PH7_VmCallUserFunction(pMap->pVm,pCallback,2,apArg,&sResult);
if( rc == SXRET_OK ){
/* Extract callback result */
if( (sResult.iFlags & MEMOBJ_INT) == 0 ){
/* Perform an int cast */
PH7_MemObjToInteger(&sResult);
}
rc = (sxi32)sResult.x.iVal;
PH7_MemObjRelease(&sResult);
if( rc == 0 ){
/* Match found*/
if( ppNode ){
*ppNode = pEntry;
}
return SXRET_OK;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* No such entry */
return SXERR_NOTFOUND;
}
/*
* Compare two hashmaps.
* Return 0 if the hashmaps are equals.Any other value indicates inequality.
* Note on array comparison operators.
* According to the PHP language reference manual.
* Array Operators Example Name Result
* $a + $b Union Union of $a and $b.
* $a == $b Equality TRUE if $a and $b have the same key/value pairs.
* $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same
* order and of the same types.
* $a != $b Inequality TRUE if $a is not equal to $b.
* $a <> $b Inequality TRUE if $a is not equal to $b.
* $a !== $b Non-identity TRUE if $a is not identical to $b.
* The + operator returns the right-hand array appended to the left-hand array;
* For keys that exist in both arrays, the elements from the left-hand array will be used
* and the matching elements from the right-hand array will be ignored.
* <?php
* $a = array("a" => "apple", "b" => "banana");
* $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");
* $c = $a + $b; // Union of $a and $b
* echo "Union of \$a and \$b: \n";
* var_dump($c);
* $c = $b + $a; // Union of $b and $a
* echo "Union of \$b and \$a: \n";
* var_dump($c);
* ?>
* When executed, this script will print the following:
* Union of $a and $b:
* array(3) {
* ["a"]=>
* string(5) "apple"
* ["b"]=>
* string(6) "banana"
* ["c"]=>
* string(6) "cherry"
* }
* Union of $b and $a:
* array(3) {
* ["a"]=>
* string(4) "pear"
* ["b"]=>
* string(10) "strawberry"
* ["c"]=>
* string(6) "cherry"
* }
* Elements of arrays are equal for the comparison if they have the same key and value.
*/
PH7_PRIVATE sxi32 PH7_HashmapCmp(
ph7_hashmap *pLeft, /* Left hashmap */
ph7_hashmap *pRight, /* Right hashmap */
int bStrict /* TRUE for strict comparison */
)
{
ph7_hashmap_node *pLe,*pRe;
sxi32 rc;
sxu32 n;
if( pLeft == pRight ){
/* Same hashmap instance. This can easily happen since hashmaps are passed by reference.
* Unlike the zend engine.
*/
return 0;
}
if( pLeft->nEntry != pRight->nEntry ){
/* Must have the same number of entries */
return pLeft->nEntry > pRight->nEntry ? 1 : -1;
}
/* Point to the first inserted entry of the left hashmap */
pLe = pLeft->pFirst;
pRe = 0; /* cc warning */
/* Perform the comparison */
n = pLeft->nEntry;
for(;;){
if( n < 1 ){
break;
}
if( pLe->iType == HASHMAP_INT_NODE){
/* Int key */
rc = HashmapLookupIntKey(&(*pRight),pLe->xKey.iKey,&pRe);
}else{
SyBlob *pKey = &pLe->xKey.sKey;
/* Blob key */
rc = HashmapLookupBlobKey(&(*pRight),SyBlobData(pKey),SyBlobLength(pKey),&pRe);
}
if( rc != SXRET_OK ){
/* No such entry in the right side */
return 1;
}
rc = 0;
if( bStrict ){
/* Make sure,the keys are of the same type */
if( pLe->iType != pRe->iType ){
rc = 1;
}
}
if( !rc ){
/* Compare nodes */
rc = HashmapNodeCmp(pLe,pRe,bStrict);
}
if( rc != 0 ){
/* Nodes key/value differ */
return rc;
}
/* Point to the next entry */
pLe = pLe->pPrev; /* Reverse link */
n--;
}
return 0; /* Hashmaps are equals */
}
/*
* Merge two hashmaps.
* Note on the merge process
* According to the PHP language reference manual.
* Merges the elements of two arrays together so that the values of one are appended
* to the end of the previous one. It returns the resulting array (pDest).
* If the input arrays have the same string keys, then the later value for that key
* will overwrite the previous one. If, however, the arrays contain numeric keys
* the later value will not overwrite the original value, but will be appended.
* Values in the input array with numeric keys will be renumbered with incrementing
* keys starting from zero in the result array.
*/
static sxi32 HashmapMerge(ph7_hashmap *pSrc,ph7_hashmap *pDest)
{
ph7_hashmap_node *pEntry;
ph7_value sKey,*pVal;
sxi32 rc;
sxu32 n;
if( pSrc == pDest ){
/* Same map. This can easily happen since hashmaps are passed by reference.
* Unlike the zend engine.
*/
return SXRET_OK;
}
/* Point to the first inserted entry in the source */
pEntry = pSrc->pFirst;
/* Perform the merge */
for( n = 0 ; n < pSrc->nEntry ; ++n ){
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if( pEntry->iType == HASHMAP_BLOB_NODE ){
/* Blob key insertion */
PH7_MemObjInitFromString(pDest->pVm,&sKey,0);
PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey));
rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal);
PH7_MemObjRelease(&sKey);
}else{
rc = HashmapInsert(&(*pDest),0/* Automatic index assign */,pVal);
}
if( rc != SXRET_OK ){
return rc;
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
return SXRET_OK;
}
/*
* Overwrite entries with the same key.
* Refer to the [array_replace()] implementation for more information.
* According to the PHP language reference manual.
* array_replace() replaces the values of the first array with the same values
* from all the following arrays. If a key from the first array exists in the second
* array, its value will be replaced by the value from the second array. If the key
* exists in the second array, and not the first, it will be created in the first array.
* If a key only exists in the first array, it will be left as is. If several arrays
* are passed for replacement, they will be processed in order, the later arrays
* overwriting the previous values.
* array_replace() is not recursive : it will replace values in the first array
* by whatever type is in the second array.
*/
static sxi32 HashmapOverwrite(ph7_hashmap *pSrc,ph7_hashmap *pDest)
{
ph7_hashmap_node *pEntry;
ph7_value sKey,*pVal;
sxi32 rc;
sxu32 n;
if( pSrc == pDest ){
/* Same map. This can easily happen since hashmaps are passed by reference.
* Unlike the zend engine.
*/
return SXRET_OK;
}
/* Point to the first inserted entry in the source */
pEntry = pSrc->pFirst;
/* Perform the merge */
for( n = 0 ; n < pSrc->nEntry ; ++n ){
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if( pEntry->iType == HASHMAP_BLOB_NODE ){
/* Blob key insertion */
PH7_MemObjInitFromString(pDest->pVm,&sKey,0);
PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey));
}else{
/* Int key insertion */
PH7_MemObjInitFromInt(pDest->pVm,&sKey,pEntry->xKey.iKey);
}
rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal);
PH7_MemObjRelease(&sKey);
if( rc != SXRET_OK ){
return rc;
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
return SXRET_OK;
}
/*
* Duplicate the contents of a hashmap. Store the copy in pDest.
* Refer to the [array_pad(),array_copy(),...] implementation for more information.
*/
PH7_PRIVATE sxi32 PH7_HashmapDup(ph7_hashmap *pSrc,ph7_hashmap *pDest)
{
ph7_hashmap_node *pEntry;
ph7_value sKey,*pVal;
sxi32 rc;
sxu32 n;
if( pSrc == pDest ){
/* Same map. This can easily happen since hashmaps are passed by reference.
* Unlike the zend engine.
*/
return SXRET_OK;
}
/* Point to the first inserted entry in the source */
pEntry = pSrc->pFirst;
/* Perform the duplication */
for( n = 0 ; n < pSrc->nEntry ; ++n ){
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if( pEntry->iType == HASHMAP_BLOB_NODE ){
/* Blob key insertion */
PH7_MemObjInitFromString(pDest->pVm,&sKey,0);
PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey));
rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal);
PH7_MemObjRelease(&sKey);
}else{
/* Int key insertion */
rc = HashmapInsertIntKey(&(*pDest),pEntry->xKey.iKey,pVal,0,FALSE);
}
if( rc != SXRET_OK ){
return rc;
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
return SXRET_OK;
}
/*
* Perform the union of two hashmaps.
* This operation is performed only if the user uses the '+' operator
* with a variable holding an array as follows:
* <?php
* $a = array("a" => "apple", "b" => "banana");
* $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");
* $c = $a + $b; // Union of $a and $b
* echo "Union of \$a and \$b: \n";
* var_dump($c);
* $c = $b + $a; // Union of $b and $a
* echo "Union of \$b and \$a: \n";
* var_dump($c);
* ?>
* When executed, this script will print the following:
* Union of $a and $b:
* array(3) {
* ["a"]=>
* string(5) "apple"
* ["b"]=>
* string(6) "banana"
* ["c"]=>
* string(6) "cherry"
* }
* Union of $b and $a:
* array(3) {
* ["a"]=>
* string(4) "pear"
* ["b"]=>
* string(10) "strawberry"
* ["c"]=>
* string(6) "cherry"
* }
* The + operator returns the right-hand array appended to the left-hand array;
* For keys that exist in both arrays, the elements from the left-hand array will be used
* and the matching elements from the right-hand array will be ignored.
*/
PH7_PRIVATE sxi32 PH7_HashmapUnion(ph7_hashmap *pLeft,ph7_hashmap *pRight)
{
ph7_hashmap_node *pEntry;
sxi32 rc = SXRET_OK;
ph7_value *pObj;
sxu32 n;
if( pLeft == pRight ){
/* Same map. This can easily happen since hashmaps are passed by reference.
* Unlike the zend engine.
*/
return SXRET_OK;
}
/* Perform the union */
pEntry = pRight->pFirst;
for(n = 0 ; n < pRight->nEntry ; ++n ){
/* Make sure the given key does not exists in the left array */
if( pEntry->iType == HASHMAP_BLOB_NODE ){
/* BLOB key */
if( SXRET_OK !=
HashmapLookupBlobKey(&(*pLeft),SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),0) ){
pObj = HashmapExtractNodeValue(pEntry);
if( pObj ){
/* Perform the insertion */
rc = HashmapInsertBlobKey(&(*pLeft),SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),
pObj,0,FALSE);
if( rc != SXRET_OK ){
return rc;
}
}
}
}else{
/* INT key */
if( SXRET_OK != HashmapLookupIntKey(&(*pLeft),pEntry->xKey.iKey,0) ){
pObj = HashmapExtractNodeValue(pEntry);
if( pObj ){
/* Perform the insertion */
rc = HashmapInsertIntKey(&(*pLeft),pEntry->xKey.iKey,pObj,0,FALSE);
if( rc != SXRET_OK ){
return rc;
}
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
return SXRET_OK;
}
/*
* Allocate a new hashmap.
* Return a pointer to the freshly allocated hashmap on success.NULL otherwise.
*/
PH7_PRIVATE ph7_hashmap * PH7_NewHashmap(
ph7_vm *pVm, /* VM that trigger the hashmap creation */
sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/
sxu32 (*xBlobHash)(const void *,sxu32) /* Hash function for BLOB keys.NULL otherwise */
)
{
ph7_hashmap *pMap;
/* Allocate a new instance */
pMap = (ph7_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_hashmap));
if( pMap == 0 ){
return 0;
}
/* Zero the structure */
SyZero(pMap,sizeof(ph7_hashmap));
/* Fill in the structure */
pMap->pVm = &(*pVm);
pMap->iRef = 1;
/* Default hash functions */
pMap->xIntHash = xIntHash ? xIntHash : IntHash;
pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash;
return pMap;
}
/*
* Install superglobals in the given virtual machine.
* Note on superglobals.
* According to the PHP language reference manual.
* Superglobals are built-in variables that are always available in all scopes.
* Description
* Several predefined variables in PHP are "superglobals", which means they
* are available in all scopes throughout a script. There is no need to do
* global $variable; to access them within functions or methods.
* These superglobal variables are:
* $GLOBALS
* $_SERVER
* $_GET
* $_POST
* $_FILES
* $_COOKIE
* $_SESSION
* $_REQUEST
* $_ENV
*/
PH7_PRIVATE sxi32 PH7_HashmapCreateSuper(ph7_vm *pVm)
{
static const char * azSuper[] = {
"_SERVER", /* $_SERVER */
"_GET", /* $_GET */
"_POST", /* $_POST */
"_FILES", /* $_FILES */
"_COOKIE", /* $_COOKIE */
"_SESSION", /* $_SESSION */
"_REQUEST", /* $_REQUEST */
"_ENV", /* $_ENV */
"_HEADER", /* $_HEADER */
"argv" /* $argv */
};
ph7_hashmap *pMap;
ph7_value *pObj;
SyString *pFile;
sxi32 rc;
sxu32 n;
/* Allocate a new hashmap for the $GLOBALS array */
pMap = PH7_NewHashmap(&(*pVm),0,0);
if( pMap == 0 ){
return SXERR_MEM;
}
pVm->pGlobal = pMap;
/* Reserve a ph7_value for the $GLOBALS array*/
pObj = PH7_ReserveMemObj(&(*pVm));
if( pObj == 0 ){
return SXERR_MEM;
}
PH7_MemObjInitFromArray(&(*pVm),pObj,pMap);
/* Record object index */
pVm->nGlobalIdx = pObj->nIdx;
/* Install the special $GLOBALS array */
rc = SyHashInsert(&pVm->hSuper,(const void *)"GLOBALS",sizeof("GLOBALS")-1,SX_INT_TO_PTR(pVm->nGlobalIdx));
if( rc != SXRET_OK ){
return rc;
}
/* Install superglobals now */
for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){
ph7_value *pSuper;
/* Request an empty array */
pSuper = ph7_new_array(&(*pVm));
if( pSuper == 0 ){
return SXERR_MEM;
}
/* Install */
rc = ph7_vm_config(&(*pVm),PH7_VM_CONFIG_CREATE_SUPER,azSuper[n]/* Super-global name*/,pSuper/* Super-global value */);
if( rc != SXRET_OK ){
return rc;
}
/* Release the value now it have been installed */
ph7_release_value(&(*pVm),pSuper);
}
/* Set some $_SERVER entries */
pFile = (SyString *)SySetPeek(&pVm->aFiles);
/*
* 'SCRIPT_FILENAME'
* The absolute pathname of the currently executing script.
*/
ph7_vm_config(pVm,PH7_VM_CONFIG_SERVER_ATTR,
"SCRIPT_FILENAME",
pFile ? pFile->zString : ":Memory:",
pFile ? pFile->nByte : sizeof(":Memory:") - 1
);
/* All done,all super-global are installed now */
return SXRET_OK;
}
/*
* Release a hashmap.
*/
PH7_PRIVATE sxi32 PH7_HashmapRelease(ph7_hashmap *pMap,int FreeDS)
{
ph7_hashmap_node *pEntry,*pNext;
ph7_vm *pVm = pMap->pVm;
sxu32 n;
if( pMap == pVm->pGlobal ){
/* Cannot delete the $GLOBALS array */
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,deletion is forbidden");
return SXRET_OK;
}
/* Start the release process */
n = 0;
pEntry = pMap->pFirst;
for(;;){
if( n >= pMap->nEntry ){
break;
}
pNext = pEntry->pPrev; /* Reverse link */
/* Remove the reference from the foreign table */
PH7_VmRefObjRemove(pVm,pEntry->nValIdx,0,pEntry);
if( (pEntry->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0 ){
/* Restore the ph7_value to the free list */
PH7_VmUnsetMemObj(pVm,pEntry->nValIdx,FALSE);
}
/* Release the node */
if( pEntry->iType == HASHMAP_BLOB_NODE ){
SyBlobRelease(&pEntry->xKey.sKey);
}
SyMemBackendPoolFree(&pVm->sAllocator,pEntry);
/* Point to the next entry */
pEntry = pNext;
n++;
}
if( pMap->nEntry > 0 ){
/* Release the hash bucket */
SyMemBackendFree(&pVm->sAllocator,pMap->apBucket);
}
if( FreeDS ){
/* Free the whole instance */
SyMemBackendPoolFree(&pVm->sAllocator,pMap);
}else{
/* Keep the instance but reset it's fields */
pMap->apBucket = 0;
pMap->iNextIdx = 0;
pMap->nEntry = pMap->nSize = 0;
pMap->pFirst = pMap->pLast = pMap->pCur = 0;
}
return SXRET_OK;
}
/*
* Decrement the reference count of a given hashmap.
* If the count reaches zero which mean no more variables
* are pointing to this hashmap,then release the whole instance.
*/
PH7_PRIVATE void PH7_HashmapUnref(ph7_hashmap *pMap)
{
ph7_vm *pVm = pMap->pVm;
/* TICKET 1432-49: $GLOBALS is not subject to garbage collection */
pMap->iRef--;
if( pMap->iRef < 1 && pMap != pVm->pGlobal){
PH7_HashmapRelease(pMap,TRUE);
}
}
/*
* Check if a given key exists in the given hashmap.
* Write a pointer to the target node on success.
* Otherwise SXERR_NOTFOUND is returned on failure.
*/
PH7_PRIVATE sxi32 PH7_HashmapLookup(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
ph7_hashmap_node **ppNode /* OUT: Target node on success */
)
{
sxi32 rc;
if( pMap->nEntry < 1 ){
/* TICKET 1433-25: Don't bother hashing,the hashmap is empty anyway.
*/
return SXERR_NOTFOUND;
}
rc = HashmapLookup(&(*pMap),&(*pKey),ppNode);
return rc;
}
/*
* Insert a given key and it's associated value (if any) in the given
* hashmap.
* If a node with the given key already exists in the database
* then this function overwrite the old value.
*/
PH7_PRIVATE sxi32 PH7_HashmapInsert(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
ph7_value *pVal /* Node value.NULL otherwise */
)
{
sxi32 rc;
if( pVal && (pVal->iFlags & MEMOBJ_HASHMAP) && (ph7_hashmap *)pVal->x.pOther == pMap->pVm->pGlobal ){
/*
* TICKET 1433-35: Insertion in the $GLOBALS array is forbidden.
*/
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_ERR,"$GLOBALS is a read-only array,insertion is forbidden");
return SXRET_OK;
}
rc = HashmapInsert(&(*pMap),&(*pKey),&(*pVal));
return rc;
}
/*
* Insert a given key and it's associated value (foreign index) in the given
* hashmap.
* This is insertion by reference so be careful to mark the node
* with the HASHMAP_NODE_FOREIGN_OBJ flag being set.
* The insertion by reference is triggered when the following
* expression is encountered.
* $var = 10;
* $a = array(&var);
* OR
* $a[] =& $var;
* That is,$var is a foreign ph7_value and the $a array have no control
* over it's contents.
* Note that the node that hold the foreign ph7_value is automatically
* removed when the foreign ph7_value is unset.
* Example:
* $var = 10;
* $a[] =& $var;
* echo count($a).PHP_EOL; //1
* //Unset the foreign ph7_value now
* unset($var);
* echo count($a); //0
* Note that this is a PH7 eXtension.
* Refer to the official documentation for more information.
* If a node with the given key already exists in the database
* then this function overwrite the old value.
*/
PH7_PRIVATE sxi32 PH7_HashmapInsertByRef(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pKey, /* Lookup key */
sxu32 nRefIdx /* Foreign ph7_value index */
)
{
sxi32 rc;
if( nRefIdx == pMap->pVm->nGlobalIdx ){
/*
* TICKET 1433-35: Insertion in the $GLOBALS array is forbidden.
*/
PH7_VmThrowError(pMap->pVm,0,PH7_CTX_ERR,"$GLOBALS is a read-only array,insertion is forbidden");
return SXRET_OK;
}
rc = HashmapInsertByRef(&(*pMap),&(*pKey),nRefIdx);
return rc;
}
/*
* Reset the node cursor of a given hashmap.
*/
PH7_PRIVATE void PH7_HashmapResetLoopCursor(ph7_hashmap *pMap)
{
/* Reset the loop cursor */
pMap->pCur = pMap->pFirst;
}
/*
* Return a pointer to the node currently pointed by the node cursor.
* If the cursor reaches the end of the list,then this function
* return NULL.
* Note that the node cursor is automatically advanced by this function.
*/
PH7_PRIVATE ph7_hashmap_node * PH7_HashmapGetNextEntry(ph7_hashmap *pMap)
{
ph7_hashmap_node *pCur = pMap->pCur;
if( pCur == 0 ){
/* End of the list,return null */
return 0;
}
/* Advance the node cursor */
pMap->pCur = pCur->pPrev; /* Reverse link */
return pCur;
}
/*
* Extract a node value.
*/
PH7_PRIVATE void PH7_HashmapExtractNodeValue(ph7_hashmap_node *pNode,ph7_value *pValue,int bStore)
{
ph7_value *pEntry = HashmapExtractNodeValue(pNode);
if( pEntry ){
if( bStore ){
PH7_MemObjStore(pEntry,pValue);
}else{
PH7_MemObjLoad(pEntry,pValue);
}
}else{
PH7_MemObjRelease(pValue);
}
}
/*
* Extract a node key.
*/
PH7_PRIVATE void PH7_HashmapExtractNodeKey(ph7_hashmap_node *pNode,ph7_value *pKey)
{
/* Fill with the current key */
if( pNode->iType == HASHMAP_INT_NODE ){
if( SyBlobLength(&pKey->sBlob) > 0 ){
SyBlobRelease(&pKey->sBlob);
}
pKey->x.iVal = pNode->xKey.iKey;
MemObjSetType(pKey,MEMOBJ_INT);
}else{
SyBlobReset(&pKey->sBlob);
SyBlobAppend(&pKey->sBlob,SyBlobData(&pNode->xKey.sKey),SyBlobLength(&pNode->xKey.sKey));
MemObjSetType(pKey,MEMOBJ_STRING);
}
}
#ifndef PH7_DISABLE_BUILTIN_FUNC
/*
* Store the address of nodes value in the given container.
* Refer to the [vfprintf(),vprintf(),vsprintf()] implementations
* defined in 'builtin.c' for more information.
*/
PH7_PRIVATE int PH7_HashmapValuesToSet(ph7_hashmap *pMap,SySet *pOut)
{
ph7_hashmap_node *pEntry = pMap->pFirst;
ph7_value *pValue;
sxu32 n;
/* Initialize the container */
SySetInit(pOut,&pMap->pVm->sAllocator,sizeof(ph7_value *));
for(n = 0 ; n < pMap->nEntry ; n++ ){
/* Extract node value */
pValue = HashmapExtractNodeValue(pEntry);
if( pValue ){
SySetPut(pOut,(const void *)&pValue);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Total inserted entries */
return (int)SySetUsed(pOut);
}
#endif /* PH7_DISABLE_BUILTIN_FUNC */
/*
* Merge sort.
* The merge sort implementation is based on the one found in the SQLite3 source tree.
* Status: Public domain
*/
/* Node comparison callback signature */
typedef sxi32 (*ProcNodeCmp)(ph7_hashmap_node *,ph7_hashmap_node *,void *);
/*
** Inputs:
** a: A sorted, null-terminated linked list. (May be null).
** b: A sorted, null-terminated linked list. (May be null).
** cmp: A pointer to the comparison function.
**
** Return Value:
** A pointer to the head of a sorted list containing the elements
** of both a and b.
**
** Side effects:
** The "next","prev" pointers for elements in the lists a and b are
** changed.
*/
static ph7_hashmap_node * HashmapNodeMerge(ph7_hashmap_node *pA,ph7_hashmap_node *pB,ProcNodeCmp xCmp,void *pCmpData)
{
ph7_hashmap_node result,*pTail;
/* Prevent compiler warning */
result.pNext = result.pPrev = 0;
pTail = &result;
while( pA && pB ){
if( xCmp(pA,pB,pCmpData) < 0 ){
pTail->pPrev = pA;
pA->pNext = pTail;
pTail = pA;
pA = pA->pPrev;
}else{
pTail->pPrev = pB;
pB->pNext = pTail;
pTail = pB;
pB = pB->pPrev;
}
}
if( pA ){
pTail->pPrev = pA;
pA->pNext = pTail;
}else if( pB ){
pTail->pPrev = pB;
pB->pNext = pTail;
}else{
pTail->pPrev = pTail->pNext = 0;
}
return result.pPrev;
}
/*
** Inputs:
** Map: Input hashmap
** cmp: A comparison function.
**
** Return Value:
** Sorted hashmap.
**
** Side effects:
** The "next" pointers for elements in list are changed.
*/
#define N_SORT_BUCKET 32
static sxi32 HashmapMergeSort(ph7_hashmap *pMap,ProcNodeCmp xCmp,void *pCmpData)
{
ph7_hashmap_node *a[N_SORT_BUCKET], *p,*pIn;
sxu32 i;
SyZero(a,sizeof(a));
/* Point to the first inserted entry */
pIn = pMap->pFirst;
while( pIn ){
p = pIn;
pIn = p->pPrev;
p->pPrev = 0;
for(i=0; i<N_SORT_BUCKET-1; i++){
if( a[i]==0 ){
a[i] = p;
break;
}else{
p = HashmapNodeMerge(a[i],p,xCmp,pCmpData);
a[i] = 0;
}
}
if( i==N_SORT_BUCKET-1 ){
/* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
* But that is impossible.
*/
a[i] = HashmapNodeMerge(a[i], p,xCmp,pCmpData);
}
}
p = a[0];
for(i=1; i<N_SORT_BUCKET; i++){
p = HashmapNodeMerge(p,a[i],xCmp,pCmpData);
}
p->pNext = 0;
/* Reflect the change */
pMap->pFirst = p;
/* Reset the loop cursor */
pMap->pCur = pMap->pFirst;
return SXRET_OK;
}
/*
* Node comparison callback.
* used-by: [sort(),asort(),...]
*/
static sxi32 HashmapCmpCallback1(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
ph7_value sA,sB;
sxi32 iFlags;
int rc;
if( pCmpData == 0 ){
/* Perform a standard comparison */
rc = HashmapNodeCmp(pA,pB,FALSE);
return rc;
}
iFlags = SX_PTR_TO_INT(pCmpData);
/* Duplicate node values */
PH7_MemObjInit(pA->pMap->pVm,&sA);
PH7_MemObjInit(pA->pMap->pVm,&sB);
PH7_HashmapExtractNodeValue(pA,&sA,FALSE);
PH7_HashmapExtractNodeValue(pB,&sB,FALSE);
if( iFlags == 5 ){
/* String cast */
if( (sA.iFlags & MEMOBJ_STRING) == 0 ){
PH7_MemObjToString(&sA);
}
if( (sB.iFlags & MEMOBJ_STRING) == 0 ){
PH7_MemObjToString(&sB);
}
}else{
/* Numeric cast */
PH7_MemObjToNumeric(&sA);
PH7_MemObjToNumeric(&sB);
}
/* Perform the comparison */
rc = PH7_MemObjCmp(&sA,&sB,FALSE,0);
PH7_MemObjRelease(&sA);
PH7_MemObjRelease(&sB);
return rc;
}
/*
* Node comparison callback: Compare nodes by keys only.
* used-by: [ksort()]
*/
static sxi32 HashmapCmpCallback2(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
sxi32 rc;
SXUNUSED(pCmpData); /* cc warning */
if( pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE ){
/* Perform a string comparison */
rc = SyBlobCmp(&pA->xKey.sKey,&pB->xKey.sKey);
}else{
SyString sStr;
sxi64 iA,iB;
/* Perform a numeric comparison */
if( pA->iType == HASHMAP_BLOB_NODE ){
/* Cast to 64-bit integer */
SyStringInitFromBuf(&sStr,SyBlobData(&pA->xKey.sKey),SyBlobLength(&pA->xKey.sKey));
if( sStr.nByte < 1 ){
iA = 0;
}else{
SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iA,0);
}
}else{
iA = pA->xKey.iKey;
}
if( pB->iType == HASHMAP_BLOB_NODE ){
/* Cast to 64-bit integer */
SyStringInitFromBuf(&sStr,SyBlobData(&pB->xKey.sKey),SyBlobLength(&pB->xKey.sKey));
if( sStr.nByte < 1 ){
iB = 0;
}else{
SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iB,0);
}
}else{
iB = pB->xKey.iKey;
}
rc = (sxi32)(iA-iB);
}
/* Comparison result */
return rc;
}
/*
* Node comparison callback.
* Used by: [rsort(),arsort()];
*/
static sxi32 HashmapCmpCallback3(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
ph7_value sA,sB;
sxi32 iFlags;
int rc;
if( pCmpData == 0 ){
/* Perform a standard comparison */
rc = HashmapNodeCmp(pA,pB,FALSE);
return -rc;
}
iFlags = SX_PTR_TO_INT(pCmpData);
/* Duplicate node values */
PH7_MemObjInit(pA->pMap->pVm,&sA);
PH7_MemObjInit(pA->pMap->pVm,&sB);
PH7_HashmapExtractNodeValue(pA,&sA,FALSE);
PH7_HashmapExtractNodeValue(pB,&sB,FALSE);
if( iFlags == 5 ){
/* String cast */
if( (sA.iFlags & MEMOBJ_STRING) == 0 ){
PH7_MemObjToString(&sA);
}
if( (sB.iFlags & MEMOBJ_STRING) == 0 ){
PH7_MemObjToString(&sB);
}
}else{
/* Numeric cast */
PH7_MemObjToNumeric(&sA);
PH7_MemObjToNumeric(&sB);
}
/* Perform the comparison */
rc = PH7_MemObjCmp(&sA,&sB,FALSE,0);
PH7_MemObjRelease(&sA);
PH7_MemObjRelease(&sB);
return -rc;
}
/*
* Node comparison callback: Invoke an user-defined callback for the purpose of node comparison.
* used-by: [usort(),uasort()]
*/
static sxi32 HashmapCmpCallback4(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
ph7_value sResult,*pCallback;
ph7_value *pV1,*pV2;
ph7_value *apArg[2]; /* Callback arguments */
sxi32 rc;
/* Point to the desired callback */
pCallback = (ph7_value *)pCmpData;
/* initialize the result value */
PH7_MemObjInit(pA->pMap->pVm,&sResult);
/* Extract nodes values */
pV1 = HashmapExtractNodeValue(pA);
pV2 = HashmapExtractNodeValue(pB);
apArg[0] = pV1;
apArg[1] = pV2;
/* Invoke the callback */
rc = PH7_VmCallUserFunction(pA->pMap->pVm,pCallback,2,apArg,&sResult);
if( rc != SXRET_OK ){
/* An error occured while calling user defined function [i.e: not defined] */
rc = -1; /* Set a dummy result */
}else{
/* Extract callback result */
if((sResult.iFlags & MEMOBJ_INT) == 0 ){
/* Perform an int cast */
PH7_MemObjToInteger(&sResult);
}
rc = (sxi32)sResult.x.iVal;
}
PH7_MemObjRelease(&sResult);
/* Callback result */
return rc;
}
/*
* Node comparison callback: Compare nodes by keys only.
* used-by: [krsort()]
*/
static sxi32 HashmapCmpCallback5(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
sxi32 rc;
SXUNUSED(pCmpData); /* cc warning */
if( pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE ){
/* Perform a string comparison */
rc = SyBlobCmp(&pA->xKey.sKey,&pB->xKey.sKey);
}else{
SyString sStr;
sxi64 iA,iB;
/* Perform a numeric comparison */
if( pA->iType == HASHMAP_BLOB_NODE ){
/* Cast to 64-bit integer */
SyStringInitFromBuf(&sStr,SyBlobData(&pA->xKey.sKey),SyBlobLength(&pA->xKey.sKey));
if( sStr.nByte < 1 ){
iA = 0;
}else{
SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iA,0);
}
}else{
iA = pA->xKey.iKey;
}
if( pB->iType == HASHMAP_BLOB_NODE ){
/* Cast to 64-bit integer */
SyStringInitFromBuf(&sStr,SyBlobData(&pB->xKey.sKey),SyBlobLength(&pB->xKey.sKey));
if( sStr.nByte < 1 ){
iB = 0;
}else{
SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iB,0);
}
}else{
iB = pB->xKey.iKey;
}
rc = (sxi32)(iA-iB);
}
return -rc; /* Reverse result */
}
/*
* Node comparison callback: Invoke an user-defined callback for the purpose of node comparison.
* used-by: [uksort()]
*/
static sxi32 HashmapCmpCallback6(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
ph7_value sResult,*pCallback;
ph7_value *apArg[2]; /* Callback arguments */
ph7_value sK1,sK2;
sxi32 rc;
/* Point to the desired callback */
pCallback = (ph7_value *)pCmpData;
/* initialize the result value */
PH7_MemObjInit(pA->pMap->pVm,&sResult);
PH7_MemObjInit(pA->pMap->pVm,&sK1);
PH7_MemObjInit(pA->pMap->pVm,&sK2);
/* Extract nodes keys */
PH7_HashmapExtractNodeKey(pA,&sK1);
PH7_HashmapExtractNodeKey(pB,&sK2);
apArg[0] = &sK1;
apArg[1] = &sK2;
/* Mark keys as constants */
sK1.nIdx = SXU32_HIGH;
sK2.nIdx = SXU32_HIGH;
/* Invoke the callback */
rc = PH7_VmCallUserFunction(pA->pMap->pVm,pCallback,2,apArg,&sResult);
if( rc != SXRET_OK ){
/* An error occured while calling user defined function [i.e: not defined] */
rc = -1; /* Set a dummy result */
}else{
/* Extract callback result */
if((sResult.iFlags & MEMOBJ_INT) == 0 ){
/* Perform an int cast */
PH7_MemObjToInteger(&sResult);
}
rc = (sxi32)sResult.x.iVal;
}
PH7_MemObjRelease(&sResult);
PH7_MemObjRelease(&sK1);
PH7_MemObjRelease(&sK2);
/* Callback result */
return rc;
}
/*
* Node comparison callback: Random node comparison.
* used-by: [shuffle()]
*/
static sxi32 HashmapCmpCallback7(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData)
{
sxu32 n;
SXUNUSED(pB); /* cc warning */
SXUNUSED(pCmpData);
/* Grab a random number */
n = PH7_VmRandomNum(pA->pMap->pVm);
/* if the random number is odd then the first node 'pA' is greater then
* the second node 'pB'. Otherwise the reverse is assumed.
*/
return n&1 ? 1 : -1;
}
/*
* Rehash all nodes keys after a merge-sort have been applied.
* Used by [sort(),usort() and rsort()].
*/
static void HashmapSortRehash(ph7_hashmap *pMap)
{
ph7_hashmap_node *p,*pLast;
sxu32 i;
/* Rehash all entries */
pLast = p = pMap->pFirst;
pMap->iNextIdx = 0; /* Reset the automatic index */
i = 0;
for( ;; ){
if( i >= pMap->nEntry ){
pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */
break;
}
if( p->iType == HASHMAP_BLOB_NODE ){
/* Do not maintain index association as requested by the PHP specification */
SyBlobRelease(&p->xKey.sKey);
/* Change key type */
p->iType = HASHMAP_INT_NODE;
}
HashmapRehashIntNode(p);
/* Point to the next entry */
i++;
pLast = p;
p = p->pPrev; /* Reverse link */
}
}
/*
* Array functions implementation.
* Authors:
* Symisc Systems,devel@symisc.net.
* Copyright (C) Symisc Systems,http://ph7.symisc.net
* Status:
* Stable.
*/
/*
* bool sort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*
*/
static int ph7_hashmap_sort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback1,SX_INT_TO_PTR(iCmpFlags));
/* Rehash [Do not maintain index association as requested by the PHP specification] */
HashmapSortRehash(pMap);
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool asort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array and maintain index association.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_asort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback1,SX_INT_TO_PTR(iCmpFlags));
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool arsort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array in reverse order and maintain index association.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_arsort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback3,SX_INT_TO_PTR(iCmpFlags));
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool ksort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array by key.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_ksort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback2,SX_INT_TO_PTR(iCmpFlags));
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool krsort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array by key in reverse order.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_krsort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback5,SX_INT_TO_PTR(iCmpFlags));
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool rsort(array &$array[,int $sort_flags = SORT_REGULAR ] )
* Sort an array in reverse order.
* Parameters
* $array
* The input array.
* $sort_flags
* The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
* Sorting type flags:
* SORT_REGULAR - compare items normally (don't change types)
* SORT_NUMERIC - compare items numerically
* SORT_STRING - compare items as strings
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_rsort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
sxi32 iCmpFlags = 0;
if( nArg > 1 ){
/* Extract comparison flags */
iCmpFlags = ph7_value_to_int(apArg[1]);
if( iCmpFlags == 3 /* SORT_REGULAR */ ){
iCmpFlags = 0; /* Standard comparison */
}
}
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback3,SX_INT_TO_PTR(iCmpFlags));
/* Rehash [Do not maintain index association as requested by the PHP specification] */
HashmapSortRehash(pMap);
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool usort(array &$array,callable $cmp_function)
* Sort an array by values using a user-defined comparison function.
* Parameters
* $array
* The input array.
* $cmp_function
* The comparison function must return an integer less than, equal to, or greater
* than zero if the first argument is considered to be respectively less than, equal
* to, or greater than the second.
* int callback ( mixed $a, mixed $b )
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_usort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
ph7_value *pCallback = 0;
ProcNodeCmp xCmp;
xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */
if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){
/* Point to the desired callback */
pCallback = apArg[1];
}else{
/* Use the default comparison function */
xCmp = HashmapCmpCallback1;
}
/* Do the merge sort */
HashmapMergeSort(pMap,xCmp,pCallback);
/* Rehash [Do not maintain index association as requested by the PHP specification] */
HashmapSortRehash(pMap);
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool uasort(array &$array,callable $cmp_function)
* Sort an array by values using a user-defined comparison function
* and maintain index association.
* Parameters
* $array
* The input array.
* $cmp_function
* The comparison function must return an integer less than, equal to, or greater
* than zero if the first argument is considered to be respectively less than, equal
* to, or greater than the second.
* int callback ( mixed $a, mixed $b )
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_uasort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
ph7_value *pCallback = 0;
ProcNodeCmp xCmp;
xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */
if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){
/* Point to the desired callback */
pCallback = apArg[1];
}else{
/* Use the default comparison function */
xCmp = HashmapCmpCallback1;
}
/* Do the merge sort */
HashmapMergeSort(pMap,xCmp,pCallback);
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool uksort(array &$array,callable $cmp_function)
* Sort an array by keys using a user-defined comparison
* function and maintain index association.
* Parameters
* $array
* The input array.
* $cmp_function
* The comparison function must return an integer less than, equal to, or greater
* than zero if the first argument is considered to be respectively less than, equal
* to, or greater than the second.
* int callback ( mixed $a, mixed $b )
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_uksort(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
ph7_value *pCallback = 0;
ProcNodeCmp xCmp;
xCmp = HashmapCmpCallback6; /* User-defined function as the comparison callback */
if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){
/* Point to the desired callback */
pCallback = apArg[1];
}else{
/* Use the default comparison function */
xCmp = HashmapCmpCallback2;
}
/* Do the merge sort */
HashmapMergeSort(pMap,xCmp,pCallback);
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* bool shuffle(array &$array)
* shuffles (randomizes the order of the elements in) an array.
* Parameters
* $array
* The input array.
* Return
* TRUE on success or FALSE on failure.
*
*/
static int ph7_hashmap_shuffle(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){
/* Missing/Invalid arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry > 1 ){
/* Do the merge sort */
HashmapMergeSort(pMap,HashmapCmpCallback7,0);
/* Fix the last link broken by the merge */
while(pMap->pLast->pPrev){
pMap->pLast = pMap->pLast->pPrev;
}
}
/* All done,return TRUE */
ph7_result_bool(pCtx,1);
return PH7_OK;
}
/*
* int count(array $var [, int $mode = COUNT_NORMAL ])
* Count all elements in an array, or something in an object.
* Parameters
* $var
* The array or the object.
* $mode
* If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count()
* will recursively count the array. This is particularly useful for counting
* all the elements of a multidimensional array. count() does not detect infinite
* recursion.
* Return
* Returns the number of elements in the array.
*/
static int ph7_hashmap_count(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
int bRecursive = FALSE;
sxi64 iCount;
if( nArg < 1 ){
/* Missing arguments,return 0 */
ph7_result_int(pCtx,0);
return PH7_OK;
}
if( !ph7_value_is_array(apArg[0]) ){
/* TICKET 1433-19: Handle objects */
int res = !ph7_value_is_null(apArg[0]);
ph7_result_int(pCtx,res);
return PH7_OK;
}
if( nArg > 1 ){
/* Recursive count? */
bRecursive = ph7_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */;
}
/* Count */
iCount = HashmapCount((ph7_hashmap *)apArg[0]->x.pOther,bRecursive,0);
ph7_result_int64(pCtx,iCount);
return PH7_OK;
}
/*
* bool array_key_exists(value $key,array $search)
* Checks if the given key or index exists in the array.
* Parameters
* $key
* Value to check.
* $search
* An array with keys to check.
* Return
* TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_key_exists(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
sxi32 rc;
if( nArg < 2 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[1]) ){
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Perform the lookup */
rc = PH7_HashmapLookup((ph7_hashmap *)apArg[1]->x.pOther,apArg[0],0);
/* lookup result */
ph7_result_bool(pCtx,rc == SXRET_OK ? 1 : 0);
return PH7_OK;
}
/*
* value array_pop(array $array)
* POP the last inserted element from the array.
* Parameter
* The array to get the value from.
* Return
* Poped value or NULL on failure.
*/
static int ph7_hashmap_pop(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
if( nArg < 1 ){
/* Missing arguments,return null */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return null */
ph7_result_null(pCtx);
return PH7_OK;
}
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry < 1 ){
/* Noting to pop,return NULL */
ph7_result_null(pCtx);
}else{
ph7_hashmap_node *pLast = pMap->pLast;
ph7_value *pObj;
pObj = HashmapExtractNodeValue(pLast);
if( pObj ){
/* Node value */
ph7_result_value(pCtx,pObj);
/* Unlink the node */
PH7_HashmapUnlinkNode(pLast,TRUE);
}else{
ph7_result_null(pCtx);
}
/* Reset the cursor */
pMap->pCur = pMap->pFirst;
}
return PH7_OK;
}
/*
* int array_push($array,$var,...)
* Push one or more elements onto the end of array. (Stack insertion)
* Parameters
* array
* The input array.
* var
* On or more value to push.
* Return
* New array count (including old items).
*/
static int ph7_hashmap_push(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
sxi32 rc;
int i;
if( nArg < 1 ){
/* Missing arguments,return 0 */
ph7_result_int(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return 0 */
ph7_result_int(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Start pushing given values */
for( i = 1 ; i < nArg ; ++i ){
rc = PH7_HashmapInsert(pMap,0,apArg[i]);
if( rc != SXRET_OK ){
break;
}
}
/* Return the new count */
ph7_result_int64(pCtx,(sxi64)pMap->nEntry);
return PH7_OK;
}
/*
* value array_shift(array $array)
* Shift an element off the beginning of array.
* Parameter
* The array to get the value from.
* Return
* Shifted value or NULL on failure.
*/
static int ph7_hashmap_shift(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
if( nArg < 1 ){
/* Missing arguments,return null */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return null */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry < 1 ){
/* Empty hashmap,return NULL */
ph7_result_null(pCtx);
}else{
ph7_hashmap_node *pEntry = pMap->pFirst;
ph7_value *pObj;
sxu32 n;
pObj = HashmapExtractNodeValue(pEntry);
if( pObj ){
/* Node value */
ph7_result_value(pCtx,pObj);
/* Unlink the first node */
PH7_HashmapUnlinkNode(pEntry,TRUE);
}else{
ph7_result_null(pCtx);
}
/* Rehash all int keys */
n = pMap->nEntry;
pEntry = pMap->pFirst;
pMap->iNextIdx = 0; /* Reset the automatic index */
for(;;){
if( n < 1 ){
break;
}
if( pEntry->iType == HASHMAP_INT_NODE ){
HashmapRehashIntNode(pEntry);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Reset the cursor */
pMap->pCur = pMap->pFirst;
}
return PH7_OK;
}
/*
* Extract the node cursor value.
*/
static sxi32 HashmapCurrentValue(ph7_context *pCtx,ph7_hashmap *pMap,int iDirection)
{
ph7_hashmap_node *pCur = pMap->pCur;
ph7_value *pVal;
if( pCur == 0 ){
/* Cursor does not point to anything,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
if( iDirection != 0 ){
if( iDirection > 0 ){
/* Point to the next entry */
pMap->pCur = pCur->pPrev; /* Reverse link */
pCur = pMap->pCur;
}else{
/* Point to the previous entry */
pMap->pCur = pCur->pNext; /* Reverse link */
pCur = pMap->pCur;
}
if( pCur == 0 ){
/* End of input reached,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
}
/* Point to the desired element */
pVal = HashmapExtractNodeValue(pCur);
if( pVal ){
ph7_result_value(pCtx,pVal);
}else{
ph7_result_bool(pCtx,0);
}
return PH7_OK;
}
/*
* value current(array $array)
* Return the current element in an array.
* Parameter
* $input: The input array.
* Return
* The current() function simply returns the value of the array element that's currently
* being pointed to by the internal pointer. It does not move the pointer in any way.
* If the internal pointer points beyond the end of the elements list or the array
* is empty, current() returns FALSE.
*/
static int ph7_hashmap_current(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
if( nArg < 1 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,0);
return PH7_OK;
}
/*
* value next(array $input)
* Advance the internal array pointer of an array.
* Parameter
* $input: The input array.
* Return
* next() behaves like current(), with one difference. It advances the internal array
* pointer one place forward before returning the element value. That means it returns
* the next array value and advances the internal array pointer by one.
*/
static int ph7_hashmap_next(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
if( nArg < 1 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,1);
return PH7_OK;
}
/*
* value prev(array $input)
* Rewind the internal array pointer.
* Parameter
* $input: The input array.
* Return
* Returns the array value in the previous place that's pointed
* to by the internal array pointer, or FALSE if there are no more
* elements.
*/
static int ph7_hashmap_prev(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
if( nArg < 1 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,-1);
return PH7_OK;
}
/*
* value end(array $input)
* Set the internal pointer of an array to its last element.
* Parameter
* $input: The input array.
* Return
* Returns the value of the last element or FALSE for empty array.
*/
static int ph7_hashmap_end(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
if( nArg < 1 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]) ){
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Point to the last node */
pMap->pCur = pMap->pLast;
/* Return the last node value */
HashmapCurrentValue(&(*pCtx),pMap,0);
return PH7_OK;
}
/*
* value reset(array $array )
* Set the internal pointer of an array to its first element.
* Parameter
* $input: The input array.
* Return
* Returns the value of the first array element,or FALSE if the array is empty.
*/
static int ph7_hashmap_reset(ph7_context *pCtx,int nArg,ph7_value **apArg)
{
ph7_hashmap *pMap;
if( nArg < 1 ){
/* Missing arguments,return FALSE */
ph7_result_bool(pCtx,0);
return PH7_OK;
}
/* Make sure we are dealing with a valid hashmap */
if( !ph7_value_is_array(apArg[0]<