5349 lines
		
	
	
		
			192 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			5349 lines
		
	
	
		
			192 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * @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;
 | |
| 		if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 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);
 | |
| 		}
 | |
| 		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 */
 | |
| 				rc = PH7_CompileExpr(&(*pGen), EXPR_FLAG_COMMA_STATEMENT, 0);
 | |
| 				if(rc == SXERR_ABORT) {
 | |
| 					return SXERR_ABORT;
 | |
| 				} else if(rc == SXERR_EMPTY) {
 | |
| 					PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Variable '$%z' is missing default value", pName);
 | |
| 				} else {
 | |
| 					PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
 | |
| 				}
 | |
| 			} else if(nType == MEMOBJ_NULL) {
 | |
| 				PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Implicitly-typed variable '$%z' must be initialized", pName);
 | |
| 			} else {
 | |
| 				pGen->pIn += 2; /* Jump the dollar '$' sign and variable name */
 | |
| 			}
 | |
| 		}
 | |
| 		if(pGen->pIn->nType == PH7_TK_SEMI) {
 | |
| 			break;
 | |
| 		} else if(pGen->pIn->nType & PH7_TK_COMMA) {
 | |
| 			pGen->pIn++;
 | |
| 		} else if(pGen->pIn < pGen->pEnd) {
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 		}
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a namespace statement
 | |
|  *  What are namespaces? In the broadest definition namespaces are a way of encapsulating items.
 | |
|  *  This can be seen as an abstract concept in many places. For example, in any operating system
 | |
|  *  directories serve to group related files, and act as a namespace for the files within them.
 | |
|  *  As a concrete example, the file foo.txt can exist in both directory /home/greg and in /home/other
 | |
|  *  but two copies of foo.txt cannot co-exist in the same directory. In addition, to access the foo.txt
 | |
|  *  file outside of the /home/greg directory, we must prepend the directory name to the file name using
 | |
|  *  the directory separator to get /home/greg/foo.txt. This same principle extends to namespaces in the
 | |
|  *  programming world.
 | |
|  *  In the Aer world, namespaces are designed to solve two problems that authors of libraries and applications
 | |
|  *  encounter when creating re-usable code elements such as classes or functions:
 | |
|  *  Name collisions between code you create, and internal Aer classes/functions/constants or third-party
 | |
|  *  classes/functions/constants.
 | |
|  *  Ability to alias (or shorten) Extra_Long_Names designed to alleviate the first problem, improving
 | |
|  *  readability of source code.
 | |
|  *  Aer Namespaces provide a way in which to group related classes, interfaces, functions and constants.
 | |
|  *  Here is an example of namespace syntax in Aer:
 | |
|  *       namespace my\name; // see "Defining Namespaces" section
 | |
|  *       class MyClass {}
 | |
|  *       function myfunction() {}
 | |
|  *       const MYCONST = 1;
 | |
|  *       $a = new MyClass;
 | |
|  *       $c = new \my\name\MyClass;
 | |
|  *       $a = strlen('hi');
 | |
|  *       $d = namespace\MYCONST;
 | |
|  *       $d = __NAMESPACE__ . '\MYCONST';
 | |
|  *       echo constant($d);
 | |
|  * NOTE
 | |
|  *  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_CompileNamespace(ph7_gen_state *pGen) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	SyToken *pEnd, *pTmp;
 | |
| 	sxi32 rc;
 | |
| 	/* Jump the 'namespace' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_NSSEP | PH7_TK_ID)) == 0) {
 | |
| 		SyToken *pTok = pGen->pIn;
 | |
| 		if(pTok >= pGen->pEnd) {
 | |
| 			pTok--;
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Namespace: Unexpected token '%z'", &pTok->sData);
 | |
| 	}
 | |
| 	/* Ignore the path */
 | |
| 	while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_NSSEP/*'\'*/ | PH7_TK_ID | PH7_TK_KEYWORD))) {
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_OCB/*'{'*/) == 0) {
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine,
 | |
| 								 "Namespace: Unexpected token '%z',expecting '{'", &pGen->pIn->sData);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the leading curly brace */
 | |
| 	pEnd = 0; /* cc warning */
 | |
| 	/* Delimit the interface body */
 | |
| 	PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_OCB/*'{'*/, PH7_TK_CCB/*'}'*/, &pEnd);
 | |
| 	if(pEnd >= pGen->pEnd) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Namespace: Missing '}' after namespace definition");
 | |
| 	}
 | |
| 	/* Swap token stream */
 | |
| 	pTmp = pGen->pEnd;
 | |
| 	pGen->pEnd = pEnd;
 | |
| 	/* Emit a warning */
 | |
| 	PH7_GenCompileError(&(*pGen), E_NOTICE, nLine,
 | |
| 						"Namespace support is disabled in the current release of the PH7(%s) engine", ph7_lib_version());
 | |
| 	rc = PH7_GenStateCompileGlobalScope(pGen);
 | |
| 	/* Point beyond the interface body */
 | |
| 	pGen->pIn  = &pEnd[1];
 | |
| 	pGen->pEnd = pTmp;
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile the 'using' statement
 | |
|  *  The ability to refer to an external fully qualified name with an alias or importing
 | |
|  *  is an important feature of namespaces. This is similar to the ability of unix-based
 | |
|  *  filesystems to create symbolic links to a file or to a directory.
 | |
|  *  Aer namespaces support three kinds of aliasing or importing: aliasing a class name
 | |
|  *  aliasing an interface name, and aliasing a namespace name. Note that importing
 | |
|  *  a function or constant is not supported.
 | |
|  *  In Aer, aliasing is accomplished with the 'use' operator.
 | |
|  * NOTE
 | |
|  *  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_CompileUsing(ph7_gen_state *pGen) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	/* Jump the 'using' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_NSSEP | PH7_TK_ID)) == 0) {
 | |
| 		SyToken *pTok = pGen->pIn;
 | |
| 		if(pTok >= pGen->pEnd) {
 | |
| 			pTok--;
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Using: Unexpected token '%z'", &pTok->sData);
 | |
| 	}
 | |
| 	/* Assemble one or more real namespace path */
 | |
| 	for(;;) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			break;
 | |
| 		}
 | |
| 		/* Ignore the path */
 | |
| 		while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_NSSEP | PH7_TK_ID))) {
 | |
| 			pGen->pIn++;
 | |
| 		}
 | |
| 		if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA/*','*/)) {
 | |
| 			pGen->pIn++; /* Jump the comma and process the next path */
 | |
| 		} else {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) {
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "using statement: Unexpected token '%z',expecting ';'",
 | |
| 								 &pGen->pIn->sData);
 | |
| 	}
 | |
| 	/* Emit a notice */
 | |
| 	PH7_GenCompileError(&(*pGen), E_NOTICE, nLine,
 | |
| 						"Namespace support is disabled in the current release of the PH7(%s) engine",
 | |
| 						ph7_lib_version()
 | |
| 					   );
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile the 'import' statement
 | |
|  */
 | |
| static sxi32 PH7_CompileImport(ph7_gen_state *pGen) {
 | |
| 	char *zModule;
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	/* Jump the 'import' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SSTR | PH7_TK_DSTR)) == 0) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			pGen->pIn--;
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Include: Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 	}
 | |
| 	zModule = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) {
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Include: Unexpected token '%z', expecting ';'",
 | |
| 								&pGen->pIn->sData);
 | |
| 	}
 | |
| 	PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_IMPORT, 0, 0, zModule, 0);
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile the 'include' and 'require' statements
 | |
|  */
 | |
| static sxi32 PH7_CompileInclude(ph7_gen_state *pGen) {
 | |
| 	char *zFile;
 | |
| 	sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData));
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	sxi32 iP1 = (nKey == PH7_KEYWORD_REQUIRE) ? 1 : 0;
 | |
| 	/* Jump the 'include' or 'require' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SSTR | PH7_TK_DSTR)) == 0) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			pGen->pIn--;
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Include: Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 	}
 | |
| 	zFile = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) {
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "Include: Unexpected token '%z', expecting ';'",
 | |
| 								&pGen->pIn->sData);
 | |
| 	}
 | |
| 	PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INCLUDE, iP1, 0, zFile, 0);
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Process default argument values. That is,a function may define C++-style default value
 | |
|  * as follows:
 | |
|  * function makecoffee($type = "cappuccino")
 | |
|  * {
 | |
|  *   return "Making a cup of $type.\n";
 | |
|  * }
 | |
|  * Symisc eXtension.
 | |
|  *  1 -) Default arguments value can be any complex expression [i.e: function call,anonymous
 | |
|  *      functions,array member,..] unlike the zend which would allow only single scalar value.
 | |
|  *      Example: Work only with PH7,generate error under zend
 | |
|  *      function test($a = 'Hello'.'World: '.rand_str(3))
 | |
|  *      {
 | |
|  *       var_dump($a);
 | |
|  *      }
 | |
|  *     //call test without args
 | |
|  *      test();
 | |
|  * 2 -) Full type hinting: (Arguments are automatically casted to the desired type)
 | |
|  *      Example:
 | |
|  *           function a(string $a){} function b(int $a,string $c,float $d){}
 | |
|  * 3 -) Function overloading!!
 | |
|  *      Example:
 | |
|  *      function foo($a) {
 | |
|  *   	  return $a.PHP_EOL;
 | |
|  *	    }
 | |
|  *	    function foo($a, $b) {
 | |
|  *   	  return $a + $b;
 | |
|  *	    }
 | |
|  *	    echo foo(5); // Prints "5"
 | |
|  *	    echo foo(5, 2); // Prints "7"
 | |
|  *      // Same arg
 | |
|  *	   function foo(string $a)
 | |
|  *	   {
 | |
|  *	     echo "a is a string\n";
 | |
|  *	     var_dump($a);
 | |
|  *	   }
 | |
|  *	  function foo(int $a)
 | |
|  *	  {
 | |
|  *	    echo "a is integer\n";
 | |
|  *	    var_dump($a);
 | |
|  *	  }
 | |
|  *	  function foo(array $a)
 | |
|  *	  {
 | |
|  * 	    echo "a is an array\n";
 | |
|  * 	    var_dump($a);
 | |
|  *	  }
 | |
|  *	  foo('This is a great feature'); // a is a string [first foo]
 | |
|  *	  foo(52); // a is integer [second foo]
 | |
|  *    foo(array(14,__TIME__,__DATE__)); // a is an array [third foo]
 | |
|  * Please refer to the official documentation for more information on the powerful extension
 | |
|  * introduced by the PH7 engine.
 | |
|  */
 | |
| static sxi32 PH7_GenStateProcessArgValue(ph7_gen_state *pGen, ph7_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) {
 | |
| 	SyToken *pTmpIn, *pTmpEnd;
 | |
| 	SySet *pInstrContainer;
 | |
| 	sxi32 rc;
 | |
| 	/* Swap token stream */
 | |
| 	SWAP_DELIMITER(pGen, pIn, pEnd);
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode);
 | |
| 	/* Compile the expression holding the argument 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);
 | |
| 	RE_SWAP_DELIMITER(pGen);
 | |
| 	if(rc == SXERR_ABORT) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Collect function arguments one after one.
 | |
|  * Information may be passed to functions via the argument list, which is a comma-delimited
 | |
|  * list of expressions.
 | |
|  * Aer supports passing arguments by value (the default), passing by reference
 | |
|  * and default argument values. Variable-length argument lists are also supported,
 | |
|  * see also the function references for func_num_args(), func_get_arg(), and func_get_args()
 | |
|  * for more information.
 | |
|  * Example #1 Passing arrays to functions
 | |
|  * function takes_array($input)
 | |
|  * {
 | |
|  *    echo "$input[0] + $input[1] = ", $input[0]+$input[1];
 | |
|  * }
 | |
|  * Making arguments be passed by reference
 | |
|  * By default, function arguments are passed by value (so that if the value of the argument
 | |
|  * within the function is changed, it does not get changed outside of the function).
 | |
|  * To allow a function to modify its arguments, they must be passed by reference.
 | |
|  * To have an argument to a function always passed by reference, prepend an ampersand (&)
 | |
|  * to the argument name in the function definition:
 | |
|  * Example #2 Passing function parameters by reference
 | |
|  * function add_some_extra(&$string)
 | |
|  * {
 | |
|  *   $string .= 'and something extra.';
 | |
|  * }
 | |
|  * $str = 'This is a string, ';
 | |
|  * add_some_extra($str);
 | |
|  * echo $str;    // outputs 'This is a string, and something extra.'
 | |
|  *
 | |
|  * PH7 have introduced powerful extension including full type hinting,function overloading
 | |
|  * complex argument values.Please refer to the official documentation for more information
 | |
|  * on these extension.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCollectFuncArgs(ph7_vm_func *pFunc, ph7_gen_state *pGen, SyToken *pEnd) {
 | |
| 	ph7_vm_func_arg sArg; /* Current processed argument */
 | |
| 	SyToken *pIn; /* Token stream */
 | |
| 	SyBlob sSig;         /* Function signature */
 | |
| 	char *zDup;          /* Copy of argument name */
 | |
| 	sxi32 rc;
 | |
| 	pIn = pGen->pIn;
 | |
| 	SyBlobInit(&sSig, &pGen->pVm->sAllocator);
 | |
| 	/* Process arguments one after one */
 | |
| 	for(;;) {
 | |
| 		if(pIn >= pEnd) {
 | |
| 			/* No more arguments to process */
 | |
| 			break;
 | |
| 		}
 | |
| 		SyZero(&sArg, sizeof(ph7_vm_func_arg));
 | |
| 		SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
 | |
| 		if(pIn->nType & (PH7_TK_ID | PH7_TK_KEYWORD)) {
 | |
| 			if(pIn->nType & PH7_TK_KEYWORD) {
 | |
| 				sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData));
 | |
| 				if(nKey & PH7_KEYWORD_BOOL) {
 | |
| 					sArg.nType = MEMOBJ_BOOL;
 | |
| 				} else if(nKey & PH7_KEYWORD_CALLBACK) {
 | |
| 					sArg.nType = MEMOBJ_CALL;
 | |
| 				} else if(nKey & PH7_KEYWORD_CHAR) {
 | |
| 					sArg.nType = MEMOBJ_CHAR;
 | |
| 				} else if(nKey & PH7_KEYWORD_FLOAT) {
 | |
| 					sArg.nType = MEMOBJ_REAL;
 | |
| 				} else if(nKey & PH7_KEYWORD_INT) {
 | |
| 					sArg.nType = MEMOBJ_INT;
 | |
| 				} else if(nKey & PH7_KEYWORD_MIXED) {
 | |
| 					sArg.nType = MEMOBJ_MIXED;
 | |
| 				} else if(nKey & PH7_KEYWORD_OBJECT) {
 | |
| 					sArg.nType = MEMOBJ_OBJ;
 | |
| 				} else if(nKey & PH7_KEYWORD_RESOURCE) {
 | |
| 					sArg.nType = MEMOBJ_RES;
 | |
| 				} else if(nKey & PH7_KEYWORD_STRING) {
 | |
| 					sArg.nType = MEMOBJ_STRING;
 | |
| 				} else if(nKey & PH7_KEYWORD_VOID) {
 | |
| 					sArg.nType = MEMOBJ_VOID;
 | |
| 				} else {
 | |
| 					PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
 | |
| 										"Unknown data type name '%z'",
 | |
| 										&pIn->sData);
 | |
| 				}
 | |
| 			} else {
 | |
| 				SyString *pName = &pIn->sData; /* Class name */
 | |
| 				char *zDup;
 | |
| 				/* Argument must be a class instance, record that*/
 | |
| 				zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
 | |
| 				if(zDup) {
 | |
| 					sArg.nType = SXU32_HIGH; /* 0xFFFFFFFF as sentinel */
 | |
| 					SyStringInitFromBuf(&sArg.sClass, zDup, pName->nByte);
 | |
| 				} else {
 | |
| 					/* This should not happen, but fallback to object anyway */
 | |
| 					sArg.nType = MEMOBJ_OBJ;
 | |
| 				}
 | |
| 			}
 | |
