Aer Interpreter Source
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

6536 lines
222 KiB

/*
* Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language.
* Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/
* Version 2.1.4
* For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://ph7.symisc.net/
*/
/* $SymiscID: compile.c v6.0 Win7 2012-08-18 05:11 stable <chm@symisc.net> $ */
#include "ph7int.h"
/*
* This file implement a thread-safe and full-reentrant compiler for the PH7 engine.
* That is, routines defined in this file takes a stream of tokens and output
* PH7 bytecode instructions.
*/
/* 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,...] */
#define GEN_BLOCK_PROTECTED 0x002 /* Protected block */
#define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/
#define GEN_BLOCK_FUNC 0x008 /* Function body */
#define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/
#define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */
#define GEN_BLOCK_EXPR 0x040 /* Expression */
#define GEN_BLOCK_STD 0x080 /* Standard block */
#define GEN_BLOCK_EXCEPTION 0x100 /* Exception block [i.e: try{ } }*/
#define GEN_BLOCK_SWITCH 0x200 /* Switch statement */
/*
* 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.
* Example
* LABEL:
* echo "hello\n";
*/
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 PHP constructs such as if, for, while, the logical or
* (||) and logical and (&&) operators in expressions requires the
* generation of forward jumps.
* Since the destination PC target of these jumps isn't known when the jumps
* are emitted, we record each forward jump in an instance of the following
* 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. */
/* The following fields are only used by the goto statement */
SyString sLabel; /* Label name */
ph7_vm_func *pFunc; /* Compiled function inside which the goto was emitted. NULL otherwise */
sxu32 nLine; /* Track line number */
};
/*
* Each language construct is represented by an instance
* of the following structure.
*/
struct LangConstruct
{
sxu32 nID; /* Language construct ID [i.e: PH7_TKWRD_WHILE,PH7_TKWRD_FOR,PH7_TKWRD_IF...] */
ProcLangConstruct xConstruct; /* C function implementing the language construct */
};
/* Compilation flags */
#define PH7_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */
/* Token stream synchronization macros */
#define SWAP_TOKEN_STREAM(GEN,START,END)\
pTmp = GEN->pEnd;\
pGen->pIn = START;\
pGen->pEnd = END
#define UPDATE_TOKEN_STREAM(GEN)\
if( GEN->pIn < pTmp ){\
GEN->pIn++;\
}\
GEN->pEnd = pTmp
#define SWAP_DELIMITER(GEN,START,END)\
pTmpIn = GEN->pIn;\
pTmpEnd = GEN->pEnd;\
GEN->pIn = START;\
GEN->pEnd = END
#define RE_SWAP_DELIMITER(GEN)\
GEN->pIn = pTmpIn;\
GEN->pEnd = pTmpEnd
/* Flags related to expression compilation */
#define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */
#define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'PH7_OP_LOAD' VM instruction for more information */
#define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by class attributes) */
/* Forward declaration */
static sxi32 PH7_CompileExpr(ph7_gen_state *pGen,sxi32 iFlags,sxi32 (*xTreeValidator)(ph7_gen_state *,ph7_expr_node *));
/*
* Local utility routines used in the code generation phase.
*/
/*
* 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,SyString *pName,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,pName,SyMemcmp) == 0 ){
/* 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.
* Return a pointer to that block on success. NULL otherwise.
*/
static GenBlock * GenStateFetchBlock(GenBlock *pCurrent,sxi32 iBlockType,sxi32 iCount)
{
GenBlock *pBlock = pCurrent;
for(;;){
if( pBlock->iFlags & iBlockType ){
iCount--; /* Decrement nesting level */
if( iCount < 1 ){
/* Block meet with the desired criteria */
return pBlock;
}
}
/* Point to the upper block */
pBlock = pBlock->pParent;
if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){
/* Forbidden */
break;
}
}
/* No such block */
return 0;
}
/*
* Initialize a freshly allocated block instance.
*/
static void GenStateInitBlock(
ph7_gen_state *pGen, /* Code generator state */
GenBlock *pBlock, /* Target block */
sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/
sxu32 nFirstInstr, /* First instruction to compile */
void *pUserData /* Upper layer private data */
)
{
/* Initialize block fields */
pBlock->nFirstInstr = nFirstInstr;
pBlock->pUserData = pUserData;
pBlock->pGen = pGen;
pBlock->iFlags = iType;
pBlock->pParent = 0;
SySetInit(&pBlock->aJumpFix,&pGen->pVm->sAllocator,sizeof(JumpFixup));
SySetInit(&pBlock->aPostContFix,&pGen->pVm->sAllocator,sizeof(JumpFixup));
}
/*
* Allocate a new block instance.
* Return SXRET_OK and write a pointer to the new instantiated block
* on success.Otherwise generate a compile-time error and abort
* processing on failure.
*/
static sxi32 GenStateEnterBlock(
ph7_gen_state *pGen, /* Code generator state */
sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/
sxu32 nFirstInstr, /* First instruction to compile */
void *pUserData, /* Upper layer private data */
GenBlock **ppBlock /* OUT: instantiated block */
)
{
GenBlock *pBlock;
/* Allocate a new block instance */
pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(GenBlock));
if( pBlock == 0 ){
/* If the supplied memory subsystem is so sick that we are unable to allocate
* a tiny chunk of memory, there is no much we can do here.
*/
PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out-of-memory");
/* Abort processing immediately */
return SXERR_ABORT;
}
/* Zero the structure */
SyZero(pBlock,sizeof(GenBlock));
GenStateInitBlock(&(*pGen),pBlock,iType,nFirstInstr,pUserData);
/* Link to the parent block */
pBlock->pParent = pGen->pCurrent;
/* Mark as the current block */
pGen->pCurrent = pBlock;
if( ppBlock ){
/* Write a pointer to the new instance */
*ppBlock = pBlock;
}
return SXRET_OK;
}
/*
* Release block fields without freeing the whole instance.
*/
static void GenStateReleaseBlock(GenBlock *pBlock)
{
SySetRelease(&pBlock->aPostContFix);
SySetRelease(&pBlock->aJumpFix);
}
/*
* Release a block.
*/
static void GenStateFreeBlock(GenBlock *pBlock)
{
ph7_gen_state *pGen = pBlock->pGen;
GenStateReleaseBlock(&(*pBlock));
/* Free the instance */
SyMemBackendPoolFree(&pGen->pVm->sAllocator,pBlock);
}
/*
* POP and release a block from the stack of compiled blocks.
*/
static sxi32 GenStateLeaveBlock(ph7_gen_state *pGen,GenBlock **ppBlock)
{
GenBlock *pBlock = pGen->pCurrent;
if( pBlock == 0 ){
/* No more block to pop */
return SXERR_EMPTY;
}
/* Point to the upper block */
pGen->pCurrent = pBlock->pParent;
if( ppBlock ){
/* Write a pointer to the popped block */
*ppBlock = pBlock;
}else{
/* Safely release the block */
GenStateFreeBlock(&(*pBlock));
}
return SXRET_OK;
}
/*
* Emit a forward jump.
* Notes on forward jumps
* Compilation of some PHP constructs such as if,for,while and the logical or
* (||) and logical and (&&) operators in expressions requires the
* generation of forward jumps.
* Since the destination PC target of these jumps isn't known when the jumps
* are emitted, we record each forward jump in an instance of the following
* structure. Those jumps are fixed later when the jump destination is resolved.
*/
static sxi32 GenStateNewJumpFixup(GenBlock *pBlock,sxi32 nJumpType,sxu32 nInstrIdx)
{
JumpFixup sJumpFix;
sxi32 rc;
/* Init the JumpFixup structure */
sJumpFix.nJumpType = nJumpType;
sJumpFix.nInstrIdx = nInstrIdx;
/* Insert in the jump fixup table */
rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix);
return rc;
}
/*
* Fix a forward jump now the jump destination is resolved.
* Return the total number of fixed jumps.
* Notes on forward jumps:
* Compilation of some PHP constructs such as if,for,while and the logical or
* (||) and logical and (&&) operators in expressions requires the
* generation of forward jumps.
* Since the destination PC target of these jumps isn't known when the jumps
* are emitted, we record each forward jump in an instance of the following
* structure.Those jumps are fixed later when the jump destination is resolved.
*/
static sxu32 GenStateFixJumps(GenBlock *pBlock,sxi32 nJumpType,sxu32 nJumpDest)
{
JumpFixup *aFix;
VmInstr *pInstr;
sxu32 nFixed;
sxu32 n;
/* Point to the jump fixup table */
aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix);
/* Fix the desired jumps */
for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){
if( aFix[n].nJumpType < 0 ){
/* Already fixed */
continue;
}
if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){
/* Not of our interest */
continue;
}
/* Point to the instruction to fix */
pInstr = PH7_VmGetInstr(pBlock->pGen->pVm,aFix[n].nInstrIdx);
if( pInstr ){
pInstr->iP2 = nJumpDest;
nFixed++;
/* Mark as fixed */
aFix[n].nJumpType = -1;
}
}
/* 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->sLabel,&pLabel);
if( rc != SXRET_OK ){
/* No such label */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pJump->nLine,"Label '%z' was referenced but not defined",&pJump->sLabel);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
continue;
}
/* Make sure the target label is reachable */
if( pLabel->pFunc != pJump->pFunc ){
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pJump->nLine,"Label '%z' is unreachable",&pJump->sLabel);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Fix the jump now the destination is resolved */
pInstr = PH7_VmGetInstr(pGen->pVm,pJump->nInstrIdx);
if( pInstr ){
pInstr->iP2 = pLabel->nJumpDest;
}
}
aLabel = (Label *)SySetBasePtr(&pGen->aLabel);
for( n = 0 ; n < SySetUsed(&pGen->aLabel) ; ++n ){
if( aLabel[n].bRef == FALSE ){
/* Emit a warning */
PH7_GenCompileError(&(*pGen),E_WARNING,aLabel[n].nLine,
"Label '%z' is defined but not referenced",&aLabel[n].sName);
}
}
return SXRET_OK;
}
/*
* Check if a given token value is installed in the literal table.
*/
static sxi32 GenStateFindLiteral(ph7_gen_state *pGen,const SyString *pValue,sxu32 *pIdx)
{
SyHashEntry *pEntry;
pEntry = SyHashGet(&pGen->hLiteral,(const void *)pValue->zString,pValue->nByte);
if( pEntry == 0 ){
return SXERR_NOTFOUND;
}
*pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData);
return SXRET_OK;
}
/*
* Install a given constant index in the literal table.
* In order to be installed, the ph7_value must be of type string.
*/
static sxi32 GenStateInstallLiteral(ph7_gen_state *pGen,ph7_value *pObj,sxu32 nIdx)
{
if( SyBlobLength(&pObj->sBlob) > 0 ){
SyHashInsert(&pGen->hLiteral,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),SX_INT_TO_PTR(nIdx));
}
return SXRET_OK;
}
/*
* Reserve a room for a numeric constant [i.e: 64-bit integer or real number]
* in the constant table.
*/
static ph7_value * GenStateInstallNumLiteral(ph7_gen_state *pGen,sxu32 *pIdx)
{
ph7_value *pObj;
sxu32 nIdx = 0; /* cc warning */
/* Reserve a new constant */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory");
return 0;
}
*pIdx = nIdx;
/* TODO(chems): Create a numeric table (64bit int keys) same as
* the constant string iterals table [optimization purposes].
*/
return pObj;
}
/*
* Implementation of the PHP language constructs.
*/
/* Forward declaration */
static sxi32 GenStateCompileChunk(ph7_gen_state *pGen,sxi32 iFlags);
/*
* Compile a numeric [i.e: integer or real] literal.
* Notes on the integer type.
* According to the PHP language reference manual
* Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8)
* or binary (base 2) notation, optionally preceded by a sign (- or +).
* To use octal notation, precede the number with a 0 (zero). To use hexadecimal
* notation precede the number with 0x. To use binary notation precede the number with 0b.
* Symisc eXtension to the integer type.
* PH7 introduced platform-independant 64-bit integer unlike the standard PHP engine
* where the size of an integer is platform-dependent.That is,the size of an integer
* is 8 bytes and the maximum integer size is 0x7FFFFFFFFFFFFFFF for all platforms
* [i.e: either 32bit or 64bit].
* For more information on this powerfull extension please refer to the official
* documentation.
*/
static sxi32 PH7_CompileNumLiteral(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
SyToken *pToken = pGen->pIn; /* Raw token */
sxu32 nIdx = 0;
if( pToken->nType & PH7_TK_INTEGER ){
ph7_value *pObj;
sxi64 iValue;
iValue = PH7_TokenValueToInt64(&pToken->sData);
pObj = GenStateInstallNumLiteral(&(*pGen),&nIdx);
if( pObj == 0 ){
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
PH7_MemObjInitFromInt(pGen->pVm,pObj,iValue);
}else{
/* Real number */
ph7_value *pObj;
/* Reserve a new constant */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory");
return SXERR_ABORT;
}
PH7_MemObjInitFromString(pGen->pVm,pObj,&pToken->sData);
PH7_MemObjToReal(pObj);
}
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a single quoted string.
* According to the PHP language reference manual:
*
* The simplest way to specify a string is to enclose it in single quotes (the character ' ).
* To specify a literal single quote, escape it with a backslash (\). To specify a literal
* backslash, double it (\\). All other instances of backslash will be treated as a literal
* backslash: this means that the other escape sequences you might be used to, such as \r
* or \n, will be output literally as specified rather than having any special meaning.
*
*/
PH7_PRIVATE sxi32 PH7_CompileSimpleString(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
const char *zIn,*zCur,*zEnd;
ph7_value *pObj;
sxu32 nIdx;
nIdx = 0; /* Prevent compiler warning */
/* Delimit the string */
zIn = pStr->zString;
zEnd = &zIn[pStr->nByte];
if( zIn >= zEnd ){
/* Empty string,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
return SXRET_OK;
}
if( SXRET_OK == GenStateFindLiteral(&(*pGen),pStr,&nIdx) ){
/* Already processed,emit the load constant instruction
* and return.
*/
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
return SXRET_OK;
}
/* Reserve a new constant */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
PH7_MemObjInitFromString(pGen->pVm,pObj,0);
/* Compile the node */
for(;;){
if( zIn >= zEnd ){
/* End of input */
break;
}
zCur = zIn;
while( zIn < zEnd && zIn[0] != '\\' ){
zIn++;
}
if( zIn > zCur ){
/* Append raw contents*/
PH7_MemObjStringAppend(pObj,zCur,(sxu32)(zIn-zCur));
}
zIn++;
if( zIn < zEnd ){
if( zIn[0] == '\\' ){
/* A literal backslash */
PH7_MemObjStringAppend(pObj,"\\",sizeof(char));
}else if( zIn[0] == '\'' ){
/* A single quote */
PH7_MemObjStringAppend(pObj,"'",sizeof(char));
}else{
/* verbatim copy */
zIn--;
PH7_MemObjStringAppend(pObj,zIn,sizeof(char)*2);
zIn++;
}
}
/* Advance the stream cursor */
zIn++;
}
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
if( pStr->nByte < 1024 ){
/* Install in the literal table */
GenStateInstallLiteral(pGen,pObj,nIdx);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a nowdoc string.
* According to the PHP language reference manual:
*
* Nowdocs are to single-quoted strings what heredocs are to double-quoted strings.
* A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc.
* The construct is ideal for embedding PHP code or other large blocks of text without the
* need for escaping. It shares some features in common with the SGML <![CDATA[ ]]>
* construct, in that it declares a block of text which is not for parsing.
* A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier
* which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc
* identifiers also apply to nowdoc identifiers, especially those regarding the appearance
* of the closing identifier.
*/
static sxi32 PH7_CompileNowDoc(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
ph7_value *pObj;
sxu32 nIdx;
nIdx = 0; /* Prevent compiler warning */
if( pStr->nByte <= 0 ){
/* Empty string,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
return SXRET_OK;
}
/* Reserve a new constant */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"PH7 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
/* No processing is done here, simply a memcpy() operation */
PH7_MemObjInitFromString(pGen->pVm,pObj,pStr);
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Process variable expression [i.e: "$var","${var}"] embedded in a double quoted/heredoc string.
* According to the PHP language reference manual
* When a string is specified in double quotes or with heredoc,variables are parsed within it.
* There are two types of syntax: a simple one and a complex one. The simple syntax is the most
* common and convenient. It provides a way to embed a variable, an array value, or an object
* property in a string with a minimum of effort.
* Simple syntax
* If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible
* to form a valid variable name. Enclose the variable name in curly braces to explicitly specify
* the end of the name.
* Similarly, an array index or an object property can be parsed. With array indices, the closing
* square bracket (]) marks the end of the index. The same rules apply to object properties
* as to simple variables.
* Complex (curly) syntax
* This isn't called complex because the syntax is complex, but because it allows for the use
* of complex expressions.
* Any scalar variable, array element or object property with a string representation can be
* included via this syntax. Simply write the expression the same way as it would appear outside
* the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only
* be recognised when the $ immediately follows the {. Use {\$ to get a literal {$
*/
static sxi32 GenStateProcessStringExpression(
ph7_gen_state *pGen, /* Code generator state */
sxu32 nLine, /* Line number */
const char *zIn, /* Raw expression */
const char *zEnd /* End of the expression */
)
{
SyToken *pTmpIn,*pTmpEnd;
SySet sToken;
sxi32 rc;
/* Initialize the token set */
SySetInit(&sToken,&pGen->pVm->sAllocator,sizeof(SyToken));
/* Preallocate some slots */
SySetAlloc(&sToken,0x08);
/* Tokenize the text */
PH7_TokenizePHP(zIn,(sxu32)(zEnd-zIn),nLine,&sToken);
/* Swap delimiter */
pTmpIn = pGen->pIn;
pTmpEnd = pGen->pEnd;
pGen->pIn = (SyToken *)SySetBasePtr(&sToken);
pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)];
/* Compile the expression */
rc = PH7_CompileExpr(&(*pGen),0,0);
/* Restore token stream */
pGen->pIn = pTmpIn;
pGen->pEnd = pTmpEnd;
/* Release the token set */
SySetRelease(&sToken);
/* Compilation result */
return rc;
}
/*
* Reserve a new constant for a double quoted/heredoc string.
*/
static ph7_value * GenStateNewStrObj(ph7_gen_state *pGen,sxi32 *pCount)
{
ph7_value *pConstObj;
sxu32 nIdx = 0;
/* Reserve a new constant */
pConstObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pConstObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"PH7 engine is running out of memory");
return 0;
}
(*pCount)++;
PH7_MemObjInitFromString(pGen->pVm,pConstObj,0);
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
return pConstObj;
}
/*
* Compile a double quoted/heredoc string.
* According to the PHP language reference manual
* Heredoc
* A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier
* is provided, then a newline. The string itself follows, and then the same identifier again
* to close the quotation.
* The closing identifier must begin in the first column of the line. Also, the identifier must
* follow the same naming rules as any other label in PHP: it must contain only alphanumeric
* characters and underscores, and must start with a non-digit character or underscore.
* Warning
* It is very important to note that the line with the closing identifier must contain
* no other characters, except possibly a semicolon (;). That means especially that the identifier
* may not be indented, and there may not be any spaces or tabs before or after the semicolon.
* It's also important to realize that the first character before the closing identifier must
* be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X.
* The closing delimiter (possibly followed by a semicolon) must also be followed by a newline.
* If this rule is broken and the closing identifier is not "clean", it will not be considered a closing
* identifier, and PHP will continue looking for one. If a proper closing identifier is not found before
* the end of the current file, a parse error will result at the last line.
* Heredocs can not be used for initializing class properties.
* Double quoted
* If the string is enclosed in double-quotes ("), PHP will interpret more escape sequences for special characters:
* Escaped characters Sequence Meaning
* \n linefeed (LF or 0x0A (10) in ASCII)
* \r carriage return (CR or 0x0D (13) in ASCII)
* \t horizontal tab (HT or 0x09 (9) in ASCII)
* \v vertical tab (VT or 0x0B (11) in ASCII)
* \f form feed (FF or 0x0C (12) in ASCII)
* \\ backslash
* \$ dollar sign
* \" double-quote
* \[0-7]{1,3} the sequence of characters matching the regular expression is a character in octal notation
* \x[0-9A-Fa-f]{1,2} the sequence of characters matching the regular expression is a character in hexadecimal notation
* As in single quoted strings, escaping any other character will result in the backslash being printed too.
* The most important feature of double-quoted strings is the fact that variable names will be expanded.
* See string parsing for details.
*/
static sxi32 GenStateCompileString(ph7_gen_state *pGen)
{
SyString *pStr = &pGen->pIn->sData; /* Raw token value */
const char *zIn,*zCur,*zEnd;
ph7_value *pObj = 0;
sxi32 iCons;
sxi32 rc;
/* Delimit the string */
zIn = pStr->zString;
zEnd = &zIn[pStr->nByte];
if( zIn >= zEnd ){
/* Empty string,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
return SXRET_OK;
}
zCur = 0;
/* Compile the node */
iCons = 0;
for(;;){
zCur = zIn;
while( zIn < zEnd && zIn[0] != '\\' ){
if( zIn[0] == '{' && &zIn[1] < zEnd && zIn[1] == '$' ){
break;
}else if(zIn[0] == '$' && &zIn[1] < zEnd &&
(((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '{' || zIn[1] == '_')) ){
break;
}
zIn++;
}
if( zIn > zCur ){
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen),&iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
PH7_MemObjStringAppend(pObj,zCur,(sxu32)(zIn-zCur));
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '\\' ){
const char *zPtr = 0;
sxu32 n;
zIn++;
if( zIn >= zEnd ){
break;
}
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen),&iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
n = sizeof(char); /* size of conversion */
switch( zIn[0] ){
case '$':
/* Dollar sign */
PH7_MemObjStringAppend(pObj,"$",sizeof(char));
break;
case '\\':
/* A literal backslash */
PH7_MemObjStringAppend(pObj,"\\",sizeof(char));
break;
case 'a':
/* The "alert" character (BEL)[ctrl+g] ASCII code 7 */
PH7_MemObjStringAppend(pObj,"\a",sizeof(char));
break;
case 'b':
/* Backspace (BS)[ctrl+h] ASCII code 8 */
PH7_MemObjStringAppend(pObj,"\b",sizeof(char));
break;
case 'f':
/* Form-feed (FF)[ctrl+l] ASCII code 12 */
PH7_MemObjStringAppend(pObj,"\f",sizeof(char));
break;
case 'n':
/* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */
PH7_MemObjStringAppend(pObj,"\n",sizeof(char));
break;
case 'r':
/* Carriage return (CR)[ctrl+m] ASCII code 13 */
PH7_MemObjStringAppend(pObj,"\r",sizeof(char));
break;
case 't':
/* Horizontal tab (HT)[ctrl+i] ASCII code 9 */
PH7_MemObjStringAppend(pObj,"\t",sizeof(char));
break;
case 'v':
/* Vertical tab(VT)[ctrl+k] ASCII code 11 */
PH7_MemObjStringAppend(pObj,"\v",sizeof(char));
break;
case '\'':
/* Single quote */
PH7_MemObjStringAppend(pObj,"'",sizeof(char));
break;
case '"':
/* Double quote */
PH7_MemObjStringAppend(pObj,"\"",sizeof(char));
break;
case '0':
/* NUL byte */
PH7_MemObjStringAppend(pObj,"\0",sizeof(char));
break;
case 'x':
if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){
int c;
/* Hex digit */
c = SyHexToint(zIn[1]) << 4;
if( &zIn[2] < zEnd ){
c += SyHexToint(zIn[2]);
}
/* Output char */
PH7_MemObjStringAppend(pObj,(const char *)&c,sizeof(char));
n += sizeof(char) * 2;
}else{
/* Output literal character */
PH7_MemObjStringAppend(pObj,"x",sizeof(char));
}
break;
case 'o':
if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){
/* Octal digit stream */
int c;
c = 0;
zIn++;
for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){
if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){
break;
}
c = c * 8 + (zPtr[0] - '0');
}
if ( c > 0 ){
PH7_MemObjStringAppend(pObj,(const char *)&c,sizeof(char));
}
n = (sxu32)(zPtr-zIn);
}else{
/* Output literal character */
PH7_MemObjStringAppend(pObj,"o",sizeof(char));
}
break;
default:
/* Output without a slash */
PH7_MemObjStringAppend(pObj,zIn,sizeof(char));
break;
}
/* Advance the stream cursor */
zIn += n;
continue;
}
if( zIn[0] == '{' ){
/* Curly syntax */
const char *zExpr;
sxi32 iNest = 1;
zIn++;
zExpr = zIn;
/* Synchronize with the next closing curly braces */
while( zIn < zEnd ){
if( zIn[0] == '{' ){
/* Increment nesting level */
iNest++;
}else if(zIn[0] == '}' ){
/* Decrement nesting level */
iNest--;
if( iNest <= 0 ){
break;
}
}
zIn++;
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),pGen->pIn->nLine,zExpr,zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
if( zIn < zEnd ){
/* Jump the trailing curly */
zIn++;
}
}else{
/* Simple syntax */
const char *zExpr = zIn;
/* Assemble variable name */
for(;;){
/* Jump leading dollars */
while( zIn < zEnd && zIn[0] == '$' ){
zIn++;
}
for(;;){
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){
zIn++;
}
if((unsigned char)zIn[0] >= 0xc0 ){
/* UTF-8 stream */
zIn++;
while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){
zIn++;
}
continue;
}
break;
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '[' ){
sxi32 iSquare = 1;
zIn++;
while( zIn < zEnd ){
if( zIn[0] == '[' ){
iSquare++;
}else if (zIn[0] == ']' ){
iSquare--;
if( iSquare <= 0 ){
break;
}
}
zIn++;
}
if( zIn < zEnd ){
zIn++;
}
break;
}else if(zIn[0] == '{' ){
sxi32 iCurly = 1;
zIn++;
while( zIn < zEnd ){
if( zIn[0] == '{' ){
iCurly++;
}else if (zIn[0] == '}' ){
iCurly--;
if( iCurly <= 0 ){
break;
}
}
zIn++;
}
if( zIn < zEnd ){
zIn++;
}
break;
}else if( zIn[0] == '-' && &zIn[1] < zEnd && zIn[1] == '>' ){
/* Member access operator '->' */
zIn += 2;
}else if(zIn[0] == ':' && &zIn[1] < zEnd && zIn[1] == ':'){
/* Static member access operator '::' */
zIn += 2;
}else{
break;
}
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),pGen->pIn->nLine,zExpr,zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
}
/* Invalidate the previously used constant */
pObj = 0;
}/*for(;;)*/
if( iCons > 1 ){
/* Concatenate all compiled constants */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_CAT,iCons,0,0,0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a double quoted string.
* See the block-comment above for more information.
*/
PH7_PRIVATE sxi32 PH7_CompileString(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
sxi32 rc;
rc = GenStateCompileString(&(*pGen));
SXUNUSED(iCompileFlag); /* cc warning */
/* Compilation result */
return rc;
}
/*
* Compile a Heredoc string.
* See the block-comment above for more information.
*/
static sxi32 PH7_CompileHereDoc(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
sxi32 rc;
rc = GenStateCompileString(&(*pGen));
SXUNUSED(iCompileFlag); /* cc warning */
/* Compilation result */
return SXRET_OK;
}
/*
* Compile an array entry whether it is a key or a value.
* Notes on array entries.
* According to the PHP language reference manual
* An array can be created by the array() language construct.
* It takes as parameters any number of comma-separated key => value pairs.
* array( key => value
* , ...
* )
* A key may be either an integer or a string. If a key is the standard representation
* of an integer, it will be interpreted as such (i.e. "8" will be interpreted as 8, while
* "08" will be interpreted as "08"). Floats in key are truncated to integer.
* The indexed and associative array types are the same type in PHP, which can both
* contain integer and string indices.
* A value can be any PHP type.
* If a key is not specified for a value, the maximum of the integer indices is taken
* and the new key will be that value plus 1. If a key that already has an assigned value
* is specified, that value will be overwritten.
*/
static sxi32 GenStateCompileArrayEntry(
ph7_gen_state *pGen, /* Code generator state */
SyToken *pIn, /* Token stream */
SyToken *pEnd, /* End of the token stream */
sxi32 iFlags, /* Compilation flags */
sxi32 (*xValidator)(ph7_gen_state *,ph7_expr_node *) /* Expression tree validator callback */
)
{
SyToken *pTmpIn,*pTmpEnd;
sxi32 rc;
/* Swap token stream */
SWAP_DELIMITER(pGen,pIn,pEnd);
/* Compile the expression*/
rc = PH7_CompileExpr(&(*pGen),iFlags,xValidator);
/* Restore token stream */
RE_SWAP_DELIMITER(pGen);
return rc;
}
/*
* Expression tree validator callback for the 'array' language construct.
* Return SXRET_OK if the tree is valid. Any other return value indicates
* an invalid expression tree and this function will generate the appropriate
* error message.
* See the routine responible of compiling the array language construct
* for more inforation.
*/
static sxi32 GenStateArrayNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot)
{
sxi32 rc = SXRET_OK;
if( pRoot->pOp ){
if( pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ &&
pRoot->pOp->iOp != EXPR_OP_FUNC_CALL /* function() [Symisc extension: i.e: array(&foo())] */
&& pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */ && pRoot->pOp->iOp != EXPR_OP_DC /* :: */){
/* Unexpected expression */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0,
"array(): Expecting a variable/array member/function call after reference operator '&'");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
}else if( pRoot->xCode != PH7_CompileVariable ){
/* Unexpected expression */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0,
"array(): Expecting a variable after reference operator '&'");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile the 'array' language construct.
* According to the PHP language reference manual
* An array in PHP is actually an ordered map. A map is a type that associates
* values to keys. This type is optimized for several different uses; it can
* be treated as an array, list (vector), hash table (an implementation of a map)
* dictionary, collection, stack, queue, and probably more. As array values can be
* other arrays, trees and multidimensional arrays are also possible.
*/
PH7_PRIVATE sxi32 PH7_CompileArray(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
sxi32 (*xValidator)(ph7_gen_state *,ph7_expr_node *); /* Expression tree validator callback */
SyToken *pKey,*pCur;
sxi32 iEmitRef = 0;
sxi32 nPair = 0;
sxi32 iNest;
sxi32 rc;
/* Jump the 'array' keyword,the leading left parenthesis and the trailing parenthesis.
*/
pGen->pIn += 2;
pGen->pEnd--;
xValidator = 0;
SXUNUSED(iCompileFlag); /* cc warning */
for(;;){
/* Jump leading commas */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA) ){
pGen->pIn++;
}
pCur = pGen->pIn;
if( SXRET_OK != PH7_GetNextExpr(pGen->pIn,pGen->pEnd,&pGen->pIn) ){
/* No more entry to process */
break;
}
if( pCur >= pGen->pIn ){
continue;
}
/* Compile the key if available */
pKey = pCur;
iNest = 0;
while( pCur < pGen->pIn ){
if( (pCur->nType & PH7_TK_ARRAY_OP) && iNest <= 0 ){
break;
}
if( pCur->nType & PH7_TK_LPAREN /*'('*/ ){
iNest++;
}else if( pCur->nType & PH7_TK_RPAREN /*')'*/ ){
/* Don't worry about mismatched parenthesis here,the expression
* parser will shortly detect any syntax error.
*/
iNest--;
}
pCur++;
}
rc = SXERR_EMPTY;
if( pCur < pGen->pIn ){
if( &pCur[1] >= pGen->pIn ){
/* Missing value */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pCur->nLine,"array(): Missing entry value");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Compile the expression holding the key */
rc = GenStateCompileArrayEntry(&(*pGen),pKey,pCur,
EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pCur++; /* Jump the '=>' operator */
}else if( pKey == pCur ){
/* Key is omitted,emit a warning */
PH7_GenCompileError(&(*pGen),E_WARNING,pCur->nLine,"array(): Missing entry key");
pCur++; /* Jump the '=>' operator */
}else{
/* Reset back the cursor and point to the entry value */
pCur = pKey;
}
if( rc == SXERR_EMPTY ){
/* No available key,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0 /* nil index */,0,0);
}
if( pCur->nType & PH7_TK_AMPER /*'&'*/){
/* Insertion by reference, [i.e: $a = array(&$x);] */
xValidator = GenStateArrayNodeValidator; /* Only variable are allowed */
iEmitRef = 1;
pCur++; /* Jump the '&' token */
if( pCur >= pGen->pIn ){
/* Missing value */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pCur->nLine,"array(): Missing referenced variable");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
}
/* Compile indice value */
rc = GenStateCompileArrayEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,xValidator);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( iEmitRef ){
/* Emit the load reference instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_REF,0,0,0,0);
}
xValidator = 0;
iEmitRef = 0;
nPair++;
}
/* Emit the load map instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_MAP,nPair * 2,0,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Expression tree validator callback for the 'list' language construct.
* Return SXRET_OK if the tree is valid. Any other return value indicates
* an invalid expression tree and this function will generate the appropriate
* error message.
* See the routine responible of compiling the list language construct
* for more inforation.
*/
static sxi32 GenStateListNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot)
{
sxi32 rc = SXRET_OK;
if( pRoot->pOp ){
if( pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ && pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */
&& pRoot->pOp->iOp != EXPR_OP_DC /* :: */ ){
/* Unexpected expression */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0,
"list(): Expecting a variable not an expression");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
}else if( pRoot->xCode != PH7_CompileVariable ){
/* Unexpected expression */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0,
"list(): Expecting a variable not an expression");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile the 'list' language construct.
* According to the PHP language reference
* list(): Assign variables as if they were an array.
* list() is used to assign a list of variables in one operation.
* Description
* array list (mixed $varname [, mixed $... ] )
* Like array(), this is not really a function, but a language construct.
* list() is used to assign a list of variables in one operation.
* Parameters
* $varname: A variable.
* Return Values
* The assigned array.
*/
PH7_PRIVATE sxi32 PH7_CompileList(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
SyToken *pNext;
sxi32 nExpr;
sxi32 rc;
nExpr = 0;
/* Jump the 'list' keyword,the leading left parenthesis and the trailing parenthesis */
pGen->pIn += 2;
pGen->pEnd--;
SXUNUSED(iCompileFlag); /* cc warning */
while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pGen->pEnd,&pNext) ){
if( pGen->pIn < pNext ){
/* Compile the expression holding the variable */
rc = GenStateCompileArrayEntry(&(*pGen),pGen->pIn,pNext,EXPR_FLAG_LOAD_IDX_STORE,GenStateListNodeValidator);
if( rc != SXRET_OK ){
/* Do not bother compiling this expression, it's broken anyway */
return SXRET_OK;
}
}else{
/* Empty entry,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0/* NULL index */,0,0);
}
nExpr++;
/* Advance the stream cursor */
pGen->pIn = &pNext[1];
}
/* Emit the LOAD_LIST instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_LIST,nExpr,0,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/* Forward declaration */
static sxi32 GenStateCompileFunc(ph7_gen_state *pGen,SyString *pName,sxi32 iFlags,int bHandleClosure,ph7_vm_func **ppFunc);
/*
* Compile an annoynmous function or a closure.
* According to the PHP language reference
* Anonymous functions, also known as closures, allow the creation of functions
* which have no specified name. They are most useful as the value of callback
* parameters, but they have many other uses. Closures can also be used as
* the values of variables; Assigning a closure to a variable uses the same
* syntax as any other assignment, including the trailing semicolon:
* Example Anonymous function variable assignment example
* <?php
* $greet = function($name)
* {
* printf("Hello %s\r\n", $name);
* };
* $greet('World');
* $greet('PHP');
* ?>
* Note that the implementation of annoynmous function and closure under
* PH7 is completely different from the one used by the zend engine.
*/
PH7_PRIVATE sxi32 PH7_CompileAnnonFunc(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
ph7_vm_func *pAnnonFunc; /* Annonymous function body */
char zName[512]; /* Unique lambda name */
static int iCnt = 1; /* There is no worry about thread-safety here,because only
* one thread is allowed to compile the script.
*/
ph7_value *pObj;
SyString sName;
sxu32 nIdx;
sxu32 nLen;
sxi32 rc;
pGen->pIn++; /* Jump the 'function' keyword */
if( pGen->pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD) ){
pGen->pIn++;
}
/* Reserve a constant for the lambda */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
/* Generate a unique name */
nLen = SyBufferFormat(zName,sizeof(zName),"[lambda_%d]",iCnt++);
/* Make sure the generated name is unique */
while( SyHashGet(&pGen->pVm->hFunction,zName,nLen) != 0 && nLen < sizeof(zName) - 2 ){
nLen = SyBufferFormat(zName,sizeof(zName),"[lambda_%d]",iCnt++);
}
SyStringInitFromBuf(&sName,zName,nLen);
PH7_MemObjInitFromString(pGen->pVm,pObj,&sName);
/* Compile the lambda body */
rc = GenStateCompileFunc(&(*pGen),&sName,0,TRUE,&pAnnonFunc);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( pAnnonFunc->iFlags & VM_FUNC_CLOSURE ){
/* Emit the load closure instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_CLOSURE,0,0,pAnnonFunc,0);
}else{
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a backtick quoted string.
*/
static sxi32 PH7_CompileBacktic(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
/* TICKET 1433-40: This construct is disabled in the current release of the PH7 engine.
* If you want this feature,please contact symisc systems via contact@symisc.net
*/
PH7_GenCompileError(&(*pGen),E_NOTICE,pGen->pIn->nLine,
"Command line invocation is disabled in the current release of the PH7(%s) engine",
ph7_lib_version()
);
/* Load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
SXUNUSED(iCompileFlag); /* cc warning */
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a function [i.e: die(),exit(),include(),...] which is a langauge
* construct.
*/
PH7_PRIVATE sxi32 PH7_CompileLangConstruct(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pName;
sxu32 nKeyID;
sxi32 rc;
/* Name of the language construct [i.e: echo,die...]*/
pName = &pGen->pIn->sData;
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
pGen->pIn++; /* Jump the language construct keyword */
if( nKeyID == PH7_TKWRD_ECHO ){
SyToken *pTmp,*pNext = 0;
/* Compile arguments one after one */
pTmp = pGen->pEnd;
/* Symisc eXtension to the PHP programming language:
* 'echo' can be used in the context of a function which
* mean that the following expression is valid:
* fopen('file.txt','r') or echo "IO error";
*/
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,1 /* Boolean true index */,0,0);
while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pTmp,&pNext) ){
if( pGen->pIn < pNext ){
pGen->pEnd = pNext;
rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
/* Ticket 1433-008: Optimization #1: Consume input directly
* without the overhead of a function call.
* This is a very powerful optimization that improve
* performance greatly.
*/
PH7_VmEmitInstr(pGen->pVm,PH7_OP_CONSUME,1,0,0,0);
}
}
/* Jump trailing commas */
while( pNext < pTmp && (pNext->nType & PH7_TK_COMMA) ){
pNext++;
}
pGen->pIn = pNext;
}
/* Restore token stream */
pGen->pEnd = pTmp;
}else{
sxi32 nArg = 0;
sxu32 nIdx = 0;
rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nArg = 1;
}
if( SXRET_OK != GenStateFindLiteral(&(*pGen),pName,&nIdx) ){
ph7_value *pObj;
/* Emit the call instruction */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
PH7_MemObjInitFromString(pGen->pVm,pObj,pName);
/* Install in the literal table */
GenStateInstallLiteral(&(*pGen),pObj,nIdx);
}
/* Emit the call instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
PH7_VmEmitInstr(pGen->pVm,PH7_OP_CALL,nArg,0,0,0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a node holding a variable declaration.
* According to the PHP language reference
* Variables in PHP are represented by a dollar sign followed by the name of the variable.
* The variable name is case-sensitive.
* Variable names follow the same rules as other labels in PHP. A valid variable name starts
* with a letter or underscore, followed by any number of letters, numbers, or underscores.
* As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'
* Note: For our purposes here, a letter is a-z, A-Z, and the bytes from 127 through 255 (0x7f-0xff).
* Note: $this is a special variable that can't be assigned.
* By default, variables are always assigned by value. That is to say, when you assign an expression
* to a variable, the entire value of the original expression is copied into the destination variable.
* This means, for instance, that after assigning one variable's value to another, changing one of those
* variables will have no effect on the other. For more information on this kind of assignment, see
* the chapter on Expressions.
* PHP also offers another way to assign values to variables: assign by reference. This means that
* the new variable simply references (in other words, "becomes an alias for" or "points to") the original
* variable. Changes to the new variable affect the original, and vice versa.
* To assign by reference, simply prepend an ampersand (&) to the beginning of the variable which
* is being assigned (the source variable).
*/
PH7_PRIVATE sxi32 PH7_CompileVariable(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
sxu32 nLine = pGen->pIn->nLine;
sxi32 iVv;
sxi32 iP1;
void *p3;
sxi32 rc;
iVv = -1; /* Variable variable counter */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_DOLLAR) ){
pGen->pIn++;
iVv++;
}
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD|PH7_TK_OCB/*'{'*/)) == 0 ){
/* Invalid variable name */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid variable name");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
p3 = 0;
if( pGen->pIn->nType & PH7_TK_OCB/*'{'*/ ){
/* Dynamic variable creation */
pGen->pIn++; /* Jump the open curly */
pGen->pEnd--; /* Ignore the trailing curly */
if( pGen->pIn >= pGen->pEnd ){
/* Empty expression */
PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Invalid variable name");
return SXRET_OK;
}
/* Compile the expression holding the variable name */
rc = PH7_CompileExpr(&(*pGen),0,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if( rc == SXERR_EMPTY ){
PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Missing variable name");
return SXRET_OK;
}
}else{
SyHashEntry *pEntry;
SyString *pName;
char *zName = 0;
/* Extract variable name */
pName = &pGen->pIn->sData;
/* Advance the stream cursor */
pGen->pIn++;
pEntry = SyHashGet(&pGen->hVar,(const void *)pName->zString,pName->nByte);
if( pEntry == 0 ){
/* Duplicate name */
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte);
if( zName == 0 ){
PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 engine is running out of memory");
return SXERR_ABORT;
}
/* Install in the hashtable */
SyHashInsert(&pGen->hVar,zName,pName->nByte,zName);
}else{
/* Name already available */
zName = (char *)pEntry->pUserData;
}
p3 = (void *)zName;
}
iP1 = 0;
if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){
if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){
/* Read-only load.In other words do not create the variable if inexistant */
iP1 = 1;
}
}
/* Emit the load instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD,iP1,0,p3,0);
while( iVv > 0 ){
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD,iP1,0,0,0);
iVv--;
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Load a literal.
*/
static sxi32 GenStateLoadLiteral(ph7_gen_state *pGen)
{
SyToken *pToken = pGen->pIn;
ph7_value *pObj;
SyString *pStr;
sxu32 nIdx;
/* Extract token value */
pStr = &pToken->sData;
/* Deal with the reserved literals [i.e: null,false,true,...] first */
if( pStr->nByte == sizeof("NULL") - 1 ){
if( SyStrnicmp(pStr->zString,"null",sizeof("NULL")-1) == 0 ){
/* NULL constant are always indexed at 0 */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
return SXRET_OK;
}else if( SyStrnicmp(pStr->zString,"true",sizeof("TRUE")-1) == 0 ){
/* TRUE constant are always indexed at 1 */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,1,0,0);
return SXRET_OK;
}
}else if (pStr->nByte == sizeof("FALSE") - 1 &&
SyStrnicmp(pStr->zString,"false",sizeof("FALSE")-1) == 0 ){
/* FALSE constant are always indexed at 2 */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,2,0,0);
return SXRET_OK;
}else if(pStr->nByte == sizeof("__LINE__") - 1 &&
SyMemcmp(pStr->zString,"__LINE__",sizeof("__LINE__")-1) == 0 ){
/* TICKET 1433-004: __LINE__ constant must be resolved at compile time,not run time */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"Fatal, PH7 engine is running out of memory");
return SXERR_ABORT;
}
PH7_MemObjInitFromInt(pGen->pVm,pObj,pToken->nLine);
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
return SXRET_OK;
}else if( (pStr->nByte == sizeof("__FUNCTION__") - 1 &&
SyMemcmp(pStr->zString,"__FUNCTION__",sizeof("__FUNCTION__")-1) == 0) ||
(pStr->nByte == sizeof("__METHOD__") - 1 &&
SyMemcmp(pStr->zString,"__METHOD__",sizeof("__METHOD__")-1) == 0) ){
GenBlock *pBlock = pGen->pCurrent;
/* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time,not run time */
while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){
/* Point to the upper block */
pBlock = pBlock->pParent;
}
if( pBlock == 0 ){
/* Called in the global scope,load NULL */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
}else{
/* Extract the target function/method */
ph7_vm_func *pFunc = (ph7_vm_func *)pBlock->pUserData;
if( pStr->zString[2] == 'M' /* METHOD */ && (pFunc->iFlags & VM_FUNC_CLASS_METHOD) == 0 ){
/* Not a class method,Load null */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0);
}else{
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"Fatal, PH7 engine is running out of memory");
return SXERR_ABORT;
}
PH7_MemObjInitFromString(pGen->pVm,pObj,&pFunc->sName);
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0);
}
}
return SXRET_OK;
}
/* Query literal table */
if( SXRET_OK != GenStateFindLiteral(&(*pGen),&pToken->sData,&nIdx) ){
ph7_value *pObj;
/* Unknown literal,install it in the literal table */
pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx);
if( pObj == 0 ){
PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory");
return SXERR_ABORT;
}
PH7_MemObjInitFromString(pGen->pVm,pObj,&pToken->sData);
GenStateInstallLiteral(&(*pGen),pObj,nIdx);
}
/* Emit the load constant instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,1,nIdx,0,0);
return SXRET_OK;
}
/*
* Resolve a namespace path or simply load a literal:
* As of this version namespace support is disabled. If you need
* a working version that implement namespace,please contact
* symisc systems via contact@symisc.net
*/
static sxi32 GenStateResolveNamespaceLiteral(ph7_gen_state *pGen)
{
int emit = 0;
sxi32 rc;
while( pGen->pIn < &pGen->pEnd[-1] ){
/* Emit a warning */
if( !emit ){
PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,
"Namespace support is disabled in the current release of the PH7(%s) engine",
ph7_lib_version()
);
emit = 1;
}
pGen->pIn++; /* Ignore the token */
}
/* Load literal */
rc = GenStateLoadLiteral(&(*pGen));
return rc;
}
/*
* Compile a literal which is an identifier(name) for a simple value.
*/
PH7_PRIVATE sxi32 PH7_CompileLiteral(ph7_gen_state *pGen,sxi32 iCompileFlag)
{
sxi32 rc;
rc = GenStateResolveNamespaceLiteral(&(*pGen));
if( rc != SXRET_OK ){
SXUNUSED(iCompileFlag); /* cc warning */
return rc;
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Recover from a compile-time error. In other words synchronize
* the token stream cursor with the first semi-colon seen.
*/
static sxi32 PH7_ErrorRecover(ph7_gen_state *pGen)
{
/* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI /*';'*/) == 0){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Check if the given identifier name is reserved or not.
* Return TRUE if reserved.FALSE otherwise.
*/
static int GenStateIsReservedConstant(SyString *pName)
{
if( pName->nByte == sizeof("null") - 1 ){
if( SyStrnicmp(pName->zString,"null",sizeof("null")-1) == 0 ){
return TRUE;
}else if( SyStrnicmp(pName->zString,"true",sizeof("true")-1) == 0 ){
return TRUE;
}
}else if( pName->nByte == sizeof("false") - 1 ){
if( SyStrnicmp(pName->zString,"false",sizeof("false")-1) == 0 ){
return TRUE;
}
}
/* Not a reserved constant */
return FALSE;
}
/*
* Compile the 'const' statement.
* According to the PHP language reference
* A constant is an identifier (name) for a simple value. As the name suggests, that value
* cannot change during the execution of the script (except for magic constants, which aren't actually constants).
* A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.
* The name of a constant follows the same rules as any label in PHP. A valid constant name starts
* with a letter or underscore, followed by any number of letters, numbers, or underscores.
* As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
* Syntax
* You can define a constant by using the define()-function or by using the const keyword outside
* a class definition. Once a constant is defined, it can never be changed or undefined.
* You can get the value of a constant by simply specifying its name. Unlike with variables
* you should not prepend a constant with a $. You can also use the function constant() to read
* a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants()
* to get a list of all defined constants.
*
* Symisc eXtension.
* PH7 allow any complex expression to be associated with the constant while the zend engine
* would allow only simple scalar value.
* Example
* const HELLO = "Welcome "." guest ".rand_str(3); //Valid under PH7/Generate error using the zend engine
* Refer to the official documentation for more information on this feature.
*/
static sxi32 PH7_CompileConstant(ph7_gen_state *pGen)
{
SySet *pConsCode,*pInstrContainer;
sxu32 nLine = pGen->pIn->nLine;
SyString *pName;
sxi32 rc;
pGen->pIn++; /* Jump the 'const' keyword */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SSTR|PH7_TK_DSTR|PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){
/* Invalid constant name */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Invalid constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Peek constant name */
pName = &pGen->pIn->sData;
/* Make sure the constant name isn't reserved */
if( GenStateIsReservedConstant(pName) ){
/* Reserved constant */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Cannot redeclare a reserved constant '%z'",pName);
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++;
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_EQUAL /* '=' */) == 0 ){
/* Invalid statement*/
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Expected '=' after constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++; /*Jump the equal sign */
/* Allocate a new constant value container */
pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(SySet));
if( pConsCode == 0 ){
PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 engine is running out of memory");
return SXERR_ABORT;
}
SySetInit(pConsCode,&pGen->pVm->sAllocator,sizeof(VmInstr));
/* Swap bytecode container */
pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
PH7_VmSetByteCodeContainer(pGen->pVm,pConsCode);
/* Compile constant value */
rc = PH7_CompileExpr(&(*pGen),0,0);
/* Emit the done instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0);
PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
SySetSetUserData(pConsCode,pGen->pVm);
/* Register the constant */
rc = PH7_VmRegisterConstant(pGen->pVm,pName,PH7_VmExpandConstantValue,pConsCode);
if( rc != SXRET_OK ){
SySetRelease(pConsCode);
SyMemBackendPoolFree(&pGen->pVm->sAllocator,pConsCode);
}
return SXRET_OK;
Synchronize:
/* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the 'continue' statement.
* According to the PHP language reference
* continue is used within looping structures to skip the rest of the current loop iteration
* and continue execution at the condition evaluation and then the beginning of the next
* iteration.
* Note: Note that in PHP the switch statement is considered a looping structure for
* the purposes of continue.
* continue accepts an optional numeric argument which tells it how many levels
* of enclosing loops it should skip to the end of.
* Note:
* continue 0; and continue 1; is the same as running continue;.
*/
static sxi32 PH7_CompileContinue(ph7_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'continue' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)PH7_TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Point to the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP,iLevel);
if( pLoop == 0 ){
/* Illegal continue */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"A 'continue' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx = 0;
if( pLoop->iFlags & GEN_BLOCK_SWITCH ){
/* According to the PHP language reference manual
* Note that unlike some other languages, the continue statement applies to switch
* and acts similar to break. If you have a switch inside a loop and wish to continue
* to the next iteration of the outer loop, use continue 2.
*/
rc = PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nInstrIdx);
if( rc == SXRET_OK ){
GenStateNewJumpFixup(pLoop,PH7_OP_JMP,nInstrIdx);
}
}else{
/* Emit the unconditional jump to the beginning of the target loop */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pLoop->nFirstInstr,0,&nInstrIdx);
if( pLoop->bPostContinue == TRUE ){
JumpFixup sJumpFix;
/* Post-continue */
sJumpFix.nJumpType = PH7_OP_JMP;
sJumpFix.nInstrIdx = nInstrIdx;
SySetPut(&pLoop->aPostContFix,(const void *)&sJumpFix);
}
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){
/* Not so fatal,emit a warning only */
PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"Expected semi-colon ';' after 'continue' statement");
}
/* Statement successfully compiled */
return SXRET_OK;
}
/*
* Compile the 'break' statement.
* According to the PHP language reference
* break ends execution of the current for, foreach, while, do-while or switch
* structure.
* break accepts an optional numeric argument which tells it how many nested
* enclosing structures are to be broken out of.
*/
static sxi32 PH7_CompileBreak(ph7_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'break' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)PH7_TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Extract the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP,iLevel);
if( pLoop == 0 ){
/* Illegal break */
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"A 'break' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx;
rc = PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nInstrIdx);
if( rc == SXRET_OK ){
/* Fix the jump later when the jump destination is resolved */
GenStateNewJumpFixup(pLoop,PH7_OP_JMP,nInstrIdx);
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){
/* Not so fatal,emit a warning only */
PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"Expected semi-colon ';' after 'break' statement");
}
/* 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 sLabel;
/* Make sure the label does not occur inside a loop or a try{}catch(); block */
pBlock = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP|GEN_BLOCK_EXCEPTION,0);
if( pBlock ){
sxi32 rc;
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,
"Label '%z' inside loop or try/catch block is disallowed",&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}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,"Fatal, PH7 is running out of memory");
return SXERR_ABORT;
}
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;
}
/* 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 */
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto: expecting a 'label_name'");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
if( (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_ID)) == 0 ){
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto: Invalid label name: '%z'",&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
}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,"Fatal, PH7 is running out of memory");
return SXERR_ABORT;
}
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 ){
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto inside try/catch block is disallowed");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
}
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,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 PHP chunk that will be processed shortly.
* Return SXRET_OK on success. Any other return value indicates
* failure.
*/
static sxi32 GenStateNextChunk(ph7_gen_state *pGen)
{
ph7_value *pRawObj; /* Raw chunk [i.e: HTML,XML...] */
sxu32 nRawObj;
sxu32 nObjIdx;
/* Consume raw chunks verbatim without any processing until we get
* a PHP block.
*/
Consume:
nRawObj = nObjIdx = 0;
while( pGen->pRawIn < pGen->pRawEnd && pGen->pRawIn->nType != PH7_TOKEN_PHP ){
pRawObj = PH7_ReserveConstObj(pGen->pVm,&nObjIdx);
if( pRawObj == 0 ){
PH7_GenCompileError(pGen,E_ERROR,1,"Fatal, PH7 engine is running out of memory");
return SXERR_ABORT;
}
/* Mark as constant and emit the load constant instruction */
PH7_MemObjInitFromString(pGen->pVm,pRawObj,&pGen->pRawIn->sData);
PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nObjIdx,0,0);
++nRawObj;
pGen->pRawIn++; /* Next chunk */
}
if( nRawObj > 0 ){
/* Emit the consume instruction */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_CONSUME,nRawObj,0,0,0);
}
if( pGen->pRawIn < pGen->pRawEnd ){
SySet *pTokenSet = pGen->pTokenSet;
/* Reset the token set */
SySetReset(pTokenSet);
/* Tokenize input */
PH7_TokenizePHP(SyStringData(&pGen->pRawIn->sData),SyStringLength(&pGen->pRawIn->sData),
pGen->pRawIn->nLine,pTokenSet);
/* Point to the fresh token stream */
pGen->pIn = (SyToken *)SySetBasePtr(pTokenSet);
pGen->pEnd = &pGen->pIn[SySetUsed(pTokenSet)];
/* Advance the stream cursor */
pGen->pRawIn++;
/* TICKET 1433-011 */
if( pGen->pIn < pGen->pEnd && ( pGen->pIn->nType & PH7_TK_EQUAL ) ){
static const sxu32 nKeyID = PH7_TKWRD_ECHO;
sxi32 rc;
/* Refer to TICKET 1433-009 */
pGen->pIn->nType = PH7_TK_KEYWORD;
pGen->pIn->pUserData = SX_INT_TO_PTR(nKeyID);
SyStringInitFromBuf(&pGen->pIn->sData,"echo",sizeof("echo")-1);
rc = PH7_CompileExpr(pGen,0,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0);
}
goto Consume;
}
}else{
/* No more chunks to process */
pGen->pIn = pGen->pEnd;
return SXERR_EOF;
}
return SXRET_OK;
}
/*
* Compile a PHP block.
* A block is simply one or more PHP statements and expressions to compile
* optionally delimited by braces {}.
* Return SXRET_OK on success. Any other return value indicates failure
* and this function takes care of generating the appropriate error
* message.
*/
static sxi32 PH7_CompileBlock(
ph7_gen_state *pGen, /* Code generator state */
sxi32 nKeywordEnd /* EOF-keyword [i.e: endif;endfor;...]. 0 (zero) otherwise */
)
{
sxi32 rc;
if( pGen->pIn->nType & PH7_TK_OCB /* '{' */ ){
sxu32 nLine = pGen->pIn->nLine;
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_STD,PH7_VmInstrLength(pGen->pVm),0,0);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
pGen->pIn++;
/* Compile until we hit the closing braces '}' */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
rc = GenStateNextChunk(&(*pGen));
if (rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc == SXERR_EOF ){
/* No more token to process. Missing closing braces */
PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Missing closing braces '}'");
break;
}
}
if( pGen->pIn->nType & PH7_TK_CCB/*'}'*/ ){
/* Closing braces found,break immediately*/
pGen->pIn++;
break;
}
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
GenStateLeaveBlock(&(*pGen),0);
}else if( (pGen->pIn->nType & PH7_TK_COLON /* ':' */) && nKeywordEnd > 0 ){
pGen->pIn++;
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_STD,PH7_VmInstrLength(pGen->pVm),0,0);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Compile until we hit the EOF-keyword [i.e: endif;endfor;...] */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
rc = GenStateNextChunk(&(*pGen));
if (rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc == SXERR_EOF || pGen->pIn >= pGen->pEnd ){
/* No more token to process */
if( rc == SXERR_EOF ){
PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pEnd[-1].nLine,
"Missing 'endfor;','endwhile;','endswitch;' or 'endforeach;' keyword");
}
break;
}
}
if( pGen->pIn->nType & PH7_TK_KEYWORD ){
sxi32 nKwrd;
/* Keyword found */
nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
if( nKwrd == nKeywordEnd ||
(nKeywordEnd == PH7_TKWRD_ENDIF && (nKwrd == PH7_TKWRD_ELSE || nKwrd == PH7_TKWRD_ELIF)) ){
/* Delimiter keyword found,break */
if( nKwrd != PH7_TKWRD_ELSE && nKwrd != PH7_TKWRD_ELIF ){
pGen->pIn++; /* endif;endswitch... */
}
break;
}
}
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
GenStateLeaveBlock(&(*pGen),0);
}else{
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Jump trailing semi-colons ';' */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the gentle 'while' statement.
* According to the PHP language reference
* while loops are the simplest type of loop in PHP.They behave just like their C counterparts.
* The basic form of a while statement is:
* while (expr)
* statement
* The meaning of a while statement is simple. It tells PHP to execute the nested statement(s)
* repeatedly, as long as the while expression evaluates to TRUE. The value of the expression
* is checked each time at the beginning of the loop, so even if this value changes during
* the execution of the nested statement(s), execution will not stop until the end of the iteration
* (each time PHP runs the statements in the loop is one iteration). Sometimes, if the while
* expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once.
* Like with the if statement, you can group multiple statements within the same while loop by surrounding
* a group of statements with curly braces, or by using the alternate syntax:
* while (expr):
* statement
* endwhile;
*/
static sxi32 PH7_CompileWhile(ph7_gen_state *pGen)
{
GenBlock *pWhileBlock = 0;
SyToken *pTmp,*pEnd = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'while' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pWhileBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the condition */
PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected expression after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
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 */
while(pGen->pIn < pEnd ){
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
/* Synchronize pointers */
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
/* Emit the false jump */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pWhileBlock,PH7_OP_JZ,nFalseJump);
/* Compile the loop body */
rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDWHILE);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Emit the unconditional jump to the start of the loop */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pWhileBlock->nFirstInstr,0,0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pWhileBlock,-1,PH7_VmInstrLength(pGen->pVm));
/* Release the loop block */
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 ugly do..while() statement.
* According to the PHP language reference
* do-while loops are very similar to while loops, except the truth expression is checked
* at the end of each iteration instead of in the beginning. The main difference from regular
* while loops is that the first iteration of a do-while loop is guaranteed to run
* (the truth expression is only checked at the end of the iteration), whereas it may not
* necessarily run with a regular while loop (the truth expression is checked at the beginning
* of each iteration, if it evaluates to FALSE right from the beginning, the loop execution
* would end immediately).
* There is just one syntax for do-while loops:
* <?php
* $i = 0;
* do {
* echo $i;
* } while ($i > 0);
* ?>
*/
static sxi32 PH7_CompileDoWhile(ph7_gen_state *pGen)
{
SyToken *pTmp,*pEnd = 0;
GenBlock *pDoBlock = 0;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'do' keyword */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pDoBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Deffer 'continue;' jumps until we compile the block */
pDoBlock->bPostContinue = TRUE;
rc = PH7_CompileBlock(&(*pGen),0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( pGen->pIn < pGen->pEnd ){
nLine = pGen->pIn->nLine;
}
if( pGen->pIn >= pGen->pEnd || pGen->pIn->nType != PH7_TK_KEYWORD ||
SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_WHILE ){
/* Missing 'while' statement */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing 'while' statement after 'do' block");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the 'while' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Delimit the condition */
PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected expression after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Fix post-continue jumps now the jump destination is resolved */
if( SySetUsed(&pDoBlock->aPostContFix) > 0 ){
JumpFixup *aPost;
VmInstr *pInstr;
sxu32 nJumpDest;
sxu32 n;
aPost = (JumpFixup *)SySetBasePtr(&pDoBlock->aPostContFix);
nJumpDest = PH7_VmInstrLength(pGen->pVm);
for( n = 0 ; n < SySetUsed(&pDoBlock->aPostContFix) ; ++n ){
pInstr = PH7_VmGetInstr(pGen->pVm,aPost[n].nInstrIdx);
if( pInstr ){
/* Fix */
pInstr->iP2 = nJumpDest;
}
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
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 */
while(pGen->pIn < pEnd ){
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
/* Emit the true jump to the beginning of the loop */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JNZ,0,pDoBlock->nFirstInstr,0,0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pDoBlock,-1,PH7_VmInstrLength(pGen->pVm));
/* Release the loop block */
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 complex and powerful 'for' statement.
* According to the PHP language reference
* for loops are the most complex loops in PHP. They behave like their C counterparts.
* The syntax of a for loop is:
* for (expr1; expr2; expr3)
* statement
* The first expression (expr1) is evaluated (executed) once unconditionally at
* the beginning of the loop.
* In the beginning of each iteration, expr2 is evaluated. If it evaluates to
* TRUE, the loop continues and the nested statement(s) are executed. If it evaluates
* to FALSE, the execution of the loop ends.
* At the end of each iteration, expr3 is evaluated (executed).
* Each of the expressions can be empty or contain multiple expressions separated by commas.
* In expr2, all expressions separated by a comma are evaluated but the result is taken
* from the last part. expr2 being empty means the loop should be run indefinitely
* (PHP implicitly considers it as TRUE, like C). This may not be as useless as you might
* think, since often you'd want to end the loop using a conditional break statement instead
* of using the for truth expression.
*/
static sxi32 PH7_CompileFor(ph7_gen_state *pGen)
{
SyToken *pTmp,*pPostStart,*pEnd = 0;
GenBlock *pForBlock = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'for' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'for' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Delimit the init-expr;condition;post-expr */
PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"for: Invalid expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
/* Synchronize */
pGen->pIn = pEnd;
if( pGen->pIn < pGen->pEnd ){
pGen->pIn++;
}
return SXRET_OK;
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile initialization expressions if available */
rc = PH7_CompileExpr(&(*pGen),0,0);
/* Pop operand lvalues */
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0);
}
if( (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,
"for: Expected ';' after initialization expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pForBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Deffer continue jumps */
pForBlock->bPostContinue = TRUE;
/* Compile the condition */
rc = PH7_CompileExpr(&(*pGen),0,0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
/* Emit the false jump */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForBlock,PH7_OP_JZ,nFalseJump);
}
if( (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,
"for: Expected ';' after conditionals expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Save the post condition stream */
pPostStart = pGen->pIn;
/* Compile the loop body */
pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */
pGen->pEnd = pTmp;
rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDFOR);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Fix post-continue jumps */
if( SySetUsed(&pForBlock->aPostContFix) > 0 ){
JumpFixup *aPost;
VmInstr *pInstr;
sxu32 nJumpDest;
sxu32 n;
aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix);
nJumpDest = PH7_VmInstrLength(pGen->pVm);
for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){
pInstr = PH7_VmGetInstr(pGen->pVm,aPost[n].nInstrIdx);
if( pInstr ){
/* Fix jump */
pInstr->iP2 = nJumpDest;
}
}
}
/* compile the post-expressions if available */
while( pPostStart < pEnd && (pPostStart->nType & PH7_TK_SEMI) ){
pPostStart++;
}
if( pPostStart < pEnd ){
SyToken *pTmpIn,*pTmpEnd;
SWAP_DELIMITER(pGen,pPostStart,pEnd);
rc = PH7_CompileExpr(&(*pGen),0,0);
if( pGen->pIn < pGen->pEnd ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"for: Expected ')' after post-expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
RE_SWAP_DELIMITER(pGen);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY){
/* Pop operand lvalue */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0);
}
}
/* Emit the unconditional jump to the start of the loop */
PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pForBlock->nFirstInstr,0,0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pForBlock,-1,PH7_VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen,0);
/* Statement successfully compiled */
return SXRET_OK;
}
/* Expression tree validator callback used by the 'foreach' statement.
* Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...]
* are allowed.
*/
static sxi32 GenStateForEachNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot)
{
sxi32 rc = SXRET_OK; /* Assume a valid expression tree */
if( pRoot->xCode != PH7_CompileVariable ){
/* Unexpected expression */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0,
"foreach: Expecting a variable name");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile the 'foreach' statement.
* According to the PHP language reference
* 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
* 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)
* statement
* foreach (array_expression as $key => $value)
* 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
* by one (so on the next loop, you'll be looking at the next element).
* The second form does the same thing, except that the current element's key will be assigned
* to the variable $key on each loop.
* 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;
GenBlock *pForeachBlock = 0;
ph7_foreach_info *pInfo;
sxu32 nFalseJump;
VmInstr *pInstr;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'foreach' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){
/* Syntax error */
rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"foreach: Expected '('");
if( rc == SXERR_ABORT ){
/* Error count limit reached,abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pForeachBlock);