This commit is contained in:
+22
-2
@@ -886,6 +886,25 @@ void *ph7_value_to_resource(ph7_value *pValue) {
|
|||||||
/*
|
/*
|
||||||
* [CAPIREF: ph7_value_compare()]
|
* [CAPIREF: ph7_value_compare()]
|
||||||
* Please refer to the official documentation for function purpose and expected parameters.
|
* Please refer to the official documentation for function purpose and expected parameters.
|
||||||
|
*
|
||||||
|
* The comparison core does NOT mutate its operands [copy-on-compare], so the
|
||||||
|
* caller's values keep their type and contents after this call.
|
||||||
|
* If either pLeft or pRight is a NULL pointer, the call is harmless
|
||||||
|
* [legacy ticket 1433-24] and returns 1 [not equal] without inspecting
|
||||||
|
* the other value.
|
||||||
|
* Return 0 when both values are equal. For the ordered categories [numbers,
|
||||||
|
* strings] the result is exactly -1 or +1 [the comparison core normalizes
|
||||||
|
* ordering results to the 3-way value; a raw SyMemcmp byte delta such as
|
||||||
|
* -26 is never handed out]. The equality-only categories [arrays, objects,
|
||||||
|
* resources, callbacks] never return < 0: a positive result simply means
|
||||||
|
* "not equal", NOT "greater".
|
||||||
|
* SXERR_NOMATCH (-26) is returned when no 3-way ordering result exists:
|
||||||
|
* different comparison categories [e.g: string vs int, or any pair
|
||||||
|
* involving the incomparable void value], a NaN operand
|
||||||
|
* [unordered], an instantiated object against NULL [equality is known to be
|
||||||
|
* false, but the pair has no ordering], or, when bStrict is non-zero, values
|
||||||
|
* whose types are not identical. It is not an ordering result; test it before
|
||||||
|
* interpreting the sign.
|
||||||
*/
|
*/
|
||||||
int ph7_value_compare(ph7_value *pLeft, ph7_value *pRight, int bStrict) {
|
int ph7_value_compare(ph7_value *pLeft, ph7_value *pRight, int bStrict) {
|
||||||
int rc;
|
int rc;
|
||||||
@@ -893,8 +912,9 @@ int ph7_value_compare(ph7_value *pLeft, ph7_value *pRight, int bStrict) {
|
|||||||
/* TICKET 1433-24: NULL values is harmless operation */
|
/* TICKET 1433-24: NULL values is harmless operation */
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* Perform the comparison */
|
/* Perform the comparison [non-erroring flavor: a host application
|
||||||
rc = PH7_MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0);
|
* must be able to probe values without risking a process exit] */
|
||||||
|
rc = PH7_MemObjCmpSafe(&(*pLeft), &(*pRight), bStrict, 0, 0);
|
||||||
/* Comparison result */
|
/* Comparison result */
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|||||||
+443
-12
@@ -70,6 +70,15 @@ static void PH7_GenStateInitBlock(
|
|||||||
) {
|
) {
|
||||||
/* Initialize block fields */
|
/* Initialize block fields */
|
||||||
pBlock->nFirstInstr = nFirstInstr;
|
pBlock->nFirstInstr = nFirstInstr;
|
||||||
|
/* Unique instance id: PH7_GenStateFreeBlock() returns the instance to the
|
||||||
|
* pool and the very same address is handed out to later blocks, so any
|
||||||
|
* identity test across block lifetimes [declared-type table] must use
|
||||||
|
* this serial, never the address. The counter is never reset inside one
|
||||||
|
* code-generator episode: PH7_InitCodeGenerator() starts a new episode and
|
||||||
|
* zeroes the counter together with the hDeclType table, and an include
|
||||||
|
* saves/restores the enclosing episode's counter in lockstep with that
|
||||||
|
* table [see PH7_CompileIncludedChunk]. */
|
||||||
|
pBlock->nSerial = ++pGen->nBlockSerial;
|
||||||
pBlock->pUserData = pUserData;
|
pBlock->pUserData = pUserData;
|
||||||
pBlock->pGen = pGen;
|
pBlock->pGen = pGen;
|
||||||
pBlock->iFlags = iType;
|
pBlock->iFlags = iType;
|
||||||
@@ -1012,6 +1021,233 @@ static sxu32 GenStateNamedType(ph7_gen_state *pGen, SyString *pName) {
|
|||||||
}
|
}
|
||||||
return ((i + 1) << 12) | MEMOBJ_OBJ;
|
return ((i + 1) << 12) | MEMOBJ_OBJ;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Classify a literal expression node for the static comparison checks.
|
||||||
|
* Returns one of the PH7_CMP_LIT_* values; PH7_CMP_LIT_NONE when the node
|
||||||
|
* is not a statically classifiable literal.
|
||||||
|
*/
|
||||||
|
#define PH7_CMP_LIT_NONE 0
|
||||||
|
#define PH7_CMP_LIT_STRING 1
|
||||||
|
#define PH7_CMP_LIT_INT 2
|
||||||
|
#define PH7_CMP_LIT_FLOAT 3
|
||||||
|
#define PH7_CMP_LIT_ARRAY 4
|
||||||
|
#define PH7_CMP_LIT_BOOL 5
|
||||||
|
#define PH7_CMP_LIT_NULL 6
|
||||||
|
#define PH7_CMP_LIT_OBJECT 7
|
||||||
|
/*
|
||||||
|
* int, float, bool and NULL literals all belong to the NUMBER comparison
|
||||||
|
* category: they are cross-comparable without an explicit rule.
|
||||||
|
*/
|
||||||
|
#define PH7_CMP_LIT_IS_NUMBERISH(X) ((X) == PH7_CMP_LIT_INT || (X) == PH7_CMP_LIT_FLOAT || (X) == PH7_CMP_LIT_BOOL || (X) == PH7_CMP_LIT_NULL)
|
||||||
|
/*
|
||||||
|
* Static comparison checker support for declared variables.
|
||||||
|
* A typed declaration stamps its mask onto the value at OP_DECLARE
|
||||||
|
* [engine/vm.c] and the runtime assignment guard keeps that category for the
|
||||||
|
* whole lifetime of the variable, even under a NULL store or a re-assignment.
|
||||||
|
* The verdicts the checker can make for literals can therefore also be made
|
||||||
|
* for variables whose declared mask pins exactly one comparison category.
|
||||||
|
* Conservative by construction: mixed, char, void, resource and callback
|
||||||
|
* masks pin no category [GenStateDeclaredCategory returns NONE], and so do
|
||||||
|
* array element types, class fields through '->', static properties,
|
||||||
|
* superglobals, foreach slots, by-ref formals [their caller-side binding is
|
||||||
|
* unproven at the declaration], call results and undeclared names. Everything
|
||||||
|
* left as NONE defers to the runtime guard, which stays authoritative.
|
||||||
|
* Entries are keyed by the interned variable name [the same hVar name pool
|
||||||
|
* that LOADV operands use] and owned by the block of the declaration: a
|
||||||
|
* lookup is valid only while that block still encloses the current block,
|
||||||
|
* walking the GenBlock chain and stopping at the function body boundary,
|
||||||
|
* exactly like the run-time variable lookup.
|
||||||
|
* INVARIANT: a block instance is identified by its unique serial
|
||||||
|
* [GenBlock::nSerial], NOT by its address. Blocks are returned to the
|
||||||
|
* memory pool on leave [PH7_GenStateFreeBlock] and the allocator hands the
|
||||||
|
* very same address to later block instances, so raw pointer identity can
|
||||||
|
* match a long-dead declaration in an unrelated function. The entry stamps
|
||||||
|
* the declaring block's serial at install time; the chain walk compares
|
||||||
|
* serials, so a recycled address never resurrects a stale entry.
|
||||||
|
* SECOND INVARIANT: an entry is a fact about the storage cell created by the
|
||||||
|
* declaration, not about the name forever. unset($name) destroys that cell
|
||||||
|
* [vm_builtin_unset → PH7_VmUnsetMemObj in engine/vm.c], so the compiler
|
||||||
|
* forgets the entry as soon as it emits such an unset [GenStateForgetUnsetName]
|
||||||
|
* and every later comparison of that name falls back to the runtime guard:
|
||||||
|
* an unset name can only be used again through a fresh declaration, and a
|
||||||
|
* declaration whose mask pins no category [mixed...] installs nothing.
|
||||||
|
* Missing static diagnostics are acceptable, false ones are not.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
sxu32 nCat; /* PH7_CMP_LIT_* category pinned by the declaration */
|
||||||
|
sxu32 nSerial; /* Serial of the block owning the declaration */
|
||||||
|
} GenDeclType;
|
||||||
|
/*
|
||||||
|
* Map a declared type mask to its static comparison category, or NONE when
|
||||||
|
* the mask does not pin exactly one [mixed/char/void/resource/callback or an
|
||||||
|
* implicit declaration].
|
||||||
|
*/
|
||||||
|
static sxi32 GenStateDeclaredCategory(sxu32 nType) {
|
||||||
|
if(nType & MEMOBJ_HASHMAP) {
|
||||||
|
/* T[] always holds an array value, whatever T says about elements */
|
||||||
|
return PH7_CMP_LIT_ARRAY;
|
||||||
|
}
|
||||||
|
if(nType & MEMOBJ_OBJ) {
|
||||||
|
return PH7_CMP_LIT_OBJECT;
|
||||||
|
}
|
||||||
|
if(nType & MEMOBJ_STRING) {
|
||||||
|
return PH7_CMP_LIT_STRING;
|
||||||
|
}
|
||||||
|
if(nType & MEMOBJ_INT) {
|
||||||
|
return PH7_CMP_LIT_INT;
|
||||||
|
}
|
||||||
|
if(nType & MEMOBJ_REAL) {
|
||||||
|
return PH7_CMP_LIT_FLOAT;
|
||||||
|
}
|
||||||
|
if(nType & MEMOBJ_BOOL) {
|
||||||
|
return PH7_CMP_LIT_BOOL;
|
||||||
|
}
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Return the persistent interned copy of a variable name, sharing the hVar
|
||||||
|
* name pool used by the LOADV operands. NULL on allocation failure.
|
||||||
|
*/
|
||||||
|
static char *GenStateInternVarName(ph7_gen_state *pGen, SyString *pName) {
|
||||||
|
SyHashEntry *pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte);
|
||||||
|
char *zName;
|
||||||
|
if(pEntry != 0) {
|
||||||
|
/* Name already available */
|
||||||
|
return (char *)pEntry->pUserData;
|
||||||
|
}
|
||||||
|
/* Duplicate name */
|
||||||
|
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
||||||
|
if(zName == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Install in the hashtable */
|
||||||
|
if(SyHashInsert(&pGen->hVar, zName, pName->nByte, zName) != SXRET_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return zName;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Drop the entry pinned for a name, if any, and release its storage.
|
||||||
|
*/
|
||||||
|
static void GenStateRemoveDeclaredType(
|
||||||
|
ph7_gen_state *pGen, /* Code generator state */
|
||||||
|
const void *pKey, sxu32 nKeyLen /* Interned variable name */
|
||||||
|
) {
|
||||||
|
void *pOld = 0; /* SyHashDeleteEntry leaves the OUT untouched when not found */
|
||||||
|
SyHashDeleteEntry(&pGen->hDeclType, pKey, nKeyLen, &pOld);
|
||||||
|
if(pOld != 0) {
|
||||||
|
SyMemBackendFree(&pGen->pVm->sAllocator, pOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Record (or replace with the most recent visible declaration) the category
|
||||||
|
* pinned for a variable name by its declaration inside pBlock.
|
||||||
|
*/
|
||||||
|
static void GenStateInstallDeclaredType(
|
||||||
|
ph7_gen_state *pGen, /* Code generator state */
|
||||||
|
const void *pKey, sxu32 nKeyLen, /* Interned variable name */
|
||||||
|
sxi32 iCat, /* PH7_CMP_LIT_* category pinned by the declaration */
|
||||||
|
GenBlock *pBlock /* Block owning the declaration */
|
||||||
|
) {
|
||||||
|
GenDeclType *pDecl;
|
||||||
|
if(pKey == 0) {
|
||||||
|
/* Nothing statically known; the runtime guard stays authoritative */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* One entry per name: the most recent visible declaration wins */
|
||||||
|
GenStateRemoveDeclaredType(&(*pGen), pKey, nKeyLen);
|
||||||
|
pDecl = (GenDeclType *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(GenDeclType));
|
||||||
|
if(pDecl == 0) {
|
||||||
|
/* Don't worry about freeing memory, everything will be released shortly */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pDecl->nCat = (sxu32)iCat;
|
||||||
|
pDecl->nSerial = pBlock->nSerial;
|
||||||
|
if(SyHashInsert(&pGen->hDeclType, pKey, nKeyLen, pDecl) != SXRET_OK) {
|
||||||
|
SyMemBackendFree(&pGen->pVm->sAllocator, pDecl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Look up the category pinned by the declaration of pName, honoured only
|
||||||
|
* while the declaring block still encloses the current block. The walk
|
||||||
|
* mirrors the scope rules: it stops at the enclosing function body block,
|
||||||
|
* so names declared in another method/closure or in a block that has already
|
||||||
|
* been left are never matched here.
|
||||||
|
*/
|
||||||
|
static sxi32 GenStateLookupDeclaredType(ph7_gen_state *pGen, SyString *pName) {
|
||||||
|
SyHashEntry *pEntry;
|
||||||
|
GenDeclType *pDecl;
|
||||||
|
GenBlock *pBlock;
|
||||||
|
pEntry = SyHashGet(&pGen->hDeclType, (const void *)pName->zString, pName->nByte);
|
||||||
|
if(pEntry == 0) {
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
pDecl = (GenDeclType *)pEntry->pUserData;
|
||||||
|
for(pBlock = pGen->pCurrent; pBlock != 0; pBlock = pBlock->pParent) {
|
||||||
|
if(pBlock->nSerial == pDecl->nSerial) {
|
||||||
|
return (sxi32)pDecl->nCat;
|
||||||
|
}
|
||||||
|
if(pBlock->iFlags & (GEN_BLOCK_PROTECTED | GEN_BLOCK_FUNC)) {
|
||||||
|
/* Function body boundary: different scope */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Return the name of a plain variable reference node, that is, exactly the
|
||||||
|
* '$' token plus the name token. Any other shape [member access, subscript,
|
||||||
|
* call result, variable-variable...] gives 0. Both the static checker and the
|
||||||
|
* unset() invalidation below must agree on this shape, so the test lives here.
|
||||||
|
*/
|
||||||
|
static SyString *GenStateVarNameNode(ph7_expr_node *pNode) {
|
||||||
|
if(pNode == 0 || pNode->xCode != PH7_CompileVariable || pNode->pStart == 0 ||
|
||||||
|
pNode->pEnd != pNode->pStart + 2 || (pNode->pStart->nType & PH7_TK_DOLLAR) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return &pNode->pStart[1].sData;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* TRUE for a call written as a bare name, 'unset(...)' in any spelling case
|
||||||
|
* [the same case-insensitive name test PH7_GenStateLoadLiteral applies to the
|
||||||
|
* reserved literals]. The callee is a single literal token here: method calls
|
||||||
|
* [$o->m()], static calls [X::m()] and dynamic calls [$f()] carry a different
|
||||||
|
* callee shape and are never matched.
|
||||||
|
*/
|
||||||
|
static sxi32 GenStateIsUnsetCall(ph7_expr_node *pNode) {
|
||||||
|
ph7_expr_node *pCallee = pNode->pLeft;
|
||||||
|
SyString *pName;
|
||||||
|
if(pCallee == 0 || pCallee->xCode != PH7_CompileLiteral ||
|
||||||
|
pCallee->pEnd != pCallee->pStart + 1) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
pName = &pCallee->pStart->sData;
|
||||||
|
return pName->nByte == sizeof("unset") - 1 &&
|
||||||
|
SyStrnicmp(pName->zString, "unset", sizeof("unset") - 1) == 0;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Feed the static comparison checker for an unset() call: every plain variable
|
||||||
|
* argument loses the category pinned by its declaration, because the builtin
|
||||||
|
* destroys the storage cell behind the name at run-time
|
||||||
|
* [vm_builtin_unset → PH7_VmUnsetMemObj in engine/vm.c] and the next
|
||||||
|
* declaration of that name may pin another category or none at all. The entry
|
||||||
|
* is dropped before the arguments [and anything later] are compiled, so those
|
||||||
|
* comparisons defer to the runtime guard, which stays authoritative.
|
||||||
|
* Subscripted unsets [unset($a[1])], class fields through '->' and superglobals
|
||||||
|
* are not plain variable references: the variable cell itself survives those
|
||||||
|
* and their entries stay pinned.
|
||||||
|
*/
|
||||||
|
static void GenStateForgetUnsetName(ph7_gen_state *pGen, ph7_expr_node *pNode) {
|
||||||
|
ph7_expr_node **apNode;
|
||||||
|
sxi32 n;
|
||||||
|
apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
|
||||||
|
for(n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n) {
|
||||||
|
SyString *pName = GenStateVarNameNode(apNode[n]);
|
||||||
|
if(pName != 0) {
|
||||||
|
GenStateRemoveDeclaredType(&(*pGen), (const void *)pName->zString, pName->nByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
static sxi32 GenStateIsNamedDeclaration(SyToken *pIn, SyToken *pEnd) {
|
static sxi32 GenStateIsNamedDeclaration(SyToken *pIn, SyToken *pEnd) {
|
||||||
if(pIn >= pEnd || (pIn->nType & PH7_TK_ID) == 0 || (pIn->nType & PH7_TK_OP)) {
|
if(pIn >= pEnd || (pIn->nType & PH7_TK_ID) == 0 || (pIn->nType & PH7_TK_OP)) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -1207,26 +1443,16 @@ PH7_PRIVATE sxi32 PH7_CompileVariable(ph7_gen_state *pGen, sxi32 iCompileFlag) {
|
|||||||
return SXERR_ABORT;
|
return SXERR_ABORT;
|
||||||
}
|
}
|
||||||
p3 = 0;
|
p3 = 0;
|
||||||
SyHashEntry *pEntry;
|
|
||||||
SyString *pName;
|
SyString *pName;
|
||||||
char *zName = 0;
|
char *zName;
|
||||||
/* Extract variable name */
|
/* Extract variable name */
|
||||||
pName = &pGen->pIn->sData;
|
pName = &pGen->pIn->sData;
|
||||||
/* Advance the stream cursor */
|
/* Advance the stream cursor */
|
||||||
pGen->pIn++;
|
pGen->pIn++;
|
||||||
pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte);
|
zName = GenStateInternVarName(&(*pGen), pName);
|
||||||
if(pEntry == 0) {
|
|
||||||
/* Duplicate name */
|
|
||||||
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
|
||||||
if(zName == 0) {
|
if(zName == 0) {
|
||||||
PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 engine is running out-of-memory");
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 engine is running out-of-memory");
|
||||||
}
|
}
|
||||||
/* Install in the hashtable */
|
|
||||||
SyHashInsert(&pGen->hVar, zName, pName->nByte, zName);
|
|
||||||
} else {
|
|
||||||
/* Name already available */
|
|
||||||
zName = (char *)pEntry->pUserData;
|
|
||||||
}
|
|
||||||
p3 = (void *)zName;
|
p3 = (void *)zName;
|
||||||
/* Emit the load instruction */
|
/* Emit the load instruction */
|
||||||
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADV, 0, 0, p3, 0);
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADV, 0, 0, p3, 0);
|
||||||
@@ -2710,6 +2936,16 @@ static sxi32 PH7_CompileVar(ph7_gen_state *pGen) {
|
|||||||
/* Storage belongs to the declaration; visibility starts here. */
|
/* Storage belongs to the declaration; visibility starts here. */
|
||||||
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 2,
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 2,
|
||||||
SySetUsed(&pFunc->aStatic) - 1, pFunc, 0);
|
SySetUsed(&pFunc->aStatic) - 1, pFunc, 0);
|
||||||
|
/* Same declared-category bookkeeping as plain declarations: the
|
||||||
|
* static keeps its category across every assignment, and the
|
||||||
|
* enclosing function block owns it. */
|
||||||
|
{
|
||||||
|
sxi32 iCat = GenStateDeclaredCategory(nType);
|
||||||
|
if(iCat != PH7_CMP_LIT_NONE) {
|
||||||
|
GenStateInstallDeclaredType(&(*pGen), GenStateInternVarName(&(*pGen), pName),
|
||||||
|
pName->nByte, iCat, pBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
||||||
if(zDup == 0) {
|
if(zDup == 0) {
|
||||||
@@ -2718,6 +2954,16 @@ static sxi32 PH7_CompileVar(ph7_gen_state *pGen) {
|
|||||||
void *p3 = (void *) zDup;
|
void *p3 = (void *) zDup;
|
||||||
/* Emit OP_DECLARE instruction */
|
/* Emit OP_DECLARE instruction */
|
||||||
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 0, nType, p3, 0);
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 0, nType, p3, 0);
|
||||||
|
/* Feed the static comparison checker: the declared mask pins this
|
||||||
|
* name's comparison category inside the current block [implicit
|
||||||
|
* declarations and category-less masks pin nothing]. */
|
||||||
|
{
|
||||||
|
sxi32 iCat = GenStateDeclaredCategory(nType);
|
||||||
|
if(iCat != PH7_CMP_LIT_NONE) {
|
||||||
|
GenStateInstallDeclaredType(&(*pGen), GenStateInternVarName(&(*pGen), pName),
|
||||||
|
pName->nByte, iCat, pGen->pCurrent);
|
||||||
|
}
|
||||||
|
}
|
||||||
/* Pop the l-value */
|
/* Pop the l-value */
|
||||||
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
|
||||||
/* Check if we have an expression to compile */
|
/* Check if we have an expression to compile */
|
||||||
@@ -3293,6 +3539,27 @@ static sxi32 PH7_GenStateCompileFuncBody(
|
|||||||
if(rc != SXRET_OK) {
|
if(rc != SXRET_OK) {
|
||||||
PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
|
PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
|
||||||
}
|
}
|
||||||
|
/* The formal arguments are declared for the whole body: hand their pinned
|
||||||
|
* categories to the static comparison checker, owned by this body block.
|
||||||
|
* By-ref formals are skipped on purpose [the task's conservative list]:
|
||||||
|
* the caller-side binding is unproven at the declaration, so their
|
||||||
|
* category stays in the runtime guard. */
|
||||||
|
{
|
||||||
|
ph7_vm_func_arg *pArg = (ph7_vm_func_arg *)SySetBasePtr(&pFunc->aArgs);
|
||||||
|
sxu32 n, nArg = SySetUsed(&pFunc->aArgs);
|
||||||
|
for(n = 0 ; n < nArg ; ++n, ++pArg) {
|
||||||
|
sxi32 iCat;
|
||||||
|
if(pArg->iFlags & VM_FUNC_ARG_BY_REF) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
iCat = GenStateDeclaredCategory(pArg->nType);
|
||||||
|
if(iCat == PH7_CMP_LIT_NONE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GenStateInstallDeclaredType(&(*pGen), GenStateInternVarName(&(*pGen), &pArg->sName),
|
||||||
|
pArg->sName.nByte, iCat, pBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
nGotoOfft = SySetUsed(&pGen->aGoto);
|
nGotoOfft = SySetUsed(&pGen->aGoto);
|
||||||
/* Swap bytecode containers */
|
/* Swap bytecode containers */
|
||||||
pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
|
pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
|
||||||
@@ -4907,6 +5174,138 @@ static sxi32 PH7_CompileSwitch(ph7_gen_state *pGen) {
|
|||||||
}
|
}
|
||||||
return SXRET_OK;
|
return SXRET_OK;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Classify an operand node for the static comparison checks.
|
||||||
|
* Returns one of the PH7_CMP_LIT_* values [constants moved above];
|
||||||
|
* PH7_CMP_LIT_NONE when the node is not statically classifiable.
|
||||||
|
*/
|
||||||
|
static sxi32 PH7_GenStateClassifyLiteralNode(ph7_gen_state *pGen, ph7_expr_node *pNode) {
|
||||||
|
if(pNode == 0 || pNode->pStart == 0) {
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
if(pNode->xCode == PH7_CompileVariable) {
|
||||||
|
/* A plain variable reference [shape test shared with the unset()
|
||||||
|
* invalidation]: the category is whatever the in-scope declaration
|
||||||
|
* pins; anything else [member access, index, static property...] is a
|
||||||
|
* different node shape and stays NONE. */
|
||||||
|
SyString *pName = GenStateVarNameNode(pNode);
|
||||||
|
if(pName == 0) {
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
return GenStateLookupDeclaredType(pGen, pName);
|
||||||
|
}
|
||||||
|
if(pNode->xCode == PH7_CompileString || pNode->xCode == PH7_CompileSimpleString) {
|
||||||
|
/* Single or double quoted string literal */
|
||||||
|
return PH7_CMP_LIT_STRING;
|
||||||
|
}
|
||||||
|
if(pNode->xCode == PH7_CompileNumLiteral) {
|
||||||
|
/* Integer or real literal: the token type already tells them apart */
|
||||||
|
if(pNode->pStart->nType & PH7_TK_REAL) {
|
||||||
|
return PH7_CMP_LIT_FLOAT;
|
||||||
|
}
|
||||||
|
return PH7_CMP_LIT_INT;
|
||||||
|
}
|
||||||
|
if(pNode->xCode == PH7_CompileArray) {
|
||||||
|
/* Array literal */
|
||||||
|
return PH7_CMP_LIT_ARRAY;
|
||||||
|
}
|
||||||
|
if(pNode->xCode == PH7_CompileLiteral) {
|
||||||
|
/* Keyword literals TRUE/FALSE/NULL: these reserved names are
|
||||||
|
* identifier-like tokens resolved by PH7_GenStateLoadLiteral()
|
||||||
|
* [same name test as PH7_GenStateIsReservedConstant()], never a
|
||||||
|
* user constant or function call [a call is an operator node]. */
|
||||||
|
SyString *pName = &pNode->pStart->sData;
|
||||||
|
if(pName->nByte == sizeof("NULL") - 1) {
|
||||||
|
if(SyStrnicmp(pName->zString, "null", sizeof("null") - 1) == 0) {
|
||||||
|
return PH7_CMP_LIT_NULL;
|
||||||
|
}
|
||||||
|
if(SyStrnicmp(pName->zString, "true", sizeof("true") - 1) == 0) {
|
||||||
|
return PH7_CMP_LIT_BOOL;
|
||||||
|
}
|
||||||
|
} else if(pName->nByte == sizeof("FALSE") - 1
|
||||||
|
&& SyStrnicmp(pName->zString, "false", sizeof("false") - 1) == 0) {
|
||||||
|
return PH7_CMP_LIT_BOOL;
|
||||||
|
}
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
if(pNode->pOp != 0 && pNode->pOp->iOp == EXPR_OP_NEW) {
|
||||||
|
/* new X(): a freshly instantiated object literal */
|
||||||
|
return PH7_CMP_LIT_OBJECT;
|
||||||
|
}
|
||||||
|
return PH7_CMP_LIT_NONE;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Static compile-time check for the relational operators [==,!=,<,<=,>,>=]:
|
||||||
|
* reject pairs of statically known incompatible operands. Detected kinds:
|
||||||
|
* literal string vs a number/bool/NULL literal, array vs any literal of
|
||||||
|
* another category, object literal [new] vs any literal of another category,
|
||||||
|
* and the same shapes for variables whose in-scope declaration pins a single
|
||||||
|
* category. int/float/bool/NULL belong to one NUMBER category and are
|
||||||
|
* mutually allowed [2==TRUE, 0==FALSE, NULL==0, true==1 stay valid].
|
||||||
|
* Equality and ordering split at the escape rules: equality admits two arrays,
|
||||||
|
* two objects and object↔NULL [the reference rule decides], while ordering
|
||||||
|
* admits only NUMBER↔NUMBER and STRING↔STRING and rejects everything else
|
||||||
|
* statically, object↔NULL included. Everything the classification leaves as
|
||||||
|
* NONE [char literals, mixed-typed declared variables, elements, fields,
|
||||||
|
* call results...] defers to the runtime guards, and a rule that cannot be
|
||||||
|
* proven never fires [a static error on a legal program is a regression].
|
||||||
|
*/
|
||||||
|
static void PH7_GenStateCheckCompareOperands(
|
||||||
|
ph7_gen_state *pGen, /* Code generator state */
|
||||||
|
ph7_expr_node *pNode,/* Operator node */
|
||||||
|
int bOrder /* TRUE for [<,<=,>,>=], FALSE for [==,!=] */
|
||||||
|
) {
|
||||||
|
static const char *const aLitName[] = {
|
||||||
|
"", "string", "int", "float", "array", "bool", "NULL", "object"
|
||||||
|
};
|
||||||
|
sxi32 iLeft, iRight;
|
||||||
|
/* Extract the raw operand nodes */
|
||||||
|
ph7_expr_node *pL = pNode->pLeft;
|
||||||
|
ph7_expr_node *pR = pNode->pRight;
|
||||||
|
if(pL == 0 || pR == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iLeft = PH7_GenStateClassifyLiteralNode(&(*pGen), pL);
|
||||||
|
iRight = PH7_GenStateClassifyLiteralNode(&(*pGen), pR);
|
||||||
|
if(iLeft == PH7_CMP_LIT_NONE || iRight == PH7_CMP_LIT_NONE) {
|
||||||
|
/* Not statically decidable */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(PH7_CMP_LIT_IS_NUMBERISH(iLeft) && PH7_CMP_LIT_IS_NUMBERISH(iRight)) {
|
||||||
|
/* NUMBER category members cross-compare: allowed for both verdicts */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(bOrder) {
|
||||||
|
if(iLeft == PH7_CMP_LIT_STRING && iRight == PH7_CMP_LIT_STRING) {
|
||||||
|
/* Byte order is a defined total order on strings */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Arrays, objects and object↔NULL have no ordering at all under the
|
||||||
|
* comparison contract; same verb as the runtime no-order guard
|
||||||
|
* [PH7_VmThrowNoOrder in engine/memobj.c]. */
|
||||||
|
PH7_GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,
|
||||||
|
"Cannot order '%z' %s with %s",
|
||||||
|
&pNode->pOp->sOp, aLitName[iLeft], aLitName[iRight]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(iLeft == iRight) {
|
||||||
|
/* Same category: allowed [two arrays, two objects, two strings...] */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if((iLeft == PH7_CMP_LIT_OBJECT && iRight == PH7_CMP_LIT_NULL)
|
||||||
|
|| (iRight == PH7_CMP_LIT_OBJECT && iLeft == PH7_CMP_LIT_NULL)) {
|
||||||
|
/* Object vs raw NULL is legal under the contract [the reference rule
|
||||||
|
* decides equality]; never reject it statically. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Every remaining literal pair spans statically incompatible categories
|
||||||
|
* [array, object or string against a foreign category]. The operator
|
||||||
|
* spelling and precise literal type names are reported; the line comes
|
||||||
|
* from the error reporter. */
|
||||||
|
PH7_GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,
|
||||||
|
"Cannot compare '%z' %s with %s",
|
||||||
|
&pNode->pOp->sOp, aLitName[iLeft], aLitName[iRight]);
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Generate bytecode for a given expression tree.
|
* Generate bytecode for a given expression tree.
|
||||||
* If something goes wrong while generating bytecode
|
* If something goes wrong while generating bytecode
|
||||||
@@ -5020,6 +5419,12 @@ static sxi32 PH7_GenStateEmitExprCode(
|
|||||||
if(iVmOp == PH7_OP_CALL) {
|
if(iVmOp == PH7_OP_CALL) {
|
||||||
ph7_expr_node **apNode;
|
ph7_expr_node **apNode;
|
||||||
sxi32 n;
|
sxi32 n;
|
||||||
|
if(GenStateIsUnsetCall(pNode)) {
|
||||||
|
/* unset() destroys the arguments' storage cells: forget the
|
||||||
|
* categories pinned for those names right here, so nothing
|
||||||
|
* later is checked against a dead declaration. */
|
||||||
|
GenStateForgetUnsetName(&(*pGen), pNode);
|
||||||
|
}
|
||||||
/* Recurse and generate bytecodes for function arguments */
|
/* Recurse and generate bytecodes for function arguments */
|
||||||
apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
|
apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
|
||||||
/* Read-only load */
|
/* Read-only load */
|
||||||
@@ -5115,6 +5520,14 @@ static sxi32 PH7_GenStateEmitExprCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(iVmOp > 0) {
|
if(iVmOp > 0) {
|
||||||
|
if(iVmOp == PH7_OP_EQ || iVmOp == PH7_OP_NEQ || iVmOp == PH7_OP_LT
|
||||||
|
|| iVmOp == PH7_OP_LE || iVmOp == PH7_OP_GT || iVmOp == PH7_OP_GE) {
|
||||||
|
/* Typed comparison contract: reject statically incompatible
|
||||||
|
* operands. The operator spelling decides the verdict [equality
|
||||||
|
* vs ordering] and the matching diagnostic verb. */
|
||||||
|
PH7_GenStateCheckCompareOperands(&(*pGen), pNode,
|
||||||
|
(iVmOp != PH7_OP_EQ && iVmOp != PH7_OP_NEQ) ? TRUE : FALSE);
|
||||||
|
}
|
||||||
if(iVmOp == PH7_OP_INCR || iVmOp == PH7_OP_DECR) {
|
if(iVmOp == PH7_OP_INCR || iVmOp == PH7_OP_DECR) {
|
||||||
if(pNode->iFlags & EXPR_NODE_PRE_INCR) {
|
if(pNode->iFlags & EXPR_NODE_PRE_INCR) {
|
||||||
/* Pre-increment/decrement operator [i.e: ++$i,--$j ] */
|
/* Pre-increment/decrement operator [i.e: ++$i,--$j ] */
|
||||||
@@ -5679,6 +6092,10 @@ PH7_PRIVATE sxi32 PH7_CompileIncludedChunk(ph7_vm *pVm, SyString *pScript, ph7_v
|
|||||||
SySetRelease(&pGen->aGoto);
|
SySetRelease(&pGen->aGoto);
|
||||||
SyHashRelease(&pGen->hLiteral);
|
SyHashRelease(&pGen->hLiteral);
|
||||||
SyHashRelease(&pGen->hVar);
|
SyHashRelease(&pGen->hVar);
|
||||||
|
SyHashRelease(&pGen->hDeclType);
|
||||||
|
/* Restore the enclosing episode in lockstep: its hDeclType table and the
|
||||||
|
* nBlockSerial counter that owns those entries come back together, so this
|
||||||
|
* unit's serials can never be read against another unit's entries. */
|
||||||
pVm->sCodeGen = sSaved;
|
pVm->sCodeGen = sSaved;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -5701,6 +6118,7 @@ PH7_PRIVATE sxi32 PH7_InitCodeGenerator(
|
|||||||
SySetInit(&pGen->aGoto, &pVm->sAllocator, sizeof(JumpFixup));
|
SySetInit(&pGen->aGoto, &pVm->sAllocator, sizeof(JumpFixup));
|
||||||
SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0);
|
SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0);
|
||||||
SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0);
|
SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0);
|
||||||
|
SyHashInit(&pGen->hDeclType, &pVm->sAllocator, 0, 0);
|
||||||
/* Error log buffer */
|
/* Error log buffer */
|
||||||
SyBlobInit(&pGen->sErrBuf, &pVm->sAllocator);
|
SyBlobInit(&pGen->sErrBuf, &pVm->sAllocator);
|
||||||
/* General purpose working buffer */
|
/* General purpose working buffer */
|
||||||
@@ -5726,6 +6144,19 @@ PH7_PRIVATE sxi32 PH7_ResetCodeGenerator(
|
|||||||
SySetReset(&pGen->aGoto);
|
SySetReset(&pGen->aGoto);
|
||||||
SyBlobRelease(&pGen->sErrBuf);
|
SyBlobRelease(&pGen->sErrBuf);
|
||||||
SyBlobRelease(&pGen->sWorker);
|
SyBlobRelease(&pGen->sWorker);
|
||||||
|
/* Declared-type entries are facts of a single compilation unit: each one
|
||||||
|
* carries the serial of a block that is recycled after the reset [VM reset,
|
||||||
|
* eval re-entry, chunk swap], so drop them here. The re-init is not the dead
|
||||||
|
* work it looks like: PH7_ResetCodeGenerator() is also the last generator
|
||||||
|
* call before a compilation on the paths that never run Init first
|
||||||
|
* [ph7_compile_code in engine/api.c, VmEvalChunk in engine/vm.c], and an
|
||||||
|
* invalid table silently switches the checker off [measured: three
|
||||||
|
* tests/errors declared-type fixtures degrade to runtime diagnostics].
|
||||||
|
* hLiteral/hVar need no flush here because their entries are name→index
|
||||||
|
* maps carrying no scope facts; the include path releases them, and this
|
||||||
|
* fresh table, at the end of the unit. */
|
||||||
|
SyHashRelease(&pGen->hDeclType);
|
||||||
|
SyHashInit(&pGen->hDeclType, &pVm->sAllocator, 0, 0);
|
||||||
/* Point to the global scope */
|
/* Point to the global scope */
|
||||||
pBlock = pGen->pCurrent;
|
pBlock = pGen->pCurrent;
|
||||||
while(pBlock->pParent != 0) {
|
while(pBlock->pParent != 0) {
|
||||||
|
|||||||
+930
-305
File diff suppressed because it is too large
Load Diff
+21
-2
@@ -61,6 +61,15 @@ PH7_PRIVATE void SyZero(void *pSrc, sxu32 nSize) {
|
|||||||
zSrc++;
|
zSrc++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Byte comparison contract: SyMemcmp() returns the RAW delta of the first
|
||||||
|
* differing byte pair [unsigned bytes], in [-255,255], or 0 when the whole
|
||||||
|
* region is equal. It is NOT a 3-way ordering result: a delta such as
|
||||||
|
* 'a' - '{' = -26 collides with the engine error sentinels [SXERR_NOMATCH
|
||||||
|
* is -26, SXERR_ABORT is -10, ...]. Callers that need an ordering must
|
||||||
|
* reduce the delta to its sign [-1, 0, +1] before exposing it; see
|
||||||
|
* MemObjCmpString() in memobj.c and HashmapKeyBlobCmp() in hashmap.c.
|
||||||
|
*/
|
||||||
PH7_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize) {
|
PH7_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize) {
|
||||||
sxi32 rc;
|
sxi32 rc;
|
||||||
if(nSize <= 0) {
|
if(nSize <= 0) {
|
||||||
@@ -712,6 +721,16 @@ PH7_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest) {
|
|||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Blob ordering as implemented here: LENGTH BEFORE CONTENT. When the two
|
||||||
|
* lengths differ the shorter blob sorts first whatever the bytes hold (so
|
||||||
|
* "b" < "aa" is TRUE); the memcmp() pass only breaks equal-length ties.
|
||||||
|
* This is NOT a lexicographic 3-way contract — do not document or reuse it
|
||||||
|
* as one. Both verdicts are reduced to their sign [-1, 0, +1]: the raw
|
||||||
|
* SyMemcmp() delta can be any value in [-255, +255] and a delta such as
|
||||||
|
* -26 would collide with SXERR_NOMATCH, so this helper never hands out a
|
||||||
|
* raw delta.
|
||||||
|
*/
|
||||||
PH7_PRIVATE sxi32 SyBlobCmp(SyBlob *pLeft, SyBlob *pRight) {
|
PH7_PRIVATE sxi32 SyBlobCmp(SyBlob *pLeft, SyBlob *pRight) {
|
||||||
sxi32 rc;
|
sxi32 rc;
|
||||||
if(pLeft == 0 || pRight == 0) {
|
if(pLeft == 0 || pRight == 0) {
|
||||||
@@ -719,14 +738,14 @@ PH7_PRIVATE sxi32 SyBlobCmp(SyBlob *pLeft, SyBlob *pRight) {
|
|||||||
}
|
}
|
||||||
if(pLeft->nByte != pRight->nByte) {
|
if(pLeft->nByte != pRight->nByte) {
|
||||||
/* Length differ */
|
/* Length differ */
|
||||||
return pLeft->nByte - pRight->nByte;
|
return pLeft->nByte < pRight->nByte ? -1 : 1;
|
||||||
}
|
}
|
||||||
if(pLeft->nByte == 0) {
|
if(pLeft->nByte == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* Perform a standard memcmp() operation */
|
/* Perform a standard memcmp() operation */
|
||||||
rc = SyMemcmp(pLeft->pBlob, pRight->pBlob, pLeft->nByte);
|
rc = SyMemcmp(pLeft->pBlob, pRight->pBlob, pLeft->nByte);
|
||||||
return rc;
|
return rc < 0 ? -1 : (rc > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
PH7_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) {
|
PH7_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) {
|
||||||
pBlob->nByte = 0;
|
pBlob->nByte = 0;
|
||||||
|
|||||||
+417
-165
@@ -1107,148 +1107,267 @@ PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj) {
|
|||||||
return SXRET_OK;
|
return SXRET_OK;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Compare two ph7_values.
|
* Typed comparison contract [AER#26]:
|
||||||
* Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2
|
* Every ph7_value belongs to exactly one comparison category:
|
||||||
* or < 0 if pObj2 is greater than pObj1.
|
* NUMBER : int, float, char, bool and the raw NULL value. All comparisons
|
||||||
* Type comparison table taken from the PHP language reference manual.
|
* inside this domain are numeric with the values bool FALSE=0/
|
||||||
* Comparisons of $x with PHP functions Expression
|
* TRUE=1, char its integer value and raw NULL 0 [the NULL-to-0
|
||||||
* gettype() empty() is_null() isset() boolean : if($x)
|
* normalization exception applies to raw MEMOBJ_NULL only].
|
||||||
* $x = ""; string TRUE FALSE TRUE FALSE
|
* STRING : byte strings compared byte-wise, never interpreted numerically.
|
||||||
* $x = null NULL TRUE TRUE FALSE FALSE
|
* VOID : the empty void value. Void is incomparable: it forms no pair
|
||||||
* var $x; NULL TRUE TRUE FALSE FALSE
|
* with any value, not even another void. Every explicit operator
|
||||||
* $x is undefined NULL TRUE TRUE FALSE FALSE
|
* on a void operand raises the incomparable-type diagnostic,
|
||||||
* $x = array(); array TRUE FALSE TRUE FALSE
|
* the search paths skip void pairs [no match, never an error],
|
||||||
* $x = false; boolean TRUE FALSE TRUE FALSE
|
* switch matching skips void, and the default-mode sort pre-scan
|
||||||
* $x = true; boolean FALSE FALSE TRUE TRUE
|
* rejects void. Emptiness [PH7_MemObjIsNull(), ??, !, if()] is
|
||||||
* $x = 1; integer FALSE FALSE TRUE TRUE
|
* unchanged: void stays empty.
|
||||||
* $x = 42; integer FALSE FALSE TRUE TRUE
|
* ARRAY : hashmaps compared for value equality only.
|
||||||
* $x = 0; integer TRUE FALSE TRUE FALSE
|
* OBJECT : class instances compared for identity (including empty references).
|
||||||
* $x = -1; integer FALSE FALSE TRUE TRUE
|
* RESOURCE/CALLBACK: equality only, no ordering. Resources compare their
|
||||||
* $x = "1"; string FALSE FALSE TRUE TRUE
|
* identity [handle pointer]; callbacks additionally compare
|
||||||
* $x = "0"; string TRUE FALSE TRUE FALSE
|
* their name/representation bytes, so two callbacks are equal
|
||||||
* $x = "-1"; string FALSE FALSE TRUE TRUE
|
* only when their representation blobs and identity match.
|
||||||
* $x = "php"; string FALSE FALSE TRUE TRUE
|
* Comparisons between values of different categories are runtime errors,
|
||||||
* $x = "true"; string FALSE FALSE TRUE TRUE
|
* except on the search paths which pre-check comparability.
|
||||||
* $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
|
|
||||||
*/
|
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest) {
|
|
||||||
sxi32 iComb;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* 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
|
* NaN handling: a NaN operand is unordered and yields SXERR_NOMATCH. The
|
||||||
* strings, then each string is converted to a number and the comparison
|
* callers map that sentinel to FALSE for == and every ordered operator and
|
||||||
* performed numerically.
|
* 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.
|
||||||
*/
|
*/
|
||||||
if(PH7_MemObjIsNumeric(pObj1)) {
|
#define CMP_INT_IS_SMALLER (-1)
|
||||||
/* Perform a numeric comparison */
|
#define CMP_INT_IS_GREATER 1
|
||||||
goto Numeric;
|
#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(PH7_MemObjIsNumeric(pObj2)) {
|
if(d >= CMP_TWO_P63) {
|
||||||
/* Perform a numeric comparison */
|
/* Every int64 is smaller than d */
|
||||||
goto Numeric;
|
return CMP_INT_IS_SMALLER;
|
||||||
}
|
}
|
||||||
|
if(d < -CMP_TWO_P63) {
|
||||||
|
/* Every int64 is greater than d */
|
||||||
|
return CMP_INT_IS_GREATER;
|
||||||
}
|
}
|
||||||
/* Perform a strict string comparison.*/
|
/* d is in [-2^63, 2^63): this conversion is defined. */
|
||||||
if((pObj1->nType & MEMOBJ_STRING) == 0) {
|
iIntPart = (sxi64)d;
|
||||||
PH7_MemObjToString(pObj1);
|
if(iVal < iIntPart) {
|
||||||
|
return CMP_INT_IS_SMALLER;
|
||||||
}
|
}
|
||||||
if((pObj2->nType & MEMOBJ_STRING) == 0) {
|
if(iVal > iIntPart) {
|
||||||
PH7_MemObjToString(pObj2);
|
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(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob));
|
||||||
SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob));
|
SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob));
|
||||||
/*
|
/*
|
||||||
@@ -1260,46 +1379,179 @@ PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict,
|
|||||||
if(s1.nByte != s2.nByte) {
|
if(s1.nByte != s2.nByte) {
|
||||||
rc = s1.nByte < s2.nByte ? -1 : 1;
|
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;
|
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) {
|
* Non-mutating comparison core implementing the typed comparison contract.
|
||||||
PH7_MemObjToNumeric(pObj1);
|
* 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((pObj2->nType & (MEMOBJ_INT | MEMOBJ_REAL)) == 0) {
|
|
||||||
PH7_MemObjToNumeric(pObj2);
|
|
||||||
}
|
}
|
||||||
if((pObj1->nType & pObj2->nType & MEMOBJ_INT) == 0) {
|
if(!MemObjCmpCategoriesCompatible(pObj1, pObj2)) {
|
||||||
ph7_real r1, r2;
|
if(bThrow) {
|
||||||
/* Compare as reals */
|
MemObjCmpThrow(pObj1, pObj2, zOp);
|
||||||
if((pObj1->nType & MEMOBJ_REAL) == 0) {
|
|
||||||
PH7_MemObjToReal(pObj1);
|
|
||||||
}
|
}
|
||||||
r1 = pObj1->x.rVal;
|
return SXERR_NOMATCH;
|
||||||
if((pObj2->nType & MEMOBJ_REAL) == 0) {
|
|
||||||
PH7_MemObjToReal(pObj2);
|
|
||||||
}
|
}
|
||||||
r2 = pObj2->x.rVal;
|
/* Work on copies: never mutate the caller's values */
|
||||||
if(r1 > r2) {
|
PH7_MemObjInit(pObj1->pVm, &s1);
|
||||||
return 1;
|
PH7_MemObjInit(pObj1->pVm, &s2);
|
||||||
} else if(r1 < r2) {
|
PH7_MemObjLoad(pObj1, &s1);
|
||||||
return -1;
|
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;
|
||||||
}
|
}
|
||||||
return 0;
|
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 {
|
} else {
|
||||||
/* Integer comparison */
|
rc = 0;
|
||||||
if(pObj1->x.iVal > pObj2->x.iVal) {
|
|
||||||
return 1;
|
|
||||||
} else if(pObj1->x.iVal < pObj2->x.iVal) {
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
return 0;
|
break;
|
||||||
}
|
}
|
||||||
}
|
PH7_MemObjRelease(&s1);
|
||||||
/* NOT REACHED */
|
PH7_MemObjRelease(&s2);
|
||||||
return 0;
|
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.
|
* Perform an addition operation of two ph7_values.
|
||||||
|
|||||||
+9
-135
@@ -1193,143 +1193,17 @@ PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Compare two class instances [i.e: Objects in the PHP jargon]
|
* Compare two class instances [i.e: Objects in the PHP jargon].
|
||||||
* Note on objects comparison:
|
* Under the typed comparison contract object comparison is IDENTITY-ONLY:
|
||||||
* According to the PHP langauge reference manual
|
* two values are equal if and only if they reference the same instance
|
||||||
* When using the comparison operator (==), object variables are compared in a simple manner
|
* [or both are empty references, i.e: x.pOther == 0]. There is no
|
||||||
* namely: Two object instances are equal if they have the same attributes and values, and are
|
* attribute comparison and no ordering; bStrict yields the same result.
|
||||||
* instances of the same class.
|
|
||||||
* On the other hand, when using the identity operator (===), object variables are identical
|
|
||||||
* if and only if they refer to the same instance of the same class.
|
|
||||||
* An example will clarify these rules.
|
|
||||||
* Example #1 Example of object comparison
|
|
||||||
* <?php
|
|
||||||
* function bool2str($bool)
|
|
||||||
* {
|
|
||||||
* if ($bool === false) {
|
|
||||||
* return 'FALSE';
|
|
||||||
* } else {
|
|
||||||
* return 'TRUE';
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* function compareObjects(&$o1, &$o2)
|
|
||||||
* {
|
|
||||||
* echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
|
|
||||||
* echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
|
|
||||||
* echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
|
|
||||||
* echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
|
|
||||||
* }
|
|
||||||
* class Flag
|
|
||||||
* {
|
|
||||||
* public $flag;
|
|
||||||
*
|
|
||||||
* function Flag($flag = true) {
|
|
||||||
* $this->flag = $flag;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* class OtherFlag
|
|
||||||
* {
|
|
||||||
* public $flag;
|
|
||||||
*
|
|
||||||
* function OtherFlag($flag = true) {
|
|
||||||
* $this->flag = $flag;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* $o = new Flag();
|
|
||||||
* $p = new Flag();
|
|
||||||
* $q = $o;
|
|
||||||
* $r = new OtherFlag();
|
|
||||||
*
|
|
||||||
* echo "Two instances of the same class\n";
|
|
||||||
* compareObjects($o, $p);
|
|
||||||
* echo "\nTwo references to the same instance\n";
|
|
||||||
* compareObjects($o, $q);
|
|
||||||
* echo "\nInstances of two different classes\n";
|
|
||||||
* compareObjects($o, $r);
|
|
||||||
* ?>
|
|
||||||
* The above example will output:
|
|
||||||
* Two instances of the same class
|
|
||||||
* o1 == o2 : TRUE
|
|
||||||
* o1 != o2 : FALSE
|
|
||||||
* o1 === o2 : FALSE
|
|
||||||
* o1 !== o2 : TRUE
|
|
||||||
* Two references to the same instance
|
|
||||||
* o1 == o2 : TRUE
|
|
||||||
* o1 != o2 : FALSE
|
|
||||||
* o1 === o2 : TRUE
|
|
||||||
* o1 !== o2 : FALSE
|
|
||||||
* Instances of two different classes
|
|
||||||
* o1 == o2 : FALSE
|
|
||||||
* o1 != o2 : TRUE
|
|
||||||
* o1 === o2 : FALSE
|
|
||||||
* o1 !== o2 : TRUE
|
|
||||||
*
|
|
||||||
* This function return 0 if the objects are equals according to the comprison rules defined above.
|
|
||||||
* Any other return values indicates difference.
|
|
||||||
*/
|
*/
|
||||||
PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft, ph7_class_instance *pRight, int bStrict, int iNest) {
|
PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft, ph7_class_instance *pRight, int bStrict, int iNest) {
|
||||||
SyHashEntry *pEntry, *pEntry2;
|
SXUNUSED(bStrict); /* cc warning */
|
||||||
ph7_value sV1, sV2;
|
SXUNUSED(iNest); /* cc warning */
|
||||||
sxi32 rc;
|
/* Identity comparison */
|
||||||
if(iNest > 31) {
|
return pLeft == pRight ? 0 : 1;
|
||||||
/* Nesting limit reached */
|
|
||||||
PH7_VmThrowError(pLeft->pVm, PH7_CTX_ERR, "Nesting limit reached, probably infinite recursion");
|
|
||||||
}
|
|
||||||
/* Comparison is performed only if the objects are instance of the same class */
|
|
||||||
if(pLeft->pClass != pRight->pClass) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(bStrict) {
|
|
||||||
/*
|
|
||||||
* According to the PHP language reference manual:
|
|
||||||
* when using the identity operator (===), object variables
|
|
||||||
* are identical if and only if they refer to the same instance
|
|
||||||
* of the same class.
|
|
||||||
*/
|
|
||||||
return pLeft != pRight;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Attribute comparison.
|
|
||||||
* According to the PHP reference manual:
|
|
||||||
* When using the comparison operator (==), object variables are compared
|
|
||||||
* in a simple manner, namely: Two object instances are equal if they have
|
|
||||||
* the same attributes and values, and are instances of the same class.
|
|
||||||
*/
|
|
||||||
if(pLeft == pRight) {
|
|
||||||
/* Same instance,don't bother processing,object are equals */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
SyHashResetLoopCursor(&pLeft->hAttr);
|
|
||||||
SyHashResetLoopCursor(&pRight->hAttr);
|
|
||||||
PH7_MemObjInit(pLeft->pVm, &sV1);
|
|
||||||
PH7_MemObjInit(pLeft->pVm, &sV2);
|
|
||||||
sV1.nIdx = sV2.nIdx = SXU32_HIGH;
|
|
||||||
while((pEntry = SyHashGetNextEntry(&pLeft->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pRight->hAttr)) != 0) {
|
|
||||||
VmClassAttr *p1 = (VmClassAttr *)pEntry->pUserData;
|
|
||||||
VmClassAttr *p2 = (VmClassAttr *)pEntry2->pUserData;
|
|
||||||
/* Compare only non-static attribute */
|
|
||||||
if((p1->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) {
|
|
||||||
ph7_value *pL, *pR;
|
|
||||||
pL = ExtractClassAttrValue(pLeft->pVm, p1);
|
|
||||||
pR = ExtractClassAttrValue(pRight->pVm, p2);
|
|
||||||
if(pL && pR) {
|
|
||||||
PH7_MemObjLoad(pL, &sV1);
|
|
||||||
PH7_MemObjLoad(pR, &sV2);
|
|
||||||
/* Compare the two values now */
|
|
||||||
rc = PH7_MemObjCmp(&sV1, &sV2, bStrict, iNest + 1);
|
|
||||||
PH7_MemObjRelease(&sV1);
|
|
||||||
PH7_MemObjRelease(&sV2);
|
|
||||||
if(rc != 0) {
|
|
||||||
/* Not equals */
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Object are equals */
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Dump a class instance and the store the dump in the BLOB given
|
* Dump a class instance and the store the dump in the BLOB given
|
||||||
|
|||||||
+54
-9
@@ -3283,8 +3283,16 @@ Resume:
|
|||||||
iRes = VmInstanceOf(pVm, pThis->pClass, pClass);
|
iRes = VmInstanceOf(pVm, pThis->pClass, pClass);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0);
|
/* Value comparison fallback: follows the typed comparison
|
||||||
|
* contract. Incomparable categories yield FALSE [no match]
|
||||||
|
* rather than a fatal error, since is is a type test with
|
||||||
|
* a value-comparison fallback. */
|
||||||
|
if(PH7_MemObjComparable(pNos, pTos)) {
|
||||||
|
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0, 0);
|
||||||
iRes = rc == 0;
|
iRes = rc == 0;
|
||||||
|
} else {
|
||||||
|
iRes = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Push result */
|
/* Push result */
|
||||||
@@ -4823,8 +4831,11 @@ Resume:
|
|||||||
if(pNos < pStack) {
|
if(pNos < pStack) {
|
||||||
goto Abort;
|
goto Abort;
|
||||||
}
|
}
|
||||||
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0);
|
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0, pInstr->iOp == PH7_OP_EQ ? "==" : "!=");
|
||||||
if(pInstr->iOp == PH7_OP_EQ) {
|
if(rc == SXERR_NOMATCH) {
|
||||||
|
/* NaN operand: == is FALSE, != is TRUE */
|
||||||
|
rc = pInstr->iOp != PH7_OP_EQ;
|
||||||
|
} else if(pInstr->iOp == PH7_OP_EQ) {
|
||||||
rc = rc == 0;
|
rc = rc == 0;
|
||||||
} else {
|
} else {
|
||||||
rc = rc != 0;
|
rc = rc != 0;
|
||||||
@@ -4870,8 +4881,19 @@ Resume:
|
|||||||
if(pNos < pStack) {
|
if(pNos < pStack) {
|
||||||
goto Abort;
|
goto Abort;
|
||||||
}
|
}
|
||||||
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0);
|
if(!PH7_MemObjOrderable(pNos, pTos)) {
|
||||||
if(pInstr->iOp == PH7_OP_LE) {
|
/* Only two numbers or two strings define an order.
|
||||||
|
* The diagnostic is terminal: abort the execution
|
||||||
|
* instead of falling through into a second,
|
||||||
|
* contradictory comparison. */
|
||||||
|
PH7_VmThrowNoOrder(pNos, pTos, pInstr->iOp == PH7_OP_LE ? "<=" : "<");
|
||||||
|
goto Abort;
|
||||||
|
}
|
||||||
|
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0, pInstr->iOp == PH7_OP_LE ? "<=" : "<");
|
||||||
|
if(rc == SXERR_NOMATCH) {
|
||||||
|
/* NaN operand: ordering is always FALSE */
|
||||||
|
rc = 0;
|
||||||
|
} else if(pInstr->iOp == PH7_OP_LE) {
|
||||||
rc = rc < 1;
|
rc = rc < 1;
|
||||||
} else {
|
} else {
|
||||||
rc = rc < 0;
|
rc = rc < 0;
|
||||||
@@ -4917,8 +4939,19 @@ Resume:
|
|||||||
if(pNos < pStack) {
|
if(pNos < pStack) {
|
||||||
goto Abort;
|
goto Abort;
|
||||||
}
|
}
|
||||||
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0);
|
if(!PH7_MemObjOrderable(pNos, pTos)) {
|
||||||
if(pInstr->iOp == PH7_OP_GE) {
|
/* Only two numbers or two strings define an order.
|
||||||
|
* The diagnostic is terminal: abort the execution
|
||||||
|
* instead of falling through into a second,
|
||||||
|
* contradictory comparison. */
|
||||||
|
PH7_VmThrowNoOrder(pNos, pTos, pInstr->iOp == PH7_OP_GE ? ">=" : ">");
|
||||||
|
goto Abort;
|
||||||
|
}
|
||||||
|
rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0, pInstr->iOp == PH7_OP_GE ? ">=" : ">");
|
||||||
|
if(rc == SXERR_NOMATCH) {
|
||||||
|
/* NaN operand: ordering is always FALSE */
|
||||||
|
rc = 0;
|
||||||
|
} else if(pInstr->iOp == PH7_OP_GE) {
|
||||||
rc = rc >= 0;
|
rc = rc >= 0;
|
||||||
} else {
|
} else {
|
||||||
rc = rc > 0;
|
rc = rc > 0;
|
||||||
@@ -5750,8 +5783,20 @@ Resume:
|
|||||||
nTarget = pc + 1;
|
nTarget = pc + 1;
|
||||||
goto Unwind;
|
goto Unwind;
|
||||||
}
|
}
|
||||||
/* Compare the two expression */
|
/* Compare the two expressions.
|
||||||
rc = PH7_MemObjCmp(&sValue, &sCaseValue, FALSE, 0);
|
* Matching follows the typed comparison contract: only
|
||||||
|
* values of the same comparison category can match;
|
||||||
|
* incomparable case labels are skipped [no match]. */
|
||||||
|
if(PH7_MemObjComparable(&sValue, &sCaseValue)) {
|
||||||
|
rc = PH7_MemObjCmp(&sValue, &sCaseValue, FALSE, 0, 0);
|
||||||
|
if(rc == SXERR_NOMATCH) {
|
||||||
|
/* NaN operand never matches a case label */
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Different categories: fall through to the next case */
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
PH7_MemObjRelease(&sValue);
|
PH7_MemObjRelease(&sValue);
|
||||||
PH7_MemObjRelease(&sCaseValue);
|
PH7_MemObjRelease(&sCaseValue);
|
||||||
if(rc == 0) {
|
if(rc == 0) {
|
||||||
|
|||||||
+9
-1
@@ -790,6 +790,7 @@ struct ph7_hashmap {
|
|||||||
sxu32(*xIntHash)(sxi64); /* Hash function for int_keys */
|
sxu32(*xIntHash)(sxi64); /* Hash function for int_keys */
|
||||||
sxu32(*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */
|
sxu32(*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */
|
||||||
sxi64 iNextIdx; /* Next available automatically assigned index */
|
sxi64 iNextIdx; /* Next available automatically assigned index */
|
||||||
|
sxi32 bIdxExhausted; /* TRUE once the int64 index space is exhausted (no index after SXI64_HIGH) */
|
||||||
sxi32 iRef; /* Reference count */
|
sxi32 iRef; /* Reference count */
|
||||||
};
|
};
|
||||||
/* An instance of the following structure is the context
|
/* An instance of the following structure is the context
|
||||||
@@ -866,6 +867,7 @@ struct GenBlock {
|
|||||||
ph7_gen_state *pGen; /* State of the code generator */
|
ph7_gen_state *pGen; /* State of the code generator */
|
||||||
GenBlock *pParent; /* Upper block or NULL if global */
|
GenBlock *pParent; /* Upper block or NULL if global */
|
||||||
sxu32 nFirstInstr; /* First instruction to execute */
|
sxu32 nFirstInstr; /* First instruction to execute */
|
||||||
|
sxu32 nSerial; /* Unique id of this instance [ph7_gen_state::nBlockSerial] */
|
||||||
sxi32 iFlags; /* Block control flags (see below) */
|
sxi32 iFlags; /* Block control flags (see below) */
|
||||||
SySet aJumpFix; /* Jump fixup (JumpFixup instance) */
|
SySet aJumpFix; /* Jump fixup (JumpFixup instance) */
|
||||||
void *pUserData; /* Upper layer private data */
|
void *pUserData; /* Upper layer private data */
|
||||||
@@ -889,6 +891,8 @@ struct ph7_gen_state {
|
|||||||
SyHash hLiteral; /* Constant string Literals table */
|
SyHash hLiteral; /* Constant string Literals table */
|
||||||
SyHash hNumLiteral; /* Numeric literals table */
|
SyHash hNumLiteral; /* Numeric literals table */
|
||||||
SyHash hVar; /* Collected variable hashtable */
|
SyHash hVar; /* Collected variable hashtable */
|
||||||
|
SyHash hDeclType; /* Declared-type categories of the variables currently visible [GenDeclType instance]. An entry is dropped as soon as the compiler emits an unset() on that name [its storage cell is destroyed] */
|
||||||
|
sxu32 nBlockSerial; /* Monotonic counter feeding GenBlock::nSerial. Unique inside one code-generator episode: PH7_InitCodeGenerator() zeroes it with the hDeclType table it empties, and PH7_CompileIncludedChunk() saves/restores the enclosing episode's counter in lockstep with that table, so a pooled block address is never mistaken for another live entry's instance */
|
||||||
GenBlock *pCurrent; /* Current processed block */
|
GenBlock *pCurrent; /* Current processed block */
|
||||||
GenBlock sGlobal; /* Global block */
|
GenBlock sGlobal; /* Global block */
|
||||||
ProcConsumer xErr; /* Error consumer callback */
|
ProcConsumer xErr; /* Error consumer callback */
|
||||||
@@ -1676,7 +1680,11 @@ static inline sxi64 PH7_Int64ShiftRight(sxi64 a, sxi32 b) {
|
|||||||
PH7_PRIVATE sxi32 PH7_MemObjDump(SyBlob *pOut, ph7_value *pObj, int ShowType, int nTab, int nDepth);
|
PH7_PRIVATE sxi32 PH7_MemObjDump(SyBlob *pOut, ph7_value *pObj, int ShowType, int nTab, int nDepth);
|
||||||
PH7_PRIVATE const char *PH7_MemObjTypeDump(ph7_value *pVal);
|
PH7_PRIVATE const char *PH7_MemObjTypeDump(ph7_value *pVal);
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1, ph7_value *pObj2, int bAddStore);
|
PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1, ph7_value *pObj2, int bAddStore);
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest);
|
PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest, const char *zOp);
|
||||||
|
PH7_PRIVATE sxi32 PH7_MemObjCmpSafe(ph7_value *pObj1, ph7_value *pObj2, int bStrict, int iNest, const char *zOp);
|
||||||
|
PH7_PRIVATE sxi32 PH7_MemObjComparable(ph7_value *pObj1, ph7_value *pObj2);
|
||||||
|
PH7_PRIVATE sxi32 PH7_MemObjOrderable(ph7_value *pObj1, ph7_value *pObj2);
|
||||||
|
PH7_PRIVATE void PH7_VmThrowNoOrder(ph7_value *pObj1, ph7_value *pObj2, const char *zOp);
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjInitFromString(ph7_vm *pVm, ph7_value *pObj, const SyString *pVal);
|
PH7_PRIVATE sxi32 PH7_MemObjInitFromString(ph7_vm *pVm, ph7_value *pObj, const SyString *pVal);
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjInitFromArray(ph7_vm *pVm, ph7_value *pObj, ph7_hashmap *pArray);
|
PH7_PRIVATE sxi32 PH7_MemObjInitFromArray(ph7_vm *pVm, ph7_value *pObj, ph7_hashmap *pArray);
|
||||||
PH7_PRIVATE sxi32 PH7_MemObjInitFromReal(ph7_vm *pVm, ph7_value *pObj, ph7_real rVal);
|
PH7_PRIVATE sxi32 PH7_MemObjInitFromReal(ph7_vm *pVm, ph7_value *pObj, ph7_real rVal);
|
||||||
|
|||||||
@@ -52,14 +52,15 @@ class Base32 {
|
|||||||
$input = str_replace('=','', $input);
|
$input = str_replace('=','', $input);
|
||||||
char[] $aInput = str_split($input);
|
char[] $aInput = str_split($input);
|
||||||
string $binaryString = '';
|
string $binaryString = '';
|
||||||
|
string[] $sInput = str_split($input);
|
||||||
for($i = 0; $i < sizeof($aInput); $i = $i + 8) {
|
for($i = 0; $i < sizeof($aInput); $i = $i + 8) {
|
||||||
string $x = '';
|
string $x = '';
|
||||||
if(!in_array($input[$i], Base32::$map))
|
if(!in_array($aInput[$i], Base32::$map))
|
||||||
return '';
|
return '';
|
||||||
for(int $j = 0; $j < 8; $j++) {
|
for(int $j = 0; $j < 8; $j++) {
|
||||||
if($i + $j < strlen($input)) {
|
if($i + $j < strlen($input)) {
|
||||||
if(array_key_exists($input[$i + $j], Base32::$flippedMap)) {
|
if(array_key_exists($sInput[$i + $j], Base32::$flippedMap)) {
|
||||||
$x += str_pad(base_convert(Base32::$flippedMap[$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
$x += str_pad(base_convert(Base32::$flippedMap[$sInput[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ class StringTokenizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool hasMoreTokens() {
|
public bool hasMoreTokens() {
|
||||||
return ($this->token != NULL);
|
return ($this->token != '');
|
||||||
}
|
}
|
||||||
|
|
||||||
public string nextToken() {
|
public string nextToken() {
|
||||||
|
|||||||
Reference in New Issue
Block a user