| 			pIn++;
 | |
| 			if((pIn->nType & PH7_TK_OSB) && &pIn[1] < pEnd && (pIn[1].nType & PH7_TK_CSB)) {
 | |
| 				sArg.nType |= MEMOBJ_HASHMAP;
 | |
| 				pIn += 2;
 | |
| 			}
 | |
| 		}
 | |
| 		if(pIn >= pEnd) {
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name");
 | |
| 		}
 | |
| 		if(pIn->nType & PH7_TK_AMPER) {
 | |
| 			/* Pass by reference,record that */
 | |
| 			sArg.iFlags = VM_FUNC_ARG_BY_REF;
 | |
| 			pIn++;
 | |
| 		}
 | |
| 		if(pIn >= pEnd || (pIn->nType & PH7_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (PH7_TK_ID | PH7_TK_KEYWORD)) == 0) {
 | |
| 			/* Invalid argument */
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name");
 | |
| 		}
 | |
| 		pIn++; /* Jump the dollar sign */
 | |
| 		if(!sArg.nType) {
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Argument '$%z' is of undefined data type", &pIn->sData);
 | |
| 		}
 | |
| 		/* Copy argument name */
 | |
| 		zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData));
 | |
| 		if(zDup == 0) {
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "PH7 engine is running out-of-memory");
 | |
| 		}
 | |
| 		SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData));
 | |
| 		pIn++;
 | |
| 		if(pIn < pEnd) {
 | |
| 			if(pIn->nType & PH7_TK_EQUAL) {
 | |
| 				SyToken *pDefend;
 | |
| 				sxi32 iNest = 0;
 | |
| 				pIn++; /* Jump the equal sign */
 | |
| 				pDefend = pIn;
 | |
| 				/* Process the default value associated with this argument */
 | |
| 				while(pDefend < pEnd) {
 | |
| 					if((pDefend->nType & PH7_TK_COMMA) && iNest <= 0) {
 | |
| 						break;
 | |
| 					}
 | |
| 					if(pDefend->nType & (PH7_TK_LPAREN/*'('*/ | PH7_TK_OCB/*'{'*/ | PH7_TK_OSB/*[*/)) {
 | |
| 						/* Increment nesting level */
 | |
| 						iNest++;
 | |
| 					} else if(pDefend->nType & (PH7_TK_RPAREN/*')'*/ | PH7_TK_CCB/*'}'*/ | PH7_TK_CSB/*]*/)) {
 | |
| 						/* Decrement nesting level */
 | |
| 						iNest--;
 | |
| 					}
 | |
| 					pDefend++;
 | |
| 				}
 | |
| 				if(pIn >= pDefend) {
 | |
| 					PH7_GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value");
 | |
| 				}
 | |
| 				/* Process default value */
 | |
| 				rc = PH7_GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend);
 | |
| 				if(rc != SXRET_OK) {
 | |
| 					return rc;
 | |
| 				}
 | |
| 				/* Point beyond the default value */
 | |
| 				pIn = pDefend;
 | |
| 			}
 | |
| 			if(pIn < pEnd && (pIn->nType & PH7_TK_COMMA) == 0) {
 | |
| 				PH7_GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData);
 | |
| 			}
 | |
| 			pIn++; /* Jump the trailing comma */
 | |
| 		}
 | |
| 		/* Append argument signature */
 | |
| 		if(sArg.nType > 0) {
 | |
| 			if(SyStringLength(&sArg.sClass) > 0) {
 | |
| 				/* Class name */
 | |
| 				SyBlobAppend(&sSig, SyStringData(&sArg.sClass), SyStringLength(&sArg.sClass));
 | |
| 			} else {
 | |
| 				int c = 'n'; /* cc warning */
 | |
| 				/* Type leading character */
 | |
| 				if(sArg.nType & MEMOBJ_BOOL) {
 | |
| 					/* Bool */
 | |
| 					c = 'b';
 | |
| 				} else if(sArg.nType & MEMOBJ_CALL) {
 | |
| 					/* Callback */
 | |
| 					c = 'a';
 | |
| 				} else if(sArg.nType & MEMOBJ_CHAR) {
 | |
| 					/* Char */
 | |
| 					c = 'c';
 | |
| 				} else if(sArg.nType & MEMOBJ_INT) {
 | |
| 					/* Integer */
 | |
| 					c = 'i';
 | |
| 				} else if(sArg.nType & MEMOBJ_MIXED) {
 | |
| 					/* Mixed */
 | |
| 					c = 'm';
 | |
| 				} else if(sArg.nType & MEMOBJ_OBJ) {
 | |
| 					/* Object */
 | |
| 					c = 'o';
 | |
| 				} else if(sArg.nType & MEMOBJ_REAL) {
 | |
| 					/* Float */
 | |
| 					c = 'f';
 | |
| 				} else if(sArg.nType & MEMOBJ_RES) {
 | |
| 					/* Resource */
 | |
| 					c = 'r';
 | |
| 				} else if(sArg.nType & MEMOBJ_STRING) {
 | |
| 					/* String */
 | |
| 					c = 's';
 | |
| 				} else if(sArg.nType & MEMOBJ_VOID) {
 | |
| 					/* Void */
 | |
| 					c = 'v';
 | |
| 				}
 | |
| 				if(sArg.nType & MEMOBJ_HASHMAP) {
 | |
| 					c = SyToUpper(c);
 | |
| 				}
 | |
| 				SyBlobAppend(&sSig, (const void *)&c, sizeof(char));
 | |
| 			}
 | |
| 		} else {
 | |
| 			/* No type is associated with this parameter which mean
 | |
| 			 * that this function is not condidate for overloading.
 | |
| 			 */
 | |
| 			SyBlobRelease(&sSig);
 | |
| 		}
 | |
| 		/* Save in the argument set */
 | |
| 		SySetPut(&pFunc->aArgs, (const void *)&sArg);
 | |
| 	}
 | |
| 	if(SyBlobLength(&sSig) > 0) {
 | |
| 		/* Save function signature */
 | |
| 		SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig));
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile function [i.e: standard function, anonymous function or closure ] body.
 | |
|  * Return SXRET_OK on success. Any other return value indicates failure
 | |
|  * and this routine takes care of generating the appropriate error message.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileFuncBody(
 | |
| 	ph7_gen_state *pGen,  /* Code generator state */
 | |
| 	ph7_vm_func *pFunc    /* Function state */
 | |
| ) {
 | |
| 	SySet *pInstrContainer; /* Instruction container */
 | |
| 	GenBlock *pBlock;
 | |
| 	sxu32 nGotoOfft;
 | |
| 	sxi32 rc;
 | |
| 	/* Attach the new function */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED | GEN_BLOCK_FUNC, PH7_VmInstrLength(pGen->pVm), pFunc, &pBlock);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	nGotoOfft = SySetUsed(&pGen->aGoto);
 | |
| 	/* Swap bytecode containers */
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode);
 | |
| 	/* Compile the body */
 | |
| 	PH7_CompileBlock(&(*pGen));
 | |
| 	/* Fix exception jumps now the destination is resolved */
 | |
| 	PH7_GenStateFixJumps(pGen->pCurrent, PH7_OP_THROW, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Emit the final return if not yet done */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DONE, 0, 0, 0, 0);
 | |
| 	/* Fix gotos jumps now the destination is resolved */
 | |
| 	if(SXERR_ABORT == GenStateFixGoto(&(*pGen), nGotoOfft)) {
 | |
| 		rc = SXERR_ABORT;
 | |
| 	}
 | |
| 	SySetTruncate(&pGen->aGoto,nGotoOfft);
 | |
| 	/* Restore the default container */
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
 | |
| 	/* Leave function block */
 | |
| 	PH7_GenStateLeaveBlock(&(*pGen), 0);
 | |
| 	if(rc == SXERR_ABORT) {
 | |
| 		/* Don't worry about freeing memory, everything will be released shortly */
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* All done, function body compiled */
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile an AerScript function whether is a Standard or Anonymous function.
 | |
|  *  Function names follow the same rules as other labels in Aer. A valid function 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]*.
 | |
|  *  Functions need not be defined before they are referenced.
 | |
|  *  All functions and classes in Aer have the global scope - they can be called outside
 | |
|  *  a function even if they were defined inside and vice versa.
 | |
|  *  It is possible to call recursive functions in Aer. However avoid recursive function/method
 | |
|  *  calls with over 32-64 recursion levels.
 | |
|  *
 | |
|  * PH7 have introduced powerful extension including full type hinting, function overloading,
 | |
|  * complex argument values and more. Please refer to the official documentation for more information
 | |
|  * on these extension.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileFunc(
 | |
| 	ph7_gen_state *pGen, /* Code generator state */
 | |
| 	SyString *pName,     /* Function name. NULL otherwise */
 | |
| 	sxi32 iFlags,        /* Control flags */
 | |
| 	int bHandleClosure,  /* TRUE if we are dealing with a closure */
 | |
| 	ph7_vm_func **ppFunc /* OUT: function state */
 | |
| ) {
 | |
| 	ph7_vm_func *pFunc;
 | |
| 	SyToken *pEnd;
 | |
| 	sxu32 nLine;
 | |
| 	char *zName;
 | |
| 	sxi32 rc;
 | |
| 	/* Extract line number */
 | |
| 	nLine = pGen->pIn->nLine;
 | |
| 	/* Jump the left parenthesis '(' */
 | |
| 	pGen->pIn++;
 | |
| 	/* Delimit the function signature */
 | |
| 	PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_LPAREN /* '(' */, PH7_TK_RPAREN /* ')' */, &pEnd);
 | |
| 	if(pEnd >= pGen->pEnd) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName);
 | |
| 	}
 | |
| 	/* Create the function state */
 | |
| 	pFunc = (ph7_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(ph7_vm_func));
 | |
| 	if(pFunc == 0) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	/* function ID */
 | |
| 	zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
 | |
| 	if(zName == 0) {
 | |
| 		/* Don't worry about freeing memory, everything will be released shortly */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, 1, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	/* Initialize the function state */
 | |
| 	PH7_VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0);
 | |
| 	if(pGen->pIn < pEnd) {
 | |
| 		/* Collect function arguments */
 | |
| 		rc = PH7_GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd);
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			/* Don't worry about freeing memory, everything will be released shortly */
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Compile function body */
 | |
| 	pGen->pIn = &pEnd[1];
 | |
| 	if(bHandleClosure) {
 | |
| 		ph7_vm_func_closure_env sEnv;
 | |
| 		int got_this = 0; /* TRUE if $this have been seen */
 | |
| 		if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)
 | |
| 				&& SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_USING) {
 | |
| 			sxu32 nLine = pGen->pIn->nLine;
 | |
| 			/* Closure,record environment variable */
 | |
| 			pGen->pIn++;
 | |
| 			if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, nLine, "Closure: Unexpected token. Expecting a left parenthesis '('");
 | |
| 			}
 | |
| 			pGen->pIn++; /* Jump the left parenthesis or any other unexpected token */
 | |
| 			/* Compile until we hit the first closing parenthesis */
 | |
| 			while(pGen->pIn < pGen->pEnd) {
 | |
| 				int iFlags = 0;
 | |
| 				if(pGen->pIn->nType & PH7_TK_RPAREN) {
 | |
| 					pGen->pIn++; /* Jump the closing parenthesis */
 | |
| 					break;
 | |
| 				}
 | |
| 				nLine = pGen->pIn->nLine;
 | |
| 				if(pGen->pIn->nType & PH7_TK_AMPER) {
 | |
| 					/* Pass by reference,record that */
 | |
| 					PH7_GenCompileError(pGen, E_WARNING, nLine,
 | |
| 										"Closure: Pass by reference is disabled in the current release of the PH7 engine,PH7 is switching to pass by value"
 | |
| 									   );
 | |
| 					iFlags = VM_FUNC_ARG_BY_REF;
 | |
| 					pGen->pIn++;
 | |
| 				}
 | |
| 				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, nLine,
 | |
| 											 "Closure: Unexpected token. Expecting a variable name");
 | |
| 				} else {
 | |
| 					SyString *pName;
 | |
| 					char *zDup;
 | |
| 					/* Duplicate variable name */
 | |
| 					pName = &pGen->pIn[1].sData;
 | |
| 					zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
 | |
| 					if(zDup) {
 | |
| 						/* Zero the structure */
 | |
| 						SyZero(&sEnv, sizeof(ph7_vm_func_closure_env));
 | |
| 						sEnv.iFlags = iFlags;
 | |
| 						PH7_MemObjInit(pGen->pVm, &sEnv.sValue);
 | |
| 						SyStringInitFromBuf(&sEnv.sName, zDup, pName->nByte);
 | |
| 						if(!got_this && pName->nByte == sizeof("this") - 1 &&
 | |
| 								SyMemcmp((const void *)zDup, (const void *)"this", sizeof("this") - 1) == 0) {
 | |
| 							got_this = 1;
 | |
| 						}
 | |
| 						/* Save imported variable */
 | |
| 						SySetPut(&pFunc->aClosureEnv, (const void *)&sEnv);
 | |
| 					} else {
 | |
| 						PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 					}
 | |
| 				}
 | |
| 				pGen->pIn += 2; /* $ + variable name or any other unexpected token */
 | |
| 				while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/)) {
 | |
| 					/* Ignore trailing commas */
 | |
| 					pGen->pIn++;
 | |
| 				}
 | |
| 			}
 | |
| 			if(!got_this) {
 | |
| 				/* Make the $this variable [Current processed Object (class instance)]
 | |
| 				 * available to the closure environment.
 | |
| 				 */
 | |
| 				SyZero(&sEnv, sizeof(ph7_vm_func_closure_env));
 | |
| 				sEnv.iFlags = VM_FUNC_ARG_IGNORE; /* Do not install if NULL */
 | |
| 				PH7_MemObjInit(pGen->pVm, &sEnv.sValue);
 | |
| 				SyStringInitFromBuf(&sEnv.sName, "this", sizeof("this") - 1);
 | |
| 				SySetPut(&pFunc->aClosureEnv, (const void *)&sEnv);
 | |
| 			}
 | |
