Browse Source

Proper implementation of so hated 'goto' statement.

release/v0.1
Rafal Kupiec 1 year ago
parent
commit
03fc167be1
Signed by: belliash GPG Key ID: 4E829243E0CFE6B4
4 changed files with 227 additions and 2 deletions
  1. +203
    -0
      engine/compiler.c
  2. +1
    -0
      engine/lexer.c
  3. +20
    -2
      include/compiler.h
  4. +3
    -0
      include/ph7int.h

+ 203
- 0
engine/compiler.c View File

@ -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 */

+ 1
- 0
engine/lexer.c View File

@ -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},

+ 20
- 2
include/compiler.h View File

@ -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 */
};
/*

+ 3
- 0
include/ph7int.h View File

@ -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 */

Loading…
Cancel
Save