/* * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ * Version 1.7.2 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES * please contact Symisc Systems via: * legal@symisc.net * licensing@symisc.net * contact@symisc.net * or visit: * http://jx9.symisc.net/ */ /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* * This file implement a thread-safe and full-reentrant compiler for the JX9 engine. * That is, routines defined in this file takes a stream of tokens and output * JX9 bytecode instructions. */ /* Forward declaration */ typedef struct LangConstruct LangConstruct; typedef struct JumpFixup JumpFixup; /* Block [i.e: set of statements] control flags */ #define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */ #define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ #define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ #define GEN_BLOCK_FUNC 0x008 /* Function body */ #define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ #define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ #define GEN_BLOCK_EXPR 0x040 /* Expression */ #define GEN_BLOCK_STD 0x080 /* Standard block */ #define GEN_BLOCK_SWITCH 0x100 /* Switch statement */ /* * Compilation of some JX9 constructs such as if, for, while, the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure. Those jumps are fixed later when the jump destination is resolved. */ struct JumpFixup { sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ }; /* * Each language construct is represented by an instance * of the following structure. */ struct LangConstruct { sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */ ProcLangConstruct xConstruct; /* C function implementing the language construct */ }; /* Compilation flags */ #define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ /* Token stream synchronization macros */ #define SWAP_TOKEN_STREAM(GEN, START, END)\ pTmp = GEN->pEnd;\ pGen->pIn = START;\ pGen->pEnd = END #define UPDATE_TOKEN_STREAM(GEN)\ if( GEN->pIn < pTmp ){\ GEN->pIn++;\ }\ GEN->pEnd = pTmp #define SWAP_DELIMITER(GEN, START, END)\ pTmpIn = GEN->pIn;\ pTmpEnd = GEN->pEnd;\ GEN->pIn = START;\ GEN->pEnd = END #define RE_SWAP_DELIMITER(GEN)\ GEN->pIn = pTmpIn;\ GEN->pEnd = pTmpEnd /* Flags related to expression compilation */ #define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ #define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */ #define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */ /* Forward declaration */ static sxi32 jx9CompileExpr( jx9_gen_state *pGen, /* Code generator state */ sxi32 iFlags, /* Control flags */ sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ ); /* * Recover from a compile-time error. In other words synchronize * the token stream cursor with the first semi-colon seen. */ static sxi32 jx9ErrorRecover(jx9_gen_state *pGen) { /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){ pGen->pIn++; } return SXRET_OK; } /* * Check if the given identifier name is reserved or not. * Return TRUE if reserved.FALSE otherwise. */ static int GenStateIsReservedID(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; } /* * Check if a given token value is installed in the literal table. */ static sxi32 GenStateFindLiteral(jx9_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 jx9_value must be of type string. */ static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_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; } /* * Generate a fatal error. */ static sxi32 GenStateOutOfMem(jx9_gen_state *pGen) { jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory"); /* Abort compilation immediately */ return SXERR_ABORT; } /* * Fetch a block that correspond to the given criteria from the stack of * compiled blocks. * Return a pointer to that block on success. NULL otherwise. */ static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount) { GenBlock *pBlock = pCurrent; for(;;){ if( pBlock->iFlags & iBlockType ){ iCount--; /* Decrement nesting level */ if( iCount < 1 ){ /* Block meet with the desired criteria */ return pBlock; } } /* Point to the upper block */ pBlock = pBlock->pParent; if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ /* Forbidden */ break; } } /* No such block */ return 0; } /* * Initialize a freshly allocated block instance. */ static void GenStateInitBlock( jx9_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; pBlock->bPostContinue = 0; SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); } /* * Allocate a new block instance. * Return SXRET_OK and write a pointer to the new instantiated block * on success.Otherwise generate a compile-time error and abort * processing on failure. */ static sxi32 GenStateEnterBlock( jx9_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. */ return GenStateOutOfMem(pGen); } /* Zero the structure */ SyZero(pBlock, sizeof(GenBlock)); GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData); /* Link to the parent block */ pBlock->pParent = pGen->pCurrent; /* Mark as the current block */ pGen->pCurrent = pBlock; if( ppBlock ){ /* Write a pointer to the new instance */ *ppBlock = pBlock; } return SXRET_OK; } /* * Release block fields without freeing the whole instance. */ static void GenStateReleaseBlock(GenBlock *pBlock) { SySetRelease(&pBlock->aPostContFix); SySetRelease(&pBlock->aJumpFix); } /* * Release a block. */ static void GenStateFreeBlock(GenBlock *pBlock) { jx9_gen_state *pGen = pBlock->pGen; GenStateReleaseBlock(&(*pBlock)); /* Free the instance */ SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock); } /* * POP and release a block from the stack of compiled blocks. */ static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock) { GenBlock *pBlock = pGen->pCurrent; if( pBlock == 0 ){ /* No more block to pop */ return SXERR_EMPTY; } /* Point to the upper block */ pGen->pCurrent = pBlock->pParent; if( ppBlock ){ /* Write a pointer to the popped block */ *ppBlock = pBlock; }else{ /* Safely release the block */ GenStateFreeBlock(&(*pBlock)); } return SXRET_OK; } /* * Emit a forward jump. * Notes on forward jumps * Compilation of some JX9 constructs such as if, for, while and the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure. Those jumps are fixed later when the jump destination is resolved. */ static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx) { JumpFixup sJumpFix; sxi32 rc; /* Init the JumpFixup structure */ sJumpFix.nJumpType = nJumpType; sJumpFix.nInstrIdx = nInstrIdx; /* Insert in the jump fixup table */ rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); return rc; } /* * Fix a forward jump now the jump destination is resolved. * Return the total number of fixed jumps. * Notes on forward jumps: * Compilation of some JX9 constructs such as if, for, while and the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure.Those jumps are fixed later when the jump destination is resolved. */ static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest) { JumpFixup *aFix; VmInstr *pInstr; sxu32 nFixed; sxu32 n; /* Point to the jump fixup table */ aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); /* Fix the desired jumps */ for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ if( aFix[n].nJumpType < 0 ){ /* Already fixed */ continue; } if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ /* Not of our interest */ continue; } /* Point to the instruction to fix */ pInstr = jx9VmGetInstr(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; } /* * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] * in the constant table. */ static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx) { jx9_value *pObj; sxu32 nIdx = 0; /* cc warning */ /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ GenStateOutOfMem(pGen); return 0; } *pIdx = nIdx; /* TODO(chems): Create a numeric table (64bit int keys) same as * the constant string iterals table [optimization purposes]. */ return pObj; } /* * Compile a numeric [i.e: integer or real] literal. * Notes on the integer type. * According to the JX9 language reference manual * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) * or binary (base 2) notation, optionally preceded by a sign (- or +). * To use octal notation, precede the number with a 0 (zero). To use hexadecimal * notation precede the number with 0x. To use binary notation precede the number with 0b. */ static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyToken *pToken = pGen->pIn; /* Raw token */ sxu32 nIdx = 0; if( pToken->nType & JX9_TK_INTEGER ){ jx9_value *pObj; sxi64 iValue; iValue = jx9TokenValueToInt64(&pToken->sData); pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } jx9MemObjInitFromInt(pGen->pVm, pObj, iValue); }else{ /* Real number */ jx9_value *pObj; /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); jx9MemObjToReal(pObj); } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a nowdoc string. * According to the JX9 language reference manual: * * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. * The construct is ideal for embedding JX9 code or other large blocks of text without the * need for escaping. It shares some features in common with the SGML * construct, in that it declares a block of text which is not for parsing. * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc * identifiers also apply to nowdoc identifiers, especially those regarding the appearance * of the closing identifier. */ static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ jx9_value *pObj; sxu32 nIdx; nIdx = 0; /* Prevent compiler warning */ if( pStr->nByte <= 0 ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory"); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } /* No processing is done here, simply a memcpy() operation */ jx9MemObjInitFromString(pGen->pVm, pObj, pStr); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a single quoted string. * According to the JX9 language reference manual: * * The simplest way to specify a string is to enclose it in single quotes (the character ' ). * To specify a literal single quote, escape it with a backslash (\). To specify a literal * backslash, double it (\\). All other instances of backslash will be treated as a literal * backslash: this means that the other escape sequences you might be used to, such as \r * or \n, will be output literally as specified rather than having any special meaning. * */ JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag) { SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ const char *zIn, *zCur, *zEnd; jx9_value *pObj; sxu32 nIdx; nIdx = 0; /* Prevent compiler warning */ /* Delimit the string */ zIn = pStr->zString; zEnd = &zIn[pStr->nByte]; if( zIn >= zEnd ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){ /* Already processed, emit the load constant instruction * and return. */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return SXRET_OK; } /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory"); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } jx9MemObjInitFromString(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*/ jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); } zIn++; if( zIn < zEnd ){ if( zIn[0] == '\\' ){ /* A literal backslash */ jx9MemObjStringAppend(pObj, "\\", sizeof(char)); }else if( zIn[0] == '\'' ){ /* A single quote */ jx9MemObjStringAppend(pObj, "'", sizeof(char)); }else{ /* verbatim copy */ zIn--; jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2); zIn++; } } /* Advance the stream cursor */ zIn++; } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); if( pStr->nByte < 1024 ){ /* Install in the literal table */ GenStateInstallLiteral(pGen, pObj, nIdx); } /* Node successfully compiled */ return SXRET_OK; } /* * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string. * According to the JX9 language reference manual * When a string is specified in double quotes or with heredoc, variables are parsed within it. * There are two types of syntax: a simple one and a complex one. The simple syntax is the most * common and convenient. It provides a way to embed a variable, an array value, or an object * property in a string with a minimum of effort. * Simple syntax * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify * the end of the name. * Similarly, an array index or an object property can be parsed. With array indices, the closing * square bracket (]) marks the end of the index. The same rules apply to object properties * as to simple variables. * Complex (curly) syntax * This isn't called complex because the syntax is complex, but because it allows for the use * of complex expressions. * Any scalar variable, array element or object property with a string representation can be * included via this syntax. Simply write the expression the same way as it would appear outside * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ */ static sxi32 GenStateProcessStringExpression( jx9_gen_state *pGen, /* Code generator state */ 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 */ jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken); /* Swap delimiter */ pTmpIn = pGen->pIn; pTmpEnd = pGen->pEnd; pGen->pIn = (SyToken *)SySetBasePtr(&sToken); pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Restore token stream */ pGen->pIn = pTmpIn; pGen->pEnd = pTmpEnd; /* Release the token set */ SySetRelease(&sToken); /* Compilation result */ return rc; } /* * Reserve a new constant for a double quoted/heredoc string. */ static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount) { jx9_value *pConstObj; sxu32 nIdx = 0; /* Reserve a new constant */ pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pConstObj == 0 ){ GenStateOutOfMem(&(*pGen)); return 0; } (*pCount)++; jx9MemObjInitFromString(pGen->pVm, pConstObj, 0); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return pConstObj; } /* * Compile a double quoted/heredoc string. * According to the JX9 language reference manual * Heredoc * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier * is provided, then a newline. The string itself follows, and then the same identifier again * to close the quotation. * The closing identifier must begin in the first column of the line. Also, the identifier must * follow the same naming rules as any other label in JX9: it must contain only alphanumeric * characters and underscores, and must start with a non-digit character or underscore. * Warning * It is very important to note that the line with the closing identifier must contain * no other characters, except possibly a semicolon (;). That means especially that the identifier * may not be indented, and there may not be any spaces or tabs before or after the semicolon. * It's also important to realize that the first character before the closing identifier must * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before * the end of the current file, a parse error will result at the last line. * Heredocs can not be used for initializing object properties. * Double quoted * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters: * Escaped characters Sequence Meaning * \n linefeed (LF or 0x0A (10) in ASCII) * \r carriage return (CR or 0x0D (13) in ASCII) * \t horizontal tab (HT or 0x09 (9) in ASCII) * \v vertical tab (VT or 0x0B (11) in ASCII) * \f form feed (FF or 0x0C (12) in ASCII) * \\ backslash * \$ dollar sign * \" double-quote * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation * As in single quoted strings, escaping any other character will result in the backslash being printed too. * The most important feature of double-quoted strings is the fact that variable names will be expanded. * See string parsing for details. */ static sxi32 GenStateCompileString(jx9_gen_state *pGen) { SyString *pStr = &pGen->pIn->sData; /* Raw token value */ const char *zIn, *zCur, *zEnd; jx9_value *pObj = 0; sxi32 iCons; sxi32 rc; /* Delimit the string */ zIn = pStr->zString; zEnd = &zIn[pStr->nByte]; if( zIn >= zEnd ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } zCur = 0; /* Compile the node */ iCons = 0; for(;;){ zCur = zIn; while( zIn < zEnd && zIn[0] != '\\' ){ if(zIn[0] == '$' && &zIn[1] < zEnd && (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){ break; } zIn++; } if( zIn > zCur ){ if( pObj == 0 ){ pObj = GenStateNewStrObj(&(*pGen), &iCons); if( pObj == 0 ){ return SXERR_ABORT; } } jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); } if( zIn >= zEnd ){ break; } if( zIn[0] == '\\' ){ const char *zPtr = 0; sxu32 n; zIn++; if( zIn >= zEnd ){ break; } if( pObj == 0 ){ pObj = GenStateNewStrObj(&(*pGen), &iCons); if( pObj == 0 ){ return SXERR_ABORT; } } n = sizeof(char); /* size of conversion */ switch( zIn[0] ){ case '$': /* Dollar sign */ jx9MemObjStringAppend(pObj, "$", sizeof(char)); break; case '\\': /* A literal backslash */ jx9MemObjStringAppend(pObj, "\\", sizeof(char)); break; case 'a': /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ jx9MemObjStringAppend(pObj, "\a", sizeof(char)); break; case 'b': /* Backspace (BS)[ctrl+h] ASCII code 8 */ jx9MemObjStringAppend(pObj, "\b", sizeof(char)); break; case 'f': /* Form-feed (FF)[ctrl+l] ASCII code 12 */ jx9MemObjStringAppend(pObj, "\f", sizeof(char)); break; case 'n': /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ jx9MemObjStringAppend(pObj, "\n", sizeof(char)); break; case 'r': /* Carriage return (CR)[ctrl+m] ASCII code 13 */ jx9MemObjStringAppend(pObj, "\r", sizeof(char)); break; case 't': /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ jx9MemObjStringAppend(pObj, "\t", sizeof(char)); break; case 'v': /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ jx9MemObjStringAppend(pObj, "\v", sizeof(char)); break; case '\'': /* Single quote */ jx9MemObjStringAppend(pObj, "'", sizeof(char)); break; case '"': /* Double quote */ jx9MemObjStringAppend(pObj, "\"", sizeof(char)); break; case '0': /* NUL byte */ jx9MemObjStringAppend(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 */ jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); n += sizeof(char) * 2; }else{ /* Output literal character */ jx9MemObjStringAppend(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 ){ jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); } n = (sxu32)(zPtr-zIn); }else{ /* Output literal character */ jx9MemObjStringAppend(pObj, "o", sizeof(char)); } break; default: /* Output without a slash */ jx9MemObjStringAppend(pObj, zIn, sizeof(char)); break; } /* Advance the stream cursor */ zIn += n; continue; } if( zIn[0] == '{' ){ /* Curly syntax */ const char *zExpr; sxi32 iNest = 1; zIn++; zExpr = zIn; /* Synchronize with the next closing curly braces */ while( zIn < zEnd ){ if( zIn[0] == '{' ){ /* Increment nesting level */ iNest++; }else if(zIn[0] == '}' ){ /* Decrement nesting level */ iNest--; if( iNest <= 0 ){ break; } } zIn++; } /* Process the expression */ rc = GenStateProcessStringExpression(&(*pGen),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] == '.' ){ /* Member access operator '.' */ zIn++; }else{ break; } } /* Process the expression */ rc = GenStateProcessStringExpression(&(*pGen),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 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0); } /* Node successfully compiled */ return SXRET_OK; } /* * Compile a double quoted string. * See the block-comment above for more information. */ JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag) { sxi32 rc; rc = GenStateCompileString(&(*pGen)); SXUNUSED(iCompileFlag); /* cc warning */ /* Compilation result */ return rc; } /* * Compile a literal which is an identifier(name) for simple values. */ JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyToken *pToken = pGen->pIn; jx9_value *pObj; SyString *pStr; sxu32 nIdx; /* Extract token value */ pStr = &pToken->sData; /* Deal with the reserved literals [i.e: null, false, true, ...] first */ if( pStr->nByte == sizeof("NULL") - 1 ){ if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){ /* NULL constant are always indexed at 0 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){ /* TRUE constant are always indexed at 1 */ jx9VmEmitInstr(pGen->pVm, JX9_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 */ jx9VmEmitInstr(pGen->pVm, JX9_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 = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return GenStateOutOfMem(pGen); } jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return SXRET_OK; }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 && SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-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 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); }else{ /* Extract the target function/method */ jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData; pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); } return SXRET_OK; } /* Query literal table */ if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){ jx9_value *pObj; /* Unknown literal, install it in the literal table */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); GenStateInstallLiteral(&(*pGen), pObj, nIdx); } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile an array entry whether it is a key or a value. */ static sxi32 GenStateCompileJSONEntry( jx9_gen_state *pGen, /* Code generator state */ SyToken *pIn, /* Token stream */ SyToken *pEnd, /* End of the token stream */ sxi32 iFlags, /* Compilation flags */ sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */ ) { SyToken *pTmpIn, *pTmpEnd; sxi32 rc; /* Swap token stream */ SWAP_DELIMITER(pGen, pIn, pEnd); /* Compile the expression*/ rc = jx9CompileExpr(&(*pGen), iFlags, xValidator); /* Restore token stream */ RE_SWAP_DELIMITER(pGen); return rc; } /* * Compile a Jx9 JSON Array. */ JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag) { sxi32 nPair = 0; SyToken *pCur; sxi32 rc; pGen->pIn++; /* Jump the open square bracket '['*/ pGen->pEnd--; SXUNUSED(iCompileFlag); /* cc warning */ for(;;){ /* Jump leading commas */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } pCur = pGen->pIn; if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ /* No more entry to process */ break; } /* Compile entry */ rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } nPair++; } /* Emit the load map instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0); /* Node successfully compiled */ return SXRET_OK; } /* * Node validator for a given JSON key. */ static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) { sxi32 rc = SXRET_OK; if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){ /* Unexpected expression */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, "JSON Object: Unexpected expression, key must be of type string, literal or simple variable"); if( rc != SXERR_ABORT ){ rc = SXERR_INVALID; } } return rc; } /* * Compile a Jx9 JSON Object */ JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag) { SyToken *pKey, *pCur; sxi32 nPair = 0; sxi32 rc; pGen->pIn++; /* Jump the open querly braces '{'*/ pGen->pEnd--; SXUNUSED(iCompileFlag); /* cc warning */ for(;;){ /* Jump leading commas */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } pCur = pGen->pIn; if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ /* No more entry to process */ break; } /* Compile the key */ pKey = pCur; while( pCur < pGen->pIn ){ if( pCur->nType & JX9_TK_COLON /*':'*/ ){ break; } pCur++; } rc = SXERR_EMPTY; if( pCur < pGen->pIn ){ if( &pCur[1] >= pGen->pIn ){ /* Missing value */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } /* Compile the expression holding the key */ rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur, EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */, GenStateJSONObjectKeyNodeValidator /* Node validator callback */ ); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pCur++; /* Jump the double colon ':' */ }else if( pKey == pCur ){ /* Key is omitted, emit an error */ jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key"); pCur++; /* Jump the double colon ':' */ }else{ /* Reset back the cursor and point to the entry value */ pCur = pKey; } /* Compile indice value */ rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } nPair++; } /* Emit the load map instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a function [i.e: print, exit(), include(), ...] which is a langauge * construct. */ JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyString *pName; sxu32 nKeyID; sxi32 rc; /* Name of the language construct [i.e: print, die...]*/ pName = &pGen->pIn->sData; nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); pGen->pIn++; /* Jump the language construct keyword */ if( nKeyID == JX9_TKWRD_PRINT ){ SyToken *pTmp, *pNext = 0; /* Compile arguments one after one */ pTmp = pGen->pEnd; jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0); while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ if( pGen->pIn < pNext ){ pGen->pEnd = pNext; rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( rc != SXERR_EMPTY ){ /* Ticket 1433-008: Optimization #1: Consume input directly * without the overhead of a function call. * This is a very powerful optimization that improve * performance greatly. */ jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0); } } /* Jump trailing commas */ while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){ pNext++; } pGen->pIn = pNext; } /* Restore token stream */ pGen->pEnd = pTmp; }else{ sxi32 nArg = 0; sxu32 nIdx = 0; rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nArg = 1; } if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){ jx9_value *pObj; /* Emit the call instruction */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, pName); /* Install in the literal table */ GenStateInstallLiteral(&(*pGen), pObj, nIdx); } /* Emit the call instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0); } /* Node successfully compiled */ return SXRET_OK; } /* * Compile a node holding a variable declaration. * According to the J9X language reference * Variables in JX9 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 JX9. A valid variable name * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters * numbers, or underscores. * By default, variables are always assigned by value unless the target value is a JSON * array or a JSON object which is passed by reference. */ JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag) { sxu32 nLine = pGen->pIn->nLine; SyHashEntry *pEntry; SyString *pName; char *zName = 0; sxi32 iP1; void *p3; sxi32 rc; pGen->pIn++; /* Jump the dollar sign '$' */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid variable name */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* 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 ){ return GenStateOutOfMem(pGen); } /* Install in the hashtable */ SyHashInsert(&pGen->hVar, zName, pName->nByte, zName); }else{ /* Name already available */ zName = (char *)pEntry->pUserData; } p3 = (void *)zName; iP1 = 0; if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ /* Read-only load.In other words do not create the variable if inexistant */ iP1 = 1; } } /* Emit the load instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0); /* Node successfully compiled */ return SXRET_OK; } /* Forward declaration */ static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc); /* * Compile an annoynmous function or a closure. * According to the JX9 language reference * Anonymous functions, also known as closures, allow the creation of functions * which have no specified name. They are most useful as the value of callback * parameters, but they have many other uses. Closures can also be used as * the values of variables; Assigning a closure to a variable uses the same * syntax as any other assignment, including the trailing semicolon: * Example Anonymous function variable assignment example * $greet = function($name) * { * printf("Hello %s\r\n", $name); * }; * $greet('World'); * $greet('JX9'); * Note that the implementation of annoynmous function and closure under * JX9 is completely different from the one used by the engine. */ JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag) { jx9_vm_func *pAnnonFunc; /* Annonymous function body */ char zName[512]; /* Unique lambda name */ static int iCnt = 1; /* There is no worry about thread-safety here, because only * one thread is allowed to compile the script. */ jx9_value *pObj; SyString sName; sxu32 nIdx; sxu32 nLen; sxi32 rc; pGen->pIn++; /* Jump the 'function' keyword */ if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ pGen->pIn++; } /* Reserve a constant for the lambda */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ GenStateOutOfMem(pGen); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } /* Generate a unique name */ nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); /* Make sure the generated name is unique */ while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){ nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); } SyStringInitFromBuf(&sName, zName, nLen); jx9MemObjInitFromString(pGen->pVm, pObj, &sName); /* Compile the lambda body */ rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile the 'continue' statement. * According to the JX9 language reference * continue is used within looping structures to skip the rest of the current loop iteration * and continue execution at the condition evaluation and then the beginning of the next * iteration. * Note: Note that in JX9 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 jx9CompileContinue(jx9_gen_state *pGen) { GenBlock *pLoop; /* Target loop */ sxi32 iLevel; /* How many nesting loop to skip */ sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; iLevel = 0; /* Jump the 'continue' keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ /* optional numeric argument which tells us how many levels * of enclosing loops we should skip to the end of. */ iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); if( iLevel < 2 ){ iLevel = 0; } pGen->pIn++; /* Jump the optional numeric argument */ } /* Point to the target loop */ pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); if( pLoop == 0 ){ /* Illegal continue */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } }else{ sxu32 nInstrIdx = 0; /* Emit the unconditional jump to the beginning of the target loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); if( pLoop->bPostContinue == TRUE ){ JumpFixup sJumpFix; /* Post-continue */ sJumpFix.nJumpType = JX9_OP_JMP; sJumpFix.nInstrIdx = nInstrIdx; SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); } } if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Not so fatal, emit a warning only */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement"); } /* Statement successfully compiled */ return SXRET_OK; } /* * Compile the 'break' statement. * According to the JX9 language reference * break ends execution of the current for, foreach, while, do-while or switch * structure. * break accepts an optional numeric argument which tells it how many nested * enclosing structures are to be broken out of. */ static sxi32 jx9CompileBreak(jx9_gen_state *pGen) { GenBlock *pLoop; /* Target loop */ sxi32 iLevel; /* How many nesting loop to skip */ sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; iLevel = 0; /* Jump the 'break' keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ /* optional numeric argument which tells us how many levels * of enclosing loops we should skip to the end of. */ iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); if( iLevel < 2 ){ iLevel = 0; } pGen->pIn++; /* Jump the optional numeric argument */ } /* Extract the target loop */ pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); if( pLoop == 0 ){ /* Illegal break */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } }else{ sxu32 nInstrIdx; rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx); if( rc == SXRET_OK ){ /* Fix the jump later when the jump destination is resolved */ GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx); } } if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Not so fatal, emit a warning only */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement"); } /* Statement successfully compiled */ return SXRET_OK; } /* Forward declaration */ static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags); /* * Compile a JX9 block. * A block is simply one or more JX9 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 jx9CompileBlock( jx9_gen_state *pGen /* Code generator state */ ) { sxi32 rc; if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){ sxu32 nLine = pGen->pIn->nLine; rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(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 ){ /* No more token to process. Missing closing braces */ jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'"); break; } if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){ /* Closing braces found, break immediately*/ pGen->pIn++; break; } /* Compile a single statement */ rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } GenStateLeaveBlock(&(*pGen), 0); }else{ /* Compile a single statement */ rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } /* Jump trailing semi-colons ';' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the gentle 'while' statement. * According to the JX9 language reference * while loops are the simplest type of loop in JX9.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 JX9 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 JX9 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 jx9CompileWhile(jx9_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 & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the condition */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } /* Synchronize pointers */ pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; /* Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump); /* Compile the loop body */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid * compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the complex and powerful 'for' statement. * According to the JX9 language reference * for loops are the most complex loops in JX9. 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 * (JX9 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 jx9CompileFor(jx9_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 & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Delimit the init-expr;condition;post-expr */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Synchronize */ pGen->pIn = pEnd; if( pGen->pIn < pGen->pEnd ){ pGen->pIn++; } return SXRET_OK; } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile initialization expressions if available */ rc = jx9CompileExpr(&(*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 ){ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ';' after initialization expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the trailing ';' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Deffer continue jumps */ pForBlock->bPostContinue = TRUE; /* Compile the condition */ rc = jx9CompileExpr(&(*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 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump); } if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ';' after conditionals expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the trailing ';' */ pGen->pIn++; /* Save the post condition stream */ pPostStart = pGen->pIn; /* Compile the loop body */ pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ pGen->pEnd = pTmp; rc = jx9CompileBlock(&(*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 = jx9VmInstrLength(pGen->pVm); for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx); if( pInstr ){ /* Fix jump */ pInstr->iP2 = nJumpDest; } } } /* compile the post-expressions if available */ while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){ pPostStart++; } if( pPostStart < pEnd ){ SyToken *pTmpIn, *pTmpEnd; SWAP_DELIMITER(pGen, pPostStart, pEnd); rc = jx9CompileExpr(&(*pGen), 0, 0); if( pGen->pIn < pGen->pEnd ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } RE_SWAP_DELIMITER(pGen); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; }else if( rc != SXERR_EMPTY){ /* Pop operand lvalue */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; } /* Expression tree validator callback used by the 'foreach' statement. * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] * are allowed. */ static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) { sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ if( pRoot->xCode != jx9CompileVariable ){ /* Unexpected expression */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, "foreach: Expecting a variable name" ); if( rc != SXERR_ABORT ){ rc = SXERR_INVALID; } } return rc; } /* * Compile the 'foreach' statement. * According to the JX9 language reference * The foreach construct simply gives an easy way to iterate over arrays. foreach works * only on arrays (and objects), and will issue an error when you try to use it on a variable * with a different data type or an uninitialized variable. There are two syntaxes; the second * is a minor but useful extension of the first: * foreach (json_array_json_object as $value) * statement * foreach (json_array_json_objec as $key,$value) * statement * The first form loops over the array given by array_expression. On each loop, the value * of the current element is assigned to $value and the internal array pointer is advanced * by one (so on the next loop, you'll be looking at the next element). * The second form does the same thing, except that the current element's key will be assigned * to the variable $key on each loop. * Note: * When foreach first starts executing, the internal array pointer is automatically reset to the * first element of the array. This means that you do not need to call reset() before a foreach loop. */ static sxi32 jx9CompileForeach(jx9_gen_state *pGen) { SyToken *pCur, *pTmp, *pEnd = 0; GenBlock *pForeachBlock = 0; jx9_foreach_info *pInfo; sxu32 nFalseJump; VmInstr *pInstr; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'foreach' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the expression */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Synchronize */ pGen->pIn = pEnd; if( pGen->pIn < pGen->pEnd ){ pGen->pIn++; } return SXRET_OK; } /* Compile the array expression */ pCur = pGen->pIn; while( pCur < pEnd ){ if( pCur->nType & JX9_TK_KEYWORD ){ sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); if( nKeywrd == JX9_TKWRD_AS ){ /* Break with the first 'as' found */ break; } } /* Advance the stream cursor */ pCur++; } if( pCur <= pGen->pIn ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing array/object expression"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } goto Synchronize; } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pCur; rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pCur ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pGen->pIn++; } pCur++; /* Jump the 'as' keyword */ pGen->pIn = pCur; if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } /* Create the foreach context */ pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info)); if( pInfo == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory"); return SXERR_ABORT; } /* Zero the structure */ SyZero(pInfo, sizeof(jx9_foreach_info)); /* Initialize structure fields */ SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *)); /* Check if we have a key field */ while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){ pCur++; } if( pCur < pEnd ){ /* Compile the expression holding the key name */ if( pGen->pIn >= pCur ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } }else{ pGen->pEnd = pCur; rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pInstr = jx9VmPopInstr(pGen->pVm); if( pInstr->p3 ){ /* Record key name */ SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); } pInfo->iFlags |= JX9_4EACH_STEP_KEY; } pGen->pIn = &pCur[1]; /* Jump the arrow */ } pGen->pEnd = pEnd; if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } goto Synchronize; } /* Compile the expression holding the value name */ rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pInstr = jx9VmPopInstr(pGen->pVm); if( pInstr->p3 ){ /* Record value name */ SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); } /* Emit the 'FOREACH_INIT' instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump); /* Record the first instruction to execute */ pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm); /* Emit the FOREACH_STEP instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump); /* Compile the loop body */ pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid * compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the infamous if/elseif/else if/else statements. * According to the JX9 language reference * The if construct is one of the most important features of many languages JX9 included. * It allows for conditional execution of code fragments. JX9 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 * elseif * elseif, as its name suggests, is a combination of if and else. Like else, it extends * an if statement to execute a different statement in case the original if expression evaluates * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif * conditional expression evaluates to TRUE. 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) { * print "a is bigger than b"; * } elseif ($a == $b) { * print "a is equal to b"; * } else { * print "a is smaller than b"; * } */ static sxi32 jx9CompileIf(jx9_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 = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(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 & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ if( pToken >= pGen->pEnd ){ pToken--; } rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pToken++; /* Delimit the condition */ jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){ /* Syntax error */ if( pToken >= pGen->pEnd ){ pToken--; } rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Swap token streams */ SWAP_TOKEN_STREAM(pGen, pToken, pEnd); /* Compile the condition */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Update token stream */ while(pGen->pIn < pEnd ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); pGen->pIn++; } 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 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx); /* Compile the body */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ break; } /* Ensure that the keyword ID is 'else if' or 'else' */ nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){ break; } /* Emit the unconditional jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx); if( nKeyID & JX9_TKWRD_ELSE ){ pToken = &pGen->pIn[1]; if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 || SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){ break; } pGen->pIn++; /* Jump the 'else' keyword */ } pGen->pIn++; /* Jump the 'elseif/if' keyword */ /* Synchronize cursors */ pToken = pGen->pIn; /* Fix the false jump */ GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); } /* For(;;) */ /* Fix the false jump */ GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) && (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){ /* Compile the else block */ pGen->pIn++; rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } nJumpIdx = jx9VmInstrLength(pGen->pVm); /* Fix all unconditional jumps now the destination is resolved */ GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx); /* Release the conditional block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the return statement. * According to the JX9 language reference * 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 JX9 has less work to do in this case. * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead.. */ static sxi32 jx9CompileReturn(jx9_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 & JX9_TK_SEMI) == 0 ){ /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nRet = 1; } } /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_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 jx9CompileHalt(jx9_gen_state *pGen) { sxi32 nExpr = 0; sxi32 rc; /* Jump the die/exit keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nExpr = 1; } } /* Emit the HALT instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0); return SXRET_OK; } /* * Compile the static statement. * According to the JX9 language reference * Another important feature of variable scoping is the static variable. * A static variable exists only in a local function scope, but it does not lose its value * when program execution leaves this scope. * Static variables also provide one way to deal with recursive functions. */ static sxi32 jx9CompileStatic(jx9_gen_state *pGen) { jx9_vm_func_static_var sStatic; /* Structure describing the static variable */ jx9_vm_func *pFunc; /* Enclosing function */ GenBlock *pBlock; SyString *pName; char *zDup; sxu32 nLine; sxi32 rc; /* Jump the static keyword */ nLine = pGen->pIn->nLine; pGen->pIn++; /* Extract the enclosing function if any */ pBlock = pGen->pCurrent; while( pBlock ){ if( pBlock->iFlags & GEN_BLOCK_FUNC){ break; } /* Point to the upper block */ pBlock = pBlock->pParent; } if( pBlock == 0 ){ /* Static statement, called outside of a function body, treat it as a simple variable. */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } /* Compile the expression holding the variable */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if( rc != SXERR_EMPTY ){ /* Emit the POP instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } return SXRET_OK; } pFunc = (jx9_vm_func *)pBlock->pUserData; /* Make sure we are dealing with a valid statement */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /* Extract variable name */ pName = &pGen->pIn->sData; pGen->pIn++; /* Jump the var name */ if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData); goto Synchronize; } /* 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 ){ jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory"); return SXERR_ABORT; } SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte); /* Check if we have an expression to compile */ if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){ SySet *pInstrContainer; pGen->pIn++; /* Jump the equal '=' sign */ /* Swap bytecode container */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode); /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); /* Restore default bytecode container */ jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); } /* Finally save the compiled static variable in the appropriate container */ SySetPut(&pFunc->aStatic, (const void *)&sStatic); return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous * statement. */ while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the 'const' statement. * According to the JX9 language reference * A constant is an identifier (name) for a simple value. As the name suggests, that value * cannot change during the execution of the script (except for magic constants, which aren't actually constants). * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. * The name of a constant follows the same rules as any label in JX9. 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 object 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. */ static sxi32 jx9CompileConstant(jx9_gen_state *pGen) { SySet *pConsCode, *pInstrContainer; sxu32 nLine = pGen->pIn->nLine; SyString *pName; sxi32 rc; pGen->pIn++; /* Jump the 'const' keyword */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid constant name */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Peek constant name */ pName = &pGen->pIn->sData; /* Make sure the constant name isn't reserved */ if( GenStateIsReservedID(pName) ){ /* Reserved constant */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){ /* Invalid statement*/ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /*Jump the equal sign */ /* Allocate a new constant value container */ pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet)); if( pConsCode == 0 ){ return GenStateOutOfMem(pGen); } SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); /* Swap bytecode container */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, pConsCode); /* Compile constant value */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } SySetSetUserData(pConsCode, pGen->pVm); /* Register the constant */ rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode); if( rc != SXRET_OK ){ SySetRelease(pConsCode); SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode); } return SXRET_OK; Synchronize: /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the uplink construct. * According to the JX9 language reference * In JX9 global variables must be declared uplink inside a function if they are going * to be used in that function. * Example #1 Using global * $a = 1; * $b = 2; * function Sum() * { * uplink $a, $b; * $b = $a + $b; * } * Sum(); * print $b; * ?> * The above script will output 3. By declaring $a and $b global within the function * all references to either variable will refer to the global version. There is no limit * to the number of global variables that can be manipulated by a function. */ static sxi32 jx9CompileUplink(jx9_gen_state *pGen) { SyToken *pTmp, *pNext = 0; sxi32 nExpr; sxi32 rc; /* Jump the 'uplink' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){ /* Nothing to process */ return SXRET_OK; } pTmp = pGen->pEnd; nExpr = 0; while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ if( pGen->pIn < pNext ){ pGen->pEnd = pNext; if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } }else{ pGen->pIn++; if( pGen->pIn >= pGen->pEnd ){ /* Emit a warning */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name"); }else{ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nExpr++; } } } } /* Next expression in the stream */ pGen->pIn = pNext; /* Jump trailing commas */ while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } } /* Restore token stream */ pGen->pEnd = pTmp; if( nExpr > 0 ){ /* Emit the uplink instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0); } return SXRET_OK; } /* * Compile a switch block. * (See block-comment below for more information) */ static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart) { sxi32 rc = SXRET_OK; while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){ /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } pGen->pIn++; /* First instruction to execute in this block. */ *pBlockStart = jx9VmInstrLength(pGen->pVm); /* Compile the block until we hit a case/default/endswitch keyword * or the '}' token */ for(;;){ if( pGen->pIn >= pGen->pEnd ){ /* No more input to process */ break; } rc = SXRET_OK; if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){ rc = SXERR_EOF; break; } }else{ sxi32 nKwrd; /* Extract the keyword */ nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){ break; } } /* Compile block */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } return rc; } /* * Compile a case eXpression. * (See block-comment below for more information) */ static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_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 & JX9_TK_LPAREN /*(*/ ){ /* Increment nesting level */ iNest++; }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){ /* Decrement nesting level */ iNest--; }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){ break; } pEnd++; } if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token stream */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode); rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(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. * According to the JX9 language reference manual * 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 JX9 begin to execute the statements. * JX9 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 jx9CompileSwitch(jx9_gen_state *pGen) { GenBlock *pSwitchBlock; SyToken *pTmp, *pEnd; jx9_switch *pSwitch; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'switch' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; pEnd = 0; /* cc warning */ /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the condition */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){ pTmp = pGen->pIn; if( pTmp >= pGen->pEnd ){ pTmp--; } /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /* Jump the leading curly braces/colons */ /* Create the switch blocks container */ pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch)); if( pSwitch == 0 ){ /* Abort compilation */ return GenStateOutOfMem(pGen); } /* Zero the structure */ SyZero(pSwitch, sizeof(jx9_switch)); /* Initialize fields */ SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr)); /* Emit the switch instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0); /* Compile case blocks */ for(;;){ sxu32 nKwrd; if( pGen->pIn >= pGen->pEnd ){ /* No more input to process */ break; } if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){ /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* FALL THROUGH */ } /* Block compiled */ break; } /* Extract the keyword */ nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); if( nKwrd == JX9_TKWRD_DEFAULT ){ /* * Accroding to the JX9 language reference manual * 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 = jx9GenCompileError(&(*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 = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault); if( rc == SXERR_ABORT){ return SXERR_ABORT; }else if( rc == SXERR_EOF ){ break; } }else if( nKwrd == JX9_TKWRD_CASE ){ jx9_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 = GenStateCompileCaseExpr(pGen, &sCase); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Compile the case block */ rc = 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 */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } break; } } /* Fix all jumps now the destination is resolved */ pSwitch->nOut = jx9VmInstrLength(pGen->pVm); GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); if( pGen->pIn < pGen->pEnd ){ /* Jump the trailing curly braces */ pGen->pIn++; } /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } 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"; * } * Some features: * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous * functions, array member, ..] * 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.JX9_EOL; * } * function foo($a, $b) { * return $a + $b; * } * print foo(5); // Prints "5" * print foo(5, 2); // Prints "7" * // Same arg * function foo(string $a) * { * print "a is a string\n"; * dump($a); * } * function foo(int $a) * { * print "a is integer\n"; * dump($a); * } * function foo(array $a) * { * print "a is an array\n"; * 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 JX9 engine. */ static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) { SyToken *pTmpIn, *pTmpEnd; SySet *pInstrContainer; sxi32 rc; /* Swap token stream */ SWAP_DELIMITER(pGen, pIn, pEnd); pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode); /* Compile the expression holding the argument value */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); RE_SWAP_DELIMITER(pGen); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } /* * Collect function arguments one after one. * According to the JX9 language reference manual. * Information may be passed to functions via the argument list, which is a comma-delimited * list of expressions. * JX9 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 * * 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 * * * JX9 have introduced powerful extension including full type hinting, function overloading * complex agrument values.Please refer to the official documentation for more information * on these extension. */ static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd) { jx9_vm_func_arg sArg; /* Current processed argument */ SyToken *pCur, *pIn; /* Token stream */ SyBlob sSig; /* Function signature */ char *zDup; /* Copy of argument name */ sxi32 rc; pIn = pGen->pIn; pCur = 0; SyBlobInit(&sSig, &pGen->pVm->sAllocator); /* Process arguments one after one */ for(;;){ if( pIn >= pEnd ){ /* No more arguments to process */ break; } SyZero(&sArg, sizeof(jx9_vm_func_arg)); SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ if( pIn->nType & JX9_TK_KEYWORD ){ sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); if( nKey & JX9_TKWRD_BOOL ){ sArg.nType = MEMOBJ_BOOL; }else if( nKey & JX9_TKWRD_INT ){ sArg.nType = MEMOBJ_INT; }else if( nKey & JX9_TKWRD_STRING ){ sArg.nType = MEMOBJ_STRING; }else if( nKey & JX9_TKWRD_FLOAT ){ sArg.nType = MEMOBJ_REAL; }else{ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Invalid argument type '%z', Automatic cast will not be performed", &pIn->sData); } } pIn++; } if( pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name"); return rc; } if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid argument */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name"); return rc; } pIn++; /* Jump the dollar sign */ /* Copy argument name */ zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData)); if( zDup == 0 ){ return GenStateOutOfMem(pGen); } SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData)); pIn++; if( pIn < pEnd ){ if( pIn->nType & JX9_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 & JX9_TK_COMMA) && iNest <= 0 ){ break; } if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){ /* Increment nesting level */ iNest++; }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){ /* Decrement nesting level */ iNest--; } pDefend++; } if( pIn >= pDefend ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value"); return rc; } /* Process default value */ rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend); if( rc != SXRET_OK ){ return rc; } /* Point beyond the default value */ pIn = pDefend; } if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData); return rc; } pIn++; /* Jump the trailing comma */ } /* Append argument signature */ if( sArg.nType > 0 ){ int c; c = 'n'; /* cc warning */ /* Type leading character */ switch(sArg.nType){ case MEMOBJ_HASHMAP: /* Hashmap aka 'array' */ c = 'h'; break; case MEMOBJ_INT: /* Integer */ c = 'i'; break; case MEMOBJ_BOOL: /* Bool */ c = 'b'; break; case MEMOBJ_REAL: /* Float */ c = 'f'; break; case MEMOBJ_STRING: /* String */ c = 's'; break; default: break; } SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); } /* 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, annonymous 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 GenStateCompileFuncBody( jx9_gen_state *pGen, /* Code generator state */ jx9_vm_func *pFunc /* Function state */ ) { SySet *pInstrContainer; /* Instruction container */ GenBlock *pBlock; sxi32 rc; /* Attach the new function */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock); if( rc != SXRET_OK ){ return GenStateOutOfMem(pGen); } /* Swap bytecode containers */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); /* Compile the body */ jx9CompileBlock(&(*pGen)); /* Emit the final return if not yet done */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0); /* Restore the default container */ jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); /* Leave function block */ 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 a JX9 function whether is a Standard or Annonymous function. * According to the JX9 language reference manual. * Function names follow the same rules as other labels in JX9. 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 objectes in JX9 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 JX9. However avoid recursive function/method * calls with over 32-64 recursion levels. * * JX9 have introduced powerful extension including full type hinting, function overloading, * complex agrument values and more. Please refer to the official documentation for more information * on these extension. */ static sxi32 GenStateCompileFunc( jx9_gen_state *pGen, /* Code generator state */ SyString *pName, /* Function name. NULL otherwise */ sxi32 iFlags, /* Control flags */ jx9_vm_func **ppFunc /* OUT: function state */ ) { jx9_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 */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pEnd >= pGen->pEnd ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } pGen->pIn = pGen->pEnd; return SXRET_OK; } /* Create the function state */ pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func)); if( pFunc == 0 ){ goto OutOfMem; } /* 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 */ goto OutOfMem; } /* Initialize the function state */ jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0); if( pGen->pIn < pEnd ){ /* Collect function arguments */ rc = 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]; /* Compile the body */ rc = GenStateCompileFuncBody(&(*pGen), pFunc); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( ppFunc ){ *ppFunc = pFunc; } /* Finally register the function */ rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0); return rc; /* Fall through if something goes wrong */ OutOfMem: /* 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. */ return GenStateOutOfMem(pGen); } /* * Compile a standard JX9 function. * Refer to the block-comment above for more information. */ static sxi32 jx9CompileFunction(jx9_gen_state *pGen) { SyString *pName; sxi32 iFlags; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; pGen->pIn++; /* Jump the 'function' keyword */ iFlags = 0; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid function name */ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Sychronize with the next semi-colon or braces*/ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } pName = &pGen->pIn->sData; nLine = pGen->pIn->nLine; /* Jump the function name */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Sychronize with the next semi-colon or '{' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* Compile function body */ rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0); return rc; } /* * 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 GenStateEmitExprCode( jx9_gen_state *pGen, /* Code generator state */ jx9_expr_node *pNode, /* Root of the expression tree */ sxi32 iFlags /* Control flags */ ) { VmInstr *pInstr; 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 ){ jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine, "Invalid expression node, JX9 is aborting compilation"); return SXERR_ABORT; } 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 = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags); if( rc != SXRET_OK ){ return rc; } nJz = nJmp = 0; /* cc -O6 warning */ /* Phase#2: Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz); if( pNode->pLeft ){ /* Phase#3: Compile the 'then' expression */ rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); if( rc != SXRET_OK ){ return rc; } } /* Phase#4: Emit the unconditional jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp); /* Phase#5: Fix the false jump now the jump destination is resolved. */ pInstr = jx9VmGetInstr(pGen->pVm, nJz); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } /* Phase#6: Compile the 'else' expression */ if( pNode->pRight ){ rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); if( rc != SXRET_OK ){ return rc; } } if( nJmp > 0 ){ /* Phase#7: Fix the unconditional jump */ pInstr = jx9VmGetInstr(pGen->pVm, nJmp); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } } /* All done */ return SXRET_OK; } /* Generate code for the left tree */ if( pNode->pLeft ){ if( iVmOp == JX9_OP_CALL ){ jx9_expr_node **apNode; sxi32 n; /* Recurse and generate bytecodes for function arguments */ apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); /* Read-only load */ iFlags |= EXPR_FLAG_RDONLY_LOAD; for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ rc = 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 = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); if( rc != SXRET_OK ){ return rc; } if( iVmOp == JX9_OP_CALL ){ pInstr = jx9VmPeekInstr(pGen->pVm); if( pInstr ){ if ( pInstr->iOp == JX9_OP_LOADC ){ /* Prevent constant expansion */ pInstr->iP1 = 0; }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){ /* Annonymous function call, flag that */ pInstr->iP2 = 1; } } }else if( iVmOp == JX9_OP_LOAD_IDX ){ jx9_expr_node **apNode; sxi32 n; /* Recurse and generate bytecodes for array index */ apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ rc = 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 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } } rc = SXRET_OK; nJmpIdx = 0; /* Generate code for the right tree */ if( pNode->pRight ){ if( iVmOp == JX9_OP_LAND ){ /* Emit the false jump so we can short-circuit the logical and */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); }else if (iVmOp == JX9_OP_LOR ){ /* Emit the true jump so we can short-circuit the logical or*/ jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 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 = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); if( iVmOp == JX9_OP_STORE ){ pInstr = jx9VmPeekInstr(pGen->pVm); if( pInstr ){ if(pInstr->iOp == JX9_OP_MEMBER ){ /* Perform a member store operation [i.e: $this.x = 50] */ iP2 = 1; }else{ if( pInstr->iOp == JX9_OP_LOAD_IDX ){ /* Transform the STORE instruction to STORE_IDX instruction */ iVmOp = JX9_OP_STORE_IDX; iP1 = pInstr->iP1; }else{ p3 = pInstr->p3; } /* POP the last dynamic load instruction */ (void)jx9VmPopInstr(pGen->pVm); } } } } if( iVmOp > 0 ){ if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){ if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */ iP1 = 1; } } /* Finally, emit the VM instruction associated with this operator */ jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0); if( nJmpIdx > 0 ){ /* Fix short-circuited jumps now the destination is resolved */ pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } } } return rc; } /* * Compile a JX9 expression. * According to the JX9 language reference manual: * Expressions are the most important building stones of JX9. * In JX9, 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 jx9CompileExpr( jx9_gen_state *pGen, /* Code generator state */ sxi32 iFlags, /* Control flags */ sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ ) { jx9_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(jx9_expr_node *)); SySetAlloc(&sExprNode, 0x10); rc = SXRET_OK; /* Delimit the expression */ pEnd = pGen->pIn; iNest = 0; while( pEnd < pGen->pEnd ){ if( pEnd->nType & JX9_TK_OCB /* '{' */ ){ /* Ticket 1433-30: Annonymous/Closure functions body */ iNest++; }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){ iNest--; }else if( pEnd->nType & JX9_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 & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){ iNest++; }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){ iNest--; }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){ if( iNest <= 0 ){ break; } } pEnd2++; } if( pEnd2 pGen->pIn ){ SyToken *pTmp = pGen->pEnd; /* Swap delimiter */ pGen->pEnd = pEnd; /* Try to get an expression tree */ rc = jx9ExprMakeTree(&(*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 = GenStateEmitExprCode(&(*pGen), pRoot, iFlags); } nExpr = 1; } /* Release the whole tree */ jx9ExprFreeTree(&(*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, ...]. */ JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType) { if( nNodeType & JX9_TK_NUM ){ /* Numeric literal: Either real or integer */ return jx9CompileNumLiteral; }else if( nNodeType & JX9_TK_DSTR ){ /* Double quoted string */ return jx9CompileString; }else if( nNodeType & JX9_TK_SSTR ){ /* Single quoted string */ return jx9CompileSimpleString; }else if( nNodeType & JX9_TK_NOWDOC ){ /* Nowdoc */ return jx9CompileNowdoc; } return 0; } /* * Jx9 Language construct table. */ static const LangConstruct aLangConstruct[] = { { JX9_TKWRD_IF, jx9CompileIf }, { JX9_TKWRD_FUNCTION, jx9CompileFunction }, { JX9_TKWRD_FOREACH, jx9CompileForeach }, { JX9_TKWRD_WHILE, jx9CompileWhile }, { JX9_TKWRD_FOR, jx9CompileFor }, { JX9_TKWRD_SWITCH, jx9CompileSwitch }, { JX9_TKWRD_DIE, jx9CompileHalt }, { JX9_TKWRD_EXIT, jx9CompileHalt }, { JX9_TKWRD_RETURN, jx9CompileReturn }, { JX9_TKWRD_BREAK, jx9CompileBreak }, { JX9_TKWRD_CONTINUE, jx9CompileContinue }, { JX9_TKWRD_STATIC, jx9CompileStatic }, { JX9_TKWRD_UPLINK, jx9CompileUplink }, { JX9_TKWRD_CONST, jx9CompileConstant }, }; /* * Return a pointer to the statement handler routine associated * with a given JX9 keyword [i.e: if, for, while, ...]. */ static ProcLangConstruct GenStateGetStatementHandler( sxu32 nKeywordID /* Keyword ID*/ ) { sxu32 n = 0; 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; } /* * Compile a jx9 program. * If something goes wrong while compiling the Jx9 chunk, this function * takes care of generating the appropriate error message. */ static sxi32 GenStateCompileChunk( jx9_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; } xCons = 0; if( pGen->pIn->nType & JX9_TK_KEYWORD ){ sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); /* Try to extract a language construct handler */ xCons = GenStateGetStatementHandler(nKeyword); if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Syntax error: Unexpected keyword '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ break; } /* Synchronize with the first semi-colon and avoid compiling * this erroneous statement. */ xCons = jx9ErrorRecover; } } if( xCons == 0 ){ /* Assume an expression an try to compile it */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc != SXERR_EMPTY ){ /* Pop l-value */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } }else{ /* Go compile the sucker */ rc = xCons(&(*pGen)); } if( rc == SXERR_ABORT ){ /* Request to abort compilation */ break; } /* Ignore trailing semi-colons ';' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ pGen->pIn++; } if( iFlags & JX9_COMPILE_SINGLE_STMT ){ /* Compile a single statement and return */ break; } /* LOOP ONE */ /* LOOP TWO */ /* LOOP THREE */ /* LOOP FOUR */ } /* Return compilation status */ return rc; } /* * Compile a raw chunk. The raw chunk can contain JX9 code embedded * in HTML, XML and so on. This function handle all the stuff. * This is the only compile interface exported from this file. */ JX9_PRIVATE sxi32 jx9CompileScript( jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */ SyString *pScript, /* Script to compile */ sxi32 iFlags /* Compile flags */ ) { jx9_gen_state *pGen; SySet aToken; sxi32 rc; if( pScript->nByte < 1 ){ /* Nothing to compile */ return JX9_OK; } /* Initialize the tokens containers */ SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken)); SySetAlloc(&aToken, 0xc0); pGen = &pVm->sCodeGen; rc = JX9_OK; /* Tokenize the JX9 chunk first */ jx9Tokenize(pScript->zString,pScript->nByte,&aToken); if( SySetUsed(&aToken) < 1 ){ return SXERR_EMPTY; } /* Point to the head and tail of the token stream. */ pGen->pIn = (SyToken *)SySetBasePtr(&aToken); pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)]; /* Compile the chunk */ rc = GenStateCompileChunk(pGen,iFlags); /* Cleanup */ SySetRelease(&aToken); return rc; } /* * Utility routines.Initialize the code generator. */ JX9_PRIVATE sxi32 jx9InitCodeGenerator( jx9_vm *pVm, /* Target VM */ ProcConsumer xErr, /* Error log consumer callabck */ void *pErrData /* Last argument to xErr() */ ) { jx9_gen_state *pGen = &pVm->sCodeGen; /* Zero the structure */ SyZero(pGen, sizeof(jx9_gen_state)); /* Initial state */ pGen->pVm = &(*pVm); pGen->xErr = xErr; pGen->pErrData = pErrData; SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); /* Create the global scope */ GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*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. */ JX9_PRIVATE sxi32 jx9ResetCodeGenerator( jx9_vm *pVm, /* Target VM */ ProcConsumer xErr, /* Error log consumer callabck */ void *pErrData /* Last argument to xErr() */ ) { jx9_gen_state *pGen = &pVm->sCodeGen; GenBlock *pBlock, *pParent; /* Point to the global scope */ pBlock = pGen->pCurrent; while( pBlock->pParent != 0 ){ pParent = pBlock->pParent; GenStateFreeBlock(pBlock); pBlock = pParent; } pGen->xErr = xErr; pGen->pErrData = pErrData; pGen->pCurrent = &pGen->sGlobal; pGen->pIn = pGen->pEnd = 0; pGen->nErr = 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. */ JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) { SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer; const char *zErr = "Error"; va_list ap; if( nErrType == E_ERROR ){ /* Increment the error counter */ pGen->nErr++; if( pGen->nErr > 15 ){ /* Error count limit reached */ SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine); /* Abort immediately */ return SXERR_ABORT; } } switch(nErrType){ case E_WARNING: zErr = "Warning"; break; case E_PARSE: zErr = "Parse error"; break; case E_NOTICE: zErr = "Notice"; break; default: break; } /* Format the error message */ SyBlobFormat(pWorker, "%u %s: ", nLine, zErr); va_start(ap, zFormat); SyBlobFormatAp(pWorker, zFormat, ap); va_end(ap); /* Append a new line */ SyBlobAppend(pWorker, (const void *)"\n", sizeof(char)); return JX9_OK; }