/** * @PROJECT PH7 Engine for the AerScript Interpreter * @COPYRIGHT See COPYING in the top level directory * @FILE engine/memobj.c * @DESCRIPTION Low-level indexed memory objects for the PH7 Engine * @DEVELOPERS Symisc Systems * Rafal Kupiec */ #include "ph7int.h" /* This file handle low-level stuff related to indexed memory objects [i.e: ph7_value] */ /* * Notes on memory objects [i.e: ph7_value]. * Internally, the PH7 virtual machine manipulates nearly all PHP values * [i.e: string,int,float,resource,object,bool,null..] as ph7_values structures. * Each ph7_values struct may cache multiple representations (string, * integer etc.) of the same value. */ /* * Convert a 64-bit IEEE double into a 64-bit signed integer. * If the double is too large, return 0x8000000000000000. * * Most systems appear to do this simply by assigning variables and without * the extra range tests. * But there are reports that windows throws an expection if the floating * point value is out of range. */ static sxi64 MemObjRealToInt(ph7_value *pObj) { /* ** Many compilers we encounter do not define constants for the ** minimum and maximum 64-bit integers, or they define them ** inconsistently. And many do not understand the "LL" notation. ** So we define our own static constants here using nothing ** larger than a 32-bit integer constant. */ static const sxi64 maxInt = LARGEST_INT64; static const sxi64 minInt = SMALLEST_INT64; ph7_real r = pObj->x.rVal; if(r < (ph7_real)minInt) { return minInt; } else if(r > (ph7_real)maxInt) { /* minInt is correct here - not maxInt. It turns out that assigning ** a very large positive number to an integer results in a very large ** negative integer. This makes no sense, but it is what x86 hardware ** does so for compatibility we will do the same in software. */ return minInt; } else { return (sxi64)r; } } /* * Convert a raw token value typically a stream of digit [i.e: hex,octal,binary or decimal] * to a 64-bit integer. */ PH7_PRIVATE sxi64 PH7_TokenValueToInt64(SyString *pVal) { sxi64 iVal = 0; if(pVal->nByte <= 0) { return 0; } if(pVal->zString[0] == '0') { sxi32 c; if(pVal->nByte == sizeof(char)) { return 0; } c = pVal->zString[1]; if(c == 'x' || c == 'X') { /* Hex digit stream */ SyHexStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } else if(c == 'b' || c == 'B') { /* Binary digit stream */ SyBinaryStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } else { /* Octal digit stream */ SyOctalStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } } else { /* Decimal digit stream */ SyStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } return iVal; } /* * Return some kind of 64-bit integer value which is the best we can * do at representing the value that pObj describes as a string * representation. */ static sxi64 MemObjStringToInt(ph7_value *pObj) { SyString sVal; SyStringInitFromBuf(&sVal, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); return PH7_TokenValueToInt64(&sVal); } /* * Call a magic class method [i.e: __toString(),__toInt(),...] * Return SXRET_OK if the magic method is available and have been * successfully called. Any other return value indicates failure. */ static sxi32 MemObjCallClassCastMethod( ph7_vm *pVm, /* VM that trigger the invocation */ ph7_class_instance *pThis, /* Target class instance [i.e: Object] */ const char *zMethod, /* Magic method name [i.e: __toString] */ sxu32 nLen, /* Method name length */ ph7_value *pResult /* OUT: Store the return value of the magic method here */ ) { ph7_class_method *pMethod = 0; /* Check if the method is available */ if(pThis) { pMethod = PH7_ClassExtractMethod(pThis->pClass, zMethod, nLen); } if(pMethod == 0) { /* No such method */ return SXERR_NOTFOUND; } /* Invoke the desired method */ PH7_VmCallClassMethod(&(*pVm), &(*pThis), pMethod, &(*pResult), 0, 0); /* Method successfully called,pResult should hold the return value */ return SXRET_OK; } /* * Return some kind of integer value which is the best we can * do at representing the value that pObj describes as an integer. * If pObj is an integer, then the value is exact. If pObj is * a floating-point then the value returned is the integer part. * If pObj is a string, then we make an attempt to convert it into * a integer and return that. * If pObj represents a NULL value, return 0. */ static sxi64 MemObjIntValue(ph7_value *pObj) { sxi32 nType; nType = pObj->nType; if(nType & MEMOBJ_REAL) { return MemObjRealToInt(&(*pObj)); } else if(nType & (MEMOBJ_INT | MEMOBJ_BOOL | MEMOBJ_CHAR)) { return pObj->x.iVal; } else if(nType & MEMOBJ_STRING) { return MemObjStringToInt(&(*pObj)); } else if(nType & (MEMOBJ_CALL | MEMOBJ_NULL | MEMOBJ_VOID)) { return 0; } else if(nType & MEMOBJ_HASHMAP) { ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; sxu32 n = pMap->nEntry; PH7_HashmapUnref(pMap); /* Return total number of entries in the hashmap */ return n; } else if(nType & MEMOBJ_OBJ) { ph7_value sResult; sxi64 iVal = 1; sxi32 rc; /* Invoke the [__toInt()] magic method if available [note that this is a symisc extension] */ PH7_MemObjInit(pObj->pVm, &sResult); rc = MemObjCallClassCastMethod(pObj->pVm, (ph7_class_instance *)pObj->x.pOther, "__toInt", sizeof("__toInt") - 1, &sResult); if(rc == SXRET_OK && (sResult.nType & MEMOBJ_INT)) { /* Extract method return value */ iVal = sResult.x.iVal; } PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); PH7_MemObjRelease(&sResult); return iVal; } else if(nType & MEMOBJ_RES) { return pObj->x.pOther != 0; } /* CANT HAPPEN */ return 0; } /* * Return some kind of real value which is the best we can * do at representing the value that pObj describes as a real. * If pObj is a real, then the value is exact.If pObj is an * integer then the integer is promoted to real and that value * is returned. * If pObj is a string, then we make an attempt to convert it * into a real and return that. * If pObj represents a NULL value, return 0.0 */ static ph7_real MemObjRealValue(ph7_value *pObj) { sxi32 nType; nType = pObj->nType; if(nType & MEMOBJ_REAL) { return pObj->x.rVal; } else if(nType & (MEMOBJ_INT | MEMOBJ_BOOL | MEMOBJ_CHAR)) { return (ph7_real)pObj->x.iVal; } else if(nType & MEMOBJ_STRING) { SyString sString; ph7_real rVal = 0.0; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if(SyBlobLength(&pObj->sBlob) > 0) { /* Convert as much as we can */ SyStrToReal(sString.zString, sString.nByte, (void *)&rVal, 0); } return rVal; } else if(nType & (MEMOBJ_CALL | MEMOBJ_NULL | MEMOBJ_VOID)) { return 0.0; } else if(nType & MEMOBJ_HASHMAP) { /* Return the total number of entries in the hashmap */ ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; ph7_real n = (ph7_real)pMap->nEntry; PH7_HashmapUnref(pMap); return n; } else if(nType & MEMOBJ_OBJ) { ph7_value sResult; ph7_real rVal = 1; sxi32 rc; /* Invoke the [__toFloat()] magic method if available [note that this is a symisc extension] */ PH7_MemObjInit(pObj->pVm, &sResult); rc = MemObjCallClassCastMethod(pObj->pVm, (ph7_class_instance *)pObj->x.pOther, "__toFloat", sizeof("__toFloat") - 1, &sResult); if(rc == SXRET_OK && (sResult.nType & MEMOBJ_REAL)) { /* Extract method return value */ rVal = sResult.x.rVal; } PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); PH7_MemObjRelease(&sResult); return rVal; } else if(nType & MEMOBJ_RES) { return (ph7_real)(pObj->x.pOther != 0); } /* NOT REACHED */ return 0; } /* * Return the string representation of a given ph7_value. * This function never fail and always return SXRET_OK. */ static sxi32 MemObjStringValue(SyBlob *pOut, ph7_value *pObj) { if(pObj->nType & MEMOBJ_REAL) { SyBlobFormat(&(*pOut), "%.15g", pObj->x.rVal); } else if(pObj->nType & MEMOBJ_INT) { SyBlobFormat(&(*pOut), "%qd", pObj->x.iVal); /* %qd (BSD quad) is equivalent to %lld in the libc printf */ } else if(pObj->nType & MEMOBJ_BOOL) { if(pObj->x.iVal) { SyBlobAppend(&(*pOut), "TRUE", sizeof("TRUE") - 1); } else { SyBlobAppend(&(*pOut), "FALSE", sizeof("FALSE") - 1); } } else if(pObj->nType & MEMOBJ_CHAR) { if(pObj->x.iVal > 0) { SyBlobFormat(&(*pOut), "%c", pObj->x.iVal); } } else if(pObj->nType & MEMOBJ_HASHMAP) { SyBlobAppend(&(*pOut), "Array", sizeof("Array") - 1); PH7_HashmapUnref((ph7_hashmap *)pObj->x.pOther); } else if(pObj->nType & MEMOBJ_OBJ) { ph7_value sResult; sxi32 rc; /* Invoke the __toString() method if available */ PH7_MemObjInit(pObj->pVm, &sResult); rc = MemObjCallClassCastMethod(pObj->pVm, (ph7_class_instance *)pObj->x.pOther, "__toString", sizeof("__toString") - 1, &sResult); if(rc == SXRET_OK && (sResult.nType & MEMOBJ_STRING) && SyBlobLength(&sResult.sBlob) > 0) { /* Expand method return value */ SyBlobDup(&sResult.sBlob, pOut); } else { /* Expand "Object" as requested by the PHP language reference manual */ SyBlobAppend(&(*pOut), "Object", sizeof("Object") - 1); } PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); PH7_MemObjRelease(&sResult); } else if(pObj->nType & MEMOBJ_RES) { SyBlobFormat(&(*pOut), "ResourceID_%#x", pObj->x.pOther); } return SXRET_OK; } /* * Return some kind of boolean value which is the best we can do * at representing the value that pObj describes as a boolean. * When converting to boolean, the following values are considered FALSE: * NULL * the boolean FALSE itself. * the integer 0 (zero). * the real 0.0 (zero). * the empty string,a stream of zero [i.e: "0","00","000",...] and the string * "false". * an array with zero elements. */ static sxi32 MemObjBooleanValue(ph7_value *pObj) { sxi32 nType; nType = pObj->nType; if(nType & MEMOBJ_REAL) { return pObj->x.rVal != 0.0 ? 1 : 0; } else if(nType & (MEMOBJ_INT | MEMOBJ_CHAR)) { return pObj->x.iVal ? 1 : 0; } else if(nType & MEMOBJ_STRING) { SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if(sString.nByte == 0) { /* Empty string */ return 0; } else if((sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString, "true", sizeof("true") - 1) == 0) || (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString, "on", sizeof("on") - 1) == 0) || (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString, "yes", sizeof("yes") - 1) == 0)) { return 1; } else if(sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString, "false", sizeof("false") - 1) == 0) { return 0; } else { const char *zIn, *zEnd; zIn = sString.zString; zEnd = &zIn[sString.nByte]; while(zIn < zEnd && zIn[0] == '0') { zIn++; } return zIn >= zEnd ? 0 : 1; } } else if(nType & MEMOBJ_CALL) { SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if(sString.nByte == 0) { /* Empty string */ return 0; } else { /* Something set, return true */ return 1; } } else if(nType & (MEMOBJ_NULL | MEMOBJ_VOID)) { return 0; } else if(nType & MEMOBJ_HASHMAP) { ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; sxu32 n = pMap->nEntry; PH7_HashmapUnref(pMap); return n > 0 ? TRUE : FALSE; } else if(nType & MEMOBJ_OBJ) { ph7_value sResult; sxi32 iVal = 1; sxi32 rc; if(!pObj->x.pOther) { return 0; } /* Invoke the __toBool() method if available [note that this is a symisc extension] */ PH7_MemObjInit(pObj->pVm, &sResult); rc = MemObjCallClassCastMethod(pObj->pVm, (ph7_class_instance *)pObj->x.pOther, "__toBool", sizeof("__toBool") - 1, &sResult); if(rc == SXRET_OK && (sResult.nType & (MEMOBJ_INT | MEMOBJ_BOOL))) { /* Extract method return value */ iVal = (sxi32)(sResult.x.iVal != 0); /* Stupid cc warning -W -Wall -O6 */ } PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); PH7_MemObjRelease(&sResult); return iVal; } else if(nType & MEMOBJ_RES) { return pObj->x.pOther != 0; } /* NOT REACHED */ return 0; } /* * Return the char representation of a given ph7_value. * This function never fail and always return SXRET_OK. */ static ph7_real MemObjCharValue(ph7_value *pObj) { sxi32 nType; nType = pObj->nType; if(nType & (MEMOBJ_CALL | MEMOBJ_REAL | MEMOBJ_HASHMAP | MEMOBJ_RES | MEMOBJ_NULL | MEMOBJ_VOID)) { return 0; } else if(nType & MEMOBJ_INT) { return pObj->x.iVal; } else if(nType & MEMOBJ_STRING) { SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if(sString.nByte == 0) { /* Empty string */ return 0; } return (int) sString.zString[0]; } /* NOT REACHED */ return 0; } /* * Checks a ph7_value variable compatibility with nType data type. */ PH7_PRIVATE sxi32 PH7_CheckVarCompat(ph7_value *pObj, int nType) { if(((nType & MEMOBJ_HASHMAP) == 0) && ((pObj->nType & MEMOBJ_HASHMAP) == 0)) { if(pObj->nType & MEMOBJ_NULL) { /* Always allow to store a NULL */ return SXRET_OK; } else if((nType & MEMOBJ_REAL) && (pObj->nType & MEMOBJ_INT)) { /* Allow to store INT to FLOAT variable */ return SXRET_OK; } else if((nType & MEMOBJ_CHAR) && (pObj->nType & MEMOBJ_INT)) { /* Allow to store INT to CHAR variable */ return SXRET_OK; } else if((nType & MEMOBJ_STRING) && (pObj->nType & MEMOBJ_CHAR)) { /* Allow to store CHAR to STRING variable */ return SXRET_OK; } else if((nType & MEMOBJ_CHAR) && (pObj->nType & MEMOBJ_STRING)) { /* Allow to store STRING to CHAR variable (if not too long) */ int len = SyBlobLength(&pObj->sBlob); if(len == 0 || len == 1) { return SXRET_OK; } } else if((nType & MEMOBJ_CALL) && (pObj->nType & MEMOBJ_STRING)) { /* Allow to store STRING to CALLBACK variable */ return SXRET_OK; } } return SXERR_NOMATCH; } /* Apply only to owned storage. Array copies are made before this walk, so a * wider destination never weakens the source array's leaf contracts. */ PH7_PRIVATE void PH7_MemObjSetDeclaredType(ph7_value *pValue, sxu32 nType) { ph7_hashmap_node *pNode; ph7_hashmap *pMap; if(nType & MEMOBJ_NAMED) { PH7_VmTypeClass(pValue->pVm, nType, TRUE); } else { nType = 0; } pValue->nDeclaredType = nType; if(nType) { pValue->iFlags = MEMOBJ_VARIABLE; } if(nType && MemObjIsNullReference(pValue)) { pValue->nType = nType & MEMOBJ_TYPEMASK; pValue->x.pOther = 0; if(nType & MEMOBJ_HASHMAP) { pValue->x.pOther = PH7_NewHashmap(pValue->pVm, 0, 0); if(pValue->x.pOther == 0) { PH7_VmMemoryError(pValue->pVm); } } } if((pValue->nType & MEMOBJ_HASHMAP) == 0 || pValue->x.pOther == 0) { return; } pMap = (ph7_hashmap *)pValue->x.pOther; pMap->nDeclaredType = nType & ~MEMOBJ_HASHMAP; for(pNode = pMap->pFirst; pNode; pNode = pNode->pPrev) { ph7_value *pLeaf = (ph7_value *)SySetAt(&pValue->pVm->aMemObj, pNode->nValIdx); PH7_MemObjSetDeclaredType(pLeaf, nType ? pMap->nDeclaredType | (pLeaf->nType & MEMOBJ_HASHMAP) : 0); } } /* * Duplicate safely the contents of a ph7_value if source and * destination are of the compatible data types. */ PH7_PRIVATE sxi32 PH7_MemObjSafeStore(ph7_value *pSrc, ph7_value *pDest) { /* Scalar destinations need not have initialized arena metadata. */ ph7_vm *pVm = (pSrc->nType & MEMOBJ_HASHMAP) ? pDest->pVm : 0; sxu32 nDest = pVm ? pDest->nIdx : SXU32_HIGH; sxi32 bArena = pVm && SySetAt(&pVm->aMemObj, nDest) == pDest; if(pDest->nDeclaredType & MEMOBJ_NAMED) { sxu32 nDeclared = pDest->nDeclaredType; sxi32 iFlags = pDest->iFlags; ph7_value sValue, sPrevious; sxi32 rc; if(PH7_VmTypeCheck(pSrc, nDeclared) != SXRET_OK) { return SXERR_NOMATCH; } /* Retain both payloads until storage has its contract again. Releasing * the old object/map may invoke a destructor which writes through an * alias of this binding. The source can also be the destination itself. */ PH7_MemObjInit(pDest->pVm, &sValue); PH7_MemObjInit(pDest->pVm, &sPrevious); PH7_MemObjLoad(pSrc, &sValue); PH7_MemObjLoad(pDest, &sPrevious); PH7_MemObjRelease(pDest); pDest->x.pOther = 0; if(MemObjIsNullReference(&sValue)) { rc = SXRET_OK; } else { rc = PH7_MemObjStore(&sValue, pDest); if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } pDest->nType = nDeclared & MEMOBJ_TYPEMASK; } PH7_MemObjSetDeclaredType(pDest, nDeclared); pDest->iFlags = iFlags; PH7_MemObjRelease(&sValue); PH7_MemObjRelease(&sPrevious); return rc; } if(pDest->nType == 0 || pDest->nType == MEMOBJ_NULL || pDest->nType == pSrc->nType) { sxu32 nType = pSrc->nType; PH7_MemObjStore(pSrc, pDest); /* Array copies can move both source and destination arena slots. */ if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } if(pDest->nType == 0 || pDest->nType == MEMOBJ_NULL) { if(nType == MEMOBJ_NULL) { pDest->nType = MEMOBJ_VOID; } else { pDest->nType = nType; } } } else if(pDest->nType & MEMOBJ_MIXED) { if(pDest->nType & MEMOBJ_HASHMAP) { /* mixed[] */ if(pSrc->nType & MEMOBJ_HASHMAP) { PH7_MemObjStore(pSrc, pDest); if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } pDest->nType |= MEMOBJ_MIXED; } else if(pSrc->nType & MEMOBJ_NULL) { PH7_MemObjToHashmap(pSrc); MemObjSetType(pSrc, pDest->nType); PH7_MemObjStore(pSrc, pDest); } else { return SXERR_NOMATCH; } } else { /* mixed */ if(pSrc->nType == MEMOBJ_NULL) { MemObjSetType(pDest, MEMOBJ_MIXED | MEMOBJ_VOID); } else if((pSrc->nType & MEMOBJ_HASHMAP) == 0) { PH7_MemObjStore(pSrc, pDest); pDest->nType |= MEMOBJ_MIXED; } else { return SXERR_NOMATCH; } } } else if((pDest->nType & MEMOBJ_HASHMAP)) { /* [] */ if(pSrc->nType & MEMOBJ_NULL) { PH7_MemObjToHashmap(pSrc); MemObjSetType(pSrc, pDest->nType); PH7_MemObjStore(pSrc, pDest); } else if(pSrc->nType & MEMOBJ_HASHMAP) { if(PH7_HashmapCast(pSrc, pDest->nType ^ MEMOBJ_HASHMAP) != SXRET_OK) { return SXERR_NOMATCH; } PH7_MemObjStore(pSrc, pDest); } else { return SXERR_NOMATCH; } } else if(PH7_CheckVarCompat(pSrc, pDest->nType) == SXRET_OK) { ProcMemObjCast xCast = PH7_MemObjCastMethod(pDest->nType); xCast(pSrc); PH7_MemObjStore(pSrc, pDest); } else { return SXERR_NOMATCH; } if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } PH7_MemObjSetDeclaredType(pDest, 0); return SXRET_OK; } /* * Convert a ph7_value to type integer.Invalidate any prior representations. */ PH7_PRIVATE sxi32 PH7_MemObjToInteger(ph7_value *pObj) { if((pObj->nType & MEMOBJ_INT) == 0) { /* Preform the conversion */ pObj->x.iVal = MemObjIntValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_INT); } return SXRET_OK; } /* * Convert a ph7_value to type real (Try to get an integer representation also). * Invalidate any prior representations */ PH7_PRIVATE sxi32 PH7_MemObjToReal(ph7_value *pObj) { if((pObj->nType & MEMOBJ_REAL) == 0) { /* Preform the conversion */ pObj->x.rVal = MemObjRealValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_REAL); } return SXRET_OK; } /* * Convert a ph7_value to type boolean.Invalidate any prior representations. */ PH7_PRIVATE sxi32 PH7_MemObjToBool(ph7_value *pObj) { if((pObj->nType & MEMOBJ_BOOL) == 0) { /* Preform the conversion */ pObj->x.iVal = MemObjBooleanValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_BOOL); } return SXRET_OK; } PH7_PRIVATE sxi32 PH7_MemObjToChar(ph7_value *pObj) { if((pObj->nType & MEMOBJ_CHAR) == 0) { /* Preform the conversion */ pObj->x.iVal = MemObjCharValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_CHAR); } return SXRET_OK; } /* * Convert a ph7_value to type void.Invalidate any prior representations. */ PH7_PRIVATE sxi32 PH7_MemObjToVoid(ph7_value *pObj) { if((pObj->nType & MEMOBJ_VOID) == 0) { PH7_MemObjRelease(pObj); MemObjSetType(pObj, MEMOBJ_VOID); } return SXRET_OK; } PH7_PRIVATE sxi32 PH7_MemObjToCallback(ph7_value *pObj) { sxi32 rc = SXRET_OK; if((pObj->nType & (MEMOBJ_CALL | MEMOBJ_STRING)) == 0) { SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ rc = MemObjStringValue(&pObj->sBlob, &(*pObj)); } MemObjSetType(pObj, MEMOBJ_CALL); return rc; } PH7_PRIVATE sxi32 PH7_MemObjToResource(ph7_value *pObj) { sxi32 rc = SXRET_OK; if((pObj->nType & MEMOBJ_NULL) == 0) { PH7_VmThrowError(&(*pObj->pVm), PH7_CTX_WARNING, "Unsafe type casting condition, assuming default value"); } if((pObj->nType & MEMOBJ_RES) == 0) { pObj->x.iVal = 0; } MemObjSetType(pObj, MEMOBJ_RES); return rc; } /* * Convert a ph7_value to type string.Prior representations are NOT invalidated. */ PH7_PRIVATE sxi32 PH7_MemObjToString(ph7_value *pObj) { sxi32 rc = SXRET_OK; if((pObj->nType & MEMOBJ_STRING) == 0) { if((pObj->nType & MEMOBJ_CALL) == 0) { /* Perform the conversion */ SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ rc = MemObjStringValue(&pObj->sBlob, &(*pObj)); } MemObjSetType(pObj, MEMOBJ_STRING); } return rc; } /* * Convert a ph7_value to type array.Invalidate any prior representations. * According to the PHP language reference manual. * For any of the types: integer, float, string, boolean converting a value * to an array results in an array with a single element with index zero * and the value of the scalar which was converted. */ PH7_PRIVATE sxi32 PH7_MemObjToHashmap(ph7_value *pObj) { if((pObj->nType & MEMOBJ_HASHMAP) == 0) { ph7_hashmap *pMap; /* Allocate a new hashmap instance */ pMap = PH7_NewHashmap(pObj->pVm, 0, 0); if(pMap == 0) { return SXERR_MEM; } if((pObj->nType & (MEMOBJ_NULL | MEMOBJ_RES)) == 0) { /* * According to the PHP language reference manual. * For any of the types: integer, float, string, boolean converting a value * to an array results in an array with a single element with index zero * and the value of the scalar which was converted. */ if(pObj->nType & MEMOBJ_OBJ) { /* Object cast */ PH7_ClassInstanceToHashmap((ph7_class_instance *)pObj->x.pOther, pMap); } else { /* Insert a single element */ PH7_HashmapInsert(pMap, 0/* Automatic index assign */, &(*pObj)); } SyBlobRelease(&pObj->sBlob); } /* Invalidate any prior representation */ MemObjSetType(pObj, MEMOBJ_HASHMAP); pObj->x.pOther = pMap; } return SXRET_OK; } /* * Convert a ph7_value to type object.Invalidate any prior representations. * The new object is instantiated from the builtin stdClass(). * The stdClass() class have a single attribute which is '$value'. This attribute * hold a copy of the converted ph7_value. * The internal of the stdClass is as follows: * class stdClass{ * public $value; * public function __toInt(){ return (int)$this->value; } * public function __toBool(){ return (bool)$this->value; } * public function __toFloat(){ return (float)$this->value; } * public function __toString(){ return (string)$this->value; } * function __construct($v){ $this->value = $v; }" * } * Refer to the official documentation for more information. */ PH7_PRIVATE sxi32 PH7_MemObjToObject(ph7_value *pObj) { if(pObj->nType & MEMOBJ_NULL) { /* Invalidate any prior representation */ PH7_MemObjRelease(pObj); /* Typecast the value */ MemObjSetType(pObj, MEMOBJ_OBJ); } else if((pObj->nType & MEMOBJ_OBJ) == 0) { ph7_class_instance *pStd; ph7_class_method *pCons; ph7_class *pClass; ph7_vm *pVm; /* Point to the underlying VM */ pVm = pObj->pVm; /* Point to the stdClass() */ pClass = PH7_VmExtractClass(pVm, "stdClass", sizeof("stdClass") - 1, TRUE); if(pClass == 0) { /* Can't happen,load null instead */ PH7_MemObjRelease(pObj); return SXRET_OK; } /* Instantiate a new stdClass() object */ pStd = PH7_NewClassInstance(pVm, pClass); if(pStd == 0) { /* Out of memory */ PH7_MemObjRelease(pObj); return SXRET_OK; } /* Check if a constructor is available */ pCons = PH7_ClassExtractMethod(pClass, "__construct", sizeof("__construct") - 1); if(pCons) { ph7_value *apArg[2]; /* Invoke the constructor with one argument */ apArg[0] = pObj; PH7_VmCallClassMethod(pVm, pStd, pCons, 0, 1, apArg); if(pStd->iRef < 1) { pStd->iRef = 1; } } /* Invalidate any prior representation */ PH7_MemObjRelease(pObj); /* Save the new instance */ pObj->x.pOther = pStd; MemObjSetType(pObj, MEMOBJ_OBJ); } return SXRET_OK; } /* * Return a pointer to the appropriate convertion method associated * with the given type. * Note on type juggling. * According to the PHP language reference manual * PHP does not require (or support) explicit type definition in variable * declaration; a variable's type is determined by the context in which * the variable is used. That is to say, if a string value is assigned * to variable $var, $var becomes a string. If an integer value is then * assigned to $var, it becomes an integer. */ PH7_PRIVATE ProcMemObjCast PH7_MemObjCastMethod(sxi32 nType) { if(nType & MEMOBJ_STRING) { return PH7_MemObjToString; } else if(nType & MEMOBJ_INT) { return PH7_MemObjToInteger; } else if(nType & MEMOBJ_REAL) { return PH7_MemObjToReal; } else if(nType & MEMOBJ_BOOL) { return PH7_MemObjToBool; } else if(nType & MEMOBJ_CHAR) { return PH7_MemObjToChar; } else if(nType & MEMOBJ_OBJ) { return PH7_MemObjToObject; } else if(nType & MEMOBJ_CALL) { return PH7_MemObjToCallback; } else if(nType & MEMOBJ_RES) { return PH7_MemObjToResource; } else if(nType & MEMOBJ_VOID) { return PH7_MemObjToVoid; } /* Release the variable */ return PH7_MemObjRelease; } /* * Check whether the ph7_value has a NULL-value. In AerScript, * the NULL-value means the default initialization value (i.e. * for INTEGER this should be 0). Exception is the variable of * void type, that cannot store values other than NULL. */ PH7_PRIVATE sxi32 PH7_MemObjIsNull(ph7_value *pObj) { if(pObj->nType & MEMOBJ_HASHMAP) { ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; return pMap->nEntry == 0 ? TRUE : FALSE; } else if(pObj->nType & (MEMOBJ_NULL | MEMOBJ_VOID)) { return TRUE; } else if(pObj->nType & (MEMOBJ_CHAR | MEMOBJ_INT)) { return pObj->x.iVal == 0 ? TRUE : FALSE; } else if(pObj->nType & MEMOBJ_REAL) { return pObj->x.rVal == (ph7_real) 0 ? TRUE : FALSE; } else if(pObj->nType & MEMOBJ_BOOL) { return !pObj->x.iVal; } else if(pObj->nType & (MEMOBJ_OBJ | MEMOBJ_RES)) { return pObj->x.pOther == 0 ? TRUE : FALSE; } else if(pObj->nType & MEMOBJ_CALL) { return SyBlobLength(&pObj->sBlob) == 0 ? TRUE : FALSE; } else if(pObj->nType & MEMOBJ_STRING) { if(SyBlobLength(&pObj->sBlob) <= 0) { return TRUE; } else { const char *zIn, *zEnd; zIn = (const char *)SyBlobData(&pObj->sBlob); zEnd = &zIn[SyBlobLength(&pObj->sBlob)]; while(zIn < zEnd) { if(zIn[0] != '0') { break; } zIn++; } return zIn >= zEnd ? TRUE : FALSE; } } /* Assume empty by default */ return TRUE; } /* * Check whether the ph7_value is an array (hashmap) * Returns TRUE if hashmap, FALSE otherwise. */ PH7_PRIVATE sxi32 PH7_MemObjIsHashmap(ph7_value *pObj) { if(pObj->nType & MEMOBJ_HASHMAP) { return TRUE; } return FALSE; } /* * Check whether the ph7_value is numeric [i.e: int/float/bool] or looks * like a numeric number [i.e: if the ph7_value is of type string.]. * Return TRUE if numeric.FALSE otherwise. */ PH7_PRIVATE sxi32 PH7_MemObjIsNumeric(ph7_value *pObj) { if(pObj->nType & (MEMOBJ_INT | MEMOBJ_REAL)) { return TRUE; } return FALSE; } /* * Convert a ph7_value so that it has types MEMOBJ_REAL or MEMOBJ_INT * or both. * Invalidate any prior representations. Every effort is made to force * the conversion, even if the input is a string that does not look * completely like a number.Convert as much of the string as we can * and ignore the rest. */ PH7_PRIVATE sxi32 PH7_MemObjToNumeric(ph7_value *pObj) { if(pObj->nType & (MEMOBJ_INT | MEMOBJ_REAL | MEMOBJ_BOOL | MEMOBJ_NULL)) { if(pObj->nType & (MEMOBJ_BOOL | MEMOBJ_NULL | MEMOBJ_VOID)) { if(pObj->nType & (MEMOBJ_CALL | MEMOBJ_NULL | MEMOBJ_VOID)) { pObj->x.iVal = 0; } MemObjSetType(pObj, MEMOBJ_INT); } /* Already numeric */ return SXRET_OK; } if(pObj->nType & MEMOBJ_STRING) { sxi32 rc = SXERR_INVALID; sxu8 bReal = FALSE; SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); /* Check if the given string looks like a numeric number */ if(sString.nByte > 0) { rc = SyStrIsNumeric(sString.zString, sString.nByte, &bReal, 0); } if(bReal) { PH7_MemObjToReal(&(*pObj)); } else { if(rc != SXRET_OK) { /* The input does not look at all like a number,set the value to 0 */ pObj->x.iVal = 0; } else { /* Convert as much as we can */ pObj->x.iVal = MemObjStringToInt(&(*pObj)); } MemObjSetType(pObj, MEMOBJ_INT); SyBlobRelease(&pObj->sBlob); } } else if(pObj->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP | MEMOBJ_RES)) { PH7_MemObjToInteger(pObj); } else { /* Perform a blind cast */ PH7_MemObjToReal(&(*pObj)); } return SXRET_OK; } /* * Initialize a ph7_value to the null type. */ PH7_PRIVATE sxi32 PH7_MemObjInit(ph7_vm *pVm, ph7_value *pObj) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the NULL type */ pObj->nType = MEMOBJ_NULL; return SXRET_OK; } /* * Initialize a ph7_value to the integer type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromInt(ph7_vm *pVm, ph7_value *pObj, sxi64 iVal) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.iVal = iVal; pObj->nType = MEMOBJ_INT; return SXRET_OK; } /* * Initialize a ph7_value to the boolean type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromBool(ph7_vm *pVm, ph7_value *pObj, sxi32 iVal) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.iVal = iVal ? 1 : 0; pObj->nType = MEMOBJ_BOOL; return SXRET_OK; } /* * Initialize a ph7_value to the real type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromReal(ph7_vm *pVm, ph7_value *pObj, ph7_real rVal) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.rVal = rVal; pObj->nType = MEMOBJ_REAL; return SXRET_OK; } /* * Initialize a ph7_value to the void type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromVoid(ph7_vm *pVm, ph7_value *pObj) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->nType = MEMOBJ_VOID; return SXRET_OK; }/* * Initialize a ph7_value to the array type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromArray(ph7_vm *pVm, ph7_value *pObj, ph7_hashmap *pArray) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->nType = MEMOBJ_HASHMAP; pObj->x.pOther = pArray; return SXRET_OK; } /* * Initialize a ph7_value to the string type. */ PH7_PRIVATE sxi32 PH7_MemObjInitFromString(ph7_vm *pVm, ph7_value *pObj, const SyString *pVal) { /* Zero the structure */ SyZero(pObj, sizeof(ph7_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); if(pVal) { /* Append contents */ SyBlobAppend(&pObj->sBlob, (const void *)pVal->zString, pVal->nByte); } /* Set the desired type */ pObj->nType = MEMOBJ_STRING; return SXRET_OK; } /* * Append some contents to the internal buffer of a given ph7_value. * If the given ph7_value is not of type string,this function * invalidate any prior representation and set the string type. * Then a simple append operation is performed. */ PH7_PRIVATE sxi32 PH7_MemObjStringAppend(ph7_value *pObj, const char *zData, sxu32 nLen) { sxi32 rc; if((pObj->nType & MEMOBJ_STRING) == 0) { /* Invalidate any prior representation */ PH7_MemObjRelease(pObj); MemObjSetType(pObj, MEMOBJ_STRING); } /* Append contents */ rc = SyBlobAppend(&pObj->sBlob, zData, nLen); return rc; } /* * Format and append some contents to the internal buffer of a given ph7_value. * If the given ph7_value is not of type string,this function invalidate * any prior representation and set the string type. * Then a simple format and append operation is performed. */ PH7_PRIVATE sxi32 PH7_MemObjStringFormat(ph7_value *pObj, const char *zFormat, va_list ap) { sxi32 rc; if((pObj->nType & MEMOBJ_STRING) == 0) { /* Invalidate any prior representation */ PH7_MemObjRelease(pObj); MemObjSetType(pObj, MEMOBJ_STRING); } /* Format and append contents */ rc = SyBlobFormatAp(&pObj->sBlob, zFormat, ap); return rc; } /* * Duplicate the contents of a ph7_value. */ PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc, ph7_value *pDest) { ph7_class_instance *pObj = 0; ph7_hashmap *pSrcMap = 0; sxi32 rc; if(pSrc->x.pOther) { if(pSrc->nType & MEMOBJ_HASHMAP) { /* Dump source hashmap */ pSrcMap = (ph7_hashmap *)pSrc->x.pOther; } else if(pSrc->nType & MEMOBJ_OBJ) { /* Increment reference count */ ((ph7_class_instance *)pSrc->x.pOther)->iRef++; } } if((pDest->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ) { pObj = (ph7_class_instance *)pDest->x.pOther; } pDest->x = pSrc->x; pDest->iFlags = pSrc->iFlags; pDest->nType = pSrc->nType; pDest->nDeclaredType = pSrc->nDeclaredType; rc = SXRET_OK; if(SyBlobLength(&pSrc->sBlob) > 0) { SyBlobReset(&pDest->sBlob); rc = SyBlobDup(&pSrc->sBlob, &pDest->sBlob); } else { if(SyBlobLength(&pDest->sBlob) > 0) { SyBlobRelease(&pDest->sBlob); } } if(pSrcMap) { ph7_vm *pVm = pDest->pVm; sxu32 nDest = pDest->nIdx; sxi32 bArena = SySetAt(&pVm->aMemObj, nDest) == pDest; ph7_hashmap *pMap; /* Finish reading the source before recursive copies relocate aMemObj. * Stack destinations have no arena slot and must not be rebound. */ pMap = PH7_NewHashmap(pVm, 0, 0); PH7_HashmapDup(pSrcMap, pMap); pMap->nDeclaredType = pSrcMap->nDeclaredType; if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } pDest->x.pOther = pMap; } if(pObj) { PH7_ClassInstanceUnref(pObj); } return rc; } /* * Duplicate the contents of a ph7_value but do not copy internal * buffer contents,simply point to it. */ PH7_PRIVATE sxi32 PH7_MemObjLoad(ph7_value *pSrc, ph7_value *pDest) { pDest->x = pSrc->x; pDest->iFlags = pSrc->iFlags; pDest->nType = pSrc->nType; pDest->nDeclaredType = pSrc->nDeclaredType; if(pSrc->x.pOther) { if(pSrc->nType & MEMOBJ_HASHMAP) { /* Increment reference count */ ((ph7_hashmap *)pSrc->x.pOther)->iRef++; } else if(pSrc->nType & MEMOBJ_OBJ) { /* Increment reference count */ ((ph7_class_instance *)pSrc->x.pOther)->iRef++; } } if(SyBlobLength(&pDest->sBlob) > 0) { SyBlobRelease(&pDest->sBlob); } if(SyBlobLength(&pSrc->sBlob) > 0) { SyBlobReadOnly(&pDest->sBlob, SyBlobData(&pSrc->sBlob), SyBlobLength(&pSrc->sBlob)); } return SXRET_OK; } /* * Invalidate any prior representation of a given ph7_value. */ PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj) { pObj->nDeclaredType = 0; if((pObj->nType & MEMOBJ_NULL) == 0) { if(pObj->x.pOther) { if(pObj->nType & MEMOBJ_HASHMAP) { PH7_HashmapUnref((ph7_hashmap *)pObj->x.pOther); } else if(pObj->nType & MEMOBJ_OBJ) { PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); } } /* Release the internal buffer */ SyBlobRelease(&pObj->sBlob); /* Invalidate any prior representation */ pObj->nType = MEMOBJ_NULL; } return SXRET_OK; } /* * Typed comparison contract [AER#26]: * Every ph7_value belongs to exactly one comparison category: * NUMBER : int, float, char, bool and the raw NULL value. All comparisons * inside this domain are numeric with the values bool FALSE=0/ * TRUE=1, char its integer value and raw NULL 0 [the NULL-to-0 * normalization exception applies to raw MEMOBJ_NULL only]. * STRING : byte strings compared byte-wise, never interpreted numerically. * VOID : the empty void value. Void is incomparable: it forms no pair * with any value, not even another void. Every explicit operator * on a void operand raises the incomparable-type diagnostic, * the search paths skip void pairs [no match, never an error], * switch matching skips void, and the default-mode sort pre-scan * rejects void. Emptiness [PH7_MemObjIsNull(), ??, !, if()] is * unchanged: void stays empty. * ARRAY : hashmaps compared for value equality only. * OBJECT : class instances compared for identity (including empty references). * RESOURCE/CALLBACK: equality only, no ordering. Resources compare their * identity [handle pointer]; callbacks additionally compare * their name/representation bytes, so two callbacks are equal * only when their representation blobs and identity match. * Comparisons between values of different categories are runtime errors, * except on the search paths which pre-check comparability. * * NaN handling: a NaN operand is unordered and yields SXERR_NOMATCH. The * callers map that sentinel to FALSE for == and every ordered operator and * to TRUE for !=. Signed zeros compare equal. * * Exact int64-vs-double comparison: an integer i and a double d are compared * exactly: * - NaN is unordered; * - d >= 2^63 means every int64 is smaller, d < -2^63 means every int64 is * greater [d == -2^63 stays on the exact path because it is representable]; * - otherwise d lies in [-2^63, 2^63), where converting it to int64 is * defined. Compare i with that truncated integer part exactly and use the * fractional remainder, if any, to order the two values. * The int64 operand is never widened to a double and no int64 subtraction is * used. */ #define CMP_INT_IS_SMALLER (-1) #define CMP_INT_IS_GREATER 1 #define CMP_TWO_P63 9223372036854775808.0 /* 2^63 as a double */ /* * Exact ordered comparison of an int64 against a double. * Return <0, 0, >0 or SXERR_NOMATCH when the double is NaN [unordered]. */ static sxi32 MemObjCmpIntReal(sxi64 iVal, ph7_real d) { sxi64 iIntPart; if(d != d) { /* NaN: no ordering exists */ return SXERR_NOMATCH; } if(d >= CMP_TWO_P63) { /* Every int64 is smaller than d */ return CMP_INT_IS_SMALLER; } if(d < -CMP_TWO_P63) { /* Every int64 is greater than d */ return CMP_INT_IS_GREATER; } /* d is in [-2^63, 2^63): this conversion is defined. */ iIntPart = (sxi64)d; if(iVal < iIntPart) { return CMP_INT_IS_SMALLER; } if(iVal > iIntPart) { return CMP_INT_IS_GREATER; } /* Equal integer parts: only a fractional remainder orders the values. */ if(d == (ph7_real)iIntPart) { return 0; } return (ph7_real)iIntPart < d ? CMP_INT_IS_SMALLER : CMP_INT_IS_GREATER; } /* * Ordered comparison of two doubles. * Return <0, 0, >0 or SXERR_NOMATCH when either side is NaN [unordered]. */ static sxi32 MemObjCmpRealReal(ph7_real r1, ph7_real r2) { if(r1 != r1 || r2 != r2) { /* NaN involved */ return SXERR_NOMATCH; } if(r1 > r2) { return 1; } else if(r1 < r2) { return -1; } return 0; } /* * Comparison categories of the typed contract */ #define MEMCMP_CAT_NUMBER 1 #define MEMCMP_CAT_STRING 2 #define MEMCMP_CAT_HASHMAP 3 #define MEMCMP_CAT_OBJ 4 #define MEMCMP_CAT_RES 5 #define MEMCMP_CAT_CALL 6 #define MEMCMP_CAT_VOID 7 static sxi32 MemObjCmpCategory(ph7_value *pObj) { sxu32 nType = pObj->nType; if(nType & MEMOBJ_HASHMAP) { return MEMCMP_CAT_HASHMAP; } if(nType & MEMOBJ_OBJ) { return MEMCMP_CAT_OBJ; } if(nType & MEMOBJ_STRING) { return MEMCMP_CAT_STRING; } if(nType & MEMOBJ_RES) { return MEMCMP_CAT_RES; } if(nType & MEMOBJ_CALL) { return MEMCMP_CAT_CALL; } if(nType & MEMOBJ_VOID) { /* Void is its own incomparable category: it is NOT part of the * numeric domain and the NULL-to-0 normalization never applies * to it [it applies to raw MEMOBJ_NULL only]. */ return MEMCMP_CAT_VOID; } /* INT, REAL, CHAR, BOOL and raw NULL are the numeric domain */ return MEMCMP_CAT_NUMBER; } /* * Decide whether two values may be compared at all. * Two values of the same category are always comparable, with one exception: * VOID [the empty void value] is incomparable with everything, including * another void, so any pair involving it is rejected here. Additionally an * object may be compared with a raw NULL for equality: the reference rule * decides [an empty reference equals NULL, an instantiated object does not]. * The pair has no ordering; PH7_MemObjOrderable() rejects it. */ static sxi32 MemObjCmpCategoriesCompatible(ph7_value *pObj1, ph7_value *pObj2) { sxi32 iCat1 = MemObjCmpCategory(pObj1); sxi32 iCat2 = MemObjCmpCategory(pObj2); if(iCat1 == MEMCMP_CAT_VOID || iCat2 == MEMCMP_CAT_VOID) { /* Void takes part in no comparison at all */ return FALSE; } if(iCat1 == iCat2) { return TRUE; } if((iCat1 == MEMCMP_CAT_OBJ && pObj2->nType & MEMOBJ_NULL) || (iCat2 == MEMCMP_CAT_OBJ && pObj1->nType & MEMOBJ_NULL)) { /* Object vs raw NULL follows the reference rule */ return TRUE; } return FALSE; } /* * Return the Aer type name of a ph7_value for diagnostics. * Aer terms: int, float, char, bool, string, NULL, array, object, * resource, callback, void. */ static const char *MemObjCmpTypeName(ph7_value *pObj) { sxu32 nType = pObj->nType; if(nType & MEMOBJ_HASHMAP) { return "array"; } if(nType & MEMOBJ_OBJ) { return "object"; } if(nType & MEMOBJ_STRING) { return "string"; } if(nType & MEMOBJ_RES) { return "resource"; } if(nType & MEMOBJ_CALL) { return "callback"; } if(nType & MEMOBJ_REAL) { return "float"; } if(nType & MEMOBJ_INT) { return "int"; } if(nType & MEMOBJ_CHAR) { return "char"; } if(nType & MEMOBJ_BOOL) { return "bool"; } if(nType & MEMOBJ_VOID) { return "void"; } return "NULL"; } /* * Throw the fatal incomparability diagnostic. The operator spelling comes * first, then the left type, then the right type [the file name and line * are appended by the VM error reporter]. A NULL operator [non-operator * internal paths] prints '-' as a placeholder. */ static void MemObjCmpThrow(ph7_value *pObj1, ph7_value *pObj2, const char *zOp) { PH7_VmThrowError(pObj1->pVm, PH7_CTX_ERR, "Cannot compare '%s' %s with %s", zOp ? zOp : "-", MemObjCmpTypeName(pObj1), MemObjCmpTypeName(pObj2)); } /* * Throw the fatal no-ordering diagnostic for [<,<=,>,>=] on operands * that support equality only [arrays, objects, resources, callbacks]. * The operator spelling is included; NULL prints '-' as a placeholder. */ void PH7_VmThrowNoOrder(ph7_value *pObj1, ph7_value *pObj2, const char *zOp) { PH7_VmThrowError(pObj1->pVm, PH7_CTX_ERR, "Cannot order '%s' %s with %s", zOp ? zOp : "-", MemObjCmpTypeName(pObj1), MemObjCmpTypeName(pObj2)); } /* * Compare two values known to be in the NUMBER category. * Operands are copies; the raw NULL value was normalized to the integer 0 * by the caller [void is no longer part of this domain]. */ static sxi32 MemObjCmpNumber(ph7_value *pObj1, ph7_value *pObj2) { sxi32 iF1 = pObj1->nType, iF2 = pObj2->nType; sxi32 rc; if((iF1 & MEMOBJ_REAL) && (iF2 & MEMOBJ_REAL)) { /* Real vs real */ return MemObjCmpRealReal(pObj1->x.rVal, pObj2->x.rVal); } if(iF1 & MEMOBJ_REAL) { /* Real vs integer-backed value */ rc = MemObjCmpIntReal(pObj2->x.iVal, pObj1->x.rVal); return rc == SXERR_NOMATCH ? SXERR_NOMATCH : -rc; } if(iF2 & MEMOBJ_REAL) { /* Integer-backed value vs real */ return MemObjCmpIntReal(pObj1->x.iVal, pObj2->x.rVal); } /* Pure integer domain: int, char, bool, raw NULL [normalized to 0] */ if(pObj1->x.iVal > pObj2->x.iVal) { return 1; } else if(pObj1->x.iVal < pObj2->x.iVal) { return -1; } return 0; } /* * Compare two values known to be in the STRING category (copies). * Exact byte comparison; shorter prefix sorts first. * The ordering result is always the 3-way value -1/0/+1: SyMemcmp() returns * a raw byte delta in [-255,255] and a delta such as 'a'-'{' = -26 would * collide with the SXERR_NOMATCH sentinel [and every other engine error * code], silently turning a legitimate ordering into "incomparable". The * delta is therefore reduced to its sign right here, where the byte * comparison is made, before any consumer can see it. */ static sxi32 MemObjCmpString(ph7_value *pObj1, ph7_value *pObj2) { SyString s1, s2; sxi32 rc; SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob)); SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob)); /* * Strings are compared using memcmp(). If one value is an exact prefix of the * other, then the shorter value is less than the longer value. */ rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte)); if(rc == 0) { if(s1.nByte != s2.nByte) { rc = s1.nByte < s2.nByte ? -1 : 1; } } else { /* 3-way result only: never leak the raw byte delta */ rc = rc < 0 ? -1 : 1; } return rc; } /* * Non-mutating comparison core implementing the typed comparison contract. * Both operands are used through stack copies, so caller-visible values are * never converted in place [copy-on-compare]. * * bThrow selects the entry flavor: * TRUE : operators/switch/is - an incomparable pair is a fatal runtime error. * FALSE : search paths - an incomparable pair yields SXERR_NOMATCH silently. * bStrict requires identical representation types: a type mismatch is an * incomparable pair and yields SXERR_NOMATCH on both entry flavors. * * zOp carries the operator spelling [i.e: "==","<"...] for the fatal * diagnostic; NULL when the entry point is not an operator. * Return the ordering result [-1, 0 or +1] from which the callers derive * the requested operator result, or SXERR_NOMATCH when the result is * unordered [NaN], when bStrict sees different types, when an instantiated * object is compared with NULL [reference inequality without an ordering], * or with bThrow FALSE when the categories are incompatible [including any * pair involving the incomparable void value]. */ static sxi32 MemObjCmpCore(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest, sxi32 bThrow, const char *zOp) { ph7_value s1, s2; sxi32 rc; if(bStrict) { /* Strict comparisons require the exact same type */ if(pObj1->nType != pObj2->nType) { /* Not of the same type: incomparable, not "greater" */ return SXERR_NOMATCH; } } if(!MemObjCmpCategoriesCompatible(pObj1, pObj2)) { if(bThrow) { MemObjCmpThrow(pObj1, pObj2, zOp); } return SXERR_NOMATCH; } /* Work on copies: never mutate the caller's values */ PH7_MemObjInit(pObj1->pVm, &s1); PH7_MemObjInit(pObj1->pVm, &s2); PH7_MemObjLoad(pObj1, &s1); PH7_MemObjLoad(pObj2, &s2); /* Object vs raw NULL is equality-only. Resolve it explicitly and * symmetrically before the category dispatch: the dispatch inspects the * left operand only, so a raw NULL on the left would take the NUMBER path * and read the object union as an integer. */ if((MemObjCmpCategory(pObj1) == MEMCMP_CAT_OBJ && (pObj2->nType & MEMOBJ_NULL)) || (MemObjCmpCategory(pObj2) == MEMCMP_CAT_OBJ && (pObj1->nType & MEMOBJ_NULL))) { ph7_class_instance *pObjSide = (s1.nType & MEMOBJ_NULL) ? (ph7_class_instance *)s2.x.pOther : (ph7_class_instance *)s1.x.pOther; /* Reference rule: only an empty reference equals NULL. */ rc = PH7_ClassInstanceCmp(pObjSide, 0, bStrict, iNest); if(rc != 0) { /* This pair has no ordering: never invent one. */ rc = SXERR_NOMATCH; } PH7_MemObjRelease(&s1); PH7_MemObjRelease(&s2); return rc; } switch(MemObjCmpCategory(pObj1)) { case MEMCMP_CAT_NUMBER: /* Normalize the raw NULL value to integer 0, per engine * convention. The exception applies to MEMOBJ_NULL only: * void is its own incomparable category and never reaches * this branch [MemObjCmpCategoriesCompatible() rejects it]. */ if(s1.nType & MEMOBJ_NULL) { s1.x.iVal = 0; MemObjSetType(&s1, MEMOBJ_INT); } if(s2.nType & MEMOBJ_NULL) { s2.x.iVal = 0; MemObjSetType(&s2, MEMOBJ_INT); } rc = MemObjCmpNumber(&s1, &s2); if(rc == SXERR_NOMATCH) { /* NaN operand: every operator yields FALSE except != which * yields TRUE. The callers map this sentinel accordingly. */ PH7_MemObjRelease(&s1); PH7_MemObjRelease(&s2); return SXERR_NOMATCH; } break; case MEMCMP_CAT_STRING: rc = MemObjCmpString(&s1, &s2); break; case MEMCMP_CAT_HASHMAP: /* Arrays support equality only */ rc = PH7_HashmapCmp((ph7_hashmap *)s1.x.pOther, (ph7_hashmap *)s2.x.pOther, bStrict); break; case MEMCMP_CAT_OBJ: /* Identity comparison [the NULL side was handled above] */ rc = PH7_ClassInstanceCmp((ph7_class_instance *)s1.x.pOther, (ph7_class_instance *)s2.x.pOther, bStrict, iNest); break; case MEMCMP_CAT_RES: /* Resource identity */ rc = s1.x.pOther == s2.x.pOther ? 0 : 1; break; default: /* MEMCMP_CAT_CALL */ /* Callback equality: same representation (blob and/or identity). * Only the zero/non-zero status of SyMemcmp is consumed here, so * its raw byte delta never escapes as a comparison result. */ if(SyBlobLength(&s1.sBlob) != SyBlobLength(&s2.sBlob) || SyMemcmp(SyBlobData(&s1.sBlob), SyBlobData(&s2.sBlob), SyBlobLength(&s1.sBlob)) != 0 || s1.x.pOther != s2.x.pOther) { rc = 1; } else { rc = 0; } break; } PH7_MemObjRelease(&s1); PH7_MemObjRelease(&s2); SXUNUSED(iNest); /* Kept for signature compatibility */ return rc; } /* * Check whether two values may be compared without raising an error. * Used by the search paths [in_array(), array_keys(),...], the switch * matcher and the is fallback, where an incompatible pair means "no * match", never a fatal error. Every pair involving the void value is * incompatible here: void is skipped by searches and switch matching * instead of matching as the number 0. */ PH7_PRIVATE sxi32 PH7_MemObjComparable(ph7_value *pObj1, ph7_value *pObj2) { return MemObjCmpCategoriesCompatible(pObj1, pObj2); } /* * Check whether two values support ORDERED comparison [<,<=,>,>=]. * Only two NUMBER values or two STRING values are orderable; arrays, * objects, resources and callbacks define equality but no ordering, and * the incomparable void value has neither equality nor ordering. */ PH7_PRIVATE sxi32 PH7_MemObjOrderable(ph7_value *pObj1, ph7_value *pObj2) { sxi32 iCat1 = MemObjCmpCategory(pObj1); sxi32 iCat2 = MemObjCmpCategory(pObj2); return (iCat1 == MEMCMP_CAT_NUMBER || iCat1 == MEMCMP_CAT_STRING) && iCat1 == iCat2; } /* * Compare two ph7_values under the typed comparison contract. * Return 0 if the values are equal, > 0 if pObj1 is greater than pObj2 * or < 0 if pObj2 is greater than pObj1. * Return SXERR_NOMATCH when the result is unordered [NaN], when bStrict is * requested and the values do not have the identical type, or when an * instantiated object is compared with NULL [reference inequality without an * ordering]. * An incomparable pair aborts with a diagnostic on this entry; zOp names * the operator for that diagnostic [NULL when not an operator]. * The operands are NOT mutated [copy-on-compare]. */ PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest, const char *zOp) { return MemObjCmpCore(pObj1, pObj2, bStrict, iNest, TRUE, zOp); } /* * Non-erroring comparison flavor for the public C API. * Behaves exactly like PH7_MemObjCmp() except that an incomparable pair * yields SXERR_NOMATCH instead of aborting: a host application must be * able to probe values without risking a process exit. */ PH7_PRIVATE sxi32 PH7_MemObjCmpSafe(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest, const char *zOp) { return MemObjCmpCore(pObj1, pObj2, bStrict, iNest, FALSE, zOp); } /* * Perform an addition operation of two ph7_values. * The reason this function is implemented here rather than 'vm.c' * is that the '+' operator is overloaded. * That is,the '+' operator is used for arithmetic operation and also * used for operation on arrays [i.e: union]. When used with an array * 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. * This function take care of handling all the scenarios. */ PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1, ph7_value *pObj2, int bAddStore) { if(((pObj1->nType | pObj2->nType) & MEMOBJ_HASHMAP) == 0) { /* Arithemtic operation */ PH7_MemObjToNumeric(pObj1); PH7_MemObjToNumeric(pObj2); if((pObj1->nType | pObj2->nType) & MEMOBJ_REAL) { /* Floating point arithmetic */ ph7_real a, b; if((pObj1->nType & MEMOBJ_REAL) == 0) { PH7_MemObjToReal(pObj1); } if((pObj2->nType & MEMOBJ_REAL) == 0) { PH7_MemObjToReal(pObj2); } a = pObj1->x.rVal; b = pObj2->x.rVal; pObj1->x.rVal = a + b; MemObjSetType(pObj1, MEMOBJ_REAL); } else { /* Integer arithmetic */ sxi64 a, b; a = pObj1->x.iVal; b = pObj2->x.iVal; pObj1->x.iVal = PH7_Int64Add(a, b); MemObjSetType(pObj1, MEMOBJ_INT); } } else { if((pObj1->nType | pObj2->nType) & MEMOBJ_HASHMAP) { ph7_hashmap *pMap; sxi32 rc; if(bAddStore) { /* Do not duplicate the hashmap,use the left one since its an add&store operation. */ if((pObj1->nType & MEMOBJ_HASHMAP) == 0) { /* Force a hashmap cast */ rc = PH7_MemObjToHashmap(pObj1); if(rc != SXRET_OK) { PH7_VmMemoryError(pObj1->pVm); } } /* Point to the structure that describe the hashmap */ pMap = (ph7_hashmap *)pObj1->x.pOther; } else { /* Create a new hashmap */ pMap = PH7_NewHashmap(pObj1->pVm, 0, 0); if(pMap == 0) { PH7_VmMemoryError(pObj1->pVm); } } if(!bAddStore) { if(pObj1->nType & MEMOBJ_HASHMAP) { /* Perform a hashmap duplication */ PH7_HashmapDup((ph7_hashmap *)pObj1->x.pOther, pMap); } else { if((pObj1->nType & MEMOBJ_NULL) == 0) { /* Simple insertion */ PH7_HashmapInsert(pMap, 0, pObj1); } } } /* Perform the union */ if(pObj2->nType & MEMOBJ_HASHMAP) { PH7_HashmapUnion(pMap, (ph7_hashmap *)pObj2->x.pOther); } else { if((pObj2->nType & MEMOBJ_NULL) == 0) { /* Simple insertion */ PH7_HashmapInsert(pMap, 0, pObj2); } } /* Reflect the change */ if(pObj1->nType & MEMOBJ_STRING) { SyBlobRelease(&pObj1->sBlob); } pObj1->x.pOther = pMap; MemObjSetType(pObj1, MEMOBJ_HASHMAP); } } return SXRET_OK; } /* * Return a printable representation of the type of a given * ph7_value. */ PH7_PRIVATE const char *PH7_MemObjTypeDump(ph7_value *pVal) { const char *zType = ""; if(pVal->nType & MEMOBJ_NULL) { zType = "NULL"; } else { if(pVal->nType & MEMOBJ_HASHMAP) { if(pVal->nType & MEMOBJ_MIXED) { zType = "array(mixed, "; } else if(pVal->nType & MEMOBJ_OBJ) { zType = "array(object, "; } else if(pVal->nType & MEMOBJ_INT) { zType = "array(int, "; } else if(pVal->nType & MEMOBJ_REAL) { zType = "array(float, "; } else if(pVal->nType & MEMOBJ_STRING) { zType = "array(string, "; } else if(pVal->nType & MEMOBJ_BOOL) { zType = "array(bool, "; } else if(pVal->nType & MEMOBJ_CHAR) { zType = "array(char, "; } else if(pVal->nType & MEMOBJ_RES) { zType = "array(resource, "; } else if(pVal->nType & MEMOBJ_CALL) { zType = "array(callback, "; } else if(pVal->nType & MEMOBJ_VOID) { zType = "array(void, "; } } else if(pVal->nType & MEMOBJ_OBJ) { zType = "object"; } else if(pVal->nType & MEMOBJ_INT) { zType = "int"; } else if(pVal->nType & MEMOBJ_REAL) { zType = "float"; } else if(pVal->nType & MEMOBJ_STRING) { zType = "string"; } else if(pVal->nType & MEMOBJ_BOOL) { zType = "bool"; } else if(pVal->nType & MEMOBJ_CHAR) { zType = "char"; } else if(pVal->nType & MEMOBJ_RES) { zType = "resource"; } else if(pVal->nType & MEMOBJ_CALL) { zType = "callback"; } else if(pVal->nType & MEMOBJ_VOID) { zType = "void"; } } return zType; } /* * Dump a ph7_value [i.e: get a printable representation of it's type and contents.]. * Store the dump in the given blob. */ PH7_PRIVATE sxi32 PH7_MemObjDump( SyBlob *pOut, /* Store the dump here */ ph7_value *pObj, /* Dump this */ int ShowType, /* TRUE to output value type */ int nTab, /* # of Whitespace to insert */ int nDepth /* Nesting level */ ) { sxi32 rc = SXRET_OK; const char *zType; int i; for(i = 0 ; i < nTab ; i++) { SyBlobAppend(&(*pOut), " ", sizeof(char)); } if(ShowType) { /* Get value type first */ zType = PH7_MemObjTypeDump(pObj); SyBlobAppend(&(*pOut), zType, SyStrlen(zType)); } if((pObj->nType & MEMOBJ_NULL) == 0) { if(ShowType && (pObj->nType & MEMOBJ_HASHMAP) == 0) { SyBlobAppend(&(*pOut), "(", sizeof(char)); } if(pObj->nType & MEMOBJ_HASHMAP) { /* Dump hashmap entries */ rc = PH7_HashmapDump(&(*pOut), (ph7_hashmap *)pObj->x.pOther, ShowType, nTab + 1, nDepth + 1); } else if(pObj->nType & MEMOBJ_OBJ) { /* Dump class instance attributes */ rc = PH7_ClassInstanceDump(&(*pOut), (ph7_class_instance *)pObj->x.pOther, ShowType, nTab + 1, nDepth + 1); } else if(pObj->nType & MEMOBJ_VOID) { SyBlobAppend(&(*pOut), "NULL", sizeof("NULL") - 1); } else { SyBlob *pContents = &pObj->sBlob; /* Get a printable representation of the contents */ if((pObj->nType & (MEMOBJ_STRING | MEMOBJ_CALL)) == 0) { MemObjStringValue(&(*pOut), &(*pObj)); } else { /* Append length first */ if(ShowType) { SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob)); } if(SyBlobLength(pContents) > 0) { SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents)); } if(ShowType) { SyBlobAppend(&(*pOut), "'", sizeof(char)); } } } if(ShowType) { if((pObj->nType & (MEMOBJ_HASHMAP | MEMOBJ_OBJ)) == 0) { SyBlobAppend(&(*pOut), ")", sizeof(char)); } } } #ifdef __WINNT__ SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1); #else SyBlobAppend(&(*pOut), "\n", sizeof(char)); #endif return rc; }