1765 lines
58 KiB
C
1765 lines
58 KiB
C
/**
|
|
* @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 <devel@symisc.net>
|
|
* Rafal Kupiec <belliash@codingworkshop.eu.org>
|
|
*/
|
|
#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;
|
|
}
|