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
+22 -2
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+21 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+4 -3
View File
@@ -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
View File
@@ -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() {