Fix value comparison and sorting semantics
Build / AerScript (push) Successful in 31s

This commit is contained in:
2026-11-05 22:26:00 +01:00
parent 3c3c15281a
commit d98f6a8e91
10 changed files with 1956 additions and 681 deletions
+438 -186
View File
@@ -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.