diff --git a/engine/compiler.c b/engine/compiler.c index b07885f..75d6e1b 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -9,6 +9,30 @@ */ #include "compiler.h" +/* + * Check if the given name refer to a valid label. + * Return SXRET_OK and write a pointer to that label on success. + * Any other return value indicates no such label. + */ +static sxi32 GenStateGetLabel(ph7_gen_state *pGen, JumpFixup *pJump, Label **ppOut) +{ + Label *aLabel; + sxu32 n; + /* Perform a linear scan on the label table */ + aLabel = (Label *)SySetBasePtr(&pGen->aLabel); + for(n = 0; n < SySetUsed(&pGen->aLabel); ++n) { + if(SyStringCmp(&aLabel[n].sName, &pJump->sLabel, SyMemcmp) == 0 && aLabel[n].pFunc == pJump->pFunc) { + /* Jump destination found */ + aLabel[n].bRef = TRUE; + if(ppOut) { + *ppOut = &aLabel[n]; + } + return SXRET_OK; + } + } + /* No such destination */ + return SXERR_NOTFOUND; +} /* * Fetch a block that correspond to the given criteria from the stack of * compiled blocks. @@ -184,6 +208,39 @@ static sxu32 PH7_GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJump /* Total number of fixed jumps */ return nFixed; } +/* + * Fix a 'goto' now the jump destination is resolved. + * The goto statement can be used to jump to another section + * in the program. + * Refer to the routine responsible of compiling the goto + * statement for more information. + */ +static sxi32 GenStateFixGoto(ph7_gen_state *pGen, sxu32 nOfft) +{ + JumpFixup *pJump,*aJumps; + Label *pLabel,*aLabel; + VmInstr *pInstr; + sxi32 rc; + sxu32 n; + /* Point to the goto table */ + aJumps = (JumpFixup *)SySetBasePtr(&pGen->aGoto); + /* Fix */ + for(n = nOfft; n < SySetUsed(&pGen->aGoto); ++n) { + pJump = &aJumps[n]; + /* Extract the target label */ + rc = GenStateGetLabel(&(*pGen), pJump, &pLabel); + if(rc != SXRET_OK) { + /* No such label */ + PH7_GenCompileError(&(*pGen), E_ERROR, pJump->nLine, "Label '%z' was referenced but not defined", &pJump->sLabel); + } + /* Fix the jump now the destination is resolved */ + pInstr = PH7_VmGetInstr(pGen->pVm, pJump->nInstrIdx); + if(pInstr) { + pInstr->iP2 = pLabel->nJumpDest; + } + } + return SXRET_OK; +} /* * Check if a given token value is installed in the literal table. */ @@ -1354,6 +1411,130 @@ static sxi32 PH7_CompileBreak(ph7_gen_state *pGen) { /* Statement successfully compiled */ return SXRET_OK; } +/* + * Compile or record a label. + * A label is a target point that is specified by an identifier followed by a colon. + * Example + * goto LABEL; + * echo 'Foo'; + * LABEL: + * echo 'Bar'; + */ +static sxi32 PH7_CompileLabel(ph7_gen_state *pGen) +{ + GenBlock *pBlock; + Label *aLabel; + Label sLabel; + /* Make sure the label does not occur inside a loop or a try{}catch(); block */ + pBlock = PH7_GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP | GEN_BLOCK_EXCEPTION, 0); + if(pBlock) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, + "Label '%z' inside loop or try/catch block is disallowed", &pGen->pIn->sData); + } else { + SyString *pTarget = &pGen->pIn->sData; + char *zDup; + /* Initialize label fields */ + sLabel.nJumpDest = PH7_VmInstrLength(pGen->pVm); + /* Duplicate label name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pTarget->zString,pTarget->nByte); + if( zDup == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"PH7 is running out-of-memory"); + } + SyStringInitFromBuf(&sLabel.sName, zDup, pTarget->nByte); + sLabel.bRef = FALSE; + sLabel.nLine = pGen->pIn->nLine; + pBlock = pGen->pCurrent; + while(pBlock) { + if(pBlock->iFlags & (GEN_BLOCK_FUNC | GEN_BLOCK_EXCEPTION)) { + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if(pBlock) { + sLabel.pFunc = (ph7_vm_func *)pBlock->pUserData; + } else { + sLabel.pFunc = 0; + } + aLabel = (Label *)SySetBasePtr(&pGen->aLabel); + for(int n = 0; n < SySetUsed(&pGen->aLabel); ++n) { + if(aLabel[n].pFunc == sLabel.pFunc && SyStringCmp(&aLabel[n].sName, &sLabel.sName, SyMemcmp) == 0) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Duplicate label '%z'", &sLabel.sName); + } + } + /* Insert in label set */ + SySetPut(&pGen->aLabel, (const void *)&sLabel); + } + pGen->pIn += 2; /* Jump the label name and the semi-colon*/ + return SXRET_OK; +} +/* + * Compile the so hated 'goto' statement. + * You've probably been taught that gotos are bad, but this sort + * of rewriting happens all the time, in fact every time you run + * a compiler it has to do this. + * According to the PHP language reference manual + * The goto operator can be used to jump to another section in the program. + * The target point is specified by a label followed by a colon, and the instruction + * is given as goto followed by the desired target label. This is not a full unrestricted goto. + * The target label must be within the same file and context, meaning that you cannot jump out + * of a function or method, nor can you jump into one. You also cannot jump into any sort of loop + * or switch structure. You may jump out of these, and a common use is to use a goto in place + * of a multi-level break + */ + +static sxi32 PH7_CompileGoto(ph7_gen_state *pGen) +{ + JumpFixup sJump; + sxi32 rc; + pGen->pIn++; /* Jump the 'goto' keyword */ + if(pGen->pIn >= pGen->pEnd) { + /* Missing label */ + PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "goto: expecting a 'label_name'"); + } + if((pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { + PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "goto: Invalid label name: '%z'", &pGen->pIn->sData); + } else { + SyString *pTarget = &pGen->pIn->sData; + GenBlock *pBlock; + char *zDup; + /* Prepare the jump destination */ + sJump.nJumpType = PH7_OP_JMP; + sJump.nLine = pGen->pIn->nLine; + /* Duplicate label name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pTarget->zString, pTarget->nByte); + if(zDup == 0) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 is running out-of-memory"); + } + SyStringInitFromBuf(&sJump.sLabel, zDup, pTarget->nByte); + pBlock = pGen->pCurrent; + while(pBlock) { + if(pBlock->iFlags & (GEN_BLOCK_FUNC | GEN_BLOCK_EXCEPTION)) { + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if(pBlock && pBlock->iFlags & GEN_BLOCK_EXCEPTION) { + PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "goto inside try/catch block is disallowed"); + } + if(pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC)) { + sJump.pFunc = (ph7_vm_func *)pBlock->pUserData; + } else { + sJump.pFunc = 0; + } + /* Emit the unconditional jump */ + if(SXRET_OK == PH7_VmEmitInstr(pGen->pVm, sJump.nLine, PH7_OP_JMP, 0, 0, 0, &sJump.nInstrIdx)) { + SySetPut(&pGen->aGoto, (const void *)&sJump); + } + } + pGen->pIn++; /* Jump the label name */ + if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) { + PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Expected semi-colon ';' after 'goto' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} /* * Point to the next AerScript chunk that will be processed shortly. * Return SXRET_OK on success. Any other return value indicates @@ -2723,12 +2904,14 @@ static sxi32 PH7_GenStateCompileFuncBody( ) { SySet *pInstrContainer; /* Instruction container */ GenBlock *pBlock; + sxu32 nGotoOfft; sxi32 rc; /* Attach the new function */ rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED | GEN_BLOCK_FUNC, PH7_VmInstrLength(pGen->pVm), pFunc, &pBlock); if(rc != SXRET_OK) { PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory"); } + nGotoOfft = SySetUsed(&pGen->aGoto); /* Swap bytecode containers */ pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); PH7_VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); @@ -2738,6 +2921,11 @@ static sxi32 PH7_GenStateCompileFuncBody( PH7_GenStateFixJumps(pGen->pCurrent, PH7_OP_THROW, PH7_VmInstrLength(pGen->pVm)); /* Emit the final return if not yet done */ PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, 0, 0, 0, 0); + /* Fix gotos jumps now the destination is resolved */ + if(SXERR_ABORT == GenStateFixGoto(&(*pGen), nGotoOfft)) { + rc = SXERR_ABORT; + } + SySetTruncate(&pGen->aGoto,nGotoOfft); /* Restore the default container */ PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer); /* Leave function block */ @@ -4607,6 +4795,7 @@ static const LangConstruct aLangConstruct[] = { { PH7_KEYWORD_EXIT, PH7_CompileHalt }, /* exit language construct */ { PH7_KEYWORD_TRY, PH7_CompileTry }, /* try statement */ { PH7_KEYWORD_THROW, PH7_CompileThrow }, /* throw statement */ + { PH7_KEYWORD_GOTO, PH7_CompileGoto }, /* goto statement */ { PH7_KEYWORD_CONST, PH7_CompileConstant }, /* const statement */ }; /* @@ -4716,6 +4905,9 @@ static sxi32 PH7_GenStateCompileChunk( "Syntax error: Unexpected keyword '%z'", &pGen->pIn->sData); } + } else if((pGen->pIn->nType & PH7_TK_ID) && (&pGen->pIn[1] < pGen->pEnd) && (pGen->pIn[1].nType & PH7_TK_COLON /*':'*/)) { + /* Label found [i.e: Out: ],point to the routine responsible of compiling it */ + xCons = PH7_CompileLabel; } if(xCons == 0) { /* Assume an expression an try to compile it */ @@ -4827,6 +5019,13 @@ static sxi32 PH7_CompileScript( } /* Fix exceptions jumps */ PH7_GenStateFixJumps(pGen->pCurrent, PH7_OP_THROW, PH7_VmInstrLength(pGen->pVm)); + /* Fix gotos now, the jump destination is resolved */ + if(SXERR_ABORT == GenStateFixGoto(&(*pGen), 0)) { + rc = SXERR_ABORT; + } + /* Reset container */ + SySetReset(&pGen->aGoto); + SySetReset(&pGen->aLabel); /* Compilation result */ return rc; } @@ -4901,6 +5100,8 @@ PH7_PRIVATE sxi32 PH7_InitCodeGenerator( pGen->pVm = &(*pVm); pGen->xErr = xErr; pGen->pErrData = pErrData; + SySetInit(&pGen->aLabel, &pVm->sAllocator, sizeof(Label)); + SySetInit(&pGen->aGoto, &pVm->sAllocator, sizeof(JumpFixup)); SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); /* Error log buffer */ @@ -4924,6 +5125,8 @@ PH7_PRIVATE sxi32 PH7_ResetCodeGenerator( ph7_gen_state *pGen = &pVm->sCodeGen; GenBlock *pBlock, *pParent; /* Reset state */ + SySetReset(&pGen->aLabel); + SySetReset(&pGen->aGoto); SyBlobRelease(&pGen->sErrBuf); SyBlobRelease(&pGen->sWorker); /* Point to the global scope */ diff --git a/engine/lexer.c b/engine/lexer.c index 053bd3f..dffef80 100644 --- a/engine/lexer.c +++ b/engine/lexer.c @@ -563,6 +563,7 @@ static sxu32 KeywordCode(const char *z, int n) { {"do", PH7_KEYWORD_DO}, {"for", PH7_KEYWORD_FOR}, {"foreach", PH7_KEYWORD_FOREACH}, + {"goto", PH7_KEYWORD_GOTO}, {"switch", PH7_KEYWORD_SWITCH}, {"else", PH7_KEYWORD_ELSE}, {"if", PH7_KEYWORD_IF}, diff --git a/include/compiler.h b/include/compiler.h index 4cc9fc7..2ab4894 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -6,6 +6,7 @@ /* Forward declaration */ typedef struct LangConstruct LangConstruct; typedef struct JumpFixup JumpFixup; +typedef struct Label Label; /* Block [i.e: set of statements] control flags */ #define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for,while,...] */ @@ -21,6 +22,20 @@ typedef struct JumpFixup JumpFixup; #define GEN_BLOCK_CLASS 0x400 /* Class definition */ #define GEN_BLOCK_NAMESPACE 0x800 /* Namespace body */ +/* + * Each label seen in the input is recorded in an instance + * of the following structure. + * A label is a target point [i.e: a jump destination] that is specified + * by an identifier followed by a colon. + */ +struct Label { + ph7_vm_func *pFunc; /* Compiled function where the label was declared.NULL otherwise */ + sxu32 nJumpDest; /* Jump destination */ + SyString sName; /* Label name */ + sxu32 nLine; /* Line number this label occurs */ + sxu8 bRef; /* True if the label was referenced */ +}; + /* * Compilation of some Aer constructs such as if, for, while, the logical or * (||) and logical and (&&) operators in expressions requires the @@ -30,8 +45,11 @@ typedef struct JumpFixup JumpFixup; * structure. Those jumps are fixed later when the jump destination is resolved. */ struct JumpFixup { - sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ - sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ + sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ + sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ + SyString sLabel; /* Label name */ + ph7_vm_func *pFunc; /* Compiled function inside which the goto was emitted. NULL otherwise */ + sxu32 nLine; /* Track line number */ }; /* diff --git a/include/ph7int.h b/include/ph7int.h index ed337df..cfbd5cb 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -884,6 +884,8 @@ struct ph7_gen_state { GenBlock sGlobal; /* Global block */ ProcConsumer xErr; /* Error consumer callback */ void *pErrData; /* Third argument to xErr() */ + SySet aLabel; /* Label table */ + SySet aGoto; /* Goto table */ SyBlob sWorker; /* General purpose working buffer */ SyBlob sErrBuf; /* Error buffer */ SyToken *pIn; /* Current processed token */ @@ -1580,6 +1582,7 @@ enum ph7_expr_id { #define PH7_KEYWORD_CATCH 53 /* catch */ #define PH7_KEYWORD_RETURN 54 /* return */ #define PH7_KEYWORD_BREAK 55 /* break */ +#define PH7_KEYWORD_GOTO 56 /* goto */ #define PH7_KEYWORD_VOID 0x1000 /* void: MUST BE A POWER OF TWO */ #define PH7_KEYWORD_CHAR 0x2000 /* char: MUST BE A POWER OF TWO */ #define PH7_KEYWORD_BOOL 0x4000 /* bool: MUST BE A POWER OF TWO */