| 			if(SySetUsed(&pFunc->aClosureEnv) > 0) {
 | |
| 				/* Mark as closure */
 | |
| 				pFunc->iFlags |= VM_FUNC_CLOSURE;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	/* Compile the body */
 | |
| 	rc = PH7_GenStateCompileFuncBody(&(*pGen), pFunc);
 | |
| 	if(rc == SXERR_ABORT) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	if(ppFunc) {
 | |
| 		*ppFunc = pFunc;
 | |
| 	}
 | |
| 	rc = SXRET_OK;
 | |
| 	if((pFunc->iFlags & VM_FUNC_CLOSURE) == 0) {
 | |
| 		/* Finally register the function */
 | |
| 		rc = PH7_VmInstallUserFunction(pGen->pVm, pFunc, 0);
 | |
| 	}
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Extract the visibility level associated with a given keyword.
 | |
|  *  Visibility:
 | |
|  *  The visibility of a property or method can be defined by prefixing
 | |
|  *  the declaration with the keywords public, protected or private.
 | |
|  *  Class members declared public can be accessed everywhere.
 | |
|  *  Members declared protected can be accessed only within the class
 | |
|  *  itself and by inherited and parent classes. Members declared as private
 | |
|  *  may only be accessed by the class that defines the member.
 | |
|  */
 | |
| static sxi32 PH7_GetProtectionLevel(sxi32 nKeyword) {
 | |
| 	if(nKeyword == PH7_KEYWORD_PRIVATE) {
 | |
| 		return PH7_CLASS_PROT_PRIVATE;
 | |
| 	} else if(nKeyword == PH7_KEYWORD_PROTECTED) {
 | |
| 		return PH7_CLASS_PROT_PROTECTED;
 | |
| 	}
 | |
| 	/* Assume public by default */
 | |
| 	return PH7_CLASS_PROT_PUBLIC;
 | |
| }
 | |
| /*
 | |
|  * Compile a class constant.
 | |
|  *  Class Constants
 | |
|  *   It is possible to define constant values on a per-class basis remaining
 | |
|  *   the same and unchangeable. Constants differ from normal variables in that
 | |
|  *   you don't use the $ symbol to declare or use them.
 | |
|  *   The value must be a constant expression, not (for example) a variable,
 | |
|  *   a property, a result of a mathematical operation, or a function call.
 | |
|  *   It's also possible for interfaces to have 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:
 | |
|  *   class Test{
 | |
|  *        const MyConst = "Hello"."world: ".rand_str(3); //concatenation operation + Function call
 | |
|  *   };
 | |
|  *   var_dump(TEST::MyConst);
 | |
|  *   Refer to the official documentation for more information on the powerful extension
 | |
|  *   introduced by the PH7 engine to the OO subsystem.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileClassConstant(ph7_gen_state *pGen, sxi32 iProtection, sxi32 iFlags, ph7_class *pClass) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	SySet *pInstrContainer;
 | |
| 	ph7_class_attr *pCons;
 | |
| 	SyString *pName;
 | |
| 	sxi32 rc;
 | |
| 	/* Extract visibility level */
 | |
| 	iProtection = PH7_GetProtectionLevel(iProtection);
 | |
| 	pGen->pIn++; /* Jump the 'const' keyword */
 | |
| loop:
 | |
| 	/* Mark as constant */
 | |
| 	iFlags |= PH7_CLASS_ATTR_CONSTANT;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 		/* Invalid constant name */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid constant name '%z' in class '%z'", &pGen->pIn->sData, pClass->sName);
 | |
| 	}
 | |
| 	/* Peek constant name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	/* Make sure the constant name isn't reserved */
 | |
| 	if(PH7_GenStateIsReservedConstant(pName)) {
 | |
| 		/* Reserved constant name */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Cannot redeclare a reserved constant '%z'", pName);
 | |
| 	}
 | |
| 	/* Advance the stream cursor */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_EQUAL /* '=' */) == 0) {
 | |
| 		/* Invalid declaration */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '=' after class constant %z'", pName);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the equal sign */
 | |
| 	/* Allocate a new class attribute */
 | |
| 	pCons = PH7_NewClassAttr(pGen->pVm, pClass, pName, nLine, iProtection, iFlags, 0);
 | |
| 	if(pCons == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Swap bytecode container */
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &pCons->aByteCode);
 | |
| 	/* Compile constant value.
 | |
| 	 */
 | |
| 	rc = PH7_CompileExpr(&(*pGen), EXPR_FLAG_COMMA_STATEMENT, 0);
 | |
| 	if(rc == SXERR_EMPTY) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Empty constant '%z' value", pName);
 | |
| 	}
 | |
| 	/* Emit the done instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_DONE, 1, 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;
 | |
| 	}
 | |
| 	/* All done,install the constant */
 | |
| 	rc = PH7_ClassInstallAttr(pClass, pCons);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/)) {
 | |
| 		/* Multiple constants declarations [i.e: const min=-1,max = 10] */
 | |
| 		pGen->pIn++; /* Jump the comma */
 | |
| 		if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 			SyToken *pTok = pGen->pIn;
 | |
| 			if(pTok >= pGen->pEnd) {
 | |
| 				pTok--;
 | |
| 			}
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Unexpected token '%z',expecting constant declaration inside class '%z'",
 | |
| 									 &pTok->sData, &pClass->sName);
 | |
| 		} else {
 | |
| 			if(pGen->pIn->nType & PH7_TK_ID) {
 | |
| 				goto loop;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| 	/* Synchronize with the first semi-colon */
 | |
| 	while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0)) {
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	return SXERR_CORRUPT;
 | |
| }
 | |
| /*
 | |
|  * compile a class attribute or properties.
 | |
|  *  Properties
 | |
|  *  Class member variables are called "properties". You may also see them referred
 | |
|  *  to using other terms such as "attributes" or "fields", but for the purposes
 | |
|  *  of this reference we will use "properties". They are defined by using one
 | |
|  *  of the keywords public, protected, or private, followed by a normal variable
 | |
|  *  declaration. This declaration may include an initialization, but this initialization
 | |
|  *  must be a constant value--that is, it must be able to be evaluated at compile time
 | |
|  *  and must not depend on run-time information in order to be evaluated.
 | |
|  * Symisc eXtension.
 | |
|  *  PH7 allow any complex expression to be associated with the attribute while
 | |
|  *  the zend engine would allow only simple scalar value.
 | |
|  *  Example:
 | |
|  *   class Test{
 | |
|  *        public static $myVar = "Hello"."world: ".rand_str(3); //concatenation operation + Function call
 | |
|  *   };
 | |
|  *   var_dump(TEST::myVar);
 | |
|  *   Refer to the official documentation for more information on the powerful extension
 | |
|  *   introduced by the PH7 engine to the OO subsystem.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileClassAttr(ph7_gen_state *pGen, sxi32 iProtection, sxi32 iFlags, sxu32 nType, ph7_class *pClass) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	ph7_class_attr *pAttr;
 | |
| 	SyString *pName;
 | |
| 	sxi32 rc;
 | |
| 	/* Extract visibility level */
 | |
| 	iProtection = PH7_GetProtectionLevel(iProtection);
 | |
| loop:
 | |
| 	pGen->pIn++; /* Jump the dollar sign */
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) {
 | |
| 		/* Invalid attribute name */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid attribute name '%z' in class '%z'", &pGen->pIn->sData, &pClass->sName);
 | |
| 	}
 | |
| 	/* Peek attribute name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	/* Advance the stream cursor */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_EQUAL/*'='*/ | PH7_TK_SEMI/*';'*/ | PH7_TK_COMMA/*','*/)) == 0) {
 | |
| 		/* Invalid declaration */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '=' or ';' after attribute name '%z'", pName);
 | |
| 	}
 | |
| 	/* Allocate a new class attribute */
 | |
| 	pAttr = PH7_NewClassAttr(pGen->pVm, pClass, pName, nLine, iProtection, iFlags, nType);
 | |
| 	if(pAttr == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	if(pGen->pIn->nType & PH7_TK_EQUAL /*'='*/) {
 | |
| 		SySet *pInstrContainer;
 | |
| 		pGen->pIn++; /*Jump the equal sign */
 | |
| 		/* Swap bytecode container */
 | |
| 		pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 		PH7_VmSetByteCodeContainer(pGen->pVm, &pAttr->aByteCode);
 | |
| 		/* Compile attribute value.
 | |
| 		 */
 | |
| 		rc = PH7_CompileExpr(&(*pGen), EXPR_FLAG_COMMA_STATEMENT, 0);
 | |
| 		if(rc == SXERR_EMPTY) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, nLine, "Attribute '%z': Missing default value", pName);
 | |
| 		}
 | |
| 		/* Emit the done instruction */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_DONE, 1, 1, 0, 0);
 | |
| 		PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
 | |
| 	}
 | |
| 	/* All done,install the attribute */
 | |
| 	rc = PH7_ClassInstallAttr(pClass, pAttr);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/)) {
 | |
| 		/* Multiple attribute declarations [i.e: public $var1,$var2=5<<1,$var3] */
 | |
| 		pGen->pIn++; /* Jump the comma */
 | |
| 		if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR/*'$'*/) == 0) {
 | |
| 			SyToken *pTok = pGen->pIn;
 | |
| 			if(pTok >= pGen->pEnd) {
 | |
| 				pTok--;
 | |
| 			}
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Unexpected token '%z',expecting attribute declaration inside class '%z'",
 | |
| 									 &pTok->sData, &pClass->sName);
 | |
| 		} else {
 | |
| 			if(pGen->pIn->nType & PH7_TK_DOLLAR) {
 | |
| 				goto loop;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| 	/* Synchronize with the first semi-colon */
 | |
| 	while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0)) {
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	return SXERR_CORRUPT;
 | |
| }
 | |
| /*
 | |
|  * Compile a class method.
 | |
|  *
 | |
|  * Refer to the official documentation for more information
 | |
|  * on the powerful extension introduced by the PH7 engine
 | |
|  * to the OO subsystem such as full type hinting,method
 | |
|  * overloading and many more.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileClassMethod(
 | |
| 	ph7_gen_state *pGen, /* Code generator state */
 | |
| 	sxu32 nType,         /* Method return data type */
 | |
| 	sxi32 iProtection,   /* Visibility level */
 | |
| 	sxi32 iFlags,        /* Configuration flags */
 | |
| 	int doBody,          /* TRUE to process method body */
 | |
| 	ph7_class *pClass    /* Class this method belongs */
 | |
| ) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	ph7_class_method *pMeth;
 | |
| 	sxi32 iFuncFlags;
 | |
| 	SyString *pName;
 | |
| 	SyToken *pEnd;
 | |
| 	sxi32 rc;
 | |
| 	/* Extract visibility level */
 | |
| 	iProtection = PH7_GetProtectionLevel(iProtection);
 | |
| 	iFuncFlags = 0;
 | |
| 	if(pGen->pIn >= pGen->pEnd) {
 | |
| 		/* Invalid method name */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid method name '%z' in class '%z'", &pGen->pIn->sData, &pClass->sName);
 | |
| 	}
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_AMPER)) {
 | |
| 		/* Return by reference,remember that */
 | |
| 		iFuncFlags |= VM_FUNC_REF_RETURN;
 | |
| 		/* Jump the '&' token */
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID)) == 0) {
 | |
| 		/* Invalid method name */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid method name '%z' in class '%z'", &pGen->pIn->sData, &pClass->sName);
 | |
| 	}
 | |
| 	/* Peek method name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	nLine = pGen->pIn->nLine;
 | |
| 	/* Jump the method name */
 | |
| 	pGen->pIn++;
 | |
| 	if(iFlags & PH7_CLASS_ATTR_VIRTUAL) {
 | |
| 		/* Virtual method */
 | |
| 		if(iProtection == PH7_CLASS_PROT_PRIVATE) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 									 "Virtual method '%z::%z()' cannot be declared private",
 | |
| 									 &pClass->sName, pName);
 | |
| 		}
 | |
| 		if((pClass->iFlags & PH7_CLASS_VIRTUAL) == 0) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 									 "A method '%z::%z()' cannot be declared as virtual in non-virtual class",
 | |
| 									 &pClass->sName, pName);
 | |
| 		}
 | |
| 		/* Assemble method signature only */
 | |
| 		doBody = FALSE;
 | |
| 	}
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after method name '%z::%z()'", &pClass->sName, pName);
 | |
| 	}
 | |
| 	/* Allocate a new class_method instance */
 | |
| 	pMeth = PH7_NewClassMethod(pGen->pVm, pClass, pName, nLine, iProtection, iFlags, iFuncFlags);
 | |
| 	if(pMeth == 0) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Jump the left parenthesis '(' */
 | |
| 	pGen->pIn++;
 | |
| 	pEnd = 0; /* cc warning */
 | |
| 	/* Delimit the method signature */
 | |
| 	PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_LPAREN /* '(' */, PH7_TK_RPAREN /* ')' */, &pEnd);
 | |
| 	if(pEnd >= pGen->pEnd) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after method '%z::%z()' declaration", &pClass->sName, pName);
 | |
| 	}
 | |
| 	if(pGen->pIn < pEnd) {
 | |
| 		/* Collect method arguments */
 | |
| 		rc = PH7_GenStateCollectFuncArgs(&pMeth->sFunc, &(*pGen), pEnd);
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Point beyond method signature */
 | |
| 	pGen->pIn = &pEnd[1];
 | |
| 	if(doBody) {
 | |
| 		if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_OCB /* '{'*/) == 0) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Non-virtual method '%z::%z()' must contain body", &pClass->sName, pName);
 | |
| 		}
 | |
| 		/* Compile method body */
 | |
| 		rc = PH7_GenStateCompileFuncBody(&(*pGen), &pMeth->sFunc);
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* Only method signature is allowed */
 | |
| 		if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI /* ';'*/) == 0) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Interface or virtual method '%z::%z()' cannot contain body", &pClass->sName, pName);
 | |
| 		}
 | |
| 	}
 | |
| 	/* Store method return data type */
 | |
| 	pMeth->sFunc.nType = nType;
 | |
| 	/* All done,install the method */
 | |
| 	rc = PH7_ClassInstallMethod(pClass, pMeth);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Fatal, PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| 	/* Synchronize with the first semi-colon */
 | |
| 	while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0)) {
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	return SXERR_CORRUPT;
 | |
| }
 | |
| /*
 | |
|  * Compile an object interface.
 | |
|  *   Object Interfaces:
 | |
|  *   Object interfaces allow you to create code which specifies which methods
 | |
|  *   a class must implement, without having to define how these methods are handled.
 | |
|  *   Interfaces are defined using the interface keyword, in the same way as a standard
 | |
|  *   class, but without any of the methods having their contents defined.
 | |
|  *   All methods declared in an interface must be public, this is the nature of an interface.
 | |
|  */
 | |
| static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	ph7_class *pClass;
 | |
| 	ph7_class_info *pClassInfo;
 | |
| 	SyToken *pEnd, *pTmp;
 | |
| 	SyString *pName;
 | |
| 	sxi32 nKwrd;
 | |
| 	sxi32 rc;
 | |
| 	sxi32 iP1 = 0;
 | |
| 	/* Jump the 'interface' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid interface name");
 | |
| 	}
 | |
| 	/* Extract interface name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	/* Advance the stream cursor */
 | |
| 	pGen->pIn++;
 | |
| 	/* Obtain a raw class */
 | |
| 	pClass = PH7_NewRawClass(pGen->pVm, pName);
 | |
| 	if(pClass == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Obtain a raw class inheritance storage */
 | |
| 	pClassInfo = PH7_NewClassInfo(pGen->pVm, pName);
 | |
| 	if(pClassInfo == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Mark as an interface */
 | |
| 	pClass->iFlags = PH7_CLASS_INTERFACE;
 | |
| 	if(pGen->pIn < pGen->pEnd  && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
 | |
| 		SyString pBaseName;
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_EXTENDS /* interface b extends a */) {
 | |
| 			/* Extract base interface */
 | |
| 			pGen->pIn++;
 | |
| 			for(;;) {
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 					/* Syntax error */
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 											 "Expected 'interface_name' after 'extends' keyword inside interface '%z'",
 | |
| 											 pName);
 | |
| 				}
 | |
| 				/* Extract base class name */
 | |
| 				char *sName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
 | |
| 				SyStringInitFromBuf(&pBaseName, sName, SyStrlen(sName));
 | |
| 				/* Register inherited class */
 | |
| 				SySetPut(&pClassInfo->sExtends, (const void *)&pBaseName);
 | |
| 				/* Advance the stream cursor */
 | |
| 				pGen->pIn++;
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_COMMA) == 0) {
 | |
| 					break;
 | |
| 				}
 | |
| 				/* Jump the comma operator */
 | |
| 				pGen->pIn++;
 | |
| 			}
 | |
| 			iP1 = 1;
 | |
| 		}
 | |
| 	}
 | |
| 	if(pGen->pIn >= pGen->pEnd  || (pGen->pIn->nType & PH7_TK_OCB /*'{'*/) == 0) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected '{' after interface '%z' definition", pName);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the leading curly brace */
 | |
| 	pEnd = 0; /* cc warning */
 | |
| 	/* Delimit the interface body */
 | |
