Aer/engine/hashmap.c
belliash 2fb123872f
All checks were successful
The build was successful.
Typecas array only if it is of different type.
2019-04-02 09:05:27 +02:00

5510 lines
158 KiB
C

/*
* 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 */
/*
* 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 bRecursive 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 */
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) {
/* 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) {
ph7_hashmap_node *pNode;
sxu32 nIdx;
sxu32 nHash;
sxi32 rc;
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;
/* Hash the key */
nHash = pMap->xIntHash(iKey);
/* Allocate a new int node */
pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx);
if(pNode == 0) {
return SXERR_MEM;
}
/* 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) {
ph7_hashmap_node *pNode;
sxu32 nHash;
sxu32 nIdx;
sxi32 rc;
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;
/* 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;
}
/* 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 {
/* Release the entry */
PH7_MemObjRelease(pElem);
}
}
return SXRET_OK;
}
/* Perform a blob-key insertion */
rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &(*pVal), 0);
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 {
/* Release the entry */
PH7_MemObjRelease(pElem);
}
}
return SXRET_OK;
}
/* Perform a 64-bit-int-key insertion */
rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal), 0);
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, &(*pVal), 0);
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);
}
} else {
/* Blob key */
rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey),
SyBlobLength(&pNode->xKey.sKey), pObj, 0);
}
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
* documentation.
*/
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 scenario.
*/
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;
sxi32 iF2 = pNeedle->iFlags;
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);
}
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);
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);
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 */
};
SyString *pFile;
sxi32 rc;
sxu32 n;
/* Install superglobals */
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;
/* 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);
/* 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) {
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 */
) {
return HashmapInsert(&(*pMap), &(*pKey), &(*pVal));
}
/*
* 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);
}
}
/*
* 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);
}
/*
* 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 ocurred 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])) {
ph7_result_int(pCtx, 1);
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
* Popped 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])) {
/* 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 first node */
pMap->pCur = pMap->pFirst;
/* Return the last node value if available */
HashmapCurrentValue(&(*pCtx), pMap, 0);
return PH7_OK;
}
/*
* value key(array $array)
* Fetch a key from an array
* Parameter
* $input
* The input array.
* Return
* The key() function simply returns the key 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, key() returns NULL.
*/
static int ph7_hashmap_simple_key(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pCur;
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;
pCur = pMap->pCur;
if(pCur == 0) {
/* Cursor does not point to anything,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(pCur->iType == HASHMAP_INT_NODE) {
/* Key is integer */
ph7_result_int64(pCtx, pCur->xKey.iKey);
} else {
/* Key is blob */
ph7_result_string(pCtx,
(const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey));
}
return PH7_OK;
}
/*
* array each(array $input)
* Return the current key and value pair from an array and advance the array cursor.
* Parameter
* $input
* The input array.
* Return
* Returns the current key and value pair from the array array. This pair is returned
* in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key
* contain the key name of the array element, and 1 and value contain the data.
* If the internal pointer for the array points past the end of the array contents
* each() returns FALSE.
*/
static int ph7_hashmap_each(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pCur;
ph7_hashmap *pMap;
ph7_value *pArray;
ph7_value *pVal;
ph7_value sKey;
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 that describe the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if(pMap->pCur == 0) {
/* Cursor does not point to anything,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
pCur = pMap->pCur;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
pVal = HashmapExtractNodeValue(pCur);
/* Insert the current value */
ph7_array_add_intkey_elem(pArray, 1, pVal);
ph7_array_add_strkey_elem(pArray, "value", pVal);
/* Make the key */
if(pCur->iType == HASHMAP_INT_NODE) {
PH7_MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey);
} else {
PH7_MemObjInitFromString(pMap->pVm, &sKey, 0);
PH7_MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey));
}
/* Insert the current key */
ph7_array_add_intkey_elem(pArray, 0, &sKey);
ph7_array_add_strkey_elem(pArray, "key", &sKey);
PH7_MemObjRelease(&sKey);
/* Advance the cursor */
pMap->pCur = pCur->pPrev; /* Reverse link */
/* Return the current entry */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array range(int $start,int $limit,int $step)
* Create an array containing a range of elements
* Parameter
* start
* First value of the sequence.
* limit
* The sequence is ended upon reaching the limit value.
* step
* If a step value is given, it will be used as the increment between elements in the sequence.
* step should be given as a positive number. If not specified, step will default to 1.
* Return
* An array of elements from start to limit, inclusive.
* NOTE:
* Only 32/64 bit integer key is supported.
*/
static int ph7_hashmap_range(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pValue, *pArray;
sxi64 iOfft, iLimit;
int iStep = 1;
iOfft = iLimit = 0; /* cc -O6 */
if(nArg > 0) {
/* Extract the offset */
iOfft = ph7_value_to_int64(apArg[0]);
if(nArg > 1) {
/* Extract the limit */
iLimit = ph7_value_to_int64(apArg[1]);
if(nArg > 2) {
/* Extract the increment */
iStep = ph7_value_to_int(apArg[2]);
if(iStep < 1) {
/* Only positive number are allowed */
iStep = 1;
}
}
}
}
/* Element container */
pValue = ph7_context_new_scalar(pCtx);
/* Create the new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Start filling */
while(iOfft <= iLimit) {
ph7_value_int64(pValue, iOfft);
/* Perform the insertion */
ph7_array_add_elem(pArray, 0/* Automatic index assign*/, pValue);
/* Increment */
iOfft += iStep;
}
/* Return the new array */
ph7_result_value(pCtx, pArray);
/* Dont' worry about freeing 'pValue',it will be released automatically
* by the virtual machine as soon we return from this foreign function.
*/
return PH7_OK;
}
/*
* array array_values(array $input)
* Returns all the values from the input array and indexes numerically the array.
* Parameters
* input: The input array.
* Return
* An indexed array of values or NULL on failure.
*/
static int ph7_hashmap_values(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pNode;
ph7_hashmap *pMap;
ph7_value *pArray;
ph7_value *pObj;
sxu32 n;
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 that describe the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Perform the requested operation */
pNode = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; ++n) {
pObj = HashmapExtractNodeValue(pNode);
if(pObj) {
/* perform the insertion */
ph7_array_add_elem(pArray, 0/* Automatic index assign */, pObj);
}
/* Point to the next entry */
pNode = pNode->pPrev; /* Reverse link */
}
/* return the new array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_keys(array $input [, val $search_value [, bool $strict = false ]] )
* Return all the keys or a subset of the keys of an array.
* Parameters
* $input
* An array containing keys to return.
* $search_value
* If specified, then only keys containing these values are returned.
* $strict
* Determines if strict comparison (===) should be used during the search.
* Return
* An array of all the keys in input or NULL on failure.
*/
static int ph7_hashmap_keys(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pNode;
ph7_hashmap *pMap;
ph7_value *pArray;
ph7_value sObj;
ph7_value sVal;
SyString sKey;
int bStrict;
sxi32 rc;
sxu32 n;
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 input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
bStrict = FALSE;
if(nArg > 2 && ph7_value_is_bool(apArg[2])) {
bStrict = ph7_value_to_bool(apArg[2]);
}
/* Perform the requested operation */
pNode = pMap->pFirst;
PH7_MemObjInit(pMap->pVm, &sVal);
for(n = 0 ; n < pMap->nEntry ; ++n) {
if(pNode->iType == HASHMAP_INT_NODE) {
PH7_MemObjInitFromInt(pMap->pVm, &sObj, pNode->xKey.iKey);
} else {
SyStringInitFromBuf(&sKey, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey));
PH7_MemObjInitFromString(pMap->pVm, &sObj, &sKey);
}
rc = 0;
if(nArg > 1) {
ph7_value *pValue = HashmapExtractNodeValue(pNode);
if(pValue) {
PH7_MemObjLoad(pValue, &sVal);
/* Filter key */
rc = ph7_value_compare(&sVal, apArg[1], bStrict);
PH7_MemObjRelease(pValue);
}
}
if(rc == 0) {
/* Perform the insertion */
ph7_array_add_elem(pArray, 0, &sObj);
}
PH7_MemObjRelease(&sObj);
/* Point to the next entry */
pNode = pNode->pPrev; /* Reverse link */
}
/* return the new array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* bool array_same(array $arr1,array $arr2)
* Return TRUE if the given arrays are the same instance.
* This function is useful under PH7 since arrays are passed
* by reference unlike the zend engine which use pass by values.
* Parameters
* $arr1
* First array
* $arr2
* Second array
* Return
* TRUE if the arrays are the same instance.FALSE otherwise.
* Note
* This function is a symisc eXtension.
*/
static int ph7_hashmap_same(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *p1, *p2;
int rc;
if(nArg < 2 || !ph7_value_is_array(apArg[0]) || !ph7_value_is_array(apArg[1])) {
/* Missing or invalid arguments,return FALSE*/
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/* Point to the hashmaps */
p1 = (ph7_hashmap *)apArg[0]->x.pOther;
p2 = (ph7_hashmap *)apArg[1]->x.pOther;
rc = (p1 == p2);
/* Same instance? */
ph7_result_bool(pCtx, rc);
return PH7_OK;
}
/*
* array array_merge(array $array1,...)
* Merge one or more arrays.
* Parameters
* $array1
* Initial array to merge.
* ...
* More array to merge.
* Return
* The resulting array.
*/
static int ph7_hashmap_merge(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap, *pSrc;
ph7_value *pArray;
int i;
if(nArg < 1) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)pArray->x.pOther;
/* Start merging */
for(i = 0 ; i < nArg ; i++) {
/* Make sure we are dealing with a valid hashmap */
if(!ph7_value_is_array(apArg[i])) {
/* Insert scalar value */
ph7_array_add_elem(pArray, 0, apArg[i]);
} else {
pSrc = (ph7_hashmap *)apArg[i]->x.pOther;
/* Merge the two hashmaps */
HashmapMerge(pSrc, pMap);
}
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_copy(array $source)
* Make a blind copy of the target array.
* Parameters
* $source
* Target array
* Return
* Copy of the target array on success.NULL otherwise.
* Note
* This function is a symisc eXtension.
*/
static int ph7_hashmap_copy(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
ph7_value *pArray;
if(nArg < 1) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)pArray->x.pOther;
if(ph7_value_is_array(apArg[0])) {
/* Point to the internal representation of the source */
ph7_hashmap *pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the copy */
PH7_HashmapDup(pSrc, pMap);
} else {
/* Simple insertion */
PH7_HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]);
}
/* Return the duplicated array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* bool array_erase(array $source)
* Remove all elements from a given array.
* Parameters
* $source
* Target array
* Return
* TRUE on success.FALSE otherwise.
* Note
* This function is a symisc eXtension.
*/
static int ph7_hashmap_erase(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
if(nArg < 1) {
/* Missing arguments */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/* Point to the target hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Erase */
PH7_HashmapRelease(pMap, FALSE);
return PH7_OK;
}
/*
* array array_slice(array $array,int $offset [,int $length [, bool $preserve_keys = false ]])
* Extract a slice of the array.
* Parameters
* $array
* The input array.
* $offset
* If offset is non-negative, the sequence will start at that offset in the array.
* If offset is negative, the sequence will start that far from the end of the array.
* $length (optional)
* If length is given and is positive, then the sequence will have that many elements
* in it. If length is given and is negative then the sequence will stop that many
* elements from the end of the array. If it is omitted, then the sequence will have
* everything from offset up until the end of the array.
* $preserve_keys (optional)
* Note that array_slice() will reorder and reset the array indices by default.
* You can change this behaviour by setting preserve_keys to TRUE.
* Return
* The new slice.
*/
static int ph7_hashmap_slice(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap, *pSrc;
ph7_hashmap_node *pCur;
ph7_value *pArray;
int iLength, iOfft;
int bPreserve;
sxi32 rc;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Missing/Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point the internal representation of the target array */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
bPreserve = FALSE;
/* Get the offset */
iOfft = ph7_value_to_int(apArg[1]);
if(iOfft < 0) {
iOfft = (int)pSrc->nEntry + iOfft;
}
if(iOfft < 0 || iOfft > (int)pSrc->nEntry) {
/* Invalid offset,return the last entry */
iOfft = (int)pSrc->nEntry - 1;
}
/* Get the length */
iLength = (int)pSrc->nEntry - iOfft;
if(nArg > 2) {
iLength = ph7_value_to_int(apArg[2]);
if(iLength < 0) {
iLength = ((int)pSrc->nEntry + iLength) - iOfft;
}
if(iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry) {
iLength = (int)pSrc->nEntry - iOfft;
}
if(nArg > 3 && ph7_value_is_bool(apArg[3])) {
bPreserve = ph7_value_to_bool(apArg[3]);
}
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
if(iLength < 1) {
/* Don't bother processing,return the empty array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/* Point to the desired entry */
pCur = pSrc->pFirst;
for(;;) {
if(iOfft < 1) {
break;
}
/* Point to the next entry */
pCur = pCur->pPrev; /* Reverse link */
iOfft--;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)pArray->x.pOther;
for(;;) {
if(iLength < 1) {
break;
}
rc = HashmapInsertNode(pMap, pCur, bPreserve);
if(rc != SXRET_OK) {
break;
}
/* Point to the next entry */
pCur = pCur->pPrev; /* Reverse link */
iLength--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_splice(array $array,int $offset [,int $length [,value $replacement ]])
* Remove a portion of the array and replace it with something else.
* Parameters
* $array
* The input array.
* $offset
* If offset is positive then the start of removed portion is at that offset from
* the beginning of the input array. If offset is negative then it starts that far
* from the end of the input array.
* $length (optional)
* If length is omitted, removes everything from offset to the end of the array.
* If length is specified and is positive, then that many elements will be removed.
* If length is specified and is negative then the end of the removed portion will
* be that many elements from the end of the array.
* $replacement (optional)
* If replacement array is specified, then the removed elements are replaced
* with elements from this array.
* If offset and length are such that nothing is removed, then the elements
* from the replacement array are inserted in the place specified by the offset.
* Note that keys in replacement array are not preserved.
* If replacement is just one element it is not necessary to put array() around
* it, unless the element is an array itself, an object or NULL.
* Return
* A new array consisting of the extracted elements.
*/
static int ph7_hashmap_splice(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pCur, *pPrev, *pRnode;
ph7_value *pArray, *pRvalue, *pOld;
ph7_hashmap *pMap, *pSrc, *pRep;
int iLength, iOfft;
sxi32 rc;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Missing/Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point the internal representation of the target array */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Get the offset */
iOfft = ph7_value_to_int(apArg[1]);
if(iOfft < 0) {
iOfft = (int)pSrc->nEntry + iOfft;
}
if(iOfft < 0 || iOfft > (int)pSrc->nEntry) {
/* Invalid offset,remove the last entry */
iOfft = (int)pSrc->nEntry - 1;
}
/* Get the length */
iLength = (int)pSrc->nEntry - iOfft;
if(nArg > 2) {
iLength = ph7_value_to_int(apArg[2]);
if(iLength < 0) {
iLength = ((int)pSrc->nEntry + iLength) - iOfft;
}
if(iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry) {
iLength = (int)pSrc->nEntry - iOfft;
}
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
if(iLength < 1) {
/* Don't bother processing,return the empty array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/* Point to the desired entry */
pCur = pSrc->pFirst;
for(;;) {
if(iOfft < 1) {
break;
}
/* Point to the next entry */
pCur = pCur->pPrev; /* Reverse link */
iOfft--;
}
pRep = 0;
if(nArg > 3) {
if(!ph7_value_is_array(apArg[3])) {
/* Perform an array cast */
PH7_MemObjToHashmap(apArg[3]);
if(ph7_value_is_array(apArg[3])) {
pRep = (ph7_hashmap *)apArg[3]->x.pOther;
}
} else {
pRep = (ph7_hashmap *)apArg[3]->x.pOther;
}
if(pRep) {
/* Reset the loop cursor */
pRep->pCur = pRep->pFirst;
}
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)pArray->x.pOther;
for(;;) {
if(iLength < 1) {
break;
}
pPrev = pCur->pPrev;
rc = HashmapInsertNode(pMap, pCur, FALSE);
if(pRep && (pRnode = PH7_HashmapGetNextEntry(pRep)) != 0) {
/* Extract node value */
pRvalue = HashmapExtractNodeValue(pRnode);
/* Replace the old node */
pOld = HashmapExtractNodeValue(pCur);
if(pRvalue && pOld) {
PH7_MemObjStore(pRvalue, pOld);
}
} else {
/* Unlink the node from the source hashmap */
PH7_HashmapUnlinkNode(pCur, TRUE);
}
if(rc != SXRET_OK) {
break;
}
/* Point to the next entry */
pCur = pPrev; /* Reverse link */
iLength--;
}
if(pRep) {
while((pRnode = PH7_HashmapGetNextEntry(pRep)) != 0) {
HashmapInsertNode(pSrc, pRnode, FALSE);
}
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* bool in_array(value $needle,array $haystack[,bool $strict = FALSE ])
* Checks if a value exists in an array.
* Parameters
* $needle
* The searched value.
* Note:
* If needle is a string, the comparison is done in a case-sensitive manner.
* $haystack
* The target array.
* $strict
* If the third parameter strict is set to TRUE then the in_array() function
* will also check the types of the needle in the haystack.
*/
static int ph7_hashmap_in_array(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pNeedle;
int bStrict;
int rc;
if(nArg < 2) {
/* Missing argument,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
pNeedle = apArg[0];
bStrict = 0;
if(nArg > 2) {
bStrict = ph7_value_to_bool(apArg[2]);
}
if(!ph7_value_is_array(apArg[1])) {
/* haystack must be an array,perform a standard comparison */
rc = ph7_value_compare(pNeedle, apArg[1], bStrict);
/* Set the comparison result */
ph7_result_bool(pCtx, rc == 0);
return PH7_OK;
}
/* Perform the lookup */
rc = HashmapFindValue((ph7_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict);
/* Lookup result */
ph7_result_bool(pCtx, rc == SXRET_OK);
return PH7_OK;
}
/*
* value array_search(value $needle,array $haystack[,bool $strict = false ])
* Searches the array for a given value and returns the corresponding key if successful.
* Parameters
* $needle
* The searched value.
* $haystack
* The array.
* $strict
* If the third parameter strict is set to TRUE then the array_search() function
* will search for identical elements in the haystack. This means it will also check
* the types of the needle in the haystack, and objects must be the same instance.
* Return
* Returns the key for needle if it is found in the array, FALSE otherwise.
*/
static int ph7_hashmap_search(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_value *pVal, sNeedle;
ph7_hashmap *pMap;
ph7_value sVal;
int bStrict;
sxu32 n;
int rc;
if(nArg < 2) {
/* Missing argument,return FALSE*/
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
bStrict = FALSE;
if(!ph7_value_is_array(apArg[1])) {
/* hasystack must be an array,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
if(nArg > 2 && ph7_value_is_bool(apArg[2])) {
bStrict = ph7_value_to_bool(apArg[2]);
}
/* Point to the internal representation of the internal hashmap */
pMap = (ph7_hashmap *)apArg[1]->x.pOther;
/* Perform a linear search since we cannot sort the hashmap based on values */
PH7_MemObjInit(pMap->pVm, &sVal);
PH7_MemObjInit(pMap->pVm, &sNeedle);
pEntry = pMap->pFirst;
n = pMap->nEntry;
for(;;) {
if(!n) {
break;
}
/* Extract node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
/* Make a copy of the current values since the comparison routine
* can change their type.
*/
PH7_MemObjLoad(pVal, &sVal);
PH7_MemObjLoad(apArg[0], &sNeedle);
rc = PH7_MemObjCmp(&sNeedle, &sVal, bStrict, 0);
PH7_MemObjRelease(&sVal);
PH7_MemObjRelease(&sNeedle);
if(rc == 0) {
/* Match found,return key */
if(pEntry->iType == HASHMAP_INT_NODE) {
/* INT key */
ph7_result_int64(pCtx, pEntry->xKey.iKey);
} else {
SyBlob *pKey = &pEntry->xKey.sKey;
/* Blob key */
ph7_result_string(pCtx, (const char *)SyBlobData(pKey), (int)SyBlobLength(pKey));
}
return PH7_OK;
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* No such value,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/*
* array array_diff(array $array1,array $array2,...)
* Computes the difference of arrays.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an array containing all the entries from array1 that
* are not present in any of the other arrays.
*/
static int ph7_hashmap_diff(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the diff */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform the lookup */
rc = HashmapFindValue(pMap, pVal, 0, TRUE);
if(rc == SXRET_OK) {
/* Value exist */
break;
}
}
if(i >= nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_udiff(array $array1,array $array2,...,$callback)
* Computes the difference of arrays by using a callback function for data comparison.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against.
* $callback
* The callback comparison 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
* Returns an array containing all the entries from array1 that
* are not present in any of the other arrays.
*/
static int ph7_hashmap_udiff(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pCallback;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Missing/Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the callback */
pCallback = apArg[nArg - 1];
if(nArg == 2) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the diff */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
for(i = 1 ; i < nArg - 1; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform the lookup */
rc = HashmapFindValueByCallback(pMap, pVal, pCallback, 0);
if(rc == SXRET_OK) {
/* Value exist */
break;
}
}
if(i >= (nArg - 1)) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_diff_assoc(array $array1,array $array2,...)
* Computes the difference of arrays with additional index check.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an array containing all the entries from array1 that
* are not present in any of the other arrays.
*/
static int ph7_hashmap_diff_assoc(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pN1, *pN2, *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the diff */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
pN1 = pN2 = 0;
for(;;) {
if(n < 1) {
break;
}
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform a key lookup first */
if(pEntry->iType == HASHMAP_INT_NODE) {
rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1);
} else {
rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1);
}
if(rc != SXRET_OK) {
/* No such key,break immediately */
break;
}
/* Extract node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
/* Perform the lookup */
rc = HashmapFindValue(pMap, pVal, &pN2, TRUE);
if(rc != SXRET_OK || pN1 != pN2) {
/* Value does not exist */
break;
}
}
}
if(i < nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_diff_uassoc(array $array1,array $array2,...,callback $key_compare_func)
* Computes the difference of arrays with additional index check which is performed
* by a user supplied callback function.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against.
* $key_compare_func
* Callback function to use. The callback 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.
* Return
* Returns an array containing all the entries from array1 that
* are not present in any of the other arrays.
*/
static int ph7_hashmap_diff_uassoc(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pN1, *pN2, *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pCallback;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Missing/Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the callback */
pCallback = apArg[nArg - 1];
if(nArg == 2) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the diff */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
pN1 = pN2 = 0; /* cc warning */
for(;;) {
if(n < 1) {
break;
}
for(i = 1 ; i < nArg - 1; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform a key lookup first */
if(pEntry->iType == HASHMAP_INT_NODE) {
rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1);
} else {
rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1);
}
if(rc != SXRET_OK) {
/* No such key,break immediately */
break;
}
/* Extract node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
/* Invoke the user callback */
rc = HashmapFindValueByCallback(pMap, pVal, pCallback, &pN2);
if(rc != SXRET_OK || pN1 != pN2) {
/* Value does not exist */
break;
}
}
}
if(i < (nArg - 1)) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_diff_key(array $array1 ,array $array2,...)
* Computes the difference of arrays using keys for comparison.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an array containing all the entries from array1 whose keys are not present
* in any of the other arrays.
* Note that NULL is returned on failure.
*/
static int ph7_hashmap_diff_key(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the main hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perfrom the diff */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
if(pEntry->iType == HASHMAP_BLOB_NODE) {
SyBlob *pKey = &pEntry->xKey.sKey;
/* Blob lookup */
rc = HashmapLookupBlobKey(pMap, SyBlobData(pKey), SyBlobLength(pKey), 0);
} else {
/* Int lookup */
rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, 0);
}
if(rc == SXRET_OK) {
/* Key exists,break immediately */
break;
}
}
if(i >= nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_intersect(array $array1 ,array $array2,...)
* Computes the intersection of arrays.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an array containing all of the values in array1 whose values exist
* in all of the parameters. .
* Note that NULL is returned on failure.
*/
static int ph7_hashmap_intersect(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the intersection */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform the lookup */
rc = HashmapFindValue(pMap, pVal, 0, TRUE);
if(rc != SXRET_OK) {
/* Value does not exist */
break;
}
}
if(i >= nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_intersect_assoc(array $array1 ,array $array2,...)
* Computes the intersection of arrays.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an array containing all of the values in array1 whose values exist
* in all of the parameters. .
* Note that NULL is returned on failure.
*/
static int ph7_hashmap_intersect_assoc(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry, *pN1, *pN2;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the intersection */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
pN1 = pN2 = 0; /* cc warning */
for(;;) {
if(n < 1) {
break;
}
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform a key lookup first */
if(pEntry->iType == HASHMAP_INT_NODE) {
rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1);
} else {
rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1);
}
if(rc != SXRET_OK) {
/* No such key,break immediately */
break;
}
/* Perform the lookup */
rc = HashmapFindValue(pMap, pVal, &pN2, TRUE);
if(rc != SXRET_OK || pN1 != pN2) {
/* Value does not exist */
break;
}
}
if(i >= nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_intersect_key(array $array1 ,array $array2,...)
* Computes the intersection of arrays using keys for comparison.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* Return
* Returns an associative array containing all the entries of array1 which
* have keys that are present in all arguments.
* Note that NULL is returned on failure.
*/
static int ph7_hashmap_intersect_key(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pArray;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg == 1) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the main hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perfrom the intersection */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
for(i = 1 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
if(pEntry->iType == HASHMAP_BLOB_NODE) {
SyBlob *pKey = &pEntry->xKey.sKey;
/* Blob lookup */
rc = HashmapLookupBlobKey(pMap, SyBlobData(pKey), SyBlobLength(pKey), 0);
} else {
/* Int key */
rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, 0);
}
if(rc != SXRET_OK) {
/* Key does not exists,break immediately */
break;
}
}
if(i >= nArg) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_uintersect(array $array1 ,array $array2,...,$callback)
* Computes the intersection of arrays.
* Parameters
* $array1
* The array to compare from
* $array2
* An array to compare against
* $...
* More arrays to compare against
* $callback
* The callback comparison 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
* Returns an array containing all of the values in array1 whose values exist
* in all of the parameters. .
* Note that NULL is returned on failure.
*/
static int ph7_hashmap_uintersect(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc, *pMap;
ph7_value *pCallback;
ph7_value *pArray;
ph7_value *pVal;
sxi32 rc;
sxu32 n;
int i;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Missing/Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the callback */
pCallback = apArg[nArg - 1];
if(nArg == 2) {
/* Return the first array since we cannot perform a diff */
ph7_result_value(pCtx, apArg[0]);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the source hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Perform the intersection */
pEntry = pSrc->pFirst;
n = pSrc->nEntry;
for(;;) {
if(n < 1) {
break;
}
/* Extract the node value */
pVal = HashmapExtractNodeValue(pEntry);
if(pVal) {
for(i = 1 ; i < nArg - 1; i++) {
if(!ph7_value_is_array(apArg[i])) {
/* ignore */
continue;
}
/* Point to the internal representation of the hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
/* Perform the lookup */
rc = HashmapFindValueByCallback(pMap, pVal, pCallback, 0);
if(rc != SXRET_OK) {
/* Value does not exist */
break;
}
}
if(i >= (nArg - 1)) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_fill(int $start_index,int $num,var $value)
* Fill an array with values.
* Parameters
* $start_index
* The first index of the returned array.
* $num
* Number of elements to insert.
* $value
* Value to use for filling.
* Return
* The filled array or null on failure.
*/
static int ph7_hashmap_fill(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pArray;
int i, nEntry;
if(nArg < 3) {
/* Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Total number of entries to insert */
nEntry = ph7_value_to_int(apArg[1]);
/* Insert the first entry alone because it have it's own key */
ph7_array_add_intkey_elem(pArray, ph7_value_to_int(apArg[0]), apArg[2]);
/* Repeat insertion of the desired value */
for(i = 1 ; i < nEntry ; i++) {
ph7_array_add_elem(pArray, 0/*Automatic index assign */, apArg[2]);
}
/* Return the filled array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_fill_keys(array $input,var $value)
* Fill an array with values, specifying keys.
* Parameters
* $input
* Array of values that will be used as key.
* $value
* Value to use for filling.
* Return
* The filled array or null on failure.
*/
static int ph7_hashmap_fill_keys(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc;
ph7_value *pArray;
sxu32 n;
if(nArg < 2) {
/* 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 input hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Perform the requested operation */
pEntry = pSrc->pFirst;
for(n = 0 ; n < pSrc->nEntry ; n++) {
ph7_array_add_elem(pArray, HashmapExtractNodeValue(pEntry), apArg[1]);
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return the filled array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_combine(array $keys,array $values)
* Creates an array by using one array for keys and another for its values.
* Parameters
* $keys
* Array of keys to be used.
* $values
* Array of values to be used.
* Return
* Returns the combined array. Otherwise FALSE if the number of elements
* for each array isn't equal or if one of the given arguments is
* not an array.
*/
static int ph7_hashmap_combine(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pKe, *pVe;
ph7_hashmap *pKey, *pValue;
ph7_value *pArray;
sxu32 n;
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[0]) || !ph7_value_is_array(apArg[1])) {
/* Invalid argument,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/* Point to the internal representation of the input hashmaps */
pKey = (ph7_hashmap *)apArg[0]->x.pOther;
pValue = (ph7_hashmap *)apArg[1]->x.pOther;
if(pKey->nEntry != pValue->nEntry) {
/* Array length differs,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
/* Perform the requested operation */
pKe = pKey->pFirst;
pVe = pValue->pFirst;
for(n = 0 ; n < pKey->nEntry ; n++) {
ph7_array_add_elem(pArray, HashmapExtractNodeValue(pKe), HashmapExtractNodeValue(pVe));
/* Point to the next entry */
pKe = pKe->pPrev; /* Reverse link */
pVe = pVe->pPrev;
}
/* Return the filled array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_reverse(array $array [,bool $preserve_keys = false ])
* Return an array with elements in reverse order.
* Parameters
* $array
* The input array.
* $preserve_keys (optional)
* If set to TRUE keys are preserved.
* Return
* The reversed array.
*/
static int ph7_hashmap_reverse(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc;
ph7_value *pArray;
int bPreserve;
sxu32 n;
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;
}
bPreserve = FALSE;
if(nArg > 1 && ph7_value_is_bool(apArg[1])) {
bPreserve = ph7_value_to_bool(apArg[1]);
}
/* Point to the internal representation of the input hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Perform the requested operation */
pEntry = pSrc->pLast;
for(n = 0 ; n < pSrc->nEntry ; n++) {
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, bPreserve);
/* Point to the previous entry */
pEntry = pEntry->pNext; /* Reverse link */
}
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_unique(array $array[,int $sort_flags = SORT_STRING ])
* Removes duplicate values from an array
* Parameter
* $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
* SORT_LOCALE_STRING - compare items as
* Return
* Filtered array or NULL on failure.
*/
static int ph7_hashmap_unique(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_value *pNeedle;
ph7_hashmap *pSrc;
ph7_value *pArray;
int bStrict;
sxi32 rc;
sxu32 n;
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;
}
bStrict = FALSE;
if(nArg > 1) {
bStrict = ph7_value_to_int(apArg[1]) == 3 /* SORT_REGULAR */ ? 1 : 0;
}
/* Point to the internal representation of the input hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Perform the requested operation */
pEntry = pSrc->pFirst;
for(n = 0 ; n < pSrc->nEntry ; n++) {
pNeedle = HashmapExtractNodeValue(pEntry);
rc = SXERR_NOTFOUND;
if(pNeedle) {
rc = HashmapFindValue((ph7_hashmap *)pArray->x.pOther, pNeedle, 0, bStrict);
}
if(rc != SXRET_OK) {
/* Perform the insertion */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_flip(array $input)
* Exchanges all keys with their associated values in an array.
* Parameter
* $input
* Input array.
* Return
* The flipped array on success or NULL on failure.
*/
static int ph7_hashmap_flip(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pSrc;
ph7_value *pArray;
ph7_value *pKey;
ph7_value sVal;
sxu32 n;
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 input hashmap */
pSrc = (ph7_hashmap *)apArg[0]->x.pOther;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Start processing */
pEntry = pSrc->pFirst;
for(n = 0 ; n < pSrc->nEntry ; n++) {
/* Extract the node value */
pKey = HashmapExtractNodeValue(pEntry);
if(pKey && (pKey->iFlags & MEMOBJ_NULL) == 0) {
/* Prepare the value for insertion */
if(pEntry->iType == HASHMAP_INT_NODE) {
PH7_MemObjInitFromInt(pSrc->pVm, &sVal, pEntry->xKey.iKey);
} else {
SyString sStr;
SyStringInitFromBuf(&sStr, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey));
PH7_MemObjInitFromString(pSrc->pVm, &sVal, &sStr);
}
/* Perform the insertion */
ph7_array_add_elem(pArray, pKey, &sVal);
/* Safely release the value because each inserted entry
* have it's own private copy of the value.
*/
PH7_MemObjRelease(&sVal);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return the freshly created array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* number array_sum(array $array )
* Calculate the sum of values in an array.
* Parameters
* $array: The input array.
* Return
* Returns the sum of values as an integer or float.
*/
static void DoubleSum(ph7_context *pCtx, ph7_hashmap *pMap) {
ph7_hashmap_node *pEntry;
ph7_value *pObj;
double dSum = 0;
sxu32 n;
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
pObj = HashmapExtractNodeValue(pEntry);
if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) {
if(pObj->iFlags & MEMOBJ_REAL) {
dSum += pObj->x.rVal;
} else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) {
dSum += (double)pObj->x.iVal;
} else if(pObj->iFlags & MEMOBJ_STRING) {
if(SyBlobLength(&pObj->sBlob) > 0) {
double dv = 0;
SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0);
dSum += dv;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return sum */
ph7_result_double(pCtx, dSum);
}
static void Int64Sum(ph7_context *pCtx, ph7_hashmap *pMap) {
ph7_hashmap_node *pEntry;
ph7_value *pObj;
sxi64 nSum = 0;
sxu32 n;
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
pObj = HashmapExtractNodeValue(pEntry);
if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) {
if(pObj->iFlags & MEMOBJ_REAL) {
nSum += (sxi64)pObj->x.rVal;
} else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) {
nSum += pObj->x.iVal;
} else if(pObj->iFlags & MEMOBJ_STRING) {
if(SyBlobLength(&pObj->sBlob) > 0) {
sxi64 nv = 0;
SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0);
nSum += nv;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return sum */
ph7_result_int64(pCtx, nSum);
}
/* number array_sum(array $array )
* (See block-coment above)
*/
static int ph7_hashmap_sum(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
ph7_value *pObj;
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;
}
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if(pMap->nEntry < 1) {
/* Nothing to compute,return 0 */
ph7_result_int(pCtx, 0);
return PH7_OK;
}
/* If the first element is of type float,then perform floating
* point computaion.Otherwise switch to int64 computaion.
*/
pObj = HashmapExtractNodeValue(pMap->pFirst);
if(pObj == 0) {
ph7_result_int(pCtx, 0);
return PH7_OK;
}
if(pObj->iFlags & MEMOBJ_REAL) {
DoubleSum(pCtx, pMap);
} else {
Int64Sum(pCtx, pMap);
}
return PH7_OK;
}
/*
* number array_product(array $array )
* Calculate the product of values in an array.
* Parameters
* $array: The input array.
* Return
* Returns the product of values as an integer or float.
*/
static void DoubleProd(ph7_context *pCtx, ph7_hashmap *pMap) {
ph7_hashmap_node *pEntry;
ph7_value *pObj;
double dProd;
sxu32 n;
pEntry = pMap->pFirst;
dProd = 1;
for(n = 0 ; n < pMap->nEntry ; n++) {
pObj = HashmapExtractNodeValue(pEntry);
if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) {
if(pObj->iFlags & MEMOBJ_REAL) {
dProd *= pObj->x.rVal;
} else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) {
dProd *= (double)pObj->x.iVal;
} else if(pObj->iFlags & MEMOBJ_STRING) {
if(SyBlobLength(&pObj->sBlob) > 0) {
double dv = 0;
SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0);
dProd *= dv;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return product */
ph7_result_double(pCtx, dProd);
}
static void Int64Prod(ph7_context *pCtx, ph7_hashmap *pMap) {
ph7_hashmap_node *pEntry;
ph7_value *pObj;
sxi64 nProd;
sxu32 n;
pEntry = pMap->pFirst;
nProd = 1;
for(n = 0 ; n < pMap->nEntry ; n++) {
pObj = HashmapExtractNodeValue(pEntry);
if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) {
if(pObj->iFlags & MEMOBJ_REAL) {
nProd *= (sxi64)pObj->x.rVal;
} else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) {
nProd *= pObj->x.iVal;
} else if(pObj->iFlags & MEMOBJ_STRING) {
if(SyBlobLength(&pObj->sBlob) > 0) {
sxi64 nv = 0;
SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0);
nProd *= nv;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* Return product */
ph7_result_int64(pCtx, nProd);
}
/* number array_product(array $array )
* (See block-block comment above)
*/
static int ph7_hashmap_product(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
ph7_value *pObj;
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;
}
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if(pMap->nEntry < 1) {
/* Nothing to compute,return 0 */
ph7_result_int(pCtx, 0);
return PH7_OK;
}
/* If the first element is of type float,then perform floating
* point computation.Otherwise switch to int64 computation.
*/
pObj = HashmapExtractNodeValue(pMap->pFirst);
if(pObj == 0) {
ph7_result_int(pCtx, 0);
return PH7_OK;
}
if(pObj->iFlags & MEMOBJ_REAL) {
DoubleProd(pCtx, pMap);
} else {
Int64Prod(pCtx, pMap);
}
return PH7_OK;
}
/*
* value array_rand(array $input[,int $num_req = 1 ])
* Pick one or more random entries out of an array.
* Parameters
* $input
* The input array.
* $num_req
* Specifies how many entries you want to pick.
* Return
* If you are picking only one entry, array_rand() returns the key for a random entry.
* Otherwise, it returns an array of keys for the random entries.
* NULL is returned on failure.
*/
static int ph7_hashmap_rand(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pNode;
ph7_hashmap *pMap;
int nItem = 1;
if(nArg < 1) {
/* Missing argument,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Make sure we are dealing with an array */
if(!ph7_value_is_array(apArg[0])) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
if(pMap->nEntry < 1) {
/* Empty hashmap,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
if(nArg > 1) {
nItem = ph7_value_to_int(apArg[1]);
}
if(nItem < 2) {
sxu32 nEntry;
/* Select a random number */
nEntry = PH7_VmRandomNum(pMap->pVm) % pMap->nEntry;
/* Extract the desired entry.
* Note that we perform a linear lookup here (later version must change this)
*/
if(nEntry > pMap->nEntry / 2) {
pNode = pMap->pLast;
nEntry = pMap->nEntry - nEntry;
if(nEntry > 1) {
for(;;) {
if(nEntry == 0) {
break;
}
/* Point to the previous entry */
pNode = pNode->pNext; /* Reverse link */
nEntry--;
}
}
} else {
pNode = pMap->pFirst;
for(;;) {
if(nEntry == 0) {
break;
}
/* Point to the next entry */
pNode = pNode->pPrev; /* Reverse link */
nEntry--;
}
}
if(pNode->iType == HASHMAP_INT_NODE) {
/* Int key */
ph7_result_int64(pCtx, pNode->xKey.iKey);
} else {
/* Blob key */
ph7_result_string(pCtx, (const char *)SyBlobData(&pNode->xKey.sKey), (int)SyBlobLength(&pNode->xKey.sKey));
}
} else {
ph7_value sKey, *pArray;
ph7_hashmap *pDest;
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the hashmap */
pDest = (ph7_hashmap *)pArray->x.pOther;
PH7_MemObjInit(pDest->pVm, &sKey);
/* Copy the first n items */
pNode = pMap->pFirst;
if(nItem > (int)pMap->nEntry) {
nItem = (int)pMap->nEntry;
}
while(nItem > 0) {
PH7_HashmapExtractNodeKey(pNode, &sKey);
PH7_HashmapInsert(pDest, 0/* Automatic index assign*/, &sKey);
PH7_MemObjRelease(&sKey);
/* Point to the next entry */
pNode = pNode->pPrev; /* Reverse link */
nItem--;
}
/* Shuffle the array */
HashmapMergeSort(pDest, HashmapCmpCallback7, 0);
/* Rehash node */
HashmapSortRehash(pDest);
/* Return the random array */
ph7_result_value(pCtx, pArray);
}
return PH7_OK;
}
/*
* array array_chunk (array $input,int $size [,bool $preserve_keys = false ])
* Split an array into chunks.
* Parameters
* $input
* The array to work on
* $size
* The size of each chunk
* $preserve_keys
* When set to TRUE keys will be preserved. Default is FALSE which will reindex
* the chunk numerically.
* Return
* Returns a multidimensional numerically indexed array, starting with
* zero, with each dimension containing size elements.
*/
static int ph7_hashmap_chunk(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pArray, *pChunk;
ph7_hashmap_node *pEntry;
ph7_hashmap *pMap;
int bPreserve;
sxu32 nChunk;
sxu32 nSize;
sxu32 n;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Extract the chunk size */
nSize = (sxu32)ph7_value_to_int(apArg[1]);
if(nSize < 1) {
ph7_result_null(pCtx);
return PH7_OK;
}
if(nSize >= pMap->nEntry) {
/* Return the whole array */
ph7_array_add_elem(pArray, 0, apArg[0]);
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
bPreserve = 0;
if(nArg > 2) {
bPreserve = ph7_value_to_bool(apArg[2]);
}
/* Start processing */
pEntry = pMap->pFirst;
nChunk = 0;
pChunk = 0;
n = pMap->nEntry;
for(;;) {
if(n < 1) {
if(nChunk > 0) {
/* Insert the last chunk */
ph7_array_add_elem(pArray, 0, pChunk); /* Will have it's own copy */
}
break;
}
if(nChunk < 1) {
if(pChunk) {
/* Put the first chunk */
ph7_array_add_elem(pArray, 0, pChunk); /* Will have it's own copy */
}
/* Create a new dimension */
pChunk = ph7_context_new_array(pCtx); /* Don't worry about freeing memory here,everything
* will be automatically released as soon we return
* from this function */
if(pChunk == 0) {
break;
}
nChunk = nSize;
}
/* Insert the entry */
HashmapInsertNode((ph7_hashmap *)pChunk->x.pOther, pEntry, bPreserve);
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
nChunk--;
n--;
}
/* Return the multidimensional array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_pad(array $input,int $pad_size,value $pad_value)
* Pad array to the specified length with a value.
* $input
* Initial array of values to pad.
* $pad_size
* New size of the array.
* $pad_value
* Value to pad if input is less than pad_size.
*/
static int ph7_hashmap_pad(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
ph7_value *pArray;
int nEntry;
if(nArg < 3 || !ph7_value_is_array(apArg[0])) {
/* Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Extract the total number of desired entry to insert */
nEntry = ph7_value_to_int(apArg[1]);
if(nEntry < 0) {
nEntry = -nEntry;
if(nEntry > 1048576) {
nEntry = 1048576; /* Limit imposed by PHP */
}
if(nEntry > (int)pMap->nEntry) {
nEntry -= (int)pMap->nEntry;
/* Insert given items first */
while(nEntry > 0) {
ph7_array_add_elem(pArray, 0, apArg[2]);
nEntry--;
}
/* Merge the two arrays */
HashmapMerge(pMap, (ph7_hashmap *)pArray->x.pOther);
} else {
PH7_HashmapDup(pMap, (ph7_hashmap *)pArray->x.pOther);
}
} else if(nEntry > 0) {
if(nEntry > 1048576) {
nEntry = 1048576; /* Limit imposed by PHP */
}
if(nEntry > (int)pMap->nEntry) {
nEntry -= (int)pMap->nEntry;
/* Merge the two arrays first */
HashmapMerge(pMap, (ph7_hashmap *)pArray->x.pOther);
/* Insert given items */
while(nEntry > 0) {
ph7_array_add_elem(pArray, 0, apArg[2]);
nEntry--;
}
} else {
PH7_HashmapDup(pMap, (ph7_hashmap *)pArray->x.pOther);
}
}
/* Return the new array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_replace(array &$array,array &$array1,...)
* Replaces elements from passed arrays into the first array.
* Parameters
* $array
* The array in which elements are replaced.
* $array1
* The array from which elements will be extracted.
* ....
* More arrays from which elements will be extracted.
* Values from later arrays overwrite the previous values.
* Return
* Returns an array, or NULL if an error occurs.
*/
static int ph7_hashmap_replace(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
ph7_value *pArray;
int i;
if(nArg < 1) {
/* Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Perform the requested operation */
for(i = 0 ; i < nArg ; i++) {
if(!ph7_value_is_array(apArg[i])) {
continue;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[i]->x.pOther;
HashmapOverwrite(pMap, (ph7_hashmap *)pArray->x.pOther);
}
/* Return the new array */
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_filter(array $input [,callback $callback ])
* Filters elements of an array using a callback function.
* Parameters
* $input
* The array to iterate over
* $callback
* The callback function to use
* If no callback is supplied, all entries of input equal to FALSE (see converting to boolean)
* will be removed.
* Return
* The filtered array.
*/
static int ph7_hashmap_filter(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pMap;
ph7_value *pArray;
ph7_value sResult; /* Callback result */
ph7_value *pValue;
sxi32 rc;
int keep;
sxu32 n;
if(nArg < 1 || !ph7_value_is_array(apArg[0])) {
/* Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
pEntry = pMap->pFirst;
PH7_MemObjInit(pMap->pVm, &sResult);
sResult.nIdx = SXU32_HIGH; /* Mark as constant */
/* Perform the requested operation */
for(n = 0 ; n < pMap->nEntry ; n++) {
/* Extract node value */
pValue = HashmapExtractNodeValue(pEntry);
if(nArg > 1 && pValue) {
/* Invoke the given callback */
keep = FALSE;
rc = PH7_VmCallUserFunction(pMap->pVm, apArg[1], 1, &pValue, &sResult);
if(rc == SXRET_OK) {
/* Perform a boolean cast */
keep = ph7_value_to_bool(&sResult);
}
PH7_MemObjRelease(&sResult);
} else {
/* No available callback */
keep = FALSE;
}
if(keep) {
/* Perform the insertion,now the callback returned true */
HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* array array_map(callback $callback,array $arr1)
* Applies the callback to the elements of the given arrays.
* Parameters
* $callback
* Callback function to run for each element in each array.
* $arr1
* An array to run through the callback function.
* Return
* Returns an array containing all the elements of arr1 after applying
* the callback function to each one.
* NOTE:
* array_map() passes only a single value to the callback.
*/
static int ph7_hashmap_map(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pArray, *pValue, sKey, sResult;
ph7_hashmap_node *pEntry;
ph7_hashmap *pMap;
sxu32 n;
if(nArg < 2 || !ph7_value_is_array(apArg[1])) {
/* Invalid arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Create a new array */
pArray = ph7_context_new_array(pCtx);
if(pArray == 0) {
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[1]->x.pOther;
PH7_MemObjInit(pMap->pVm, &sResult);
PH7_MemObjInit(pMap->pVm, &sKey);
sResult.nIdx = SXU32_HIGH; /* Mark as constant */
sKey.nIdx = SXU32_HIGH; /* Mark as constant */
/* Perform the requested operation */
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
/* Extract the node value */
pValue = HashmapExtractNodeValue(pEntry);
if(pValue) {
sxi32 rc;
/* Invoke the supplied callback */
rc = PH7_VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult);
/* Extract the node key */
PH7_HashmapExtractNodeKey(pEntry, &sKey);
if(rc != SXRET_OK) {
/* An error ocurred while invoking the supplied callback [i.e: not defined] */
ph7_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */
} else {
/* Insert the callback return value */
ph7_array_add_elem(pArray, &sKey, &sResult);
}
PH7_MemObjRelease(&sKey);
PH7_MemObjRelease(&sResult);
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
ph7_result_value(pCtx, pArray);
return PH7_OK;
}
/*
* value array_reduce(array $input,callback $function[, value $initial = NULL ])
* Iteratively reduce the array to a single value using a callback function.
* Parameters
* $input
* The input array.
* $function
* The callback function.
* $initial
* If the optional initial is available, it will be used at the beginning
* of the process, or as a final result in case the array is empty.
* Return
* Returns the resulting value.
* If the array is empty and initial is not passed, array_reduce() returns NULL.
*/
static int ph7_hashmap_reduce(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap_node *pEntry;
ph7_hashmap *pMap;
ph7_value *pValue;
ph7_value sResult;
sxu32 n;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Invalid/Missing arguments,return NULL */
ph7_result_null(pCtx);
return PH7_OK;
}
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
/* Assume a NULL initial value */
PH7_MemObjInit(pMap->pVm, &sResult);
sResult.nIdx = SXU32_HIGH; /* Mark as constant */
if(nArg > 2) {
/* Set the initial value */
PH7_MemObjLoad(apArg[2], &sResult);
}
/* Perform the requested operation */
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
/* Extract the node value */
pValue = HashmapExtractNodeValue(pEntry);
/* Invoke the supplied callback */
PH7_VmCallUserFunctionAp(pMap->pVm, apArg[1], &sResult, &sResult, pValue, 0);
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
ph7_result_value(pCtx, &sResult); /* Will make it's own copy */
PH7_MemObjRelease(&sResult);
return PH7_OK;
}
/*
* bool array_walk(array &$array,callback $funcname [, value $userdata ] )
* Apply a user function to every member of an array.
* Parameters
* $array
* The input array.
* $funcname
* Typically, funcname takes on two parameters.The array parameter's value being
* the first, and the key/index second.
* Note:
* If funcname needs to be working with the actual values of the array,specify the first
* parameter of funcname as a reference. Then, any changes made to those elements will
* be made in the original array itself.
* $userdata
* If the optional userdata parameter is supplied, it will be passed as the third parameter
* to the callback funcname.
* Return
* Returns TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_walk(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_value *pValue, *pUserData, sKey;
ph7_hashmap_node *pEntry;
ph7_hashmap *pMap;
sxi32 rc;
sxu32 n;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Invalid/Missing arguments,return FALSE */
ph7_result_bool(pCtx, 0);
return PH7_OK;
}
pUserData = nArg > 2 ? apArg[2] : 0;
/* Point to the internal representation of the input hashmap */
pMap = (ph7_hashmap *)apArg[0]->x.pOther;
PH7_MemObjInit(pMap->pVm, &sKey);
sKey.nIdx = SXU32_HIGH; /* Mark as constant */
/* Perform the desired operation */
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
/* Extract the node value */
pValue = HashmapExtractNodeValue(pEntry);
if(pValue) {
/* Extract the entry key */
PH7_HashmapExtractNodeKey(pEntry, &sKey);
/* Invoke the supplied callback */
rc = PH7_VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0);
PH7_MemObjRelease(&sKey);
if(rc != SXRET_OK) {
/* An error ocurred while invoking the supplied callback [i.e: not defined] */
ph7_result_bool(pCtx, 0); /* return FALSE */
return PH7_OK;
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
/* All done,return TRUE */
ph7_result_bool(pCtx, 1);
return PH7_OK;
}
/*
* Apply a user function to every member of an array.(Recurse on array's).
* Refer to the [array_walk_recursive()] implementation for more information.
*/
static int HashmapWalkRecursive(
ph7_hashmap *pMap, /* Target hashmap */
ph7_value *pCallback, /* User callback */
ph7_value *pUserData, /* Callback private data */
int iNest /* Nesting level */
) {
ph7_hashmap_node *pEntry;
ph7_value *pValue, sKey;
sxi32 rc;
sxu32 n;
/* Iterate throw hashmap entries */
PH7_MemObjInit(pMap->pVm, &sKey);
sKey.nIdx = SXU32_HIGH; /* Mark as constant */
pEntry = pMap->pFirst;
for(n = 0 ; n < pMap->nEntry ; n++) {
/* Extract the node value */
pValue = HashmapExtractNodeValue(pEntry);
if(pValue) {
if(pValue->iFlags & MEMOBJ_HASHMAP) {
if(iNest < 32) {
/* Recurse */
iNest++;
HashmapWalkRecursive((ph7_hashmap *)pValue->x.pOther, pCallback, pUserData, iNest);
iNest--;
}
} else {
/* Extract the node key */
PH7_HashmapExtractNodeKey(pEntry, &sKey);
/* Invoke the supplied callback */
rc = PH7_VmCallUserFunctionAp(pMap->pVm, pCallback, 0, pValue, &sKey, pUserData, 0);
PH7_MemObjRelease(&sKey);
if(rc != SXRET_OK) {
return rc;
}
}
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
}
return SXRET_OK;
}
/*
* bool array_walk_recursive(array &$array,callback $funcname [, value $userdata ] )
* Apply a user function recursively to every member of an array.
* Parameters
* $array
* The input array.
* $funcname
* Typically, funcname takes on two parameters.The array parameter's value being
* the first, and the key/index second.
* Note:
* If funcname needs to be working with the actual values of the array,specify the first
* parameter of funcname as a reference. Then, any changes made to those elements will
* be made in the original array itself.
* $userdata
* If the optional userdata parameter is supplied, it will be passed as the third parameter
* to the callback funcname.
* Return
* Returns TRUE on success or FALSE on failure.
*/
static int ph7_hashmap_walk_recursive(ph7_context *pCtx, int nArg, ph7_value **apArg) {
ph7_hashmap *pMap;
sxi32 rc;
if(nArg < 2 || !ph7_value_is_array(apArg[0])) {
/* Invalid/Missing 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;
/* Perform the desired operation */
rc = HashmapWalkRecursive(pMap, apArg[1], nArg > 2 ? apArg[2] : 0, 0);
/* All done */
ph7_result_bool(pCtx, rc == SXRET_OK);
return PH7_OK;
}
/*
* Table of hashmap functions.
*/
static const ph7_builtin_func aHashmapFunc[] = {
{"sizeof", ph7_hashmap_count },
{"array_key_exists", ph7_hashmap_key_exists },
{"array_pop", ph7_hashmap_pop },
{"array_push", ph7_hashmap_push },
{"array_shift", ph7_hashmap_shift },
{"array_product", ph7_hashmap_product },
{"array_sum", ph7_hashmap_sum },
{"array_keys", ph7_hashmap_keys },
{"array_values", ph7_hashmap_values },
{"array_same", ph7_hashmap_same }, /* Symisc eXtension */
{"array_merge", ph7_hashmap_merge },
{"array_slice", ph7_hashmap_slice },
{"array_splice", ph7_hashmap_splice },
{"array_search", ph7_hashmap_search },
{"array_diff", ph7_hashmap_diff },
{"array_udiff", ph7_hashmap_udiff },
{"array_diff_assoc", ph7_hashmap_diff_assoc },
{"array_diff_uassoc", ph7_hashmap_diff_uassoc },
{"array_diff_key", ph7_hashmap_diff_key },
{"array_intersect", ph7_hashmap_intersect},
{"array_intersect_assoc", ph7_hashmap_intersect_assoc},
{"array_uintersect", ph7_hashmap_uintersect},
{"array_intersect_key", ph7_hashmap_intersect_key},
{"array_copy", ph7_hashmap_copy },
{"array_erase", ph7_hashmap_erase },
{"array_fill", ph7_hashmap_fill },
{"array_fill_keys", ph7_hashmap_fill_keys},
{"array_combine", ph7_hashmap_combine },
{"array_reverse", ph7_hashmap_reverse },
{"array_unique", ph7_hashmap_unique },
{"array_flip", ph7_hashmap_flip },
{"array_rand", ph7_hashmap_rand },
{"array_chunk", ph7_hashmap_chunk },
{"array_pad", ph7_hashmap_pad },
{"array_replace", ph7_hashmap_replace },
{"array_filter", ph7_hashmap_filter },
{"array_map", ph7_hashmap_map },
{"array_reduce", ph7_hashmap_reduce },
{"array_walk", ph7_hashmap_walk },
{"array_walk_recursive", ph7_hashmap_walk_recursive },
{"in_array", ph7_hashmap_in_array},
{"sort", ph7_hashmap_sort },
{"asort", ph7_hashmap_asort },
{"arsort", ph7_hashmap_arsort },
{"ksort", ph7_hashmap_ksort },
{"krsort", ph7_hashmap_krsort },
{"rsort", ph7_hashmap_rsort },
{"usort", ph7_hashmap_usort },
{"uasort", ph7_hashmap_uasort },
{"uksort", ph7_hashmap_uksort },
{"shuffle", ph7_hashmap_shuffle },
{"range", ph7_hashmap_range },
{"current", ph7_hashmap_current },
{"each", ph7_hashmap_each },
{"pos", ph7_hashmap_current },
{"next", ph7_hashmap_next },
{"prev", ph7_hashmap_prev },
{"end", ph7_hashmap_end },
{"reset", ph7_hashmap_reset },
{"key", ph7_hashmap_simple_key }
};
/*
* Register the built-in hashmap functions defined above.
*/
PH7_PRIVATE void PH7_RegisterHashmapFunctions(ph7_vm *pVm) {
sxu32 n;
for(n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++) {
ph7_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0);
}
}
/*
* Dump a hashmap instance and it's entries and the store the dump in
* the BLOB given as the first argument.
* This function is typically invoked when the user issue a call to
* [var_dump(),var_export(),print_r(),...]
* This function SXRET_OK on success. Any other return value including
* SXERR_LIMIT(infinite recursion) indicates failure.
*/
PH7_PRIVATE sxi32 PH7_HashmapDump(SyBlob *pOut, ph7_hashmap *pMap, int ShowType, int nTab, int nDepth) {
ph7_hashmap_node *pEntry;
ph7_value *pObj;
sxu32 n = 0;
sxi32 rc;
int i;
if(nDepth > 31) {
static const char zInfinite[] = "Nesting limit reached: Infinite recursion?";
/* Nesting limit reached */
SyBlobAppend(&(*pOut), zInfinite, sizeof(zInfinite) - 1);
if(ShowType) {
SyBlobAppend(&(*pOut), ")", sizeof(char));
}
return SXERR_LIMIT;
}
rc = SXRET_OK;
if(!ShowType) {
SyBlobAppend(&(*pOut), "Array(", sizeof("Array(") - 1);
}
if(pMap) {
/* Point to the first inserted entry */
pEntry = pMap->pFirst;
/* Total entries */
SyBlobFormat(&(*pOut), "%u) {", pMap->nEntry);
#ifdef __WINNT__
SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1);
#else
SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
for(;;) {
if(n >= pMap->nEntry) {
break;
}
for(i = 0 ; i < nTab ; i++) {
SyBlobAppend(&(*pOut), " ", sizeof(char));
}
/* Dump key */
if(pEntry->iType == HASHMAP_INT_NODE) {
SyBlobFormat(&(*pOut), "[%qd] =>", pEntry->xKey.iKey);
} else {
SyBlobFormat(&(*pOut), "[%.*s] =>",
SyBlobLength(&pEntry->xKey.sKey), SyBlobData(&pEntry->xKey.sKey));
}
#ifdef __WINNT__
SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1);
#else
SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
/* Dump node value */
pObj = HashmapExtractNodeValue(pEntry);
if(pObj) {
rc = PH7_MemObjDump(&(*pOut), pObj, ShowType, nTab + 1, nDepth);
if(rc == SXERR_LIMIT) {
break;
}
}
/* Point to the next entry */
n++;
pEntry = pEntry->pPrev; /* Reverse link */
}
for(i = 0 ; i < nTab ; i++) {
SyBlobAppend(&(*pOut), " ", sizeof(char));
}
SyBlobAppend(&(*pOut), "}", sizeof(char));
} else {
SyBlobAppend(&(*pOut), "0) {", sizeof("0) {"));
#ifdef __WINNT__
SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1);
#else
SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
SyBlobAppend(&(*pOut), " }", sizeof(" }"));
}
return rc;
}
/*
* Iterate through hashmap entries and invoke the given callback [i.e: xWalk()] for each
* retrieved entry.
* Note that argument are passed to the callback by copy. That is,any modification to
* the entry value in the callback body will not alter the real value.
* If the callback wishes to abort processing [i.e: it's invocation] it must return
* a value different from PH7_OK.
* Refer to [ph7_array_walk()] for more information.
*/
PH7_PRIVATE sxi32 PH7_HashmapWalk(
ph7_hashmap *pMap, /* Target hashmap */
int (*xWalk)(ph7_value *, ph7_value *, void *), /* Walker callback */
void *pUserData /* Last argument to xWalk() */
) {
ph7_hashmap_node *pEntry;
ph7_value sKey, sValue;
sxi32 rc;
sxu32 n;
/* Initialize walker parameter */
rc = SXRET_OK;
PH7_MemObjInit(pMap->pVm, &sKey);
PH7_MemObjInit(pMap->pVm, &sValue);
n = pMap->nEntry;
pEntry = pMap->pFirst;
/* Start the iteration process */
for(;;) {
if(n < 1) {
break;
}
/* Extract a copy of the key and a copy the current value */
PH7_HashmapExtractNodeKey(pEntry, &sKey);
PH7_HashmapExtractNodeValue(pEntry, &sValue, FALSE);
/* Invoke the user callback */
rc = xWalk(&sKey, &sValue, pUserData);
/* Release the copy of the key and the value */
PH7_MemObjRelease(&sKey);
PH7_MemObjRelease(&sValue);
if(rc != PH7_OK) {
/* Callback request an operation abort */
return SXERR_ABORT;
}
/* Point to the next entry */
pEntry = pEntry->pPrev; /* Reverse link */
n--;
}
/* All done */
return SXRET_OK;
}
/*
* Iterate through hashmap entries and typecast all of them recursively to specified type.
* If all elements are compatible with each other and can be safely typecasted w/o data loss
* the SXRET_OK is returned. Otherwise, SXERR_NOMATCH is returned.
*/
PH7_PRIVATE sxi32 PH7_HashmapCast(ph7_value *pObj, sxi32 nType) {
sxi32 rc;
if((pObj->iFlags & MEMOBJ_HASHMAP)) {
if((pObj->iFlags & nType) == 0) {
ph7_hashmap *pMap;
ph7_hashmap_node *pNode;
ph7_value pValue, pKey;
pMap = (ph7_hashmap *) pObj->x.pOther;
while((pNode = PH7_HashmapGetNextEntry(pMap)) != 0) {
if(pNode->iType == 2) {
PH7_MemObjInitFromString(pObj->pVm, &pKey, 0);
PH7_MemObjStringAppend(&pKey, (const char *)SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey));
} else {
PH7_MemObjInitFromInt(pObj->pVm, &pKey, pNode->xKey.iKey);
}
PH7_MemObjInit(pObj->pVm, &pValue);
PH7_HashmapExtractNodeValue(pNode, &pValue, FALSE);
rc = PH7_HashmapCast(&pValue, nType);
if(rc == SXERR_NOMATCH) {
return SXERR_NOMATCH;
}
PH7_HashmapInsert(pMap, &pKey, &pValue);
}
pObj->iFlags = MEMOBJ_HASHMAP | nType;
}
} else {
if(pObj->iFlags != nType && PH7_CheckVarCompat(pObj, nType) != SXRET_OK) {
return SXERR_NOMATCH;
}
ProcMemObjCast xCast = PH7_MemObjCastMethod(nType);
xCast(pObj);
}
return SXRET_OK;
}