/* * 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 $ */ #include "ph7int.h" /* This file implement generic hashmaps known as 'array' in the PHP world */ /* Allowed node types */ #define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ #define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ /* Node control flags */ #define HASHMAP_NODE_FOREIGN_OBJ 0x001 /* Node hold a reference to a foreign ph7_value * [i.e: array(&var)/$a[] =& $var ] */ /* * Default hash function for int [i.e; 64-bit integer] keys. */ static sxu32 IntHash(sxi64 iKey) { return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); } /* * Default hash function for string/BLOB keys. */ static sxu32 BinHash(const void *pSrc, sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; sxu32 nH = 5381; zEnd = &zIn[nLen]; for(;;) { if(zIn >= zEnd) { break; } nH = nH * 33 + zIn[0] ; zIn++; if(zIn >= zEnd) { break; } nH = nH * 33 + zIn[0] ; zIn++; if(zIn >= zEnd) { break; } nH = nH * 33 + zIn[0] ; zIn++; if(zIn >= zEnd) { break; } nH = nH * 33 + zIn[0] ; zIn++; } return nH; } /* * Return the total number of entries in a given hashmap. * If 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 */ if((pNode->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0) { PH7_VmUnsetMemObj(pVm, pNode->nValIdx, FALSE); } } if(pNode->iType == HASHMAP_BLOB_NODE) { SyBlobRelease(&pNode->xKey.sKey); } SyMemBackendPoolFree(&pVm->sAllocator, pNode); pMap->nEntry--; if(pMap->nEntry < 1 && pMap != pVm->pGlobal) { /* Free the hash-bucket */ SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); pMap->apBucket = 0; pMap->nSize = 0; pMap->pFirst = pMap->pLast = pMap->pCur = 0; } } #define HASHMAP_FILL_FACTOR 3 /* * Grow the hash-table and rehash all entries. */ static sxi32 HashmapGrowBucket(ph7_hashmap *pMap) { if(pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR) { ph7_hashmap_node **apOld = pMap->apBucket; ph7_hashmap_node *pEntry, **apNew; sxu32 nNew = pMap->nSize << 1; sxu32 nBucket; sxu32 n; if(nNew < 1) { nNew = 16; } /* Allocate a new bucket */ apNew = (ph7_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(ph7_hashmap_node *)); if(apNew == 0) { if(pMap->nSize < 1) { return SXERR_MEM; /* Fatal */ } /* Not so fatal here,simply a performance hit */ return SXRET_OK; } /* Zero the table */ SyZero((void *)apNew, nNew * sizeof(ph7_hashmap_node *)); /* Reflect the change */ pMap->apBucket = apNew; pMap->nSize = nNew; if(apOld == 0) { /* First allocated table [i.e: no entry],return immediately */ return SXRET_OK; } /* Rehash old entries */ pEntry = pMap->pFirst; n = 0; for(;;) { if(n >= pMap->nEntry) { break; } /* Clear the old collision link */ pEntry->pNextCollide = pEntry->pPrevCollide = 0; /* Link to the new bucket */ nBucket = pEntry->nHash & (nNew - 1); if(pMap->apBucket[nBucket] != 0) { pEntry->pNextCollide = pMap->apBucket[nBucket]; pMap->apBucket[nBucket]->pPrevCollide = pEntry; } pMap->apBucket[nBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n++; } /* Free the old table */ SyMemBackendFree(&pMap->pVm->sAllocator, (void *)apOld); } return SXRET_OK; } /* * Insert a 64-bit integer key and it's associated value (if any) in the given * hashmap. */ static sxi32 HashmapInsertIntKey(ph7_hashmap *pMap, sxi64 iKey, ph7_value *pValue, sxu32 nRefIdx, int isForeign) { ph7_hashmap_node *pNode; sxu32 nIdx; sxu32 nHash; sxi32 rc; if(!isForeign) { ph7_value *pObj; /* Reserve a ph7_value for the value */ pObj = PH7_ReserveMemObj(pMap->pVm); if(pObj == 0) { return SXERR_MEM; } if(pValue) { /* Duplicate the value */ PH7_MemObjStore(pValue, pObj); } nIdx = pObj->nIdx; } else { nIdx = nRefIdx; } /* Hash the key */ nHash = pMap->xIntHash(iKey); /* Allocate a new int node */ pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx); if(pNode == 0) { return SXERR_MEM; } if(isForeign) { /* Mark as a foregin entry */ pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ; } /* Make sure the bucket is big enough to hold the new entry */ rc = HashmapGrowBucket(&(*pMap)); if(rc != SXRET_OK) { SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); return rc; } /* Perform the insertion */ HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); /* Install in the reference table */ PH7_VmRefObjInstall(pMap->pVm, nIdx, 0, pNode, 0); /* All done */ return SXRET_OK; } /* * Insert a BLOB key and it's associated value (if any) in the given * hashmap. */ static sxi32 HashmapInsertBlobKey(ph7_hashmap *pMap, const void *pKey, sxu32 nKeyLen, ph7_value *pValue, sxu32 nRefIdx, int isForeign) { ph7_hashmap_node *pNode; sxu32 nHash; sxu32 nIdx; sxi32 rc; if(!isForeign) { ph7_value *pObj; /* Reserve a ph7_value for the value */ pObj = PH7_ReserveMemObj(pMap->pVm); if(pObj == 0) { return SXERR_MEM; } if(pValue) { /* Duplicate the value */ PH7_MemObjStore(pValue, pObj); } nIdx = pObj->nIdx; } else { nIdx = nRefIdx; } /* Hash the key */ nHash = pMap->xBlobHash(pKey, nKeyLen); /* Allocate a new blob node */ pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx); if(pNode == 0) { return SXERR_MEM; } if(isForeign) { /* Mark as a foregin entry */ pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ; } /* Make sure the bucket is big enough to hold the new entry */ rc = HashmapGrowBucket(&(*pMap)); if(rc != SXRET_OK) { SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); return rc; } /* Perform the insertion */ HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); /* Install in the reference table */ PH7_VmRefObjInstall(pMap->pVm, nIdx, 0, pNode, 0); /* All done */ return SXRET_OK; } /* * Check if a given 64-bit integer key exists in the given hashmap. * Write a pointer to the target node on success. Otherwise * SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookupIntKey( ph7_hashmap *pMap, /* Target hashmap */ sxi64 iKey, /* lookup key */ ph7_hashmap_node **ppNode /* OUT: target node on success */ ) { ph7_hashmap_node *pNode; sxu32 nHash; if(pMap->nEntry < 1) { /* Don't bother hashing,there is no entry anyway */ return SXERR_NOTFOUND; } /* Hash the key first */ nHash = pMap->xIntHash(iKey); /* Point to the appropriate bucket */ pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; /* Perform the lookup */ for(;;) { if(pNode == 0) { break; } if(pNode->iType == HASHMAP_INT_NODE && pNode->nHash == nHash && pNode->xKey.iKey == iKey) { /* Node found */ if(ppNode) { *ppNode = pNode; } return SXRET_OK; } /* Follow the collision link */ pNode = pNode->pNextCollide; } /* No such entry */ return SXERR_NOTFOUND; } /* * Check if a given BLOB key exists in the given hashmap. * Write a pointer to the target node on success. Otherwise * SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookupBlobKey( ph7_hashmap *pMap, /* Target hashmap */ const void *pKey, /* Lookup key */ sxu32 nKeyLen, /* Key length in bytes */ ph7_hashmap_node **ppNode /* OUT: target node on success */ ) { ph7_hashmap_node *pNode; sxu32 nHash; if(pMap->nEntry < 1) { /* Don't bother hashing,there is no entry anyway */ return SXERR_NOTFOUND; } /* Hash the key first */ nHash = pMap->xBlobHash(pKey, nKeyLen); /* Point to the appropriate bucket */ pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; /* Perform the lookup */ for(;;) { if(pNode == 0) { break; } if(pNode->iType == HASHMAP_BLOB_NODE && pNode->nHash == nHash && SyBlobLength(&pNode->xKey.sKey) == nKeyLen && SyMemcmp(SyBlobData(&pNode->xKey.sKey), pKey, nKeyLen) == 0) { /* Node found */ if(ppNode) { *ppNode = pNode; } return SXRET_OK; } /* Follow the collision link */ pNode = pNode->pNextCollide; } /* No such entry */ return SXERR_NOTFOUND; } /* * Check if the given BLOB key looks like a decimal number. * Retrurn TRUE on success.FALSE otherwise. */ static int HashmapIsIntKey(SyBlob *pKey) { const char *zIn = (const char *)SyBlobData(pKey); const char *zEnd = &zIn[SyBlobLength(pKey)]; if((int)(zEnd - zIn) > 1 && zIn[0] == '0') { /* Octal not decimal number */ return FALSE; } if((zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd) { zIn++; } for(;;) { if(zIn >= zEnd) { return TRUE; } if((unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0])) { break; } zIn++; } /* Key does not look like a decimal number */ return FALSE; } /* * Check if a given key exists in the given hashmap. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookup( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ ph7_hashmap_node **ppNode /* OUT: target node on success */ ) { ph7_hashmap_node *pNode = 0; /* cc -O6 warning */ sxi32 rc; if(pKey->iFlags & (MEMOBJ_STRING | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) { if((pKey->iFlags & MEMOBJ_STRING) == 0) { /* Force a string cast */ PH7_MemObjToString(&(*pKey)); } if(SyBlobLength(&pKey->sBlob) > 0 && !HashmapIsIntKey(&pKey->sBlob)) { /* Perform a blob lookup */ rc = HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode); goto result; } } /* Perform an int lookup */ if((pKey->iFlags & MEMOBJ_INT) == 0) { /* Force an integer cast */ PH7_MemObjToInteger(pKey); } /* Perform an int lookup */ rc = HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode); result: if(rc == SXRET_OK) { /* Node found */ if(ppNode) { *ppNode = pNode; } return SXRET_OK; } /* No such entry */ return SXERR_NOTFOUND; } /* * Insert a given key and it's associated value (if any) in the given * hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ static sxi32 HashmapInsert( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ ph7_value *pVal /* Node value */ ) { ph7_hashmap_node *pNode = 0; sxi32 rc = SXRET_OK; if(pKey && pKey->iFlags & (MEMOBJ_STRING | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) { if((pKey->iFlags & MEMOBJ_STRING) == 0) { /* Force a string cast */ PH7_MemObjToString(&(*pKey)); } if(SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob)) { if(SyBlobLength(&pKey->sBlob) < 1) { /* Automatic index assign */ pKey = 0; } goto IntKey; } if(SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode)) { /* Overwrite the old value */ ph7_value *pElem; pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if(pElem) { if(pVal) { PH7_MemObjStore(pVal, pElem); } else { /* Nullify the entry */ PH7_MemObjToNull(pElem); } } return SXRET_OK; } if(pMap == pMap->pVm->pGlobal) { /* Forbidden */ PH7_VmThrowError(pMap->pVm, PH7_CTX_NOTICE, "$GLOBALS is a read-only array, insertion is forbidden"); return SXRET_OK; } /* Perform a blob-key insertion */ rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &(*pVal), 0, FALSE); return rc; } IntKey: if(pKey) { if((pKey->iFlags & MEMOBJ_INT) == 0) { /* Force an integer cast */ PH7_MemObjToInteger(pKey); } if(SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode)) { /* Overwrite the old value */ ph7_value *pElem; pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if(pElem) { if(pVal) { PH7_MemObjStore(pVal, pElem); } else { /* Nullify the entry */ PH7_MemObjToNull(pElem); } } return SXRET_OK; } if(pMap == pMap->pVm->pGlobal) { /* Forbidden */ PH7_VmThrowError(pMap->pVm, PH7_CTX_NOTICE, "$GLOBALS is a read-only array, insertion is forbidden"); return SXRET_OK; } /* Perform a 64-bit-int-key insertion */ rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal), 0, FALSE); if(rc == SXRET_OK) { if(pKey->x.iVal >= pMap->iNextIdx) { /* Increment the automatic index */ pMap->iNextIdx = pKey->x.iVal + 1; /* Make sure the automatic index is not reserved */ while(SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0)) { pMap->iNextIdx++; } } } } else { if(pMap == pMap->pVm->pGlobal) { /* Forbidden */ PH7_VmThrowError(pMap->pVm, PH7_CTX_NOTICE, "$GLOBALS is a read-only array, insertion is forbidden"); return SXRET_OK; } /* Assign an automatic index */ rc = HashmapInsertIntKey(&(*pMap), pMap->iNextIdx, &(*pVal), 0, FALSE); if(rc == SXRET_OK) { ++pMap->iNextIdx; } } /* Insertion result */ return rc; } /* * Insert a given key and it's associated value (foreign index) in the given * hashmap. * This is insertion by reference so be careful to mark the node * with the HASHMAP_NODE_FOREIGN_OBJ flag being set. * The insertion by reference is triggered when the following * expression is encountered. * $var = 10; * $a = array(&var); * OR * $a[] =& $var; * That is,$var is a foreign ph7_value and the $a array have no control * over it's contents. * Note that the node that hold the foreign ph7_value is automatically * removed when the foreign ph7_value is unset. * Example: * $var = 10; * $a[] =& $var; * echo count($a).PHP_EOL; //1 * //Unset the foreign ph7_value now * unset($var); * echo count($a); //0 * Note that this is a PH7 eXtension. * Refer to the official documentation for more information. * If a node with the given key already exists in the database * then this function overwrite the old value. */ static sxi32 HashmapInsertByRef( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ sxu32 nRefIdx /* Foreign ph7_value index */ ) { ph7_hashmap_node *pNode = 0; sxi32 rc = SXRET_OK; if(pKey && pKey->iFlags & (MEMOBJ_STRING | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) { if((pKey->iFlags & MEMOBJ_STRING) == 0) { /* Force a string cast */ PH7_MemObjToString(&(*pKey)); } if(SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob)) { if(SyBlobLength(&pKey->sBlob) < 1) { /* Automatic index assign */ pKey = 0; } goto IntKey; } if(SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode)) { /* Overwrite */ PH7_VmRefObjRemove(pMap->pVm, pNode->nValIdx, 0, pNode); pNode->nValIdx = nRefIdx; /* Install in the reference table */ PH7_VmRefObjInstall(pMap->pVm, nRefIdx, 0, pNode, 0); return SXRET_OK; } /* Perform a blob-key insertion */ rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), 0, nRefIdx, TRUE); return rc; } IntKey: if(pKey) { if((pKey->iFlags & MEMOBJ_INT) == 0) { /* Force an integer cast */ PH7_MemObjToInteger(pKey); } if(SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode)) { /* Overwrite */ PH7_VmRefObjRemove(pMap->pVm, pNode->nValIdx, 0, pNode); pNode->nValIdx = nRefIdx; /* Install in the reference table */ PH7_VmRefObjInstall(pMap->pVm, nRefIdx, 0, pNode, 0); return SXRET_OK; } /* Perform a 64-bit-int-key insertion */ rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, 0, nRefIdx, TRUE); if(rc == SXRET_OK) { if(pKey->x.iVal >= pMap->iNextIdx) { /* Increment the automatic index */ pMap->iNextIdx = pKey->x.iVal + 1; /* Make sure the automatic index is not reserved */ while(SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0)) { pMap->iNextIdx++; } } } } else { /* Assign an automatic index */ rc = HashmapInsertIntKey(&(*pMap), pMap->iNextIdx, 0, nRefIdx, TRUE); if(rc == SXRET_OK) { ++pMap->iNextIdx; } } /* Insertion result */ return rc; } /* * Extract node value. */ static ph7_value *HashmapExtractNodeValue(ph7_hashmap_node *pNode) { /* Point to the desired object */ ph7_value *pObj; pObj = (ph7_value *)SySetAt(&pNode->pMap->pVm->aMemObj, pNode->nValIdx); return pObj; } /* * Insert a node in the given hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ static sxi32 HashmapInsertNode(ph7_hashmap *pMap, ph7_hashmap_node *pNode, int bPreserve) { ph7_value *pObj; sxi32 rc; /* Extract the node value */ pObj = HashmapExtractNodeValue(&(*pNode)); if(pObj == 0) { return SXERR_EMPTY; } /* Preserve key */ if(pNode->iType == HASHMAP_INT_NODE) { /* Int64 key */ if(!bPreserve) { /* Assign an automatic index */ rc = HashmapInsert(&(*pMap), 0, pObj); } else { rc = HashmapInsertIntKey(&(*pMap), pNode->xKey.iKey, pObj, 0, FALSE); } } else { /* Blob key */ rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey), pObj, 0, FALSE); } return rc; } /* * Compare two node values. * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight * or < 0 if pRight is greater than pLeft. * For a full description on ph7_values comparison,refer to the implementation * of the [PH7_MemObjCmp()] function defined in memobj.c or the official * 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 & ~MEMOBJ_AUX; sxi32 iF2 = pNeedle->iFlags & ~MEMOBJ_AUX; if(iF1 == iF2) { /* NULL values are equals */ if(ppNode) { *ppNode = pEntry; } return SXRET_OK; } } else { /* Duplicate value */ PH7_MemObjLoad(pVal, &sVal); PH7_MemObjLoad(pNeedle, &sNeedle); rc = PH7_MemObjCmp(&sNeedle, &sVal, bStrict, 0); PH7_MemObjRelease(&sVal); PH7_MemObjRelease(&sNeedle); if(rc == 0) { if(ppNode) { *ppNode = pEntry; } /* Match found*/ return SXRET_OK; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* No such entry */ return SXERR_NOTFOUND; } /* * Perform a linear search on a given hashmap but use an user-defined callback * for values comparison. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. * Refer to [array_uintersect(),array_udiff()...] implementations * for more information. */ static int HashmapFindValueByCallback( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pNeedle, /* Lookup key */ ph7_value *pCallback, /* User defined callback */ ph7_hashmap_node **ppNode /* OUT: target node on success */ ) { ph7_hashmap_node *pEntry; ph7_value sResult, *pVal; ph7_value *apArg[2]; /* Callback arguments */ sxi32 rc; sxu32 n; /* Perform a linear search since we cannot sort the array based on values */ pEntry = pMap->pFirst; n = pMap->nEntry; /* Store callback result here */ PH7_MemObjInit(pMap->pVm, &sResult); /* First argument to the callback */ apArg[0] = pNeedle; for(;;) { if(n < 1) { break; } /* Extract node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { /* Invoke the user callback */ apArg[1] = pVal; /* Second argument to the callback */ rc = PH7_VmCallUserFunction(pMap->pVm, pCallback, 2, apArg, &sResult); if(rc == SXRET_OK) { /* Extract callback result */ if((sResult.iFlags & MEMOBJ_INT) == 0) { /* Perform an int cast */ PH7_MemObjToInteger(&sResult); } rc = (sxi32)sResult.x.iVal; PH7_MemObjRelease(&sResult); if(rc == 0) { /* Match found*/ if(ppNode) { *ppNode = pEntry; } return SXRET_OK; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* No such entry */ return SXERR_NOTFOUND; } /* * Compare two hashmaps. * Return 0 if the hashmaps are equals.Any other value indicates inequality. * Note on array comparison operators. * According to the PHP language reference manual. * Array Operators Example Name Result * $a + $b Union Union of $a and $b. * $a == $b Equality TRUE if $a and $b have the same key/value pairs. * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same * order and of the same types. * $a != $b Inequality TRUE if $a is not equal to $b. * $a <> $b Inequality TRUE if $a is not equal to $b. * $a !== $b Non-identity TRUE if $a is not identical to $b. * The + operator returns the right-hand array appended to the left-hand array; * For keys that exist in both arrays, the elements from the left-hand array will be used * and the matching elements from the right-hand array will be ignored. * "apple", "b" => "banana"); * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); * $c = $a + $b; // Union of $a and $b * echo "Union of \$a and \$b: \n"; * var_dump($c); * $c = $b + $a; // Union of $b and $a * echo "Union of \$b and \$a: \n"; * var_dump($c); * ?> * When executed, this script will print the following: * Union of $a and $b: * array(3) { * ["a"]=> * string(5) "apple" * ["b"]=> * string(6) "banana" * ["c"]=> * string(6) "cherry" * } * Union of $b and $a: * array(3) { * ["a"]=> * string(4) "pear" * ["b"]=> * string(10) "strawberry" * ["c"]=> * string(6) "cherry" * } * Elements of arrays are equal for the comparison if they have the same key and value. */ PH7_PRIVATE sxi32 PH7_HashmapCmp( ph7_hashmap *pLeft, /* Left hashmap */ ph7_hashmap *pRight, /* Right hashmap */ int bStrict /* TRUE for strict comparison */ ) { ph7_hashmap_node *pLe, *pRe; sxi32 rc; sxu32 n; if(pLeft == pRight) { /* Same hashmap instance. This can easily happen since hashmaps are passed by reference. * Unlike the zend engine. */ return 0; } if(pLeft->nEntry != pRight->nEntry) { /* Must have the same number of entries */ return pLeft->nEntry > pRight->nEntry ? 1 : -1; } /* Point to the first inserted entry of the left hashmap */ pLe = pLeft->pFirst; pRe = 0; /* cc warning */ /* Perform the comparison */ n = pLeft->nEntry; for(;;) { if(n < 1) { break; } if(pLe->iType == HASHMAP_INT_NODE) { /* Int key */ rc = HashmapLookupIntKey(&(*pRight), pLe->xKey.iKey, &pRe); } else { SyBlob *pKey = &pLe->xKey.sKey; /* Blob key */ rc = HashmapLookupBlobKey(&(*pRight), SyBlobData(pKey), SyBlobLength(pKey), &pRe); } if(rc != SXRET_OK) { /* No such entry in the right side */ return 1; } rc = 0; if(bStrict) { /* Make sure,the keys are of the same type */ if(pLe->iType != pRe->iType) { rc = 1; } } if(!rc) { /* Compare nodes */ rc = HashmapNodeCmp(pLe, pRe, bStrict); } if(rc != 0) { /* Nodes key/value differ */ return rc; } /* Point to the next entry */ pLe = pLe->pPrev; /* Reverse link */ n--; } return 0; /* Hashmaps are equals */ } /* * Merge two hashmaps. * Note on the merge process * According to the PHP language reference manual. * Merges the elements of two arrays together so that the values of one are appended * to the end of the previous one. It returns the resulting array (pDest). * If the input arrays have the same string keys, then the later value for that key * will overwrite the previous one. If, however, the arrays contain numeric keys * the later value will not overwrite the original value, but will be appended. * Values in the input array with numeric keys will be renumbered with incrementing * keys starting from zero in the result array. */ static sxi32 HashmapMerge(ph7_hashmap *pSrc, ph7_hashmap *pDest) { ph7_hashmap_node *pEntry; ph7_value sKey, *pVal; sxi32 rc; sxu32 n; if(pSrc == pDest) { /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the zend engine. */ return SXRET_OK; } /* Point to the first inserted entry in the source */ pEntry = pSrc->pFirst; /* Perform the merge */ for(n = 0 ; n < pSrc->nEntry ; ++n) { /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pEntry->iType == HASHMAP_BLOB_NODE) { /* Blob key insertion */ PH7_MemObjInitFromString(pDest->pVm, &sKey, 0); PH7_MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); rc = PH7_HashmapInsert(&(*pDest), &sKey, pVal); PH7_MemObjRelease(&sKey); } else { rc = HashmapInsert(&(*pDest), 0/* Automatic index assign */, pVal); } if(rc != SXRET_OK) { return rc; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Overwrite entries with the same key. * Refer to the [array_replace()] implementation for more information. * According to the PHP language reference manual. * array_replace() replaces the values of the first array with the same values * from all the following arrays. If a key from the first array exists in the second * array, its value will be replaced by the value from the second array. If the key * exists in the second array, and not the first, it will be created in the first array. * If a key only exists in the first array, it will be left as is. If several arrays * are passed for replacement, they will be processed in order, the later arrays * overwriting the previous values. * array_replace() is not recursive : it will replace values in the first array * by whatever type is in the second array. */ static sxi32 HashmapOverwrite(ph7_hashmap *pSrc, ph7_hashmap *pDest) { ph7_hashmap_node *pEntry; ph7_value sKey, *pVal; sxi32 rc; sxu32 n; if(pSrc == pDest) { /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the zend engine. */ return SXRET_OK; } /* Point to the first inserted entry in the source */ pEntry = pSrc->pFirst; /* Perform the merge */ for(n = 0 ; n < pSrc->nEntry ; ++n) { /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pEntry->iType == HASHMAP_BLOB_NODE) { /* Blob key insertion */ PH7_MemObjInitFromString(pDest->pVm, &sKey, 0); PH7_MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); } else { /* Int key insertion */ PH7_MemObjInitFromInt(pDest->pVm, &sKey, pEntry->xKey.iKey); } rc = PH7_HashmapInsert(&(*pDest), &sKey, pVal); PH7_MemObjRelease(&sKey); if(rc != SXRET_OK) { return rc; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Duplicate the contents of a hashmap. Store the copy in pDest. * Refer to the [array_pad(),array_copy(),...] implementation for more information. */ PH7_PRIVATE sxi32 PH7_HashmapDup(ph7_hashmap *pSrc, ph7_hashmap *pDest) { ph7_hashmap_node *pEntry; ph7_value sKey, *pVal; sxi32 rc; sxu32 n; if(pSrc == pDest) { /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the zend engine. */ return SXRET_OK; } /* Point to the first inserted entry in the source */ pEntry = pSrc->pFirst; /* Perform the duplication */ for(n = 0 ; n < pSrc->nEntry ; ++n) { /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pEntry->iType == HASHMAP_BLOB_NODE) { /* Blob key insertion */ PH7_MemObjInitFromString(pDest->pVm, &sKey, 0); PH7_MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); rc = PH7_HashmapInsert(&(*pDest), &sKey, pVal); PH7_MemObjRelease(&sKey); } else { /* Int key insertion */ rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal, 0, FALSE); } if(rc != SXRET_OK) { return rc; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Perform the union of two hashmaps. * This operation is performed only if the user uses the '+' operator * with a variable holding an array as follows: * "apple", "b" => "banana"); * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); * $c = $a + $b; // Union of $a and $b * echo "Union of \$a and \$b: \n"; * var_dump($c); * $c = $b + $a; // Union of $b and $a * echo "Union of \$b and \$a: \n"; * var_dump($c); * ?> * When executed, this script will print the following: * Union of $a and $b: * array(3) { * ["a"]=> * string(5) "apple" * ["b"]=> * string(6) "banana" * ["c"]=> * string(6) "cherry" * } * Union of $b and $a: * array(3) { * ["a"]=> * string(4) "pear" * ["b"]=> * string(10) "strawberry" * ["c"]=> * string(6) "cherry" * } * The + operator returns the right-hand array appended to the left-hand array; * For keys that exist in both arrays, the elements from the left-hand array will be used * and the matching elements from the right-hand array will be ignored. */ PH7_PRIVATE sxi32 PH7_HashmapUnion(ph7_hashmap *pLeft, ph7_hashmap *pRight) { ph7_hashmap_node *pEntry; sxi32 rc = SXRET_OK; ph7_value *pObj; sxu32 n; if(pLeft == pRight) { /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the zend engine. */ return SXRET_OK; } /* Perform the union */ pEntry = pRight->pFirst; for(n = 0 ; n < pRight->nEntry ; ++n) { /* Make sure the given key does not exists in the left array */ if(pEntry->iType == HASHMAP_BLOB_NODE) { /* BLOB key */ if(SXRET_OK != HashmapLookupBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), 0)) { pObj = HashmapExtractNodeValue(pEntry); if(pObj) { /* Perform the insertion */ rc = HashmapInsertBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), pObj, 0, FALSE); if(rc != SXRET_OK) { return rc; } } } } else { /* INT key */ if(SXRET_OK != HashmapLookupIntKey(&(*pLeft), pEntry->xKey.iKey, 0)) { pObj = HashmapExtractNodeValue(pEntry); if(pObj) { /* Perform the insertion */ rc = HashmapInsertIntKey(&(*pLeft), pEntry->xKey.iKey, pObj, 0, FALSE); if(rc != SXRET_OK) { return rc; } } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Allocate a new hashmap. * Return a pointer to the freshly allocated hashmap on success.NULL otherwise. */ PH7_PRIVATE ph7_hashmap *PH7_NewHashmap( ph7_vm *pVm, /* VM that trigger the hashmap creation */ sxu32(*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/ sxu32(*xBlobHash)(const void *, sxu32) /* Hash function for BLOB keys.NULL otherwise */ ) { ph7_hashmap *pMap; /* Allocate a new instance */ pMap = (ph7_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_hashmap)); if(pMap == 0) { return 0; } /* Zero the structure */ SyZero(pMap, sizeof(ph7_hashmap)); /* Fill in the structure */ pMap->pVm = &(*pVm); pMap->iRef = 1; /* Default hash functions */ pMap->xIntHash = xIntHash ? xIntHash : IntHash; pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash; return pMap; } /* * Install superglobals in the given virtual machine. * Note on superglobals. * According to the PHP language reference manual. * Superglobals are built-in variables that are always available in all scopes. * Description * Several predefined variables in PHP are "superglobals", which means they * are available in all scopes throughout a script. There is no need to do * global $variable; to access them within functions or methods. * These superglobal variables are: * $GLOBALS * $_SERVER * $_GET * $_POST * $_FILES * $_COOKIE * $_SESSION * $_REQUEST * $_ENV */ PH7_PRIVATE sxi32 PH7_HashmapCreateSuper(ph7_vm *pVm) { static const char *azSuper[] = { "_SERVER", /* $_SERVER */ "_GET", /* $_GET */ "_POST", /* $_POST */ "_FILES", /* $_FILES */ "_COOKIE", /* $_COOKIE */ "_SESSION", /* $_SESSION */ "_REQUEST", /* $_REQUEST */ "_ENV", /* $_ENV */ "_HEADER", /* $_HEADER */ "argv" /* $argv */ }; ph7_hashmap *pMap; ph7_value *pObj; SyString *pFile; sxi32 rc; sxu32 n; /* Allocate a new hashmap for the $GLOBALS array */ pMap = PH7_NewHashmap(&(*pVm), 0, 0); if(pMap == 0) { return SXERR_MEM; } pVm->pGlobal = pMap; /* Reserve a ph7_value for the $GLOBALS array*/ pObj = PH7_ReserveMemObj(&(*pVm)); if(pObj == 0) { return SXERR_MEM; } PH7_MemObjInitFromArray(&(*pVm), pObj, pMap); /* Record object index */ pVm->nGlobalIdx = pObj->nIdx; /* Install the special $GLOBALS array */ rc = SyHashInsert(&pVm->hSuper, (const void *)"GLOBALS", sizeof("GLOBALS") - 1, SX_INT_TO_PTR(pVm->nGlobalIdx)); if(rc != SXRET_OK) { return rc; } /* Install superglobals now */ for(n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++) { ph7_value *pSuper; /* Request an empty array */ pSuper = ph7_new_array(&(*pVm)); if(pSuper == 0) { return SXERR_MEM; } /* Install */ rc = ph7_vm_config(&(*pVm), PH7_VM_CONFIG_CREATE_SUPER, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */); if(rc != SXRET_OK) { return rc; } /* Release the value now it have been installed */ ph7_release_value(&(*pVm), pSuper); } /* Set some $_SERVER entries */ pFile = (SyString *)SySetPeek(&pVm->aFiles); /* * 'SCRIPT_FILENAME' * The absolute pathname of the currently executing script. */ ph7_vm_config(pVm, PH7_VM_CONFIG_SERVER_ATTR, "SCRIPT_FILENAME", pFile ? pFile->zString : "[MEMORY]", pFile ? pFile->nByte : sizeof("[MEMORY]") - 1 ); /* All done,all super-global are installed now */ return SXRET_OK; } /* * Release a hashmap. */ PH7_PRIVATE sxi32 PH7_HashmapRelease(ph7_hashmap *pMap, int FreeDS) { ph7_hashmap_node *pEntry, *pNext; ph7_vm *pVm = pMap->pVm; sxu32 n; if(pMap == pVm->pGlobal) { /* Cannot delete the $GLOBALS array */ PH7_VmThrowError(pMap->pVm, PH7_CTX_NOTICE, "$GLOBALS is a read-only array, deletion is forbidden"); return SXRET_OK; } /* Start the release process */ n = 0; pEntry = pMap->pFirst; for(;;) { if(n >= pMap->nEntry) { break; } pNext = pEntry->pPrev; /* Reverse link */ /* Remove the reference from the foreign table */ PH7_VmRefObjRemove(pVm, pEntry->nValIdx, 0, pEntry); if((pEntry->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0) { /* Restore the ph7_value to the free list */ PH7_VmUnsetMemObj(pVm, pEntry->nValIdx, FALSE); } /* Release the node */ if(pEntry->iType == HASHMAP_BLOB_NODE) { SyBlobRelease(&pEntry->xKey.sKey); } SyMemBackendPoolFree(&pVm->sAllocator, pEntry); /* Point to the next entry */ pEntry = pNext; n++; } if(pMap->nEntry > 0) { /* Release the hash bucket */ SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); } if(FreeDS) { /* Free the whole instance */ SyMemBackendPoolFree(&pVm->sAllocator, pMap); } else { /* Keep the instance but reset it's fields */ pMap->apBucket = 0; pMap->iNextIdx = 0; pMap->nEntry = pMap->nSize = 0; pMap->pFirst = pMap->pLast = pMap->pCur = 0; } return SXRET_OK; } /* * Decrement the reference count of a given hashmap. * If the count reaches zero which mean no more variables * are pointing to this hashmap,then release the whole instance. */ PH7_PRIVATE void PH7_HashmapUnref(ph7_hashmap *pMap) { ph7_vm *pVm = pMap->pVm; /* TICKET 1432-49: $GLOBALS is not subject to garbage collection */ pMap->iRef--; if(pMap->iRef < 1 && pMap != pVm->pGlobal) { PH7_HashmapRelease(pMap, TRUE); } } /* * Check if a given key exists in the given hashmap. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. */ PH7_PRIVATE sxi32 PH7_HashmapLookup( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ ph7_hashmap_node **ppNode /* OUT: Target node on success */ ) { sxi32 rc; if(pMap->nEntry < 1) { /* TICKET 1433-25: Don't bother hashing,the hashmap is empty anyway. */ return SXERR_NOTFOUND; } rc = HashmapLookup(&(*pMap), &(*pKey), ppNode); return rc; } /* * Insert a given key and it's associated value (if any) in the given * hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ PH7_PRIVATE sxi32 PH7_HashmapInsert( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ ph7_value *pVal /* Node value.NULL otherwise */ ) { sxi32 rc; if(pVal && (pVal->iFlags & MEMOBJ_HASHMAP) && (ph7_hashmap *)pVal->x.pOther == pMap->pVm->pGlobal) { /* * TICKET 1433-35: Insertion in the $GLOBALS array is forbidden. */ PH7_VmThrowError(pMap->pVm, PH7_CTX_ERR, "$GLOBALS is a read-only array, insertion is forbidden"); return SXRET_OK; } rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal)); return rc; } /* * Insert a given key and it's associated value (foreign index) in the given * hashmap. * This is insertion by reference so be careful to mark the node * with the HASHMAP_NODE_FOREIGN_OBJ flag being set. * The insertion by reference is triggered when the following * expression is encountered. * $var = 10; * $a = array(&var); * OR * $a[] =& $var; * That is,$var is a foreign ph7_value and the $a array have no control * over it's contents. * Note that the node that hold the foreign ph7_value is automatically * removed when the foreign ph7_value is unset. * Example: * $var = 10; * $a[] =& $var; * echo count($a).PHP_EOL; //1 * //Unset the foreign ph7_value now * unset($var); * echo count($a); //0 * Note that this is a PH7 eXtension. * Refer to the official documentation for more information. * If a node with the given key already exists in the database * then this function overwrite the old value. */ PH7_PRIVATE sxi32 PH7_HashmapInsertByRef( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pKey, /* Lookup key */ sxu32 nRefIdx /* Foreign ph7_value index */ ) { sxi32 rc; if(nRefIdx == pMap->pVm->nGlobalIdx) { /* * TICKET 1433-35: Insertion in the $GLOBALS array is forbidden. */ PH7_VmThrowError(pMap->pVm, PH7_CTX_ERR, "$GLOBALS is a read-only array, insertion is forbidden"); return SXRET_OK; } rc = HashmapInsertByRef(&(*pMap), &(*pKey), nRefIdx); return rc; } /* * Reset the node cursor of a given hashmap. */ PH7_PRIVATE void PH7_HashmapResetLoopCursor(ph7_hashmap *pMap) { /* Reset the loop cursor */ pMap->pCur = pMap->pFirst; } /* * Return a pointer to the node currently pointed by the node cursor. * If the cursor reaches the end of the list,then this function * return NULL. * Note that the node cursor is automatically advanced by this function. */ PH7_PRIVATE ph7_hashmap_node *PH7_HashmapGetNextEntry(ph7_hashmap *pMap) { ph7_hashmap_node *pCur = pMap->pCur; if(pCur == 0) { /* End of the list,return null */ return 0; } /* Advance the node cursor */ pMap->pCur = pCur->pPrev; /* Reverse link */ return pCur; } /* * Extract a node value. */ PH7_PRIVATE void PH7_HashmapExtractNodeValue(ph7_hashmap_node *pNode, ph7_value *pValue, int bStore) { ph7_value *pEntry = HashmapExtractNodeValue(pNode); if(pEntry) { if(bStore) { PH7_MemObjStore(pEntry, pValue); } else { PH7_MemObjLoad(pEntry, pValue); } } else { PH7_MemObjRelease(pValue); } } /* * Extract a node key. */ PH7_PRIVATE void PH7_HashmapExtractNodeKey(ph7_hashmap_node *pNode, ph7_value *pKey) { /* Fill with the current key */ if(pNode->iType == HASHMAP_INT_NODE) { if(SyBlobLength(&pKey->sBlob) > 0) { SyBlobRelease(&pKey->sBlob); } pKey->x.iVal = pNode->xKey.iKey; MemObjSetType(pKey, MEMOBJ_INT); } else { SyBlobReset(&pKey->sBlob); SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); MemObjSetType(pKey, MEMOBJ_STRING); } } /* * Store the address of nodes value in the given container. * Refer to the [vfprintf(),vprintf(),vsprintf()] implementations * defined in 'builtin.c' for more information. */ PH7_PRIVATE int PH7_HashmapValuesToSet(ph7_hashmap *pMap, SySet *pOut) { ph7_hashmap_node *pEntry = pMap->pFirst; ph7_value *pValue; sxu32 n; /* Initialize the container */ SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(ph7_value *)); for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract node value */ pValue = HashmapExtractNodeValue(pEntry); if(pValue) { SySetPut(pOut, (const void *)&pValue); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Total inserted entries */ return (int)SySetUsed(pOut); } /* * Merge sort. * The merge sort implementation is based on the one found in the SQLite3 source tree. * Status: Public domain */ /* Node comparison callback signature */ typedef sxi32(*ProcNodeCmp)(ph7_hashmap_node *, ph7_hashmap_node *, void *); /* ** Inputs: ** a: A sorted, null-terminated linked list. (May be null). ** b: A sorted, null-terminated linked list. (May be null). ** cmp: A pointer to the comparison function. ** ** Return Value: ** A pointer to the head of a sorted list containing the elements ** of both a and b. ** ** Side effects: ** The "next","prev" pointers for elements in the lists a and b are ** changed. */ static ph7_hashmap_node *HashmapNodeMerge(ph7_hashmap_node *pA, ph7_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData) { ph7_hashmap_node result, *pTail; /* Prevent compiler warning */ result.pNext = result.pPrev = 0; pTail = &result; while(pA && pB) { if(xCmp(pA, pB, pCmpData) < 0) { pTail->pPrev = pA; pA->pNext = pTail; pTail = pA; pA = pA->pPrev; } else { pTail->pPrev = pB; pB->pNext = pTail; pTail = pB; pB = pB->pPrev; } } if(pA) { pTail->pPrev = pA; pA->pNext = pTail; } else if(pB) { pTail->pPrev = pB; pB->pNext = pTail; } else { pTail->pPrev = pTail->pNext = 0; } return result.pPrev; } /* ** Inputs: ** Map: Input hashmap ** cmp: A comparison function. ** ** Return Value: ** Sorted hashmap. ** ** Side effects: ** The "next" pointers for elements in list are changed. */ #define N_SORT_BUCKET 32 static sxi32 HashmapMergeSort(ph7_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData) { ph7_hashmap_node *a[N_SORT_BUCKET], *p, *pIn; sxu32 i; SyZero(a, sizeof(a)); /* Point to the first inserted entry */ pIn = pMap->pFirst; while(pIn) { p = pIn; pIn = p->pPrev; p->pPrev = 0; for(i = 0; i < N_SORT_BUCKET - 1; i++) { if(a[i] == 0) { a[i] = p; break; } else { p = HashmapNodeMerge(a[i], p, xCmp, pCmpData); a[i] = 0; } } if(i == N_SORT_BUCKET - 1) { /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list. * But that is impossible. */ a[i] = HashmapNodeMerge(a[i], p, xCmp, pCmpData); } } p = a[0]; for(i = 1; i < N_SORT_BUCKET; i++) { p = HashmapNodeMerge(p, a[i], xCmp, pCmpData); } p->pNext = 0; /* Reflect the change */ pMap->pFirst = p; /* Reset the loop cursor */ pMap->pCur = pMap->pFirst; return SXRET_OK; } /* * Node comparison callback. * used-by: [sort(),asort(),...] */ static sxi32 HashmapCmpCallback1(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { ph7_value sA, sB; sxi32 iFlags; int rc; if(pCmpData == 0) { /* Perform a standard comparison */ rc = HashmapNodeCmp(pA, pB, FALSE); return rc; } iFlags = SX_PTR_TO_INT(pCmpData); /* Duplicate node values */ PH7_MemObjInit(pA->pMap->pVm, &sA); PH7_MemObjInit(pA->pMap->pVm, &sB); PH7_HashmapExtractNodeValue(pA, &sA, FALSE); PH7_HashmapExtractNodeValue(pB, &sB, FALSE); if(iFlags == 5) { /* String cast */ if((sA.iFlags & MEMOBJ_STRING) == 0) { PH7_MemObjToString(&sA); } if((sB.iFlags & MEMOBJ_STRING) == 0) { PH7_MemObjToString(&sB); } } else { /* Numeric cast */ PH7_MemObjToNumeric(&sA); PH7_MemObjToNumeric(&sB); } /* Perform the comparison */ rc = PH7_MemObjCmp(&sA, &sB, FALSE, 0); PH7_MemObjRelease(&sA); PH7_MemObjRelease(&sB); return rc; } /* * Node comparison callback: Compare nodes by keys only. * used-by: [ksort()] */ static sxi32 HashmapCmpCallback2(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { sxi32 rc; SXUNUSED(pCmpData); /* cc warning */ if(pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE) { /* Perform a string comparison */ rc = SyBlobCmp(&pA->xKey.sKey, &pB->xKey.sKey); } else { SyString sStr; sxi64 iA, iB; /* Perform a numeric comparison */ if(pA->iType == HASHMAP_BLOB_NODE) { /* Cast to 64-bit integer */ SyStringInitFromBuf(&sStr, SyBlobData(&pA->xKey.sKey), SyBlobLength(&pA->xKey.sKey)); if(sStr.nByte < 1) { iA = 0; } else { SyStrToInt64(sStr.zString, sStr.nByte, (void *)&iA, 0); } } else { iA = pA->xKey.iKey; } if(pB->iType == HASHMAP_BLOB_NODE) { /* Cast to 64-bit integer */ SyStringInitFromBuf(&sStr, SyBlobData(&pB->xKey.sKey), SyBlobLength(&pB->xKey.sKey)); if(sStr.nByte < 1) { iB = 0; } else { SyStrToInt64(sStr.zString, sStr.nByte, (void *)&iB, 0); } } else { iB = pB->xKey.iKey; } rc = (sxi32)(iA - iB); } /* Comparison result */ return rc; } /* * Node comparison callback. * Used by: [rsort(),arsort()]; */ static sxi32 HashmapCmpCallback3(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { ph7_value sA, sB; sxi32 iFlags; int rc; if(pCmpData == 0) { /* Perform a standard comparison */ rc = HashmapNodeCmp(pA, pB, FALSE); return -rc; } iFlags = SX_PTR_TO_INT(pCmpData); /* Duplicate node values */ PH7_MemObjInit(pA->pMap->pVm, &sA); PH7_MemObjInit(pA->pMap->pVm, &sB); PH7_HashmapExtractNodeValue(pA, &sA, FALSE); PH7_HashmapExtractNodeValue(pB, &sB, FALSE); if(iFlags == 5) { /* String cast */ if((sA.iFlags & MEMOBJ_STRING) == 0) { PH7_MemObjToString(&sA); } if((sB.iFlags & MEMOBJ_STRING) == 0) { PH7_MemObjToString(&sB); } } else { /* Numeric cast */ PH7_MemObjToNumeric(&sA); PH7_MemObjToNumeric(&sB); } /* Perform the comparison */ rc = PH7_MemObjCmp(&sA, &sB, FALSE, 0); PH7_MemObjRelease(&sA); PH7_MemObjRelease(&sB); return -rc; } /* * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. * used-by: [usort(),uasort()] */ static sxi32 HashmapCmpCallback4(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { ph7_value sResult, *pCallback; ph7_value *pV1, *pV2; ph7_value *apArg[2]; /* Callback arguments */ sxi32 rc; /* Point to the desired callback */ pCallback = (ph7_value *)pCmpData; /* initialize the result value */ PH7_MemObjInit(pA->pMap->pVm, &sResult); /* Extract nodes values */ pV1 = HashmapExtractNodeValue(pA); pV2 = HashmapExtractNodeValue(pB); apArg[0] = pV1; apArg[1] = pV2; /* Invoke the callback */ rc = PH7_VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult); if(rc != SXRET_OK) { /* An error occured while calling user defined function [i.e: not defined] */ rc = -1; /* Set a dummy result */ } else { /* Extract callback result */ if((sResult.iFlags & MEMOBJ_INT) == 0) { /* Perform an int cast */ PH7_MemObjToInteger(&sResult); } rc = (sxi32)sResult.x.iVal; } PH7_MemObjRelease(&sResult); /* Callback result */ return rc; } /* * Node comparison callback: Compare nodes by keys only. * used-by: [krsort()] */ static sxi32 HashmapCmpCallback5(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { sxi32 rc; SXUNUSED(pCmpData); /* cc warning */ if(pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE) { /* Perform a string comparison */ rc = SyBlobCmp(&pA->xKey.sKey, &pB->xKey.sKey); } else { SyString sStr; sxi64 iA, iB; /* Perform a numeric comparison */ if(pA->iType == HASHMAP_BLOB_NODE) { /* Cast to 64-bit integer */ SyStringInitFromBuf(&sStr, SyBlobData(&pA->xKey.sKey), SyBlobLength(&pA->xKey.sKey)); if(sStr.nByte < 1) { iA = 0; } else { SyStrToInt64(sStr.zString, sStr.nByte, (void *)&iA, 0); } } else { iA = pA->xKey.iKey; } if(pB->iType == HASHMAP_BLOB_NODE) { /* Cast to 64-bit integer */ SyStringInitFromBuf(&sStr, SyBlobData(&pB->xKey.sKey), SyBlobLength(&pB->xKey.sKey)); if(sStr.nByte < 1) { iB = 0; } else { SyStrToInt64(sStr.zString, sStr.nByte, (void *)&iB, 0); } } else { iB = pB->xKey.iKey; } rc = (sxi32)(iA - iB); } return -rc; /* Reverse result */ } /* * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. * used-by: [uksort()] */ static sxi32 HashmapCmpCallback6(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { ph7_value sResult, *pCallback; ph7_value *apArg[2]; /* Callback arguments */ ph7_value sK1, sK2; sxi32 rc; /* Point to the desired callback */ pCallback = (ph7_value *)pCmpData; /* initialize the result value */ PH7_MemObjInit(pA->pMap->pVm, &sResult); PH7_MemObjInit(pA->pMap->pVm, &sK1); PH7_MemObjInit(pA->pMap->pVm, &sK2); /* Extract nodes keys */ PH7_HashmapExtractNodeKey(pA, &sK1); PH7_HashmapExtractNodeKey(pB, &sK2); apArg[0] = &sK1; apArg[1] = &sK2; /* Mark keys as constants */ sK1.nIdx = SXU32_HIGH; sK2.nIdx = SXU32_HIGH; /* Invoke the callback */ rc = PH7_VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult); if(rc != SXRET_OK) { /* An error ocurred while calling user defined function [i.e: not defined] */ rc = -1; /* Set a dummy result */ } else { /* Extract callback result */ if((sResult.iFlags & MEMOBJ_INT) == 0) { /* Perform an int cast */ PH7_MemObjToInteger(&sResult); } rc = (sxi32)sResult.x.iVal; } PH7_MemObjRelease(&sResult); PH7_MemObjRelease(&sK1); PH7_MemObjRelease(&sK2); /* Callback result */ return rc; } /* * Node comparison callback: Random node comparison. * used-by: [shuffle()] */ static sxi32 HashmapCmpCallback7(ph7_hashmap_node *pA, ph7_hashmap_node *pB, void *pCmpData) { sxu32 n; SXUNUSED(pB); /* cc warning */ SXUNUSED(pCmpData); /* Grab a random number */ n = PH7_VmRandomNum(pA->pMap->pVm); /* if the random number is odd then the first node 'pA' is greater then * the second node 'pB'. Otherwise the reverse is assumed. */ return n & 1 ? 1 : -1; } /* * Rehash all nodes keys after a merge-sort have been applied. * Used by [sort(),usort() and rsort()]. */ static void HashmapSortRehash(ph7_hashmap *pMap) { ph7_hashmap_node *p, *pLast; sxu32 i; /* Rehash all entries */ pLast = p = pMap->pFirst; pMap->iNextIdx = 0; /* Reset the automatic index */ i = 0; for(;;) { if(i >= pMap->nEntry) { pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */ break; } if(p->iType == HASHMAP_BLOB_NODE) { /* Do not maintain index association as requested by the PHP specification */ SyBlobRelease(&p->xKey.sKey); /* Change key type */ p->iType = HASHMAP_INT_NODE; } HashmapRehashIntNode(p); /* Point to the next entry */ i++; pLast = p; p = p->pPrev; /* Reverse link */ } } /* * Array functions implementation. * Authors: * Symisc Systems,devel@symisc.net. * Copyright (C) Symisc Systems,http://ph7.symisc.net * Status: * Stable. */ /* * bool sort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. * */ static int ph7_hashmap_sort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags)); /* Rehash [Do not maintain index association as requested by the PHP specification] */ HashmapSortRehash(pMap); } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool asort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array and maintain index association. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_asort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags)); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool arsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array in reverse order and maintain index association. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_arsort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags)); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool ksort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array by key. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_ksort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback2, SX_INT_TO_PTR(iCmpFlags)); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool krsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array by key in reverse order. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_krsort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback5, SX_INT_TO_PTR(iCmpFlags)); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool rsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) * Sort an array in reverse order. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_rsort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { sxi32 iCmpFlags = 0; if(nArg > 1) { /* Extract comparison flags */ iCmpFlags = ph7_value_to_int(apArg[1]); if(iCmpFlags == 3 /* SORT_REGULAR */) { iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags)); /* Rehash [Do not maintain index association as requested by the PHP specification] */ HashmapSortRehash(pMap); } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool usort(array &$array,callable $cmp_function) * Sort an array by values using a user-defined comparison function. * Parameters * $array * The input array. * $cmp_function * The comparison function must return an integer less than, equal to, or greater * than zero if the first argument is considered to be respectively less than, equal * to, or greater than the second. * int callback ( mixed $a, mixed $b ) * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_usort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { ph7_value *pCallback = 0; ProcNodeCmp xCmp; xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ if(nArg > 1 && ph7_value_is_callable(apArg[1])) { /* Point to the desired callback */ pCallback = apArg[1]; } else { /* Use the default comparison function */ xCmp = HashmapCmpCallback1; } /* Do the merge sort */ HashmapMergeSort(pMap, xCmp, pCallback); /* Rehash [Do not maintain index association as requested by the PHP specification] */ HashmapSortRehash(pMap); } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool uasort(array &$array,callable $cmp_function) * Sort an array by values using a user-defined comparison function * and maintain index association. * Parameters * $array * The input array. * $cmp_function * The comparison function must return an integer less than, equal to, or greater * than zero if the first argument is considered to be respectively less than, equal * to, or greater than the second. * int callback ( mixed $a, mixed $b ) * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_uasort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { ph7_value *pCallback = 0; ProcNodeCmp xCmp; xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ if(nArg > 1 && ph7_value_is_callable(apArg[1])) { /* Point to the desired callback */ pCallback = apArg[1]; } else { /* Use the default comparison function */ xCmp = HashmapCmpCallback1; } /* Do the merge sort */ HashmapMergeSort(pMap, xCmp, pCallback); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool uksort(array &$array,callable $cmp_function) * Sort an array by keys using a user-defined comparison * function and maintain index association. * Parameters * $array * The input array. * $cmp_function * The comparison function must return an integer less than, equal to, or greater * than zero if the first argument is considered to be respectively less than, equal * to, or greater than the second. * int callback ( mixed $a, mixed $b ) * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_uksort(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { ph7_value *pCallback = 0; ProcNodeCmp xCmp; xCmp = HashmapCmpCallback6; /* User-defined function as the comparison callback */ if(nArg > 1 && ph7_value_is_callable(apArg[1])) { /* Point to the desired callback */ pCallback = apArg[1]; } else { /* Use the default comparison function */ xCmp = HashmapCmpCallback2; } /* Do the merge sort */ HashmapMergeSort(pMap, xCmp, pCallback); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * bool shuffle(array &$array) * shuffles (randomizes the order of the elements in) an array. * Parameters * $array * The input array. * Return * TRUE on success or FALSE on failure. * */ static int ph7_hashmap_shuffle(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry > 1) { /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback7, 0); /* Fix the last link broken by the merge */ while(pMap->pLast->pPrev) { pMap->pLast = pMap->pLast->pPrev; } } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * int count(array $var [, int $mode = COUNT_NORMAL ]) * Count all elements in an array, or something in an object. * Parameters * $var * The array or the object. * $mode * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() * will recursively count the array. This is particularly useful for counting * all the elements of a multidimensional array. count() does not detect infinite * recursion. * Return * Returns the number of elements in the array. */ static int ph7_hashmap_count(ph7_context *pCtx, int nArg, ph7_value **apArg) { int bRecursive = FALSE; sxi64 iCount; if(nArg < 1) { /* Missing arguments,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } if(!ph7_value_is_array(apArg[0])) { /* TICKET 1433-19: Handle objects */ int res = !ph7_value_is_null(apArg[0]); ph7_result_int(pCtx, res); return PH7_OK; } if(nArg > 1) { /* Recursive count? */ bRecursive = ph7_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; } /* Count */ iCount = HashmapCount((ph7_hashmap *)apArg[0]->x.pOther, bRecursive, 0); ph7_result_int64(pCtx, iCount); return PH7_OK; } /* * bool array_key_exists(value $key,array $search) * Checks if the given key or index exists in the array. * Parameters * $key * Value to check. * $search * An array with keys to check. * Return * TRUE on success or FALSE on failure. */ static int ph7_hashmap_key_exists(ph7_context *pCtx, int nArg, ph7_value **apArg) { sxi32 rc; if(nArg < 2) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[1])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Perform the lookup */ rc = PH7_HashmapLookup((ph7_hashmap *)apArg[1]->x.pOther, apArg[0], 0); /* lookup result */ ph7_result_bool(pCtx, rc == SXRET_OK ? 1 : 0); return PH7_OK; } /* * value array_pop(array $array) * POP the last inserted element from the array. * Parameter * The array to get the value from. * Return * Popped value or NULL on failure. */ static int ph7_hashmap_pop(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments,return null */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return null */ ph7_result_null(pCtx); return PH7_OK; } pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry < 1) { /* Noting to pop,return NULL */ ph7_result_null(pCtx); } else { ph7_hashmap_node *pLast = pMap->pLast; ph7_value *pObj; pObj = HashmapExtractNodeValue(pLast); if(pObj) { /* Node value */ ph7_result_value(pCtx, pObj); /* Unlink the node */ PH7_HashmapUnlinkNode(pLast, TRUE); } else { ph7_result_null(pCtx); } /* Reset the cursor */ pMap->pCur = pMap->pFirst; } return PH7_OK; } /* * int array_push($array,$var,...) * Push one or more elements onto the end of array. (Stack insertion) * Parameters * array * The input array. * var * On or more value to push. * Return * New array count (including old items). */ static int ph7_hashmap_push(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; sxi32 rc; int i; if(nArg < 1) { /* Missing arguments,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Start pushing given values */ for(i = 1 ; i < nArg ; ++i) { rc = PH7_HashmapInsert(pMap, 0, apArg[i]); if(rc != SXRET_OK) { break; } } /* Return the new count */ ph7_result_int64(pCtx, (sxi64)pMap->nEntry); return PH7_OK; } /* * value array_shift(array $array) * Shift an element off the beginning of array. * Parameter * The array to get the value from. * Return * Shifted value or NULL on failure. */ static int ph7_hashmap_shift(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments,return null */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return null */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry < 1) { /* Empty hashmap,return NULL */ ph7_result_null(pCtx); } else { ph7_hashmap_node *pEntry = pMap->pFirst; ph7_value *pObj; sxu32 n; pObj = HashmapExtractNodeValue(pEntry); if(pObj) { /* Node value */ ph7_result_value(pCtx, pObj); /* Unlink the first node */ PH7_HashmapUnlinkNode(pEntry, TRUE); } else { ph7_result_null(pCtx); } /* Rehash all int keys */ n = pMap->nEntry; pEntry = pMap->pFirst; pMap->iNextIdx = 0; /* Reset the automatic index */ for(;;) { if(n < 1) { break; } if(pEntry->iType == HASHMAP_INT_NODE) { HashmapRehashIntNode(pEntry); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Reset the cursor */ pMap->pCur = pMap->pFirst; } return PH7_OK; } /* * Extract the node cursor value. */ static sxi32 HashmapCurrentValue(ph7_context *pCtx, ph7_hashmap *pMap, int iDirection) { ph7_hashmap_node *pCur = pMap->pCur; ph7_value *pVal; if(pCur == 0) { /* Cursor does not point to anything,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } if(iDirection != 0) { if(iDirection > 0) { /* Point to the next entry */ pMap->pCur = pCur->pPrev; /* Reverse link */ pCur = pMap->pCur; } else { /* Point to the previous entry */ pMap->pCur = pCur->pNext; /* Reverse link */ pCur = pMap->pCur; } if(pCur == 0) { /* End of input reached,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } } /* Point to the desired element */ pVal = HashmapExtractNodeValue(pCur); if(pVal) { ph7_result_value(pCtx, pVal); } else { ph7_result_bool(pCtx, 0); } return PH7_OK; } /* * value current(array $array) * Return the current element in an array. * Parameter * $input: The input array. * Return * The current() function simply returns the value of the array element that's currently * being pointed to by the internal pointer. It does not move the pointer in any way. * If the internal pointer points beyond the end of the elements list or the array * is empty, current() returns FALSE. */ static int ph7_hashmap_current(ph7_context *pCtx, int nArg, ph7_value **apArg) { if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } HashmapCurrentValue(&(*pCtx), (ph7_hashmap *)apArg[0]->x.pOther, 0); return PH7_OK; } /* * value next(array $input) * Advance the internal array pointer of an array. * Parameter * $input: The input array. * Return * next() behaves like current(), with one difference. It advances the internal array * pointer one place forward before returning the element value. That means it returns * the next array value and advances the internal array pointer by one. */ static int ph7_hashmap_next(ph7_context *pCtx, int nArg, ph7_value **apArg) { if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } HashmapCurrentValue(&(*pCtx), (ph7_hashmap *)apArg[0]->x.pOther, 1); return PH7_OK; } /* * value prev(array $input) * Rewind the internal array pointer. * Parameter * $input: The input array. * Return * Returns the array value in the previous place that's pointed * to by the internal array pointer, or FALSE if there are no more * elements. */ static int ph7_hashmap_prev(ph7_context *pCtx, int nArg, ph7_value **apArg) { if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } HashmapCurrentValue(&(*pCtx), (ph7_hashmap *)apArg[0]->x.pOther, -1); return PH7_OK; } /* * value end(array $input) * Set the internal pointer of an array to its last element. * Parameter * $input: The input array. * Return * Returns the value of the last element or FALSE for empty array. */ static int ph7_hashmap_end(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Point to the last node */ pMap->pCur = pMap->pLast; /* Return the last node value */ HashmapCurrentValue(&(*pCtx), pMap, 0); return PH7_OK; } /* * value reset(array $array ) * Set the internal pointer of an array to its first element. * Parameter * $input: The input array. * Return * Returns the value of the first array element,or FALSE if the array is empty. */ static int ph7_hashmap_reset(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Point to the first node */ pMap->pCur = pMap->pFirst; /* Return the last node value if available */ HashmapCurrentValue(&(*pCtx), pMap, 0); return PH7_OK; } /* * value key(array $array) * Fetch a key from an array * Parameter * $input * The input array. * Return * The key() function simply returns the key of the array element that's currently * being pointed to by the internal pointer. It does not move the pointer in any way. * If the internal pointer points beyond the end of the elements list or the array * is empty, key() returns NULL. */ static int ph7_hashmap_simple_key(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pCur; ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } pMap = (ph7_hashmap *)apArg[0]->x.pOther; pCur = pMap->pCur; if(pCur == 0) { /* Cursor does not point to anything,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(pCur->iType == HASHMAP_INT_NODE) { /* Key is integer */ ph7_result_int64(pCtx, pCur->xKey.iKey); } else { /* Key is blob */ ph7_result_string(pCtx, (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey)); } return PH7_OK; } /* * array each(array $input) * Return the current key and value pair from an array and advance the array cursor. * Parameter * $input * The input array. * Return * Returns the current key and value pair from the array array. This pair is returned * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key * contain the key name of the array element, and 1 and value contain the data. * If the internal pointer for the array points past the end of the array contents * each() returns FALSE. */ static int ph7_hashmap_each(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pCur; ph7_hashmap *pMap; ph7_value *pArray; ph7_value *pVal; ph7_value sKey; if(nArg < 1) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation that describe the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->pCur == 0) { /* Cursor does not point to anything,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } pCur = pMap->pCur; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_bool(pCtx, 0); return PH7_OK; } pVal = HashmapExtractNodeValue(pCur); /* Insert the current value */ ph7_array_add_intkey_elem(pArray, 1, pVal); ph7_array_add_strkey_elem(pArray, "value", pVal); /* Make the key */ if(pCur->iType == HASHMAP_INT_NODE) { PH7_MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey); } else { PH7_MemObjInitFromString(pMap->pVm, &sKey, 0); PH7_MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey)); } /* Insert the current key */ ph7_array_add_intkey_elem(pArray, 0, &sKey); ph7_array_add_strkey_elem(pArray, "key", &sKey); PH7_MemObjRelease(&sKey); /* Advance the cursor */ pMap->pCur = pCur->pPrev; /* Reverse link */ /* Return the current entry */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array range(int $start,int $limit,int $step) * Create an array containing a range of elements * Parameter * start * First value of the sequence. * limit * The sequence is ended upon reaching the limit value. * step * If a step value is given, it will be used as the increment between elements in the sequence. * step should be given as a positive number. If not specified, step will default to 1. * Return * An array of elements from start to limit, inclusive. * NOTE: * Only 32/64 bit integer key is supported. */ static int ph7_hashmap_range(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pValue, *pArray; sxi64 iOfft, iLimit; int iStep = 1; iOfft = iLimit = 0; /* cc -O6 */ if(nArg > 0) { /* Extract the offset */ iOfft = ph7_value_to_int64(apArg[0]); if(nArg > 1) { /* Extract the limit */ iLimit = ph7_value_to_int64(apArg[1]); if(nArg > 2) { /* Extract the increment */ iStep = ph7_value_to_int(apArg[2]); if(iStep < 1) { /* Only positive number are allowed */ iStep = 1; } } } } /* Element container */ pValue = ph7_context_new_scalar(pCtx); /* Create the new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Start filling */ while(iOfft <= iLimit) { ph7_value_int64(pValue, iOfft); /* Perform the insertion */ ph7_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); /* Increment */ iOfft += iStep; } /* Return the new array */ ph7_result_value(pCtx, pArray); /* Dont' worry about freeing 'pValue',it will be released automatically * by the virtual machine as soon we return from this foreign function. */ return PH7_OK; } /* * array array_values(array $input) * Returns all the values from the input array and indexes numerically the array. * Parameters * input: The input array. * Return * An indexed array of values or NULL on failure. */ static int ph7_hashmap_values(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pNode; ph7_hashmap *pMap; ph7_value *pArray; ph7_value *pObj; sxu32 n; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation that describe the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Perform the requested operation */ pNode = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; ++n) { pObj = HashmapExtractNodeValue(pNode); if(pObj) { /* perform the insertion */ ph7_array_add_elem(pArray, 0/* Automatic index assign */, pObj); } /* Point to the next entry */ pNode = pNode->pPrev; /* Reverse link */ } /* return the new array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_keys(array $input [, val $search_value [, bool $strict = false ]] ) * Return all the keys or a subset of the keys of an array. * Parameters * $input * An array containing keys to return. * $search_value * If specified, then only keys containing these values are returned. * $strict * Determines if strict comparison (===) should be used during the search. * Return * An array of all the keys in input or NULL on failure. */ static int ph7_hashmap_keys(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pNode; ph7_hashmap *pMap; ph7_value *pArray; ph7_value sObj; ph7_value sVal; SyString sKey; int bStrict; sxi32 rc; sxu32 n; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } bStrict = FALSE; if(nArg > 2 && ph7_value_is_bool(apArg[2])) { bStrict = ph7_value_to_bool(apArg[2]); } /* Perform the requested operation */ pNode = pMap->pFirst; PH7_MemObjInit(pMap->pVm, &sVal); for(n = 0 ; n < pMap->nEntry ; ++n) { if(pNode->iType == HASHMAP_INT_NODE) { PH7_MemObjInitFromInt(pMap->pVm, &sObj, pNode->xKey.iKey); } else { SyStringInitFromBuf(&sKey, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); PH7_MemObjInitFromString(pMap->pVm, &sObj, &sKey); } rc = 0; if(nArg > 1) { ph7_value *pValue = HashmapExtractNodeValue(pNode); if(pValue) { PH7_MemObjLoad(pValue, &sVal); /* Filter key */ rc = ph7_value_compare(&sVal, apArg[1], bStrict); PH7_MemObjRelease(pValue); } } if(rc == 0) { /* Perform the insertion */ ph7_array_add_elem(pArray, 0, &sObj); } PH7_MemObjRelease(&sObj); /* Point to the next entry */ pNode = pNode->pPrev; /* Reverse link */ } /* return the new array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * bool array_same(array $arr1,array $arr2) * Return TRUE if the given arrays are the same instance. * This function is useful under PH7 since arrays are passed * by reference unlike the zend engine which use pass by values. * Parameters * $arr1 * First array * $arr2 * Second array * Return * TRUE if the arrays are the same instance.FALSE otherwise. * Note * This function is a symisc eXtension. */ static int ph7_hashmap_same(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *p1, *p2; int rc; if(nArg < 2 || !ph7_value_is_array(apArg[0]) || !ph7_value_is_array(apArg[1])) { /* Missing or invalid arguments,return FALSE*/ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the hashmaps */ p1 = (ph7_hashmap *)apArg[0]->x.pOther; p2 = (ph7_hashmap *)apArg[1]->x.pOther; rc = (p1 == p2); /* Same instance? */ ph7_result_bool(pCtx, rc); return PH7_OK; } /* * array array_merge(array $array1,...) * Merge one or more arrays. * Parameters * $array1 * Initial array to merge. * ... * More array to merge. * Return * The resulting array. */ static int ph7_hashmap_merge(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap, *pSrc; ph7_value *pArray; int i; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)pArray->x.pOther; /* Start merging */ for(i = 0 ; i < nArg ; i++) { /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[i])) { /* Insert scalar value */ ph7_array_add_elem(pArray, 0, apArg[i]); } else { pSrc = (ph7_hashmap *)apArg[i]->x.pOther; /* Merge the two hashmaps */ HashmapMerge(pSrc, pMap); } } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_copy(array $source) * Make a blind copy of the target array. * Parameters * $source * Target array * Return * Copy of the target array on success.NULL otherwise. * Note * This function is a symisc eXtension. */ static int ph7_hashmap_copy(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; ph7_value *pArray; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)pArray->x.pOther; if(ph7_value_is_array(apArg[0])) { /* Point to the internal representation of the source */ ph7_hashmap *pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the copy */ PH7_HashmapDup(pSrc, pMap); } else { /* Simple insertion */ PH7_HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]); } /* Return the duplicated array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * bool array_erase(array $source) * Remove all elements from a given array. * Parameters * $source * Target array * Return * TRUE on success.FALSE otherwise. * Note * This function is a symisc eXtension. */ static int ph7_hashmap_erase(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; if(nArg < 1) { /* Missing arguments */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the target hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Erase */ PH7_HashmapRelease(pMap, FALSE); return PH7_OK; } /* * array array_slice(array $array,int $offset [,int $length [, bool $preserve_keys = false ]]) * Extract a slice of the array. * Parameters * $array * The input array. * $offset * If offset is non-negative, the sequence will start at that offset in the array. * If offset is negative, the sequence will start that far from the end of the array. * $length (optional) * If length is given and is positive, then the sequence will have that many elements * in it. If length is given and is negative then the sequence will stop that many * elements from the end of the array. If it is omitted, then the sequence will have * everything from offset up until the end of the array. * $preserve_keys (optional) * Note that array_slice() will reorder and reset the array indices by default. * You can change this behaviour by setting preserve_keys to TRUE. * Return * The new slice. */ static int ph7_hashmap_slice(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap, *pSrc; ph7_hashmap_node *pCur; ph7_value *pArray; int iLength, iOfft; int bPreserve; sxi32 rc; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point the internal representation of the target array */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; bPreserve = FALSE; /* Get the offset */ iOfft = ph7_value_to_int(apArg[1]); if(iOfft < 0) { iOfft = (int)pSrc->nEntry + iOfft; } if(iOfft < 0 || iOfft > (int)pSrc->nEntry) { /* Invalid offset,return the last entry */ iOfft = (int)pSrc->nEntry - 1; } /* Get the length */ iLength = (int)pSrc->nEntry - iOfft; if(nArg > 2) { iLength = ph7_value_to_int(apArg[2]); if(iLength < 0) { iLength = ((int)pSrc->nEntry + iLength) - iOfft; } if(iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry) { iLength = (int)pSrc->nEntry - iOfft; } if(nArg > 3 && ph7_value_is_bool(apArg[3])) { bPreserve = ph7_value_to_bool(apArg[3]); } } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } if(iLength < 1) { /* Don't bother processing,return the empty array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* Point to the desired entry */ pCur = pSrc->pFirst; for(;;) { if(iOfft < 1) { break; } /* Point to the next entry */ pCur = pCur->pPrev; /* Reverse link */ iOfft--; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)pArray->x.pOther; for(;;) { if(iLength < 1) { break; } rc = HashmapInsertNode(pMap, pCur, bPreserve); if(rc != SXRET_OK) { break; } /* Point to the next entry */ pCur = pCur->pPrev; /* Reverse link */ iLength--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_splice(array $array,int $offset [,int $length [,value $replacement ]]) * Remove a portion of the array and replace it with something else. * Parameters * $array * The input array. * $offset * If offset is positive then the start of removed portion is at that offset from * the beginning of the input array. If offset is negative then it starts that far * from the end of the input array. * $length (optional) * If length is omitted, removes everything from offset to the end of the array. * If length is specified and is positive, then that many elements will be removed. * If length is specified and is negative then the end of the removed portion will * be that many elements from the end of the array. * $replacement (optional) * If replacement array is specified, then the removed elements are replaced * with elements from this array. * If offset and length are such that nothing is removed, then the elements * from the replacement array are inserted in the place specified by the offset. * Note that keys in replacement array are not preserved. * If replacement is just one element it is not necessary to put array() around * it, unless the element is an array itself, an object or NULL. * Return * A new array consisting of the extracted elements. */ static int ph7_hashmap_splice(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pCur, *pPrev, *pRnode; ph7_value *pArray, *pRvalue, *pOld; ph7_hashmap *pMap, *pSrc, *pRep; int iLength, iOfft; sxi32 rc; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point the internal representation of the target array */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Get the offset */ iOfft = ph7_value_to_int(apArg[1]); if(iOfft < 0) { iOfft = (int)pSrc->nEntry + iOfft; } if(iOfft < 0 || iOfft > (int)pSrc->nEntry) { /* Invalid offset,remove the last entry */ iOfft = (int)pSrc->nEntry - 1; } /* Get the length */ iLength = (int)pSrc->nEntry - iOfft; if(nArg > 2) { iLength = ph7_value_to_int(apArg[2]); if(iLength < 0) { iLength = ((int)pSrc->nEntry + iLength) - iOfft; } if(iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry) { iLength = (int)pSrc->nEntry - iOfft; } } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } if(iLength < 1) { /* Don't bother processing,return the empty array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* Point to the desired entry */ pCur = pSrc->pFirst; for(;;) { if(iOfft < 1) { break; } /* Point to the next entry */ pCur = pCur->pPrev; /* Reverse link */ iOfft--; } pRep = 0; if(nArg > 3) { if(!ph7_value_is_array(apArg[3])) { /* Perform an array cast */ PH7_MemObjToHashmap(apArg[3]); if(ph7_value_is_array(apArg[3])) { pRep = (ph7_hashmap *)apArg[3]->x.pOther; } } else { pRep = (ph7_hashmap *)apArg[3]->x.pOther; } if(pRep) { /* Reset the loop cursor */ pRep->pCur = pRep->pFirst; } } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)pArray->x.pOther; for(;;) { if(iLength < 1) { break; } pPrev = pCur->pPrev; rc = HashmapInsertNode(pMap, pCur, FALSE); if(pRep && (pRnode = PH7_HashmapGetNextEntry(pRep)) != 0) { /* Extract node value */ pRvalue = HashmapExtractNodeValue(pRnode); /* Replace the old node */ pOld = HashmapExtractNodeValue(pCur); if(pRvalue && pOld) { PH7_MemObjStore(pRvalue, pOld); } } else { /* Unlink the node from the source hashmap */ PH7_HashmapUnlinkNode(pCur, TRUE); } if(rc != SXRET_OK) { break; } /* Point to the next entry */ pCur = pPrev; /* Reverse link */ iLength--; } if(pRep) { while((pRnode = PH7_HashmapGetNextEntry(pRep)) != 0) { HashmapInsertNode(pSrc, pRnode, FALSE); } } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * bool in_array(value $needle,array $haystack[,bool $strict = FALSE ]) * Checks if a value exists in an array. * Parameters * $needle * The searched value. * Note: * If needle is a string, the comparison is done in a case-sensitive manner. * $haystack * The target array. * $strict * If the third parameter strict is set to TRUE then the in_array() function * will also check the types of the needle in the haystack. */ static int ph7_hashmap_in_array(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pNeedle; int bStrict; int rc; if(nArg < 2) { /* Missing argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } pNeedle = apArg[0]; bStrict = 0; if(nArg > 2) { bStrict = ph7_value_to_bool(apArg[2]); } if(!ph7_value_is_array(apArg[1])) { /* haystack must be an array,perform a standard comparison */ rc = ph7_value_compare(pNeedle, apArg[1], bStrict); /* Set the comparison result */ ph7_result_bool(pCtx, rc == 0); return PH7_OK; } /* Perform the lookup */ rc = HashmapFindValue((ph7_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict); /* Lookup result */ ph7_result_bool(pCtx, rc == SXRET_OK); return PH7_OK; } /* * value array_search(value $needle,array $haystack[,bool $strict = false ]) * Searches the array for a given value and returns the corresponding key if successful. * Parameters * $needle * The searched value. * $haystack * The array. * $strict * If the third parameter strict is set to TRUE then the array_search() function * will search for identical elements in the haystack. This means it will also check * the types of the needle in the haystack, and objects must be the same instance. * Return * Returns the key for needle if it is found in the array, FALSE otherwise. */ static int ph7_hashmap_search(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_value *pVal, sNeedle; ph7_hashmap *pMap; ph7_value sVal; int bStrict; sxu32 n; int rc; if(nArg < 2) { /* Missing argument,return FALSE*/ ph7_result_bool(pCtx, 0); return PH7_OK; } bStrict = FALSE; if(!ph7_value_is_array(apArg[1])) { /* hasystack must be an array,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } if(nArg > 2 && ph7_value_is_bool(apArg[2])) { bStrict = ph7_value_to_bool(apArg[2]); } /* Point to the internal representation of the internal hashmap */ pMap = (ph7_hashmap *)apArg[1]->x.pOther; /* Perform a linear search since we cannot sort the hashmap based on values */ PH7_MemObjInit(pMap->pVm, &sVal); PH7_MemObjInit(pMap->pVm, &sNeedle); pEntry = pMap->pFirst; n = pMap->nEntry; for(;;) { if(!n) { break; } /* Extract node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { /* Make a copy of the current values since the comparison routine * can change their type. */ PH7_MemObjLoad(pVal, &sVal); PH7_MemObjLoad(apArg[0], &sNeedle); rc = PH7_MemObjCmp(&sNeedle, &sVal, bStrict, 0); PH7_MemObjRelease(&sVal); PH7_MemObjRelease(&sNeedle); if(rc == 0) { /* Match found,return key */ if(pEntry->iType == HASHMAP_INT_NODE) { /* INT key */ ph7_result_int64(pCtx, pEntry->xKey.iKey); } else { SyBlob *pKey = &pEntry->xKey.sKey; /* Blob key */ ph7_result_string(pCtx, (const char *)SyBlobData(pKey), (int)SyBlobLength(pKey)); } return PH7_OK; } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* No such value,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* * array array_diff(array $array1,array $array2,...) * Computes the difference of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all the entries from array1 that * are not present in any of the other arrays. */ static int ph7_hashmap_diff(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, 0, TRUE); if(rc == SXRET_OK) { /* Value exist */ break; } } if(i >= nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_udiff(array $array1,array $array2,...,$callback) * Computes the difference of arrays by using a callback function for data comparison. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against. * $callback * The callback comparison function. * The comparison function must return an integer less than, equal to, or greater than zero * if the first argument is considered to be respectively less than, equal to, or greater * than the second. * int callback ( mixed $a, mixed $b ) * Return * Returns an array containing all the entries from array1 that * are not present in any of the other arrays. */ static int ph7_hashmap_udiff(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pCallback; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the callback */ pCallback = apArg[nArg - 1]; if(nArg == 2) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { for(i = 1 ; i < nArg - 1; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValueByCallback(pMap, pVal, pCallback, 0); if(rc == SXRET_OK) { /* Value exist */ break; } } if(i >= (nArg - 1)) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_diff_assoc(array $array1,array $array2,...) * Computes the difference of arrays with additional index check. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all the entries from array1 that * are not present in any of the other arrays. */ static int ph7_hashmap_diff_assoc(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pN1, *pN2, *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; pN1 = pN2 = 0; for(;;) { if(n < 1) { break; } for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform a key lookup first */ if(pEntry->iType == HASHMAP_INT_NODE) { rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1); } else { rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1); } if(rc != SXRET_OK) { /* No such key,break immediately */ break; } /* Extract node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, &pN2, TRUE); if(rc != SXRET_OK || pN1 != pN2) { /* Value does not exist */ break; } } } if(i < nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_diff_uassoc(array $array1,array $array2,...,callback $key_compare_func) * Computes the difference of arrays with additional index check which is performed * by a user supplied callback function. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against. * $key_compare_func * Callback function to use. The callback function must return an integer * less than, equal to, or greater than zero if the first argument is considered * to be respectively less than, equal to, or greater than the second. * Return * Returns an array containing all the entries from array1 that * are not present in any of the other arrays. */ static int ph7_hashmap_diff_uassoc(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pN1, *pN2, *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pCallback; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the callback */ pCallback = apArg[nArg - 1]; if(nArg == 2) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; pN1 = pN2 = 0; /* cc warning */ for(;;) { if(n < 1) { break; } for(i = 1 ; i < nArg - 1; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform a key lookup first */ if(pEntry->iType == HASHMAP_INT_NODE) { rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1); } else { rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1); } if(rc != SXRET_OK) { /* No such key,break immediately */ break; } /* Extract node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { /* Invoke the user callback */ rc = HashmapFindValueByCallback(pMap, pVal, pCallback, &pN2); if(rc != SXRET_OK || pN1 != pN2) { /* Value does not exist */ break; } } } if(i < (nArg - 1)) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_diff_key(array $array1 ,array $array2,...) * Computes the difference of arrays using keys for comparison. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all the entries from array1 whose keys are not present * in any of the other arrays. * Note that NULL is returned on failure. */ static int ph7_hashmap_diff_key(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the main hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perfrom the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } pMap = (ph7_hashmap *)apArg[i]->x.pOther; if(pEntry->iType == HASHMAP_BLOB_NODE) { SyBlob *pKey = &pEntry->xKey.sKey; /* Blob lookup */ rc = HashmapLookupBlobKey(pMap, SyBlobData(pKey), SyBlobLength(pKey), 0); } else { /* Int lookup */ rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, 0); } if(rc == SXRET_OK) { /* Key exists,break immediately */ break; } } if(i >= nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_intersect(array $array1 ,array $array2,...) * Computes the intersection of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all of the values in array1 whose values exist * in all of the parameters. . * Note that NULL is returned on failure. */ static int ph7_hashmap_intersect(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the intersection */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, 0, TRUE); if(rc != SXRET_OK) { /* Value does not exist */ break; } } if(i >= nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_intersect_assoc(array $array1 ,array $array2,...) * Computes the intersection of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all of the values in array1 whose values exist * in all of the parameters. . * Note that NULL is returned on failure. */ static int ph7_hashmap_intersect_assoc(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry, *pN1, *pN2; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the intersection */ pEntry = pSrc->pFirst; n = pSrc->nEntry; pN1 = pN2 = 0; /* cc warning */ for(;;) { if(n < 1) { break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform a key lookup first */ if(pEntry->iType == HASHMAP_INT_NODE) { rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, &pN1); } else { rc = HashmapLookupBlobKey(pMap, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), &pN1); } if(rc != SXRET_OK) { /* No such key,break immediately */ break; } /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, &pN2, TRUE); if(rc != SXRET_OK || pN1 != pN2) { /* Value does not exist */ break; } } if(i >= nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_intersect_key(array $array1 ,array $array2,...) * Computes the intersection of arrays using keys for comparison. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an associative array containing all the entries of array1 which * have keys that are present in all arguments. * Note that NULL is returned on failure. */ static int ph7_hashmap_intersect_key(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pArray; sxi32 rc; sxu32 n; int i; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg == 1) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the main hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perfrom the intersection */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } for(i = 1 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } pMap = (ph7_hashmap *)apArg[i]->x.pOther; if(pEntry->iType == HASHMAP_BLOB_NODE) { SyBlob *pKey = &pEntry->xKey.sKey; /* Blob lookup */ rc = HashmapLookupBlobKey(pMap, SyBlobData(pKey), SyBlobLength(pKey), 0); } else { /* Int key */ rc = HashmapLookupIntKey(pMap, pEntry->xKey.iKey, 0); } if(rc != SXRET_OK) { /* Key does not exists,break immediately */ break; } } if(i >= nArg) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_uintersect(array $array1 ,array $array2,...,$callback) * Computes the intersection of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * $callback * The callback comparison function. * The comparison function must return an integer less than, equal to, or greater than zero * if the first argument is considered to be respectively less than, equal to, or greater * than the second. * int callback ( mixed $a, mixed $b ) * Return * Returns an array containing all of the values in array1 whose values exist * in all of the parameters. . * Note that NULL is returned on failure. */ static int ph7_hashmap_uintersect(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc, *pMap; ph7_value *pCallback; ph7_value *pArray; ph7_value *pVal; sxi32 rc; sxu32 n; int i; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Missing/Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the callback */ pCallback = apArg[nArg - 1]; if(nArg == 2) { /* Return the first array since we cannot perform a diff */ ph7_result_value(pCtx, apArg[0]); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the intersection */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;) { if(n < 1) { break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if(pVal) { for(i = 1 ; i < nArg - 1; i++) { if(!ph7_value_is_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValueByCallback(pMap, pVal, pCallback, 0); if(rc != SXRET_OK) { /* Value does not exist */ break; } } if(i >= (nArg - 1)) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_fill(int $start_index,int $num,var $value) * Fill an array with values. * Parameters * $start_index * The first index of the returned array. * $num * Number of elements to insert. * $value * Value to use for filling. * Return * The filled array or null on failure. */ static int ph7_hashmap_fill(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pArray; int i, nEntry; if(nArg < 3) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Total number of entries to insert */ nEntry = ph7_value_to_int(apArg[1]); /* Insert the first entry alone because it have it's own key */ ph7_array_add_intkey_elem(pArray, ph7_value_to_int(apArg[0]), apArg[2]); /* Repeat insertion of the desired value */ for(i = 1 ; i < nEntry ; i++) { ph7_array_add_elem(pArray, 0/*Automatic index assign */, apArg[2]); } /* Return the filled array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_fill_keys(array $input,var $value) * Fill an array with values, specifying keys. * Parameters * $input * Array of values that will be used as key. * $value * Value to use for filling. * Return * The filled array or null on failure. */ static int ph7_hashmap_fill_keys(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc; ph7_value *pArray; sxu32 n; if(nArg < 2) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Perform the requested operation */ pEntry = pSrc->pFirst; for(n = 0 ; n < pSrc->nEntry ; n++) { ph7_array_add_elem(pArray, HashmapExtractNodeValue(pEntry), apArg[1]); /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return the filled array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_combine(array $keys,array $values) * Creates an array by using one array for keys and another for its values. * Parameters * $keys * Array of keys to be used. * $values * Array of values to be used. * Return * Returns the combined array. Otherwise FALSE if the number of elements * for each array isn't equal or if one of the given arguments is * not an array. */ static int ph7_hashmap_combine(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pKe, *pVe; ph7_hashmap *pKey, *pValue; ph7_value *pArray; sxu32 n; if(nArg < 2) { /* Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0]) || !ph7_value_is_array(apArg[1])) { /* Invalid argument,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmaps */ pKey = (ph7_hashmap *)apArg[0]->x.pOther; pValue = (ph7_hashmap *)apArg[1]->x.pOther; if(pKey->nEntry != pValue->nEntry) { /* Array length differs,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_bool(pCtx, 0); return PH7_OK; } /* Perform the requested operation */ pKe = pKey->pFirst; pVe = pValue->pFirst; for(n = 0 ; n < pKey->nEntry ; n++) { ph7_array_add_elem(pArray, HashmapExtractNodeValue(pKe), HashmapExtractNodeValue(pVe)); /* Point to the next entry */ pKe = pKe->pPrev; /* Reverse link */ pVe = pVe->pPrev; } /* Return the filled array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_reverse(array $array [,bool $preserve_keys = false ]) * Return an array with elements in reverse order. * Parameters * $array * The input array. * $preserve_keys (optional) * If set to TRUE keys are preserved. * Return * The reversed array. */ static int ph7_hashmap_reverse(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc; ph7_value *pArray; int bPreserve; sxu32 n; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } bPreserve = FALSE; if(nArg > 1 && ph7_value_is_bool(apArg[1])) { bPreserve = ph7_value_to_bool(apArg[1]); } /* Point to the internal representation of the input hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Perform the requested operation */ pEntry = pSrc->pLast; for(n = 0 ; n < pSrc->nEntry ; n++) { HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, bPreserve); /* Point to the previous entry */ pEntry = pEntry->pNext; /* Reverse link */ } ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_unique(array $array[,int $sort_flags = SORT_STRING ]) * Removes duplicate values from an array * Parameter * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * SORT_LOCALE_STRING - compare items as * Return * Filtered array or NULL on failure. */ static int ph7_hashmap_unique(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_value *pNeedle; ph7_hashmap *pSrc; ph7_value *pArray; int bStrict; sxi32 rc; sxu32 n; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } bStrict = FALSE; if(nArg > 1) { bStrict = ph7_value_to_int(apArg[1]) == 3 /* SORT_REGULAR */ ? 1 : 0; } /* Point to the internal representation of the input hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Perform the requested operation */ pEntry = pSrc->pFirst; for(n = 0 ; n < pSrc->nEntry ; n++) { pNeedle = HashmapExtractNodeValue(pEntry); rc = SXERR_NOTFOUND; if(pNeedle) { rc = HashmapFindValue((ph7_hashmap *)pArray->x.pOther, pNeedle, 0, bStrict); } if(rc != SXRET_OK) { /* Perform the insertion */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_flip(array $input) * Exchanges all keys with their associated values in an array. * Parameter * $input * Input array. * Return * The flipped array on success or NULL on failure. */ static int ph7_hashmap_flip(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pSrc; ph7_value *pArray; ph7_value *pKey; ph7_value sVal; sxu32 n; if(nArg < 1) { /* Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pSrc = (ph7_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Start processing */ pEntry = pSrc->pFirst; for(n = 0 ; n < pSrc->nEntry ; n++) { /* Extract the node value */ pKey = HashmapExtractNodeValue(pEntry); if(pKey && (pKey->iFlags & MEMOBJ_NULL) == 0) { /* Prepare the value for insertion */ if(pEntry->iType == HASHMAP_INT_NODE) { PH7_MemObjInitFromInt(pSrc->pVm, &sVal, pEntry->xKey.iKey); } else { SyString sStr; SyStringInitFromBuf(&sStr, SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); PH7_MemObjInitFromString(pSrc->pVm, &sVal, &sStr); } /* Perform the insertion */ ph7_array_add_elem(pArray, pKey, &sVal); /* Safely release the value because each inserted entry * have it's own private copy of the value. */ PH7_MemObjRelease(&sVal); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return the freshly created array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * number array_sum(array $array ) * Calculate the sum of values in an array. * Parameters * $array: The input array. * Return * Returns the sum of values as an integer or float. */ static void DoubleSum(ph7_context *pCtx, ph7_hashmap *pMap) { ph7_hashmap_node *pEntry; ph7_value *pObj; double dSum = 0; sxu32 n; pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { pObj = HashmapExtractNodeValue(pEntry); if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) { if(pObj->iFlags & MEMOBJ_REAL) { dSum += pObj->x.rVal; } else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) { dSum += (double)pObj->x.iVal; } else if(pObj->iFlags & MEMOBJ_STRING) { if(SyBlobLength(&pObj->sBlob) > 0) { double dv = 0; SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); dSum += dv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return sum */ ph7_result_double(pCtx, dSum); } static void Int64Sum(ph7_context *pCtx, ph7_hashmap *pMap) { ph7_hashmap_node *pEntry; ph7_value *pObj; sxi64 nSum = 0; sxu32 n; pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { pObj = HashmapExtractNodeValue(pEntry); if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) { if(pObj->iFlags & MEMOBJ_REAL) { nSum += (sxi64)pObj->x.rVal; } else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) { nSum += pObj->x.iVal; } else if(pObj->iFlags & MEMOBJ_STRING) { if(SyBlobLength(&pObj->sBlob) > 0) { sxi64 nv = 0; SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); nSum += nv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return sum */ ph7_result_int64(pCtx, nSum); } /* number array_sum(array $array ) * (See block-coment above) */ static int ph7_hashmap_sum(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; ph7_value *pObj; if(nArg < 1) { /* Missing arguments,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry < 1) { /* Nothing to compute,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* If the first element is of type float,then perform floating * point computaion.Otherwise switch to int64 computaion. */ pObj = HashmapExtractNodeValue(pMap->pFirst); if(pObj == 0) { ph7_result_int(pCtx, 0); return PH7_OK; } if(pObj->iFlags & MEMOBJ_REAL) { DoubleSum(pCtx, pMap); } else { Int64Sum(pCtx, pMap); } return PH7_OK; } /* * number array_product(array $array ) * Calculate the product of values in an array. * Parameters * $array: The input array. * Return * Returns the product of values as an integer or float. */ static void DoubleProd(ph7_context *pCtx, ph7_hashmap *pMap) { ph7_hashmap_node *pEntry; ph7_value *pObj; double dProd; sxu32 n; pEntry = pMap->pFirst; dProd = 1; for(n = 0 ; n < pMap->nEntry ; n++) { pObj = HashmapExtractNodeValue(pEntry); if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) { if(pObj->iFlags & MEMOBJ_REAL) { dProd *= pObj->x.rVal; } else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) { dProd *= (double)pObj->x.iVal; } else if(pObj->iFlags & MEMOBJ_STRING) { if(SyBlobLength(&pObj->sBlob) > 0) { double dv = 0; SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); dProd *= dv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return product */ ph7_result_double(pCtx, dProd); } static void Int64Prod(ph7_context *pCtx, ph7_hashmap *pMap) { ph7_hashmap_node *pEntry; ph7_value *pObj; sxi64 nProd; sxu32 n; pEntry = pMap->pFirst; nProd = 1; for(n = 0 ; n < pMap->nEntry ; n++) { pObj = HashmapExtractNodeValue(pEntry); if(pObj && (pObj->iFlags & (MEMOBJ_NULL | MEMOBJ_HASHMAP | MEMOBJ_OBJ | MEMOBJ_RES)) == 0) { if(pObj->iFlags & MEMOBJ_REAL) { nProd *= (sxi64)pObj->x.rVal; } else if(pObj->iFlags & (MEMOBJ_INT | MEMOBJ_BOOL)) { nProd *= pObj->x.iVal; } else if(pObj->iFlags & MEMOBJ_STRING) { if(SyBlobLength(&pObj->sBlob) > 0) { sxi64 nv = 0; SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); nProd *= nv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return product */ ph7_result_int64(pCtx, nProd); } /* number array_product(array $array ) * (See block-block comment above) */ static int ph7_hashmap_product(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; ph7_value *pObj; if(nArg < 1) { /* Missing arguments,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* Make sure we are dealing with a valid hashmap */ if(!ph7_value_is_array(apArg[0])) { /* Invalid argument,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry < 1) { /* Nothing to compute,return 0 */ ph7_result_int(pCtx, 0); return PH7_OK; } /* If the first element is of type float,then perform floating * point computation.Otherwise switch to int64 computation. */ pObj = HashmapExtractNodeValue(pMap->pFirst); if(pObj == 0) { ph7_result_int(pCtx, 0); return PH7_OK; } if(pObj->iFlags & MEMOBJ_REAL) { DoubleProd(pCtx, pMap); } else { Int64Prod(pCtx, pMap); } return PH7_OK; } /* * value array_rand(array $input[,int $num_req = 1 ]) * Pick one or more random entries out of an array. * Parameters * $input * The input array. * $num_req * Specifies how many entries you want to pick. * Return * If you are picking only one entry, array_rand() returns the key for a random entry. * Otherwise, it returns an array of keys for the random entries. * NULL is returned on failure. */ static int ph7_hashmap_rand(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pNode; ph7_hashmap *pMap; int nItem = 1; if(nArg < 1) { /* Missing argument,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Make sure we are dealing with an array */ if(!ph7_value_is_array(apArg[0])) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; if(pMap->nEntry < 1) { /* Empty hashmap,return NULL */ ph7_result_null(pCtx); return PH7_OK; } if(nArg > 1) { nItem = ph7_value_to_int(apArg[1]); } if(nItem < 2) { sxu32 nEntry; /* Select a random number */ nEntry = PH7_VmRandomNum(pMap->pVm) % pMap->nEntry; /* Extract the desired entry. * Note that we perform a linear lookup here (later version must change this) */ if(nEntry > pMap->nEntry / 2) { pNode = pMap->pLast; nEntry = pMap->nEntry - nEntry; if(nEntry > 1) { for(;;) { if(nEntry == 0) { break; } /* Point to the previous entry */ pNode = pNode->pNext; /* Reverse link */ nEntry--; } } } else { pNode = pMap->pFirst; for(;;) { if(nEntry == 0) { break; } /* Point to the next entry */ pNode = pNode->pPrev; /* Reverse link */ nEntry--; } } if(pNode->iType == HASHMAP_INT_NODE) { /* Int key */ ph7_result_int64(pCtx, pNode->xKey.iKey); } else { /* Blob key */ ph7_result_string(pCtx, (const char *)SyBlobData(&pNode->xKey.sKey), (int)SyBlobLength(&pNode->xKey.sKey)); } } else { ph7_value sKey, *pArray; ph7_hashmap *pDest; /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the hashmap */ pDest = (ph7_hashmap *)pArray->x.pOther; PH7_MemObjInit(pDest->pVm, &sKey); /* Copy the first n items */ pNode = pMap->pFirst; if(nItem > (int)pMap->nEntry) { nItem = (int)pMap->nEntry; } while(nItem > 0) { PH7_HashmapExtractNodeKey(pNode, &sKey); PH7_HashmapInsert(pDest, 0/* Automatic index assign*/, &sKey); PH7_MemObjRelease(&sKey); /* Point to the next entry */ pNode = pNode->pPrev; /* Reverse link */ nItem--; } /* Shuffle the array */ HashmapMergeSort(pDest, HashmapCmpCallback7, 0); /* Rehash node */ HashmapSortRehash(pDest); /* Return the random array */ ph7_result_value(pCtx, pArray); } return PH7_OK; } /* * array array_chunk (array $input,int $size [,bool $preserve_keys = false ]) * Split an array into chunks. * Parameters * $input * The array to work on * $size * The size of each chunk * $preserve_keys * When set to TRUE keys will be preserved. Default is FALSE which will reindex * the chunk numerically. * Return * Returns a multidimensional numerically indexed array, starting with * zero, with each dimension containing size elements. */ static int ph7_hashmap_chunk(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pArray, *pChunk; ph7_hashmap_node *pEntry; ph7_hashmap *pMap; int bPreserve; sxu32 nChunk; sxu32 nSize; sxu32 n; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Extract the chunk size */ nSize = (sxu32)ph7_value_to_int(apArg[1]); if(nSize < 1) { ph7_result_null(pCtx); return PH7_OK; } if(nSize >= pMap->nEntry) { /* Return the whole array */ ph7_array_add_elem(pArray, 0, apArg[0]); ph7_result_value(pCtx, pArray); return PH7_OK; } bPreserve = 0; if(nArg > 2) { bPreserve = ph7_value_to_bool(apArg[2]); } /* Start processing */ pEntry = pMap->pFirst; nChunk = 0; pChunk = 0; n = pMap->nEntry; for(;;) { if(n < 1) { if(nChunk > 0) { /* Insert the last chunk */ ph7_array_add_elem(pArray, 0, pChunk); /* Will have it's own copy */ } break; } if(nChunk < 1) { if(pChunk) { /* Put the first chunk */ ph7_array_add_elem(pArray, 0, pChunk); /* Will have it's own copy */ } /* Create a new dimension */ pChunk = ph7_context_new_array(pCtx); /* Don't worry about freeing memory here,everything * will be automatically released as soon we return * from this function */ if(pChunk == 0) { break; } nChunk = nSize; } /* Insert the entry */ HashmapInsertNode((ph7_hashmap *)pChunk->x.pOther, pEntry, bPreserve); /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ nChunk--; n--; } /* Return the multidimensional array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_pad(array $input,int $pad_size,value $pad_value) * Pad array to the specified length with a value. * $input * Initial array of values to pad. * $pad_size * New size of the array. * $pad_value * Value to pad if input is less than pad_size. */ static int ph7_hashmap_pad(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; ph7_value *pArray; int nEntry; if(nArg < 3 || !ph7_value_is_array(apArg[0])) { /* Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Extract the total number of desired entry to insert */ nEntry = ph7_value_to_int(apArg[1]); if(nEntry < 0) { nEntry = -nEntry; if(nEntry > 1048576) { nEntry = 1048576; /* Limit imposed by PHP */ } if(nEntry > (int)pMap->nEntry) { nEntry -= (int)pMap->nEntry; /* Insert given items first */ while(nEntry > 0) { ph7_array_add_elem(pArray, 0, apArg[2]); nEntry--; } /* Merge the two arrays */ HashmapMerge(pMap, (ph7_hashmap *)pArray->x.pOther); } else { PH7_HashmapDup(pMap, (ph7_hashmap *)pArray->x.pOther); } } else if(nEntry > 0) { if(nEntry > 1048576) { nEntry = 1048576; /* Limit imposed by PHP */ } if(nEntry > (int)pMap->nEntry) { nEntry -= (int)pMap->nEntry; /* Merge the two arrays first */ HashmapMerge(pMap, (ph7_hashmap *)pArray->x.pOther); /* Insert given items */ while(nEntry > 0) { ph7_array_add_elem(pArray, 0, apArg[2]); nEntry--; } } else { PH7_HashmapDup(pMap, (ph7_hashmap *)pArray->x.pOther); } } /* Return the new array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_replace(array &$array,array &$array1,...) * Replaces elements from passed arrays into the first array. * Parameters * $array * The array in which elements are replaced. * $array1 * The array from which elements will be extracted. * .... * More arrays from which elements will be extracted. * Values from later arrays overwrite the previous values. * Return * Returns an array, or NULL if an error occurs. */ static int ph7_hashmap_replace(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; ph7_value *pArray; int i; if(nArg < 1) { /* Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Perform the requested operation */ for(i = 0 ; i < nArg ; i++) { if(!ph7_value_is_array(apArg[i])) { continue; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[i]->x.pOther; HashmapOverwrite(pMap, (ph7_hashmap *)pArray->x.pOther); } /* Return the new array */ ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_filter(array $input [,callback $callback ]) * Filters elements of an array using a callback function. * Parameters * $input * The array to iterate over * $callback * The callback function to use * If no callback is supplied, all entries of input equal to FALSE (see converting to boolean) * will be removed. * Return * The filtered array. */ static int ph7_hashmap_filter(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pMap; ph7_value *pArray; ph7_value sResult; /* Callback result */ ph7_value *pValue; sxi32 rc; int keep; sxu32 n; if(nArg < 1 || !ph7_value_is_array(apArg[0])) { /* Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; pEntry = pMap->pFirst; PH7_MemObjInit(pMap->pVm, &sResult); sResult.nIdx = SXU32_HIGH; /* Mark as constant */ /* Perform the requested operation */ for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract node value */ pValue = HashmapExtractNodeValue(pEntry); if(nArg > 1 && pValue) { /* Invoke the given callback */ keep = FALSE; rc = PH7_VmCallUserFunction(pMap->pVm, apArg[1], 1, &pValue, &sResult); if(rc == SXRET_OK) { /* Perform a boolean cast */ keep = ph7_value_to_bool(&sResult); } PH7_MemObjRelease(&sResult); } else { /* No available callback,check for empty item */ keep = !PH7_MemObjIsEmpty(pValue); } if(keep) { /* Perform the insertion,now the callback returned true */ HashmapInsertNode((ph7_hashmap *)pArray->x.pOther, pEntry, TRUE); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } ph7_result_value(pCtx, pArray); return PH7_OK; } /* * array array_map(callback $callback,array $arr1) * Applies the callback to the elements of the given arrays. * Parameters * $callback * Callback function to run for each element in each array. * $arr1 * An array to run through the callback function. * Return * Returns an array containing all the elements of arr1 after applying * the callback function to each one. * NOTE: * array_map() passes only a single value to the callback. */ static int ph7_hashmap_map(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pArray, *pValue, sKey, sResult; ph7_hashmap_node *pEntry; ph7_hashmap *pMap; sxu32 n; if(nArg < 2 || !ph7_value_is_array(apArg[1])) { /* Invalid arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Create a new array */ pArray = ph7_context_new_array(pCtx); if(pArray == 0) { ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[1]->x.pOther; PH7_MemObjInit(pMap->pVm, &sResult); PH7_MemObjInit(pMap->pVm, &sKey); sResult.nIdx = SXU32_HIGH; /* Mark as constant */ sKey.nIdx = SXU32_HIGH; /* Mark as constant */ /* Perform the requested operation */ pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract the node value */ pValue = HashmapExtractNodeValue(pEntry); if(pValue) { sxi32 rc; /* Invoke the supplied callback */ rc = PH7_VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult); /* Extract the node key */ PH7_HashmapExtractNodeKey(pEntry, &sKey); if(rc != SXRET_OK) { /* An error ocurred while invoking the supplied callback [i.e: not defined] */ ph7_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */ } else { /* Insert the callback return value */ ph7_array_add_elem(pArray, &sKey, &sResult); } PH7_MemObjRelease(&sKey); PH7_MemObjRelease(&sResult); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } ph7_result_value(pCtx, pArray); return PH7_OK; } /* * value array_reduce(array $input,callback $function[, value $initial = NULL ]) * Iteratively reduce the array to a single value using a callback function. * Parameters * $input * The input array. * $function * The callback function. * $initial * If the optional initial is available, it will be used at the beginning * of the process, or as a final result in case the array is empty. * Return * Returns the resulting value. * If the array is empty and initial is not passed, array_reduce() returns NULL. */ static int ph7_hashmap_reduce(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap_node *pEntry; ph7_hashmap *pMap; ph7_value *pValue; ph7_value sResult; sxu32 n; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Invalid/Missing arguments,return NULL */ ph7_result_null(pCtx); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Assume a NULL initial value */ PH7_MemObjInit(pMap->pVm, &sResult); sResult.nIdx = SXU32_HIGH; /* Mark as constant */ if(nArg > 2) { /* Set the initial value */ PH7_MemObjLoad(apArg[2], &sResult); } /* Perform the requested operation */ pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract the node value */ pValue = HashmapExtractNodeValue(pEntry); /* Invoke the supplied callback */ PH7_VmCallUserFunctionAp(pMap->pVm, apArg[1], &sResult, &sResult, pValue, 0); /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } ph7_result_value(pCtx, &sResult); /* Will make it's own copy */ PH7_MemObjRelease(&sResult); return PH7_OK; } /* * bool array_walk(array &$array,callback $funcname [, value $userdata ] ) * Apply a user function to every member of an array. * Parameters * $array * The input array. * $funcname * Typically, funcname takes on two parameters.The array parameter's value being * the first, and the key/index second. * Note: * If funcname needs to be working with the actual values of the array,specify the first * parameter of funcname as a reference. Then, any changes made to those elements will * be made in the original array itself. * $userdata * If the optional userdata parameter is supplied, it will be passed as the third parameter * to the callback funcname. * Return * Returns TRUE on success or FALSE on failure. */ static int ph7_hashmap_walk(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_value *pValue, *pUserData, sKey; ph7_hashmap_node *pEntry; ph7_hashmap *pMap; sxi32 rc; sxu32 n; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Invalid/Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } pUserData = nArg > 2 ? apArg[2] : 0; /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; PH7_MemObjInit(pMap->pVm, &sKey); sKey.nIdx = SXU32_HIGH; /* Mark as constant */ /* Perform the desired operation */ pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract the node value */ pValue = HashmapExtractNodeValue(pEntry); if(pValue) { /* Extract the entry key */ PH7_HashmapExtractNodeKey(pEntry, &sKey); /* Invoke the supplied callback */ rc = PH7_VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0); PH7_MemObjRelease(&sKey); if(rc != SXRET_OK) { /* An error ocurred while invoking the supplied callback [i.e: not defined] */ ph7_result_bool(pCtx, 0); /* return FALSE */ return PH7_OK; } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* All done,return TRUE */ ph7_result_bool(pCtx, 1); return PH7_OK; } /* * Apply a user function to every member of an array.(Recurse on array's). * Refer to the [array_walk_recursive()] implementation for more information. */ static int HashmapWalkRecursive( ph7_hashmap *pMap, /* Target hashmap */ ph7_value *pCallback, /* User callback */ ph7_value *pUserData, /* Callback private data */ int iNest /* Nesting level */ ) { ph7_hashmap_node *pEntry; ph7_value *pValue, sKey; sxi32 rc; sxu32 n; /* Iterate throw hashmap entries */ PH7_MemObjInit(pMap->pVm, &sKey); sKey.nIdx = SXU32_HIGH; /* Mark as constant */ pEntry = pMap->pFirst; for(n = 0 ; n < pMap->nEntry ; n++) { /* Extract the node value */ pValue = HashmapExtractNodeValue(pEntry); if(pValue) { if(pValue->iFlags & MEMOBJ_HASHMAP) { if(iNest < 32) { /* Recurse */ iNest++; HashmapWalkRecursive((ph7_hashmap *)pValue->x.pOther, pCallback, pUserData, iNest); iNest--; } } else { /* Extract the node key */ PH7_HashmapExtractNodeKey(pEntry, &sKey); /* Invoke the supplied callback */ rc = PH7_VmCallUserFunctionAp(pMap->pVm, pCallback, 0, pValue, &sKey, pUserData, 0); PH7_MemObjRelease(&sKey); if(rc != SXRET_OK) { return rc; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * bool array_walk_recursive(array &$array,callback $funcname [, value $userdata ] ) * Apply a user function recursively to every member of an array. * Parameters * $array * The input array. * $funcname * Typically, funcname takes on two parameters.The array parameter's value being * the first, and the key/index second. * Note: * If funcname needs to be working with the actual values of the array,specify the first * parameter of funcname as a reference. Then, any changes made to those elements will * be made in the original array itself. * $userdata * If the optional userdata parameter is supplied, it will be passed as the third parameter * to the callback funcname. * Return * Returns TRUE on success or FALSE on failure. */ static int ph7_hashmap_walk_recursive(ph7_context *pCtx, int nArg, ph7_value **apArg) { ph7_hashmap *pMap; sxi32 rc; if(nArg < 2 || !ph7_value_is_array(apArg[0])) { /* Invalid/Missing arguments,return FALSE */ ph7_result_bool(pCtx, 0); return PH7_OK; } /* Point to the internal representation of the input hashmap */ pMap = (ph7_hashmap *)apArg[0]->x.pOther; /* Perform the desired operation */ rc = HashmapWalkRecursive(pMap, apArg[1], nArg > 2 ? apArg[2] : 0, 0); /* All done */ ph7_result_bool(pCtx, rc == SXRET_OK); return PH7_OK; } /* * Table of hashmap functions. */ static const ph7_builtin_func aHashmapFunc[] = { {"sizeof", ph7_hashmap_count }, {"array_key_exists", ph7_hashmap_key_exists }, {"array_pop", ph7_hashmap_pop }, {"array_push", ph7_hashmap_push }, {"array_shift", ph7_hashmap_shift }, {"array_product", ph7_hashmap_product }, {"array_sum", ph7_hashmap_sum }, {"array_keys", ph7_hashmap_keys }, {"array_values", ph7_hashmap_values }, {"array_same", ph7_hashmap_same }, /* Symisc eXtension */ {"array_merge", ph7_hashmap_merge }, {"array_slice", ph7_hashmap_slice }, {"array_splice", ph7_hashmap_splice }, {"array_search", ph7_hashmap_search }, {"array_diff", ph7_hashmap_diff }, {"array_udiff", ph7_hashmap_udiff }, {"array_diff_assoc", ph7_hashmap_diff_assoc }, {"array_diff_uassoc", ph7_hashmap_diff_uassoc }, {"array_diff_key", ph7_hashmap_diff_key }, {"array_intersect", ph7_hashmap_intersect}, {"array_intersect_assoc", ph7_hashmap_intersect_assoc}, {"array_uintersect", ph7_hashmap_uintersect}, {"array_intersect_key", ph7_hashmap_intersect_key}, {"array_copy", ph7_hashmap_copy }, {"array_erase", ph7_hashmap_erase }, {"array_fill", ph7_hashmap_fill }, {"array_fill_keys", ph7_hashmap_fill_keys}, {"array_combine", ph7_hashmap_combine }, {"array_reverse", ph7_hashmap_reverse }, {"array_unique", ph7_hashmap_unique }, {"array_flip", ph7_hashmap_flip }, {"array_rand", ph7_hashmap_rand }, {"array_chunk", ph7_hashmap_chunk }, {"array_pad", ph7_hashmap_pad }, {"array_replace", ph7_hashmap_replace }, {"array_filter", ph7_hashmap_filter }, {"array_map", ph7_hashmap_map }, {"array_reduce", ph7_hashmap_reduce }, {"array_walk", ph7_hashmap_walk }, {"array_walk_recursive", ph7_hashmap_walk_recursive }, {"in_array", ph7_hashmap_in_array}, {"sort", ph7_hashmap_sort }, {"asort", ph7_hashmap_asort }, {"arsort", ph7_hashmap_arsort }, {"ksort", ph7_hashmap_ksort }, {"krsort", ph7_hashmap_krsort }, {"rsort", ph7_hashmap_rsort }, {"usort", ph7_hashmap_usort }, {"uasort", ph7_hashmap_uasort }, {"uksort", ph7_hashmap_uksort }, {"shuffle", ph7_hashmap_shuffle }, {"range", ph7_hashmap_range }, {"current", ph7_hashmap_current }, {"each", ph7_hashmap_each }, {"pos", ph7_hashmap_current }, {"next", ph7_hashmap_next }, {"prev", ph7_hashmap_prev }, {"end", ph7_hashmap_end }, {"reset", ph7_hashmap_reset }, {"key", ph7_hashmap_simple_key } }; /* * Register the built-in hashmap functions defined above. */ PH7_PRIVATE void PH7_RegisterHashmapFunctions(ph7_vm *pVm) { sxu32 n; for(n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++) { ph7_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0); } } /* * Dump a hashmap instance and it's entries and the store the dump in * the BLOB given as the first argument. * This function is typically invoked when the user issue a call to * [var_dump(),var_export(),print_r(),...] * This function SXRET_OK on success. Any other return value including * SXERR_LIMIT(infinite recursion) indicates failure. */ PH7_PRIVATE sxi32 PH7_HashmapDump(SyBlob *pOut, ph7_hashmap *pMap, int ShowType, int nTab, int nDepth) { ph7_hashmap_node *pEntry; ph7_value *pObj; sxu32 n = 0; int isRef; 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; } /* Point to the first inserted entry */ pEntry = pMap->pFirst; rc = SXRET_OK; if(!ShowType) { SyBlobAppend(&(*pOut), "Array(", sizeof("Array(") - 1); } /* 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); isRef = 0; if(pObj) { if(pEntry->iFlags & HASHMAP_NODE_FOREIGN_OBJ) { /* Referenced object */ isRef = 1; } rc = PH7_MemObjDump(&(*pOut), pObj, ShowType, nTab + 1, nDepth, isRef); 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)); return rc; } /* * Iterate throw 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; }