/* * 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; }