This commit is contained in:
+438
-186
@@ -1107,199 +1107,451 @@ PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj) {
|
||||
return SXRET_OK;
|
||||
}
|
||||
/*
|
||||
* Compare two ph7_values.
|
||||
* Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2
|
||||
* or < 0 if pObj2 is greater than pObj1.
|
||||
* Type comparison table taken from the PHP language reference manual.
|
||||
* Comparisons of $x with PHP functions Expression
|
||||
* gettype() empty() is_null() isset() boolean : if($x)
|
||||
* $x = ""; string TRUE FALSE TRUE FALSE
|
||||
* $x = null NULL TRUE TRUE FALSE FALSE
|
||||
* var $x; NULL TRUE TRUE FALSE FALSE
|
||||
* $x is undefined NULL TRUE TRUE FALSE FALSE
|
||||
* $x = array(); array TRUE FALSE TRUE FALSE
|
||||
* $x = false; boolean TRUE FALSE TRUE FALSE
|
||||
* $x = true; boolean FALSE FALSE TRUE TRUE
|
||||
* $x = 1; integer FALSE FALSE TRUE TRUE
|
||||
* $x = 42; integer FALSE FALSE TRUE TRUE
|
||||
* $x = 0; integer TRUE FALSE TRUE FALSE
|
||||
* $x = -1; integer FALSE FALSE TRUE TRUE
|
||||
* $x = "1"; string FALSE FALSE TRUE TRUE
|
||||
* $x = "0"; string TRUE FALSE TRUE FALSE
|
||||
* $x = "-1"; string FALSE FALSE TRUE TRUE
|
||||
* $x = "php"; string FALSE FALSE TRUE TRUE
|
||||
* $x = "true"; string FALSE FALSE TRUE TRUE
|
||||
* $x = "false"; string FALSE FALSE TRUE TRUE
|
||||
* Loose comparisons with ==
|
||||
* TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "php" ""
|
||||
* TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE
|
||||
* FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE
|
||||
* 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE
|
||||
* -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
|
||||
* "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
|
||||
* "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
|
||||
* NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE
|
||||
* array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
|
||||
* "php" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
|
||||
* "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE
|
||||
* Strict comparisons with ===
|
||||
* TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "php" ""
|
||||
* TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
|
||||
* "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
|
||||
* "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
|
||||
* NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
|
||||
* array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
|
||||
* "php" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
|
||||
* "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
|
||||
* 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.
|
||||
*/
|
||||
PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest) {
|
||||
sxi32 iComb;
|
||||
#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) {
|
||||
sxi32 iF1, iF2;
|
||||
/* Strict comparisons with === */
|
||||
iF1 = pObj1->nType;
|
||||
iF2 = pObj2->nType;
|
||||
if(iF1 != iF2) {
|
||||
/* Not of the same type */
|
||||
return 1;
|
||||
/* Strict comparisons require the exact same type */
|
||||
if(pObj1->nType != pObj2->nType) {
|
||||
/* Not of the same type: incomparable, not "greater" */
|
||||
return SXERR_NOMATCH;
|
||||
}
|
||||
}
|
||||
/* Combine flag together */
|
||||
iComb = pObj1->nType | pObj2->nType;
|
||||
if(iComb & (MEMOBJ_NULL | MEMOBJ_RES | MEMOBJ_BOOL)) {
|
||||
/* Explicitly handle NULL/VOID comparisons with numeric types */
|
||||
if(pObj1->nType & (MEMOBJ_NULL | MEMOBJ_VOID))
|
||||
{
|
||||
/* Convert NULL/VOID to numeric */
|
||||
PH7_MemObjToNumeric(pObj1);
|
||||
}
|
||||
if(pObj2->nType & (MEMOBJ_NULL | MEMOBJ_VOID)) {
|
||||
/* Convert NULL/VOID to numeric */
|
||||
PH7_MemObjToNumeric(pObj2);
|
||||
}
|
||||
if(PH7_MemObjIsNumeric(pObj1) && PH7_MemObjIsNumeric(pObj2)) {
|
||||
/* Jump to the numeric comparison section */
|
||||
goto Numeric;
|
||||
}
|
||||
/* Convert to boolean: Keep in mind FALSE < TRUE */
|
||||
if((pObj1->nType & MEMOBJ_BOOL) == 0) {
|
||||
PH7_MemObjToBool(pObj1);
|
||||
}
|
||||
if((pObj2->nType & MEMOBJ_BOOL) == 0) {
|
||||
PH7_MemObjToBool(pObj2);
|
||||
}
|
||||
return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0));
|
||||
} else if(iComb & MEMOBJ_HASHMAP) {
|
||||
/* Hashmap aka 'array' comparison */
|
||||
if((pObj1->nType & MEMOBJ_HASHMAP) == 0) {
|
||||
/* Array is always greater */
|
||||
return -1;
|
||||
}
|
||||
if((pObj2->nType & MEMOBJ_HASHMAP) == 0) {
|
||||
/* Array is always greater */
|
||||
return 1;
|
||||
}
|
||||
/* Perform the comparison */
|
||||
rc = PH7_HashmapCmp((ph7_hashmap *)pObj1->x.pOther, (ph7_hashmap *)pObj2->x.pOther, bStrict);
|
||||
return rc;
|
||||
} else if(iComb & MEMOBJ_OBJ) {
|
||||
/* Object comparison */
|
||||
if((pObj1->nType & MEMOBJ_OBJ) == 0) {
|
||||
/* Object is always greater */
|
||||
return -1;
|
||||
}
|
||||
if((pObj2->nType & MEMOBJ_OBJ) == 0) {
|
||||
/* Object is always greater */
|
||||
return 1;
|
||||
}
|
||||
/* Perform the comparison */
|
||||
rc = PH7_ClassInstanceCmp((ph7_class_instance *)pObj1->x.pOther, (ph7_class_instance *)pObj2->x.pOther, bStrict, iNest);
|
||||
return rc;
|
||||
} else if(iComb & MEMOBJ_STRING) {
|
||||
SyString s1, s2;
|
||||
if(!bStrict) {
|
||||
/*
|
||||
* According to the PHP language reference manual:
|
||||
*
|
||||
* If you compare a number with a string or the comparison involves numerical
|
||||
* strings, then each string is converted to a number and the comparison
|
||||
* performed numerically.
|
||||
*/
|
||||
if(PH7_MemObjIsNumeric(pObj1)) {
|
||||
/* Perform a numeric comparison */
|
||||
goto Numeric;
|
||||
}
|
||||
if(PH7_MemObjIsNumeric(pObj2)) {
|
||||
/* Perform a numeric comparison */
|
||||
goto Numeric;
|
||||
}
|
||||
}
|
||||
/* Perform a strict string comparison.*/
|
||||
if((pObj1->nType & MEMOBJ_STRING) == 0) {
|
||||
PH7_MemObjToString(pObj1);
|
||||
}
|
||||
if((pObj2->nType & MEMOBJ_STRING) == 0) {
|
||||
PH7_MemObjToString(pObj2);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
} else if(iComb & (MEMOBJ_INT | MEMOBJ_REAL)) {
|
||||
Numeric:
|
||||
/* Perform a numeric comparison if one of the operand is numeric(integer or real) */
|
||||
if((pObj1->nType & (MEMOBJ_INT | MEMOBJ_REAL)) == 0) {
|
||||
PH7_MemObjToNumeric(pObj1);
|
||||
}
|
||||
if((pObj2->nType & (MEMOBJ_INT | MEMOBJ_REAL)) == 0) {
|
||||
PH7_MemObjToNumeric(pObj2);
|
||||
}
|
||||
if((pObj1->nType & pObj2->nType & MEMOBJ_INT) == 0) {
|
||||
ph7_real r1, r2;
|
||||
/* Compare as reals */
|
||||
if((pObj1->nType & MEMOBJ_REAL) == 0) {
|
||||
PH7_MemObjToReal(pObj1);
|
||||
}
|
||||
r1 = pObj1->x.rVal;
|
||||
if((pObj2->nType & MEMOBJ_REAL) == 0) {
|
||||
PH7_MemObjToReal(pObj2);
|
||||
}
|
||||
r2 = pObj2->x.rVal;
|
||||
if(r1 > r2) {
|
||||
return 1;
|
||||
} else if(r1 < r2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
/* Integer comparison */
|
||||
if(pObj1->x.iVal > pObj2->x.iVal) {
|
||||
return 1;
|
||||
} else if(pObj1->x.iVal < pObj2->x.iVal) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
if(!MemObjCmpCategoriesCompatible(pObj1, pObj2)) {
|
||||
if(bThrow) {
|
||||
MemObjCmpThrow(pObj1, pObj2, zOp);
|
||||
}
|
||||
return SXERR_NOMATCH;
|
||||
}
|
||||
/* NOT REACHED */
|
||||
return 0;
|
||||
/* 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.
|
||||
|
||||
Reference in New Issue
Block a user