From 9ee8378d393778ac67314be7ea8d5bcbaeee9ee0 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Sun, 7 Dec 2014 10:08:07 +0100 Subject: try out unqlite --- common/unqlite/jx9_hashmap.c | 2989 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2989 insertions(+) create mode 100644 common/unqlite/jx9_hashmap.c (limited to 'common/unqlite/jx9_hashmap.c') diff --git a/common/unqlite/jx9_hashmap.c b/common/unqlite/jx9_hashmap.c new file mode 100644 index 0000000..e97d41d --- /dev/null +++ b/common/unqlite/jx9_hashmap.c @@ -0,0 +1,2989 @@ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * 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://jx9.symisc.net/ + */ + /* $SymiscID: hashmap.c v2.6 Win7 2012-12-11 00:50 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implement generic hashmaps used to represent JSON arrays and objects */ +/* Allowed node types */ +#define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ +#define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ +/* + * Default hash function for int [i.e; 64-bit integer] keys. + */ +static sxu32 IntHash(sxi64 iKey) +{ + return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); +} +/* + * Default hash function for string/BLOB keys. + */ +static sxu32 BinHash(const void *pSrc, sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* + * Return the total number of entries in a given hashmap. + * If bRecurisve is set to TRUE then recurse on hashmap entries. + * If the nesting limit is reached, this function abort immediately. + */ +static sxi64 HashmapCount(jx9_hashmap *pMap, int bRecursive, int iRecCount) +{ + sxi64 iCount = 0; + if( !bRecursive ){ + iCount = pMap->nEntry; + }else{ + /* Recursive hashmap walk */ + jx9_hashmap_node *pEntry = pMap->pLast; + jx9_value *pElem; + sxu32 n = 0; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + /* Point to the element value */ + pElem = (jx9_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((jx9_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 [jx9_hashmap_node] instance is returned. + */ +static jx9_hashmap_node * HashmapNewIntNode(jx9_hashmap *pMap, sxi64 iKey, sxu32 nHash, sxu32 nValIdx) +{ + jx9_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode, sizeof(jx9_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 [jx9_hashmap_node] instance is returned. + */ +static jx9_hashmap_node * HashmapNewBlobNode(jx9_hashmap *pMap, const void *pKey, sxu32 nKeyLen, sxu32 nHash, sxu32 nValIdx) +{ + jx9_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode, sizeof(jx9_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(jx9_hashmap *pMap, jx9_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. + */ +static void jx9HashmapUnlinkNode(jx9_hashmap_node *pNode) +{ + jx9_hashmap *pMap = pNode->pMap; + jx9_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); + /* Restore to the free list */ + jx9VmUnsetMemObj(pVm, pNode->nValIdx); + if( pNode->iType == HASHMAP_BLOB_NODE ){ + SyBlobRelease(&pNode->xKey.sKey); + } + SyMemBackendPoolFree(&pVm->sAllocator, pNode); + pMap->nEntry--; + if( pMap->nEntry < 1 ){ + /* Free the hash-bucket */ + SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); + pMap->apBucket = 0; + pMap->nSize = 0; + pMap->pFirst = pMap->pLast = pMap->pCur = 0; + } +} +#define HASHMAP_FILL_FACTOR 3 +/* + * Grow the hash-table and rehash all entries. + */ +static sxi32 HashmapGrowBucket(jx9_hashmap *pMap) +{ + if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){ + jx9_hashmap_node **apOld = pMap->apBucket; + jx9_hashmap_node *pEntry, **apNew; + sxu32 nNew = pMap->nSize << 1; + sxu32 nBucket; + sxu32 n; + if( nNew < 1 ){ + nNew = 16; + } + /* Allocate a new bucket */ + apNew = (jx9_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(jx9_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(jx9_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(jx9_hashmap *pMap,sxi64 iKey,jx9_value *pValue) +{ + jx9_hashmap_node *pNode; + jx9_value *pObj; + sxu32 nIdx; + sxu32 nHash; + sxi32 rc; + /* Reserve a jx9_value for the value */ + pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + jx9MemObjStore(pValue, pObj); + } + /* Hash the key */ + nHash = pMap->xIntHash(iKey); + /* Allocate a new int node */ + pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); + /* All done */ + return SXRET_OK; +} +/* + * Insert a BLOB key and it's associated value (if any) in the given + * hashmap. + */ +static sxi32 HashmapInsertBlobKey(jx9_hashmap *pMap,const void *pKey,sxu32 nKeyLen,jx9_value *pValue) +{ + jx9_hashmap_node *pNode; + jx9_value *pObj; + sxu32 nHash; + sxu32 nIdx; + sxi32 rc; + /* Reserve a jx9_value for the value */ + pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + jx9MemObjStore(pValue, pObj); + } + /* Hash the key */ + nHash = pMap->xBlobHash(pKey, nKeyLen); + /* Allocate a new blob node */ + pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); + /* 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( + jx9_hashmap *pMap, /* Target hashmap */ + sxi64 iKey, /* lookup key */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_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( + jx9_hashmap *pMap, /* Target hashmap */ + const void *pKey, /* Lookup key */ + sxu32 nKeyLen, /* Key length in bytes */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_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( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_hashmap_node *pNode = 0; /* cc -O6 warning */ + sxi32 rc; + if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) > 0 ){ + /* 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 */ + jx9MemObjToInteger(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( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_value *pVal /* Node value */ + ) +{ + jx9_hashmap_node *pNode = 0; + sxi32 rc = SXRET_OK; + if( pMap->nEntry < 1 && pKey && (pKey->iFlags & MEMOBJ_STRING) ){ + pMap->iFlags |= HASHMAP_JSON_OBJECT; + } + if( pKey && (pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES)) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(&(*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 */ + jx9_value *pElem; + pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); + if( pElem ){ + if( pVal ){ + jx9MemObjStore(pVal, pElem); + }else{ + /* Nullify the entry */ + jx9MemObjToNull(pElem); + } + } + return SXRET_OK; + } + /* Perform a blob-key insertion */ + rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal)); + return rc; + } +IntKey: + if( pKey ){ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + jx9MemObjToInteger(pKey); + } + if( SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode) ){ + /* Overwrite the old value */ + jx9_value *pElem; + pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); + if( pElem ){ + if( pVal ){ + jx9MemObjStore(pVal, pElem); + }else{ + /* Nullify the entry */ + jx9MemObjToNull(pElem); + } + } + return SXRET_OK; + } + /* Perform a 64-bit-int-key insertion */ + rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal)); + if( rc == SXRET_OK ){ + if( pKey->x.iVal >= pMap->iNextIdx ){ + /* Increment the automatic index */ + pMap->iNextIdx = pKey->x.iVal + 1; + /* Make sure the automatic index is not reserved */ + while( SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0) ){ + pMap->iNextIdx++; + } + } + } + }else{ + /* Assign an automatic index */ + rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal)); + if( rc == SXRET_OK ){ + ++pMap->iNextIdx; + } + } + /* Insertion result */ + return rc; +} +/* + * Extract node value. + */ +static jx9_value * HashmapExtractNodeValue(jx9_hashmap_node *pNode) +{ + /* Point to the desired object */ + jx9_value *pObj; + pObj = (jx9_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(jx9_hashmap *pMap, jx9_hashmap_node *pNode, int bPreserve) +{ + jx9_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); + } + }else{ + /* Blob key */ + rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey), + SyBlobLength(&pNode->xKey.sKey), pObj); + } + 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 jx9_values comparison, refer to the implementation + * of the [jx9MemObjCmp()] function defined in memobj.c or the official + * documenation. + */ +static sxi32 HashmapNodeCmp(jx9_hashmap_node *pLeft, jx9_hashmap_node *pRight, int bStrict) +{ + jx9_value sObj1, sObj2; + sxi32 rc; + if( pLeft == pRight ){ + /* + * Same node.Refer to the sort() implementation defined + * below for more information on this sceanario. + */ + return 0; + } + /* Do the comparison */ + jx9MemObjInit(pLeft->pMap->pVm, &sObj1); + jx9MemObjInit(pLeft->pMap->pVm, &sObj2); + jx9HashmapExtractNodeValue(pLeft, &sObj1, FALSE); + jx9HashmapExtractNodeValue(pRight, &sObj2, FALSE); + rc = jx9MemObjCmp(&sObj1, &sObj2, bStrict, 0); + jx9MemObjRelease(&sObj1); + jx9MemObjRelease(&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(jx9_hashmap_node *pEntry) +{ + jx9_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( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pNeedle, /* Lookup key */ + jx9_hashmap_node **ppNode, /* OUT: target node on success */ + int bStrict /* TRUE for strict comparison */ + ) +{ + jx9_hashmap_node *pEntry; + jx9_value sVal, *pVal; + jx9_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; + jx9MemObjInit(pMap->pVm, &sVal); + jx9MemObjInit(pMap->pVm, &sNeedle); + for(;;){ + if( n < 1 ){ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){ + sxi32 iF1 = pVal->iFlags; + sxi32 iF2 = pNeedle->iFlags; + if( iF1 == iF2 ){ + /* NULL values are equals */ + if( ppNode ){ + *ppNode = pEntry; + } + return SXRET_OK; + } + }else{ + /* Duplicate value */ + jx9MemObjLoad(pVal, &sVal); + jx9MemObjLoad(pNeedle, &sNeedle); + rc = jx9MemObjCmp(&sNeedle, &sVal, bStrict, 0); + jx9MemObjRelease(&sVal); + jx9MemObjRelease(&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; +} +/* + * Compare two hashmaps. + * Return 0 if the hashmaps are equals.Any other value indicates inequality. + * Note on array comparison operators. + * According to the JX9 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 + * print "Union of \$a and \$b: \n"; + * dump($c); + * $c = $b + $a; // Union of $b and $a + * print "Union of \$b and \$a: \n"; + * 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. + */ +JX9_PRIVATE sxi32 jx9HashmapCmp( + jx9_hashmap *pLeft, /* Left hashmap */ + jx9_hashmap *pRight, /* Right hashmap */ + int bStrict /* TRUE for strict comparison */ + ) +{ + jx9_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 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 JX9 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(jx9_hashmap *pSrc, jx9_hashmap *pDest) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, *pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the 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 */ + jx9MemObjInitFromString(pDest->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); + rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); + jx9MemObjRelease(&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; +} +/* + * Duplicate the contents of a hashmap. Store the copy in pDest. + * Refer to the [array_pad(), array_copy(), ...] implementation for more information. + */ +JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, *pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the 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 */ + jx9MemObjInitFromString(pDest->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); + rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); + jx9MemObjRelease(&sKey); + }else{ + /* Int key insertion */ + rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal); + } + 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 + * print "Union of \$a and \$b: \n"; + * dump($c); + * $c = $b + $a; // Union of $b and $a + * print "Union of \$b and \$a: \n"; + * 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. + */ +JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight) +{ + jx9_hashmap_node *pEntry; + sxi32 rc = SXRET_OK; + jx9_value *pObj; + sxu32 n; + if( pLeft == pRight ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the 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); + 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); + 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. + */ +JX9_PRIVATE jx9_hashmap * jx9NewHashmap( + jx9_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 */ + ) +{ + jx9_hashmap *pMap; + /* Allocate a new instance */ + pMap = (jx9_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_hashmap)); + if( pMap == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pMap, sizeof(jx9_hashmap)); + /* Fill in the structure */ + pMap->pVm = &(*pVm); + pMap->iRef = 1; + /* pMap->iFlags = 0; */ + /* 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 JX9 language reference manual. + * Superglobals are built-in variables that are always available in all scopes. +* Description +* All predefined variables in JX9 are "superglobals", which means they +* are available in all scopes throughout a script. +* These variables are: +* $_SERVER +* $_GET +* $_POST +* $_FILES +* $_REQUEST +* $_ENV +*/ +JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm) +{ + static const char * azSuper[] = { + "_SERVER", /* $_SERVER */ + "_GET", /* $_GET */ + "_POST", /* $_POST */ + "_FILES", /* $_FILES */ + "_REQUEST", /* $_REQUEST */ + "_COOKIE", /* $_COOKIE */ + "_ENV", /* $_ENV */ + "_HEADER", /* $_HEADER */ + "argv" /* $argv */ + }; + SyString *pFile; + sxi32 rc; + sxu32 n; + /* Install globals variable now */ + for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){ + jx9_value *pSuper; + /* Request an empty array */ + pSuper = jx9_new_array(&(*pVm)); + if( pSuper == 0 ){ + return SXERR_MEM; + } + /* Install */ + rc = jx9_vm_config(&(*pVm),JX9_VM_CONFIG_CREATE_VAR, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */); + if( rc != SXRET_OK ){ + return rc; + } + /* Release the value now it have been installed */ + jx9_release_value(&(*pVm), pSuper); + } + /* Set some $_SERVER entries */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + /* + * 'SCRIPT_FILENAME' + * The absolute pathname of the currently executing script. + */ + jx9_vm_config(pVm, JX9_VM_CONFIG_SERVER_ATTR, + "SCRIPT_FILENAME", + pFile ? pFile->zString : ":Memory:", + pFile ? pFile->nByte : sizeof(":Memory:") - 1 + ); + /* All done, all global variables are installed now */ + return SXRET_OK; +} +/* + * Release a hashmap. + */ +JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS) +{ + jx9_hashmap_node *pEntry, *pNext; + jx9_vm *pVm = pMap->pVm; + sxu32 n; + /* Start the release process */ + n = 0; + pEntry = pMap->pFirst; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + pNext = pEntry->pPrev; /* Reverse link */ + /* Restore the jx9_value to the free list */ + jx9VmUnsetMemObj(pVm, pEntry->nValIdx); + /* 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. + */ +JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap) +{ + pMap->iRef--; + if( pMap->iRef < 1 ){ + jx9HashmapRelease(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. + */ +JX9_PRIVATE sxi32 jx9HashmapLookup( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_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. + */ +JX9_PRIVATE sxi32 jx9HashmapInsert( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_value *pVal /* Node value.NULL otherwise */ + ) +{ + sxi32 rc; + rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal)); + return rc; +} +/* + * Reset the node cursor of a given hashmap. + */ +JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_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. + */ +JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap) +{ + jx9_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. + */ +JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode) +{ + jx9_value *pValue; + pValue = HashmapExtractNodeValue(pNode); + return pValue; +} +/* + * Extract a node value (Second). + */ +JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore) +{ + jx9_value *pEntry = HashmapExtractNodeValue(pNode); + if( pEntry ){ + if( bStore ){ + jx9MemObjStore(pEntry, pValue); + }else{ + jx9MemObjLoad(pEntry, pValue); + } + }else{ + jx9MemObjRelease(pValue); + } +} +/* + * Extract a node key. + */ +JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode,jx9_value *pKey) +{ + /* Fill with the current key */ + if( pNode->iType == HASHMAP_INT_NODE ){ + if( SyBlobLength(&pKey->sBlob) > 0 ){ + SyBlobRelease(&pKey->sBlob); + } + pKey->x.iVal = pNode->xKey.iKey; + MemObjSetType(pKey, MEMOBJ_INT); + }else{ + SyBlobReset(&pKey->sBlob); + SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); + MemObjSetType(pKey, MEMOBJ_STRING); + } +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * Store the address of nodes value in the given container. + * Refer to the [vfprintf(), vprintf(), vsprintf()] implementations + * defined in 'builtin.c' for more information. + */ +JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut) +{ + jx9_hashmap_node *pEntry = pMap->pFirst; + jx9_value *pValue; + sxu32 n; + /* Initialize the container */ + SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(jx9_value *)); + for(n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + SySetPut(pOut, (const void *)&pValue); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Total inserted entries */ + return (int)SySetUsed(pOut); +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Merge sort. + * The merge sort implementation is based on the one found in the SQLite3 source tree. + * Status: Public domain + */ +/* Node comparison callback signature */ +typedef sxi32 (*ProcNodeCmp)(jx9_hashmap_node *, jx9_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 jx9_hashmap_node * HashmapNodeMerge(jx9_hashmap_node *pA, jx9_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData) +{ + jx9_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(jx9_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData) +{ + jx9_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; ipNext = 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(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_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 */ + jx9MemObjInit(pA->pMap->pVm, &sA); + jx9MemObjInit(pA->pMap->pVm, &sB); + jx9HashmapExtractNodeValue(pA, &sA, FALSE); + jx9HashmapExtractNodeValue(pB, &sB, FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + jx9MemObjToNumeric(&sA); + jx9MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); + jx9MemObjRelease(&sA); + jx9MemObjRelease(&sB); + return rc; +} +/* + * Node comparison callback. + * Used by: [rsort(), arsort()]; + */ +static sxi32 HashmapCmpCallback3(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_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 */ + jx9MemObjInit(pA->pMap->pVm, &sA); + jx9MemObjInit(pA->pMap->pVm, &sB); + jx9HashmapExtractNodeValue(pA, &sA, FALSE); + jx9HashmapExtractNodeValue(pB, &sB, FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + jx9MemObjToNumeric(&sA); + jx9MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); + jx9MemObjRelease(&sA); + jx9MemObjRelease(&sB); + return -rc; +} +/* + * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. + * used-by: [usort(), uasort()] + */ +static sxi32 HashmapCmpCallback4(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_value sResult, *pCallback; + jx9_value *pV1, *pV2; + jx9_value *apArg[2]; /* Callback arguments */ + sxi32 rc; + /* Point to the desired callback */ + pCallback = (jx9_value *)pCmpData; + /* initialize the result value */ + jx9MemObjInit(pA->pMap->pVm, &sResult); + /* Extract nodes values */ + pV1 = HashmapExtractNodeValue(pA); + pV2 = HashmapExtractNodeValue(pB); + apArg[0] = pV1; + apArg[1] = pV2; + /* Invoke the callback */ + rc = jx9VmCallUserFunction(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 */ + jx9MemObjToInteger(&sResult); + } + rc = (sxi32)sResult.x.iVal; + } + jx9MemObjRelease(&sResult); + /* Callback result */ + return rc; +} +/* + * Rehash all nodes keys after a merge-sort have been applied. + * Used by [sort(), usort() and rsort()]. + */ +static void HashmapSortRehash(jx9_hashmap *pMap) +{ + jx9_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 JX9 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://jx9.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 jx9_hashmap_sort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = jx9_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 JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_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 jx9_hashmap_rsort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = jx9_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 JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_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 jx9_hashmap_usort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + jx9_value *pCallback = 0; + ProcNodeCmp xCmp; + xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ + if( nArg > 1 && jx9_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 JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_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 jx9_hashmap_count(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int bRecursive = FALSE; + sxi64 iCount; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( !jx9_value_is_json_array(apArg[0]) ){ + /* TICKET 1433-19: Handle objects */ + int res = !jx9_value_is_null(apArg[0]); + jx9_result_int(pCtx, res); + return JX9_OK; + } + if( nArg > 1 ){ + /* Recursive count? */ + bRecursive = jx9_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; + } + /* Count */ + iCount = HashmapCount((jx9_hashmap *)apArg[0]->x.pOther, bRecursive, 0); + jx9_result_int64(pCtx, iCount); + return JX9_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 jx9_hashmap_key_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[1]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the lookup */ + rc = jx9HashmapLookup((jx9_hashmap *)apArg[1]->x.pOther, apArg[0], 0); + /* lookup result */ + jx9_result_bool(pCtx, rc == SXRET_OK ? 1 : 0); + return JX9_OK; +} +/* + * value array_pop(array $array) + * POP the last inserted element from the array. + * Parameter + * The array to get the value from. + * Return + * Poped value or NULL on failure. + */ +static int jx9_hashmap_pop(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Noting to pop, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_hashmap_node *pLast = pMap->pLast; + jx9_value *pObj; + pObj = HashmapExtractNodeValue(pLast); + if( pObj ){ + /* Node value */ + jx9_result_value(pCtx, pObj); + /* Unlink the node */ + jx9HashmapUnlinkNode(pLast); + }else{ + jx9_result_null(pCtx); + } + /* Reset the cursor */ + pMap->pCur = pMap->pFirst; + } + return JX9_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 jx9_hashmap_push(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + sxi32 rc; + int i; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Start pushing given values */ + for( i = 1 ; i < nArg ; ++i ){ + rc = jx9HashmapInsert(pMap, 0, apArg[i]); + if( rc != SXRET_OK ){ + break; + } + } + /* Return the new count */ + jx9_result_int64(pCtx, (sxi64)pMap->nEntry); + return JX9_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 jx9_hashmap_shift(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty hashmap, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_hashmap_node *pEntry = pMap->pFirst; + jx9_value *pObj; + sxu32 n; + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Node value */ + jx9_result_value(pCtx, pObj); + /* Unlink the first node */ + jx9HashmapUnlinkNode(pEntry); + }else{ + jx9_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 JX9_OK; +} +/* + * Extract the node cursor value. + */ +static sxi32 HashmapCurrentValue(jx9_context *pCtx, jx9_hashmap *pMap, int iDirection) +{ + jx9_hashmap_node *pCur = pMap->pCur; + jx9_value *pVal; + if( pCur == 0 ){ + /* Cursor does not point to anything, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_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 */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + } + /* Point to the desired element */ + pVal = HashmapExtractNodeValue(pCur); + if( pVal ){ + jx9_result_value(pCtx, pVal); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_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 jx9_hashmap_current(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 0); + return JX9_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 jx9_hashmap_next(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 1); + return JX9_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 jx9_hashmap_prev(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, -1); + return JX9_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 jx9_hashmap_end(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Point to the last node */ + pMap->pCur = pMap->pLast; + /* Return the last node value */ + HashmapCurrentValue(&(*pCtx), pMap, 0); + return JX9_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 jx9_hashmap_reset(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_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 JX9_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 jx9_hashmap_simple_key(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pCur; + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + pCur = pMap->pCur; + if( pCur == 0 ){ + /* Cursor does not point to anything, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( pCur->iType == HASHMAP_INT_NODE){ + /* Key is integer */ + jx9_result_int64(pCtx, pCur->xKey.iKey); + }else{ + /* Key is blob */ + jx9_result_string(pCtx, + (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey)); + } + return JX9_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 jx9_hashmap_each(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pCur; + jx9_hashmap *pMap; + jx9_value *pArray; + jx9_value *pVal; + jx9_value sKey; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->pCur == 0 ){ + /* Cursor does not point to anything, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pCur = pMap->pCur; + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pVal = HashmapExtractNodeValue(pCur); + /* Insert the current value */ + jx9_array_add_strkey_elem(pArray, "1", pVal); + jx9_array_add_strkey_elem(pArray, "value", pVal); + /* Make the key */ + if( pCur->iType == HASHMAP_INT_NODE ){ + jx9MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey); + }else{ + jx9MemObjInitFromString(pMap->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey)); + } + /* Insert the current key */ + jx9_array_add_elem(pArray, 0, &sKey); + jx9_array_add_strkey_elem(pArray, "key", &sKey); + jx9MemObjRelease(&sKey); + /* Advance the cursor */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + /* Return the current entry */ + jx9_result_value(pCtx, pArray); + return JX9_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 jx9_hashmap_values(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pNode; + jx9_hashmap *pMap; + jx9_value *pArray; + jx9_value *pObj; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Perform the requested operation */ + pNode = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; ++n ){ + pObj = HashmapExtractNodeValue(pNode); + if( pObj ){ + /* perform the insertion */ + jx9_array_add_elem(pArray, 0/* Automatic index assign */, pObj); + } + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + } + /* return the new array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool array_same(array $arr1, array $arr2) + * Return TRUE if the given arrays are the same instance. + * This function is useful under JX9 since arrays and objects + * are passed by reference. + * 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 jx9_hashmap_same(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *p1, *p2; + int rc; + if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ + /* Missing or invalid arguments, return FALSE*/ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the hashmaps */ + p1 = (jx9_hashmap *)apArg[0]->x.pOther; + p2 = (jx9_hashmap *)apArg[1]->x.pOther; + rc = (p1 == p2); + /* Same instance? */ + jx9_result_bool(pCtx, rc); + return JX9_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 jx9_hashmap_merge(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap, *pSrc; + jx9_value *pArray; + int i; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)pArray->x.pOther; + /* Start merging */ + for( i = 0 ; i < nArg ; i++ ){ + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[i]) ){ + /* Insert scalar value */ + jx9_array_add_elem(pArray, 0, apArg[i]); + }else{ + pSrc = (jx9_hashmap *)apArg[i]->x.pOther; + /* Merge the two hashmaps */ + HashmapMerge(pSrc, pMap); + } + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_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 jx9_hashmap_in_array(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pNeedle; + int bStrict; + int rc; + if( nArg < 2 ){ + /* Missing argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pNeedle = apArg[0]; + bStrict = 0; + if( nArg > 2 ){ + bStrict = jx9_value_to_bool(apArg[2]); + } + if( !jx9_value_is_json_array(apArg[1]) ){ + /* haystack must be an array, perform a standard comparison */ + rc = jx9_value_compare(pNeedle, apArg[1], bStrict); + /* Set the comparison result */ + jx9_result_bool(pCtx, rc == 0); + return JX9_OK; + } + /* Perform the lookup */ + rc = HashmapFindValue((jx9_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict); + /* Lookup result */ + jx9_result_bool(pCtx, rc == SXRET_OK); + return JX9_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 jx9_hashmap_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pArray; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)pArray->x.pOther; + if( jx9_value_is_json_array(apArg[0])){ + /* Point to the internal representation of the source */ + jx9_hashmap *pSrc = (jx9_hashmap *)apArg[0]->x.pOther; + /* Perform the copy */ + jx9HashmapDup(pSrc, pMap); + }else{ + /* Simple insertion */ + jx9HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]); + } + /* Return the duplicated array */ + jx9_result_value(pCtx, pArray); + return JX9_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 jx9_hashmap_erase(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Erase */ + jx9HashmapRelease(pMap, FALSE); + return JX9_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 jx9_hashmap_diff(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pEntry; + jx9_hashmap *pSrc, *pMap; + jx9_value *pArray; + jx9_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + jx9_result_value(pCtx, apArg[0]); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (jx9_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( !jx9_value_is_json_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_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((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_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 jx9_hashmap_intersect(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pEntry; + jx9_hashmap *pSrc, *pMap; + jx9_value *pArray; + jx9_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + jx9_result_value(pCtx, apArg[0]); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (jx9_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( !jx9_value_is_json_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_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((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_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(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_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_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 */ + jx9_result_double(pCtx, dSum); +} +static void Int64Sum(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_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_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 */ + jx9_result_int64(pCtx, nSum); +} +/* number array_sum(array $array ) + * (See block-coment above) + */ +static int jx9_hashmap_sum(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pObj; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_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 ){ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleSum(pCtx, pMap); + }else{ + Int64Sum(pCtx, pMap); + } + return JX9_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(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_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_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 */ + jx9_result_double(pCtx, dProd); +} +static void Int64Prod(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_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_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 */ + jx9_result_int64(pCtx, nProd); +} +/* number array_product(array $array ) + * (See block-block comment above) + */ +static int jx9_hashmap_product(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pObj; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_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 ){ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleProd(pCtx, pMap); + }else{ + Int64Prod(pCtx, pMap); + } + return JX9_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 jx9_hashmap_map(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue, sKey, sResult; + jx9_hashmap_node *pEntry; + jx9_hashmap *pMap; + sxu32 n; + if( nArg < 2 || !jx9_value_is_json_array(apArg[1]) ){ + /* Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[1]->x.pOther; + jx9MemObjInit(pMap->pVm, &sResult); + jx9MemObjInit(pMap->pVm, &sKey); + /* Perform the requested operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extrcat the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + sxi32 rc; + /* Invoke the supplied callback */ + rc = jx9VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult); + /* Extract the node key */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + jx9_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */ + }else{ + /* Insert the callback return value */ + jx9_array_add_elem(pArray, &sKey, &sResult); + } + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sResult); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + jx9_result_value(pCtx, pArray); + return JX9_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 jx9_hashmap_walk(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pValue, *pUserData, sKey; + jx9_hashmap_node *pEntry; + jx9_hashmap *pMap; + sxi32 rc; + sxu32 n; + if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid/Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pUserData = nArg > 2 ? apArg[2] : 0; + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + jx9MemObjInit(pMap->pVm, &sKey); + /* 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 */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + /* Invoke the supplied callback */ + rc = jx9VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0); + jx9MemObjRelease(&sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + jx9_result_bool(pCtx, 0); /* return FALSE */ + return JX9_OK; + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * Table of built-in hashmap functions. + */ +static const jx9_builtin_func aHashmapFunc[] = { + {"count", jx9_hashmap_count }, + {"sizeof", jx9_hashmap_count }, + {"array_key_exists", jx9_hashmap_key_exists }, + {"array_pop", jx9_hashmap_pop }, + {"array_push", jx9_hashmap_push }, + {"array_shift", jx9_hashmap_shift }, + {"array_product", jx9_hashmap_product }, + {"array_sum", jx9_hashmap_sum }, + {"array_values", jx9_hashmap_values }, + {"array_same", jx9_hashmap_same }, + {"array_merge", jx9_hashmap_merge }, + {"array_diff", jx9_hashmap_diff }, + {"array_intersect", jx9_hashmap_intersect}, + {"in_array", jx9_hashmap_in_array }, + {"array_copy", jx9_hashmap_copy }, + {"array_erase", jx9_hashmap_erase }, + {"array_map", jx9_hashmap_map }, + {"array_walk", jx9_hashmap_walk }, + {"sort", jx9_hashmap_sort }, + {"rsort", jx9_hashmap_rsort }, + {"usort", jx9_hashmap_usort }, + {"current", jx9_hashmap_current }, + {"each", jx9_hashmap_each }, + {"pos", jx9_hashmap_current }, + {"next", jx9_hashmap_next }, + {"prev", jx9_hashmap_prev }, + {"end", jx9_hashmap_end }, + {"reset", jx9_hashmap_reset }, + {"key", jx9_hashmap_simple_key } +}; +/* + * Register the built-in hashmap functions defined above. + */ +JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm) +{ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){ + jx9_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0); + } +} +/* + * 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 JX9_OK. + * Refer to [jx9_array_walk()] for more information. + */ +JX9_PRIVATE sxi32 jx9HashmapWalk( + jx9_hashmap *pMap, /* Target hashmap */ + int (*xWalk)(jx9_value *, jx9_value *, void *), /* Walker callback */ + void *pUserData /* Last argument to xWalk() */ + ) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, sValue; + sxi32 rc; + sxu32 n; + /* Initialize walker parameter */ + rc = SXRET_OK; + jx9MemObjInit(pMap->pVm, &sKey); + jx9MemObjInit(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 */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + jx9HashmapExtractNodeValue(pEntry, &sValue, FALSE); + /* Invoke the user callback */ + rc = xWalk(&sKey, &sValue, pUserData); + /* Release the copy of the key and the value */ + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sValue); + if( rc != JX9_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; +} -- cgit v1.2.3