| 	PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_OCB/*'{'*/, PH7_TK_CCB/*'}'*/, &pEnd);
 | |
| 	if(pEnd >= pGen->pEnd) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Missing '}' after interface '%z' definition", pName);
 | |
| 	}
 | |
| 	/* Swap token stream */
 | |
| 	pTmp = pGen->pEnd;
 | |
| 	pGen->pEnd = pEnd;
 | |
| 	/* Start the parse process
 | |
| 	 *  Only constants and function signatures(without body) are allowed.
 | |
| 	 *  Only 'public' visibility is allowed.
 | |
| 	 */
 | |
| 	for(;;) {
 | |
| 		/* Jump leading/trailing semi-colons */
 | |
| 		while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/)) {
 | |
| 			pGen->pIn++;
 | |
| 		}
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* End of interface body */
 | |
| 			break;
 | |
| 		}
 | |
| 		if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Unexpected token '%z'. Expecting method signature or constant declaration inside interface '%z'",
 | |
| 									 &pGen->pIn->sData, pName);
 | |
| 		}
 | |
| 		/* Extract the current keyword */
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) {
 | |
| 			/* Emit a warning and switch to public visibility */
 | |
| 			PH7_GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "interface: Access type must be public");
 | |
| 			nKwrd = PH7_KEYWORD_PUBLIC;
 | |
| 		}
 | |
| 		if(nKwrd == PH7_KEYWORD_PUBLIC) {
 | |
| 			/* Advance the stream cursor */
 | |
| 			pGen->pIn++;
 | |
| 			if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 										 "Expecting method signature inside interface '%z'", pName);
 | |
| 			}
 | |
| 			/* Extract the keyword */
 | |
| 			nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		}
 | |
| 		if(nKwrd == PH7_KEYWORD_CONST) {
 | |
| 			/* Process constant declaration */
 | |
| 			rc = PH7_GenStateCompileClassConstant(&(*pGen), 0, 0, pClass);
 | |
| 			if(rc != SXRET_OK) {
 | |
| 				if(rc == SXERR_ABORT) {
 | |
| 					return SXERR_ABORT;
 | |
| 				}
 | |
| 				goto done;
 | |
| 			}
 | |
| 		} else {
 | |
| 			sxi32 iFlags = 0;
 | |
| 			if(nKwrd == PH7_KEYWORD_STATIC) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 										 "Cannot declare a static method in an interface '%z'",
 | |
| 										 pName);
 | |
| 			}
 | |
| 			if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 										 "Unexpected token '%z', expecting data type for method signature inside interface '%z'",
 | |
| 										 &pGen->pIn->sData, pName);
 | |
| 			}
 | |
| 			sxu32 nType = 0;
 | |
| 			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_DOLLAR/*'$'*/) {
 | |
| 				PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Attributes cannot be declared inside interface '%z'", pName);
 | |
| 			} else {
 | |
| 				/* Process method signature */
 | |
| 				rc = PH7_GenStateCompileClassMethod(&(*pGen), nType, 0, iFlags, FALSE/* Only method signature*/, pClass);
 | |
| 				if(rc != SXRET_OK) {
 | |
| 					if(rc == SXERR_ABORT) {
 | |
| 						return SXERR_ABORT;
 | |
| 					}
 | |
| 					goto done;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	/* Install the interface */
 | |
| 	rc = PH7_VmInstallClass(pGen->pVm, pClass);
 | |
| 	if(iP1) {
 | |
| 		/* Emit the INTERFACE_INIT instruction only if there is such a need */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INTERFACE_INIT, iP1, 0, pClassInfo, 0);
 | |
| 	}
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| done:
 | |
| 	/* Point beyond the interface body */
 | |
| 	pGen->pIn  = &pEnd[1];
 | |
| 	pGen->pEnd = pTmp;
 | |
| 	return PH7_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a user-defined class.
 | |
|  *  class
 | |
|  *  Basic class definitions begin with the keyword class, followed by a class
 | |
|  *  name, followed by a pair of curly braces which enclose the definitions
 | |
|  *  of the properties and methods belonging to the class.
 | |
|  *  The class name can be any valid label which is a not a Aer reserved word.
 | |
|  *  A valid class 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]*.
 | |
|  *  A class may contain its own constants, variables (called "properties"), and functions
 | |
|  *  (called "methods").
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	ph7_class *pClass;
 | |
| 	ph7_class_info *pClassInfo;
 | |
| 	SyToken *pEnd, *pTmp;
 | |
| 	sxi32 iProtection;
 | |
| 	sxi32 iAttrflags;
 | |
| 	SyString *pName;
 | |
| 	sxi32 nKwrd;
 | |
| 	sxi32 rc;
 | |
| 	sxi32 iP1 = 0;
 | |
| 	sxu32 iP2 = 0;
 | |
| 	/* Jump the 'class' keyword */
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Invalid class name");
 | |
| 	}
 | |
| 	/* Extract class name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	/* Advance the stream cursor */
 | |
| 	pGen->pIn++;
 | |
| 	/* Obtain a raw class */
 | |
| 	pClass = PH7_NewRawClass(pGen->pVm, pName);
 | |
| 	if(pClass == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Obtain a raw class inheritance storage */
 | |
| 	pClassInfo = PH7_NewClassInfo(pGen->pVm, pName);
 | |
| 	if(pClassInfo == 0) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Assume a standalone class */
 | |
| 	if(pGen->pIn < pGen->pEnd  && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
 | |
| 		SyString pBaseName;
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_EXTENDS /* class b extends a */) {
 | |
| 			pGen->pIn++; /* Advance the stream cursor */
 | |
| 			for(;;) {
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 					/* Syntax error */
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 											 "Expected 'class_name' after 'extends' keyword inside class '%z'",
 | |
| 											 pName);
 | |
| 				}
 | |
| 				/* Extract base class name */
 | |
| 				char *sName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
 | |
| 				SyStringInitFromBuf(&pBaseName, sName, SyStrlen(sName));
 | |
| 				/* Register inherited class */
 | |
| 				SySetPut(&pClassInfo->sExtends, (const void *)&pBaseName);
 | |
| 				/* Advance the stream cursor */
 | |
| 				pGen->pIn++;
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_COMMA) == 0) {
 | |
| 					break;
 | |
| 				}
 | |
| 				/* Jump the comma operator */
 | |
| 				pGen->pIn++;
 | |
| 			}
 | |
| 			iP1 = 1;
 | |
| 		}
 | |
| 		if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_IMPLEMENTS) {
 | |
| 			SyString pIntName;
 | |
| 			/* Interface implementation */
 | |
| 			pGen->pIn++; /* Advance the stream cursor */
 | |
| 			for(;;) {
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0) {
 | |
| 					/* Syntax error */
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, nLine,
 | |
| 											 "Expected 'interface_name' after 'implements' keyword inside class '%z' declaration",
 | |
| 											 pName);
 | |
| 				}
 | |
| 				/* Extract interface name */
 | |
| 				char *sName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn->sData.zString, pGen->pIn->sData.nByte);
 | |
| 				SyStringInitFromBuf(&pIntName, sName, SyStrlen(sName));
 | |
| 				/* Register inherited class */
 | |
| 				SySetPut(&pClassInfo->sImplements, (const void *)&pIntName);
 | |
| 				/* Advance the stream cursor */
 | |
| 				pGen->pIn++;
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_COMMA) == 0) {
 | |
| 					break;
 | |
| 				}
 | |
| 				/* Jump the comma */
 | |
| 				pGen->pIn++;
 | |
| 			}
 | |
| 			iP2 = 1;
 | |
| 		}
 | |
| 	}
 | |
| 	if(pGen->pIn >= pGen->pEnd  || (pGen->pIn->nType & PH7_TK_OCB /*'{'*/) == 0) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Expected opening braces '{' after class '%z' declaration", pName);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the leading curly brace */
 | |
| 	pEnd = 0; /* cc warning */
 | |
| 	/* Delimit the class body */
 | |
| 	PH7_DelimitNestedTokens(pGen->pIn, pGen->pEnd, PH7_TK_OCB/*'{'*/, PH7_TK_CCB/*'}'*/, &pEnd);
 | |
| 	if(pEnd >= pGen->pEnd) {
 | |
| 		/* Syntax error */
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "Missing closing braces '}' after class '%z' definition", pName);
 | |
| 	}
 | |
| 	/* Swap token stream */
 | |
| 	pTmp = pGen->pEnd;
 | |
| 	pGen->pEnd = pEnd;
 | |
| 	/* Set the inherited flags */
 | |
| 	pClass->iFlags = iFlags;
 | |
| 	/* Create the class block */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_CLASS, PH7_VmInstrLength(pGen->pVm), pClassInfo, 0);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Start the parse process */
 | |
| 	for(;;) {
 | |
| 		/* Jump leading/trailing semi-colons */
 | |
| 		while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/)) {
 | |
| 			pGen->pIn++;
 | |
| 		}
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* End of class body */
 | |
| 			break;
 | |
| 		}
 | |
| 		if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 									 "Unexpected token '%z'. Expecting attribute or method declaration inside class '%z'",
 | |
| 									 &pGen->pIn->sData, pName);
 | |
| 		}
 | |
| 		/* Assume private visibility */
 | |
| 		iProtection = PH7_KEYWORD_PRIVATE;
 | |
| 		iAttrflags = 0;
 | |
| 		/* Extract the current keyword */
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_PUBLIC || nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) {
 | |
| 			iProtection = nKwrd;
 | |
| 			pGen->pIn++; /* Jump the visibility token */
 | |
| 			if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 										 "Unexpected token '%z'. Expecting attribute or method declaration inside class '%z'",
 | |
| 										 &pGen->pIn->sData, pName);
 | |
| 			}
 | |
| 			/* Extract the keyword */
 | |
| 			nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		}
 | |
| 		if(nKwrd == PH7_KEYWORD_CONST) {
 | |
| 			/* Process constant declaration */
 | |
| 			rc = PH7_GenStateCompileClassConstant(&(*pGen), iProtection, iAttrflags, pClass);
 | |
| 			if(rc != SXRET_OK) {
 | |
| 				if(rc == SXERR_ABORT) {
 | |
| 					return SXERR_ABORT;
 | |
| 				}
 | |
| 				goto done;
 | |
| 			}
 | |
| 		} else {
 | |
| 			if(nKwrd == PH7_KEYWORD_STATIC) {
 | |
| 				/* Static method or attribute,record that */
 | |
| 				iAttrflags |= PH7_CLASS_ATTR_STATIC;
 | |
| 				pGen->pIn++; /* Jump the static keyword */
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 											 "Unexpected token '%z',Expecting attribute or method declaration inside class '%z'",
 | |
| 											 &pGen->pIn->sData, pName);
 | |
| 				}
 | |
| 				/* Extract the keyword */
 | |
| 				nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 				if(nKwrd == PH7_KEYWORD_PUBLIC || nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) {
 | |
| 					iProtection = nKwrd;
 | |
| 					pGen->pIn++; /* Jump the visibility token */
 | |
| 				}
 | |
| 				/* Extract the keyword */
 | |
| 				nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 			} else if(nKwrd == PH7_KEYWORD_VIRTUAL) {
 | |
| 				/* Virtual method,record that */
 | |
| 				iAttrflags |= PH7_CLASS_ATTR_VIRTUAL;
 | |
| 				/* Advance the stream cursor */
 | |
| 				pGen->pIn++;
 | |
| 				if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
 | |
| 					nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 					if(nKwrd == PH7_KEYWORD_PUBLIC || nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) {
 | |
| 						iProtection = nKwrd;
 | |
| 						pGen->pIn++; /* Jump the visibility token */
 | |
| 					}
 | |
| 				}
 | |
| 				if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) &&
 | |
| 						SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_STATIC) {
 | |
| 					/* Static method */
 | |
| 					iAttrflags |= PH7_CLASS_ATTR_STATIC;
 | |
| 					pGen->pIn++; /* Jump the static keyword */
 | |
| 				}
 | |
| 				nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ||
 | |
| 						((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) {
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 											 "Unexpected token '%z', expecting method declaration after 'virtual' keyword inside class '%z'",
 | |
| 											 &pGen->pIn->sData, pName);
 | |
| 				}
 | |
| 			} else if(nKwrd == PH7_KEYWORD_FINAL) {
 | |
| 				/* final method ,record that */
 | |
| 				iAttrflags |= PH7_CLASS_ATTR_FINAL;
 | |
| 				pGen->pIn++; /* Jump the final keyword */
 | |
| 				if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
 | |
| 					/* Extract the keyword */
 | |
| 					nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 					if(nKwrd == PH7_KEYWORD_PUBLIC || nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) {
 | |
| 						iProtection = nKwrd;
 | |
| 						pGen->pIn++; /* Jump the visibility token */
 | |
| 					}
 | |
| 				}
 | |
| 				if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) &&
 | |
| 						SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_STATIC) {
 | |
| 					/* Static method */
 | |
| 					iAttrflags |= PH7_CLASS_ATTR_STATIC;
 | |
| 					pGen->pIn++; /* Jump the static keyword */
 | |
| 				}
 | |
| 				nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 				if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ||
 | |
| 						((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) {
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 											 "Unexpected token '%z', expecting method declaration after 'final' keyword inside class '%z'",
 | |
| 											 &pGen->pIn->sData, pName);
 | |
| 				}
 | |
| 			}
 | |
| 			if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 										 "Unexpected token '%z', expecting data type for attribute or method declaration inside class '%z'",
 | |
| 										 &pGen->pIn->sData, pName);
 | |
| 			}
 | |
| 			sxu32 nType = 0;
 | |
| 			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_DOLLAR/*'$'*/) {
 | |
| 				/* Attribute declaration */
 | |
| 				rc = PH7_GenStateCompileClassAttr(&(*pGen), iProtection, iAttrflags, nType, pClass);
 | |
| 			} else {
 | |
| 				/* Process method declaration */
 | |
| 				rc = PH7_GenStateCompileClassMethod(&(*pGen), nType, iProtection, iAttrflags, TRUE, pClass);
 | |
| 			}
 | |
| 			if(rc != SXRET_OK) {
 | |
| 				if(rc == SXERR_ABORT) {
 | |
| 					return SXERR_ABORT;
 | |
| 				}
 | |
| 				goto done;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	/* Install the class */
 | |
| 	rc = PH7_VmInstallClass(pGen->pVm, pClass);
 | |
| 	if(iP1 || iP2) {
 | |
| 		/* Emit the CLASS_INIT instruction only if there is such a need */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_CLASS_INIT, iP1, iP2, pClassInfo, 0);
 | |
| 	}
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| done:
 | |
| 	/* Leave the class block */
 | |
| 	PH7_GenStateLeaveBlock(&(*pGen), 0);
 | |
| 	/* Point beyond the class body */
 | |
| 	pGen->pIn = &pEnd[1];
 | |
| 	pGen->pEnd = pTmp;
 | |
| 	return PH7_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a user-defined virtual class.
 | |
|  *   Aer introduces virtual classes and methods. Classes defined as abstract
 | |
|  *   may not be instantiated, and any class that contains at least one abstract
 | |
|  *   method must also be abstract. Methods defined as abstract simply declare
 | |
|  *   the method's signature - they cannot define the implementation.
 | |
|  *   When inheriting from an abstract class, all methods marked abstract in the parent's
 | |
|  *   class declaration must be defined by the child; additionally, these methods must be
 | |
|  *   defined with the same (or a less restricted) visibility. For example, if the abstract
 | |
|  *   method is defined as protected, the function implementation must be defined as either
 | |
|  *   protected or public, but not private. Furthermore the signatures of the methods must
 | |
|  *   match, i.e. the type hints and the number of required arguments must be the same.
 | |
|  */
 | |
