All checks were successful
The build was successful.
After last change, AerScript does not pass arrays as reference, so there is no chance to get this function return true, unless you pass array as reference.
5433 lines
156 KiB
C
5433 lines
156 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;
|
|
/* 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 */
|
|
}
|
|
/* Save the next array index */
|
|
pDest->iNextIdx = pSrc->iNextIdx;
|
|
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 occurred 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;
|
|
}
|
|
/*
|
|
* 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;
|
|
}
|
|
/*
|
|
* 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_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_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;
|
|
}
|