|
/**
|
|
* @PROJECT PH7 Engine for the AerScript Interpreter
|
|
* @COPYRIGHT See COPYING in the top level directory
|
|
* @FILE engine/compiler.c
|
|
* @DESCRIPTION PH7 Engine Compiler for the Aer language
|
|
* @DEVELOPERS Symisc Systems <devel@symisc.net>
|
|
* Rafal Kupiec <belliash@codingworkshop.eu.org>
|
|
* Piotr Likoski <likoski@codingworkshop.eu.org>
|
|
*/
|
|
#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.
|
|
* Return a pointer to that block on success. NULL otherwise.
|
|
*/
|
|
static GenBlock *PH7_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 PH7_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 PH7_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, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Zero the structure */
|
|
SyZero(pBlock, sizeof(GenBlock));
|
|
PH7_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 PH7_GenStateReleaseBlock(GenBlock *pBlock) {
|
|
SySetRelease(&pBlock->aPostContFix);
|
|
SySetRelease(&pBlock->aJumpFix);
|
|
}
|
|
/*
|
|
* Release a block.
|
|
*/
|
|
static void PH7_GenStateFreeBlock(GenBlock *pBlock) {
|
|
ph7_gen_state *pGen = pBlock->pGen;
|
|
PH7_GenStateReleaseBlock(&(*pBlock));
|
|
/* Free the instance */
|
|
SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock);
|
|
}
|
|
/*
|
|
* POP and release a block from the stack of compiled blocks.
|
|
*/
|
|
static sxi32 PH7_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 */
|
|
PH7_GenStateFreeBlock(&(*pBlock));
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Emit a forward jump.
|
|
* Notes on forward jumps
|
|
* Compilation of some Aer 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 PH7_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 Aer 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 PH7_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 = 0;
|
|
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.
|
|
*/
|
|
static sxi32 PH7_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 PH7_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 *PH7_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");
|
|
}
|
|
*pIdx = nIdx;
|
|
return pObj;
|
|
}
|
|
/*
|
|
* Compile a numeric [i.e: integer or real] literal.
|
|
* Notes on the integer type.
|
|
* 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 = PH7_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");
|
|
}
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
|
|
PH7_MemObjToReal(pObj);
|
|
}
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile a single quoted string.
|
|
* 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];
|
|
SXUNUSED(iCompileFlag);
|
|
if(SXRET_OK == PH7_GenStateFindLiteral(&(*pGen), pStr, &nIdx)) {
|
|
/* Already processed,emit the load constant instruction
|
|
* and return.
|
|
*/
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, 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");
|
|
}
|
|
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, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
if(pStr->nByte < 1024) {
|
|
/* Install in the literal table */
|
|
PH7_GenStateInstallLiteral(pGen, pObj, nIdx);
|
|
}
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Process variable expression [i.e: "$var","${var}"] embedded in a double quoted string.
|
|
* When a string is specified in double quotes,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 PH7_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_TokenizeAerScript(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 string.
|
|
*/
|
|
static ph7_value *PH7_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");
|
|
}
|
|
(*pCount)++;
|
|
PH7_MemObjInitFromString(pGen->pVm, pConstObj, 0);
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
return pConstObj;
|
|
}
|
|
/*
|
|
* Compile a double quoted string.
|
|
* Double quoted
|
|
* If the string is enclosed in double-quotes ("), Aer 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 PH7_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 can be served as single-quoted string */
|
|
return PH7_CompileSimpleString(pGen, 0);
|
|
}
|
|
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 = PH7_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 = PH7_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 = PH7_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 = PH7_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, pGen->pIn->nLine, PH7_OP_ADD, iCons, 1, 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 = PH7_GenStateCompileString(&(*pGen));
|
|
SXUNUSED(iCompileFlag); /* cc warning */
|
|
/* Compilation result */
|
|
return rc;
|
|
}
|
|
/*
|
|
* Compile an array entry whether it is a key or a value.
|
|
* Notes on array entries.
|
|
* 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 Aer, which can both
|
|
* contain integer and string indices.
|
|
* A value can be any Aer 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 PH7_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;
|
|
}
|
|
/*
|
|
* Compile the 'array' language construct.
|
|
* An array in Aer 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 nPair = 0;
|
|
sxi32 rc;
|
|
/* Jump the opening and the trailing parenthesis. */
|
|
pGen->pIn++;
|
|
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;
|
|
while(pCur < pGen->pIn) {
|
|
if(pCur->nType & PH7_TK_ARRAY_OP) {
|
|
break;
|
|
}
|
|
pCur++;
|
|
}
|
|
rc = SXERR_EMPTY;
|
|
if(pCur < pGen->pIn) {
|
|
if(&pCur[1] >= pGen->pIn) {
|
|
/* Missing value */
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "array(): Missing entry value");
|
|
}
|
|
/* Compile the expression holding the key */
|
|
rc = PH7_GenStateCompileArrayEntry(&(*pGen), pKey, pCur,
|
|
EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if non-existent*/, 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, pCur->nLine, PH7_OP_LOADC, 0, 0 /* nil index */, 0, 0);
|
|
}
|
|
/* Compile indice value */
|
|
rc = PH7_GenStateCompileArrayEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if non-existent*/, xValidator);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
xValidator = 0;
|
|
nPair++;
|
|
}
|
|
/* Emit the load map instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOAD_MAP, nPair * 2, 0, 0, 0);
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile a closure (anonymous function).
|
|
* Closures (also known as anonymous functions), 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
|
|
* $greet = function($name)
|
|
* {
|
|
* printf("Hello %s\r\n", $name);
|
|
* };
|
|
* $greet('World');
|
|
* $greet('AerScript');
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_CompileClosure(ph7_gen_state *pGen, sxi32 iCompileFlag) {
|
|
ph7_vm_func *pAnonFunc = 0; /* Anonymous function body */
|
|
char zName[512]; /* Unique closure 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;
|
|
sxu32 nType = 0;
|
|
SXUNUSED(iCompileFlag);
|
|
sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
if(nKey & PH7_KEYWORD_BOOL) {
|
|
nType = MEMOBJ_BOOL;
|
|
} else if(nKey & PH7_KEYWORD_CALLBACK) {
|
|
nType = MEMOBJ_CALL;
|
|
} else if(nKey & PH7_KEYWORD_CHAR) {
|
|
nType = MEMOBJ_CHAR;
|
|
} else if(nKey & PH7_KEYWORD_FLOAT) {
|
|
nType = MEMOBJ_REAL;
|
|
} else if(nKey & PH7_KEYWORD_INT) {
|
|
nType = MEMOBJ_INT;
|
|
} else if(nKey & PH7_KEYWORD_MIXED) {
|
|
nType = MEMOBJ_MIXED;
|
|
} else if(nKey & PH7_KEYWORD_OBJECT) {
|
|
nType = MEMOBJ_OBJ;
|
|
} else if(nKey & PH7_KEYWORD_RESOURCE) {
|
|
nType = MEMOBJ_RES;
|
|
} else if(nKey & PH7_KEYWORD_STRING) {
|
|
nType = MEMOBJ_STRING;
|
|
} else if(nKey & PH7_KEYWORD_VOID) {
|
|
nType = MEMOBJ_VOID;
|
|
} else {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid return data type '%z'", &pGen->pIn->sData);
|
|
}
|
|
pGen->pIn++; /* Jump the return data type */
|
|
if(pGen->pIn->nType & PH7_TK_OSB && pGen->pIn[1].nType & PH7_TK_CSB) {
|
|
pGen->pIn += 2;
|
|
nType |= MEMOBJ_HASHMAP;
|
|
}
|
|
if(pGen->pIn->nType & (PH7_TK_ID | PH7_TK_KEYWORD)) {
|
|
pGen->pIn++;
|
|
}
|
|
/* Reserve a constant for the closure */
|
|
pObj = PH7_ReserveConstObj(pGen->pVm, &nIdx);
|
|
if(pObj == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Generate a unique name */
|
|
nLen = SyBufferFormat(zName, sizeof(zName), "{closure_%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), "{closure_%d}", iCnt++);
|
|
}
|
|
SyStringInitFromBuf(&sName, zName, nLen);
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &sName);
|
|
pObj->nType = MEMOBJ_CALL;
|
|
/* Compile the closure body */
|
|
rc = PH7_GenStateCompileFunc(&(*pGen), &sName, 0, TRUE, &pAnonFunc);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
pAnonFunc->nType = nType;
|
|
if(pAnonFunc->iFlags & VM_FUNC_CLOSURE) {
|
|
/* Emit the load closure instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOAD_CLOSURE, 0, 0, pAnonFunc, 0);
|
|
} else {
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
}
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Expression tree validator callback used by the 'define' statement.
|
|
*/
|
|
static sxi32 GenStateDefineNodeValidator(ph7_gen_state *pGen, ph7_expr_node *pRoot) {
|
|
if(pRoot->xCode != PH7_CompileArray && pRoot->xCode != PH7_CompileLiteral && pRoot->xCode != PH7_CompileNumLiteral &&
|
|
pRoot->xCode != PH7_CompileSimpleString && pRoot->xCode != PH7_CompileString) {
|
|
/* Unexpected expression */
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pRoot->pStart ? pRoot->pStart->nLine : 0,
|
|
"Define: Expecting a constant simple value, not expression");
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile a global 'define' construct.
|
|
* A global constant, defined in global scope can be accessible from any place.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_CompileDefine(ph7_gen_state *pGen) {
|
|
SySet *pConsCode, *pInstrContainer;
|
|
SyString *pName;
|
|
sxi32 rc;
|
|
/* Jump the 'define' keyword */
|
|
pGen->pIn++;
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID | PH7_TK_KEYWORD)) == 0) {
|
|
/* Invalid variable name */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Invalid constant name");
|
|
}
|
|
/* Extract constant name */
|
|
pName = &pGen->pIn->sData;
|
|
if(PH7_GenStateIsReservedConstant(pName)) {
|
|
/* Reserved constant */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "define: Cannot redeclare a reserved constant '%z'", pName);
|
|
}
|
|
/* Advance the stream cursor */
|
|
pGen->pIn++;
|
|
/* Allocate a new constant value container */
|
|
pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet));
|
|
if(pConsCode == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
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, GenStateDefineNodeValidator);
|
|
/* Emit the done instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 1, 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 global constant */
|
|
rc = PH7_VmRegisterConstant(pGen->pVm, pName, PH7_VmExpandConstantValue, pConsCode, TRUE);
|
|
if(rc != SXRET_OK) {
|
|
SySetRelease(pConsCode);
|
|
SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode);
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile a node holding a variable declaration.
|
|
* Variables in Aer 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 Aer. 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.
|
|
* Aer 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;
|
|
void *p3;
|
|
SXUNUSED(iCompileFlag);
|
|
/* Jump the dollar sign */
|
|
pGen->pIn++;
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID | PH7_TK_KEYWORD)) == 0) {
|
|
/* Invalid variable name */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name");
|
|
}
|
|
p3 = 0;
|
|
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, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Install in the hashtable */
|
|
SyHashInsert(&pGen->hVar, zName, pName->nByte, zName);
|
|
} else {
|
|
/* Name already available */
|
|
zName = (char *)pEntry->pUserData;
|
|
}
|
|
p3 = (void *)zName;
|
|
/* Emit the load instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADV, 0, 0, p3, 0);
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Load a literal.
|
|
*/
|
|
static sxi32 PH7_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 && SyStrnicmp(pStr->zString, "null", sizeof("NULL") - 1) == 0) {
|
|
/* NULL constant are always indexed at 0 */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, 0, 0, 0);
|
|
return SXRET_OK;
|
|
} else if(pStr->nByte == sizeof("TRUE") - 1 && SyStrnicmp(pStr->zString, "true", sizeof("TRUE") - 1) == 0) {
|
|
/* TRUE constant are always indexed at 1 */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, 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, pGen->pIn->nLine, 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, "PH7 engine is running out-of-memory");
|
|
}
|
|
PH7_MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine);
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
return SXRET_OK;
|
|
} else if((pStr->nByte == sizeof("__FILE__") - 1 &&
|
|
SyMemcmp(pStr->zString, "__FILE__", sizeof("__FILE__") - 1) == 0) ||
|
|
(pStr->nByte == sizeof("__DIR__") - 1 &&
|
|
SyMemcmp(pStr->zString, "__DIR__", sizeof("__DIR__") - 1) == 0)) {
|
|
pObj = PH7_ReserveConstObj(pGen->pVm, &nIdx);
|
|
if(pObj == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
SyString *pFile = (SyString *)SySetPeek(&pGen->pVm->aFiles);
|
|
if(pFile == 0) {
|
|
SyString pMemory;
|
|
SyStringInitFromBuf(&pMemory, "[MEMORY]", (int)sizeof("[MEMORY]") - 1);
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pMemory);
|
|
} else {
|
|
if(pStr->zString[2] == 'F' /* FILE */) {
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, pFile);
|
|
} else {
|
|
SyString pDir;
|
|
if(pFile->nByte > 0) {
|
|
const char *zDir;
|
|
int nLen;
|
|
zDir = PH7_ExtractDirName(pFile->zString, (int)pFile->nByte, &nLen);
|
|
SyStringInitFromBuf(&pDir, zDir, nLen);
|
|
} else {
|
|
SyStringInitFromBuf(&pDir, ".", 1);
|
|
}
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pDir);
|
|
}
|
|
}
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
return SXRET_OK;
|
|
} else if(pStr->nByte == sizeof("__CLASS__") - 1 && SyMemcmp(pStr->zString, "__CLASS__", sizeof("__CLASS__") - 1) == 0) {
|
|
GenBlock *pBlock = pGen->pCurrent;
|
|
while(pBlock && (pBlock->iFlags & GEN_BLOCK_CLASS) == 0) {
|
|
/* Point to the upper block */
|
|
pBlock = pBlock->pParent;
|
|
}
|
|
if(pBlock == 0) {
|
|
/* Called in the global scope,load NULL */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, 0, 0, 0);
|
|
} else {
|
|
/* Extract the target class */
|
|
ph7_class_info *pClassInfo = (ph7_class_info *)pBlock->pUserData;
|
|
pObj = PH7_ReserveConstObj(pGen->pVm, &nIdx);
|
|
if(pObj == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pClassInfo->sName);
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, 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, pGen->pIn->nLine, 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, pGen->pIn->nLine, PH7_OP_LOADC, 0, 0, 0, 0);
|
|
} else {
|
|
pObj = PH7_ReserveConstObj(pGen->pVm, &nIdx);
|
|
if(pObj == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName);
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOADC, 0, nIdx, 0, 0);
|
|
}
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/* Query literal table */
|
|
if(SXRET_OK != PH7_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");
|
|
}
|
|
PH7_MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
|
|
PH7_GenStateInstallLiteral(&(*pGen), pObj, nIdx);
|
|
}
|
|
/* Emit the load constant instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, 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 PH7_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_NOTICE, 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 = PH7_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 = PH7_GenStateResolveNamespaceLiteral(&(*pGen));
|
|
if(rc != SXRET_OK) {
|
|
SXUNUSED(iCompileFlag); /* cc warning */
|
|
return rc;
|
|
}
|
|
/* Node successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Check if the given identifier name is reserved or not.
|
|
* Return TRUE if reserved. FALSE otherwise.
|
|
*/
|
|
static int PH7_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.
|
|
* 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 Aer. 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 *pInstrContainer;
|
|
ph7_constant_info *pConstInfo;
|
|
char *zName;
|
|
sxi32 rc;
|
|
pGen->pIn++; /* Jump the 'const' keyword */
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
|
|
/* Invalid constant name */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "const: Invalid constant name");
|
|
}
|
|
/* Allocate a new instance */
|
|
pConstInfo = (ph7_constant_info *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(ph7_constant_info));
|
|
if(pConstInfo == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Zero the structure */
|
|
SyZero(pConstInfo, sizeof(ph7_constant_info));
|
|
/* Peek constant name */
|
|
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
|
|
if(zName == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Duplicate constant name */
|
|
SyStringInitFromBuf(&pConstInfo->pName, zName, pGen->pIn->sData.nByte);
|
|
/* Make sure the constant name isn't reserved */
|
|
if(PH7_GenStateIsReservedConstant(&pConstInfo->pName)) {
|
|
/* Reserved constant */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "const: Cannot redeclare a reserved constant '%z'", pConstInfo->pName);
|
|
}
|
|
pGen->pIn++;
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_EQUAL /* '=' */) == 0) {
|
|
/* Invalid statement*/
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "const: Expected '=' after constant name");
|
|
}
|
|
pGen->pIn++; /*Jump the equal sign */
|
|
/* Allocate a new constant value container */
|
|
pConstInfo->pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet));
|
|
if(pConstInfo->pConsCode == 0) {
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
SySetInit(pConstInfo->pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
|
|
/* Swap bytecode container */
|
|
pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
|
|
PH7_VmSetByteCodeContainer(pGen->pVm, pConstInfo->pConsCode);
|
|
/* Compile constant value */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, 0);
|
|
/* Emit the done instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 1, 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(pConstInfo->pConsCode, pGen->pVm);
|
|
/* Declare the constant in active frame */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 1, 0, pConstInfo, 0);
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile the 'continue' statement.
|
|
* 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 Aer 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 */
|
|
sxu32 nLine;
|
|
nLine = pGen->pIn->nLine;
|
|
/* Jump the 'continue' keyword */
|
|
pGen->pIn++;
|
|
/* Point to the target loop */
|
|
pLoop = PH7_GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, 0);
|
|
if(pLoop == 0 || pLoop->iFlags & GEN_BLOCK_SWITCH) {
|
|
/* Illegal continue */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop");
|
|
} else {
|
|
sxu32 nInstrIdx = 0;
|
|
if(!pLoop->bPostContinue) {
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
}
|
|
PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx);
|
|
if(pLoop->bPostContinue) {
|
|
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.
|
|
* 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 */
|
|
sxu32 nLine;
|
|
sxi32 rc;
|
|
nLine = pGen->pIn->nLine;
|
|
/* Jump the 'break' keyword */
|
|
pGen->pIn++;
|
|
/* Extract the target loop */
|
|
pLoop = PH7_GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, 0);
|
|
if(pLoop == 0) {
|
|
/* Illegal break */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch");
|
|
} else {
|
|
sxu32 nInstrIdx;
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
rc = PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, 0, 0, &nInstrIdx);
|
|
if(rc == SXRET_OK) {
|
|
/* Fix the jump later when the jump destination is resolved */
|
|
PH7_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 *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(sxu32 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;
|
|
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;
|
|
}
|
|
/* Make sure there will not stay any loop frame opened (i.e. goto inside a loop) */
|
|
PH7_VmEmitInstr(pGen->pVm, sJump.nLine, PH7_OP_LF_STOP, 0, 0, 0, 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
|
|
* failure.
|
|
*/
|
|
static sxi32 PH7_GenStateNextChunk(ph7_gen_state *pGen) {
|
|
SySet *pTokenSet = pGen->pTokenSet;
|
|
/* Reset the token set */
|
|
SySetReset(pTokenSet);
|
|
/* Tokenize input */
|
|
PH7_TokenizeAerScript(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++;
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile an AerScript block.
|
|
* A block is simply one or more Aer 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 rc;
|
|
if(pGen->pIn->nType & PH7_TK_OCB /* '{' */) {
|
|
sxu32 nLine = pGen->pIn->nLine;
|
|
rc = PH7_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 = PH7_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 '}'");
|
|
}
|
|
}
|
|
if(pGen->pIn->nType & PH7_TK_CCB/*'}'*/) {
|
|
/* Closing braces found,break immediately*/
|
|
pGen->pIn++;
|
|
break;
|
|
}
|
|
/* Compile a single statement */
|
|
rc = PH7_GenStateCompileChunk(&(*pGen), PH7_COMPILE_STATEMENT);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
}
|
|
PH7_GenStateLeaveBlock(&(*pGen), 0);
|
|
} else {
|
|
/* Compile a single statement */
|
|
rc = PH7_GenStateCompileChunk(&(*pGen), PH7_COMPILE_STATEMENT);
|
|
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.
|
|
* while loops are the simplest type of loop in Aer. 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 Aer 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 Aer 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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword");
|
|
}
|
|
/* Jump the left parenthesis '(' */
|
|
pGen->pIn++;
|
|
/* Create the loop block */
|
|
rc = PH7_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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword");
|
|
}
|
|
/* 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 */
|
|
if(pGen->pIn < pEnd) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
|
|
}
|
|
/* Synchronize pointers */
|
|
pGen->pIn = &pEnd[1];
|
|
pGen->pEnd = pTmp;
|
|
/* Emit the false jump */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMPZ, 0, 0, 0, &nFalseJump);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pWhileBlock, PH7_OP_JMPZ, nFalseJump);
|
|
/* Emit the OP_LF_START instruction to enter a loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_START, 0, 0, 0, 0);
|
|
/* Compile the loop body */
|
|
rc = PH7_CompileBlock(&(*pGen));
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
/* Emit the unconditional jump to the start of the loop */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0);
|
|
/* Fix all jumps now the destination is resolved */
|
|
PH7_GenStateFixJumps(pWhileBlock, -1, PH7_VmInstrLength(pGen->pVm));
|
|
/* Release the loop block */
|
|
PH7_GenStateLeaveBlock(pGen, 0);
|
|
/* Statement successfully compiled */
|
|
return SXRET_OK;
|
|
/* 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.
|
|
* 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:
|
|
* $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 = PH7_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;
|
|
/* Emit the OP_LF_START instruction to enter a loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_START, 0, 0, 0, 0);
|
|
rc = PH7_CompileBlock(&(*pGen));
|
|
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_KEYWORD_WHILE) {
|
|
/* Missing 'while' statement */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Missing 'while' statement after 'do' block");
|
|
}
|
|
/* Jump the 'while' keyword */
|
|
pGen->pIn++;
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0) {
|
|
/* Syntax error */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword");
|
|
}
|
|
/* 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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword");
|
|
}
|
|
/* 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 */
|
|
if(pGen->pIn < pEnd) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
|
|
}
|
|
pGen->pIn = &pEnd[1];
|
|
pGen->pEnd = pTmp;
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
/* Emit the true jump to the beginning of the loop */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMPNZ, 0, pDoBlock->nFirstInstr, 0, 0);
|
|
/* Fix all jumps now the destination is resolved */
|
|
PH7_GenStateFixJumps(pDoBlock, -1, PH7_VmInstrLength(pGen->pVm));
|
|
/* Release the loop block */
|
|
PH7_GenStateLeaveBlock(pGen, 0);
|
|
/* Statement successfully compiled */
|
|
return SXRET_OK;
|
|
/* 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.
|
|
* for loops are the most complex loops in Aer. 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
|
|
* (Aer 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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Expected '(' after 'for' keyword");
|
|
}
|
|
/* 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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Invalid expression");
|
|
}
|
|
/* Swap token streams */
|
|
pTmp = pGen->pEnd;
|
|
pGen->pEnd = pEnd;
|
|
sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) {
|
|
PH7_CompileVar(&(*pGen));
|
|
}
|
|
/* 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, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
|
|
}
|
|
if((pGen->pIn->nType & PH7_TK_SEMI) == 0) {
|
|
/* Syntax error */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
|
|
"for: Expected ';' after initialization expressions");
|
|
}
|
|
/* Jump the trailing ';' */
|
|
pGen->pIn++;
|
|
/* Create the loop block */
|
|
rc = PH7_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, nLine, PH7_OP_JMPZ, 0, 0, 0, &nFalseJump);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pForBlock, PH7_OP_JMPZ, nFalseJump);
|
|
}
|
|
if((pGen->pIn->nType & PH7_TK_SEMI) == 0) {
|
|
/* Syntax error */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
|
|
"for: Expected ';' after conditionals expressions");
|
|
}
|
|
/* Emit the OP_LF_START instruction to enter a loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_START, 0, 0, 0, 0);
|
|
/* 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));
|
|
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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions");
|
|
}
|
|
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, nLine, PH7_OP_POP, 1, 0, 0, 0);
|
|
}
|
|
}
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
/* Emit the unconditional jump to the start of the loop */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0);
|
|
/* Fix all jumps now the destination is resolved */
|
|
PH7_GenStateFixJumps(pForBlock, -1, PH7_VmInstrLength(pGen->pVm));
|
|
/* Release the loop block */
|
|
PH7_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) {
|
|
if(pRoot->xCode != PH7_CompileVariable) {
|
|
/* Unexpected expression */
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pRoot->pStart ? pRoot->pStart->nLine : 0,
|
|
"foreach: Expecting a variable name");
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile the 'foreach' statement.
|
|
* The foreach construct simply gives an easy way to iterate over arrays. foreach works
|
|
* only on arrays, and will issue an error when you try to use it on a variable
|
|
* with a different data type or an uninitialized variable. There are two syntaxes; the second
|
|
* is a minor but useful extension of the first:
|
|
* foreach ($value in array_expression)
|
|
* statement
|
|
* foreach ($key => $value in array_expression)
|
|
* statement
|
|
* The first form loops over the array given by array_expression. On each loop, the value
|
|
* of the current element is assigned to $value and the internal array pointer is advanced
|
|
* 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.
|
|
*/
|
|
static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) {
|
|
SyToken *pCur, *pTmp, *pEnd = 0;
|
|
GenBlock *pForeachBlock = 0;
|
|
ph7_foreach_info *pInfo;
|
|
sxu32 nFalseJump;
|
|
VmInstr *pInstr;
|
|
char *zName;
|
|
sxu32 nLine;
|
|
sxu32 nKey;
|
|
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 */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('");
|
|
}
|
|
/* Jump the left parenthesis '(' */
|
|
pGen->pIn++;
|
|
/* Create the loop block */
|
|
rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, PH7_VmInstrLength(pGen->pVm), 0, &pForeachBlock);
|
|
if(rc != SXRET_OK) {
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Delimit the expression */
|
|
PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_LPAREN /* '(' */, PH7_TK_RPAREN /* ')' */, &pEnd);
|
|
if(pGen->pIn == pEnd || pEnd >= pGen->pEnd) {
|
|
/* Empty expression */
|
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression");
|
|
}
|
|
/* Create the foreach context */
|
|
pInfo = (ph7_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_foreach_info));
|
|
if(pInfo == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
/* Zero the structure */
|
|
SyZero(pInfo, sizeof(ph7_foreach_info));
|
|
|
|
pCur = pGen->pIn;
|
|
while(pCur < pEnd && (pCur->nType & PH7_TK_ARRAY_OP) == 0) {
|
|
pCur++;
|
|
}
|
|
if(pCur < pEnd) {
|
|
if(pCur <= pGen->pIn) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key");
|
|
}
|
|
pTmp = pGen->pEnd;
|
|
pGen->pEnd = pCur;
|
|
nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) {
|
|
/* Hack to compile variable */
|
|
pGen->pEnd->nType = PH7_TK_SEMI;
|
|
if(pGen->pIn[3].nType != PH7_TK_SEMI) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Improper key declaration");
|
|
}
|
|
/* Extract variable name */
|
|
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn[2].sData.zString, pGen->pIn[2].sData.nByte);
|
|
/* Compile variable declaration */
|
|
PH7_CompileVar(&(*pGen));
|
|
/* Record key name */
|
|
SyStringInitFromBuf(&pInfo->sKey, zName, SyStrlen(zName));
|
|
} else {
|
|
rc = PH7_CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
|
|
if(rc == SXERR_ABORT) {
|
|
/* Expression handler request an operation abort [i.e: Out-of-memory] */
|
|
return SXERR_ABORT;
|
|
}
|
|
pInstr = PH7_VmPopInstr(pGen->pVm);
|
|
if(pInstr->p3) {
|
|
/* Record key name */
|
|
SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3));
|
|
}
|
|
}
|
|
pCur++; /* Jump the array operator */
|
|
pGen->pIn = pCur;
|
|
pGen->pEnd = pTmp;
|
|
}
|
|
pCur = pGen->pIn;
|
|
while(pCur < pEnd) {
|
|
if(pCur->nType & PH7_TK_KEYWORD) {
|
|
sxi32 nKeyword = SX_PTR_TO_INT(pCur->pUserData);
|
|
if(nKeyword == PH7_KEYWORD_IN) {
|
|
/* Break with the first 'in' found */
|
|
break;
|
|
}
|
|
}
|
|
/* Advance the stream cursor */
|
|
pCur++;
|
|
}
|
|
if(pCur <= pGen->pIn) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value");
|
|
} else if(pCur->nType == PH7_TK_RPAREN) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Expecting 'in' keyword");
|
|
}
|
|
/* Swap token streams */
|
|
pTmp = pGen->pEnd;
|
|
pGen->pEnd = pCur;
|
|
nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) {
|
|
/* Hack to compile variable */
|
|
pGen->pEnd->nType = PH7_TK_SEMI;
|
|
if(pGen->pIn[3].nType != PH7_TK_SEMI) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Improper value declaration");
|
|
}
|
|
/* Extract variable name */
|
|
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn[2].sData.zString, pGen->pIn[2].sData.nByte);
|
|
/* Compile variable declaration */
|
|
PH7_CompileVar(&(*pGen));
|
|
/* Record value name */
|
|
SyStringInitFromBuf(&pInfo->sValue, zName, SyStrlen(zName));
|
|
} else {
|
|
/* Compile the expression holding the value name */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
|
|
if(rc == SXERR_ABORT) {
|
|
/* Expression handler request an operation abort [i.e: Out-of-memory] */
|
|
return SXERR_ABORT;
|
|
}
|
|
pInstr = PH7_VmPopInstr(pGen->pVm);
|
|
if(pInstr->p3) {
|
|
/* Record value name */
|
|
SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3));
|
|
}
|
|
}
|
|
if(pGen->pIn < pCur) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData);
|
|
}
|
|
pCur++; /* Jump the 'in' keyword */
|
|
pGen->pIn = pCur;
|
|
if(pGen->pIn >= pEnd) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing array expression");
|
|
}
|
|
pGen->pEnd = pEnd;
|
|
/* Compile the expression holding an array */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, 0);
|
|
if(rc == SXERR_ABORT) {
|
|
/* Don't worry about freeing memory, everything will be released shortly */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Emit the 'FOREACH_INIT' instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pForeachBlock, PH7_OP_FOREACH_INIT, nFalseJump);
|
|
/* Record the first instruction to execute */
|
|
pForeachBlock->nFirstInstr = PH7_VmInstrLength(pGen->pVm);
|
|
/* Emit the FOREACH_STEP instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pForeachBlock, PH7_OP_FOREACH_STEP, nFalseJump);
|
|
/* Emit the OP_LF_START instruction to enter a loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_START, 0, 0, 0, 0);
|
|
/* Compile the loop body */
|
|
pGen->pIn = &pEnd[1];
|
|
pGen->pEnd = pTmp;
|
|
rc = PH7_CompileBlock(&(*pGen));
|
|
if(rc == SXERR_ABORT) {
|
|
/* Don't worry about freeing memory, everything will be released shortly */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Emit the OP_LF_STOP instruction to leave the loop frame */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_LF_STOP, 0, 0, 0, 0);
|
|
/* Emit the unconditional jump to the start of the loop */
|
|
PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0);
|
|
/* Fix all jumps now the destination is resolved */
|
|
PH7_GenStateFixJumps(pForeachBlock, -1, PH7_VmInstrLength(pGen->pVm));
|
|
/* Release the loop block */
|
|
PH7_GenStateLeaveBlock(pGen, 0);
|
|
/* Statement successfully compiled */
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile the if/else statements.
|
|
* The if construct is one of the most important features of many languages, Aer included.
|
|
* It allows for conditional execution of code fragments. Aer features an if structure
|
|
* that is similar to that of C:
|
|
* if (expr)
|
|
* statement
|
|
* else construct:
|
|
* Often you'd want to execute a statement if a certain condition is met, and a different
|
|
* statement if the condition is not met. This is what else is for. else extends an if statement
|
|
* to execute a statement in case the expression in the if statement evaluates to FALSE.
|
|
* For example, the following code would display a is greater than b if $a is greater than
|
|
* $b, and a is NOT greater than b otherwise.
|
|
* The else statement is only executed if the if expression evaluated to FALSE, and if there
|
|
* were any elseif expressions - only if they evaluated to FALSE as well
|
|
* For example, the following code would display a is bigger than b, a equal to b or a is smaller than b:
|
|
* if ($a > $b) {
|
|
* echo "a is bigger than b";
|
|
* } else if ($a == $b) {
|
|
* echo "a is equal to b";
|
|
* } else {
|
|
* echo "a is smaller than b";
|
|
* }
|
|
*/
|
|
static sxi32 PH7_CompileIf(ph7_gen_state *pGen) {
|
|
SyToken *pToken, *pTmp, *pEnd = 0;
|
|
GenBlock *pCondBlock = 0;
|
|
sxu32 nJumpIdx;
|
|
sxu32 nKeyID;
|
|
sxi32 rc;
|
|
/* Jump the 'if' keyword */
|
|
pGen->pIn++;
|
|
pToken = pGen->pIn;
|
|
/* Create the conditional block */
|
|
rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, PH7_VmInstrLength(pGen->pVm), 0, &pCondBlock);
|
|
if(rc != SXRET_OK) {
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Process as many [if/else if/elseif/else] blocks as we can */
|
|
for(;;) {
|
|
if(pToken >= pGen->pEnd || (pToken->nType & PH7_TK_LPAREN) == 0) {
|
|
/* Syntax error */
|
|
PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('");
|
|
}
|
|
/* Jump the left parenthesis '(' */
|
|
pToken++;
|
|
/* Delimit the condition */
|
|
PH7_DelimitNestedTokens(pToken, pGen->pEnd, PH7_TK_LPAREN /* '(' */, PH7_TK_RPAREN /* ')' */, &pEnd);
|
|
if(pToken >= pEnd || (pEnd->nType & PH7_TK_RPAREN) == 0) {
|
|
/* Syntax error */
|
|
PH7_GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'");
|
|
}
|
|
/* Swap token streams */
|
|
SWAP_TOKEN_STREAM(pGen, pToken, pEnd);
|
|
/* Compile the condition */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, 0);
|
|
/* Update token stream */
|
|
if(pGen->pIn < pEnd) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
|
|
}
|
|
pGen->pIn = &pEnd[1];
|
|
pGen->pEnd = pTmp;
|
|
if(rc == SXERR_ABORT) {
|
|
/* Expression handler request an operation abort [i.e: Out-of-memory] */
|
|
return SXERR_ABORT;
|
|
}
|
|
/* Emit the false jump */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMPZ, 0, 0, 0, &nJumpIdx);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pCondBlock, PH7_OP_JMPZ, nJumpIdx);
|
|
/* Compile the body */
|
|
rc = PH7_CompileBlock(&(*pGen));
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
|
|
break;
|
|
}
|
|
/* Ensure that the keyword ID is 'else' */
|
|
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
|
|
if(nKeyID != PH7_KEYWORD_ELSE) {
|
|
break;
|
|
}
|
|
/* Emit the unconditional jump */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMP, 0, 0, 0, &nJumpIdx);
|
|
/* Save the instruction index so we can fix it later when the jump destination is resolved */
|
|
PH7_GenStateNewJumpFixup(pCondBlock, PH7_OP_JMP, nJumpIdx);
|
|
if(nKeyID == PH7_KEYWORD_ELSE) {
|
|
pToken = &pGen->pIn[1];
|
|
if(pToken >= pGen->pEnd || (pToken->nType & PH7_TK_KEYWORD) == 0 ||
|
|
SX_PTR_TO_INT(pToken->pUserData) != PH7_KEYWORD_IF) {
|
|
break;
|
|
}
|
|
pGen->pIn++; /* Jump the 'else' keyword */
|
|
}
|
|
pGen->pIn++; /* Jump the 'if' keyword */
|
|
/* Synchronize cursors */
|
|
pToken = pGen->pIn;
|
|
/* Fix the false jump */
|
|
PH7_GenStateFixJumps(pCondBlock, PH7_OP_JMPZ, PH7_VmInstrLength(pGen->pVm));
|
|
} /* For(;;) */
|
|
/* Fix the false jump */
|
|
PH7_GenStateFixJumps(pCondBlock, PH7_OP_JMPZ, PH7_VmInstrLength(pGen->pVm));
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) &&
|
|
(SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_ELSE)) {
|
|
/* Compile the else block */
|
|
pGen->pIn++;
|
|
rc = PH7_CompileBlock(&(*pGen));
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
}
|
|
}
|
|
nJumpIdx = PH7_VmInstrLength(pGen->pVm);
|
|
/* Fix all unconditional jumps now the destination is resolved */
|
|
PH7_GenStateFixJumps(pCondBlock, PH7_OP_JMP, nJumpIdx);
|
|
/* Release the conditional block */
|
|
PH7_GenStateLeaveBlock(pGen, 0);
|
|
/* Statement successfully compiled */
|
|
return SXRET_OK;
|
|
/* 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 return statement.
|
|
* If called from within a function, the return() statement immediately ends execution
|
|
* of the current function, and returns its argument as the value of the function call.
|
|
* return() will also end the execution of an eval() statement or script file.
|
|
* If called from the global scope, then execution of the current script file is ended.
|
|
* If the current script file was include()ed or require()ed, then control is passed back
|
|
* to the calling file. Furthermore, if the current script file was include()ed, then the value
|
|
* given to return() will be returned as the value of the include() call. If return() is called
|
|
* from within the main script file, then script execution end.
|
|
* Note that since return() is a language construct and not a function, the parentheses
|
|
* surrounding its arguments are not required. It is common to leave them out, and you actually
|
|
* should do so as Aer has less work to do in this case.
|
|
* Note: If no parameter is supplied, then the parentheses must be omitted and NULL will be returned.
|
|
*/
|
|
static sxi32 PH7_CompileReturn(ph7_gen_state *pGen) {
|
|
sxi32 nRet = 0; /* TRUE if there is a return value */
|
|
sxi32 rc;
|
|
/* Jump the 'return' keyword */
|
|
pGen->pIn++;
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) {
|
|
/* Compile the expression */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, 0);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
} else if(rc != SXERR_EMPTY) {
|
|
nRet = 1;
|
|
}
|
|
}
|
|
/* Emit the done instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, nRet, 0, 0, 0);
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile the die/exit language construct.
|
|
* The role of these constructs is to terminate execution of the script.
|
|
* Shutdown functions will always be executed even if exit() is called.
|
|
*/
|
|
static sxi32 PH7_CompileHalt(ph7_gen_state *pGen) {
|
|
sxi32 nExpr = 0;
|
|
sxi32 rc;
|
|
/* Jump the exit keyword */
|
|
pGen->pIn++;
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) {
|
|
/* Compile the expression */
|
|
rc = PH7_CompileExpr(&(*pGen), 0, 0);
|
|
if(rc == SXERR_ABORT) {
|
|
return SXERR_ABORT;
|
|
} else if(rc != SXERR_EMPTY) {
|
|
nExpr = 1;
|
|
}
|
|
}
|
|
/* Emit the HALT instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_HALT, nExpr, 0, 0, 0);
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Compile the var statement.
|
|
*/
|
|
static sxi32 PH7_CompileVar(ph7_gen_state *pGen) {
|
|
sxbool bStatic = FALSE;
|
|
ph7_vm_func_static_var sStatic;
|
|
ph7_vm_func *pFunc;
|
|
GenBlock *pBlock;
|
|
SyString *pName;
|
|
sxu32 nKey, nType;
|
|
char *zDup;
|
|
sxi32 rc;
|
|
nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
if(nKey == PH7_KEYWORD_STATIC) {
|
|
bStatic = TRUE;
|
|
/* Jump the 'static' keyword' */
|
|
pGen->pIn++;
|
|
nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
|
|
}
|
|
if(nKey & PH7_KEYWORD_BOOL) {
|
|
nType = MEMOBJ_BOOL;
|
|
} else if(nKey & PH7_KEYWORD_CALLBACK) {
|
|
nType = MEMOBJ_CALL;
|
|
} else if(nKey & PH7_KEYWORD_CHAR) {
|
|
nType = MEMOBJ_CHAR;
|
|
} else if(nKey & PH7_KEYWORD_FLOAT) {
|
|
nType = MEMOBJ_REAL;
|
|
} else if(nKey & PH7_KEYWORD_INT) {
|
|
nType = MEMOBJ_INT;
|
|
} else if(nKey & PH7_KEYWORD_MIXED) {
|
|
nType = MEMOBJ_MIXED;
|
|
} else if(nKey & PH7_KEYWORD_OBJECT) {
|
|
nType = MEMOBJ_OBJ;
|
|
} else if(nKey & PH7_KEYWORD_RESOURCE) {
|
|
nType = MEMOBJ_RES;
|
|
} else if(nKey & PH7_KEYWORD_STRING) {
|
|
nType = MEMOBJ_STRING;
|
|
} else if(nKey & PH7_KEYWORD_VOID) {
|
|
nType = MEMOBJ_VOID;
|
|
} else {
|
|
/* No data type at all */
|
|
nType = MEMOBJ_NULL;
|
|
}
|
|
/* Jump the data type keyword */
|
|
pGen->pIn++;
|
|
if(nType != MEMOBJ_NULL && (pGen->pIn->nType & PH7_TK_OSB) && &pGen->pIn[1] < pGen->pEnd && (pGen->pIn[1].nType & PH7_TK_CSB)) {
|
|
nType |= MEMOBJ_HASHMAP;
|
|
pGen->pIn += 2;
|
|
}
|
|
for(;;) {
|
|
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR/*'$'*/) == 0 || &pGen->pIn[1] >= pGen->pEnd ||
|
|
(pGen->pIn[1].nType & (PH7_TK_ID | PH7_TK_KEYWORD)) == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
|
|
"Unexpected '%z', expecting variable name",
|
|
&pGen->pIn->sData);
|
|
}
|
|
/* Extract variable name */
|
|
pName = &pGen->pIn[1].sData;
|
|
if(bStatic) {
|
|
/* Extract the enclosing method/closure */
|
|
pBlock = pGen->pCurrent;
|
|
while(pBlock) {
|
|
if(pBlock->iFlags & GEN_BLOCK_FUNC) {
|
|
break;
|
|
}
|
|
/* Point to the upper block */
|
|
pBlock = pBlock->pParent;
|
|
}
|
|
pFunc = (ph7_vm_func *)pBlock->pUserData;
|
|
pGen->pIn += 2; /* Jump the dollar '$' sign and variable name */
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_COMMA/*','*/ | PH7_TK_SEMI/*';'*/ | PH7_TK_EQUAL/*'='*/)) == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
|
|
}
|
|
/* TODO: Check if static variable exists to avoid redeclaration */
|
|
|
|
/* Initialize the structure describing the static variable */
|
|
SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
|
|
sStatic.nIdx = SXU32_HIGH; /* Not yet created */
|
|
/* Duplicate variable name */
|
|
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
|
if(zDup == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte);
|
|
/* Check if we have an expression to compile */
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_EQUAL)) {
|
|
SySet *pInstrContainer;
|
|
pGen->pIn++; /* Jump the equal '=' sign */
|
|
if(nType == MEMOBJ_NULL && pGen->pIn->nType & PH7_TK_OCB) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
|
|
"Cannot initialize an implicitly-typed variable '$%z' with an array initializer", pName);
|
|
}
|
|
/* Swap bytecode container */
|
|
pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
|
|
PH7_VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode);
|
|
/* Compile the expression */
|
|
rc = PH7_CompileExpr(&(*pGen), EXPR_FLAG_COMMA_STATEMENT, 0);
|
|
if(rc == SXERR_EMPTY) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Static variable '$%z' is missing default value", pName);
|
|
}
|
|
/* Emit the done instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 1, 0, 0);
|
|
/* Restore default bytecode container */
|
|
PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
|
|
} else if(nType == MEMOBJ_NULL) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Implicitly-typed variable '$%z' must be initialized", pName);
|
|
}
|
|
/* Set static variable type */
|
|
sStatic.iFlags = nType;
|
|
/* Finally save the compiled static variable in the appropriate container */
|
|
SySetPut(&pFunc->aStatic, (const void *)&sStatic);
|
|
} else {
|
|
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
|
|
if(zDup == 0) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
|
|
}
|
|
void *p3 = (void *) zDup;
|
|
/* Emit OP_DECLARE instruction */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 0, nType, p3, 0);
|
|
/* Pop the l-value */
|
|
PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
|
|
/* Check if we have an expression to compile */
|
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn[2].nType & PH7_TK_EQUAL)) {
|
|
if(nType == MEMOBJ_NULL && pGen->pIn[3].nType & PH7_TK_OCB) {
|
|
PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
|
|
"Cannot initialize an implicitly-typed variable '$%z' with an array initializer", pName);
|
|
}
|
|
/* Compile the expression */
|