| static sxi32 PH7_CompileFinalVirtualClass(ph7_gen_state *pGen) {
 | |
| 	sxi32 nKwrd, rc;
 | |
| 	sxi32 iFlags = 0;
 | |
| 	for(;;) {
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_FINAL) {
 | |
| 			if(iFlags & PH7_CLASS_FINAL) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Duplicate 'final' modifier");
 | |
| 			}
 | |
| 			iFlags |= PH7_CLASS_FINAL;
 | |
| 		} else if(nKwrd == PH7_KEYWORD_VIRTUAL) {
 | |
| 			if(iFlags & PH7_CLASS_VIRTUAL) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Duplicate 'virtual' modifier");
 | |
| 			}
 | |
| 			iFlags |= PH7_CLASS_VIRTUAL;
 | |
| 		} else if(nKwrd == PH7_KEYWORD_CLASS) {
 | |
| 			break;
 | |
| 		} else {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z' in class declaration", &pGen->pIn->sData);
 | |
| 		}
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	rc = PH7_GenStateCompileClass(&(*pGen), iFlags);
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile a user-defined class.
 | |
|  *   Basic class definitions begin with the keyword class, followed
 | |
|  *   by a class name, followed by a pair of curly braces which enclose
 | |
|  *   the definitions of the properties and methods belonging to the class.
 | |
|  *   A class may contain its own constants, variables (called "properties")
 | |
|  *   and functions (called "methods").
 | |
|  */
 | |
| static sxi32 PH7_CompileClass(ph7_gen_state *pGen) {
 | |
| 	sxi32 rc;
 | |
| 	rc = PH7_GenStateCompileClass(&(*pGen), 0);
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Exception handling.
 | |
|  *    An exception can be thrown, and caught within Aer. Code may be surrounded
 | |
|  *    in a try block, to facilitate the catching of potential exceptions. Each try must have
 | |
|  *    at least one corresponding catch block. Multiple catch blocks can be used to catch
 | |
|  *    different classes of exceptions. Normal execution (when no exception is thrown within
 | |
|  *    the try block, or when a catch matching the thrown exception's class is not present)
 | |
|  *    will continue after that last catch block defined in sequence. Exceptions can be thrown
 | |
|  *    (or re-thrown) within a catch block.
 | |
|  *    When an exception is thrown, code following the statement will not be executed, and Aer
 | |
|  *    will attempt to find the first matching catch block. If an exception is not caught, a Aer
 | |
|  *    Fatal Error will be issued with an "Uncaught Exception ..." message, unless a handler has
 | |
|  *    been defined with set_exception_handler().
 | |
|  *    The thrown object must be an instance of the Exception class or a subclass of Exception.
 | |
|  *    Trying to throw an object that is not will result in a Aer Error.
 | |
|  */
 | |
| /*
 | |
|  * Expression tree validator callback associated with the 'throw' statement.
 | |
|  * Return SXRET_OK if the tree form a valid expression.Any other error
 | |
|  * indicates failure.
 | |
|  */
 | |
| static sxi32 PH7_GenStateThrowNodeValidator(ph7_gen_state *pGen, ph7_expr_node *pRoot) {
 | |
| 	if(pRoot->pOp) {
 | |
| 		if(pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ && pRoot->pOp->iOp != EXPR_OP_NEW  /* new Exception() */
 | |
| 				&& pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */ && pRoot->pOp->iOp != EXPR_OP_DC /* :: */) {
 | |
| 			/* Unexpected expression */
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pRoot->pStart ? pRoot->pStart->nLine : 0,
 | |
| 									 "throw: Expecting an exception class instance");
 | |
| 		}
 | |
| 	} else if(pRoot->xCode != PH7_CompileVariable) {
 | |
| 		/* Unexpected expression */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pRoot->pStart ? pRoot->pStart->nLine : 0,
 | |
| 								 "throw: Expecting an exception class instance");
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a 'throw' statement.
 | |
|  * throw: This is how you trigger an exception.
 | |
|  * Each "throw" block must have at least one "catch" block associated with it.
 | |
|  */
 | |
| static sxi32 PH7_CompileThrow(ph7_gen_state *pGen) {
 | |
| 	sxu32 nLine = pGen->pIn->nLine;
 | |
| 	GenBlock *pBlock;
 | |
| 	sxu32 nIdx;
 | |
| 	sxi32 rc;
 | |
| 	pGen->pIn++; /* Jump the 'throw' keyword */
 | |
| 	/* Compile the expression */
 | |
| 	rc = PH7_CompileExpr(&(*pGen), 0, PH7_GenStateThrowNodeValidator);
 | |
| 	if(rc == SXERR_EMPTY) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, nLine, "throw: Expecting an exception class instance");
 | |
| 	}
 | |
| 	pBlock = pGen->pCurrent;
 | |
| 	/* Point to the top most function or try block and emit the forward jump */
 | |
| 	while(pBlock->pParent) {
 | |
| 		if(pBlock->iFlags & (GEN_BLOCK_EXCEPTION | GEN_BLOCK_FUNC)) {
 | |
| 			break;
 | |
| 		}
 | |
| 		/* Point to the parent block */
 | |
| 		pBlock = pBlock->pParent;
 | |
| 	}
 | |
| 	/* Emit the throw instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_THROW, 0, 0, 0, &nIdx);
 | |
| 	/* Emit the jump */
 | |
| 	PH7_GenStateNewJumpFixup(pBlock, PH7_OP_THROW, nIdx);
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a 'catch' block.
 | |
|  * Catch: A "catch" block retrieves an exception and creates
 | |
|  * an object containing the exception information.
 | |
|  */
 | |
| static sxi32 PH7_CompileCatch(ph7_gen_state *pGen, ph7_exception *pException) {
 | |
| 	ph7_exception_block sCatch;
 | |
| 	SySet *pInstrContainer;
 | |
| 	GenBlock *pCatch;
 | |
| 	SyToken *pToken;
 | |
| 	SyString *pName;
 | |
| 	char *zDup;
 | |
| 	sxi32 rc;
 | |
| 	pGen->pIn++; /* Jump the 'catch' keyword */
 | |
| 	/* Zero the structure */
 | |
| 	SyZero(&sCatch, sizeof(ph7_exception_block));
 | |
| 	/* Initialize fields */
 | |
| 	SySetInit(&sCatch.sByteCode, &pException->pVm->sAllocator, sizeof(VmInstr));
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 /*(*/ ||
 | |
| 			&pGen->pIn[1] >= pGen->pEnd || (pGen->pIn[1].nType & (PH7_TK_ID | PH7_TK_KEYWORD)) == 0) {
 | |
| 		/* Unexpected token, break immediately */
 | |
| 		pToken = pGen->pIn;
 | |
| 		if(pToken >= pGen->pEnd) {
 | |
| 			pToken--;
 | |
| 		}
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, pToken->nLine,
 | |
| 								 "Catch: Unexpected token '%z',expecting class name", &pToken->sData);
 | |
| 	}
 | |
| 	/* Extract the exception class */
 | |
| 	pGen->pIn++; /* Jump the left parenthesis '(' */
 | |
| 	/* Duplicate class name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	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(&sCatch.sClass, zDup, pName->nByte);
 | |
| 	pGen->pIn++;
 | |
| 	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) {
 | |
| 		/* Unexpected token, break immediately */
 | |
| 		pToken = pGen->pIn;
 | |
| 		if(pToken >= pGen->pEnd) {
 | |
| 			pToken--;
 | |
| 		}
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, pToken->nLine,
 | |
| 								 "Catch: Unexpected token '%z',expecting variable name", &pToken->sData);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the dollar sign */
 | |
| 	/* Duplicate instance name */
 | |
| 	pName = &pGen->pIn->sData;
 | |
| 	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(&sCatch.sThis, zDup, pName->nByte);
 | |
| 	pGen->pIn++;
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_RPAREN) == 0 /*)*/) {
 | |
| 		/* Unexpected token, break immediately */
 | |
| 		pToken = pGen->pIn;
 | |
| 		if(pToken >= pGen->pEnd) {
 | |
| 			pToken--;
 | |
| 		}
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, pToken->nLine,
 | |
| 								 "Catch: Unexpected token '%z',expecting right parenthesis ')'", &pToken->sData);
 | |
| 	}
 | |
| 	/* Compile the block */
 | |
| 	pGen->pIn++; /* Jump the right parenthesis */
 | |
| 	/* Create the catch block */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_EXCEPTION, PH7_VmInstrLength(pGen->pVm), 0, &pCatch);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Swap bytecode container */
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &sCatch.sByteCode);
 | |
| 	/* Compile the block */
 | |
| 	PH7_CompileBlock(&(*pGen));
 | |
| 	/* Fix forward jumps now the destination is resolved  */
 | |
| 	PH7_GenStateFixJumps(pCatch, -1, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Emit the DONE instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nType, PH7_OP_DONE, 0, 0, 0, 0);
 | |
| 	/* Leave the block */
 | |
| 	PH7_GenStateLeaveBlock(&(*pGen), 0);
 | |
| 	/* Restore the default container */
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
 | |
| 	/* Install the catch block */
 | |
| 	rc = SySetPut(&pException->sEntry, (const void *)&sCatch);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a 'finally' block.
 | |
|  * A "finally' block will get executed regardless of whether
 | |
|  * or not there is an exception. This comes in very handy when
 | |
|  * it comes to certain housekeeping functions you need to always
 | |
|  * run like closing connections.
 | |
|  */
 | |
| static sxi32 PH7_CompileFinally(ph7_gen_state *pGen, ph7_exception *pException) {
 | |
| 	SySet *pInstrContainer;
 | |
| 	GenBlock *pFinally;
 | |
| 	sxi32 rc;
 | |
| 	pGen->pIn++; /* Jump the 'finally' keyword */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_EXCEPTION, PH7_VmInstrLength(pGen->pVm), 0, &pFinally);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Swap bytecode container */
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &pException->sFinally);
 | |
| 	/* Compile the block */
 | |
| 	PH7_CompileBlock(&(*pGen));
 | |
| 	/* Fix forward jumps now the destination is resolved */
 | |
| 	PH7_GenStateFixJumps(pFinally, -1, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Emit the DONE instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nType, PH7_OP_DONE, 0, 0, 0, 0);
 | |
| 	/* Leave the block */
 | |
| 	PH7_GenStateLeaveBlock(&(*pGen), 0);
 | |
| 	/* Restore the default container */
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a 'try' block.
 | |
|  * A function using an exception should be in a "try" block.
 | |
|  * If the exception does not trigger, the code will continue
 | |
|  * as normal. However if the exception triggers, an exception
 | |
|  * is "thrown".
 | |
|  */
 | |
| static sxi32 PH7_CompileTry(ph7_gen_state *pGen) {
 | |
| 	ph7_exception *pException;
 | |
| 	GenBlock *pTry;
 | |
| 	sxu32 nJmpIdx;
 | |
| 	sxi32 rc;
 | |
| 	/* Create the exception container */
 | |
| 	pException = (ph7_exception *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_exception));
 | |
| 	if(pException == 0) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR,
 | |
| 							pGen->pIn->nLine, "PH7 engine is running out-of-memory");
 | |
| 	}
 | |
| 	/* Zero the structure */
 | |
| 	SyZero(pException, sizeof(ph7_exception));
 | |
| 	/* Initialize fields */
 | |
| 	SySetInit(&pException->sEntry, &pGen->pVm->sAllocator, sizeof(ph7_exception_block));
 | |
| 	SySetInit(&pException->sFinally, &pGen->pVm->sAllocator, sizeof(VmInstr));
 | |
| 	pException->pVm = pGen->pVm;
 | |
| 	/* Create the try block */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_EXCEPTION, PH7_VmInstrLength(pGen->pVm), 0, &pTry);
 | |
| 	if(rc != SXRET_OK) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Emit the 'LOAD_EXCEPTION' instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_LOAD_EXCEPTION, 0, 0, pException, &nJmpIdx);
 | |
| 	/* Fix the jump later when the destination is resolved */
 | |
| 	PH7_GenStateNewJumpFixup(pTry, PH7_OP_LOAD_EXCEPTION, nJmpIdx);
 | |
| 	pGen->pIn++; /* Jump the 'try' keyword */
 | |
| 	/* Compile the block */
 | |
| 	rc = PH7_CompileBlock(&(*pGen));
 | |
| 	if(rc == SXERR_ABORT) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Fix forward jumps now the destination is resolved */
 | |
| 	PH7_GenStateFixJumps(pTry, -1, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Emit the 'POP_EXCEPTION' instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP_EXCEPTION, 0, 0, pException, 0);
 | |
| 	/* Leave the block */
 | |
| 	PH7_GenStateLeaveBlock(&(*pGen), 0);
 | |
| 	/* Compile the catch block */
 | |
| 	if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ||
 | |
| 			(SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_KEYWORD_CATCH &&
 | |
| 			SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_KEYWORD_FINALLY)) {
 | |
| 		SyToken *pTok = pGen->pIn;
 | |
| 		if(pTok >= pGen->pEnd) {
 | |
| 			pTok--; /* Point back */
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		rc = PH7_GenCompileError(&(*pGen), E_ERROR, pTok->nLine,
 | |
| 								 "Try: Unexpected token '%z',expecting 'catch' or 'finally' block", &pTok->sData);
 | |
| 	}
 | |
| 	/* Compile one or more catch blocks */
 | |
| 	if(SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_CATCH) {
 | |
| 		for(;;) {
 | |
| 			if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0
 | |
| 					|| SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_KEYWORD_CATCH) {
 | |
| 				/* No more blocks */
 | |
| 				break;
 | |
| 			}
 | |
| 			/* Compile the catch block */
 | |
| 			rc = PH7_CompileCatch(&(*pGen), pException);
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				return SXERR_ABORT;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if(SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_KEYWORD_FINALLY) {
 | |
| 		rc = PH7_CompileFinally(&(*pGen), pException);
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile a switch block.
 | |
|  *  (See block-comment below for more information)
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileSwitchBlock(ph7_gen_state *pGen, sxu32 *pBlockStart) {
 | |
| 	sxi32 rc = SXRET_OK;
 | |
| 	if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/ | PH7_TK_COLON/*':'*/)) == 0) {
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 	}
 | |
| 	pGen->pIn++;
 | |
| 	/* First instruction to execute in this block. */
 | |
| 	*pBlockStart = PH7_VmInstrLength(pGen->pVm);
 | |
| 	/* Compile the block until we hit a case/default keyword or the '}' token */
 | |
| 	for(;;) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* No more input to process */
 | |
| 			break;
 | |
| 		}
 | |
| 		rc = SXRET_OK;
 | |
| 		if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 			if(pGen->pIn->nType & PH7_TK_CCB /*'}' */) {
 | |
| 				rc = SXERR_EOF;
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			sxi32 nKwrd;
 | |
| 			/* Extract the keyword */
 | |
| 			nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 			if(nKwrd == PH7_KEYWORD_CASE || nKwrd == PH7_KEYWORD_DEFAULT) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		/* Compile block */
 | |
| 		rc = PH7_CompileBlock(&(*pGen));
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	}
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile a case eXpression.
 | |
|  *  (See block-comment below for more information)
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileCaseExpr(ph7_gen_state *pGen, ph7_case_expr *pExpr) {
 | |
| 	SySet *pInstrContainer;
 | |
| 	SyToken *pEnd, *pTmp;
 | |
| 	sxi32 iNest = 0;
 | |
| 	sxi32 rc;
 | |
| 	/* Delimit the expression */
 | |
| 	pEnd = pGen->pIn;
 | |
| 	while(pEnd < pGen->pEnd) {
 | |
| 		if(pEnd->nType & PH7_TK_LPAREN /*(*/) {
 | |
| 			/* Increment nesting level */
 | |
| 			iNest++;
 | |
| 		} else if(pEnd->nType & PH7_TK_RPAREN /*)*/) {
 | |
| 			/* Decrement nesting level */
 | |
| 			iNest--;
 | |
| 		} else if(pEnd->nType & (PH7_TK_SEMI/*';'*/ | PH7_TK_COLON/*;'*/) && iNest < 1) {
 | |
| 			break;
 | |
| 		}
 | |
| 		pEnd++;
 | |
| 	}
 | |
| 	if(pGen->pIn >= pEnd) {
 | |
| 		PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression");
 | |
| 	}
 | |
| 	/* Swap token stream */
 | |
| 	pTmp = pGen->pEnd;
 | |
| 	pGen->pEnd = pEnd;
 | |
| 	pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm);
 | |
