Files
Aer/engine/memobj.c
T
belliash d98f6a8e91
Build / AerScript (push) Successful in 31s
Fix value comparison and sorting semantics
2026-11-05 22:26:00 +01:00

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