From 4d8d92092e7c64cc4aec20882a68ed1db336c201 Mon Sep 17 00:00:00 2001 From: belliash Date: Tue, 30 Apr 2019 23:38:59 +0200 Subject: [PATCH] Refactor foreach() loop. In AerScript, the foreach() loop is syntatically more similiar to C#, than PHP. However the optional '$key => $value' construct is still available, because arrays in AerScript are still a hashmaps. --- engine/compiler.c | 131 +++++++++++++------------------ engine/lexer.c | 2 +- engine/vm.c | 146 +++++++---------------------------- include/ph7int.h | 9 +-- tests/arab_to_roman.aer | 2 +- tests/unicode_characters.aer | 2 +- 6 files changed, 89 insertions(+), 203 deletions(-) diff --git a/engine/compiler.c b/engine/compiler.c index 3b84bc4..3fb00c7 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -1789,12 +1789,12 @@ static sxi32 GenStateForEachNodeValidator(ph7_gen_state *pGen, ph7_expr_node *pR /* * Compile the 'foreach' statement. * The foreach construct simply gives an easy way to iterate over arrays. foreach works - * only on arrays (and objects), and will issue an error when you try to use it on a variable + * only on arrays, and will issue an error when you try to use it on a variable * with a different data type or an uninitialized variable. There are two syntaxes; the second * is a minor but useful extension of the first: - * foreach (array_expression as $value) + * foreach ($value in array_expression) * statement - * foreach (array_expression as $key => $value) + * foreach ($key => $value in array_expression) * statement * The first form loops over the array given by array_expression. On each loop, the value * of the current element is assigned to $value and the internal array pointer is advanced @@ -1804,12 +1804,6 @@ static sxi32 GenStateForEachNodeValidator(ph7_gen_state *pGen, ph7_expr_node *pR * Note: * When foreach first starts executing, the internal array pointer is automatically reset to the * first element of the array. This means that you do not need to call reset() before a foreach loop. - * Note: - * Unless the array is referenced, foreach operates on a copy of the specified array and not the array - * itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during - * or after the foreach without resetting it. - * You can easily modify array's elements by preceding $value with &. This will assign reference instead - * of copying the value. */ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { SyToken *pCur, *pTmp, *pEnd = 0; @@ -1839,40 +1833,6 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { /* Empty expression */ PH7_GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); } - /* Compile the array expression */ - pCur = pGen->pIn; - while(pCur < pEnd) { - if(pCur->nType & PH7_TK_KEYWORD) { - sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); - if(nKeywrd == PH7_KEYWORD_AS) { - /* Break with the first 'as' found */ - break; - } - } - /* Advance the stream cursor */ - pCur++; - } - if(pCur <= pGen->pIn) { - PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, - "foreach: Missing array/object expression"); - } - /* Swap token streams */ - pTmp = pGen->pEnd; - pGen->pEnd = pCur; - rc = PH7_CompileExpr(&(*pGen), 0, 0); - if(rc == SXERR_ABORT) { - /* Expression handler request an operation abort [i.e: Out-of-memory] */ - return SXERR_ABORT; - } - /* Update token stream */ - if(pGen->pIn < pCur) { - PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); - } - pCur++; /* Jump the 'as' keyword */ - pGen->pIn = pCur; - if(pGen->pIn >= pEnd) { - PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); - } /* Create the foreach context */ pInfo = (ph7_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_foreach_info)); if(pInfo == 0) { @@ -1882,43 +1842,55 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { SyZero(pInfo, sizeof(ph7_foreach_info)); /* Initialize structure fields */ SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(ph7_foreach_step *)); - /* Check if we have a key field */ + + pCur = pGen->pIn; while(pCur < pEnd && (pCur->nType & PH7_TK_ARRAY_OP) == 0) { pCur++; } if(pCur < pEnd) { - /* Compile the expression holding the key name */ - if(pGen->pIn >= pCur) { + if(pCur <= pGen->pIn) { PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); - } else { - pGen->pEnd = pCur; - rc = PH7_CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); - if(rc == SXERR_ABORT) { - /* Don't worry about freeing memory, everything will be released shortly */ - return SXERR_ABORT; - } - pInstr = PH7_VmPopInstr(pGen->pVm); - if(pInstr->p3) { - /* Record key name */ - SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); - } - pInfo->iFlags |= PH7_4EACH_STEP_KEY; } - pGen->pIn = &pCur[1]; /* Jump the arrow */ + pTmp = pGen->pEnd; + pGen->pEnd = pCur; + rc = PH7_CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); + if(rc == SXERR_ABORT) { + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + pInstr = PH7_VmPopInstr(pGen->pVm); + if(pInstr->p3) { + /* Record key name */ + SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + } + pCur++; /* Jump the array operator */ + pGen->pIn = pCur; + pGen->pEnd = pTmp; } - pGen->pEnd = pEnd; - if(pGen->pIn >= pEnd) { + pCur = pGen->pIn; + while(pCur < pEnd) { + if(pCur->nType & PH7_TK_KEYWORD) { + sxi32 nKeyword = SX_PTR_TO_INT(pCur->pUserData); + if(nKeyword == PH7_KEYWORD_IN) { + /* Break with the first 'in' found */ + break; + } + } + /* Advance the stream cursor */ + pCur++; + } + if(pCur <= pGen->pIn) { PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); + } else if(pCur->nType == PH7_TK_RPAREN) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Expecting 'in' keyword"); } - if(pGen->pIn->nType & PH7_TK_AMPER /*'&'*/) { - pGen->pIn++; - /* Pass by reference */ - pInfo->iFlags |= PH7_4EACH_STEP_REF; - } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pCur; /* Compile the expression holding the value name */ rc = PH7_CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); if(rc == SXERR_ABORT) { - /* Don't worry about freeing memory, everything will be released shortly */ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } pInstr = PH7_VmPopInstr(pGen->pVm); @@ -1926,6 +1898,21 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { /* Record value name */ SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); } + if(pGen->pIn < pCur) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); + } + pCur++; /* Jump the 'in' keyword */ + pGen->pIn = pCur; + if(pGen->pIn >= pEnd) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing array expression"); + } + pGen->pEnd = pEnd; + /* Compile the expression holding an array */ + rc = PH7_CompileExpr(&(*pGen), 0, 0); + if(rc == SXERR_ABORT) { + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } /* Emit the 'FOREACH_INIT' instruction */ PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ @@ -1956,14 +1943,6 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { PH7_GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; -Synchronize: - /* Synchronize with the first semi-colon ';' so we can avoid - * compiling this erroneous block. - */ - while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI | PH7_TK_OCB)) == 0) { - pGen->pIn++; - } - return SXRET_OK; } /* * Compile the infamous if/elseif/else if/else statements. diff --git a/engine/lexer.c b/engine/lexer.c index f1a5cda..053bd3f 100644 --- a/engine/lexer.c +++ b/engine/lexer.c @@ -556,7 +556,6 @@ static sxu32 KeywordCode(const char *z, int n) { {"string", PH7_KEYWORD_STRING}, {"void", PH7_KEYWORD_VOID}, /* Loops & Controls */ - {"as", PH7_KEYWORD_AS}, {"break", PH7_KEYWORD_BREAK}, {"case", PH7_KEYWORD_CASE}, {"continue", PH7_KEYWORD_CONTINUE}, @@ -567,6 +566,7 @@ static sxu32 KeywordCode(const char *z, int n) { {"switch", PH7_KEYWORD_SWITCH}, {"else", PH7_KEYWORD_ELSE}, {"if", PH7_KEYWORD_IF}, + {"in", PH7_KEYWORD_IN}, {"while", PH7_KEYWORD_WHILE}, /* Reserved keywords */ {"eval", PH7_KEYWORD_EVAL}, diff --git a/engine/vm.c b/engine/vm.c index 1b7a5d6..e0ef2cf 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -4049,11 +4049,11 @@ static sxi32 VmByteCodeExec( goto Abort; } #endif - /* Make sure we are dealing with a hashmap aka 'array' or an object */ - if((pTos->iFlags & (MEMOBJ_HASHMAP | MEMOBJ_OBJ)) == 0 || SyStringLength(&pInfo->sValue) < 1) { + /* Make sure we are dealing with an array or an object */ + if((pTos->iFlags & MEMOBJ_HASHMAP) == 0 || SyStringLength(&pInfo->sValue) < 1) { /* Jump out of the loop */ if((pTos->iFlags & MEMOBJ_NULL) == 0) { - PH7_VmThrowError(&(*pVm), PH7_CTX_WARNING, "Invalid argument supplied for the foreach statement, expecting array or class instance"); + PH7_VmThrowError(&(*pVm), PH7_CTX_WARNING, "Invalid argument supplied for the foreach statement, expecting an array"); } pc = pInstr->iP2 - 1; } else { @@ -4065,24 +4065,12 @@ static sxi32 VmByteCodeExec( /* Zero the structure */ SyZero(pStep, sizeof(ph7_foreach_step)); /* Prepare the step */ - pStep->iFlags = pInfo->iFlags; - if(pTos->iFlags & MEMOBJ_HASHMAP) { - ph7_hashmap *pMap = (ph7_hashmap *)pTos->x.pOther; - /* Reset the internal loop cursor */ - PH7_HashmapResetLoopCursor(pMap); - /* Mark the step */ - pStep->iFlags |= PH7_4EACH_STEP_HASHMAP; - pStep->xIter.pMap = pMap; - pMap->iRef++; - } else { - ph7_class_instance *pThis = (ph7_class_instance *)pTos->x.pOther; - /* Reset the loop cursor */ - SyHashResetLoopCursor(&pThis->hAttr); - /* Mark the step */ - pStep->iFlags |= PH7_4EACH_STEP_OBJECT; - pStep->xIter.pThis = pThis; - pThis->iRef++; - } + ph7_hashmap *pMap = (ph7_hashmap *)pTos->x.pOther; + /* Reset the internal loop cursor */ + PH7_HashmapResetLoopCursor(pMap); + /* Mark the step */ + pStep->xIter.pMap = pMap; + pMap->iRef++; } if(SXRET_OK != SySetPut(&pInfo->aStep, (const void *)&pStep)) { PH7_VmMemoryError(&(*pVm)); @@ -4108,104 +4096,30 @@ static sxi32 VmByteCodeExec( /* Safely ignore the exception frame */ pFrame = pFrame->pParent; } - if(pStep->iFlags & PH7_4EACH_STEP_HASHMAP) { - ph7_hashmap *pMap = pStep->xIter.pMap; - ph7_hashmap_node *pNode; - /* Extract the current node value */ - pNode = PH7_HashmapGetNextEntry(pMap); - if(pNode == 0) { - /* No more entry to process */ - pc = pInstr->iP2 - 1; /* Jump to this destination */ - if(pStep->iFlags & PH7_4EACH_STEP_REF) { - /* Break the reference with the last element */ - SyHashDeleteEntry(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue), 0); - } - /* Automatically reset the loop cursor */ - PH7_HashmapResetLoopCursor(pMap); - /* Cleanup the mess left behind */ - SyMemBackendPoolFree(&pVm->sAllocator, pStep); - SySetPop(&pInfo->aStep); - PH7_HashmapUnref(pMap); - } else { - if((pStep->iFlags & PH7_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0) { - ph7_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, FALSE); - if(pKey) { - PH7_HashmapExtractNodeKey(pNode, pKey); - } - } - if(pStep->iFlags & PH7_4EACH_STEP_REF) { - SyHashEntry *pEntry; - /* Pass by reference */ - pEntry = SyHashGet(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue)); - if(pEntry) { - pEntry->pUserData = SX_INT_TO_PTR(pNode->nValIdx); - } else { - SyHashInsert(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue), - SX_INT_TO_PTR(pNode->nValIdx)); - } - } else { - /* Make a copy of the entry value */ - pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, FALSE); - if(pValue) { - PH7_HashmapExtractNodeValue(pNode, pValue, TRUE); - } - } - } + ph7_hashmap *pMap = pStep->xIter.pMap; + ph7_hashmap_node *pNode; + /* Extract the current node value */ + pNode = PH7_HashmapGetNextEntry(pMap); + if(pNode == 0) { + /* No more entry to process */ + pc = pInstr->iP2 - 1; /* Jump to this destination */ + /* Automatically reset the loop cursor */ + PH7_HashmapResetLoopCursor(pMap); + /* Cleanup the mess left behind */ + SyMemBackendPoolFree(&pVm->sAllocator, pStep); + SySetPop(&pInfo->aStep); + PH7_HashmapUnref(pMap); } else { - ph7_class_instance *pThis = pStep->xIter.pThis; - VmClassAttr *pVmAttr = 0; /* Stupid cc -06 warning */ - SyHashEntry *pEntry; - /* Point to the next attribute */ - while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) { - pVmAttr = (VmClassAttr *)pEntry->pUserData; - /* Check access permission */ - if(VmClassMemberAccess(&(*pVm), pThis->pClass, &pVmAttr->pAttr->sName, - pVmAttr->pAttr->iProtection, FALSE)) { - break; /* Access is granted */ + if(SyStringLength(&pInfo->sKey) > 0) { + ph7_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, FALSE); + if(pKey) { + PH7_HashmapExtractNodeKey(pNode, pKey); } } - if(pEntry == 0) { - /* Clean up the mess left behind */ - pc = pInstr->iP2 - 1; /* Jump to this destination */ - if(pStep->iFlags & PH7_4EACH_STEP_REF) { - /* Break the reference with the last element */ - SyHashDeleteEntry(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue), 0); - } - SyMemBackendPoolFree(&pVm->sAllocator, pStep); - SySetPop(&pInfo->aStep); - PH7_ClassInstanceUnref(pThis); - } else { - SyString *pAttrName = &pVmAttr->pAttr->sName; - ph7_value *pAttrValue; - if((pStep->iFlags & PH7_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0) { - /* Fill with the current attribute name */ - ph7_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, FALSE); - if(pKey) { - SyBlobReset(&pKey->sBlob); - SyBlobAppend(&pKey->sBlob, pAttrName->zString, pAttrName->nByte); - MemObjSetType(pKey, MEMOBJ_STRING); - } - } - /* Extract attribute value */ - pAttrValue = PH7_ClassInstanceExtractAttrValue(pThis, pVmAttr); - if(pAttrValue) { - if(pStep->iFlags & PH7_4EACH_STEP_REF) { - /* Pass by reference */ - pEntry = SyHashGet(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue)); - if(pEntry) { - pEntry->pUserData = SX_INT_TO_PTR(pVmAttr->nIdx); - } else { - SyHashInsert(&pFrame->hVar, SyStringData(&pInfo->sValue), SyStringLength(&pInfo->sValue), - SX_INT_TO_PTR(pVmAttr->nIdx)); - } - } else { - /* Make a copy of the attribute value */ - pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, FALSE); - if(pValue) { - PH7_MemObjStore(pAttrValue, pValue); - } - } - } + /* Make a copy of the entry value */ + pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, FALSE); + if(pValue) { + PH7_HashmapExtractNodeValue(pNode, pValue, TRUE); } } break; diff --git a/include/ph7int.h b/include/ph7int.h index 6f6260c..c7aa980 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -789,11 +789,9 @@ struct ph7_hashmap { struct ph7_foreach_info { SyString sKey; /* Key name. Empty otherwise*/ SyString sValue; /* Value name */ - sxi32 iFlags; /* Control flags */ SySet aStep; /* Stack of steps [i.e: ph7_foreach_step instance] */ }; struct ph7_foreach_step { - sxi32 iFlags; /* Control flags (see below) */ /* Iterate on those values */ union { ph7_hashmap *pMap; /* Hashmap [i.e: array in the PHP jargon] iteration @@ -802,11 +800,6 @@ struct ph7_foreach_step { ph7_class_instance *pThis; /* Class instance [i.e: object] iteration */ } xIter; }; -/* Foreach step control flags */ -#define PH7_4EACH_STEP_HASHMAP 0x001 /* Hashmap iteration */ -#define PH7_4EACH_STEP_OBJECT 0x002 /* Object iteration */ -#define PH7_4EACH_STEP_KEY 0x004 /* Make Key available */ -#define PH7_4EACH_STEP_REF 0x008 /* Pass value by reference not copy */ /* * Each PH7 engine is identified by an instance of the following structure. * Please refer to the official documentation for more information @@ -1580,7 +1573,7 @@ enum ph7_expr_id { #define PH7_KEYWORD_TRY 30 /* try */ #define PH7_KEYWORD_DEFAULT 31 /* default */ #define PH7_KEYWORD_CLASS 32 /* class */ -#define PH7_KEYWORD_AS 33 /* as */ +#define PH7_KEYWORD_IN 33 /* in */ #define PH7_KEYWORD_CONTINUE 34 /* continue */ #define PH7_KEYWORD_EXIT 35 /* exit */ #define PH7_KEYWORD_FINALLY 36 /* finally */ diff --git a/tests/arab_to_roman.aer b/tests/arab_to_roman.aer index d197f0f..802a45e 100644 --- a/tests/arab_to_roman.aer +++ b/tests/arab_to_roman.aer @@ -10,7 +10,7 @@ class Program { int $matches; string $roman; int $value; - foreach($lookup as $roman => $value) { + foreach($roman => $value in $lookup) { $matches = (int) ($n / $value); $result += str_repeat($roman, $matches); $n = $n % $value; diff --git a/tests/unicode_characters.aer b/tests/unicode_characters.aer index 1cc905e..5771fca 100644 --- a/tests/unicode_characters.aer +++ b/tests/unicode_characters.aer @@ -38,7 +38,7 @@ class Unicode { if($to_uni) { $str = strtr($str, $cp); } else { - foreach($cp as $c) { + foreach($c in $cp) { $cpp[$c] = array_search($c, $cp); } $str = strtr($str, $cpp);