| 	PH7_VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode);
 | |
| 	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);
 | |
| 	/* Update token stream */
 | |
| 	pGen->pIn  = pEnd;
 | |
| 	pGen->pEnd = pTmp;
 | |
| 	if(rc == SXERR_ABORT) {
 | |
| 		return SXERR_ABORT;
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Compile the smart switch statement.
 | |
|  *  The switch statement is similar to a series of IF statements on the same expression.
 | |
|  *  In many occasions, you may want to compare the same variable (or expression) with many
 | |
|  *  different values, and execute a different piece of code depending on which value it equals to.
 | |
|  *  This is exactly what the switch statement is for.
 | |
|  *  Note: Note that unlike some other languages, the continue statement applies to switch and acts
 | |
|  *  similar to break. If you have a switch inside a loop and wish to continue to the next iteration
 | |
|  *  of the outer loop, use continue 2.
 | |
|  *  Note that switch/case does loose comparision.
 | |
|  *  It is important to understand how the switch statement is executed in order to avoid mistakes.
 | |
|  *  The switch statement executes line by line (actually, statement by statement).
 | |
|  *  In the beginning, no code is executed. Only when a case statement is found with a value that
 | |
|  *  matches the value of the switch expression does Aer begin to execute the statements.
 | |
|  *  Aer continues to execute the statements until the end of the switch block, or the first time
 | |
|  *  it sees a break statement. If you don't write a break statement at the end of a case's statement list.
 | |
|  *  In a switch statement, the condition is evaluated only once and the result is compared to each
 | |
|  *  case statement. In an elseif statement, the condition is evaluated again. If your condition
 | |
|  *  is more complicated than a simple compare and/or is in a tight loop, a switch may be faster.
 | |
|  *  The statement list for a case can also be empty, which simply passes control into the statement
 | |
|  *  list for the next case.
 | |
|  *  The case expression may be any expression that evaluates to a simple type, that is, integer
 | |
|  *  or floating-point numbers and strings.
 | |
|  */
 | |
| static sxi32 PH7_CompileSwitch(ph7_gen_state *pGen) {
 | |
| 	GenBlock *pSwitchBlock;
 | |
| 	SyToken *pTmp, *pEnd;
 | |
| 	ph7_switch *pSwitch;
 | |
| 	sxu32 nLine;
 | |
| 	sxi32 rc;
 | |
| 	nLine = pGen->pIn->nLine;
 | |
| 	/* Jump the 'switch' 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 'switch' keyword");
 | |
| 	}
 | |
| 	/* Jump the left parenthesis '(' */
 | |
| 	pGen->pIn++;
 | |
| 	pEnd = 0; /* cc warning */
 | |
| 	/* Create the loop block */
 | |
| 	rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP | GEN_BLOCK_SWITCH,
 | |
| 							PH7_VmInstrLength(pGen->pVm), 0, &pSwitchBlock);
 | |
| 	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 'switch' 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,
 | |
| 								 "Switch: Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 	}
 | |
| 	pGen->pIn  = &pEnd[1];
 | |
| 	pGen->pEnd = pTmp;
 | |
| 	if(pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd ||
 | |
| 			(pGen->pIn->nType & PH7_TK_OCB/*'{'*/) == 0) {
 | |
| 		pTmp = pGen->pIn;
 | |
| 		if(pTmp >= pGen->pEnd) {
 | |
| 			pTmp--;
 | |
| 		}
 | |
| 		/* Unexpected token */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData);
 | |
| 	}
 | |
| 	pGen->pIn++; /* Jump the leading curly braces/colons */
 | |
| 	/* Create the switch blocks container */
 | |
| 	pSwitch = (ph7_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_switch));
 | |
| 	if(pSwitch == 0) {
 | |
| 		/* Abort compilation */
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "PH7 is running out-of-memory");
 | |
| 	}
 | |
| 	/* Zero the structure */
 | |
| 	SyZero(pSwitch, sizeof(ph7_switch));
 | |
| 	/* Initialize fields */
 | |
| 	SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(ph7_case_expr));
 | |
| 	/* Emit the switch instruction */
 | |
| 	PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_SWITCH, 0, 0, pSwitch, 0);
 | |
| 	/* Compile case blocks */
 | |
| 	for(;;) {
 | |
| 		sxu32 nKwrd;
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* No more input to process and switch block not ended */
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected end of file, expecting 'case' or 'default' or '}'");
 | |
| 		}
 | |
| 		if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) {
 | |
| 			/* Block compiled */
 | |
| 			break;
 | |
| 		}
 | |
| 		/* Extract the keyword */
 | |
| 		nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 		if(nKwrd == PH7_KEYWORD_DEFAULT) {
 | |
| 			/*
 | |
| 			 * A special case is the default case. This case matches anything
 | |
| 			 * that wasn't matched by the other cases.
 | |
| 			 */
 | |
| 			if(pSwitch->nDefault > 0) {
 | |
| 				/* Default case already compiled */
 | |
| 				rc = PH7_GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled");
 | |
| 				if(rc == SXERR_ABORT) {
 | |
| 					return SXERR_ABORT;
 | |
| 				}
 | |
| 			}
 | |
| 			pGen->pIn++; /* Jump the 'default' keyword */
 | |
| 			/* Compile the default block */
 | |
| 			rc = PH7_GenStateCompileSwitchBlock(pGen, &pSwitch->nDefault);
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				return SXERR_ABORT;
 | |
| 			} else if(rc == SXERR_EOF) {
 | |
| 				break;
 | |
| 			}
 | |
| 		} else if(nKwrd == PH7_KEYWORD_CASE) {
 | |
| 			ph7_case_expr sCase;
 | |
| 			/* Standard case block */
 | |
| 			pGen->pIn++; /* Jump the 'case' keyword */
 | |
| 			/* initialize the structure */
 | |
| 			SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
 | |
| 			/* Compile the case expression */
 | |
| 			rc = PH7_GenStateCompileCaseExpr(pGen, &sCase);
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				return SXERR_ABORT;
 | |
| 			}
 | |
| 			/* Compile the case block */
 | |
| 			rc = PH7_GenStateCompileSwitchBlock(pGen, &sCase.nStart);
 | |
| 			/* Insert in the switch container */
 | |
| 			SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase);
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				return SXERR_ABORT;
 | |
| 			} else if(rc == SXERR_EOF) {
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			/* Unexpected token */
 | |
| 			PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
 | |
| 									 &pGen->pIn->sData);
 | |
| 		}
 | |
| 	}
 | |
| 	/* Fix all jumps now the destination is resolved */
 | |
| 	pSwitch->nOut = PH7_VmInstrLength(pGen->pVm);
 | |
| 	PH7_GenStateFixJumps(pSwitchBlock, -1, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Release the loop block */
 | |
| 	PH7_GenStateLeaveBlock(pGen, 0);
 | |
| 	if(pGen->pIn < pGen->pEnd) {
 | |
| 		/* Jump the trailing curly braces or the endswitch keyword*/
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	/* Statement successfully compiled */
 | |
| 	return SXRET_OK;
 | |
| 	/* Synchronize with the first semi-colon */
 | |
| 	while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0) {
 | |
| 		pGen->pIn++;
 | |
| 	}
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Generate bytecode for a given expression tree.
 | |
|  * If something goes wrong while generating bytecode
 | |
|  * for the expression tree (A very unlikely scenario)
 | |
|  * this function takes care of generating the appropriate
 | |
|  * error message.
 | |
|  */
 | |
| static sxi32 PH7_GenStateEmitExprCode(
 | |
| 	ph7_gen_state *pGen,  /* Code generator state */
 | |
| 	ph7_expr_node *pNode, /* Root of the expression tree */
 | |
| 	sxi32 iFlags /* Control flags */
 | |
| ) {
 | |
| 	VmInstr *pInstr = 0;
 | |
| 	sxu32 nJmpIdx;
 | |
| 	sxi32 iP1 = 0;
 | |
| 	sxu32 iP2 = 0;
 | |
| 	void *p3  = 0;
 | |
| 	sxi32 iVmOp;
 | |
| 	sxi32 rc;
 | |
| 	if(pNode->xCode) {
 | |
| 		SyToken *pTmpIn, *pTmpEnd;
 | |
| 		/* Compile node */
 | |
| 		SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd);
 | |
| 		rc = pNode->xCode(&(*pGen), iFlags);
 | |
| 		RE_SWAP_DELIMITER(pGen);
 | |
| 		return rc;
 | |
| 	}
 | |
| 	if(pNode->pOp == 0) {
 | |
| 		PH7_GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine,
 | |
| 							"Invalid expression node,PH7 is aborting compilation");
 | |
| 	}
 | |
| 	iVmOp = pNode->pOp->iVmOp;
 | |
| 	if(pNode->pOp->iOp == EXPR_OP_QUESTY) {
 | |
| 		sxu32 nJz, nJmp;
 | |
| 		/* Ternary operator require special handling */
 | |
| 		/* Phase#1: Compile the condition */
 | |
| 		rc = PH7_GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags);
 | |
| 		if(rc != SXRET_OK) {
 | |
| 			return rc;
 | |
| 		}
 | |
| 		nJz = nJmp = 0; /* cc -O6 warning */
 | |
| 		/* Phase#2: Emit the false jump */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMPZ, 0, 0, 0, &nJz);
 | |
| 		if(pNode->pLeft) {
 | |
| 			/* Phase#3: Compile the 'then' expression  */
 | |
| 			rc = PH7_GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags);
 | |
| 			if(rc != SXRET_OK) {
 | |
| 				return rc;
 | |
| 			}
 | |
| 		}
 | |
| 		/* Phase#4: Emit the unconditional jump */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMP, 0, 0, 0, &nJmp);
 | |
| 		/* Phase#5: Fix the false jump now the jump destination is resolved. */
 | |
| 		pInstr = PH7_VmGetInstr(pGen->pVm, nJz);
 | |
| 		if(pInstr) {
 | |
| 			pInstr->iP2 = PH7_VmInstrLength(pGen->pVm);
 | |
| 		}
 | |
| 		/* Phase#6: Compile the 'else' expression */
 | |
| 		if(pNode->pRight) {
 | |
| 			rc = PH7_GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags);
 | |
| 			if(rc != SXRET_OK) {
 | |
| 				return rc;
 | |
| 			}
 | |
| 		}
 | |
| 		if(nJmp > 0) {
 | |
| 			/* Phase#7: Fix the unconditional jump */
 | |
| 			pInstr = PH7_VmGetInstr(pGen->pVm, nJmp);
 | |
| 			if(pInstr) {
 | |
| 				pInstr->iP2 = PH7_VmInstrLength(pGen->pVm);
 | |
| 			}
 | |
| 		}
 | |
| 		/* All done */
 | |
| 		return SXRET_OK;
 | |
| 	}
 | |
| 	/* Generate code for the left tree */
 | |
| 	if(pNode->pLeft) {
 | |
| 		if(iVmOp == PH7_OP_CALL) {
 | |
| 			ph7_expr_node **apNode;
 | |
| 			sxi32 n;
 | |
| 			/* Recurse and generate bytecodes for function arguments */
 | |
| 			apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
 | |
| 			/* Read-only load */
 | |
| 			iFlags |= EXPR_FLAG_RDONLY_LOAD;
 | |
| 			for(n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n) {
 | |
| 				rc = PH7_GenStateEmitExprCode(&(*pGen), apNode[n], iFlags & ~EXPR_FLAG_LOAD_IDX_STORE);
 | |
| 				if(rc != SXRET_OK) {
 | |
| 					return rc;
 | |
| 				}
 | |
| 			}
 | |
| 			/* Total number of given arguments */
 | |
| 			iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs);
 | |
| 			/* Remove stale flags now */
 | |
| 			iFlags &= ~EXPR_FLAG_RDONLY_LOAD;
 | |
| 		}
 | |
| 		rc = PH7_GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags);
 | |
| 		if(rc != SXRET_OK) {
 | |
| 			return rc;
 | |
| 		}
 | |
| 		if(iVmOp == PH7_OP_CALL) {
 | |
| 			pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 			if(pInstr) {
 | |
| 				if(pInstr->iOp == PH7_OP_LOADC) {
 | |
| 					/* Prevent constant expansion */
 | |
| 					pInstr->iP1 = 0;
 | |
| 				} else if(pInstr->iOp == PH7_OP_MEMBER /* $a->b(1,2,3) */ || pInstr->iOp == PH7_OP_NEW) {
 | |
| 					/* Method call,flag that */
 | |
| 					pInstr->iP2 = 1;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if(iVmOp == PH7_OP_LOAD_IDX) {
 | |
| 			ph7_expr_node **apNode;
 | |
| 			sxi32 n;
 | |
| 			/* Recurse and generate bytecodes for array index */
 | |
| 			apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
 | |
| 			for(n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n) {
 | |
| 				rc = PH7_GenStateEmitExprCode(&(*pGen), apNode[n], iFlags & ~EXPR_FLAG_LOAD_IDX_STORE);
 | |
| 				if(rc != SXRET_OK) {
 | |
| 					return rc;
 | |
| 				}
 | |
| 			}
 | |
| 			if(SySetUsed(&pNode->aNodeArgs) > 0) {
 | |
| 				iP1 = 1; /* Node have an index associated with it */
 | |
| 			}
 | |
| 			if(iFlags & EXPR_FLAG_LOAD_IDX_STORE) {
 | |
| 				/* Create an empty entry when the desired index is not found */
 | |
| 				iP2 = 1;
 | |
| 			}
 | |
| 		} else if(pNode->pOp->iOp == EXPR_OP_COMMA) {
 | |
| 			/* POP the left node */
 | |
| 			PH7_VmEmitInstr(pGen->pVm, pInstr->iLine, PH7_OP_POP, 1, 0, 0, 0);
 | |
| 		}
 | |
| 	}
 | |
| 	rc = SXRET_OK;
 | |
| 	nJmpIdx = 0;
 | |
| 	/* Generate code for the right tree */
 | |
| 	if(pNode->pRight) {
 | |
| 		if(iVmOp == PH7_OP_LAND) {
 | |
| 			/* Emit the false jump so we can short-circuit the logical and */
 | |
| 			PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMPZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx);
 | |
| 		} else if(iVmOp == PH7_OP_LOR) {
 | |
| 			/* Emit the true jump so we can short-circuit the logical or*/
 | |
| 			PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_JMPNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx);
 | |
| 		} else if(pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =,'.=','+=',*=' ...] precedence */) {
 | |
| 			iFlags |= EXPR_FLAG_LOAD_IDX_STORE;
 | |
| 		}
 | |
| 		rc = PH7_GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags);
 | |
| 		if(iVmOp == PH7_OP_STORE) {
 | |
| 			pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 			if(pInstr) {
 | |
| 				if(pInstr->iOp == PH7_OP_MEMBER) {
 | |
| 					/* Perform a member store operation [i.e: $this->x = 50] */
 | |
| 					iP2 = 1;
 | |
| 				} else {
 | |
| 					if(pInstr->iOp == PH7_OP_LOAD_IDX) {
 | |
| 						/* Transform the STORE instruction to STORE_IDX instruction */
 | |
| 						iVmOp = PH7_OP_STORE_IDX;
 | |
| 						iP1 = pInstr->iP1;
 | |
| 					} else {
 | |
| 						p3 = pInstr->p3;
 | |
| 					}
 | |
| 					/* POP the last dynamic load instruction */
 | |
| 					(void)PH7_VmPopInstr(pGen->pVm);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if(iVmOp > 0) {
 | |
| 		if(iVmOp == PH7_OP_INCR || iVmOp == PH7_OP_DECR) {
 | |
| 			if(pNode->iFlags & EXPR_NODE_PRE_INCR) {
 | |
| 				/* Pre-increment/decrement operator [i.e: ++$i,--$j ] */
 | |
| 				iP1 = 1;
 | |
| 			}
 | |
| 		} else if(iVmOp == PH7_OP_NEW) {
 | |
| 			pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 			if(pInstr && pInstr->iOp == PH7_OP_CALL) {
 | |
| 				VmInstr *pPrev;
 | |
| 				pPrev = PH7_VmPeekNextInstr(pGen->pVm);
 | |
| 				if(pPrev == 0 || pPrev->iOp != PH7_OP_MEMBER) {
 | |
| 					/* Pop the call instruction */
 | |
| 					iP1 = pInstr->iP1;
 | |
| 					(void)PH7_VmPopInstr(pGen->pVm);
 | |
| 				}
 | |
| 			}
 | |
| 		} else if(iVmOp == PH7_OP_MEMBER) {
 | |
| 			if(pNode->pOp->iOp == EXPR_OP_DC /* '::' */) {
 | |
| 				/* Static member access,remember that */
 | |
| 				iP1 = 1;
 | |
| 				pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 				if(pInstr && pInstr->iOp == PH7_OP_LOADV) {
 | |
| 					p3 = pInstr->p3;
 | |
| 					(void)PH7_VmPopInstr(pGen->pVm);
 | |
| 				}
 | |
| 			}
 | |
| 		} else if(iVmOp == PH7_OP_IS) {
 | |
| 			ph7_expr_node *pNext = pNode->pRight;
 | |
| 			sxu32 nKwrd = 0;
 | |
| 			if(pNext->pLeft && pNext->pLeft->pStart->nType == PH7_TK_KEYWORD) {
 | |
| 				nKwrd = SX_PTR_TO_INT(pNext->pLeft->pStart->pUserData);
 | |
| 				pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 				if(pInstr && pInstr->iOp == PH7_OP_LOAD_IDX) {
 | |
| 					iP2 = MEMOBJ_HASHMAP;
 | |
| 					(void)PH7_VmPopInstr(pGen->pVm);
 | |
| 				}
 | |
| 			} else if(pNext->pStart->nType == PH7_TK_KEYWORD) {
 | |
| 				nKwrd = SX_PTR_TO_INT(pNext->pStart->pUserData);
 | |
| 			}
 | |
| 			if(nKwrd & PH7_KEYWORD_BOOL) {
 | |
| 				iP2 |= MEMOBJ_BOOL;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_CALLBACK) {
 | |
| 				iP2 |= MEMOBJ_CALL;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_CHAR) {
 | |
| 				iP2 |= MEMOBJ_CHAR;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_FLOAT) {
 | |
| 				iP2 |= MEMOBJ_REAL;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_INT) {
 | |
| 				iP2 |= MEMOBJ_INT;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_MIXED) {
 | |
| 				iP2 |= MEMOBJ_MIXED;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_OBJECT) {
 | |
| 				iP2 |= MEMOBJ_OBJ;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_RESOURCE) {
 | |
| 				iP2 |= MEMOBJ_RES;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_STRING) {
 | |
| 				iP2 |= MEMOBJ_STRING;
 | |
| 			} else if(nKwrd & PH7_KEYWORD_VOID) {
 | |
| 				iP2 |= MEMOBJ_VOID;
 | |
| 			}
 | |
| 			pInstr = PH7_VmPeekInstr(pGen->pVm);
 | |
| 			if(pInstr && pInstr->iOp == PH7_OP_LOADV) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A constant value is expected");
 | |
| 			}
 | |
| 		}
 | |
| 		/* Finally,emit the VM instruction associated with this operator */
 | |
| 		PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, iVmOp, iP1, iP2, p3, 0);
 | |
| 		if(nJmpIdx > 0) {
 | |
| 			/* Fix short-circuited jumps now the destination is resolved */
 | |
| 			pInstr = PH7_VmGetInstr(pGen->pVm, nJmpIdx);
 | |
| 			if(pInstr) {
 | |
| 				pInstr->iP2 = PH7_VmInstrLength(pGen->pVm);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile an AerScript expression.
 | |
|  *  Expressions are the most important building stones of Aer.
 | |
|  *  In Aer, almost anything you write is an expression.
 | |
|  *  The simplest yet most accurate way to define an expression
 | |
|  *  is "anything that has a value".
 | |
|  * If something goes wrong while compiling the expression,this
 | |
|  * function takes care of generating the appropriate error
 | |
|  * message.
 | |
|  */
 | |
| static sxi32 PH7_CompileExpr(
 | |
| 	ph7_gen_state *pGen, /* Code generator state */
 | |
| 	sxi32 iFlags,        /* Control flags */
 | |
| 	sxi32(*xTreeValidator)(ph7_gen_state *, ph7_expr_node *) /* Node validator callback.NULL otherwise */
 | |
| ) {
 | |
| 	ph7_expr_node *pRoot;
 | |
| 	SySet sExprNode;
 | |
| 	SyToken *pEnd;
 | |
| 	sxi32 nExpr;
 | |
| 	sxi32 iNest;
 | |
| 	sxi32 rc;
 | |
| 	/* Initialize worker variables */
 | |
| 	nExpr = 0;
 | |
| 	pRoot = 0;
 | |
| 	SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(ph7_expr_node *));
 | |
| 	SySetAlloc(&sExprNode, 0x10);
 | |
| 	rc = SXRET_OK;
 | |
| 	/* Delimit the expression */
 | |
| 	pEnd = pGen->pIn;
 | |
| 	iNest = 0;
 | |
| 	while(pEnd < pGen->pEnd) {
 | |
| 		if(pEnd->nType & PH7_TK_OCB /* '{' */) {
 | |
| 			/* Ticket 1433-30: Anonymous/Closure functions body */
 | |
| 			iNest++;
 | |
| 		} else if(pEnd->nType & PH7_TK_CCB /* '}' */) {
 | |
| 			iNest--;
 | |
| 		} else if(pEnd->nType & PH7_TK_SEMI /* ';' */) {
 | |
| 			if(iNest <= 0) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		pEnd++;
 | |
| 	}
 | |
| 	if(iFlags & EXPR_FLAG_COMMA_STATEMENT) {
 | |
| 		SyToken *pEnd2 = pGen->pIn;
 | |
| 		iNest = 0;
 | |
| 		/* Stop at the first comma */
 | |
| 		while(pEnd2 < pEnd) {
 | |
| 			if(pEnd2->nType & (PH7_TK_OCB/*'{'*/ | PH7_TK_OSB/*'['*/ | PH7_TK_LPAREN/*'('*/)) {
 | |
| 				iNest++;
 | |
| 			} else if(pEnd2->nType & (PH7_TK_CCB/*'}'*/ | PH7_TK_CSB/*']'*/ | PH7_TK_RPAREN/*')'*/)) {
 | |
| 				iNest--;
 | |
| 			} else if(pEnd2->nType & PH7_TK_COMMA /*','*/) {
 | |
| 				if(iNest <= 0) {
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 			pEnd2++;
 | |
| 		}
 | |
| 		if(pEnd2 < pEnd) {
 | |
| 			pEnd = pEnd2;
 | |
| 		}
 | |
| 	}
 | |
| 	if(pEnd > pGen->pIn) {
 | |
| 		SyToken *pTmp = pGen->pEnd;
 | |
| 		/* Swap delimiter */
 | |
| 		pGen->pEnd = pEnd;
 | |
| 		/* Try to get an expression tree */
 | |
| 		rc = PH7_ExprMakeTree(&(*pGen), &sExprNode, &pRoot);
 | |
| 		if(rc == SXRET_OK && pRoot) {
 | |
| 			rc = SXRET_OK;
 | |
| 			if(xTreeValidator) {
 | |
| 				/* Call the upper layer validator callback */
 | |
| 				rc = xTreeValidator(&(*pGen), pRoot);
 | |
| 			}
 | |
| 			if(rc != SXERR_ABORT) {
 | |
| 				/* Generate code for the given tree */
 | |
| 				rc = PH7_GenStateEmitExprCode(&(*pGen), pRoot, iFlags);
 | |
| 			}
 | |
| 			nExpr = 1;
 | |
| 		}
 | |
| 		/* Release the whole tree */
 | |
| 		PH7_ExprFreeTree(&(*pGen), &sExprNode);
 | |
| 		/* Synchronize token stream */
 | |
| 		pGen->pEnd = pTmp;
 | |
| 		pGen->pIn  = pEnd;
 | |
| 		if(rc == SXERR_ABORT) {
 | |
| 			SySetRelease(&sExprNode);
 | |
| 			return SXERR_ABORT;
 | |
| 		}
 | |
| 	}
 | |
| 	SySetRelease(&sExprNode);
 | |
| 	return nExpr > 0 ? SXRET_OK : SXERR_EMPTY;
 | |
| }
 | |
| /*
 | |
|  * Return a pointer to the node construct handler associated
 | |
|  * with a given node type [i.e: string,integer,float,...].
 | |
|  */
 | |
| PH7_PRIVATE ProcNodeConstruct PH7_GetNodeHandler(sxu32 nNodeType) {
 | |
| 	if(nNodeType & PH7_TK_NUM) {
 | |
| 		/* Numeric literal: Either real or integer */
 | |
| 		return PH7_CompileNumLiteral;
 | |
| 	} else if(nNodeType & PH7_TK_DSTR) {
 | |
| 		/* Double quoted string */
 | |
| 		return PH7_CompileString;
 | |
| 	} else if(nNodeType & PH7_TK_SSTR) {
 | |
| 		/* Single quoted string */
 | |
| 		return PH7_CompileSimpleString;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| /*
 | |
|  * Aer Language construct table.
 | |
|  */
 | |
| static const LangConstruct aLangConstruct[] = {
 | |
| 	{ PH7_KEYWORD_IF,       PH7_CompileIf       }, /* if statement */
 | |
| 	{ PH7_KEYWORD_FOR,      PH7_CompileFor      }, /* for statement */
 | |
| 	{ PH7_KEYWORD_WHILE,    PH7_CompileWhile    }, /* while statement */
 | |
| 	{ PH7_KEYWORD_FOREACH,  PH7_CompileForeach  }, /* foreach statement */
 | |
| 	{ PH7_KEYWORD_CONTINUE, PH7_CompileContinue }, /* continue statement */
 | |
| 	{ PH7_KEYWORD_BREAK,    PH7_CompileBreak    }, /* break statement */
 | |
| 	{ PH7_KEYWORD_RETURN,   PH7_CompileReturn   }, /* return statement */
 | |
| 	{ PH7_KEYWORD_SWITCH,   PH7_CompileSwitch   }, /* Switch statement */
 | |
| 	{ PH7_KEYWORD_DO,       PH7_CompileDoWhile  }, /* do{ }while(); statement */
 | |
| 	{ PH7_KEYWORD_EXIT,     PH7_CompileHalt     }, /* exit language construct */
 | |
| 	{ PH7_KEYWORD_TRY,      PH7_CompileTry      }, /* try statement */
 | |
| 	{ PH7_KEYWORD_THROW,    PH7_CompileThrow    }, /* throw statement */
 | |
| 	{ PH7_KEYWORD_GOTO,     PH7_CompileGoto     }, /* goto statement */
 | |
| 	{ PH7_KEYWORD_CONST,    PH7_CompileConstant }, /* const statement */
 | |
| 	{ PH7_KEYWORD_INCLUDE,  PH7_CompileInclude  }, /* include statement */
 | |
| 	{ PH7_KEYWORD_REQUIRE,  PH7_CompileInclude  }, /* require statement */
 | |
| };
 | |
| /*
 | |
|  * Return a pointer to the global scope handler routine associated
 | |
|  */
 | |
| static ProcLangConstruct PH7_GenStateGetGlobalScopeHandler(
 | |
| 	sxu32 nKeywordID   /* Keyword ID */
 | |
| ) {
 | |
| 	switch(nKeywordID) {
 | |
| 		case PH7_KEYWORD_DEFINE:
 | |
| 			return PH7_CompileDefine;
 | |
| 		case PH7_KEYWORD_INTERFACE:
 | |
| 			return PH7_CompileClassInterface;
 | |
| 		case PH7_KEYWORD_FINAL:
 | |
| 		case PH7_KEYWORD_VIRTUAL:
 | |
| 			return PH7_CompileFinalVirtualClass;
 | |
| 		case PH7_KEYWORD_CLASS:
 | |
| 			return PH7_CompileClass;
 | |
| 		case PH7_KEYWORD_NAMESPACE:
 | |
| 			return PH7_CompileNamespace;
 | |
| 		case PH7_KEYWORD_USING:
 | |
| 			return PH7_CompileUsing;
 | |
| 		case PH7_KEYWORD_IMPORT:
 | |
| 			return PH7_CompileImport;
 | |
| 		case PH7_KEYWORD_REQUIRE:
 | |
| 			return PH7_CompileInclude;
 | |
| 		default:
 | |
| 			/* Not a global scope language construct */
 | |
| 			return 0;
 | |
| 	}
 | |
| }
 | |
| /*
 | |
|  * Return a pointer to the statement handler routine associated
 | |
|  * with a given Aer keyword [i.e: if,for,while,...].
 | |
|  */
 | |
| static ProcLangConstruct PH7_GenStateGetStatementHandler(
 | |
| 	sxu32 nKeywordID,   /* Keyword ID */
 | |
| 	SyToken *pLookahead  /* Look-ahead token */
 | |
| ) {
 | |
| 	sxu32 n = 0;
 | |
| 	if((nKeywordID & PH7_KEYWORD_AUTO) != 0 || (nKeywordID & PH7_KEYWORD_TYPEDEF) != 0 || nKeywordID == PH7_KEYWORD_STATIC) {
 | |
| 		if(nKeywordID == PH7_KEYWORD_STATIC && pLookahead && (pLookahead->nType & PH7_TK_OP)) {
 | |
| 			const ph7_expr_op *pOp = (const ph7_expr_op *)pLookahead->pUserData;
 | |
| 			if(pOp && pOp->iOp == EXPR_OP_DC /*::*/) {
 | |
| 				/* 'static' (class context), return null */
 | |
| 				return 0;
 | |
| 			}
 | |
| 		}
 | |
| 		return PH7_CompileVar;
 | |
| 	} else {
 | |
| 		for(;;) {
 | |
| 			if(n >= SX_ARRAYSIZE(aLangConstruct)) {
 | |
| 				break;
 | |
| 			}
 | |
| 			if(aLangConstruct[n].nID == nKeywordID) {
 | |
| 				/* Return a pointer to the handler.
 | |
| 				*/
 | |
| 				return aLangConstruct[n].xConstruct;
 | |
| 			}
 | |
| 			n++;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Not a language construct */
 | |
| 	return 0;
 | |
| }
 | |
| /*
 | |
|  * Return TRUE if the given ID represent a language construct. FALSE otherwise.
 | |
|  */
 | |
| static int PH7_IsLangConstruct(sxu32 nKeywordID) {
 | |
| 	if(nKeywordID == PH7_KEYWORD_EVAL || nKeywordID == PH7_KEYWORD_STATIC || nKeywordID == PH7_KEYWORD_NEW || nKeywordID == PH7_KEYWORD_CLONE) {
 | |
| 		return TRUE;
 | |
| 	}
 | |
| 	/* Not a language construct */
 | |
| 	return FALSE;
 | |
| }
 | |
| /*
 | |
|  * Compile an AerScript chunk.
 | |
|  * If something goes wrong while compiling the Aer chunk, this function
 | |
|  * takes care of generating the appropriate error message.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileChunk(
 | |
| 	ph7_gen_state *pGen, /* Code generator state */
 | |
| 	sxi32 iFlags         /* Compile flags */
 | |
| ) {
 | |
| 	ProcLangConstruct xCons;
 | |
| 	sxi32 rc;
 | |
| 	rc = SXRET_OK; /* Prevent compiler warning */
 | |
| 	for(;;) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* No more input to process */
 | |
| 			break;
 | |
| 		}
 | |
| 		if(pGen->pIn->nType & PH7_TK_OCB /* '{' */) {
 | |
| 			/* Compile block */
 | |
| 			rc = PH7_CompileBlock(&(*pGen));
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			xCons = 0;
 | |
| 			if(pGen->pIn->nType & PH7_TK_KEYWORD) {
 | |
| 				sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 				/* Try to extract a language construct handler */
 | |
| 				xCons = PH7_GenStateGetStatementHandler(nKeyword, (&pGen->pIn[1] < pGen->pEnd) ? &pGen->pIn[1] : 0);
 | |
| 				if(xCons == 0 && PH7_IsLangConstruct(nKeyword) == FALSE) {
 | |
| 					PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
 | |
| 											 "Syntax error: Unexpected keyword '%z'",
 | |
| 											 &pGen->pIn->sData);
 | |
| 				}
 | |
| 			} else if((pGen->pIn->nType & PH7_TK_ID) && (&pGen->pIn[1] < pGen->pEnd) && (pGen->pIn[1].nType & PH7_TK_COLON /*':'*/)) {
 | |
| 				/* Label found [i.e: Out: ],point to the routine responsible of compiling it */
 | |
| 				xCons = PH7_CompileLabel;
 | |
| 			}
 | |
| 			if(xCons == 0) {
 | |
| 				/* Assume an expression an try to compile it */
 | |
| 				rc = PH7_CompileExpr(&(*pGen), 0, 0);
 | |
| 				if(rc != SXERR_EMPTY) {
 | |
| 					/* Pop l-value */
 | |
| 					PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_POP, 1, 0, 0, 0);
 | |
| 				}
 | |
| 			} else {
 | |
| 				/* Compile the statement */
 | |
| 				rc = xCons(&(*pGen));
 | |
| 			}
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				/* Request to abort compilation */
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		/* Ignore trailing semi-colons ';' */
 | |
| 		while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI)) {
 | |
| 			pGen->pIn++;
 | |
| 		}
 | |
| 		if(iFlags & PH7_COMPILE_STATEMENT) {
 | |
| 			/* Compile a single statement and return */
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Return compilation status */
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile an AerScript global scope.
 | |
|  * If something goes wrong while compiling the Aer global scope, this function
 | |
|  * takes care of generating the appropriate error message.
 | |
|  */
 | |
| static sxi32 PH7_GenStateCompileGlobalScope(
 | |
| 	ph7_gen_state *pGen /* Code generator state */
 | |
| ) {
 | |
| 	ProcLangConstruct xCons;
 | |
| 	sxi32 rc;
 | |
| 	rc = SXRET_OK; /* Prevent compiler warning */
 | |
| 	for(;;) {
 | |
| 		if(pGen->pIn >= pGen->pEnd) {
 | |
| 			/* No more input to process */
 | |
| 			break;
 | |
| 		}
 | |
| 		xCons = 0;
 | |
| 		if(pGen->pIn->nType & PH7_TK_KEYWORD) {
 | |
| 			sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
 | |
| 			/* Try to extract a language construct handler */
 | |
| 			xCons = PH7_GenStateGetGlobalScopeHandler(nKeyword);
 | |
| 			if(xCons == 0) {
 | |
| 				PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Syntax error: Unexpected keyword '%z'", &pGen->pIn->sData);
 | |
| 			}
 | |
| 			/* Compile the statement */
 | |
| 			rc = xCons(&(*pGen));
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				/* Request to abort compilation */
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Syntax error: Unexpected token '%z'", &pGen->pIn->sData);
 | |
| 		}
 | |
| 		/* Ignore trailing semi-colons ';' */
 | |
| 		while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI)) {
 | |
| 			pGen->pIn++;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Return compilation status */
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile a Raw Aer chunk.
 | |
|  * If something goes wrong while compiling the Aer chunk,this function
 | |
|  * takes care of generating the appropriate error message.
 | |
|  */
 | |
| static sxi32 PH7_CompileScript(
 | |
| 	ph7_gen_state *pGen,  /* Code generator state */
 | |
| 	SySet *pTokenSet,     /* Token set */
 | |
| 	sxi32 iFlags          /* Compiler flags */
 | |
| ) {
 | |
| 	SyToken *pScript = pGen->pRawIn; /* Script to compile */
 | |
| 	sxi32 rc;
 | |
| 	/* Reset the token set */
 | |
| 	SySetReset(&(*pTokenSet));
 | |
| 	/* Mark as the default token set */
 | |
| 	pGen->pTokenSet = &(*pTokenSet);
 | |
| 	/* Advance the stream cursor */
 | |
| 	pGen->pRawIn++;
 | |
| 	/* Tokenize the Aer chunk first */
 | |
| 	PH7_TokenizeAerScript(SyStringData(&pScript->sData), SyStringLength(&pScript->sData), pScript->nLine, &(*pTokenSet));
 | |
| 	/* Point to the head and tail of the token stream. */
 | |
| 	pGen->pIn  = (SyToken *)SySetBasePtr(pTokenSet);
 | |
| 	pGen->pEnd = &pGen->pIn[SySetUsed(pTokenSet)];
 | |
| 	if(iFlags & PH7_AERSCRIPT_EXPR) {
 | |
| 		rc = SXERR_EMPTY;
 | |
| 		if(pGen->pIn < pGen->pEnd) {
 | |
| 			/* A simple expression,compile it */
 | |
| 			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);
 | |
| 		return SXRET_OK;
 | |
| 	} else if(iFlags & PH7_AERSCRIPT_CHNK) {
 | |
| 		/* Compile a chunk of code */
 | |
| 		rc = PH7_GenStateCompileChunk(pGen, 0);
 | |
| 	} else {
 | |
| 		/* Compile the Aer global scope */
 | |
| 		rc = PH7_GenStateCompileGlobalScope(pGen);
 | |
| 	}
 | |
| 	/* Fix exceptions jumps */
 | |
| 	PH7_GenStateFixJumps(pGen->pCurrent, PH7_OP_THROW, PH7_VmInstrLength(pGen->pVm));
 | |
| 	/* Fix gotos now, the jump destination is resolved */
 | |
| 	if(SXERR_ABORT == GenStateFixGoto(&(*pGen), 0)) {
 | |
| 		rc = SXERR_ABORT;
 | |
| 	}
 | |
| 	/* Reset container */
 | |
| 	SySetReset(&pGen->aGoto);
 | |
| 	SySetReset(&pGen->aLabel);
 | |
| 	/* Compilation result */
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Compile a raw chunk. The raw chunk can contain Aer code embedded
 | |
|  * in HTML, XML and so on. This function handle all the stuff.
 | |
|  * This is the only compile interface exported from this file.
 | |
|  */
 | |
| PH7_PRIVATE sxi32 PH7_CompileAerScript(
 | |
| 	ph7_vm *pVm,        /* Generate PH7 byte-codes for this Virtual Machine */
 | |
| 	SyString *pScript,  /* Script to compile */
 | |
| 	sxi32 iFlags        /* Compiler flags */
 | |
| ) {
 | |
| 	SySet aAerToken, aRawToken;
 | |
| 	ph7_gen_state *pCodeGen;
 | |
| 	sxi32 rc;
 | |
| 	if(pScript->nByte < 1) {
 | |
| 		/* Nothing to compile */
 | |
| 		return PH7_OK;
 | |
| 	}
 | |
| 	/* Initialize the tokens containers */
 | |
| 	SySetInit(&aRawToken, &pVm->sAllocator, sizeof(SyToken));
 | |
| 	SySetInit(&aAerToken, &pVm->sAllocator, sizeof(SyToken));
 | |
| 	SySetAlloc(&aAerToken, 0xc0);
 | |
| 	SyToken sTmp;
 | |
| 	sTmp.nLine = 1;
 | |
| 	sTmp.pUserData = 0;
 | |
| 	SyStringDupPtr(&sTmp.sData, pScript);
 | |
| 	SySetPut(&aRawToken, (const void *)&sTmp);
 | |
| 	pCodeGen = &pVm->sCodeGen;
 | |
| 	/* Process high-level tokens */
 | |
| 	pCodeGen->pRawIn = (SyToken *)SySetBasePtr(&aRawToken);
 | |
| 	pCodeGen->pRawEnd = &pCodeGen->pRawIn[SySetUsed(&aRawToken)];
 | |
| 	rc = PH7_OK;
 | |
| 	if(iFlags & PH7_AERSCRIPT_EXPR) {
 | |
| 		/* Compile the expression */
 | |
| 		rc = PH7_CompileScript(pCodeGen, &aAerToken, iFlags);
 | |
| 	} else {
 | |
| 		/* Start the compilation process */
 | |
| 		for(;;) {
 | |
| 			/* Compile Aer block of code */
 | |
| 			if(pCodeGen->pRawIn >= pCodeGen->pRawEnd) {
 | |
| 				break; /* No more tokens to process */
 | |
| 			}
 | |
| 			/* Compile the code */
 | |
| 			rc = PH7_CompileScript(pCodeGen, &aAerToken, iFlags);
 | |
| 			if(rc == SXERR_ABORT) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	SySetRelease(&aRawToken);
 | |
| 	SySetRelease(&aAerToken);
 | |
| 	return rc;
 | |
| }
 | |
| /*
 | |
|  * Utility routines.Initialize the code generator.
 | |
|  */
 | |
| PH7_PRIVATE sxi32 PH7_InitCodeGenerator(
 | |
| 	ph7_vm *pVm,       /* Target VM */
 | |
| 	ProcConsumer xErr, /* Error log consumer callback  */
 | |
| 	void *pErrData     /* Last argument to xErr() */
 | |
| ) {
 | |
| 	ph7_gen_state *pGen = &pVm->sCodeGen;
 | |
| 	/* Zero the structure */
 | |
| 	SyZero(pGen, sizeof(ph7_gen_state));
 | |
| 	/* Initial state */
 | |
| 	pGen->pVm  = &(*pVm);
 | |
| 	pGen->xErr = xErr;
 | |
| 	pGen->pErrData = pErrData;
 | |
| 	SySetInit(&pGen->aLabel, &pVm->sAllocator, sizeof(Label));
 | |
| 	SySetInit(&pGen->aGoto, &pVm->sAllocator, sizeof(JumpFixup));
 | |
| 	SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0);
 | |
| 	SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0);
 | |
| 	/* Error log buffer */
 | |
| 	SyBlobInit(&pGen->sErrBuf, &pVm->sAllocator);
 | |
| 	/* General purpose working buffer */
 | |
| 	SyBlobInit(&pGen->sWorker, &pVm->sAllocator);
 | |
| 	/* Create the global scope */
 | |
| 	PH7_GenStateInitBlock(pGen, &pGen->sGlobal, GEN_BLOCK_GLOBAL, PH7_VmInstrLength(&(*pVm)), 0);
 | |
| 	/* Point to the global scope */
 | |
| 	pGen->pCurrent = &pGen->sGlobal;
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Utility routines. Reset the code generator to it's initial state.
 | |
|  */
 | |
| PH7_PRIVATE sxi32 PH7_ResetCodeGenerator(
 | |
| 	ph7_vm *pVm,       /* Target VM */
 | |
| 	ProcConsumer xErr, /* Error log consumer callback  */
 | |
| 	void *pErrData     /* Last argument to xErr() */
 | |
| ) {
 | |
| 	ph7_gen_state *pGen = &pVm->sCodeGen;
 | |
| 	GenBlock *pBlock, *pParent;
 | |
| 	/* Reset state */
 | |
| 	SySetReset(&pGen->aLabel);
 | |
| 	SySetReset(&pGen->aGoto);
 | |
| 	SyBlobRelease(&pGen->sErrBuf);
 | |
| 	SyBlobRelease(&pGen->sWorker);
 | |
| 	/* Point to the global scope */
 | |
| 	pBlock = pGen->pCurrent;
 | |
| 	while(pBlock->pParent != 0) {
 | |
| 		pParent = pBlock->pParent;
 | |
| 		PH7_GenStateFreeBlock(pBlock);
 | |
| 		pBlock = pParent;
 | |
| 	}
 | |
| 	pGen->xErr = xErr;
 | |
| 	pGen->pErrData = pErrData;
 | |
| 	pGen->pCurrent = &pGen->sGlobal;
 | |
| 	pGen->pRawIn = pGen->pRawEnd = 0;
 | |
| 	pGen->pIn = pGen->pEnd = 0;
 | |
| 	return SXRET_OK;
 | |
| }
 | |
| /*
 | |
|  * Generate a compile-time error message.
 | |
|  * If the error count limit is reached (usually 15 error message)
 | |
|  * this function return SXERR_ABORT.In that case upper-layers must
 | |
|  * abort compilation immediately.
 | |
|  */
 | |
| PH7_PRIVATE sxi32 PH7_GenCompileError(ph7_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...) {
 | |
| 	SyBlob *pWorker = &pGen->sErrBuf;
 | |
| 	const char *zErr;
 | |
| 	SyString *pFile;
 | |
| 	va_list ap;
 | |
| 	sxi32 rc;
 | |
| 	/* Reset the working buffer */
 | |
| 	SyBlobReset(pWorker);
 | |
| 	/* Peek the processed file path if available */
 | |
| 	pFile = (SyString *)SySetPeek(&pGen->pVm->aFiles);
 | |
| 	if(pGen->xErr == 0) {
 | |
| 		/* No available error consumer, return immediately */
 | |
| 		return SXRET_OK;
 | |
| 	}
 | |
| 	switch(nErrType) {
 | |
| 		case E_DEPRECATED:
 | |
| 			zErr = "Deprecated";
 | |
| 			break;
 | |
| 		case E_NOTICE:
 | |
| 			zErr = "Notice";
 | |
| 			break;
 | |
| 		case E_WARNING:
 | |
| 			zErr = "Warning";
 | |
| 			break;
 | |
| 		default:
 | |
| 			zErr = "Error";
 | |
| 			break;
 | |
| 	}
 | |
| 	rc = SXRET_OK;
 | |
| 	/* Format the error message */
 | |
| 	SyBlobFormat(pWorker, "%s: ", zErr);
 | |
| 	va_start(ap, zFormat);
 | |
| 	SyBlobFormatAp(pWorker, zFormat, ap);
 | |
| 	va_end(ap);
 | |
| 	if(pFile && pGen->xErr) {
 | |
| 		/* Append file name and line */
 | |
| 		SyBlobFormat(pWorker, " in %s:%d", pFile->zString, nLine);
 | |
| 	}
 | |
| 	/* Append a new line */
 | |
| 	SyBlobAppend(pWorker, (const void *)"\n", sizeof(char));
 | |
| 	if(SyBlobLength(pWorker) > 0) {
 | |
| 		/* Consume the generated error message */
 | |
| 		pGen->xErr(SyBlobData(pWorker), SyBlobLength(pWorker), pGen->pErrData);
 | |
| 	}
 | |
| 	if(nErrType == E_ERROR) {
 | |
| 		/* Shutdown library and abort script execution */
 | |
| 		ph7_lib_shutdown();
 | |
| 		exit(255);
 | |
| 	}
 | |
| 	return rc;
 | |
| }
 |